├── .gitignore
├── LICENSE
├── README.md
├── doc
├── basics.txt
└── usage.txt
├── example
├── ucf_data
│ ├── ace_exploit.mgc
│ ├── codehandler.bin
│ ├── codeset_v100.mgc
│ ├── codeset_v101.mgc
│ ├── codeset_v102.mgc
│ └── datacopy_table.mgc
└── ucf_v073.mgc
├── melee_gci_compiler.py
└── mgc
├── asm.py
├── commands.py
├── compiler.py
├── context.py
├── datatypes.py
├── errors.py
├── files.py
├── gci_tools
├── gci_encode.py
├── meleegci.py
├── mem2gci.py
├── ppc_opcodes.py
└── savefile.py
├── init_gci
├── header.bin
└── init_gci.mgc
├── line.py
├── logger.py
├── pyiiasmh
├── COPYING
├── CREDITS
├── bin
│ ├── LICENSE
│ ├── README
│ ├── darwin_arm64
│ │ ├── powerpc-eabi-as
│ │ ├── powerpc-eabi-ld
│ │ └── powerpc-eabi-objcopy
│ ├── darwin_i386
│ │ ├── powerpc-eabi-as
│ │ ├── powerpc-eabi-ld
│ │ └── powerpc-eabi-objcopy
│ ├── linux_aarch64
│ │ ├── powerpc-eabi-as
│ │ ├── powerpc-eabi-ld
│ │ └── powerpc-eabi-objcopy
│ ├── linux_armv7l
│ │ ├── ppc-eabi-ar
│ │ ├── ppc-eabi-as
│ │ └── ppc-eabi-objcopy
│ ├── linux_i686
│ │ ├── powerpc-eabi-as
│ │ ├── powerpc-eabi-ld
│ │ ├── powerpc-eabi-objcopy
│ │ └── vdappc
│ ├── linux_x86_64
│ │ ├── powerpc-eabi-as
│ │ ├── powerpc-eabi-ld
│ │ ├── powerpc-eabi-objcopy
│ │ └── vdappc
│ └── windows_amd64
│ │ ├── powerpc-eabi-as.exe
│ │ ├── powerpc-eabi-ld.exe
│ │ ├── powerpc-eabi-objcopy.exe
│ │ └── vdappc.exe
├── errors.py
└── ppctools.py
└── type_validator.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Custom entries
2 | notes/
3 | test gcis/
4 | *.tmp
5 | code.bin
6 | code.txt
7 | src1.o
8 | src2.o
9 | test.mgc
10 | *.gci
11 |
12 | # Byte-compiled / optimized / DLL files
13 | __pycache__/
14 | *.py[cod]
15 | *$py.class
16 |
17 | # C extensions
18 | *.so
19 |
20 | # Distribution / packaging
21 | .Python
22 | build/
23 | develop-eggs/
24 | dist/
25 | downloads/
26 | eggs/
27 | .eggs/
28 | lib/
29 | lib64/
30 | parts/
31 | sdist/
32 | var/
33 | wheels/
34 | pip-wheel-metadata/
35 | share/python-wheels/
36 | *.egg-info/
37 | .installed.cfg
38 | *.egg
39 | MANIFEST
40 |
41 | # PyInstaller
42 | # Usually these files are written by a python script from a template
43 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
44 | *.manifest
45 | *.spec
46 |
47 | # Installer logs
48 | pip-log.txt
49 | pip-delete-this-directory.txt
50 |
51 | # Unit test / coverage reports
52 | htmlcov/
53 | .tox/
54 | .nox/
55 | .coverage
56 | .coverage.*
57 | .cache
58 | nosetests.xml
59 | coverage.xml
60 | *.cover
61 | *.py,cover
62 | .hypothesis/
63 | .pytest_cache/
64 |
65 | # Translations
66 | *.mo
67 | *.pot
68 |
69 | # Django stuff:
70 | *.log
71 | local_settings.py
72 | db.sqlite3
73 | db.sqlite3-journal
74 |
75 | # Flask stuff:
76 | instance/
77 | .webassets-cache
78 |
79 | # Scrapy stuff:
80 | .scrapy
81 |
82 | # Sphinx documentation
83 | docs/_build/
84 |
85 | # PyBuilder
86 | target/
87 |
88 | # Jupyter Notebook
89 | .ipynb_checkpoints
90 |
91 | # IPython
92 | profile_default/
93 | ipython_config.py
94 |
95 | # pyenv
96 | .python-version
97 |
98 | # pipenv
99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
102 | # install all needed dependencies.
103 | #Pipfile.lock
104 |
105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
106 | __pypackages__/
107 |
108 | # Celery stuff
109 | celerybeat-schedule
110 | celerybeat.pid
111 |
112 | # SageMath parsed files
113 | *.sage.py
114 |
115 | # Environments
116 | .env
117 | .venv
118 | env/
119 | venv/
120 | ENV/
121 | env.bak/
122 | venv.bak/
123 |
124 | # Spyder project settings
125 | .spyderproject
126 | .spyproject
127 |
128 | # Rope project settings
129 | .ropeproject
130 |
131 | # mkdocs documentation
132 | /site
133 |
134 | # mypy
135 | .mypy_cache/
136 | .dmypy.json
137 | dmypy.json
138 |
139 | # Pyre type checker
140 | .pyre/
141 |
142 | # macOS Finder metadata
143 | .DS_Store
144 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Melee GCI Compiler
2 |
3 | Melee GCI Compiler is an application and scripting language that makes it easy
4 | for Super Smash Bros. Melee mod developers to inject custom code and data into
5 | Melee save files.
6 |
7 | ## Background
8 |
9 | Melee contains a buffer overflow exploit that can be used to trigger ACE
10 | (Arbitrary Code Execution). Developers can package their own custom code into a
11 | Melee save file, and then trigger the exploit in-game to run their code. This
12 | enables Melee mods to run natively on unmodified consoles using nothing more
13 | than a memory card and a special save file.
14 |
15 | The exploit was originally discovered by wParam
16 | ([website](http://wparam.com/ssbm/)) in 2008 but went largely unnoticed by the
17 | competitive Melee community, as mods at the time were focused more on wacky fun
18 | than on competitive play.
19 |
20 | Starting around 2013, training-focused AR/Gecko codes started to appear, with
21 | complete mod packs soon following, such as the 20XX Training Hack Pack
22 | ([Smashboards](https://smashboards.com/threads/the-20xx-melee-training-hack-pack-v4-07-7-04-17.351221/)).
23 |
24 | When the save file exploit was rediscovered in 2014, 20XX Tournament Edition
25 | ([website](http://www.20xx.me/)) became the second memory card mod and focused
26 | on the competitive Melee community by encouraging tournament organizers to use
27 | it.
28 |
29 | In 2018, 20XXTE was used as a base to deliver UCF (Universal Controller Fix) as
30 | a memory card mod ([website](http://www.20xx.me/ucf.html)).
31 |
32 | In 2019, UnclePunch's Training Mode
33 | ([GitHub](https://github.com/UnclePunch/Training-Mode)) built upon old
34 | knowledge with modern programming tools to deliver a refined and powerful
35 | training toolkit in memory card form.
36 |
37 | During Melee's peak competitive years, mod developers were concerned that
38 | memory card mods could be used to easily cheat at major tournaments. So, they
39 | opted for "security by obscurity" and chose not to share the knowledge and
40 | tools they developed to create memory card mods. However, both the modding and
41 | competitive communities have evolved since then. I believe the time has come to
42 | preserve developer knowledge and ensure that this formerly enigmatic capability
43 | becomes accessible to future enthusiasts and competitive players. Preservation
44 | of the past and accessibility for the future are the core reasons I decided to
45 | make Melee GCI Compiler.
46 |
47 | ## Requirements
48 |
49 | Melee GCI Compiler requires Python 3.6 or higher. It also uses pre-compiled GNU
50 | GCC binaries `powerpc-eabi-as`, `powerpc-eabi-ld`, and `powerpc-eabi-objcopy`.
51 | For convenience, x86 binaries for Windows, macOS, and Linux are already
52 | included and will be called with no user configuration needed. ARM binaries are
53 | included for macOS (Apple Silicon) and Linux (Raspberry Pi, etc.).
54 |
55 | ### macOS
56 |
57 | You may get some permission-related errors on macOS. If you get a `Permission
58 | denied` error during ASM compilation, you will need to run `chmod +x` on the
59 | binary files in `mgc/pyiiasmh/bin/darwin_xxxx` (`i386` or `arm64` depending on
60 | your architecture).
61 |
62 | It is also likely that macOS will block these binaries from being run due to
63 | being from an unidentified developer. If so, the system will tell you in a
64 | pop-up dialog. To get around this, go to Settings > Security & Privacy >
65 | General, where at the bottom you will find a "Run Anyway" button. You will need
66 | to do this three times, one for each binary. After doing so, run Melee GCI
67 | Compiler again, and the ASM compilation should succeed.
68 |
69 | ## Documentation
70 |
71 | Please see the `doc` folder for basic usage and an overview of the MGC
72 | scripting language features. The `example` folder contains example MGC script
73 | files with plenty of explanation text inside. Please feel free to examine the
74 | example script and try compiling it yourself.
75 |
76 | ## Bugs
77 |
78 | The most obvious sign of bugs is if you receive a Python stack trace - that
79 | means some case has been unhandled. If you want to hunt for bugs, try to cause
80 | stack traces and report them in the issue tracker!
81 |
82 | If you believe MGC is mistakenly logging an error or some data is not being
83 | compiled correctly, please feel free to submit those issues as well, and they
84 | will be reviewed.
85 |
86 | ## Attributions
87 |
88 | Thanks to wParam for discovering and documenting the Melee save file exploit.
89 |
90 | Thanks to metaconstruct for doing some incredible work to reverse engineer the
91 | encoding/decoding routines Melee uses to obfuscate its save files. Melee GCI
92 | Compiler is possible thanks to that. `meleegci.py` was written by
93 | metaconstruct, and `gci_encode.py`, the GCI encoding/decoding routine, is a
94 | Python re-implementation of his C program.
95 |
96 | Thanks to Sean Power for PyiiASMH ([Google
97 | Code](https://code.google.com/archive/p/pyiiasmh/)), a Python wrapper for the
98 | PPC toolchain with a focus on generating Gecko codes, which is used (and
99 | thoroughly butchered) in Melee GCI Compiler.
100 |
101 | Thanks to JoshuaMKW for updating and maintaining PyiiASMH
102 | ([GitHub](https://github.com/JoshuaMKW/pyiiasmh)) with Python 3 support, even
103 | though I somehow didn't know about it until I started writing this readme, and
104 | therefore didn't use it.
105 |
106 | Thanks to all my friends in the Melee modding community. I'm sorry I peaced out
107 | to go make an anime dating sim. Everyone is doing all these incredible things
108 | that I never dreamed would be possible with Melee.
109 |
--------------------------------------------------------------------------------
/doc/basics.txt:
--------------------------------------------------------------------------------
1 | MGC SCRIPT FORMAT BASICS
2 |
3 | This is an overview of the main features and usage of the MGC script language.
4 | To see most of these in practice, feel free to examine the scripts in the
5 | 'example' folder.
6 |
7 | STANDARD OPERATIONS
8 | -------------------
9 |
10 | 0123456789ABCDEF
11 | All standalone hex is written to Melee memory as-is. Whitespace is ignored,
12 | so formats like "0D 15 3A 5E" and "c2380580 00000006" are fine too. It is
13 | also not case-sensitive.
14 | %10010110
15 | Lines beginning with '%' denote binary instead of hex.
16 | +macro_name 10
17 | Runs a macro that was defined using !macro (see Special Commands). Optional
18 | count argument specifies how many times to run the macro, eg. 10 (ten) or
19 | 0x10 (sixteen) times.
20 | [alias_name]
21 | Gets replaced by the data specified using !define (see Special Commands).
22 | # Comment
23 | Anything after '#' on a line is a comment and ignored by the compiler.
24 |
25 |
26 | SPECIAL COMMANDS
27 | ----------------
28 | Commands give the compiler special information or allow you to load data in
29 | unique ways.
30 |
31 | !loc 8045f000
32 | Denotes where to inject any following data in Melee memory, until the next
33 | location is specified.
34 | Lower bound: 8045bf28
35 | Upper bound: 8046b0f0
36 | !gci 4f50
37 | Advanced usage only - lets you specify a GCI offset location rather than a
38 | Melee memory location to inject data to. You'll almost always want to use
39 | !loc to specify where in Melee memory you want the data to go, rather than
40 | worry about where it goes in the GCI.
41 | !patch 4f50
42 | Advanced usage only - like !gci, but writing data while in patch mode defers
43 | that data from being written until the very end of compilation, right before
44 | the GCI is packed. If !blockorder is used, block reordering happens before
45 | this as well. Using !loc or !gci after !patch will disable patch mode for
46 | following data writes.
47 | !add 1a0
48 | Adds a relative amount to the loc pointer.
49 | !src "path/to/source"
50 | Adds the given MGC script file to the build script at that location.
51 | !asmsrc "path/to/source"
52 | Compiles and adds the ASM source file to the current location.
53 | !bin "path/to/file"
54 | Adds the entire binary file directly to Melee memory.
55 | !file "path/to/file"
56 | Deprecated - same as !bin.
57 | !geckocodelist "path/to/codelist"
58 | Compiles the Gecko codelist text file, then adds the resulting binary to
59 | Melee memory. Gecko codelists have their own format that is not covered
60 | here. This command is for convenience purposes only for those using Gecko
61 | codelist files, but adding your Gecko codes directly into the build script
62 | will work equally well, because they're just hex anyway. If not using
63 | !geckocodelist, make sure your Gecko codeset begins with "00d0c0de 00d0c0de"
64 | and ends with "f0000000 00000000".
65 | !fill 0x120 ff
66 | Fills the pattern in the second argument the number of times in the first
67 | argument. The pattern can be hex (eg. ff) or binary (eg. %11111111). It can
68 | be any number of bytes long.
69 | !string "This is a text string"
70 | Adds a raw ASCII text string to Melee memory. Escape characters are honored,
71 | so using \n in a text string will appropriately write 0xa into memory.
72 | !asm ... !asmend
73 | Compiles the PPC ASM and adds the resulting binary to Melee memory.
74 | !c2 80380580 ... !c2end
75 | Compiles the PPC ASM and wraps it in a Gecko C2 code, then adds the
76 | resulting binary to Melee memory.
77 | !begin
78 | Optionally denotes where in the current file the build script begins.
79 | Anything before this line is ignored by the compiler. If not specified, the
80 | build script begins on the first line of the file.
81 | !end
82 | Optionally denotes where in the current file the build script ends. Anything
83 | after this line is ignored by the compiler. If not specified, the build
84 | script ends on the last line of the file.
85 | !echo "message"
86 | Prints a message to the console during compilation.
87 | !macro macro_name ... !macroend
88 | Defines a macro of script lines that can be applied anywhere.
89 | !define alias_name ...
90 | Defines an alias. Lets you use [alias_name] in your script and it will be
91 | replaced with the contents of the alias before the script line is even
92 | processed. For example, "!define codeset_loc "80465000"" will later let you
93 | write "!loc [codeset_loc]" and it will be parsed as "!loc 80465000".
94 | !blockorder 0 1 2...
95 | Changes the order in which blocks are written to the GCI file. When Melee
96 | writes save data, the blocks can be written out of order, and sometimes the
97 | same block is written twice. This command lets you replicate that behavior,
98 | although it shouldn't affect the way Melee loads the data. You must specify
99 | 10 total block numbers (from 0 to 9, inclusive), and those blocks will be
100 | written to the GCI in that order.
101 |
102 |
--------------------------------------------------------------------------------
/doc/usage.txt:
--------------------------------------------------------------------------------
1 | Usage: melee_gci_compiler.py [options] [script_path]
2 |
3 | script_path The path to the MGC script file you want to compile.
4 | -i Optionally input a Melee GCI to use its existing data as a base.
5 | -o The GCI file to output. If omitted, no data will be written.
6 | -h, --help Displays this usage text.
7 | --nopack Do not pack the GCI, so you can inspect the outputted data.
8 | --silent Suppress command line output, except for fatal errors.
9 | --debug Output extra information while compiling and on errors.
10 |
11 | You can omit script_path to pack or unpack a GCI without changing its content.
12 |
--------------------------------------------------------------------------------
/example/ucf_data/ace_exploit.mgc:
--------------------------------------------------------------------------------
1 | ACE EXPLOIT
2 |
3 | This file sets up the data and code for Melee's ACE (Arbitrary Code Execution)
4 | save file exploit. It is required in order to run custom code from a save file.
5 | --------------------------------------------------------------------------------
6 |
7 | !begin
8 | # Make the first nametag so long that it overflows into the stack's return pointer
9 | !loc 8045d850
10 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
11 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
12 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
13 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
14 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
15 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
16 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
17 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
18 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
19 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
20 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
21 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
22 | DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD
23 | DDDDDDDD 804ee8f8 8045d930 00000000
24 |
25 | # This forces execution to branch to 8045d930 instead of back to the nametag
26 | # function. Better yet, 8045d930 is at a different offset in each version
27 | # of Melee, because the save file loads into slightly different spots.
28 | # This means we can conveniently run different code for each Melee version
29 | # using only one save file.
30 | #
31 | # v1.02: 8045d930
32 | # v1.01: 8045e610
33 | # v1.00: 8045f8f8
34 |
35 | # --------------
36 | # v1.02 Loader 1
37 | # --------------
38 | !loc 8045d930
39 | !asm
40 |
41 | lis r11,0x8046
42 | ori r11,r11,0xaf00
43 | lis r12,0x8023
44 | ori r12,r12,0x9e9c
45 | lis r3,0x8043
46 | lwz r4,0x2f74(r3)
47 | cmpwi r4,0 #is a memcard save in progress?
48 | beq 0x10
49 | stw r11,0x2f74(r3) #if so, run loader2 when it's done and return
50 | mtctr r12
51 | bctr
52 | mtctr r11 #if not, run loader2 now and set LR to return
53 | mtlr r12
54 | bctr
55 |
56 | !asmend
57 |
58 | # --------------
59 | # v1.01 Loader 1
60 | # --------------
61 | !loc 8045e610
62 | !asm
63 |
64 | lis r11,0x8046
65 | ori r11,r11,0xa020
66 | lis r12,0x8023
67 | ori r12,r12,0x9700
68 | lis r3,0x8043
69 | lwz r4,0x2294(r3)
70 | cmpwi r4,0 #is a memcard save in progress?
71 | beq 0x10
72 | stw r11,0x2294(r3) #if so, run loader2 when it's done and return
73 | mtctr r12
74 | bctr
75 | mtctr r11 #if not, run loader2 now and set LR to return
76 | mtlr r12
77 | bctr
78 |
79 | !asmend
80 |
81 | # --------------
82 | # v1.00 Loader 1
83 | # --------------
84 | !loc 8045f8f8
85 | !asm
86 |
87 | lis r11,0x8046
88 | ori r11,r11,0x8b38
89 | lis r12,0x8023
90 | ori r12,r12,0x8b90
91 | lis r3,0x8043
92 | lwz r4,0x0fb4(r3)
93 | cmpwi r4,0 #is a memcard save in progress?
94 | beq 0x10
95 | stw r11,0x0fb4(r3) #if so, run loader2 when it's done and return
96 | mtctr r12
97 | bctr
98 | mtctr r11 #if not, run loader2 now and set LR to return
99 | mtlr r12
100 | bctr
101 |
102 | !asmend
103 |
104 |
105 | # Now, we branch to the Loader 2 function and run whatever code we want before
106 | # rebooting the game. In this example, we iterate through a datacopy table
107 | # that conveniently lets us copy our custom data to other places in Melee
108 | # memory, away from the save data region. (See datacopy_table.mgc)
109 | # Then, we run the Gecko codehandler one time to apply our Gecko codes.
110 | # Unlike Gecko OS and Nintendont, we don't hook the codehandler to run every
111 | # frame, so keep that in mind if your codes require that.
112 |
113 | # --------------
114 | # v1.02 Loader 2
115 | # --------------
116 | !loc 8046af00
117 | !asm
118 |
119 | mflr r0
120 | stw r0,0x4(sp)
121 | stwu sp,-0x20(sp)
122 | stw r31,0x1c(sp)
123 | stw r30,0x18(sp)
124 | stw r29,0x14(sp)
125 | stw r28,0x10(sp)
126 |
127 | #r7: pointer to datacopy table
128 | lis r7,0x8046
129 | ori r7,r7,0x0a64
130 |
131 | DATACOPYLOOP:
132 | lwz r4,0(r7) #source
133 | cmpwi r4,0
134 | beq CODEHANDLER
135 | lwz r3,4(r7) #destination
136 | lwz r5,8(r7) #size
137 |
138 | #copy data
139 | subi r4,r4,4
140 | subi r6,r3,4
141 | addi r5,r5,4
142 | b 0xc
143 | lwzu r0,0x4(r4)
144 | stwu r0,0x4(r6)
145 | subic. r5,r5,4
146 | bne+ -0xc
147 |
148 | #next string
149 | addi r7,r7,0xc
150 | b DATACOPYLOOP
151 |
152 | CODEHANDLER:
153 |
154 | #Zero fill entire nametag area
155 | lis r3,0x8045
156 | ori r3,r3,0xd850
157 | li r4,0
158 | li r5,0
159 | ori r5,r5,0xc344
160 | lis r12,0x8000
161 | ori r12,r12,0x3130
162 | mtctr r12
163 | bctrl
164 |
165 | #Init nametag data
166 | li r3,2
167 | lis r12,0x8015
168 | ori r12,r12,0xf600
169 | mtctr r12
170 | bctrl
171 |
172 | #Reload game data
173 | lis r12,0x8001
174 | ori r12,r12,0xcbbc
175 | mtctr r12
176 | bctrl
177 |
178 | #Run codehandler once
179 | lis r12,0x8019
180 | ori r12,r12,0x10e4
181 | li r4,0
182 | icbi r4,r12
183 | sync
184 | isync
185 | mtctr r12
186 | bctrl
187 |
188 | #Clear cache
189 | lis r3,0x8000
190 | lis r4,0x3c
191 | lis r12,0x8032
192 | ori r12,r12,0x8f50
193 | mtctr r12
194 | bctrl
195 |
196 | li r3,6
197 | lis r12,0x801a
198 | ori r12,r12,0x428c
199 | mtctr r12
200 | bctrl
201 |
202 | li r0,5
203 | sth r0,-0x4ad8(r13)
204 | lwz r3,-0x4f80(r13)
205 | lwz r4,0x8(r3)
206 | li r30,0
207 | stb r30,0(r4)
208 |
209 | END:
210 | lis r12,0x801a
211 | ori r12,r12,0x4b60
212 | mtctr r12
213 | bctrl
214 |
215 | lwz r0,0x24(sp)
216 | lwz r31,0x1c(sp)
217 | lwz r30,0x18(sp)
218 | lwz r29,0x14(sp)
219 | lwz r28,0x10(sp)
220 | addi sp,sp,0x20
221 | mtlr r0
222 | blr
223 |
224 | !asmend
225 |
226 | # --------------
227 | # v1.01 Loader 2
228 | # --------------
229 | !loc 8046ad00
230 | !asm
231 |
232 | mflr r0
233 | stw r0,0x4(sp)
234 | stwu sp,-0x20(sp)
235 | stw r31,0x1c(sp)
236 | stw r30,0x18(sp)
237 | stw r29,0x14(sp)
238 | stw r28,0x10(sp)
239 |
240 | #r7: pointer to datacopy table
241 | #8045FB84 in v1.01
242 |
243 | lis r7,0x8045
244 | ori r7,r7,0xfb84
245 |
246 | DATACOPYLOOP:
247 | lwz r4,0(r7) #source
248 | cmpwi r4,0
249 | beq CODEHANDLER
250 | lwz r3,4(r7) #destination
251 | lwz r5,8(r7) #size
252 |
253 | #copy data
254 | subi r4,r4,4
255 | subi r6,r3,4
256 | addi r5,r5,4
257 | b 0xc
258 | lwzu r0,0x4(r4)
259 | stwu r0,0x4(r6)
260 | subic. r5,r5,4
261 | bne+ -0xc
262 |
263 | #next data
264 | addi r7,r7,0xc
265 | b DATACOPYLOOP
266 |
267 | CODEHANDLER:
268 |
269 | #Zero fill entire nametag area
270 | lis r3,0x8045
271 | ori r3,r3,0xcb70
272 | li r4,0
273 | li r5,0
274 | ori r5,r5,0xc344
275 | lis r12,0x8000
276 | ori r12,r12,0x3130
277 | mtctr r12
278 | bctrl
279 |
280 | #Init nametag data
281 | li r3,2
282 | lis r12,0x8015
283 | ori r12,r12,0xf37c
284 | mtctr r12
285 | bctrl
286 |
287 | #Reload game data
288 | lis r12,0x8001
289 | ori r12,r12,0xcbbc
290 | mtctr r12
291 | bctrl
292 |
293 | #Run codehandler once
294 | lis r12,0x8019
295 | ori r12,r12,0x0a98
296 | li r4,0
297 | icbi r4,r12
298 | sync
299 | isync
300 | mtctr r12
301 | bctrl
302 |
303 | #Clear cache
304 | lis r3,0x8000
305 | lis r4,0x3c
306 | lis r12,0x8032
307 | ori r12,r12,0x8278
308 | mtctr r12
309 | bctrl
310 |
311 | #80479050: scene controller, equivalent to 80479d30
312 | li r3,6
313 | lis r12,0x801a
314 | ori r12,r12,0x3c44
315 | mtctr r12
316 | bctrl
317 |
318 | li r0,5
319 | sth r0,-0x4ad8(r13)
320 | lwz r3,-0x4f80(r13)
321 | lwz r3,0x8(r3)
322 | li r30,0
323 | stb r30,0(r3)
324 |
325 | lis r12,0x801a
326 | ori r12,r12,0x4518
327 | mtctr r12
328 | bctrl
329 |
330 | lwz r0,0x24(sp)
331 | lwz r31,0x1c(sp)
332 | lwz r30,0x18(sp)
333 | lwz r29,0x14(sp)
334 | lwz r28,0x10(sp)
335 | addi sp,sp,0x20
336 | mtlr r0
337 | blr
338 |
339 | !asmend
340 |
341 | # --------------
342 | # v1.00 Loader 2
343 | # --------------
344 | !loc 8046ab00
345 | !asm
346 |
347 | mflr r0
348 | stw r0,0x4(sp)
349 | stwu sp,-0x20(sp)
350 | stw r31,0x1c(sp)
351 | stw r30,0x18(sp)
352 | stw r29,0x14(sp)
353 | stw r28,0x10(sp)
354 |
355 | #r7: pointer to datacopy table
356 | #8045E69C in v1.00
357 |
358 | lis r7,0x8045
359 | ori r7,r7,0xe69c
360 |
361 | DATACOPYLOOP:
362 | lwz r4,0(r7) #source
363 | cmpwi r4,0
364 | beq CODEHANDLER
365 | lwz r3,4(r7) #destination
366 | lwz r5,8(r7) #size
367 |
368 | #copy data
369 | subi r4,r4,4
370 | subi r6,r3,4
371 | addi r5,r5,4
372 | b 0xc
373 | lwzu r0,0x4(r4)
374 | stwu r0,0x4(r6)
375 | subic. r5,r5,4
376 | bne+ -0xc
377 |
378 | #next data
379 | addi r7,r7,0xc
380 | b DATACOPYLOOP
381 |
382 | CODEHANDLER:
383 |
384 | #Hook codehandler
385 | lis r3,0x8037
386 | ori r3,r3,0x43ec
387 | lis r4,0x4be1
388 | ori r4,r4,0xbdf5
389 | stw r4,0(r3)
390 | li r0,0
391 | icbi r0,r3
392 | sync
393 | isync
394 |
395 | #Zero fill entire nametag area
396 | lis r3,0x8045
397 | ori r3,r3,0xb888
398 | li r4,0
399 | li r5,0
400 | ori r5,r5,0xc344
401 | lis r12,0x8000
402 | ori r12,r12,0x3130
403 | mtctr r12
404 | bctrl
405 |
406 | #Init nametag data
407 | li r3,2
408 | lis r12,0x8015
409 | ori r12,r12,0xed9c
410 | mtctr r12
411 | bctrl
412 |
413 | #Reload game data
414 | lis r12,0x8001
415 | ori r12,r12,0xcb3c
416 | mtctr r12
417 | bctrl
418 |
419 | #Run codehandler once
420 | lis r12,0x8019
421 | ori r12,r12,0x01e4
422 | li r4,0
423 | icbi r4,r12
424 | sync
425 | isync
426 | mtctr r12
427 | bctrl
428 |
429 | #Clear cache
430 | lis r3,0x8000
431 | lis r4,0x3c
432 | lis r12,0x8032
433 | ori r12,r12,0x75ec
434 | mtctr r12
435 | bctrl
436 |
437 | #80477d68: scene controller, equivalent to 80479d30
438 | li r3,6
439 | lis r12,0x801a
440 | ori r12,r12,0x3544
441 | mtctr r12
442 | bctrl
443 |
444 | li r0,5
445 | sth r0,-0x4ad8(r13)
446 | lwz r3,-0x4f80(r13)
447 | lwz r3,0x8(r3)
448 | li r30,0
449 | stb r30,0(r3)
450 |
451 | lis r12,0x801a
452 | ori r12,r12,0x3e18
453 | mtctr r12
454 | bctrl
455 |
456 | lwz r0,0x24(sp)
457 | lwz r31,0x1c(sp)
458 | lwz r30,0x18(sp)
459 | lwz r29,0x14(sp)
460 | lwz r28,0x10(sp)
461 | addi sp,sp,0x20
462 | mtlr r0
463 | blr
464 |
465 | !asmend
--------------------------------------------------------------------------------
/example/ucf_data/codehandler.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dansalvato/melee-gci-compiler/44addb662e524d793df9795e0b9947b93094bd38/example/ucf_data/codehandler.bin
--------------------------------------------------------------------------------
/example/ucf_data/codeset_v100.mgc:
--------------------------------------------------------------------------------
1 | # UCF Codeset for Melee v1.00
2 |
3 | # Gecko codeset header
4 | 00d0c0de 00d0c0de
5 |
6 | # If Codes Not Written
7 | 22000034 817f4000
8 |
9 | # Permanently disable saving/loading if card unplugged
10 | 0401c5b8 38000004
11 | 0401c5bc 901f0008
12 |
13 | # Unlock all characters + stages
14 | 04459F60 FFFFFFFF
15 | 04459F64 FFFFFFFF
16 |
17 | # All Trophies
18 | 0245A3C8 00000125
19 | 0245A3CD 01266363
20 |
21 | # Disable Title Screen Demos
22 | 041A0F10 38000000
23 |
24 | # Disable Trophy Messages
25 | 0415CFD0 4e800020
26 |
27 | # Disable Special Messages
28 | 0415CF98 4e800020
29 |
30 | # Set ArenaHi to 817f4000
31 | 04000034 817f4000
32 |
33 | # Apply Deflicker on Title Screen
34 | C21A14C8 00000004
35 | 806D8840 88631CC5
36 | 3D808015 618CED24
37 | 7D8903A6 4E800421
38 | BB21007C 00000000
39 |
40 | # Tournament Melee loads VS instead
41 | 0422c340 38000002
42 |
43 | # UCF v0.73
44 | C20C968C 00000022
45 | A09F03E8 2C044000
46 | 40820100 808DAEB4
47 | C03F0620 C05F2344
48 | EC2100B2 C044003C
49 | FC011040 4C411382
50 | 408200E0 88BF0670
51 | 2C050002 408000D4
52 | 889F221F 54840739
53 | 41A20008 480000C4
54 | 3C80804B 6084FDF8
55 | 88A40001 98A1FFF8
56 | 4800003C 38A5FFFF
57 | 2C050000 40800008
58 | 38A50005 3C808046
59 | 60849140 1CA50030
60 | 7C842A14 88BF000C
61 | 1CA5000C 7C842A14
62 | 88A40002 7CA50774
63 | 4E800020 38A5FFFE
64 | 4BFFFFC5 90A1FFF4
65 | 88A1FFF8 4BFFFFB9
66 | 8081FFF4 7CA42850
67 | 7CA529D6 2C0515F9
68 | 40810050 38000001
69 | 901F2358 901F2340
70 | 889F0007 2C04000A
71 | 40A20038 80830010
72 | 8084002C 80841ECC
73 | D0040018 80A40018
74 | 3D803F80 7C056000
75 | 41820010 38A00080
76 | 98A40006 4800000C
77 | 38A0007F 98A40006
78 | D01F002C 00000000
79 | C20995F8 0000001E
80 | 8063002C C023063C
81 | C0050314 FC010040
82 | 408100D4 3C8042A0
83 | 9081FFF4 3C803727
84 | 9081FFF8 3C804330
85 | 9081FFE4 C0030620
86 | 38000000 FC000210
87 | C021FFF4 EC000072
88 | C021FFF8 EC000828
89 | FC00001E D801FFEC
90 | 8081FFF0 38840002
91 | 6C848000 9081FFE8
92 | C801FFE4 C8228B88
93 | EC000828 C021FFF4
94 | EC000824 2C000000
95 | 40820014 38000001
96 | D001FFE0 C0030624
97 | 4BFFFFAC C021FFE0
98 | EC210072 EC000032
99 | EC00082A C022894C
100 | FC000840 4C411382
101 | 4082003C 88830670
102 | 2C040003 40810030
103 | C005002C FC000050
104 | C0230624 FC000840
105 | 4080001C 8061001C
106 | 38630008 83E10014
107 | 38210018 7C6803A6
108 | 4E800020 7FC3F378
109 | 8083002C 00000000
110 |
111 | # UCF v0.73 Indicator
112 | C2264FB8 00000018
113 | 9421FFBC BE810008
114 | 7C0802A6 90010040
115 | 38600000 38800000
116 | 3DC0803A 61CE4890
117 | 7DC903A6 4E800421
118 | 7C7F1B78 4800005D
119 | 7FC802A6 7FE3FB78
120 | C03E0000 C05E0004
121 | 48000059 7C8802A6
122 | 3DC0803A 61CE4CD4
123 | 7DC903A6 4E800421
124 | 807F005C 3C80FFFF
125 | 6084FF0E 90830006
126 | 38800001 989F0049
127 | 38800001 989F004A
128 | C03E0008 D03F0024
129 | D03F0028 48000024
130 | 4E800021 42820000
131 | C40F4000 3D3851EC
132 | 4E800021 55434620
133 | 76302E37 33000000
134 | 80010040 7C0803A6
135 | BA810008 38210044
136 | 38980000 00000000
137 |
138 | # Endif (write once)
139 | e2000001 00000000
140 |
141 | # Gecko codeset end
142 | f0000000 00000000
--------------------------------------------------------------------------------
/example/ucf_data/codeset_v101.mgc:
--------------------------------------------------------------------------------
1 | # UCF Codeset for Melee v1.01
2 |
3 | # Gecko codeset header
4 | 00d0c0de 00d0c0de
5 |
6 | # If Codes Not Written
7 | 22000034 817f4000
8 |
9 | # Permanently disable saving/loading if card unplugged
10 | 0401c638 38000004
11 | 0401c63c 901f0008
12 |
13 | # Unlock all characters + stages
14 | 0445b248 FFFFFFFF
15 | 0445b24c FFFFFFFF
16 |
17 | # All Trophies
18 | 0245b6b0 00000125
19 | 0245b6b5 01266363
20 |
21 | # Disable Title Screen Demos
22 | 041A1610 38000000
23 |
24 | # Disable Trophy Messages
25 | 0415d55c 4e800020
26 |
27 | # Disable Special Messages
28 | 0415d524 4e800020
29 |
30 | # Set ArenaHi to 817f4000
31 | 04000034 817f4000
32 |
33 | # Apply Deflicker on Title Screen
34 | C21A1BC8 00000004
35 | 806D8840 88631CC5
36 | 3D808015 618CF304
37 | 7D8903A6 4E800421
38 | BB21007C 00000000
39 |
40 | # Tournament Melee loads VS instead
41 | 0422ceb0 38000002
42 |
43 | # UCF v0.73
44 | C20C97D0 00000022
45 | A09F03E8 2C044000
46 | 40820100 808DAEB4
47 | C03F0620 C05F2344
48 | EC2100B2 C044003C
49 | FC011040 4C411382
50 | 408200E0 88BF0670
51 | 2C050002 408000D4
52 | 889F221F 54840739
53 | 41A20008 480000C4
54 | 3C80804C 60841258
55 | 88A40001 98A1FFF8
56 | 4800003C 38A5FFFF
57 | 2C050000 40800008
58 | 38A50005 3C808046
59 | 6084A428 1CA50030
60 | 7C842A14 88BF000C
61 | 1CA5000C 7C842A14
62 | 88A40002 7CA50774
63 | 4E800020 38A5FFFE
64 | 4BFFFFC5 90A1FFF4
65 | 88A1FFF8 4BFFFFB9
66 | 8081FFF4 7CA42850
67 | 7CA529D6 2C0515F9
68 | 40810050 38000001
69 | 901F2358 901F2340
70 | 889F0007 2C04000A
71 | 40A20038 80830010
72 | 8084002C 80841ECC
73 | D0040018 80A40018
74 | 3D803F80 7C056000
75 | 41820010 38A00080
76 | 98A40006 4800000C
77 | 38A0007F 98A40006
78 | D01F002C 00000000
79 | C20996E0 0000001E
80 | 8063002C C023063C
81 | C0050314 FC010040
82 | 408100D4 3C8042A0
83 | 9081FFF4 3C803727
84 | 9081FFF8 3C804330
85 | 9081FFE4 C0030620
86 | 38000000 FC000210
87 | C021FFF4 EC000072
88 | C021FFF8 EC000828
89 | FC00001E D801FFEC
90 | 8081FFF0 38840002
91 | 6C848000 9081FFE8
92 | C801FFE4 C8228B88
93 | EC000828 C021FFF4
94 | EC000824 2C000000
95 | 40820014 38000001
96 | D001FFE0 C0030624
97 | 4BFFFFAC C021FFE0
98 | EC210072 EC000032
99 | EC00082A C022894C
100 | FC000840 4C411382
101 | 4082003C 88830670
102 | 2C040003 40810030
103 | C005002C FC000050
104 | C0230624 FC000840
105 | 4080001C 8061001C
106 | 38630008 83E10014
107 | 38210018 7C6803A6
108 | 4E800020 7FC3F378
109 | 8083002C 00000000
110 |
111 | # UCF v0.73 Indicator
112 | C2265B34 00000018
113 | 9421FFBC BE810008
114 | 7C0802A6 90010040
115 | 38600000 38800000
116 | 3DC0803A 61CE5A74
117 | 7DC903A6 4E800421
118 | 7C7F1B78 4800005D
119 | 7FC802A6 7FE3FB78
120 | C03E0000 C05E0004
121 | 48000059 7C8802A6
122 | 3DC0803A 61CE5EB8
123 | 7DC903A6 4E800421
124 | 807F005C 3C80FFFF
125 | 6084FF0E 90830006
126 | 38800001 989F0049
127 | 38800001 989F004A
128 | C03E0008 D03F0024
129 | D03F0028 48000024
130 | 4E800021 42820000
131 | C40F4000 3D3851EC
132 | 4E800021 55434620
133 | 76302E37 33000000
134 | 80010040 7C0803A6
135 | BA810008 38210044
136 | 38980000 00000000
137 |
138 | # Endif
139 | e2000001 00000000
140 |
141 | # Gecko codeset end
142 | f0000000 00000000
--------------------------------------------------------------------------------
/example/ucf_data/codeset_v102.mgc:
--------------------------------------------------------------------------------
1 | # UCF Codeset for Melee v1.02
2 |
3 | # Gecko codeset header
4 | 00d0c0de 00d0c0de
5 |
6 | # If Codes Not Written
7 | 22000034 817f4000
8 |
9 | # Permanently disable saving/loading if card unplugged
10 | 0401c638 38000004
11 | 0401c63c 901f0008
12 |
13 | # Unlock all characters + stages
14 | 0445BF28 FFFFFFFF
15 | 0445BF2C FFFFFFFF
16 |
17 | # All event matches
18 | 0424cec4 48000058
19 |
20 | # All Trophies
21 | 0245C390 00000125
22 | 0245C395 01266363
23 |
24 | # Disable Title Screen Demos
25 | 041A1C58 38000000
26 |
27 | # Disable Trophy Messages
28 | 0415d984 4e800020
29 |
30 | # Disable Special Messages
31 | 0415d94c 4e800020
32 |
33 | # Set ArenaHi to 817f4000
34 | 04000034 817f4000
35 |
36 | # Apply Deflicker and Volume Settings on Title Screen
37 | C21A2210 00000007
38 | 806D8840 88631CC5
39 | 3D808015 618CF588
40 | 7D8903A6 4E800421
41 | BB21007C 806D8840
42 | 88631CC4 3D808016
43 | 618C02C0 7D8903A6
44 | 4E800421 00000000
45 |
46 | # Tournament Melee loads VS instead
47 | 0422d638 38000002
48 |
49 | # Prevent Game Data Re-initialization
50 | 041a4234 60000000
51 |
52 | # UCF v0.73
53 | C20C9A44 00000022
54 | A09F03E8 2C044000
55 | 40820100 808DAEB4
56 | C03F0620 C05F2344
57 | EC2100B2 C044003C
58 | FC011040 4C411382
59 | 408200E0 88BF0670
60 | 2C050002 408000D4
61 | 889F221F 54840739
62 | 41A20008 480000C4
63 | 3C80804C 60841F78
64 | 88A40001 98A1FFF8
65 | 4800003C 38A5FFFF
66 | 2C050000 40800008
67 | 38A50005 3C808046
68 | 6084B108 1CA50030
69 | 7C842A14 88BF000C
70 | 1CA5000C 7C842A14
71 | 88A40002 7CA50774
72 | 4E800020 38A5FFFE
73 | 4BFFFFC5 90A1FFF4
74 | 88A1FFF8 4BFFFFB9
75 | 8081FFF4 7CA42850
76 | 7CA529D6 2C0515F9
77 | 40810050 38000001
78 | 901F2358 901F2340
79 | 889F0007 2C04000A
80 | 40A20038 80830010
81 | 8084002C 80841ECC
82 | D0040018 80A40018
83 | 3D803F80 7C056000
84 | 41820010 38A00080
85 | 98A40006 4800000C
86 | 38A0007F 98A40006
87 | D01F002C 00000000
88 | C20998A4 0000001E
89 | 8063002C C023063C
90 | C0050314 FC010040
91 | 408100D4 3C8042A0
92 | 9081FFF4 3C803727
93 | 9081FFF8 3C804330
94 | 9081FFE4 C0030620
95 | 38000000 FC000210
96 | C021FFF4 EC000072
97 | C021FFF8 EC000828
98 | FC00001E D801FFEC
99 | 8081FFF0 38840002
100 | 6C848000 9081FFE8
101 | C801FFE4 C8228B90
102 | EC000828 C021FFF4
103 | EC000824 2C000000
104 | 40820014 38000001
105 | D001FFE0 C0030624
106 | 4BFFFFAC C021FFE0
107 | EC210072 EC000032
108 | EC00082A C0228954
109 | FC000840 4C411382
110 | 4082003C 88830670
111 | 2C040003 40810030
112 | C005002C FC000050
113 | C0230624 FC000840
114 | 4080001C 8061001C
115 | 38630008 83E10014
116 | 38210018 7C6803A6
117 | 4E800020 7FC3F378
118 | 8083002C 00000000
119 |
120 | # UCF v0.73 Indicator
121 | C22662D0 00000018
122 | 9421FFBC BE810008
123 | 7C0802A6 90010040
124 | 38600000 38800000
125 | 3DC0803A 61CE6754
126 | 7DC903A6 4E800421
127 | 7C7F1B78 4800005D
128 | 7FC802A6 7FE3FB78
129 | C03E0000 C05E0004
130 | 48000059 7C8802A6
131 | 3DC0803A 61CE6B98
132 | 7DC903A6 4E800421
133 | 807F005C 3C80FFFF
134 | 6084FF0E 90830006
135 | 38800001 989F0049
136 | 38800001 989F004A
137 | C03E0008 D03F0024
138 | D03F0028 48000024
139 | 4E800021 42820000
140 | C40F4000 3D3851EC
141 | 4E800021 55434620
142 | 76302E37 33000000
143 | 80010040 7C0803A6
144 | BA810008 38210044
145 | 38980000 00000000
146 |
147 | # Endif
148 | e2000001 00000000
149 |
150 | # Gecko codeset end
151 | f0000000 00000000
--------------------------------------------------------------------------------
/example/ucf_data/datacopy_table.mgc:
--------------------------------------------------------------------------------
1 | DATACOPY TABLE
2 |
3 | During the ACE exploit loader, the datacopy table is used to copy our custom
4 | data from the save data memory region to other places in memory. This is because
5 | Melee wants to write things to the save data region such as nametags and match
6 | data. So, the first thing we need to do after triggering ACE is to get all of
7 | our custom data out of there and into safe memory regions that Melee won't
8 | touch.
9 |
10 | Each version of Melee needs its own datacopy table because the save file is
11 | loaded into different memory regions in each version. For example, address
12 | 80460000 in v1.02 is loaded to 8045f320 in v1.01 and 8045e038 in v1.00.
13 |
14 | Another reason to use a different datacopy table per Melee version is that you
15 | probably want to copy different data for each version. For example, a Gecko
16 | codeset usually needs to be version-specific.
17 |
18 | Format:
19 | 80465000 <-- The current location of our data
20 | 817f4000 <-- The location we want to move the data to
21 | 00003700 <-- The length of the data to copy
22 | --------------------------------------------------------------------------------
23 |
24 | !begin
25 |
26 | # --------------------
27 | # v1.02 datacopy table
28 | # --------------------
29 | !loc 80460a64
30 |
31 | # Internal filename
32 | 8046180c
33 | 803bac5c
34 | 00000018
35 |
36 | # Displayed filename
37 | 80461824
38 | 803bac3c
39 | 0000000c
40 |
41 | # Gecko codehandler
42 | 80460c64
43 | 801910e0
44 | 00000ba8
45 |
46 | # Gecko codeset
47 | 80465000
48 | 817f4000
49 | 00003700
50 |
51 | # Default tournament settings
52 | 80461830
53 | 8045bf10
54 | 0000000c
55 |
56 | # End table
57 | 00000000
58 |
59 | # --------------------
60 | # v1.01 datacopy table
61 | # --------------------
62 | !loc 80460864
63 |
64 | # Internal filename
65 | 80460b2c
66 | 803b9f7c
67 | 00000018
68 |
69 | # Displayed filename
70 | 80460b44
71 | 803b9f3c
72 | 0000000c
73 |
74 | # Gecko codehandler
75 | 8045ff84
76 | 80190a94
77 | 00000ba8
78 |
79 | # Gecko codeset
80 | 8045da00
81 | 817f4000
82 | 000010d8
83 |
84 | # Default tournament settings
85 | 80460b50
86 | 8045b230
87 | 0000000c
88 |
89 | # End table
90 | 00000000
91 |
92 | # --------------------
93 | # v1.00 datacopy table
94 | # --------------------
95 | !loc 80460664
96 |
97 | # Internal filename
98 | 8045f844
99 | 803b8d9c
100 | 00000018
101 |
102 | # Displayed filename
103 | 8045f85c
104 | 803b8d7c
105 | 0000000c
106 |
107 | # Gecko codehandler
108 | 8045ec9c
109 | 801901e0
110 | 00000ba8
111 |
112 | # Gecko codeset
113 | 80466c9c
114 | 817f4000
115 | 000010d8
116 |
117 | # Default tournament settings
118 | 8045f868
119 | 80459f48
120 | 0000000c
121 |
122 | # End table
123 | 00000000
--------------------------------------------------------------------------------
/example/ucf_v073.mgc:
--------------------------------------------------------------------------------
1 | UCF v0.73 MGC SCRIPT
2 |
3 | Welcome to this example MGC script. This script compiles a save file containing
4 | the UCF codeset. You can find more information on each of the script components
5 | by reading through the MGC files in the ucf_data folder.
6 |
7 | The save file has three main components:
8 | 1. The ACE exploit that runs our custom code when entering "Name Entry" in Melee
9 | 2. The custom data we want to load, such as the Gecko codehandler and codeset
10 | 3. The datacopy table, which tells the ACE loader where to copy our custom data
11 | --------------------------------------------------------------------------------
12 |
13 | !begin
14 |
15 | !src "ucf_data/ace_exploit.mgc"
16 | !src "ucf_data/datacopy_table.mgc"
17 |
18 | # Load our custom data into the addresses expected by the datacopy table
19 |
20 | # Internal filename
21 | !loc 8046180c
22 | !string "UCFSettings"
23 | # pad to 0x18 length
24 | 00 00 00 00 00 00 00 00 00 00 00 00 00
25 |
26 | # Displayed filename
27 | !loc 80461824
28 | !string "UCF Save"
29 |
30 | # Default Tournament Settings
31 | !loc 80461830
32 | 00 34 01 02 04 00 0a 00 08 01 01 00
33 |
34 | # Gecko codehandler
35 | !loc 80460c64
36 | !file "ucf_data/codehandler.bin"
37 |
38 | # v1.02 Gecko codeset
39 | !loc 80465000
40 | !src "ucf_data/codeset_v102.mgc"
41 |
42 | # v1.01 Gecko codeset
43 | !loc 8045e6e0
44 | !src "ucf_data/codeset_v101.mgc"
45 |
46 | # v1.00 Gecko codeset
47 | !loc 80468c64
48 | !src "ucf_data/codeset_v100.mgc"
49 |
--------------------------------------------------------------------------------
/melee_gci_compiler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """melee-gci-compiler.py: Compiles custom data into Melee GCI save files using
3 | MGC script files"""
4 | import sys
5 | import getopt
6 | import hashlib
7 | from pathlib import Path
8 | import mgc.compiler as compiler
9 | import mgc.logger as logger
10 | from mgc.errors import CompileError
11 |
12 | USAGE_TEXT = """\
13 | Usage: melee_gci_compiler.py [options] [script_path]
14 |
15 | script_path The path to the MGC script file you want to compile.
16 | -i Optionally input a Melee GCI to use its existing data as a base.
17 | -o The GCI file to output. If omitted, no data will be written.
18 | -h, --help Displays this usage text.
19 | --nopack Do not pack the GCI, so you can inspect the outputted data.
20 | --silent Suppress command line output, except for fatal errors.
21 | --debug Output extra information while compiling and on errors.
22 |
23 | You can omit script_path to pack or unpack a GCI without changing its content.
24 | """
25 |
26 |
27 | def main(argv):
28 | try:
29 | opts, args = getopt.getopt(argv[1:],'i:o:h',['help','nopack','silent','debug'])
30 | except getopt.GetoptError:
31 | return 2
32 | if len(args) > 1:
33 | return 2
34 | elif len(args) == 0 and len(opts) == 0:
35 | return 0
36 | script_path = args[0] if args else None
37 | input_gci = None
38 | output_gci = None
39 | nopack = False
40 | silent = False
41 | debug = False
42 | usage = False
43 | error = False
44 | for opt, arg in opts:
45 | match opt:
46 | case '-h'|'--help': usage = True
47 | case '-i': input_gci = arg
48 | case '-o': output_gci = arg
49 | case '--nopack': nopack = True
50 | case '--silent': silent = True
51 | case '--debug': debug = True
52 | case _: error = True
53 | if usage:
54 | print(USAGE_TEXT)
55 | return 0
56 | if error:
57 | return 2
58 | if not script_path:
59 | logger.warning("No MGC script specified; no custom data will be compiled")
60 | try:
61 | gci_data = compiler.init(script_path, input_gci_path=input_gci,
62 | nopack=nopack, silent=silent, debug=debug)
63 | except CompileError as e:
64 | if debug:
65 | raise
66 | else:
67 | logger.error(e.message)
68 | _cleanup(script_path)
69 | return 10
70 | logger.info("Compile successful")
71 | if not output_gci:
72 | logger.info("No output GCI specified; no files will be written")
73 | else:
74 | if nopack:
75 | msg = "Writing unpacked GCI file; not loadable by Melee"
76 | else:
77 | msg = "Writing final GCI file"
78 | logger.info(msg)
79 | _write_gci(output_gci, gci_data, debug)
80 | md5 = hashlib.md5(gci_data).hexdigest()
81 | logger.info(f"MD5: {md5}")
82 | logger.info("All tasks finished")
83 | _cleanup(script_path)
84 | return 0
85 |
86 |
87 | def _write_gci(path: str, data: bytes, debug: bool):
88 | try:
89 | with open(path, 'wb') as f: f.write(data)
90 | except Exception as e:
91 | if debug:
92 | raise
93 | else:
94 | logger.error(f"Couldn't write GCI file: {e}")
95 |
96 |
97 | def _cleanup(script_path):
98 | """Deletes temp files generated by the ASM compiler."""
99 | if not script_path:
100 | return
101 | tmp_path = Path(script_path).parent/"tmp"
102 | (tmp_path/"code.txt").unlink(missing_ok=True)
103 | (tmp_path/"code.bin").unlink(missing_ok=True)
104 | (tmp_path/"src1.o").unlink(missing_ok=True)
105 | (tmp_path/"src2.o").unlink(missing_ok=True)
106 | # Remove tmp directory only if empty
107 | try:
108 | tmp_path.rmdir()
109 | except Exception:
110 | pass
111 | return
112 |
113 |
114 | if __name__ == "__main__":
115 | r = main(sys.argv)
116 | if r == 2:
117 | print(USAGE_TEXT)
118 | sys.exit(r)
119 |
120 |
--------------------------------------------------------------------------------
/mgc/asm.py:
--------------------------------------------------------------------------------
1 | """asm.py: Compiles ASM using pyiiasmh."""
2 | import re
3 | from .pyiiasmh import ppctools
4 | from .errors import BuildError
5 | from . import context
6 |
7 | def _make_tmp_directory():
8 | root = context.root()
9 | tmp_directory = root.path.parent/'tmp'
10 | try:
11 | tmp_directory.mkdir(exist_ok=True)
12 | except FileNotFoundError:
13 | raise BuildError("Unable to create tmp directory")
14 | return tmp_directory
15 |
16 |
17 | def _compile(asm: list[str]) -> str:
18 | tmp_dir = _make_tmp_directory()
19 | txtfile = tmp_dir/"code.txt"
20 | with open(txtfile, 'w') as f:
21 | f.write('\n'.join(asm))
22 | with context.top() as c:
23 | try:
24 | compiled_asm = ppctools.asm_opcodes(tmp_dir)
25 | except RuntimeError as e:
26 | r = re.search(r'code\.txt\:(\d+)\: Error: (.*?)\\', str(e))
27 | if r:
28 | asm_line_number = int(r.group(1))
29 | error = r.group(2)
30 | if c.line_number:
31 | c.line_number += asm_line_number
32 | else:
33 | c.line_number = asm_line_number
34 | raise BuildError(f"Error compiling ASM: {error}")
35 | else:
36 | raise BuildError(f"Error compiling ASM")
37 | except Exception as e:
38 | raise BuildError(f"Error compiling ASM: {e}")
39 | return compiled_asm
40 |
41 |
42 | def compile_asm(asm: list[str]) -> bytes:
43 | """Takes ASM and compiles it to hex using pyiiasmh."""
44 | return bytes.fromhex(_compile(asm))
45 |
46 |
47 | def compile_c2(asm: list[str], c2_ba: int) -> bytes:
48 | """Takes ASM and compiles it into a C2 code using pyiiasmh."""
49 | compiled_asm = _compile(asm)
50 | c2_ba_str = "%08x" % c2_ba
51 | try:
52 | compiled_c2 = ppctools.construct_code(compiled_asm, bapo=c2_ba_str, ctype='C2D2')
53 | except Exception as e:
54 | raise BuildError(f"Error compiling ASM: {e}")
55 | return bytes.fromhex(compiled_c2)
56 |
57 |
--------------------------------------------------------------------------------
/mgc/commands.py:
--------------------------------------------------------------------------------
1 | """commands.py: Commands that were parsed from an MGC script file. These
2 | commands take the current compiler state and modify or add data to it."""
3 | from typing import Callable
4 | from pathlib import Path
5 | from . import logger
6 | from .files import asm_file, bin_file, gecko_file, mgc_file
7 | from .datatypes import CompilerState
8 | from .datatypes import WriteEntry, WriteEntryList
9 | from .errors import CompileError
10 | from .context import Context
11 | from .context import in_stack
12 |
13 |
14 | def loc(address: int, state: CompilerState) -> CompilerState:
15 | """Sets the loc pointer."""
16 | state.gci_pointer_mode = False
17 | state.patch_mode = False
18 | state.pointer = address
19 | return state
20 |
21 |
22 | def gci(address: int, state: CompilerState) -> CompilerState:
23 | """Sets the gci pointer."""
24 | state.gci_pointer_mode = True
25 | state.patch_mode = False
26 | state.pointer = address
27 | return state
28 |
29 |
30 | def patch(address: int, state: CompilerState) -> CompilerState:
31 | """Sets the patch pointer."""
32 | state.gci_pointer_mode = True
33 | state.patch_mode = True
34 | state.pointer = address
35 | return state
36 |
37 |
38 | def add(amount: int, state: CompilerState) -> CompilerState:
39 | """Adds to the currently-active pointer."""
40 | state.pointer += amount
41 | return state
42 |
43 |
44 | def write(data: bytes, state: CompilerState) -> CompilerState:
45 | """(Base class) Writes data to the write table."""
46 | entries = WriteEntryList(data, state)
47 | if state.patch_mode:
48 | state.patch_table += entries
49 | else:
50 | _check_collisions(state.write_table, entries)
51 | state.write_table += entries
52 | state.pointer += sum([len(entry.data) for entry in entries])
53 | return state
54 |
55 |
56 | def _check_collisions(old_entries: list[WriteEntry], new_entries: list[WriteEntry]) -> None:
57 | """Checks for and warns about collisions in the write table."""
58 | for curr in new_entries:
59 | for prev in old_entries:
60 | if curr.intersects(prev):
61 | logger.warning(f"GCI location 0x{max(prev.address, curr.address):x} "
62 | f"was already written to by {prev.context.path.name} "
63 | f"(Line {prev.context.line_number+1}) and is being overwritten")
64 | return
65 |
66 |
67 | def string(data: str, state: CompilerState) -> CompilerState:
68 | """Writes a string as bytes to the write table. Escape characters are decoded."""
69 | b = bytes(bytes(data, 'utf-8').decode("unicode_escape"), encoding='ascii')
70 | return write(b, state)
71 |
72 |
73 | def fill(count: int, pattern: bytes, state: CompilerState) -> CompilerState:
74 | """Writes a fill pattern to the write table."""
75 | b = pattern * count
76 | return write(b, state)
77 |
78 |
79 | def _file(path: str, filetype: Callable[[Path], bytes], state: CompilerState) -> CompilerState:
80 | """(Base class) Writes a file to the write table."""
81 | binpath = (state.path/Path(path)).resolve()
82 | if binpath not in state.bin_files:
83 | state.bin_files[binpath] = filetype(binpath)
84 | data = state.bin_files[binpath]
85 | return write(data, state)
86 |
87 |
88 | def bin(path: str, state: CompilerState) -> CompilerState:
89 | """Writes a binary file to the write table."""
90 | return _file(path, bin_file, state)
91 |
92 |
93 | def asmsrc(path: str, state: CompilerState) -> CompilerState:
94 | """Writes a compiled version of an ASM source file to the write table."""
95 | return _file(path, asm_file, state)
96 |
97 |
98 | def geckocodelist(path: str, state: CompilerState) -> CompilerState:
99 | """Writes a Gecko codelist file to the write table."""
100 | return _file(path, gecko_file, state)
101 |
102 |
103 | def src(path: str, state: CompilerState) -> CompilerState:
104 | """Sources and compiles an MGC file."""
105 | oldpath = state.path
106 | filepath = (state.path/Path(path)).resolve()
107 | if filepath not in state.mgc_files:
108 | state.mgc_files[filepath] = mgc_file(filepath)
109 | state.path = filepath.parent
110 | state = _compile_file(filepath, state)
111 | state.path = oldpath
112 | return state
113 |
114 |
115 | def _compile_file(path: Path, state: CompilerState) -> CompilerState:
116 | """Compiles an MGC file requested by the !src command."""
117 | if in_stack(path):
118 | raise CompileError("MGC files are sourcing each other in an infinite loop")
119 | with Context(path) as c:
120 | for line in state.mgc_files[path]:
121 | c.line_number = line.line_number
122 | func = _FUNCS[line.command]
123 | if state.current_macro and func is not macroend:
124 | state.macro_files[state.current_macro].append(line)
125 | else:
126 | state = func(*line.args, state.copy())
127 | if state.current_macro:
128 | raise CompileError("Macro does not have an end")
129 | return state
130 |
131 |
132 | def asm(data: bytes, state: CompilerState) -> CompilerState:
133 | """Writes a compiled version of an ASM block to the write table."""
134 | return write(data, state)
135 |
136 |
137 | def c2(data: bytes, state: CompilerState) -> CompilerState:
138 | """Writes a compiled version of a C2 ASM block to the write table."""
139 | return write(data, state)
140 |
141 |
142 | def macro(name: str, state: CompilerState) -> CompilerState:
143 | """Defines a macro and adds all following commands to it until the end tag."""
144 | if state.current_macro:
145 | raise CompileError("Cannot define a macro inside another macro")
146 | state.current_macro = name
147 | if name in state.macro_files:
148 | logger.warning(f"Macro {name} already exists and is being overwritten")
149 | state.macro_files[name] = []
150 | return state
151 |
152 |
153 | def callmacro(name: str, count: int, state: CompilerState) -> CompilerState:
154 | """Calls and runs a macro that was defined earlier."""
155 | if state.current_macro:
156 | raise CompileError("Cannot call a macro from within another macro""")
157 | if name not in state.macro_files:
158 | raise CompileError(f"Macro {name} is undefined")
159 | for _ in range(count):
160 | for line in state.macro_files[name]:
161 | func = _FUNCS[line.command]
162 | state = func(*line.args, state.copy())
163 | return state
164 |
165 |
166 | def blockorder(b0: int, b1: int, b2: int, b3: int, b4: int,
167 | b5: int, b6: int, b7: int, b8: int, b9: int,
168 | state: CompilerState) -> CompilerState:
169 | """Changes the order that blocks get arranged in the GCI file."""
170 | block_order = [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9]
171 | for b in block_order:
172 | if b < 0:
173 | raise CompileError("Block number cannot be negative")
174 | elif b > 9:
175 | raise CompileError("Block number cannot be greater than 9")
176 | state.block_order = block_order
177 | return state
178 |
179 |
180 | def echo(message: str, state: CompilerState) -> CompilerState:
181 | """Logs a message."""
182 | logger.info(message)
183 | return state
184 |
185 |
186 | def macroend(state: CompilerState) -> CompilerState:
187 | """Ends a macro block or raises an error if orphaned."""
188 | if state.current_macro:
189 | state.current_macro = ''
190 | return state
191 | message = "!macroend is used without a !macro preceding it"
192 | raise CompileError(message)
193 |
194 |
195 | def asmend(_: CompilerState) -> CompilerState:
196 | """Not runnable. Signifies the end of an ASM block."""
197 | message = "!asmend is used without a !asm preceding it"
198 | raise CompileError(message)
199 |
200 |
201 | def c2end(_: CompilerState) -> CompilerState:
202 | """Not runnable. Signifies the end of a C2 ASM block."""
203 | message = "!c2end is used without a !c2 preceding it"
204 | raise CompileError(message)
205 |
206 |
207 | def begin(_: CompilerState) -> CompilerState:
208 | """Not runnable. Signifies the beginning of an MGC script."""
209 | message = "!begin is used more than once in this file"
210 | raise CompileError(message)
211 |
212 |
213 | def end(_: CompilerState) -> CompilerState:
214 | """Not runnable. Signifies the end of an MGC script."""
215 | message = "!end is used more than once in this file"
216 | raise CompileError(message)
217 |
218 |
219 | def define(_: CompilerState) -> CompilerState:
220 | """Unreachable, because aliases are handled in preprocessing."""
221 | message = "Preprocessor failed to handle !define"
222 | raise CompileError(message)
223 |
224 |
225 | _FUNCS: dict[str, Callable] = {
226 | 'loc': loc,
227 | 'gci': gci,
228 | 'patch': patch,
229 | 'add': add,
230 | 'write': write,
231 | 'src': src,
232 | 'asmsrc': asmsrc,
233 | 'file': bin,
234 | 'bin': bin,
235 | 'geckocodelist': geckocodelist,
236 | 'string': string,
237 | 'fill': fill,
238 | 'asm': asm,
239 | 'asmend': asmend,
240 | 'c2': c2,
241 | 'c2end': c2end,
242 | 'begin': begin,
243 | 'end': end,
244 | 'echo': echo,
245 | 'macro': macro,
246 | 'macroend': macroend,
247 | 'callmacro': callmacro,
248 | 'blockorder': blockorder,
249 | 'define': define
250 | }
251 |
252 |
--------------------------------------------------------------------------------
/mgc/compiler.py:
--------------------------------------------------------------------------------
1 | """compiler.py: Compiles MGC files into a block of data that is ready to write
2 | to the GCI."""
3 | from pathlib import Path
4 | from . import logger
5 | from .commands import src
6 | from .errors import CompileError
7 | from .gci_tools.meleegci import melee_gamedata
8 | from .datatypes import CompilerState
9 |
10 |
11 | def _init_new_gci() -> melee_gamedata:
12 | """Creates a new gamedata object from the init_gci MGC script."""
13 | init_gci_path = Path(__file__).parent/"init_gci"/"init_gci.mgc"
14 | silent = logger.silent_log
15 | logger.silent_log = True
16 | state = src(str(init_gci_path), CompilerState())
17 | gci_data = bytearray(0x16040)
18 | for w in state.write_table:
19 | gci_data[w.address:w.address+len(w.data)] = w.data
20 | logger.silent_log = silent
21 | return melee_gamedata(raw_bytes=gci_data)
22 |
23 |
24 | def _load_gci(gci_path: str) -> melee_gamedata:
25 | """Creates a gamedata object by loading an existing GCI file."""
26 | try:
27 | input_gci = melee_gamedata(filename=gci_path, packed=True)
28 | except FileNotFoundError:
29 | raise CompileError(f"Input GCI not found: {gci_path}")
30 | try:
31 | input_gci.unpack()
32 | except Exception as e:
33 | raise CompileError(f"GCI decoder: {e}")
34 | gci_data = input_gci.raw_bytes
35 | if len(gci_data) != 0x16040:
36 | raise CompileError(f"Input GCI is the wrong size; make sure it's a Melee save file")
37 | return input_gci
38 |
39 |
40 | def init(root_mgc_path: str=None, input_gci_path: str=None, silent=False, debug=False, nopack=False) -> bytearray:
41 | """Begins compilation by taking a root MGC path and parameters, then
42 | returns the raw bytes of the final GCI."""
43 | logger.silent_log = silent
44 | logger.debug_log = debug
45 | if input_gci_path:
46 | logger.info("Loading and unpacking input GCI")
47 | input_gci = _load_gci(input_gci_path)
48 | else:
49 | logger.info("Initializing new GCI")
50 | input_gci = _init_new_gci()
51 | if root_mgc_path:
52 | state = src(root_mgc_path, CompilerState())
53 | for w in state.write_table:
54 | input_gci.raw_bytes[w.address:w.address+len(w.data)] = w.data
55 | if state.block_order:
56 | input_gci.block_order = state.block_order
57 | input_gci.reorder_blocks()
58 | for w in state.patch_table:
59 | input_gci.raw_bytes[w.address:w.address+len(w.data)] = w.data
60 | input_gci.recompute_checksums()
61 | if not nopack:
62 | logger.info("Packing GCI")
63 | input_gci.pack()
64 | return input_gci.raw_bytes
65 |
66 |
--------------------------------------------------------------------------------
/mgc/context.py:
--------------------------------------------------------------------------------
1 | """context.py: A class and context stack that keeps track of the current file
2 | and line number. Used for logging and detecting circular imports."""
3 | from pathlib import Path
4 |
5 |
6 | class Context:
7 | """A file and line number responsible for the current operation.
8 | Contexts can be created using the with statement, and the line number can
9 | be updated as the relevant file is traversed."""
10 |
11 | def __init__(self, path: Path, line_number: int=-1):
12 | self.path = path
13 | self.line_number = line_number
14 |
15 | def __repr__(self):
16 | return f"{self.path.name} line {self.line_number+1}"
17 |
18 | def __enter__(self):
19 | _context_stack.append(self)
20 | return self
21 |
22 | def __exit__(self, type, value, traceback):
23 | # If receiving an Exception, preserve the context stack
24 | if type is not None:
25 | return
26 | # Otherwise, remove us from the top of the context stack
27 | if _context_stack[-1] is not self:
28 | raise IndexError(f"Attempting to remove a non-top-level context: {self}")
29 | else:
30 | _context_stack.pop()
31 |
32 | def copy(self) -> 'Context':
33 | return Context(self.path, self.line_number)
34 |
35 |
36 | EMPTY_CONTEXT = Context(Path())
37 | _context_stack = [EMPTY_CONTEXT]
38 |
39 |
40 | def in_stack(path: Path) -> bool:
41 | """Determines whether a given path is already in the context stack."""
42 | return path in [c.path for c in _context_stack]
43 |
44 |
45 | def top() -> Context:
46 | """Returns the top context to use for log messages."""
47 | return _context_stack[-1].copy()
48 |
49 |
50 | def root() -> Context:
51 | """Returns the root context."""
52 | if len(_context_stack) > 1:
53 | return _context_stack[1]
54 | else:
55 | return _context_stack[0]
56 |
57 |
--------------------------------------------------------------------------------
/mgc/datatypes.py:
--------------------------------------------------------------------------------
1 | """Contains custom data classes that are used throughout compilation."""
2 |
3 | from copy import copy
4 | from pathlib import Path
5 | from dataclasses import dataclass, field
6 | from .errors import CompileError
7 | from .gci_tools.mem2gci import *
8 | from . import logger
9 | from . import context
10 | from .context import Context
11 | from typing import NamedTuple
12 |
13 |
14 | @dataclass
15 | class WriteEntry:
16 | """An entry of data to write to the GCI."""
17 | address: int
18 | data: bytes
19 | context: Context = field(default_factory=context.top, init=False)
20 |
21 | def intersects(self, entry: 'WriteEntry') -> bool:
22 | """Tests if two WriteEntries intersect with each other."""
23 | return ((self.address <= entry.address and
24 | self.address + len(self.data) > entry.address) or
25 | (entry.address <= self.address and
26 | entry.address + len(entry.data) > self.address))
27 |
28 |
29 | class MGCLine(NamedTuple):
30 | """A parsed MGC script line, containing the line number from its original
31 | file (for logging) and a command. The command already has all its args
32 | in place; it only needs a CompilerState as a parameter when called."""
33 | line_number: int
34 | command: str
35 | args: list
36 |
37 |
38 | class CompilerState:
39 | """Keeps track of the current state of the compiler."""
40 |
41 | def __init__(self):
42 | self.path: Path = Path()
43 | self.pointer: int = 0
44 | self.gci_pointer_mode: bool = False
45 | self.patch_mode: bool = False
46 | self.current_macro: str = ''
47 | self.mgc_files: dict[Path, list[MGCLine]] = {}
48 | self.bin_files: dict[Path, bytes] = {}
49 | self.macro_files: dict[str, list[MGCLine]] = {}
50 | self.write_table: list[WriteEntry] = []
51 | self.patch_table: list[WriteEntry] = []
52 | self.block_order: list[int] = []
53 |
54 | def copy(self) -> 'CompilerState':
55 | """Easily creates a shallow copy of this object."""
56 | return copy(self)
57 |
58 |
59 | def WriteEntryList(data: bytes, state: CompilerState) -> list[WriteEntry]:
60 | """Creates a list of write entries out of data."""
61 | if state.gci_pointer_mode:
62 | logger.debug(f"Writing 0x{len(data):x} bytes in gci mode:")
63 | if state.pointer < 0:
64 | raise CompileError("Data pointer must be a positive value")
65 | if state.pointer + len(data) > 0x16040:
66 | raise CompileError("Attempting to write past the end of the GCI")
67 | logger.debug(f" 0x{len(data):x} bytes to 0x{state.pointer:x}")
68 | return [WriteEntry(state.pointer, data)]
69 | else:
70 | logger.debug(f"Writing 0x{len(data):x} bytes in loc mode:")
71 | if state.pointer < 0:
72 | raise CompileError("Data pointer must be a positive value")
73 | try:
74 | entries = data2gci(state.pointer, data)
75 | except ValueError as e:
76 | raise CompileError(e.args[0])
77 | for pointer, data in entries:
78 | logger.debug(f" 0x{len(data):x} bytes to 0x{pointer:x}")
79 | return [WriteEntry(*entry) for entry in entries]
80 |
81 |
--------------------------------------------------------------------------------
/mgc/errors.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 |
4 | @dataclass
5 | class CompileError(Exception):
6 | """Raised when there is an error during execution of script commands."""
7 | message: str
8 |
9 |
10 | class BuildError(CompileError):
11 | """Raised when there is an error during a file building process."""
12 | pass
13 |
14 |
--------------------------------------------------------------------------------
/mgc/files.py:
--------------------------------------------------------------------------------
1 | """files.py: Loads and builds files when sourced by the MGC script."""
2 | from pathlib import Path
3 | from . import logger
4 | from . import line
5 | from . import asm
6 | from .datatypes import MGCLine
7 | from .context import Context
8 | from .errors import BuildError
9 |
10 |
11 | def bin_file(path: Path) -> bytes:
12 | """A binary file loaded from disk."""
13 | logger.info(f"Reading binary file {path.name}")
14 | data = _read_bin_file(path)
15 | return data
16 |
17 |
18 | def asm_file(path: Path) -> bytes:
19 | """An ASM file loaded from disk and compiled into binary."""
20 | logger.info(f"Reading ASM source file {path.name}")
21 | data = _read_text_file(path)
22 | return _build_asmfile(data)
23 |
24 |
25 | def gecko_file(path: Path) -> bytes:
26 | """A Gecko codelist file loaded from disk and compiled into binary."""
27 | logger.info(f"Reading Gecko codelist file {path.name}")
28 | data = _read_text_file(path)
29 | return _build_geckofile(path, data)
30 |
31 |
32 | def mgc_file(path: Path) -> list:
33 | """An MGC file loaded from disk and parsed into a Command list."""
34 | logger.info(f"Reading MGC file {path.name}")
35 | data = _read_text_file(path)
36 | return _build_mgcfile(path, data)
37 |
38 |
39 | def _read_bin_file(path: Path) -> bytes:
40 | """Reads a binary file from disk and returns the byte array"""
41 | try:
42 | with path.open('rb') as f:
43 | data = f.read()
44 | except FileNotFoundError:
45 | raise BuildError(f"File not found: {str(path)}")
46 | return data
47 |
48 |
49 | def _read_text_file(path: Path) -> list[str]:
50 | """Reads a text file from disk and returns a list of each line of data"""
51 | try:
52 | with path.open('r') as f:
53 | data = f.readlines()
54 | except FileNotFoundError:
55 | raise BuildError(f"File not found: {str(path)}")
56 | except UnicodeDecodeError:
57 | raise BuildError("Unable to read file; make sure it's a text file")
58 | return data
59 |
60 |
61 | def _build_asmfile(filedata: list[str]) -> bytes:
62 | """Builds an ASM file and returns it in bytes."""
63 | compiled_asm = asm.compile_asm(filedata)
64 | return compiled_asm
65 |
66 |
67 | def _build_geckofile(path: Path, data: list[str]):
68 | """Builds a file in Gecko codelist format and returns it in bytes."""
69 | with Context(path) as c:
70 | header = bytes.fromhex('00d0c0de00d0c0de')
71 | footer = bytes.fromhex('f000000000000000')
72 | bytedata = bytes()
73 | for line_number, line in enumerate(data):
74 | c.line_number = line_number
75 | if line[0] != '*':
76 | continue
77 | line = line[1:]
78 | try:
79 | bytedata += bytes.fromhex(line)
80 | except ValueError:
81 | raise BuildError("Invalid Gecko code line")
82 | return header + bytedata + footer
83 |
84 |
85 | def _build_mgcfile(path: Path, data: list[str]) -> list[MGCLine]:
86 | """Builds an MGC script file and returns it as a list of commands."""
87 | with Context(path) as c:
88 | start, end = _preprocess_begin_end(data)
89 | op_lines = []
90 | asm_lines = []
91 | asm_cmd = ''
92 | asm_args = []
93 | for line_number, script_line in enumerate(data[start:end], start=start):
94 | if asm_cmd:
95 | if not line.is_command(script_line, asm_cmd + 'end'):
96 | asm_lines.append(script_line)
97 | continue
98 | if asm_cmd == 'c2':
99 | asmdata = asm.compile_c2(asm_lines, asm_args[0])
100 | else:
101 | asmdata = asm.compile_asm(asm_lines)
102 | op_lines.append(MGCLine(c.line_number, asm_cmd, [asmdata]))
103 | asm_cmd = ''
104 | asm_args = []
105 | asm_lines.clear()
106 | else:
107 | c.line_number = line_number
108 | command, args = line.parse(script_line)
109 | if not command:
110 | continue
111 | if command in ['asm', 'c2']:
112 | asm_cmd = command
113 | asm_args = args
114 | else:
115 | op_lines.append(MGCLine(line_number, command, args))
116 | if asm_cmd:
117 | raise BuildError("Command does not have an end specified")
118 | return op_lines
119 |
120 |
121 | def _preprocess_begin_end(filedata):
122 | """Finds the !begin and !end lines of the MGC file."""
123 | start_line = 0
124 | end_line = len(filedata)
125 | for line_number, script_line in enumerate(filedata):
126 | if line.is_command(script_line, 'begin'):
127 | start_line = line_number+1
128 | break
129 | for line_number, script_line in enumerate(reversed(filedata)):
130 | if line.is_command(script_line, 'end'):
131 | end_line = len(filedata)-line_number-1
132 | break
133 | return start_line, end_line
134 |
135 |
--------------------------------------------------------------------------------
/mgc/gci_tools/gci_encode.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python
2 | """gci_encode.py: Encodes and decodes bytes in a Melee GCI file."""
3 |
4 | from .ppc_opcodes import *
5 |
6 | CHECKSUM_SEED = bytes([
7 | 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
8 | 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10
9 | ])
10 |
11 | UNK_ARR = [
12 | 0x00000026,
13 | 0x000000FF,
14 | 0x000000E8,
15 | 0x000000EF,
16 | 0x00000042,
17 | 0x000000D6,
18 | 0x00000001,
19 | 0x00000054,
20 | 0x00000014,
21 | 0x000000A3,
22 | 0x00000080,
23 | 0x000000FD,
24 | 0x0000006E,
25 | ]
26 |
27 | def decode_byte(prev_byte: int, current_byte: int) -> int:
28 | """Decodes a byte from an encoded GCI."""
29 | r0 = r3 = r4 = r5 = r6 = r7 = 0
30 |
31 | if prev_byte == 0:
32 | r5 = 0x92492493
33 | r5 = ((r5 * prev_byte) >> 32) & 0xffffffff
34 | else:
35 | r5 = ~0x92492493 & 0xffffffff
36 | r5 = (~(r5 * prev_byte) >> 32) & 0xffffffff
37 |
38 | r5 = (r5 + prev_byte) & 0xff
39 | r5 = r5 >> 2
40 | r6 = rlwinm(r5, 1, 31, 31)
41 | r5 = r5 + r6
42 | r5 = (r5 * 7) & 0xffffffff
43 | r7 = (prev_byte - r5) & 0xffffffff
44 |
45 | if r7 == 0:
46 | r5 = rlwinm(current_byte, 1, 29, 29);
47 | r5 = rlwimi(r5, current_byte, 0, 31, 31);
48 | r5 = rlwimi(r5, current_byte, 2, 27, 27);
49 | r5 = rlwimi(r5, current_byte, 3, 25, 25);
50 | r5 = rlwimi(r5, current_byte, 29, 30, 30);
51 | r5 = rlwimi(r5, current_byte, 30, 28, 28);
52 | r5 = rlwimi(r5, current_byte, 31, 26, 26);
53 | r5 = rlwimi(r5, current_byte, 0, 24, 24);
54 | r4 = rlwinm(r5, 0, 24, 31);
55 | elif r7 == 1:
56 | r5 = rlwinm(current_byte, 6, 24, 24);
57 | r5 = rlwimi(r5, current_byte, 1, 30, 30);
58 | r5 = rlwimi(r5, current_byte, 0, 29, 29);
59 | r5 = rlwimi(r5, current_byte, 29, 31, 31);
60 | r5 = rlwimi(r5, current_byte, 1, 26, 26);
61 | r5 = rlwimi(r5, current_byte, 31, 27, 27);
62 | r5 = rlwimi(r5, current_byte, 29, 28, 28);
63 | r5 = rlwimi(r5, current_byte, 31, 25, 25);
64 | r4 = rlwinm(r5, 0, 24, 31);
65 | elif r7 == 2:
66 | r5 = rlwinm(current_byte, 2, 28, 28);
67 | r5 = rlwimi(r5, current_byte, 2, 29, 29);
68 | r5 = rlwimi(r5, current_byte, 4, 25, 25);
69 | r5 = rlwimi(r5, current_byte, 1, 27, 27);
70 | r5 = rlwimi(r5, current_byte, 3, 24, 24);
71 | r5 = rlwimi(r5, current_byte, 28, 30, 30);
72 | r5 = rlwimi(r5, current_byte, 26, 31, 31);
73 | r5 = rlwimi(r5, current_byte, 30, 26, 26);
74 | r4 = rlwinm(r5, 0, 24, 31);
75 | elif r7 == 3:
76 | r5 = rlwinm(current_byte, 31, 31, 31);
77 | r5 = rlwimi(r5, current_byte, 4, 27, 27);
78 | r5 = rlwimi(r5, current_byte, 3, 26, 26);
79 | r5 = rlwimi(r5, current_byte, 30, 30, 30);
80 | r5 = rlwimi(r5, current_byte, 31, 28, 28);
81 | r5 = rlwimi(r5, current_byte, 1, 25, 25);
82 | r5 = rlwimi(r5, current_byte, 1, 24, 24);
83 | r5 = rlwimi(r5, current_byte, 27, 29, 29);
84 | r4 = rlwinm(r5, 0, 24, 31);
85 | elif r7 == 4:
86 | r5 = rlwinm(current_byte, 4, 26, 26);
87 | r5 = rlwimi(r5, current_byte, 3, 28, 28);
88 | r5 = rlwimi(r5, current_byte, 31, 30, 30);
89 | r5 = rlwimi(r5, current_byte, 4, 24, 24);
90 | r5 = rlwimi(r5, current_byte, 2, 25, 25);
91 | r5 = rlwimi(r5, current_byte, 29, 29, 29);
92 | r5 = rlwimi(r5, current_byte, 30, 27, 27);
93 | r5 = rlwimi(r5, current_byte, 25, 31, 31);
94 | r4 = rlwinm(r5, 0, 24, 31);
95 | elif r7 == 5:
96 | r5 = rlwinm(current_byte, 5, 25, 25);
97 | r5 = rlwimi(r5, current_byte, 5, 26, 26);
98 | r5 = rlwimi(r5, current_byte, 5, 24, 24);
99 | r5 = rlwimi(r5, current_byte, 0, 28, 28);
100 | r5 = rlwimi(r5, current_byte, 30, 29, 29);
101 | r5 = rlwimi(r5, current_byte, 27, 31, 31);
102 | r5 = rlwimi(r5, current_byte, 27, 30, 30);
103 | r5 = rlwimi(r5, current_byte, 29, 27, 27);
104 | r4 = rlwinm(r5, 0, 24, 31);
105 | elif r7 == 6:
106 | r5 = rlwinm(current_byte, 0, 30, 30);
107 | r5 = rlwimi(r5, current_byte, 6, 25, 25);
108 | r5 = rlwimi(r5, current_byte, 30, 31, 31);
109 | r5 = rlwimi(r5, current_byte, 2, 26, 26);
110 | r5 = rlwimi(r5, current_byte, 0, 27, 27);
111 | r5 = rlwimi(r5, current_byte, 2, 24, 24);
112 | r5 = rlwimi(r5, current_byte, 28, 29, 29);
113 | r5 = rlwimi(r5, current_byte, 28, 28, 28);
114 | r4 = rlwinm(r5, 0, 24, 31);
115 | else:
116 | raise ValueError("r7 should be no greater than 6")
117 |
118 | r5 = 0x4ec4ec4f
119 | r5 = ((r5 * prev_byte) >> 32) & 0xffffffff
120 | r5 = r5 >> 2
121 | r6 = rlwinm(r5, 1, 31, 31)
122 | r5 = r5 + r6
123 | r5 = r5 * 13
124 | r0 = (prev_byte - r5) & 0xff
125 | r6 = rlwinm(r0, 2, 0, 29)
126 | r0 = UNK_ARR[int(r6 / 4)]
127 | r4 = r4 ^ r0
128 | r4 = (r4 ^ prev_byte) & 0xff
129 | r3 = r4 + 0
130 | return r3
131 |
132 |
133 |
134 | def encode_byte(prev_byte: int, current_byte: int) -> int:
135 | """Encodes a byte from a decoded GCI."""
136 | r0 = r3 = r5 = 0
137 |
138 | r5 = ((0x4ec4ec4f * prev_byte) >> 32) & 0xffffffff
139 |
140 | if prev_byte == 0:
141 | r0 = 0x92492493
142 | r0 = ((r0 * prev_byte) >> 32) & 0xffffffff
143 | else:
144 | r0 = ~0x92492493 & 0xffffffff
145 | r0 = (~(r0 * prev_byte) >> 32) & 0xffffffff
146 |
147 | r3 = r5 >> 2
148 | r5 = rlwinm(r3, 1, 31, 31)
149 | r0 = (r0 + prev_byte) & 0xff
150 | r3 = r3 + r5
151 | r0 = r0 >> 2
152 | r5 = r3 * 13
153 | r3 = rlwinm(r0, 1, 31, 31)
154 | r0 = r0 + r3
155 | r0 = r0 * 7
156 | r5 = (prev_byte - r5) & 0xff
157 | r0 = (prev_byte - r0) & 0xff
158 | r5 = rlwinm(r5, 2, 0, 29)
159 |
160 | r5 = UNK_ARR[int(r5 / 4)]
161 |
162 | r3 = prev_byte ^ current_byte
163 | r3 = r3 ^ r5
164 |
165 | if r0 > 6:
166 | raise ValueError("r0 should be no greater than 6")
167 | r0 = rlwinm(r0, 2, 0, 29)
168 | if r0 == 0x0:
169 | r0 = rlwinm(r3, 3, 27, 27);
170 | r0 = rlwimi(r0, r3, 0, 31, 31);
171 | r0 = rlwimi(r0, r3, 31, 30, 30);
172 | r0 = rlwimi(r0, r3, 2, 26, 26);
173 | r0 = rlwimi(r0, r3, 30, 29, 29);
174 | r0 = rlwimi(r0, r3, 1, 25, 25);
175 | r0 = rlwimi(r0, r3, 29, 28, 28);
176 | r0 = rlwimi(r0, r3, 0, 24, 24);
177 | r3 = rlwinm(r0, 0, 24, 31);
178 | elif r0 == 0x4:
179 | r0 = rlwinm(r3, 31, 31, 31);
180 | r0 = rlwimi(r0, r3, 3, 28, 28);
181 | r0 = rlwimi(r0, r3, 0, 29, 29);
182 | r0 = rlwimi(r0, r3, 3, 25, 25);
183 | r0 = rlwimi(r0, r3, 1, 26, 26);
184 | r0 = rlwimi(r0, r3, 31, 27, 27);
185 | r0 = rlwimi(r0, r3, 1, 24, 24);
186 | r0 = rlwimi(r0, r3, 26, 30, 30);
187 | r3 = rlwinm(r0, 0, 24, 31);
188 | elif r0 == 0x8:
189 | r0 = rlwinm(r3, 4, 26, 26);
190 | r0 = rlwimi(r0, r3, 6, 25, 25);
191 | r0 = rlwimi(r0, r3, 30, 31, 31);
192 | r0 = rlwimi(r0, r3, 30, 30, 30);
193 | r0 = rlwimi(r0, r3, 31, 28, 28);
194 | r0 = rlwimi(r0, r3, 2, 24, 24);
195 | r0 = rlwimi(r0, r3, 28, 29, 29);
196 | r0 = rlwimi(r0, r3, 29, 27, 27);
197 | r3 = rlwinm(r0, 0, 24, 31);
198 | elif r0 == 0xc:
199 | r0 = rlwinm(r3, 2, 28, 28);
200 | r0 = rlwimi(r0, r3, 1, 30, 30);
201 | r0 = rlwimi(r0, r3, 5, 24, 24);
202 | r0 = rlwimi(r0, r3, 1, 27, 27);
203 | r0 = rlwimi(r0, r3, 28, 31, 31);
204 | r0 = rlwimi(r0, r3, 29, 29, 29);
205 | r0 = rlwimi(r0, r3, 31, 26, 26);
206 | r0 = rlwimi(r0, r3, 31, 25, 25);
207 | r3 = rlwinm(r0, 0, 24, 31);
208 | elif r0 == 0x10:
209 | r0 = rlwinm(r3, 1, 29, 29);
210 | r0 = rlwimi(r0, r3, 7, 24, 24);
211 | r0 = rlwimi(r0, r3, 3, 26, 26);
212 | r0 = rlwimi(r0, r3, 29, 31, 31);
213 | r0 = rlwimi(r0, r3, 2, 25, 25);
214 | r0 = rlwimi(r0, r3, 28, 30, 30);
215 | r0 = rlwimi(r0, r3, 30, 27, 27);
216 | r0 = rlwimi(r0, r3, 28, 28, 28);
217 | r3 = rlwinm(r0, 0, 24, 31);
218 | elif r0 == 0x14:
219 | r0 = rlwinm(r3, 5, 25, 25);
220 | r0 = rlwimi(r0, r3, 5, 26, 26);
221 | r0 = rlwimi(r0, r3, 2, 27, 27);
222 | r0 = rlwimi(r0, r3, 0, 28, 28);
223 | r0 = rlwimi(r0, r3, 3, 24, 24);
224 | r0 = rlwimi(r0, r3, 27, 31, 31);
225 | r0 = rlwimi(r0, r3, 27, 30, 30);
226 | r0 = rlwimi(r0, r3, 27, 29, 29);
227 | r3 = rlwinm(r0, 0, 24, 31);
228 | elif r0 == 0x18:
229 | r0 = rlwinm(r3, 0, 30, 30);
230 | r0 = rlwimi(r0, r3, 2, 29, 29);
231 | r0 = rlwimi(r0, r3, 4, 25, 25);
232 | r0 = rlwimi(r0, r3, 4, 24, 24);
233 | r0 = rlwimi(r0, r3, 0, 27, 27);
234 | r0 = rlwimi(r0, r3, 30, 28, 28);
235 | r0 = rlwimi(r0, r3, 26, 31, 31);
236 | r0 = rlwimi(r0, r3, 30, 26, 26);
237 | r3 = rlwinm(r0, 0, 24, 31);
238 | return r3
239 |
--------------------------------------------------------------------------------
/mgc/gci_tools/meleegci.py:
--------------------------------------------------------------------------------
1 | """ meleegci.py - interfaces for manipulating Melee savefiles """
2 |
3 | import struct
4 |
5 | from .gci_encode import decode_byte as unpack
6 | from .gci_encode import encode_byte as pack
7 | from .. import logger
8 |
9 | class melee_gci(object):
10 | """ Base class for GCI files. Just basic setter/getter stuff for dentry
11 | data, and some machinery for reading files """
12 |
13 | def __init__(self, filename=None, raw_bytes=None, packed=None):
14 | if filename:
15 | self.raw_bytes = bytearray()
16 | try:
17 | with open(filename, "rb") as fd:
18 | self.raw_bytes = bytearray(fd.read())
19 | self.filesize = len(self.raw_bytes)
20 | self.packed = packed
21 | logger.debug("Read {} bytes from input GCI".format(hex(self.filesize)))
22 | except FileNotFoundError:
23 | raise
24 | elif raw_bytes:
25 | self.raw_bytes = raw_bytes
26 | self.filesize = len(raw_bytes)
27 | else:
28 | return None
29 | # Let the user tell us whether or not the GCI is packed when importing
30 | # a file - this should help us tell the user not to do something that
31 | # might end up corrupting their data (or something to that effect).
32 | self.packed = packed
33 | # Some Melee save files have a different block order, so the user can
34 | # change this if desired.
35 | self.block_order = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
36 |
37 | ''' These functions return other types '''
38 |
39 | def blocksize(self):
40 | return struct.unpack(">h", self.raw_bytes[0x38:0x3a])[0]
41 |
42 | ''' These functions return raw bytes '''
43 |
44 | def dump(self):
45 | return self.raw_bytes
46 | def get_dentry(self):
47 | return self.raw_bytes[0:0x40]
48 | def get_game_id(self):
49 | return self.raw_bytes[0x00:0x04]
50 | def get_maker_code(self):
51 | return self.raw_bytes[0x04:0x06]
52 | def get_filename(self):
53 | return self.raw_bytes[0x08:0x28]
54 | def get_modtime(self):
55 | return self.raw_bytes[0x28:0x2c]
56 | def get_image_off(self):
57 | return self.raw_bytes[0x2c:0x30]
58 | def get_icon_fmt(self):
59 | return self.raw_bytes[0x30:0x32]
60 | def get_anim_speed(self):
61 | return self.raw_bytes[0x32:0x34]
62 | def get_permissions(self):
63 | return self.raw_bytes[0x34:0x35]
64 | def get_copy_ctr(self):
65 | return self.raw_bytes[0x35:0x36]
66 | def get_first_block(self):
67 | return self.raw_bytes[0x36:0x38]
68 | def get_block_count(self):
69 | return self.raw_bytes[0x38:0x3a]
70 | def get_comment_addr(self):
71 | return self.raw_bytes[0x3c:0x40]
72 | def set_filename(self, new_filename):
73 | self.raw_bytes[0x08:0x28] = new_filename
74 | def set_modtime(self, new_modtime):
75 | self.raw_bytes[0x28:0x2c] = struct.pack(">L", new_modtime)
76 | def set_block_count(self, new_bc):
77 | self.raw_bytes[0x38:0x3a] = new_bc
78 | def set_comment_addr(self, new_addr):
79 | self.raw_bytes[0x3c:0x40] = new_addr
80 | def set_permissions(self, new_perm):
81 | self.raw_bytes[0x34:0x35] = struct.pack(">B", new_perm)
82 | def _checksum(self, target_offset, count):
83 | """ Given some offset into raw_bytes and a count, compute checksum
84 | over the set of bytes in the GCI """
85 |
86 | # This is the seed for all checksum values
87 | new_checksum = bytearray( b'\x01\x23\x45\x67\x89\xAB\xCD\xEF' +
88 | b'\xFE\xDC\xBA\x98\x76\x54\x32\x10' )
89 | cur = 0
90 | cur_arr = 0
91 | arr_pos = 0
92 | x = 0
93 | y = 0
94 | ctr = (count) / 8
95 | while (ctr > 0):
96 | for i in range(0, 8):
97 | cur = self.raw_bytes[target_offset + i]
98 | cur_arr = new_checksum[(arr_pos & 0xf)]
99 | new_checksum[(arr_pos & 0xf)] = (cur + cur_arr) & 0xff
100 | arr_pos += 1
101 | ctr -= 1
102 | target_offset += 8
103 | for i in range(1, 0xf):
104 | x = new_checksum[i-1]
105 | y = new_checksum[i]
106 | if (x == y):
107 | x = y ^ 0x00FF
108 | new_checksum[i] = x
109 | return new_checksum
110 |
111 | class melee_gamedata(melee_gci):
112 | ''' Class representing a plain-ol' Melee gamedata savefile (0x16040 bytes).
113 | The checksum/packing functions here are specific to the format,
114 | so you'll need another class for other types of save files. '''
115 |
116 | def get_raw_checksum(self, blknum):
117 | """ Return checksum bytes for some block 0-10 """
118 | base_offset = 0x2040
119 | if (blknum >= 0) and (blknum <= (self.blocksize()-1)):
120 | target_offset = base_offset + (blknum * 0x2000)
121 | return self.raw_bytes[target_offset:target_offset + 0x10]
122 | else:
123 | return None
124 |
125 | def set_raw_checksum(self, blknum, new_checksum):
126 | """ Given some blknum 0-10 and a 0x10-byte bytearray, replace the
127 | specified checksum bytes with the new bytes """
128 | base_offset = 0x2040
129 | if (blknum >= 0) and (blknum <= (self.blocksize() -1)):
130 | target_offset = base_offset + (blknum * 0x2000)
131 | self.raw_bytes[target_offset:target_offset + 0x10] = new_checksum
132 | else:
133 | raise Exception("Can't set checksum bytes for block {}".format(blknum))
134 |
135 |
136 | def checksum_block(self, blknum):
137 | """ Given some block number 0-10, compute the checksum for the
138 | associated data. Returns the raw checksum bytes. """
139 | base_offset = 0x2050
140 | data_size = 0x1ff0
141 | if (blknum >= 0) and (blknum <= (self.blocksize() - 1)):
142 | target_offset = base_offset + (blknum * 0x2000)
143 | return self._checksum(target_offset, data_size)
144 | else:
145 | raise Exception("Can't compute checksum bytes for block {}".format(blknum))
146 |
147 |
148 | def recompute_checksums(self):
149 | """ Recompute all checksum values and write them back """
150 | if (self.packed is True):
151 | raise Exception("You can only recompute checksums on unpacked data")
152 |
153 |
154 | # Retrieve checksum values for all blocks
155 | current = []
156 | for i in range(0, self.blocksize()-1):
157 | current.append(self.get_raw_checksum(i))
158 |
159 | # Compute checksum values for all blocks
160 | computed = []
161 | for i in range(0, self.blocksize()-1):
162 | computed.append(self.checksum_block(i))
163 |
164 | # If current checksums don't match, write them back
165 | for i in range(0, self.blocksize()-1):
166 | if (current[i] != computed[i]):
167 | logger.debug("Block {} checksum mismatch, fixing ..".format(i))
168 | self.set_raw_checksum(i, computed[i])
169 | else:
170 | logger.debug("Block {} checksum unchanged".format(i))
171 |
172 | def get_block(self, blknum):
173 | ''' Get the data portion of some block '''
174 | if (blknum > 10):
175 | return None
176 | base = 0x2000 * blknum + 0x2060
177 | return self.raw_bytes[base:(base + 0x1fe0)]
178 |
179 | def set_block(self, blknum, data):
180 | ''' Set the data on some block; takes a 0x1fe0-byte bytearray '''
181 | if (blknum > 10):
182 | return None
183 | base = 0x2000 * blknum + 0x2060
184 | self.raw_bytes[base:(base + 0x1fe0)] = data
185 |
186 | def reorder_blocks(self):
187 | ''' Reorder the blocks according to block_order '''
188 | new_bytes = bytearray(len(self.raw_bytes))
189 | for index, blknum in enumerate(self.block_order):
190 | base = 0x2000 * blknum + 0x2040
191 | newbase = 0x2000 * index + 0x2040
192 | new_bytes[newbase:(newbase + 0x2000)] = self.raw_bytes[base:(base + 0x2000)]
193 | self.raw_bytes[0x2040:] = new_bytes[0x2040:]
194 | def unpack(self):
195 | """ Unpack all blocks of data """
196 | if (self.packed is False):
197 | raise Exception("Data is already unpacked - refusing to unpack")
198 |
199 | logger.debug("Unpacking GCI data")
200 |
201 | PREV_BYTE_OFFSET = 0x204f
202 | BASE_OFFSET = 0x2050
203 | DATA_SIZE = 0x1ff0
204 | for _ in range(0, self.blocksize()-1):
205 | prev = self.raw_bytes[PREV_BYTE_OFFSET]
206 | for i in range(BASE_OFFSET, BASE_OFFSET + DATA_SIZE):
207 | cursor = self.raw_bytes[i]
208 | res = unpack(prev, cursor)
209 | self.raw_bytes[i] = res
210 | prev = cursor
211 | PREV_BYTE_OFFSET += 0x2000
212 | BASE_OFFSET += 0x2000
213 | if (self.packed is True):
214 | self.packed = False
215 |
216 | def pack(self):
217 | """ Pack all blocks of data """
218 | if (self.packed is True):
219 | raise Exception("Data is already packed -- refusing to pack")
220 |
221 | logger.debug("Packing GCI data")
222 |
223 | PREV_BYTE_OFFSET = 0x204f
224 | BASE_OFFSET = 0x2050
225 | DATA_SIZE = 0x1ff0
226 | for _ in range(0, self.blocksize()-1):
227 | prev = self.raw_bytes[PREV_BYTE_OFFSET]
228 | for i in range(BASE_OFFSET, BASE_OFFSET + DATA_SIZE):
229 | cursor = self.raw_bytes[i]
230 | res = pack(prev, cursor)
231 | self.raw_bytes[i] = res
232 | prev = res
233 | PREV_BYTE_OFFSET += 0x2000
234 | BASE_OFFSET += 0x2000
235 | if (self.packed is False):
236 | self.packed = True
237 |
238 |
--------------------------------------------------------------------------------
/mgc/gci_tools/mem2gci.py:
--------------------------------------------------------------------------------
1 | """mem2gci.py: Translates Melee memory addresses to their corresponding location in an unpacked GCI, and vice-versa."""
2 |
3 | from typing import List, Tuple
4 |
5 | BLOCK_LIST = [ #GCI block offset list
6 | 0x02060, #Block 0
7 | 0x04060, #Block 1
8 | 0x06060, #Block 2
9 | 0x08060, #Block 3
10 | 0x0a060, #Block 4
11 | 0x0c060, #Block 5
12 | 0x0e060, #Block 6
13 | 0x10060, #Block 7
14 | 0x12060, #Block 8
15 | 0x14060, #Block 9
16 | ]
17 |
18 | MEM_LIST = [ #Melee start address for each GCI block
19 | 0x00000000, #Block 0 (not in memory)
20 | 0x8045d6b8, #Block 1
21 | 0x8045f5e4, #Block 2
22 | 0x80461510, #Block 3
23 | 0x8046343c, #Block 4
24 | 0x80465368, #Block 5
25 | 0x80467294, #Block 6
26 | 0x804691c0, #Block 7
27 | 0x00000000, #Block 8 (not in memory)
28 | 0x8045bf28, #Block 9
29 | ]
30 |
31 | BLOCK_SIZE = [0, 0x1f2c, 0x1f2c, 0x1f2c, 0x1f2c, 0x1f2c, 0x1f2c, 0x1f2c, 0, 0x1790]
32 | BLOCK_START = BLOCK_LIST[0]
33 | BLOCK_END = BLOCK_LIST[9] + BLOCK_SIZE[9]
34 | MEM_START = MEM_LIST[9] # Memory starts with block 9 for some reason
35 | MEM_END = MEM_LIST[7] + BLOCK_SIZE[7]
36 |
37 | def mem2gci_tuple(mem_address: int) -> Tuple[int, int]:
38 | """Takes a Melee memory address and returns the corresponding unpacked GCI
39 | block number and offset."""
40 | if mem_address < MEM_START or mem_address >= MEM_END:
41 | raise ValueError("Melee address 0x%08x does not have a corresponding GCI location" % mem_address)
42 | block_number = -1
43 | offset = 0
44 | for index, block_address in enumerate(MEM_LIST):
45 | offset = mem_address - block_address
46 | if offset >= BLOCK_SIZE[index] or offset < 0: continue
47 | block_number = index
48 | break
49 | if block_number < 0:
50 | raise ValueError("Melee address 0x%08x does not have a corresponding GCI location" % mem_address)
51 | return block_number, offset
52 |
53 | def mem2gci(mem_address: int) -> int:
54 | """Takes a Melee memory address and returns the corresponding unpacked GCI
55 | location."""
56 | block_number, offset = mem2gci_tuple(mem_address)
57 | return BLOCK_LIST[block_number] + offset
58 |
59 | def gci2mem(gci_address: int) -> int:
60 | """Takes a GCI offset address and returns the corresponding Melee memory
61 | location."""
62 | if gci_address < BLOCK_START or gci_address >= BLOCK_END:
63 | raise ValueError("GCI address 0x%05x does not have a corresponding Melee memory location" % gci_address)
64 | block_number = -1
65 | offset = 0
66 | for index, block_address in enumerate(BLOCK_LIST):
67 | offset = gci_address - block_address
68 | if offset >= BLOCK_SIZE[index] or offset < 0: continue
69 | block_number = index
70 | break
71 | if block_number < 0:
72 | raise ValueError("GCI address 0x%05x does not have a corresponding Melee memory location" % gci_address)
73 | return MEM_LIST[block_number] + offset
74 |
75 | def data2gci(mem_start_address: int, data: bytes) -> List[Tuple[int, bytes]]:
76 | """Takes a Melee address and bytes of data, and returns them in a list of
77 | GCI offsets and data blocks that correspond exactly to where the data
78 | should go in the GCI."""
79 | if not data:
80 | raise ValueError("Data length must be greater than 0.")
81 | if mem_start_address < MEM_START:
82 | raise ValueError("Start address 0x%08x is not present in the GCI; earliest possible start address is 0x%08x" % (mem_start_address, MEM_START))
83 | if mem_start_address + len(data) > MEM_END:
84 | raise ValueError("Data ends at 0x%08x which overflows the last address present in the GCI (0x%08x)" % (mem_start_address + len(data), MEM_END))
85 | current_address = mem_start_address
86 | remaining_data = len(data)
87 | gci_list: List[Tuple[int, bytes]] = []
88 | data_pointer = 0
89 | while remaining_data > 0:
90 | current_block_number, current_offset = mem2gci_tuple(current_address)
91 | current_gci_address = BLOCK_LIST[current_block_number] + current_offset
92 | amount_block_can_fit = BLOCK_SIZE[current_block_number] - current_offset
93 | gci_list.append((current_gci_address, data[data_pointer:data_pointer+min(remaining_data, amount_block_can_fit)]))
94 | remaining_data -= amount_block_can_fit
95 | current_address += amount_block_can_fit
96 | data_pointer += amount_block_can_fit
97 | return gci_list
98 |
99 |
--------------------------------------------------------------------------------
/mgc/gci_tools/ppc_opcodes.py:
--------------------------------------------------------------------------------
1 | """ppc_opcodes.py: Python implementations of the PPC opcodes needed for GCI encode/decode functions."""
2 |
3 | def mask(mb: int, me: int) -> int:
4 | if mb >= 32 or me >= 32:
5 | raise ValueError("Argument values must be between 0 and 31.")
6 | x = 0xffffffff >> mb
7 | y = (0xffffffff << 31 - me) & 0xffffffff # The & truncates to 32-bit
8 | if mb <= me: return x & y
9 | else: return x | y
10 |
11 | def rotl(rx: int, sh: int) -> int:
12 | if sh >= 32:
13 | raise ValueError("Shift amount must be between 0 and 31.")
14 | return ((rx << sh) & 0xffffffff) | (rx >> ((32 - sh) & 31))
15 |
16 | def rlwinm(rs: int, sh: int, mb: int, me: int) -> int:
17 | return rotl(rs, sh) & mask(mb, me)
18 |
19 | def rlwimi(ra: int, rs: int, sh: int, mb: int, me: int) -> int:
20 | m = mask(mb, me)
21 | r = rotl(rs, sh)
22 | return (r & m) | (ra & ~m)
--------------------------------------------------------------------------------
/mgc/gci_tools/savefile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | """ savefile - example script for [un]packing a save file. """
3 |
4 | import sys
5 |
6 | from .meleegci import *
7 |
8 | print(sys.argv)
9 | if len(sys.argv) < 4:
10 | print("Usage: savefile.py [--pack | --unpack]