├── UTTDumper ├── include │ ├── init.h │ ├── common_string.h │ ├── dumper.h │ ├── version.h │ ├── engine.h │ ├── rtti.h │ ├── info.h │ ├── native_object.h │ ├── transfer.h │ ├── utils.h │ ├── typetree.h │ └── md4.h ├── lib │ ├── version.cpp │ ├── common_string.cpp │ ├── init.cpp │ ├── windll.cpp │ ├── native_object.cpp │ ├── libso.cpp │ ├── info.cpp │ ├── engine.cpp │ ├── rtti.cpp │ ├── typetree.cpp │ └── dumper.cpp ├── config.toml ├── CMakeLists.txt └── .editorconfig ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── CMakePresets.json └── .gitignore /UTTDumper/include/init.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int init(std::filesystem::path path); -------------------------------------------------------------------------------- /UTTDumper/include/common_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace UTTD::Unity { 8 | class CommonString { 9 | public: 10 | CommonString(char* ptr); 11 | 12 | std::string_view string(size_t offset) const; 13 | 14 | std::map strings() const { return m_strings; } 15 | std::pair range() const { return std::make_pair(m_begin, m_end); } 16 | private: 17 | char* m_begin; 18 | char* m_end; 19 | std::map m_strings; 20 | }; 21 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : Top-level CMake project file, do global configuration 2 | # and include sub-projects here. 3 | # 4 | cmake_minimum_required (VERSION 3.20) 5 | 6 | # Enable Hot Reload for MSVC compilers if supported. 7 | if (POLICY CMP0141) 8 | cmake_policy(SET CMP0141 NEW) 9 | set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") 10 | endif() 11 | 12 | project ("UTTDumper") 13 | 14 | # Include sub-projects. 15 | add_subdirectory ("UTTDumper") -------------------------------------------------------------------------------- /UTTDumper/lib/version.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace UTTD::Unity { 8 | Version::Version(const char* string) { 9 | char dot = 0; 10 | std::stringstream ss(string); 11 | ss >> major >> dot >> minor >> dot >> patch >> type >> build; 12 | } 13 | 14 | std::ostream& operator<<(std::ostream& os, const Version& version) { 15 | return os << version.major << '.' << version.minor << '.' << version.patch << version.type << version.build; 16 | } 17 | 18 | std::string Version::str() const { 19 | std::ostringstream ss; 20 | ss << *this; 21 | return std::move(ss).str(); 22 | } 23 | } -------------------------------------------------------------------------------- /UTTDumper/lib/common_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace UTTD::Unity { 7 | 8 | CommonString::CommonString(char* ptr) : m_begin(ptr), m_end(ptr), m_strings() { 9 | size_t length, offset = 0, count = 0; 10 | while ((length = strlen(m_begin + offset))) { 11 | m_strings[offset] = std::string_view{ m_begin + offset }; 12 | offset += length + 1; 13 | count++; 14 | } 15 | 16 | m_end = m_begin + offset; 17 | 18 | std::cout << std::format("Found {0} common strings.", count) << std::endl; 19 | } 20 | 21 | std::string_view CommonString::string(size_t offset) const { 22 | return m_strings.contains(offset) ? m_strings.at(offset) : std::string_view{}; 23 | } 24 | } -------------------------------------------------------------------------------- /UTTDumper/include/dumper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace UTTD { 8 | class Dumper { 9 | public: 10 | Dumper(const Engine& engine) : m_engine(engine) {} 11 | 12 | void execute(); 13 | private: 14 | void dumpStructData(Unity::TransferInstruction transfer) const; 15 | void dumpStruct(Unity::TransferInstruction transfer) const; 16 | void dumpStringData(const Unity::CommonString& commonString) const; 17 | void dumpClassesJson() const; 18 | void dumpHashesJson() const; 19 | void dumpInfoJson() const; 20 | void dumpRTTI() const; 21 | 22 | const Engine& m_engine; 23 | std::unique_ptr m_info; 24 | }; 25 | 26 | static void dumpNodes(const InfoNode& node, std::ofstream& stream); 27 | static void dumpBinary(const Unity::ITypeTree& tree, std::ofstream& stream); 28 | } -------------------------------------------------------------------------------- /UTTDumper/lib/init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int init(std::filesystem::path path) { 10 | try { 11 | UTTD::Engine engine{ path }; 12 | engine.parse(); 13 | if (engine.options().delay > 0) { 14 | std::cout << std::format("Waiting for {0} seconds...", engine.options().delay) << std::endl; 15 | std::this_thread::sleep_for(std::chrono::seconds(engine.options().delay)); 16 | } 17 | if (!engine.initialize()) { 18 | std::cout << "Unable to initialize engine, aborting..." << std::endl; 19 | return 1; 20 | } 21 | 22 | UTTD::Dumper dumper{ engine }; 23 | dumper.execute(); 24 | } 25 | catch (const std::exception& e) { 26 | std::cout << "Error while dumping..." << std::endl; 27 | std::cout << e.what() << std::endl; 28 | std::cout << "Aborting..." << std::endl; 29 | } 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Razmoth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UTTDumper/include/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace UTTD::Unity { 6 | struct MemLabelId { 7 | int32_t identifier; 8 | }; 9 | 10 | struct Version { 11 | int major; 12 | int minor; 13 | int patch; 14 | char type; 15 | int build; 16 | 17 | Version(const char* string); 18 | constexpr Version(int32_t major, int32_t minor, int32_t patch, char type, int32_t build) : 19 | major(major), 20 | minor(minor), 21 | patch(patch), 22 | type(type), 23 | build(build) { 24 | 25 | } 26 | 27 | bool operator==(const Version& other) const = default; 28 | std::strong_ordering operator<=>(const Version& other) const { 29 | if (major != other.major) 30 | return major <=> other.major; 31 | 32 | if (minor != other.minor) 33 | return minor <=> other.minor; 34 | 35 | if (patch != other.patch) 36 | return patch <=> other.patch; 37 | 38 | if (type != other.type) 39 | return type <=> other.type; 40 | 41 | return major <=> other.major; 42 | } 43 | 44 | friend std::ostream& operator<<(std::ostream& os, const Version& version); 45 | 46 | std::string str() const; 47 | }; 48 | 49 | template 50 | static constexpr Version s_version = Version{ major, minor, patch, type, build }; 51 | } -------------------------------------------------------------------------------- /UTTDumper/config.toml: -------------------------------------------------------------------------------- 1 | # How to use: 2 | # - Update [engine]. 3 | # - Define new key []. 4 | # - Update values of fields. 5 | # - Update name in [engine]. 6 | # 7 | # Values: 8 | # delay: seconds to wait before dumping. 9 | # binary: name of binary to dump from. 10 | # output_dir: output folder (defaults to "cwd/|data/data//" if empty). 11 | # transfer: TransferInstruction flags (check transfer.h for possible values). 12 | # json_dump: Enable json output (classes.json/info.json). 13 | # text_dump: Enable text files output (RTTI.dump/structs.dump). 14 | # binary_dump: Enable binary files output (strings.dat/structs.dat). 15 | # exclude: List of types to exclude while dumping. 16 | # version: GetUnityBuildFullVersion RVA or string (ex '2017.3.4f1'). 17 | # common_strings: Unity::CommonString begin RVA. 18 | # rtti: RTTI::ms_runtimeTypes RVA. 19 | # type_tree_ctor: TypeTree::TypeTree RVA. 20 | # type_tree: TypeTreeCache::GetTypeTree or GenerateTypeTree RVA (TypeTreeCache::GetTypeTree: version > 2019.x.x else GenerateTypeTree). 21 | # produce: Object::Produce RVA. 22 | 23 | [engine] 24 | name = "dummy" 25 | 26 | [dummy] 27 | delay = 0 28 | binary = "UnityPlayer.dll" 29 | output_dir = "" 30 | transfer = 256 31 | json_dump = true 32 | text_dump = true 33 | binary_dump = true 34 | exclude = [1, 2, 3, 4] 35 | version = 0xF07E30 36 | common_strings = 0x167EA90 37 | rtti = 0x19E0A00 38 | type_tree_ctor = 0x90FB90 39 | type_tree = 0x9221E0 40 | produce = 0x58E370 -------------------------------------------------------------------------------- /UTTDumper/include/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace UTTD { 12 | struct Options { 13 | std::string name; 14 | uint32_t delay; 15 | std::string binary; 16 | std::string outputDirectory; 17 | Unity::TransferInstruction transfer = Unity::TransferInstruction::SerializeGameRelease; 18 | bool jsonDump = true; 19 | bool textDump = true; 20 | bool binaryDump = true; 21 | std::vector exclude; 22 | uintptr_t version = 0; 23 | uintptr_t commonStrings = 0; 24 | uintptr_t rtti = 0; 25 | uintptr_t typeTreeCtor = 0; 26 | uintptr_t typeTree = 0; 27 | uintptr_t produce = 0; 28 | }; 29 | 30 | class Engine { 31 | protected: 32 | static constexpr std::string_view filename = "config.toml"; 33 | 34 | public: 35 | Engine(std::filesystem::path path) : 36 | m_path(path) { 37 | 38 | } 39 | 40 | void parse(); 41 | bool initialize(); 42 | 43 | const Options& options() const { return m_options; } 44 | const Unity::Version& version() const { return *m_version; } 45 | const Unity::CommonString& commonString() const { return *m_commonString; } 46 | const Unity::RTTI& rtti() const { return *m_rtti; } 47 | const Unity::TypeTree& typeTree() const { return *m_typeTree; } 48 | const Unity::NativeObject& nativeObject() const { return *m_nativeObject; } 49 | private: 50 | Options m_options; 51 | std::filesystem::path m_path; 52 | 53 | std::unique_ptr m_version; 54 | std::unique_ptr m_commonString; 55 | std::unique_ptr m_rtti; 56 | std::unique_ptr m_typeTree; 57 | std::unique_ptr m_nativeObject; 58 | }; 59 | } -------------------------------------------------------------------------------- /UTTDumper/include/rtti.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace UTTD::Unity { 10 | class IRTTI { 11 | public: 12 | virtual std::shared_ptr base() const = 0; 13 | virtual std::string_view name() const = 0; 14 | virtual std::string_view nameSpace() const = 0; 15 | virtual std::string_view module() const = 0; 16 | virtual uint32_t typeID() const = 0; 17 | virtual int32_t size() const = 0; 18 | virtual uint32_t typeIndex() const = 0; 19 | virtual uint32_t descendantCount() const = 0; 20 | virtual bool isAbstract() const = 0; 21 | virtual bool isSealed() const = 0; 22 | virtual bool isEditorOnly() const = 0; 23 | virtual bool isStripped() const = 0; 24 | virtual void* attributes() const = 0; 25 | virtual uint64_t attributeCount() const = 0; 26 | virtual std::vector>& derived() = 0; 27 | virtual void* ptr() = 0; 28 | 29 | std::string fullName() const { 30 | if (nameSpace().empty() || name().empty()) { 31 | return std::string(name()); 32 | } 33 | else { 34 | return std::format("{0}.{1}", nameSpace(), name()); 35 | } 36 | } 37 | }; 38 | 39 | class RTTI { 40 | protected: 41 | static const int16_t MaxRuntimeTypeId = 2000; 42 | 43 | typedef void* ClassIDToRTTI(int32_t classID); 44 | 45 | struct Info { 46 | int32_t count; 47 | void* first; 48 | }; 49 | public: 50 | RTTI(void* ptr, const Version& version) : 51 | m_ptr(ptr), 52 | m_version(version) { 53 | 54 | } 55 | 56 | bool initialize(); 57 | const std::vector>& types() const { return m_types; } 58 | private: 59 | void* m_ptr; 60 | const Version& m_version; 61 | std::vector> m_types; 62 | }; 63 | 64 | static std::shared_ptr s_RTTI(void* ptr, const Version& version); 65 | } -------------------------------------------------------------------------------- /UTTDumper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : CMake project for UTTDumper, include source and define 2 | # project specific logic here. 3 | # 4 | 5 | # Add source to this project's executable. 6 | set(HEADER_DIR "include") 7 | set(SOURCE_DIR "lib") 8 | set(SOURCE_FILES 9 | ${SOURCE_DIR}/info.cpp 10 | ${SOURCE_DIR}/rtti.cpp 11 | ${SOURCE_DIR}/init.cpp 12 | ${SOURCE_DIR}/dumper.cpp 13 | ${SOURCE_DIR}/engine.cpp 14 | ${SOURCE_DIR}/version.cpp 15 | ${SOURCE_DIR}/typetree.cpp 16 | ${SOURCE_DIR}/common_string.cpp 17 | ${SOURCE_DIR}/native_object.cpp 18 | ) 19 | 20 | if (WIN32) 21 | set(SOURCE_FILES ${SOURCE_FILES} ${SOURCE_DIR}/windll.cpp) 22 | else() 23 | set(SOURCE_FILES ${SOURCE_FILES} ${SOURCE_DIR}/libso.cpp) 24 | endif() 25 | 26 | add_library(UTTDumper SHARED ${SOURCE_FILES}) 27 | set_target_properties(UTTDumper PROPERTIES CXX_STANDARD 20) 28 | target_include_directories(UTTDumper PRIVATE ${HEADER_DIR}) 29 | target_compile_definitions(UTTDumper PRIVATE TOML_EXCEPTIONS=1) 30 | 31 | include(FetchContent) 32 | 33 | FetchContent_Declare( 34 | tomlplusplus 35 | GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git 36 | GIT_TAG v3.4.0 37 | ) 38 | 39 | FetchContent_Declare( 40 | nlohmann_json 41 | GIT_REPOSITORY https://github.com/nlohmann/json.git 42 | GIT_TAG v3.11.3 43 | ) 44 | 45 | FetchContent_MakeAvailable(tomlplusplus) 46 | FetchContent_MakeAvailable(nlohmann_json) 47 | 48 | target_link_libraries(UTTDumper PRIVATE tomlplusplus::tomlplusplus) 49 | target_link_libraries(UTTDumper PRIVATE nlohmann_json::nlohmann_json) 50 | 51 | if (ANDROID) 52 | find_library(loglib log) 53 | target_link_libraries(UTTDumper PRIVATE ${loglib}) 54 | target_compile_definitions(UTTDumper PRIVATE ANDROID_TAG="UTTDumper") 55 | endif() 56 | 57 | add_custom_command( 58 | TARGET UTTDumper 59 | COMMAND ${CMAKE_COMMAND} -E copy 60 | ${CMAKE_CURRENT_SOURCE_DIR}/config.toml 61 | $ 62 | ) -------------------------------------------------------------------------------- /UTTDumper/include/info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace UTTD { 11 | struct InfoString { 12 | size_t index; 13 | std::string_view string; 14 | 15 | static std::vector s_makeList(const Unity::CommonString& commonString); 16 | }; 17 | 18 | struct InfoNode { 19 | std::string type; 20 | std::string name; 21 | uint8_t level; 22 | int32_t byteSize; 23 | int32_t index; 24 | int16_t version; 25 | uint8_t nodeType; 26 | uint32_t meta; 27 | std::vector> subNodes; 28 | struct InfoNode* parent; 29 | 30 | InfoNode(const Unity::ITypeTreeNode& node); 31 | InfoNode(const Unity::ITypeTreeNode& node, InfoNode* parent); 32 | 33 | void hash(std::shared_ptr md4); 34 | 35 | static std::shared_ptr s_rootNode(const Unity::ITypeTree& typeTree); 36 | }; 37 | 38 | struct InfoClass { 39 | std::string name; 40 | std::string nameSpace; 41 | std::string fullName; 42 | std::string module; 43 | int32_t typeID; 44 | std::string base; 45 | std::vector derived; 46 | uint32_t descendantCount; 47 | int32_t size; 48 | uint32_t typeIndex; 49 | bool isAbstract; 50 | bool isSealed; 51 | bool isEditorOnly; 52 | bool isStripped; 53 | std::shared_ptr editorRootNode; 54 | std::shared_ptr releaseRootNode; 55 | 56 | InfoClass(Unity::IRTTI& rtti); 57 | 58 | static std::vector> s_makeList(const Engine& engine, Unity::TransferInstruction release, Unity::TransferInstruction editor); 59 | }; 60 | 61 | struct Info { 62 | std::string version; 63 | std::vector strings; 64 | std::vector> classes; 65 | 66 | Info(const Engine& engine, Unity::TransferInstruction release, Unity::TransferInstruction editor); 67 | }; 68 | } -------------------------------------------------------------------------------- /UTTDumper/lib/windll.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static std::streambuf* CinBuffer, *CoutBuffer, *CerrBuffer; 7 | static std::fstream ConsoleInput, ConsoleOutput, ConsoleError; 8 | 9 | static void RedirectIO() { 10 | CinBuffer = std::cin.rdbuf(); 11 | CoutBuffer = std::cout.rdbuf(); 12 | CerrBuffer = std::cerr.rdbuf(); 13 | 14 | ConsoleInput.open("CONIN$", std::ios::in); 15 | ConsoleOutput.open("CONOUT$", std::ios::out); 16 | ConsoleError.open("CONOUT$", std::ios::out); 17 | 18 | std::cin.rdbuf(ConsoleInput.rdbuf()); 19 | std::cout.rdbuf(ConsoleOutput.rdbuf()); 20 | std::cerr.rdbuf(ConsoleError.rdbuf()); 21 | } 22 | 23 | static void ResetIO() { 24 | ConsoleInput.close(); 25 | ConsoleOutput.close(); 26 | ConsoleError.close(); 27 | 28 | std::cin.rdbuf(CinBuffer); 29 | std::cout.rdbuf(CoutBuffer); 30 | std::cerr.rdbuf(CerrBuffer); 31 | 32 | CinBuffer = nullptr; 33 | CoutBuffer = nullptr; 34 | CerrBuffer = nullptr; 35 | } 36 | 37 | static void StartWin(LPVOID hModule) { 38 | AllocConsole(); 39 | RedirectIO(); 40 | { 41 | std::string path; 42 | path.resize(FILENAME_MAX, 0); 43 | auto path_size(GetModuleFileName(static_cast(hModule), &path.front(), FILENAME_MAX)); 44 | path.resize(path_size); 45 | 46 | init(path); 47 | 48 | std::cout << "Press any key to continue..." << std::endl; 49 | std::cin.get(); 50 | } 51 | ResetIO(); 52 | FreeConsole(); 53 | FreeLibraryAndExitThread(static_cast(hModule), 0); 54 | } 55 | 56 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { 57 | switch (ul_reason_for_call) 58 | { 59 | case DLL_PROCESS_ATTACH: 60 | CreateThread(NULL, 0, reinterpret_cast(StartWin), hModule, 0, NULL); 61 | break; 62 | case DLL_PROCESS_DETACH: 63 | case DLL_THREAD_ATTACH: 64 | case DLL_THREAD_DETACH: 65 | break; 66 | } 67 | return TRUE; 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UTTDumper 2 | A tool to help with Unity type trees 3 | 4 | Check the tutorial [here](https://gist.github.com/Modder4869/55df4f3f9fa585a0057b9765e7fe4c4f) (Thanks to Modder4869 for the tutorial) 5 | # How to use: 6 | - Update `[engine]`. 7 | - Define new key `[]`. 8 | - Update values of fields. 9 | - Update name in `[engine]`. 10 | # Values: 11 | - `delay`: seconds to wait before dumping. 12 | - `binary`: name of binary to dump from. 13 | - `output_dir`: output folder (defaults to "cwd/|data/data//" if empty). 14 | - `transfer`: `TransferInstruction` flags (check transfer.h for possible values). 15 | - `json_dump`: Enable json output (classes.json/info.json). 16 | - `text_dump`: Enable text files output (RTTI.dump/structs.dump). 17 | - `binary_dump`: Enable binary files output (strings.dat/structs.dat). 18 | - `exclude`: List of types to exclude while dumping. 19 | - `version`: `GetUnityBuildFullVersion` RVA or string (ex '2017.3.4f1'). 20 | - `common_strings`: `Unity::CommonString` begin RVA. 21 | - `rtti`: `RTTI::ms_runtimeTypes` RVA. 22 | - `type_tree_ctor`: `TypeTree::TypeTree` RVA. 23 | - `type_tree`: `TypeTreeCache::GetTypeTree` RVA for `version > 2019.x.x` else `GenerateTypeTree` RVA. 24 | - `produce`: `Object::Produce` RVA. 25 | # Example: 26 | ``` 27 | [engine] 28 | name = "dummy" 29 | 30 | [dummy] 31 | delay = 0 32 | binary = "UnityPlayer.dll" 33 | output_dir = "" 34 | transfer = 256 35 | json_dump = true 36 | text_dump = true 37 | binary_dump = true 38 | exclude = [1, 2, 3, 4] 39 | version = 0xF07E30 40 | common_strings = 0x167EA90 41 | rtti = 0x19E0A00 42 | type_tree_ctor = 0x90FB90 43 | type_tree = 0x9221E0 44 | produce = 0x58E370 45 | ``` 46 | # Special Thank to: 47 | - [DaZombieKiller](https://github.com/DaZombieKiller) 48 | - [ds5678](https://github.com/ds5678) 49 | - [Khang06](https://github.com/Khang06) 50 | - [Modder4869](https://github.com/Modder4869) 51 | - [Ladilib](https://github.com/ladilib/) 52 | - [Dimbreath](https://github.com/Dimbreath) 53 | 54 | If you find `UTTDumper` useful, you can leave a star 😄 55 | Thank you, looking forward for your feedback 56 | -------------------------------------------------------------------------------- /UTTDumper/include/native_object.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace UTTD::Unity { 9 | enum class CreationMode { 10 | Default = 0, 11 | FromNonMainThread = 1, 12 | DefaultNoLock = 2, 13 | }; 14 | 15 | enum class Hide { 16 | None, 17 | HideInHierarchy = 1 << 0, 18 | HideInInspector = 1 << 1, 19 | DontSaveInEditor = 1 << 2, 20 | NotEditable = 1 << 3, 21 | DontSaveInBuild = 1 << 4, 22 | DontUnloadUnusedAsset = 1 << 5, 23 | DontSave = 52, 24 | HideAndDontSave = 61 25 | }; 26 | 27 | class INativeObject { 28 | protected: 29 | template 30 | constexpr std::bitset range(std::bitset b) const 31 | { 32 | static_assert(R <= L && L <= N, "invalid bitrange"); 33 | b >>= R; 34 | b <<= (N - L + R); 35 | b >>= (N - L); 36 | return b; 37 | } 38 | public: 39 | virtual int32_t instanceID() const = 0; 40 | virtual MemLabelId memLabel() const = 0; 41 | virtual uint8_t temporary() const = 0; 42 | virtual Hide hide() const = 0; 43 | virtual bool isPersistent() const = 0; 44 | virtual uint32_t cachedTypeIndex() const = 0; 45 | virtual void* ptr() = 0; 46 | }; 47 | 48 | class NativeObject { 49 | protected: 50 | const MemLabelId MemLabel = MemLabelId{ 0x38 }; 51 | 52 | typedef void* Produce3_5(uint32_t typeID, int32_t instanceID, MemLabelId memLabel, CreationMode creationMode); 53 | typedef void* Produce5_5(void* typePtr, int32_t instanceID, MemLabelId memLabel, CreationMode creationMode); 54 | typedef void* Produce2017_2(void* typeInPtr, const void* typeOutPtr, int32_t instanceID, MemLabelId memLabel, CreationMode creationMode); 55 | public: 56 | NativeObject(void* ptr, const Version& version) : 57 | m_ptr(ptr), 58 | m_version(version) { 59 | 60 | } 61 | 62 | std::shared_ptr produce(IRTTI& rtti, int32_t instanceID, CreationMode creationMode) const; 63 | private: 64 | void* m_ptr; 65 | const Version& m_version; 66 | }; 67 | 68 | static std::shared_ptr s_nativeObject(void* ptr, const Version& version); 69 | } -------------------------------------------------------------------------------- /UTTDumper/lib/native_object.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace UTTD::Unity { 5 | class NativeObject_Unity5_0 : public INativeObject { 6 | struct Info { 7 | void* virtualFunctionTabel; 8 | int32_t instanceID; 9 | std::bitset<32> bits; 10 | }; 11 | 12 | public: 13 | NativeObject_Unity5_0(void* ptr) { 14 | if (!ptr) throw std::invalid_argument("empty object ptr !!"); 15 | m_info = static_cast(ptr); 16 | } 17 | 18 | int32_t instanceID() const { return m_info->instanceID; } 19 | MemLabelId memLabel() const { return MemLabelId{ (int32_t)range<0, 11>(m_info->bits).to_ulong() }; } 20 | uint8_t temporary() const { return (unsigned char)range<11, 12>(m_info->bits).to_ulong(); } 21 | Hide hide() const { return static_cast(range<12, 18>(m_info->bits).to_ulong()); } 22 | bool isPersistent() const { return range<18, 19>(m_info->bits).test(0); } 23 | uint32_t cachedTypeIndex() const { return range<19, 29>(m_info->bits).to_ulong(); } 24 | void* ptr() { return static_cast(m_info); } 25 | private: 26 | Info* m_info; 27 | }; 28 | 29 | std::shared_ptr NativeObject::produce(IRTTI& rtti, int32_t instanceID, CreationMode creationMode) const { 30 | if (rtti.isAbstract()) 31 | return nullptr; 32 | 33 | void* ptr = nullptr; 34 | if (m_version < s_version<3, 5>) { 35 | throw std::runtime_error("version not supported !!"); //TODO: add support ? 36 | } 37 | else if (m_version < s_version<5, 5>) { 38 | Produce3_5* produce = (Produce3_5*)m_ptr; 39 | ptr = produce(rtti.typeID(), instanceID, MemLabel, creationMode); 40 | } 41 | else if (m_version < s_version<2017, 2>) { 42 | Produce5_5* produce = (Produce5_5*)m_ptr; 43 | ptr = produce(rtti.ptr(), instanceID, MemLabel, creationMode); 44 | } 45 | else if (m_version < s_version<2023, 1, 0, 'a', 2>) { 46 | Produce2017_2* produce = (Produce2017_2*)m_ptr; 47 | ptr = produce(rtti.ptr(), rtti.ptr(), instanceID, MemLabel, creationMode); 48 | } 49 | 50 | return ptr == nullptr ? nullptr : s_nativeObject(ptr, m_version); 51 | } 52 | 53 | static std::shared_ptr s_nativeObject(void* ptr, const Version& version) { 54 | if (version < s_version<5, 0>) { 55 | throw std::runtime_error("version not supported !!"); //TODO: add support? 56 | } 57 | else { 58 | return std::make_shared(ptr); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /UTTDumper/include/transfer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace UTTD::Unity { 4 | enum class TransferInstruction { 5 | None, 6 | ReadWriteFromSerializedFile = 1 << 0, 7 | AssetMetaDataOnly = 1 << 1, 8 | HandleDrivenProperties = 1 << 2, 9 | LoadAndUnloadAssetsDuringBuild = 1 << 3, 10 | SerializeDebugProperties = 1 << 4, 11 | IgnoreDebugPropertiesForIndex = 1 << 5, 12 | BuildPlayerOnlySerializeBuildProperties = 1 << 6, 13 | IsCloningObject = 1 << 7, 14 | SerializeGameRelease = 1 << 8, 15 | SwapEndianess = 1 << 9, 16 | ResolveStreamedResourceSources = 1 << 10, 17 | DontReadObjectsFromDiskBeforeWriting = 1 << 11, 18 | SerializeMonoReload = 1 << 12, 19 | DontRequireAllMetaFlags = 1 << 13, 20 | SerializeForPrefabSystem = 1 << 14, 21 | WarnAboutLeakedObjects = 1 << 15, 22 | LoadPrefabAsScene = 1 << 16, 23 | SerializeCopyPasteTransfer = 1 << 17, 24 | EditorPlayMode = 1 << 18, 25 | BuildResourceImage = 1 << 19, 26 | SerializeEditorMinimalScene = 1 << 21, 27 | GenerateBakedPhysixMeshes = 1 << 22, 28 | ThreadedSerialization = 1 << 23, 29 | IsBuiltinResourcesFile = 1 << 24, 30 | PerformUnloadDependencyTracking = 1 << 25, 31 | DisableWriteTypeTree = 1 << 26, 32 | AutoreplaceEditorWindow = 1 << 27, 33 | DontCreateMonoBehaviourScriptWrapper = 1 << 28, 34 | SerializeForInspector = 1 << 29, 35 | SerializedAssetBundleVersion = 1 << 30, 36 | AllowTextSerialization = 1 << 31 37 | }; 38 | 39 | static inline TransferInstruction operator |(TransferInstruction lhs, TransferInstruction rhs) { 40 | using T = std::underlying_type_t ; 41 | return static_cast(static_cast(lhs) | static_cast(rhs)); 42 | } 43 | 44 | static inline TransferInstruction operator &(TransferInstruction lhs, TransferInstruction rhs) { 45 | using T = std::underlying_type_t ; 46 | return static_cast(static_cast(lhs) & static_cast(rhs)); 47 | } 48 | 49 | static inline TransferInstruction operator ~(TransferInstruction value) { 50 | using T = std::underlying_type_t ; 51 | return static_cast(~static_cast(value)); 52 | } 53 | 54 | static inline TransferInstruction& operator |=(TransferInstruction& lhs, TransferInstruction rhs) { 55 | lhs = lhs | rhs; 56 | return lhs; 57 | } 58 | 59 | static inline TransferInstruction& operator &=(TransferInstruction& lhs, TransferInstruction rhs) { 60 | lhs = lhs & rhs; 61 | return lhs; 62 | } 63 | } -------------------------------------------------------------------------------- /UTTDumper/lib/libso.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void main_thread() { 7 | Dl_info dl_info; 8 | if (dladdr((void*)main_thread, &dl_info) != 0) { 9 | std::filesystem::path path(dl_info.dli_fname); 10 | 11 | init(path); 12 | } 13 | } 14 | 15 | #ifdef ANDROID 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | class StreamBuf2Log : public std::streambuf { 22 | public: 23 | enum Type { OUT, ERR }; 24 | StreamBuf2Log(Type t) { 25 | this->setp(buffer, buffer + bufsize - 2); 26 | switch (t) { 27 | case OUT: 28 | PRIORITY = ANDROID_LOG_INFO; 29 | strcpy(TAG, ANDROID_TAG); 30 | break; 31 | case ERR: 32 | PRIORITY = ANDROID_LOG_ERROR; 33 | strcpy(TAG, ANDROID_TAG); 34 | break; 35 | } 36 | } 37 | 38 | private: 39 | int overflow(int c) { 40 | *this->pptr() = traits_type::to_char_type(c); 41 | pbump(1); 42 | sync(); 43 | return 0; 44 | } 45 | 46 | int sync() { 47 | int n = pptr() - pbase(); 48 | if (!n || (n == 1 && buffer[0] == '\n')) return 0; 49 | buffer[n] = '\0'; 50 | __android_log_write(PRIORITY, TAG, buffer); 51 | pbump(-n); 52 | return 0; 53 | } 54 | 55 | static constexpr int bufsize = 2048; 56 | char TAG[10]; 57 | android_LogPriority PRIORITY; 58 | char buffer[bufsize]; 59 | }; 60 | 61 | class StdStreamRedirector { 62 | private: 63 | StreamBuf2Log outbuf; 64 | StreamBuf2Log errbuf; 65 | public: 66 | StdStreamRedirector() : outbuf(StreamBuf2Log::OUT), errbuf(StreamBuf2Log::ERR) { 67 | std::cout.rdbuf(&outbuf); 68 | std::cerr.rdbuf(&errbuf); 69 | } 70 | ~StdStreamRedirector() { 71 | std::cout << std::flush; 72 | std::cerr << std::flush; 73 | } 74 | }; 75 | 76 | StdStreamRedirector* redirector; 77 | 78 | extern "C" jint JNIEXPORT JNI_OnLoad(JavaVM* vm, void* reserved) 79 | { 80 | redirector = new StdStreamRedirector(); 81 | 82 | JNIEnv* env = nullptr; 83 | if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_OK) { 84 | std::cout << "JavaEnv: " << env << std::endl; 85 | } 86 | 87 | std::thread(main_thread).detach(); 88 | 89 | return JNI_VERSION_1_6; 90 | } 91 | 92 | extern "C" void JNIEXPORT JNI_OnUnload(JavaVM* vm, void* reserved) 93 | { 94 | delete redirector; 95 | } 96 | 97 | #else 98 | 99 | static void __attribute__((constructor)) onLoad() { 100 | std::thread(main_thread).detach(); 101 | } 102 | 103 | static void __attribute__((destructor)) onUnload() { } 104 | 105 | #endif -------------------------------------------------------------------------------- /UTTDumper/include/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | NLOHMANN_JSON_NAMESPACE_BEGIN 11 | 12 | template 13 | struct adl_serializer> { 14 | template static void to_json(BasicJsonType& json_value, const std::shared_ptr& ptr) { 15 | if (ptr.get()) { 16 | json_value = *ptr; 17 | } 18 | else { 19 | json_value = nullptr; 20 | } 21 | } 22 | 23 | template static void from_json(const BasicJsonType& json_value, std::shared_ptr& ptr) { 24 | T inner_val = json_value.template get(); 25 | ptr = std::make_unique(std::move(inner_val)); 26 | } 27 | }; 28 | 29 | NLOHMANN_JSON_NAMESPACE_END 30 | 31 | namespace UTTD { 32 | template 33 | static std::vector sorted(const std::vector& vec) { return sorted(vec, [&vec](size_t a, size_t b) { return vec[a] < vec[b]; }); } 34 | 35 | template 36 | static std::vector sorted(const std::vector& vec, Fn predicate) { 37 | std::vector idx(vec.size()); 38 | std::iota(idx.begin(), idx.end(), 0); 39 | 40 | std::stable_sort(idx.begin(), idx.end(), predicate); 41 | 42 | return idx; 43 | } 44 | 45 | template 46 | bool contains(const std::vector& vec, T val) { return find(vec.begin(), vec.end(), val) != vec.end(); } 47 | 48 | template 49 | static std::ostream& operator<=(std::ostream& os, T const& value) { return os.write(reinterpret_cast(&value), sizeof(T)); } 50 | 51 | static inline void to_json(nlohmann::ordered_json& json, const InfoString& type) { 52 | json["Index"] = type.index; 53 | json["String"] = type.string; 54 | } 55 | 56 | static inline void to_json(nlohmann::ordered_json& json, const InfoNode& type) { 57 | json["TypeName"] = type.type; 58 | json["Name"] = type.name; 59 | json["Level"] = type.level; 60 | json["ByteSize"] = type.byteSize; 61 | json["Index"] = type.index; 62 | json["Version"] = type.version; 63 | json["TypeFlags"] = type.nodeType; 64 | json["MetaFlag"] = type.meta; 65 | json["SubNodes"] = type.subNodes; 66 | } 67 | 68 | static inline void to_json(nlohmann::ordered_json& json, const InfoClass& type) { 69 | json["Name"] = type.name; 70 | json["Namespace"] = type.nameSpace; 71 | json["FullName"] = type.fullName; 72 | json["Module"] = type.module; 73 | json["TypeID"] = type.typeID; 74 | json["Base"] = type.base; 75 | json["Derived"] = type.derived; 76 | json["DescendantCount"] = type.descendantCount; 77 | json["Size"] = type.size; 78 | json["TypeIndex"] = type.typeIndex; 79 | json["IsAbstract"] = type.isAbstract; 80 | json["IsSealed"] = type.isSealed; 81 | json["IsEditorOnly"] = type.isEditorOnly; 82 | json["IsStripped"] = type.isStripped; 83 | json["EditorRootNode"] = type.editorRootNode; 84 | json["ReleaseRootNode"] = type.releaseRootNode; 85 | } 86 | 87 | static inline void to_json(nlohmann::ordered_json& json, const Info& type) { 88 | json["Version"] = type.version; 89 | json["Strings"] = type.strings; 90 | json["Classes"] = type.classes; 91 | } 92 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - '**/LICENSE' 9 | - '**/README.md' 10 | - '**/build.yml' 11 | - '**/.gitignore' 12 | - '**/.gitattributes' 13 | workflow_dispatch: 14 | 15 | jobs: 16 | windows: 17 | runs-on: windows-latest 18 | 19 | strategy: 20 | matrix: 21 | architecture: [x86, x64] 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup MSVC 28 | uses: TheMrMilchmann/setup-msvc-dev@v3 29 | with: 30 | arch: ${{ matrix.architecture }} 31 | 32 | - name: Setup Ninja 33 | shell: bash 34 | run: choco install ninja 35 | 36 | - name: Configure CMake 37 | run: cmake --preset=win-${{ matrix.architecture }} 38 | 39 | - name: Build CMake 40 | run: cmake --build --preset=win-${{ matrix.architecture }}-release 41 | 42 | - name: Upload Windows Artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: win-${{ matrix.architecture }}-${{ github.sha }} 46 | path: ${{ github.workspace }}/out/build/win-${{ matrix.architecture }}/UTTDumper/Release 47 | 48 | linux: 49 | runs-on: ubuntu-24.04 50 | 51 | strategy: 52 | matrix: 53 | architecture: [x86, x64] 54 | 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | 59 | - name: Install dependencies 60 | run: sudo apt-get update && sudo apt-get install -y cmake ninja-build build-essential 61 | 62 | - name: Configure CMake 63 | run: cmake --preset=linux-${{ matrix.architecture }} 64 | 65 | - name: Build CMake 66 | run: cmake --build --preset=linux-${{ matrix.architecture }}-release 67 | 68 | - name: Upload Linux Artifact 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: linux-${{ matrix.architecture }}-${{ github.sha }} 72 | path: ${{ github.workspace }}/out/build/linux-${{ matrix.architecture }}/UTTDumper/Release 73 | 74 | android: 75 | runs-on: ubuntu-latest 76 | 77 | strategy: 78 | matrix: 79 | architecture: [arm64-v8a, armeabi-v7a, x86, x86_64] 80 | 81 | steps: 82 | - name: Checkout code 83 | uses: actions/checkout@v4 84 | 85 | - name: Set up JDK 17 86 | uses: actions/setup-java@v4 87 | with: 88 | java-version: '17' 89 | distribution: 'temurin' 90 | 91 | - name: Set up Android SDK and NDK 92 | uses: android-actions/setup-android@v3 93 | with: 94 | cmdline-tools-version: 11076708 95 | packages: 'platform-tools platforms;android-21 build-tools;34.0.0 ndk;27.1.12297006' 96 | 97 | - name: Install dependencies 98 | run: sudo apt-get update && sudo apt-get install -y cmake ninja-build 99 | 100 | - name: Configure CMake 101 | run: cmake --preset=android-${{ matrix.architecture }} 102 | 103 | - name: Build CMake 104 | run: cmake --build --preset=android-${{ matrix.architecture }}-release 105 | 106 | - name: Upload Android Artifact 107 | uses: actions/upload-artifact@v4 108 | with: 109 | name: android-${{ matrix.architecture }}-${{ github.sha }} 110 | path: ${{ github.workspace }}/out/build/android-${{ matrix.architecture }}/UTTDumper/Release 111 | -------------------------------------------------------------------------------- /UTTDumper/.editorconfig: -------------------------------------------------------------------------------- 1 | # Visual Studio generated .editorconfig file with C++ settings. 2 | root = true 3 | 4 | [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] 5 | 6 | # Visual C++ Code Style settings 7 | 8 | cpp_generate_documentation_comments = xml 9 | 10 | # Visual C++ Formatting settings 11 | 12 | cpp_indent_braces = false 13 | cpp_indent_multi_line_relative_to = innermost_parenthesis 14 | cpp_indent_within_parentheses = indent 15 | cpp_indent_preserve_within_parentheses = true 16 | cpp_indent_case_contents = true 17 | cpp_indent_case_labels = false 18 | cpp_indent_case_contents_when_block = false 19 | cpp_indent_lambda_braces_when_parameter = true 20 | cpp_indent_goto_labels = one_left 21 | cpp_indent_preprocessor = leftmost_column 22 | cpp_indent_access_specifiers = false 23 | cpp_indent_namespace_contents = true 24 | cpp_indent_preserve_comments = false 25 | cpp_new_line_before_open_brace_namespace = ignore 26 | cpp_new_line_before_open_brace_type = ignore 27 | cpp_new_line_before_open_brace_function = ignore 28 | cpp_new_line_before_open_brace_block = ignore 29 | cpp_new_line_before_open_brace_lambda = ignore 30 | cpp_new_line_scope_braces_on_separate_lines = false 31 | cpp_new_line_close_brace_same_line_empty_type = false 32 | cpp_new_line_close_brace_same_line_empty_function = false 33 | cpp_new_line_before_catch = true 34 | cpp_new_line_before_else = true 35 | cpp_new_line_before_while_in_do_while = false 36 | cpp_space_before_function_open_parenthesis = remove 37 | cpp_space_within_parameter_list_parentheses = false 38 | cpp_space_between_empty_parameter_list_parentheses = false 39 | cpp_space_after_keywords_in_control_flow_statements = true 40 | cpp_space_within_control_flow_statement_parentheses = false 41 | cpp_space_before_lambda_open_parenthesis = false 42 | cpp_space_within_cast_parentheses = false 43 | cpp_space_after_cast_close_parenthesis = false 44 | cpp_space_within_expression_parentheses = false 45 | cpp_space_before_block_open_brace = true 46 | cpp_space_between_empty_braces = false 47 | cpp_space_before_initializer_list_open_brace = false 48 | cpp_space_within_initializer_list_braces = true 49 | cpp_space_preserve_in_initializer_list = true 50 | cpp_space_before_open_square_bracket = false 51 | cpp_space_within_square_brackets = false 52 | cpp_space_before_empty_square_brackets = false 53 | cpp_space_between_empty_square_brackets = false 54 | cpp_space_group_square_brackets = true 55 | cpp_space_within_lambda_brackets = false 56 | cpp_space_between_empty_lambda_brackets = false 57 | cpp_space_before_comma = false 58 | cpp_space_after_comma = true 59 | cpp_space_remove_around_member_operators = true 60 | cpp_space_before_inheritance_colon = true 61 | cpp_space_before_constructor_colon = true 62 | cpp_space_remove_before_semicolon = true 63 | cpp_space_after_semicolon = true 64 | cpp_space_remove_around_unary_operator = true 65 | cpp_space_around_binary_operator = insert 66 | cpp_space_around_assignment_operator = insert 67 | cpp_space_pointer_reference_alignment = left 68 | cpp_space_around_ternary_operator = insert 69 | cpp_use_unreal_engine_macro_formatting = true 70 | cpp_wrap_preserve_blocks = one_liners 71 | 72 | # Visual C++ Inlcude Cleanup settings 73 | 74 | cpp_include_cleanup_add_missing_error_tag_type = suggestion 75 | cpp_include_cleanup_remove_unused_error_tag_type = dimmed 76 | cpp_include_cleanup_optimize_unused_error_tag_type = suggestion 77 | cpp_include_cleanup_sort_after_edits = false 78 | cpp_sort_includes_error_tag_type = none 79 | cpp_sort_includes_priority_case_sensitive = false 80 | cpp_sort_includes_priority_style = quoted 81 | cpp_includes_style = default 82 | cpp_includes_use_forward_slash = true 83 | -------------------------------------------------------------------------------- /UTTDumper/include/typetree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace UTTD::Unity { 11 | enum class NodeType : uint8_t { 12 | None, 13 | IsArray = 1 << 0, 14 | IsManagedReference = 1 << 1, 15 | IsManagedReferenceRegistry = 1 << 2, 16 | IsArrayOfRefs = 1 << 3, 17 | }; 18 | 19 | enum class TransferMeta { 20 | None, 21 | HideInEditor = 1 << 0, 22 | NotEditable = 1 << 4, 23 | StrongPPtr = 1 << 6, 24 | TreatIntegerValueAsBoolean = 1 << 8, 25 | SimpleEditor = 1 << 11, 26 | DebugProperty = 1 << 12, 27 | AlignBytes = 1 << 14, 28 | AnyChildUsesAlignBytesFlag = 1 << 15, 29 | IgnoreWithInspectorUndo = 1 << 16, 30 | EditorDisplaysCharacterMap = 1 << 18, 31 | IgnoreInMetaFiles = 1 << 19, 32 | TransferAsArrayEntryNameInMetaFiles = 1 << 20, 33 | TransferUsingFlowMappingStyle = 1 << 21, 34 | GenerateBitwiseDifferences = 1 << 22, 35 | DontAnimate = 1 << 23, 36 | TransferHex64 = 1 << 24, 37 | CharPropertyMask = 1 << 25, 38 | DontValidateUTF8 = 1 << 26, 39 | FixedBuffer = 1 << 27, 40 | DisallowSerializedPropertyModification = 1 << 28, 41 | }; 42 | 43 | template 44 | struct DynamicArray { 45 | T* data; 46 | MemLabelId label; //TODO: template 47 | size_t size; 48 | size_t capacity; 49 | }; 50 | 51 | class ITypeTreeNode { 52 | public: 53 | virtual int16_t version() const = 0; 54 | virtual uint8_t level() const = 0; 55 | virtual NodeType nodeType() const = 0; 56 | virtual uint32_t typeStrOffset() const = 0; 57 | virtual uint32_t nameStrOffset() const = 0; 58 | virtual std::string_view type() const = 0; 59 | virtual std::string_view name() const = 0; 60 | virtual int32_t byteSize() const = 0; 61 | virtual int32_t index() const = 0; 62 | virtual TransferMeta meta() const = 0; 63 | virtual void* ptr() = 0; 64 | }; 65 | 66 | class ITypeTree { 67 | protected: 68 | const MemLabelId MemLabel = MemLabelId{ 0x53 }; 69 | public: 70 | ITypeTree(const CommonString& commonString) : m_commonString(commonString) { } 71 | 72 | virtual const std::vector>& nodes() const = 0; 73 | virtual DynamicArray stringBuffer() const = 0; 74 | virtual DynamicArray offsets() const = 0; 75 | virtual void* ptr() = 0; 76 | 77 | virtual void populate() = 0; 78 | 79 | std::string_view string(uint32_t offset) const { 80 | if (offset > INT_MAX) { 81 | return m_commonString.string(offset & INT_MAX); 82 | }; 83 | 84 | return std::string_view{ (char*)(stringBuffer().data + offset) }; 85 | } 86 | private: 87 | const CommonString& m_commonString; 88 | }; 89 | 90 | class TypeTree { 91 | protected: 92 | typedef unsigned char GetTypeTree(void* object, TransferInstruction flags, void* typetree); 93 | typedef void GenerateTypeTree(void* object, void* typetree, TransferInstruction flags); 94 | 95 | public: 96 | TypeTree(void* ptr, void* ctor, const CommonString& commonString, const Version& version) : 97 | m_ptr(ptr), 98 | m_ctorPtr(ctor), 99 | m_version(version), 100 | m_commonString(commonString) { 101 | 102 | } 103 | 104 | std::shared_ptr typeTree(INativeObject& object, TransferInstruction flags) const; 105 | private: 106 | void* m_ptr; 107 | void* m_ctorPtr; 108 | const Version& m_version; 109 | const CommonString& m_commonString; 110 | }; 111 | 112 | static std::shared_ptr s_typeTree(void* ptr, const CommonString& commonString, const Version& version); 113 | } -------------------------------------------------------------------------------- /UTTDumper/include/md4.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace UTTD { 8 | class MD4 { 9 | public: 10 | MD4() { 11 | reset(); 12 | } 13 | 14 | void reset() { 15 | count = 0; 16 | context.assign(4, 0); 17 | context[0] = 0x67452301; 18 | context[1] = 0xefcdab89; 19 | context[2] = 0x98badcfe; 20 | context[3] = 0x10325476; 21 | x.assign(16, 0); 22 | buffer.assign(BLOCK_LENGTH, 0); 23 | } 24 | 25 | template 26 | void update(T const& value) { 27 | update((uint8_t*)&value, 0, sizeof(T)); 28 | } 29 | 30 | void update(const std::vector& vector) { 31 | update(vector.data(), 0, vector.size()); 32 | } 33 | 34 | 35 | void update(const uint8_t* data, int32_t offset, int32_t size) { 36 | if (offset < 0 || size < 0 || offset > size) { 37 | throw std::invalid_argument("Incorrect arguments for function engine_update"); 38 | } 39 | 40 | int32_t buffer_index = static_cast(count % BLOCK_LENGTH); 41 | count += size; 42 | const int32_t partial_length = BLOCK_LENGTH - buffer_index; 43 | int32_t i = 0; 44 | 45 | if (size >= partial_length) { 46 | for (int32_t j = 0; j < partial_length; ++j) { 47 | buffer[buffer_index + j] = data[offset + j]; 48 | } 49 | 50 | transform(buffer.data(), 0); 51 | i = partial_length; 52 | while (i + BLOCK_LENGTH - 1 < size) { 53 | transform(data, offset + i); 54 | i += BLOCK_LENGTH; 55 | } 56 | 57 | buffer_index = 0; 58 | } 59 | 60 | if (i < size) { 61 | for (int32_t j = 0; j < size - i; ++j) { 62 | buffer[buffer_index + j] = data[offset + i + j]; 63 | } 64 | } 65 | } 66 | 67 | std::vector digest() { 68 | const int32_t buffer_index = static_cast(count % BLOCK_LENGTH); 69 | const int32_t padding_length = (buffer_index < 56) ? 56 - buffer_index : 120 - buffer_index; 70 | 71 | std::vector tail(padding_length + 8, 0); 72 | tail[0] = static_cast(0x80); 73 | 74 | for (int32_t i = 0; i < 8; ++i) { 75 | tail[padding_length + i] = static_cast(unsigned_right_shift(count * 8, 8 * i)); 76 | } 77 | 78 | update(tail); 79 | 80 | std::vector result(16, 0); 81 | for (int32_t i = 0; i < 4; ++i) { 82 | for (int32_t j = 0; j < 4; ++j) { 83 | result[i * 4 + j] = static_cast(unsigned_right_shift(context[i], 8 * j)); 84 | } 85 | } 86 | 87 | reset(); 88 | return result; 89 | } 90 | 91 | private: 92 | void transform(const uint8_t* data, int32_t offset) { 93 | for (int32_t i = 0; i < 16; ++i) { 94 | x[i] = ((data[offset + 0] & 0xff)) | 95 | ((data[offset + 1] & 0xff) << 8) | 96 | ((data[offset + 2] & 0xff) << 16) | 97 | ((data[offset + 3] & 0xff) << 24); 98 | offset += 4; 99 | } 100 | 101 | uint32_t a = context[0]; 102 | uint32_t b = context[1]; 103 | uint32_t c = context[2]; 104 | uint32_t d = context[3]; 105 | 106 | for (const int32_t& i : { 0, 4, 8, 12 }) { 107 | a = ff(a, b, c, d, x[i + 0], 3); 108 | d = ff(d, a, b, c, x[i + 1], 7); 109 | c = ff(c, d, a, b, x[i + 2], 11); 110 | b = ff(b, c, d, a, x[i + 3], 19); 111 | } 112 | 113 | for (const int32_t& i : { 0, 1, 2, 3 }) { 114 | a = gg(a, b, c, d, x[i + 0], 3); 115 | d = gg(d, a, b, c, x[i + 4], 5); 116 | c = gg(c, d, a, b, x[i + 8], 9); 117 | b = gg(b, c, d, a, x[i + 12], 13); 118 | } 119 | 120 | for (const int32_t& i : { 0, 2, 1, 3 }) { 121 | a = hh(a, b, c, d, x[i + 0], 3); 122 | d = hh(d, a, b, c, x[i + 8], 9); 123 | c = hh(c, d, a, b, x[i + 4], 11); 124 | b = hh(b, c, d, a, x[i + 12], 15); 125 | } 126 | 127 | context[0] += a; 128 | context[1] += b; 129 | context[2] += c; 130 | context[3] += d; 131 | } 132 | 133 | static uint32_t unsigned_right_shift(const uint32_t& base, const uint32_t& shift) { 134 | if (shift < 0 || shift >= 32 || base == 0) { 135 | return 0; 136 | } 137 | 138 | return (base > 0) ? base >> shift : static_cast(base) >> shift; 139 | } 140 | 141 | static uint32_t rotate(const uint32_t& t, const uint32_t& s) { 142 | return (t << s) | unsigned_right_shift(t, 32 - s); 143 | } 144 | 145 | static uint32_t ff(const uint32_t& a, const uint32_t& b, const uint32_t& c, const uint32_t& d, const uint32_t& x, const uint32_t& s) { 146 | return rotate(a + ((b & c) | (~b & d)) + x, s); 147 | } 148 | 149 | static uint32_t gg(const uint32_t& a, const uint32_t& b, const uint32_t& c, const uint32_t& d, const uint32_t& x, const uint32_t& s) { 150 | return rotate(a + ((b & (c | d)) | (c & d)) + x + 0x5A827999, s); 151 | } 152 | 153 | static uint32_t hh(const uint32_t& a, const uint32_t& b, const uint32_t& c, const uint32_t& d, const uint32_t& x, const uint32_t& s) { 154 | return rotate(a + (b ^ c ^ d) + x + 0x6ED9EBA1, s); 155 | } 156 | 157 | uint64_t count; 158 | std::vector x; 159 | std::vector context; 160 | std::vector buffer; 161 | 162 | const int32_t BLOCK_LENGTH = 64; 163 | }; 164 | } -------------------------------------------------------------------------------- /UTTDumper/lib/info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace UTTD { 9 | Info::Info(const Engine& engine, 10 | Unity::TransferInstruction release = Unity::TransferInstruction::SerializeGameRelease, 11 | Unity::TransferInstruction editor = Unity::TransferInstruction::None) { 12 | version = engine.version().str(); 13 | strings = InfoString::s_makeList(engine.commonString()); 14 | classes = InfoClass::s_makeList(engine, release, editor); 15 | } 16 | 17 | InfoClass::InfoClass(Unity::IRTTI& rtti) { 18 | name = rtti.name(); 19 | nameSpace = rtti.nameSpace(); 20 | fullName = rtti.fullName(); 21 | module = rtti.module(); 22 | typeID = rtti.typeID(); 23 | base = rtti.base() != nullptr ? rtti.base()->name() : ""; 24 | descendantCount = rtti.descendantCount(); 25 | size = rtti.size(); 26 | typeIndex = rtti.typeIndex(); 27 | isAbstract = rtti.isAbstract(); 28 | isSealed = rtti.isSealed(); 29 | isEditorOnly = rtti.isEditorOnly(); 30 | isStripped = rtti.isStripped(); 31 | 32 | std::vector>& derived = rtti.derived(); 33 | std::vector names; 34 | std::transform(derived.begin(), derived.end(), std::back_inserter(names), [](std::shared_ptr const& x) { return x != nullptr ? std::string(x->name()) : ""; }); 35 | 36 | this->derived = names; 37 | } 38 | 39 | InfoNode::InfoNode(const Unity::ITypeTreeNode& node) { 40 | level = node.level(); 41 | type = node.type(); 42 | name = node.name(); 43 | byteSize = node.byteSize(); 44 | index = node.index(); 45 | version = node.version(); 46 | nodeType = static_cast(node.nodeType()); 47 | meta = static_cast(node.meta()); 48 | subNodes = std::vector>(); 49 | } 50 | 51 | InfoNode::InfoNode(const Unity::ITypeTreeNode& node, InfoNode* parent) : InfoNode(node) { 52 | this->parent = parent; 53 | } 54 | 55 | void InfoNode::hash(std::shared_ptr md4) { 56 | std::vector vec; 57 | 58 | vec.assign(type.begin(), type.end()); 59 | md4->update(vec); 60 | vec.assign(name.begin(), name.end()); 61 | md4->update(vec); 62 | md4->update(byteSize); 63 | md4->update((int32_t)nodeType); 64 | md4->update((int32_t)version); 65 | md4->update((int32_t)(meta & 0x4000)); 66 | 67 | for (const std::shared_ptr& i : subNodes) { 68 | i->hash(md4); 69 | } 70 | } 71 | 72 | std::vector InfoString::s_makeList(const Unity::CommonString& commonString) { 73 | std::vector strings; 74 | 75 | for (const std::pair& i : commonString.strings()) { 76 | InfoString string{ i.first, i.second }; 77 | strings.emplace_back(string); 78 | } 79 | 80 | return strings; 81 | } 82 | 83 | std::vector> InfoClass::s_makeList(const Engine& engine, Unity::TransferInstruction release, Unity::TransferInstruction editor) { 84 | std::vector> result; 85 | 86 | std::vector> types = engine.rtti().types(); 87 | 88 | for (auto i : sorted(types, [&types](size_t a, size_t b) { return types[a]->typeID() < types[b]->typeID(); })) { 89 | const std::shared_ptr& type = types[i]; 90 | 91 | std::shared_ptr infoClass = std::make_shared(*type); 92 | 93 | std::shared_ptr iter = type; 94 | while (iter->isAbstract()) { 95 | std::shared_ptr base = iter->base(); 96 | if (base == nullptr) { 97 | break; 98 | } 99 | else { 100 | iter.swap(base); 101 | } 102 | } 103 | 104 | if (iter->typeID() == 116) continue; // MonoManager 105 | 106 | if (contains(engine.options().exclude, iter->typeID())) { 107 | std::cout << std::format("Type {0} is excluded, skipping...", iter->name()) << std::endl; 108 | continue; 109 | } 110 | 111 | std::shared_ptr object = engine.nativeObject().produce(*iter, 0, Unity::CreationMode::Default); 112 | 113 | if (object.get() != nullptr) { 114 | std::shared_ptr releaseTree = engine.typeTree().typeTree(*object, release); 115 | std::shared_ptr editorTree = engine.typeTree().typeTree(*object, editor); 116 | 117 | infoClass->releaseRootNode = InfoNode::s_rootNode(*releaseTree); 118 | infoClass->editorRootNode = InfoNode::s_rootNode(*editorTree); 119 | } 120 | 121 | result.emplace_back(infoClass); 122 | } 123 | 124 | return result; 125 | } 126 | 127 | std::shared_ptr InfoNode::s_rootNode(const Unity::ITypeTree& typeTree) { 128 | std::vector> nodes = typeTree.nodes(); 129 | const Unity::ITypeTreeNode& rootNode = *nodes[0]; 130 | 131 | std::shared_ptr root = std::make_shared(rootNode); 132 | 133 | InfoNode* iter = root.get(); 134 | for (int32_t i = 1; i < nodes.size(); i++) { 135 | const Unity::ITypeTreeNode& treeNode = *nodes[i]; 136 | 137 | while (treeNode.level() <= iter->level) { 138 | iter = iter->parent; 139 | } 140 | 141 | std::shared_ptr node = std::make_shared(treeNode, iter); 142 | 143 | iter->subNodes.emplace_back(node); 144 | iter = node.get(); 145 | } 146 | 147 | return root; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "hidden": true, 7 | "generator": "Ninja Multi-Config", 8 | "binaryDir": "${sourceDir}/out/build/${presetName}", 9 | "installDir": "${sourceDir}/out/install/${presetName}", 10 | "cacheVariables": { 11 | "CMAKE_CONFIGURATION_TYPES": "Debug;Release" 12 | } 13 | }, 14 | { 15 | "name": "windows-base", 16 | "inherits": "base", 17 | "hidden": true, 18 | "cacheVariables": { 19 | "CMAKE_C_COMPILER": "cl.exe", 20 | "CMAKE_CXX_COMPILER": "cl.exe" 21 | }, 22 | "condition": { 23 | "type": "equals", 24 | "lhs": "${hostSystemName}", 25 | "rhs": "Windows" 26 | } 27 | }, 28 | { 29 | "name": "linux-base", 30 | "inherits": "base", 31 | "hidden": true, 32 | "condition": { 33 | "type": "equals", 34 | "lhs": "${hostSystemName}", 35 | "rhs": "Linux" 36 | }, 37 | "vendor": { 38 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { 39 | "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" 40 | } 41 | } 42 | }, 43 | { 44 | "name": "android-base", 45 | "inherits": "linux-base", 46 | "hidden": true, 47 | "cacheVariables": { 48 | "ANDROID_PLATFORM": "android-21" 49 | }, 50 | "toolchainFile": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" 51 | }, 52 | { 53 | "name": "win-x86", 54 | "inherits": "windows-base", 55 | "displayName": "Windows x86", 56 | "architecture": { 57 | "value": "x86", 58 | "strategy": "external" 59 | } 60 | }, 61 | { 62 | "name": "win-x64", 63 | "inherits": "windows-base", 64 | "displayName": "Windows x64", 65 | "architecture": { 66 | "value": "x64", 67 | "strategy": "external" 68 | } 69 | }, 70 | { 71 | "name": "linux-x86", 72 | "inherits": "linux-base", 73 | "displayName": "Linux x86", 74 | "architecture": { 75 | "value": "x86", 76 | "strategy": "external" 77 | } 78 | }, 79 | { 80 | "name": "linux-x64", 81 | "inherits": "linux-base", 82 | "displayName": "Linux x64", 83 | "architecture": { 84 | "value": "x64", 85 | "strategy": "external" 86 | } 87 | }, 88 | { 89 | "name": "android-arm64-v8a", 90 | "inherits": "android-base", 91 | "displayName": "Android arm64-v8a", 92 | "cacheVariables": { 93 | "ANDROID_ABI": "arm64-v8a" 94 | } 95 | }, 96 | { 97 | "name": "android-armeabi-v7a", 98 | "inherits": "android-base", 99 | "displayName": "Android armeabi-v7a", 100 | "cacheVariables": { 101 | "ANDROID_ABI": "armeabi-v7a" 102 | } 103 | }, 104 | { 105 | "name": "android-x86", 106 | "inherits": "android-base", 107 | "displayName": "Android x86", 108 | "cacheVariables": { 109 | "ANDROID_ABI": "x86" 110 | } 111 | }, 112 | { 113 | "name": "android-x86_64", 114 | "inherits": "android-base", 115 | "displayName": "Android x86_64", 116 | "cacheVariables": { 117 | "ANDROID_ABI": "x86_64" 118 | } 119 | } 120 | ], 121 | "buildPresets": [ 122 | { 123 | "name": "win-x86-debug", 124 | "configurePreset": "win-x86", 125 | "configuration": "Debug", 126 | "displayName": "Debug" 127 | }, 128 | { 129 | "name": "win-x86-release", 130 | "configurePreset": "win-x86", 131 | "configuration": "Release", 132 | "displayName": "Release" 133 | }, 134 | { 135 | "name": "win-x64-debug", 136 | "configurePreset": "win-x64", 137 | "configuration": "Debug", 138 | "displayName": "Debug" 139 | }, 140 | { 141 | "name": "win-x64-release", 142 | "configurePreset": "win-x64", 143 | "configuration": "Release", 144 | "displayName": "Release" 145 | }, 146 | { 147 | "name": "linux-x86-debug", 148 | "configurePreset": "linux-x86", 149 | "configuration": "Debug", 150 | "displayName": "Debug" 151 | }, 152 | { 153 | "name": "linux-x86-release", 154 | "configurePreset": "linux-x86", 155 | "configuration": "Release", 156 | "displayName": "Release" 157 | }, 158 | { 159 | "name": "linux-x64-debug", 160 | "configurePreset": "linux-x64", 161 | "configuration": "Debug", 162 | "displayName": "Debug" 163 | }, 164 | { 165 | "name": "linux-x64-release", 166 | "configurePreset": "linux-x64", 167 | "configuration": "Release", 168 | "displayName": "Release" 169 | }, 170 | { 171 | "name": "android-arm64-v8a-debug", 172 | "configurePreset": "android-arm64-v8a", 173 | "configuration": "Debug", 174 | "displayName": "Debug" 175 | }, 176 | { 177 | "name": "android-arm64-v8a-release", 178 | "configurePreset": "android-arm64-v8a", 179 | "configuration": "Release", 180 | "displayName": "Release" 181 | }, 182 | { 183 | "name": "android-armeabi-v7a-debug", 184 | "configurePreset": "android-armeabi-v7a", 185 | "configuration": "Debug", 186 | "displayName": "Debug" 187 | }, 188 | { 189 | "name": "android-armeabi-v7a-release", 190 | "configurePreset": "android-armeabi-v7a", 191 | "configuration": "Release", 192 | "displayName": "Release" 193 | }, 194 | { 195 | "name": "android-x86-debug", 196 | "configurePreset": "android-x86", 197 | "configuration": "Debug", 198 | "displayName": "Debug" 199 | }, 200 | { 201 | "name": "android-x86-release", 202 | "configurePreset": "android-x86", 203 | "configuration": "Release", 204 | "displayName": "Release" 205 | }, 206 | { 207 | "name": "android-x86_64-debug", 208 | "configurePreset": "android-x86_64", 209 | "configuration": "Debug", 210 | "displayName": "Debug" 211 | }, 212 | { 213 | "name": "android-x86_64-release", 214 | "configurePreset": "android-x86_64", 215 | "configuration": "Release", 216 | "displayName": "Release" 217 | } 218 | ] 219 | } -------------------------------------------------------------------------------- /UTTDumper/lib/engine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef WIN32 10 | #include 11 | 12 | #define GetModuleBase(moduleName) reinterpret_cast(GetModuleHandleA(moduleName)) 13 | 14 | #else 15 | 16 | #include 17 | #include 18 | 19 | static uintptr_t get_module_base_from_exports(const char* moduleName) { 20 | uintptr_t base; 21 | 22 | void* unitySendMessage = dlsym(RTLD_DEFAULT, "UnitySendMessage"); 23 | 24 | Dl_info dlInfo; 25 | if (dladdr(unitySendMessage, &dlInfo) != 0) { 26 | base = reinterpret_cast(dlInfo.dli_fbase); 27 | } 28 | 29 | return base; 30 | } 31 | 32 | static uintptr_t get_module_base_from_maps(const char* moduleName) { 33 | std::ifstream maps("/proc/self/maps"); 34 | 35 | char temp; 36 | ino_t inode; 37 | int32_t dev_major, dev_minor; 38 | uintptr_t begin, end, offset; 39 | std::string line, path, perms; 40 | while (std::getline(maps, line)) { 41 | std::istringstream ss(line); 42 | 43 | ss >> std::hex; 44 | ss >> begin >> temp >> end >> perms >> offset >> dev_major >> temp >> dev_minor; 45 | 46 | ss >> std::dec; 47 | ss >> inode; 48 | 49 | ss >> std::ws; 50 | if (std::getline(ss, path) && path.find(moduleName) != path.npos && inode == 0) { 51 | break; 52 | } 53 | } 54 | 55 | return begin; 56 | } 57 | 58 | static uintptr_t get_module_base(const char* moduleName) { 59 | uintptr_t base; 60 | 61 | base = get_module_base_from_exports(moduleName); 62 | if (base != 0) { 63 | return base; 64 | } 65 | 66 | std::cout << "Unable to find module base from exports, attempting to find from maps..." << std::endl; 67 | 68 | base = get_module_base_from_maps(moduleName); 69 | if (base != 0) { 70 | return base; 71 | } 72 | 73 | throw std::runtime_error(std::format("Unable to find base address for {0}", moduleName)); 74 | } 75 | 76 | #define GetModuleBase(moduleName) get_module_base(moduleName) 77 | 78 | #endif 79 | 80 | namespace UTTD { 81 | void Engine::parse() 82 | { 83 | try { 84 | auto parent = m_path.parent_path(); 85 | auto tomlPath = parent / filename; 86 | 87 | std::cout << std::format("Parsing config file {0}..", filename) << std::endl; 88 | 89 | toml::table table = toml::parse_file(tomlPath.string()); 90 | 91 | toml::v3::node_view engineNode = table["engine"]; 92 | 93 | std::string name = engineNode["name"].value_or(""); 94 | if (name.empty()) { 95 | throw std::runtime_error("no game is selected !!"); 96 | } 97 | 98 | toml::v3::node_view gameNode = table[name]; 99 | 100 | std::string binary = gameNode["binary"].value_or(""); 101 | if (binary.empty()) { 102 | throw std::logic_error("binary name is empty !!"); 103 | } 104 | 105 | std::string outputDir = gameNode["output_dir"].value_or(""); 106 | if (outputDir.empty()) { 107 | #ifdef ANDROID 108 | std::ifstream cmdline("/proc/self/cmdline"); 109 | std::string pkgID; 110 | std::getline(cmdline, pkgID, '\0'); 111 | 112 | std::filesystem::path output(std::format("/data/data/{0}/{1}", pkgID, name)); 113 | outputDir = output.string(); 114 | #else 115 | std::filesystem::path output(parent / name); 116 | outputDir = output.string(); 117 | #endif 118 | } 119 | 120 | 121 | std::vector exclude = {}; 122 | toml::array* arr = gameNode["exclude"].as_array(); 123 | if (arr->size() > 0 && arr->is_homogeneous()) { 124 | exclude.reserve(arr->size()); 125 | for (const toml::node& i : *arr) { 126 | exclude.emplace_back(i.value_or(0)); 127 | } 128 | } 129 | 130 | uint32_t delay = gameNode["delay"].value_or(0); 131 | UTTD::Unity::TransferInstruction transfer = gameNode["transfer"].value_or(UTTD::Unity::TransferInstruction::None); 132 | bool jsonDump = gameNode["json_dump"].value().value(); 133 | bool textDump = gameNode["text_dump"].value_or(false); 134 | bool binaryDump = gameNode["binary_dump"].value_or(false); 135 | uintptr_t commonStrings = gameNode["common_strings"].value_or(0); 136 | uintptr_t rtti = gameNode["rtti"].value_or(0); 137 | uintptr_t typeTree = gameNode["type_tree"].value_or(0); 138 | uintptr_t typeTreeCtor = gameNode["type_tree_ctor"].value_or(0); 139 | uintptr_t produce = gameNode["produce"].value_or(0); 140 | 141 | toml::v3::node_view versionNode = gameNode["version"]; 142 | 143 | uintptr_t version = 0; 144 | if (versionNode.type() == toml::node_type::string) { 145 | m_version = std::make_unique(versionNode.value_or("")); 146 | } 147 | else if (versionNode.type() == toml::node_type::integer) { 148 | version = versionNode.value_or(0); 149 | } 150 | else { 151 | throw std::runtime_error(std::format("invalid version for key {0}", name)); 152 | } 153 | 154 | m_options = Options{ 155 | name, 156 | delay, 157 | binary, 158 | outputDir, 159 | transfer, 160 | jsonDump, 161 | textDump, 162 | binaryDump, 163 | exclude, 164 | version, 165 | commonStrings, 166 | rtti, 167 | typeTreeCtor, 168 | typeTree, 169 | produce 170 | }; 171 | } 172 | catch (const toml::parse_error& err) { 173 | throw std::runtime_error(std::format("Error while parsing config file {0}: {1}", filename, err.description())); 174 | } 175 | } 176 | 177 | bool Engine::initialize() 178 | { 179 | try { 180 | std::cout << "Initializing engine..." << std::endl; 181 | 182 | uintptr_t base = GetModuleBase(m_options.binary.c_str()); 183 | 184 | if (m_version.get() == nullptr && m_options.version != 0) { 185 | typedef const char* GetUnityVersion(); 186 | 187 | GetUnityVersion* getUnityVersion = reinterpret_cast(base + m_options.version); 188 | m_version = std::make_unique(getUnityVersion()); 189 | } 190 | 191 | char* commonStrings = reinterpret_cast(base + m_options.commonStrings); 192 | 193 | m_commonString = std::make_unique(commonStrings); 194 | 195 | void* rtti = reinterpret_cast(base + m_options.rtti); 196 | 197 | m_rtti = std::make_unique(rtti, *m_version); 198 | 199 | if (!m_rtti->initialize()) { 200 | std::cout << "Failed to initialize RTTI, aborting..." << std::endl; 201 | return false; 202 | } 203 | 204 | void* typeTree = reinterpret_cast(base + m_options.typeTree); 205 | void* typeTreeCtor = reinterpret_cast(base + m_options.typeTreeCtor); 206 | 207 | m_typeTree = std::make_unique(typeTree, typeTreeCtor, *m_commonString, *m_version); 208 | 209 | void* produce = reinterpret_cast(base + m_options.produce); 210 | 211 | m_nativeObject = std::make_unique(produce, *m_version); 212 | } 213 | catch (const std::exception& err) { 214 | throw std::runtime_error(std::format("Error while initializing engine: {0}", err.what())); 215 | } 216 | 217 | return true; 218 | } 219 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /UTTDumper/lib/rtti.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace UTTD::Unity { 12 | class RTTI_Unity2017_3 : public IRTTI { 13 | struct Info { 14 | void* base; 15 | void* factory; 16 | char* className; 17 | char* classNamespace; 18 | char* module; 19 | uint32_t typeID; 20 | int32_t size; 21 | uint32_t typeIndex; 22 | uint32_t descendantCount; 23 | bool isAbstract; 24 | bool isSealed; 25 | bool isEditorOnly; 26 | bool isStripped; 27 | void* attributes; 28 | uint64_t attributeCount; 29 | }; 30 | 31 | public: 32 | RTTI_Unity2017_3(void* ptr) { 33 | m_info = static_cast(ptr); 34 | } 35 | 36 | std::shared_ptr base() const { return m_info->base != nullptr ? std::make_shared(m_info->base) : nullptr; }; 37 | std::string_view name() const { return m_info->className != nullptr ? std::string_view{ m_info->className } : std::string_view{}; }; 38 | std::string_view nameSpace() const { return m_info->classNamespace != nullptr ? std::string_view{ m_info->classNamespace } : std::string_view{}; }; 39 | std::string_view module() const { return m_info->module != nullptr ? std::string_view{ m_info->module } : std::string_view{}; } 40 | uint32_t typeID() const { return m_info->typeID; } 41 | int32_t size() const { return m_info->size; } 42 | uint32_t typeIndex() const { return m_info->typeIndex; } 43 | uint32_t descendantCount() const { return m_info->descendantCount; } 44 | bool isAbstract() const { return m_info->isAbstract; } 45 | bool isSealed() const { return m_info->isSealed; } 46 | bool isEditorOnly() const { return m_info->isEditorOnly; } 47 | bool isStripped() const { return m_info->isStripped; } 48 | void* attributes() const { return m_info->attributes; } 49 | uint64_t attributeCount() const { return m_info->attributeCount; } 50 | std::vector>& derived() { return m_derived; } 51 | void* ptr() { return m_info; } 52 | 53 | private: 54 | Info* m_info; 55 | std::vector> m_derived; 56 | }; 57 | 58 | class RTTI_Unity5_5 : public IRTTI { 59 | struct Info { 60 | void* base; 61 | void* factory; 62 | char* className; 63 | char* classNamespace; 64 | uint32_t typeID; 65 | int32_t size; 66 | uint32_t typeIndex; 67 | uint32_t descendantCount; 68 | bool isAbstract; 69 | bool isSealed; 70 | bool isEditorOnly; 71 | }; 72 | 73 | public: 74 | RTTI_Unity5_5(void* ptr) { 75 | m_info = static_cast(ptr); 76 | } 77 | 78 | std::shared_ptr base() const { return m_info->base != nullptr ? std::make_shared(m_info->base) : nullptr; }; 79 | std::string_view name() const { return m_info->className != nullptr ? std::string_view{ m_info->className } : std::string_view{}; }; 80 | std::string_view nameSpace() const { return m_info->classNamespace != nullptr ? std::string_view{ m_info->classNamespace } : std::string_view{}; }; 81 | std::string_view module() const { return std::string_view{}; } 82 | uint32_t typeID() const { return m_info->typeID; } 83 | int32_t size() const { return m_info->size; } 84 | uint32_t typeIndex() const { return m_info->typeIndex; } 85 | uint32_t descendantCount() const { return m_info->descendantCount; } 86 | bool isAbstract() const { return m_info->isAbstract; } 87 | bool isSealed() const { return m_info->isSealed; } 88 | bool isEditorOnly() const { return m_info->isEditorOnly; } 89 | bool isStripped() const { return false; } 90 | void* attributes() const { return nullptr; } 91 | uint64_t attributeCount() const { return 0; } 92 | std::vector>& derived() { return m_derived; } 93 | void* ptr() { return m_info; } 94 | 95 | private: 96 | Info* m_info; 97 | std::vector> m_derived; 98 | }; 99 | 100 | class RTTI_Unity5_4 : public IRTTI { 101 | struct Info { 102 | void* base; 103 | void* factory; 104 | char* className; 105 | uint32_t typeID; 106 | int32_t size; 107 | uint32_t typeIndex; 108 | uint32_t descendantCount; 109 | bool isAbstract; 110 | bool isSealed; 111 | bool isEditorOnly; 112 | }; 113 | 114 | public: 115 | RTTI_Unity5_4(void* ptr) { 116 | m_info = static_cast(ptr); 117 | } 118 | 119 | std::shared_ptr base() const { return m_info->base != nullptr ? std::make_shared(m_info->base) : nullptr; }; 120 | std::string_view name() const { return m_info->className != nullptr ? std::string_view{ m_info->className } : std::string_view{}; }; 121 | std::string_view nameSpace() const { return std::string_view{}; }; 122 | std::string_view module() const { return std::string_view{}; } 123 | uint32_t typeID() const { return m_info->typeID; } 124 | int32_t size() const { return m_info->size; } 125 | uint32_t typeIndex() const { return m_info->typeIndex; } 126 | uint32_t descendantCount() const { return m_info->descendantCount; } 127 | bool isAbstract() const { return m_info->isAbstract; } 128 | bool isSealed() const { return m_info->isSealed; } 129 | bool isEditorOnly() const { return m_info->isEditorOnly; } 130 | bool isStripped() const { return false; } 131 | void* attributes() const { return nullptr; } 132 | uint64_t attributeCount() const { return 0; } 133 | std::vector>& derived() { return m_derived; } 134 | void* ptr() { return m_info; } 135 | 136 | private: 137 | Info* m_info; 138 | std::vector> m_derived; 139 | }; 140 | 141 | class RTTI_Unity5_2 : public IRTTI { 142 | struct Info { 143 | void* base; 144 | void* factory; 145 | uint32_t typeID; 146 | char* className; 147 | int32_t size; 148 | bool isAbstract; 149 | }; 150 | 151 | public: 152 | RTTI_Unity5_2(void* ptr) { 153 | m_info = static_cast(ptr); 154 | } 155 | 156 | std::shared_ptr base() const { return m_info->base != nullptr ? std::make_shared(m_info->base) : nullptr; }; 157 | std::string_view name() const { return m_info->className != nullptr ? std::string_view{ m_info->className } : std::string_view{}; }; 158 | std::string_view nameSpace() const { return std::string_view{}; }; 159 | std::string_view module() const { return std::string_view{}; } 160 | uint32_t typeID() const { return m_info->typeID; } 161 | int32_t size() const { return m_info->size; } 162 | uint32_t typeIndex() const { return 0; } 163 | uint32_t descendantCount() const { return 0; } 164 | bool isAbstract() const { return m_info->isAbstract; } 165 | bool isSealed() const { return false; } 166 | bool isEditorOnly() const { return false; } 167 | bool isStripped() const { return false; } 168 | void* attributes() const { return nullptr; } 169 | uint64_t attributeCount() const { return 0; } 170 | std::vector>& derived() { return m_derived; } 171 | void* ptr() { return m_info; } 172 | 173 | private: 174 | Info* m_info; 175 | std::vector> m_derived; 176 | }; 177 | 178 | bool RTTI::initialize() { 179 | if (m_version >= UTTD::Unity::s_version<5, 5>) { 180 | Info* info = static_cast(m_ptr); 181 | 182 | m_types = std::vector>(info->count); 183 | for (int32_t i = 0; i < info->count; i++) { 184 | void* ptr = (&info->first)[i]; 185 | m_types[i] = s_RTTI(ptr, m_version); 186 | } 187 | } 188 | else { 189 | ClassIDToRTTI* classIDToRTTI = (ClassIDToRTTI*)m_ptr; 190 | 191 | m_types = std::vector>(MaxRuntimeTypeId); 192 | for (int32_t i = 0; i < MaxRuntimeTypeId; i++) { 193 | void* ptr = classIDToRTTI(i); 194 | m_types[i] = s_RTTI(ptr, m_version); 195 | } 196 | } 197 | 198 | for (const std::shared_ptr& type : m_types) { 199 | std::shared_ptr base = type->base(); 200 | if (base.get() != nullptr) { 201 | std::vector>::iterator iter = std::find_if(m_types.begin(), m_types.end(), [&base](std::shared_ptr const& other) { return other->fullName() == base->fullName(); }); 202 | if (iter != m_types.end()) { 203 | std::vector>& derived = iter->get()->derived(); 204 | derived.emplace_back(type); 205 | } 206 | } 207 | } 208 | 209 | return m_types.size() != 0; 210 | } 211 | 212 | static std::shared_ptr s_RTTI(void* ptr, const Version& version) { 213 | if (version >= s_version<2017, 3>) { 214 | return std::make_shared(ptr); 215 | } 216 | else if (version >= s_version<5, 5>) { 217 | return std::make_shared(ptr); 218 | } 219 | else if (version >= s_version<5, 4>) { 220 | return std::make_shared(ptr); 221 | } 222 | else if (version >= s_version<5, 2>) { 223 | return std::make_shared(ptr); 224 | } 225 | else { 226 | throw std::invalid_argument("unknown version !!"); 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /UTTDumper/lib/typetree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace UTTD::Unity { 5 | class TypeTreeNode_Unity2019_1 : public ITypeTreeNode { 6 | public: 7 | struct Node { 8 | int16_t version; 9 | uint8_t level; 10 | NodeType type; 11 | uint32_t typeStrOffset; 12 | uint32_t nameStrOffset; 13 | int32_t byteSize; 14 | int32_t index; 15 | TransferMeta meta; 16 | uint64_t refTypeHash; 17 | }; 18 | 19 | TypeTreeNode_Unity2019_1(Node node, ITypeTree* owner) : 20 | m_owner(owner), 21 | m_node(node) { 22 | 23 | } 24 | 25 | int16_t version() const { return m_node.version; } 26 | uint8_t level() const { return m_node.level; } 27 | NodeType nodeType() const { return m_node.type; } 28 | uint32_t typeStrOffset() const { return m_node.typeStrOffset; } 29 | uint32_t nameStrOffset() const { return m_node.nameStrOffset; } 30 | std::string_view type() const { return m_owner->string(m_node.typeStrOffset); } 31 | std::string_view name() const { return m_owner->string(m_node.nameStrOffset); } 32 | int32_t byteSize() const { return m_node.byteSize; } 33 | int32_t index() const { return m_node.index; } 34 | TransferMeta meta() const { return m_node.meta; } 35 | void* ptr() { return static_cast(&m_node); } 36 | 37 | private: 38 | Node m_node; 39 | ITypeTree* m_owner; 40 | }; 41 | 42 | class TypeTreeNode_Unity5_0 : public ITypeTreeNode { 43 | public: 44 | struct Node { 45 | int16_t version; 46 | uint8_t level; 47 | NodeType type; 48 | uint32_t typeStrOffset; 49 | uint32_t nameStrOffset; 50 | int32_t byteSize; 51 | int32_t index; 52 | TransferMeta meta; 53 | }; 54 | 55 | TypeTreeNode_Unity5_0(Node node, ITypeTree* owner) : 56 | m_owner(owner), 57 | m_node(node) { 58 | 59 | } 60 | 61 | int16_t version() const { return m_node.version; } 62 | uint8_t level() const { return m_node.level; } 63 | NodeType nodeType() const { return m_node.type; } 64 | uint32_t typeStrOffset() const { return m_node.typeStrOffset; } 65 | uint32_t nameStrOffset() const { return m_node.nameStrOffset; } 66 | std::string_view type() const { return m_owner->string(m_node.typeStrOffset); } 67 | std::string_view name() const { return m_owner->string(m_node.nameStrOffset); } 68 | int32_t byteSize() const { return m_node.byteSize; } 69 | int32_t index() const { return m_node.index; } 70 | TransferMeta meta() const { return m_node.meta; } 71 | void* ptr() { return static_cast(&m_node); } 72 | 73 | private: 74 | Node m_node; 75 | ITypeTree* m_owner; 76 | }; 77 | 78 | class TypeTree_Unity2022_2 : public ITypeTree { 79 | struct TypeTreeShareableData { 80 | DynamicArray nodes; 81 | DynamicArray levels; 82 | DynamicArray nextIndex; 83 | DynamicArray stringBuffer; 84 | DynamicArray offsets; 85 | TransferInstruction flags; 86 | int32_t refCount; 87 | MemLabelId memLabel; 88 | }; 89 | 90 | struct TypeTree { 91 | TypeTreeShareableData* data; 92 | void* referencedTypes; 93 | bool poolOwned; 94 | }; 95 | 96 | typedef void TypeConstructor(TypeTree_Unity2022_2::TypeTree* typeTree, MemLabelId label); 97 | public: 98 | TypeTree_Unity2022_2(void* ptr, const CommonString& commonString) : ITypeTree(commonString) { 99 | m_tree = TypeTree{}; 100 | } 101 | 102 | const std::vector>& nodes() const { return m_nodes; } 103 | DynamicArray stringBuffer() const { return m_tree.data->stringBuffer; } 104 | DynamicArray offsets() const { return m_tree.data->offsets; } 105 | void* ptr() { return static_cast(&m_tree); } 106 | 107 | void populate() { 108 | m_nodes = std::vector>(m_tree.data->nodes.size); 109 | 110 | for (int32_t i = 0; i < m_tree.data->nodes.size; i++) { 111 | m_nodes[i] = std::make_shared(m_tree.data->nodes.data[i], static_cast(this)); 112 | }; 113 | } 114 | 115 | private: 116 | TypeTree m_tree; 117 | std::vector> m_nodes; 118 | }; 119 | 120 | class TypeTree_Unity2019_3 : public ITypeTree { 121 | struct TypeTreeShareableData { 122 | DynamicArray nodes; 123 | DynamicArray stringBuffer; 124 | DynamicArray offsets; 125 | TransferInstruction flags; 126 | int32_t refCount; 127 | MemLabelId memLabel; 128 | }; 129 | 130 | struct TypeTree { 131 | TypeTreeShareableData* data; 132 | void* referencedTypes; 133 | bool poolOwned; 134 | }; 135 | 136 | typedef void TypeConstructor(TypeTree_Unity2019_3::TypeTree* typeTree, MemLabelId label); 137 | public: 138 | TypeTree_Unity2019_3(void* ptr, const CommonString& commonString) : ITypeTree(commonString) { 139 | TypeConstructor* constructor = (TypeConstructor*)ptr; 140 | TypeTree typeTree; 141 | constructor(&typeTree, MemLabel); 142 | m_tree = typeTree; 143 | } 144 | 145 | const std::vector>& nodes() const { return m_nodes; } 146 | DynamicArray stringBuffer() const { return m_tree.data->stringBuffer; } 147 | DynamicArray offsets() const { return m_tree.data->offsets; } 148 | void* ptr() { return static_cast(&m_tree); } 149 | 150 | void populate() { 151 | m_nodes = std::vector>(m_tree.data->nodes.size); 152 | 153 | for (int32_t i = 0; i < m_tree.data->nodes.size; i++) { 154 | m_nodes[i] = std::make_shared(m_tree.data->nodes.data[i], static_cast(this)); 155 | }; 156 | } 157 | 158 | private: 159 | TypeTree m_tree; 160 | std::vector> m_nodes; 161 | }; 162 | 163 | class TypeTree_Unity2019_1 : public ITypeTree { 164 | struct TypeTreeShareableData { 165 | DynamicArray nodes; 166 | DynamicArray stringBuffer; 167 | DynamicArray offsets; 168 | TransferInstruction flags; 169 | int32_t refCount; 170 | MemLabelId memLabel; 171 | }; 172 | 173 | struct TypeTree { 174 | TypeTreeShareableData* data; 175 | TypeTreeShareableData privateData; 176 | }; 177 | 178 | typedef void TypeConstructor(TypeTree_Unity2019_1::TypeTree* typeTree, MemLabelId label, uint8_t flag); 179 | public: 180 | TypeTree_Unity2019_1(void* ptr, const CommonString& commonString) : ITypeTree(commonString) { 181 | TypeConstructor* constructor = (TypeConstructor*)ptr; 182 | TypeTree typeTree; 183 | constructor(&typeTree, MemLabel, 0); 184 | m_tree = typeTree; 185 | } 186 | 187 | const std::vector>& nodes() const { return m_nodes; } 188 | DynamicArray stringBuffer() const { return m_tree.data->stringBuffer; } 189 | DynamicArray offsets() const { return m_tree.data->offsets; } 190 | void* ptr() { return static_cast(&m_tree); } 191 | 192 | void populate() { 193 | m_nodes = std::vector>(m_tree.data->nodes.size); 194 | 195 | for (int32_t i = 0; i < m_tree.data->nodes.size; i++) { 196 | m_nodes[i] = std::make_shared(m_tree.data->nodes.data[i], static_cast(this)); 197 | }; 198 | } 199 | 200 | private: 201 | TypeTree m_tree; 202 | std::vector> m_nodes; 203 | }; 204 | 205 | class TypeTree_Unity5_3 : public ITypeTree { 206 | struct TypeTree { 207 | DynamicArray nodes; 208 | DynamicArray stringBuffer; 209 | DynamicArray offsets; 210 | }; 211 | 212 | typedef void TypeConstructor(TypeTree_Unity5_3::TypeTree* typeTree, MemLabelId label); 213 | public: 214 | TypeTree_Unity5_3(void* ptr, const CommonString& commonString) : ITypeTree(commonString) { 215 | TypeConstructor* constructor = (TypeConstructor*)ptr; 216 | TypeTree typeTree; 217 | constructor(&typeTree, MemLabel); 218 | m_tree = typeTree; 219 | } 220 | 221 | const std::vector>& nodes() const { return m_nodes; } 222 | DynamicArray stringBuffer() const { return m_tree.stringBuffer; } 223 | DynamicArray offsets() const { return m_tree.offsets; } 224 | void* ptr() { return static_cast(&m_tree); } 225 | 226 | void populate() { 227 | m_nodes = std::vector>(m_tree.nodes.size); 228 | 229 | for (int32_t i = 0; i < m_tree.nodes.size; i++) { 230 | m_nodes[i] = std::make_shared(m_tree.nodes.data[i], static_cast(this)); 231 | }; 232 | } 233 | 234 | private: 235 | TypeTree m_tree; 236 | std::vector> m_nodes; 237 | }; 238 | 239 | class TypeTree_Unity5_0 : public ITypeTree { 240 | struct TypeTree { 241 | DynamicArray nodes; 242 | DynamicArray stringBuffer; 243 | DynamicArray offsets; 244 | }; 245 | 246 | typedef void TypeConstructor(TypeTree_Unity5_0::TypeTree* typeTree); 247 | public: 248 | TypeTree_Unity5_0(void* ptr, const CommonString& commonString) : ITypeTree(commonString) { 249 | TypeConstructor* constructor = (TypeConstructor*)ptr; 250 | TypeTree typeTree; 251 | constructor(&typeTree); 252 | m_tree = typeTree; 253 | } 254 | 255 | const std::vector>& nodes() const { return m_nodes; } 256 | DynamicArray stringBuffer() const { return m_tree.stringBuffer; } 257 | DynamicArray offsets() const { return m_tree.offsets; } 258 | void* ptr() { return static_cast(&m_tree); } 259 | 260 | void populate() { 261 | m_nodes = std::vector>(m_tree.nodes.size); 262 | 263 | for (int32_t i = 0; i < m_tree.nodes.size; i++) { 264 | m_nodes[i] = std::make_shared(m_tree.nodes.data[i], static_cast(this)); 265 | }; 266 | } 267 | 268 | private: 269 | TypeTree m_tree; 270 | std::vector> m_nodes; 271 | }; 272 | 273 | std::shared_ptr s_typeTree(void* ptr, const CommonString& commonString, const Version& version) { 274 | if (version < s_version<5, 0>) { 275 | throw std::runtime_error("version not supported !!"); //TODO: add support? 276 | } 277 | else if (version < s_version<5, 3>) { 278 | return std::make_shared(ptr, commonString); 279 | } 280 | else if (version < s_version<2019, 1>) { 281 | return std::make_shared(ptr, commonString); 282 | } 283 | else if (version < s_version<2019, 3>) { 284 | return std::make_shared(ptr, commonString); 285 | } 286 | else if (version < s_version<2022, 2>) { 287 | return std::make_shared(ptr, commonString); 288 | } 289 | else if (version < s_version<2023, 1, 0, 'a', 2>) { 290 | return std::make_shared(ptr, commonString); 291 | } 292 | else { 293 | throw std::invalid_argument("unknown version !!"); 294 | } 295 | } 296 | 297 | std::shared_ptr TypeTree::typeTree(INativeObject& object, TransferInstruction flags) const { 298 | std::shared_ptr typeTree = s_typeTree(m_ctorPtr, m_commonString, m_version); 299 | 300 | if (m_version >= s_version<2019>) { 301 | GetTypeTree* getTypeTree = (GetTypeTree*)m_ptr; 302 | getTypeTree(object.ptr(), flags, typeTree->ptr()); 303 | } 304 | else { 305 | GenerateTypeTree* generateTypeTree = (GenerateTypeTree*)m_ptr; 306 | generateTypeTree(object.ptr(), typeTree->ptr(), flags); 307 | } 308 | 309 | typeTree->populate(); 310 | 311 | return typeTree; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /UTTDumper/lib/dumper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace UTTD 17 | { 18 | void Dumper::execute() { 19 | std::cout << std::format("Starting dumper. UnityVersion {}", m_engine.version().str()) << std::endl; 20 | std::filesystem::create_directories(m_engine.options().outputDirectory); 21 | 22 | Unity::TransferInstruction release = m_engine.options().transfer | Unity::TransferInstruction::SerializeGameRelease; 23 | Unity::TransferInstruction editor = m_engine.options().transfer & ~Unity::TransferInstruction::SerializeGameRelease; 24 | 25 | m_info = std::make_unique(m_engine, release, editor); 26 | 27 | if (m_engine.options().jsonDump) { 28 | dumpClassesJson(); 29 | dumpHashesJson(); 30 | dumpInfoJson(); 31 | } 32 | 33 | if (m_engine.options().textDump) { 34 | dumpRTTI(); 35 | dumpStruct(release); 36 | } 37 | 38 | if (m_engine.options().binaryDump) { 39 | dumpStringData(m_engine.commonString()); 40 | dumpStructData(release); 41 | } 42 | 43 | std::cout << "Success" << std::endl; 44 | } 45 | 46 | void Dumper::dumpStringData(const Unity::CommonString& commonString) const { 47 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 48 | std::ofstream os(outDirectory / "strings.dat", std::ios::binary); 49 | 50 | std::cout << "Writing common string buffer..." << std::endl; 51 | 52 | std::pair range = commonString.range(); 53 | os.write(range.first, range.second - range.first - 1); 54 | } 55 | 56 | void Dumper::dumpInfoJson() const { 57 | std::cout << "Writing information json..." << std::endl; 58 | 59 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 60 | std::ofstream os(outDirectory / "info.json"); 61 | 62 | nlohmann::ordered_json json; 63 | 64 | to_json(json, *m_info); 65 | 66 | os << std::setfill('\t') << std::setw(1) << json; 67 | } 68 | 69 | void Dumper::dumpClassesJson() const { 70 | std::cout << "Writing classes.json..." << std::endl; 71 | 72 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 73 | std::ofstream os(outDirectory / "classes.json"); 74 | 75 | std::vector> classes = m_info->classes; 76 | 77 | nlohmann::ordered_json json; 78 | 79 | for (size_t i : sorted(classes, [&classes](size_t a, size_t b) { return classes[a]->typeID < classes[b]->typeID; })) { 80 | const InfoClass& cls = *classes[i]; 81 | 82 | json[std::to_string(cls.typeID)] = cls.name; 83 | } 84 | 85 | os << std::setw(2) << json << std::endl; 86 | } 87 | 88 | void Dumper::dumpHashesJson() const { 89 | std::cout << "Writing hashes.json..." << std::endl; 90 | 91 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 92 | std::ofstream os(outDirectory / "hashes.json"); 93 | 94 | std::vector> classes = m_info->classes; 95 | 96 | nlohmann::ordered_json json; 97 | 98 | for (size_t i : sorted(classes, [&classes](size_t a, size_t b) { return classes[a]->typeID < classes[b]->typeID; })) { 99 | const InfoClass& cls = *classes[i]; 100 | 101 | if (!cls.isAbstract && cls.releaseRootNode != nullptr) { 102 | std::stringstream ss; 103 | 104 | std::shared_ptr md4 = std::make_shared(); 105 | cls.releaseRootNode->hash(md4); 106 | std::vector hash = md4->digest(); 107 | for (int i = 0; i < hash.size(); i++) { 108 | ss << std::format("{:02X}", hash[i]); 109 | } 110 | 111 | json[cls.name] = ss.str(); 112 | } 113 | } 114 | 115 | os << std::setw(2) << json << std::endl; 116 | } 117 | 118 | void Dumper::dumpRTTI() const { 119 | std::cout << "Writing RTTI..." << std::endl; 120 | 121 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 122 | std::ofstream os(outDirectory / "RTTI.dump"); 123 | 124 | std::vector> classes = m_info->classes; 125 | 126 | for (size_t i : sorted(classes, [&classes](size_t a, size_t b) { return classes[a]->typeID < classes[b]->typeID; })) { 127 | const InfoClass& cls = *classes[i]; 128 | 129 | os << std::format("PersistentTypeID {}\n", cls.typeID); 130 | os << std::format(" Name {}\n", cls.name); 131 | os << std::format(" Namespace {}\n", cls.nameSpace); 132 | os << std::format(" Module {}\n", cls.module); 133 | os << std::format(" Base {}\n", cls.base); 134 | os << std::format(" DescendantCount {}\n", cls.descendantCount); 135 | os << std::format(" IsAbstract {}\n", cls.isAbstract ? "True" : "False"); 136 | os << std::format(" IsSealed {}\n", cls.isSealed ? "True" : "False"); 137 | os << std::format(" IsStripped {}\n", cls.isStripped ? "True" : "False"); 138 | os << std::format(" IsEditorOnly {}\n", cls.isEditorOnly ? "True" : "False"); 139 | os << std::endl; 140 | } 141 | } 142 | 143 | void Dumper::dumpStruct(Unity::TransferInstruction transfer) const { 144 | std::cout << "Writing structure information dump..." << std::endl; 145 | 146 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 147 | std::ofstream os(outDirectory / "structs.dump", std::ios::binary); 148 | 149 | std::vector> classes = m_info->classes; 150 | 151 | int32_t typeCount = 0; 152 | for (size_t i : sorted(classes, [&classes](size_t a, size_t b) { return classes[a]->typeID < classes[b]->typeID; })) { 153 | InfoClass type = *classes[i]; 154 | InfoClass iter = type; 155 | 156 | std::cout << std::format("[{0}] Child: {1}::{2}, {3}, {4}", typeCount, iter.nameSpace, iter.name, iter.module, iter.typeID) << std::endl; 157 | std::cout << std::format("[{0}] Getting base type...", typeCount) << std::endl; 158 | 159 | std::string inheritance; 160 | while (true) { 161 | inheritance += iter.name; 162 | 163 | if (iter.base.empty()) { 164 | break; 165 | } 166 | 167 | inheritance += " <- "; 168 | std::vector>::iterator base = std::find_if(classes.begin(), classes.end(), [&iter](std::shared_ptr const& other) { return other->name == iter.base; }); 169 | if (base != classes.end()) { 170 | iter = *base->get(); 171 | } 172 | } 173 | 174 | os << std::format("\n// classID{{{0}}}: {1}\r\n", type.typeID, inheritance); 175 | iter = type; 176 | 177 | while (iter.isAbstract) { 178 | os << std::format("// {0} is abstract\r\n", iter.name); 179 | 180 | if (iter.base.empty()) { 181 | break; 182 | } 183 | 184 | std::vector>::iterator base = std::find_if(classes.begin(), classes.end(), [&iter](std::shared_ptr const& other) { return other->name == iter.base; }); 185 | if (base != classes.end()) { 186 | iter = *base->get(); 187 | } 188 | } 189 | 190 | std::cout << std::format("[{0}] Base: {1}::{2}, {3}, {4}", typeCount, iter.nameSpace, iter.name, iter.module, iter.typeID) << std::endl; 191 | 192 | std::shared_ptr tree = (int32_t)(transfer & Unity::TransferInstruction::SerializeGameRelease) != 0 ? iter.releaseRootNode : iter.editorRootNode; 193 | if (tree.get() != nullptr) { 194 | dumpNodes(*tree, os); 195 | } 196 | 197 | typeCount++; 198 | } 199 | } 200 | 201 | void Dumper::dumpStructData(Unity::TransferInstruction transfer) const { 202 | std::cout << "Writing structure information..." << std::endl; 203 | 204 | std::filesystem::path outDirectory(m_engine.options().outputDirectory); 205 | std::ofstream os(outDirectory / "structs.dat", std::ios::binary); 206 | 207 | os << m_engine.version().str(); 208 | os <= (uint8_t)0; 209 | os <= (int32_t)7; 210 | os <= (uint8_t)1; // hasTypeTrees 211 | 212 | auto countPos = os.tellp(); 213 | 214 | std::vector> types = m_engine.rtti().types(); 215 | 216 | int32_t typeCount = 0; 217 | os <= typeCount; 218 | for (size_t i : sorted(types, [&types](size_t a, size_t b) { return types[a]->typeID() < types[b]->typeID(); })) { 219 | std::shared_ptr type = types[i]; 220 | std::shared_ptr iter; 221 | 222 | std::cout << std::format("[{0}] Child: {1}::{2}, {3}, {4}", typeCount, type->nameSpace(), type->name(), type->module(), type->typeID()) << std::endl; 223 | std::cout << std::format("[{0}] Getting base type...", typeCount) << std::endl; 224 | 225 | iter.swap(type); 226 | 227 | while (iter->isAbstract()) { 228 | std::shared_ptr base = iter->base(); 229 | 230 | if (base.get() == nullptr) { 231 | break; 232 | } 233 | 234 | iter.swap(base); 235 | } 236 | 237 | std::cout << std::format("[{0}] Base: {1}::{2}, {3}, {4}", typeCount, iter->nameSpace(), iter->name(), iter->module(), iter->typeID()) << std::endl; 238 | 239 | if (iter->typeID() == 116) continue; // MonoManager 240 | 241 | if (contains(m_engine.options().exclude, iter->typeID())) { 242 | std::cout << std::format("[{0}] Type {1} is excluded, skipping...", typeCount, iter->name()) << std::endl; 243 | continue; 244 | } 245 | 246 | std::cout << std::format("[{0}] Producing native object...", typeCount) << std::endl; 247 | std::shared_ptr object = m_engine.nativeObject().produce(*iter, 0, Unity::CreationMode::Default); 248 | 249 | if (object.get() == nullptr) { 250 | continue; 251 | } 252 | 253 | std::cout << std::format("[{0}] Produced object {1}. Persistent = {2}.", typeCount, object->instanceID(), object->isPersistent()) << std::endl; 254 | std::cout << std::format("[{0}] Generating type tree...", typeCount) << std::endl; 255 | 256 | std::shared_ptr tree = m_engine.typeTree().typeTree(*object, transfer); 257 | 258 | std::cout << std::format("[{0}] Getting GUID...", typeCount) << std::endl; 259 | os <= iter->typeID(); 260 | for (int32_t j = 0, n = iter->typeID() < 0 ? 0x20 : 0x10; j < n; j++) { 261 | os <= (uint8_t)0; 262 | } 263 | 264 | dumpBinary(*tree, os); 265 | 266 | typeCount++; 267 | } 268 | 269 | os.seekp(countPos); 270 | os <= typeCount; 271 | } 272 | 273 | static void dumpNodes(const InfoNode& node, std::ofstream& stream) { 274 | for (int32_t i = 0; i < node.level; i++) { 275 | stream << '\t'; 276 | } 277 | 278 | stream << std::format("{0} {1} // ByteSize{{{2}}}, Index{{{3}}}, Version{{{4}}}, IsArray{{{5}}}, MetaFlag{{{6}}}\r\n", 279 | node.type, 280 | node.name, 281 | std::format("{:x}", (uint32_t)node.byteSize), 282 | std::format("{:x}", node.index), 283 | std::format("{:x}", node.version), 284 | std::format("{:x}", node.nodeType), 285 | std::format("{:x}", node.meta) 286 | ); 287 | 288 | if (!node.subNodes.empty()) { 289 | for (std::shared_ptr sub : node.subNodes) { 290 | dumpNodes(*sub, stream); 291 | } 292 | } 293 | } 294 | 295 | static void dumpBinary(const Unity::ITypeTree& tree, std::ofstream& stream) { 296 | std::vector> nodes = tree.nodes(); 297 | Unity::DynamicArray stringBuffer = tree.stringBuffer(); 298 | 299 | stream <= (uint32_t)nodes.size(); 300 | stream <= (uint32_t)stringBuffer.size; 301 | for (std::shared_ptr node : nodes) { 302 | stream <= node->version(); 303 | stream <= node->level(); 304 | stream <= (uint8_t)node->nodeType(); 305 | stream <= node->typeStrOffset(); 306 | stream <= node->nameStrOffset(); 307 | stream <= node->byteSize(); 308 | stream <= node->index(); 309 | stream <= (uint32_t)node->meta(); 310 | } 311 | 312 | for (int64_t i = 0, n = stringBuffer.size; i < n; i++) { 313 | stream <= stringBuffer.data[i]; 314 | } 315 | } 316 | } --------------------------------------------------------------------------------