├── source ├── __init__.py ├── preprocessor │ └── __init__.py ├── config │ └── __init__.py ├── patch_generator │ └── __init__.py └── project_manager │ └── __init__.py ├── banner.png ├── demo ├── lib │ ├── build.json │ └── README.md ├── modules │ ├── prologue │ │ ├── module.json │ │ ├── prologue.hpp │ │ └── prologue.cpp │ ├── reload │ │ ├── module.json │ │ ├── reload.hpp │ │ └── reload.cpp │ └── demo │ │ ├── module.json │ │ ├── demo.hpp │ │ └── demo.cpp ├── project.json └── externals │ └── RMCP01.txt ├── libpokey ├── debug.hpp ├── mkw │ ├── heaps.hpp │ └── environment.h ├── debug.h └── hooks.h ├── pokey_frontend.py ├── runtime └── mkw │ ├── v2.0 │ ├── build.sh │ ├── loader.cpp │ ├── bootstrap.cpp │ └── standalone.hpp │ └── v1.0 │ └── mkw_heap_runtime.cpp ├── LICENSE ├── .gitignore └── README.md /source/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snailspeed3/Pokey/HEAD/banner.png -------------------------------------------------------------------------------- /demo/lib/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "sources": [], 3 | "objects": [] 4 | } -------------------------------------------------------------------------------- /demo/modules/prologue/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ ], 3 | "sources": [ "prologue.cpp" ] 4 | } -------------------------------------------------------------------------------- /demo/modules/reload/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ ], 3 | "sources": [ "reload.cpp" ] 4 | } -------------------------------------------------------------------------------- /demo/lib/README.md: -------------------------------------------------------------------------------- 1 | # Library folder 2 | 3 | Headers, sources and precompiled object files may be added here. -------------------------------------------------------------------------------- /libpokey/debug.hpp: -------------------------------------------------------------------------------- 1 | 2 | // Compatibility redirection. debug.h is C and C++ compatible 3 | 4 | #include -------------------------------------------------------------------------------- /demo/modules/demo/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ ], 3 | "comment": "No additional includes needed here.", 4 | "sources": [ "demo.cpp" ] 5 | } -------------------------------------------------------------------------------- /demo/modules/reload/reload.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Reload { 5 | 6 | void reload(); 7 | 8 | } // namespace Reload -------------------------------------------------------------------------------- /libpokey/mkw/heaps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline 6 | EGG::ExpHeap* getMem1Heap() 7 | { 8 | __asm { 9 | lwz r3, -0x6C38(r13); 10 | lwz r3, 0x18(r3); 11 | } 12 | } -------------------------------------------------------------------------------- /demo/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": [ "prologue", "reload", "demo" ], 3 | "includes": [ 4 | "$MKW_DECOMP_ROOT", 5 | "$RVL_DECOMP_ROOT", 6 | "$MSL_ROOT", 7 | "$EABI_ROOT" 8 | ], 9 | "externals": "RMCP01.txt", 10 | "static": "0x809c4fa0" 11 | } -------------------------------------------------------------------------------- /source/preprocessor/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | def process_string(config, x): 3 | """"Process a string. 4 | 5 | This is necessary especially for the heavily used $DECOMP_ROOT variable. 6 | """ 7 | 8 | for key, item in config.variables.items(): 9 | x = x.replace(key, item) 10 | 11 | return x 12 | -------------------------------------------------------------------------------- /demo/modules/demo/demo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | extern "C" 5 | { 6 | bool OSDisableInterrupts(); 7 | bool OSEnableInterrupts(); 8 | bool OSRestoreInterrupts(bool stat); 9 | void OSReport(const char* str, ...); 10 | } 11 | 12 | namespace Demo { 13 | 14 | void applyProcessBar(); 15 | void prologue(); 16 | 17 | } // namespace Demo -------------------------------------------------------------------------------- /libpokey/mkw/environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define DATA_HACK 4 | 5 | 6 | // TODO: Network load 7 | 8 | #ifdef DATA_HACK 9 | #define PATH_CODE_BIN "Race/Course/draw_demo.szs" 10 | #define PATH_PATCH_BIN "Race/Course/draw_demo_d.szs" 11 | 12 | #else 13 | #define PATH_CODE_BIN "CODE.bin" 14 | #define PATH_PATCH_BIN "PATCH.bin" 15 | #endif 16 | -------------------------------------------------------------------------------- /source/config/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class Config: 4 | def __init(self): 5 | # $DECOMP_ROOT 6 | self.variables = {} 7 | self.paths = {} 8 | 9 | def fromFile(self, path): 10 | with open(path, "r") as file: 11 | j = json.load(file) 12 | self.variables = j["variables"] 13 | self.paths = j["paths"] -------------------------------------------------------------------------------- /demo/modules/prologue/prologue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // deprecated patch history 6 | #if 0 7 | //! @brief What was at the address before it was patched. internally array of 30 entries 8 | struct patchEntry 9 | { 10 | void* addr; 11 | u32 oldVal; 12 | }; 13 | #define NUM_PATCH_HISTORY 30 14 | patchEntry* getPatchEntries(); 15 | int getPatchCursor(); 16 | #endif 17 | 18 | namespace Prologue { 19 | 20 | //! @brief apply the patches 21 | void applyPatches(u32* block); 22 | 23 | } // namespace Prologue -------------------------------------------------------------------------------- /pokey_frontend.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from source.project_manager import ProjectManager 4 | 5 | parser = argparse.ArgumentParser(description='Pokey frontend') 6 | 7 | parser.add_argument('project_path', type=str, help='Directory of project to build') 8 | parser.add_argument('build_mode', type=str, help='Build mode. debug or release') 9 | parser.add_argument('--verbose', action='store_true') 10 | 11 | args = parser.parse_args() 12 | 13 | ProjectManager(args.project_path, args.verbose).build( 14 | args.build_mode == "release") 15 | -------------------------------------------------------------------------------- /runtime/mkw/v2.0/build.sh: -------------------------------------------------------------------------------- 1 | # Based on kamek loader build script 2 | 3 | CW_PATH=../../tool/ 4 | CPPFILES="bootstrap loader" 5 | 6 | CC=$CW_PATH/mwcceppc 7 | CFLAGS="-i . -I- -i ../../ -Cpp_exceptions off -enum int -Os -use_lmw_stmw on -fp hard -rostr -sdata 0 -sdata2 0" 8 | 9 | for i in $CPPFILES 10 | do 11 | echo Compiling $i.cpp... 12 | $CC $CFLAGS -c -o $i.o $i.cpp 13 | done 14 | 15 | echo Linking... 16 | 17 | ../../tool/Kamek.exe bootstrap.o loader.o -static=0x80218FF0 -output-gecko=loader.txt -externals="../externals/RMCP01.txt" -input-dol="../../tool/main.dol" -output-dol=comet.dol -------------------------------------------------------------------------------- /demo/externals/RMCP01.txt: -------------------------------------------------------------------------------- 1 | OSReport=0x801A25D0 2 | OSDisableInterrupts=801A65AC 3 | OSEnableInterrupts=0x801A65C0 4 | OSRestoreInterrupts=0x801A65D4 5 | _savegpr_14=0x8002156C 6 | _savegpr_15=0x80021570 7 | _savegpr_16=0x80021574 8 | _savegpr_17=0x80021578 9 | _savegpr_18=0x8002157C 10 | _savegpr_19=0x80021580 11 | _savegpr_20=0x80021584 12 | _savegpr_21=0x80021588 13 | _savegpr_22=0x8002158C 14 | _savegpr_23=0x80021590 15 | _savegpr_24=0x80021594 16 | _savegpr_25=0x80021598 17 | _savegpr_26=0x8002159C 18 | _savegpr_27=0x800215A0 19 | _savegpr_28=0x800215A4 20 | _savegpr_29=0x800215A8 21 | _restgpr_14=0x800215B8 22 | _restgpr_21=0x800215D4 23 | _restgpr_22=0x800215D8 24 | _restgpr_23=0x800215DC 25 | _restgpr_24=0x800215E0 26 | _restgpr_25=0x800215E4 27 | _restgpr_26=0x800215E8 28 | _restgpr_27=0x800215EC 29 | _restgpr_28=0x800215F0 30 | _restgpr_29=0x800215F4 31 | -------------------------------------------------------------------------------- /runtime/mkw/v1.0/mkw_heap_runtime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | kmBranchDefCpp(0x80007B5C, bool, System::ModuleLinker* linker, int moduleID) 12 | { 13 | u8* block; 14 | // Before we call the module, let's add our code 15 | EGG::ExpHeap* pHeap = getMem1Heap(); 16 | 17 | 18 | if (!pHeap) 19 | DebugReport("Could not acquire MEM1 heap!"); 20 | else if (!(block = EGG::DvdRipper::loadToMainRAM(PATH_CODE_BIN, NULL, pHeap, EGG::DvdRipper::ALLOC_DIR_TOP, 0, NULL, NULL))) 21 | DebugReport("Couldn't rip file!"); 22 | else if ((u32)block != 0x809c4fa0) 23 | { 24 | DebugReport("Block allocated at unexpected location (%p)!\n", block); 25 | pHeap->free(block); 26 | } 27 | else 28 | { 29 | DebugReport("Block successfully allocated at expected location!\n"); 30 | ((u32(*)(void))block)(); 31 | } 32 | 33 | // Call down to the original module 34 | linker->callModule(moduleID); 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 | -------------------------------------------------------------------------------- /demo/modules/demo/demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Demo { 4 | 5 | void prologue() 6 | { 7 | // Disable multitasking while recording the prior interrupt state. 8 | bool iState = OSDisableInterrupts(); 9 | 10 | DebugReport("Applying debug bar...\n"); 11 | 12 | // Modify control flow to enable the bar. 13 | applyProcessBar(); 14 | 15 | DebugReport("...Applied!\n"); 16 | 17 | // Restore our previous interrupt state. 18 | OSRestoreInterrupts(iState); 19 | } 20 | 21 | 22 | /*------------------------------------------------------- 23 | 24 | NOTE: 25 | - Patches are only done this way for demonstration. 26 | - Using kamek macros would be preferable! 27 | 28 | -------------------------------------------------------*/ 29 | 30 | 31 | 32 | inline void Write32(void* addr, u32 val) 33 | { __asm { 34 | stw r4, 0(r3); // store value 35 | 36 | // update cache 37 | dcbf 0, r3; 38 | sync; 39 | icbi 0, r3; 40 | isync; 41 | } } 42 | 43 | void applyProcessBar() 44 | { 45 | /* 46 | $Process Bar (Riidefi) (PAL) 47 | 04238F14 4E800020 48 | 04009600 48239100 49 | 0424271C 4BDC6EF0 50 | 0400979C 4823912C 51 | 042428E4 4BDC6EC4 52 | */ 53 | Write32((void*)0x80009600, 0x48239100); 54 | Write32((void*)0x8024271C, 0x4BDC6EF0); 55 | Write32((void*)0x8000979C, 0x4823912C); 56 | Write32((void*)0x802428E4, 0x4BDC6EC4); 57 | } 58 | 59 | } // namespace Demo -------------------------------------------------------------------------------- /source/patch_generator/__init__.py: -------------------------------------------------------------------------------- 1 | from struct import pack 2 | 3 | 4 | class PatchGenerator: 5 | def __init__(self): 6 | self.output = [] # Reset our output buffer 7 | self.writes = [] # Reset our gathered write lines 8 | 9 | def parse_gecko(self, lines): 10 | """ 11 | Parse a Kamek generated Gecko file to gather writes. 12 | 13 | :param lines: The lines of the gecko file. 14 | """ 15 | primary_payload_size = int(lines[0].split()[1], 16) / 8 + 1 16 | self.writes = [line.split() for line in lines[int(primary_payload_size):]] 17 | 18 | def process(self): 19 | """ 20 | Produce the internal structure based on write lists. 21 | """ 22 | self.output = [] # Reset output buffer 23 | for write in self.writes: 24 | w0 = write[0] 25 | if w0[:2] != "04": 26 | print("[WARN] Only 32 bit writes are currently supported!") 27 | continue 28 | self.output += [int("80" + w0[2:], 16), int(write[1], 16)] 29 | self.output += [0, 0] # Append termination sequence 30 | 31 | def write_to_file(self, path): 32 | """ 33 | Write the result to a file. 34 | 35 | :param path: Path to the file on disc. 36 | """ 37 | with open(path, "wb") as file: 38 | for u in self.output: 39 | file.write(pack('>L', u)) 40 | -------------------------------------------------------------------------------- /runtime/mkw/v2.0/loader.cpp: -------------------------------------------------------------------------------- 1 | #define DEBUG 2 | 3 | #include "standalone.hpp" 4 | #include 5 | #include 6 | 7 | 8 | 9 | inline 10 | EGG::Heap* getMem1Heap() 11 | { 12 | __asm { 13 | lwz r3, -0x6C38(r13); 14 | lwz r3, 0x18(r3); 15 | } 16 | } 17 | 18 | kmCallDefCpp(0x80007B5C, bool, System::ModuleLinker* linker, int moduleID) 19 | { 20 | void* block; 21 | DVDFileInfo fileInfo; 22 | u32 fileLenRounded; 23 | // Before we call the module, let's add our code 24 | EGG::Heap* pHeap = getMem1Heap(); 25 | 26 | if (pHeap) 27 | { 28 | DebugReport("Loaded heap!\n"); 29 | } 30 | else 31 | { 32 | DebugReport("Could not acquire MEM1 heap!\n"); 33 | goto out; 34 | } 35 | if (!DVDOpen(PATH_CODE_BIN, &fileInfo)) 36 | { 37 | DebugReport("Failed to open code file!\n"); 38 | goto out; 39 | } 40 | 41 | fileLenRounded = (((u32*)&fileInfo)[13] + 31) & ~31; 42 | DebugReport("Expecting %u bytes...\n", fileLenRounded); 43 | 44 | block = EGG::Heap::alloc(fileLenRounded + 2048, 32, pHeap); 45 | 46 | if ((u32)block != 0x809c4fa0) 47 | { 48 | DebugReport("Block allocated at unexpected location (%p)!\n", block); 49 | 50 | u32 dif = 0x809c4fa0 - (u32)block; 51 | 52 | if (dif <= 2048) 53 | { 54 | DebugReport("Off by %u.. within 2048 room for error\n", dif); 55 | block = (void*)0x809c4fa0; 56 | goto call; 57 | } 58 | 59 | pHeap->free(block); 60 | goto out; 61 | } 62 | else 63 | { 64 | call: 65 | DebugReport("Block successfully allocated at expected location!\n"); 66 | DVDReadPrio(&fileInfo, block, fileLenRounded, 0, 2); 67 | DVDClose(&fileInfo); 68 | ((u32(*)(void))block)(); 69 | } 70 | 71 | 72 | 73 | 74 | out: 75 | // Call down to the original module 76 | return linker->callModule(moduleID); 77 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | *.o 9 | *.a 10 | *.lib 11 | *.exe 12 | 13 | 14 | # Pycharm 15 | .idea/ 16 | *.xml 17 | *.iml 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | # conflicts with demo lib folder 28 | # lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | env.bak/ 102 | venv.bak/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | 117 | *.txt 118 | 119 | *.id* 120 | *.nam 121 | *.til 122 | *.bin 123 | 124 | *.dol -------------------------------------------------------------------------------- /runtime/mkw/v2.0/bootstrap.cpp: -------------------------------------------------------------------------------- 1 | #include "standalone.hpp" 2 | #include 3 | // filepaths from all regions combined 4 | static const char* BootStrapFilePaths[] = { 5 | "/Boot/Strap/jp/jp.szs", // 0 6 | "/Boot/Strap/eu/English.szs", // 1 7 | "/Boot/Strap/eu/German.szs", // 2 8 | "/Boot/Strap/eu/French.szs", // 3 9 | // "/Boot/Strap/eu/Spanish_US.szs" 10 | "/Boot/Strap/eu/Spanish_EU.szs", // 4 11 | "/Boot/Strap/eu/Italian.szs", // 5 12 | "/Boot/Strap/eu/Dutch.szs" // 6 13 | }; 14 | 15 | const char** getBootStrapFilePaths() 16 | { 17 | return BootStrapFilePaths; 18 | } 19 | 20 | u8* bootStrapLoad(System::SystemManager* pManager, char* path, EGG::Heap* heap, bool allocTop, u32* fsizeOutput) 21 | { 22 | DVDFileInfo fInfo; 23 | 24 | 25 | if (DVDOpen(path, &fInfo)) 26 | goto out; 27 | 28 | // Support for US Spanish 29 | if (!strcmp(path, BootStrapFilePaths[4])) 30 | { 31 | path = (char*)"/Boot/Strap/us/Spanish_US.szs"; 32 | if (DVDOpen(path, &fInfo)) 33 | goto out; 34 | } 35 | // Support for US French 36 | if (!strcmp(path, BootStrapFilePaths[3])) 37 | { 38 | path = (char*)"/Boot/Strap/us/French.szs"; 39 | if (DVDOpen(path, &fInfo)) 40 | goto out; 41 | } 42 | 43 | path = (char*)BootStrapFilePaths[1]; // Default to EU English 44 | if (DVDOpen(path, &fInfo)) 45 | goto out; 46 | 47 | path = (char*)"/Boot/Strap/us/English.szs"; // US english 48 | if (DVDOpen(path, &fInfo)) 49 | goto out; 50 | 51 | path = (char*)BootStrapFilePaths[0]; // Japan 52 | if (DVDOpen(path, &fInfo)) 53 | goto out; 54 | 55 | // Fatal: file doesn't exist 56 | 57 | { 58 | u32 fg = 0x000000ff, bg = 0xffffffff; 59 | Fatal(&fg, &bg, "Failed to load bootstrap file!\n"); 60 | return 0; 61 | } 62 | 63 | 64 | out: 65 | DVDClose(&fInfo); 66 | return pManager->ripFromDisc((const char*)path, heap, allocTop, fsizeOutput); 67 | } 68 | // Overwrite load to game table.. needs to load to r25 not r3 69 | //PokeyCall(0x80007500, getBootStrapFilePaths); 70 | 71 | // Overwrite call to rip file 72 | PokeyCall(0x80007528, bootStrapLoad); 73 | -------------------------------------------------------------------------------- /demo/modules/reload/reload.cpp: -------------------------------------------------------------------------------- 1 | #include "reload.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace Reload { 11 | #ifdef DEBUG 12 | 13 | void reload() 14 | { 15 | BOOL iState = OSDisableInterrupts(); 16 | 17 | DVDFileInfo fileInfo; 18 | bool success = false; 19 | u8* block = (u8*)0x809c4fa0;//getBlock(); 20 | 21 | PokeyDebugReport("block: %p\n", block); 22 | 23 | 24 | 25 | 26 | PokeyVerboseReport("---\nRELOADING\n---\n"); 27 | 28 | if (DVDOpen(PATH_PATCH_BIN, &fileInfo)) 29 | { 30 | u32 fileLen = fileInfo.length; 31 | PokeyDebugReport("File len: %u\n", fileLen); 32 | u32 fileLen32 = OSRoundUp32B(fileLen); 33 | PokeyDebugReport("File len (rounded): %u\n", fileLen32); 34 | u32 amountRead = DVDRead(&fileInfo, block, fileLen32, 0); 35 | DVDClose(&fileInfo); 36 | if (fileLen <= amountRead) 37 | { 38 | PokeyDebugReport("Loaded file!\n"); 39 | success = true; 40 | } 41 | } 42 | if (success) 43 | { 44 | #if 0 45 | patchEntry* entry = getPatchEntries(); 46 | 47 | PokeyDebugReport("--- Rollback %u patches ---\n", getPatchCursor()); 48 | for (int i = 0; i < getPatchCursor(); i++) 49 | { 50 | PokeyDebugReport("-> %p was 0x%08x now 0x%08x. reverting.\n", entry->addr, entry->oldVal, *(u32*)entry->addr); 51 | doPatch32((void*)entry); 52 | entry++; 53 | } 54 | #endif 55 | } 56 | else 57 | { 58 | PokeyDebugReport("Reload Failed!\n"); 59 | } 60 | 61 | 62 | PokeyDebugReport("Calling prologue: %p\n", block); 63 | 64 | 65 | // Call the prologue, again. this will load patches 66 | ((u32(*)(void))block)(); 67 | 68 | PokeyDebugReport("Reload Success!\n"); 69 | OSRestoreInterrupts(iState); 70 | } 71 | 72 | #if 1 73 | void loopAdditions() 74 | { 75 | PADStatus padStatusArr[PAD_MAX_CONTROLLERS]; 76 | 77 | PADRead(padStatusArr); 78 | if (padStatusArr[0].button & PAD_BUTTON_X) 79 | { 80 | reload(); 81 | } 82 | 83 | } 84 | PokeyBranch(0x80009824, loopAdditions); 85 | #endif 86 | 87 | 88 | #endif 89 | } // namespace Reload -------------------------------------------------------------------------------- /libpokey/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | #ifdef DEBUG 6 | 7 | //#define POKEY_ENABLE_VERBOSE_REPORT 8 | #define POKEY_ENABLE_DEBUG_REPORT 9 | // Critical problems that are not entirely unlikely to happen 10 | #define POKEY_ENABLE_CRITICAL_ASSERT 11 | // Unlikely critical problems 12 | #define POKEY_ENABLE_UNLIKELY 13 | // Nonfatal problems 14 | #define POKEY_ENABLE_CHECK 15 | 16 | 17 | // Display asserts on-screen 18 | // Useful for debugging on retail wiis 19 | #define POKEY_VISUAL_FAIL 20 | 21 | 22 | #define POKEY_BG_COLOR (GXColor) {00, 0x1b, 0x58 } 23 | #define POKEY_FG_COLOR (GXColor) { 0xff,0xff,0xff, 0xff } 24 | 25 | #define POKEY_FATAL_HEADER "" 26 | #define POKEY_FATAL_FOOTER "\n\n\nPokey v1.0 by Riidefi\n" 27 | #endif 28 | 29 | // backwards compat 30 | #define DebugAssert PokeyCriticalAssert 31 | #define DebugReport PokeyDebugReport 32 | 33 | // https://stackoverflow.com/questions/2670816/how-can-i-use-the-compile-time-constant-line-in-a-string 34 | #define STRINGIZE(x) STRINGIZE2(x) 35 | #define STRINGIZE2(x) #x 36 | #define LINE_STRING STRINGIZE(__LINE__) 37 | 38 | // todo: clean up 39 | #ifndef POKEY_VISUAL_FAIL 40 | #define PokeyAssert(exp) (void) ((exp) || (PokeyFailAssert(__FILE__, __LINE__, "Failed Assert: " #exp), 0)) 41 | #define PokeyFailAssert(file, line, msg) OSPanic(file, line, msg) 42 | #else 43 | #define PokeyAssert(exp) (void) ((exp) || (PokeyFailAssert(__FILE__, LINE_STRING, "Failed Assert: " #exp), 0)) 44 | #define PokeyFailAssert(file, line, msg) OSFatal(POKEY_FG_COLOR, POKEY_BG_COLOR, POKEY_FATAL_HEADER msg "\nin " file ":" line POKEY_FATAL_FOOTER ) 45 | #endif 46 | 47 | 48 | #define PokeyPass(...) ((void) 0) 49 | 50 | 51 | #ifdef POKEY_ENABLE_DEBUG_REPORT 52 | #define PokeyDebugReport(...) OSReport("["__FILE__ ":" LINE_STRING "] " __VA_ARGS__ ) 53 | #else 54 | #define PokeyDebugReport PokeyPass 55 | #endif 56 | 57 | #ifdef POKEY_ENABLE_VERBOSE_REPORT 58 | #define PokeyVerboseReport PokeyDebugReport 59 | #else 60 | #define PokeyVerboseReport PokeyPass 61 | #endif 62 | 63 | #ifdef POKEY_ENABLE_CRITICAL_ASSERT 64 | #define PokeyCriticalAssert PokeyAssert 65 | #else 66 | #define PokeyCriticalAssert PokeyPass 67 | #endif 68 | #ifdef POKEY_ENABLE_UNLIKELY 69 | #define PokeyUnlikelyAssert PokeyAssert 70 | #else 71 | #define PokeyUnlikelyAssert PokeyPass 72 | #endif 73 | #ifdef POKEY_ENABLE_CHECK 74 | #define PokeyCheck PokeyAssert 75 | #else 76 | #define PokeyCheck PokeyPass 77 | #endif -------------------------------------------------------------------------------- /runtime/mkw/v2.0/standalone.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | extern "C" void OSReport(const char* str, ...); 5 | 6 | typedef unsigned long u32; 7 | typedef unsigned char u8; 8 | #define NULL 0 9 | #define kmCall PokeyCall 10 | #ifdef DEBUG 11 | // https://stackoverflow.com/questions/2670816/how-can-i-use-the-compile-time-constant-line-in-a-string 12 | #define STRINGIZE(x) STRINGIZE2(x) 13 | #define STRINGIZE2(x) #x 14 | #define LINE_STRING STRINGIZE(__LINE__) 15 | #define DebugReport(...) OSReport("["__FILE__ ":" LINE_STRING "] " __VA_ARGS__ ) 16 | #else 17 | #define DebugReport(...) ((void) 0) 18 | #endif 19 | 20 | 21 | // points to OSFatal with colors passed by structure (EABI calls to pass as pointer to callee stack) 22 | extern "C" void Fatal(u32* fg, u32* bg, const char* msg); 23 | 24 | namespace EGG 25 | { 26 | struct Heap 27 | { 28 | virtual ~Heap(); 29 | virtual int getHeapKind() const = 0; 30 | virtual void initAllocator(void* allocator, int align) = 0; 31 | virtual void* alloc(u32 size, int align) = 0; 32 | virtual void free(void* block) = 0; 33 | virtual void destroy() = 0; 34 | virtual u32 resizeForMBlock(void* block, u32 size) = 0; 35 | virtual u32 getAllocatableSize(int align) = 0; 36 | virtual u32 adjust() = 0; 37 | static void* alloc(u32 size, int align, Heap* heap); 38 | }; 39 | 40 | struct DvdRipper { 41 | 42 | //! @brief Describes the direction of allocating new blocks in a free memory region. 43 | //! 44 | enum EAllocDirection 45 | { 46 | ALLOC_DIR_PAD, // Unseen/unhandled so far 47 | ALLOC_DIR_TOP, //!< [1] Negative alignment; allocate block from top of free block. 48 | ALLOC_DIR_BOTTOM //!< [2] Positive alignment; allocate block from bottom of free block. 49 | }; 50 | static u8* loadToMainRAM(const char* path, u8* dst, Heap* pHeap, EAllocDirection allocDirection, u32 offset, u32* pAmountRead, u32* pFileSize); 51 | }; 52 | } 53 | namespace System 54 | { 55 | struct ModuleLinker 56 | { 57 | bool callModule(int moduleID); 58 | }; 59 | struct SystemManager 60 | { 61 | static u8* ripFromDisc(const char path[64], EGG::Heap* heap, bool allocTop, u32* fsizeOutput); 62 | }; 63 | } 64 | extern "C" int strcmp( 65 | char const* _Str1, 66 | char const* _Str2 67 | ); 68 | struct DVDFileInfo 69 | { 70 | u8 _[0x3C]; 71 | }; 72 | extern "C" int DVDOpen(const char* path, DVDFileInfo* fInfo); 73 | extern "C" void DVDClose(DVDFileInfo* fInfo); 74 | extern "C" int DVDReadPrio(DVDFileInfo* fInfo, void* addr, int len, int offs, int prio); 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Work In Progress 2 |

