forked from ReScrap/ScrapHacks
		
	Lots of Updates (expand for more):
- Started implementing new parser for chunked data - Started documenting data formats - Started dissector for network protocol - Added AI-Graph renderer (converts .pth files to python data you can import into Blender) - Added Script to convert savefile to JSON - Added (old) parser for chunked data format - Added basic parser for LFVF data section (Vertex Data) - Added script to analyze and filter read trace generated with frida script - Added various Frida scripts
This commit is contained in:
		
							parent
							
								
									aabacafd9c
								
							
						
					
					
						commit
						8d92f25b8c
					
				
					 47 changed files with 2744 additions and 411 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -265,3 +265,5 @@ __pycache__/ | |||
| ScrapHacks/build/* | ||||
| ScrapHacks/src/D3D8_VMT.hpp | ||||
| .vscode/c_cpp_properties.json | ||||
| tools/*.log | ||||
| frida/*.mp | ||||
							
								
								
									
										104
									
								
								NOTES.md
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								NOTES.md
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| # Infos | ||||
| 
 | ||||
| - Engine: ScrapEngine | ||||
| - Engine: ScrapEngine/Mercury Engine | ||||
| - Ingame Scripting Language: Python 1.5.2 | ||||
| - Interesting memory locations and functions are noted in `config.yml` | ||||
| 
 | ||||
|  | @ -8,6 +8,8 @@ | |||
| 
 | ||||
| - `-console`: open external console window on start | ||||
| - `-wideWindow`: start game in widescreen mode | ||||
| - `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`) | ||||
| - `-server`: start in multiplayer server mode | ||||
| 
 | ||||
| # Functions identified: | ||||
| 
 | ||||
|  | @ -23,10 +25,10 @@ | |||
| 
 | ||||
| ## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`): | ||||
| 
 | ||||
| * `listar luces` | ||||
| * `listar` | ||||
| * `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end)) | ||||
| * `mem` | ||||
| * `listar luces` List lights in scene | ||||
| * `listar` list models in scene | ||||
| * `arbol <model_name>` show details for model  | ||||
| * `mem` (doesn't do anything?) | ||||
| * `ver uniones`  | ||||
| * Easter Eggs: | ||||
|   * `imbecil` | ||||
|  | @ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s) | |||
| 
 | ||||
| ## Other Functions: | ||||
| 
 | ||||
| Check `r2_analyze.py` for full list | ||||
| Check `config.yml` for full list | ||||
| 
 | ||||
| ## File Index struct @ `0x7fcbec` | ||||
| 
 | ||||
| ```cpp | ||||
| 
 | ||||
| struct FileEntry { | ||||
|   uint32_t offset; | ||||
|   uint32_t size; | ||||
|  | @ -87,7 +88,7 @@ struct FileIDX { | |||
| }; | ||||
| ``` | ||||
| 
 | ||||
| ## Packed Index struct (array @ `0x7fc1b0`) | ||||
| ## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`) | ||||
| ```cpp | ||||
| struct PackedIDX { | ||||
|   void** VMT; | ||||
|  | @ -111,7 +112,10 @@ struct CPP_Callback { | |||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## Game engine Variables Pointer @ `0x7FBE4C` | ||||
| 
 | ||||
| ## Game engine Variables Hashtable @ `0x7fbe50` | ||||
| 
 | ||||
| ## Game engine Variables @ `0x7fbe4c` | ||||
| 
 | ||||
| Structure: | ||||
| 
 | ||||
|  | @ -131,19 +135,20 @@ struct GameVar { | |||
| Types | ||||
| 
 | ||||
| | Value | Type            | | ||||
| | ------ | ----------------------- | | ||||
| | `0x10` | const char*             | | ||||
| | `0x20` | int32_t                 | | ||||
| | `0x30` | User Control Definition | | ||||
| | `0x40` | float                   | | ||||
| | `0x60` | Callback function       | | ||||
| |-------|-----------------| | ||||
| | `0x1` | const char*     | | ||||
| | `0x2` | int32_t         | | ||||
| | `0x3` | List of Defines | | ||||
| | `0x4` | float           | | ||||
| | `0x5` | function        | | ||||
| | `0x6` | Script function | | ||||
| 
 | ||||
| ## Game World/State Pointer @ `0x7fe944` | ||||
| 
 | ||||
| Points to World struct | ||||
| 
 | ||||
| | Offset | Type                     | Description                            | | ||||
| | ------ | ------------------------ | -------------------------------------- | | ||||
| |--------|--------------------------|----------------------------------------| | ||||
| | 0x0000 | `void**`                 | Virtual Method Table                   | | ||||
| | 0x0004 | `uint32_t`               | Size of Entity Hashtable               | | ||||
| | 0x0008 | `void**`                 | Pointer to Entity Hashtable            | | ||||
|  | @ -172,6 +177,15 @@ Points to World struct | |||
| | 0x2238 | `???`                    | Used in `World_Init`                   | | ||||
| | 0x2254 | `float`                  | Used in `World_Init`                   | | ||||
| 
 | ||||
| ## cPyEntity structure | ||||
| 
 | ||||
| | Offset | Type     | Description          | | ||||
| |--------|----------|----------------------| | ||||
| | 0x0000 | `void**` | Virtual Method Table | | ||||
| | 0x0004 | `char*`  | Name                 | | ||||
| | 0x0008 | `void*`  | ???                  | | ||||
| 
 | ||||
| 
 | ||||
| ## Entity Hash Table | ||||
| 
 | ||||
| Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation) | ||||
|  | @ -189,7 +203,7 @@ struct HT_Entry { | |||
| Data format: | ||||
| 
 | ||||
| | Offset | Type          | Description              | | ||||
| | ------ | ------------- | ------------------------ | | ||||
| |--------|---------------|--------------------------| | ||||
| | 0x0    | `void**`      | Virtual Method Table (?) | | ||||
| | 0x4    | `const char*` | name as string           | | ||||
| | 0x14   | `void*`       | pointer to self (why?)   | | ||||
|  | @ -204,21 +218,57 @@ Attributes: | |||
| - `OnDeath` | ||||
| - `OnDamage` | ||||
| 
 | ||||
| # File Formats | ||||
| # Netplay protocol (WIP) | ||||
| 
 | ||||
| ## .packed File Format: | ||||
| Game Info Packet | ||||
| 
 | ||||
| ``` | ||||
| Header: | ||||
|     "BFPK\0\0\0\0" | ||||
|     Int32ul: number of files | ||||
|     for each file: | ||||
|         Int32ul: path length | ||||
|         String: path | ||||
|         Int32ul: size | ||||
|         Int32ul: offset in file | ||||
| Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086 | ||||
| [0-3] header/ID? | ||||
| [4-5] port (16-bit) | ||||
| [6-7] max_players (16-bit) | ||||
| [8-9] curr_player (16-bit) | ||||
| [10-x] server name (char*) | ||||
| 
 | ||||
|            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF | ||||
| 0019fdc0  ba ce 00 01 b6 6d 0a 00 00 00 42 00 30 fe 19 00  .....m....B.0... | ||||
| 0019fdd0  ff ff ff ff 27 2b b3 9b c7 3e bb 00 9c af 29 00  ....'+...>....). | ||||
| 0019fde0  db 69 00 00 00 00 00 00 00 00 44 65 61 74 68 4d  .i........DeathM | ||||
| 0019fdf0  61 74 63 68 00 00 00 00 ff ff 46 5a 00 4a 91 f0  atch......FZ.J.. | ||||
| 0019fe00  92 8b 57 4e 7f 00 00 00 10 21 fe 38 0d ae 00 00  ..WN.....!.8.... | ||||
| 0019fe10  f0 ce f3 36 a0 e8 0b 77 a0 e8                    ...6...w.. | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| Player Join Packet | ||||
| 
 | ||||
| ``` | ||||
| [0-3] header/ID? | ||||
| [6-x] Player name | ||||
| 
 | ||||
|            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF | ||||
| 09c9dfe8  7f 47 00 00 00 0e 55 6e 6e 61 6d 65 64 20 50 6c  .G....Unnamed Pl | ||||
| 09c9dff8  61 79 65 72 06 53 42 6f 73 73 31 b9 00 07 50 5f  ayer.SBoss1...P_ | ||||
| 09c9e008  42 65 74 74 79 06 4d 42 4f 53 53 31 06 4d 42 4f  Betty.MBOSS1.MBO | ||||
| 09c9e018  53 53 31 00 00 10 30 2c 31 35 2c 30 2c 30 2c 31  SS1...0,15,0,0,1 | ||||
| 09c9e028  35 2c 31 35 2c 31 02 00 00 00                    5,15,1.... | ||||
| ``` | ||||
| 
 | ||||
| | Message                                  | Description                                                       | | ||||
| |------------------------------------------|-------------------------------------------------------------------| | ||||
| | `5c68625c32383230395c73637261706c616e64` | "Scrapland Server" announcement broadcast (`\hb\28209\scrapland`) | | ||||
| | `7f01000007`                             | Retrieve Game info                                                | | ||||
| | `48423d35323932322c3235363a323830383600` | Connection Information (`HB=52922,256:28086`)                     | | ||||
| 
 | ||||
| # [Notes](NOTES.md) | ||||
| 
 | ||||
| ## File Formats | ||||
| 
 | ||||
| - [Chunked](file_formats/chunked.md) | ||||
| - [Packed](file_formats/packed.md) | ||||
| - [AI Pathfinding Graph](file_formats/ai_path.md) | ||||
| 
 | ||||
| 
 | ||||
| # Virtual Method Tables: | ||||
| 
 | ||||
| check `r2_analyze.py` for full list | ||||
|  |  | |||
							
								
								
									
										37
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,13 +1,24 @@ | |||
| # Scrapland Reverse Engineering notes and tools | ||||
| 
 | ||||
| ## Scripts: | ||||
| * `parse_save.py`: Dumps information extracted from Save file | ||||
| 
 | ||||
| ## Note! | ||||
| 
 | ||||
| All memory addresses are only valid for an unprotected `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match and you try to inject ScrapHacks | ||||
| 
 | ||||
| [Computer Bild Spiele Issue 2006/08](https://archive.org/download/cbs-2006-08-coverdisc/) Contains a full version of the game which was used as the basis for this project | ||||
| 
 | ||||
| ## Scripts | ||||
| 
 | ||||
| * `tools/rbingrep.py`: Search for pattern in all files and generate radare2 script to find all references (currently configured to search for chunked file section headers) | ||||
| * `frida/`: Scripts for use with Frida | ||||
| * `parse_chunked.py`: WIP Parser for the game's chunked data format (Models, Animations, Maps) | ||||
| * `save_to_json.py`: Convert game save to JSON | ||||
| * `scrapper.py`: Extractor and Repacker for *.packed files, needs the `construct` and `tqdm` python modules and python 3.x | ||||
|  - Run `scrapper.py -h` for help | ||||
| * `r2_analyze.py`: uses radare2 to parse and label a lot of interesting stuff in the `Scrap.exe` binary | ||||
| * `lib/dbg.py`: general Script for poking around inside the game's scripting system | ||||
|  - Run `import dbg` inside the Game's Console, | ||||
|   this will load all builtin modules and enable godmode | ||||
|  - Run `import dbg;dbg.init()` inside the Game's Console, | ||||
|   this will load all builtin modules, ScrapHacks and enable godmode | ||||
|  - The dbg module also enables writing to the ingame console using `print <var>` | ||||
|   and defines two global functions s_write() and e_write() for writing to the Ingame Console's Stdout and Stderr Stream | ||||
|  - `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly) | ||||
|  | @ -24,9 +35,15 @@ WIP Memory hacking library | |||
| 
 | ||||
| # Tools used: | ||||
| 
 | ||||
| - [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) | ||||
| - [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/) | ||||
| - [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) | ||||
| - [HxD](https://mh-nexus.de/en/hxd/) | ||||
| - [Kaitai Struct](http://kaitai.io/) | ||||
| - [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/) | ||||
| - Binary parsing: | ||||
|   - [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis | ||||
|   - [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing | ||||
|   - [Kaitai Struct](http://kaitai.io/) for binary parsing | ||||
| - Static analysis: | ||||
|   - [IDA](https://www.hex-rays.com/products/ida/index.shtml) initialy used, later replaced by radare2 and Cutter | ||||
|   - [radare2](https://www.radare.org/) | ||||
|   - [Cutter](https://cutter.re/) | ||||
| - Dynamic analysis: | ||||
|   - [x64dbg](https://x64dbg.com/) for dynamic analysis | ||||
|   - [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) to analyze structures and classes in memory | ||||
|   - [Frida](https://frida.re/) for tracing and instrumenting functions | ||||
							
								
								
									
										3
									
								
								ScrapHacks/.vscode/c_cpp_properties.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								ScrapHacks/.vscode/c_cpp_properties.json
									
										
									
									
										vendored
									
									
								
							|  | @ -17,8 +17,7 @@ | |||
|             "cStandard": "c11", | ||||
|             "cppStandard": "c++17", | ||||
|             "intelliSenseMode": "msvc-x86", | ||||
|             "configurationProvider": "vector-of-bool.cmake-tools", | ||||
|             "compileCommands": "${workspaceFolder}/build/compile_commands.json" | ||||
|             "configurationProvider": "vector-of-bool.cmake-tools" | ||||
|         } | ||||
|     ], | ||||
|     "version": 4 | ||||
|  |  | |||
							
								
								
									
										8
									
								
								ScrapHacks/.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								ScrapHacks/.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -78,5 +78,11 @@ | |||
|         "xtr1common": "cpp", | ||||
|         "xtree": "cpp", | ||||
|         "xutility": "cpp" | ||||
|     } | ||||
|     }, | ||||
|     "cmake.generator": "NMake Makefiles", | ||||
|     "cmake.preferredGenerators": [ | ||||
|         "NMake Makefiles", | ||||
|         "Ninja", | ||||
|         "Unix Makefiles" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										21
									
								
								ScrapHacks/.vscode/tasks.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ScrapHacks/.vscode/tasks.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| { | ||||
|     // See https://go.microsoft.com/fwlink/?LinkId=733558 | ||||
|     // for the documentation about the tasks.json format | ||||
|     "version": "2.0.0", | ||||
|     "tasks": [ | ||||
|         { | ||||
|             "label": "build", | ||||
|             "type": "shell", | ||||
|             "command": "${workspaceFolder}\\build.bat", | ||||
|             "problemMatcher": [ | ||||
|                 "$msCompile" | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             "label": "clean", | ||||
|             "type": "shell", | ||||
|             "command": "rm -Force -Recurse ${workspaceFolder}\\build", | ||||
|             "problemMatcher": [] | ||||
|         } | ||||
|     ] | ||||
| } | ||||
|  | @ -1,6 +1,5 @@ | |||
| cmake_minimum_required(VERSION 3.1) | ||||
| cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) | ||||
| 
 | ||||
| project(ScrapHacks | ||||
|         VERSION 1.0 | ||||
|         DESCRIPTION "Scrapland memory hacking library" | ||||
|  | @ -13,6 +12,8 @@ if(NOT IS_ABSOLUTE "${SCRAPLAND_DIR}" OR NOT EXISTS "${SCRAPLAND_DIR}") | |||
|   message(FATAL_ERROR "Scrapland installation folder not found!") | ||||
| endif() | ||||
| 
 | ||||
| message(STATUS "Scrapland found at ${SCRAPLAND_DIR}") | ||||
| 
 | ||||
| message(STATUS "Checking Scrap.exe hash") | ||||
| file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" SCRAP_EXE_HASH) | ||||
| 
 | ||||
|  | @ -20,14 +21,26 @@ if(NOT ${SCRAP_EXE_HASH} STREQUAL "d2dde960e8eca69d60c2e39a439088b75f0c89fa") | |||
|   message(FATAL_ERROR "Scrap.exe hash miss match!") | ||||
| endif() | ||||
| 
 | ||||
| # ============================== | ||||
| # "${SCRAPLAND_DIR}/Bin/Scrap.exe" | ||||
| add_custom_target( | ||||
|   run | ||||
|   COMMAND "Scrap.exe" | ||||
|   WORKING_DIRECTORY "${SCRAPLAND_DIR}/Bin" | ||||
|   VERBATIM | ||||
| ) | ||||
| add_dependencies(run install) | ||||
| # ============================== | ||||
| 
 | ||||
| set(COMPONENT "ScrapHacks") | ||||
| set(FETCHCONTENT_QUIET 0) | ||||
| set(CMAKE_BUILD_TYPE "RelMinSize") | ||||
| set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}") | ||||
| set(ASMJIT_EMBED true) | ||||
| set(ASMTK_EMBED true) | ||||
| set(ZYDIS_BUILD_TOOLS false) | ||||
| set(ZYDIS_BUILD_EXAMPLES false) | ||||
| 
 | ||||
| set(ASMJIT_EMBED ON CACHE INTERNAL "") | ||||
| set(ASMTK_EMBED ON CACHE INTERNAL "") | ||||
| set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "") | ||||
| set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "") | ||||
| set(toml11_BUILD_TEST OFF CACHE INTERNAL "")   | ||||
| 
 | ||||
| if(WIN32) | ||||
|   if(MSVC) | ||||
|  | @ -44,54 +57,67 @@ endif(WIN32) | |||
| 
 | ||||
| include(FetchContent) | ||||
| 
 | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|   Python | ||||
|   PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||
|   URL https://www.python.org/ftp/python/src/py152.tgz | ||||
|   URL_HASH SHA1=2d648d07b1aa1aab32a3a24851c33715141779b9 | ||||
| ) | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|   DirectX | ||||
|   PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||
|   URL | ||||
|     https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip | ||||
|   URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312) | ||||
| 
 | ||||
| FetchContent_MakeAvailable(DirectX) | ||||
|   URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip | ||||
|   URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312 | ||||
| ) | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|     ASM_JIT | ||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     GIT_REPOSITORY git@github.com:asmjit/asmjit.git | ||||
|     GIT_SHALLOW true | ||||
|     GIT_PROGRESS true | ||||
|     INSTALL_COMMAND "" | ||||
|     CONFIGURE_COMMAND "" | ||||
| ) | ||||
| 
 | ||||
| FetchContent_MakeAvailable(ASM_JIT) | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|     ASM_TK | ||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     GIT_REPOSITORY git@github.com:asmjit/asmtk.git | ||||
|     GIT_SHALLOW true | ||||
|     GIT_PROGRESS true | ||||
|     INSTALL_COMMAND "" | ||||
| ) | ||||
| 
 | ||||
| FetchContent_MakeAvailable(ASM_TK) | ||||
| 
 | ||||
| set(ASMJIT_DIR ${asm_jit_SOURCE_DIR}) | ||||
| 
 | ||||
| include(${asm_tk_SOURCE_DIR}/CMakeLists.txt) | ||||
| 
 | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|     Zydis | ||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     GIT_REPOSITORY git@github.com:zyantific/zydis.git | ||||
|     GIT_SHALLOW true | ||||
|     GIT_PROGRESS true | ||||
|     INSTALL_COMMAND "" | ||||
| ) | ||||
| 
 | ||||
| # FetchContent_MakeAvailable(Zydis) | ||||
| FetchContent_MakeAvailable(Python DirectX ASM_JIT ASM_TK Zydis) | ||||
| 
 | ||||
| include_directories(AFTER ${directx_SOURCE_DIR}/8.0/include/ ${ASMTK_INCLUDE_DIRS} ${ASMJIT_INCLUDE_DIRS}) | ||||
| set(ASMJIT_DIR ${asm_jit_SOURCE_DIR}) | ||||
| 
 | ||||
| include(${asm_tk_SOURCE_DIR}/CMakeLists.txt) | ||||
| 
 | ||||
| 
 | ||||
| message(STATUS "Python 1.5.2: ${python_SOURCE_DIR}") | ||||
| message(STATUS "DX8: ${directx_SOURCE_DIR}") | ||||
| message(STATUS "Zydis: ${zydis_SOURCE_DIR}") | ||||
| 
 | ||||
| include_directories(AFTER | ||||
|   ${directx_SOURCE_DIR}/8.0/include/ | ||||
|   ${python_SOURCE_DIR}/Include/ | ||||
|   ${ASMTK_INCLUDE_DIRS} | ||||
|   ${ASMJIT_INCLUDE_DIRS} | ||||
|   ${toml_SOURCE_DIR} | ||||
| ) | ||||
| link_directories(AFTER ${directx_SOURCE_DIR}/8.0/lib/) | ||||
| 
 | ||||
| find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter) | ||||
| find_package(Python3 REQUIRED COMPONENTS Interpreter) | ||||
| 
 | ||||
| add_custom_command( | ||||
|   OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp | ||||
|  | @ -111,7 +137,10 @@ add_library(ScrapHack SHARED | |||
|   ${ASMJIT_SRC} | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd") | ||||
| set_target_properties(ScrapHack PROPERTIES PREFIX "_") | ||||
| 
 | ||||
| add_dependencies(ScrapHack D3D8_VMT) | ||||
| # add_dependencies(ScrapHack Python152) | ||||
| # add_dependencies(ScrapHack Python152_Bin) | ||||
|  | @ -119,10 +148,13 @@ target_link_libraries(ScrapHack | |||
|                       d3d8 | ||||
|                       d3dx8 | ||||
|                       dxerr8 | ||||
|                       gdiplus | ||||
|                       # gdiplus | ||||
|                       # PYTHON15 | ||||
|                       # Zydis | ||||
|                       Zydis | ||||
|                       legacy_stdio_definitions) | ||||
| 
 | ||||
| install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib) | ||||
| target_compile_features(ScrapHack PUBLIC cxx_std_11) | ||||
| install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks) | ||||
| install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/ScrapHack.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks) | ||||
| install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/dbg.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks) | ||||
| 
 | ||||
| target_compile_features(ScrapHack PUBLIC cxx_std_17) | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| ## Features | ||||
| 
 | ||||
| - read and write memory | ||||
| - disassemble memory (using zydis) | ||||
| - change DirectX state | ||||
| - Draw DirectX overlay (still need to make a useful overlay) | ||||
| - Dump various data structures to the console | ||||
| - Assemble and execute code on the fly | ||||
| - Assemble and execute code on the fly (using asmtk) | ||||
| - Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands) | ||||
| 
 | ||||
| ## Prerequisites | ||||
|  | @ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have | |||
| - type `import ScrapHack` | ||||
| - type `$help` | ||||
| 
 | ||||
| ## Notes | ||||
| ## Config file keys | ||||
| 
 | ||||
| (this has only been tested with a (cracked/unpacked/de-obfuscated) `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match) | ||||
| - patches.asm: map of address->list of assembly instructions | ||||
| - patches.hex: map of address->hex bytes | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
|     "patches": { | ||||
|         "hex": { | ||||
|             "0xDEADBEEF": "BADFOODDEADFEED" | ||||
|         }, | ||||
|         "asm": { | ||||
|             "0xBADF00D": [ | ||||
|                 "pushad", | ||||
|                 "call 0xf00dbabe", | ||||
|                 "popad", | ||||
|                 "mov eax, 0x42", | ||||
|                 "ret" | ||||
|             ] | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## Third-Party components used | ||||
| 
 | ||||
| - Zydis disassembler | ||||
| - asmJIT/asmTK assembler | ||||
| - nlohmann/json JSON-parser | ||||
|  | @ -5,6 +5,15 @@ if "%VSINSTALLDIR%"=="" ( | |||
|     call "%%i" x86 | ||||
|   ) | ||||
| ) | ||||
| if "%VSINSTALLDIR%"=="" ( | ||||
|   echo "VSINSTALLDIR" not set something is wrong! | ||||
| ) else ( | ||||
|   if not exist build cmake -G"NMake Makefiles" -B build | ||||
|   if "%1"=="--run" ( | ||||
|     cmake --build build --target run | ||||
|   ) else ( | ||||
|     cmake --build build --target install | ||||
|   ) | ||||
| ) | ||||
| 
 | ||||
| endlocal | ||||
|  | @ -147,6 +147,13 @@ void unhook_d3d8() { | |||
|     hooked=false; | ||||
| } | ||||
| 
 | ||||
| map<size_t,void*> *dx_hooks = new map<size_t,void*>({ | ||||
|     {VMT_IDirect3DDevice8::m_EndScene,H_EndScene}, | ||||
|     {VMT_IDirect3DDevice8::m_BeginScene,H_BeginScene}, | ||||
|     {VMT_IDirect3DDevice8::m_DrawIndexedPrimitive,H_DrawIndexedPrimitive}, | ||||
|     {VMT_IDirect3DDevice8::m_SetLight,H_SetLight}, | ||||
| }); | ||||
| 
 | ||||
| void hook_d3d8() { | ||||
|     if (hooked) { | ||||
|         return; | ||||
|  | @ -162,11 +169,9 @@ void hook_d3d8() { | |||
|     hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0, | ||||
|                         0, 0, "Lucida Console"); | ||||
|     hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0)); | ||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_EndScene], H_EndScene); | ||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_BeginScene], H_BeginScene); | ||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_DrawIndexedPrimitive], | ||||
|                H_DrawIndexedPrimitive); | ||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_SetLight], H_SetLight); | ||||
|     for (auto h: *dx_hooks) { | ||||
|         Hook::addr(GetVTable(dev)[h.first], h.second); | ||||
|     } | ||||
|     hooked=true; | ||||
|     return; | ||||
| } | ||||
|  |  | |||
|  | @ -9,9 +9,10 @@ | |||
| 
 | ||||
| using namespace std; | ||||
| 
 | ||||
| /*
 | ||||
| 
 | ||||
| vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) { | ||||
|     using namespace asmjit; | ||||
|     vector<uint8_t> ret; | ||||
|     JitRuntime rt; | ||||
|     CodeHolder code; | ||||
|     CodeInfo ci=rt.codeInfo(); | ||||
|  | @ -23,9 +24,15 @@ vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) { | |||
|     code.resolveUnresolvedLinks(); | ||||
|     code.relocateToBase(orig); | ||||
|     size_t code_size=code.sectionById(0)->buffer().size(); | ||||
|     code.copyFlattenedData((void*)orig, code_size, CodeHolder::kCopyWithPadding); | ||||
|     uint8_t* buffer=new uint8_t[code_size]; | ||||
|     code.copyFlattenedData((void*)buffer, code_size, CodeHolder::kCopyWithPadding); | ||||
|     for (size_t i=0;i<code_size;++i) { | ||||
|         ret.push_back(buffer[i]); | ||||
|     } | ||||
| */ | ||||
|     delete buffer; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class Hook { | ||||
|   private: | ||||
|  | @ -33,29 +40,32 @@ class Hook { | |||
|     void *orig; | ||||
|     void *detour; | ||||
|     bool enabled; | ||||
|     uint8_t orig_bytes[6]; | ||||
|     uint8_t jmp_bytes[6]; | ||||
|     uint8_t *orig_bytes; | ||||
|     uint8_t *jmp_bytes; | ||||
|     size_t size; | ||||
|     static map<uintptr_t, shared_ptr<Hook>> hooks; | ||||
| 
 | ||||
|   public: | ||||
|     Hook(void *func, void *detour) { | ||||
|         // TODO: build jmp_bytes using asmjit
 | ||||
|         uintptr_t dest = reinterpret_cast<uintptr_t>(detour); | ||||
|         uintptr_t src = reinterpret_cast<uintptr_t>(func); | ||||
|         this->orig = func; | ||||
|         this->detour = detour; | ||||
|         this->jmp_bytes[0] = 0x68; // push
 | ||||
|         this->jmp_bytes[1] = (dest >> 0) & 0xff; | ||||
|         this->jmp_bytes[2] = (dest >> 8) & 0xff; | ||||
|         this->jmp_bytes[3] = (dest >> 16) & 0xff; | ||||
|         this->jmp_bytes[4] = (dest >> 24) & 0xff; | ||||
|         this->jmp_bytes[5] = 0xC3; // ret
 | ||||
|         VirtualQuery(func, &mbi, sizeof(mbi)); | ||||
|         vector<uint8_t> code = make_trampoline(src,dest); | ||||
|         this->orig_bytes = new uint8_t[code.size()]; | ||||
|         this->jmp_bytes = new uint8_t[code.size()]; | ||||
|         this->size = code.size(); | ||||
|         this->enabled = false; | ||||
|         uint8_t* func_b = reinterpret_cast<uint8_t*>(this->orig); | ||||
|         for (size_t i=0;i<this->size;++i) { | ||||
|             this->orig_bytes[i]=func_b[i]; | ||||
|             this->jmp_bytes[i]=code[i]; | ||||
|         } | ||||
|         VirtualQuery(this->orig, &mbi, sizeof(mbi)); | ||||
|         VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, | ||||
|                        &mbi.Protect); | ||||
|         memcpy(this->orig_bytes, this->orig, 1 + 4 + 1); | ||||
|         VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|         this->enabled = false; | ||||
|         cout<<"Constructed hook from "<<func<<" to "<<detour<<", size: " << this->size<<endl; | ||||
|     } | ||||
| 
 | ||||
|     ~Hook() { | ||||
|  | @ -64,6 +74,11 @@ class Hook { | |||
|         this->disable(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     static void addr(uintptr_t _addr, void *detour) { | ||||
|         Hook::addr(reinterpret_cast<void*>(_addr),detour); | ||||
|     } | ||||
| 
 | ||||
|     static void addr(void *addr, void *detour) { | ||||
|         cout << "Hooking: [" << addr << " -> " << detour << "]" << endl; | ||||
|         uintptr_t key = reinterpret_cast<uintptr_t>(detour); | ||||
|  | @ -105,23 +120,23 @@ class Hook { | |||
| 
 | ||||
|     void disable() { | ||||
|         if (this->enabled) { | ||||
|             // cout << "Disabling: [" << this->orig << " <- " << this->detour <<
 | ||||
|             // "]"
 | ||||
|             // << endl;
 | ||||
|             cout << "Disabling: [" << this->orig << " <- " << this->detour << | ||||
|             "]" | ||||
|             << endl; | ||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, | ||||
|                            PAGE_EXECUTE_READWRITE, NULL); | ||||
|             memcpy(this->orig, this->orig_bytes, 1 + 4 + 1); | ||||
|             memcpy(this->orig, this->orig_bytes, this->size); | ||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|             this->enabled = false; | ||||
|         } | ||||
|     } | ||||
|     void enable() { | ||||
|         if (!this->enabled) { | ||||
|             // cout << "Enabling: [" << this->orig << " -> " << this->detour <<
 | ||||
|             // "]" << endl;
 | ||||
|             cout << "Enabling: [" << this->orig << " -> " << this->detour << | ||||
|             "]" << endl; | ||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, | ||||
|                            PAGE_EXECUTE_READWRITE, NULL); | ||||
|             memcpy(this->orig, this->jmp_bytes, 1 + 4 + 1); | ||||
|             memcpy(this->orig, this->jmp_bytes, this->size); | ||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|             this->enabled = true; | ||||
|         } | ||||
|  |  | |||
							
								
								
									
										74
									
								
								ScrapHacks/src/Py_Mod.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								ScrapHacks/src/Py_Mod.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| #pragma once | ||||
| #include "Scrapland.hpp" | ||||
| 
 | ||||
| using namespace std; | ||||
| 
 | ||||
| static void *py_asm(void *self, void *args) | ||||
| { | ||||
|     void* ret; | ||||
|     void *addr = nullptr; | ||||
|     char *asm_code; | ||||
|     if (!PyArg_ParseTuple(args, "s|i", &asm_code, &addr)) | ||||
|     { | ||||
|         return nullptr; | ||||
|     } | ||||
|     string code(asm_code); | ||||
|     size_t data_size=asm_size(split(code,';')); | ||||
|     if (addr==nullptr) { | ||||
|         addr = malloc(data_size); | ||||
|         if (addr==nullptr) { | ||||
|             scrap_log(ERR_COLOR, "ERROR: "); | ||||
|             scrap_log(ERR_COLOR, "malloc() failed"); | ||||
|             scrap_log(ERR_COLOR, "\n"); | ||||
|             return Py_BuildValue("s",nullptr); | ||||
|         } | ||||
|         char ptr[255]; | ||||
|         snprintf(ptr,255,"Buffer @ %p\n",(void*)addr); | ||||
|         scrap_log(INFO_COLOR,ptr); | ||||
|         ret=Py_BuildValue("(l,l)", data_size, addr); | ||||
|     } else { | ||||
|         ret=Py_BuildValue("(l,l)", data_size, addr); | ||||
|     } | ||||
|     assemble(split(code,';'),reinterpret_cast<uint64_t>(addr)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void *py_dasm(void *self, void *args) { | ||||
|     // TODO: return list of (addr,asm)
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| static void *py_read_mem(void *self, void *args) | ||||
| { | ||||
|     //TODO: implement reading memory and return data as hex string
 | ||||
|     void* ret = nullptr; | ||||
|     void *addr = nullptr; | ||||
|     size_t size = 256; | ||||
|     char *data; | ||||
|     if (!PyArg_ParseTuple(args, "ii", &addr, &size)) | ||||
|     { | ||||
|         return nullptr; | ||||
|     } | ||||
|     char* buffer=new char[size*2]; | ||||
|     for (size_t i=0;i<size;++i) { | ||||
|          | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void *py_write_mem(void *self, void *args) { | ||||
|     //TODO: implement (return None or error string)
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| static PyMethodDef SH_Methods[] = { | ||||
|     // {"asm", py_asm, 1},
 | ||||
|     // {"read", py_read_mem, 1},
 | ||||
|     // {"write", py_write_mem, 1},
 | ||||
|     {NULL, NULL} | ||||
| }; | ||||
| 
 | ||||
| void InitPyMod() | ||||
| { | ||||
|     Py_InitModule("_ScrapHack", SH_Methods, nullptr, nullptr, 1007); | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ | |||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <string> | ||||
| 
 | ||||
| // #include <Python.h>
 | ||||
| #include "Structures.hpp" | ||||
| 
 | ||||
| using namespace std; | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include <string> | ||||
| 
 | ||||
| #include <asmtk/asmtk.h> | ||||
| #include <Zydis/Zydis.h> | ||||
| #include "Scrapland.hpp" | ||||
| #include "Util.hpp" | ||||
| 
 | ||||
|  | @ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) { | |||
|     for (string line:assembly) { | ||||
|         if (err = p.parse((line+"\n").c_str())) { | ||||
|             snprintf(err_msg,1024,"PARSE ERROR: [%s] %08x (%s)\n",line.c_str(), err, DebugUtils::errorAsString(err)); | ||||
|             cerr<<err_msg<<endl; | ||||
|             scrap_log(ERR_COLOR,err_msg); | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|     if (err=code.flatten()) { | ||||
|         snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); | ||||
|         cerr<<err_msg<<endl; | ||||
|         scrap_log(ERR_COLOR,err_msg); | ||||
|         return 0; | ||||
|     } | ||||
|     if (err=code.resolveUnresolvedLinks()) { | ||||
|         snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); | ||||
|         cerr<<err_msg<<endl; | ||||
|         scrap_log(ERR_COLOR,err_msg); | ||||
|         return 0; | ||||
|     } | ||||
|  | @ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) { | |||
|     MEMORY_BASIC_INFORMATION mbi; | ||||
|      | ||||
|     if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) { | ||||
|         cerr<<"ERROR: "<< GetLastErrorAsString() <<endl; | ||||
|         scrap_log(ERR_COLOR, "ERROR: "); | ||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|  | @ -70,11 +75,13 @@ size_t assemble(vector<string> assembly,uint64_t base) { | |||
|     }; | ||||
|     if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) | ||||
|     { | ||||
|         cerr<<"ERROR: "<< GetLastErrorAsString() <<endl; | ||||
|         scrap_log(ERR_COLOR, "ERROR: "); | ||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|         return 0; | ||||
|     }; | ||||
|     cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl; | ||||
|     memcpy((void*)base,buffer.data(),buffer.size()); | ||||
|     scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size())); | ||||
|     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|  | @ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) { | |||
|     return assemble(assembly,0); | ||||
| } | ||||
| 
 | ||||
| string disassemble(void* addr, size_t num,bool compact) { | ||||
|     stringstream ret; | ||||
|     ZyanU64 z_addr = reinterpret_cast<uintptr_t>(addr); | ||||
|     ZydisDecoder decoder; | ||||
|     ZydisFormatter formatter; | ||||
|     ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_ADDRESS_WIDTH_32); | ||||
|     ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); | ||||
|      | ||||
|     ZydisDecodedInstruction instruction; | ||||
|     while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, addr, -1, &instruction))) | ||||
|     { | ||||
|         char buffer[256]; | ||||
|         ZydisFormatterFormatInstruction(&formatter, &instruction, buffer, sizeof(buffer), z_addr); | ||||
|         if (!compact) { | ||||
|             ret<< "[" << std::hex << setfill('0') << setw(8) << z_addr << "]: "; | ||||
|         } | ||||
| 
 | ||||
|         ret << buffer; | ||||
|          | ||||
|         if (compact) { | ||||
|             ret<<"; "; | ||||
|         } else { | ||||
|             ret<<endl; | ||||
|         } | ||||
|         addr = reinterpret_cast<void*>(z_addr += instruction.length); | ||||
|         num--; | ||||
|         if (num==0) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return ret.str(); | ||||
| } | ||||
| 
 | ||||
| struct Command { | ||||
|     t_cmd_func func; | ||||
|  | @ -217,6 +255,46 @@ get_protection(void *addr) { | |||
|     return mbi.Protect; | ||||
| } | ||||
| 
 | ||||
| void cmd_disassemble(Command* cmd, vector<string> args) { | ||||
|     MEMORY_BASIC_INFORMATION mbi; | ||||
|     if (args.size()<1) { | ||||
|         scrap_log(ERR_COLOR, cmd->usage); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|         return; | ||||
|     } | ||||
|     uintptr_t addr = UINTPTR_MAX; | ||||
|     size_t size = 0xff; | ||||
|     try { | ||||
|         addr = stoull(args[0], 0, 16); | ||||
|         if (args.size()>1) { | ||||
|             size = stoull(args[1]); | ||||
|         } | ||||
|     } catch (exception e) { | ||||
|         scrap_log(ERR_COLOR, "ERROR: "); | ||||
|         scrap_log(ERR_COLOR, e.what()); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|         return; | ||||
|     } | ||||
|     void *mptr = reinterpret_cast<void *>(addr); | ||||
|     if (VirtualQuery(mptr, &mbi, sizeof(mbi)) == 0) { | ||||
|         scrap_log(ERR_COLOR, "ERROR: "); | ||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|         return; | ||||
|     }; | ||||
|     if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, | ||||
|                    &mbi.Protect)) { | ||||
|         scrap_log(ERR_COLOR, "ERROR: "); | ||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||
|         scrap_log(ERR_COLOR, "\n"); | ||||
|         return; | ||||
|     }; | ||||
|     string dasm = disassemble(mptr, size, false); | ||||
|     scrap_log(INFO_COLOR, dasm); | ||||
|     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| void cmd_exec(Command* cmd, vector<string> args) { | ||||
|     void *addr; | ||||
|     MEMORY_BASIC_INFORMATION mbi; | ||||
|  | @ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) { | |||
|         return; | ||||
|     }; | ||||
|     string hxd = hexdump_s(mptr, size); | ||||
|     scrap_log(INFO_COLOR, hxd.c_str()); | ||||
|     scrap_log(INFO_COLOR, hxd); | ||||
|     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||
|     if (buffer) { | ||||
|         free(buffer); | ||||
|  | @ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) { | |||
| } | ||||
| 
 | ||||
| void cmd_dump_stack(Command* cmd, vector<string> args) { | ||||
|     stringstream ret; | ||||
|     void** stack=(void**)_AddressOfReturnAddress(); | ||||
|     cout<<"ESP:    "<<stack<<endl; | ||||
|     for (size_t n=0;n<0x100;++n) { | ||||
|         if (!addr_exists(stack[n])) { | ||||
|             continue; | ||||
|         } | ||||
|         char R=can_read(stack[n]) ? 'R' : ' '; | ||||
|         char W=can_write(stack[n]) ? 'W' : ' '; | ||||
|         char X=can_execute(stack[n]) ? 'X' : ' '; | ||||
|         cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X; | ||||
|         if (can_read(stack[n])) { | ||||
|             cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl; | ||||
|         } else { | ||||
|             cout<<endl; | ||||
|         bool r,w,x; | ||||
|         char R=(r=can_read(stack[n])) ? 'R' : ' '; | ||||
|         char W=(w=can_write(stack[n])) ? 'W' : ' '; | ||||
|         char X=(x=can_execute(stack[n])) ? 'X' : ' '; | ||||
|         ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X; | ||||
|         if (r && !x) { | ||||
|             ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"; | ||||
|         } else if (r && x) { | ||||
|             ret<<" [ "<<disassemble(stack[n],5,true)<<"]"; | ||||
|         } | ||||
|         ret<<endl; | ||||
|     } | ||||
|     scrap_log(INFO_COLOR,ret.str()); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
|  | @ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) { | |||
|                     << meth.second->ml_meth << endl; | ||||
|         } | ||||
|     } | ||||
|     scrap_log(INFO_COLOR,out.str().c_str()); | ||||
|     scrap_log(INFO_COLOR,out.str()); | ||||
| } | ||||
| 
 | ||||
| void cmd_dump_vars(Command* cmd, vector<string> args) { | ||||
|  | @ -437,7 +519,7 @@ void cmd_dump_vars(Command* cmd, vector<string> args) { | |||
|         out<<var->name<< "[" <<std::hex <<(uint16_t)var->type <<","<< (uint16_t)var->subtype << std::dec << "]: " << var->desc<<" ("<<var->value<<", "<<var->default<<")"<<endl; | ||||
|         var=var->next; | ||||
|     } | ||||
|     scrap_log(INFO_COLOR,out.str().c_str()); | ||||
|     scrap_log(INFO_COLOR,out.str()); | ||||
| } | ||||
| 
 | ||||
| void cmd_dump_ents(Command* cmd,vector<string> args) { | ||||
|  | @ -446,7 +528,7 @@ void cmd_dump_ents(Command* cmd,vector<string> args) { | |||
|     dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out); | ||||
|     out << "Entity Lists:" << endl; | ||||
|     dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out); | ||||
|     scrap_log(INFO_COLOR,out.str().c_str()); | ||||
|     scrap_log(INFO_COLOR,out.str()); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
|  | @ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) { | |||
| 
 | ||||
| void cmd_print_alarm(Command* cmd,vector<string> args) { | ||||
|     stringstream out; | ||||
|     float *alarm = ptr<float>(P_WORLD, O_ALARM); | ||||
|     float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW); | ||||
|     out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl; | ||||
|     scrap_log(INFO_COLOR,out.str().c_str()); | ||||
|     float alarm = ptr<float>(P_WORLD, O_ALARM)[0]; | ||||
|     float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0]; | ||||
|     if (alarm_grow<0) { | ||||
|         out << "Alarm: " << alarm << " - " << alarm_grow << endl; | ||||
|     } else { | ||||
|         out << "Alarm: " << alarm << " + " << alarm_grow << endl; | ||||
|     } | ||||
|     scrap_log(INFO_COLOR,out.str()); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
|  | @ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) { | |||
|     assemble(split(code,';'),buffer_addr); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void cmd_help(Command* cmd,vector<string> args); | ||||
| 
 | ||||
| static REPL* repl=new REPL( | ||||
|  | @ -538,6 +625,7 @@ static REPL* repl=new REPL( | |||
|         {"exec",new Command(cmd_exec,"Usage: $exec <addr>","Start a new thread at the specified address")}, | ||||
|         {"asm",new Command(cmd_asm,"Usage: $asm [addr] <inst1>;<inst2>;...","Assemble instructions at address, if no address is given allocate memory and assemble code into that")}, | ||||
|         {"stack",new Command(cmd_dump_stack,"Usage: $mem stack","Dump stack contents")}, | ||||
|         {"dasm",new Command(cmd_disassemble,"Usage: $mem dasm <addr> [num_inst]","Disassemble memory at address")}, | ||||
|     })}, | ||||
|     {"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")}, | ||||
|     {"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{ | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ | |||
| #include <string> | ||||
| #include <typeinfo> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <fstream> | ||||
| // #include <Python.h>
 | ||||
| #include <Windows.h> | ||||
| 
 | ||||
| using namespace std; | ||||
|  | @ -18,6 +19,7 @@ using namespace std; | |||
| #include "Scrapland.hpp" | ||||
| #include "Structures.hpp" | ||||
| #include "Util.hpp" | ||||
| #include "Py_Mod.hpp" | ||||
| 
 | ||||
| bool initialized = false; | ||||
| bool running = true; | ||||
|  | @ -28,12 +30,14 @@ void DllUnload(); | |||
| int hooked_console(const char *); | ||||
| void hook_exit(); | ||||
| 
 | ||||
| void setup_hooks() { | ||||
|     Hook::addr(reinterpret_cast<void *>(P_SCRAP_EXIT), hook_exit); | ||||
|     Hook::addr(reinterpret_cast<void *>(P_CON_HANDLER), hooked_console); | ||||
| void setup_hooks() | ||||
| { | ||||
|     Hook::addr(P_SCRAP_EXIT, hook_exit); | ||||
|     Hook::addr(P_CON_HANDLER, hooked_console); | ||||
| } | ||||
| 
 | ||||
| void MainLoop() { | ||||
| void MainLoop() | ||||
| { | ||||
|     setup_hooks(); | ||||
|     overlay = true; | ||||
|     cout << "[*] Starting main Loop" << endl; | ||||
|  | @ -49,42 +53,56 @@ void MainLoop() { | |||
|     cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)" | ||||
|          << endl; | ||||
| 
 | ||||
|     while (running) { | ||||
|     while (running) | ||||
|     { | ||||
|         Sleep(100); | ||||
|         while (key_down('F')) { | ||||
|         while (key_down('F')) | ||||
|         { | ||||
|             scrap_exec("dbg.brake()"); | ||||
|         } | ||||
|         if (key_down_norepeat(VK_F3)) { | ||||
|         if (key_down_norepeat(VK_F3)) | ||||
|         { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if (key_down_norepeat(VK_F7)) { | ||||
|         if (key_down_norepeat(VK_F7)) | ||||
|         { | ||||
|             int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY); | ||||
|             money[0] = 0x7fffffff; | ||||
|         } | ||||
| 
 | ||||
|         if (key_down_norepeat(VK_F9)) { | ||||
|         if (key_down_norepeat(VK_F8)) | ||||
|         { | ||||
|             cout << "Not yet implemented" << endl; | ||||
|         } | ||||
| 
 | ||||
|         if (key_down_norepeat(VK_F9)) | ||||
|         { | ||||
|             cout << "Entities:" << endl; | ||||
|             dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS)); | ||||
|             cout << "Entity Lists:" << endl; | ||||
|             dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS)); | ||||
|         } | ||||
|         if (key_down_norepeat(VK_F10)) { | ||||
|         if (key_down_norepeat(VK_F10)) | ||||
|         { | ||||
|             scrap_exec("dbg.settrace()"); | ||||
|         } | ||||
|     } | ||||
|     FreeLibraryAndExitThread(hMod, 0); | ||||
| } | ||||
| 
 | ||||
| void InitConsole() { | ||||
| void InitConsole() | ||||
| { | ||||
|     char me[1024]; | ||||
|     GetModuleFileName(hMod, me, 1024); | ||||
|     GetModuleFileNameA(hMod, me, 1024); | ||||
|     SetupConsole(me); | ||||
| } | ||||
| 
 | ||||
| int hooked_console(const char *cmd) { | ||||
| int hooked_console(const char *cmd) | ||||
| { | ||||
|     typedef decltype(&hooked_console) t_func; | ||||
|     if (cmd[0] == '$') { | ||||
|     if (cmd[0] == '$') | ||||
|     { | ||||
|         handle_command(++cmd); | ||||
|         return 0; | ||||
|     } | ||||
|  | @ -93,7 +111,8 @@ int hooked_console(const char *cmd) { | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void hook_exit() { | ||||
| void hook_exit() | ||||
| { | ||||
|     typedef decltype(&hook_exit) t_func; | ||||
|     shared_ptr<Hook> hook = Hook::get(hook_exit); | ||||
|     DllUnload(); | ||||
|  | @ -102,7 +121,8 @@ void hook_exit() { | |||
|     return; | ||||
| } | ||||
| 
 | ||||
| void DllInit(HMODULE mod) { | ||||
| void DllInit(HMODULE mod) | ||||
| { | ||||
|     hMod = mod; | ||||
|     char mfn[1024]; | ||||
|     GetModuleFileNameA(0, mfn, 1024); | ||||
|  | @ -119,36 +139,23 @@ void DllInit(HMODULE mod) { | |||
|     CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0); | ||||
|     cout << "[*] Starting message pump" << endl; | ||||
|     MSG msg; | ||||
|     while (GetMessage(&msg, NULL, 0, 0)) { | ||||
|     while (GetMessage(&msg, NULL, 0, 0)) | ||||
|     { | ||||
|         TranslateMessage(&msg); | ||||
|         DispatchMessage(&msg); | ||||
|     } | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| void *H_port_FixupExtension(char *name, char *filename) { | ||||
|     cout<<"FixupExtension: "<<name<<": "<<filename<<endl; | ||||
|     Hook::drop(H_port_FixupExtension); | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| void *H_PyEval_CallObjectWithKeywords(void *func, void *arg, void *kwarg) { | ||||
|     cout<<"PyEval_CallObjectWithKeywords:"<<endl; | ||||
|     cout<<"\t func: "<<func<<endl; | ||||
|     cout<<"\t arg: "<<arg<<endl; | ||||
|     cout<<"\t kwarg: "<<kwarg<<endl; | ||||
|     Hook::drop(H_PyEval_CallObjectWithKeywords); | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| void DllPreInit() { | ||||
| void DllPreInit() | ||||
| { | ||||
|     Sleep(100); | ||||
|     InitConsole(); | ||||
|     Hook::addr(reinterpret_cast<void *>(0x5a9ca0), H_port_FixupExtension); | ||||
|     Hook::addr(reinterpret_cast<void *>(0x5cdb00), | ||||
|                H_PyEval_CallObjectWithKeywords); | ||||
|     InitPyMod(); | ||||
| } | ||||
| 
 | ||||
| void DllUnload() { | ||||
| void DllUnload() | ||||
| { | ||||
|     SetConsoleCtrlHandler(NULL, false); | ||||
|     unhook_d3d8(); | ||||
|     Hook::clear(); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| #pragma once | ||||
| #include "Structures.hpp" | ||||
| 
 | ||||
| #include "Util.hpp" | ||||
| #include <sstream> | ||||
| using namespace std; | ||||
| 
 | ||||
|  | @ -17,14 +17,21 @@ using namespace std; | |||
| #define P_PY_MODS 0x79C698 | ||||
| 
 | ||||
| // FUNCTION ADDRESSES
 | ||||
| // ENGINE INTERNALS
 | ||||
| #define P_CON_HANDLER 0x402190 | ||||
| #define P_SCRAP_LOG 0x4134C0 | ||||
| #define P_SCRAP_EXEC 0x5a8390 | ||||
| #define P_SCRAP_EXIT 0x4010c0 | ||||
| #define P_D3DCHECK 0x602a70 | ||||
| #define P_D3DDEV 0x852914 | ||||
| // PYTHON FUNCTIONS
 | ||||
| #define P_Py_InitModule 0x5A8FB0 | ||||
| #define P_PyArg_ParseTuple 0x5bb9d0 | ||||
| #define P_PyBuildValue 0x5a90f0 | ||||
| #define P_PyList_New 0x5b3120 | ||||
| #define P_PyTuple_New 0x5b91a0 | ||||
| #define P_PyList_SetItem 0x5b3240 | ||||
| #define P_PyTuple_SetItem 0x5b92a0 | ||||
| 
 | ||||
| 
 | ||||
| #define MSG_COLOR scrap_RGB(128,0,255) | ||||
|  | @ -41,14 +48,20 @@ uint32_t scrap_RGB(uint8_t r,uint8_t g,uint8_t b) { | |||
| typedef int(_cdecl *t_scrap_log)(unsigned int color, const char *message); | ||||
| typedef int(_cdecl *t_scrap_exec)(const char *code); | ||||
| typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...); | ||||
| typedef void*(_cdecl *t_Py_BuildValue)(char *format, ...); | ||||
| typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods, | ||||
|                                      const char *doc, void *passthrough, | ||||
|                                      int module_api_version); | ||||
| typedef void*(_cdecl *t_PyList_New)(char *format, ...); | ||||
| typedef void*(_cdecl *t_PyTuple_New)(char *format, ...); | ||||
| typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...); | ||||
| typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...); | ||||
| 
 | ||||
| // GLOBAL FUNCTIONS
 | ||||
| auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC; | ||||
| auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple; | ||||
| auto py_initmodule = (t_Py_InitModule)P_Py_InitModule; | ||||
| auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple; | ||||
| auto Py_BuildValue = (t_Py_BuildValue)P_PyBuildValue; | ||||
| auto Py_InitModule = (t_Py_InitModule)P_Py_InitModule; | ||||
| 
 | ||||
| int scrap_log(unsigned int color,const char* msg) { | ||||
|     return ((t_scrap_log)P_SCRAP_LOG)(color,msg); | ||||
|  | @ -58,7 +71,6 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,const char* msg) { | |||
|     return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int scrap_log(unsigned int color,string msg) { | ||||
|     return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str()); | ||||
| } | ||||
|  | @ -67,6 +79,12 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,string msg) { | |||
|     return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg.c_str()); | ||||
| } | ||||
| 
 | ||||
| int scrap_err() { | ||||
|     string err("Error: "); | ||||
|     err+=GetLastErrorAsString(); | ||||
|     err+="\n"; | ||||
|     return scrap_log(ERR_COLOR,err); | ||||
| } | ||||
| 
 | ||||
| size_t size_ht(HashTable<EntityList> *ht) { | ||||
|     size_t cnt = 0; | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ string GetLastErrorAsString() { | |||
|     size_t m_size = FormatMessageA( | ||||
|         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | | ||||
|             FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|         NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||||
|         NULL, errorMessageID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), | ||||
|         (LPSTR)&messageBuffer, 0, NULL); | ||||
|     string message(messageBuffer, m_size); | ||||
|     LocalFree(messageBuffer); | ||||
|  |  | |||
|  | @ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE; | |||
| bool loaded = false; | ||||
| HMODULE mod = nullptr; | ||||
| 
 | ||||
| DLL_EXPORT void initScrapHack() { | ||||
| DLL_EXPORT void init_ScrapHack() { | ||||
|     DllPreInit(); | ||||
|     if (!loaded) { | ||||
|         Sleep(1000); | ||||
|         hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod, | ||||
|                                0, 0); | ||||
|         CloseHandle(hThread); | ||||
|  |  | |||
							
								
								
									
										23
									
								
								ScrapHacks/src/python/ScrapHack.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ScrapHacks/src/python/ScrapHack.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| # ScrapHack python interface | ||||
| import _ScrapHack | ||||
| 
 | ||||
| 
 | ||||
| class Mem: | ||||
|     def __init__(self): | ||||
|         return | ||||
| 
 | ||||
|     def __getitem__(self, key): | ||||
|         print("GI:", key) | ||||
| 
 | ||||
|     def __setitem__(self, key, value): | ||||
|         print("SI:", key, value) | ||||
| 
 | ||||
| Mem = Mem() | ||||
| 
 | ||||
| def asm(code,address=0): | ||||
|     """ | ||||
|     asm(code, address): | ||||
|      | ||||
|     Assemble code at address | ||||
|     """ | ||||
|     return _ScrapHack.asm(address, code) | ||||
|  | @ -5,10 +5,12 @@ import MissionsFuncs | |||
| import SScorer | ||||
| import Menu | ||||
| import sys | ||||
| 
 | ||||
| QC = quickconsole | ||||
| MF = MissionsFuncs | ||||
| last_frame = None | ||||
| 
 | ||||
| level = 3 | ||||
| initialized = 0 | ||||
| sys.path.append(".\\pylib\\Lib") | ||||
| sys.path.append(".\\pylib\\Libs") | ||||
| sys.path.append(".\\pylib") | ||||
|  | @ -16,11 +18,18 @@ sys.path.append(".\\pylib") | |||
| 
 | ||||
| def reload(): | ||||
|     sys.settrace(None) | ||||
|     sys.modules['__builtin__'].reload(sys.modules['dbg']) | ||||
|     sys.modules['__builtin__'].reload(sys.modules[__name__]) | ||||
| 
 | ||||
| 
 | ||||
| def dgb_info(): | ||||
|     SScorer.SetLabelText(`last_frame`, Scrap.GetTime() + 0.1) | ||||
|     if me: | ||||
|         try: | ||||
|             dbg_text = str(SVec.Mod(me.Vel)) | ||||
|         except: | ||||
|             dbg_text="" | ||||
|     else: | ||||
|         dbg_text = "" | ||||
|     SScorer.SetLabelText(dbg_text, Scrap.GetTime() + 0.1) | ||||
|     Scrap.DeleteScheduledFuncs("dbg.dbg_info") | ||||
|     Scrap.DeleteScheduledFuncs("dbg.dbg_info") | ||||
|     Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info") | ||||
|  | @ -197,12 +206,11 @@ def helplib(): | |||
|     logfile_name = None | ||||
|     print "Done!" | ||||
| 
 | ||||
| 
 | ||||
| def enable_all_conv(): | ||||
|     try: | ||||
|         import CharConversor | ||||
|     except ImportError: | ||||
|         print("CharConversor not available") | ||||
|         # print("CharConversor not available") | ||||
|         return | ||||
|     CharConversor.ConversionChars = list(CharConversor.ConversionChars) | ||||
|     E = Scrap.GetFirst() | ||||
|  | @ -244,20 +252,6 @@ def nuke(): | |||
|         E = Scrap.GetEntity(E.NextInSlot) | ||||
| 
 | ||||
| 
 | ||||
| def test_func(): | ||||
|     E = Scrap.GetFirst() | ||||
|     me = Scrap.UsrEntity(0) | ||||
|     while E: | ||||
|         if E.Name == me.Name: | ||||
|             E = Scrap.GetEntity(E.NextInSlot) | ||||
|         try: | ||||
|             E.Money=1024*1024*1024 | ||||
|             # SAI.SetStateVehicle(8,me.Name,E.Name) | ||||
|         except: | ||||
|             pass | ||||
|         E = Scrap.GetEntity(E.NextInSlot) | ||||
| 
 | ||||
| 
 | ||||
| def become(name): | ||||
|     import CharConversor | ||||
|     enable_all_conv() | ||||
|  | @ -299,7 +293,7 @@ def getall(): | |||
|     me = Scrap.UsrEntity(0) | ||||
|     while E: | ||||
|         try: | ||||
|             E.Pos = me.Pos | ||||
|             E.Descriptor = "HAXX!" | ||||
|         except: | ||||
|             pass | ||||
|         E = Scrap.GetEntity(E.NextInSlot) | ||||
|  | @ -308,6 +302,11 @@ def getall(): | |||
| def god(e=None): | ||||
|     if e == None: | ||||
|         e = Scrap.UsrEntity(0) | ||||
|         if e: | ||||
|             try: | ||||
|                 e.IsType("Car") | ||||
|             except: | ||||
|                 return | ||||
|     if e: | ||||
|         if e.IsType("Car"): | ||||
|             e.Ammo00 = SWeap.GetFAmmo(0, "Max") | ||||
|  | @ -324,9 +323,18 @@ def god(e=None): | |||
|         elif e.IsType("WalkChar"): | ||||
|             e.Energy = 1 | ||||
|         e.Invulnerable = 1 | ||||
|         e.TimeSpeed = 2.0 | ||||
|         e.Mass = 100 | ||||
|     # Scrap.SetAlarm(0.0) | ||||
|     Scrap.SetAlarmGrow(-0.5) | ||||
|     Scrap.DeleteScheduledFuncs("dbg.god") | ||||
|     Scrap.DeleteScheduledFuncs("dbg.god") | ||||
|     Scrap.AddScheduledFunc(Scrap.GetTime(), god, (e,), "dbg.god") | ||||
|     Scrap.AddScheduledFunc(Scrap.GetTime() + 0.01, god, (e,), "dbg.god") | ||||
| 
 | ||||
| 
 | ||||
| def ungod(): | ||||
|     for _ in range(1024): | ||||
|         Scrap.DeleteScheduledFuncs("dbg.god") | ||||
| 
 | ||||
| 
 | ||||
| def ultranuke(): | ||||
|  | @ -337,11 +345,92 @@ def ultranuke(): | |||
|                            (), "dbg.ultranuke") | ||||
| 
 | ||||
| 
 | ||||
| def freeze(_=None): | ||||
|     QC.freeze() | ||||
|     Scrap.DeleteScheduledFuncs("dbg.freeze") | ||||
|     Scrap.DeleteScheduledFuncs("dbg.freeze") | ||||
|     Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, freeze, (None,), "dbg.freeze") | ||||
| 
 | ||||
| 
 | ||||
| def unfreeze(_): | ||||
|     Scrap.DeleteScheduledFuncs("dbg.freeze") | ||||
|     Scrap.DeleteScheduledFuncs("dbg.freeze") | ||||
|     QC.unfreeze() | ||||
| 
 | ||||
| 
 | ||||
| def brake(): | ||||
|     if me: | ||||
|         me.Vel = (0, 0, 0) | ||||
| 
 | ||||
| 
 | ||||
| weaps_hacked = { | ||||
|     "Laser": { | ||||
|         "AmmoCost": 0, | ||||
|         "TimeDelay": 0, | ||||
|     }, | ||||
|     "Vulcan": { | ||||
|         "TimeDelay": 0.01, | ||||
|         "TimeDelayUPG": 0.01, | ||||
|         "AmmoCost": 0 | ||||
|     }, | ||||
|     "Devastator": { | ||||
|         "AmmoCost": 0, | ||||
|         "RechargeTime": 0, | ||||
|         "SpreadAngle": 0, | ||||
|     }, | ||||
|     "Tesla": { | ||||
|         "AmmoCost": 0, | ||||
|     }, | ||||
|     "ATPC": { | ||||
|         "AmmoCost": 0, | ||||
|         "UpgradeDelay": 0, | ||||
|         "Delay": 0, | ||||
|     }, | ||||
|     "Swarm": { | ||||
|         "AmmoCost1": 0, | ||||
|         "AmmoCost2": 0, | ||||
|         "AmmoCost3": 0, | ||||
|         "AmmoCost4": 0, | ||||
|         "Number1": 20, | ||||
|         "Number2": 20, | ||||
|         "Number3": 20, | ||||
|         "Number4": 20, | ||||
|         "TurnSpeed": 360000, | ||||
|         "TurnSpeedUPG": 360000, | ||||
|         "TimeDelay": 1.0, | ||||
|     }, | ||||
|     "Inferno": { | ||||
|         "AmmoCost": 1 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def weaphacks(): | ||||
|     for weapon, properties in weaps_hacked.items(): | ||||
|         for prop, value in properties.items(): | ||||
|             Scrap.Set(weapon+prop, value) | ||||
| 
 | ||||
| 
 | ||||
| def unweaphacks(): | ||||
|     for weapon, properties in weaps_hacked.items(): | ||||
|         for prop, value in properties.items(): | ||||
|             Scrap.Set(weapon+prop, Scrap.Def(weapon+prop)) | ||||
| 
 | ||||
| 
 | ||||
| def test_func(): | ||||
|     E = Scrap.GetFirst() | ||||
|     me = Scrap.UsrEntity(0) | ||||
|     while E: | ||||
|         if E.Name == me.Name: | ||||
|             E = Scrap.GetEntity(E.NextInSlot) | ||||
|         try: | ||||
|             E.Money = 1024*1024*1024 | ||||
|             # SAI.SetStateVehicle(8,me.Name,E.Name) | ||||
|         except: | ||||
|             pass | ||||
|         E = Scrap.GetEntity(E.NextInSlot) | ||||
| 
 | ||||
| 
 | ||||
| for _ in range(1024): | ||||
|     Scrap.DeleteScheduledFuncs("dbg.dbg_info") | ||||
|     Scrap.DeleteScheduledFuncs("dbg.god") | ||||
|  | @ -354,18 +443,31 @@ for module in sys.builtin_module_names: | |||
|     exec("import " + module) | ||||
| 
 | ||||
| sys.settrace(None) | ||||
| 
 | ||||
| me = Scrap.UsrEntity(0) | ||||
| notrace() | ||||
| helplib() | ||||
| # settrace() | ||||
| 
 | ||||
| 
 | ||||
| def init(): | ||||
|     global me | ||||
|     global initialized | ||||
|     if initialized == 0: | ||||
|         from ScrapHack import Mem, asm | ||||
|         sys.modules[__name__].mem = Mem | ||||
|         sys.modules[__name__].asm = asm | ||||
|         me = Scrap.UsrEntity(0) | ||||
|     dgb_info() | ||||
|     enable_all_conv() | ||||
|     god() | ||||
| Scrap.Set("debug", 3) | ||||
|     Scrap.Set("debug", level) | ||||
|     Scrap.Set("ShowConsoleLog", 1) | ||||
|     Scrap.Set("AlwaysFlushLog", 1) | ||||
| Scrap.Set("PythonExecute", "import dbg") | ||||
| exec("import QuickConsole;QuickConsole.debug=sys.modules['dbg']") | ||||
|     Scrap.Set("PythonExecute", "import dbg;dbg.init()") | ||||
|     Scrap.DeleteScheduledFuncs("dbg_init") | ||||
|     Scrap.DeleteScheduledFuncs("dbg_init") | ||||
|     Scrap.AddScheduledFunc(Scrap.GetTime()+1, init, (), "dbg_init") | ||||
|     initialized = 1 | ||||
| 
 | ||||
| print "Debug Module loaded" | ||||
| 
 | ||||
| exec("import QuickConsole;QuickConsole.dbg=sys.modules['dbg']") | ||||
| print "Debug Module loaded use /dbg.init to initialize" | ||||
							
								
								
									
										439
									
								
								config.yml
									
										
									
									
									
								
							
							
						
						
									
										439
									
								
								config.yml
									
										
									
									
									
								
							|  | @ -1,15 +1,26 @@ | |||
| notes: | | ||||
|   0x7faa4c: temp storage? | ||||
|   0x7d2094: some reference count | ||||
|   0x4039b0: fcn.handle_cli_opts? | ||||
|   0x668007: ? | ||||
| 
 | ||||
| comments: | ||||
|   0x6113f9: Check if Window exists | ||||
| 
 | ||||
| flags: | ||||
|   0x7fbfa0: P_HT_SaveVars | ||||
|   0x7fbe50: P_HT_Eng_Vars | ||||
|   0x8c8d60: P_Addr_master | ||||
|   0x8c8d50: P_Addr_client | ||||
|   0x7fa748: P_Socket | ||||
|   0x8045dc: P_Socket_Server | ||||
|   0x7FE944: P_World | ||||
|   0x792618: P_Eng3d_ver | ||||
|   0x853a24: P_gWorld | ||||
|   0x7FBE4C: P_Vars | ||||
|   0x79C698: Py_Mods | ||||
|   0x852914: P_D3D8_Dev | ||||
|   0x850258: P_D3D8_ZBuffer | ||||
|   0x850408: P_D3D8_BackBuffer | ||||
|   0x7FCC00: N_Paks_opened | ||||
|   0x7fcbec: Hash_Index_Size | ||||
|   0x7fcbf0: P_Hash_Index | ||||
|  | @ -36,7 +47,27 @@ flags: | |||
|   0x7fadd8: is_python | ||||
|   0x7fc084: pak_lock | ||||
|   0x7fbe7c: current_language | ||||
|   0x7d2094: py_refcnt_unk | ||||
|   0x7d2094: refcnt_Py_None | ||||
|   0x7fa830: P_Window | ||||
|   0x7fadd0: P_PyExecute | ||||
|   0x84d3ec: Py_Initialized | ||||
|   0x8c8f10: Py_Debug | ||||
|   0x84d3e8: Py_Verbose | ||||
|   0x84db38: Py_Optimize | ||||
|   0x84dd60: Py_interpr | ||||
|   0x7fae38: Debug_Level | ||||
|   0x7fae40: Console_Out_Buffer_132_23 | ||||
|   0x7fbe20: Console_Curr_Line | ||||
|   0x84db30: Py_Dummy | ||||
|   0x8ca2c4: cmdline | ||||
|   0x8c6350: module_filename | ||||
|   0x8c6140: P_module_filename | ||||
|   0x853954: P_D3DApp | ||||
|   0x853091: N_Uniones | ||||
|    | ||||
|   # 0x7fbe24: | ||||
|   # 0x7fa778: | ||||
|   # 0x8c8d78:  | ||||
| 
 | ||||
| VMTs: | ||||
|   0x78d4d8: Py_entity | ||||
|  | @ -56,6 +87,9 @@ VMTs: | |||
|   0x7933ac: 3d_Gfx | ||||
|   0x7933a0: NodeFX | ||||
| 
 | ||||
| classes: | ||||
|   World:  | ||||
| 
 | ||||
| types: | ||||
|   - "struct PyMethodDef { char* ml_name; void* ml_meth; int ml_flags; char* ml_doc;};" | ||||
|   - "struct GameVar { struct GameVar* next; const char* name; const char* desc; uint64_t d_type; void* value; void* def_value; };" | ||||
|  | @ -65,124 +99,297 @@ types: | |||
|   - "struct HashIndex { uint32_t size; struct HashIndexEntry** data; };" | ||||
|   - "struct HashTableEntry { void* data; const char* key; struct HashTableEntry* next; };" | ||||
|   - "struct HashTable { uint32_t size; struct HashTableEntry** data; };" | ||||
| 
 | ||||
| function_signatures: | ||||
|   0x5A8390: "int PyRun_SimpleString(const char* command);" | ||||
|   0x5BB9D0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);" | ||||
|   0x413ee0: "int dbg_log(const char* fmt,...);" | ||||
|   0x4134C0: "int write_log(unsigned int color, const char* msg);" | ||||
|   0x47C1E0: "int ht_hash_ent_list(const char* str);" | ||||
|   0x404BB0: "int ht_hash_ent(const char* str);" | ||||
|   0x4016F0: "int reg_get_val(const char* value);" | ||||
|   0x414280: "int prepare_html_log(const char* filename);" | ||||
|   0x6597d0: "bool read_ini_entry(void* dest,const char* key, const char* section);" | ||||
|   0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);" | ||||
|   0x5E3800: "int fopen_from_pak(const char* filename);" | ||||
|   0x419950: "int fopen_2(const char* filename);" | ||||
|   0x41AB50: "int open_pak(const char* filename, int unk_1,void* unk_ptr);" | ||||
|   0x404460: "int register_c_callback(const char* name,void* func);" | ||||
|   0x414070: "void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);" | ||||
|   0x5FBC50: "void throw_assertion_1(const char* check,const char* file, unsigned int line);" | ||||
|   0x5BC140: "static char* convertsimple1(void *arg, char **p_format, void *p_va);" | ||||
|   0x5E3800: "int32_t fopen_from_pak(const char* filename,const char* mode);" | ||||
|   0x5a90f0: "void* Py_BuildValue(const char* format, ...);" | ||||
|   0x5B9E70: "void* PyObject_GetAttrString(void* obj, const char* attr);" | ||||
| 
 | ||||
|   - "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };" | ||||
| functions: | ||||
|   0x6B1C70: strcmp | ||||
|   0x5BB9D0: PyArg_ParseTuple | ||||
|   0x5DD510: init_engine_3d | ||||
|   0x401180: create_window | ||||
|   0x401240: create_main_window | ||||
|   0x4016F0: reg_get_val | ||||
|   0x4134C0: write_log | ||||
|   0x414280: prepare_html_log | ||||
|   0x418220: get_version_info | ||||
|   0x4137E0: write_html_log | ||||
|   0x402190: handle_console_input | ||||
|   0x5F9520: handle_render_console_input | ||||
|   0x404A50: find_entity | ||||
|   0x47C1E0: ht_hash_ent_list | ||||
|   0x404BB0: ht_hash_ent | ||||
|   0x404460: register_c_callback | ||||
|   0x417470: load_game | ||||
|   0x5E3800: fopen_from_pak | ||||
|   0x5e3500: fopen | ||||
|   0x403370: init_debug | ||||
|   0x401770: init | ||||
|   0x4026D0: init_py | ||||
|   0x405B40: init_py_sub | ||||
|   0x5A8FB0: Py_InitModule | ||||
|   0x41AB50: open_pak | ||||
|   0x5A8390: PyRun_SimpleString | ||||
|   0x414570: setup_game_vars | ||||
|   0x5FBC50: throw_assertion_1 | ||||
|   0x414070: throw_assertion_2 | ||||
|   0x5F7000: read_ini | ||||
|   0x650F80: load_sm3 | ||||
|   0x6665A0: load_m3d_1 | ||||
|   0x666900: load_m3d_2 | ||||
|   0x479B20: world_constructor | ||||
|   0x479B40: init_world | ||||
|   0x402510: deinit_world | ||||
|   0x479870: make_world | ||||
|   0x602A70: render_frame | ||||
|   0x6B738C: handle_exception | ||||
|   0x5B9E70: PyObject_GetAttrString | ||||
|   0x413ee0: dbg_log | ||||
|   0x5f75e0: init_d3d | ||||
|   0x63a2f0: gdi_draw_line | ||||
|   0x5e3250: read_stream | ||||
|   0x5e3bb0: read_stream_wrapper | ||||
|   0x50b9b0: init_scorer | ||||
|   0x582e10: init_action_class_list | ||||
|   0x528910: init_sound_sys | ||||
|   0x5268d0: try_init_sound_sys | ||||
|   0x404280: cPyFunction_set_func | ||||
|   0x414680: load_config | ||||
|   0x414810: save_config | ||||
|   0x4f42a0: close_server_socket | ||||
|   0x4f4d10: close_server | ||||
|   0x4f48e0: close_client | ||||
|   0x4f4fb0: is_server | ||||
|   0x4f4a10: is_client | ||||
|   0x4fac50: is_master | ||||
|   0x526910: close_sound_sys | ||||
|   0x526520: shutdown_sound_sys | ||||
|   0x5dd700: close_3d_engine | ||||
|   0x5a7320: close_window | ||||
|   0x5dff20: set_exception_handler | ||||
|   0x5a7f20: get_console_wnd | ||||
|   0x5a73a0: show_console | ||||
|   0x666c60: read_m3d | ||||
|   0x417df0: snprintf | ||||
|   0x5fc930: printf | ||||
|   0x6597d0: read_ini_entry | ||||
|   0x5fc0a0: engine_debug_log | ||||
|   0x5a7440: create_console_window | ||||
|   0x6114e0: setup_window | ||||
|   0x404420: clear_functions | ||||
|   0x405ca0: close_py_subsys | ||||
|   0x50bcb0: close_scorer | ||||
|   0x479b20: close_world | ||||
|   0x582e70: close_action_class | ||||
|   0x50b6a0: get_scorer | ||||
|   0x50ea20: scorer_parse_type | ||||
|   0x636580: list_models | ||||
|   0x5a90f0: Py_BuildValue | ||||
|   0x41c5a0: has_lst_file | ||||
|   0x5a8e90: py_error | ||||
|   0x5a9890: get_module_dict | ||||
|   0x5c7bb0: get_current_thread | ||||
|   0x5aa140: preload_lib | ||||
|   0x413c10: sprintf | ||||
|   0x405850: check_is_python | ||||
|   0x47bf90: setup_ent_list | ||||
|   0x474f80: ent_list_get_set | ||||
|   0x6283a0: | ||||
|     name: load_emi | ||||
|   0x4fa9f0: | ||||
|     name: send_pkt | ||||
|   0x5ca9e0: | ||||
|     signature: void* PyFrame_New(void* thread_state, void* code_object,void* globals, void* locals) | ||||
|     name: PyFrame_New | ||||
|   0x5bcae0: | ||||
|     signature: void PyErr_SetString(void* obj, const char* err_msg); | ||||
|     name: PyErr_SetString | ||||
|   0x5cb040: | ||||
|     signature: void* eval_code2(void* dict, const char* key, void* item); | ||||
|     name: eval_code2 | ||||
|   0x5e3c50: | ||||
|     convention: cdecl-thiscall-ms | ||||
|     name: read_int | ||||
|   0x5e3b50: | ||||
|     convention: cdecl-thiscall-ms | ||||
|     name: read_block_header | ||||
|   0x5c66d0: | ||||
|     signature: void initerrors(void* dict); | ||||
|     name: initerrors | ||||
|   0x5bb370: | ||||
|     signature: int PyDict_SetItemString(void* dict, const char* key, void* item); | ||||
|     name: PyDict_SetItemString | ||||
|   0x5b9960: | ||||
|     signature: void* PyObject_NEW(void* type, void* typeobj); | ||||
|     name: PyObject_NEW | ||||
|   0x4145e0: | ||||
|     convention: cdecl-thiscall-ms | ||||
|     signature: bool get_config_var(char* name); | ||||
|     name: get_config_var | ||||
|   0x413470: | ||||
|     signature: void init_logging(); | ||||
|     name: init_logging | ||||
|   0x5a8040: | ||||
|     signature: void Py_Initialize(); | ||||
|     name: Py_Initialize | ||||
|   0x5bb4e0: | ||||
|     name: PyModule_GetDict | ||||
|     signature: void* PyModule_GetDict(void*); | ||||
|   0x5c6610: | ||||
|     name: _PyBuiltin_Init_1 | ||||
|     signature: void* _PyBuiltin_Init_1(); | ||||
|   0x5b5db0: | ||||
|     name: PyString_FromString | ||||
|     signature: void* PyString_FromString(const char*); | ||||
|   0x5ba3a0: | ||||
|     name: PyDict_New | ||||
|     signature: void* PyDict_New(); | ||||
|   0x5c7bd0: | ||||
|     name: PyThreadState_Swap | ||||
|     signature: void* PyThreadState_Swap(void* new); | ||||
|   0x5c7870: | ||||
|     name: PyInterpreterState_New | ||||
|     signature: void* PyInterpreterState_New(); | ||||
|   0x5c79b0: | ||||
|     name: PyThreadState_New | ||||
|     signature: void* PyThreadState_New(void* interp); | ||||
|   0x6ad1e9: | ||||
|     name: getenv | ||||
|     signature: char* getenv(char* var); | ||||
|   0x401180: | ||||
|     name: create_window | ||||
|   0x401240: | ||||
|     name: create_main_window | ||||
|   0x4016f0: | ||||
|     name: reg_get_val | ||||
|     signature: int reg_get_val(const char* value); | ||||
|   0x401770: | ||||
|     name: init | ||||
|   0x402190: | ||||
|     name: handle_console_input | ||||
|     signature: int handle_console_input(const char* input); | ||||
|   0x402510: | ||||
|     name: deinit_world | ||||
|   0x4026d0: | ||||
|     name: init_py | ||||
|   0x403370: | ||||
|     name: init_engine | ||||
|   0x4037e0: | ||||
|     name: init_debug | ||||
|   0x404280: | ||||
|     name: cPyFunction_set_func | ||||
|   0x404420: | ||||
|     name: clear_functions | ||||
|   0x404460: | ||||
|     name: register_c_callback | ||||
|     signature: int register_c_callback(const char* name,void* func); | ||||
|   0x404a50: | ||||
|     name: find_entity | ||||
|   0x404bb0: | ||||
|     name: ht_hash_ent | ||||
|     signature: int ht_hash_ent(const char* str); | ||||
|   0x405850: | ||||
|     name: check_is_python | ||||
|   0x405b40: | ||||
|     name: init_py_sub | ||||
|   0x405ca0: | ||||
|     name: close_py_subsys | ||||
|   0x4134c0: | ||||
|     signature: int write_log(unsigned int color, const char* msg); | ||||
|     name: write_log | ||||
|   0x4137e0: | ||||
|     signature: void write_html_log(const char* fmt,...); | ||||
|     name: write_html_log | ||||
|   0x413c10: | ||||
|     name: sprintf | ||||
|   0x413ee0: | ||||
|     name: dbg_log | ||||
|     signature: int dbg_log(const char* fmt,...); | ||||
|   0x414070: | ||||
|     name: throw_assertion_2 | ||||
|     signature: void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line); | ||||
|   0x414280: | ||||
|     name: prepare_html_log | ||||
|     signature: int prepare_html_log(const char* filename); | ||||
|   0x414570: | ||||
|     name: setup_game_vars | ||||
|   0x414680: | ||||
|     name: load_config | ||||
|   0x414810: | ||||
|     name: save_config | ||||
|   0x417470: | ||||
|     name: load_game | ||||
|   0x417df0: | ||||
|     name: snprintf_1 | ||||
|   0x417d80: | ||||
|     name: snprintf_2 | ||||
|   0x418220: | ||||
|     name: get_version_info | ||||
|   0x419950: | ||||
|     name: fopen_2 | ||||
|     signature: int fopen_2(const char* filename); | ||||
|   0x41ab50: | ||||
|     name: open_pak | ||||
|     signature: int open_pak(const char* filename, int unk_1,void* unk_ptr); | ||||
|   0x41c5a0: | ||||
|     name: has_lst_file | ||||
|     signature: int has_lst_file(void* unk_ptr); | ||||
|   0x474f80: | ||||
|     name: ent_list_get_set | ||||
|     signature: bool ent_list_get_set(const char* name); | ||||
|   0x479870: | ||||
|     name: make_world | ||||
|   0x479b20: | ||||
|     name: close_world | ||||
|   0x479b40: | ||||
|     name: init_world | ||||
|   0x47bf90: | ||||
|     convention: cdecl-thiscall-ms | ||||
|     name: create_ent_list | ||||
|     signature: bool create_ent_list(const char* name); | ||||
|   0x47c1e0: | ||||
|     name: ht_hash_ent_list | ||||
|     signature: int ht_hash_ent_list(const char* str); | ||||
|   0x4f42a0: | ||||
|     name: close_server_socket | ||||
|   0x4f48e0: | ||||
|     name: close_client | ||||
|   0x4f4a10: | ||||
|     name: is_client | ||||
|   0x4f4d10: | ||||
|     name: close_server | ||||
|   0x4f4fb0: | ||||
|     name: is_server | ||||
|   0x4fac50: | ||||
|     name: is_master | ||||
|   0x50b6a0: | ||||
|     name: get_scorer | ||||
|   0x50b9b0: | ||||
|     name: init_scorer | ||||
|   0x50bcb0: | ||||
|     name: close_scorer | ||||
|   0x50ea20: | ||||
|     name: scorer_parse_type | ||||
|   0x526520: | ||||
|     name: shutdown_sound_sys | ||||
|   0x5268d0: | ||||
|     name: try_init_sound_sys | ||||
|   0x526910: | ||||
|     name: close_sound_sys | ||||
|   0x528910: | ||||
|     name: init_sound_sys | ||||
|   0x582e10: | ||||
|     name: init_action_class_list | ||||
|   0x582e70: | ||||
|     name: close_action_class | ||||
|   0x5a7320: | ||||
|     name: close_window | ||||
|   0x5a73a0: | ||||
|     name: show_console | ||||
|   0x5a7440: | ||||
|     name: create_console_window | ||||
|   0x5a7f20: | ||||
|     name: get_console_wnd | ||||
|   0x5a8390: | ||||
|     name: PyRun_SimpleString | ||||
|     signature: int PyRun_SimpleString(const char* command); | ||||
|   0x5a8e90: | ||||
|     name: Py_FatalError | ||||
|     signature: void Py_FatalError(const char* msg); | ||||
|   0x5a8fb0: | ||||
|     name: Py_InitModule | ||||
|     signature: void* Py_InitModule(const char* name,void* methods); | ||||
|   0x5a90f0: | ||||
|     name: Py_BuildValue | ||||
|     signature: void* Py_BuildValue(const char* format, ...); | ||||
|   0x5a9890: | ||||
|     name: PyImport_GetModuleDict | ||||
|     signature: void* PyImport_GetModuleDict(); | ||||
|   0x5aa140: | ||||
|     name: preload_lib | ||||
|   0x5b9e70: | ||||
|     name: PyObject_GetAttrString | ||||
|     signature: void* PyObject_GetAttrString(void* obj, const char* attr); | ||||
|   0x5bb9d0: | ||||
|     name: PyArg_ParseTuple | ||||
|     signature: int PyArg_ParseTuple(void* PyObj, char* format, ...); | ||||
|   0x5bc140: | ||||
|     name: convertsimple1 | ||||
|     signature: static char* convertsimple1(void* arg, char** p_format, void* va_list); | ||||
|   0x5bc0f0: | ||||
|     name: convertsimple | ||||
|     signature: static char* convertsimple(void* arg, char** p_format, char* msgbuf); | ||||
|   0x5bbf60: | ||||
|     name: converttuple | ||||
|     signature: static char* converttuple(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf, int toplevel); | ||||
|   0x5bbee0: | ||||
|     name: convertitem | ||||
|     signature: static char* convertitem(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf); | ||||
|   0x5c7bb0: | ||||
|     name: PyThreadState_Get | ||||
|     signature: void* PyThreadState_Get(); | ||||
|   0x5dd510: | ||||
|     name: init_engine_3d | ||||
|   0x5dd700: | ||||
|     name: close_3d_engine | ||||
|   0x5dff20: | ||||
|     name: set_exception_handler | ||||
|   0x5e3250: | ||||
|     name: read_stream | ||||
|   0x5e3500: | ||||
|     name: fopen | ||||
|   0x5e3800: | ||||
|     name: fopen_from_pak | ||||
|     signature: int fopen_from_pak(const char* filename,const char* mode); | ||||
|   0x5e3bb0: | ||||
|     name: read_stream_wrapper | ||||
|   0x5f7000: | ||||
|     name: read_ini | ||||
|   0x5f75e0: | ||||
|     name: init_d3d | ||||
|   0x5f9520: | ||||
|     name: handle_render_console_input | ||||
|   0x5fbc50: | ||||
|     name: throw_assertion_1 | ||||
|     signature: void throw_assertion_1(const char* check,const char* file, unsigned int line); | ||||
|   0x5fc0a0: | ||||
|     name: engine_debug_log | ||||
|   0x5fc930: | ||||
|     name: printf | ||||
|   0x602a70: | ||||
|     name: render_frame | ||||
|   0x6114e0: | ||||
|     name: setup_window | ||||
|   0x636580: | ||||
|     name: list_models | ||||
|   0x63a2f0: | ||||
|     name: gdi_draw_line | ||||
|   0x650f80: | ||||
|     name: load_sm3 | ||||
|   0x6597d0: | ||||
|     name: read_ini_entry | ||||
|     signature: bool read_ini_entry(void* dest,const char* key, const char* section); | ||||
|   0x6665a0: | ||||
|     name: load_m3d_1 | ||||
|   0x666900: | ||||
|     name: load_m3d_2 | ||||
|   0x666c60: | ||||
|     name: read_m3d | ||||
|   0x6b1c70: | ||||
|     name: strcmp | ||||
|     signature: bool strcmp(const char* s1,const char* s2); | ||||
|   0x6b738c: | ||||
|     name: handle_exception | ||||
| 
 | ||||
| script: | | ||||
|   e asm.cmt.right = true | ||||
|   e cmd.stack = true | ||||
|   e scr.utf8 = true | ||||
|   e asm.describe = false | ||||
|   e graph.cmtright = true | ||||
|  |  | |||
							
								
								
									
										22
									
								
								file_formats/ai_path.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								file_formats/ai_path.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| # Structure of Graph | ||||
| 
 | ||||
| ```cpp | ||||
| template<size_t n> | ||||
| struct Node { | ||||
|     float pos[n], | ||||
| } | ||||
| 
 | ||||
| template<size_t n> | ||||
| struct Edge { | ||||
|     uint32_t num_edge_nodes, | ||||
|     Node<n> nodes[], | ||||
| } | ||||
| 
 | ||||
| template<size_t n> | ||||
| struct Graph { | ||||
|     uint32_t num_nodes, | ||||
|     Node<n> nodes[], | ||||
|     uint32_t num_edges, | ||||
|     Edge<n> edges[], | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										126
									
								
								file_formats/chunked.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								file_formats/chunked.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,126 @@ | |||
| # General Block format | ||||
| 
 | ||||
| ```cpp | ||||
| struct Block { | ||||
|     unsigned char block_id[4], | ||||
|     uint32_t size, | ||||
|     unsigned char data[size], | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| struct Block { | ||||
|     unsigned char block_id[4], | ||||
|     uint32_t size, | ||||
|     T data, | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| # Block IDs | ||||
| 
 | ||||
| 
 | ||||
| | File Extension | Description              | | ||||
| |----------------|--------------------------| | ||||
| | .cm3           | Animation file           | | ||||
| | .sm3           | 3d model file            | | ||||
| | .dum           | Dummy (map object) file  | | ||||
| | .pth           | AI Path                  | | ||||
| | .emi           | Emission maps/Materials? | | ||||
| | .amc           | ???                      | | ||||
| 
 | ||||
| | File ID | Chunk IDs                                                                | | ||||
| |---------|--------------------------------------------------------------------------| | ||||
| | AMC     | AMC, CMSH, QUAD                                                          | | ||||
| | CM3     | ANI, CM3, EVA, NAE, NAM, SCN                                             | | ||||
| | DUM     | DUM, INI                                                                 | | ||||
| | EMI     | EMI, LFVF, MAP, MAT, TRI                                                 | | ||||
| | SM3     | ANI, CAM, INI, LFVF, LUZ, MAP, MAT, MD3D, NAE, NAM, PORT, SCN, SM3, SUEL | | ||||
| 
 | ||||
| | Chunk ID | Description                 | | ||||
| |----------|-----------------------------| | ||||
| | ANI      | Animation data?             | | ||||
| | AMC      | Collision Data              | | ||||
| | CMSH     | Mesh data?                  | | ||||
| | INI      | INI-Configuration data      | | ||||
| | LUZ      | Lighting information        | | ||||
| | MAT      | Material information        | | ||||
| | QUAD     | Mesh data?                  | | ||||
| | SCN      | Scene data?                 | | ||||
| | CAM      | Camera info?                | | ||||
| | PORT     | Light portals?              | | ||||
| | SUEL     | Ground plane?               | | ||||
| | DUM      | Dummy (map object) data     | | ||||
| | MAP      | UV Map?                     | | ||||
| | LFVF     | FVF Vertex Data             | | ||||
| | TRI      | Triangle strip definitions? | | ||||
| | NAM,NAE  | Animation Data?             | | ||||
| 
 | ||||
| # Format of Specific chunks | ||||
| 
 | ||||
| ## INI | ||||
| 
 | ||||
| Configuration Data | ||||
| 
 | ||||
| ```cpp | ||||
| struct INI { | ||||
|     uint32_t num_sections, | ||||
|     struct { | ||||
|         uint32_t num_lines, | ||||
|         struct { | ||||
|             uint32_t num_chars, | ||||
|             char line[num_chars] | ||||
|         } lines[num_lines], | ||||
|     } sections[num_sections] | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ## LFVF | ||||
| 
 | ||||
| DirectX Flexible Vertex Format Data | ||||
| 
 | ||||
| ```cpp | ||||
| struct Vertex { // fields according to flags | ||||
|     float position[3], | ||||
|     float rhw, | ||||
|     float weights[3], | ||||
|     float normal[3], | ||||
|     float point_size, | ||||
|     uint32_t diffuse, //RGBA | ||||
|     uint32_t specular, //RGBA | ||||
|     float tex_coords[1 to 4][8] // ??? decided by flags? | ||||
| } | ||||
| 
 | ||||
| struct LFVF { | ||||
|     uint32_t unk, | ||||
|     uint32_t num_entries, | ||||
|     struct { | ||||
|         uint32_t FVF, // FVF vertex data configuration | ||||
|         uint32_t vert_size //?, | ||||
|         uint32_t num_verts, | ||||
|         Vertex vertices[num_vers] | ||||
|     } entry[num_entries] | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## DUM | ||||
| 
 | ||||
| Map object data | ||||
| 
 | ||||
| ```cpp | ||||
| struct DUM { | ||||
|     uint32_t unk_1, | ||||
|     uint32_t num_dummies, | ||||
|     uint32_t unk_2, | ||||
|     struct { | ||||
|         uint32_t name_length, | ||||
|         char name[name_length], | ||||
|         float position[3], | ||||
|         float rotation[3], | ||||
|         uint32_t has_ini, | ||||
|         if (has_ini) { | ||||
|             Block<INI> ini | ||||
|         }, | ||||
|         uint32_t unk_1 // has_next? | ||||
|     } sections[num_sections] | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										15
									
								
								file_formats/packed.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								file_formats/packed.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| # Format | ||||
| ```cpp | ||||
| struct Packed { | ||||
|     unsigned char magic[4], // always BFPK | ||||
|     uint32_t version, | ||||
|     uint32_t number_of_files, | ||||
|     struct File { | ||||
|         uint32_t path_length, | ||||
|         char path[path_length], // latin1 encoding | ||||
|         uint32_t data_size, | ||||
|         uint32_t data_offset | ||||
|     } files[number_of_files], | ||||
|     char data[] | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										60
									
								
								frida/frida_hook_net.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frida/frida_hook_net.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| var sendto = Module.getExportByName("WSOCK32.dll", "sendto") | ||||
| var recvfrom = Module.getExportByName("WSOCK32.dll", "recvfrom") | ||||
| 
 | ||||
| Interceptor.attach(ptr("0x004f9300"), { | ||||
|     onEnter: function (args) { | ||||
|         console.log("[SendUsrString]", JSON.stringify({ | ||||
|             data: args[0].readCString(), | ||||
|             dst: args[1].toInt32(), | ||||
|             chat: args[2].toInt32() | ||||
|         })); | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| Interceptor.attach(ptr(sendto), { | ||||
|     onEnter: function (args) { | ||||
|         this.socket = args[0]; | ||||
|         this.buffer = args[1]; | ||||
|         this.size = args[2].toInt32(); | ||||
|         this.flags = args[3].toInt32(); | ||||
|         this.sock_addr = args[4]; | ||||
|         this.to_len = args[5].toInt32(); | ||||
|     }, | ||||
|     onLeave: function (ret) { | ||||
|         var port = this.sock_addr.add(2).readU16(); | ||||
|         var addr = this.sock_addr.add(4).readU32(); | ||||
|         var data = Memory.readByteArray(this.buffer, ret.toInt32()) | ||||
|         send({ | ||||
|             type: "SEND", | ||||
|             ptr: this.buffer.toInt32(), | ||||
|             addr, | ||||
|             port | ||||
|         }, data); | ||||
|         return ret; | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| Interceptor.attach(ptr(recvfrom), { | ||||
|     onEnter: function (args) { | ||||
|         this.socket = args[0]; | ||||
|         this.buffer = args[1]; | ||||
|         this.size = args[2].toInt32(); | ||||
|         this.flags = args[3].toInt32(); | ||||
|         this.sock_addr = args[4]; | ||||
|         this.from_len = args[5].toInt32(); | ||||
|     }, | ||||
|     onLeave: function (ret) { | ||||
|         if (!ret.equals(ptr("0xffffffff"))) { | ||||
|             var port = this.sock_addr.add(2).readU16(); | ||||
|             var addr = this.sock_addr.add(4).readU32(); | ||||
|             var data = Memory.readByteArray(this.buffer, ret.toInt32()) | ||||
|             send({ | ||||
|                 type: "RECV", | ||||
|                 ptr: this.buffer.toInt32(), | ||||
|                 addr, | ||||
|                 port | ||||
|             }, data); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| }) | ||||
							
								
								
									
										174
									
								
								frida/frida_hook_read_trace.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								frida/frida_hook_read_trace.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | |||
| var pak_files = {} | ||||
| var ftypes = {} | ||||
| var record=false; | ||||
| 
 | ||||
| var current_block_id; | ||||
| var filename; | ||||
| var t0 = performance.now(); | ||||
| 
 | ||||
| Interceptor.attach(ptr("0x5e3b50"), { //read_block_header
 | ||||
|     onEnter: function (args) { | ||||
|         filename = pak_files[this.context.ecx] || this.context.ecx; | ||||
|         current_block_id = args[0].readUtf8String(); | ||||
|     }, | ||||
| 
 | ||||
|     onLeave: function(ret) { | ||||
|         return ret; | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| // Interceptor.attach(ptr("0x5e3c50"), { // read_block_id
 | ||||
| //     onEnter: function (args) {
 | ||||
| //         var filename=pak_files[this.context.ecx]||this.context.ecx;
 | ||||
| //         var id=args[1].readUtf8String();
 | ||||
| //         console.log("[+read_block("+filename+")]",id,args[1]);
 | ||||
| //     },
 | ||||
| 
 | ||||
| //     // onLeave: function(ret) {
 | ||||
| //     //     console.log("[-read_ini_block] Ret:",ret);
 | ||||
| //     // }
 | ||||
| // })
 | ||||
| 
 | ||||
| Interceptor.attach(ptr("0x7B43B020"),{ | ||||
|     onEnter: function(args) { | ||||
|         var info={}; | ||||
|         info['this']=args[0]; | ||||
|         info['Length']=args[1]; | ||||
|         info['Usage']=args[2]; | ||||
|         info['FVF']=args[3]; | ||||
|         info['Pool']=args[4]; | ||||
|         info['ppVertexBuffer']=args[4]; | ||||
|         send({CreateVertexBuffer:info}); | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| Interceptor.attach(ptr("0x5e3bb0"), { // read_stream_wrapper
 | ||||
|     onEnter: function (args) { | ||||
|         this.args = {}; | ||||
|         this.args[0] = args[0]; | ||||
|         this.args[1] = args[1]; | ||||
|         this.timestamp = performance.now()-t0; | ||||
|     }, | ||||
|     onLeave: function (ret) { | ||||
|         var data=Memory.readByteArray(this.args[0],this.args[1].toInt32()); | ||||
|         var stack = Thread.backtrace(this.context,Backtracer.ACCURATE); | ||||
|         var obj={ | ||||
|             filename, | ||||
|             timestamp: this.timestamp, | ||||
|             block_id: current_block_id, | ||||
|             stack | ||||
|         }; | ||||
|         send(obj,data); | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| Interceptor.attach(ptr("0x5e3800"), { // fopen_from_pak
 | ||||
|     onEnter: function (args) { | ||||
|         this.filename = args[0].readUtf8String(); | ||||
|     }, | ||||
|     onLeave: function (ret) { | ||||
|         if (ret != 0) { | ||||
|             pak_files[ret] = this.filename; | ||||
|         } | ||||
|     } | ||||
| }) | ||||
| 
 | ||||
| // Interceptor.attach(ptr("0x5e3c50"), { // read_block_id
 | ||||
| //     onEnter: function (args) {
 | ||||
| //         console.log("[+read]",args[0],args[1]);
 | ||||
| //     },
 | ||||
| //     onLeave: function(ret) {
 | ||||
| //         console.log("[-read] Ret:",ret);
 | ||||
| //     }
 | ||||
| // })
 | ||||
| 
 | ||||
| // Interceptor.attach(ptr("0x6665a0"), { // load_m3d_1
 | ||||
| //     onEnter: function (args) {
 | ||||
| //         console.log("[M3D_1]",args[0].readUtf8String());
 | ||||
| //     }
 | ||||
| // })
 | ||||
| 
 | ||||
| 
 | ||||
| // Interceptor.attach(ptr("0x666900"), { // load_m3d_2
 | ||||
| //     onEnter: function (args) {
 | ||||
| //         console.log("[M3D_2]",args[0].readUtf8String());
 | ||||
| //     }
 | ||||
| // })
 | ||||
| 
 | ||||
| 
 | ||||
| function dasm(addr, size) { | ||||
|     var size = size || 8; | ||||
|     var offset = 0; | ||||
|     var ret = []; | ||||
|     while (ret.length != size) { | ||||
|         var inst = Instruction.parse(ptr(addr).add(offset)); | ||||
|         ret.push(("[" + inst.address + "] " + inst.mnemonic + " " + inst.opStr).trim()); | ||||
|         offset += inst.size; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| function r(addr, options) { | ||||
|     var options = options || {} | ||||
|     var max_depth = options.max_depth || 4; | ||||
|     var num = options.num || 4; | ||||
|     var ret = {}; | ||||
|     var vals = [ | ||||
|         "S8", | ||||
|         "U8", | ||||
|         "S16", | ||||
|         "U16", | ||||
|         "S32", | ||||
|         "U32", | ||||
|         "Float", | ||||
|         "Double", | ||||
|         "Pointer", | ||||
|         "CString", | ||||
|         "Utf8String", | ||||
|         "Utf16String", | ||||
|         "AnsiString" | ||||
|     ]; | ||||
|     vals.forEach(function (k) { | ||||
|         try { | ||||
|             ret[k] = ptr(addr)['read' + k]() | ||||
|         } catch (e) { | ||||
|             ret[k] = undefined; | ||||
|         } | ||||
|     }) | ||||
|     try { | ||||
|         ret["code"] = dasm(addr, 8); | ||||
|     } catch (e) { | ||||
|         ret["code"] = undefined; | ||||
|     } | ||||
| 
 | ||||
|     if (max_depth > 1) { | ||||
|         var p = {}; | ||||
|         var read_ptr = false; | ||||
|         for (var i = 0; i < num; ++i) { | ||||
|             if (ret["Pointer"] === undefined) { | ||||
|                 continue; | ||||
|             } | ||||
|             p[i * Process.pointerSize] = r(ret["Pointer"].add(i * Process.pointerSize), { | ||||
|                 max_depth: max_depth - 1, | ||||
|                 num | ||||
|             }); | ||||
|             read_ptr = true; | ||||
|         } | ||||
|         if (read_ptr) { | ||||
|             ret["p"] = p; | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // function test() {
 | ||||
| //     for (var p = 0; p < 4; ++p) {
 | ||||
| //         var player = ptr(0x7FE944).readPointer().add(0x288 + p * 4).readPointer();
 | ||||
| //         if (!player.isNull()) {
 | ||||
| //             console.log("Player " + (p+1) + ":", player);
 | ||||
| //             console.log(JSON.stringify(r(player),null,4));
 | ||||
| //         }
 | ||||
| //     }
 | ||||
| // }
 | ||||
							
								
								
									
										56
									
								
								frida/frida_inject_net.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								frida/frida_inject_net.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| import frida | ||||
| import psutil | ||||
| from binascii import hexlify | ||||
| import subprocess as SP | ||||
| import string | ||||
| import ipaddress | ||||
| from dissect_net import packet,printable_chars,hexdump,is_printable | ||||
| 
 | ||||
| def on_message(msg, data=None): | ||||
|     if not data: | ||||
|         return | ||||
|     msg = msg["payload"] | ||||
|     IP = ipaddress.IPv4Address(msg["addr"]) | ||||
|     IP = ipaddress.IPv4Address(IP.packed[::-1]) | ||||
|     direction = msg["type"] | ||||
|     port = msg["port"] | ||||
|     ptr = msg["ptr"] | ||||
| 
 | ||||
|     with open("netlog.txt","a",encoding="utf8") as of: | ||||
|         print( | ||||
|             "{} {}:{} 0x{:x} {}".format(msg["type"], IP, port, ptr, str(hexlify(data),"utf8")), | ||||
|             file=of | ||||
|         ) | ||||
|          | ||||
|     if is_printable(data): | ||||
|         print(direction, addr, buffer_addr, data) | ||||
|         return | ||||
|      | ||||
|     try: | ||||
|         parsed_data = packet.parse(data) | ||||
|         print( | ||||
|             "{} {}:{} 0x{:x}".format(msg["type"], IP, port, ptr) | ||||
|         ) | ||||
|         print(hexdump(data)) | ||||
|         print(parsed_data) | ||||
|         print() | ||||
|     except Exception as e: | ||||
|         print(e) | ||||
|         pass | ||||
| 
 | ||||
| def main(): | ||||
|     pid = frida.spawn(sys.argv[1:]) | ||||
|     session = frida.attach(pid) | ||||
|     session.enable_jit() | ||||
|     script = session.create_script(open("frida_hook_net.js").read()) | ||||
|     open(f"netlog.txt","w",encoding="utf8").close() | ||||
|     script.on("message", on_message) | ||||
|     script.load() | ||||
|     frida.resume(pid) | ||||
|     proc = psutil.Process(pid) | ||||
|     proc.wait() | ||||
|     session.detach() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										59
									
								
								frida/frida_inject_read_trace.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frida/frida_inject_read_trace.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| from __future__ import print_function | ||||
| import frida | ||||
| import os | ||||
| import sys | ||||
| import psutil | ||||
| import binascii | ||||
| import sqlite3 | ||||
| import json | ||||
| import time | ||||
| import msgpack | ||||
| from multiprocessing import JoinableQueue | ||||
| import threading | ||||
| 
 | ||||
| 
 | ||||
| q = JoinableQueue() | ||||
| 
 | ||||
| 
 | ||||
| def db_worker(q): | ||||
|     with open("dump.mp", "wb") as of: | ||||
|         while True: | ||||
|             args = q.get() | ||||
|             if args is None: | ||||
|                 q.task_done() | ||||
|                 break | ||||
|             msgpack.dump(args, of) | ||||
|             q.task_done() | ||||
| 
 | ||||
| 
 | ||||
| db_w = threading.Thread(target=db_worker, args=(q,)) | ||||
| 
 | ||||
| db_w.start() | ||||
| 
 | ||||
| 
 | ||||
| def on_message(msg, data): | ||||
|     filename = msg.get("payload", {}).get("filename", "<UNKNOWN>").replace("\\", "/") | ||||
|     block_id = msg.get("payload", {}).get("block_id", "<UNKNOWN>") | ||||
|     print(filename,block_id,data) | ||||
|     msg["payload"]["data"] = data | ||||
|     q.put(msg["payload"]) | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     pid = frida.spawn(sys.argv[1:]) | ||||
|     session = frida.attach(pid) | ||||
|     script = session.create_script(open("frida_hook_read_trace.js").read()) | ||||
|     script.on("message", on_message) | ||||
|     script.load() | ||||
|     frida.resume(pid) | ||||
|     proc = psutil.Process(pid) | ||||
|     proc.wait() | ||||
|     session.detach() | ||||
|     q.put(None) | ||||
|     q.join() | ||||
|     q.close() | ||||
|     db_w.join() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										8
									
								
								frida/frida_mem_mon.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frida/frida_mem_mon.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| MemoryAccessMonitor.enable({ | ||||
|     base: ptr("0x7fe944"), | ||||
|     size: 4 | ||||
| }, { | ||||
|     onAccess: function (details) { | ||||
|         console.log(details.operation, details.from, details.address) | ||||
|     }, | ||||
| }) | ||||
							
								
								
									
										22
									
								
								frida/frida_mem_mon.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frida/frida_mem_mon.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| import frida | ||||
| import sys | ||||
| import psutil | ||||
| 
 | ||||
| def on_message(msg, data=None): | ||||
|     print(msg,data) | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     pid = frida.spawn(sys.argv[1:]) | ||||
|     session = frida.attach(pid) | ||||
|     session.enable_jit() | ||||
|     script = session.create_script(open("frida_mem_mon.js").read()) | ||||
|     script.on("message", on_message) | ||||
|     script.load() | ||||
|     frida.resume(pid) | ||||
|     proc = psutil.Process(pid) | ||||
|     proc.wait() | ||||
|     session.detach() | ||||
|      | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										43
									
								
								frida/frida_stalker_test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								frida/frida_stalker_test.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| var stalked_threads = []; | ||||
| var excluded_modules = [] | ||||
| var sent=false; | ||||
| setInterval(() => { | ||||
|     Process.enumerateModules().forEach(mod => { | ||||
|         if (mod.name == "Scrap.exe") { | ||||
|             if (!sent) { | ||||
|                 send({ | ||||
|                     mod: mod | ||||
|                 }) | ||||
|                 sent=true; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|         if (excluded_modules.indexOf(mod.name) == -1) { | ||||
|             Stalker.exclude(mod); | ||||
|             excluded_modules.push(mod.name); | ||||
|         } | ||||
|     }) | ||||
|     Process.enumerateThreads().forEach(thread => { | ||||
|         if (stalked_threads.indexOf(thread.id) != -1) { | ||||
|             return; | ||||
|         } | ||||
|         Stalker.follow(thread.id, { | ||||
|             events: { | ||||
|                 call: true, | ||||
|                 block: true, | ||||
|                 compile: true, | ||||
|                 ret: true, | ||||
|                 exec: true | ||||
|             }, | ||||
|             onReceive: function (events) { | ||||
|                 send({ | ||||
|                     stalker: Stalker.parse(events, { | ||||
|                         annotate: true, | ||||
|                         stringify: true | ||||
|                     }) | ||||
|                 }); | ||||
|             } | ||||
|         }) | ||||
|         stalked_threads.push(thread.id); | ||||
|     }) | ||||
| }, 0) | ||||
							
								
								
									
										67
									
								
								frida/frida_stalker_test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frida/frida_stalker_test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| import frida | ||||
| import sys | ||||
| import psutil | ||||
| import subprocess as SP | ||||
| import threading | ||||
| from multiprocessing import JoinableQueue | ||||
| import msgpack | ||||
| 
 | ||||
| 
 | ||||
| q = JoinableQueue() | ||||
| 
 | ||||
| 
 | ||||
| def db_worker(q): | ||||
|     events = 0 | ||||
|     with open("trace.mp", "wb") as of: | ||||
|         while True: | ||||
|             args = q.get() | ||||
|             if args is None: | ||||
|                 q.task_done() | ||||
|                 break | ||||
|             events += 1 | ||||
|             msgpack.dump(args, of) | ||||
|             q.task_done() | ||||
|     print("Wrote", events, "events") | ||||
| 
 | ||||
| 
 | ||||
| db_w = threading.Thread(target=db_worker, args=(q,)) | ||||
| 
 | ||||
| db_w.start() | ||||
| modules = {} | ||||
| mem_range = None | ||||
| 
 | ||||
| 
 | ||||
| def on_message(msg, data=None): | ||||
|     global mem_range | ||||
|     data = msg["payload"] | ||||
|     if "stalker" in data: | ||||
|         for val in data["stalker"]: | ||||
|             q.put(val) | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     pid = frida.spawn(sys.argv[1:]) | ||||
|     session = frida.attach(pid) | ||||
|     session.enable_jit() | ||||
|     script = session.create_script(open("frida_stalker_test.js").read()) | ||||
|     script.on("message", on_message) | ||||
|     script.load() | ||||
|     frida.resume(pid) | ||||
|     proc = psutil.Process(pid) | ||||
|     proc.wait() | ||||
|     session.detach() | ||||
|     q.put(None) | ||||
|     q.join() | ||||
|     q.close() | ||||
|     db_w.join() | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| import msgpack as mp | ||||
| from collections import Counter | ||||
| data=list(mp.Unpacker(open("trace.mp","rb"), raw=False)) | ||||
| Counter(v[1] for v in data).most_common(10) | ||||
| """ | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
|  | @ -1,21 +0,0 @@ | |||
| import sys | ||||
| from construct import * | ||||
| from pprint import pprint | ||||
| 
 | ||||
| ScrapSaveVar = Struct( | ||||
|     "name" / PascalString(Int32ul, encoding="utf-8"), | ||||
|     "data" / PascalString(Int32ul, encoding="utf-8"), | ||||
| ) | ||||
| ScrapSave = "ScarpSaveGame" / Struct( | ||||
|     "title" / PascalString(Int32ul, encoding="utf-8"), | ||||
|     "id" / PascalString(Int32ul, encoding="utf-8"), | ||||
|     "data" / PrefixedArray(Int32ul, ScrapSaveVar), | ||||
|     Terminated, | ||||
| ) | ||||
| with open(sys.argv[1], "rb") as sav_file: | ||||
|     save = ScrapSave.parse_stream(sav_file) | ||||
|     print("ID:", save.id) | ||||
|     print("Title:", save.title) | ||||
|     for var in save.data: | ||||
|         print("   {}: {}".format(var.name, var.data)) | ||||
| 
 | ||||
|  | @ -4,11 +4,11 @@ import json | |||
| from datetime import datetime | ||||
| import subprocess as SP | ||||
| from tqdm import tqdm | ||||
| from pprint import pprint | ||||
| import os | ||||
| import sys | ||||
| import yaml | ||||
| 
 | ||||
| tqdm_ascii = False | ||||
| 
 | ||||
| r2cmds = [] | ||||
| x64_dbg_script = [] | ||||
| script_path = os.path.dirname(os.path.abspath(__file__)) | ||||
|  | @ -24,10 +24,12 @@ file_hashes = r2.cmdj("itj") | |||
| target_hashes = { | ||||
|     "sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa", | ||||
|     "md5": "a934c85dca5ab1c32f05c0977f62e186", | ||||
|     "sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88", | ||||
| } | ||||
| 
 | ||||
| assert file_hashes == target_hashes, "Hash mismatch" | ||||
| 
 | ||||
| 
 | ||||
| def x64_dbg_label(addr, name, prefix=None): | ||||
|     global x64_dbg_script | ||||
|     if isinstance(addr, int): | ||||
|  | @ -37,6 +39,7 @@ def x64_dbg_label(addr,name,prefix=None): | |||
|     else: | ||||
|         x64_dbg_script.append(f'lbl {addr},"{name}"') | ||||
| 
 | ||||
| 
 | ||||
| def r2_cmd(cmd): | ||||
|     global r2, r2cmds | ||||
|     r2cmds.append(cmd) | ||||
|  | @ -54,8 +57,10 @@ def r2_cmdJ(cmd): | |||
|     r2cmds.append(cmd) | ||||
|     return r2.cmdJ(cmd) | ||||
| 
 | ||||
| 
 | ||||
| t_start = datetime.today() | ||||
| 
 | ||||
| 
 | ||||
| def analysis(full=False): | ||||
|     print("[*] Running analysis") | ||||
|     steps = [] | ||||
|  | @ -72,11 +77,15 @@ def analysis(full=False): | |||
|             "e anal.vinfun = true", | ||||
|             "e asm.anal = true", | ||||
|         ] | ||||
|     steps+=["aaaaa"] | ||||
|     if full: | ||||
|         steps += ["aaaa"] | ||||
|     else: | ||||
|         steps += ["aaa"] | ||||
|     for ac in steps: | ||||
|         print(f"[*] Running '{ac}'") | ||||
|         r2_cmd(f"{ac} 2>NUL") | ||||
| 
 | ||||
| 
 | ||||
| with open(os.path.join(script_path, "config.yml")) as cfg: | ||||
|     print("[*] Loading config") | ||||
|     config = type("Config", (object,), yaml.load(cfg, Loader=yaml.SafeLoader)) | ||||
|  | @ -97,25 +106,27 @@ for addr, name in config.flags.items(): | |||
|     r2_cmd(f"f loc.{name} 4 {hex(addr)}") | ||||
| 
 | ||||
| 
 | ||||
| for addr, name in config.functions.items(): | ||||
| for addr, func in config.functions.items(): | ||||
|     name, sig = func.get("name"), func.get("signature") | ||||
|     if name: | ||||
|         x64_dbg_label(addr, name, "fcn") | ||||
|         r2_cmd(f"afr fcn.{name} {hex(addr)}") | ||||
| 
 | ||||
| for addr,sig in config.function_signatures.items(): | ||||
|     r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}') | ||||
| 
 | ||||
|         r2_cmd(f"afn fcn.{name} {hex(addr)}") | ||||
|     if sig: | ||||
|         sig = sig.replace(name, "fcn." + name) | ||||
|         r2_cmd(f'"afs {sig}" @{hex(addr)}') | ||||
| 
 | ||||
| 
 | ||||
| def vtables(): | ||||
|     ret = {} | ||||
|     print("[*] Analyzing VTables") | ||||
|     vtables = r2_cmdJ("avj") | ||||
|     for c in tqdm(vtables, ascii=True): | ||||
|     for c in tqdm(vtables, ascii=tqdm_ascii): | ||||
|         methods = [] | ||||
|         name = config.VMTs.get(c.offset, f"{c.offset:08x}") | ||||
|         x64_dbg_label(c.offset, name, "vmt") | ||||
|         r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}") | ||||
|         for idx,m in enumerate(tqdm(c.methods, ascii=True, leave=False)): | ||||
|         for idx, m in enumerate(tqdm(c.methods, ascii=tqdm_ascii, leave=False)): | ||||
|             methods.append(hex(m.offset)) | ||||
|             x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt") | ||||
|             r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL") | ||||
|  | @ -127,7 +138,7 @@ def c_callbacks(): | |||
|     print("[*] Parsing C Callbacks") | ||||
|     funcs = {} | ||||
|     res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines() | ||||
|     for addr in tqdm(res, ascii=True): | ||||
|     for addr in tqdm(res, ascii=tqdm_ascii): | ||||
|         r2_cmd(f"s {addr}") | ||||
|         r2_cmd(f"so -3") | ||||
|         func, name = r2_cmdJ(f"pdj 2") | ||||
|  | @ -142,22 +153,22 @@ def c_callbacks(): | |||
| def assertions(): | ||||
|     assertions = {} | ||||
|     for (n_args, a_addr) in [ | ||||
|         (4, "fcn.throw_assertion_1"), | ||||
|         (3, "fcn.throw_assertion_2"), | ||||
|         (3, "fcn.throw_assertion_1"), | ||||
|         (4, "fcn.throw_assertion_2"), | ||||
|     ]: | ||||
|         print(f"[*] Parsing C assertions for {a_addr}") | ||||
|         res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() | ||||
|         print() | ||||
|         for line in tqdm(res, ascii=True): | ||||
|         for line in tqdm(res, ascii=tqdm_ascii): | ||||
|             addr = line.strip() | ||||
|             r2_cmd(f"s {addr}") | ||||
|             r2_cmd(f"so -{n_args}") | ||||
|             dis = r2_cmdJ(f"pij {n_args}") | ||||
|             if n_args == 4: | ||||
|                 file, msg, date, line = dis | ||||
|                 line, date, file, msg = dis | ||||
|             elif n_args == 3: | ||||
|                 date = None | ||||
|                 file, msg, line = dis | ||||
|                 line, file, msg = dis | ||||
|             try: | ||||
|                 file = r2_cmd(f"psz @{file.refs[0].addr}").strip() | ||||
|                 msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() | ||||
|  | @ -180,32 +191,35 @@ def bb_refs(addr): | |||
|     ret = {} | ||||
|     res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines() | ||||
|     print() | ||||
|     for ent in res: | ||||
|     for ent in tqdm(res, ascii=tqdm_ascii): | ||||
|         func, hit = ent.split() | ||||
|         ret[hit] = {"asm": [], "func": func} | ||||
|         for ins in r2_cmdJ(f"pdbj @{hit}"): | ||||
|             ret[hit]["asm"].append(ins.disasm) | ||||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| def world(): | ||||
|     print("[*] Parsing World offsets") | ||||
|     return bb_refs("loc.P_World") | ||||
| 
 | ||||
| 
 | ||||
| def render(): | ||||
|     print("[*] Parsing D3D_Device offsets") | ||||
|     return bb_refs("loc.P_D3D8_Dev") | ||||
| 
 | ||||
| 
 | ||||
| def py_mods(): | ||||
|     print("[*] Parsing Python modules") | ||||
|     res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() | ||||
|     print() | ||||
|     py_mods = {} | ||||
|     for call_loc in tqdm(res, ascii=True): | ||||
|     for call_loc in tqdm(res, ascii=tqdm_ascii): | ||||
|         r2_cmd(f"s {call_loc}") | ||||
|         r2_cmd(f"so -3") | ||||
|         args = r2_cmdJ("pdj 3") | ||||
|         refs = [] | ||||
|         if not all([arg.type == "push" for arg in args]): | ||||
|         if not all(arg.type == "push" for arg in args): | ||||
|             continue | ||||
|         for arg in args: | ||||
|             refs.append(hex(arg.val)) | ||||
|  | @ -240,7 +254,7 @@ def game_vars(): | |||
|     print("[*] Parsing Game variables") | ||||
|     res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines() | ||||
|     print() | ||||
|     for line in tqdm(res, ascii=True): | ||||
|     for line in tqdm(res, ascii=tqdm_ascii): | ||||
|         addr = line.strip() | ||||
|         r2_cmd(f"s {addr}") | ||||
|         args = r2_cmd("pdj -5")  # seek and print disassembly | ||||
|  | @ -259,7 +273,7 @@ def game_vars(): | |||
|                 break | ||||
|         if len(args_a) != 4: | ||||
|             continue | ||||
|         if not all(["val" in v for v in args_a]): | ||||
|         if not all("val" in v for v in args_a): | ||||
|             continue | ||||
|         addr, name, _, desc = [v["val"] for v in args_a] | ||||
|         name = r2_cmd(f"psz @{hex(name)}").strip() | ||||
|  | @ -271,15 +285,10 @@ def game_vars(): | |||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| ret = dict( | ||||
|     game_vars=game_vars(), | ||||
|     c_callbacks=c_callbacks(), | ||||
|     py_mods=py_mods(), | ||||
|     assertions=assertions(), | ||||
|     vtables=vtables(), | ||||
|     world=world(), | ||||
|     render=render(), | ||||
| ) | ||||
| ret = {} | ||||
| # world, render | ||||
| for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]: | ||||
|     ret[func] = globals()[func]() | ||||
| 
 | ||||
| analysis(True) | ||||
| 
 | ||||
|  | @ -296,6 +305,8 @@ print("[+] Wrote scrap_dissect.x32dbg.txt") | |||
| with open(r2_script_path, "w") as of: | ||||
|     wcmds = [] | ||||
|     for cmd in r2cmds: | ||||
|         if cmd == "avj": | ||||
|             continue | ||||
|         record = True | ||||
|         for start in ["p", "/", "s"]: | ||||
|             if cmd.strip('"').startswith(start): | ||||
|  | @ -306,20 +317,25 @@ with open(r2_script_path, "w") as of: | |||
| 
 | ||||
| print("[+] Wrote scrap_dissect.r2") | ||||
| 
 | ||||
| r2.quit() | ||||
| 
 | ||||
| def start_program(cmdl, **kwargs): | ||||
|     if os.name=='nt': | ||||
|         return SP.Popen(['cmd','/c','start']+cmdl,**kwargs) | ||||
|     if os.name == "nt": | ||||
|         return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs) | ||||
|     else: | ||||
|         return SP.Popen(cmdl, **kwargs) | ||||
| 
 | ||||
| print("[+] Analysis took:",datetime.today()-t_start) | ||||
| 
 | ||||
| print("[+] Analysis took:", datetime.today() - t_start) | ||||
| 
 | ||||
| print("[+] Executing Cutter") | ||||
| try: | ||||
|     start_program(['cutter','-A','0','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False) | ||||
|     start_program( | ||||
|         ["cutter", "-A", "0", "-i", r2_script_path, scrap_exe], | ||||
|         cwd=scrapland_folder, | ||||
|         shell=False, | ||||
|     ) | ||||
| except FileNotFoundError: | ||||
|     print("[-] cutter not installed, falling back to r2") | ||||
|     start_program(['r2','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False) | ||||
|     start_program( | ||||
|         ["r2", "-i", r2_script_path, scrap_exe], cwd=scrapland_folder, shell=False | ||||
|     ) | ||||
|  |  | |||
							
								
								
									
										68
									
								
								tools/analyze_read_trace.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								tools/analyze_read_trace.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| import msgpack as mp | ||||
| import sys | ||||
| import os | ||||
| from tqdm import tqdm | ||||
| import struct | ||||
| import binascii | ||||
| import string | ||||
| import re | ||||
| from binascii import hexlify | ||||
| 
 | ||||
| def gen(): | ||||
|     with open(sys.argv[1], "rb") as fh: | ||||
|         size = os.stat(sys.argv[1]).st_size | ||||
|         progbar = tqdm(total=size, unit="bytes", unit_scale=True, unit_divisor=1024) | ||||
|         pos = 0 | ||||
|         for entry in mp.Unpacker(fh, raw=True): | ||||
|             progbar.update(fh.tell() - pos) | ||||
|             pos = fh.tell() | ||||
|             for k in entry.copy(): | ||||
|                 k_s = str(k, "utf8") | ||||
|                 if k_s not in ["data", "stack", "timestamp"]: | ||||
|                     entry[k] = str(entry.pop(k), "utf8") | ||||
|                 entry[k_s] = entry.pop(k) | ||||
|             entry["stack"] = "|".join( | ||||
|                 ["{:08X}".format(int(str(v, "utf8"), 16)) for v in entry["stack"][::-1]] | ||||
|             ) | ||||
|             yield entry | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def strdump(data): | ||||
|     printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c") | ||||
|     return "".join(chr(c) if c in printable_chars else "." for c in data) | ||||
| 
 | ||||
| def tohex(data): | ||||
|     return str(hexlify(data), "utf8").upper() | ||||
| 
 | ||||
| 
 | ||||
| # best=sorted(tqdm(gen(),ascii=True),key=lambda v:len(v['data']),reverse=True) | ||||
| 
 | ||||
| # def score(entry): | ||||
| #     return len(entry['data']) | ||||
| 
 | ||||
| # def analyze(entry): | ||||
| #     data=entry['data'] | ||||
| #     entry['infos'] = { | ||||
| #         'len':len(data), | ||||
| #     } | ||||
| #     for bo in "><": | ||||
| #         for t in "hHiIlLqQefd": | ||||
| #             fmt="{}{}".format(bo,t) | ||||
| #             if len(data)%struct.calcsize(fmt)==0: | ||||
| #                 entry['infos'][fmt]=[v[0] for v in struct.iter_unpack(fmt,data)] | ||||
| #     return entry | ||||
| 
 | ||||
| filters=[re.compile(s) for s in sys.argv[2:]] | ||||
| 
 | ||||
| with open("all.log", "w") as of: | ||||
|     for entry in gen(): | ||||
|         fm=[(f.match(entry['filename']) is not None) for f in filters] | ||||
|         if filters and not any(fm): | ||||
|             continue | ||||
|         entry["data_len"] = len(entry["data"]) | ||||
|         entry["str"] = strdump(entry["data"]) | ||||
|         entry["data"] = tohex(entry["data"]) | ||||
|         print( | ||||
|             "{timestamp} {block_id} {filename} {data_len:08X} {data} {str}".format(**entry), file=of | ||||
|         ) | ||||
							
								
								
									
										57
									
								
								tools/binvis.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tools/binvis.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| import struct | ||||
| from collections import OrderedDict, ChainMap | ||||
| 
 | ||||
| 
 | ||||
| class LittleEndian: | ||||
|     byteorder = "<" | ||||
| 
 | ||||
| 
 | ||||
| class BigEndian: | ||||
|     byteorder = ">" | ||||
| 
 | ||||
| 
 | ||||
| class NativeEndian: | ||||
|     byteorder = "@" | ||||
| 
 | ||||
| 
 | ||||
| class Field: | ||||
|     def __init__(self, struct_type=None, size=None, byteorder=None): | ||||
|         self.struct = struct_type | ||||
|         self.size = size | ||||
|         self.byteorder = byteorder | ||||
|         self.data = None | ||||
|         self.parsed = False | ||||
| 
 | ||||
|     def parse(self, data): | ||||
|         return | ||||
| 
 | ||||
| 
 | ||||
| class ParserMeta(type): | ||||
|     def __new__(cls, name, bases, namespace, **kwargs): | ||||
|         if object in bases: | ||||
|             return type.__new__(cls, name, bases, dict(namespace)) | ||||
|         fields = [] | ||||
|         for item_name, item_value in namespace.items(): | ||||
|             if isinstance(item_value, Field): | ||||
|                 fields.append(item_name) | ||||
|         ret = super().__new__(cls, name, bases, namespace) | ||||
|         ret._fields = fields | ||||
|         return ret | ||||
| 
 | ||||
|     @classmethod | ||||
|     def __prepare__(metacls, name, bases, **kwds): | ||||
|         return OrderedDict() | ||||
| 
 | ||||
| 
 | ||||
| class Parser(metaclass=ParserMeta): | ||||
|     def __init__(self, data): | ||||
|         for field in self._fields: | ||||
|             print(field, getattr(self, field)) | ||||
| 
 | ||||
| 
 | ||||
| class ChunkedHeader(Parser, LittleEndian): | ||||
|     size = Field("I") | ||||
|     data = Field(size=size) | ||||
| 
 | ||||
| 
 | ||||
| print(ChunkedHeader(b"")) | ||||
							
								
								
									
										149
									
								
								tools/dissect_net.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								tools/dissect_net.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | |||
| from construct import * | ||||
| from binascii import unhexlify | ||||
| from collections import defaultdict, Counter | ||||
| import string | ||||
| 
 | ||||
| 
 | ||||
| class CustomError(SymmetricAdapter): | ||||
|     def __init__(self, msg): | ||||
|         super(SymmetricAdapter, self).__init__(Pass) | ||||
|         self._message = msg | ||||
| 
 | ||||
|     def _decode(self, obj, context, path): | ||||
|         # print("Error",path) | ||||
|         # print(str(context)) | ||||
|         msg = self._message.format(ctx=context, obj=obj) | ||||
|         raise ValidationError(message=msg, path=this.path) | ||||
| 
 | ||||
| 
 | ||||
| paket_type = Enum( | ||||
|     Int8ub, | ||||
|     GetGameInfo=0x7F01, # 0x7f3d ? | ||||
|     Connect=0x7F47, | ||||
|     GameInfo=0xBACE, | ||||
|     LevelInfo=0x8017, | ||||
|     Announce=0x4842, | ||||
|     Disconnect=0x0F02, | ||||
|     UpdatePlayerInfo=0xC49,  # ??? | ||||
|     # UpdatePlayerInfo=0x8a4c, | ||||
|     ChatIn=0x921E, | ||||
|     ChatOut=0x0A1E, | ||||
|     # Movement=0x802 | ||||
| ) | ||||
| 
 | ||||
| paket_subtype = Enum( | ||||
|     Int8ub | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| packet_types = { | ||||
|     "Movement": Struct("data" / GreedyBytes), | ||||
|     "ChatIn": Struct( | ||||
|         "unk" / Int16ub, | ||||
|         "unk_2" / Int8ub, | ||||
|         "msg" / PascalString(Int8ub, "utf-8"), | ||||
|         "rest" / GreedyBytes, | ||||
|     ), | ||||
|     "ChatOut": Struct( | ||||
|         "unk" / Int16ub, | ||||
|         "unk_2" / Int8ub, | ||||
|         "msg" / PascalString(Int8ub, "utf-8"), | ||||
|         "rest" / GreedyBytes, | ||||
|     ), | ||||
|     "UpdatePlayerInfo": Struct( | ||||
|         "data" / GreedyBytes | ||||
|         # "name"/PascalString(Int32ub,"utf-8"), | ||||
|         # "ship"/PascalString(Int8ub,"utf-8"), | ||||
|         # "max_life"/Int8ub, | ||||
|         # "player_char"/PascalString(Int16ub,"utf-8"), | ||||
|         # "engines"/PascalString(Int8ub,"utf-8")[4], | ||||
|         # "weapons"/PascalString(Int8ub,"utf-8"), | ||||
|         # "team_id"/Int32ul | ||||
|     ), | ||||
|     "Announce": "info" / CString("utf-8"), | ||||
|     "GetGameInfo": Const(b"\x00\x00\x07"), | ||||
|     "Disconnect": Const(b"\x00\x0c\x02"), | ||||
|     "GameInfo": Struct( | ||||
|         "version_minor" / Int8ul, | ||||
|         "version_major" / Int8ul, | ||||
|         "port" / Int16ul, | ||||
|         "max_players" / Int16ul, | ||||
|         "curr_players" / Int16ul, | ||||
|         "name" / FixedSized(0x20, CString("utf-8")), | ||||
|         "mode" / FixedSized(0x10, CString("utf-8")), | ||||
|         "map" / Bytes(2), | ||||
|         "rest" / GreedyBytes, | ||||
|     ), | ||||
|     "Connect": Struct( | ||||
|         "name" / PascalString(Int32ub, "utf-8"), | ||||
|         "ship" / PascalString(Int8ub, "utf-8"), | ||||
|         "max_life" / Int8ub, | ||||
|         "player_char" / PascalString(Int16ub, "utf-8"), | ||||
|         "engines" / PascalString(Int8ub, "utf-8")[4], | ||||
|         "weapons" / PascalString(Int8ub, "utf-8"), | ||||
|         "team_id" / Int32ul, | ||||
|     ), | ||||
|     "LevelInfo": Struct( | ||||
|         "path" / PascalString(Int32ub, "utf-8"), | ||||
|         "mode" / PascalString(Int8ub, "utf-8"), | ||||
|         "rest" / GreedyBytes, | ||||
|     ), | ||||
| } | ||||
| 
 | ||||
| default = "Unknown ID" / Struct("data" / GreedyBytes) | ||||
| # CustomError("Invalid ID: 0x{ctx.type:02x}") | ||||
| packet = Struct( | ||||
|     "type" / Int8ub, | ||||
|     "subtype"/ Int8ub | ||||
|     # "data" / Switch(this.type, packet_types, default=default) | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c") | ||||
| 
 | ||||
| 
 | ||||
| def is_printable(s): | ||||
|     return all(c in printable_chars for c in s.rstrip(b"\0")) | ||||
| 
 | ||||
| 
 | ||||
| def hexdump(data, cols=16, offset=0): | ||||
|     lines = [] | ||||
|     while data: | ||||
|         hexdata = " ".join("{:02X}".format(v) for v in data[:cols]).ljust( | ||||
|             3 * cols - 1, " " | ||||
|         ) | ||||
|         print_data = "".join( | ||||
|             [chr(v) if v in printable_chars else "." for v in data[:cols]] | ||||
|         ) | ||||
|         lines.append("{:04X}   {}   {}".format(offset, hexdata, print_data)) | ||||
|         offset += len(data[:cols]) | ||||
|         data = data[cols:] | ||||
|     return "\n".join(lines).strip() | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     data_type = Counter() | ||||
|     with open("netlog.txt", "r") as netlog: | ||||
|         for line in netlog: | ||||
|             direction, addr, buffer_addr, data = line.strip().split() | ||||
|             data = unhexlify(data) | ||||
|             print(direction, addr, buffer_addr) | ||||
|             print(hexdump(data)) | ||||
|             print() | ||||
|             try: | ||||
|                 parsed_data = packet.parse(data) | ||||
|                 data_type["{0} {1:08b}:{2:08b} ({1:02X}:{2:02X})".format(direction, parsed_data.type,parsed_data.subtype)] += len(data) | ||||
|             except Exception: | ||||
|                 pass | ||||
|     bar_width = 50 | ||||
|     label = "Data type (main:sub)" | ||||
|     print("=" * 10, label, "=" * 10) | ||||
|     max_v = max(data_type.values()) | ||||
|     total = sum(data_type.values()) | ||||
|     for k, v in sorted(data_type.items(), key=lambda v: v[1], reverse=True): | ||||
|         bar = ("#" * round((v / max_v) * bar_width)).ljust(bar_width, " ") | ||||
|         print(k, bar, "({}, {:.02%})".format(v, v / total)) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
							
								
								
									
										46
									
								
								tools/packed.ksy
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								tools/packed.ksy
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| meta: | ||||
|   id: packed | ||||
|   application: Scrapland | ||||
|   file-extension: packed | ||||
|   endian: le | ||||
|   xref: http://wiki.xentax.com/index.php/Scrapland_PACKED | ||||
|   license: MIT | ||||
|   encoding: latin1 | ||||
| 
 | ||||
| seq: | ||||
|   - id: magic | ||||
|     contents: BFPK | ||||
|     doc: File Magic | ||||
|   - id: version | ||||
|     type: u2 | ||||
|     size: 4 | ||||
|     doc: Second File Magic | ||||
|   - id: num_files | ||||
|     type: u4 | ||||
|     doc: Number of files | ||||
|   - id: files | ||||
|     type: file_entry | ||||
|     repeat: expr | ||||
|     repeat-expr: num_files | ||||
|     doc: Directory entry for each file | ||||
| 
 | ||||
| types: | ||||
|   file_entry: | ||||
|     seq: | ||||
|       - id: path_len | ||||
|         type: u4 | ||||
|         doc: Length of file path | ||||
|       - id: path | ||||
|         type: str | ||||
|         size: path_len | ||||
|         doc: File path | ||||
|       - id: size | ||||
|         type: u4 | ||||
|         doc: File size | ||||
|       - id: offset | ||||
|         type: u4 | ||||
|         doc: Absoulte File offset | ||||
|     instances: | ||||
|       data: | ||||
|         pos: offset | ||||
|         size: size | ||||
							
								
								
									
										122
									
								
								tools/parse_LFVF.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								tools/parse_LFVF.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,122 @@ | |||
| import os | ||||
| import json | ||||
| from construct import * | ||||
| 
 | ||||
| blocksize = 1024 * 4 | ||||
| 
 | ||||
| 
 | ||||
| def search(pattern, path): | ||||
|     seen = set() | ||||
|     with open(path, "rb") as infile: | ||||
|         buffer = bytearray(infile.read(blocksize)) | ||||
|         while infile.peek(1): | ||||
|             for block in iter(lambda: infile.read(blocksize), b""): | ||||
|                 buffer += block | ||||
|                 buffer = buffer[-(blocksize * 2) :] | ||||
|                 idx = buffer.find(pattern) | ||||
|                 if idx != -1: | ||||
|                     pos = (infile.tell() - blocksize * 2) + idx | ||||
|                     if pos not in seen: | ||||
|                         seen.add(pos) | ||||
|     return sorted(seen) | ||||
| 
 | ||||
| 
 | ||||
| has_pos = [ | ||||
|     "D3DFVF_XYZ", | ||||
|     "D3DFVF_XYZRHW", | ||||
| ] | ||||
| 
 | ||||
| num_blend = { | ||||
|     'D3DFVF_XYZB1': 1, | ||||
|     'D3DFVF_XYZB2': 2, | ||||
|     'D3DFVF_XYZB3': 3, | ||||
|     'D3DFVF_XYZB4': 4, | ||||
| } | ||||
| 
 | ||||
| Vertex = Struct( | ||||
|     "pos" / If(lambda ctx: ctx._._.fvf.position in has_pos, Float32l[3]), | ||||
|     "rhw" / If(lambda ctx: ctx._._.fvf.position == "D3DFVF_XYZRHW", Float32l), | ||||
|     "w_blend" / If(lambda ctx: num_blend.get(ctx._._.fvf.position,0)!=0, Int32ul), | ||||
|     "normal" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_NORMAL, Float32l[3]), | ||||
|     "diffuse" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_DIFFUSE, Int8ul[4]), | ||||
|     "specular" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_SPECULAR, Int8ul[4]), | ||||
|     "tex" / Float32l[this.num_tex_coords][this._._.fvf.num_tex], | ||||
| ) | ||||
| 
 | ||||
| D3DFVF_POSITION_MASK = 0xE | ||||
| D3DFVF_TEXCOUNT_MASK = 0xF00 | ||||
| D3DFVF_TEXCOUNT_SHIFT = 8 | ||||
| 
 | ||||
| FVF = "fvf" / Union( | ||||
|     0, | ||||
|     "value" / Int32ul, | ||||
|     "num_tex" | ||||
|     / Computed( | ||||
|         lambda ctx: 1 + ((ctx.value & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_MASK) | ||||
|     ), | ||||
|     "position" | ||||
|     / Enum( | ||||
|         Computed(lambda ctx: (ctx.value & D3DFVF_POSITION_MASK)), | ||||
|         D3DFVF_XYZ=0x2, | ||||
|         D3DFVF_XYZRHW=0x4, | ||||
|         D3DFVF_XYZB1=0x6, | ||||
|         D3DFVF_XYZB2=0x8, | ||||
|         D3DFVF_XYZB3=0xA, | ||||
|         D3DFVF_XYZB4=0xC, | ||||
|     ), | ||||
|     "flags" | ||||
|     / FlagsEnum( | ||||
|         Int32ul, | ||||
|         D3DFVF_RESERVED0=0x1, | ||||
|         D3DFVF_NORMAL=0x10, | ||||
|         D3DFVF_PSIZE=0x20, | ||||
|         D3DFVF_DIFFUSE=0x40, | ||||
|         D3DFVF_SPECULAR=0x80, | ||||
|     ), | ||||
| ) | ||||
| 
 | ||||
| LFVF_Data = Struct( | ||||
|     "unk" / Int32ul, | ||||
|     "num_entries"/Int32ul, | ||||
|     "data"/Struct( | ||||
|         FVF, | ||||
|         "unk_size" / Int32ul, | ||||
|         "vertices" / PrefixedArray(Int32ul, Vertex), | ||||
|     ) | ||||
|     # Terminated, | ||||
| ) | ||||
| 
 | ||||
| LFVF = Struct( | ||||
|     Const(b"LFVF"), "size" / Int32ul, "data" / RestreamData(Bytes(this.size), LFVF_Data) | ||||
| ) | ||||
| 
 | ||||
| files = [ | ||||
|     r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\skies\orbit\sky.sm3", | ||||
|     r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\chars\boss\boss.sm3", | ||||
|     r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\chars\dtritus\dtritus.sm3", | ||||
|     r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\levels\gdb\map\map3d.emi" | ||||
| ] | ||||
| 
 | ||||
| vert_pos = {} | ||||
| 
 | ||||
| for path in files: | ||||
|     name = os.path.split(path)[-1] | ||||
|     fh = open(path, "rb") | ||||
|     offsets = search(b"LFVF", path) | ||||
|     for offset in sorted(offsets): | ||||
|         fh.seek(offset) | ||||
|         print("Offset:", offset) | ||||
|         s = LFVF.parse_stream(fh) | ||||
|         print(s) | ||||
|         print("=" * 10) | ||||
|         continue | ||||
|     #     # print(s) | ||||
|     #     print(path, fh.tell(), list(s.unk_ints), list(s.data.unk), fh.read(8)) | ||||
|     #     s = s.data | ||||
|     #     vpos = [ | ||||
|     #         tuple(p for p in v.pos) for v in s.vertices | ||||
|     #     ]  # leave vertices alone because we don't need to reproject shit :| | ||||
|     #     vert_pos["{}@{}".format(name, hex(offset))] = vpos | ||||
|     # with open("LFVF_Data.json", "w") as of: | ||||
|     #     json.dump(vert_pos, of) | ||||
|     # break | ||||
							
								
								
									
										117
									
								
								tools/parse_chunked.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								tools/parse_chunked.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | |||
| from construct import * | ||||
| import binascii | ||||
| import os | ||||
| 
 | ||||
| Chunked = LazyBound(lambda: struct) | ||||
| 
 | ||||
| 
 | ||||
| class CustomError(SymmetricAdapter): | ||||
|     def __init__(self, msg): | ||||
|         super(SymmetricAdapter, self).__init__(Pass) | ||||
|         self._message = msg | ||||
| 
 | ||||
|     def _decode(self, obj, context, path): | ||||
|         # print("Error",path) | ||||
|         # print(str(context)) | ||||
|         msg = "Invalid ID: " + repr(context.id) | ||||
|         raise ValidationError(message=msg, path=this.path) | ||||
| 
 | ||||
| 
 | ||||
| RGB = NamedTuple("RGB", "R G B", Int8ul[3]) | ||||
| 
 | ||||
| RGBA = NamedTuple("RGBA", "R G B A", Int8ul[4]) | ||||
| 
 | ||||
| 
 | ||||
| def make_chain(*sizes): | ||||
|     "utility function to make sequence of byte arrays" | ||||
|     return Sequence(*[Bytes(s) for s in sizes]) | ||||
| 
 | ||||
| 
 | ||||
| child_nodes = "children" / Struct("num" / Int32ul, "nodes" / Chunked[this.num]) | ||||
| 
 | ||||
| subchunks = { | ||||
|     b"SM3\0": Struct( | ||||
|         "unk" / Bytes(4), | ||||
|         "timestamp" / Timestamp(Int32ul, 1, 1970), | ||||
|         child_nodes, | ||||
|         "scene" / Chunked, | ||||
|     ), | ||||
|     b"SCN\0": Struct( | ||||
|         "version" / Int32ul, | ||||
|         "m3d_name" / PascalString(Int32ul, "utf8"), | ||||
|         "name" / PascalString(Int32ul, "utf8"), | ||||
|         child_nodes, | ||||
|     ), | ||||
|     b"INI\0": Struct( | ||||
|         "data" | ||||
|         / PrefixedArray(Int32ul, PrefixedArray(Int32ul, PascalString(Int32ul, "utf8"))), | ||||
|         "colors?" / Sequence(Int8ul, Int8ul, Int8ul, Int8ul, Float32l)[2], | ||||
|         "unk_data" / Bytes(0x18), | ||||
|         "unk_float" / Float32l, | ||||
|         "unk_int" / Int32ul, | ||||
|         child_nodes, | ||||
|     ), | ||||
|     b"EMI\0": Struct( | ||||
|         "version"/Int32ul, | ||||
|         "num_materials"/Int32ul, | ||||
|         "num_unk"/Int32ul, | ||||
|         "materials"/Chunked | ||||
|     ), | ||||
| 
 | ||||
|     b"MAT\0": Struct( | ||||
|         "tris"/Int32ul, | ||||
|         "name"/PascalString(Int32ul,"utf8"), | ||||
|         "idx"/Bytes(this.tris*4*4) | ||||
|     ), | ||||
| 
 | ||||
|     None: Bytes(lambda ctx:ctx.size), | ||||
| } | ||||
| 
 | ||||
| struct = Struct( | ||||
|     "id" / Bytes(4), | ||||
|     "size" / Int32ul, | ||||
|     "data" / Switch(this.id, subchunks, default=subchunks[None]), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def io_peek(fh, n): | ||||
|     p = fh.tell() | ||||
|     ret = fh.read(n) | ||||
|     fh.seek(p) | ||||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| basedir = r"D:/Games/Deep Silver/Scrapland/extracted/Data.packed" | ||||
| 
 | ||||
| files = [ | ||||
|     r"Models/Elements/AnilloEstructuraA/AnilloEstructuraA.SM3", | ||||
|     r"models/elements/antenaa/antenaa.lod1.sm3", | ||||
|     r"models/elements/abshield/anm/loop.cm3", | ||||
|     r"levels/fake/map/map3d.amc", | ||||
|     r"levels/shipedit/map/map3d.dum", | ||||
|     r"levels/menu/map/map3d.emi", | ||||
|     r"Models/Skies/Menu/Sky.SM3", | ||||
|     r"Levels/Menu/Map/Map3D.SM3", | ||||
|     r"Models/Elements/AnilloEstructuraD/AnilloEstructuraD.LOD1.SM3", | ||||
|     r"levels/menu/map/map3d.amc", | ||||
|     r"levels/menu/map/map3d.dum", | ||||
|     r"levels/menu/map/scenecamera/anm/loop.cm3", | ||||
|     r"models/chars/boss/boss.sm3", | ||||
|     r"models/chars/boss/anm/boss_walk.cm3", | ||||
| ] | ||||
| for file in files: | ||||
|     file = os.path.join(basedir, file).replace("/","\\") | ||||
|     print() | ||||
|     print("#" * 3, file) | ||||
|     with open(file, "rb") as infile: | ||||
|         try: | ||||
|             data = struct.parse_stream(infile) | ||||
|             # assert infile.read()==b"","leftover data" | ||||
|         except Exception as ex: | ||||
|             print("Error:", ex) | ||||
|             data = None | ||||
|         if data: | ||||
|             print(data) | ||||
|         print("OFFSET:", hex(infile.tell())) | ||||
|         print("NEXT:", io_peek(infile, 16)) | ||||
|         print("NEXT:", binascii.hexlify(io_peek(infile, 16))) | ||||
							
								
								
									
										255
									
								
								tools/parse_chunked_new.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								tools/parse_chunked_new.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,255 @@ | |||
| import os | ||||
| import sys | ||||
| import struct | ||||
| import string | ||||
| from pprint import pprint | ||||
| from io import BytesIO | ||||
| from contextlib import contextmanager | ||||
| from datetime import timedelta, datetime | ||||
| import glob | ||||
| 
 | ||||
| printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c") | ||||
| 
 | ||||
| 
 | ||||
| def hexdump(data, cols=16, offset=0, markers=None): | ||||
|     if markers is None: | ||||
|         markers = [] | ||||
|     lines = [] | ||||
|     while True: | ||||
|         hexdata = " ".join("{:02X}".format(v) for v in data[:cols]).ljust( | ||||
|             3 * cols - 1, " " | ||||
|         ) | ||||
|         print_data = "".join( | ||||
|             [chr(v) if v in printable_chars else "." for v in data[:cols]] | ||||
|         ) | ||||
|         lines.append("{:04X}   {}   {}".format(offset, hexdata, print_data)) | ||||
|         offset += len(data[:cols]) | ||||
|         data = data[cols:] | ||||
|         if not data: | ||||
|             break | ||||
|     return "\n".join(lines).strip() | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def seek_to(fh, offset, pos=None): | ||||
|     if pos is None: | ||||
|         pos = fh.tell() | ||||
|     fh.seek(offset) | ||||
|     yield | ||||
|     fh.seek(pos) | ||||
| 
 | ||||
| def read_array(s,fh): | ||||
|     ret=[] | ||||
|     count = read_struct("<I", fh)[0] | ||||
|     size = struct.calcsize(s) | ||||
|     for _ in range(count): | ||||
|         ret.append(read_struct(s,fh)) | ||||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| def read_struct(s, fh): | ||||
|     size = struct.calcsize(s) | ||||
|     return struct.unpack(s, fh.read(size)) | ||||
| 
 | ||||
| 
 | ||||
| def read_str(fh): | ||||
|     size = read_struct("<I", fh)[0] | ||||
|     return fh.read(size) | ||||
| 
 | ||||
| 
 | ||||
| def read_block(fh): | ||||
|     try: | ||||
|         pos = fh.tell() | ||||
|         magic = str(fh.read(4).rstrip(b"\x00"), "utf8") | ||||
|         size = read_struct("<I", fh)[0] | ||||
|         data = fh.read(size) | ||||
|         return magic, data | ||||
|     except struct.error: | ||||
|         fh.seek(pos) | ||||
|         return | ||||
| 
 | ||||
| 
 | ||||
| vals = set() | ||||
| 
 | ||||
| # ================================ | ||||
| class Parser: | ||||
|     depth = 0 | ||||
|     dump_size = 0x100 | ||||
| 
 | ||||
|     def __init__(self, debug=False): | ||||
|         self.debug = debug | ||||
| 
 | ||||
|     def _default(self, magic, fh): | ||||
|         print("=====", magic, "=====") | ||||
|         if self.debug: | ||||
|             print(hexdump(fh.read(self.dump_size))) | ||||
|             rest = len(fh.read()) | ||||
|             if rest: | ||||
|                 print("<{} more bytes>".format(rest)) | ||||
|         fh.seek(0) | ||||
|         return "<Unparsed {} ({} bytes)>".format(magic, len(fh.read())) | ||||
| 
 | ||||
|     def parse(self, magic, data, depth=0): | ||||
|         print("{}[{}] {} bytes".format("  " * self.depth, magic, len(data))) | ||||
|         self.depth += 1 | ||||
|         fh = BytesIO(data) | ||||
|         ret = getattr(self, magic, lambda fh: self._default(magic, fh))(fh) | ||||
|         pos = fh.tell() | ||||
|         leftover = len(fh.read()) | ||||
|         fh.seek(pos) | ||||
|         self.depth -= 1 | ||||
|         if leftover: | ||||
|             print("{}[{}] {} bytes unparsed".format("  " * self.depth, magic, leftover)) | ||||
|             if self.debug: | ||||
|                 print(hexdump(fh.read(self.dump_size))) | ||||
|                 rest = len(fh.read()) | ||||
|                 if rest: | ||||
|                     print("<{} more bytes>".format(rest)) | ||||
|             print("-" * 50) | ||||
|         return ret | ||||
| 
 | ||||
|     def parse_block(self, fh): | ||||
|         block = read_block(fh) | ||||
|         if block: | ||||
|             return self.parse(*block) | ||||
| 
 | ||||
|     # Block definitions | ||||
| 
 | ||||
|     def SM3(self, fh): | ||||
|         ret = {} | ||||
|         ret["unk_1"] = fh.read(4)  # always F8156500 | ||||
|         ret["timestamp_2"] = datetime.fromtimestamp(read_struct("<I", fh)[0]) | ||||
|         ret["unk_2"] = fh.read(4)  # always 00000000 | ||||
|         ret["scene"] = self.parse_block(fh) | ||||
|         assert fh.read() == b"", "Leftover Data" | ||||
|         return ret | ||||
| 
 | ||||
|     def SCN(self, fh): | ||||
|         ret = {} | ||||
|         ret["unk_1"] = read_struct("<I", fh)[0] | ||||
|         ret["model_name"] = read_str(fh) | ||||
|         ret["node_name"] = read_str(fh) | ||||
|         if read_struct("<I", fh)[0]: | ||||
|             ret["ini_1"] = self.parse_block(fh) | ||||
|         ret["unk_c_1"] = read_struct("<BBBB", fh) | ||||
|         ret["unk_f_1"] = read_struct("<f", fh)[0] | ||||
|         ret["unk_c_2"] = read_struct("<BBBB", fh) | ||||
|         ret["unk_f_l"] = read_struct("<ffffffff", fh) | ||||
|         if read_struct("<I", fh)[0]: | ||||
|             ret["ini_2"] = self.parse_block(fh) | ||||
|         ret["num_mat"] = read_struct("<I", fh)[0] | ||||
|         ret["mat"] = [] | ||||
|         for _ in range(ret["num_mat"]): | ||||
|             ret["mat"].append(self.parse_block(fh)) | ||||
|         #     ret["children"] = [] | ||||
|         #     for _ in range(read_struct("<I", fh)[0]): | ||||
|         #         ret["children"].append(self.parse_block(fh)) | ||||
|         #     ret["unk_2"] = [] | ||||
|         #     for _ in range(4): | ||||
|         #         ret["unk_2"].append(read_struct("<fff", fh)) | ||||
|         #     ret["materials"] = [] | ||||
|         #     for _ in range(read_struct("<I", fh)[0]): | ||||
|         #         ret["materials"].append(self.parse_block(fh)) | ||||
|         return ret | ||||
| 
 | ||||
|     def INI(self, fh): | ||||
|         num_sections = read_struct("<I", fh)[0] | ||||
|         sections = [] | ||||
|         for _ in range(num_sections): | ||||
|             num_lines = read_struct("<I", fh)[0] | ||||
|             lines = [] | ||||
|             for _ in range(num_lines): | ||||
|                 lines.append(str(read_str(fh).rstrip(b"\0"), "latin1")) | ||||
|             sections.append("\n".join(lines)) | ||||
|             lines.clear() | ||||
|         assert fh.read() == b"", "Leftover Data" | ||||
|         return sections | ||||
| 
 | ||||
|     def MAT(self, fh): | ||||
|         #     ret = {} | ||||
|         #     ret["unk_1"] = read_struct("<I", fh)[0] | ||||
|         #     ret["name"] = read_str(fh) | ||||
|         #     ret["colors?"] = ["{:08X}".format(v) for v in read_struct(">7I", fh)] | ||||
|         # ret["maps"]=[] | ||||
|         # for _ in range(ret["num_maps"]): | ||||
|         #     ret["maps"].append(self.parse_block(fh)) | ||||
|         return {"maps": fh.read().count(b"MAP\0")} | ||||
| 
 | ||||
|     def MAP(self, fh): | ||||
|         ret = {} | ||||
|         ret["unk_1"] = read_struct("<I", fh)[0] | ||||
|         ret["name"] = read_str(fh) | ||||
|         ret["unk_2"] = read_struct("<IIII", fh) | ||||
|         ret["unk_3"] = read_struct("<fff", fh) | ||||
|         ret["unk_4"] = read_struct("<II", fh) | ||||
|         ret["rest"] = fh.read() | ||||
|         return ret | ||||
| 
 | ||||
|     # def CM3(self, fh): | ||||
|     #     return len(fh.read()) | ||||
| 
 | ||||
|     def DUM(self, fh): | ||||
|         ret = {} | ||||
|         ret["unk_1"] = read_struct("<I", fh) | ||||
|         ret["num_dummies"] = read_struct("<I", fh)[0] | ||||
|         ret["unk_2"] = read_struct("<I", fh) | ||||
|         ret["dummies"] = [] | ||||
|         for _ in range(ret["num_dummies"]): | ||||
|             dum = {} | ||||
|             dum["name"] = read_str(fh) | ||||
|             dum["pos"] = read_struct("<fff", fh) | ||||
|             dum["rot"] = read_struct("<fff", fh) | ||||
|             dum["has_ini"] = read_struct("<I", fh)[0] | ||||
|             if dum["has_ini"]: | ||||
|                 dum['ini']=self.parse_block(fh) | ||||
|             dum["has_next"] = read_struct("<I", fh)[0] | ||||
|             ret["dummies"].append(dum) | ||||
|         assert fh.read() == b"", "Leftover Data" | ||||
|         return ret | ||||
| 
 | ||||
|     # def AMC(self, fh): | ||||
|     #     return len(fh.read()) | ||||
| 
 | ||||
|     # def EMI(self, fh): | ||||
|     #     return len(fh.read()) | ||||
| 
 | ||||
| 
 | ||||
| # ================================ | ||||
| 
 | ||||
| basedir = r"D:/Games/Deep Silver/Scrapland/extracted/Data.packed" | ||||
| 
 | ||||
| files = [ | ||||
|     r"Models/Chars/Dtritus/Dtritus.sm3", | ||||
|     r"Models/Elements/AnilloEstructuraA/AnilloEstructuraA.SM3", | ||||
|     r"models/elements/antenaa/antenaa.lod1.sm3", | ||||
|     # r"models/elements/abshield/anm/loop.cm3", | ||||
|     # r"levels/fake/map/map3d.amc", | ||||
|     # r"levels/shipedit/map/map3d.dum", | ||||
|     # r"levels/menu/map/map3d.emi", | ||||
|     r"Models/Skies/Menu/Sky.SM3", | ||||
|     r"Levels/Menu/Map/Map3D.SM3", | ||||
|     r"Models/Elements/AnilloEstructuraD/AnilloEstructuraD.LOD1.SM3", | ||||
|     # r"levels/menu/map/map3d.amc", | ||||
|     # r"levels/menu/map/map3d.dum", | ||||
|     # r"levels/menu/map/scenecamera/anm/loop.cm3", | ||||
|     r"models/chars/boss/boss.sm3", | ||||
|     # r"models/chars/boss/anm/boss_walk.cm3", | ||||
| ] | ||||
| 
 | ||||
| filt = [s.lower() for s in sys.argv[1:]] | ||||
| 
 | ||||
| for root, folders, files in os.walk(basedir): | ||||
|     for file in files: | ||||
|         path = os.path.join(root, file).replace("\\","/") | ||||
|         if not path.lower().endswith(".dum".lower()): | ||||
|             continue | ||||
|         print("Parsing", path) | ||||
|         p = Parser(debug=True) | ||||
|         with open(path, "rb") as fh: | ||||
|             while True: | ||||
|                 parsed = p.parse_block(fh) | ||||
|                 if not parsed: | ||||
|                     break | ||||
|                 pprint(parsed, compact=False, indent=4) | ||||
|         print("#" * 50) | ||||
| 
 | ||||
							
								
								
									
										31
									
								
								tools/rbingrep.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tools/rbingrep.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import binascii | ||||
| import os | ||||
| import sys | ||||
| 
 | ||||
| exe_file = os.path.abspath(sys.argv[1]) | ||||
| 
 | ||||
| def search(pattern, path): | ||||
|     seen = set() | ||||
|     with open(path, "rb") as infile: | ||||
|         buffer = bytearray(infile.read(blocksize)) | ||||
|         while infile.peek(1): | ||||
|             for block in iter(lambda: infile.read(blocksize), b""): | ||||
|                 buffer += block | ||||
|                 buffer = buffer[-(blocksize * 2) :] | ||||
|                 idx = buffer.find(pattern) | ||||
|                 if idx != -1: | ||||
|                     pos = (infile.tell() - blocksize * 2) + idx | ||||
|                     if pos not in seen: | ||||
|                         seen.add(pos) | ||||
|     return sorted(seen) | ||||
| 
 | ||||
| 
 | ||||
| markers = [ "AMC", "ANI", "CAM", "CM3", "CMSH", "DUM", "EMI", "EVA", "INI", "LFVF", "LUZ", "MAP", "MAT", "MD3D", "NAE", "NAM", "PORT", "QUAD", "SCN", "SM3", "SUEL", "TRI", ] | ||||
| 
 | ||||
| blocksize = 1024 * 4 | ||||
| for marker in markers: | ||||
|     pattern = bytes(marker, "utf8").ljust(4, b"\0") | ||||
|     res = search(pattern, exe_file) | ||||
|     print("?e "+marker) | ||||
|     for addr in res: | ||||
|         print("/r `?P {}`".format(hex(addr))) | ||||
							
								
								
									
										50
									
								
								tools/render_ai_path.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								tools/render_ai_path.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| from construct import * | ||||
| 
 | ||||
| AI_PATH = "Path" / Struct( | ||||
|     "num_nodes" / Int32ul, | ||||
|     "nodes" / Float32l[3][this.num_nodes], | ||||
|     "edges" / PrefixedArray(Int32ul, Float32l[3])[this.num_nodes], | ||||
| ) | ||||
| 
 | ||||
| data = AI_PATH.parse_file(sys.argv[1]) | ||||
| 
 | ||||
| nodes = [tuple(node) for node in data.nodes] | ||||
| edges = [[nodes.index(tuple(p)) for p in edge] for edge in data.edges] | ||||
| 
 | ||||
| # Run in Blender: | ||||
| """ | ||||
| import bpy | ||||
| import numpy as np | ||||
| import itertools as ITT | ||||
| 
 | ||||
| nodes = <paste_nodes> | ||||
| edges_=<paste_edges> | ||||
| 
 | ||||
| # pasted node and edges here | ||||
| 
 | ||||
| edges=[] | ||||
| 
 | ||||
| for edge in edges_: | ||||
|     for a,b in zip(edge,edge[1:]): | ||||
|         edges.append(a) | ||||
|         edges.append(b) | ||||
| 
 | ||||
| nodes=[[p*0.0001 for p in node] for node in nodes] | ||||
| 
 | ||||
| me = bpy.data.meshes.new("Test") | ||||
| 
 | ||||
| nodes = np.array(list(ITT.chain.from_iterable(nodes))) | ||||
| 
 | ||||
| me.vertices.add(len(nodes)//3) | ||||
| me.vertices.foreach_set("co", nodes) | ||||
| me.edges.add(len(edges)//2) | ||||
| me.edges.foreach_set("vertices", np.array(edges)) | ||||
| 
 | ||||
| me.update(calc_edges=True) | ||||
| me.validate() | ||||
| 
 | ||||
| ob = bpy.data.objects.new("Test", me) | ||||
| 
 | ||||
| scene = bpy.context.scene | ||||
| scene.collection.objects.link(ob) | ||||
| """ | ||||
							
								
								
									
										27
									
								
								tools/save_to_json.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tools/save_to_json.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import sys | ||||
| import os | ||||
| from construct import * | ||||
| import json | ||||
| 
 | ||||
| save_data = {} | ||||
| 
 | ||||
| ScrapSaveVar = Struct( | ||||
|     "name" / PascalString(Int32ul, encoding="windows-1252"), | ||||
|     "data" / PascalString(Int32ul, encoding="windows-1252"), | ||||
| ) | ||||
| ScrapSave = "ScarpSaveGame" / Struct( | ||||
|     "title" / PascalString(Int32ul, encoding="windows-1252"), | ||||
|     "id" / PascalString(Int32ul, encoding="windows-1252"), | ||||
|     "data" / PrefixedArray(Int32ul, ScrapSaveVar), | ||||
|     Terminated, | ||||
| ) | ||||
| 
 | ||||
| with open(sys.argv[1], "rb") as sav_file: | ||||
|     save = ScrapSave.parse_stream(sav_file) | ||||
|     save_data["id"] = save.id | ||||
|     save_data["title"] = save.title | ||||
|     save_data["data"] = {} | ||||
|     for var in save.data: | ||||
|         save_data["data"][var.name] = var.data | ||||
|     with open(os.path.basename(sys.argv[1]) + ".json", "w") as of: | ||||
|         json.dump(save_data, of, indent=4) | ||||
|  | @ -3,18 +3,29 @@ from collections import OrderedDict | |||
| import glob | ||||
| import os | ||||
| import shutil | ||||
| from construct import * | ||||
| from construct import ( | ||||
|     Struct, | ||||
|     PascalString, | ||||
|     Int32ul, | ||||
|     Lazy, | ||||
|     Pointer, | ||||
|     Bytes, | ||||
|     this, | ||||
|     PrefixedArray, | ||||
|     Const, | ||||
|     Debugger | ||||
| ) | ||||
| from tqdm import tqdm | ||||
| 
 | ||||
| setglobalstringencoding(None) | ||||
| 
 | ||||
| ScrapFile = Struct( | ||||
|     "path" / PascalString(Int32ul), | ||||
|     "path" / PascalString(Int32ul, encoding="ascii"), | ||||
|     "size" / Int32ul, | ||||
|     "offset" / Int32ul, | ||||
|     "data" / OnDemandPointer(this.offset, Bytes(this.size)), | ||||
|     "data" / Lazy(Pointer(this.offset, Bytes(this.size))), | ||||
| ) | ||||
| DummyFile = Struct( | ||||
|     "path" / PascalString(Int32ul, encoding="u8"), "size" / Int32ul, "offset" / Int32ul | ||||
| ) | ||||
| DummyFile = Struct("path" / PascalString(Int32ul), "size" / Int32ul, "offset" / Int32ul) | ||||
| 
 | ||||
| PackedHeader = Struct( | ||||
|     Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile) | ||||
							
								
								
									
										45
									
								
								tools/server.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								tools/server.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| import socket | ||||
| import binascii | ||||
| import select | ||||
| from construct import * | ||||
| from socketserver import BaseRequestHandler,UDPServer | ||||
| 
 | ||||
| INFO = Struct( | ||||
|     "version_minor" / Int8ul, | ||||
|     "version_major" / Int8ul, | ||||
|     "port" / Int16ul, | ||||
|     "max_players" / Int16ul, | ||||
|     "curr_players" / Int16ul, | ||||
|     "name" / FixedSized(0x20, CString("utf-8")), | ||||
|     "mode" / FixedSized(0x10, CString("utf-8")), | ||||
|     "map" / Bytes(2), | ||||
|     "rest" / GreedyBytes, | ||||
| ) | ||||
| 
 | ||||
| class ScrapHandler(BaseRequestHandler): | ||||
|     def handle(self): | ||||
|         data, socket = self.request | ||||
|         print(self.client_address,data) | ||||
|         socket.sendto(data, self.client_address) | ||||
| 
 | ||||
| class ScrapSrv(UDPServer): | ||||
|     def __init__(self,port=5000): | ||||
|         super().__init__(("0.0.0.0",port),ScrapHandler) | ||||
| 
 | ||||
| with ScrapSrv() as srv: | ||||
|     srv.serve_forever() | ||||
| 
 | ||||
| exit() | ||||
| 
 | ||||
| # sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||||
| 
 | ||||
| while True: | ||||
|     rl, wl, xl = select.select([sock], [sock], [sock], 0.1) | ||||
|     if rl: | ||||
|         print(rl) | ||||
|     for sock in rl: | ||||
|         data, src = sock.recvfrom(1024) | ||||
|         print(src, data) | ||||
|         if data == b"\x7f\x01\x00\x00\x07": | ||||
|             game_info = INFO.build() | ||||
|             sock.sendto(game_info, src) | ||||
							
								
								
									
										4
									
								
								tools/test.bv
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tools/test.bv
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| < | ||||
| magic s 4 | ||||
| size I | ||||
| data n $size | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue