mirror of
https://github.com/ScrelliCopter/tmx2gba.git
synced 2025-02-21 03:29:25 +11:00
@@ -1,5 +1,8 @@
|
||||
add_executable(tmx2gba
|
||||
argparse.hpp argparse.cpp
|
||||
tmxlayer.hpp
|
||||
tmxobject.hpp
|
||||
tmxtileset.hpp
|
||||
tmxreader.hpp tmxreader.cpp
|
||||
convert.hpp convert.cpp
|
||||
headerwriter.hpp headerwriter.cpp
|
||||
@@ -18,7 +21,10 @@ target_compile_options(tmx2gba PRIVATE
|
||||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -pedantic>
|
||||
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded>)
|
||||
|
||||
target_link_libraries(tmx2gba tmxlite)
|
||||
target_link_libraries(${PROJECT_NAME} base64::base64 pugixml Zstd::Zstd
|
||||
$<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB>
|
||||
$<$<TARGET_EXISTS:miniz::miniz>:miniz::miniz>)
|
||||
target_link_libraries(${PROJECT_NAME} External::rapidxml)
|
||||
|
||||
if (TMX2GBA_DKP_INSTALL)
|
||||
if (DEFINED ENV{DEVKITPRO})
|
||||
|
||||
125
src/gzip.cpp
Normal file
125
src/gzip.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// gzip.cpp - portable memory miniz based gzip reader
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#include "tmxlite/detail/gzip.hpp"
|
||||
#include <string_view>
|
||||
|
||||
|
||||
GZipReader::GZipReader() noexcept :
|
||||
mSourceLen(0), mBytesRead(0),
|
||||
mModificationTime(0), mCrc(0), mInputSize(0), mComputedCrc(0),
|
||||
crc16(0), mFlags(0), mXflags(0), mOsId(0)
|
||||
{
|
||||
tinfl_init(&mState);
|
||||
mComputedCrc = static_cast<uint32_t>(mz_crc32(0, nullptr, 0));
|
||||
}
|
||||
|
||||
bool GZipReader::OpenMemory(const std::span<const uint8_t> source) noexcept
|
||||
{
|
||||
if (source.size() < 20)
|
||||
return false;
|
||||
|
||||
auto it = std::cbegin(source), end = std::cend(source);
|
||||
|
||||
constexpr uint8_t magic[2] = { 0x1F, 0x8B };
|
||||
if (*it++ != magic[0] || *it++ != magic[1])
|
||||
return false;
|
||||
|
||||
constexpr uint8_t CM_DEFLATE = 8;
|
||||
uint8_t compression = *it++;
|
||||
if (compression != CM_DEFLATE)
|
||||
return false;
|
||||
|
||||
mFlags = *it++;
|
||||
mModificationTime = *it++;
|
||||
mModificationTime |= *it++ << 8;
|
||||
mModificationTime |= *it++ << 16;
|
||||
mModificationTime |= *it++ << 24;
|
||||
mXflags = *it++;
|
||||
mOsId = *it++;
|
||||
|
||||
if (mFlags & FEXTRA)
|
||||
{
|
||||
// Skip "extra" field
|
||||
if (it + 2 >= end)
|
||||
return false;
|
||||
uint16_t extraLen = *it++;
|
||||
extraLen = *it++ << 8;
|
||||
if (it + extraLen >= end)
|
||||
return false;
|
||||
it += extraLen;
|
||||
}
|
||||
if (mFlags & FNAME)
|
||||
{
|
||||
// Skip null-terminated name string
|
||||
do
|
||||
{
|
||||
if (++it == end)
|
||||
return false;
|
||||
} while (*it != '\0');
|
||||
if (++it == end)
|
||||
return false;
|
||||
}
|
||||
if (mFlags & FCOMMENT)
|
||||
{
|
||||
// Skip null-terminated comment string
|
||||
do
|
||||
{
|
||||
if (++it == end)
|
||||
return false;
|
||||
} while (*it != '\0');
|
||||
if (++it == end)
|
||||
return false;
|
||||
}
|
||||
if (mFlags & FHCRC)
|
||||
{
|
||||
if (it + 2 >= end)
|
||||
return false;
|
||||
crc16 = *it++;
|
||||
crc16 |= *it++;
|
||||
}
|
||||
|
||||
mIt = it;
|
||||
mSourceLen = end - it - 8;
|
||||
|
||||
it += mSourceLen;
|
||||
mCrc = *it++;
|
||||
mCrc |= *it++ << 8;
|
||||
mCrc |= *it++ << 16;
|
||||
mCrc |= *it++ << 24;
|
||||
mInputSize = *it++;
|
||||
mInputSize |= *it++ << 8;
|
||||
mInputSize |= *it++ << 16;
|
||||
mInputSize |= *it++ << 24;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GZipReader::Read(std::span<uint8_t> out) noexcept
|
||||
{
|
||||
size_t outLen = out.size();
|
||||
auto res = tinfl_decompress(&mState,
|
||||
static_cast<const mz_uint8*>(&*mIt), &mSourceLen,
|
||||
static_cast<mz_uint8*>(out.data()), static_cast<mz_uint8*>(out.data()), &outLen,
|
||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
if (res != TINFL_STATUS_DONE)
|
||||
return false;
|
||||
|
||||
mIt += outLen;
|
||||
mBytesRead += outLen;
|
||||
mComputedCrc = static_cast<uint32_t>(mz_crc32(static_cast<mz_ulong>(mComputedCrc), out.data(), outLen));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GZipReader::Check() const noexcept
|
||||
{
|
||||
if (mComputedCrc != mCrc)
|
||||
return false;
|
||||
|
||||
if (static_cast<uint32_t>(mBytesRead & UINT32_MAX) != mInputSize)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
39
src/gzip.hpp
Normal file
39
src/gzip.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// gzip.hpp - portable memory miniz based gzip reader
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#ifndef GZIP_HPP
|
||||
#define GZIP_HPP
|
||||
|
||||
#include "miniz.h"
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
|
||||
class GZipReader
|
||||
{
|
||||
static constexpr uint8_t
|
||||
FTEXT = 1, FHCRC = 1<<1, FEXTRA = 1<<2, FNAME = 1<<3, FCOMMENT = 1<<4;
|
||||
|
||||
static constexpr uint8_t XFL_BEST = 2, XFL_FASTEST = 4;
|
||||
|
||||
tinfl_decompressor mState;
|
||||
std::span<const uint8_t>::iterator mIt;
|
||||
|
||||
size_t mSourceLen, mBytesRead;
|
||||
uint32_t mModificationTime, mCrc, mInputSize, mComputedCrc;
|
||||
uint16_t crc16;
|
||||
uint8_t mFlags, mXflags, mOsId;
|
||||
|
||||
public:
|
||||
GZipReader() noexcept;
|
||||
|
||||
constexpr size_t SourceLength() const noexcept { return mSourceLen; }
|
||||
constexpr uint32_t OutputLength() const noexcept { return mInputSize; }
|
||||
|
||||
bool OpenMemory(const std::span<const uint8_t> source) noexcept;
|
||||
bool Read(std::span<uint8_t> out) noexcept;
|
||||
bool Check() const noexcept;
|
||||
};
|
||||
|
||||
#endif//GZIP_HPP
|
||||
@@ -9,7 +9,7 @@ template <> constexpr std::string_view DatType<uint8_t>() { return "unsigned cha
|
||||
template <> constexpr std::string_view DatType<uint16_t>() { return "unsigned short"; }
|
||||
template <> constexpr std::string_view DatType<uint32_t>() { return "unsigned int"; }
|
||||
|
||||
void HeaderWriter::WriteSize(unsigned width, unsigned height)
|
||||
void HeaderWriter::WriteSize(int width, int height)
|
||||
{
|
||||
stream << std::endl;
|
||||
WriteDefine(mName + "Width", width);
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
WriteDefine(name, std::to_string(value));
|
||||
}
|
||||
|
||||
void WriteSize(unsigned width, unsigned height);
|
||||
void WriteSize(int width, int height);
|
||||
void WriteCharacterMap(const std::span<uint16_t> charData);
|
||||
void WriteCollision(const std::span<uint8_t> collisionData);
|
||||
void WriteObjects(const std::span<uint32_t> objData);
|
||||
|
||||
34
src/tmxlayer.hpp
Normal file
34
src/tmxlayer.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/* tmxlayer.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#ifndef TMXLAYER_HPP
|
||||
#define TMXLAYER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
class TmxLayer
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t FLIP_HORZ = 0x80000000;
|
||||
static constexpr uint32_t FLIP_VERT = 0x40000000;
|
||||
static constexpr uint32_t FLIP_DIAG = 0x20000000;
|
||||
static constexpr uint32_t FLIP_MASK = 0xE0000000;
|
||||
|
||||
TmxLayer() : mWidth(0), mHeight(0), mTileDat(nullptr) {}
|
||||
TmxLayer(int aWidth, int aHeight, std::string aName, uint32_t* aTileDat)
|
||||
: mName(std::move(aName)), mWidth(aWidth), mHeight(aHeight), mTileDat(aTileDat) {}
|
||||
inline ~TmxLayer() { delete[] mTileDat; }
|
||||
|
||||
constexpr const std::string& GetName() const { return mName; }
|
||||
constexpr int GetWidth() const { return mWidth; }
|
||||
constexpr int GetHeight() const { return mHeight; }
|
||||
constexpr const uint32_t* GetData() const { return mTileDat; }
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
int mWidth, mHeight;
|
||||
uint32_t* mTileDat;
|
||||
};
|
||||
|
||||
#endif//TMXLAYER_HPP
|
||||
25
src/tmxobject.hpp
Normal file
25
src/tmxobject.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/* tmxobject.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#ifndef TMXOBJECT_HPP
|
||||
#define TMXOBJECT_HPP
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class TmxObject
|
||||
{
|
||||
public:
|
||||
TmxObject() : mX(0.0f), mY(0.0f) {}
|
||||
TmxObject(std::string aName, float aX, float aY)
|
||||
: mName(std::move(aName)), mX(aX), mY(aY) {}
|
||||
~TmxObject() = default;
|
||||
|
||||
constexpr const std::string& GetName() const { return mName; }
|
||||
inline void GetPos(float& aOutX, float& aOutY) const { aOutX = mX; aOutY = mY; }
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
float mX, mY;
|
||||
};
|
||||
|
||||
#endif//TMXOBJECT_HPP
|
||||
@@ -1,12 +1,191 @@
|
||||
/* tmxreader.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#include "tmxreader.hpp"
|
||||
#include "tmxlite/Map.hpp"
|
||||
#include "tmxlite/TileLayer.hpp"
|
||||
#include "tmxlite/ObjectGroup.hpp"
|
||||
#include "tmxtileset.hpp"
|
||||
#include "tmxobject.hpp"
|
||||
#include "tmxlayer.hpp"
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
|
||||
bool TmxReader::DecodeMap(uint32_t* aOut, size_t aOutSize, const std::string& aBase64Dat)
|
||||
{
|
||||
// Cut leading & trailing whitespace (including newlines)
|
||||
auto beg = std::find_if_not(aBase64Dat.begin(), aBase64Dat.end(), ::isspace);
|
||||
if (beg == std::end(aBase64Dat))
|
||||
return false;
|
||||
auto end = std::find_if_not(aBase64Dat.rbegin(), aBase64Dat.rend(), ::isspace);
|
||||
std::size_t begOff = std::distance(aBase64Dat.begin(), beg);
|
||||
std::size_t endOff = std::distance(end, aBase64Dat.rend()) - begOff;
|
||||
auto trimmed = aBase64Dat.substr(begOff, endOff);
|
||||
|
||||
// Decode base64 string
|
||||
std::string decoded = base64_decode(trimmed);
|
||||
|
||||
// Decompress compressed data
|
||||
auto dstSize = static_cast<mz_ulong>(aOutSize);
|
||||
int res = uncompress(
|
||||
reinterpret_cast<unsigned char*>(aOut),
|
||||
&dstSize,
|
||||
reinterpret_cast<const unsigned char*>(decoded.data()),
|
||||
static_cast<mz_ulong>(decoded.size()));
|
||||
decoded.clear();
|
||||
if (res < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TmxReader::ReadTileset(rapidxml::xml_node<>* aXNode)
|
||||
{
|
||||
rapidxml::xml_attribute<>* xAttrib;
|
||||
|
||||
const char* name = "";
|
||||
const char* source = "";
|
||||
uint32_t firstGid = 0;
|
||||
|
||||
// Read name
|
||||
xAttrib = aXNode->first_attribute("name");
|
||||
if (xAttrib != nullptr)
|
||||
name = xAttrib->value();
|
||||
|
||||
// Read source
|
||||
xAttrib = aXNode->first_attribute("source");
|
||||
if (xAttrib != nullptr)
|
||||
source = xAttrib->value();
|
||||
|
||||
// Read first global ID
|
||||
xAttrib = aXNode->first_attribute("firstgid");
|
||||
if (xAttrib != nullptr)
|
||||
firstGid = static_cast<uint32_t>(std::stoul(xAttrib->value()));
|
||||
|
||||
mTilesets.push_back(new TmxTileset(name, source, firstGid));
|
||||
}
|
||||
|
||||
void TmxReader::ReadLayer(rapidxml::xml_node<>* aXNode)
|
||||
{
|
||||
rapidxml::xml_attribute<>* xAttrib;
|
||||
const char* name = "";
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
uint32_t* tileDat = nullptr;
|
||||
|
||||
// Read name
|
||||
xAttrib = aXNode->first_attribute("name");
|
||||
if (xAttrib != nullptr)
|
||||
name = xAttrib->value();
|
||||
|
||||
// Read width
|
||||
xAttrib = aXNode->first_attribute("width");
|
||||
if (xAttrib != nullptr)
|
||||
width = std::stoi(xAttrib->value());
|
||||
|
||||
// Read height
|
||||
xAttrib = aXNode->first_attribute("height");
|
||||
if (xAttrib != nullptr)
|
||||
height = std::stoi(xAttrib->value());
|
||||
|
||||
// Read tile data
|
||||
auto xData = aXNode->first_node("data");
|
||||
if (xData != nullptr)
|
||||
{
|
||||
// TODO: don't assume base64 & zlib
|
||||
tileDat = new uint32_t[width * height];
|
||||
if (!DecodeMap(tileDat, width * height * sizeof(uint32_t), std::string(xData->value())))
|
||||
{
|
||||
delete[] tileDat;
|
||||
tileDat = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
mLayers.push_back(new TmxLayer(width, height, name, tileDat));
|
||||
}
|
||||
|
||||
void TmxReader::ReadObjects(rapidxml::xml_node<>* aXNode)
|
||||
{
|
||||
for (auto xNode = aXNode->first_node(); xNode != nullptr; xNode = xNode->next_sibling())
|
||||
{
|
||||
if (strcmp(xNode->name(), "object") != 0)
|
||||
continue;
|
||||
|
||||
rapidxml::xml_attribute<>* xAttrib;
|
||||
const char* name = "";
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
// Read name
|
||||
xAttrib = xNode->first_attribute("name");
|
||||
if (xAttrib != nullptr)
|
||||
name = xAttrib->value();
|
||||
|
||||
// Read X pos
|
||||
xAttrib = xNode->first_attribute("x");
|
||||
if (xAttrib != nullptr)
|
||||
x = std::stof(xAttrib->value());
|
||||
|
||||
// Read Y pos
|
||||
xAttrib = xNode->first_attribute("y");
|
||||
if (xAttrib != nullptr)
|
||||
y = std::stof(xAttrib->value());
|
||||
|
||||
mObjects.push_back(new TmxObject(name, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
void TmxReader::Open(std::istream& aIn)
|
||||
{
|
||||
// Delete old tilesets
|
||||
for (auto tileset : mTilesets)
|
||||
delete tileset;
|
||||
mTilesets.clear();
|
||||
|
||||
// Delete old layers
|
||||
for (auto layer : mLayers)
|
||||
delete layer;
|
||||
mLayers.clear();
|
||||
|
||||
mGidTable.clear();
|
||||
|
||||
// Read string into a buffer
|
||||
std::stringstream buf;
|
||||
buf << aIn.rdbuf();
|
||||
std::string strXml = buf.str();
|
||||
buf.clear();
|
||||
|
||||
// Parse document
|
||||
rapidxml::xml_document<> xDoc;
|
||||
xDoc.parse<0>(const_cast<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)
|
||||
mWidth = std::stoi(xAttrib->value());
|
||||
if ((xAttrib = xMap->first_attribute("height")) != nullptr)
|
||||
mHeight = std::stoi(xAttrib->value());
|
||||
|
||||
// Read nodes
|
||||
for (auto xNode = xMap->first_node(); xNode != nullptr; xNode = xNode->next_sibling())
|
||||
{
|
||||
// Read layer nodes
|
||||
if (strcmp(xNode->name(), "layer") == 0)
|
||||
ReadLayer(xNode);
|
||||
else
|
||||
if (strcmp(xNode->name(), "tileset") == 0)
|
||||
ReadTileset(xNode);
|
||||
else
|
||||
if (strcmp(xNode->name(), "objectgroup") == 0)
|
||||
ReadObjects(xNode);
|
||||
}
|
||||
|
||||
// Generate global id table
|
||||
for (auto tileset : mTilesets)
|
||||
mGidTable.push_back(tileset->GetFirstGid());
|
||||
std::sort(mGidTable.rbegin(), mGidTable.rend());
|
||||
}
|
||||
|
||||
TmxReader::Error TmxReader::Open(const std::string& inPath,
|
||||
const std::string_view graphicsName,
|
||||
@@ -67,7 +246,7 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
|
||||
return Error::PALETTE_NOTFOUND;
|
||||
|
||||
// Read TMX map
|
||||
mSize = Size { map.getTileCount().x, map.getTileCount().y };
|
||||
mSize = Size{ map.getTileCount().x, map.getTileCount().y };
|
||||
size_t numTiles = static_cast<size_t>(mSize.width) * static_cast<size_t>(mSize.height);
|
||||
|
||||
// Read graphics layer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* tmxreader.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
/* tmxreader.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#ifndef TMXREADER_HPP
|
||||
#define TMXREADER_HPP
|
||||
@@ -10,6 +10,11 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <rapidxml/rapidxml.hpp>
|
||||
|
||||
class TmxTileset;
|
||||
class TmxLayer;
|
||||
class TmxObject;
|
||||
|
||||
class TmxReader
|
||||
{
|
||||
@@ -34,7 +39,7 @@ public:
|
||||
const std::string_view paletteName,
|
||||
const std::string_view collisionName,
|
||||
const std::map<std::string, uint32_t>& objMapping);
|
||||
struct Size { unsigned width, height; };
|
||||
struct Size { int width, height; };
|
||||
|
||||
[[nodiscard]] constexpr Size GetSize() const { return mSize; }
|
||||
[[nodiscard]] constexpr size_t TileCount() const { return
|
||||
|
||||
28
src/tmxtileset.hpp
Normal file
28
src/tmxtileset.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
/* tmxtileset.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#ifndef TMXTILESET_HPP
|
||||
#define TMXTILESET_HPP
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
class TmxTileset
|
||||
{
|
||||
public:
|
||||
TmxTileset() : mFirstGid(0) {}
|
||||
TmxTileset(std::string aName, std::string aSource, uint32_t aFirstGid)
|
||||
: mName(std::move(aName)), mSource(std::move(aSource)), mFirstGid(aFirstGid) {}
|
||||
~TmxTileset() = default;
|
||||
|
||||
constexpr const std::string& GetName() const { return mName; }
|
||||
constexpr const std::string& GetSource() const { return mSource; }
|
||||
constexpr uint32_t GetFirstGid() const { return mFirstGid; }
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mSource;
|
||||
uint32_t mFirstGid;
|
||||
};
|
||||
|
||||
#endif//TMXTILESET_HPP
|
||||
Reference in New Issue
Block a user