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/build/* | ||||||
| ScrapHacks/src/D3D8_VMT.hpp | ScrapHacks/src/D3D8_VMT.hpp | ||||||
| .vscode/c_cpp_properties.json | .vscode/c_cpp_properties.json | ||||||
|  | tools/*.log | ||||||
|  | frida/*.mp | ||||||
							
								
								
									
										104
									
								
								NOTES.md
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								NOTES.md
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| # Infos | # Infos | ||||||
| 
 | 
 | ||||||
| - Engine: ScrapEngine | - Engine: ScrapEngine/Mercury Engine | ||||||
| - Ingame Scripting Language: Python 1.5.2 | - Ingame Scripting Language: Python 1.5.2 | ||||||
| - Interesting memory locations and functions are noted in `config.yml` | - Interesting memory locations and functions are noted in `config.yml` | ||||||
| 
 | 
 | ||||||
|  | @ -8,6 +8,8 @@ | ||||||
| 
 | 
 | ||||||
| - `-console`: open external console window on start | - `-console`: open external console window on start | ||||||
| - `-wideWindow`: start game in widescreen mode | - `-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: | # Functions identified: | ||||||
| 
 | 
 | ||||||
|  | @ -23,10 +25,10 @@ | ||||||
| 
 | 
 | ||||||
| ## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`): | ## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`): | ||||||
| 
 | 
 | ||||||
| * `listar luces` | * `listar luces` List lights in scene | ||||||
| * `listar` | * `listar` list models in scene | ||||||
| * `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end)) | * `arbol <model_name>` show details for model  | ||||||
| * `mem` | * `mem` (doesn't do anything?) | ||||||
| * `ver uniones`  | * `ver uniones`  | ||||||
| * Easter Eggs: | * Easter Eggs: | ||||||
|   * `imbecil` |   * `imbecil` | ||||||
|  | @ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s) | ||||||
| 
 | 
 | ||||||
| ## Other Functions: | ## Other Functions: | ||||||
| 
 | 
 | ||||||
| Check `r2_analyze.py` for full list | Check `config.yml` for full list | ||||||
| 
 | 
 | ||||||
| ## File Index struct @ `0x7fcbec` | ## File Index struct @ `0x7fcbec` | ||||||
| 
 | 
 | ||||||
| ```cpp | ```cpp | ||||||
| 
 |  | ||||||
| struct FileEntry { | struct FileEntry { | ||||||
|   uint32_t offset; |   uint32_t offset; | ||||||
|   uint32_t size; |   uint32_t size; | ||||||
|  | @ -87,7 +88,7 @@ struct FileIDX { | ||||||
| }; | }; | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Packed Index struct (array @ `0x7fc1b0`) | ## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`) | ||||||
| ```cpp | ```cpp | ||||||
| struct PackedIDX { | struct PackedIDX { | ||||||
|   void** VMT; |   void** VMT; | ||||||
|  | @ -111,7 +112,10 @@ struct CPP_Callback { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Game engine Variables Pointer @ `0x7FBE4C` | 
 | ||||||
|  | ## Game engine Variables Hashtable @ `0x7fbe50` | ||||||
|  | 
 | ||||||
|  | ## Game engine Variables @ `0x7fbe4c` | ||||||
| 
 | 
 | ||||||
| Structure: | Structure: | ||||||
| 
 | 
 | ||||||
|  | @ -131,19 +135,20 @@ struct GameVar { | ||||||
| Types | Types | ||||||
| 
 | 
 | ||||||
| | Value | Type            | | | Value | Type            | | ||||||
| | ------ | ----------------------- | | |-------|-----------------| | ||||||
| | `0x10` | const char*             | | | `0x1` | const char*     | | ||||||
| | `0x20` | int32_t                 | | | `0x2` | int32_t         | | ||||||
| | `0x30` | User Control Definition | | | `0x3` | List of Defines | | ||||||
| | `0x40` | float                   | | | `0x4` | float           | | ||||||
| | `0x60` | Callback function       | | | `0x5` | function        | | ||||||
|  | | `0x6` | Script function | | ||||||
| 
 | 
 | ||||||
| ## Game World/State Pointer @ `0x7fe944` | ## Game World/State Pointer @ `0x7fe944` | ||||||
| 
 | 
 | ||||||
| Points to World struct | Points to World struct | ||||||
| 
 | 
 | ||||||
| | Offset | Type                     | Description                            | | | Offset | Type                     | Description                            | | ||||||
| | ------ | ------------------------ | -------------------------------------- | | |--------|--------------------------|----------------------------------------| | ||||||
| | 0x0000 | `void**`                 | Virtual Method Table                   | | | 0x0000 | `void**`                 | Virtual Method Table                   | | ||||||
| | 0x0004 | `uint32_t`               | Size of Entity Hashtable               | | | 0x0004 | `uint32_t`               | Size of Entity Hashtable               | | ||||||
| | 0x0008 | `void**`                 | Pointer to Entity Hashtable            | | | 0x0008 | `void**`                 | Pointer to Entity Hashtable            | | ||||||
|  | @ -172,6 +177,15 @@ Points to World struct | ||||||
| | 0x2238 | `???`                    | Used in `World_Init`                   | | | 0x2238 | `???`                    | Used in `World_Init`                   | | ||||||
| | 0x2254 | `float`                  | 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 | ## Entity Hash Table | ||||||
| 
 | 
 | ||||||
| Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation) | 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: | Data format: | ||||||
| 
 | 
 | ||||||
| | Offset | Type          | Description              | | | Offset | Type          | Description              | | ||||||
| | ------ | ------------- | ------------------------ | | |--------|---------------|--------------------------| | ||||||
| | 0x0    | `void**`      | Virtual Method Table (?) | | | 0x0    | `void**`      | Virtual Method Table (?) | | ||||||
| | 0x4    | `const char*` | name as string           | | | 0x4    | `const char*` | name as string           | | ||||||
| | 0x14   | `void*`       | pointer to self (why?)   | | | 0x14   | `void*`       | pointer to self (why?)   | | ||||||
|  | @ -204,21 +218,57 @@ Attributes: | ||||||
| - `OnDeath` | - `OnDeath` | ||||||
| - `OnDamage` | - `OnDamage` | ||||||
| 
 | 
 | ||||||
| # File Formats | # Netplay protocol (WIP) | ||||||
| 
 | 
 | ||||||
| ## .packed File Format: | Game Info Packet | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| Header: | Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086 | ||||||
|     "BFPK\0\0\0\0" | [0-3] header/ID? | ||||||
|     Int32ul: number of files | [4-5] port (16-bit) | ||||||
|     for each file: | [6-7] max_players (16-bit) | ||||||
|         Int32ul: path length | [8-9] curr_player (16-bit) | ||||||
|         String: path | [10-x] server name (char*) | ||||||
|         Int32ul: size | 
 | ||||||
|         Int32ul: offset in file |            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: | # Virtual Method Tables: | ||||||
| 
 | 
 | ||||||
| check `r2_analyze.py` for full list | check `r2_analyze.py` for full list | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,13 +1,24 @@ | ||||||
| # Scrapland Reverse Engineering notes and tools | # 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 | * `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 |  - 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 | * `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 | * `lib/dbg.py`: general Script for poking around inside the game's scripting system | ||||||
|  - Run `import dbg` inside the Game's Console, |  - Run `import dbg;dbg.init()` inside the Game's Console, | ||||||
|   this will load all builtin modules and enable godmode |   this will load all builtin modules, ScrapHacks and enable godmode | ||||||
|  - The dbg module also enables writing to the ingame console using `print <var>` |  - 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 |   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) |  - `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly) | ||||||
|  | @ -24,9 +35,15 @@ WIP Memory hacking library | ||||||
| 
 | 
 | ||||||
| # Tools used: | # Tools used: | ||||||
| 
 | 
 | ||||||
| - [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) | - Binary parsing: | ||||||
| - [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/) |   - [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis | ||||||
| - [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) |   - [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing | ||||||
| - [HxD](https://mh-nexus.de/en/hxd/) |   - [Kaitai Struct](http://kaitai.io/) for binary parsing | ||||||
| - [Kaitai Struct](http://kaitai.io/) | - Static analysis: | ||||||
| - [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/) |   - [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", |             "cStandard": "c11", | ||||||
|             "cppStandard": "c++17", |             "cppStandard": "c++17", | ||||||
|             "intelliSenseMode": "msvc-x86", |             "intelliSenseMode": "msvc-x86", | ||||||
|             "configurationProvider": "vector-of-bool.cmake-tools", |             "configurationProvider": "vector-of-bool.cmake-tools" | ||||||
|             "compileCommands": "${workspaceFolder}/build/compile_commands.json" |  | ||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "version": 4 |     "version": 4 | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								ScrapHacks/.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								ScrapHacks/.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -78,5 +78,11 @@ | ||||||
|         "xtr1common": "cpp", |         "xtr1common": "cpp", | ||||||
|         "xtree": "cpp", |         "xtree": "cpp", | ||||||
|         "xutility": "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_minimum_required(VERSION 3.1) | ||||||
| cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) | cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) | ||||||
| 
 |  | ||||||
| project(ScrapHacks | project(ScrapHacks | ||||||
|         VERSION 1.0 |         VERSION 1.0 | ||||||
|         DESCRIPTION "Scrapland memory hacking library" |         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!") |   message(FATAL_ERROR "Scrapland installation folder not found!") | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
|  | message(STATUS "Scrapland found at ${SCRAPLAND_DIR}") | ||||||
|  | 
 | ||||||
| message(STATUS "Checking Scrap.exe hash") | message(STATUS "Checking Scrap.exe hash") | ||||||
| file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" 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!") |   message(FATAL_ERROR "Scrap.exe hash miss match!") | ||||||
| endif() | 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(FETCHCONTENT_QUIET 0) | ||||||
| set(CMAKE_BUILD_TYPE "RelMinSize") | set(CMAKE_BUILD_TYPE "RelMinSize") | ||||||
| set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}") | set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}") | ||||||
| set(ASMJIT_EMBED true) | set(ASMJIT_EMBED ON CACHE INTERNAL "") | ||||||
| set(ASMTK_EMBED true) | set(ASMTK_EMBED ON CACHE INTERNAL "") | ||||||
| set(ZYDIS_BUILD_TOOLS false) | set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "") | ||||||
| set(ZYDIS_BUILD_EXAMPLES false) | set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "") | ||||||
| 
 | set(toml11_BUILD_TEST OFF CACHE INTERNAL "")   | ||||||
| 
 | 
 | ||||||
| if(WIN32) | if(WIN32) | ||||||
|   if(MSVC) |   if(MSVC) | ||||||
|  | @ -44,54 +57,67 @@ endif(WIN32) | ||||||
| 
 | 
 | ||||||
| include(FetchContent) | 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( | FetchContent_Declare( | ||||||
|   DirectX |   DirectX | ||||||
|   PREFIX ${CMAKE_CURRENT_BINARY_DIR} |   PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||||
|   URL |   URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip | ||||||
|     https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip |   URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312 | ||||||
|   URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312) | ) | ||||||
| 
 |  | ||||||
| FetchContent_MakeAvailable(DirectX) |  | ||||||
| 
 | 
 | ||||||
| FetchContent_Declare( | FetchContent_Declare( | ||||||
|     ASM_JIT |     ASM_JIT | ||||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} |     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||||
|     GIT_REPOSITORY git@github.com:asmjit/asmjit.git |     GIT_REPOSITORY git@github.com:asmjit/asmjit.git | ||||||
|     GIT_SHALLOW true |     GIT_SHALLOW true | ||||||
|     GIT_PROGRESS true |     INSTALL_COMMAND "" | ||||||
|  |     CONFIGURE_COMMAND "" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| FetchContent_MakeAvailable(ASM_JIT) |  | ||||||
| 
 |  | ||||||
| FetchContent_Declare( | FetchContent_Declare( | ||||||
|     ASM_TK |     ASM_TK | ||||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} |     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||||
|     GIT_REPOSITORY git@github.com:asmjit/asmtk.git |     GIT_REPOSITORY git@github.com:asmjit/asmtk.git | ||||||
|     GIT_SHALLOW true |     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( | FetchContent_Declare( | ||||||
|     Zydis |     Zydis | ||||||
|     PREFIX ${CMAKE_CURRENT_BINARY_DIR} |     PREFIX ${CMAKE_CURRENT_BINARY_DIR} | ||||||
|     GIT_REPOSITORY git@github.com:zyantific/zydis.git |     GIT_REPOSITORY git@github.com:zyantific/zydis.git | ||||||
|     GIT_SHALLOW true |     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/) | 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( | add_custom_command( | ||||||
|   OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp |   OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp | ||||||
|  | @ -111,7 +137,10 @@ add_library(ScrapHack SHARED | ||||||
|   ${ASMJIT_SRC} |   ${ASMJIT_SRC} | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd") | set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd") | ||||||
|  | set_target_properties(ScrapHack PROPERTIES PREFIX "_") | ||||||
|  | 
 | ||||||
| add_dependencies(ScrapHack D3D8_VMT) | add_dependencies(ScrapHack D3D8_VMT) | ||||||
| # add_dependencies(ScrapHack Python152) | # add_dependencies(ScrapHack Python152) | ||||||
| # add_dependencies(ScrapHack Python152_Bin) | # add_dependencies(ScrapHack Python152_Bin) | ||||||
|  | @ -119,10 +148,13 @@ target_link_libraries(ScrapHack | ||||||
|                       d3d8 |                       d3d8 | ||||||
|                       d3dx8 |                       d3dx8 | ||||||
|                       dxerr8 |                       dxerr8 | ||||||
|                       gdiplus |                       # gdiplus | ||||||
|                       # PYTHON15 |                       # PYTHON15 | ||||||
|                       # Zydis |                       Zydis | ||||||
|                       legacy_stdio_definitions) |                       legacy_stdio_definitions) | ||||||
| 
 | 
 | ||||||
| install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib) | install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks) | ||||||
| target_compile_features(ScrapHack PUBLIC cxx_std_11) | 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 | ## Features | ||||||
| 
 | 
 | ||||||
| - read and write memory | - read and write memory | ||||||
|  | - disassemble memory (using zydis) | ||||||
| - change DirectX state | - change DirectX state | ||||||
| - Draw DirectX overlay (still need to make a useful overlay) | - Draw DirectX overlay (still need to make a useful overlay) | ||||||
| - Dump various data structures to the console | - 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) | - Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands) | ||||||
| 
 | 
 | ||||||
| ## Prerequisites | ## Prerequisites | ||||||
|  | @ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have | ||||||
| - type `import ScrapHack` | - type `import ScrapHack` | ||||||
| - type `$help` | - 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 |     call "%%i" x86 | ||||||
|   ) |   ) | ||||||
| ) | ) | ||||||
|  | if "%VSINSTALLDIR%"=="" ( | ||||||
|  |   echo "VSINSTALLDIR" not set something is wrong! | ||||||
|  | ) else ( | ||||||
|   if not exist build cmake -G"NMake Makefiles" -B build |   if not exist build cmake -G"NMake Makefiles" -B build | ||||||
|  |   if "%1"=="--run" ( | ||||||
|  |     cmake --build build --target run | ||||||
|  |   ) else ( | ||||||
|     cmake --build build --target install |     cmake --build build --target install | ||||||
|  |   ) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| endlocal | endlocal | ||||||
|  | @ -147,6 +147,13 @@ void unhook_d3d8() { | ||||||
|     hooked=false; |     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() { | void hook_d3d8() { | ||||||
|     if (hooked) { |     if (hooked) { | ||||||
|         return; |         return; | ||||||
|  | @ -162,11 +169,9 @@ void hook_d3d8() { | ||||||
|     hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0, |     hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0, | ||||||
|                         0, 0, "Lucida Console"); |                         0, 0, "Lucida Console"); | ||||||
|     hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0)); |     hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0)); | ||||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_EndScene], H_EndScene); |     for (auto h: *dx_hooks) { | ||||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_BeginScene], H_BeginScene); |         Hook::addr(GetVTable(dev)[h.first], h.second); | ||||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_DrawIndexedPrimitive], |     } | ||||||
|                H_DrawIndexedPrimitive); |  | ||||||
|     Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_SetLight], H_SetLight); |  | ||||||
|     hooked=true; |     hooked=true; | ||||||
|     return; |     return; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,9 +9,10 @@ | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
| 
 | 
 | ||||||
| /*
 | 
 | ||||||
| vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) { | vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) { | ||||||
|     using namespace asmjit; |     using namespace asmjit; | ||||||
|  |     vector<uint8_t> ret; | ||||||
|     JitRuntime rt; |     JitRuntime rt; | ||||||
|     CodeHolder code; |     CodeHolder code; | ||||||
|     CodeInfo ci=rt.codeInfo(); |     CodeInfo ci=rt.codeInfo(); | ||||||
|  | @ -23,9 +24,15 @@ vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) { | ||||||
|     code.resolveUnresolvedLinks(); |     code.resolveUnresolvedLinks(); | ||||||
|     code.relocateToBase(orig); |     code.relocateToBase(orig); | ||||||
|     size_t code_size=code.sectionById(0)->buffer().size(); |     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 { | class Hook { | ||||||
|   private: |   private: | ||||||
|  | @ -33,29 +40,32 @@ class Hook { | ||||||
|     void *orig; |     void *orig; | ||||||
|     void *detour; |     void *detour; | ||||||
|     bool enabled; |     bool enabled; | ||||||
|     uint8_t orig_bytes[6]; |     uint8_t *orig_bytes; | ||||||
|     uint8_t jmp_bytes[6]; |     uint8_t *jmp_bytes; | ||||||
|  |     size_t size; | ||||||
|     static map<uintptr_t, shared_ptr<Hook>> hooks; |     static map<uintptr_t, shared_ptr<Hook>> hooks; | ||||||
| 
 | 
 | ||||||
|   public: |   public: | ||||||
|     Hook(void *func, void *detour) { |     Hook(void *func, void *detour) { | ||||||
|         // TODO: build jmp_bytes using asmjit
 |  | ||||||
|         uintptr_t dest = reinterpret_cast<uintptr_t>(detour); |         uintptr_t dest = reinterpret_cast<uintptr_t>(detour); | ||||||
|         uintptr_t src = reinterpret_cast<uintptr_t>(func); |         uintptr_t src = reinterpret_cast<uintptr_t>(func); | ||||||
|         this->orig = func; |         this->orig = func; | ||||||
|         this->detour = detour; |         this->detour = detour; | ||||||
|         this->jmp_bytes[0] = 0x68; // push
 |         vector<uint8_t> code = make_trampoline(src,dest); | ||||||
|         this->jmp_bytes[1] = (dest >> 0) & 0xff; |         this->orig_bytes = new uint8_t[code.size()]; | ||||||
|         this->jmp_bytes[2] = (dest >> 8) & 0xff; |         this->jmp_bytes = new uint8_t[code.size()]; | ||||||
|         this->jmp_bytes[3] = (dest >> 16) & 0xff; |         this->size = code.size(); | ||||||
|         this->jmp_bytes[4] = (dest >> 24) & 0xff; |         this->enabled = false; | ||||||
|         this->jmp_bytes[5] = 0xC3; // ret
 |         uint8_t* func_b = reinterpret_cast<uint8_t*>(this->orig); | ||||||
|         VirtualQuery(func, &mbi, sizeof(mbi)); |         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, |         VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, | ||||||
|                        &mbi.Protect); |                        &mbi.Protect); | ||||||
|         memcpy(this->orig_bytes, this->orig, 1 + 4 + 1); |  | ||||||
|         VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); |         VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||||
|         this->enabled = false; |         cout<<"Constructed hook from "<<func<<" to "<<detour<<", size: " << this->size<<endl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ~Hook() { |     ~Hook() { | ||||||
|  | @ -64,6 +74,11 @@ class Hook { | ||||||
|         this->disable(); |         this->disable(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     static void addr(uintptr_t _addr, void *detour) { | ||||||
|  |         Hook::addr(reinterpret_cast<void*>(_addr),detour); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     static void addr(void *addr, void *detour) { |     static void addr(void *addr, void *detour) { | ||||||
|         cout << "Hooking: [" << addr << " -> " << detour << "]" << endl; |         cout << "Hooking: [" << addr << " -> " << detour << "]" << endl; | ||||||
|         uintptr_t key = reinterpret_cast<uintptr_t>(detour); |         uintptr_t key = reinterpret_cast<uintptr_t>(detour); | ||||||
|  | @ -105,23 +120,23 @@ class Hook { | ||||||
| 
 | 
 | ||||||
|     void disable() { |     void disable() { | ||||||
|         if (this->enabled) { |         if (this->enabled) { | ||||||
|             // cout << "Disabling: [" << this->orig << " <- " << this->detour <<
 |             cout << "Disabling: [" << this->orig << " <- " << this->detour << | ||||||
|             // "]"
 |             "]" | ||||||
|             // << endl;
 |             << endl; | ||||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, |             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, | ||||||
|                            PAGE_EXECUTE_READWRITE, NULL); |                            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); |             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||||
|             this->enabled = false; |             this->enabled = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     void enable() { |     void enable() { | ||||||
|         if (!this->enabled) { |         if (!this->enabled) { | ||||||
|             // cout << "Enabling: [" << this->orig << " -> " << this->detour <<
 |             cout << "Enabling: [" << this->orig << " -> " << this->detour << | ||||||
|             // "]" << endl;
 |             "]" << endl; | ||||||
|             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, |             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, | ||||||
|                            PAGE_EXECUTE_READWRITE, NULL); |                            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); |             VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||||
|             this->enabled = true; |             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 <iostream> | ||||||
| #include <map> | #include <map> | ||||||
| #include <string> | #include <string> | ||||||
| 
 | // #include <Python.h>
 | ||||||
| #include "Structures.hpp" | #include "Structures.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #include <string> | #include <string> | ||||||
| 
 | 
 | ||||||
| #include <asmtk/asmtk.h> | #include <asmtk/asmtk.h> | ||||||
|  | #include <Zydis/Zydis.h> | ||||||
| #include "Scrapland.hpp" | #include "Scrapland.hpp" | ||||||
| #include "Util.hpp" | #include "Util.hpp" | ||||||
| 
 | 
 | ||||||
|  | @ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) { | ||||||
|     for (string line:assembly) { |     for (string line:assembly) { | ||||||
|         if (err = p.parse((line+"\n").c_str())) { |         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)); |             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); |             scrap_log(ERR_COLOR,err_msg); | ||||||
|             return 0; |             return 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (err=code.flatten()) { |     if (err=code.flatten()) { | ||||||
|         snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); |         snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); | ||||||
|  |         cerr<<err_msg<<endl; | ||||||
|         scrap_log(ERR_COLOR,err_msg); |         scrap_log(ERR_COLOR,err_msg); | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|     if (err=code.resolveUnresolvedLinks()) { |     if (err=code.resolveUnresolvedLinks()) { | ||||||
|         snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); |         snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); | ||||||
|  |         cerr<<err_msg<<endl; | ||||||
|         scrap_log(ERR_COLOR,err_msg); |         scrap_log(ERR_COLOR,err_msg); | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  | @ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) { | ||||||
|     MEMORY_BASIC_INFORMATION mbi; |     MEMORY_BASIC_INFORMATION mbi; | ||||||
|      |      | ||||||
|     if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) { |     if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) { | ||||||
|  |         cerr<<"ERROR: "<< GetLastErrorAsString() <<endl; | ||||||
|         scrap_log(ERR_COLOR, "ERROR: "); |         scrap_log(ERR_COLOR, "ERROR: "); | ||||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); |         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||||
|         scrap_log(ERR_COLOR, "\n"); |         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)) |     if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) | ||||||
|     { |     { | ||||||
|  |         cerr<<"ERROR: "<< GetLastErrorAsString() <<endl; | ||||||
|         scrap_log(ERR_COLOR, "ERROR: "); |         scrap_log(ERR_COLOR, "ERROR: "); | ||||||
|         scrap_log(ERR_COLOR, GetLastErrorAsString()); |         scrap_log(ERR_COLOR, GetLastErrorAsString()); | ||||||
|         scrap_log(ERR_COLOR, "\n"); |         scrap_log(ERR_COLOR, "\n"); | ||||||
|         return 0; |         return 0; | ||||||
|     }; |     }; | ||||||
|  |     cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl; | ||||||
|     memcpy((void*)base,buffer.data(),buffer.size()); |     memcpy((void*)base,buffer.data(),buffer.size()); | ||||||
|     scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size())); |     scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size())); | ||||||
|     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); |     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||||
|  | @ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) { | ||||||
|     return assemble(assembly,0); |     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 { | struct Command { | ||||||
|     t_cmd_func func; |     t_cmd_func func; | ||||||
|  | @ -217,6 +255,46 @@ get_protection(void *addr) { | ||||||
|     return mbi.Protect; |     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 cmd_exec(Command* cmd, vector<string> args) { | ||||||
|     void *addr; |     void *addr; | ||||||
|     MEMORY_BASIC_INFORMATION mbi; |     MEMORY_BASIC_INFORMATION mbi; | ||||||
|  | @ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) { | ||||||
|         return; |         return; | ||||||
|     }; |     }; | ||||||
|     string hxd = hexdump_s(mptr, size); |     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); |     VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); | ||||||
|     if (buffer) { |     if (buffer) { | ||||||
|         free(buffer); |         free(buffer); | ||||||
|  | @ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cmd_dump_stack(Command* cmd, vector<string> args) { | void cmd_dump_stack(Command* cmd, vector<string> args) { | ||||||
|  |     stringstream ret; | ||||||
|     void** stack=(void**)_AddressOfReturnAddress(); |     void** stack=(void**)_AddressOfReturnAddress(); | ||||||
|     cout<<"ESP:    "<<stack<<endl; |     cout<<"ESP:    "<<stack<<endl; | ||||||
|     for (size_t n=0;n<0x100;++n) { |     for (size_t n=0;n<0x100;++n) { | ||||||
|         if (!addr_exists(stack[n])) { |         if (!addr_exists(stack[n])) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         char R=can_read(stack[n]) ? 'R' : ' '; |         bool r,w,x; | ||||||
|         char W=can_write(stack[n]) ? 'W' : ' '; |         char R=(r=can_read(stack[n])) ? 'R' : ' '; | ||||||
|         char X=can_execute(stack[n]) ? 'X' : ' '; |         char W=(w=can_write(stack[n])) ? 'W' : ' '; | ||||||
|         cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X; |         char X=(x=can_execute(stack[n])) ? 'X' : ' '; | ||||||
|         if (can_read(stack[n])) { |         ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X; | ||||||
|             cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl; |         if (r && !x) { | ||||||
|         } else { |             ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"; | ||||||
|             cout<<endl; |         } else if (r && x) { | ||||||
|  |             ret<<" [ "<<disassemble(stack[n],5,true)<<"]"; | ||||||
|         } |         } | ||||||
|  |         ret<<endl; | ||||||
|     } |     } | ||||||
|  |     scrap_log(INFO_COLOR,ret.str()); | ||||||
|     return; |     return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) { | ||||||
|                     << meth.second->ml_meth << endl; |                     << 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) { | 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; |         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; |         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) { | 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); |     dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out); | ||||||
|     out << "Entity Lists:" << endl; |     out << "Entity Lists:" << endl; | ||||||
|     dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out); |     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; |     return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) { | ||||||
| 
 | 
 | ||||||
| void cmd_print_alarm(Command* cmd,vector<string> args) { | void cmd_print_alarm(Command* cmd,vector<string> args) { | ||||||
|     stringstream out; |     stringstream out; | ||||||
|     float *alarm = ptr<float>(P_WORLD, O_ALARM); |     float alarm = ptr<float>(P_WORLD, O_ALARM)[0]; | ||||||
|     float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW); |     float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0]; | ||||||
|     out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl; |     if (alarm_grow<0) { | ||||||
|     scrap_log(INFO_COLOR,out.str().c_str()); |         out << "Alarm: " << alarm << " - " << alarm_grow << endl; | ||||||
|  |     } else { | ||||||
|  |         out << "Alarm: " << alarm << " + " << alarm_grow << endl; | ||||||
|  |     } | ||||||
|  |     scrap_log(INFO_COLOR,out.str()); | ||||||
|     return; |     return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) { | ||||||
|     assemble(split(code,';'),buffer_addr); |     assemble(split(code,';'),buffer_addr); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| void cmd_help(Command* cmd,vector<string> args); | void cmd_help(Command* cmd,vector<string> args); | ||||||
| 
 | 
 | ||||||
| static REPL* repl=new REPL( | 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")}, |         {"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")}, |         {"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")}, |         {"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")}, |     {"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")}, | ||||||
|     {"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{ |     {"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{ | ||||||
|  |  | ||||||
|  | @ -6,7 +6,8 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <typeinfo> | #include <typeinfo> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | #include <fstream> | ||||||
|  | // #include <Python.h>
 | ||||||
| #include <Windows.h> | #include <Windows.h> | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
|  | @ -18,6 +19,7 @@ using namespace std; | ||||||
| #include "Scrapland.hpp" | #include "Scrapland.hpp" | ||||||
| #include "Structures.hpp" | #include "Structures.hpp" | ||||||
| #include "Util.hpp" | #include "Util.hpp" | ||||||
|  | #include "Py_Mod.hpp" | ||||||
| 
 | 
 | ||||||
| bool initialized = false; | bool initialized = false; | ||||||
| bool running = true; | bool running = true; | ||||||
|  | @ -28,12 +30,14 @@ void DllUnload(); | ||||||
| int hooked_console(const char *); | int hooked_console(const char *); | ||||||
| void hook_exit(); | void hook_exit(); | ||||||
| 
 | 
 | ||||||
| void setup_hooks() { | void setup_hooks() | ||||||
|     Hook::addr(reinterpret_cast<void *>(P_SCRAP_EXIT), hook_exit); | { | ||||||
|     Hook::addr(reinterpret_cast<void *>(P_CON_HANDLER), hooked_console); |     Hook::addr(P_SCRAP_EXIT, hook_exit); | ||||||
|  |     Hook::addr(P_CON_HANDLER, hooked_console); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MainLoop() { | void MainLoop() | ||||||
|  | { | ||||||
|     setup_hooks(); |     setup_hooks(); | ||||||
|     overlay = true; |     overlay = true; | ||||||
|     cout << "[*] Starting main Loop" << endl; |     cout << "[*] Starting main Loop" << endl; | ||||||
|  | @ -49,42 +53,56 @@ void MainLoop() { | ||||||
|     cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)" |     cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)" | ||||||
|          << endl; |          << endl; | ||||||
| 
 | 
 | ||||||
|     while (running) { |     while (running) | ||||||
|  |     { | ||||||
|         Sleep(100); |         Sleep(100); | ||||||
|         while (key_down('F')) { |         while (key_down('F')) | ||||||
|  |         { | ||||||
|             scrap_exec("dbg.brake()"); |             scrap_exec("dbg.brake()"); | ||||||
|         } |         } | ||||||
|         if (key_down_norepeat(VK_F3)) { |         if (key_down_norepeat(VK_F3)) | ||||||
|  |         { | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (key_down_norepeat(VK_F7)) { |         if (key_down_norepeat(VK_F7)) | ||||||
|  |         { | ||||||
|             int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY); |             int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY); | ||||||
|             money[0] = 0x7fffffff; |             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; |             cout << "Entities:" << endl; | ||||||
|             dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS)); |             dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS)); | ||||||
|             cout << "Entity Lists:" << endl; |             cout << "Entity Lists:" << endl; | ||||||
|             dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS)); |             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()"); |             scrap_exec("dbg.settrace()"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     FreeLibraryAndExitThread(hMod, 0); |     FreeLibraryAndExitThread(hMod, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void InitConsole() { | void InitConsole() | ||||||
|  | { | ||||||
|     char me[1024]; |     char me[1024]; | ||||||
|     GetModuleFileName(hMod, me, 1024); |     GetModuleFileNameA(hMod, me, 1024); | ||||||
|     SetupConsole(me); |     SetupConsole(me); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int hooked_console(const char *cmd) { | int hooked_console(const char *cmd) | ||||||
|  | { | ||||||
|     typedef decltype(&hooked_console) t_func; |     typedef decltype(&hooked_console) t_func; | ||||||
|     if (cmd[0] == '$') { |     if (cmd[0] == '$') | ||||||
|  |     { | ||||||
|         handle_command(++cmd); |         handle_command(++cmd); | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  | @ -93,7 +111,8 @@ int hooked_console(const char *cmd) { | ||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void hook_exit() { | void hook_exit() | ||||||
|  | { | ||||||
|     typedef decltype(&hook_exit) t_func; |     typedef decltype(&hook_exit) t_func; | ||||||
|     shared_ptr<Hook> hook = Hook::get(hook_exit); |     shared_ptr<Hook> hook = Hook::get(hook_exit); | ||||||
|     DllUnload(); |     DllUnload(); | ||||||
|  | @ -102,7 +121,8 @@ void hook_exit() { | ||||||
|     return; |     return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DllInit(HMODULE mod) { | void DllInit(HMODULE mod) | ||||||
|  | { | ||||||
|     hMod = mod; |     hMod = mod; | ||||||
|     char mfn[1024]; |     char mfn[1024]; | ||||||
|     GetModuleFileNameA(0, mfn, 1024); |     GetModuleFileNameA(0, mfn, 1024); | ||||||
|  | @ -119,36 +139,23 @@ void DllInit(HMODULE mod) { | ||||||
|     CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0); |     CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0); | ||||||
|     cout << "[*] Starting message pump" << endl; |     cout << "[*] Starting message pump" << endl; | ||||||
|     MSG msg; |     MSG msg; | ||||||
|     while (GetMessage(&msg, NULL, 0, 0)) { |     while (GetMessage(&msg, NULL, 0, 0)) | ||||||
|  |     { | ||||||
|         TranslateMessage(&msg); |         TranslateMessage(&msg); | ||||||
|         DispatchMessage(&msg); |         DispatchMessage(&msg); | ||||||
|     } |     } | ||||||
|     return; |     return; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void *H_port_FixupExtension(char *name, char *filename) { | void DllPreInit() | ||||||
|     cout<<"FixupExtension: "<<name<<": "<<filename<<endl; | { | ||||||
|     Hook::drop(H_port_FixupExtension); |     Sleep(100); | ||||||
|     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() { |  | ||||||
|     InitConsole(); |     InitConsole(); | ||||||
|     Hook::addr(reinterpret_cast<void *>(0x5a9ca0), H_port_FixupExtension); |     InitPyMod(); | ||||||
|     Hook::addr(reinterpret_cast<void *>(0x5cdb00), |  | ||||||
|                H_PyEval_CallObjectWithKeywords); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void DllUnload() { | void DllUnload() | ||||||
|  | { | ||||||
|     SetConsoleCtrlHandler(NULL, false); |     SetConsoleCtrlHandler(NULL, false); | ||||||
|     unhook_d3d8(); |     unhook_d3d8(); | ||||||
|     Hook::clear(); |     Hook::clear(); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| #pragma once | #pragma once | ||||||
| #include "Structures.hpp" | #include "Structures.hpp" | ||||||
| 
 | #include "Util.hpp" | ||||||
| #include <sstream> | #include <sstream> | ||||||
| using namespace std; | using namespace std; | ||||||
| 
 | 
 | ||||||
|  | @ -17,14 +17,21 @@ using namespace std; | ||||||
| #define P_PY_MODS 0x79C698 | #define P_PY_MODS 0x79C698 | ||||||
| 
 | 
 | ||||||
| // FUNCTION ADDRESSES
 | // FUNCTION ADDRESSES
 | ||||||
|  | // ENGINE INTERNALS
 | ||||||
| #define P_CON_HANDLER 0x402190 | #define P_CON_HANDLER 0x402190 | ||||||
| #define P_SCRAP_LOG 0x4134C0 | #define P_SCRAP_LOG 0x4134C0 | ||||||
| #define P_SCRAP_EXEC 0x5a8390 | #define P_SCRAP_EXEC 0x5a8390 | ||||||
| #define P_SCRAP_EXIT 0x4010c0 | #define P_SCRAP_EXIT 0x4010c0 | ||||||
| #define P_D3DCHECK 0x602a70 | #define P_D3DCHECK 0x602a70 | ||||||
| #define P_D3DDEV 0x852914 | #define P_D3DDEV 0x852914 | ||||||
|  | // PYTHON FUNCTIONS
 | ||||||
| #define P_Py_InitModule 0x5A8FB0 | #define P_Py_InitModule 0x5A8FB0 | ||||||
| #define P_PyArg_ParseTuple 0x5bb9d0 | #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) | #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_log)(unsigned int color, const char *message); | ||||||
| typedef int(_cdecl *t_scrap_exec)(const char *code); | typedef int(_cdecl *t_scrap_exec)(const char *code); | ||||||
| typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...); | 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, | typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods, | ||||||
|                                      const char *doc, void *passthrough, |                                      const char *doc, void *passthrough, | ||||||
|                                      int module_api_version); |                                      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
 | // GLOBAL FUNCTIONS
 | ||||||
| auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC; | auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC; | ||||||
| auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple; | auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple; | ||||||
| auto py_initmodule = (t_Py_InitModule)P_Py_InitModule; | 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) { | int scrap_log(unsigned int color,const char* msg) { | ||||||
|     return ((t_scrap_log)P_SCRAP_LOG)(color,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); |     return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| int scrap_log(unsigned int color,string msg) { | int scrap_log(unsigned int color,string msg) { | ||||||
|     return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str()); |     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()); |     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 size_ht(HashTable<EntityList> *ht) { | ||||||
|     size_t cnt = 0; |     size_t cnt = 0; | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ string GetLastErrorAsString() { | ||||||
|     size_t m_size = FormatMessageA( |     size_t m_size = FormatMessageA( | ||||||
|         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | |         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | | ||||||
|             FORMAT_MESSAGE_IGNORE_INSERTS, |             FORMAT_MESSAGE_IGNORE_INSERTS, | ||||||
|         NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |         NULL, errorMessageID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), | ||||||
|         (LPSTR)&messageBuffer, 0, NULL); |         (LPSTR)&messageBuffer, 0, NULL); | ||||||
|     string message(messageBuffer, m_size); |     string message(messageBuffer, m_size); | ||||||
|     LocalFree(messageBuffer); |     LocalFree(messageBuffer); | ||||||
|  |  | ||||||
|  | @ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE; | ||||||
| bool loaded = false; | bool loaded = false; | ||||||
| HMODULE mod = nullptr; | HMODULE mod = nullptr; | ||||||
| 
 | 
 | ||||||
| DLL_EXPORT void initScrapHack() { | DLL_EXPORT void init_ScrapHack() { | ||||||
|     DllPreInit(); |     DllPreInit(); | ||||||
|     if (!loaded) { |     if (!loaded) { | ||||||
|  |         Sleep(1000); | ||||||
|         hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod, |         hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod, | ||||||
|                                0, 0); |                                0, 0); | ||||||
|         CloseHandle(hThread); |         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 SScorer | ||||||
| import Menu | import Menu | ||||||
| import sys | import sys | ||||||
|  | 
 | ||||||
| QC = quickconsole | QC = quickconsole | ||||||
| MF = MissionsFuncs | MF = MissionsFuncs | ||||||
| last_frame = None | last_frame = None | ||||||
| 
 | level = 3 | ||||||
|  | initialized = 0 | ||||||
| sys.path.append(".\\pylib\\Lib") | sys.path.append(".\\pylib\\Lib") | ||||||
| sys.path.append(".\\pylib\\Libs") | sys.path.append(".\\pylib\\Libs") | ||||||
| sys.path.append(".\\pylib") | sys.path.append(".\\pylib") | ||||||
|  | @ -16,11 +18,18 @@ sys.path.append(".\\pylib") | ||||||
| 
 | 
 | ||||||
| def reload(): | def reload(): | ||||||
|     sys.settrace(None) |     sys.settrace(None) | ||||||
|     sys.modules['__builtin__'].reload(sys.modules['dbg']) |     sys.modules['__builtin__'].reload(sys.modules[__name__]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def dgb_info(): | 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.DeleteScheduledFuncs("dbg.dbg_info") |     Scrap.DeleteScheduledFuncs("dbg.dbg_info") | ||||||
|     Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info") |     Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info") | ||||||
|  | @ -197,12 +206,11 @@ def helplib(): | ||||||
|     logfile_name = None |     logfile_name = None | ||||||
|     print "Done!" |     print "Done!" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def enable_all_conv(): | def enable_all_conv(): | ||||||
|     try: |     try: | ||||||
|         import CharConversor |         import CharConversor | ||||||
|     except ImportError: |     except ImportError: | ||||||
|         print("CharConversor not available") |         # print("CharConversor not available") | ||||||
|         return |         return | ||||||
|     CharConversor.ConversionChars = list(CharConversor.ConversionChars) |     CharConversor.ConversionChars = list(CharConversor.ConversionChars) | ||||||
|     E = Scrap.GetFirst() |     E = Scrap.GetFirst() | ||||||
|  | @ -244,20 +252,6 @@ def nuke(): | ||||||
|         E = Scrap.GetEntity(E.NextInSlot) |         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): | def become(name): | ||||||
|     import CharConversor |     import CharConversor | ||||||
|     enable_all_conv() |     enable_all_conv() | ||||||
|  | @ -299,7 +293,7 @@ def getall(): | ||||||
|     me = Scrap.UsrEntity(0) |     me = Scrap.UsrEntity(0) | ||||||
|     while E: |     while E: | ||||||
|         try: |         try: | ||||||
|             E.Pos = me.Pos |             E.Descriptor = "HAXX!" | ||||||
|         except: |         except: | ||||||
|             pass |             pass | ||||||
|         E = Scrap.GetEntity(E.NextInSlot) |         E = Scrap.GetEntity(E.NextInSlot) | ||||||
|  | @ -308,6 +302,11 @@ def getall(): | ||||||
| def god(e=None): | def god(e=None): | ||||||
|     if e == None: |     if e == None: | ||||||
|         e = Scrap.UsrEntity(0) |         e = Scrap.UsrEntity(0) | ||||||
|  |         if e: | ||||||
|  |             try: | ||||||
|  |                 e.IsType("Car") | ||||||
|  |             except: | ||||||
|  |                 return | ||||||
|     if e: |     if e: | ||||||
|         if e.IsType("Car"): |         if e.IsType("Car"): | ||||||
|             e.Ammo00 = SWeap.GetFAmmo(0, "Max") |             e.Ammo00 = SWeap.GetFAmmo(0, "Max") | ||||||
|  | @ -324,9 +323,18 @@ def god(e=None): | ||||||
|         elif e.IsType("WalkChar"): |         elif e.IsType("WalkChar"): | ||||||
|             e.Energy = 1 |             e.Energy = 1 | ||||||
|         e.Invulnerable = 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.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(): | def ultranuke(): | ||||||
|  | @ -337,11 +345,92 @@ def ultranuke(): | ||||||
|                            (), "dbg.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(): | def brake(): | ||||||
|     if me: |     if me: | ||||||
|         me.Vel = (0, 0, 0) |         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): | for _ in range(1024): | ||||||
|     Scrap.DeleteScheduledFuncs("dbg.dbg_info") |     Scrap.DeleteScheduledFuncs("dbg.dbg_info") | ||||||
|     Scrap.DeleteScheduledFuncs("dbg.god") |     Scrap.DeleteScheduledFuncs("dbg.god") | ||||||
|  | @ -354,18 +443,31 @@ for module in sys.builtin_module_names: | ||||||
|     exec("import " + module) |     exec("import " + module) | ||||||
| 
 | 
 | ||||||
| sys.settrace(None) | sys.settrace(None) | ||||||
| 
 |  | ||||||
| me = Scrap.UsrEntity(0) |  | ||||||
| notrace() | notrace() | ||||||
| helplib() | helplib() | ||||||
| # settrace() | # 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() |     dgb_info() | ||||||
|     enable_all_conv() |     enable_all_conv() | ||||||
|     god() |     god() | ||||||
| Scrap.Set("debug", 3) |     Scrap.Set("debug", level) | ||||||
|     Scrap.Set("ShowConsoleLog", 1) |     Scrap.Set("ShowConsoleLog", 1) | ||||||
|     Scrap.Set("AlwaysFlushLog", 1) |     Scrap.Set("AlwaysFlushLog", 1) | ||||||
| Scrap.Set("PythonExecute", "import dbg") |     Scrap.Set("PythonExecute", "import dbg;dbg.init()") | ||||||
| exec("import QuickConsole;QuickConsole.debug=sys.modules['dbg']") |     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: | | notes: | | ||||||
|   0x7faa4c: temp storage? |   0x7faa4c: temp storage? | ||||||
|   0x7d2094: some reference count |   0x4039b0: fcn.handle_cli_opts? | ||||||
|  |   0x668007: ? | ||||||
| 
 | 
 | ||||||
| comments: | comments: | ||||||
|   0x6113f9: Check if Window exists |   0x6113f9: Check if Window exists | ||||||
| 
 | 
 | ||||||
| flags: | 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 |   0x7FE944: P_World | ||||||
|  |   0x792618: P_Eng3d_ver | ||||||
|  |   0x853a24: P_gWorld | ||||||
|   0x7FBE4C: P_Vars |   0x7FBE4C: P_Vars | ||||||
|   0x79C698: Py_Mods |   0x79C698: Py_Mods | ||||||
|   0x852914: P_D3D8_Dev |   0x852914: P_D3D8_Dev | ||||||
|  |   0x850258: P_D3D8_ZBuffer | ||||||
|  |   0x850408: P_D3D8_BackBuffer | ||||||
|   0x7FCC00: N_Paks_opened |   0x7FCC00: N_Paks_opened | ||||||
|   0x7fcbec: Hash_Index_Size |   0x7fcbec: Hash_Index_Size | ||||||
|   0x7fcbf0: P_Hash_Index |   0x7fcbf0: P_Hash_Index | ||||||
|  | @ -36,7 +47,27 @@ flags: | ||||||
|   0x7fadd8: is_python |   0x7fadd8: is_python | ||||||
|   0x7fc084: pak_lock |   0x7fc084: pak_lock | ||||||
|   0x7fbe7c: current_language |   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: | VMTs: | ||||||
|   0x78d4d8: Py_entity |   0x78d4d8: Py_entity | ||||||
|  | @ -56,6 +87,9 @@ VMTs: | ||||||
|   0x7933ac: 3d_Gfx |   0x7933ac: 3d_Gfx | ||||||
|   0x7933a0: NodeFX |   0x7933a0: NodeFX | ||||||
| 
 | 
 | ||||||
|  | classes: | ||||||
|  |   World:  | ||||||
|  | 
 | ||||||
| types: | types: | ||||||
|   - "struct PyMethodDef { char* ml_name; void* ml_meth; int ml_flags; char* ml_doc;};" |   - "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; };" |   - "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 HashIndex { uint32_t size; struct HashIndexEntry** data; };" | ||||||
|   - "struct HashTableEntry { void* data; const char* key; struct HashTableEntry* next; };" |   - "struct HashTableEntry { void* data; const char* key; struct HashTableEntry* next; };" | ||||||
|   - "struct HashTable { uint32_t size; struct HashTableEntry** data; };" |   - "struct HashTable { uint32_t size; struct HashTableEntry** data; };" | ||||||
| 
 |   - "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };" | ||||||
| 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);" |  | ||||||
| 
 |  | ||||||
| functions: | functions: | ||||||
|   0x6B1C70: strcmp |   0x6283a0: | ||||||
|   0x5BB9D0: PyArg_ParseTuple |     name: load_emi | ||||||
|   0x5DD510: init_engine_3d |   0x4fa9f0: | ||||||
|   0x401180: create_window |     name: send_pkt | ||||||
|   0x401240: create_main_window |   0x5ca9e0: | ||||||
|   0x4016F0: reg_get_val |     signature: void* PyFrame_New(void* thread_state, void* code_object,void* globals, void* locals) | ||||||
|   0x4134C0: write_log |     name: PyFrame_New | ||||||
|   0x414280: prepare_html_log |   0x5bcae0: | ||||||
|   0x418220: get_version_info |     signature: void PyErr_SetString(void* obj, const char* err_msg); | ||||||
|   0x4137E0: write_html_log |     name: PyErr_SetString | ||||||
|   0x402190: handle_console_input |   0x5cb040: | ||||||
|   0x5F9520: handle_render_console_input |     signature: void* eval_code2(void* dict, const char* key, void* item); | ||||||
|   0x404A50: find_entity |     name: eval_code2 | ||||||
|   0x47C1E0: ht_hash_ent_list |   0x5e3c50: | ||||||
|   0x404BB0: ht_hash_ent |     convention: cdecl-thiscall-ms | ||||||
|   0x404460: register_c_callback |     name: read_int | ||||||
|   0x417470: load_game |   0x5e3b50: | ||||||
|   0x5E3800: fopen_from_pak |     convention: cdecl-thiscall-ms | ||||||
|   0x5e3500: fopen |     name: read_block_header | ||||||
|   0x403370: init_debug |   0x5c66d0: | ||||||
|   0x401770: init |     signature: void initerrors(void* dict); | ||||||
|   0x4026D0: init_py |     name: initerrors | ||||||
|   0x405B40: init_py_sub |   0x5bb370: | ||||||
|   0x5A8FB0: Py_InitModule |     signature: int PyDict_SetItemString(void* dict, const char* key, void* item); | ||||||
|   0x41AB50: open_pak |     name: PyDict_SetItemString | ||||||
|   0x5A8390: PyRun_SimpleString |   0x5b9960: | ||||||
|   0x414570: setup_game_vars |     signature: void* PyObject_NEW(void* type, void* typeobj); | ||||||
|   0x5FBC50: throw_assertion_1 |     name: PyObject_NEW | ||||||
|   0x414070: throw_assertion_2 |   0x4145e0: | ||||||
|   0x5F7000: read_ini |     convention: cdecl-thiscall-ms | ||||||
|   0x650F80: load_sm3 |     signature: bool get_config_var(char* name); | ||||||
|   0x6665A0: load_m3d_1 |     name: get_config_var | ||||||
|   0x666900: load_m3d_2 |   0x413470: | ||||||
|   0x479B20: world_constructor |     signature: void init_logging(); | ||||||
|   0x479B40: init_world |     name: init_logging | ||||||
|   0x402510: deinit_world |   0x5a8040: | ||||||
|   0x479870: make_world |     signature: void Py_Initialize(); | ||||||
|   0x602A70: render_frame |     name: Py_Initialize | ||||||
|   0x6B738C: handle_exception |   0x5bb4e0: | ||||||
|   0x5B9E70: PyObject_GetAttrString |     name: PyModule_GetDict | ||||||
|   0x413ee0: dbg_log |     signature: void* PyModule_GetDict(void*); | ||||||
|   0x5f75e0: init_d3d |   0x5c6610: | ||||||
|   0x63a2f0: gdi_draw_line |     name: _PyBuiltin_Init_1 | ||||||
|   0x5e3250: read_stream |     signature: void* _PyBuiltin_Init_1(); | ||||||
|   0x5e3bb0: read_stream_wrapper |   0x5b5db0: | ||||||
|   0x50b9b0: init_scorer |     name: PyString_FromString | ||||||
|   0x582e10: init_action_class_list |     signature: void* PyString_FromString(const char*); | ||||||
|   0x528910: init_sound_sys |   0x5ba3a0: | ||||||
|   0x5268d0: try_init_sound_sys |     name: PyDict_New | ||||||
|   0x404280: cPyFunction_set_func |     signature: void* PyDict_New(); | ||||||
|   0x414680: load_config |   0x5c7bd0: | ||||||
|   0x414810: save_config |     name: PyThreadState_Swap | ||||||
|   0x4f42a0: close_server_socket |     signature: void* PyThreadState_Swap(void* new); | ||||||
|   0x4f4d10: close_server |   0x5c7870: | ||||||
|   0x4f48e0: close_client |     name: PyInterpreterState_New | ||||||
|   0x4f4fb0: is_server |     signature: void* PyInterpreterState_New(); | ||||||
|   0x4f4a10: is_client |   0x5c79b0: | ||||||
|   0x4fac50: is_master |     name: PyThreadState_New | ||||||
|   0x526910: close_sound_sys |     signature: void* PyThreadState_New(void* interp); | ||||||
|   0x526520: shutdown_sound_sys |   0x6ad1e9: | ||||||
|   0x5dd700: close_3d_engine |     name: getenv | ||||||
|   0x5a7320: close_window |     signature: char* getenv(char* var); | ||||||
|   0x5dff20: set_exception_handler |   0x401180: | ||||||
|   0x5a7f20: get_console_wnd |     name: create_window | ||||||
|   0x5a73a0: show_console |   0x401240: | ||||||
|   0x666c60: read_m3d |     name: create_main_window | ||||||
|   0x417df0: snprintf |   0x4016f0: | ||||||
|   0x5fc930: printf |     name: reg_get_val | ||||||
|   0x6597d0: read_ini_entry |     signature: int reg_get_val(const char* value); | ||||||
|   0x5fc0a0: engine_debug_log |   0x401770: | ||||||
|   0x5a7440: create_console_window |     name: init | ||||||
|   0x6114e0: setup_window |   0x402190: | ||||||
|   0x404420: clear_functions |     name: handle_console_input | ||||||
|   0x405ca0: close_py_subsys |     signature: int handle_console_input(const char* input); | ||||||
|   0x50bcb0: close_scorer |   0x402510: | ||||||
|   0x479b20: close_world |     name: deinit_world | ||||||
|   0x582e70: close_action_class |   0x4026d0: | ||||||
|   0x50b6a0: get_scorer |     name: init_py | ||||||
|   0x50ea20: scorer_parse_type |   0x403370: | ||||||
|   0x636580: list_models |     name: init_engine | ||||||
|   0x5a90f0: Py_BuildValue |   0x4037e0: | ||||||
|   0x41c5a0: has_lst_file |     name: init_debug | ||||||
|   0x5a8e90: py_error |   0x404280: | ||||||
|   0x5a9890: get_module_dict |     name: cPyFunction_set_func | ||||||
|   0x5c7bb0: get_current_thread |   0x404420: | ||||||
|   0x5aa140: preload_lib |     name: clear_functions | ||||||
|   0x413c10: sprintf |   0x404460: | ||||||
|   0x405850: check_is_python |     name: register_c_callback | ||||||
|   0x47bf90: setup_ent_list |     signature: int register_c_callback(const char* name,void* func); | ||||||
|   0x474f80: ent_list_get_set |   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: | | script: | | ||||||
|   e asm.cmt.right = true |   e asm.cmt.right = true | ||||||
|   e cmd.stack = true |  | ||||||
|   e scr.utf8 = true |   e scr.utf8 = true | ||||||
|   e asm.describe = false |   e asm.describe = false | ||||||
|   e graph.cmtright = true |   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 | from datetime import datetime | ||||||
| import subprocess as SP | import subprocess as SP | ||||||
| from tqdm import tqdm | from tqdm import tqdm | ||||||
| from pprint import pprint |  | ||||||
| import os |  | ||||||
| import sys | import sys | ||||||
| import yaml | import yaml | ||||||
| 
 | 
 | ||||||
|  | tqdm_ascii = False | ||||||
|  | 
 | ||||||
| r2cmds = [] | r2cmds = [] | ||||||
| x64_dbg_script = [] | x64_dbg_script = [] | ||||||
| script_path = os.path.dirname(os.path.abspath(__file__)) | script_path = os.path.dirname(os.path.abspath(__file__)) | ||||||
|  | @ -24,10 +24,12 @@ file_hashes = r2.cmdj("itj") | ||||||
| target_hashes = { | target_hashes = { | ||||||
|     "sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa", |     "sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa", | ||||||
|     "md5": "a934c85dca5ab1c32f05c0977f62e186", |     "md5": "a934c85dca5ab1c32f05c0977f62e186", | ||||||
|  |     "sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| assert file_hashes == target_hashes, "Hash mismatch" | assert file_hashes == target_hashes, "Hash mismatch" | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def x64_dbg_label(addr, name, prefix=None): | def x64_dbg_label(addr, name, prefix=None): | ||||||
|     global x64_dbg_script |     global x64_dbg_script | ||||||
|     if isinstance(addr, int): |     if isinstance(addr, int): | ||||||
|  | @ -37,6 +39,7 @@ def x64_dbg_label(addr,name,prefix=None): | ||||||
|     else: |     else: | ||||||
|         x64_dbg_script.append(f'lbl {addr},"{name}"') |         x64_dbg_script.append(f'lbl {addr},"{name}"') | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def r2_cmd(cmd): | def r2_cmd(cmd): | ||||||
|     global r2, r2cmds |     global r2, r2cmds | ||||||
|     r2cmds.append(cmd) |     r2cmds.append(cmd) | ||||||
|  | @ -54,8 +57,10 @@ def r2_cmdJ(cmd): | ||||||
|     r2cmds.append(cmd) |     r2cmds.append(cmd) | ||||||
|     return r2.cmdJ(cmd) |     return r2.cmdJ(cmd) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| t_start = datetime.today() | t_start = datetime.today() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def analysis(full=False): | def analysis(full=False): | ||||||
|     print("[*] Running analysis") |     print("[*] Running analysis") | ||||||
|     steps = [] |     steps = [] | ||||||
|  | @ -72,11 +77,15 @@ def analysis(full=False): | ||||||
|             "e anal.vinfun = true", |             "e anal.vinfun = true", | ||||||
|             "e asm.anal = true", |             "e asm.anal = true", | ||||||
|         ] |         ] | ||||||
|     steps+=["aaaaa"] |     if full: | ||||||
|  |         steps += ["aaaa"] | ||||||
|  |     else: | ||||||
|  |         steps += ["aaa"] | ||||||
|     for ac in steps: |     for ac in steps: | ||||||
|         print(f"[*] Running '{ac}'") |         print(f"[*] Running '{ac}'") | ||||||
|         r2_cmd(f"{ac} 2>NUL") |         r2_cmd(f"{ac} 2>NUL") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| with open(os.path.join(script_path, "config.yml")) as cfg: | with open(os.path.join(script_path, "config.yml")) as cfg: | ||||||
|     print("[*] Loading config") |     print("[*] Loading config") | ||||||
|     config = type("Config", (object,), yaml.load(cfg, Loader=yaml.SafeLoader)) |     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)}") |     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") |         x64_dbg_label(addr, name, "fcn") | ||||||
|         r2_cmd(f"afr fcn.{name} {hex(addr)}") |         r2_cmd(f"afr fcn.{name} {hex(addr)}") | ||||||
| 
 |         r2_cmd(f"afn fcn.{name} {hex(addr)}") | ||||||
| for addr,sig in config.function_signatures.items(): |     if sig: | ||||||
|     r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}') |         sig = sig.replace(name, "fcn." + name) | ||||||
| 
 |         r2_cmd(f'"afs {sig}" @{hex(addr)}') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def vtables(): | def vtables(): | ||||||
|     ret = {} |     ret = {} | ||||||
|     print("[*] Analyzing VTables") |     print("[*] Analyzing VTables") | ||||||
|     vtables = r2_cmdJ("avj") |     vtables = r2_cmdJ("avj") | ||||||
|     for c in tqdm(vtables, ascii=True): |     for c in tqdm(vtables, ascii=tqdm_ascii): | ||||||
|         methods = [] |         methods = [] | ||||||
|         name = config.VMTs.get(c.offset, f"{c.offset:08x}") |         name = config.VMTs.get(c.offset, f"{c.offset:08x}") | ||||||
|         x64_dbg_label(c.offset, name, "vmt") |         x64_dbg_label(c.offset, name, "vmt") | ||||||
|         r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}") |         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)) |             methods.append(hex(m.offset)) | ||||||
|             x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt") |             x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt") | ||||||
|             r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL") |             r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL") | ||||||
|  | @ -127,7 +138,7 @@ def c_callbacks(): | ||||||
|     print("[*] Parsing C Callbacks") |     print("[*] Parsing C Callbacks") | ||||||
|     funcs = {} |     funcs = {} | ||||||
|     res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines() |     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"s {addr}") | ||||||
|         r2_cmd(f"so -3") |         r2_cmd(f"so -3") | ||||||
|         func, name = r2_cmdJ(f"pdj 2") |         func, name = r2_cmdJ(f"pdj 2") | ||||||
|  | @ -142,22 +153,22 @@ def c_callbacks(): | ||||||
| def assertions(): | def assertions(): | ||||||
|     assertions = {} |     assertions = {} | ||||||
|     for (n_args, a_addr) in [ |     for (n_args, a_addr) in [ | ||||||
|         (4, "fcn.throw_assertion_1"), |         (3, "fcn.throw_assertion_1"), | ||||||
|         (3, "fcn.throw_assertion_2"), |         (4, "fcn.throw_assertion_2"), | ||||||
|     ]: |     ]: | ||||||
|         print(f"[*] Parsing C assertions for {a_addr}") |         print(f"[*] Parsing C assertions for {a_addr}") | ||||||
|         res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() |         res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() | ||||||
|         print() |         print() | ||||||
|         for line in tqdm(res, ascii=True): |         for line in tqdm(res, ascii=tqdm_ascii): | ||||||
|             addr = line.strip() |             addr = line.strip() | ||||||
|             r2_cmd(f"s {addr}") |             r2_cmd(f"s {addr}") | ||||||
|             r2_cmd(f"so -{n_args}") |             r2_cmd(f"so -{n_args}") | ||||||
|             dis = r2_cmdJ(f"pij {n_args}") |             dis = r2_cmdJ(f"pij {n_args}") | ||||||
|             if n_args == 4: |             if n_args == 4: | ||||||
|                 file, msg, date, line = dis |                 line, date, file, msg = dis | ||||||
|             elif n_args == 3: |             elif n_args == 3: | ||||||
|                 date = None |                 date = None | ||||||
|                 file, msg, line = dis |                 line, file, msg = dis | ||||||
|             try: |             try: | ||||||
|                 file = r2_cmd(f"psz @{file.refs[0].addr}").strip() |                 file = r2_cmd(f"psz @{file.refs[0].addr}").strip() | ||||||
|                 msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() |                 msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() | ||||||
|  | @ -180,32 +191,35 @@ def bb_refs(addr): | ||||||
|     ret = {} |     ret = {} | ||||||
|     res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines() |     res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines() | ||||||
|     print() |     print() | ||||||
|     for ent in res: |     for ent in tqdm(res, ascii=tqdm_ascii): | ||||||
|         func, hit = ent.split() |         func, hit = ent.split() | ||||||
|         ret[hit] = {"asm": [], "func": func} |         ret[hit] = {"asm": [], "func": func} | ||||||
|         for ins in r2_cmdJ(f"pdbj @{hit}"): |         for ins in r2_cmdJ(f"pdbj @{hit}"): | ||||||
|             ret[hit]["asm"].append(ins.disasm) |             ret[hit]["asm"].append(ins.disasm) | ||||||
|     return ret |     return ret | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def world(): | def world(): | ||||||
|     print("[*] Parsing World offsets") |     print("[*] Parsing World offsets") | ||||||
|     return bb_refs("loc.P_World") |     return bb_refs("loc.P_World") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def render(): | def render(): | ||||||
|     print("[*] Parsing D3D_Device offsets") |     print("[*] Parsing D3D_Device offsets") | ||||||
|     return bb_refs("loc.P_D3D8_Dev") |     return bb_refs("loc.P_D3D8_Dev") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def py_mods(): | def py_mods(): | ||||||
|     print("[*] Parsing Python modules") |     print("[*] Parsing Python modules") | ||||||
|     res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() |     res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() | ||||||
|     print() |     print() | ||||||
|     py_mods = {} |     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"s {call_loc}") | ||||||
|         r2_cmd(f"so -3") |         r2_cmd(f"so -3") | ||||||
|         args = r2_cmdJ("pdj 3") |         args = r2_cmdJ("pdj 3") | ||||||
|         refs = [] |         refs = [] | ||||||
|         if not all([arg.type == "push" for arg in args]): |         if not all(arg.type == "push" for arg in args): | ||||||
|             continue |             continue | ||||||
|         for arg in args: |         for arg in args: | ||||||
|             refs.append(hex(arg.val)) |             refs.append(hex(arg.val)) | ||||||
|  | @ -240,7 +254,7 @@ def game_vars(): | ||||||
|     print("[*] Parsing Game variables") |     print("[*] Parsing Game variables") | ||||||
|     res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines() |     res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines() | ||||||
|     print() |     print() | ||||||
|     for line in tqdm(res, ascii=True): |     for line in tqdm(res, ascii=tqdm_ascii): | ||||||
|         addr = line.strip() |         addr = line.strip() | ||||||
|         r2_cmd(f"s {addr}") |         r2_cmd(f"s {addr}") | ||||||
|         args = r2_cmd("pdj -5")  # seek and print disassembly |         args = r2_cmd("pdj -5")  # seek and print disassembly | ||||||
|  | @ -259,7 +273,7 @@ def game_vars(): | ||||||
|                 break |                 break | ||||||
|         if len(args_a) != 4: |         if len(args_a) != 4: | ||||||
|             continue |             continue | ||||||
|         if not all(["val" in v for v in args_a]): |         if not all("val" in v for v in args_a): | ||||||
|             continue |             continue | ||||||
|         addr, name, _, desc = [v["val"] for v in args_a] |         addr, name, _, desc = [v["val"] for v in args_a] | ||||||
|         name = r2_cmd(f"psz @{hex(name)}").strip() |         name = r2_cmd(f"psz @{hex(name)}").strip() | ||||||
|  | @ -271,15 +285,10 @@ def game_vars(): | ||||||
|     return ret |     return ret | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ret = dict( | ret = {} | ||||||
|     game_vars=game_vars(), | # world, render | ||||||
|     c_callbacks=c_callbacks(), | for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]: | ||||||
|     py_mods=py_mods(), |     ret[func] = globals()[func]() | ||||||
|     assertions=assertions(), |  | ||||||
|     vtables=vtables(), |  | ||||||
|     world=world(), |  | ||||||
|     render=render(), |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| analysis(True) | analysis(True) | ||||||
| 
 | 
 | ||||||
|  | @ -296,6 +305,8 @@ print("[+] Wrote scrap_dissect.x32dbg.txt") | ||||||
| with open(r2_script_path, "w") as of: | with open(r2_script_path, "w") as of: | ||||||
|     wcmds = [] |     wcmds = [] | ||||||
|     for cmd in r2cmds: |     for cmd in r2cmds: | ||||||
|  |         if cmd == "avj": | ||||||
|  |             continue | ||||||
|         record = True |         record = True | ||||||
|         for start in ["p", "/", "s"]: |         for start in ["p", "/", "s"]: | ||||||
|             if cmd.strip('"').startswith(start): |             if cmd.strip('"').startswith(start): | ||||||
|  | @ -306,20 +317,25 @@ with open(r2_script_path, "w") as of: | ||||||
| 
 | 
 | ||||||
| print("[+] Wrote scrap_dissect.r2") | print("[+] Wrote scrap_dissect.r2") | ||||||
| 
 | 
 | ||||||
| r2.quit() |  | ||||||
| 
 | 
 | ||||||
| def start_program(cmdl, **kwargs): | def start_program(cmdl, **kwargs): | ||||||
|     if os.name=='nt': |     if os.name == "nt": | ||||||
|         return SP.Popen(['cmd','/c','start']+cmdl,**kwargs) |         return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs) | ||||||
|     else: |     else: | ||||||
|         return SP.Popen(cmdl, **kwargs) |         return SP.Popen(cmdl, **kwargs) | ||||||
| 
 | 
 | ||||||
| print("[+] Analysis took:",datetime.today()-t_start) |  | ||||||
| 
 | 
 | ||||||
|  | print("[+] Analysis took:", datetime.today() - t_start) | ||||||
| 
 | 
 | ||||||
| print("[+] Executing Cutter") | print("[+] Executing Cutter") | ||||||
| try: | 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: | except FileNotFoundError: | ||||||
|     print("[-] cutter not installed, falling back to r2") |     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 glob | ||||||
| import os | import os | ||||||
| import shutil | import shutil | ||||||
| from construct import * | from construct import ( | ||||||
|  |     Struct, | ||||||
|  |     PascalString, | ||||||
|  |     Int32ul, | ||||||
|  |     Lazy, | ||||||
|  |     Pointer, | ||||||
|  |     Bytes, | ||||||
|  |     this, | ||||||
|  |     PrefixedArray, | ||||||
|  |     Const, | ||||||
|  |     Debugger | ||||||
|  | ) | ||||||
| from tqdm import tqdm | from tqdm import tqdm | ||||||
| 
 | 
 | ||||||
| setglobalstringencoding(None) |  | ||||||
| 
 |  | ||||||
| ScrapFile = Struct( | ScrapFile = Struct( | ||||||
|     "path" / PascalString(Int32ul), |     "path" / PascalString(Int32ul, encoding="ascii"), | ||||||
|     "size" / Int32ul, |     "size" / Int32ul, | ||||||
|     "offset" / 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( | PackedHeader = Struct( | ||||||
|     Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile) |     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