├── .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 |
--------------------------------------------------------------------------------