├── .gitignore ├── LICENSE ├── README.md ├── SIGNATURES.md ├── dlls └── .gitkeep ├── find_sp2x_patches.py ├── patches └── .gitkeep ├── print_all_occurences.py ├── requirements.txt └── signatures ├── KFC-signatures.json ├── L44-signatures.json ├── LDJ-signatures.json ├── MDX-signatures.json ├── PAN-signatures.json └── REC-signatures.json /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea 3 | *.dll 4 | *.json 5 | !*-signatures.json 6 | logs.txt 7 | /.vs 8 | /.gitignore 9 | print_all_occurences.py 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 202X Akitake & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # find_sp2x_patches 2 | 3 | ## About 4 | 5 | This project includes scripts to find Spice2x patches for various game versions. 6 | 7 | - **`find_sp2x_patches.py`**: Finds as many Spice2x patches as possible for various game versions. 8 | - **`print_all_occurences.py`**: A helper script to quickly test run a signature search for a specific DLL. 9 | 10 | ### Notes 11 | 12 | 1. The provided **signatures will break over time**. I will do my best to keep updating them. 13 | 2. The provided **signatures are built for the bleeding edge of n-0**. They may not work for older content. 14 | 3. Documentation on how to create a signature file is provided in [SIGNATURES.md](SIGNATURES.md). 15 | 16 | ## Requirements 17 | 18 | - **Python 3** 19 | - **pip** 20 | - `pefile` library (install with `pip install -r requirements.txt`) 21 | 22 | ## Directories 23 | 24 | - **`dlls`**: Contains the game's .dll files you want to find patches for. 25 | - **`patches`**: The output folder for Spice2x-compatible .json files. 26 | - **`signatures`**: Contains `-signatures.json` files used by the script to determine patches. 27 | 28 | ## Usage 29 | 30 | Run the script with the following command: 31 | 32 | `python find_sp2x_patches.py [-h] [--game GAME] [--loglevel {DEBUG,INFO,WARNING,ERROR,CRITICAL}]` 33 | 34 | ### Arguments 35 | 36 | _All arguments are optional_ 37 | 38 | - `-h`: Prints out a help message. 39 | - `--game`: Specify a game to run the script for (example: KFC, default: ALL). 40 | - `--loglevel`: Set the console logging level (default: INFO). 41 | 42 | ### Logging 43 | 44 | - Logs are sent to the console and a `logs.txt` file. 45 | - The console shows INFO and above messages by default, but this can be changed with the `--loglevel` argument. 46 | - `logs.txt` logs everything, including DEBUG messages, and is overwritten on each run. 47 | 48 | ### Output 49 | 50 | Spice2x-compatible `patches.json` files are outputted to the `patches` directory. 51 | 52 | ### Examples 53 | 54 | #### File Tree 55 | 56 | > find_sp2x_patches/ 57 | > ├─ dlls/ 58 | > ├─── bm2dx31_012-0826.dll 59 | > ├─── bm2dx32_012-1009.dll 60 | > ├─── soundvoltex-1022.dll 61 | > ├─ patches/ 62 | > ├─ signatures/ 63 | > ├─── KFC-signatures.json 64 | > ├─── LDJ-signatures.json 65 | > ├─ find_sp2x_patches.py 66 | 67 | #### Commands and Output 68 | 69 | _Note: The patches marked as not found in this first example are expected. 70 | The signature file is the same for both LDJ-010 and LDJ-012 dlls, and these are LDJ-010 only patches._ 71 | 72 | **1.** 73 | 74 | ``` 75 | > python find_sp2x_patches.py 76 | 2024-10-27 16:44:39,245 - INFO: [KFC] 77 | 2024-10-27 16:44:39,245 - INFO: Processing 'dlls\soundvoltex-1022.dll' 78 | 2024-10-27 16:44:39,801 - INFO: -> patches\KFC-67108c5c_6d2ec8.json (15/15) 79 | 2024-10-27 16:44:39,802 - INFO: [LDJ] 80 | 2024-10-27 16:44:39,802 - INFO: Processing 'dlls\bm2dx31_012-0826.dll' 81 | 2024-10-27 16:44:40,153 - WARNING: [memory] 'Force LDJ Mode' not found (0/1) 82 | 2024-10-27 16:44:40,942 - WARNING: [memory] 'Force LDJ Software Video Decoder' not found (0/1) 83 | 2024-10-27 16:44:41,076 - INFO: -> patches\LDJ-66c58ff1_9b323c.json (32/34) 84 | 2024-10-27 16:44:41,076 - INFO: Processing 'dlls\bm2dx32_012-1009.dll' 85 | 2024-10-27 16:44:41,217 - WARNING: [memory] 'Force LDJ Mode' not found (0/1) 86 | 2024-10-27 16:44:42,070 - WARNING: [memory] 'Force LDJ Software Video Decoder' not found (0/1) 87 | 2024-10-27 16:44:42,212 - INFO: -> patches\LDJ-66ff5733_a6589c.json (32/34) 88 | ``` 89 | 90 | **2.** 91 | 92 | ``` 93 | 2. 94 | > python .\find_sp2x_patches.py --game KFC --loglevel DEBUG 95 | 2024-10-27 16:45:41,985 - INFO: [KFC] 96 | 2024-10-27 16:45:41,986 - INFO: Processing 'dlls\soundvoltex-1022.dll' 97 | 2024-10-27 16:45:41,992 - DEBUG: [union] 'Game FPS Target' found (2/2) 98 | 2024-10-27 16:45:42,017 - DEBUG: [union] 'Note FPS Target' found (2/2) 99 | 2024-10-27 16:45:42,032 - DEBUG: [memory] 'Shared mode WASAPI' found (1/1) 100 | 2024-10-27 16:45:42,047 - DEBUG: [memory] 'Shared mode WASAPI Valkyrie' found (1/1) 101 | 2024-10-27 16:45:42,167 - DEBUG: [hardcoded] 'Hide premium guide banner' found (kfc_001) 102 | 2024-10-27 16:45:42,258 - DEBUG: [memory] 'Hide all bottom text' found (12/12) 103 | 2024-10-27 16:45:42,286 - DEBUG: [memory] 'Standard/Menu Timer Freeze' found (1/1) 104 | 2024-10-27 16:45:42,342 - DEBUG: [memory] 'Premium Free Timer Freeze' found (3/3) 105 | 2024-10-27 16:45:42,358 - DEBUG: [union] 'Premium Time Length' found (14/14) 106 | 2024-10-27 16:45:42,374 - DEBUG: [memory] 'ASIO 2 Channels Mode' found (1/1) 107 | 2024-10-27 16:45:42,391 - DEBUG: [memory] 'Disable power change' found (1/1) 108 | 2024-10-27 16:45:42,411 - DEBUG: [memory] 'Disable monitor change' found (1/1) 109 | 2024-10-27 16:45:42,425 - DEBUG: [memory] 'Disable Subscreen in Valkyrie mode' found (1/1) 110 | 2024-10-27 16:45:42,513 - DEBUG: [memory] 'Valkyrie Mode 60Hz' found (3/3) 111 | 2024-10-27 16:45:42,549 - DEBUG: [memory] 'Force BIO2 (KFC) IO in Valkyrie mode' found (1/1) 112 | 2024-10-27 16:45:42,550 - INFO: -> patches\KFC-67108c5c_6d2ec8.json (15/15) 113 | 2024-10-27 16:45:42,551 - DEBUG: Skipping 'LDJ' 114 | ``` -------------------------------------------------------------------------------- /SIGNATURES.md: -------------------------------------------------------------------------------- 1 | ## Creating Signature Files 2 | 3 | Signature files are JSON files that define instructions on how to find patches for inside game DLLs. 4 | We will not explain how to find signatures as it's a very technical and game-specific process. 5 | However we will explain how signature files are created and formatted: 6 | 7 | ## File Naming 8 | 9 | Name your file `-signatures.json`, where `` is the game's unique identifier (KFC, LDJ, etc.). 10 | 11 | ## Structure 12 | 13 | Each file should start with a header object followed by an array of patch objects. 14 | 15 | ### Header Object 16 | 17 | - `gameCode`: The game's unique identifier (KFC, LDJ, etc.). 18 | - `dllName`: The name of the DLL file the patches are meant for. 19 | - `lastUpdated`: The date when the signature file was last updated. 20 | - `source`: The source URL or reference. 21 | 22 | ###### Example header 23 | 24 | ```json 25 | { 26 | "gameCode": "KFC", 27 | "dllName": "soundvoltex.dll", 28 | "lastUpdated": "2024-10-27", 29 | "source": "https://sp2x.two-torial.xyz/" 30 | }, 31 | ``` 32 | 33 | ### Patch Objects 34 | 35 | - `type`: The type of patch (e.g. `memory`, `union`, `number`, `hardcoded`). 36 | - `name`: A descriptive name for the patch. 37 | - `description`: A brief description of what the patch does. 38 | - `caution`: (Optional) A warning about the patch. 39 | 40 |
41 |

