2023-11-19 21:28:06 -05:00
|
|
|
|
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
#include <base/ByteSwap.hpp>
|
2023-11-19 21:28:06 -05:00
|
|
|
#include <base/MmapFile.hpp>
|
|
|
|
#include <bolt/Reader.hpp>
|
|
|
|
#include <structs/BoltStructs.hpp>
|
|
|
|
|
|
|
|
namespace lightningbolt {
|
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-19 21:28:06 -05:00
|
|
|
struct BoltReader::Impl {
|
2023-11-21 21:13:07 -05:00
|
|
|
Impl(Game game) : game(game) { std::printf("game %04x\n", game); }
|
2023-11-19 21:28:06 -05:00
|
|
|
|
|
|
|
ErrorOr<void> OpenBolt(const fs::path& path) {
|
|
|
|
// Load the BOLT file
|
2023-11-21 21:13:07 -05:00
|
|
|
if(auto error = boltFile.Open(path / MakeGamePath(game)); error.HasError())
|
2023-11-19 21:28:06 -05:00
|
|
|
return error;
|
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
std::printf("mewow\n");
|
|
|
|
|
2023-11-19 21:28:06 -05:00
|
|
|
lib = std::bit_cast<BoltLibraryHeader*>(boltFile.GetMapping());
|
|
|
|
|
|
|
|
if(!lib->Validate())
|
|
|
|
return std::make_error_code(BoltErrc::InvalidMagic);
|
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
if(game == Game::SimpsonsSkateboarding) {
|
2023-11-21 21:13:07 -05:00
|
|
|
if(auto error = elfFile.Open(path / "SLUS_201.14"); error.HasError())
|
2023-11-21 20:33:26 -05:00
|
|
|
return error;
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
// Load table entries.
|
|
|
|
GetTableEntries();
|
|
|
|
}
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
std::printf("ret\n");
|
|
|
|
|
2023-11-19 21:28:06 -05:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<BoltReader::File>& GetTableEntries() {
|
2023-11-21 20:33:26 -05:00
|
|
|
if(game == Game::SimpsonsSkateboarding) {
|
|
|
|
if(entryTable.empty()) {
|
|
|
|
auto* base = elfFile.GetMapping();
|
|
|
|
auto* table = std::bit_cast<elf::BoltTableEntry*>(base + elf::BoltTableOffsets.usTable);
|
|
|
|
while(table->filenamePtr != 0x0) {
|
|
|
|
auto string_offset = elf::AddressToElfFileOffset(table->filenamePtr);
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
BoltReader::File te;
|
|
|
|
te.filename = { std::bit_cast<char*>(base + string_offset) };
|
|
|
|
te.index = table->entryId;
|
|
|
|
te.gid = table->groupId;
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
if(te.filename == "")
|
|
|
|
break;
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
entryTable.emplace_back(te);
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
table++;
|
|
|
|
}
|
2023-11-19 21:28:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return entryTable;
|
|
|
|
}
|
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
template <class T>
|
|
|
|
auto SwapIfGcn(T v) {
|
|
|
|
return (game == Game::NamcoMuseumGCN) ? ByteSwap(v) : v;
|
|
|
|
}
|
|
|
|
|
2023-11-19 21:28:06 -05:00
|
|
|
template <class F>
|
|
|
|
void ForEachFile(F f) {
|
2023-11-21 21:13:07 -05:00
|
|
|
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;
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
file.compressed = !(entries[file.index & 0x00ff].unk & 0x8);
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
file.uncompressedData = std::bit_cast<u8*>(boltFile.GetMapping() + offset);
|
|
|
|
file.uncompressedSize = size;
|
|
|
|
}
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
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];
|
|
|
|
|
|
|
|
// We can't use the helper since it doesn't know about GCN byteswapping.
|
|
|
|
// FIXME/TODO?
|
|
|
|
std::span<BoltGroupEntry> groupEntries = {
|
|
|
|
std::bit_cast<BoltGroupEntry*>(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<u8*>(boltFile.GetMapping() + SwapIfGcn(ent.fileOffset));
|
|
|
|
|
|
|
|
if(!f(file))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2023-11-19 21:28:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-11-21 20:33:26 -05:00
|
|
|
Game game;
|
2023-11-21 21:13:07 -05:00
|
|
|
MmapFile boltFile;
|
|
|
|
|
|
|
|
// Only applicable if game == Game::SimpsonsSkateboarding
|
2023-11-19 21:28:06 -05:00
|
|
|
std::vector<BoltReader::File> entryTable;
|
2023-11-21 20:30:37 -05:00
|
|
|
MmapFile elfFile;
|
2023-11-19 21:28:06 -05:00
|
|
|
|
2023-11-21 20:30:37 -05:00
|
|
|
BoltLibraryHeader* lib;
|
2023-11-19 21:28:06 -05:00
|
|
|
};
|
|
|
|
|
2023-11-21 21:13:07 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-11-21 20:33:26 -05:00
|
|
|
BoltReader::BoltReader(Game game) : impl(std::make_unique<Impl>(game)) {
|
|
|
|
}
|
2023-11-19 21:28:06 -05:00
|
|
|
|
|
|
|
BoltReader::~BoltReader() = default;
|
|
|
|
|
|
|
|
ErrorOr<void> BoltReader::OpenBolt(const fs::path& path) {
|
|
|
|
return impl->OpenBolt(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BoltReader::ForEachFile(std::function<bool(File&)> f) {
|
2023-11-21 20:33:26 -05:00
|
|
|
impl->ForEachFile([&f](auto& file) { return f(file); });
|
2023-11-19 21:28:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace lightningbolt
|