diff --git a/lib/base/ByteSwap.hpp b/lib/base/ByteSwap.hpp new file mode 100644 index 0000000..6146c5f --- /dev/null +++ b/lib/base/ByteSwap.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include + +namespace lightningbolt { + namespace detail { + + template + constexpr T PunCast(U v) { + return *std::bit_cast(&v); + } + + template + constexpr T ByteSwap(T v) { + if constexpr(sizeof(T) == 2) + return static_cast(__builtin_bswap16(PunCast(v))); + else if constexpr(sizeof(T) == 4) + return static_cast(__builtin_bswap32(PunCast(v))); + else if constexpr(sizeof(T) == 8) + return static_cast(__builtin_bswap64(PunCast(v))); + + return v; + } + + } // namespace detail + + using detail::ByteSwap; +} // namespace lightningbolt diff --git a/lib/bolt/Reader.cpp b/lib/bolt/Reader.cpp index 154b3c5..2c5b39a 100644 --- a/lib/bolt/Reader.cpp +++ b/lib/bolt/Reader.cpp @@ -1,35 +1,48 @@ +#include #include #include #include namespace lightningbolt { + fs::path MakeGamePath(BoltReader::Game game) { + switch(game) { + case BoltReader::Game::LooseBolt: + return "./"; // for now + break; + + case BoltReader::Game::SimpsonsSkateboarding: return "ASSETS.BLT"; break; + case BoltReader::Game::NamcoMuseumGCN: return "Data0.blt"; break; + } + } + struct BoltReader::Impl { - Impl(Game game) : game(game) {} + Impl(Game game) : game(game) { std::printf("game %04x\n", game); } ErrorOr OpenBolt(const fs::path& path) { // Load the BOLT file - if(auto error = boltFile.Open(path); error.HasError()) + if(auto error = boltFile.Open(path / MakeGamePath(game)); error.HasError()) return error; + std::printf("mewow\n"); + lib = std::bit_cast(boltFile.GetMapping()); if(!lib->Validate()) return std::make_error_code(BoltErrc::InvalidMagic); if(game == Game::SimpsonsSkateboarding) { - auto p = path; - p.replace_filename("SLUS_201.14"); - - if(auto error = elfFile.Open(p); error.HasError()) + if(auto error = elfFile.Open(path / "SLUS_201.14"); error.HasError()) return error; // Load table entries. GetTableEntries(); } + std::printf("ret\n"); + return {}; } @@ -58,35 +71,90 @@ namespace lightningbolt { return entryTable; } + template + auto SwapIfGcn(T v) { + return (game == Game::NamcoMuseumGCN) ? ByteSwap(v) : v; + } + template void ForEachFile(F f) { - for(auto& file : entryTable) { - if(file.uncompressedData == nullptr) { - auto gid = (file.gid >> 8); - auto entries = lib->GroupDescriptors()[gid].Entries(boltFile.GetMapping()); - auto size = entries[file.index & 0x00ff].fileSize; - auto offset = entries[file.index & 0x00ff].fileOffset; + if(game == Game::SimpsonsSkateboarding) { + for(auto& file : entryTable) { + if(file.uncompressedData == nullptr) { + auto gid = (file.gid >> 8); + auto entries = lib->GroupDescriptors()[gid].Entries(boltFile.GetMapping()); + auto size = entries[file.index & 0x00ff].fileSize; + auto offset = entries[file.index & 0x00ff].fileOffset; - file.compressed = !(entries[file.index & 0x00ff].unk & 0x8); + file.compressed = !(entries[file.index & 0x00ff].unk & 0x8); - file.uncompressedData = std::bit_cast(boltFile.GetMapping() + offset); - file.uncompressedSize = size; + file.uncompressedData = std::bit_cast(boltFile.GetMapping() + offset); + file.uncompressedSize = size; + } + + if(!f(file)) + break; } + } else { + // generic "We don't have a table" implementation. You don't get nice filenames like this, + // simply because we cannot create them (.. maybe, if the unk3 is a hash, we could crack it, + // but that would bloat extraction times to potentionally hours if not days, and also might + // end up with wrong filenames at the end anyways due to the hash algorithm possibly being weak) + // + // We still end up synthesizing a File object for the user to interact with anyways. + + for(u32 i = 0; i < lib->groupCount; ++i) { + auto groupDescriptor = lib->GroupDescriptors()[i]; - if(!f(file)) - break; + // We can't use the helper since it doesn't know about GCN byteswapping. + // FIXME/TODO? + std::span groupEntries = { + std::bit_cast(boltFile.GetMapping() + SwapIfGcn(groupDescriptor.groupOffset)), groupDescriptor.EntryCount() + }; + + for(u32 j = 0; j < groupEntries.size(); ++j) { + auto& ent = groupEntries[j]; + auto filename = std::format("blt_{:02x}_{:02x}.bin", i, j); + BoltReader::File file; + + file.gid = i; + file.index = j; + + // Maybe BANG! but this is only accessed here. + file.filename = filename; + + file.compressed = !(ent.unk & 0x8); + + file.uncompressedSize = SwapIfGcn(ent.fileSize); + file.uncompressedData = std::bit_cast(boltFile.GetMapping() + SwapIfGcn(ent.fileOffset)); + + if(!f(file)) + return; + } + } } } private: Game game; + MmapFile boltFile; + + // Only applicable if game == Game::SimpsonsSkateboarding std::vector entryTable; MmapFile elfFile; - MmapFile boltFile; BoltLibraryHeader* lib; }; + BoltReader::Game BoltReader::GuessGame(const fs::path& path) { + if(fs::exists(path / "SLUS_201.14") && fs::exists(path / "ASSETS.BLT")) + return Game::SimpsonsSkateboarding; + else if(fs::exists(path / "Data0.blt")) + return Game::NamcoMuseumGCN; + else + return Game::LooseBolt; + } + BoltReader::BoltReader(Game game) : impl(std::make_unique(game)) { } diff --git a/lib/bolt/Reader.hpp b/lib/bolt/Reader.hpp index df9dc85..9832f7b 100644 --- a/lib/bolt/Reader.hpp +++ b/lib/bolt/Reader.hpp @@ -9,9 +9,12 @@ namespace lightningbolt { enum class Game { LooseBolt, ///< Use this for games with no bolt entry SimpsonsSkateboarding, - NamcoMuseumGCN = LooseBolt + NamcoMuseumGCN }; + // Guess the game the user wants to extract + static Game GuessGame(const fs::path& path); + struct File { std::string_view filename; u16 index; diff --git a/lib/structs/BoltStructs.hpp b/lib/structs/BoltStructs.hpp index 508e4a1..ada50ce 100644 --- a/lib/structs/BoltStructs.hpp +++ b/lib/structs/BoltStructs.hpp @@ -67,8 +67,10 @@ namespace lightningbolt { }; struct [[gnu::packed]] BoltLibraryHeader { - static constexpr char VALID_MAGIC[] = "BOLT\r\n"; - char magic[6]; + static constexpr char VALID_MAGIC[] = "BOLT"; + char magic[4]; + + u8 unkmaybemagic[2]; // could be platform flags ? u8 unk; u8 unk2; diff --git a/src/main.cpp b/src/main.cpp index 845a52f..167b424 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,13 +24,16 @@ fs::path ConvertBoltPath(const std::string_view filename) { } int main() { - lightningbolt::BoltReader reader; - if(auto error = reader.OpenBolt(fs::current_path() / "ASSETS.BLT"); error.HasError()) { + auto path = fs::current_path(); + + lightningbolt::BoltReader reader{lightningbolt::BoltReader::GuessGame(path)}; + if(auto error = reader.OpenBolt(path); error.HasError()) { std::cout << std::format("Error initalizing bolt reader: {}\n", error.Error()); } reader.ForEachFile([](lightningbolt::BoltReader::File& ent) { std::cout << std::format("File: {}", ent.filename); +#if 1 auto p = ConvertBoltPath(ent.filename); @@ -64,6 +67,7 @@ int main() { ofs.write((char*)ent.uncompressedData, ent.uncompressedSize); std::cout << std::format(", written to \"{}\"\n", p.string()); } +#endif return true; });