1
0
mirror of https://github.com/ScrelliCopter/tmx2gba.git synced 2025-02-21 03:29:25 +11:00

Refactored TMX loading & argument parsing code.

This commit is contained in:
2015-10-26 01:33:32 +11:00
parent 0d30afd74d
commit f213fca3cc
7 changed files with 402 additions and 197 deletions

View File

@@ -20,18 +20,17 @@
*/ */
#include "tmxreader.h"
#include "tmxlayer.h"
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <string> #include <string>
#include <sstream>
#include <cstdint> #include <cstdint>
#include <algorithm> #include <algorithm>
#include <rapidxml/rapidxml.hpp>
#include <base64.h>
#include <miniz.h>
#include <XGetopt.h> #include <XGetopt.h>
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_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> 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> --- Path to input TMX file. -i <path> --- Path to input TMX file.
-o <path> --- Path to output files.)"; -o <path> --- Path to output files.)";
struct STilemapInfo
struct SParams
{ {
uint32_t width; std::string inPath, outPath;
uint32_t height; std::string layer, collisionlay, paletteLay;
int offset = 0;
int palette = 0;
}; };
bool ParseArgs ( int argc, char** argv, SParams* params )
bool DecodeMapData (
const std::string& a_strEncData,
int a_iWidth, int a_iHeight,
std::vector<uint8_t>* a_pvucOut
)
{ {
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; 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 ) switch ( cOption )
{ {
@@ -100,27 +63,31 @@ int main ( int argc, char** argv )
return 0; return 0;
case ( 'l' ): case ( 'l' ):
strLayer = optarg; params->layer = optarg;
break; break;
case ( 'c' ): case ( 'c' ):
strCollisionlay = optarg; params->collisionlay = optarg;
break;
case ( 'y' ):
params->paletteLay = optarg;
break; break;
case ( 'r' ): case ( 'r' ):
iOffset = std::stoi ( optarg ); params->offset = std::stoi ( optarg );
break; break;
case ( 'p' ): case ( 'p' ):
iPalette = std::stoi ( optarg ); params->palette = std::stoi ( optarg );
break; break;
case ( 'i' ): case ( 'i' ):
strInPath = optarg; params->inPath = optarg;
break; break;
case ( 'o' ): case ( 'o' ):
strOutPath = optarg; params->outPath = optarg;
break; break;
default: default:
@@ -129,156 +96,71 @@ int main ( int argc, char** argv )
} }
// Check my paranoia. // Check my paranoia.
if ( strInPath.empty () ) if ( params->inPath.empty () )
{ {
std::cerr << "No input file specified." << std::endl; std::cerr << "No input file specified." << std::endl;
std::cout << g_strUsage << 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::cerr << "No output file specified." << std::endl;
std::cout << g_strUsage << 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::cerr << "Invalid palette index." << std::endl;
std::cout << g_strUsage << std::endl; std::cout << g_strUsage << std::endl;
return false;
}
return true;
}
int main ( int argc, char** argv )
{
SParams params;
if ( !ParseArgs ( argc, argv, &params ) )
{
return -1; return -1;
} }
// Open & read input file. // Open & read input file.
std::ifstream fin ( strInPath ); CTmxReader tmx;
std::ifstream fin ( params.inPath );
if ( !fin.is_open () ) if ( !fin.is_open () )
{ {
std::cerr << "Failed to open input file." << std::endl; std::cerr << "Failed to open input file." << std::endl;
return -1; return -1;
} }
std::stringstream buf; tmx.Open ( fin );
buf << fin.rdbuf ();
fin.close ();
std::string strXml = buf.str ();
buf.clear ();
STilemapInfo info; // Get layers.
memset ( &info, 0, sizeof(STilemapInfo) ); if ( tmx.GetLayerCount () == 0 )
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 )
{ {
// Read map attribs. std::cerr << "No layers found." << std::endl;
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.";
return -1; 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. if ( pLayerGfx == nullptr )
std::vector<uint8_t> vucLayerDat;
if ( !DecodeMapData ( strEncData, info.width, info.height, &vucLayerDat ) )
{ {
std::cerr << "Decompression error."; std::cerr << "Input layer not found." << std::endl;
return -1; return -1;
} }
// Convert to GBA-friendly charmap data. // 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<uint16_t> vucCharDat; std::vector<uint16_t> vucCharDat;
vucCharDat.reserve ( info.width * info.height ); vucCharDat.reserve ( pLayerGfx->GetWidth () * pLayerGfx->GetHeight () );
for ( size_t i = 0; i < size_t(info.width * info.height * 2); ++i ) for ( size_t i = 0; i < size_t(pLayerGfx->GetWidth () * pLayerGfx->GetHeight () * 2); ++i )
{ {
uint16_t usTile = std::max<uint16_t> ( (*pRead++) + (uint16_t)iOffset, 0 ); uint16_t usTile = std::max<uint16_t> ( (*pRead++) + (uint16_t)params.offset, 0 );
bool bFlipH = ( 0x8000 & *pRead ) ? true : false; bool bFlipH = ( 0x8000 & *pRead ) ? true : false;
bool bFlipV = ( 0x4000 & *pRead++ ) ? true : false; bool bFlipV = ( 0x4000 & *pRead++ ) ? true : false;
@@ -286,14 +168,13 @@ int main ( int argc, char** argv )
uint8_t ucFlags = 0x0; uint8_t ucFlags = 0x0;
ucFlags |= ( bFlipH ) ? 0x4 : 0x0; ucFlags |= ( bFlipH ) ? 0x4 : 0x0;
ucFlags |= ( bFlipV ) ? 0x8 : 0x0; ucFlags |= ( bFlipV ) ? 0x8 : 0x0;
ucFlags |= iPalette << 4; ucFlags |= params.palette << 4;
vucCharDat.push_back ( usTile | ucFlags << 8 ); vucCharDat.push_back ( usTile | ucFlags << 8 );
} }
vucLayerDat.clear ();
// Save out charmap. // Save out charmap.
std::ofstream fout ( strOutPath, std::ios::binary ); std::ofstream fout ( params.outPath, std::ios::binary );
if ( !fout.is_open () ) if ( !fout.is_open () )
{ {
std::cerr << "Failed to create output file."; 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.write ( (const char*)vucCharDat.data (), vucCharDat.size () );
fout.close (); fout.close ();
// Decode & convert collision map. // Convert collision map & save it out.
std::vector<uint8_t> vucCollisionDat; if ( pLayerCls != nullptr )
if ( !strEncCollisionDat.empty () )
{ {
std::vector<uint8_t> vucLayerDat; std::vector<uint8_t> vucCollisionDat;
if ( DecodeMapData ( strEncCollisionDat, info.width, info.height, &vucLayerDat ) ) vucCollisionDat.reserve ( pLayerCls->GetWidth () * pLayerCls->GetHeight () );
{
vucCollisionDat.reserve ( info.width * info.height ); const uint8_t* pRead = (const uint8_t*)pLayerCls->GetData ();
uint8_t* pRead = vucLayerDat.data (); for ( size_t i = 0; i < pLayerCls->GetWidth () * pLayerCls->GetHeight (); ++i )
for ( size_t i = 0; i < info.width * info.height; ++i ) {
{ uint8_t ucTile = *pRead;
uint8_t ucTile = *pRead; pRead += 4;
pRead += 4; vucCollisionDat.push_back ( ucTile );
vucCollisionDat.push_back ( ucTile ); }
}
}
}
// Save it out or something like that.
if ( !vucCollisionDat.empty () )
{
// Try to nicely append "_collision" to the output name. // Try to nicely append "_collision" to the output name.
std::string strPath; std::string strPath;
size_t extPos = strOutPath.find_last_of ( '.' ); size_t extPos = params.outPath.find_last_of ( '.' );
if ( extPos != std::string::npos ) if ( extPos != std::string::npos )
{ {
strPath = strOutPath.insert ( extPos, "_collision" ); strPath = params.outPath.insert ( extPos, "_collision" );
} }
else else
{ {
strPath = strOutPath + "_collision"; strPath = params.outPath + "_collision";
} }
// Save it out. // Save it out.

68
src/tmxlayer.cpp Normal file
View File

@@ -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;
}

26
src/tmxlayer.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef __TMXLAYER_H__
#define __TMXLAYER_H__
#include <string>
#include <cstdint>
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__

191
src/tmxreader.cpp Normal file
View File

@@ -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 <cstdint>
#include <sstream>
#include <rapidxml/rapidxml.hpp>
#include <base64.h>
#include <miniz.h>
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 ();
}

30
src/tmxreader.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef __TMXREADER_H__
#define __TMXREADER_H__
#include <istream>
#include <vector>
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<CTmxLayer*> m_vpLayers;
};
#endif//__TMXREADER_H__

View File

@@ -68,6 +68,8 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="src\base64.cpp" /> <ClCompile Include="src\base64.cpp" />
<ClCompile Include="src\tmx2gba.cpp" /> <ClCompile Include="src\tmx2gba.cpp" />
<ClCompile Include="src\tmxlayer.cpp" />
<ClCompile Include="src\tmxreader.cpp" />
<ClCompile Include="src\XGetopt.cpp" /> <ClCompile Include="src\XGetopt.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -78,6 +80,8 @@
<ClInclude Include="inc\rapidxml\rapidxml_print.hpp" /> <ClInclude Include="inc\rapidxml\rapidxml_print.hpp" />
<ClInclude Include="inc\rapidxml\rapidxml_utils.hpp" /> <ClInclude Include="inc\rapidxml\rapidxml_utils.hpp" />
<ClInclude Include="inc\XGetopt.h" /> <ClInclude Include="inc\XGetopt.h" />
<ClInclude Include="src\tmxlayer.h" />
<ClInclude Include="src\tmxreader.h" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">

View File

@@ -24,6 +24,12 @@
<ClCompile Include="src\XGetopt.cpp"> <ClCompile Include="src\XGetopt.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\tmxreader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\tmxlayer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="inc\base64.h"> <ClInclude Include="inc\base64.h">
@@ -47,5 +53,11 @@
<ClInclude Include="inc\XGetopt.h"> <ClInclude Include="inc\XGetopt.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="src\tmxreader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\tmxlayer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>