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 <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <cstdint>
#include <algorithm>
#include <rapidxml/rapidxml.hpp>
#include <base64.h>
#include <miniz.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_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.
-o <path> --- 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<uint8_t>* 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, &params ) )
{
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<uint8_t> 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<uint16_t> 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<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 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<uint8_t> vucCollisionDat;
if ( !strEncCollisionDat.empty () )
// Convert collision map & save it out.
if ( pLayerCls != nullptr )
{
std::vector<uint8_t> 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<uint8_t> 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.

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

View File

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