diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e348053..0a013b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,8 @@ add_executable(tmx2gba tmxobject.hpp tmxreader.hpp tmxreader.cpp tmxtileset.hpp + swriter.hpp + headerwriter.hpp headerwriter.cpp tmx2gba.cpp) target_link_libraries(tmx2gba diff --git a/src/headerwriter.cpp b/src/headerwriter.cpp new file mode 100644 index 0000000..46844b7 --- /dev/null +++ b/src/headerwriter.cpp @@ -0,0 +1,93 @@ +/* headerwriter.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */ + +#include "headerwriter.hpp" + + +template static constexpr std::string_view DatType(); +template <> constexpr std::string_view DatType() { return "unsigned char"; } +template <> constexpr std::string_view DatType() { return "unsigned short"; } +template <> constexpr std::string_view DatType() { return "unsigned int"; } + +void HeaderWriter::WriteSize(int width, int height) +{ + stream << std::endl; + WriteDefine(name + "Width", width); + WriteDefine(name + "Height", height); +} + +void HeaderWriter::WriteCharacterMap(const std::span charData) +{ + stream << std::endl; + WriteDefine(name + "TilesLen", charData.size() * 2); + WriteSymbol(name + "Tiles", DatType(), charData.size()); +} + +void HeaderWriter::WriteCollision(const std::span collisionData) +{ + stream << std::endl; + WriteDefine(name + "CollisionLen", collisionData.size()); + WriteSymbol(name + "Collision", DatType(), collisionData.size()); +} + +void HeaderWriter::WriteObjects(const std::span objData) +{ + stream << std::endl; + WriteDefine(name + "ObjCount", objData.size() / 3); + WriteDefine(name + "ObjdatLen", objData.size() * sizeof(int)); + WriteSymbol(name + "Objdat", DatType(), objData.size()); +} + + +static std::string GuardName(const std::string_view name) +{ + auto upper = std::string(name); + for (auto& c: upper) + c = static_cast(toupper(c)); + return "TMX2GBA_" + upper; +} + + +void HeaderWriter::WriteGuardStart() +{ + const std::string guard = GuardName(name); + stream << "#ifndef " << guard << std::endl; + stream << "#define " << guard << std::endl; +} + +void HeaderWriter::WriteGuardEnd() +{ + const std::string guard = GuardName(name); + stream << std::endl << "#endif//" << guard << std::endl; +} + + +HeaderWriter::~HeaderWriter() +{ + if (stream.is_open()) + { + WriteGuardEnd(); + stream.close(); + } +} + + +bool HeaderWriter::Open(const std::string_view path, const std::string_view name) +{ + this->name = name; + stream.open(path); + if (!stream.is_open()) + return false; + + WriteGuardStart(); + return true; +} + +void HeaderWriter::WriteDefine(const std::string_view name, const std::string_view value) +{ + stream << "#define " << name << " " << value << std::endl; +} + +void HeaderWriter::WriteSymbol(const std::string_view name, const std::string_view type, std::size_t count) +{ + stream << "extern const " << type << " " << name << "[" << count << "];" << std::endl; +} diff --git a/src/headerwriter.hpp b/src/headerwriter.hpp new file mode 100644 index 0000000..fdf3244 --- /dev/null +++ b/src/headerwriter.hpp @@ -0,0 +1,45 @@ +/* headerwriter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */ + +#ifndef HEADERWRITER_HPP +#define HEADERWRITER_HPP + +#include +#include +#include +#include +#include +#include +#include + +template +concept NumericType = std::integral || std::floating_point; + +class HeaderWriter +{ + std::ofstream stream; + std::string name; + + void WriteGuardStart(); + void WriteGuardEnd(); + +public: + ~HeaderWriter(); + + [[nodiscard]] bool Open(const std::string_view path, const std::string_view name); + + void WriteDefine(const std::string_view name, const std::string_view value); + void WriteSymbol(const std::string_view name, const std::string_view type, std::size_t count); + + template + void WriteDefine(const std::string_view name, T value) + { + WriteDefine(name, std::to_string(value)); + } + + void WriteSize(int width, int height); + void WriteCharacterMap(const std::span charData); + void WriteCollision(const std::span collisionData); + void WriteObjects(const std::span objData); +}; + +#endif//HEADERWRITER_HPP diff --git a/src/swriter.hpp b/src/swriter.hpp new file mode 100644 index 0000000..e340eba --- /dev/null +++ b/src/swriter.hpp @@ -0,0 +1,86 @@ +/* swwriter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */ + +#ifndef SWRITER_HPP +#define SWRITER_HPP + +#include +#include +#include +#include +#include +#include + +class SWriter +{ + std::ofstream stream; + int writes = 0; + + template static constexpr const char* DatType(); + template <> constexpr const char* DatType() { return ".byte"; } + template <> constexpr const char* DatType() { return ".hword"; } + template <> constexpr const char* DatType() { return ".word"; } + + template + static void WriteArray(std::ostream& aOut, const std::vector& aDat, int aPerCol = 16) + { + int col = 0; + + aOut.setf(std::ios::hex, std::ios::basefield); + aOut.setf(std::ios::showbase); + + size_t i = 0; + for (T element : aDat) + { + if (col == 0) + aOut << "\t" << DatType() << " "; + + aOut << std::hex << (int)element; + + if (i < aDat.size() - 1) + { + if (++col < aPerCol) + { + aOut << ","; + } + else + { + aOut << "" << std::endl; + col = 0; + } + } + + ++i; + } + } + +public: + [[nodiscard]] bool Open(const std::string_view path) + { + stream.open(path); + return stream.is_open(); + } + + ~SWriter() + { + if (stream.is_open()) + { + stream.close(); + } + } + + template + void WriteArray(const std::string_view name, T data) + { + if (writes++ != 0) + stream << std::endl; + stream << "\t.section .rodata" << std::endl; + stream << "\t.align 2" << std::endl; + stream << "\t.global " << name << "Tiles" << std::endl; + stream << "\t.hidden " << name << "Tiles" << std::endl; + stream << name << "Tiles" << ":" << std::endl; + WriteArray(stream, data); + stream << std::endl; + } +}; + +#endif//SWRITER_HPP diff --git a/src/tmx2gba.cpp b/src/tmx2gba.cpp index 2312b3f..5af2e24 100644 --- a/src/tmx2gba.cpp +++ b/src/tmx2gba.cpp @@ -4,10 +4,10 @@ #include "tmxreader.hpp" #include "tmxlayer.hpp" #include "tmxobject.hpp" +#include "headerwriter.hpp" +#include "swriter.hpp" #include -#include #include -#include #include @@ -118,44 +118,6 @@ bool ParseArgs(int argc, char** argv, Arguments& params) } -template constexpr const char* DatType(); -template <> constexpr const char* DatType() { return ".byte"; } -template <> constexpr const char* DatType() { return ".hword"; } -template <> constexpr const char* DatType() { return ".word"; } - -template -void WriteArray(std::ofstream& aOut, const std::vector& aDat, int aPerCol = 16) -{ - int col = 0; - - aOut.setf(std::ios::hex, std::ios::basefield); - aOut.setf(std::ios::showbase); - - size_t i = 0; - for (T element : aDat) - { - if (col == 0) - aOut << "\t" << DatType() << " "; - - aOut << std::hex << (int)element; - - if (i < aDat.size() - 1) - { - if (++col < aPerCol) - { - aOut << ","; - } - else - { - aOut << "" << std::endl; - col = 0; - } - } - - ++i; - } -} - int main(int argc, char** argv) { Arguments p; @@ -182,7 +144,7 @@ int main(int argc, char** argv) if (splitter == std::string::npos) { std::cerr << "Malformed mapping (missing a splitter)." << std::endl; - return -1; + return 1; } try @@ -205,7 +167,7 @@ int main(int argc, char** argv) if (!fin.is_open()) { std::cerr << "Failed to open input file." << std::endl; - return -1; + return 1; } tmx.Open(fin); @@ -213,7 +175,7 @@ int main(int argc, char** argv) if (tmx.GetLayerCount() == 0) { std::cerr << "No layers found." << std::endl; - return -1; + return 1; } const TmxLayer* layerGfx = p.layer.empty() ? tmx.GetLayer(0) @@ -228,33 +190,28 @@ int main(int argc, char** argv) if (layerGfx == nullptr) { std::cerr << "Input layer not found." << std::endl; - return -1; - } - - // Open output files - std::ofstream foutS(p.outPath + ".s"); - std::ofstream foutH(p.outPath + ".h"); - if (!foutS.is_open() || !foutH.is_open()) - { - std::cerr << "Failed to create output file(s)."; - return -1; + return 1; } + // Get name from file + //TODO: properly sanitise int slashPos = std::max((int)p.outPath.find_last_of('/'), (int)p.outPath.find_last_of('\\')); std::string name = p.outPath; if (slashPos != -1) name = name.substr(slashPos + 1); - // Write header guards - std::string guard = "TMX2GBA_" + name; - for (auto& c: guard) - c = static_cast(toupper(c)); - foutH << "#ifndef " << guard << std::endl; - foutH << "#define " << guard << std::endl; - foutH << std::endl; - foutH << "#define " << name << "Width " << tmx.GetWidth() << std::endl; - foutH << "#define " << name << "Height " << tmx.GetHeight() << std::endl; - foutH << std::endl; + // Open output files + SWriter outS; HeaderWriter outH; + if (!outS.Open(p.outPath + ".s")) + { + std::cerr << "Failed to create output file \"" << p.outPath << ".s\"."; + return 1; + } + if (!outH.Open(p.outPath + ".h", name)) + { + std::cerr << "Failed to create output file \"" << p.outPath << ".h\"."; + return 1; + } // Convert to GBA-friendly charmap data const uint32_t* gfxTiles = layerGfx->GetData(); @@ -285,29 +242,21 @@ int main(int argc, char** argv) } // Write out charmap - foutH << "#define " << name << "TilesLen " << charDat.size() * 2 << std::endl; - foutH << "extern const unsigned short " << name << "Tiles[" << charDat.size() << "];" << std::endl; - foutH << std::endl; - - foutS << "\t.section .rodata" << std::endl; - foutS << "\t.align 2" << std::endl; - foutS << "\t.global " << name << "Tiles" << std::endl; - foutS << "\t.hidden " << name << "Tiles" << std::endl; - foutS << name << "Tiles" << ":" << std::endl; - WriteArray(foutS, charDat); - foutS << std::endl; + outH.WriteSize(tmx.GetWidth(), tmx.GetHeight()); + outH.WriteCharacterMap(charDat); + outS.WriteArray("Tiles", charDat); // Convert collision map & write it out if (layerCls != nullptr) { - std::vector vucCollisionDat; - vucCollisionDat.reserve(layerCls->GetWidth() * layerCls->GetHeight()); + std::vector collisionDat; + collisionDat.reserve(layerCls->GetWidth() * layerCls->GetHeight()); gfxTiles = layerCls->GetData(); for (int i = 0; i < layerCls->GetWidth() * layerCls->GetHeight(); ++i) { uint8_t ucTile = (uint8_t)tmx.LidFromGid((*gfxTiles++) & ~TmxLayer::FLIP_MASK); - vucCollisionDat.push_back(ucTile); + collisionDat.push_back(ucTile); } // Try to nicely append "_collision" to the output name @@ -319,18 +268,8 @@ int main(int argc, char** argv) path = p.outPath + "_collision"; // Write collision - foutH << "#define " << name << "CollisionLen " << vucCollisionDat.size() << std::endl; - foutH << "extern const unsigned char " << name << "Collision[" << vucCollisionDat.size() << "];" << std::endl; - foutH << std::endl; - - foutS << std::endl; - foutS << "\t.section .rodata" << std::endl; - foutS << "\t.align 2" << std::endl; - foutS << "\t.global " << name << "Collision" << std::endl; - foutS << "\t.hidden " << name << "Collision" << std::endl; - foutS << name << "Collision" << ":" << std::endl; - WriteArray(foutS, vucCollisionDat); - foutS << std::endl; + outH.WriteCollision(collisionDat); + outS.WriteArray("Collision", collisionDat); } if (!p.objMappings.empty()) @@ -351,25 +290,9 @@ int main(int argc, char** argv) } // Write objects - foutH << "#define " << name << "ObjCount " << objDat.size() / 3 << std::endl; - foutH << "#define " << name << "ObjdatLen " << objDat.size() * sizeof(int) << std::endl; - foutH << "extern const unsigned int " << name << "Objdat[" << objDat.size() << "];" << std::endl; - foutH << std::endl; - - foutS << std::endl; - foutS << "\t.section .rodata" << std::endl; - foutS << "\t.align 2" << std::endl; - foutS << "\t.global " << name << "Objdat" << std::endl; - foutS << "\t.hidden " << name << "Objdat" << std::endl; - foutS << name << "Objdat" << ":" << std::endl; - WriteArray(foutS, objDat); - foutS << std::endl; + outH.WriteObjects(objDat); + outS.WriteArray("Objdat", objDat); } - foutH << "#endif//" << guard << std::endl; - - foutH.close(); - foutS.close(); - return 0; }