├── .gitignore ├── LICENSE ├── README.md ├── configure.py ├── port.py ├── riivolution └── mkw-cs.xml ├── source ├── Common.h ├── Common.hxx ├── egg │ └── core │ │ ├── eggDisposer.hxx │ │ ├── eggExpHeap.hxx │ │ ├── eggHeap.hxx │ │ ├── eggScene.hxx │ │ └── eggSceneManager.hxx ├── game │ └── host_system │ │ ├── BootStrapScene.cxx │ │ ├── BootStrapScene.hxx │ │ ├── Dol.cxx │ │ ├── Dol.hxx │ │ ├── Loader.cxx │ │ ├── Patcher.cxx │ │ ├── Patcher.hxx │ │ ├── Payload.cxx │ │ ├── Rel.cxx │ │ ├── Rel.hxx │ │ ├── RkSystem.cxx │ │ ├── RkSystem.hxx │ │ ├── Scene.hxx │ │ ├── SceneCreatorStatic.cxx │ │ ├── SceneCreatorStatic.hxx │ │ ├── SceneManager.cxx │ │ └── SceneManager.hxx ├── rvl │ ├── dvd.h │ ├── gx │ │ └── GXStruct.h │ ├── os.h │ └── os │ │ ├── OSCache.h │ │ ├── OSModule.h │ │ └── OSThread.h └── stddef.h ├── symbols.txt └── vendor └── ninja_syntax.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | build.ninja 3 | __pycache__ 4 | tools 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Pablo Stebler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mkw-cs 2 | 3 | This project is abandoned in favor of [stblr/mkw-sp](https://github.com/stblr/mkw-sp). 4 | 5 | Client-server netcode for Mario Kart Wii, currently in early stages of development. 6 | 7 | ## Overview 8 | 9 | The project is in 4 parts: 10 | 11 | - A new protocol suited for client-server and compatibility with custom content. 12 | 13 | - A client patchset that implements its part of the protocol, lag compensation, as well as a fully 14 | custom multiplayer UI. 15 | 16 | - A server patchset that also implements its part of the protocol and uses a tailored main loop with 17 | a CLI-only option. 18 | 19 | - A dispatcher to handle multiple rooms from a single endpoint. 20 | 21 | Both the client and the server patchsets are compatible with Wii, vWii and Dolphin. 22 | 23 | ## Roadmap 24 | 25 | - [ ] Pre-alpha 26 | - [ ] Protocol 27 | - [ ] Design it 28 | - [ ] Client 29 | - [ ] Basic connection UI without any option 30 | - [ ] Socket code 31 | - [ ] Protocol reader/writer 32 | - [ ] Connect everything together 33 | - [ ] Server 34 | - [ ] Main loop 35 | - [ ] Socket code 36 | - [ ] Protocol reader/writer 37 | - [ ] Connect everything together 38 | 39 | # Building 40 | 41 | 1. Download and build the CodeWarrior-compatible LLVM. 42 | 43 | ```bash 44 | mkdir tools 45 | 46 | cd tools 47 | 48 | git clone git@github.com:DotKuribo/llvm-project.git 49 | 50 | cd llvm-project 51 | 52 | mkdir build 53 | 54 | cd build 55 | 56 | cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD=PowerPC -DLLVM_ENABLE_PROJECTS=clang ../llvm 57 | 58 | cmake --build . 59 | ``` 60 | 61 | 2. Build the loader. 62 | 63 | ```bash 64 | cd ../../loader 65 | make 66 | ``` 67 | -------------------------------------------------------------------------------- /configure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | import os 5 | from vendor.ninja_syntax import Writer 6 | 7 | 8 | n = Writer(open('build.ninja', 'w')) 9 | 10 | n.variable('ninja_required_version', '1.3') 11 | n.newline() 12 | 13 | n.variable('builddir', 'build') 14 | n.newline() 15 | 16 | n.variable('cc', os.path.join('tools', 'clang')) 17 | n.variable('cxx', os.path.join('tools', 'clang')) 18 | n.variable('port', os.path.join('.', 'port.py')) 19 | n.variable('ld', os.path.join('tools', 'clang')) 20 | n.newline() 21 | 22 | cflags = [ 23 | '-fdata-sections', 24 | '-ffunction-sections', 25 | '-fno-asynchronous-unwind-tables', 26 | '-fno-zero-initialized-in-bss', 27 | '-fshort-wchar', 28 | '-iquote', 'source', 29 | '-isystem', 'source', 30 | '-nostdlib', 31 | '-O2', 32 | '-target', 'powerpc-kuribo-eabi', 33 | '-Wall', 34 | ] 35 | cxxflags = [ 36 | '$cflags', 37 | '-fno-exceptions', 38 | '-fno-rtti', 39 | ] 40 | linkflags = [ 41 | '-fuse-ld=lld', 42 | '-nostdlib', 43 | '-target', 'powerpc-kuribo-eabi', 44 | '-Wl,--gc-sections', 45 | '-Wl,--nmagic', 46 | '-Wl,--no-dynamic-linker', 47 | '-Wl,--oformat,binary', 48 | ] 49 | n.variable('cflags', ' '.join(cflags)) 50 | n.variable('cxxflags', ' '.join(cxxflags)) 51 | n.variable('linkflags', ' '.join(linkflags)) 52 | n.newline() 53 | 54 | n.rule( 55 | 'cc', 56 | command = '$cc -MMD -MT $out -MF $out.d $cflags -D $target -c $in -o $out', 57 | depfile = '$out.d', 58 | deps = 'gcc', 59 | description = 'CC $out', 60 | ) 61 | n.newline() 62 | 63 | n.rule( 64 | 'cxx', 65 | command = '$cxx -MMD -MT $out -MF $out.d $cxxflags -D $target -c $in -o $out', 66 | depfile = '$out.d', 67 | deps = 'gcc', 68 | description = 'CXX $out', 69 | ) 70 | n.newline() 71 | 72 | n.rule( 73 | 'port', 74 | command = '$port $region $in $out', 75 | description = 'PORT $out' 76 | ) 77 | n.newline() 78 | 79 | linkparams = [ 80 | '-Wl,--entry=$entry', 81 | '-Wl,-image-base=$base', 82 | '-Wl,--section-start=.text.$entry=$base', 83 | '-Wl,-T,$script', 84 | ] 85 | n.rule( 86 | 'link', 87 | command = '$ld $linkflags ' + ' '.join(linkparams) + ' $in -o $out', 88 | description = 'LINK $out', 89 | ) 90 | n.newline() 91 | 92 | sourcefiles = { 93 | 'loader': [ 94 | os.path.join('game', 'host_system', 'Loader.cxx'), 95 | os.path.join('game', 'host_system', 'Rel.cxx'), 96 | ], 97 | 'client': [ 98 | os.path.join('game', 'host_system', 'BootStrapScene.cxx'), 99 | os.path.join('game', 'host_system', 'Dol.cxx'), 100 | os.path.join('game', 'host_system', 'Patcher.cxx'), 101 | os.path.join('game', 'host_system', 'Payload.cxx'), 102 | os.path.join('game', 'host_system', 'Rel.cxx'), 103 | os.path.join('game', 'host_system', 'RkSystem.cxx'), 104 | os.path.join('game', 'host_system', 'SceneCreatorStatic.cxx'), 105 | os.path.join('game', 'host_system', 'SceneManager.cxx'), 106 | ], 107 | 'server': [ 108 | os.path.join('game', 'host_system', 'Dol.cxx'), 109 | os.path.join('game', 'host_system', 'Patcher.cxx'), 110 | os.path.join('game', 'host_system', 'Payload.cxx'), 111 | os.path.join('game', 'host_system', 'Rel.cxx'), 112 | os.path.join('game', 'host_system', 'RkSystem.cxx'), 113 | ], 114 | } 115 | ofiles = {target: [] for target in sourcefiles} 116 | 117 | for target in sourcefiles: 118 | for sourcefile in sourcefiles[target]: 119 | base, ext = os.path.splitext(sourcefile) 120 | ofile = os.path.join('$builddir', target, base + '.o') 121 | rule = { 122 | '.c': 'cc', 123 | '.cxx': 'cxx', 124 | }[ext] 125 | n.build( 126 | ofile, 127 | rule, 128 | os.path.join('source', sourcefile), 129 | variables = { 130 | 'target': target.upper(), 131 | }, 132 | ) 133 | ofiles[target] += [ofile] 134 | n.newline() 135 | 136 | for region in ['P', 'E', 'J', 'K']: 137 | n.build( 138 | os.path.join('$builddir', f'RMC{region}.ld'), 139 | 'port', 140 | os.path.join('.', 'symbols.txt'), 141 | variables = { 142 | 'region': region, 143 | }, 144 | implicit = '$port', 145 | ) 146 | n.newline() 147 | 148 | for region in ['P', 'E', 'J', 'K']: 149 | n.build( 150 | os.path.join('$builddir', f'loader{region}.bin'), 151 | 'link', 152 | ofiles['loader'], 153 | variables = { 154 | 'entry': 'main__Q26System6LoaderFv', 155 | 'base': '0x80003f00', 156 | 'script': os.path.join('$builddir', f'RMC{region}.ld'), 157 | }, 158 | implicit = os.path.join('$builddir', f'RMC{region}.ld'), 159 | ) 160 | n.newline() 161 | 162 | for target in ['client', 'server']: 163 | for region in ['P', 'E', 'J', 'K']: 164 | n.build( 165 | os.path.join('$builddir', f'{target}{region}.bin'), 166 | 'link', 167 | ofiles[target], 168 | variables = { 169 | 'entry': 'main__Q26System7PayloadFv', 170 | 'base': { 171 | 'P': '0x8076db60', 172 | 'E': '0x80769400', 173 | 'J': '0x8076cca0', 174 | 'K': '0x8075bfe0', 175 | }[region], 176 | 'script': os.path.join('$builddir', f'RMC{region}.ld'), 177 | }, 178 | implicit = os.path.join('$builddir', f'RMC{region}.ld'), 179 | ) 180 | n.newline() 181 | 182 | n.variable('configure', os.path.join('.', 'configure.py')) 183 | n.newline() 184 | 185 | n.rule( 186 | 'configure', 187 | command = '$configure', 188 | generator = True, 189 | ) 190 | n.build( 191 | 'build.ninja', 192 | 'configure', 193 | implicit = [ 194 | '$configure', 195 | os.path.join('vendor', 'ninja_syntax.py'), 196 | ], 197 | ) 198 | -------------------------------------------------------------------------------- /port.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | from argparse import ArgumentParser 5 | from dataclasses import dataclass 6 | import sys 7 | 8 | 9 | @dataclass 10 | class Section: 11 | start: int 12 | end: int 13 | 14 | def __contains__(self, address): 15 | return self.start <= address < self.end 16 | 17 | @dataclass 18 | class SrcBinary: 19 | start: int 20 | sections: [Section] 21 | 22 | def __contains__(self, address): 23 | return any(address in section for section in self.sections) 24 | 25 | @dataclass 26 | class DstBinary: 27 | start: int 28 | end: int 29 | 30 | @dataclass 31 | class Chunk: 32 | src_start: int 33 | src_end: int 34 | dst_start: int 35 | 36 | def __contains__(self, address): 37 | return self.src_start <= address < self.src_end 38 | 39 | def port(self, address): 40 | return address - self.src_start + self.dst_start 41 | 42 | 43 | SRC_BINARIES = { 44 | 'dol': SrcBinary( 45 | 0x80004000, 46 | [ 47 | Section(0x80004000, 0x80006460), 48 | Section(0x80006460, 0x80006a20), 49 | Section(0x80006a20, 0x800072c0), 50 | Section(0x800072c0, 0x80244de0), 51 | Section(0x80244de0, 0x80244e90), 52 | Section(0x80244ea0, 0x80244eac), 53 | Section(0x80244ec0, 0x80258580), 54 | Section(0x80258580, 0x802a4040), 55 | Section(0x802a4080, 0x80384c00), 56 | Section(0x80384c00, 0x80385fc0), 57 | Section(0x80385fc0, 0x80386fa0), 58 | Section(0x80386fa0, 0x80389140), 59 | Section(0x80389140, 0x8038917c), 60 | ], 61 | ), 62 | 'rel': SrcBinary( 63 | 0x805102e0, 64 | [ 65 | Section(0x805103b4, 0x8088f400), 66 | Section(0x8088f400, 0x8088f704), 67 | Section(0x8088f704, 0x8088f710), 68 | Section(0x8088f720, 0x808b2bd0), 69 | Section(0x808b2bd0, 0x808dd3d4), 70 | Section(0x809bd6e0, 0x809c4f90), 71 | ], 72 | ), 73 | } 74 | 75 | DST_BINARIES = { 76 | 'P': { 77 | 'dol': DstBinary(0x80004000, 0x8038917c), 78 | 'rel': DstBinary(0x80399180, 0x8076db50), 79 | }, 80 | 'E': { 81 | 'dol': DstBinary(0x80004000, 0x80384dfc), 82 | 'rel': DstBinary(0x80394e00, 0x807693f0), 83 | }, 84 | 'J': { 85 | 'dol': DstBinary(0x80004000, 0x80388afc), 86 | 'rel': DstBinary(0x80398b00, 0x8076cc90), 87 | }, 88 | 'K': { 89 | 'dol': DstBinary(0x80004000, 0x8037719c), 90 | 'rel': DstBinary(0x803871a0, 0x8075bfd0), 91 | }, 92 | } 93 | 94 | CHUNKS = { 95 | 'E': [ 96 | Chunk(0x80004000, 0x80008004, 0x80004000), 97 | Chunk(0x800080f4, 0x8000ac50, 0x800080b4), 98 | Chunk(0x8000af78, 0x8000b6b4, 0x8000aed8), 99 | Chunk(0x80021bb0, 0x80225f14, 0x80021b10), 100 | Chunk(0x80226464, 0x802402e0, 0x802260e0), 101 | Chunk(0x802a4080, 0x80384c18, 0x8029fd00), 102 | Chunk(0x80385fc0, 0x80386008, 0x80381c40), 103 | Chunk(0x80386f48, 0x80386f90, 0x80382bc0), 104 | ], 105 | 'J': [ 106 | Chunk(0x80004000, 0x80008004, 0x80004000), 107 | Chunk(0x800080f4, 0x8000ac50, 0x80008050), 108 | Chunk(0x8000af78, 0x80021ba8, 0x8000ae9c), 109 | Chunk(0x80021bb0, 0x80244ea4, 0x80021ad0), 110 | Chunk(0x802a4080, 0x8038917c, 0x802a3a00), 111 | ], 112 | 'K': [ 113 | Chunk(0x80004000, 0x800074d8, 0x80004000), 114 | Chunk(0x800077c8, 0x800079d0, 0x80007894), 115 | Chunk(0x80007f2c, 0x80008004, 0x80008034), 116 | Chunk(0x80008c10, 0x80009198, 0x80008d60), 117 | Chunk(0x8000951c, 0x8000ac54, 0x80009624), 118 | Chunk(0x8000af78, 0x8000b610, 0x8000b024), 119 | Chunk(0x8000b654, 0x80021ba8, 0x8000b6bc), 120 | Chunk(0x80021bb0, 0x800ea264, 0x80021c10), 121 | Chunk(0x800ea4d4, 0x80164294, 0x800ea54c), 122 | Chunk(0x80164364, 0x801746fc, 0x80164400), 123 | Chunk(0x8017f680, 0x801e8414, 0x8017f9dc), 124 | Chunk(0x802100a0, 0x80244de0, 0x80210414), 125 | Chunk(0x802a4080, 0x803858e0, 0x80292080), 126 | Chunk(0x80385fc0, 0x8038917c, 0x80373fe0), 127 | ], 128 | } 129 | 130 | 131 | def write_symbol(out_file, name, address): 132 | out_file.write(f' {name} = {address:#x};\n'); 133 | 134 | def get_binary_name(address): 135 | return next(name for name, src_binary in SRC_BINARIES.items() if address in src_binary) 136 | 137 | def port(region, address): 138 | if region == 'P': 139 | return address 140 | 141 | return next((chunk.port(address) for chunk in CHUNKS[region] if address in chunk), None) 142 | 143 | 144 | parser = ArgumentParser() 145 | parser.add_argument('region') 146 | parser.add_argument('in_path') 147 | parser.add_argument('out_path') 148 | args = parser.parse_args() 149 | 150 | out_file = open(args.out_path, 'w') 151 | out_file.write('SECTIONS {\n') 152 | 153 | for name, dst_binary in DST_BINARIES[args.region].items(): 154 | write_symbol(out_file, f'{name}Start', dst_binary.start) 155 | write_symbol(out_file, f'{name}End', dst_binary.end) 156 | out_file.write('\n') 157 | 158 | symbols = open(args.in_path) 159 | for symbol in symbols.readlines(): 160 | if symbol.isspace(): 161 | out_file.write('\n') 162 | continue 163 | address, name = symbol.split() 164 | address = int(address, 16) 165 | binary_name = get_binary_name(address) 166 | is_rel_bss = 0x809bd6e0 <= address < 0x809c4f90 167 | address = port(args.region, address) 168 | if address is None: 169 | sys.exit(f'Couldn\'t port symbol {name} to region {args.region}!') 170 | if is_rel_bss: 171 | address -= 0xe02e0 172 | address -= SRC_BINARIES[binary_name].start 173 | address += DST_BINARIES[args.region][binary_name].start 174 | write_symbol(out_file, name, address) 175 | 176 | out_file.write('}\n') 177 | -------------------------------------------------------------------------------- /riivolution/mkw-cs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /source/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef int BOOL; 4 | 5 | typedef signed char s8; 6 | typedef signed short s16; 7 | typedef signed long s32; 8 | typedef signed long long s64; 9 | 10 | typedef unsigned char u8; 11 | typedef unsigned short u16; 12 | typedef unsigned long u32; 13 | typedef unsigned long long u64; 14 | 15 | typedef float f32; 16 | typedef double f64; 17 | 18 | struct Patch { 19 | u8 *from; 20 | u8 *to; 21 | }; 22 | 23 | #define REPLACE(func) \ 24 | extern u8 func; \ 25 | extern u8 my_ ## func; \ 26 | __attribute__((section("patches"))) \ 27 | extern const struct Patch func ## _patch = { \ 28 | &func, \ 29 | &my_ ## func, \ 30 | } 31 | -------------------------------------------------------------------------------- /source/Common.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | } 6 | -------------------------------------------------------------------------------- /source/egg/core/eggDisposer.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace EGG { 6 | 7 | class Disposer { 8 | protected: 9 | Disposer(); 10 | 11 | virtual ~Disposer(); 12 | 13 | private: 14 | u8 _4[0x10 - 0x4]; 15 | }; 16 | 17 | } // namespace EGG 18 | -------------------------------------------------------------------------------- /source/egg/core/eggExpHeap.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "eggHeap.hxx" 4 | 5 | namespace EGG { 6 | 7 | class ExpHeap : public Heap { 8 | public: 9 | static ExpHeap *create(void *block, u32 size, u16 attrs); 10 | }; 11 | 12 | } // namespace EGG 13 | -------------------------------------------------------------------------------- /source/egg/core/eggHeap.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "eggDisposer.hxx" 4 | 5 | extern "C" { 6 | #include 7 | } 8 | 9 | namespace EGG { 10 | 11 | class Allocator; 12 | 13 | class Heap : public Disposer { 14 | public: 15 | static void initialize(); 16 | 17 | virtual ~Heap(); 18 | 19 | virtual s32 getHeapKind() const = 0; 20 | 21 | virtual void initAllocator(Allocator *allocator, s32 align) = 0; 22 | 23 | virtual void *alloc(u32 size, s32 align) = 0; 24 | 25 | virtual void free(void *block) = 0; 26 | 27 | virtual void destroy() = 0; 28 | 29 | virtual u32 resizeForMBlock(void *block, u32 size) = 0; 30 | 31 | virtual u32 getAllocatableSize(s32 align) = 0; 32 | 33 | virtual u32 adjust() = 0; 34 | 35 | Heap *becomeCurrentHeap(); 36 | 37 | private: 38 | u8 _10[0x38 - 0x10]; 39 | }; 40 | 41 | } // namespace EGG 42 | 43 | void *operator new(size_t size); 44 | 45 | void *operator new(size_t size, EGG::Heap *heap, int align); 46 | -------------------------------------------------------------------------------- /source/egg/core/eggScene.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "eggDisposer.hxx" 4 | #include "eggHeap.hxx" 5 | 6 | namespace EGG { 7 | 8 | class Scene : public Disposer { 9 | public: 10 | Scene(); 11 | 12 | virtual ~Scene(); 13 | 14 | virtual void calc(); 15 | 16 | virtual void draw(); 17 | 18 | virtual void enter(); 19 | 20 | virtual void exit(); 21 | 22 | virtual void reinit(); 23 | 24 | virtual void incoming_childDestroy(); 25 | 26 | virtual void outgoing_childCreate(); 27 | 28 | private: 29 | u8 _10[0x14 - 0x10]; 30 | 31 | protected: 32 | Heap *m_heapMem1; 33 | 34 | private: 35 | u8 _18[0x30 - 0x18]; 36 | }; 37 | 38 | } // namespace EGG 39 | -------------------------------------------------------------------------------- /source/egg/core/eggSceneManager.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "eggScene.hxx" 4 | 5 | namespace EGG { 6 | 7 | class SceneCreator { 8 | public: 9 | virtual Scene *create(s32 sceneId) = 0; 10 | 11 | virtual void destroy(s32 sceneId) = 0; 12 | }; 13 | 14 | class SceneManager { 15 | public: 16 | virtual void calc(); 17 | 18 | virtual void draw(); 19 | 20 | virtual void calcCurrentScene(); 21 | 22 | virtual void calcCurrentFader(); 23 | 24 | virtual void drawCurrentScene(); 25 | 26 | virtual void drawCurrentFader(); 27 | 28 | virtual void createDefaultFader(); 29 | 30 | void changeSiblingScene(s32 sceneId); 31 | 32 | protected: 33 | SceneCreator *m_creator; 34 | 35 | private: 36 | u8 _08[0x2c - 0x08]; 37 | }; 38 | 39 | } // namespace EGG 40 | -------------------------------------------------------------------------------- /source/game/host_system/BootStrapScene.cxx: -------------------------------------------------------------------------------- 1 | #include "BootStrapScene.hxx" 2 | 3 | #include "Patcher.hxx" 4 | 5 | namespace System { 6 | 7 | BootStrapScene::BootStrapScene() : m_relLoader(nullptr) {} 8 | 9 | BootStrapScene::~BootStrapScene() {} 10 | 11 | void BootStrapScene::calc() { 12 | Rel::EntryFunction entry = m_relLoader->poke(); 13 | if (entry) { 14 | Patcher::patch(Patcher::Binary::Rel); 15 | 16 | entry(); 17 | } 18 | } 19 | 20 | void BootStrapScene::enter() { 21 | m_relLoader = new (m_heapMem1, 0x4) Rel::Loader(m_heapMem1); 22 | } 23 | 24 | void BootStrapScene::exit() { 25 | delete m_relLoader; 26 | } 27 | 28 | } // namespace System 29 | -------------------------------------------------------------------------------- /source/game/host_system/BootStrapScene.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Rel.hxx" 4 | 5 | #include 6 | 7 | namespace System { 8 | 9 | class BootStrapScene : public EGG::Scene { 10 | public: 11 | BootStrapScene(); 12 | 13 | ~BootStrapScene() override; 14 | 15 | void calc() override; 16 | 17 | void enter() override; 18 | 19 | void exit() override; 20 | 21 | private: 22 | Rel::Loader *m_relLoader; 23 | }; 24 | 25 | } // namespace System 26 | -------------------------------------------------------------------------------- /source/game/host_system/Dol.cxx: -------------------------------------------------------------------------------- 1 | #include "Dol.hxx" 2 | 3 | extern u8 dolStart; 4 | extern u8 dolEnd; 5 | 6 | namespace System { 7 | namespace Dol { 8 | 9 | void *getStart() { 10 | return &dolStart; 11 | } 12 | 13 | void *getEnd() { 14 | return &dolEnd; 15 | } 16 | 17 | u32 getSize() { 18 | return &dolEnd - &dolStart; 19 | } 20 | 21 | } // namespace Dol 22 | } // namespace System 23 | -------------------------------------------------------------------------------- /source/game/host_system/Dol.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace System { 6 | namespace Dol { 7 | 8 | void *getStart(); 9 | 10 | void *getEnd(); 11 | 12 | u32 getSize(); 13 | 14 | } // namespace Dol 15 | } // namespace System 16 | -------------------------------------------------------------------------------- /source/game/host_system/Loader.cxx: -------------------------------------------------------------------------------- 1 | #include "Rel.hxx" 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | } 8 | 9 | namespace System { 10 | namespace Loader { 11 | 12 | typedef void (*PayloadEntryFunction)(); 13 | 14 | PayloadEntryFunction loadPayload() { 15 | DVDFileInfo fileInfo; 16 | if (!DVDOpen("/cs/payload.bin", &fileInfo)) { 17 | return nullptr; 18 | } 19 | 20 | s32 size = OSRoundUp32B(fileInfo.length); 21 | void *payload = OSAllocFromMEM1ArenaLo(size, 0x20); 22 | s32 result = DVDRead(&fileInfo, payload, size, 0); 23 | DVDClose(&fileInfo); 24 | if (result != size) { 25 | return nullptr; 26 | } 27 | 28 | ICInvalidateRange(payload, size); 29 | 30 | return reinterpret_cast(payload); 31 | } 32 | 33 | void main() { 34 | OSInit(); 35 | 36 | OSAllocFromMEM1ArenaLo(Rel::getSize(), 0x20); 37 | 38 | PayloadEntryFunction entry = loadPayload(); 39 | if (!entry) { 40 | GXColor fg = { 255, 255, 255, 255 }; 41 | GXColor bg = { 0, 0, 0, 255 }; 42 | OSFatal(fg, bg, "Couldn't load mkw-cs payload!"); 43 | } 44 | 45 | entry(); 46 | } 47 | 48 | } // namespace Loader 49 | } // namespace System 50 | -------------------------------------------------------------------------------- /source/game/host_system/Patcher.cxx: -------------------------------------------------------------------------------- 1 | #include "Patcher.hxx" 2 | 3 | #include "Dol.hxx" 4 | #include "Rel.hxx" 5 | 6 | extern "C" { 7 | #include 8 | } 9 | 10 | extern "C" const Patch __start_patches; 11 | extern "C" const Patch __stop_patches; 12 | 13 | namespace System { 14 | namespace Patcher { 15 | 16 | static Binary getBinary(void *address) { 17 | if (address >= Dol::getStart() && address < Dol::getEnd()) { 18 | return Binary::Dol; 19 | } 20 | 21 | if (address >= Rel::getStart() && address < Rel::getEnd()) { 22 | return Binary::Rel; 23 | } 24 | 25 | return Binary::None; 26 | } 27 | 28 | void patch(Binary binary) { 29 | const Patch *patches = &__start_patches; 30 | u32 patchCount = &__stop_patches - &__start_patches; 31 | 32 | for (u32 i = 0; i < patchCount; i++) { 33 | if (getBinary(patches[i].from) != binary) { 34 | continue; 35 | } 36 | 37 | s32 diff = patches[i].to - patches[i].from; 38 | u32 ins = 0x12 << 26 | diff; 39 | *reinterpret_cast(patches[i].from) = ins; 40 | 41 | DCFlushRange(patches[i].from, sizeof(u32)); 42 | ICInvalidateRange(patches[i].from, sizeof(u32)); 43 | } 44 | } 45 | 46 | } // namespace Patcher 47 | } // namespace System 48 | -------------------------------------------------------------------------------- /source/game/host_system/Patcher.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace System { 6 | namespace Patcher { 7 | 8 | enum class Binary { 9 | None, 10 | Dol, 11 | Rel, 12 | }; 13 | 14 | void patch(Binary binary); 15 | 16 | } // namespace Patcher 17 | } // namespace System 18 | -------------------------------------------------------------------------------- /source/game/host_system/Payload.cxx: -------------------------------------------------------------------------------- 1 | #include "Patcher.hxx" 2 | 3 | namespace System { 4 | namespace Payload { 5 | 6 | void main() { 7 | Patcher::patch(Patcher::Binary::Dol); 8 | } 9 | 10 | } // namespace Payload 11 | } // namespace System 12 | -------------------------------------------------------------------------------- /source/game/host_system/Rel.cxx: -------------------------------------------------------------------------------- 1 | #include "Rel.hxx" 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | } 11 | 12 | extern u8 relStart; 13 | extern u8 relEnd; 14 | 15 | namespace System { 16 | namespace Rel { 17 | 18 | void *getStart() { 19 | return &relStart; 20 | } 21 | 22 | void *getEnd() { 23 | return &relEnd; 24 | } 25 | 26 | u32 getSize() { 27 | return &relEnd - &relStart; 28 | } 29 | 30 | EntryFunction load(EGG::Heap *heap) { 31 | DVDFileInfo fileInfo; 32 | if (!DVDOpen("/rel/StaticR.rel", &fileInfo)) { 33 | return nullptr; 34 | } 35 | 36 | s32 size = OSRoundUp32B(fileInfo.length); 37 | void *src = heap->alloc(size, 0x20); 38 | if (!src) { 39 | DVDClose(&fileInfo); 40 | return nullptr; 41 | } 42 | 43 | s32 result = DVDRead(&fileInfo, src, size, 0); 44 | DVDClose(&fileInfo); 45 | if (result != size) { 46 | return nullptr; 47 | } 48 | 49 | void *dst = getStart(); 50 | auto *srcHeader = static_cast(src); 51 | memcpy(dst, src, srcHeader->fixSize); 52 | ICInvalidateRange(dst, srcHeader->fixSize); 53 | auto *dstHeader = static_cast(dst); 54 | 55 | void *bss = static_cast(dst) + OSRoundUp32B(srcHeader->fixSize); 56 | 57 | dstHeader->info.sectionInfoOffset += reinterpret_cast(dst); 58 | auto *dstSectionInfo = reinterpret_cast(dstHeader->info.sectionInfoOffset); 59 | for (u32 i = 1; i < dstHeader->info.numSections; i++) { 60 | if (dstSectionInfo[i].offset != 0) { 61 | dstSectionInfo[i].offset += reinterpret_cast(dst); 62 | } else if (dstSectionInfo[i].size != 0) { 63 | dstSectionInfo[i].offset = reinterpret_cast(bss); 64 | } 65 | } 66 | 67 | dstHeader->impOffset += reinterpret_cast(src); 68 | auto *importInfo = reinterpret_cast(dstHeader->impOffset); 69 | for (u32 i = 0; i < dstHeader->impSize / sizeof(OSImportInfo); i++) { 70 | importInfo[i].offset += reinterpret_cast(src); 71 | } 72 | 73 | Relocate(nullptr, dstHeader); 74 | Relocate(dstHeader, dstHeader); 75 | 76 | heap->free(src); 77 | 78 | auto *prologSectionInfo = dstSectionInfo + dstHeader->prologSection; 79 | return reinterpret_cast(prologSectionInfo->offset + dstHeader->prolog); 80 | } 81 | 82 | Loader::Loader(EGG::Heap *heap) : m_heap(heap) { 83 | u32 stackSize = 0x5000; 84 | u8 *stack = (u8 *)m_heap->alloc(stackSize, 0x20); 85 | void *stackBase = stack + stackSize; 86 | OSCreateThread(&m_thread, start, this, stackBase, stackSize, 20, 0); 87 | OSResumeThread(&m_thread); 88 | } 89 | 90 | Loader::~Loader() { 91 | OSDetachThread(&m_thread); 92 | } 93 | 94 | EntryFunction Loader::poke() { 95 | void *entry; 96 | if (!OSJoinThread(&m_thread, &entry)) { 97 | return nullptr; 98 | } 99 | 100 | return reinterpret_cast(entry); 101 | } 102 | 103 | void *Loader::start(void *loader) { 104 | EntryFunction entry = static_cast(loader)->run(); 105 | return reinterpret_cast(entry); 106 | } 107 | 108 | EntryFunction Loader::run() { 109 | return load(m_heap); 110 | } 111 | 112 | } // namespace Rel 113 | } // namespace System 114 | -------------------------------------------------------------------------------- /source/game/host_system/Rel.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern "C" { 6 | #include 7 | } 8 | 9 | namespace System { 10 | namespace Rel { 11 | 12 | typedef void (*EntryFunction)(void); 13 | 14 | void *getStart(); 15 | 16 | void *getEnd(); 17 | 18 | u32 getSize(); 19 | 20 | EntryFunction load(EGG::Heap *heap); 21 | 22 | class Loader { 23 | public: 24 | Loader(EGG::Heap *heap); 25 | 26 | ~Loader(); 27 | 28 | EntryFunction poke(); 29 | 30 | private: 31 | static void *start(void *loader); 32 | 33 | EntryFunction run(); 34 | 35 | EGG::Heap *m_heap; 36 | OSThread m_thread; 37 | }; 38 | 39 | } // namespace Rel 40 | } // namespace System 41 | -------------------------------------------------------------------------------- /source/game/host_system/RkSystem.cxx: -------------------------------------------------------------------------------- 1 | #include "RkSystem.hxx" 2 | 3 | #include "Patcher.hxx" 4 | #include "Rel.hxx" 5 | #include "Scene.hxx" 6 | 7 | #include 8 | 9 | extern "C" { 10 | #include 11 | } 12 | 13 | namespace System { 14 | 15 | #ifdef CLIENT 16 | void RkSystem::my_main(int argc, const char *const *argv) { 17 | s_instance = &s_system; 18 | s_parentInstance = &s_system; 19 | 20 | s_argc = argc; 21 | s_argv = argv; 22 | 23 | s_system.initialize(); 24 | 25 | s_sceneCreatorStatic = new (s_system.getSystemHeap(), 0x4) SceneCreatorStatic; 26 | s_system.m_sceneManager->my_changeSceneWithCreator(SCENE_ID_BOOT_STRAP, s_sceneCreatorStatic); 27 | 28 | delete s_system.m_relHeap; 29 | 30 | s_system.run(); 31 | } 32 | #endif 33 | 34 | #ifdef SERVER 35 | void RkSystem::my_main(int argc, const char *const *argv) { 36 | EGG::Heap::initialize(); 37 | void *start = OSGetMEM1ArenaLo(); 38 | u32 size = static_cast(OSGetMEM1ArenaHi()) - static_cast(OSGetMEM1ArenaLo()); 39 | EGG::Heap *heap = EGG::ExpHeap::create(start, size, 0); 40 | heap->becomeCurrentHeap(); 41 | 42 | Rel::load(heap); 43 | Patcher::patch(Patcher::Binary::Rel); 44 | } 45 | #endif 46 | 47 | } // namespace System 48 | 49 | REPLACE(main__Q26System8RkSystemFiPCPCc); 50 | -------------------------------------------------------------------------------- /source/game/host_system/RkSystem.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SceneCreatorStatic.hxx" 4 | #include "SceneManager.hxx" 5 | 6 | namespace System { 7 | 8 | // Don't bother with the full inheritance and templates for now 9 | class RkSystem { 10 | public: 11 | static void my_main(int argc, const char *const *argv); 12 | 13 | virtual void vf_08(); 14 | virtual EGG::Heap *getSystemHeap(); 15 | virtual void vf_10(); 16 | virtual void vf_14(); 17 | virtual void vf_18(); 18 | virtual void vf_1c(); 19 | virtual void vf_20(); 20 | virtual void vf_24(); 21 | virtual void vf_28(); 22 | virtual void vf_2c(); 23 | virtual void vf_30(); 24 | virtual void run(); 25 | virtual void initialize(); 26 | 27 | static int s_argc; 28 | static const char *const *s_argv; 29 | 30 | private: 31 | static RkSystem s_system; 32 | static RkSystem *s_instance; 33 | static RkSystem *s_parentInstance; // Actually part of the parent class 34 | static SceneCreatorStatic *s_sceneCreatorStatic; 35 | 36 | u8 _04[0x54 - 0x04]; 37 | SceneManager *m_sceneManager; 38 | u8 _58[0x60 - 0x58]; 39 | EGG::Heap *m_relHeap; 40 | u8 _64[0x74 - 0x64]; 41 | }; 42 | 43 | } // namespace System 44 | -------------------------------------------------------------------------------- /source/game/host_system/Scene.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace System { 4 | 5 | enum SceneId { 6 | SCENE_ID_BOOT_STRAP = 0x5, 7 | }; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /source/game/host_system/SceneCreatorStatic.cxx: -------------------------------------------------------------------------------- 1 | #include "SceneCreatorStatic.hxx" 2 | 3 | #include "BootStrapScene.hxx" 4 | #include "Scene.hxx" 5 | 6 | namespace System { 7 | 8 | EGG::Scene *SceneCreatorStatic::create(s32 sceneId) { 9 | switch (sceneId) { 10 | case SCENE_ID_BOOT_STRAP: 11 | return new BootStrapScene; 12 | default: 13 | return nullptr; 14 | } 15 | } 16 | 17 | void SceneCreatorStatic::destroy(s32 sceneId) {} 18 | 19 | } // namespace System 20 | -------------------------------------------------------------------------------- /source/game/host_system/SceneCreatorStatic.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace System { 6 | 7 | class SceneCreatorStatic : public EGG::SceneCreator { 8 | public: 9 | EGG::Scene *create(s32 sceneId) override; 10 | 11 | void destroy(s32 sceneId) override; 12 | }; 13 | 14 | } // namespace System 15 | -------------------------------------------------------------------------------- /source/game/host_system/SceneManager.cxx: -------------------------------------------------------------------------------- 1 | #include "SceneManager.hxx" 2 | 3 | namespace System { 4 | 5 | void SceneManager::my_changeSceneWithCreator(s32 sceneId, EGG::SceneCreator *creator) { 6 | m_creator = creator; 7 | changeSiblingScene(sceneId); 8 | } 9 | 10 | } // namespace System 11 | 12 | REPLACE(changeSceneWithCreator__Q26System12SceneManagerFlPQ23EGG12SceneCreator); 13 | -------------------------------------------------------------------------------- /source/game/host_system/SceneManager.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace System { 6 | 7 | class SceneManager : public EGG::SceneManager { 8 | public: 9 | void my_changeSceneWithCreator(s32 sceneId, EGG::SceneCreator *creator); 10 | }; 11 | 12 | } // namespace System 13 | -------------------------------------------------------------------------------- /source/rvl/dvd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct DVDFileInfo DVDFileInfo; 6 | 7 | struct DVDFileInfo { 8 | u8 _00[0x34]; 9 | u32 length; 10 | u8 _38[0x4]; 11 | }; 12 | 13 | BOOL DVDOpen(const char *fileName, DVDFileInfo *fileInfo); 14 | 15 | s32 DVDReadPrio(DVDFileInfo *fileInfo, void *addr, s32 length, s32 offset, s32 prio); 16 | 17 | #define DVDRead(fileInfo, addr, length, offset) \ 18 | DVDReadPrio((fileInfo), (addr), (length), (offset), 2) 19 | 20 | BOOL DVDClose(DVDFileInfo *fileInfo); 21 | -------------------------------------------------------------------------------- /source/rvl/gx/GXStruct.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct GXColor GXColor; 6 | 7 | struct GXColor { 8 | u8 r; 9 | u8 g; 10 | u8 b; 11 | u8 a; 12 | }; 13 | -------------------------------------------------------------------------------- /source/rvl/os.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gx/GXStruct.h" 4 | 5 | void *OSGetMEM1ArenaHi(void); 6 | void *OSGetMEM1ArenaLo(void); 7 | 8 | void *OSAllocFromMEM1ArenaLo(u32 size, u32 align); 9 | 10 | #define OSRoundUp32B(x) (((u32)(x) + 32 - 1) & ~(32 - 1)) 11 | #define OSRoundDown32B(x) (((u32)(x)) & ~(32 - 1)) 12 | 13 | void OSInit(void); 14 | 15 | void OSFatal(GXColor fg, GXColor bg, const char* msg); 16 | -------------------------------------------------------------------------------- /source/rvl/os/OSCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void DCFlushRange(void *addr, u32 nBytes); 4 | 5 | void ICInvalidateRange(void *addr, u32 nBytes); 6 | -------------------------------------------------------------------------------- /source/rvl/os/OSModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct OSModuleInfo OSModuleInfo; 6 | typedef struct OSModuleHeader OSModuleHeader; 7 | typedef struct OSSectionInfo OSSectionInfo; 8 | typedef struct OSImportInfo OSImportInfo; 9 | 10 | struct OSModuleInfo { 11 | u8 _00[0x0c]; 12 | u32 numSections; 13 | u32 sectionInfoOffset; 14 | u8 _14[0x0c]; 15 | }; 16 | 17 | struct OSModuleHeader { 18 | OSModuleInfo info; 19 | u8 _20[0x08]; 20 | u32 impOffset; 21 | u32 impSize; 22 | u8 prologSection; 23 | u8 _31[0x03]; 24 | u32 prolog; 25 | u8 _38[0x10]; 26 | u32 fixSize; 27 | }; 28 | 29 | struct OSSectionInfo { 30 | u32 offset; 31 | u32 size; 32 | }; 33 | 34 | struct OSImportInfo { 35 | u8 _0[0x4]; 36 | u32 offset; 37 | }; 38 | 39 | // Not actually exposed in the API 40 | void Relocate(OSModuleHeader *existingModule, OSModuleHeader *newModule); 41 | -------------------------------------------------------------------------------- /source/rvl/os/OSThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct OSThread OSThread; 6 | 7 | struct OSThread { 8 | u8 _0[0x318]; 9 | }; 10 | 11 | BOOL OSCreateThread(OSThread *thread, void *(*func)(void *), void *param, void *stack, 12 | u32 stackSize, s32 priority, u16 attr); 13 | 14 | BOOL OSJoinThread(OSThread *thread, void **val); 15 | 16 | void OSDetachThread(OSThread *thread); 17 | 18 | s32 OSResumeThread(OSThread *thread); 19 | -------------------------------------------------------------------------------- /source/stddef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef __SIZE_TYPE__ size_t; 4 | -------------------------------------------------------------------------------- /symbols.txt: -------------------------------------------------------------------------------- 1 | 0x80005f34 memcpy 2 | 3 | 0x80007f34 outgoing_childCreate__Q23EGG5SceneFv 4 | 0x80007f38 incoming_childDestroy__Q23EGG5SceneFv 5 | 0x80007f3c reinit__Q23EGG5SceneFv 6 | 0x80007f48 draw__Q23EGG5SceneFv 7 | 0x80008ef0 main__Q26System8RkSystemFiPCPCc 8 | 0x80008fac getSystemHeap__Q26System8RkSystemFv 9 | 0x80009194 initialize__Q26System8RkSystemFv 10 | 0x8000951c run__Q26System8RkSystemFv 11 | 0x80009844 changeSceneWithCreator__Q26System12SceneManagerFlPQ23EGG12SceneCreator 12 | 0x8000b6b0 main 13 | 0x8015e2bc DVDOpen 14 | 0x8015e834 DVDReadPrio 15 | 0x8015e568 DVDClose 16 | 0x8019fc68 OSInit 17 | 0x801a10a4 OSGetMEM1ArenaHi 18 | 0x801a10bc OSGetMEM1ArenaLo 19 | 0x801a1104 OSAllocFromMEM1ArenaLo 20 | 0x801a162c DCFlushRange 21 | 0x801a1710 ICInvalidateRange 22 | 0x801a4ec4 OSFatal 23 | 0x801a6d3c Relocate 24 | 0x801a9e84 OSCreateThread 25 | 0x801aa3ac OSJoinThread 26 | 0x801aa4ec OSDetachThread 27 | 0x801aa58c OSResumeThread 28 | 0x80226a1c create__Q23EGG7ExpHeapFPvUlUs 29 | 0x802296a8 initialize__Q23EGG4HeapFv 30 | 0x80229d74 becomeCurrentHeap__Q23EGG4HeapFv 31 | 0x80229dcc __nw__FUl 32 | 0x80229de0 __nw__FUlPQ23EGG4Heapi 33 | 0x80229e14 __dl__FPv 34 | 0x8023ad10 __ct__Q23EGG5SceneFv 35 | 0x8023ad84 __dt__Q23EGG5SceneFv 36 | 0x8023afe0 changeSiblingScene__Q23EGG12SceneManagerFl 37 | 38 | 0x802a4080 s_system__Q26System8RkSystem 39 | 40 | 0x80385fc8 s_instance__Q26System8RkSystem 41 | 0x80385fe8 s_argc__Q26System8RkSystem 42 | 0x80385fec s_argv__Q26System8RkSystem 43 | 0x80385ff0 s_sceneCreatorStatic__Q26System8RkSystem 44 | 45 | 0x80386f60 s_parentInstance__Q26System8RkSystem 46 | -------------------------------------------------------------------------------- /vendor/ninja_syntax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2011 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Python module for generating .ninja files. 18 | 19 | Note that this is emphatically not a required piece of Ninja; it's 20 | just a helpful utility for build-file-generation systems that already 21 | use Python. 22 | """ 23 | 24 | import re 25 | import textwrap 26 | 27 | def escape_path(word): 28 | return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') 29 | 30 | class Writer(object): 31 | def __init__(self, output, width=78): 32 | self.output = output 33 | self.width = width 34 | 35 | def newline(self): 36 | self.output.write('\n') 37 | 38 | def comment(self, text): 39 | for line in textwrap.wrap(text, self.width - 2, break_long_words=False, 40 | break_on_hyphens=False): 41 | self.output.write('# ' + line + '\n') 42 | 43 | def variable(self, key, value, indent=0): 44 | if value is None: 45 | return 46 | if isinstance(value, list): 47 | value = ' '.join(filter(None, value)) # Filter out empty strings. 48 | self._line('%s = %s' % (key, value), indent) 49 | 50 | def pool(self, name, depth): 51 | self._line('pool %s' % name) 52 | self.variable('depth', depth, indent=1) 53 | 54 | def rule(self, name, command, description=None, depfile=None, 55 | generator=False, pool=None, restat=False, rspfile=None, 56 | rspfile_content=None, deps=None): 57 | self._line('rule %s' % name) 58 | self.variable('command', command, indent=1) 59 | if description: 60 | self.variable('description', description, indent=1) 61 | if depfile: 62 | self.variable('depfile', depfile, indent=1) 63 | if generator: 64 | self.variable('generator', '1', indent=1) 65 | if pool: 66 | self.variable('pool', pool, indent=1) 67 | if restat: 68 | self.variable('restat', '1', indent=1) 69 | if rspfile: 70 | self.variable('rspfile', rspfile, indent=1) 71 | if rspfile_content: 72 | self.variable('rspfile_content', rspfile_content, indent=1) 73 | if deps: 74 | self.variable('deps', deps, indent=1) 75 | 76 | def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, 77 | variables=None, implicit_outputs=None, pool=None, dyndep=None): 78 | outputs = as_list(outputs) 79 | out_outputs = [escape_path(x) for x in outputs] 80 | all_inputs = [escape_path(x) for x in as_list(inputs)] 81 | 82 | if implicit: 83 | implicit = [escape_path(x) for x in as_list(implicit)] 84 | all_inputs.append('|') 85 | all_inputs.extend(implicit) 86 | if order_only: 87 | order_only = [escape_path(x) for x in as_list(order_only)] 88 | all_inputs.append('||') 89 | all_inputs.extend(order_only) 90 | if implicit_outputs: 91 | implicit_outputs = [escape_path(x) 92 | for x in as_list(implicit_outputs)] 93 | out_outputs.append('|') 94 | out_outputs.extend(implicit_outputs) 95 | 96 | self._line('build %s: %s' % (' '.join(out_outputs), 97 | ' '.join([rule] + all_inputs))) 98 | if pool is not None: 99 | self._line(' pool = %s' % pool) 100 | if dyndep is not None: 101 | self._line(' dyndep = %s' % dyndep) 102 | 103 | if variables: 104 | if isinstance(variables, dict): 105 | iterator = iter(variables.items()) 106 | else: 107 | iterator = iter(variables) 108 | 109 | for key, val in iterator: 110 | self.variable(key, val, indent=1) 111 | 112 | return outputs 113 | 114 | def include(self, path): 115 | self._line('include %s' % path) 116 | 117 | def subninja(self, path): 118 | self._line('subninja %s' % path) 119 | 120 | def default(self, paths): 121 | self._line('default %s' % ' '.join(as_list(paths))) 122 | 123 | def _count_dollars_before_index(self, s, i): 124 | """Returns the number of '$' characters right in front of s[i].""" 125 | dollar_count = 0 126 | dollar_index = i - 1 127 | while dollar_index > 0 and s[dollar_index] == '$': 128 | dollar_count += 1 129 | dollar_index -= 1 130 | return dollar_count 131 | 132 | def _line(self, text, indent=0): 133 | """Write 'text' word-wrapped at self.width characters.""" 134 | leading_space = ' ' * indent 135 | while len(leading_space) + len(text) > self.width: 136 | # The text is too wide; wrap if possible. 137 | 138 | # Find the rightmost space that would obey our width constraint and 139 | # that's not an escaped space. 140 | available_space = self.width - len(leading_space) - len(' $') 141 | space = available_space 142 | while True: 143 | space = text.rfind(' ', 0, space) 144 | if (space < 0 or 145 | self._count_dollars_before_index(text, space) % 2 == 0): 146 | break 147 | 148 | if space < 0: 149 | # No such space; just use the first unescaped space we can find. 150 | space = available_space - 1 151 | while True: 152 | space = text.find(' ', space + 1) 153 | if (space < 0 or 154 | self._count_dollars_before_index(text, space) % 2 == 0): 155 | break 156 | if space < 0: 157 | # Give up on breaking. 158 | break 159 | 160 | self.output.write(leading_space + text[0:space] + ' $\n') 161 | text = text[space+1:] 162 | 163 | # Subsequent lines are continuations, so indent them. 164 | leading_space = ' ' * (indent+2) 165 | 166 | self.output.write(leading_space + text + '\n') 167 | 168 | def close(self): 169 | self.output.close() 170 | 171 | 172 | def as_list(input): 173 | if input is None: 174 | return [] 175 | if isinstance(input, list): 176 | return input 177 | return [input] 178 | 179 | 180 | def escape(string): 181 | """Escape a string such that it can be embedded into a Ninja file without 182 | further interpretation.""" 183 | assert '\n' not in string, 'Ninja syntax does not allow newlines' 184 | # We only have one special metacharacter: '$'. 185 | return string.replace('$', '$$') 186 | 187 | 188 | def expand(string, vars, local_vars={}): 189 | """Expand a string containing $vars as Ninja would. 190 | 191 | Note: doesn't handle the full Ninja variable syntax, but it's enough 192 | to make configure.py's use of it work. 193 | """ 194 | def exp(m): 195 | var = m.group(1) 196 | if var == '$': 197 | return '$' 198 | return local_vars.get(var, vars.get(var, '')) 199 | return re.sub(r'\$(\$|\w*)', exp, string) 200 | --------------------------------------------------------------------------------