diff --git a/DataSpec/CMakeLists.txt b/DataSpec/CMakeLists.txt index f6c61ee60..7dea5a421 100644 --- a/DataSpec/CMakeLists.txt +++ b/DataSpec/CMakeLists.txt @@ -1,5 +1,6 @@ -include_directories(${HECL_INCLUDE_DIR} ${NOD_LIB_INCLUDE_DIR}) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +include_directories(${HECL_INCLUDE_DIR} ${NOD_LIB_INCLUDE_DIR} ${LIBPNG_INCLUDE_DIR} + ${SQUISH_INCLUDE_DIR}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-multichar") # Magic ingredient find_package(atdna REQUIRED) diff --git a/DataSpec/DNACommon/CMakeLists.txt b/DataSpec/DNACommon/CMakeLists.txt index bc0a36683..9e9e6ae1a 100644 --- a/DataSpec/DNACommon/CMakeLists.txt +++ b/DataSpec/DNACommon/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(DNACommon - STRG.hpp - STRG.cpp - DNACommon.hpp - DNACommon.cpp) + STRG.hpp STRG.cpp + TXTR.hpp TXTR.cpp + DNACommon.hpp DNACommon.cpp) diff --git a/DataSpec/DNACommon/TXTR.cpp b/DataSpec/DNACommon/TXTR.cpp new file mode 100644 index 000000000..b8528501e --- /dev/null +++ b/DataSpec/DNACommon/TXTR.cpp @@ -0,0 +1,444 @@ +#include +#include +#include "TXTR.hpp" + +namespace Retro +{ + +static LogVisor::LogModule Log("libpng"); + +/* GX uses this upsampling technique to prevent banding on downsampled texture formats */ +static inline uint8_t Convert3To8(uint8_t v) +{ + /* Swizzle bits: 00000123 -> 12312312 */ + return (v << 5) | (v << 2) | (v >> 1); +} + +static inline uint8_t Convert4To8(uint8_t v) +{ + /* Swizzle bits: 00001234 -> 12341234 */ + return (v << 4) | v; +} + +static inline uint8_t Convert5To8(uint8_t v) +{ + /* Swizzle bits: 00012345 -> 12345123 */ + return (v << 3) | (v >> 2); +} + +static inline uint8_t Convert6To8(uint8_t v) +{ + /* Swizzle bits: 00123456 -> 12345612 */ + return (v << 2) | (v >> 4); +} + +static inline uint8_t Lookup4BPP(const uint8_t* texels, int width, int x, int y) +{ + int bwidth = (width + 7) / 8; + int bx = x / 8; + int by = y / 8; + int rx = x % 8; + int ry = y % 8; + int bidx = by * bwidth + bx; + const uint8_t* btexels = &texels[32*bidx]; + return btexels[ry*4+rx/2] << (rx%2*4) & 0xf; +} + +static inline uint8_t Lookup8BPP(const uint8_t* texels, int width, int x, int y) +{ + int bwidth = (width + 7) / 8; + int bx = x / 8; + int by = y / 4; + int rx = x % 8; + int ry = y % 4; + int bidx = by * bwidth + bx; + const uint8_t* btexels = &texels[32*bidx]; + return btexels[ry*8+rx]; +} + +static inline uint16_t Lookup16BPP(const uint8_t* texels, int width, int x, int y) +{ + int bwidth = (width + 3) / 4; + int bx = x / 4; + int by = y / 4; + int rx = x % 4; + int ry = y % 4; + int bidx = by * bwidth + bx; + const uint16_t* btexels = (uint16_t*)&texels[32*bidx]; + return btexels[ry*4+rx]; +} + +static inline void LookupRGBA8(const uint8_t* texels, int width, int x, int y, + uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) +{ + int bwidth = (width + 3) / 4; + int bx = x / 4; + int by = y / 4; + int rx = x % 4; + int ry = y % 4; + int bidx = (by * bwidth + bx) * 2; + const uint16_t* artexels = (uint16_t*)&texels[32*bidx]; + const uint16_t* gbtexels = (uint16_t*)&texels[32*(bidx+1)]; + uint16_t ar = HECL::SBig(artexels[ry*4+rx]); + *a = ar >> 8 & 0xff; + *r = ar & 0xff; + uint16_t gb = HECL::SBig(gbtexels[ry*4+rx]); + *g = gb >> 8 & 0xff; + *b = gb & 0xff; +} + +static void DecodeI4(png_structrp png, png_infop info, + const uint8_t* texels, int width, int height) +{ + png_set_IHDR(png, info, width, height, 4, + PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, info); + std::unique_ptr buf(new uint8_t[width]); + memset(buf.get(), 0, width); + for (int y=0 ; y buf(new uint8_t[width]); + for (int y=0 ; y buf(new uint8_t[width]); + for (int y=0 ; y buf(new uint16_t[width]); + for (int y=0 ; y> 11 & 0x1f); + cEntries[e].green = Convert6To8(texel >> 5 & 0x3f); + cEntries[e].blue = Convert5To8(texel & 0x1f); + } + break; + } + case 2: + { + /* RGB5A3 */ + const uint16_t* data16 = (uint16_t*)data; + for (int e=0 ; e> 10 & 0x1f); + cEntries[e].green = Convert5To8(texel >> 5 & 0x1f); + cEntries[e].blue = Convert5To8(texel & 0x1f); + aEntries[e] = 0xff; + } + else + { + cEntries[e].red = Convert4To8(texel >> 8 & 0xf); + cEntries[e].green = Convert4To8(texel >> 4 & 0xf); + cEntries[e].blue = Convert4To8(texel & 0xf); + aEntries[e] = Convert3To8(texel >> 12 & 0x7); + } + } + break; + } + } + png_set_PLTE(png, info, cEntries, numEntries); + if (format == 0 || format == 2) + png_set_tRNS(png, info, aEntries, numEntries, nullptr); + data += numEntries * 2; + return data; +} + +static void DecodeC4(png_structrp png, png_infop info, + const uint8_t* data, int width, int height) +{ + png_set_IHDR(png, info, width, height, 4, + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + const uint8_t* texels = DecodePalette(png, info, 16, data); + png_write_info(png, info); + std::unique_ptr buf(new uint8_t[width]); + memset(buf.get(), 0, width); + for (int y=0 ; y buf(new uint8_t[width]); + for (int y=0 ; y buf(new uint8_t[width*3]); + for (int y=0 ; y> 11 & 0x1f); + buf[x*3+1] = Convert6To8(texel >> 5 & 0x3f); + buf[x*3+2] = Convert5To8(texel & 0x1f); + } + png_write_row(png, buf.get()); + } +} + +static void DecodeRGB5A3(png_structrp png, png_infop info, + const uint8_t* texels, int width, int height) +{ + png_set_IHDR(png, info, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, info); + std::unique_ptr buf(new uint8_t[width*4]); + for (int y=0 ; y> 10 & 0x1f); + buf[x*4+1] = Convert5To8(texel >> 5 & 0x1f); + buf[x*4+2] = Convert5To8(texel & 0x1f); + buf[x*4+3] = 0xff; + } + else + { + buf[x*4] = Convert4To8(texel >> 8 & 0xf); + buf[x*4+1] = Convert4To8(texel >> 4 & 0xf); + buf[x*4+2] = Convert4To8(texel & 0xf); + buf[x*4+3] = Convert3To8(texel >> 12 & 0x7); + } + } + png_write_row(png, buf.get()); + } +} + +static void DecodeRGBA8(png_structrp png, png_infop info, + const uint8_t* texels, int width, int height) +{ + png_set_IHDR(png, info, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, info); + std::unique_ptr buf(new uint8_t[width*4]); + for (int y=0 ; y buf(new uint32_t[width*8]); + uint32_t* bTargets[4] = { + buf.get(), + buf.get() + 4, + buf.get() + 4 * width, + buf.get() + 4 * width + 4 + }; + int bwidth = (width + 7) / 8; + for (int y=height/8-1 ; y>=0 ; --y) + { + const DXTBlock* blks = (DXTBlock*)(texels + 32 * bwidth * y); + for (int x=0 ; x=0 ; --r) + png_write_row(png, (png_bytep)(bTargets[0] + width * r)); + } +} + +static void PNGErr(png_structp png, png_const_charp msg) +{ + Log.report(LogVisor::Error, msg); +} + +static void PNGWarn(png_structp png, png_const_charp msg) +{ + Log.report(LogVisor::Warning, msg); +} + +bool TXTR::Extract(PAKEntryReadStream& rs, const HECL::ProjectPath& outPath) +{ + rs.setEndian(Athena::BigEndian); + uint32_t format = rs.readUint32(); + uint16_t width = rs.readUint16(); + uint16_t height = rs.readUint16(); + uint32_t numMips = rs.readUint32(); + + FILE* fp = HECL::Fopen(outPath.getAbsolutePath().c_str(), _S("wb")); + if (!fp) + { + Log.report(LogVisor::Error, + _S("Unable to open '%s' for writing"), + outPath.getAbsolutePath().c_str()); + return false; + } + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PNGErr, PNGWarn); + png_init_io(png, fp); + png_infop info = png_create_info_struct(png); + + switch (format) + { + case 0: + DecodeI4(png, info, rs.data() + 12, width, height); + break; + case 1: + DecodeI8(png, info, rs.data() + 12, width, height); + break; + case 2: + DecodeIA4(png, info, rs.data() + 12, width, height); + break; + case 3: + DecodeIA8(png, info, rs.data() + 12, width, height); + break; + case 4: + DecodeC4(png, info, rs.data() + 12, width, height); + break; + case 5: + DecodeC8(png, info, rs.data() + 12, width, height); + break; + case 7: + DecodeRGB565(png, info, rs.data() + 12, width, height); + break; + case 8: + DecodeRGB5A3(png, info, rs.data() + 12, width, height); + break; + case 9: + DecodeRGBA8(png, info, rs.data() + 12, width, height); + break; + case 10: + DecodeCMPR(png, info, rs.data() + 12, width, height); + break; + } + + png_write_end(png, info); + png_write_flush(png); + png_destroy_write_struct(&png, &info); + fclose(fp); +} + +bool TXTR::Cook(const HECL::ProjectPath& inPath, const HECL::ProjectPath& outPath) +{ + +} + +} diff --git a/DataSpec/DNACommon/TXTR.hpp b/DataSpec/DNACommon/TXTR.hpp new file mode 100644 index 000000000..29f031071 --- /dev/null +++ b/DataSpec/DNACommon/TXTR.hpp @@ -0,0 +1,17 @@ +#ifndef __DNACOMMON_TXTR_HPP__ +#define __DNACOMMON_TXTR_HPP__ + +#include "DNACommon.hpp" + +namespace Retro +{ + +struct TXTR +{ + static bool Extract(PAKEntryReadStream& rs, const HECL::ProjectPath& outPath); + static bool Cook(const HECL::ProjectPath& inPath, const HECL::ProjectPath& outPath); +}; + +} + +#endif // __DNACOMMON_TXTR_HPP__ diff --git a/DataSpec/DNAMP1/CMakeLists.txt b/DataSpec/DNAMP1/CMakeLists.txt index 591e6c8c9..6214e755f 100644 --- a/DataSpec/DNAMP1/CMakeLists.txt +++ b/DataSpec/DNAMP1/CMakeLists.txt @@ -3,8 +3,7 @@ make_dnalist(liblist MLVL STRG) add_library(DNAMP1 - DNAMP1.hpp - DNAMP1.cpp + DNAMP1.hpp DNAMP1.cpp ${liblist} PAK.cpp STRG.cpp) diff --git a/DataSpec/DNAMP1/DNAMP1.cpp b/DataSpec/DNAMP1/DNAMP1.cpp index 0a44fb0f1..dffaf7a24 100644 --- a/DataSpec/DNAMP1/DNAMP1.cpp +++ b/DataSpec/DNAMP1/DNAMP1.cpp @@ -4,6 +4,7 @@ #include "DNAMP1.hpp" #include "STRG.hpp" #include "MLVL.hpp" +#include "../DNACommon/TXTR.hpp" namespace Retro { @@ -18,9 +19,9 @@ PAKBridge::PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPar m_pak.read(rs); } -std::string PAKBridge::getLevelString() const +HECL::SystemString PAKBridge::getLevelString() const { - std::string retval; + HECL::SystemString retval; for (const PAK::Entry& entry : m_pak.m_entries) { if (entry.type == Retro::MLVL) @@ -36,7 +37,7 @@ std::string PAKBridge::getLevelString() const mlvlName.read(rs); if (retval.size()) retval += _S(", "); - retval += mlvlName.getUTF8(ENGL, 0); + retval += mlvlName.getSystemString(ENGL, 0); } } } @@ -45,8 +46,13 @@ std::string PAKBridge::getLevelString() const ResExtractor PAKBridge::LookupExtractor(const PAK::Entry& entry) { - if (entry.type == Retro::STRG) + switch (entry.type.toUint32()) + { + case SBIG('STRG'): return {STRG::Extract, ".as"}; + case SBIG('TXTR'): + return {TXTR::Extract, ".png"}; + } return {}; } diff --git a/DataSpec/DNAMP1/DNAMP1.hpp b/DataSpec/DNAMP1/DNAMP1.hpp index 45c61c3f8..30f20f36b 100644 --- a/DataSpec/DNAMP1/DNAMP1.hpp +++ b/DataSpec/DNAMP1/DNAMP1.hpp @@ -21,7 +21,7 @@ class PAKBridge public: PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPartition::Node& node); const std::string& getName() const {return m_node.getName();} - std::string getLevelString() const; + HECL::SystemString getLevelString() const; bool extractResources(const HECL::ProjectPath& dirOut, const HECL::ProjectPath& cookedOut, bool force); diff --git a/DataSpec/DNAMP2/CMakeLists.txt b/DataSpec/DNAMP2/CMakeLists.txt index 2cbf0709b..40ab93d00 100644 --- a/DataSpec/DNAMP2/CMakeLists.txt +++ b/DataSpec/DNAMP2/CMakeLists.txt @@ -2,7 +2,6 @@ make_dnalist(liblist MLVL STRG) add_library(DNAMP2 - DNAMP2.hpp - DNAMP2.cpp + DNAMP2.hpp DNAMP2.cpp ${liblist} STRG.cpp) diff --git a/DataSpec/DNAMP2/DNAMP2.cpp b/DataSpec/DNAMP2/DNAMP2.cpp index 5d9f19f30..dea2bde68 100644 --- a/DataSpec/DNAMP2/DNAMP2.cpp +++ b/DataSpec/DNAMP2/DNAMP2.cpp @@ -2,6 +2,7 @@ #include "DNAMP2.hpp" #include "STRG.hpp" #include "MLVL.hpp" +#include "../DNACommon/TXTR.hpp" namespace Retro { @@ -17,9 +18,9 @@ PAKBridge::PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPar m_pak.read(rs); } -std::string PAKBridge::getLevelString() const +HECL::SystemString PAKBridge::getLevelString() const { - std::string retval; + HECL::SystemString retval; for (const DNAMP1::PAK::Entry& entry : m_pak.m_entries) { if (entry.type == Retro::MLVL) @@ -35,7 +36,7 @@ std::string PAKBridge::getLevelString() const mlvlName.read(rs); if (retval.size()) retval += _S(", "); - retval += mlvlName.getUTF8(ENGL, 0); + retval += mlvlName.getSystemString(ENGL, 0); } } } @@ -44,8 +45,13 @@ std::string PAKBridge::getLevelString() const ResExtractor PAKBridge::LookupExtractor(const DNAMP1::PAK::Entry& entry) { - if (entry.type == Retro::STRG) + switch (entry.type.toUint32()) + { + case SBIG('STRG'): return {STRG::Extract, ".as"}; + case SBIG('TXTR'): + return {TXTR::Extract, ".png"}; + } return {}; } diff --git a/DataSpec/DNAMP2/DNAMP2.hpp b/DataSpec/DNAMP2/DNAMP2.hpp index a175f7833..bd294c67e 100644 --- a/DataSpec/DNAMP2/DNAMP2.hpp +++ b/DataSpec/DNAMP2/DNAMP2.hpp @@ -21,7 +21,7 @@ class PAKBridge public: PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPartition::Node& node); const std::string& getName() const {return m_node.getName();} - std::string getLevelString() const; + HECL::SystemString getLevelString() const; bool extractResources(const HECL::ProjectPath& dirOut, const HECL::ProjectPath& cookedOut, bool force); diff --git a/DataSpec/DNAMP3/CMakeLists.txt b/DataSpec/DNAMP3/CMakeLists.txt index 90bff379e..027b62f61 100644 --- a/DataSpec/DNAMP3/CMakeLists.txt +++ b/DataSpec/DNAMP3/CMakeLists.txt @@ -3,8 +3,7 @@ make_dnalist(liblist MLVL STRG) add_library(DNAMP3 - DNAMP3.hpp - DNAMP3.cpp + DNAMP3.hpp DNAMP3.cpp ${liblist} PAK.cpp STRG.cpp) diff --git a/DataSpec/DNAMP3/DNAMP3.cpp b/DataSpec/DNAMP3/DNAMP3.cpp index c5e4999ba..dfe326c64 100644 --- a/DataSpec/DNAMP3/DNAMP3.cpp +++ b/DataSpec/DNAMP3/DNAMP3.cpp @@ -4,6 +4,7 @@ #include "DNAMP3.hpp" #include "STRG.hpp" #include "MLVL.hpp" +#include "../DNACommon/TXTR.hpp" namespace Retro { @@ -19,9 +20,9 @@ PAKBridge::PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPar m_pak.read(rs); } -std::string PAKBridge::getLevelString() const +HECL::SystemString PAKBridge::getLevelString() const { - std::set uniq; + std::set uniq; for (const PAK::Entry& entry : m_pak.m_entries) { if (entry.type == Retro::MLVL) @@ -35,13 +36,13 @@ std::string PAKBridge::getLevelString() const PAKEntryReadStream rs = nameEnt->beginReadStream(m_node); STRG mlvlName; mlvlName.read(rs); - uniq.insert(mlvlName.getUTF8(ENGL, 0)); + uniq.insert(mlvlName.getSystemString(ENGL, 0)); } } } - std::string retval; + HECL::SystemString retval; bool comma = false; - for (const std::string& str : uniq) + for (const HECL::SystemString& str : uniq) { if (comma) retval += _S(", "); @@ -53,8 +54,13 @@ std::string PAKBridge::getLevelString() const ResExtractor PAKBridge::LookupExtractor(const PAK::Entry& entry) { - if (entry.type == Retro::STRG) + switch (entry.type.toUint32()) + { + case SBIG('STRG'): return {STRG::Extract, ".as"}; + case SBIG('TXTR'): + return {TXTR::Extract, ".png"}; + } return {}; } diff --git a/DataSpec/DNAMP3/DNAMP3.hpp b/DataSpec/DNAMP3/DNAMP3.hpp index c721dd437..ad57d1118 100644 --- a/DataSpec/DNAMP3/DNAMP3.hpp +++ b/DataSpec/DNAMP3/DNAMP3.hpp @@ -21,7 +21,7 @@ class PAKBridge public: PAKBridge(HECL::Database::Project& project, const NOD::DiscBase::IPartition::Node& node); const std::string& getName() const {return m_node.getName();} - std::string getLevelString() const; + HECL::SystemString getLevelString() const; bool extractResources(const HECL::ProjectPath& dirOut, const HECL::ProjectPath& cookedOut, bool force);