diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 594f5cc..c569600 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(tmx2gba + strtools.hpp strtools.cpp argparse.hpp argparse.cpp $<$>:gzip.hpp gzip.cpp> tmxlayer.hpp diff --git a/src/strtools.cpp b/src/strtools.cpp new file mode 100644 index 0000000..3ad5c6f --- /dev/null +++ b/src/strtools.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Zlib +// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur + +#include "strtools.hpp" +#include +#include + + +const std::string_view TrimWhitespace(const std::string_view str) +{ + auto beg = std::find_if_not(str.begin(), str.end(), ::isspace); + if (beg == std::end(str)) + { + return {}; + } + auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace); + auto begOff = std::distance(str.begin(), beg); + auto endOff = std::distance(end, str.rend()) - begOff; + using size_type = std::string::size_type; + return str.substr(static_cast(begOff), static_cast(endOff)); +} + +std::string SanitiseLabel(const std::string_view ident) +{ + std::string out; + out.reserve(ident.length()); + + int last = '_'; + for (int i : ident) + { + if (out.empty() && std::isdigit(i)) { continue; } + if (!std::isalnum(i)) { i = '_'; } + if (i != '_' || last != '_') { out.push_back(i); } + last = i; + } + return out; +} diff --git a/src/strtools.hpp b/src/strtools.hpp new file mode 100644 index 0000000..ad0d0d7 --- /dev/null +++ b/src/strtools.hpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Zlib +// SPDX-FileCopyrightText: (c) 2024 a dinosaur + +#ifndef STRTOOLS_HPP +#define STRTOOLS_HPP + +#include +#include + +// Cut leading & trailing whitespace (including newlines) +[[nodiscard]] const std::string_view TrimWhitespace(const std::string_view str); + +// Convert string to valid C identifier +[[nodiscard]] std::string SanitiseLabel(const std::string_view ident); + + +#include + +// Template functions for converting unsigned ints to C/GNU style hex + +static inline constexpr char CHexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; } +static inline constexpr char CHexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; } + +template static void CHex(std::ostream& s, T x); +template <> void CHex(std::ostream& s, uint8_t x) +{ + if (x > 9) s << "0x"; + if (x > 15) s << CHexU(x); + s << CHexL(x); +} +template <> void CHex(std::ostream& s, uint16_t x) +{ + if (x > 9) s << "0x"; + if (x > 4095) s << CHexU(static_cast(x >> 8)); + if (x > 255) s << CHexL(static_cast(x >> 8)); + if (x > 15) s << CHexU(static_cast(x)); + s << CHexL(static_cast(x)); +} +template <> void CHex(std::ostream& s, uint32_t x) +{ + if (x > 9) s << "0x"; + if (x > 0xFFFFFFF) s << CHexU(static_cast(x >> 24)); + if (x > 0xFFFFFF) s << CHexL(static_cast(x >> 24)); + if (x > 0xFFFFF) s << CHexU(static_cast(x >> 16)); + if (x > 65535) s << CHexL(static_cast(x >> 16)); + if (x > 4095) s << CHexU(static_cast(x >> 8)); + if (x > 255) s << CHexL(static_cast(x >> 8)); + if (x > 15) s << CHexU(static_cast(x)); + s << CHexL(static_cast(x)); +} + + +#include +#include +#include + +// Templated string to int/float w/ exception-less error handling + +template +[[nodiscard]] static std::optional IntFromStr(const char* str, int base = 0) noexcept +{ + using std::numeric_limits; + + errno = 0; + char* end = nullptr; + long res = std::strtol(str, &end, base); + if (errno == ERANGE) { return std::nullopt; } + if (str == end) { return std::nullopt; } + if constexpr (sizeof(long) > sizeof(T)) + { + if (res > numeric_limits::max() || res < numeric_limits::min()) + return std::nullopt; + } + + return static_cast(res); +} + +template +[[nodiscard]] static std::optional UintFromStr(const char* str, int base = 0) noexcept +{ + using std::numeric_limits; + + char* end = nullptr; + errno = 0; + unsigned long res = std::strtoul(str, &end, base); + if (errno == ERANGE) { return std::nullopt; } + if (str == end) { return std::nullopt; } + if constexpr (numeric_limits::max() > numeric_limits::max()) + { + if (res > numeric_limits::max()) { return std::nullopt; } + } + + return static_cast(res); +} + +template +[[nodiscard]] static std::optional FloatFromStr(const char* str) noexcept +{ + char* end = nullptr; + T res; + errno = 0; + if constexpr (std::is_same_v) + res = std::strtof(str, &end); + else + res = static_cast(std::strtod(str, &end)); + if (errno == ERANGE) { return std::nullopt; } + if (str == end) { return std::nullopt; } + + return res; +} + +#endif//STRTOOLS_HPP diff --git a/src/swriter.cpp b/src/swriter.cpp index ebe6595..b703b10 100644 --- a/src/swriter.cpp +++ b/src/swriter.cpp @@ -2,43 +2,11 @@ // SPDX-FileCopyrightText: (c) 2024 a dinosaur #include "swriter.hpp" +#include "strtools.hpp" #include -#include #include -static inline constexpr char HexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; } -static inline constexpr char HexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; } - -template static void CHex(std::ostream& s, T x); -template <> void CHex(std::ostream& s, uint8_t x) -{ - if (x > 9) s << "0x"; - if (x > 15) s << HexU(x); - s << HexL(x); -} -template <> void CHex(std::ostream& s, uint16_t x) -{ - if (x > 9) s << "0x"; - if (x > 4095) s << HexU(static_cast(x >> 8)); - if (x > 255) s << HexL(static_cast(x >> 8)); - if (x > 15) s << HexU(static_cast(x)); - s << HexL(static_cast(x)); -} -template <> void CHex(std::ostream& s, uint32_t x) -{ - if (x > 9) s << "0x"; - if (x > 0xFFFFFFF) s << HexU(static_cast(x >> 24)); - if (x > 0xFFFFFF) s << HexL(static_cast(x >> 24)); - if (x > 0xFFFFF) s << HexU(static_cast(x >> 16)); - if (x > 65535) s << HexL(static_cast(x >> 16)); - if (x > 4095) s << HexU(static_cast(x >> 8)); - if (x > 255) s << HexL(static_cast(x >> 8)); - if (x > 15) s << HexU(static_cast(x)); - s << HexL(static_cast(x)); -} - - template static constexpr const std::string_view DataType(); template <> constexpr const std::string_view DataType() { return ".byte"; } template <> constexpr const std::string_view DataType() { return ".hword"; } diff --git a/src/tmx2gba.cpp b/src/tmx2gba.cpp index 7fa9d4c..e5c74f7 100644 --- a/src/tmx2gba.cpp +++ b/src/tmx2gba.cpp @@ -10,6 +10,7 @@ constexpr std::string_view copyrightStr("(c) 2015-2024 a dinosaur"); #include "convert.hpp" #include "headerwriter.hpp" #include "swriter.hpp" +#include "strtools.hpp" #include "config.h" #include #include @@ -115,26 +116,6 @@ static bool ParseArgs(int argc, char** argv, Arguments& params) return true; } - -static std::string SanitiseLabel(const std::string_view ident) -{ - std::string out; - out.reserve(ident.length()); - - int last = '_'; - for (int i : ident) - { - if (out.empty() && std::isdigit(i)) - continue; - if (!std::isalnum(i)) - i = '_'; - if (i != '_' || last != '_') - out.push_back(i); - last = i; - } - return out; -} - int main(int argc, char** argv) { Arguments p; diff --git a/src/tmxmap.cpp b/src/tmxmap.cpp index 4dbba55..3d91b0e 100644 --- a/src/tmxmap.cpp +++ b/src/tmxmap.cpp @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur #include "tmxmap.hpp" +#include "strtools.hpp" #include "config.h" #include #include @@ -11,80 +12,10 @@ # include "gzip.hpp" #endif #include -#include #include -#include #include -template -[[nodiscard]] static std::optional IntFromStr(const char* str, int base = 0) noexcept -{ - using std::numeric_limits; - - errno = 0; - char* end = nullptr; - long res = std::strtol(str, &end, base); - if (errno == ERANGE) { return std::nullopt; } - if (str == end) { return std::nullopt; } - if constexpr (sizeof(long) > sizeof(T)) - { - if (res > numeric_limits::max() || res < numeric_limits::min()) - return std::nullopt; - } - - return static_cast(res); -} - -template -[[nodiscard]] static std::optional UintFromStr(const char* str, int base = 0) noexcept -{ - using std::numeric_limits; - - char* end = nullptr; - errno = 0; - unsigned long res = std::strtoul(str, &end, base); - if (errno == ERANGE) { return std::nullopt; } - if (str == end) { return std::nullopt; } - if constexpr (numeric_limits::max() > numeric_limits::max()) - { - if (res > numeric_limits::max()) { return std::nullopt; } - } - - return static_cast(res); -} - -template -[[nodiscard]] static std::optional FloatFromStr(const char* str) noexcept -{ - char* end = nullptr; - T res; - errno = 0; - if constexpr (std::is_same_v) - res = std::strtof(str, &end); - else - res = static_cast(std::strtod(str, &end)); - if (errno == ERANGE) { return std::nullopt; } - if (str == end) { return std::nullopt; } - - return res; -} - -[[nodiscard]] static std::optional UnBase64(const std::string_view base64) -{ - // Cut leading & trailing whitespace (including newlines) - auto beg = std::find_if_not(base64.begin(), base64.end(), ::isspace); - if (beg == std::end(base64)) { return std::nullopt; } - auto end = std::find_if_not(base64.rbegin(), base64.rend(), ::isspace); - auto begOff = std::distance(base64.begin(), beg); - auto endOff = std::distance(end, base64.rend()) - begOff; - using size_type = std::string::size_type; - const auto trimmed = base64.substr(static_cast(begOff), static_cast(endOff)); - - // Decode base64 string - return base64_decode(trimmed); -} - enum class Encoding { XML, BASE64, CSV, INVALID }; enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID }; @@ -105,6 +36,7 @@ enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID }; return Compression::INVALID; } + [[nodiscard]] static bool Decompress(Compression compression, std::span out, const std::string_view decoded) { //FIXME: lmao what is big endian @@ -192,22 +124,22 @@ void TmxMap::ReadLayer(const pugi::xml_node& xNode) if (encoding == Encoding::BASE64) { // Decode base64 string - auto decoded = UnBase64(xData.child_value()); - if (!decoded.has_value()) + auto decoded = base64_decode(TrimWhitespace(xData.child_value())); + if (decoded.empty()) return; auto compression = CompressionFromStr(xData.attribute("compression").value()); if (compression == Compression::GZIP || compression == Compression::ZLIB || compression == Compression::ZSTD) { tileDat.resize(numTiles); - if (!Decompress(compression, tileDat, decoded.value())) + if (!Decompress(compression, tileDat, decoded)) return; } else if (compression == Compression::NONE) { tileDat.reserve(numTiles); - const auto end = decoded.value().end(); - for (auto it = decoded.value().begin(); it < end - 3;) + const auto end = decoded.end(); + for (auto it = decoded.begin(); it < end - 3;) { uint32_t tile = static_cast(static_cast(*it++)); tile |= static_cast(static_cast(*it++)) << 8u;