diff --git a/README.md b/README.md index 7f761aa..3fb5540 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,22 @@ My set of tools. The code is more important than the features, so feel free to reuse it. 🙂 -**Supported games** +**Table of contents** + +- [Atian Tools](#atian-tools) + - [GSC Compiler/Decompiler](#gsc-compilerdecompiler) + - [Dumper](#dumper) + - [ACTS Lib](#acts-lib) + - [Dependencies](#dependencies) + - [Downloads](#downloads) + - [Related repositories](#related-repositories) + - [Lookup](#lookup) + - [Credits](#credits) + + +## GSC Compiler/Decompiler + +**Supported features** | Name | Revision | Decompiler | Compiler | PS4 support | | ------------------------ | -------- | ---------- | -------- | ----------- | @@ -16,10 +31,49 @@ My set of tools. The code is more important than the features, so feel free to r | Modern Warfare III (JUP) | 8A | EXT | EXT | ❌ | | Modern Warfare III (JUP) | 8B | DEC & EXT | EXT | ❌ | - -- **DEC**: with pre-decode +- **DEC**: With pre-decode - **EXT**: With extensions, ***The extensions aren't provided publicly, at least not by me.*** +**Commands** +```pwsh +# Compile gsc file + +acts gscc -g + +# Example +acts gscc my_script.gsc -g cw # Compile my_script.gsc into a cold war script +``` + +``` +# Decompile gsc file + +acts gscd file.gscc -g + +# Example +acts gscd compiled.gscc -g # Decompile the script compiled.gscc +``` + +## Dumper + +**Supported pools** + +- Black Ops 3: `scriptbundle`, `stringtable`, `structuredtable`, `rawfile`, `scriptparsetree`. +- Black Ops 4: `weapon`, `customizationtable`, `rawfile`, `stringtable`, `structuredtable`, `ddl`, `scriptparsetree`, `scriptparsetreeforced`, `scriptbundle`, `scriptbundlelist`, `ttf`, `bgcache`, `maptable`, `maptablelist`, `maptableloadingimages`, `maptablepreviewimages`, `playerrolecategory`, `playerrolecategorytable`, `gametypetable`, `unlockableitem`, `unlockableitemtable`, `playlists`, `hierarchicaltasknetwork`, `storagefile`, `storagefilelist`, `storeproduct`, `storecategory`, `storecategorylist`, `rank`, `ranktable`, `prestige`, `prestigetable`, `labelstore`, `labelstorelist`, `rawstring`. +- Black Ops Cold War (Dec): `rawfile`, `rawfilepreproc`, `rawtextfile`, `stringtable`, `scriptparsetree`, `scriptbundle`. +- Modern Warfare III (COR): `gscobj`, `scriptbundle`, `stringtable`, `localize`, `luafile`. + +- **DEC**: Requires pre-decode +- **COR**: Using [Cordycep](https://github.com/Scobalula/Cordycep). + +**Commands** +```pwsh +# Command +acts dp + +# Example +acts dp stringtable +``` + ## ACTS Lib Prototype libary for random stuff, probably not linked to Call of Duty. @@ -53,36 +107,9 @@ You can download the latest release here: - [ate47/bo3-source](https://github.com/ate47/bo3-source) : Black Ops 3 Dump - [ate47/bo4-source](https://github.com/ate47/bo4-source) : Black Ops 4 Dump - [ate47/bocw-source](https://github.com/ate47/bocw-source) : Black Ops Cold War Dump +- [ate47/mwiii-source](https://github.com/ate47/mwiii-source) : Modern Warfare III Dump - [ate47/BOHashTool](https://github.com/ate47/BOHashTool) : Tool to test hashes with error (en/de)coder for Black Ops games -## Tools - -### Mods - -Mods implemented in my tool, run `acts mod` for the list. - -- `acts mod t8cee` - enable EEs in Custom mutations, offline or casual (Black Ops 4). -- `acts mod t9cee` - enable EEs in offline (Black Ops Cold War). - -### Decompiler/Disassembler - -Tools to decompile or disassemble the GSC scripts, a bo4 script decompilation is available in the [bo4-source](https://github.com/ate47/bo4-source) and [bocw-source](https://github.com/ate47/bocw-source) repositories. - -- gsc disassembler, made in 3 days with a lot of alcohol so don't use it. `acts gscinfo -a -o "output" [input=scriptparsetree]` -- gsc decompiler, same as the disassembler, but 10 days after, not any better. `acts gscinfo -g -o "output" [input=scriptparsetree]` - -### Compiler - -GSC compiler, not for all the games, the scripts can be compiled using the command `acts gscc -g [game] [directory]`. - -### GSC Development (Black Ops 4) - -Tools to help with the GSC development. - -- gsc vm debugger, dump the function stack when the vm has a crash, `acts dbg` - - can dump the var stack `-s` local var `-v` - - can look inside structures with the depth for array `-A [depth]` and structs `-S [depth]` (need the game started) - ## Lookup To have a lookup over the extracted hashes, you can use a file named `strings.txt` when using the process, it will be loaded automatically, one string per line. diff --git a/src/acts/compatibility/scobalula_csi.cpp b/src/acts/compatibility/scobalula_csi.cpp new file mode 100644 index 0000000..f1b701f --- /dev/null +++ b/src/acts/compatibility/scobalula_csi.cpp @@ -0,0 +1,53 @@ +#include +#include "scobalula_csi.hpp" + +namespace compatibility::scobalula::csi { + + const char* CordycepGameName(CordycepGame game) { + switch (game) { + case CG_GHOSTS: return "Ghosts"; + case CG_AW: return "Advanced Warfare"; + case CG_IW: return "Infinite Warfare"; + case CG_MWR: return "Modern Warfare Remastered"; + case CG_MW2R: return "Modern Warfare 2 Remastered"; + case CG_MW4: return "Modern Warfare 2019"; + case CG_MW5: return "Modern Warfare II"; + case CG_MW6: return "Modern Warfare III"; + case CG_VANGUARD: return "Vanguard"; + default: return ""; + } + } + + bool CordycepProc::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; + } + + LOG_DEBUG("Loading Cordycep CSI..."); + CordycepCsiHeader* header{ (CordycepCsiHeader*)dbBuff.data() }; + + const char* buffer{ dbBuff.data() }; + size_t idx{ offsetof(CordycepCsiHeader, gameDir) }; + + gameDir.resize(header->stringSize); + memcpy(gameDir.data(), buffer + idx, header->stringSize); + idx += header->stringSize; + + + LOG_DEBUG("Game Id .. {} (0x{:x})", CordycepGameName(header->gameId), (uint64_t)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; + } + + +} \ No newline at end of file diff --git a/src/acts/compatibility/scobalula_csi.hpp b/src/acts/compatibility/scobalula_csi.hpp new file mode 100644 index 0000000..433b26c --- /dev/null +++ b/src/acts/compatibility/scobalula_csi.hpp @@ -0,0 +1,35 @@ +#pragma once + +namespace compatibility::scobalula::csi { + enum CordycepGame : uint64_t { + CG_GHOSTS = 0xA0A5354534F4847, + CG_AW = 0x5241574E41564441, + CG_IW = 0x4652415749464E49, + CG_MWR = 0x30305453414D4552, + CG_MW2R = 0x32305453414D4552, + CG_MW4 = 0x3931524157444F4D, + CG_MW5 = 0x3232524157444F4D, + CG_MW6 = 0x4B4F4D41594D4159, + CG_VANGUARD = 0x44524155474E4156, + }; + + const char* CordycepGameName(CordycepGame game); + + struct CordycepProc { + CordycepGame gameId; + uintptr_t poolsAddress; + uintptr_t stringsAddress; + std::string gameDir; + + bool ReadCsi(const std::filesystem::path& path); + }; + + struct CordycepCsiHeader { + CordycepGame gameId; + uintptr_t poolsAddress; + uintptr_t stringsAddress; + uint32_t stringSize; + byte gameDir[4]; + }; + +} \ No newline at end of file diff --git a/src/acts/compatibility/scobalula_wni.hpp b/src/acts/compatibility/scobalula_wni.hpp index 1493e5d..7888417 100644 --- a/src/acts/compatibility/scobalula_wni.hpp +++ b/src/acts/compatibility/scobalula_wni.hpp @@ -1,7 +1,5 @@ #pragma once -#include - namespace compatibility::scobalula::wni { constexpr const char* packageIndexDir = "package_index"; constexpr uint32_t WNI_MAGIC = 0x20494E57; diff --git a/src/acts/compatibility/serious.cpp b/src/acts/compatibility/serious_db2.cpp similarity index 98% rename from src/acts/compatibility/serious.cpp rename to src/acts/compatibility/serious_db2.cpp index 611bb8a..052a3cc 100644 --- a/src/acts/compatibility/serious.cpp +++ b/src/acts/compatibility/serious_db2.cpp @@ -1,10 +1,10 @@ #include #include "tools/gsc_opcodes.hpp" -#include "compatibility/serious.hpp" +#include "compatibility/serious_db2.hpp" using namespace tool::gsc::opcode; -using namespace compatibility::serious; +using namespace compatibility::serious::db2; namespace { @@ -191,7 +191,7 @@ namespace { } } -namespace compatibility::serious { +namespace compatibility::serious::db2 { OPCode ConvertFrom(SeriousId id) { return Maps().ConvertFrom(id); } diff --git a/src/acts/compatibility/serious.hpp b/src/acts/compatibility/serious_db2.hpp similarity index 98% rename from src/acts/compatibility/serious.hpp rename to src/acts/compatibility/serious_db2.hpp index 10309a3..efbe839 100644 --- a/src/acts/compatibility/serious.hpp +++ b/src/acts/compatibility/serious_db2.hpp @@ -1,9 +1,8 @@ #pragma once -#include #include "tools/gsc_opcodes.hpp" -namespace compatibility::serious { +namespace compatibility::serious::db2 { constexpr auto VM_CODES_DB = "vm_codes.db2"; enum SeriousId : byte { diff --git a/src/acts/hashutils.cpp b/src/acts/hashutils.cpp index 2573cae..ee904f3 100644 --- a/src/acts/hashutils.cpp +++ b/src/acts/hashutils.cpp @@ -177,7 +177,9 @@ int hashutils::LoadMap(const char* file, bool ignoreCol, bool iw) { AddPrecomputed(Hash64("uint"), "uint"); AddPrecomputed(Hash64("long"), "long"); AddPrecomputed(Hash64("ulong"), "ulong"); - AddPrecomputed(Hash64("$$padding"), "$$padding"); + AddPrecomputed(Hash64("$$padding"), "$$padding"); + // Dump CF + AddPrecomputed(Hash64("localize.json"), "localize.json"); std::ifstream s(file); diff --git a/src/acts/tools/bo3/dumpt7.cpp b/src/acts/tools/bo3/dumpt7.cpp index 8f721b4..16a3e76 100644 --- a/src/acts/tools/bo3/dumpt7.cpp +++ b/src/acts/tools/bo3/dumpt7.cpp @@ -194,7 +194,7 @@ namespace { return false; } auto flagName = args[++i]; - + if (!_strcmpi("allraw", flagName)) { flags |= PDOF_DUMP_ALL_RAW; } @@ -360,7 +360,7 @@ namespace { ScriptBundleKVP& kvp{ kvps[i] }; utils::Padding(out, depth) << "\"" << GetMTString(proc, kvp.key) << "\"" << ": "; - + switch (kvp.type) { case KVP_INT: case KVP_VEHICLE: @@ -397,7 +397,8 @@ namespace { return true; } - int t7dp(Process& proc, int argc, const char* argv[]) { +} +int bo3::pool::t7dp(Process& proc, int argc, const char* argv[]) { PoolOption opt; if (!opt.Compute(argv, 2, argc) || opt.m_help) { @@ -860,7 +861,7 @@ namespace { return tool::OK; } - +namespace { int t7poolscripts(Process& proc, int argc, const char* argv[]) { std::filesystem::path outDir; if (argc == 2) { @@ -997,6 +998,6 @@ namespace { - ADD_TOOL("dpt7", "bo3", " [pool]", "dump pool (bo3)", L"BlackOps3.exe", t7dp); + ADD_TOOL("dpt7", "bo3", " [pool]", "Black Ops 3 dump pool", L"BlackOps3.exe", t7dp); ADD_TOOL("wpst7", "bo3", " [output=scriptparsetree_t7]", "dump pooled scripts (bo3)", L"BlackOps3.exe", t7poolscripts); } diff --git a/src/acts/tools/bo3/pools.hpp b/src/acts/tools/bo3/pools.hpp index 10e3e0c..8438006 100644 --- a/src/acts/tools/bo3/pools.hpp +++ b/src/acts/tools/bo3/pools.hpp @@ -168,5 +168,5 @@ namespace bo3::pool { T7_TYPE_THREAD_LIST = 28, T7_TYPE_ENT_LIST = 29, }; - + int t7dp(Process& proc, int argc, const char* argv[]); } \ No newline at end of file diff --git a/src/acts/tools/common.cpp b/src/acts/tools/common.cpp new file mode 100644 index 0000000..8b16871 --- /dev/null +++ b/src/acts/tools/common.cpp @@ -0,0 +1,51 @@ +#include + +namespace { + int commonpooltool(Process& _, int argc, const char* argv[]) { + const char* tools[]{ + "dpbo4", + "dpcw", + "dpt7", + "dpcord", + }; + + for (const char* name : tools) { + const tool::toolfunctiondata& tool = tool::findtool(name); + + if (!tool || !tool.m_game) { + LOG_WARNING("Invalid tool config: {}", name); + continue; + } + + Process proc{ tool.m_game }; + + if (!proc) continue; + + LOG_INFO("Find process {} {}", utils::WStrToStr(tool.m_game), proc); + + if (!proc.Open()) { + LOG_ERROR("Can't open game process: 0x{:x}", GetLastError()); + return -1; + } + + return tool.m_func(proc, argc, argv); + } + + LOG_ERROR("Can't find any supported game"); + LOG_ERROR("Available games:"); + for (const char* name : tools) { + const tool::toolfunctiondata& tool = tool::findtool(name); + + if (!tool || !tool.m_game) { + continue; + } + LOG_ERROR("- {} : {} ({})", utils::WStrToStr(tool.m_game), tool.m_description, tool.m_category); + } + + return tool::BASIC_ERROR; + } + +} + + +ADD_TOOL("dp", "common", " [pool]+", "dump pool", nullptr, commonpooltool); \ No newline at end of file diff --git a/src/acts/tools/compatibility/serious_db2gen.cpp b/src/acts/tools/compatibility/serious_db2gen.cpp index 562cb76..c9c0828 100644 --- a/src/acts/tools/compatibility/serious_db2gen.cpp +++ b/src/acts/tools/compatibility/serious_db2gen.cpp @@ -1,5 +1,5 @@ #include -#include "compatibility/serious.hpp" +#include "compatibility/serious_db2.hpp" #include "tools/gsc_opcodes.hpp" #include "tools/gsc.hpp" @@ -72,10 +72,10 @@ namespace { LOG_INFO("Building db for {} vm(s)...", asked.size()); - std::ofstream vmfile{ compatibility::serious::VM_CODES_DB, std::ios::binary }; + std::ofstream vmfile{ compatibility::serious::db2::VM_CODES_DB, std::ios::binary }; if (!vmfile) { - LOG_ERROR("Can't open file {}", compatibility::serious::VM_CODES_DB); + LOG_ERROR("Can't open file {}", compatibility::serious::db2::VM_CODES_DB); return tool::BASIC_ERROR; } @@ -83,7 +83,7 @@ namespace { for (const auto& [uid, bld] : asked) { // clear buffer - memset(buffer, compatibility::serious::SERID_Invalid, sizeof(buffer)); + memset(buffer, compatibility::serious::db2::SERID_Invalid, sizeof(buffer)); // header @@ -95,9 +95,9 @@ namespace { for (size_t i = 0; i < 0x1000; i++) { auto* loc = LookupOpCode(bld.vm->vm, bld.platform, (uint16_t) i); - auto mappedId = compatibility::serious::ConvertTo(loc->m_id); + auto mappedId = compatibility::serious::db2::ConvertTo(loc->m_id); - if (mappedId == compatibility::serious::SERID_Invalid) { + if (mappedId == compatibility::serious::db2::SERID_Invalid) { continue; } @@ -110,7 +110,7 @@ namespace { } vmfile.close(); - LOG_INFO("DB created into {}", compatibility::serious::VM_CODES_DB); + LOG_INFO("DB created into {}", compatibility::serious::db2::VM_CODES_DB); return tool::OK; } diff --git a/src/acts/tools/compatibility/serious_fakedb2gen.cpp b/src/acts/tools/compatibility/serious_fakedb2gen.cpp index 81c1eb2..981aadd 100644 --- a/src/acts/tools/compatibility/serious_fakedb2gen.cpp +++ b/src/acts/tools/compatibility/serious_fakedb2gen.cpp @@ -1,10 +1,10 @@ #include -#include "compatibility/serious.hpp" +#include "compatibility/serious_db2.hpp" #include "tools/gsc.hpp" namespace { using namespace tool::gsc::opcode; - using namespace compatibility::serious; + using namespace compatibility::serious::db2; int fakedb(Process& proc, int argc, const char* argv[]) { if (argc < 4) { @@ -40,7 +40,7 @@ namespace { byte buffer[0x1004] = {}; // clear buffer - memset(buffer, compatibility::serious::SERID_Invalid, sizeof(buffer)); + memset(buffer, SERID_Invalid, sizeof(buffer)); // header diff --git a/src/acts/tools/cordycep_dump.cpp b/src/acts/tools/cordycep_dump.cpp new file mode 100644 index 0000000..5629ced --- /dev/null +++ b/src/acts/tools/cordycep_dump.cpp @@ -0,0 +1,882 @@ +#include +#include "compatibility/scobalula_csi.hpp" +#include "tools/sp23/sp23.hpp" +#include "tools/hashes/compiled_files.hpp" + +namespace tool::cordycep::dump { + using namespace sp23; + + std::ostream& PrintFormattedString(std::ostream& out, const char* str) { + if (!str) { + return out << "nullptr"; + } + for (; *str; str++) { + switch (*str) { + case '\n': + out << "\\n"; + break; + case '\r': + out << "\\r"; + break; + case '\t': + out << "\\t"; + break; + case '\a': + out << "\\a"; + break; + case '\b': + out << "\\b"; + break; + case '\v': + out << "\\v"; + break; + case '"': + out << "\\\""; + break; + default: + if (*str < 0x20 || *str >= 0x7F) { + out << "\\" << std::oct << (unsigned int)(*reinterpret_cast(str)) << std::dec; + } + else { + out << *str; + } + break; + } + } + return out; + } + + namespace jup { + class PoolOptionJup { + public: + bool m_help = false; + bool m_any_type = false; + bool m_dump_info = false; + bool m_dump_all_available = false; + const char* m_output = "output_jup"; + const char* m_dump_strings{}; + std::vector m_dump_types{}; + uint64_t flags{}; + std::unordered_set dstrings{}; + + bool Compute(const char** args, INT startIndex, INT endIndex) { + m_dump_types.clear(); + m_dump_types.reserve(sp23::ASSET_COUNT); + for (size_t i = 0; i < sp23::ASSET_COUNT; i++) { + m_dump_types.push_back(false); + } + // default values + for (size_t i = startIndex; i < endIndex; i++) { + const char* arg = args[i]; + + if (!strcmp("-?", arg) || !_strcmpi("--help", arg) || !strcmp("-h", arg)) { + m_help = true; + } + else if (!strcmp("-o", arg) || !_strcmpi("--output", arg)) { + if (i + 1 == endIndex) { + std::cerr << "Missing value for param: " << arg << "!\n"; + return false; + } + m_output = args[++i]; + } + else if (!_strcmpi("--all", arg) || !strcmp("-a", arg)) { + m_dump_all_available = true; + m_any_type = true; + } + else if (!strcmp("-S", arg) || !_strcmpi("--strings", arg)) { + if (i + 1 == endIndex) { + std::cerr << "Missing value for param: " << arg << "!\n"; + return false; + } + m_dump_strings = args[++i]; + } + else if (*arg == '-') { + std::cerr << "Invalid argument: " << arg << "!\n"; + return false; + } + else { + auto assetType = sp23::AssetTypeFromName(arg); + + if (assetType == sp23::ASSET_COUNT) { + try { + assetType = (sp23::AssetType)std::strtol(arg, nullptr, 10); + } + catch (const std::invalid_argument& e) { + std::cerr << e.what() << "\n"; + assetType = sp23::ASSET_COUNT; + } + if (assetType < 0 || assetType >= sp23::ASSET_COUNT) { + std::cerr << "Invalid pool name: " << arg << "!\n"; + return false; + } + } + + m_dump_types[assetType] = true; + m_any_type = true; + } + } + return true; + } + void PrintHelp() { + LOG_INFO("-h --help : Print help"); + LOG_INFO("-o --output [d] : Output dir"); + } + + const char* AddString(const char* str) { + if (m_dump_strings) { + dstrings.insert(str); + } + return str; + } + }; + 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 GscObjEntry { + uint64_t name; + int len; + int padc; + uintptr_t buffer; + }; + + + 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_HASH = 5, // hash (0xCBF29CE484222325/0x100000001B3) + STT_HASH_RESOURCE = 6, // hash (0x47F5817A5EF961BA/0x100000001B3) + STT_UNK_7_64 = 7, // ? + + STT_FLOAT = 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; + }; + + + 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 name0; + 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; + }; + + struct LocalizeEntry { + uint64_t hash; + uintptr_t value; + }; + struct LuaFileEntry { + uintptr_t name; + uint32_t len; + uintptr_t buffer; + }; + + bool WriteBundleDef(PoolOptionJup& opt, 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 << "\"&" << opt.AddString(proc.ReadStringTmp(entry.rawdata + def.value.id)) << "\""; + break; + case SBT_STRING: + os << "\"" << opt.AddString(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(opt, 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(opt, 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(opt, 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 = opt.AddString(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 = opt.AddString(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 }; + size_t count{}; + 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, count++)) { + ok = false; + } + res++; + } + + curr = asset.Next; + } + + return ok ? res : -res; + } + + int dpcordjup(Process& proc, compatibility::scobalula::csi::CordycepProc& cordycep, int argc, const char* argv[]) { + PoolOptionJup opt{}; + + if (!opt.Compute(argv, 2, argc) || opt.m_help || !opt.m_any_type) { + opt.PrintHelp(); + return tool::OK; + } + + + 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 = opt.m_output; + + size_t total{}; + + auto HandlePool = [&opt, &pools, &proc, &total](sp23::AssetType type, const std::filesystem::path& outDir, std::function func) { + if (!opt.m_dump_types[type] && !opt.m_dump_all_available) { + return; + } + + opt.m_dump_types[type] = false; + + std::filesystem::create_directories(outDir); + + int du = ForEachEntry(proc, pools[type], func); + + if (du < 0) { + LOG_ERROR("Error reading pool: {}", sp23::AssetTypeName(type)); + } + else { + total += du; + } + }; + + const std::filesystem::path gscDir = outDir / "gsc"; + HandlePool(ASSET_GSCOBJ, gscDir, [&proc, gscDir](uintptr_t asset, size_t count) -> 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; + }); + + const std::filesystem::path gdbDir = outDir / "gscgdb"; + HandlePool(ASSET_GSCGDB, gdbDir, [&proc, gdbDir](uintptr_t asset, size_t count) -> 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 = gdbDir / 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; + }); + + const std::filesystem::path luaDir = outDir / "lua"; + HandlePool(ASSET_LUAFILE, luaDir, [&proc, luaDir](uintptr_t asset, size_t count) -> bool { + LuaFileEntry entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read luafile {: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 = luaDir / utils::va("lua_%llx.lua", 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 luafile 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; + }); + + std::filesystem::path dumpDir = outDir / "dump"; + HandlePool(ASSET_STRINGTABLE, dumpDir, [&opt, &proc, dumpDir](uintptr_t asset, size_t count) -> 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_HASH: + case STT_HASH_RESOURCE: + case STT_UNK_7_64: + elemSize = 8; + break; + case STT_FLOAT: + 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 << opt.AddString(proc.ReadStringTmp(*reinterpret_cast(value))); + break; + case STT_UNK_2_64: + // int? + os << std::dec << *reinterpret_cast(value); + break; + case STT_HASH: + os << "#hash_" << std::hex << *reinterpret_cast(value); + break; + case STT_HASH_RESOURCE: + os << "%hash_" << std::hex << *reinterpret_cast(value); + break; + case STT_UNK_7_64: + os << "@hash_" << std::hex << *reinterpret_cast(value); + break; + case STT_FLOAT: + os << *reinterpret_cast(value); + break; + case STT_UNK_8_32: + os << "t#hash_" << 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; + }); + + HandlePool(ASSET_SCRIPTBUNDLE, dumpDir, [&opt, &proc, dumpDir](uintptr_t asset, size_t count) -> 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(opt, 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; + }); + + // LocalizeEntry + if (opt.m_dump_types[ASSET_LOCALIZE] || opt.m_dump_all_available) { + opt.m_dump_types[ASSET_LOCALIZE] = false; + + std::ostringstream os{}; + + os << "{"; + + int du = ForEachEntry(proc, pools[ASSET_LOCALIZE], [&proc, &os](uintptr_t asset, size_t count) { + LocalizeEntry entry{}; + if (!proc.ReadMemory(&entry, asset, sizeof(entry))) { + LOG_ERROR("Can't read LocalizeEntry {:x}", asset); + return false; + } + + if (count) os << ","; + + utils::Padding(os << "\n", 1) << "\"#" << hashutils::ExtractTmp("hash", entry.hash) << "\": \""; + PrintFormattedString(os, proc.ReadStringTmp(entry.value)) << "\""; + + return true; + }); + + os << "\n}"; + + if (du < 0) { + LOG_ERROR("Error reading pool: {}", sp23::AssetTypeName(ASSET_LOCALIZE)); + } + else { + total += du; + + std::filesystem::create_directories(outDir); + std::filesystem::path loc{ outDir / "localize.cf" }; + std::ofstream osf{ loc, std::ios::binary}; + if (osf) { + tool::hashes::compiled_files::CompiledFileHeader header{}; + header.name = hash::Hash64("localize.json"); + header.isString = true; + strcpy_s(header.type, "localize"); + 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(); + LOG_INFO("Dump into {}", loc.string()); + } + else { + LOG_ERROR("Can't open {}", loc.string()); + } + + } + + } + + for (size_t i = 0; i < opt.m_dump_types.size(); i++) { + if (!opt.m_dump_types[i]) { + continue; + } + const char* pn = sp23::AssetTypeName((sp23::AssetType)i); + + std::filesystem::path rawDir = outDir / "raw" / pn; + + // TODO: hex dump + LOG_ERROR("Can't parse type {}", pn); + } + + LOG_INFO("Dumped {} file(s)", total); + + if (opt.m_dump_strings) { + std::ofstream outStr{ opt.m_dump_strings }; + if (outStr) { + outStr << "string"; + for (const std::string& str : opt.dstrings) { + outStr << "\n" << str; + } + outStr.close(); + LOG_INFO("Dumped {} string(s) into '{}'", opt.dstrings.size(), opt.m_dump_strings); + } + else { + LOG_ERROR("Can't open file '{}'", opt.m_dump_strings); + } + } + + return tool::OK; + } + + } + + namespace { + int csi_test(Process& proc, int argc, const char* argv[]) { + if (!argv[2]) return tool::BAD_USAGE; + + compatibility::scobalula::csi::CordycepProc cordycep{}; + + if (!cordycep.ReadCsi(argv[2])) { + LOG_ERROR("Can't read Cordycep database: {}", argv[2]); + return tool::BASIC_ERROR; + } + + LOG_INFO("Loaded"); + + LOG_INFO("Game Id .. {} (0x{:x})", compatibility::scobalula::csi::CordycepGameName(cordycep.gameId), (uint64_t)cordycep.gameId); + LOG_INFO("Pools .... 0x{:x}", cordycep.poolsAddress); + LOG_INFO("Strings .. 0x{:x}", cordycep.stringsAddress); + LOG_INFO("Game dir . '{}'", cordycep.gameDir); + + return tool::OK; + } + + int dpcord(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{}; + + compatibility::scobalula::csi::CordycepProc cordycep{}; + + if (!cordycep.ReadCsi(db)) { + LOG_ERROR("Can't read Cordycep database: {}", db.string()); + return tool::BASIC_ERROR; + } + + int ret; + switch (cordycep.gameId) { + case compatibility::scobalula::csi::CG_MW6: + ret = jup::dpcordjup(proc, cordycep, argc, argv); + break; + default: + LOG_ERROR("Can't find dumper for game {} (0x{:x})", CordycepGameName(cordycep.gameId), (uint64_t)cordycep.gameId); + return tool::BASIC_ERROR; + } + return ret; + } + } + ADD_TOOL("csid", "compatibility", "", "Dump csi info", nullptr, csi_test); + ADD_TOOL("dpcord", "common", "", "Cordycep dump tool", L"Cordycep.CLI.exe", dpcord); +} \ No newline at end of file diff --git a/src/acts/tools/cw/dump.cpp b/src/acts/tools/cw/poolt9.cpp similarity index 65% rename from src/acts/tools/cw/dump.cpp rename to src/acts/tools/cw/poolt9.cpp index 6ce05a6..205ef8c 100644 --- a/src/acts/tools/cw/dump.cpp +++ b/src/acts/tools/cw/poolt9.cpp @@ -3,6 +3,7 @@ #include "tools/pool.hpp" #include "tools/gsc.hpp" #include "tools/cw/cw.hpp" +#include "tools/cw/poolt9.hpp" #include #include @@ -54,7 +55,7 @@ namespace { - struct StringTable { + struct StringTable { uint64_t name; int columnCount; int rowCount; @@ -71,7 +72,7 @@ namespace { uintptr_t offset; int size; }; - + FuncInfo func_info_cw[] = { { "GSC-Function-d7b8d60", 0xD7B8D60, 0x8 }, { "GSC-Function-deb19b0", 0xDEB19B0, 0x1C1 }, @@ -130,66 +131,66 @@ namespace { SB_ObjectsArray sbObjectsArray; }; - int dumppoolcw(Process& proc, int argc, const char* argv[]) { + int dumppoolcw(Process& proc, int argc, const char* argv[]) { - const char* outFile; - if (argc == 2) { - outFile = "scriptparsetree_cw"; - } - else { - outFile = argv[2]; - } + const char* outFile; + if (argc == 2) { + outFile = "scriptparsetree_cw"; + } + else { + outFile = argv[2]; + } - std::filesystem::create_directories(outFile); + std::filesystem::create_directories(outFile); - int gObjFileInfoCount[2]; + int gObjFileInfoCount[2]; - if (!proc.ReadMemory(gObjFileInfoCount, proc[0xF6F5FD0], sizeof(gObjFileInfoCount))) { - std::cerr << "Can't read gObjFileInfoCount\n"; - return tool::BASIC_ERROR; - } + if (!proc.ReadMemory(gObjFileInfoCount, proc[0xF6F5FD0], sizeof(gObjFileInfoCount))) { + std::cerr << "Can't read gObjFileInfoCount\n"; + return tool::BASIC_ERROR; + } - hashutils::ReadDefaultFile(); + hashutils::ReadDefaultFile(); - auto elements = std::make_unique(800ull * 2 * sizeof(tool::dump::T8ObjFileInfo)); + auto elements = std::make_unique(800ull * 2 * sizeof(tool::dump::T8ObjFileInfo)); - if (!proc.ReadMemory(&elements[0], proc[0xF6EC9D0], sizeof(elements[0]) * 800 * 2)) { - std::cerr << "Can't read gObjFileInfo\n"; - return tool::BASIC_ERROR; - } + if (!proc.ReadMemory(&elements[0], proc[0xF6EC9D0], sizeof(elements[0]) * 800 * 2)) { + std::cerr << "Can't read gObjFileInfo\n"; + return tool::BASIC_ERROR; + } - tool::gsc::T9GSCOBJ headerTmp{}; + tool::gsc::T9GSCOBJ headerTmp{}; - char namebuff[MAX_PATH + 10]; + char namebuff[MAX_PATH + 10]; std::cout << "dump using linked scripts\n"; - for (size_t i = 0; i < scriptinstance::SI_COUNT; i++) { - for (size_t j = 0; j < gObjFileInfoCount[i]; j++) { - auto& elem = elements[i * 800 + j]; - - if (!proc.ReadMemory(&headerTmp, elem.activeVersion, sizeof(headerTmp))) { - std::cerr << "Can't read elem at " << j << "\n"; - continue; - } - - auto file = std::make_unique(headerTmp.file_size); - - if (!proc.ReadMemory(&file[0], elem.activeVersion, headerTmp.file_size)) { - std::cerr << "Can't read file elem at " << j << "\n"; - continue; - } - - sprintf_s(namebuff, "%s/script_%llx.gscc", outFile, headerTmp.name); - - if (utils::WriteFile(namebuff, &file[0], headerTmp.file_size)) { - std::cout << "- " << hashutils::ExtractTmpScript(headerTmp.name) << " -> " << namebuff << "\n"; - } - else { - std::cerr << "Error when writting " << namebuff << "\n"; - } - } - } - + for (size_t i = 0; i < scriptinstance::SI_COUNT; i++) { + for (size_t j = 0; j < gObjFileInfoCount[i]; j++) { + auto& elem = elements[i * 800 + j]; + + if (!proc.ReadMemory(&headerTmp, elem.activeVersion, sizeof(headerTmp))) { + std::cerr << "Can't read elem at " << j << "\n"; + continue; + } + + auto file = std::make_unique(headerTmp.file_size); + + if (!proc.ReadMemory(&file[0], elem.activeVersion, headerTmp.file_size)) { + std::cerr << "Can't read file elem at " << j << "\n"; + continue; + } + + sprintf_s(namebuff, "%s/script_%llx.gscc", outFile, headerTmp.name); + + if (utils::WriteFile(namebuff, &file[0], headerTmp.file_size)) { + std::cout << "- " << hashutils::ExtractTmpScript(headerTmp.name) << " -> " << namebuff << "\n"; + } + else { + std::cerr << "Error when writting " << namebuff << "\n"; + } + } + } + XAssetPool sptPool{}; uintptr_t poolLoc = cw::ScanPool(proc); @@ -200,7 +201,7 @@ namespace { std::cerr << "Can't read SPT pool\n"; return tool::BASIC_ERROR; } - + auto entries = std::make_unique(sptPool.itemAllocCount); if (!proc.ReadMemory(&entries[0], sptPool.pool, sptPool.itemAllocCount * sizeof(entries[0]))) { @@ -235,49 +236,49 @@ namespace { } - return tool::OK; - } + return tool::OK; + } - const char* ReadTmpStr(const Process& proc, uintptr_t location) { - static char tmp_buff[0x1000]; + const char* ReadTmpStr(const Process& proc, uintptr_t location) { + static char tmp_buff[0x1000]; - if (proc.ReadString(tmp_buff, location, sizeof(tmp_buff)) < 0) { - sprintf_s(tmp_buff, "", location); - } - return tmp_buff; - } + if (proc.ReadString(tmp_buff, location, sizeof(tmp_buff)) < 0) { + sprintf_s(tmp_buff, "", location); + } + return tmp_buff; + } - int dpnamescw(Process& proc, int argc, const char* argv[]) { + int dpnamescw(Process& proc, int argc, const char* argv[]) { - auto loc = proc[0xD7C8D90]; + auto loc = proc[0xD7C8D90]; - XAssetPool pool{}; - char tmp_buff[0x50]; + XAssetPool pool{}; + char tmp_buff[0x50]; - int id = 0; + int id = 0; uintptr_t poolLoc = cw::ScanPool(proc); - std::cout << "id,name,itemSize,itemCount,itemAllocCount\n"; + std::cout << "id,name,itemSize,itemCount,itemAllocCount\n"; - while (true) { - auto addr = proc.ReadMemory(loc + id * 8); - if (!addr || proc.ReadString(tmp_buff, addr, sizeof(tmp_buff)) < 0) { - break; - } - if (!proc.ReadMemory(&pool, poolLoc + sizeof(pool) * id, sizeof(pool))) { - break; - } + while (true) { + auto addr = proc.ReadMemory(loc + id * 8); + if (!addr || proc.ReadString(tmp_buff, addr, sizeof(tmp_buff)) < 0) { + break; + } + if (!proc.ReadMemory(&pool, poolLoc + sizeof(pool) * id, sizeof(pool))) { + break; + } - std::cout << std::dec << id << "," << tmp_buff << "," << std::hex << pool.itemSize << "," << pool.itemCount << "," << pool.itemAllocCount << "\n"; + std::cout << std::dec << id << "," << tmp_buff << "," << std::hex << pool.itemSize << "," << pool.itemCount << "," << pool.itemAllocCount << "\n"; - id++; - } + id++; + } - return tool::OK; - } + return tool::OK; + } #pragma region ddl_dump @@ -433,7 +434,7 @@ namespace { currentShift = mbm.offset + mbm.bitSize; //if (opt.flags & DDL_OFFSET) { - utils::Padding(defout << "#offset 0x" << std::hex << currentShift << "\n", 1); + utils::Padding(defout << "#offset 0x" << std::hex << currentShift << "\n", 1); //} bool addSize = false; @@ -485,7 +486,7 @@ namespace { } defout << "\n"; //if (opt.flags & DDL_OFFSET) { - utils::Padding(defout, 1) << "#offset 0x" << std::hex << currentShift << "\n"; + utils::Padding(defout, 1) << "#offset 0x" << std::hex << currentShift << "\n"; //} } @@ -605,7 +606,7 @@ namespace { for (const auto& func : func_info_cw) { auto pool = std::make_unique(func.size); - + if (!proc.ReadMemory(&pool[0], proc[func.offset], sizeof(pool[0]) * func.size)) { std::cerr << "Can't read pool " << std::hex << func.offset << "\n"; continue; @@ -613,8 +614,8 @@ namespace { for (size_t i = 0; i < func.size; i++) { out - << "\n" - << func.pool << "," + << "\n" + << func.pool << "," << hashutils::ExtractTmp("function", pool[i].canonId) << "," << pool[i].min_args << "," << pool[i].max_args << "," @@ -929,437 +930,438 @@ namespace { return true; } +} +int cw::pool::pooltool(Process& proc, int argc, const char* argv[]) { + using namespace pool; + if (argc < 3) { + return tool::BAD_USAGE; + } + PoolOption opt; - int pooltool(Process& proc, int argc, const char* argv[]) { - using namespace pool; - if (argc < 3) { - return tool::BAD_USAGE; - } - PoolOption opt; + if (!opt.Compute(argv, 2, argc) || opt.m_help) { + opt.PrintHelp(std::cout); + return tool::OK; + } - if (!opt.Compute(argv, 2, argc) || opt.m_help) { - opt.PrintHelp(std::cout); - return tool::OK; - } + hashutils::SaveExtracted(opt.m_dump_hashmap != NULL); + hashutils::ReadDefaultFile(); - hashutils::SaveExtracted(opt.m_dump_hashmap != NULL); - hashutils::ReadDefaultFile(); + std::error_code ec; + std::filesystem::create_directories(opt.m_output, ec); - std::error_code ec; - std::filesystem::create_directories(opt.m_output, ec); + auto id = std::atoi(argv[2]); - auto id = std::atoi(argv[2]); + std::cout << std::hex << "pool id: " << id << "\n"; - std::cout << std::hex << "pool id: " << id << "\n"; + XAssetPool entry{}; - XAssetPool entry{}; + uintptr_t poolLoc = cw::ScanPool(proc); + if (!proc.ReadMemory(&entry, poolLoc + sizeof(entry) * id, sizeof(entry))) { + std::cerr << "Can't read pool entry\n"; + return tool::BASIC_ERROR; + } - uintptr_t poolLoc = cw::ScanPool(proc); - if (!proc.ReadMemory(&entry, poolLoc + sizeof(entry) * id, sizeof(entry))) { - std::cerr << "Can't read pool entry\n"; - return tool::BASIC_ERROR; - } + char outputName[256]; + sprintf_s(outputName, "%s/pool_%x", opt.m_output, id); + char dumpbuff[MAX_PATH + 10]; - char outputName[256]; - sprintf_s(outputName, "%s/pool_%x", opt.m_output, id); - char dumpbuff[MAX_PATH + 10]; + std::cout << std::hex + << "pool ........ " << entry.pool << "\n" + << "free head ... " << entry.freeHead << "\n" + << "item size ... " << entry.itemSize << "\n" + << "count ....... " << entry.itemCount << "\n" + << "alloc count . " << entry.itemAllocCount << "\n" + << "singleton ... " << (entry.isSingleton ? "true" : "false") << "\n" + ; - std::cout << std::hex - << "pool ........ " << entry.pool << "\n" - << "free head ... " << entry.freeHead << "\n" - << "item size ... " << entry.itemSize << "\n" - << "count ....... " << entry.itemCount << "\n" - << "alloc count . " << entry.itemAllocCount << "\n" - << "singleton ... " << (entry.isSingleton ? "true" : "false") << "\n" - ; + switch (id) { + case cw::ASSET_TYPE_STRINGTABLE: { - switch (id) { - case cw::ASSET_TYPE_STRINGTABLE: { + auto stPool = std::make_unique(entry.itemAllocCount); - auto stPool = std::make_unique(entry.itemAllocCount); + if (!proc.ReadMemory(&stPool[0], entry.pool, sizeof(stPool[0]) * entry.itemAllocCount)) { + std::cerr << "Can't read pool data\n"; + return tool::BASIC_ERROR; + } - if (!proc.ReadMemory(&stPool[0], entry.pool, sizeof(stPool[0]) * entry.itemAllocCount)) { - std::cerr << "Can't read pool data\n"; - return tool::BASIC_ERROR; - } + size_t readFile = 0; + for (size_t i = 0; i < entry.itemAllocCount; i++) { + const auto& e = stPool[i]; - size_t readFile = 0; - for (size_t i = 0; i < entry.itemAllocCount; i++) { - const auto& e = stPool[i]; + const auto size = e.columnCount * e.rowCount; - const auto size = e.columnCount * e.rowCount; + int test; + if (!e.values || (size && !proc.ReadMemory(&test, e.values, sizeof(test)))) { + continue; // check that we can read at least the cell + } - int test; - if (!e.values || (size && !proc.ReadMemory(&test, e.values, sizeof(test)))) { - continue; // check that we can read at least the cell - } + auto n = hashutils::ExtractPtr(e.name); - auto n = hashutils::ExtractPtr(e.name); + std::cout << std::dec << i << ": "; - std::cout << std::dec << i << ": "; + if (n) { + std::cout << n; + sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); + } + else { + std::cout << "file_" << std::hex << e.name << std::dec; + sprintf_s(dumpbuff, "%s/hashed/stringtables/file_%llx.csv", opt.m_output, e.name); - if (n) { - std::cout << n; - sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); - } - else { - std::cout << "file_" << std::hex << e.name << std::dec; - sprintf_s(dumpbuff, "%s/hashed/stringtables/file_%llx.csv", opt.m_output, e.name); + } - } + std::cout << " (columns: " << e.columnCount << ", rows:" << e.rowCount << "/" << std::hex << (entry.pool + i * sizeof(entry)) << std::dec << ") into " << dumpbuff; - std::cout << " (columns: " << e.columnCount << ", rows:" << e.rowCount << "/" << std::hex << (entry.pool + i * sizeof(entry)) << std::dec << ") into " << dumpbuff; + std::filesystem::path file(dumpbuff); + std::filesystem::create_directories(file.parent_path(), ec); - std::filesystem::path file(dumpbuff); - std::filesystem::create_directories(file.parent_path(), ec); + if (!std::filesystem::exists(file, ec)) { + readFile++; + std::cout << " (new)"; + } + std::cout << "\n"; - if (!std::filesystem::exists(file, ec)) { - readFile++; - std::cout << " (new)"; - } - std::cout << "\n"; + std::ofstream out{ file }; - std::ofstream out{ file }; + if (!out) { + std::cerr << "Can't open file " << file << "\n"; + continue; + } - if (!out) { - std::cerr << "Can't open file " << file << "\n"; - continue; - } + auto cell = std::make_unique(e.columnCount); - auto cell = std::make_unique(e.columnCount); + //e.cells + if (!(size)) { + out.close(); + continue; + } - //e.cells - if (!(size)) { + for (size_t i = 0; i < e.rowCount; i++) { + if (!proc.ReadMemory(&cell[0], e.values + sizeof(cell[0]) * e.columnCount * i, sizeof(cell[0]) * e.columnCount)) { + std::cerr << "can't read cells for " << dumpbuff << "\n"; out.close(); continue; } - - for (size_t i = 0; i < e.rowCount; i++) { - if (!proc.ReadMemory(&cell[0], e.values + sizeof(cell[0]) * e.columnCount * i, sizeof(cell[0]) * e.columnCount)) { - std::cerr << "can't read cells for " << dumpbuff << "\n"; - out.close(); - continue; + for (size_t j = 0; j < e.columnCount; j++) { + switch (cell[j].type) + { + case STC_TYPE_UNDEFINED: + out << "undefined"; + break; + case STC_TYPE_STRING: + out << ReadTmpStr(proc, cell[j].value.pointer_value); + break; + case STC_TYPE_INT: + out << cell[j].value.int_value; + break; + case STC_TYPE_FLOAT: + out << cell[j].value.float_value; + break; + case STC_TYPE_BOOL: + out << (cell[j].value.bool_value ? "true" : "false"); + break; + case STC_TYPE_HASHED7: + case STC_TYPE_HASHED8: + //out << cell[j].type; + case STC_TYPE_HASHED2: + out << "#" << hashutils::ExtractTmp("hash", cell[j].value.hash_value); + break; + default: + //out << "unk type: " << cell[j].type; + out << "?" << std::hex + << cell[j].value.hash_value + // << ':' << *reinterpret_cast(&cell[j].value[8]) + // << ':' << *reinterpret_cast(&cell[j].value[16]) + << std::dec; + break; } - for (size_t j = 0; j < e.columnCount; j++) { - switch (cell[j].type) - { - case STC_TYPE_UNDEFINED: - out << "undefined"; - break; - case STC_TYPE_STRING: - out << ReadTmpStr(proc, cell[j].value.pointer_value); - break; - case STC_TYPE_INT: - out << cell[j].value.int_value; - break; - case STC_TYPE_FLOAT: - out << cell[j].value.float_value; - break; - case STC_TYPE_BOOL: - out << (cell[j].value.bool_value ? "true" : "false"); - break; - case STC_TYPE_HASHED7: - case STC_TYPE_HASHED8: - //out << cell[j].type; - case STC_TYPE_HASHED2: - out << "#" << hashutils::ExtractTmp("hash", cell[j].value.hash_value); - break; - default: - //out << "unk type: " << cell[j].type; - out << "?" << std::hex - << cell[j].value.hash_value - // << ':' << *reinterpret_cast(&cell[j].value[8]) - // << ':' << *reinterpret_cast(&cell[j].value[16]) - << std::dec; - break; - } - if (j + 1 != e.columnCount) { - out << ","; - } + if (j + 1 != e.columnCount) { + out << ","; } - out << "\n"; } - out.close(); + out << "\n"; } - std::cout << "Dump " << readFile << " new file(s)\n"; - break; + out.close(); } - case cw::ASSET_TYPE_SCRIPTBUNDLE: { - auto pool = std::make_unique(entry.itemAllocCount); - - if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { - std::cerr << "Can't read pool data\n"; - return tool::BASIC_ERROR; - } - + std::cout << "Dump " << readFile << " new file(s)\n"; + break; + } + case cw::ASSET_TYPE_SCRIPTBUNDLE: { + auto pool = std::make_unique(entry.itemAllocCount); - std::filesystem::path progpath = utils::GetProgDir(); - std::filesystem::path dllfile = progpath / std::filesystem::path("acts-bocw-dll.dll"); - auto str = dllfile.string(); + if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { + std::cerr << "Can't read pool data\n"; + return tool::BASIC_ERROR; + } - std::cout << "dll location -> " << str << "\n"; - if (!proc.LoadDll(str.c_str())) { - std::cerr << "Can't inject dll\n"; - return tool::BASIC_ERROR; - } - std::cout << "dll injected, decrypting MT Buffer...\n"; + std::filesystem::path progpath = utils::GetProgDir(); + std::filesystem::path dllfile = progpath / std::filesystem::path("acts-bocw-dll.dll"); + auto str = dllfile.string(); - auto& DLL_DecryptMTBufferFunc = proc["acts-bocw-dll.dll"]["DLL_DecryptMTBuffer"]; + std::cout << "dll location -> " << str << "\n"; - if (!DLL_DecryptMTBufferFunc) { - std::cerr << "Can't find DLL_DecryptMTBuffer export\n"; - return tool::BASIC_ERROR; - } + if (!proc.LoadDll(str.c_str())) { + std::cerr << "Can't inject dll\n"; + return tool::BASIC_ERROR; + } + std::cout << "dll injected, decrypting MT Buffer...\n"; - auto thr = proc.Exec(DLL_DecryptMTBufferFunc.m_location, 0x100000); + auto& DLL_DecryptMTBufferFunc = proc["acts-bocw-dll.dll"]["DLL_DecryptMTBuffer"]; - if (!(thr == INVALID_HANDLE_VALUE || !thr)) { - WaitForSingleObject(thr, INFINITE); - CloseHandle(thr); - } - else { - std::cerr << "Can't create decryption thread\n"; - } - std::cout << "decrypted, dumping SB...\n"; + if (!DLL_DecryptMTBufferFunc) { + std::cerr << "Can't find DLL_DecryptMTBuffer export\n"; + return tool::BASIC_ERROR; + } - std::unordered_set strings{}; + auto thr = proc.Exec(DLL_DecryptMTBufferFunc.m_location, 0x100000); - size_t readFile = 0; - for (size_t i = 0; i < entry.itemAllocCount; i++) { - const auto& e = pool[i]; + if (!(thr == INVALID_HANDLE_VALUE || !thr)) { + WaitForSingleObject(thr, INFINITE); + CloseHandle(thr); + } + else { + std::cerr << "Can't create decryption thread\n"; + } + std::cout << "decrypted, dumping SB...\n"; + std::unordered_set strings{}; - if (e.hash < 0x1000000000000) { - continue; // probably a ptr - } - ReadSBName(proc, e.sbObjectsArray); + size_t readFile = 0; + for (size_t i = 0; i < entry.itemAllocCount; i++) { + const auto& e = pool[i]; - auto n = hashutils::ExtractPtr(e.hash); - std::cout << std::dec << i << ": "; + if (e.hash < 0x1000000000000) { + continue; // probably a ptr + } + ReadSBName(proc, e.sbObjectsArray); - if (n) { - std::cout << n; - sprintf_s(dumpbuff, "%s/scriptbundle/%s.json", opt.m_output, n); - } - else { - std::cout << "file_" << std::hex << e.hash << std::dec; - sprintf_s(dumpbuff, "%s/scriptbundle/file_%llx.json", opt.m_output, e.hash); + auto n = hashutils::ExtractPtr(e.hash); - } + std::cout << std::dec << i << ": "; - std::cout << " into " << dumpbuff; + if (n) { + std::cout << n; + sprintf_s(dumpbuff, "%s/scriptbundle/%s.json", opt.m_output, n); + } + else { + std::cout << "file_" << std::hex << e.hash << std::dec; + sprintf_s(dumpbuff, "%s/scriptbundle/file_%llx.json", opt.m_output, e.hash); + } - std::filesystem::path file(dumpbuff); - std::filesystem::create_directories(file.parent_path(), ec); + std::cout << " into " << dumpbuff; - if (!std::filesystem::exists(file, ec)) { - readFile++; - std::cout << " (new)"; - } - std::cout << "\n"; - std::ofstream out{ file }; + std::filesystem::path file(dumpbuff); + std::filesystem::create_directories(file.parent_path(), ec); - if (!out) { - std::cerr << "Can't open file " << file << "\n"; - continue; - } + if (!std::filesystem::exists(file, ec)) { + readFile++; + std::cout << " (new)"; + } + std::cout << "\n"; - ReadSBObject(proc, out, 0, e.sbObjectsArray, strings); + std::ofstream out{ file }; - out.close(); + if (!out) { + std::cerr << "Can't open file " << file << "\n"; + continue; } - std::ofstream outStr{ std::format("{}/scriptbundle_str.txt", opt.m_output) }; - if (outStr) { - for (const auto& st : strings) { - outStr << st << "\n"; - } - outStr.close(); - } + ReadSBObject(proc, out, 0, e.sbObjectsArray, strings); - std::cout << "Dump " << readFile << " new file(s)\n"; - break; + out.close(); } - case cw::ASSET_TYPE_DDL: { - struct DDLEntry { - uint64_t name; - uint64_t name2; - uintptr_t ddlDef; // DDLDef* - uint64_t pad[8]; - }; static_assert(sizeof(DDLEntry) == 0x58 && "bad DDLEntry size"); + std::ofstream outStr{ std::format("{}/scriptbundle_str.txt", opt.m_output) }; + if (outStr) { + for (const auto& st : strings) { + outStr << st << "\n"; + } + outStr.close(); + } + std::cout << "Dump " << readFile << " new file(s)\n"; + break; + } + case cw::ASSET_TYPE_DDL: { + struct DDLEntry { + uint64_t name; + uint64_t name2; + uintptr_t ddlDef; // DDLDef* + uint64_t pad[8]; + }; static_assert(sizeof(DDLEntry) == 0x58 && "bad DDLEntry size"); - auto pool = std::make_unique(entry.itemAllocCount); - if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { - std::cerr << "Can't read pool data\n"; - return tool::BASIC_ERROR; - } - char dumpbuff[MAX_PATH + 10]; - const size_t dumpbuffsize = sizeof(dumpbuff); - std::vector read{}; - size_t readFile = 0; - for (size_t i = 0; i < entry.itemAllocCount; i++) { - const auto& p = pool[i]; + auto pool = std::make_unique(entry.itemAllocCount); - auto* n = hashutils::ExtractPtr(p.name); - if (n) { - sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); - } - else { - sprintf_s(dumpbuff, "%s/hashed/ddl/file_%llx.ddl", opt.m_output, p.name); - } + if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { + std::cerr << "Can't read pool data\n"; + return tool::BASIC_ERROR; + } + char dumpbuff[MAX_PATH + 10]; + const size_t dumpbuffsize = sizeof(dumpbuff); + std::vector read{}; + size_t readFile = 0; - std::cout << "Writing DDL #" << std::dec << i << " -> " << dumpbuff << "\n"; + for (size_t i = 0; i < entry.itemAllocCount; i++) { + const auto& p = pool[i]; + auto* n = hashutils::ExtractPtr(p.name); + if (n) { + sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); + } + else { + sprintf_s(dumpbuff, "%s/hashed/ddl/file_%llx.ddl", opt.m_output, p.name); + } + std::cout << "Writing DDL #" << std::dec << i << " -> " << dumpbuff << "\n"; - std::filesystem::path file(dumpbuff); - std::filesystem::create_directories(file.parent_path(), ec); - std::ofstream defout{ file }; - if (!defout) { - std::cerr << "Can't open output file\n"; - continue; - } + std::filesystem::path file(dumpbuff); + std::filesystem::create_directories(file.parent_path(), ec); - ReadDDLDefEntry(proc, defout, p.ddlDef); + std::ofstream defout{ file }; - defout.close(); + if (!defout) { + std::cerr << "Can't open output file\n"; + continue; } - std::cout << "Dump " << readFile << " new file(s)\n"; - } - case cw::ASSET_TYPE_RAWFILE: - case cw::ASSET_TYPE_RAWTEXTFILE: - case cw::ASSET_TYPE_RAWFILEPREPROC: { - auto pool = std::make_unique(entry.itemAllocCount); + ReadDDLDefEntry(proc, defout, p.ddlDef); - if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { - std::cerr << "Can't read pool data\n"; - return tool::BASIC_ERROR; - } + defout.close(); + } - size_t readFile = 0; - for (size_t i = 0; i < entry.itemAllocCount; i++) { - const auto& e = pool[i]; + std::cout << "Dump " << readFile << " new file(s)\n"; + } + case cw::ASSET_TYPE_RAWFILE: + case cw::ASSET_TYPE_RAWTEXTFILE: + case cw::ASSET_TYPE_RAWFILEPREPROC: { + auto pool = std::make_unique(entry.itemAllocCount); - int test; - if (!e.buffer || (e.size && !proc.ReadMemory(&test, e.buffer, sizeof(test)))) { - continue; // check that we can read at least the data - } + if (!proc.ReadMemory(&pool[0], entry.pool, sizeof(pool[0]) * entry.itemAllocCount)) { + std::cerr << "Can't read pool data\n"; + return tool::BASIC_ERROR; + } - auto n = hashutils::ExtractPtr(e.name); + size_t readFile = 0; + for (size_t i = 0; i < entry.itemAllocCount; i++) { + const auto& e = pool[i]; - std::cout << std::dec << i << ": "; + int test; + if (!e.buffer || (e.size && !proc.ReadMemory(&test, e.buffer, sizeof(test)))) { + continue; // check that we can read at least the data + } - if (n) { - std::cout << n; - sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); - } - else { - std::cout << "file_" << std::hex << e.name << std::dec; - sprintf_s(dumpbuff, "%s/hashed/rawfile/file_%llx.raw", opt.m_output, e.name); + auto n = hashutils::ExtractPtr(e.name); - } + std::cout << std::dec << i << ": "; - std::cout << " into " << dumpbuff; + if (n) { + std::cout << n; + sprintf_s(dumpbuff, "%s/%s", opt.m_output, n); + } + else { + std::cout << "file_" << std::hex << e.name << std::dec; + sprintf_s(dumpbuff, "%s/hashed/rawfile/file_%llx.raw", opt.m_output, e.name); + } - std::filesystem::path file(dumpbuff); - std::filesystem::create_directories(file.parent_path(), ec); + std::cout << " into " << dumpbuff; - if (!std::filesystem::exists(file, ec)) { - readFile++; - std::cout << " (new)"; - } - std::cout << "... "; - if (!e.size) { - // empty file - if (!utils::WriteFile(file, "", 0)) { - std::cerr << "Can't write file\n"; - } - std::cout << " empty / dumped\n"; - continue; - } + std::filesystem::path file(dumpbuff); + std::filesystem::create_directories(file.parent_path(), ec); - auto buff = std::make_unique(e.size + 0x10); + if (!std::filesystem::exists(file, ec)) { + readFile++; + std::cout << " (new)"; + } + std::cout << "... "; - if (!proc.ReadMemory(&buff[0], e.buffer, e.size + 0x10)) { - std::cerr << "Can't read buffer\n"; - continue; + if (!e.size) { + // empty file + if (!utils::WriteFile(file, "", 0)) { + std::cerr << "Can't write file\n"; } + std::cout << " empty / dumped\n"; + continue; + } + auto buff = std::make_unique(e.size + 0x10); - // decrypt - byte* buffDecrypt{ &buff[0]}; - size_t size{ e.size }; - if (id != cw::ASSET_TYPE_RAWFILE) { - buffDecrypt = cw::DecryptRawBuffer(buffDecrypt); - size--; - } - + if (!proc.ReadMemory(&buff[0], e.buffer, e.size + 0x10)) { + std::cerr << "Can't read buffer\n"; + continue; + } - if (!utils::WriteFile(file, buffDecrypt, size)) { - std::cerr << "Can't write file\n"; - continue; - } - std::cout << " dumped\n"; + // decrypt + byte* buffDecrypt{ &buff[0]}; + size_t size{ e.size }; + if (id != cw::ASSET_TYPE_RAWFILE) { + buffDecrypt = cw::DecryptRawBuffer(buffDecrypt); + size--; } - std::cout << "Dump " << readFile << " new file(s)\n"; - break; - } - default: { - std::cout << "Item data\n"; - auto raw = std::make_unique(entry.itemSize * entry.itemAllocCount); - if (!proc.ReadMemory(&raw[0], entry.pool, entry.itemSize * entry.itemAllocCount)) { - std::cerr << "Can't read pool data\n"; - return tool::BASIC_ERROR; + + if (!utils::WriteFile(file, buffDecrypt, size)) { + std::cerr << "Can't write file\n"; + continue; } + std::cout << " dumped\n"; + } + std::cout << "Dump " << readFile << " new file(s)\n"; + break; + } + default: { + std::cout << "Item data\n"; - char dumpbuff[MAX_PATH + 10]; - for (size_t i = 0; i < entry.itemAllocCount; i++) { - sprintf_s(dumpbuff, "%s/rawpool/%d/%lld.json", opt.m_output, (int)id, i); + auto raw = std::make_unique(entry.itemSize * entry.itemAllocCount); - std::cout << "Element #" << std::dec << i << " -> " << dumpbuff << "\n"; + if (!proc.ReadMemory(&raw[0], entry.pool, entry.itemSize * entry.itemAllocCount)) { + std::cerr << "Can't read pool data\n"; + return tool::BASIC_ERROR; + } + char dumpbuff[MAX_PATH + 10]; + for (size_t i = 0; i < entry.itemAllocCount; i++) { + sprintf_s(dumpbuff, "%s/rawpool/%d/%lld.json", opt.m_output, (int)id, i); + std::cout << "Element #" << std::dec << i << " -> " << dumpbuff << "\n"; - std::filesystem::path file(dumpbuff); - std::filesystem::create_directories(file.parent_path(), ec); - std::ofstream defout{ file }; - if (!defout) { - std::cerr << "Can't open output file\n"; - continue; - } + std::filesystem::path file(dumpbuff); + std::filesystem::create_directories(file.parent_path(), ec); - tool::pool::WriteHex(defout, entry.pool + entry.itemSize * i, &raw[0] + (entry.itemSize * i), entry.itemSize, proc); + std::ofstream defout{ file }; - defout.close(); + if (!defout) { + std::cerr << "Can't open output file\n"; + continue; } - } - break; + tool::pool::WriteHex(defout, entry.pool + entry.itemSize * i, &raw[0] + (entry.itemSize * i), entry.itemSize, proc); + + defout.close(); } - return tool::OK; } + break; + } + + return tool::OK; +} +namespace { int dbgcw(Process& proc, int argc, const char* argv[]) { if (argc < 4) { return tool::BAD_USAGE; @@ -1579,13 +1581,14 @@ namespace { return tool::OK; } -} -ADD_TOOL("dpcw", "cw", " [input=pool_name] (output=pool_id)", "dump pool", L"BlackOpsColdWar.exe", pooltool); -ADD_TOOL("wpscw", "cw", "", "write pooled scripts (cw)", L"BlackOpsColdWar.exe", dumppoolcw); -ADD_TOOL("dpncw", "cw", "", "dump pool names (cw)", L"BlackOpsColdWar.exe", dpnamescw); -ADD_TOOL("dfuncscw", "cw", "", "dump function names (cw)", L"BlackOpsColdWar.exe", dfuncscw); -ADD_TOOL("dcfuncscw", "cw", "", "dump cmd names (cw)", L"BlackOpsColdWar.exe", dcfuncscw); -ADD_TOOL("dbgcw", "cw", " [inst]", "dbg (cw)", L"BlackOpsColdWar.exe", dbgcw); -ADD_TOOL("injectcw", "cw", " (script) (target) (replace)", "inject script (cw)", L"BlackOpsColdWar.exe", injectcw); -ADD_TOOL("injectcwalpha", "cw", " (script) (target) (replace)", "inject script (cw alpha)", L"COD2020.exe", injectcwalpha); -ADD_TOOL("dbgpcw", "cw", "", "dump bg pool (cw)", L"BlackOpsColdWar.exe", dbgp); \ No newline at end of file + using namespace cw::pool; + ADD_TOOL("dpcw", "cw", " [input=pool_name] (output=pool_id)", "Black Ops Cold War dump pool", L"BlackOpsColdWar.exe", pooltool); + ADD_TOOL("wpscw", "cw", "", "write pooled scripts (cw)", L"BlackOpsColdWar.exe", dumppoolcw); + ADD_TOOL("dpncw", "cw", "", "dump pool names (cw)", L"BlackOpsColdWar.exe", dpnamescw); + ADD_TOOL("dfuncscw", "cw", "", "dump function names (cw)", L"BlackOpsColdWar.exe", dfuncscw); + ADD_TOOL("dcfuncscw", "cw", "", "dump cmd names (cw)", L"BlackOpsColdWar.exe", dcfuncscw); + ADD_TOOL("dbgcw", "cw", " [inst]", "dbg (cw)", L"BlackOpsColdWar.exe", dbgcw); + ADD_TOOL("injectcw", "cw", " (script) (target) (replace)", "inject script (cw)", L"BlackOpsColdWar.exe", injectcw); + ADD_TOOL("injectcwalpha", "cw", " (script) (target) (replace)", "inject script (cw alpha)", L"COD2020.exe", injectcwalpha); + ADD_TOOL("dbgpcw", "cw", "", "dump bg pool (cw)", L"BlackOpsColdWar.exe", dbgp); +} \ No newline at end of file diff --git a/src/acts/tools/cw/poolt9.hpp b/src/acts/tools/cw/poolt9.hpp new file mode 100644 index 0000000..1d6cea6 --- /dev/null +++ b/src/acts/tools/cw/poolt9.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace cw::pool { + int pooltool(Process& proc, int argc, const char* argv[]); +} \ No newline at end of file diff --git a/src/acts/tools/gsc_opcodes_load.cpp b/src/acts/tools/gsc_opcodes_load.cpp index be65ee2..7faa2ee 100644 --- a/src/acts/tools/gsc_opcodes_load.cpp +++ b/src/acts/tools/gsc_opcodes_load.cpp @@ -1,7 +1,7 @@ #include #include #include "compatibility/scobalula_wni.hpp" -#include "compatibility/serious.hpp" +#include "compatibility/serious_db2.hpp" #include "tools/gsc_opcodes.hpp" #include "tools/gsc_opcodes_load.hpp" @@ -1413,7 +1413,7 @@ namespace tool::gsc::opcode { }); for (const auto& db : dbFiles) { - compatibility::serious::LoadVMDatabase(db); + compatibility::serious::db2::LoadVMDatabase(db); } std::filesystem::path actsopDirPath = utils::GetProgDir() / compatibility::scobalula::wni::packageIndexDir; diff --git a/src/acts/tools/hashes/compiled_files.cpp b/src/acts/tools/hashes/compiled_files.cpp index bc33d1a..39c1d50 100644 --- a/src/acts/tools/hashes/compiled_files.cpp +++ b/src/acts/tools/hashes/compiled_files.cpp @@ -44,7 +44,12 @@ namespace { // script bundle thing n = utils::MapString(utils::CloneString(n), [](char c) -> char { return c == ':' ? '/' : c; }); if (header.isSpecial) { - out = outputDir / header.type / n; + if (*header.preferedExtension) { + out = outputDir / header.type / utils::va("%s.%s", n, header.preferedExtension); + } + else { + out = outputDir / header.type / n; + } } else{ out = outputDir / n; } diff --git a/src/acts/tools/pool.cpp b/src/acts/tools/pool.cpp index 003b41c..44ec576 100644 --- a/src/acts/tools/pool.cpp +++ b/src/acts/tools/pool.cpp @@ -360,7 +360,7 @@ class PoolOption { m_dump_hashmap = args[++i]; } else if (*arg == '-') { - std::cerr << "Invalid argurment: " << arg << "!\n"; + std::cerr << "Invalid argument: " << arg << "!\n"; return false; } else { @@ -1314,8 +1314,8 @@ int pooltoolnames(Process& proc, int argc, const char* argv[]) { return tool::OK; } -int pooltool(Process& proc, int argc, const char* argv[]) { - using namespace pool; +int tool::pool::pooltool(Process& proc, int argc, const char* argv[]) { + using namespace ::pool; PoolOption opt; if (!opt.Compute(argv, 2, argc) || opt.m_help) { @@ -1388,14 +1388,14 @@ int pooltool(Process& proc, int argc, const char* argv[]) { std::unordered_set strings{}; XAssetPoolEntry entry{}; - auto ShouldHandle = [&proc, &opt, &outputName, &entry](pool::XAssetType id, bool isKnown = true) { + auto ShouldHandle = [&proc, &opt, &outputName, &entry](XAssetType id, bool isKnown = true) { if (!opt.m_dump_types[id] && !(isKnown && opt.m_dump_all_available)) { return false; } // set to false for the default loop opt.m_dump_types[id] = false; - std::cout << "pool: " << std::dec << pool::XAssetNameFromId(id) << " (" << (int)id << ")\n"; + std::cout << "pool: " << std::dec << XAssetNameFromId(id) << " (" << (int)id << ")\n"; if (!proc.ReadMemory(&entry, proc[offset::assetPool] + sizeof(entry) * id, sizeof(entry))) { std::cerr << "Can't read pool entry\n"; @@ -4617,16 +4617,16 @@ int pooltool(Process& proc, int argc, const char* argv[]) { - BGCacheInfo entryinfo[pool::BG_CACHE_TYPE_COUNT]{}; + BGCacheInfo entryinfo[BG_CACHE_TYPE_COUNT]{}; if (!proc.ReadMemory(&entryinfo[0], proc[0x4EC9A90], sizeof(entryinfo))) { std::cerr << "Can't read cache\n"; return tool::BASIC_ERROR; } - char nameInfo[pool::BG_CACHE_TYPE_COUNT][200] = {}; + char nameInfo[BG_CACHE_TYPE_COUNT][200] = {}; // buffer pool names - for (size_t i = 0; i < pool::BG_CACHE_TYPE_COUNT; i++) { + for (size_t i = 0; i < BG_CACHE_TYPE_COUNT; i++) { if (proc.ReadString(nameInfo[i], entryinfo[i].name, sizeof(nameInfo[i])) < 0) { std::cerr << "Can't read bgcache info names\n"; return tool::BASIC_ERROR; @@ -4690,7 +4690,7 @@ int pooltool(Process& proc, int argc, const char* argv[]) { auto& p2 = defs[i]; defout << "\n" - << (p2.type >= 0 && p2.type < pool::BG_CACHE_TYPE_COUNT ? nameInfo[p2.type] : "") << "," + << (p2.type >= 0 && p2.type < BG_CACHE_TYPE_COUNT ? nameInfo[p2.type] : "") << "," << hashutils::ExtractTmp("hash", p2.name) << "," << std::hex << p2.string_count << std::flush; @@ -6374,8 +6374,7 @@ int dbgp(Process& proc, int argc, const char* argv[]) { return tool::OK; } - -ADD_TOOL("dp", "bo4", " [pool]+", "dump pool", L"BlackOps4.exe", pooltool); +ADD_TOOL("dpbo4", "bo4", " [pool]+", "Black Ops 4 dump pool", L"BlackOps4.exe", pooltool); ADD_TOOL("dpn", "bo4", "", "dump pool names", L"BlackOps4.exe", pooltoolnames); ADD_TOOL("dbgcache", "bo4", "", "dump bg cache", L"BlackOps4.exe", dumpbgcache); ADD_TOOL("dbmtstrs", "bo4", "", "dump mt strings", L"BlackOps4.exe", dbmtstrs); diff --git a/src/acts/tools/pool.hpp b/src/acts/tools/pool.hpp index 6959dfd..2fd7dd2 100644 --- a/src/acts/tools/pool.hpp +++ b/src/acts/tools/pool.hpp @@ -15,6 +15,7 @@ namespace tool::pool { if (v) return v; return defaultValue; } + int pooltool(Process& proc, int argc, const char* argv[]); struct XHash { uint64_t name; diff --git a/src/acts/tools/sp23/jup_dump.cpp b/src/acts/tools/sp23/jup_dump.cpp deleted file mode 100644 index 3114b43..0000000 --- a/src/acts/tools/sp23/jup_dump.cpp +++ /dev/null @@ -1,686 +0,0 @@ -#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/tools.cpp b/src/acts/tools/tools.cpp index 224acf6..7fa7673 100644 --- a/src/acts/tools/tools.cpp +++ b/src/acts/tools/tools.cpp @@ -10,19 +10,19 @@ std::map& tool::tools() { std::map& tool::toolsCategories() { static std::map map{}; if (map.empty()) { - map["acts"] = { "acts", "acts tools", true, {} }; - map["lib"] = { "lib", "acts lib tools and tests", true, {} }; + map["acts"] = { "acts", "ACTS tools", true, {} }; + map["lib"] = { "lib", "ACTS lib tools and tests", true, {} }; map["gsc"] = { "gsc", "GSC utilities", true, {} }; - map["hash"] = { "hash", "hash utilities", true, {} }; + map["hash"] = { "hash", "Hash utilities", true, {} }; map["bo4"] = { "bo4", "Black Ops 4 tools", true, {} }; map["cw"] = { "cw", "Black Ops Cold War tools", true, {} }; map["mwiii"] = { "mwiii", "Modern Warfare III tools", true, {} }; map["bo3"] = { "bo3", "Black Ops 3 tools", true, {} }; map["ps4"] = { "ps4", "PS4 tools", true, {} }; - map["common"] = { "common", "common tools", true, {} }; - map["dev"] = { "dev", "dev tools", true, {} }; - map["compatibility"] = { "compatibility", "compatibility tools", true, {} }; - map["fastfile"] = { "fastfile", "fastfile tools", true, {} }; + map["common"] = { "common", "Common tools", true, {} }; + map["dev"] = { "dev", "Dev tools", true, {} }; + map["compatibility"] = { "compatibility", "Compatibility tools", true, {} }; + map["fastfile"] = { "fastfile", "Fastfile tools", true, {} }; map["lua"] = { "lua", "LUA tools", true, {} }; } @@ -55,15 +55,15 @@ tool::toolfunctiondata::toolfunctiondata(const char* name, const char* category, } bool tool::toolfunctiondata::operator!() const { - return m_name == NULL; + return !m_name; } bool tool::toolfunctiondata::operatorbool() const { - return m_name != NULL; + return !m_name; } const tool::toolfunctiondata& tool::findtool(const char* name) { - static tool::toolfunctiondata invalid{ NULL, NULL, NULL, NULL, NULL, NULL }; + static tool::toolfunctiondata invalid{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; auto& tls = tools(); @@ -255,7 +255,7 @@ namespace { auto it = info.opcodemappltlookup.find(plt); if (it != info.opcodemappltlookup.end()) { - for (auto& [op, reg] : it->second) { + for (const auto& [op, reg] : it->second) { count += reg.size(); } } diff --git a/src/shared/filesystem/afs.cpp b/src/shared/filesystem/afs.cpp new file mode 100644 index 0000000..d3bc9f8 --- /dev/null +++ b/src/shared/filesystem/afs.cpp @@ -0,0 +1,79 @@ +#include +#include "afs.hpp" +#include "utils.hpp" + +namespace filesystem::afs { + ActsFileSystem::ActsFileSystem(byte* data, size_t len) : data(data) { + ActsFileSystemHeader& header = *(ActsFileSystemHeader*)data; + + if (*(uint64_t*)header.magic != AFS_MAGIC) { + throw std::runtime_error(utils::va("Invalid magic: 0x%llx", *(uint64_t*)header.magic)); + } + + if (header.fileCount && header.fileTable + (size_t)header.fileCount * header.fileItemSize > len) { + throw std::runtime_error(utils::va("File table too big: 0x%llx > 0x%llx", header.fileTable + (size_t)header.fileCount * header.fileItemSize, len)); + } + + for (size_t i = 0; i < header.fileCount; i++) { + ActsFileSystemFile* file = (ActsFileSystemFile*)(header.magic + header.fileTable + i * header.fileItemSize); + + uint64_t nameoffset = reinterpret_cast(file->name); + + if (nameoffset > len) { + throw std::runtime_error(utils::va("Invalid name offset for file %lld: 0x%llx", i, nameoffset)); + } + uint64_t offset = reinterpret_cast(file->offset); + if (offset + file->size > len) { + throw std::runtime_error(utils::va("Invalid file offset for file %lld: 0x%llx(0x%llx)", i, offset, file->size)); + } + + + // link file + file->name = (const char*)(header.magic + nameoffset); + file->offset = header.magic + offset; + + + files[file->name] = file; + } + } + ActsFileSystemFile* ActsFileSystem::QueryFS(const std::string& path) { + auto it = files.find(path); + + if (it == files.end()) return nullptr; + + return it->second; + } + + ActsFileSystemWriter::ActsFileSystemWriter() { + utils::Allocate(data, sizeof(ActsFileSystemHeader)); + } + + void ActsFileSystemWriter::WriteFile(const char* name, void* file, size_t len) { + if (files.size() == INT32_MAX) throw std::runtime_error("too many files for this datastructure"); + size_t dataOffset = data.size(); + utils::Allocate(data, len); + memcpy(data.data() + dataOffset, file, len); + size_t nameOffset = data.size(); + utils::WriteString(data, name); + files.emplace_back(nameOffset, dataOffset, len); + } + + void ActsFileSystemWriter::Save(const std::filesystem::path& output) { + ActsFileSystemHeader& header = *(ActsFileSystemHeader*)data.data(); + *(uint64_t*)(header.magic) = AFS_MAGIC; + header.fileCount = (uint32_t)files.size(); + header.fileItemSize = sizeof(ActsFileSystemFilePre); + header.fileTable = data.size(); + + std::ofstream out{ output, std::ios::binary }; + + if (!out) { + std::string fn = output.string(); + throw std::runtime_error(utils::va("Can't open file '%s'", fn.c_str())); + } + + out.write((const char*)data.data(), data.size()); + out.write((const char*)files.data(), files.size() * sizeof(files[0])); + out.close(); + } +} \ No newline at end of file diff --git a/src/shared/filesystem/afs.hpp b/src/shared/filesystem/afs.hpp new file mode 100644 index 0000000..e418086 --- /dev/null +++ b/src/shared/filesystem/afs.hpp @@ -0,0 +1,49 @@ +#pragma once + +namespace filesystem::afs { + + constexpr uint64_t AFS_MAGIC = 0x0d0AF5008010; + + struct ActsFileSystemFilePre { + uint64_t name; + uint64_t offset; + uint64_t size; + }; + + struct ActsFileSystemFile { + const char* name; + byte* offset; + uint64_t size; + }; + + struct ActsFileSystemHeader { + byte magic[sizeof(uint64_t)]; + uint64_t fileTable{}; + uint32_t fileCount{}; + uint32_t fileItemSize{}; + }; + + class ActsFileSystem { + byte* data; + + std::unordered_map files{}; + public: + ActsFileSystem(byte* data, size_t len); + + ActsFileSystemFile* QueryFS(const std::string& path); + }; + + class ActsFileSystemWriter { + std::vector data{}; + std::vector files{}; + + public: + ActsFileSystemWriter(); + + void WriteFile(const char* name, void* file, size_t len); + inline void WriteFile(const std::string& name, void* file, size_t len) { + WriteFile(name.c_str(), file, len); + } + void Save(const std::filesystem::path& output); + }; +} \ No newline at end of file