Compare commits
No commits in common. "094f9a66f2a787d8baa3e9bc5063a032615e0df9" and "3bcc4d242f0b2569ca6b646f8ca37fbb711e07c4" have entirely different histories.
094f9a66f2
...
3bcc4d242f
|
@ -1,28 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <bit>
|
|
||||||
#include <base/Types.hpp>
|
|
||||||
|
|
||||||
namespace lightningbolt {
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
template <class T, class U>
|
|
||||||
constexpr T PunCast(U v) {
|
|
||||||
return *std::bit_cast<const T*>(&v);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
constexpr T ByteSwap(T v) {
|
|
||||||
if constexpr(sizeof(T) == 2)
|
|
||||||
return static_cast<T>(__builtin_bswap16(PunCast<u16>(v)));
|
|
||||||
else if constexpr(sizeof(T) == 4)
|
|
||||||
return static_cast<T>(__builtin_bswap32(PunCast<u32>(v)));
|
|
||||||
else if constexpr(sizeof(T) == 8)
|
|
||||||
return static_cast<T>(__builtin_bswap64(PunCast<u64>(v)));
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
using detail::ByteSwap;
|
|
||||||
} // namespace lightningbolt
|
|
|
@ -1,53 +1,42 @@
|
||||||
|
|
||||||
|
|
||||||
#include <base/ByteSwap.hpp>
|
|
||||||
#include <base/MmapFile.hpp>
|
#include <base/MmapFile.hpp>
|
||||||
#include <bolt/Reader.hpp>
|
#include <bolt/Reader.hpp>
|
||||||
|
|
||||||
#include <structs/BoltStructs.hpp>
|
#include <structs/BoltStructs.hpp>
|
||||||
|
#include "base/ErrorOr.hpp"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace lightningbolt {
|
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 {
|
struct BoltReader::Impl {
|
||||||
Impl(Game game) : game(game) { std::printf("game %04x\n", game); }
|
|
||||||
|
|
||||||
ErrorOr<void> OpenBolt(const fs::path& path) {
|
ErrorOr<void> OpenBolt(const fs::path& path) {
|
||||||
// Load the BOLT file
|
// Load the BOLT file
|
||||||
if(auto error = boltFile.Open(path / MakeGamePath(game)); error.HasError())
|
if(auto error = boltFile.Open(path); error.HasError())
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
std::printf("mewow\n");
|
|
||||||
|
|
||||||
lib = std::bit_cast<BoltLibraryHeader*>(boltFile.GetMapping());
|
lib = std::bit_cast<BoltLibraryHeader*>(boltFile.GetMapping());
|
||||||
|
|
||||||
if(!lib->Validate())
|
if(!lib->Validate())
|
||||||
return std::make_error_code(BoltErrc::InvalidMagic);
|
return std::make_error_code(BoltErrc::InvalidMagic);
|
||||||
|
|
||||||
if(game == Game::SimpsonsSkateboarding) {
|
auto p = path;
|
||||||
if(auto error = elfFile.Open(path / "SLUS_201.14"); error.HasError())
|
p.replace_filename("SLUS_201.14");
|
||||||
|
|
||||||
|
if(auto error = elfFile.Open(p); error.HasError())
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
// Load table entries.
|
// Load table entries.
|
||||||
GetTableEntries();
|
GetTableEntries();
|
||||||
}
|
|
||||||
|
|
||||||
std::printf("ret\n");
|
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<BoltReader::File>& GetTableEntries() {
|
const std::vector<BoltReader::File>& GetTableEntries() {
|
||||||
if(game == Game::SimpsonsSkateboarding) {
|
|
||||||
if(entryTable.empty()) {
|
if(entryTable.empty()) {
|
||||||
auto* base = elfFile.GetMapping();
|
auto* base = elfFile.GetMapping();
|
||||||
auto* table = std::bit_cast<elf::BoltTableEntry*>(base + elf::BoltTableOffsets.usTable);
|
auto* table = std::bit_cast<elf::BoltTableEntry*>(base + elf::BoltTableOffsets.usTable);
|
||||||
|
@ -67,18 +56,11 @@ namespace lightningbolt {
|
||||||
table++;
|
table++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return entryTable;
|
return entryTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
|
||||||
auto SwapIfGcn(T v) {
|
|
||||||
return (game == Game::NamcoMuseumGCN) ? ByteSwap(v) : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class F>
|
template <class F>
|
||||||
void ForEachFile(F f) {
|
void ForEachFile(F f) {
|
||||||
if(game == Game::SimpsonsSkateboarding) {
|
|
||||||
for(auto& file : entryTable) {
|
for(auto& file : entryTable) {
|
||||||
if(file.uncompressedData == nullptr) {
|
if(file.uncompressedData == nullptr) {
|
||||||
auto gid = (file.gid >> 8);
|
auto gid = (file.gid >> 8);
|
||||||
|
@ -95,68 +77,18 @@ namespace lightningbolt {
|
||||||
if(!f(file))
|
if(!f(file))
|
||||||
break;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Game game;
|
|
||||||
MmapFile boltFile;
|
|
||||||
|
|
||||||
// Only applicable if game == Game::SimpsonsSkateboarding
|
|
||||||
std::vector<BoltReader::File> entryTable;
|
std::vector<BoltReader::File> entryTable;
|
||||||
MmapFile elfFile;
|
lightningbolt::MmapFile elfFile;
|
||||||
|
lightningbolt::MmapFile boltFile;
|
||||||
|
|
||||||
BoltLibraryHeader* lib;
|
lightningbolt::BoltLibraryHeader* lib;
|
||||||
};
|
};
|
||||||
|
|
||||||
BoltReader::Game BoltReader::GuessGame(const fs::path& path) {
|
BoltReader::BoltReader():
|
||||||
if(fs::exists(path / "SLUS_201.14") && fs::exists(path / "ASSETS.BLT"))
|
impl(std::make_unique<Impl>()) {}
|
||||||
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<Impl>(game)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BoltReader::~BoltReader() = default;
|
BoltReader::~BoltReader() = default;
|
||||||
|
|
||||||
|
@ -165,7 +97,9 @@ namespace lightningbolt {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoltReader::ForEachFile(std::function<bool(File&)> f) {
|
void BoltReader::ForEachFile(std::function<bool(File&)> f) {
|
||||||
impl->ForEachFile([&f](auto& file) { return f(file); });
|
impl->ForEachFile([&f](auto& file) {
|
||||||
|
return f(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace lightningbolt
|
} // namespace lightningbolt
|
||||||
|
|
|
@ -6,15 +6,6 @@
|
||||||
namespace lightningbolt {
|
namespace lightningbolt {
|
||||||
|
|
||||||
struct BoltReader {
|
struct BoltReader {
|
||||||
enum class Game {
|
|
||||||
LooseBolt, ///< Use this for games with no bolt entry
|
|
||||||
SimpsonsSkateboarding,
|
|
||||||
NamcoMuseumGCN
|
|
||||||
};
|
|
||||||
|
|
||||||
// Guess the game the user wants to extract
|
|
||||||
static Game GuessGame(const fs::path& path);
|
|
||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
std::string_view filename;
|
std::string_view filename;
|
||||||
u16 index;
|
u16 index;
|
||||||
|
@ -24,9 +15,10 @@ namespace lightningbolt {
|
||||||
|
|
||||||
u8* uncompressedData { nullptr };
|
u8* uncompressedData { nullptr };
|
||||||
usize uncompressedSize;
|
usize uncompressedSize;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BoltReader(Game game = Game::SimpsonsSkateboarding);
|
BoltReader();
|
||||||
~BoltReader();
|
~BoltReader();
|
||||||
|
|
||||||
ErrorOr<void> OpenBolt(const fs::path& path);
|
ErrorOr<void> OpenBolt(const fs::path& path);
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
namespace lightningbolt {
|
namespace lightningbolt {
|
||||||
|
|
||||||
/// Elf structures, only applicable to Simpsons Skateboarding
|
|
||||||
namespace elf {
|
namespace elf {
|
||||||
|
|
||||||
/// Table entry in the ELF. We use this to create file names.
|
/// Table entry in the ELF. We use this to create file names.
|
||||||
|
@ -67,10 +66,8 @@ namespace lightningbolt {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct [[gnu::packed]] BoltLibraryHeader {
|
struct [[gnu::packed]] BoltLibraryHeader {
|
||||||
static constexpr char VALID_MAGIC[] = "BOLT";
|
static constexpr char VALID_MAGIC[] = "BOLT\r\n";
|
||||||
char magic[4];
|
char magic[6];
|
||||||
|
|
||||||
u8 unkmaybemagic[2]; // could be platform flags ?
|
|
||||||
|
|
||||||
u8 unk;
|
u8 unk;
|
||||||
u8 unk2;
|
u8 unk2;
|
||||||
|
|
|
@ -24,16 +24,13 @@ fs::path ConvertBoltPath(const std::string_view filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto path = fs::current_path();
|
lightningbolt::BoltReader reader;
|
||||||
|
if(auto error = reader.OpenBolt(fs::current_path() / "ASSETS.BLT"); error.HasError()) {
|
||||||
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());
|
std::cout << std::format("Error initalizing bolt reader: {}\n", error.Error());
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.ForEachFile([](lightningbolt::BoltReader::File& ent) {
|
reader.ForEachFile([](lightningbolt::BoltReader::File& ent) {
|
||||||
std::cout << std::format("File: {}", ent.filename);
|
std::cout << std::format("File: {}", ent.filename);
|
||||||
#if 1
|
|
||||||
|
|
||||||
auto p = ConvertBoltPath(ent.filename);
|
auto p = ConvertBoltPath(ent.filename);
|
||||||
|
|
||||||
|
@ -67,7 +64,6 @@ int main() {
|
||||||
ofs.write((char*)ent.uncompressedData, ent.uncompressedSize);
|
ofs.write((char*)ent.uncompressedData, ent.uncompressedSize);
|
||||||
std::cout << std::format(", written to \"{}\"\n", p.string());
|
std::cout << std::format(", written to \"{}\"\n", p.string());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue