From f213fca3ccdbd194bed3655d9014e47e40de256e Mon Sep 17 00:00:00 2001 From: ScrelliCopter Date: Mon, 26 Oct 2015 01:33:32 +1100 Subject: [PATCH] Refactored TMX loading & argument parsing code. --- src/tmx2gba.cpp | 268 +++++++++++----------------------------- src/tmxlayer.cpp | 68 ++++++++++ src/tmxlayer.h | 26 ++++ src/tmxreader.cpp | 191 ++++++++++++++++++++++++++++ src/tmxreader.h | 30 +++++ tmx2gba.vcxproj | 4 + tmx2gba.vcxproj.filters | 12 ++ 7 files changed, 402 insertions(+), 197 deletions(-) create mode 100644 src/tmxlayer.cpp create mode 100644 src/tmxlayer.h create mode 100644 src/tmxreader.cpp create mode 100644 src/tmxreader.h diff --git a/src/tmx2gba.cpp b/src/tmx2gba.cpp index 1d66ee1..487f9f3 100644 --- a/src/tmx2gba.cpp +++ b/src/tmx2gba.cpp @@ -20,18 +20,17 @@ */ +#include "tmxreader.h" +#include "tmxlayer.h" #include #include #include #include -#include #include #include -#include -#include -#include #include + static const std::string g_strUsage = "Usage: tmx2gba [-h] [-r offset] [-lc name] [-p 0-15] <-i inpath> <-o outpath>\nRun 'tmx2gba -h' to view all available options."; static const std::string g_strFullHelp = R"(Usage: tmx2gba [-hr] [-p index] <-i inpath> <-o outpath> @@ -43,55 +42,19 @@ static const std::string g_strFullHelp = R"(Usage: tmx2gba [-hr] [-p index] <-i -i --- Path to input TMX file. -o --- Path to output files.)"; -struct STilemapInfo + +struct SParams { - uint32_t width; - uint32_t height; + std::string inPath, outPath; + std::string layer, collisionlay, paletteLay; + int offset = 0; + int palette = 0; }; - -bool DecodeMapData ( - const std::string& a_strEncData, - int a_iWidth, int a_iHeight, - std::vector* a_pvucOut - ) +bool ParseArgs ( int argc, char** argv, SParams* params ) { - if ( a_pvucOut == nullptr ) - { - return false; - } - - // Decode base64 string. - unsigned int cutTheCrap = a_strEncData.find_first_not_of ( " \t\n\r" ); - std::string strDec = base64_decode ( a_strEncData.substr ( cutTheCrap ) ); - a_pvucOut->clear (); - a_pvucOut->resize ( a_iWidth * a_iHeight * 4 ); - mz_ulong uiDstSize = a_pvucOut->size (); - - // Decompress compressed data. - int iRes = uncompress ( - a_pvucOut->data (), &uiDstSize, - (const uint8_t*)strDec.data (), strDec.size () - ); - strDec.clear (); - if ( iRes < 0 ) - { - return false; - } - - return true; -} - -int main ( int argc, char** argv ) -{ - // Parse cmd line args. - std::string strInPath, strOutPath; - std::string strLayer, strCollisionlay; - int iOffset = 0; - int iPalette = 0; - char cOption; - while ( ( cOption = getopt ( argc, argv, "hr:l:c:p:i:o:" ) ) > 0 ) + while ( ( cOption = getopt ( argc, argv, "hr:l:c:p:y:i:o:" ) ) > 0 ) { switch ( cOption ) { @@ -100,27 +63,31 @@ int main ( int argc, char** argv ) return 0; case ( 'l' ): - strLayer = optarg; + params->layer = optarg; break; case ( 'c' ): - strCollisionlay = optarg; + params->collisionlay = optarg; + break; + + case ( 'y' ): + params->paletteLay = optarg; break; case ( 'r' ): - iOffset = std::stoi ( optarg ); + params->offset = std::stoi ( optarg ); break; case ( 'p' ): - iPalette = std::stoi ( optarg ); + params->palette = std::stoi ( optarg ); break; case ( 'i' ): - strInPath = optarg; + params->inPath = optarg; break; case ( 'o' ): - strOutPath = optarg; + params->outPath = optarg; break; default: @@ -129,156 +96,71 @@ int main ( int argc, char** argv ) } // Check my paranoia. - if ( strInPath.empty () ) + if ( params->inPath.empty () ) { std::cerr << "No input file specified." << std::endl; std::cout << g_strUsage << std::endl; - return -1; + return false; } - if ( strOutPath.empty () ) + if ( params->outPath.empty () ) { std::cerr << "No output file specified." << std::endl; std::cout << g_strUsage << std::endl; - return -1; + return false; } - if ( iPalette < 0 || iPalette > 15 ) + if ( params->palette < 0 || params->palette > 15 ) { std::cerr << "Invalid palette index." << std::endl; std::cout << g_strUsage << std::endl; + return false; + } + + return true; +} + + +int main ( int argc, char** argv ) +{ + SParams params; + if ( !ParseArgs ( argc, argv, ¶ms ) ) + { return -1; } // Open & read input file. - std::ifstream fin ( strInPath ); + CTmxReader tmx; + std::ifstream fin ( params.inPath ); if ( !fin.is_open () ) { std::cerr << "Failed to open input file." << std::endl; return -1; } - std::stringstream buf; - buf << fin.rdbuf (); - fin.close (); - std::string strXml = buf.str (); - buf.clear (); + tmx.Open ( fin ); - STilemapInfo info; - memset ( &info, 0, sizeof(STilemapInfo) ); - std::string strEncData, strEncCollisionDat; - - // Parse document. - rapidxml::xml_document<> xDoc; - xDoc.parse<0> ( (char*)strXml.c_str () ); - - // Get map node. - auto xMap = xDoc.first_node ( "map" ); - if ( xMap != nullptr ) + // Get layers. + if ( tmx.GetLayerCount () == 0 ) { - // Read map attribs. - rapidxml::xml_attribute<>* xAttrib = nullptr; - if ( ( xAttrib = xMap->first_attribute ( "width" ) ) != nullptr ) - { - info.width = std::stoi ( xAttrib->value () ); - } - if ( ( xAttrib = xMap->first_attribute ( "height" ) ) != nullptr ) - { - info.height = std::stoi ( xAttrib->value () ); - } - - // Get layer node. - rapidxml::xml_node<>* xLayer = nullptr; - if ( strLayer.empty () ) - { - xLayer = xMap->first_node ( "layer" ); - } - else - { - // Find specified layer. - for ( auto xNode = xMap->first_node (); xNode != nullptr; xNode = xNode->next_sibling () ) - { - // We only want layers. - if ( strcmp ( xNode->name (), "layer" ) == 0 ) - { - // Use this layer if it matches our specified name. - auto xName = xNode->first_attribute ( "name" ); - if ( xName != nullptr ) - { - if ( strcmp ( xName->value (), strLayer.c_str () ) == 0 ) - { - xLayer = xNode; - break; - } - } - } - } - } - - // Find collision layer. - rapidxml::xml_node<>* xCollisionLay = nullptr; - if ( !strCollisionlay.empty () ) - { - for ( auto xNode = xMap->first_node (); xNode != nullptr; xNode = xNode->next_sibling () ) - { - if ( strcmp ( xNode->name (), "layer" ) == 0 ) - { - // Use this layer if it matches our specified name. - auto xName = xNode->first_attribute ( "name" ); - if ( xName != nullptr ) - { - if ( strcmp ( xName->value (), strCollisionlay.c_str () ) == 0 ) - { - xCollisionLay = xNode; - break; - } - } - } - } - } - - // Read data from layer node. - if ( xLayer != nullptr ) - { - auto xData = xLayer->first_node ( "data" ); - if ( xData != nullptr ) - { - // Get encoded data. - strEncData = xData->value (); - } - } - - // Read data from collision layer. - if ( xCollisionLay != nullptr ) - { - auto xData = xCollisionLay->first_node ( "data" ); - if ( xData != nullptr ) - { - // Get encoded data. - strEncCollisionDat = xData->value (); - } - } - } - - // Check what we (don't) have. - if ( info.width == 0 || info.height == 0 || strEncData.empty () ) - { - std::cerr << "Parse error."; + std::cerr << "No layers found." << std::endl; return -1; } + const CTmxLayer* pLayerGfx = params.inPath.empty () ? tmx.GetLayer ( 0 ) : tmx.GetLayer ( params.layer ); + const CTmxLayer* pLayerCls = params.inPath.empty () ? nullptr : tmx.GetLayer ( params.collisionlay ); + const CTmxLayer* pLayerPal = params.inPath.empty () ? nullptr : tmx.GetLayer ( params.paletteLay ); - // Decode and decompress layer data. - std::vector vucLayerDat; - if ( !DecodeMapData ( strEncData, info.width, info.height, &vucLayerDat ) ) + if ( pLayerGfx == nullptr ) { - std::cerr << "Decompression error."; + std::cerr << "Input layer not found." << std::endl; return -1; } // Convert to GBA-friendly charmap data. - uint16_t* pRead = (uint16_t*)vucLayerDat.data (); + const uint16_t* pRead = (const uint16_t*)pLayerGfx->GetData (); + //const uint16_t* pPalRead = (const uint16_t*)pLayerPal->GetData (); std::vector vucCharDat; - vucCharDat.reserve ( info.width * info.height ); - for ( size_t i = 0; i < size_t(info.width * info.height * 2); ++i ) + vucCharDat.reserve ( pLayerGfx->GetWidth () * pLayerGfx->GetHeight () ); + for ( size_t i = 0; i < size_t(pLayerGfx->GetWidth () * pLayerGfx->GetHeight () * 2); ++i ) { - uint16_t usTile = std::max ( (*pRead++) + (uint16_t)iOffset, 0 ); + uint16_t usTile = std::max ( (*pRead++) + (uint16_t)params.offset, 0 ); bool bFlipH = ( 0x8000 & *pRead ) ? true : false; bool bFlipV = ( 0x4000 & *pRead++ ) ? true : false; @@ -286,14 +168,13 @@ int main ( int argc, char** argv ) uint8_t ucFlags = 0x0; ucFlags |= ( bFlipH ) ? 0x4 : 0x0; ucFlags |= ( bFlipV ) ? 0x8 : 0x0; - ucFlags |= iPalette << 4; + ucFlags |= params.palette << 4; vucCharDat.push_back ( usTile | ucFlags << 8 ); } - vucLayerDat.clear (); // Save out charmap. - std::ofstream fout ( strOutPath, std::ios::binary ); + std::ofstream fout ( params.outPath, std::ios::binary ); if ( !fout.is_open () ) { std::cerr << "Failed to create output file."; @@ -302,37 +183,30 @@ int main ( int argc, char** argv ) fout.write ( (const char*)vucCharDat.data (), vucCharDat.size () ); fout.close (); - // Decode & convert collision map. - std::vector vucCollisionDat; - if ( !strEncCollisionDat.empty () ) + // Convert collision map & save it out. + if ( pLayerCls != nullptr ) { - std::vector vucLayerDat; - if ( DecodeMapData ( strEncCollisionDat, info.width, info.height, &vucLayerDat ) ) - { - vucCollisionDat.reserve ( info.width * info.height ); - uint8_t* pRead = vucLayerDat.data (); - for ( size_t i = 0; i < info.width * info.height; ++i ) - { - uint8_t ucTile = *pRead; - pRead += 4; - vucCollisionDat.push_back ( ucTile ); - } - } - } + std::vector vucCollisionDat; + vucCollisionDat.reserve ( pLayerCls->GetWidth () * pLayerCls->GetHeight () ); + + const uint8_t* pRead = (const uint8_t*)pLayerCls->GetData (); + for ( size_t i = 0; i < pLayerCls->GetWidth () * pLayerCls->GetHeight (); ++i ) + { + uint8_t ucTile = *pRead; + pRead += 4; + vucCollisionDat.push_back ( ucTile ); + } - // Save it out or something like that. - if ( !vucCollisionDat.empty () ) - { // Try to nicely append "_collision" to the output name. std::string strPath; - size_t extPos = strOutPath.find_last_of ( '.' ); + size_t extPos = params.outPath.find_last_of ( '.' ); if ( extPos != std::string::npos ) { - strPath = strOutPath.insert ( extPos, "_collision" ); + strPath = params.outPath.insert ( extPos, "_collision" ); } else { - strPath = strOutPath + "_collision"; + strPath = params.outPath + "_collision"; } // Save it out. diff --git a/src/tmxlayer.cpp b/src/tmxlayer.cpp new file mode 100644 index 0000000..3db2fa6 --- /dev/null +++ b/src/tmxlayer.cpp @@ -0,0 +1,68 @@ +/* tmxlayer.cpp + + Copyright (C) 2015 Nicholas Curtis + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#include "tmxlayer.h" + + +CTmxLayer::CTmxLayer () : + m_iWidth ( 0 ), + m_iHeight ( 0 ), + m_strName ( "" ), + m_pTileDat ( nullptr ) +{ + +} + +CTmxLayer::CTmxLayer ( int a_iWidth, int a_iHeight, const char* a_szName, uint8_t* a_pTileDat ) : + m_iWidth ( a_iWidth ), + m_iHeight ( a_iHeight ), + m_strName ( a_szName ), + m_pTileDat ( a_pTileDat ) +{ + +} + +CTmxLayer::~CTmxLayer () +{ + delete[] m_pTileDat; +} + + +const std::string& CTmxLayer::GetName () const +{ + return m_strName; +} + +int CTmxLayer::GetWidth () const +{ + return m_iWidth; +} + +int CTmxLayer::GetHeight () const +{ + return m_iHeight; +} + +const uint8_t* CTmxLayer::GetData () const +{ + return m_pTileDat; +} diff --git a/src/tmxlayer.h b/src/tmxlayer.h new file mode 100644 index 0000000..79834cd --- /dev/null +++ b/src/tmxlayer.h @@ -0,0 +1,26 @@ +#ifndef __TMXLAYER_H__ +#define __TMXLAYER_H__ + +#include +#include + +class CTmxLayer +{ +public: + CTmxLayer (); + CTmxLayer ( int a_iWidth, int a_iHeight, const char* a_szName, uint8_t* a_pTileDat ); + ~CTmxLayer (); + + const std::string& GetName () const; + int GetWidth () const; + int GetHeight () const; + const uint8_t* GetData () const; + +private: + std::string m_strName; + int m_iWidth, m_iHeight; + uint8_t* m_pTileDat; + +}; + +#endif//__TMXLAYER_H__ diff --git a/src/tmxreader.cpp b/src/tmxreader.cpp new file mode 100644 index 0000000..e41dc3f --- /dev/null +++ b/src/tmxreader.cpp @@ -0,0 +1,191 @@ +/* tmxreader.cpp + + Copyright (C) 2015 Nicholas Curtis + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#include "tmxreader.h" +#include "tmxlayer.h" +#include +#include +#include +#include +#include + + +CTmxReader::CTmxReader () +{ + +} + +CTmxReader::~CTmxReader () +{ + // Delete old layers. + for ( auto pLay : m_vpLayers ) + { + delete pLay; + } + m_vpLayers.clear (); +} + + +bool DecodeMap ( uint8_t* a_szOut, size_t a_outSize, const std::string& a_strIn ) +{ + // Decode base64 string. + size_t cutTheCrap = a_strIn.find_first_not_of ( " \t\n\r" ); + std::string strDec = base64_decode ( a_strIn.substr ( cutTheCrap ) ); + + // Decompress compressed data. + mz_ulong uiDstSize = a_outSize; + int iRes = uncompress ( + a_szOut, &uiDstSize, + (const uint8_t*)strDec.data (), strDec.size () + ); + strDec.clear (); + if ( iRes < 0 ) + { + return false; + } + + return true; +} + +void CTmxReader::Open ( std::istream& a_in ) +{ + // Delete old layers. + for ( auto pLay : m_vpLayers ) + { + delete pLay; + } + m_vpLayers.clear (); + + // Read string into a buffer. + std::stringstream buf; + buf << a_in.rdbuf (); + std::string strXml = buf.str (); + buf.clear (); + + // Parse document. + rapidxml::xml_document<> xDoc; + xDoc.parse<0> ( (char*)strXml.c_str () ); + + // Get map node. + auto xMap = xDoc.first_node ( "map" ); + if ( xMap == nullptr ) + { + return; + } + + // Read map attribs. + rapidxml::xml_attribute<>* xAttrib = nullptr; + if ( ( xAttrib = xMap->first_attribute ( "width" ) ) != nullptr ) + { + m_iWidth = std::stoi ( xAttrib->value () ); + } + if ( ( xAttrib = xMap->first_attribute ( "height" ) ) != nullptr ) + { + m_iHeight = std::stoi ( xAttrib->value () ); + } + + // Read layer nodes. + for ( auto xNode = xMap->first_node (); xNode != nullptr; xNode = xNode->next_sibling () ) + { + // Make sure it's a layer. + if ( strcmp ( xNode->name (), "layer" ) != 0 ) + { + continue; + } + + rapidxml::xml_attribute<>* xAttrib; + const char* szName = nullptr; + int iWidth = 0; + int iHeight = 0; + uint8_t* pTileDat = nullptr; + + // Read name. + xAttrib = xNode->first_attribute ( "name" ); + if ( xAttrib != nullptr ) + { + szName = xAttrib->value (); + } + + // Read width. + xAttrib = xNode->first_attribute ( "width" ); + if ( xAttrib != nullptr ) + { + iWidth = std::stoi ( xAttrib->value () ); + } + + // Read height. + xAttrib = xNode->first_attribute ( "height" ); + if ( xAttrib != nullptr ) + { + iHeight = std::stoi ( xAttrib->value () ); + } + + // Read tile data. + auto xData = xNode->first_node ( "data" ); + if ( xData != nullptr ) + { + // TODO: don't assume base64 & zlib. + pTileDat = new uint8_t[iWidth * iHeight * 4]; + if ( !DecodeMap ( pTileDat, iWidth * iHeight * 4, std::string ( xData->value () ) ) ) + { + pTileDat = nullptr; + } + } + + m_vpLayers.push_back ( new CTmxLayer ( iWidth, iHeight, szName, pTileDat ) ); + } +} + + +int CTmxReader::GetWidth () const +{ + return m_iWidth; +} + +int CTmxReader::GetHeight () const +{ + return m_iHeight; +} + + +const CTmxLayer* CTmxReader::GetLayer ( int a_iId ) const +{ + return m_vpLayers[a_iId]; +} + +const CTmxLayer* CTmxReader::GetLayer ( std::string a_strName ) const +{ + for ( auto pLay : m_vpLayers ) + { + if ( pLay->GetName ().compare ( a_strName ) == 0 ) + { + return pLay; + } + } + + return nullptr; +} + +int CTmxReader::GetLayerCount () const +{ + return m_vpLayers.size (); +} diff --git a/src/tmxreader.h b/src/tmxreader.h new file mode 100644 index 0000000..5c2105e --- /dev/null +++ b/src/tmxreader.h @@ -0,0 +1,30 @@ +#ifndef __TMXREADER_H__ +#define __TMXREADER_H__ + +#include +#include + +class CTmxLayer; + +class CTmxReader +{ +public: + CTmxReader (); + ~CTmxReader (); + + void Open ( std::istream& a_in ); + + int GetWidth () const; + int GetHeight () const; + + const CTmxLayer* GetLayer ( int a_iId ) const; + const CTmxLayer* GetLayer ( std::string a_strName ) const; + int GetLayerCount () const; + +private: + int m_iWidth, m_iHeight; + std::vector m_vpLayers; + +}; + +#endif//__TMXREADER_H__ diff --git a/tmx2gba.vcxproj b/tmx2gba.vcxproj index 3c69404..db378e5 100644 --- a/tmx2gba.vcxproj +++ b/tmx2gba.vcxproj @@ -68,6 +68,8 @@ + + @@ -78,6 +80,8 @@ + + diff --git a/tmx2gba.vcxproj.filters b/tmx2gba.vcxproj.filters index 4bc4e31..c64c194 100644 --- a/tmx2gba.vcxproj.filters +++ b/tmx2gba.vcxproj.filters @@ -24,6 +24,12 @@ Source Files + + Source Files + + + Source Files + @@ -47,5 +53,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file