Files
AC6_recomp/src/ac6_pac_index.cpp
T
salh 64f8efbb2b Restore AC6 mode-1 decoder dump hook via midasm at 0x821CCC5C
Recovers the Apr 23 hand-edit (lost in subsequent refactors) and re-wires
it as a TOML midasm hook so codegen regeneration can no longer drop it.
Hook fires immediately after the guest decompressor (bl 0x822CF510)
returns, reads the entry record via r11 (codec at +1, csize at +8,
usize at +12), source offset from *(r31+22888), entry tag from r10, and
calls Ac6DumpPacDecodedEntry with the decoded buffer at r4. With
AC6_DUMP_PAC_DECODED=1, all 800 compressed entries now drop as
FHM-magic'd entry_*_mode1_*.bin in out/ac6_pac_runtime_dump/.

Also adds streamer-worker dispatch probes (AC6_TRACE_PAC_WORK_ITEMS),
PPC stack walking on PAC reads (AC6_TRACE_PAC_STACKS), the AC6 PAC
index parser, the chunk-coalescing dump fallback, and a user-facing
walkthrough at docs/ac6_asset_extraction_walkthrough.txt.
2026-05-01 21:22:14 +03:00

153 lines
4.3 KiB
C++

#include "ac6_pac_index.h"
#include <algorithm>
#include <cctype>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <rex/logging.h>
namespace ac6_pac_index {
namespace {
constexpr size_t kHeaderSize = 8;
constexpr size_t kEntrySize = 16;
constexpr uint32_t kGroupBitData01 = 0x01000000u;
constexpr uint32_t kGroupBitRaw = 0x00020000u;
uint32_t ReadBE32(const uint8_t* p) {
return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) | uint32_t(p[3]);
}
struct State {
std::mutex mutex;
bool loaded = false;
std::vector<Entry> entries;
// Key: archive (false=DATA00, true=DATA01) packed with offset and csize.
std::unordered_map<uint64_t, uint32_t> by_offset_csize;
};
State& Get() {
static State state;
return state;
}
uint64_t MakeKey(bool is_data01, uint32_t offset, uint32_t csize) {
return (uint64_t(is_data01 ? 1 : 0) << 63) | (uint64_t(offset) << 32) | uint64_t(csize);
}
bool AsciiContainsCi(std::string_view haystack, std::string_view needle) {
auto eq = [](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
};
return std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), eq) !=
haystack.end();
}
} // namespace
bool LoadFromBuffer(const uint8_t* data, size_t size) {
if (!data || size < kHeaderSize) {
return false;
}
const uint32_t count = ReadBE32(data);
const size_t expected = kHeaderSize + (size_t(count) * kEntrySize);
if (size != expected) {
return false;
}
std::vector<Entry> entries;
entries.reserve(count);
std::unordered_map<uint64_t, uint32_t> by_key;
by_key.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
const uint8_t* p = data + kHeaderSize + (size_t(i) * kEntrySize);
Entry e;
e.index = i;
e.group = ReadBE32(p + 0);
e.offset = ReadBE32(p + 4);
e.compressed_size = ReadBE32(p + 8);
e.decompressed_size = ReadBE32(p + 12);
e.is_data01 = (e.group & kGroupBitData01) != 0;
e.storage_kind = (e.group & kGroupBitRaw) ? StorageKind::kRaw : StorageKind::kCompressed;
entries.push_back(e);
if (e.compressed_size > 0) {
by_key.emplace(MakeKey(e.is_data01, e.offset, e.compressed_size), i);
}
}
auto& s = Get();
std::scoped_lock lock(s.mutex);
s.entries = std::move(entries);
s.by_offset_csize = std::move(by_key);
s.loaded = true;
REXFS_INFO("[AC6 PAC] DATA.TBL parsed: {} entries", s.entries.size());
return true;
}
bool IsLoaded() {
auto& s = Get();
std::scoped_lock lock(s.mutex);
return s.loaded;
}
std::optional<Entry> Find(bool is_data01, uint32_t offset, uint32_t compressed_size) {
auto& s = Get();
std::scoped_lock lock(s.mutex);
if (!s.loaded) {
return std::nullopt;
}
auto it = s.by_offset_csize.find(MakeKey(is_data01, offset, compressed_size));
if (it == s.by_offset_csize.end()) {
return std::nullopt;
}
return s.entries[it->second];
}
bool ClassifyPacPath(std::string_view path, bool* is_data01) {
if (AsciiContainsCi(path, "DATA01.PAC")) {
if (is_data01) *is_data01 = true;
return true;
}
if (AsciiContainsCi(path, "DATA00.PAC")) {
if (is_data01) *is_data01 = false;
return true;
}
return false;
}
bool IsDataTblPath(std::string_view path) {
return AsciiContainsCi(path, "DATA.TBL");
}
std::vector<uint32_t> FindOverlapping(bool is_data01, uint32_t range_begin, uint32_t range_end) {
std::vector<uint32_t> hits;
if (range_end <= range_begin) return hits;
auto& s = Get();
std::scoped_lock lock(s.mutex);
if (!s.loaded) return hits;
for (const auto& e : s.entries) {
if (e.is_data01 != is_data01 || e.compressed_size == 0) continue;
const uint32_t e_begin = e.offset;
const uint32_t e_end = e.offset + e.compressed_size;
if (e_begin < range_end && range_begin < e_end) {
hits.push_back(e.index);
}
}
return hits;
}
std::optional<Entry> GetByIndex(uint32_t entry_index) {
auto& s = Get();
std::scoped_lock lock(s.mutex);
if (!s.loaded || entry_index >= s.entries.size()) return std::nullopt;
return s.entries[entry_index];
}
} // namespace ac6_pac_index