For memory patches

42 | 43 | - `patches`: Array containing objects with the following properties: 44 | - `start`: (Optional) The starting offset to search at, 0 by default (start of file). 45 | - `signature`: The byte signature to search for, with '??' representing any bytes. 46 | - `adjust`: (Optional) Adjustment to the found offset. 47 | - `data`: The data to write at the patch location. Can be set to "NUL" to set all bytes in the signature to 0. 48 | - `patchall`: (Optional) If true, apply the patch to all occurrences of the signature. 49 | 50 | ###### Example memory patch 51 | 52 | ```json 53 | { 54 | "type": "memory", 55 | "name": "Mute Announcer", 56 | "description": "Mutes the announcer voice.", 57 | "patches": [ 58 | { 59 | "start": 200000, 60 | "signature": "40 85 C0 0F 84 AE 03 00 00 83 F8 03 0F 84 A5 03", 61 | "adjust": 3, 62 | "data": "90 E9" 63 | }, 64 | { 65 | "start": 2000000, 66 | "signature": "73 ?? 00 00 76 6F 69 63 65 00 00 00 ?? ?? ?? 10", 67 | "adjust": 4, 68 | "data": "62" 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 |
75 |
76 |

For union patches

77 | 78 | - `start`: (Optional) The starting offset to search at, 0 by default (start of file). 79 | - `signature`: The byte signature to search for, with '??' representing any bytes. 80 | - `adjust`: (Optional) Adjustment to the found offset. 81 | - `patches`: Array containing one object per option with the following properties: 82 | - `name`: The name of the option. 83 | - `data`: The data to write at the patch location. If set to "default" it'll just add an option for the default value present in the DLL. 84 | 85 | ###### Example union patch 86 | 87 | ```json 88 | { 89 | "type": "union", 90 | "name": "Game FPS Target", 91 | "description": "Forces the game to run at a specific FPS target.", 92 | "start": 9000000, 93 | "signature": "00 00 00 00 00 00 4E 40", 94 | "adjust": 6, 95 | "patches": [ 96 | { 97 | "name": "60 FPS", 98 | "data": "default" 99 | }, 100 | { 101 | "name": "120 FPS", 102 | "data": "5E" 103 | } 104 | ] 105 | } 106 | ``` 107 | 108 |
109 |
110 |