3 | 4 |

5 | 6 | ## How does it work? 7 | Pokey aims to seamlessly integrate custom C and C++ code into existing games. Custom code is loaded by a game-specific runtime, which can be injected directly into the dol natively or used as a code. 8 | 9 | ## Extending Game Classes 10 | Pokey supports extending game classes with proper multiple inheritance and virtual function support. Programmers can effortlessly write C++ classes for new game elements as they were intended to be written. 11 | 12 | ### Runtime 13 | The runtime must load the compiled code file. Runtime 2.0 also comes with Universal Bootstrap, allowing a PAL dol to boot on other region discs. A demo project that implements the patch files and much more has been included (see section below). 14 | 15 | ## Project Format 16 | 17 | ### Project Configuration File 18 | The project configuration file (`project.json`) ties everything together, including: the module registration list, global includes, the path to external symbols, and the static base to link code at. 19 | 20 | ### The Library (`lib`) Folder 21 | The library folder houses module-independent code and external precompiled object files which will be compiled and linked when building. The library `build.json` file directs this process. 22 | 23 | ### Modules 24 | Pokey projects are divided into modules. Each module must have a `module.json` file, which enumerates source files to build and (optional) additional includes. 25 | 26 | ## Usage 27 | To build a project: 28 | ``` 29 | pokey_frontend.py [debug|release] 30 | ``` 31 | ### Hooks 32 | In any source file, to insert a call (bl) to a defined function: 33 | ``` 34 | PokeyCall(dest, symbol); 35 | ``` 36 | To branch (b): 37 | ``` 38 | PokeyBranch(dest, symbol); 39 | ``` 40 | To write a 32 bit value: 41 | ``` 42 | PokeyWrite32(dest, value); 43 | PokeyWritePointer(dest, value); 44 | ``` 45 | More information can be found on Kamek's page. 46 | 47 | ## Demo 48 | An easy to follow, C demo project has been provided. This project is for the PAL release of Mario Kart Wii. A more sophisticated, C++ project using Pokey can be found here. 49 | 50 | ## Dependencies 51 | - Pokey runs on Python 3 52 | - Code is linked with Kamek by Treeki 53 | - Code is compiled with Freescale 54 | CodeWarrior Special Edition for MPC55xx/MPC56xx v2.10 55 | 56 | ## Credits 57 | - Treeki for his Kamek linker without which this would not be possible 58 | - Zachruff for the lovely banner 59 | -------------------------------------------------------------------------------- /demo/modules/prologue/prologue.cpp: -------------------------------------------------------------------------------- 1 | #include "prologue.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // for ctors 13 | #include 14 | 15 | namespace Prologue { 16 | 17 | void applyPatches(u32* block); 18 | void doPatch32(void* patch); 19 | 20 | void ctor_top(); 21 | 22 | void* __constructors[] = 23 | { 24 | ctor_head, 25 | 26 | // Debug module init function 27 | Demo::prologue, 28 | 29 | ctor_tail, 30 | 0 // NULL-terminated 31 | }; 32 | 33 | 34 | // The first function will be the entry point. 35 | void prologue() 36 | { 37 | u32 patch_block[30 * 2 + (8)]; 38 | DVDFileInfo fileInfo; 39 | BOOL iState = OSDisableInterrupts(); 40 | u32 amountRead; 41 | void* buf = (void*)OSRoundUp32B((u32)&patch_block); 42 | 43 | // Read patches from disc 44 | if (DVDOpen(PATH_PATCH_BIN, &fileInfo)) 45 | { 46 | u32 fileLen = fileInfo.length; 47 | 48 | u32 fileLen32 = OSRoundUp32B(fileLen); 49 | 50 | if (fileLen32 > 30 * 2 * 4) 51 | { 52 | DebugReport("Too many patches (max 30)!\n"); 53 | goto fail; 54 | } 55 | 56 | amountRead = DVDRead(&fileInfo, buf, fileLen32, 0); 57 | DVDClose(&fileInfo); 58 | if (fileLen32 > amountRead) 59 | { 60 | fail: 61 | DebugReport("Failed to load PATCH.bin!\n"); 62 | } 63 | else 64 | { 65 | // NULL terminated 66 | applyPatches((u32*)buf); 67 | DebugReport("Success!\n"); 68 | } 69 | } 70 | 71 | // call prologue functions 72 | for (u32* f = (u32*)__constructors; *f; f++) 73 | ((void(*)(void))*f)(); 74 | DebugReport("DONE\n"); 75 | 76 | RustTest(); 77 | 78 | 79 | OSRestoreInterrupts(iState); 80 | } 81 | void ctor_head() 82 | { 83 | DebugReport("Calling constructors...\n"); 84 | } 85 | void ctor_tail() 86 | { 87 | DebugReport("...done!\n"); 88 | } 89 | 90 | #if 0 91 | // ensure enough space for all patches 92 | 93 | patchEntry patchRecord[NUM_PATCH_HISTORY]; 94 | int patchCursor = 0; 95 | 96 | patchEntry* getPatchEntries() 97 | { 98 | return &patchRecord[0]; 99 | } 100 | int getPatchCursor() 101 | { 102 | return patchCursor; 103 | } 104 | 105 | #endif 106 | inline void applyPatches(u32* block) 107 | { 108 | int i; 109 | for (i = 0; block[i]; i += 2) 110 | { 111 | u32* dst = (u32*)block[i]; 112 | u32 val = block[i + 1]; 113 | #if 0 114 | patchRecord[patchCursor].addr = dst; 115 | patchRecord[patchCursor].oldVal = *dst; 116 | patchCursor++; 117 | #endif 118 | DebugReport("Patching 32: %p = 0x%08x\n", dst, val); 119 | doPatch32(&block[i]); 120 | } 121 | DebugReport("Made %u patches!\n", i / 2); 122 | } 123 | inline void doPatch32(void* patch) 124 | { 125 | 126 | __asm 127 | { 128 | lwz r4, 4(r3) // value 129 | lwz r3, 0(r3) // address to write to 130 | stw r4, 0(r3) 131 | 132 | // cache 133 | dcbf 0, r3 134 | sync 135 | icbi 0, r3 136 | isync 137 | } 138 | } 139 | 140 | } // namespace Prologue -------------------------------------------------------------------------------- /libpokey/hooks.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file 3 | * @brief Updated kamek hooks. 4 | * 5 | * @details Key differences from original: 6 | * - Macros renamed to be consistent with rest of the Pokey standard library. 7 | * - Runtime 32 bit patch restriction enforcement. 8 | * - Fully documented 9 | */ 10 | 11 | #pragma once 12 | 13 | // allow Kamek hooks to be defined from C++ source files 14 | #pragma section ".kamek" 15 | 16 | // hook type IDs _must_ match what's in the Kamek source! 17 | #define kctWrite 1 18 | #define kctConditionalWrite 2 19 | #define kctInjectBranch 3 20 | #define kctInjectCall 4 21 | #define kctPatchExit 5 22 | 23 | #define kmIdentifier(key, counter) \ 24 | _k##key##counter 25 | #define kmHookInt(counter) \ 26 | __declspec (section ".kamek") static const u32 kmIdentifier(Hook, counter) 27 | 28 | #define kmHook0(type) \ 29 | kmHookInt(__COUNTER__)[2] = { 0, (type) } 30 | #define kmHook1(type, arg0) \ 31 | kmHookInt(__COUNTER__)[3] = { 1, (type), (u32)(arg0) } 32 | #define kmHook2(type, arg0, arg1) \ 33 | kmHookInt(__COUNTER__)[4] = { 2, (type), (u32)(arg0), (u32)(arg1) } 34 | #define kmHook3(type, arg0, arg1, arg2) \ 35 | kmHookInt(__COUNTER__)[5] = { 3, (type), (u32)(arg0), (u32)(arg1), (u32)(arg2) } 36 | #define kmHook4(type, arg0, arg1, arg2, arg3) \ 37 | kmHookInt(__COUNTER__)[6] = { 4, (type), (u32)(arg0), (u32)(arg1), (u32)(arg2), (u32)(arg3) } 38 | 39 | #define PokeyCondWritePointer(addr, original, value) kmHook4(kctConditionalWrite, 1, (addr), (value), (original)) 40 | #define PokeyCondWrite32(addr, original, value) kmHook4(kctConditionalWrite, 2, (addr), (value), (original)) 41 | 42 | //! @brief Write a pointer at a specified address 43 | //! 44 | //! @param[in] addr Address to write the pointer at. 45 | //! @param[in] ptr Pointer to write. 46 | //! 47 | #define PokeyWritePointer(addr, ptr) kmHook3(kctWrite, 1, (addr), (ptr)) 48 | 49 | //! @brief Write a 32 bit value at a specified address 50 | //! 51 | //! @param[in] addr Address to write the pointer at. 52 | //! @param[in] ptr 32 bit value to write. 53 | //! 54 | #define PokeyWrite32(addr, value) kmHook3(kctWrite, 2, (addr), (value)) 55 | 56 | //! @brief Write a branch (b) instruction at a specified address. 57 | //! 58 | //! @param[in] addr Address to write the pointer at (branch start). 59 | //! @param[in] ptr Where to branch to (branch end). 60 | //! 61 | #define PokeyBranch(addr, ptr) kmHook2(kctInjectBranch, (addr), (ptr)) 62 | 63 | //! @brief Write a branch with link (bl) instruction at a specified address. 64 | //! 65 | //! @param[in] addr Address to write the pointer at (branch start). 66 | //! @param[in] ptr Where to branch to (branch end). 67 | //! 68 | #define PokeyCall(addr, ptr) kmHook2(kctInjectCall, (addr), (ptr)) 69 | 70 | // kmPatchExitPoint 71 | // Force the end of a Kamek function to always jump to a specific address 72 | // (if the address is 0, the end remains as-is (i.e. blr)) 73 | #define kmPatchExitPoint(funcStart, dest) kmHook2(kctPatchExit, (funcStart), (dest)) 74 | 75 | 76 | 77 | // kmBranchDefCpp, kmBranchDefAsm 78 | // Set up a branch (b) from a specific instruction to a function defined 79 | // directly underneath. If exitPoint is not NULL, the function will 80 | // branch to exitPoint when done; otherwise, it executes blr as normal 81 | #define kmBranchDefInt(counter, addr, exitPoint, returnType, ...) \ 82 | returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__); \ 83 | kmBranch(addr, kmIdentifier(UserFunc, counter)); \ 84 | kmPatchExitPoint(kmIdentifier(UserFunc, counter), exitPoint); \ 85 | returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__) 86 | 87 | #define kmBranchDefCpp(addr, exitPoint, returnType, ...) \ 88 | kmBranchDefInt(__COUNTER__, addr, exitPoint, returnType, __VA_ARGS__) 89 | #define kmBranchDefAsm(addr, exitPoint) \ 90 | kmBranchDefInt(__COUNTER__, addr, exitPoint, asm void, ) 91 | 92 | // kmCallDefCpp, kmCallDefAsm 93 | // Set up a branch with link (bl) from a specific instruction to a function 94 | // defined directly underneath. 95 | #define kmCallDefInt(counter, addr, returnType, ...) \ 96 | returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__); \ 97 | kmCall(addr, kmIdentifier(UserFunc, counter)); \ 98 | returnType kmIdentifier(UserFunc, counter) (__VA_ARGS__) 99 | 100 | #define kmCallDefCpp(addr, returnType, ...) \ 101 | kmCallDefInt(__COUNTER__, addr, returnType, __VA_ARGS__) 102 | #define kmCallDefAsm(addr) \ 103 | kmCallDefInt(__COUNTER__, addr, asm void, ) 104 | -------------------------------------------------------------------------------- /source/project_manager/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import subprocess 4 | from shutil import copyfile 5 | 6 | from source.preprocessor import process_string 7 | from source.config import Config 8 | from source.patch_generator import PatchGenerator 9 | 10 | cwFlags = "-proc gekko -I- -Cpp_exceptions off -enum int -Os -use_lmw_stmw on -fp hard -rostr -sdata 0 -sdata2 0" 11 | cwFlags += " -DRVL -DNDEBUG -DRVL_SDK -DTRK_INTEGRATION -DRVL_OS -DEPPC -DGEKKO -DHOLLYWOOD_REV=1 -DBROADWAY_REV=1 -DIOP_REV=1 -DNW4R_RELEASE -DNW4R_CW3 -DNW4R_RVL" 12 | 13 | 14 | class ProjectManager: 15 | def __init__(self, path, verbose=False): 16 | # Set our project path 17 | self.project_path = path 18 | 19 | self.verbose = verbose 20 | 21 | # Load our configuration 22 | with open(os.path.join(path, "project.json"), "r") as file: 23 | self.project_cfg = json.load(file) 24 | 25 | self.global_config = Config() 26 | self.global_config.fromFile(os.path.join(self.project_path, "..\\global_config.json")) 27 | 28 | # Process our project includes ahead of time 29 | self.project_includes = " ".join(("-I%s" % process_string(self.global_config, i)) for i in self.project_cfg["includes"]) 30 | 31 | self.project_includes += " -I" + self.project_path 32 | 33 | def build_library(self, release=False): 34 | """ 35 | Build our library components. 36 | 37 | :param release: Whether or not to build for release. 38 | :return: List of library objects 39 | """ 40 | 41 | verbose = False 42 | 43 | # Save our working directory 44 | cwd = os.getcwd() 45 | 46 | # Set our current directory to our project path 47 | os.chdir(self.project_path) 48 | 49 | result = [] 50 | 51 | # If our library bin folder does not exit, create it 52 | if not os.path.isdir("./lib/bin"): 53 | os.mkdir("./lib/bin") 54 | 55 | # Arguments for our compiler 56 | cwArg = cwFlags + " " + self.project_includes + " -I%s\\lib\\" % self.project_path 57 | 58 | # pass down our build mode 59 | cwArg += " -DRELEASE" if release else " -DDEBUG" 60 | 61 | with open(self.project_path + "\\lib\\build.json", "r") as makefile: 62 | lib_make = json.load(makefile) 63 | os.chdir(self.project_path + "\\..\\tool\\") 64 | for file in lib_make["sources"]: 65 | # TODO: bin subfolder not auto created! 66 | compiler_string = "./mwcceppc.exe %s -c -o %s/lib/bin/%s.o %s/lib/%s" % ( 67 | cwArg, self.project_path, file, self.project_path, file) 68 | if self.verbose: 69 | print(compiler_string) 70 | if subprocess.call(compiler_string): 71 | raise RuntimeError("[FATAL] Failed to compile %s!", file) 72 | else: 73 | result.append("%s/lib/bin/%s.o" % (self.project_path, file)) 74 | 75 | # Restore our working directory 76 | os.chdir(cwd) 77 | 78 | self.library_objects = result + lib_make["objects"] 79 | 80 | return self.library_objects 81 | 82 | def compile_module(self, module, release=False): 83 | result = [] 84 | module_cfg = None 85 | 86 | # Save our working directory 87 | cwd = os.getcwd() 88 | 89 | # Set our current directory to our project path 90 | os.chdir(self.project_path) 91 | 92 | # Load our module config 93 | module_cfg = json.load(open("./modules/%s/module.json" % module, "r")) 94 | 95 | if self.verbose: 96 | print("Compiling module %s..." % module) 97 | 98 | # Create our module bin folder 99 | if not os.path.isdir("./modules/%s/bin" % module): 100 | os.mkdir("./modules/%s/bin" % module) 101 | 102 | # Construct our compiler arguments 103 | cwArg = cwFlags + " " + self.project_includes + " " + " ".join( 104 | "-I%s" % (process_string(self.global_config, i)) for i in module_cfg["includes"]) + " -I%s\\modules\\%s\\" % ( 105 | self.project_path, module) 106 | 107 | # pass down our build mode 108 | cwArg += " -DRELEASE" if release else " -DDEBUG" 109 | 110 | os.chdir(self.project_path + "\\..\\tool\\") 111 | for file in module_cfg["sources"]: 112 | print("...compiling %s" % file) 113 | compiler_string = "./mwcceppc.exe %s -c -o %s/modules/%s/bin/%s.o %s/modules/%s/%s" % ( 114 | cwArg, self.project_path, module, file, self.project_path, module, file) 115 | if self.verbose: 116 | print(compiler_string) 117 | if subprocess.call(compiler_string): 118 | raise RuntimeError("[FATAL] Failed to compile %s!", file) 119 | else: 120 | result.append("%s/modules/%s/bin/%s.o" % (self.project_path, module, file)) 121 | 122 | # Restore our working directory 123 | os.chdir(cwd) 124 | 125 | return result 126 | 127 | def apply_paddding_absolute(self, source_file, dest_filesize): 128 | source_file_read = open(source_file, 'rb').read() 129 | source_filesize = len(source_file_read) 130 | 131 | print("Applying padding...:\n\tSource Filesize: %s\n\tTarget Filesize: %s" % (source_filesize, dest_filesize)) 132 | 133 | if source_filesize > dest_filesize: 134 | raise ValueError("Source filesize exceeds target padded size. Consider adding compression to debug target or increasing target filesize.") 135 | elif source_filesize == dest_filesize: 136 | print("\n---\n\nSource file size matches target filesize. No padding applied.\n\n---\n") 137 | else: 138 | open(source_file, 'wb').write(source_file_read + bytes([0] * (dest_filesize - source_filesize))) 139 | 140 | def apply_paddding_relative(self, source_file, amount): 141 | source_file_read = open(source_file, 'rb').read() 142 | source_filesize = len(source_file_read) 143 | 144 | print("Applying padding...:\n\tSource Filesize: %s\n\tRelative Padding Size: %s\n\tComputed Target Filesize: %s" % (source_filesize, amount, source_filesize+amount)) 145 | 146 | open(source_file, 'wb').write(source_file_read + bytes([0] * amount)) 147 | 148 | def build(self, release=False): 149 | """ 150 | Build the project! 151 | 152 | :param release: Whether or not to build in release mode. 153 | """ 154 | 155 | mode = "release" if release else "debug" 156 | print("Building configuration: %s" % mode) 157 | 158 | # Compile all modules 159 | objects = [] 160 | 161 | for module in self.project_cfg["modules"]: 162 | objects += self.compile_module(module, release) 163 | 164 | objects += self.build_library(release) 165 | 166 | os.chdir(self.project_path + "/../tool/") 167 | 168 | code_file = self.project_path + "\\bin\\%s\\CODE.bin" % mode 169 | 170 | kamek_command = "Kamek.exe %s -static=%s -output-gecko=%s -output-code=%s -externals=%s/externals/%s" % ( 171 | " ".join(objects), self.project_cfg["static"], self.project_path + "\\bin\\gecko.txt", 172 | code_file, self.project_path, self.project_cfg["externals"]) 173 | if self.verbose: 174 | print(kamek_command) 175 | 176 | if subprocess.call(kamek_command): 177 | raise RuntimeError("Kamek fail") 178 | else: 179 | # TODO: this is far from optimal. add padding bytes for sbss/bss 180 | self.apply_paddding_relative(code_file, 1024) 181 | 182 | copyfile(code_file, self.global_config.paths["CODE_DEPLOY"]) 183 | with open(self.project_path + "\\bin\\gecko.txt", "r") as gecko: 184 | pg = PatchGenerator() 185 | pg.parse_gecko(gecko.readlines()) 186 | pg.process() 187 | pg.write_to_file(self.project_path + "\\bin\\PATCH.bin") 188 | 189 | copyfile(self.project_path + "\\bin\\PATCH.bin", 190 | self.global_config.paths["PATCH_DEPLOY"]) --------------------------------------------------------------------------------