From 21d6c4effca98004ae5723f68ee158f6f58fcc22 Mon Sep 17 00:00:00 2001 From: qaate47 Date: Wed, 31 Jul 2024 20:07:55 +0200 Subject: [PATCH] load mwiii assets from Cordycep --- config/common_hashes/path.txt | 9 +- src/acts/hashutils.cpp | 2 +- src/acts/tools/hash.cpp | 2 +- src/acts/tools/hashes/compiled_files.cpp | 135 +++++ src/acts/tools/hashes/compiled_files.hpp | 13 + src/acts/tools/sp23/jup_dump.cpp | 686 +++++++++++++++++++++++ src/acts/tools/sp23/sp23.cpp | 267 +++++++++ src/acts/tools/sp23/sp23.hpp | 5 +- 8 files changed, 1115 insertions(+), 4 deletions(-) create mode 100644 src/acts/tools/hashes/compiled_files.cpp create mode 100644 src/acts/tools/hashes/compiled_files.hpp create mode 100644 src/acts/tools/sp23/jup_dump.cpp diff --git a/config/common_hashes/path.txt b/config/common_hashes/path.txt index e2f81eb..fccbe01 100644 --- a/config/common_hashes/path.txt +++ b/config/common_hashes/path.txt @@ -6554,4 +6554,11 @@ leaderboards/mp_leaderboards_reset.csv gamedata/tables/common/countrytable.csv scripts/core_common/encounters/wave_manager.gsc scripts/core_common/ai/systems/ai_interactables.gsc -scripts/core_common/ai/systems/ai_interactables.csc \ No newline at end of file +scripts/core_common/ai/systems/ai_interactables.csc +scripts/mp/gametypes/ob_season1.gsc +scripts/mp/gametypes/ob_season2.gsc +scripts/mp/gametypes/ob_season3.gsc +scripts/mp/gametypes/ob_season4.gsc +scripts/mp/gametypes/ob_season5.gsc +scripts/unittest/private.gsc +scripts/unittest/callback.gsc \ No newline at end of file diff --git a/src/acts/hashutils.cpp b/src/acts/hashutils.cpp index 49b9c6e..2573cae 100644 --- a/src/acts/hashutils.cpp +++ b/src/acts/hashutils.cpp @@ -283,7 +283,7 @@ char* hashutils::ExtractTmp(const char* type, uint64_t hash) { const char* hashutils::ExtractPtr(uint64_t hash) { ReadDefaultFile(); - const auto res = g_hashMap.find(hash); + const auto res = g_hashMap.find(hash & 0x7FFFFFFFFFFFFFFF); if (res == g_hashMap.end()) { return NULL; } diff --git a/src/acts/tools/hash.cpp b/src/acts/tools/hash.cpp index 8c3188c..e906106 100644 --- a/src/acts/tools/hash.cpp +++ b/src/acts/tools/hash.cpp @@ -6,7 +6,7 @@ namespace { int lookuptool(Process& proc, int argc, const char* argv[]) { hashutils::ReadDefaultFile(); for (int i = 2; i < argc; i++) { - int64_t v = strtoull(argv[i], nullptr, 0x10); + uint64_t v = strtoull(argv[i], nullptr, 0x10); const char* extract = hashutils::ExtractPtr(v); if (extract) { LOG_INFO("{:x}={}", v, extract); diff --git a/src/acts/tools/hashes/compiled_files.cpp b/src/acts/tools/hashes/compiled_files.cpp new file mode 100644 index 0000000..bc33d1a --- /dev/null +++ b/src/acts/tools/hashes/compiled_files.cpp @@ -0,0 +1,135 @@ +#include +#include "compiled_files.hpp" +#include + +namespace { + using namespace tool::hashes::compiled_files; + + int cfd(Process& proc, int argc, const char* argv[]) { + if (argc < 3) { + return tool::BAD_USAGE; + } + const char* input{ argv[2] }; + + const char* output{ argc < 4 ? "output_raw" : argv[3] }; + + std::vector files{}; + + utils::GetFileRecurse(input, files, [](const std::filesystem::path& p) { + auto s = p.string(); + return s.ends_with(".cf"); + }); + + hashutils::ReadDefaultFile(); + + std::string buff{}; + std::filesystem::path outputDir{ output }; + for (const std::filesystem::path& file : files) { + if (!utils::ReadFile(file, buff)) { + LOG_ERROR("Can't read file {}", file.string()); + continue; + } + + const CompiledFileHeader& header = *reinterpret_cast(buff.data()); + + if (header.magic != MAGIC) { + LOG_ERROR("Can't read file {}: invalid magic", file.string()); + continue; + } + + const char* n = hashutils::ExtractPtr(header.name); + + std::filesystem::path out; + if (n) { + // script bundle thing + n = utils::MapString(utils::CloneString(n), [](char c) -> char { return c == ':' ? '/' : c; }); + if (header.isSpecial) { + out = outputDir / header.type / n; + } else{ + out = outputDir / n; + } + } + else { + char* fileName; + if (*header.preferedExtension) { + fileName = utils::va("file_%llx.%s", header.name, header.preferedExtension); + } + else { + fileName = utils::va("file_%llx", header.name); + } + if (header.isSpecial) { + out = outputDir / header.type / fileName; + } + else { + if (*header.type) { + out = outputDir / "hashed" / header.type / fileName; + } + else { + out = outputDir / fileName; + } + } + } + + std::filesystem::create_directories(out.parent_path()); + + + if (header.isString) { + std::string strBuff{ buff.data() + sizeof(header), buff.size() - sizeof(header) }; + + static std::regex pattern{ "hash_([0-9a-fA-F]{1,16})" }; + + size_t idx{}; + while (idx < strBuff.size()) { + auto rbegin = std::sregex_iterator(strBuff.begin() + idx, strBuff.end(), pattern); + + if (rbegin == std::sregex_iterator()) { + break; // nothing else + } + + std::smatch match = *rbegin; + size_t mstart = match.position() + idx; + size_t mlen = match.length(); + + try { + uint64_t hash = std::stoull(match[1].str(), nullptr, 16); + const char* ptr = hashutils::ExtractPtr(hash); + if (ptr) { + std::string before = strBuff.substr(0, mstart); + std::string after = strBuff.substr(mstart + mlen, strBuff.size() - mstart - mlen); + + strBuff = before + ptr + after; + mlen = strlen(ptr); + LOG_TRACE("{} REPLACED {:x} -> {}", out.string(), hash, ptr); + } + } + catch (std::runtime_error& e) { + LOG_WARNING("Ignored bad hash: {}", e.what()); + } + //LOG_TRACE("{} old {} -> {}", idx, mstart + mlen, out.string()); + idx = mstart + mlen; + } + + + if (!utils::WriteFile(out, strBuff.data(), strBuff.size())) { + LOG_ERROR("Can't write file {}", out.string()); + } + else { + LOG_INFO("Write {} -> {}", file.string(), out.string()); + } + } + else { + if (!utils::WriteFile(out, buff.data() + sizeof(header), buff.size() - sizeof(header))) { + LOG_ERROR("Can't write file {}", out.string()); + } + else { + LOG_INFO("Write {} -> {}", file.string(), out.string()); + } + } + + } + return tool::OK; + } + + ADD_TOOL("cfd", "hash", " [dir] (output)", "decompile raw compiled file (.cf)", nullptr, cfd); + +} \ No newline at end of file diff --git a/src/acts/tools/hashes/compiled_files.hpp b/src/acts/tools/hashes/compiled_files.hpp new file mode 100644 index 0000000..e4e7bde --- /dev/null +++ b/src/acts/tools/hashes/compiled_files.hpp @@ -0,0 +1,13 @@ +#pragma once +namespace tool::hashes::compiled_files { + constexpr uint64_t MAGIC = 0x123456ACCF10; + + struct CompiledFileHeader { + uint64_t magic{ 0x123456ACCF10 }; + uint64_t name; + uint8_t isString{}; + uint8_t isSpecial{}; + char type[0x10]{ 0 }; + char preferedExtension[0x10]{ 0 }; + }; +} \ No newline at end of file diff --git a/src/acts/tools/sp23/jup_dump.cpp b/src/acts/tools/sp23/jup_dump.cpp new file mode 100644 index 0000000..3114b43 --- /dev/null +++ b/src/acts/tools/sp23/jup_dump.cpp @@ -0,0 +1,686 @@ +#include +#include "sp23.hpp" +#include "tools/hashes/compiled_files.hpp" + +namespace sp23::dump { + + struct CordycepProc { + uint64_t gameId; + uintptr_t poolsAddress; + uintptr_t stringsAddress; + std::string gameDir; + + bool ReadCsi(const std::filesystem::path& path) { + std::string dbBuff{}; + + if (!utils::ReadFile(path, dbBuff)) { + LOG_ERROR("Can't read Cordycep database: {}", path.string()); + return false; + } + + struct CordycepBase { + uint64_t gameId; + uintptr_t poolsAddress; + uintptr_t stringsAddress; + uint32_t stringSize; + byte gameDir[4]; + }; + + LOG_DEBUG("Loading Cordycep csi..."); + CordycepBase* header{ (CordycepBase*)dbBuff.data() }; + + const char* buffer{ dbBuff.data() }; + size_t idx{ offsetof(CordycepBase, gameDir) }; + + gameDir.resize(header->stringSize); + memcpy(gameDir.data(), buffer + idx, header->stringSize); + idx += header->stringSize; + + + LOG_DEBUG("Game Id .. 0x{:x}", header->gameId); + LOG_DEBUG("Pools .... 0x{:x}", header->poolsAddress); + LOG_DEBUG("Strings .. 0x{:x}", header->stringsAddress); + LOG_DEBUG("Game dir . '{}'", gameDir); + + gameId = header->gameId; + poolsAddress = header->poolsAddress; + stringsAddress = header->stringsAddress; + + return true; + } + }; + + namespace { + int csi_test(Process& proc, int argc, const char* argv[]) { + if (!argv[2]) return tool::BAD_USAGE; + + CordycepProc cordycep{}; + + if (!cordycep.ReadCsi(argv[2])) { + LOG_ERROR("Can't read Cordycep database: {}", argv[2]); + return tool::BASIC_ERROR; + } + + LOG_INFO("Loaded"); + + return tool::OK; + } + struct XAsset64 { + uintptr_t Header; + uint64_t Temp; + uintptr_t Next; + uintptr_t Previous; + }; + + struct XAssetPool64 { + uintptr_t Root; + uintptr_t End; + uint64_t LookupTable; + uint64_t HeaderMemory; + uint64_t AssetMemory; + }; + + + struct ScriptBundleObject; + struct ScriptBundleObjectDef; + struct ScriptBundleObjectDefValueArrayIndexedVal; + + struct ScriptBundle { + uint64_t name; + uint64_t unk8; + uint64_t unk10; + uintptr_t rawdata; // uint8_t* + uint32_t count; + uintptr_t objects; // ScriptBundleObject* + }; + + enum ScriptBundleObjectDefType : byte { + SBT_UNDEFINED = 0x0, + SBT_INT1 = 0x1, + SBT_INT2 = 0x2, + SBT_FLOAT = 0x3, + SBT_ISTRING = 0x4, + SBT_STRING = 0x5, + SBT_STRUCT = 0x6, + SBT_ARRAY_INDEXED = 0x7, + SBT_ARRAY = 0x8, + SBT_STRUCT_ANIMATION = 0x9, + SBT_STRUCT_NAMEID = 0xA, + SBT_HASH = 0xB, + SBT_HASH_TAG = 0xC, + SBT_HASH_DVAR = 0xD, + SBT_HASH_RESOURCE = 0xE, + }; + + struct ScriptBundleObjectDefValueAnim + { + int32_t name_offset; + int32_t id; + uint64_t unk8; + uint64_t anim; + }; + + struct ScriptBundleObjectDefValueArray + { + int32_t count; + int32_t id; + uintptr_t defs; // ScriptBundleObjectDef* + }; + + struct ScriptBundleObjectDefValueArrayIndexed + { + int32_t count; + int32_t id; + uintptr_t vals; // ScriptBundleObjectDefValueArrayIndexedVal* + }; + + union ScriptBundleObjectDefValue + { + int32_t int_val; + float float_val; + uint32_t id; + uint64_t hash; + uint32_t hash_tag; + ScriptBundleObjectDefValueAnim anim; + ScriptBundleObjectDefValueArray array; + ScriptBundleObjectDefValueArrayIndexed arrayIndexed; + uint8_t data[24]; + }; + + struct ScriptBundleObjectDef { + ScriptBundleObjectDefType type; + uint8_t pad[7]; + ScriptBundleObjectDefValue value; + }; + + struct ScriptBundleObject { + uint64_t unk0; + uint64_t name; + ScriptBundleObjectDef def; + }; + + struct ScriptBundleObjectDefValueArrayIndexedVal { + uint64_t name1; + uint64_t name2; + uintptr_t def; // ScriptBundleObjectDef* + uint64_t unk18; + uint64_t unk20; + uint64_t unk28; + }; + + bool WriteBundleDef(std::ostringstream& os, Process& proc, ScriptBundle& entry, ScriptBundleObjectDef& def, int depth) { + switch (def.type) { + case SBT_UNDEFINED: + os << "undefined"; + break; + case SBT_INT1: + case SBT_INT2: + os << std::dec << def.value.int_val; + break; + case SBT_FLOAT: + os << def.value.float_val; + break; + case SBT_ISTRING: + os << "\"&" << proc.ReadStringTmp(entry.rawdata + def.value.id) << "\""; + break; + case SBT_STRING: + os << "\"" << proc.ReadStringTmp(entry.rawdata + def.value.id) << "\""; + break; + case SBT_STRUCT: { + os << "{"; + ScriptBundleObjectDefValueArrayIndexed& arrayIndexed = def.value.arrayIndexed; + + if (arrayIndexed.count) { + auto [vals, ok] = proc.ReadMemoryArray(arrayIndexed.vals, arrayIndexed.count); + + if (!ok) { + LOG_ERROR("Can't read vals"); + return false; + } + + for (size_t i = 0; i < arrayIndexed.count; i++) { + ScriptBundleObject& val = vals[i]; + + if (i) os << ","; + utils::Padding(os << "\n", depth + 1) << "\"#" << hashutils::ExtractTmp("hash", val.name) << "\": "; + if (!WriteBundleDef(os, proc, entry, val.def, depth + 1)) return false; + } + + } + + utils::Padding(os << "\n", depth) << "}"; + break; + } + case SBT_ARRAY_INDEXED: { + os << "["; + ScriptBundleObjectDefValueArrayIndexed& arrayIndexed = def.value.arrayIndexed; + + if (arrayIndexed.count) { + auto [vals, ok] = proc.ReadMemoryArray(arrayIndexed.vals, arrayIndexed.count); + + if (!ok) { + LOG_ERROR("Can't read vals"); + return false; + } + + for (size_t i = 0; i < arrayIndexed.count; i++) { + ScriptBundleObjectDefValueArrayIndexedVal& val = vals[i]; + + auto [def, ok] = proc.ReadMemoryObject(val.def); + + if (!ok) { + LOG_ERROR("Can't read def"); + return false; + } + + if (i) os << ","; + utils::Padding(os << "\n", depth + 1) << "\"#" << hashutils::ExtractTmp("hash", val.name1) << "////" << hashutils::ExtractTmp("hash", val.name2) << "\": "; + if (!WriteBundleDef(os, proc, entry, *def, depth + 1)) return false; + } + + } + + utils::Padding(os << "\n", depth) << "]"; + break; + } + case SBT_ARRAY: { + os << "["; + ScriptBundleObjectDefValueArray& array = def.value.array; + + if (array.count) { + auto [defs, ok] = proc.ReadMemoryArray(array.defs, array.count); + if (!ok) { + LOG_ERROR("Can't read vals"); + return false; + } + for (size_t i = 0; i < array.count; i++) { + if (i) os << ","; + utils::Padding(os << "\n", depth + 1); + if (!WriteBundleDef(os, proc, entry, defs[i], depth + 1)) return false; + } + } + + utils::Padding(os << "\n", depth) << "]"; + break; + } + case SBT_STRUCT_ANIMATION: { + os << "{"; + ScriptBundleObjectDefValueAnim& anim = def.value.anim; + const char* name = proc.ReadStringTmp(entry.rawdata + anim.name_offset); + hashutils::Add(name, true, true); + utils::Padding(os << "\n", depth + 1) << "\"#name\": \"" << name << "\","; + utils::Padding(os << "\n", depth + 1) << "\"#id\": \"anim#" << hashutils::ExtractTmp("hash", anim.anim) << "\""; + utils::Padding(os << "\n", depth) << "}"; + break; + } + case SBT_STRUCT_NAMEID: { + os << "{"; + ScriptBundleObjectDefValueAnim& anim = def.value.anim; + const char* name = proc.ReadStringTmp(entry.rawdata + anim.name_offset); + utils::Padding(os << "\n", depth + 1) << "\"#name\": \"" << name << "\","; + utils::Padding(os << "\n", depth + 1) << "\"#id\": " << std::dec << anim.id; + utils::Padding(os << "\n", depth) << "}"; + break; + } + case SBT_HASH: + os << "\"#" << hashutils::ExtractTmp("hash", def.value.hash) << "\""; + break; + case SBT_HASH_TAG: + os << "\"t#" << hashutils::ExtractTmp("hash", def.value.hash_tag) << "\""; + break; + case SBT_HASH_DVAR: + os << "\"@" << hashutils::ExtractTmp("hash", def.value.hash) << "\""; + break; + case SBT_HASH_RESOURCE: + os << "\"%" << hashutils::ExtractTmp("hash", def.value.hash) << "\""; + break; + default: + os << "error"; + return false; + } + return true; + } + + int ForEachEntry(Process& proc, XAssetPool64& pool, std::function func) { + uintptr_t curr = pool.Root; + + XAsset64 asset{}; + int res{}; + bool ok{ true }; + while (curr) { + if (!proc.ReadMemory(&asset, curr, sizeof(asset))) { + LOG_ERROR("Can't read pool entry {:x}", curr); + return false; + } + + if (asset.Header) { + if (!func(asset.Header)) { + ok = false; + } + res++; + } + + curr = asset.Next; + } + + return ok ? res : -res; + } + + int dpjup(Process& proc, int argc, const char* argv[]) { + std::filesystem::path exe{ proc[nullptr].path }; + std::filesystem::path db{ exe.parent_path() / "Data/CurrentHandler.csi" }; + std::string dbBuff{}; + + CordycepProc cordycep{}; + + if (!cordycep.ReadCsi(db)) { + LOG_ERROR("Can't read Cordycep database: {}", db.string()); + return tool::BASIC_ERROR; + } + + auto[pools, ok] = proc.ReadMemoryArray(cordycep.poolsAddress, sp23::ASSET_COUNT); + + if (!ok) { + LOG_ERROR("Can't read pools"); + return tool::BASIC_ERROR; + } + + hashutils::ReadDefaultFile(); + + std::filesystem::path outDir = "output_jup"; + + size_t total{}; + for (size_t i = 2; i < argc; i++) { + const char* pn = argv[i]; + + AssetType type = sp23::AssetTypeFromName(pn); + + if (type == ASSET_COUNT) { + LOG_ERROR("Invalid asset type name: {}", pn); + return tool::BAD_USAGE; + } + + std::function func{}; + + switch (type) { + case ASSET_GSCOBJ: { + struct GscObjEntry { + uint64_t name; + int len; + int padc; + uintptr_t buffer; + }; + + std::filesystem::path gscDir = outDir / "gsc"; + std::filesystem::create_directories(gscDir); + + func = [&proc, gscDir](uintptr_t asset) -> bool { + GscObjEntry entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read gscobj {:x}", asset); + return false; + } + + if (entry.len <= 0) { + LOG_INFO("Invalid size for {} ({})", hashutils::ExtractTmpScript(entry.name), entry.len); + return false; + } + + std::filesystem::path out = gscDir / utils::va("script_%llx.gscc", entry.name); + LOG_INFO("Dump {} -> {} (0x{:x})", hashutils::ExtractTmpScript(entry.name), out.string(), entry.len); + auto buff = std::make_unique(entry.len); + if (!proc.ReadMemory(&buff[0], entry.buffer, entry.len)) { + LOG_ERROR("Can't read gscobj buff {}", entry.name); + return false; + } + if (!utils::WriteFile(out, &buff[0], entry.len)) { + LOG_ERROR("Can't write file {}", out.string()); + return false; + } + + return true; + }; + break; + } + case ASSET_GSCGDB: { + struct GscObjEntry { + uint64_t name; + int len; + int padc; + uintptr_t buffer; + }; + + std::filesystem::path gscDir = outDir / "gscgdb"; + std::filesystem::create_directories(gscDir); + + func = [&proc, gscDir](uintptr_t asset) -> bool { + GscObjEntry entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read gscgdb {:x}", asset); + return false; + } + + if (entry.len <= 0) { + LOG_INFO("Invalid size for {} ({})", hashutils::ExtractTmpScript(entry.name), entry.len); + return false; + } + + std::filesystem::path out = gscDir / utils::va("script_%llx.gdbc", entry.name); + LOG_INFO("Dump {} -> {} (0x{:x})", hashutils::ExtractTmpScript(entry.name), out.string(), entry.len); + auto buff = std::make_unique(entry.len); + if (!proc.ReadMemory(&buff[0], entry.buffer, entry.len)) { + LOG_ERROR("Can't read gscgdb buff {}", entry.name); + return false; + } + if (!utils::WriteFile(out, &buff[0], entry.len)) { + LOG_ERROR("Can't write file {}", out.string()); + return false; + } + + return true; + }; + break; + } + case ASSET_STRINGTABLE: { + enum StringTableCellType : uint8_t { + STT_UNK_1_64 = 1, // string + STT_UNK_9_64 = 9, // string2?? + STT_UNK_A_64 = 0xA, // string3?? + + STT_UNK_2_64 = 2, // int?? + STT_UNK_5_64 = 5, // hash (0xCBF29CE484222325/0x100000001B3) + STT_UNK_6_64 = 6, // hash (0x47F5817A5EF961BA/0x100000001B3) + STT_UNK_7_64 = 7, // ? + + STT_UNK_3_32 = 3, // float?? + STT_UNK_8_32 = 8, // ? + + STT_BYTE = 4, + }; + + struct StringTableResult { + uint8_t* result; // 0 + int unk8; // 8 + StringTableCellType type; // 12 + }; + + struct StringTableColumn { + StringTableCellType type; + uint16_t* unk8; + uintptr_t rowindexes; // uint16_t* + uint64_t unk18; + uintptr_t rowdata; // uint8_t* + }; + + struct StringTable { + uint64_t name; + int columnCount; + int rowCount; + uint64_t cellIndices; + uintptr_t columns; // StringTableColumn* + uint64_t strings; + }; + + + std::filesystem::path dumpDir = outDir / "dump"; + std::filesystem::create_directories(dumpDir); + + func = [&proc, dumpDir](uintptr_t asset) -> bool { + StringTable entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read StringTable {:x}", asset); + return false; + } + + std::filesystem::path out = dumpDir / std::format("stringtable_{:x}.cf", entry.name); + + LOG_INFO("Dump {} -> {}", hashutils::ExtractTmp("hash", entry.name), out.string()); + std::ostringstream os{}; + if (entry.columnCount * entry.rowCount) { + auto columns = std::make_unique(entry.columnCount); + if (!proc.ReadMemory(&columns[0], entry.columns, sizeof(StringTableColumn) * entry.columnCount)) { + LOG_ERROR("Can't read columns"); + return false; + } + + for (size_t i = 0; i < entry.rowCount; i++) { + for (size_t j = 0; j < entry.columnCount; j++) { + if (j) { + os << ","; + } + + auto& column = columns[j]; + + auto rowIndex = proc.ReadMemory(column.rowindexes + i * sizeof(int16_t)); + + int elemSize; + switch (column.type) { + case STT_UNK_1_64: + case STT_UNK_9_64: + case STT_UNK_A_64: + case STT_UNK_2_64: + case STT_UNK_5_64: + case STT_UNK_6_64: + case STT_UNK_7_64: + elemSize = 8; + break; + case STT_UNK_3_32: + case STT_UNK_8_32: + elemSize = 4; + break; + case STT_BYTE: + elemSize = 1; + break; + default: + elemSize = 0; + break; + } + if (!elemSize) { + os << ""; + continue; // wtf? + } + + byte value[0x20]; + + if (!proc.ReadMemory(value, column.rowdata + elemSize * rowIndex, elemSize)) { + LOG_ERROR("Can't read column {}x{}", i, j); + return false; + } + + switch (column.type) { + case STT_UNK_1_64: + case STT_UNK_9_64: + case STT_UNK_A_64: + // strings?? + os << proc.ReadStringTmp(*reinterpret_cast(value)); + break; + case STT_UNK_2_64: + // int? + os << std::dec << *reinterpret_cast(value); + break; + case STT_UNK_5_64: + case STT_UNK_6_64: + os << "#hash_" << std::hex << *reinterpret_cast(value); + break; + case STT_UNK_7_64: + os << "7?" << std::hex << *reinterpret_cast(value); + break; + case STT_UNK_3_32: + os << *reinterpret_cast(value); + break; + case STT_UNK_8_32: + os << "8?" << std::hex << *reinterpret_cast(value); + break; + case STT_BYTE: + os << (*value ? "TRUE" : "FALSE"); + break; + default: + os << ""; + break; + } + } + os << "\n"; + } + } + + + std::ofstream osf{ out, std::ios::binary }; + if (osf) { + tool::hashes::compiled_files::CompiledFileHeader header{}; + header.name = entry.name; + header.isString = true; + strcpy_s(header.type, "stringtables"); + strcpy_s(header.preferedExtension, "csv"); + osf.write((const char*)&header, sizeof(header)); + std::string osc = os.str(); + osf.write(osc.data(), osc.size()); + osf.close(); + } + + return true; + }; + + } + break; + case ASSET_SCRIPTBUNDLE: { + std::filesystem::path dumpDir = outDir / "dump"; + std::filesystem::create_directories(dumpDir); + + func = [&proc, dumpDir](uintptr_t asset) -> bool { + ScriptBundle entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read ScriptBundle {:x}", asset); + return false; + } + + std::filesystem::path out = dumpDir / std::format("scriptbundle_{:x}.cf", entry.name); + + LOG_INFO("Dump {} -> {}", hashutils::ExtractTmp("hash", entry.name), out.string()); + std::ostringstream os{}; + + os << "{"; + + if (entry.count) { + auto [objects, ok] = proc.ReadMemoryArray(entry.objects, entry.count); + + if (!ok) { + LOG_ERROR("Can't read objects"); + return false; + } + + for (size_t i = 0; i < entry.count; i++) { + ScriptBundleObject& obj = objects[i]; + + if (i) os << ","; + os << "\n"; + utils::Padding(os, 1) << "\"#" << hashutils::ExtractTmp("hash", obj.name) << "\": "; + WriteBundleDef(os, proc, entry, obj.def, 1); + } + + } + os + << "\n" + << "}"; + + std::ofstream osf{ out, std::ios::binary }; + if (osf) { + tool::hashes::compiled_files::CompiledFileHeader header{}; + header.name = entry.name; + header.isString = true; + header.isSpecial = true; + strcpy_s(header.type, "scriptbundle"); + strcpy_s(header.preferedExtension, "json"); + osf.write((const char*)&header, sizeof(header)); + std::string osc = os.str(); + osf.write(osc.data(), osc.size()); + osf.close(); + } + + return true; + }; + break; + } + default: + LOG_ERROR("Handler not implemented for type {}", pn); + continue; + } + int du = ForEachEntry(proc, pools[type], func); + + if (du < 0) { + LOG_ERROR("Error reading pool: {}", pn); + } + else { + total += du; + } + } + + LOG_INFO("Dumped {} file(s)", total); + + return tool::OK; + } + } +#ifndef CI_BUILD + ADD_TOOL("csi_test", "dev", " [file]", "dump pool", nullptr, csi_test); +#endif + ADD_TOOL("dpjup", "mwiii", " [pool]", "dump pool", L"Cordycep.CLI.exe", dpjup); +} \ No newline at end of file diff --git a/src/acts/tools/sp23/sp23.cpp b/src/acts/tools/sp23/sp23.cpp index a386504..fedfaf8 100644 --- a/src/acts/tools/sp23/sp23.cpp +++ b/src/acts/tools/sp23/sp23.cpp @@ -1,5 +1,6 @@ #include #include "tools/gsc.hpp" +#include "sp23.hpp" #include #include @@ -426,6 +427,272 @@ namespace { return tool::OK; } + + + const char* assetNames[]{ + "physicslibrary", + "physicssfxeventasset", + "physicsvfxeventasset", + "physicsasset", + "physicsasset", + "physicsfxshape", + "physicsdebugdata", + "xanim", + "xmodelsurfs", + "xmodel", + "mayhem", + "material", + "computeshader", + "tileshader", + "libshader", + "vertexshader", + "hullshader", + "domainshader", + "pixelshader", + "serializedshad", + "techset", + "image", + "soundglobalvolmod", + "soundglobalentchannel", + "soundglobalcontext", + "soundglobalwhizby", + "soundglobalbulletcrack", + "soundglobalperk", + "soundglobalocclusion", + "soundglobalsurfaceinfo", + "soundglobalcurve", + "soundglobaldoppler", + "soundglobalshapecone", + "soundglobalfutz", + "soundglobalsendeffect", + "soundglobalnametable", + "soundbanktransient", + "soundbank", + "soundbank_merge", + "soundbanktrans", + "colmap", + "commap", + "glassmap", + "aipaths", + "navmesh", + "tacgraph", + "airegiongraphs", + "mapents", + "mapentstrzone", + "fxmap", + "gfxmap", + "gfxmaptrzone", + "iesprofile", + "lightdef", + "gradingclut", + "fogspline", + "animclass", + "playeranim", + "nk_8a41d00", + "localize", + "attachment", + "weapon", + "vfx", + "impactfx", + "surfacefx", + "aitype", + "character", + "xmodelalias", + "rawfile", + "gscobj", + "gscgdb", + "stringtable", + "ddl", + "tracer", + "vehicle", + "netconststrings", + "luafile", + "scriptable", + "vectorfield", + "particlesimanimation", + "streaminginfo", + "laser", + "gameprops", + "materialstandard", + "beam", + "ttf", + "suit", + "suitanimpackage", + "camera", + "hudoutline", + "rumble", + "rumblegraph", + "animpkg", + "sfxpkg", + "vfxpkg", + "footstepvfx", + "behaviortree", + "behaviorsequencer", + "sightconfig", + "sightconfigtemplate", + "aianimset", + "aiasm", + "proceduralbones", + "dynamicbones", + "proceduralblendweights", + "reticle", + "xanimcurve", + "coverselector", + "enemyselector", + "clientcharacter", + "clothasset", + "cinematicmotion", + "accessory", + "locdmgtable", + "bulletpenetration", + "scriptbundle", + "blendspace2d", + "xcam", + "camo", + "xcompositemodel", + "xmodeldetailcollision", + "streamtreeoverride", + "keyvaluepairs", + "stterrain", + "vhmdata", + "vtm_surfacemapsdata", + "collisiontile", + "execution", + "carryobject", + "soundbanklist", + "weaponaccuracy", + "decalvolumematerial", + "decalvolumemask", + "dynentitylist", + "fxmaptrzone", + "volumetricheig", + "dlogschema", + "edgelist", + "standaloneumbratome", + "xboneset", + "ragdollasset", + "physicsbonegraph", + "curve", + "skeletonconstraints", + "triggereffect", + "weapontrigger", + "volumetricclou", + "codcasterdata", + "watersystem", + "waterbuoyancy", + "keybinds_0", + "calloutmarkerp", + "lightstate", + "radianttelemetry", + "aimarkup", + "aimarkup_generated", + "scenario", + "ai_interaction", + "mapvoxelizedstateformat", + "watervis", + "gametype_0", + "gametypetable", + "sndmodifier", + "weaponblueprin", + "attachmentblue", + "movingplatform", + "hwconfig", + "timelapsesky", + "hwconfigurator", + "objectivedata", + "font", + "motionmatchingfeatures", + "motionmatchingset", + "gametypestatda", + "fonticon", + "calloutmarkerpingwheel", + "hwconfigvar", + "zivart", + "movie", + "mapinfo", + "maptable", + "achievement_0", + "achievementlis", + "materialdebugd", + "scriptablevari", + "leagueplayseas", + "settingcontext", + "aieventlist", + "soundevent", + "calloutmarkerp_3", + "project", + "projecttable", + "gamemode", + "sndasset", + "gfxumbratome", + "audiovisualize", + "materialanimat_0", + "nameplatesetti", + "reactiveaudiop", + "reactivevfxpac", + "materialsfxtab", + "footstepsfxtab", + "reactivestages", + "foliagesfxtabl", + "impactsfxtable", + "aiimpactvfxtab", + "typeinfo", + "handplantsfxta", + "sndtable", + "equipmentsfx", + "soundsubmix", + "shock", + "storagefile", + "ecsasset", + "trackerfootste", + "playerspawnset", + "playerspawninf", + "soundspeakerma", + "reverbpreset", + "aishootstylesl", + "operatorlist", + "operator", + "operatorskin", + "dismemberment", + "conversation", + "xanimnode", + "sndmodifierset", + "sndcurve", + "ttlos", + "materialtintan", + "materialuvanim", + "materialcamoan", + "materialanimat", + "impactfxtable", + "impacttypetoim", + "reactiveoperat", + "weathervolume", + "vehicletrick", + "reactiveaudiop_2", + "ambientsfxpack", + "objectstorepro", + "objectstoregam", + "proceduralbone_2", + "hwconfigvargro", + "hwconfigtiered", + "sndmasterprese", + "genericbluepri", + }; +} +namespace sp23 { + const char* AssetTypeName(AssetType type) { + if (type >= ARRAYSIZE(assetNames)) return "undefined"; + return assetNames[type]; + } + + AssetType AssetTypeFromName(const char* name) { + for (size_t i = 0; i < ARRAYSIZE(assetNames); i++) { + if (!_strcmpi(name, assetNames[i])) { + return (AssetType)i; + } + } + return ASSET_COUNT; + } } #ifndef CI_BUILD ADD_TOOL("local23", "mwiii", " [file]", "decrypt local dump 23", nullptr, decryptlocalize); diff --git a/src/acts/tools/sp23/sp23.hpp b/src/acts/tools/sp23/sp23.hpp index 645069b..937d2c3 100644 --- a/src/acts/tools/sp23/sp23.hpp +++ b/src/acts/tools/sp23/sp23.hpp @@ -73,7 +73,7 @@ namespace sp23 { ASSET_RAWFILE = 0x44, ASSET_GSCOBJ = 0x45, ASSET_GSCGDB = 0x46, - ASSET_STRINGTABLE_0 = 0x47, + ASSET_STRINGTABLE = 0x47, ASSET_DDL = 0x48, ASSET_TRACER = 0x49, ASSET_VEHICLE = 0x4A, @@ -251,4 +251,7 @@ namespace sp23 { ASSET_GENERICBLUEPRI = 0xF6, ASSET_COUNT = 0xF7, }; + + const char* AssetTypeName(AssetType type); + AssetType AssetTypeFromName(const char* name); } \ No newline at end of file