For number patches

111 | 112 | - `patch`: Object containing the following properties: 113 | - `start`: (Optional) The starting offset to search at, 0 by default (start of file). 114 | - `signature`: The byte signature to search for, with '??' representing any bytes. 115 | - `adjust`: Adjustment to the found offset. 116 | - `size`: The size (in bytes) of the number to patch. 117 | - `min`: The minimum value of the number. 118 | - `max`: The maximum value of the number. 119 | 120 | ###### Example number patch 121 | 122 | ```json 123 | { 124 | "type": "number", 125 | "name": "Render Offset", 126 | "description": "Sets the render offset (Default: 2).", 127 | "patch": { 128 | "start": 100000, 129 | "signature": "00 00 00 B9 57 00 00 00 BA 24 00 00 00 BE 02 00", 130 | "adjust": 14, 131 | "size": 4, 132 | "min": 0, 133 | "max": 1000 134 | } 135 | } 136 | ``` 137 | 138 |
139 |
140 |

For hardcoded patches

141 | 142 | **Hardcoded patches are a special case where the patch is not found by searching for a signature.** 143 | Instead it's generated by a function named after the `id`, found inside the `find_sp2x_patches.py` script itself. 144 | 145 | ###### Example hardcoded patch 146 | 147 | ```json 148 | { 149 | "type": "hardcoded", 150 | "name": "Hide premium guide banner", 151 | "description": "blpass_ef (rainbow outline on health gauge) is shown instead of pt_sousa_usr.", 152 | "id": "kfc_001" 153 | } 154 | ``` 155 | 156 |
157 | -------------------------------------------------------------------------------- /dlls/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotAkitake/find_sp2x_patches/2528e2482f962e685cd6af861168d70041cf4a01/dlls/.gitkeep -------------------------------------------------------------------------------- /find_sp2x_patches.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from datetime import datetime, timezone 3 | import json 4 | import logging 5 | from typing import BinaryIO 6 | from venv import logger 7 | import pefile 8 | import re 9 | import struct 10 | from pathlib import Path 11 | 12 | class BasePatch: 13 | def __init__(self, name: str, description: str, game_code: str, caution: str = None): 14 | self.name = name 15 | self.description = description 16 | self.game_code = game_code 17 | self.caution = caution 18 | 19 | def to_dict(self) -> dict: 20 | base_dict = { 21 | "name": self.name, 22 | "description": self.description, 23 | "caution": self.caution, 24 | "gameCode": self.game_code 25 | } 26 | return {k: v for k, v in base_dict.items() if v is not None} 27 | 28 | def __str__(self): 29 | return json.dumps(self.to_dict(), indent=4) 30 | 31 | 32 | class MemorySubPatch: 33 | def __init__(self, offset: int, dll_name: str, data_disabled: str, data_enabled: str): 34 | self.offset = offset 35 | self.dll_name = dll_name 36 | self.data_disabled = data_disabled.replace(" ", "") 37 | self.data_enabled = data_enabled.replace(" ", "").replace("NUL", "0" * len(data_disabled)) 38 | 39 | def to_dict(self): 40 | return { 41 | "offset": self.offset, 42 | "dllName": self.dll_name, 43 | "dataDisabled": self.data_disabled, 44 | "dataEnabled": self.data_enabled, 45 | } 46 | 47 | def __str__(self): 48 | return json.dumps(self.to_dict(), indent = 4) 49 | 50 | 51 | class MemoryPatch(BasePatch): 52 | def __init__(self, name: str, description: str, game_code: str, patches: list[MemorySubPatch], caution: str = None): 53 | super().__init__(name, description, game_code, caution) 54 | self.patches = patches 55 | 56 | def to_dict(self) -> dict: 57 | patch_dict = super().to_dict() 58 | patch_dict.update({ 59 | "type": "memory", 60 | "patches": [p.to_dict() for p in self.patches] 61 | }) 62 | return patch_dict 63 | 64 | 65 | class UnionSubPatch: 66 | def __init__(self, name: str, offset: int, dll_name: str, data: str): 67 | self.name = name 68 | self.offset = offset 69 | self.dll_name = dll_name 70 | self.data = data.replace(" ", "") 71 | 72 | def to_dict(self): 73 | return { 74 | "name": self.name, 75 | "patch": { 76 | "offset": self.offset, 77 | "dllName": self.dll_name, 78 | "data": self.data 79 | } 80 | } 81 | 82 | def __str__(self): 83 | return json.dumps(self.to_dict(), indent=4) 84 | 85 | 86 | class UnionPatch(BasePatch): 87 | def __init__(self, name: str, description: str, game_code: str, patches: list[UnionSubPatch], caution: str = None): 88 | super().__init__(name, description, game_code, caution) 89 | self.patches = patches 90 | 91 | def to_dict(self) -> dict: 92 | patch_dict = super().to_dict() 93 | patch_dict.update({ 94 | "type": "union", 95 | "patches": [p.to_dict() for p in self.patches] 96 | }) 97 | return patch_dict 98 | 99 | 100 | class NumberPatch(BasePatch): 101 | def __init__(self, name: str, description: str, game_code: str, dll_name: str, offset: int, size: int, i_min: int, i_max: int, caution: str = None): 102 | super().__init__(name, description, game_code, caution) 103 | self.dll_name = dll_name 104 | self.offset = offset 105 | self.size = size 106 | self.i_min = i_min 107 | self.i_max = i_max 108 | 109 | def to_dict(self) -> dict: 110 | patch_dict = super().to_dict() 111 | patch_dict.update({ 112 | "type": "number", 113 | "patch": { 114 | "dllName": self.dll_name, 115 | "offset": self.offset, 116 | "size": self.size, 117 | "min": self.i_min, 118 | "max": self.i_max 119 | } 120 | }) 121 | return patch_dict 122 | 123 | 124 | def signature_to_regex(signature: str) -> str: 125 | """ 126 | Converts a wildcarded signature into a regex pattern. 127 | :param signature: Allows '??' for wildcard bytes, for example: 'E8 45 15 ?? 00 00' 128 | :return: regex pattern 129 | """ 130 | pattern: list[str] = [] 131 | for byte in signature.split(): 132 | if byte == '??': 133 | pattern.append('.{2}') 134 | else: 135 | pattern.append(re.escape(byte)) 136 | 137 | return ''.join(pattern) 138 | 139 | 140 | def find(signature: str, dll: BinaryIO, start_offset: int = 0, adjust: int = 0) -> int | None: 141 | """ 142 | Finds a wildcarded bytes signature inside a dll's hex data. 143 | :param signature: Allows '??' for wildcard bytes, for example: 'E8 45 15 ?? 00 00'. 144 | :param dll: Dll file opened in binary mode. 145 | :param start_offset: (optional) decimal offset to start the search at, default: 0. 146 | :param adjust: (optional) Value added to the returned decimal offset, default: 0. 147 | :return: decimal offset if a match is found, otherwise None. 148 | """ 149 | signature_regex = signature_to_regex(signature) 150 | 151 | # Place cursor at start_offset 152 | dll.seek(start_offset) 153 | # Read all hex data from cursor to EOF 154 | data = dll.read() 155 | hex_data = data.hex().upper() 156 | 157 | # Search for the regex signature 158 | match = re.search(signature_regex, hex_data) 159 | if match: 160 | # If a match is found, calculate the final offset and return it 161 | offset = int(match.start() / 2) + start_offset + adjust 162 | return offset 163 | return None 164 | 165 | 166 | def read_dword(dll: BinaryIO, offset: int) -> int: 167 | """ 168 | Reads and returns dword in file (open as r+b) at offset. 169 | :param dll: Dll file opened in binary mode. 170 | :param offset: Offset to read the dword from. 171 | :return: struct: Unpacked dword. 172 | """ 173 | dll.seek(offset) 174 | return struct.unpack(' str: 178 | """ 179 | Concatenates 'game_code' with the PE identifier for 'dll'. 180 | :param game_code: Game code for the dll (KFC, LDJ, M39, ...). 181 | :param dll: Dll file opened in binary mode. 182 | :return: Identifier for the dll. 183 | """ 184 | try: 185 | with open(dll_path, 'rb') as dll: 186 | # Read DOS header to get PE header offset 187 | pe_header_offset = read_dword(dll, 0x3c) 188 | 189 | # Check for "PE\0\0" signature 190 | dll.seek(pe_header_offset) 191 | if dll.read(4) != b'PE\0\0': 192 | raise ValueError(f"File '{dll}' is not a valid PE file.") 193 | 194 | # Read TimeDateStamp 195 | timestamp = read_dword(dll, pe_header_offset + 8) 196 | 197 | # Read AddressOfEntryPoint 198 | optional_header_offset = pe_header_offset + 24 199 | entry_point = read_dword(dll, optional_header_offset + 16) 200 | 201 | # Concatenate GameCode, TimeDateStamp, and AddressOfEntryPoint 202 | identifier = f"{game_code.upper()}-{timestamp:x}_{entry_point:x}" 203 | return identifier 204 | except Exception as e: 205 | print(f"Error getting identifier from file: {e}") 206 | raise 207 | 208 | 209 | def parse_args() -> argparse.Namespace: 210 | """ 211 | Parses script arguments. 212 | :return: Parsed arguments. 213 | """ 214 | parser = argparse.ArgumentParser() 215 | parser.add_argument( 216 | '--game', 217 | default='ALL', 218 | help='(optional) Set a specific game to run the script for (example: KFC, default: ALL)' 219 | ) 220 | parser.add_argument( 221 | '--loglevel', 222 | default='INFO', 223 | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], 224 | help='(optional) Set the console logging level (default: INFO)' 225 | ) 226 | return parser.parse_args() 227 | 228 | 229 | def set_logger(loglevel: str) -> None: 230 | """ 231 | Sets logger custom formatting and loglevel. 232 | :param loglevel: Loglevel applied to the console only (DEBUG, INFO, WARNING, ERROR, CRITICAL). 233 | :return: None 234 | """ 235 | # Create a custom logger 236 | logger.setLevel(logging.DEBUG) 237 | 238 | # Create a file handler with UTF-8 encoding 239 | file_handler: logging.FileHandler = logging.FileHandler("logs.txt", mode="w", encoding="utf-8") 240 | console_handler: logging.StreamHandler = logging.StreamHandler() 241 | 242 | # Set the logging level for handlers 243 | file_handler.setLevel(logging.DEBUG) 244 | console_handler.setLevel(loglevel) 245 | 246 | # Create a custom formatter for the console with colors based on log levels 247 | class CustomFormatter(logging.Formatter): 248 | # Define color mappings for different log levels 249 | FORMATS = { 250 | logging.DEBUG: "\033[36m%(asctime)s - %(levelname)s: %(message)s", 251 | logging.INFO: "\033[32m%(asctime)s - %(levelname)s: %(message)s", 252 | logging.WARNING: "\033[33m%(asctime)s - %(levelname)s: %(message)s", 253 | logging.ERROR: "\033[31m%(asctime)s - %(levelname)s: %(message)s", 254 | logging.CRITICAL: "\033[35m%(asctime)s - %(levelname)s: %(message)s", 255 | } 256 | 257 | def format(self, record): 258 | log_fmt = self.FORMATS.get(record.levelno, "%(asctime)s - %(levelname)s: %(message)s") 259 | formatter = logging.Formatter(log_fmt) 260 | return formatter.format(record) 261 | 262 | # Apply different formatters to the file and console handlers 263 | file_formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s') 264 | file_handler.setFormatter(file_formatter) 265 | console_handler.setFormatter(CustomFormatter()) 266 | 267 | # Add handlers to the logger 268 | logger.addHandler(file_handler) 269 | logger.addHandler(console_handler) 270 | 271 | # HIDE PREMIUM GUIDE BANNER 272 | def kfc_001(dll: BinaryIO, dll_path: str, dll_name: str, game_code: str, name: str, description: str, caution: str = None) -> MemoryPatch | None: 273 | pe = pefile.PE(dll_path, fast_load=True) 274 | 275 | # Signature is 'pt_sousa_usr' 276 | offset = find("70 74 5F 73 6F 75 73 61 5F 75 73 72", dll) 277 | if offset is None: 278 | logger.error(f"[kfc_001] Step #1 failed for '{name}'") 279 | return None 280 | pt = pe.get_rva_from_offset(offset) 281 | offset = find("00 ?? 89 ?? 24 28 48 8D 45 58 48 89", dll, 2090000) 282 | if offset is None: 283 | logger.error(f"[kfc_001] Step #2 failed for '{name}'") 284 | return None 285 | for _ in range(4): 286 | offset = find("45 33 C0", dll, offset, 6) 287 | if offset is None: 288 | logger.error(f"[kfc_001] Step #3 failed for '{name}'") 289 | return None 290 | 291 | data_enabled = struct.pack(" UnionPatch | None: 300 | pe = pefile.PE(dll_path, fast_load=True) 301 | 302 | # Signature for instruction that sets J region 303 | setter_offset = find("89 05 ?? ?? ?? ?? 48 8B 4C 24 ?? 48 33 CC E8 ?? ?? ?? ?? 48 83 C4 58 C3 B8 02 00 00 00", dll) 304 | if setter_offset is None: 305 | logger.error(f"[kfc_002] Step #1 failed for '{name}'") 306 | return None 307 | 308 | # skip two bytes, next 4 bytes (little endian) are rip relative address to our data 309 | dll.seek(setter_offset + 2) 310 | region_offset = struct.unpack(" UnionPatch | None: 339 | pe = pefile.PE(dll_path, fast_load=True) 340 | 341 | # TICKER OFFSET 342 | ticker_offset = find("48 8D 0D ?? ?? ?? ?? 48 8B D3 FF 15 ?? ?? ?? ?? 48 8B 5C 24 ?? 33 C0 89 3D ?? ?? ?? ?? 48 83 C4 20 5F C3", dll, 8000000, 3) 343 | if ticker_offset is None: 344 | logger.error(f"[ldj_001] Step #1 failed for '{name}'") 345 | return None 346 | relative = pe.get_rva_from_offset(ticker_offset) 347 | dll.seek(ticker_offset) 348 | ticker_offset = struct.unpack("