cartridge-sdk 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cartridge_sdk-1.0.0/LICENSE +21 -0
- cartridge_sdk-1.0.0/PKG-INFO +178 -0
- cartridge_sdk-1.0.0/README.md +165 -0
- cartridge_sdk-1.0.0/cartridge/__init__.py +2 -0
- cartridge_sdk-1.0.0/cartridge/cli.py +17 -0
- cartridge_sdk-1.0.0/cartridge/modules/build.py +38 -0
- cartridge_sdk-1.0.0/cartridge/modules/conv.py +19 -0
- cartridge_sdk-1.0.0/cartridge/modules/hdr/__init__.py +10 -0
- cartridge_sdk-1.0.0/cartridge/modules/hdr/dump.py +9 -0
- cartridge_sdk-1.0.0/cartridge/modules/hdr/gen.py +10 -0
- cartridge_sdk-1.0.0/cartridge/modules/init.py +42 -0
- cartridge_sdk-1.0.0/cartridge/services/conversion.py +156 -0
- cartridge_sdk-1.0.0/cartridge/services/generation.py +85 -0
- cartridge_sdk-1.0.0/cartridge/services/header.py +228 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/PKG-INFO +178 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/SOURCES.txt +20 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/dependency_links.txt +1 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/entry_points.txt +2 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/requires.txt +2 -0
- cartridge_sdk-1.0.0/cartridge_sdk.egg-info/top_level.txt +1 -0
- cartridge_sdk-1.0.0/pyproject.toml +28 -0
- cartridge_sdk-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lukas Soigneux
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cartridge-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: GBA SDK — build tool for Game Boy Advance
|
|
5
|
+
Author-email: Lukas Soigneux <lukas.soigneux@epitech.eu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: GBA,Game Boy,GameBoy Advance,Cartridge
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: click
|
|
11
|
+
Requires-Dist: Pillow
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
<a href="https://github.com/lukas-sgx/">
|
|
16
|
+
<img src="https://github.com/lukas-sgx/GBA-sdk/blob/main/assets/gba-logo.png?raw=true" alt="Logo" height="180" style="border-radius: 10px">
|
|
17
|
+
</a>
|
|
18
|
+
|
|
19
|
+
<h3 align="center">GameBoy Advance - SDK</h3>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
A Software Development Kit for developers who want to build GameBoy Advance games.
|
|
23
|
+
<br />
|
|
24
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk"><strong>Explore the docs »</strong></a>
|
|
25
|
+
<br />
|
|
26
|
+
<br />
|
|
27
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk">View Demo</a>
|
|
28
|
+
·
|
|
29
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
|
|
30
|
+
·
|
|
31
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<details>
|
|
36
|
+
<summary>Table of Contents</summary>
|
|
37
|
+
<ol>
|
|
38
|
+
<li>
|
|
39
|
+
<a href="#about-the-project">About The Project</a>
|
|
40
|
+
<ul>
|
|
41
|
+
<li><a href="#built-with">Built With</a></li>
|
|
42
|
+
</ul>
|
|
43
|
+
</li>
|
|
44
|
+
<li>
|
|
45
|
+
<a href="#getting-started">Getting Started</a>
|
|
46
|
+
<ul>
|
|
47
|
+
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
48
|
+
<li><a href="#installation">Installation</a></li>
|
|
49
|
+
</ul>
|
|
50
|
+
</li>
|
|
51
|
+
<li><a href="#usage">Usage</a></li>
|
|
52
|
+
<li><a href="#roadmap">Roadmap</a></li>
|
|
53
|
+
<li><a href="#contributing">Contributing</a></li>
|
|
54
|
+
<li><a href="#license">License</a></li>
|
|
55
|
+
<li><a href="#contact">Contact</a></li>
|
|
56
|
+
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
|
57
|
+
</ol>
|
|
58
|
+
</details>
|
|
59
|
+
|
|
60
|
+
## About The Project
|
|
61
|
+
|
|
62
|
+
This SDK aims to simplify GameBoy Advance homebrew development by allowing developers to write game logic in Python. It handles the underlying compilation, asset conversion, and bindings to interface efficiently with the GBA hardware.
|
|
63
|
+
|
|
64
|
+
### Built With
|
|
65
|
+
|
|
66
|
+
[![Python][Python-shield]][Python-url]
|
|
67
|
+
[![C][C-shield]][C-url]
|
|
68
|
+
[![Assembly][ASM-shield]][ASM-url]
|
|
69
|
+
[![CMake][CMake-shield]][CMake-url]
|
|
70
|
+
|
|
71
|
+
## Getting Started
|
|
72
|
+
|
|
73
|
+
To get a local copy up and running, follow these simple steps.
|
|
74
|
+
|
|
75
|
+
### Prerequisites
|
|
76
|
+
|
|
77
|
+
You need Python 3.x installed on your system
|
|
78
|
+
|
|
79
|
+
### Installation
|
|
80
|
+
|
|
81
|
+
#### Development mode (clone the repo, with local changes)
|
|
82
|
+
1. Clone the repo
|
|
83
|
+
```sh
|
|
84
|
+
git clone https://github.com/lukas-sgx/GBA-sdk.git
|
|
85
|
+
cd GBA-sdk
|
|
86
|
+
```
|
|
87
|
+
2. Install the SDK in development mode
|
|
88
|
+
```sh
|
|
89
|
+
pip install -e .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Release mode (stable version from PyPI)
|
|
93
|
+
```sh
|
|
94
|
+
pip install cartridge-sdk
|
|
95
|
+
```
|
|
96
|
+
## Usage
|
|
97
|
+
|
|
98
|
+
Here is a quick example of how to dump header of ROM:
|
|
99
|
+
```sh
|
|
100
|
+
$ cartridge-sdk hdr dump bin/ExampleGBA.gba
|
|
101
|
+
bin/ExampleGBA.gba:
|
|
102
|
+
|-- entry
|
|
103
|
+
| |-- valid: True
|
|
104
|
+
| |-- raw: 0xea00002e
|
|
105
|
+
| `-- opcode: b 0xc0
|
|
106
|
+
|-- nintendo logo:
|
|
107
|
+
| |-- status: True
|
|
108
|
+
| `-- debugging: True
|
|
109
|
+
|-- game title: EXAMPLEGBA
|
|
110
|
+
|-- game code:
|
|
111
|
+
| |-- code: BXXE
|
|
112
|
+
| |-- date: 2003.. (new)
|
|
113
|
+
| `-- language: USA/English
|
|
114
|
+
|-- marker code:
|
|
115
|
+
| |-- id: 01
|
|
116
|
+
| `-- developer: Nintendo
|
|
117
|
+
|-- fixed value: valid (96h)
|
|
118
|
+
|-- unit code: 00h
|
|
119
|
+
|-- device type: 00h
|
|
120
|
+
|-- reserved: valid
|
|
121
|
+
|-- software_ver: 00h
|
|
122
|
+
`-- checksum:
|
|
123
|
+
|-- valid: True
|
|
124
|
+
|-- rom: e3
|
|
125
|
+
`-- our: e3
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
*For more advanced examples, please refer to the [Documentation](https://github.com/lukas-sgx/GBA-sdk).*
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Roadmap
|
|
132
|
+
|
|
133
|
+
- [x] Automated header checker `.gba` ROM
|
|
134
|
+
- [x] Automated compilation to `.gba` ROM
|
|
135
|
+
- [x] Font asset pipeline (PNG to C file converter)
|
|
136
|
+
- [ ] Core GBA bindings (Video, Audio, Inputs)
|
|
137
|
+
- [ ] Asset pipeline (PNG to GBA sprite palette converter)
|
|
138
|
+
|
|
139
|
+
See the [open issues](https://github.com/lukas-sgx/GBA-sdk/issues) for a full list of proposed features (and known issues).
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Contributing
|
|
143
|
+
|
|
144
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
145
|
+
|
|
146
|
+
1. Fork the Project
|
|
147
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
148
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
149
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
150
|
+
5. Open a Pull Request
|
|
151
|
+
|
|
152
|
+
### Top contributors:
|
|
153
|
+
|
|
154
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/graphs/contributors">
|
|
155
|
+
<img src="https://contrib.rocks/image?repo=lukas-sgx/GBA-sdk" alt="contrib.rocks image" />
|
|
156
|
+
</a>
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Distributed under the MIT License. See [LICENSE](./LICENSE) for more information.
|
|
161
|
+
|
|
162
|
+
## Contact
|
|
163
|
+
|
|
164
|
+
Lukas Soigneux - lukas.soigneux@epitech.eu
|
|
165
|
+
|
|
166
|
+
## Acknowledgments
|
|
167
|
+
|
|
168
|
+
* [GBATEK](https://mgba-emu.github.io/gbatek/) - GameBoy Advance Technical Info
|
|
169
|
+
* [Ayyboy-Advance](https://github.com/YannMagnin/ayyboy-advance) - Great emulator for testing
|
|
170
|
+
|
|
171
|
+
[Python-shield]: https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white
|
|
172
|
+
[Python-url]: https://www.python.org/
|
|
173
|
+
[C-shield]: https://img.shields.io/badge/C-005895?style=for-the-badge&logo=c&logoColor=white
|
|
174
|
+
[C-url]: https://www.c-language.org/
|
|
175
|
+
[ASM-shield]: https://img.shields.io/badge/-Assembly-f2921d?style=for-the-badge&logo=assemblyscript&logoColor=white
|
|
176
|
+
[ASM-url]: https://developer.arm.com/
|
|
177
|
+
[CMake-shield]: https://img.shields.io/badge/-CMake-064F8C?style=for-the-badge&logo=cmake&logoColor=white
|
|
178
|
+
[CMake-url]: https://cmake.org/
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/lukas-sgx/">
|
|
3
|
+
<img src="https://github.com/lukas-sgx/GBA-sdk/blob/main/assets/gba-logo.png?raw=true" alt="Logo" height="180" style="border-radius: 10px">
|
|
4
|
+
</a>
|
|
5
|
+
|
|
6
|
+
<h3 align="center">GameBoy Advance - SDK</h3>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
A Software Development Kit for developers who want to build GameBoy Advance games.
|
|
10
|
+
<br />
|
|
11
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk"><strong>Explore the docs »</strong></a>
|
|
12
|
+
<br />
|
|
13
|
+
<br />
|
|
14
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk">View Demo</a>
|
|
15
|
+
·
|
|
16
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
|
|
17
|
+
·
|
|
18
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<details>
|
|
23
|
+
<summary>Table of Contents</summary>
|
|
24
|
+
<ol>
|
|
25
|
+
<li>
|
|
26
|
+
<a href="#about-the-project">About The Project</a>
|
|
27
|
+
<ul>
|
|
28
|
+
<li><a href="#built-with">Built With</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
</li>
|
|
31
|
+
<li>
|
|
32
|
+
<a href="#getting-started">Getting Started</a>
|
|
33
|
+
<ul>
|
|
34
|
+
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
35
|
+
<li><a href="#installation">Installation</a></li>
|
|
36
|
+
</ul>
|
|
37
|
+
</li>
|
|
38
|
+
<li><a href="#usage">Usage</a></li>
|
|
39
|
+
<li><a href="#roadmap">Roadmap</a></li>
|
|
40
|
+
<li><a href="#contributing">Contributing</a></li>
|
|
41
|
+
<li><a href="#license">License</a></li>
|
|
42
|
+
<li><a href="#contact">Contact</a></li>
|
|
43
|
+
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
|
44
|
+
</ol>
|
|
45
|
+
</details>
|
|
46
|
+
|
|
47
|
+
## About The Project
|
|
48
|
+
|
|
49
|
+
This SDK aims to simplify GameBoy Advance homebrew development by allowing developers to write game logic in Python. It handles the underlying compilation, asset conversion, and bindings to interface efficiently with the GBA hardware.
|
|
50
|
+
|
|
51
|
+
### Built With
|
|
52
|
+
|
|
53
|
+
[![Python][Python-shield]][Python-url]
|
|
54
|
+
[![C][C-shield]][C-url]
|
|
55
|
+
[![Assembly][ASM-shield]][ASM-url]
|
|
56
|
+
[![CMake][CMake-shield]][CMake-url]
|
|
57
|
+
|
|
58
|
+
## Getting Started
|
|
59
|
+
|
|
60
|
+
To get a local copy up and running, follow these simple steps.
|
|
61
|
+
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
You need Python 3.x installed on your system
|
|
65
|
+
|
|
66
|
+
### Installation
|
|
67
|
+
|
|
68
|
+
#### Development mode (clone the repo, with local changes)
|
|
69
|
+
1. Clone the repo
|
|
70
|
+
```sh
|
|
71
|
+
git clone https://github.com/lukas-sgx/GBA-sdk.git
|
|
72
|
+
cd GBA-sdk
|
|
73
|
+
```
|
|
74
|
+
2. Install the SDK in development mode
|
|
75
|
+
```sh
|
|
76
|
+
pip install -e .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Release mode (stable version from PyPI)
|
|
80
|
+
```sh
|
|
81
|
+
pip install cartridge-sdk
|
|
82
|
+
```
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
Here is a quick example of how to dump header of ROM:
|
|
86
|
+
```sh
|
|
87
|
+
$ cartridge-sdk hdr dump bin/ExampleGBA.gba
|
|
88
|
+
bin/ExampleGBA.gba:
|
|
89
|
+
|-- entry
|
|
90
|
+
| |-- valid: True
|
|
91
|
+
| |-- raw: 0xea00002e
|
|
92
|
+
| `-- opcode: b 0xc0
|
|
93
|
+
|-- nintendo logo:
|
|
94
|
+
| |-- status: True
|
|
95
|
+
| `-- debugging: True
|
|
96
|
+
|-- game title: EXAMPLEGBA
|
|
97
|
+
|-- game code:
|
|
98
|
+
| |-- code: BXXE
|
|
99
|
+
| |-- date: 2003.. (new)
|
|
100
|
+
| `-- language: USA/English
|
|
101
|
+
|-- marker code:
|
|
102
|
+
| |-- id: 01
|
|
103
|
+
| `-- developer: Nintendo
|
|
104
|
+
|-- fixed value: valid (96h)
|
|
105
|
+
|-- unit code: 00h
|
|
106
|
+
|-- device type: 00h
|
|
107
|
+
|-- reserved: valid
|
|
108
|
+
|-- software_ver: 00h
|
|
109
|
+
`-- checksum:
|
|
110
|
+
|-- valid: True
|
|
111
|
+
|-- rom: e3
|
|
112
|
+
`-- our: e3
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
*For more advanced examples, please refer to the [Documentation](https://github.com/lukas-sgx/GBA-sdk).*
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Roadmap
|
|
119
|
+
|
|
120
|
+
- [x] Automated header checker `.gba` ROM
|
|
121
|
+
- [x] Automated compilation to `.gba` ROM
|
|
122
|
+
- [x] Font asset pipeline (PNG to C file converter)
|
|
123
|
+
- [ ] Core GBA bindings (Video, Audio, Inputs)
|
|
124
|
+
- [ ] Asset pipeline (PNG to GBA sprite palette converter)
|
|
125
|
+
|
|
126
|
+
See the [open issues](https://github.com/lukas-sgx/GBA-sdk/issues) for a full list of proposed features (and known issues).
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Contributing
|
|
130
|
+
|
|
131
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
132
|
+
|
|
133
|
+
1. Fork the Project
|
|
134
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
135
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
136
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
137
|
+
5. Open a Pull Request
|
|
138
|
+
|
|
139
|
+
### Top contributors:
|
|
140
|
+
|
|
141
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/graphs/contributors">
|
|
142
|
+
<img src="https://contrib.rocks/image?repo=lukas-sgx/GBA-sdk" alt="contrib.rocks image" />
|
|
143
|
+
</a>
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Distributed under the MIT License. See [LICENSE](./LICENSE) for more information.
|
|
148
|
+
|
|
149
|
+
## Contact
|
|
150
|
+
|
|
151
|
+
Lukas Soigneux - lukas.soigneux@epitech.eu
|
|
152
|
+
|
|
153
|
+
## Acknowledgments
|
|
154
|
+
|
|
155
|
+
* [GBATEK](https://mgba-emu.github.io/gbatek/) - GameBoy Advance Technical Info
|
|
156
|
+
* [Ayyboy-Advance](https://github.com/YannMagnin/ayyboy-advance) - Great emulator for testing
|
|
157
|
+
|
|
158
|
+
[Python-shield]: https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white
|
|
159
|
+
[Python-url]: https://www.python.org/
|
|
160
|
+
[C-shield]: https://img.shields.io/badge/C-005895?style=for-the-badge&logo=c&logoColor=white
|
|
161
|
+
[C-url]: https://www.c-language.org/
|
|
162
|
+
[ASM-shield]: https://img.shields.io/badge/-Assembly-f2921d?style=for-the-badge&logo=assemblyscript&logoColor=white
|
|
163
|
+
[ASM-url]: https://developer.arm.com/
|
|
164
|
+
[CMake-shield]: https://img.shields.io/badge/-CMake-064F8C?style=for-the-badge&logo=cmake&logoColor=white
|
|
165
|
+
[CMake-url]: https://cmake.org/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from cartridge.modules.build import build
|
|
3
|
+
from cartridge.modules.hdr import hdr
|
|
4
|
+
from cartridge.modules.conv import conv
|
|
5
|
+
from cartridge.modules.init import init
|
|
6
|
+
|
|
7
|
+
@click.group(help="Cartridge SDK CLI entry")
|
|
8
|
+
def cli():
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
cli.add_command(build)
|
|
12
|
+
cli.add_command(hdr)
|
|
13
|
+
cli.add_command(conv)
|
|
14
|
+
cli.add_command(init)
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
cli()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
import os
|
|
4
|
+
import glob
|
|
5
|
+
from cartridge.services.generation import Generation
|
|
6
|
+
|
|
7
|
+
def listBin() -> list[str]:
|
|
8
|
+
return glob.glob("bin/*.bin")
|
|
9
|
+
|
|
10
|
+
@click.command(help="Craft cartridge ROM (GBA)")
|
|
11
|
+
@click.option("-s", "--source", required=True, type=click.Path(exists=True), help="Path to the CMake source directory")
|
|
12
|
+
def build(source: str) -> None:
|
|
13
|
+
os.makedirs("bin", exist_ok=True)
|
|
14
|
+
os.makedirs("build", exist_ok=True)
|
|
15
|
+
|
|
16
|
+
PREV_DIR = os.getcwd()
|
|
17
|
+
|
|
18
|
+
os.chdir(source)
|
|
19
|
+
|
|
20
|
+
subprocess.run([
|
|
21
|
+
"cmake",
|
|
22
|
+
"."
|
|
23
|
+
], check=True)
|
|
24
|
+
|
|
25
|
+
os.chdir(PREV_DIR)
|
|
26
|
+
|
|
27
|
+
subprocess.run([
|
|
28
|
+
"cmake",
|
|
29
|
+
"--build",
|
|
30
|
+
source
|
|
31
|
+
], check=True)
|
|
32
|
+
|
|
33
|
+
for binary in listBin():
|
|
34
|
+
project = binary.split(".")[0]
|
|
35
|
+
output_path = f"{project}.gba"
|
|
36
|
+
|
|
37
|
+
with open(output_path, "wb") as output_file:
|
|
38
|
+
Generation(binary, output_file, project.split("/")[1], "BXXE")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from PIL import Image
|
|
3
|
+
from cartridge.services.conversion import Conversion
|
|
4
|
+
|
|
5
|
+
@click.command(help="Convert font asset into raw C file")
|
|
6
|
+
@click.option("-o", "--output", required=True, type=click.Path(exists=False), help="Path to the generated C file")
|
|
7
|
+
@click.option("-w", "--width", required=True, type=click.INT, help="Character width in pixels")
|
|
8
|
+
@click.option("-h", "--height", required=True, type=click.INT, help="Character height in pixels")
|
|
9
|
+
@click.option("-m", "--margin", required=True, type=click.INT, help="Margin between characters in pixels")
|
|
10
|
+
@click.option("-l", "--line-height", required=True, type=click.INT, help="Virtual alignment line height in pixels")
|
|
11
|
+
@click.option("-d", "--debug", is_flag=True, help="Enable debug mode")
|
|
12
|
+
@click.argument("ASSET_PATH", required=True, type=click.STRING, nargs=1)
|
|
13
|
+
def conv(asset_path: str, output: str, width: int, height: int, margin: int, line_height: int, debug) -> None:
|
|
14
|
+
img = Image.open(asset_path, mode="r").convert("L")
|
|
15
|
+
dimension = (height, width, line_height, margin)
|
|
16
|
+
convert = Conversion(img, dimension, debug)
|
|
17
|
+
|
|
18
|
+
with open(output, mode="w") as file:
|
|
19
|
+
file.write(convert.get_content())
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from cartridge.services.header import Header
|
|
3
|
+
|
|
4
|
+
@click.command(help="Dump cartridge header information")
|
|
5
|
+
@click.argument("GBA_FILES", required=False, nargs=-1)
|
|
6
|
+
def dump(gba_files):
|
|
7
|
+
for file in gba_files:
|
|
8
|
+
header = Header(file)
|
|
9
|
+
header.display_header()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from cartridge.services.generation import Generation
|
|
3
|
+
from typing import BinaryIO
|
|
4
|
+
|
|
5
|
+
@click.command(help="Insert the header into a binary")
|
|
6
|
+
@click.option("-b", "--binary", required=True, nargs=1, type=click.File("rb"), help="input binary file")
|
|
7
|
+
@click.option("-o", "--output", required=True, nargs=1, type=click.File("wb"), help="output filename")
|
|
8
|
+
@click.option("-n", "--name", nargs=1, type=click.STRING, help="internal cartridge name", show_default=True, default="filename")
|
|
9
|
+
def gen(binary: click.File, output: BinaryIO, name: str):
|
|
10
|
+
Generation(binary.name, output, name, "BXXE")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import urllib.request
|
|
3
|
+
import io
|
|
4
|
+
import tarfile
|
|
5
|
+
from cartridge import __version__
|
|
6
|
+
|
|
7
|
+
@click.command(help="Initialize project with default build")
|
|
8
|
+
def init():
|
|
9
|
+
url = f"https://api.github.com/repos/lukas-sgx/GBA-sdk/tarball/v{__version__}"
|
|
10
|
+
headers = {"User-Agent": "Python-Cartridge"}
|
|
11
|
+
req = urllib.request.Request(url, headers=headers)
|
|
12
|
+
|
|
13
|
+
click.echo(
|
|
14
|
+
click.style("Downloading ", fg="green", bold=True) +
|
|
15
|
+
click.style(f"GBA-sdk v{__version__}\n") +
|
|
16
|
+
click.style(f"\t {url}", fg=(138,138,138))
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
with urllib.request.urlopen(req) as response:
|
|
20
|
+
tar_data = io.BytesIO(response.read())
|
|
21
|
+
|
|
22
|
+
extracted_count = 0
|
|
23
|
+
with tarfile.open(fileobj=tar_data, mode="r:gz") as tar:
|
|
24
|
+
|
|
25
|
+
for member in tar.getmembers():
|
|
26
|
+
parts = member.name.split("/", 1)
|
|
27
|
+
if len(parts) < 2:
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
relative_path = parts[1]
|
|
31
|
+
if relative_path.startswith(("sandbox/", "libs/")) and not member.isdir():
|
|
32
|
+
member.name = relative_path
|
|
33
|
+
click.echo(
|
|
34
|
+
click.style("Extracting ", fg="green", bold=True) +
|
|
35
|
+
click.style(f"{member.name} -> .")
|
|
36
|
+
)
|
|
37
|
+
tar.extract(member, path=".")
|
|
38
|
+
extracted_count += 1
|
|
39
|
+
click.echo(
|
|
40
|
+
click.style("Finished", fg="green", bold=True) +
|
|
41
|
+
click.style(f" {extracted_count} dir(s) extracted\n")
|
|
42
|
+
)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
class Conversion:
|
|
2
|
+
def __init__(self, img, attributes, debug) -> None:
|
|
3
|
+
self.img = img
|
|
4
|
+
self.height = attributes[0]
|
|
5
|
+
self.width = attributes[1]
|
|
6
|
+
self.attributes = attributes
|
|
7
|
+
self.is_debug = debug
|
|
8
|
+
|
|
9
|
+
def get_content(self):
|
|
10
|
+
content = "// Auto Generated font asset file - CARTRIDGE\n\n"
|
|
11
|
+
content += "#include <stdint.h>\n"
|
|
12
|
+
content += '#include "font.h"\n\n'
|
|
13
|
+
|
|
14
|
+
content += "void init_font(gba_font_t *font, enum font_type type) {\n"
|
|
15
|
+
content += "\tstatic const uint8_t font_bitmap_monospaced[] = {\n"
|
|
16
|
+
|
|
17
|
+
for ascii in range(32, 128):
|
|
18
|
+
pixels_bytes = self.get_pixels_bitmap_glyph(ascii)
|
|
19
|
+
|
|
20
|
+
row_values = ", ".join(self.get_monospace_glyph(pixels_bytes, ascii))
|
|
21
|
+
printable = (
|
|
22
|
+
chr(ascii) if chr(ascii) not in ("\\", "'") else f"\\{chr(ascii)}"
|
|
23
|
+
)
|
|
24
|
+
content += f"\t\t{row_values}, // {ascii}: '{printable}'\n"
|
|
25
|
+
|
|
26
|
+
content += "\t};\n\n"
|
|
27
|
+
|
|
28
|
+
content += "\tconst glyph_t glyphs_monospaced[] = {\n\t\t{"
|
|
29
|
+
content += f" .width = {self.width}, .height = {self.height} "
|
|
30
|
+
content += "},\n\t\t{ .width = 0, .height = 0 }\n"
|
|
31
|
+
content += "\t};\n\n"
|
|
32
|
+
|
|
33
|
+
content += "\tstatic const uint8_t font_bitmap_proportional[] = {\n"
|
|
34
|
+
for ascii in range(32, 128):
|
|
35
|
+
pixels_bytes = self.get_pixels_bitmap_glyph(ascii)
|
|
36
|
+
proporitonal_glyph = self.get_proportional_glyph(pixels_bytes)
|
|
37
|
+
content += "\t\t"
|
|
38
|
+
content += ", ".join(proporitonal_glyph)
|
|
39
|
+
content += ",\n"
|
|
40
|
+
content += "\t};\n\n"
|
|
41
|
+
|
|
42
|
+
content += "\tstatic const glyph_t glyphs_proportional[] = {\n"
|
|
43
|
+
for ascii in range(32, 128):
|
|
44
|
+
pixels_bytes = self.get_pixels_bitmap_glyph(ascii)
|
|
45
|
+
proporitonal_glyph = self.get_proportional_glyph(pixels_bytes)
|
|
46
|
+
content += "\t\t{"
|
|
47
|
+
content += f".height = {len(proporitonal_glyph)}, "
|
|
48
|
+
content += f".width = {self.get_max_width_proportional(proporitonal_glyph)}, "
|
|
49
|
+
content += "},\n"
|
|
50
|
+
content += "\t};\n\n"
|
|
51
|
+
|
|
52
|
+
content += "\tfont->type = type;\n"
|
|
53
|
+
content += "\tfont->monospaced.bitmap = font_bitmap_monospaced;\n"
|
|
54
|
+
content += "\tfont->monospaced.glyphs = glyphs_monospaced;\n"
|
|
55
|
+
content += "\tfont->proportional.bitmap = font_bitmap_proportional;\n"
|
|
56
|
+
content += "\tfont->proportional.glyphs = glyphs_proportional;\n"
|
|
57
|
+
|
|
58
|
+
content += "}\n"
|
|
59
|
+
|
|
60
|
+
return content
|
|
61
|
+
|
|
62
|
+
def get_glyph_list(self, pixel_bytes: bytes) -> list[str]:
|
|
63
|
+
glyph_list: list[str] = []
|
|
64
|
+
|
|
65
|
+
for y in range(self.height):
|
|
66
|
+
row = str()
|
|
67
|
+
for x in range(self.width):
|
|
68
|
+
row += "1" if pixel_bytes[y * self.width + x] > 200 else "0"
|
|
69
|
+
glyph_list.append(f"0x{(int(row, 2)):02X}")
|
|
70
|
+
|
|
71
|
+
return glyph_list
|
|
72
|
+
|
|
73
|
+
def get_monospace_glyph(self, pixel_bytes: bytes, ascii: int) -> list[str]:
|
|
74
|
+
glyph_list: list[str] = self.get_glyph_list(pixel_bytes)
|
|
75
|
+
|
|
76
|
+
if self.is_debug:
|
|
77
|
+
print(f"{ascii}:")
|
|
78
|
+
print(format("~" * 28))
|
|
79
|
+
for glyph in glyph_list:
|
|
80
|
+
bytes_glyph = bytes.fromhex(glyph[2:])
|
|
81
|
+
row = "".join(f"{b:08b}" for b in bytes_glyph)
|
|
82
|
+
print(
|
|
83
|
+
f"{''.join('#' if r == '1' else '-' for r in row)} -> {row} -> 0x{int(row, 2):02x}"
|
|
84
|
+
)
|
|
85
|
+
print(format("~" * 28) + "\n")
|
|
86
|
+
|
|
87
|
+
return glyph_list
|
|
88
|
+
|
|
89
|
+
def get_max_width_proportional(self, glyph_list: list[str]) -> int:
|
|
90
|
+
min_x = None
|
|
91
|
+
max_x = None
|
|
92
|
+
max_width = 0
|
|
93
|
+
|
|
94
|
+
for glyph in glyph_list:
|
|
95
|
+
bytes_glyph = bytes.fromhex(glyph[2:])
|
|
96
|
+
row = "".join(f"{b:08b}" for b in bytes_glyph)
|
|
97
|
+
start_idx = row.find("1")
|
|
98
|
+
end_idx = row.rfind("1")
|
|
99
|
+
|
|
100
|
+
if start_idx != -1:
|
|
101
|
+
if min_x is None or start_idx < min_x:
|
|
102
|
+
min_x = start_idx
|
|
103
|
+
if max_x is None or end_idx > max_x:
|
|
104
|
+
max_x = end_idx + 1
|
|
105
|
+
|
|
106
|
+
if min_x is None or max_x is None:
|
|
107
|
+
return 8
|
|
108
|
+
else:
|
|
109
|
+
max_width = max_x - min_x
|
|
110
|
+
return max_width
|
|
111
|
+
|
|
112
|
+
def get_proportional_glyph(self, pixel_bytes: bytes) -> list[str]:
|
|
113
|
+
glyph_list: list[str] = self.get_glyph_list(pixel_bytes)
|
|
114
|
+
min_x = None
|
|
115
|
+
max_x = None
|
|
116
|
+
min_y = None
|
|
117
|
+
max_y = None
|
|
118
|
+
idx = 0
|
|
119
|
+
|
|
120
|
+
for glyph in glyph_list:
|
|
121
|
+
bytes_glyph = bytes.fromhex(glyph[2:])
|
|
122
|
+
glyph = "".join(f"{b:08b}" for b in bytes_glyph)
|
|
123
|
+
start_idx = glyph.find("1")
|
|
124
|
+
end_idx = glyph.rfind("1")
|
|
125
|
+
if start_idx != -1:
|
|
126
|
+
if min_y is None:
|
|
127
|
+
min_y = idx
|
|
128
|
+
max_y = idx + 1
|
|
129
|
+
if min_x is None or start_idx < min_x:
|
|
130
|
+
min_x = start_idx
|
|
131
|
+
if max_x is None or end_idx > max_x:
|
|
132
|
+
max_x = end_idx + 1
|
|
133
|
+
idx += 1
|
|
134
|
+
|
|
135
|
+
if min_y is not None or max_y is not None:
|
|
136
|
+
glyph_list = glyph_list[min_y:max_y]
|
|
137
|
+
|
|
138
|
+
idx = 0
|
|
139
|
+
for glyph in glyph_list:
|
|
140
|
+
bytes_glyph = bytes.fromhex(glyph[2:])
|
|
141
|
+
row = "".join(f"{b:08b}" for b in bytes_glyph)
|
|
142
|
+
row = row[min_x:max_x]
|
|
143
|
+
glyph_list[idx] = f"0x{(int(row, 2)):02X}"
|
|
144
|
+
idx += 1
|
|
145
|
+
|
|
146
|
+
return glyph_list
|
|
147
|
+
|
|
148
|
+
def get_pixels_bitmap_glyph(self, ascii):
|
|
149
|
+
index = ascii - 32
|
|
150
|
+
top = index // 16 * (self.attributes[0] + self.attributes[2])
|
|
151
|
+
left = index % 16 * (self.attributes[1] + self.attributes[3])
|
|
152
|
+
glyph = self.img.crop(
|
|
153
|
+
(left, top, left + self.attributes[1], top + self.attributes[0])
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return glyph.tobytes()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import struct
|
|
3
|
+
from typing import BinaryIO
|
|
4
|
+
|
|
5
|
+
ROM_ENTRY_POINT: int = 0xEA00002E
|
|
6
|
+
|
|
7
|
+
NINTENDO_LOGO = bytes([
|
|
8
|
+
0x24, 0xFF, 0xAE, 0x51, 0x69, 0x9A, 0xA2, 0x21,
|
|
9
|
+
0x3D, 0x84, 0x82, 0x0A, 0x84, 0xE4, 0x09, 0xAD,
|
|
10
|
+
0x11, 0x24, 0x8B, 0x98, 0xC0, 0x81, 0x7F, 0x21,
|
|
11
|
+
0xA3, 0x52, 0xBE, 0x19, 0x93, 0x09, 0xCE, 0x20,
|
|
12
|
+
0x10, 0x46, 0x4A, 0x4A, 0xF8, 0x27, 0x31, 0xEC,
|
|
13
|
+
0x58, 0xC7, 0xE8, 0x33, 0x82, 0xE3, 0xCE, 0xBF,
|
|
14
|
+
0x85, 0xF4, 0xDF, 0x94, 0xCE, 0x4B, 0x09, 0xC1,
|
|
15
|
+
0x94, 0x56, 0x8A, 0xC0, 0x13, 0x72, 0xA7, 0xFC,
|
|
16
|
+
0x9F, 0x84, 0x4D, 0x73, 0xA3, 0xCA, 0x9A, 0x61,
|
|
17
|
+
0x58, 0x97, 0xA3, 0x27, 0xFC, 0x03, 0x98, 0x76,
|
|
18
|
+
0x23, 0x1D, 0xC7, 0x61, 0x03, 0x04, 0xAE, 0x56,
|
|
19
|
+
0xBF, 0x38, 0x84, 0x00, 0x40, 0xA7, 0x0E, 0xFD,
|
|
20
|
+
0xFF, 0x52, 0xFE, 0x03, 0x6F, 0x95, 0x30, 0xF1,
|
|
21
|
+
0x97, 0xFB, 0xC0, 0x85, 0x60, 0xD6, 0x80, 0x25,
|
|
22
|
+
0xA9, 0x63, 0xBE, 0x03, 0x01, 0x4E, 0x38, 0xE2,
|
|
23
|
+
0xF9, 0xA2, 0x34, 0xFF, 0xBB, 0x3E, 0x03, 0x44,
|
|
24
|
+
0x78, 0x00, 0x90, 0xCB, 0x88, 0x11, 0x3A, 0x94,
|
|
25
|
+
0x65, 0xC0, 0x7C, 0x63, 0x87, 0xF0, 0x3C, 0xAF,
|
|
26
|
+
0xD6, 0x25, 0xE4, 0x8B, 0x38, 0x0A, 0xAC, 0x72,
|
|
27
|
+
0x21, 0xD4, 0xF8, 0x07,
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Generation:
|
|
32
|
+
def __init__(self, bin_file: str, output: BinaryIO, name: str, game_code: str) -> None:
|
|
33
|
+
self.bin_file = bin_file
|
|
34
|
+
self.output = output
|
|
35
|
+
self.name = name
|
|
36
|
+
self.game_code = game_code
|
|
37
|
+
|
|
38
|
+
self.write()
|
|
39
|
+
|
|
40
|
+
def checksum(self, header: bytearray) -> int:
|
|
41
|
+
chk = 0
|
|
42
|
+
for i in range(0xA0, 0xBD):
|
|
43
|
+
chk -= header[i]
|
|
44
|
+
return (chk - 0x19) & 0xFF
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def header_fill(self,
|
|
48
|
+
game_title: str = "",
|
|
49
|
+
game_code: str = "BXXE",
|
|
50
|
+
maker_code: str = "01",
|
|
51
|
+
version: int = 0,
|
|
52
|
+
) -> bytearray:
|
|
53
|
+
header = bytearray(0xC0)
|
|
54
|
+
struct.pack_into("<I", header, 0x00, ROM_ENTRY_POINT)
|
|
55
|
+
|
|
56
|
+
header[0x04:0xA0] = NINTENDO_LOGO
|
|
57
|
+
header[0xA0:0xAC] = game_title.upper().encode("ascii")[:12].ljust(12, b"\x00")
|
|
58
|
+
header[0xAC:0xB0] = game_code.upper().encode("ascii")[:4].ljust(4, b"\x00")
|
|
59
|
+
header[0xB0:0xB2] = maker_code.upper().encode("ascii")[:2].ljust(2, b"\x00")
|
|
60
|
+
header[0xB2] = 0x96
|
|
61
|
+
header[0xB3] = 0x00
|
|
62
|
+
header[0xB4] = 0x00
|
|
63
|
+
header[0xBC] = version & 0xFF
|
|
64
|
+
header[0xBD] = self.checksum(header)
|
|
65
|
+
header[0xBE] = 0x0
|
|
66
|
+
header[0xBF] = 0x0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
return header
|
|
70
|
+
|
|
71
|
+
def write(self):
|
|
72
|
+
click.echo(
|
|
73
|
+
click.style("[GEN] ", fg="blue", bold=True) +
|
|
74
|
+
click.style(f"{self.bin_file} " , bold=True) +
|
|
75
|
+
click.style(f"-> {self.output.name}", fg="cyan")
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with open(f"{self.bin_file}", "rb") as bin_content:
|
|
79
|
+
header = self.header_fill(game_title=self.name, game_code=self.game_code)
|
|
80
|
+
rom_data = bin_content.read()
|
|
81
|
+
|
|
82
|
+
self.output.write(header + rom_data)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
click.echo(click.style("[OK] GBA succefully build", fg="green", bold=True))
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
BYTE_LEN = 4
|
|
4
|
+
LOGO_BYTE_LEN = 156
|
|
5
|
+
GAME_TITLE_BYTE_LEN = 12
|
|
6
|
+
|
|
7
|
+
class Header:
|
|
8
|
+
def __init__(self, gba_file: str) -> None:
|
|
9
|
+
self.gba_file = gba_file
|
|
10
|
+
self.raw: bytes
|
|
11
|
+
self.address = []
|
|
12
|
+
self.pc = 0
|
|
13
|
+
|
|
14
|
+
with open(gba_file, "rb") as fd:
|
|
15
|
+
self.raw = fd.read()
|
|
16
|
+
self.stock_address()
|
|
17
|
+
|
|
18
|
+
def debug(self):
|
|
19
|
+
for addr in self.address:
|
|
20
|
+
click.echo(f"{addr:08x}")
|
|
21
|
+
|
|
22
|
+
def stock_address(self):
|
|
23
|
+
for i in range(0, len(self.raw), 4):
|
|
24
|
+
chunk = self.raw[i : i + 4]
|
|
25
|
+
if len(chunk) == 4:
|
|
26
|
+
self.address.append(int.from_bytes(chunk, "big"))
|
|
27
|
+
|
|
28
|
+
def to_little_endian(self, addr: int) -> int:
|
|
29
|
+
return int.from_bytes(addr.to_bytes(4, "big"), "little")
|
|
30
|
+
|
|
31
|
+
def get_opcode_branch(self, addr: int) -> str:
|
|
32
|
+
addr = self.to_little_endian(addr)
|
|
33
|
+
family = (addr >> 25) & 0x7
|
|
34
|
+
instruction = ""
|
|
35
|
+
|
|
36
|
+
if family == 0b101:
|
|
37
|
+
instruction = "b"
|
|
38
|
+
offset = addr & 0xFFFFFF
|
|
39
|
+
if offset & 0x8000000:
|
|
40
|
+
offset |= 0xFF0000000
|
|
41
|
+
target = self.pc + (BYTE_LEN * 2) + (offset << 2)
|
|
42
|
+
return f"{instruction} 0x{target:02x}"
|
|
43
|
+
|
|
44
|
+
def is_valid_entry(self, addr: int) -> bool:
|
|
45
|
+
addr = self.to_little_endian(addr)
|
|
46
|
+
cond = (addr >> 28) & 0xF
|
|
47
|
+
family = (addr >> 25) & 0x7
|
|
48
|
+
return cond == 0xE and family == 0b101
|
|
49
|
+
|
|
50
|
+
def is_valid_nintendo_logo(self, addresses: list[int]) -> bool:
|
|
51
|
+
NINTENDO_LOGO = bytes([
|
|
52
|
+
0x24, 0xFF, 0xAE, 0x51, 0x69, 0x9A, 0xA2, 0x21,
|
|
53
|
+
0x3D, 0x84, 0x82, 0x0A, 0x84, 0xE4, 0x09, 0xAD,
|
|
54
|
+
0x11, 0x24, 0x8B, 0x98, 0xC0, 0x81, 0x7F, 0x21,
|
|
55
|
+
0xA3, 0x52, 0xBE, 0x19, 0x93, 0x09, 0xCE, 0x20,
|
|
56
|
+
0x10, 0x46, 0x4A, 0x4A, 0xF8, 0x27, 0x31, 0xEC,
|
|
57
|
+
0x58, 0xC7, 0xE8, 0x33, 0x82, 0xE3, 0xCE, 0xBF,
|
|
58
|
+
0x85, 0xF4, 0xDF, 0x94, 0xCE, 0x4B, 0x09, 0xC1,
|
|
59
|
+
0x94, 0x56, 0x8A, 0xC0, 0x13, 0x72, 0xA7, 0xFC,
|
|
60
|
+
0x9F, 0x84, 0x4D, 0x73, 0xA3, 0xCA, 0x9A, 0x61,
|
|
61
|
+
0x58, 0x97, 0xA3, 0x27, 0xFC, 0x03, 0x98, 0x76,
|
|
62
|
+
0x23, 0x1D, 0xC7, 0x61, 0x03, 0x04, 0xAE, 0x56,
|
|
63
|
+
0xBF, 0x38, 0x84, 0x00, 0x40, 0xA7, 0x0E, 0xFD,
|
|
64
|
+
0xFF, 0x52, 0xFE, 0x03, 0x6F, 0x95, 0x30, 0xF1,
|
|
65
|
+
0x97, 0xFB, 0xC0, 0x85, 0x60, 0xD6, 0x80, 0x25,
|
|
66
|
+
0xA9, 0x63, 0xBE, 0x03, 0x01, 0x4E, 0x38, 0xE2,
|
|
67
|
+
0xF9, 0xA2, 0x34, 0xFF, 0xBB, 0x3E, 0x03, 0x44,
|
|
68
|
+
0x78, 0x00, 0x90, 0xCB, 0x88, 0x11, 0x3A, 0x94,
|
|
69
|
+
0x65, 0xC0, 0x7C, 0x63, 0x87, 0xF0, 0x3C, 0xAF,
|
|
70
|
+
0xD6, 0x25, 0xE4, 0x8B, 0x38, 0x0A, 0xAC, 0x72,
|
|
71
|
+
0x21, 0xD4, 0xF8, 0x07,
|
|
72
|
+
])
|
|
73
|
+
raw_bytes = b''
|
|
74
|
+
for hexAddr in addresses:
|
|
75
|
+
raw_bytes += hexAddr.to_bytes(4, byteorder="big") # todo: verif bit 2, 7 on 0x21h if debugging or not -> https://mgba-emu.github.io/gbatek/#gbacartridgeheader
|
|
76
|
+
|
|
77
|
+
return raw_bytes == NINTENDO_LOGO
|
|
78
|
+
|
|
79
|
+
def is_debugging(self, addr: int) -> bool:
|
|
80
|
+
address = list(addr.to_bytes(4, byteorder="big"))
|
|
81
|
+
return (address[0] & 0b00100001) == 0b00100001
|
|
82
|
+
|
|
83
|
+
def get_game_title(self, addresses: list[int]) -> str:
|
|
84
|
+
listByte = b""
|
|
85
|
+
for addr in addresses:
|
|
86
|
+
listByte += addr.to_bytes(4, byteorder="big")
|
|
87
|
+
result: str = str(listByte.decode(encoding="UTF-8")).rstrip("\x00")
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def get_code(self, addr: int) -> str:
|
|
91
|
+
code = addr.to_bytes(4, byteorder="big")
|
|
92
|
+
return str(code.decode(encoding="UTF-8"))
|
|
93
|
+
|
|
94
|
+
def get_date(self, addr: int) -> str:
|
|
95
|
+
code = self.get_code(addr)
|
|
96
|
+
if code[0] == "A":
|
|
97
|
+
return "2001..2003 (old)"
|
|
98
|
+
if code[0] == "B":
|
|
99
|
+
return "2003.. (new)"
|
|
100
|
+
return ""
|
|
101
|
+
|
|
102
|
+
def get_language(self, addr: int) -> str:
|
|
103
|
+
code = self.get_code(addr)
|
|
104
|
+
if code[3] == "E":
|
|
105
|
+
return "USA/English"
|
|
106
|
+
if code[3] == "J":
|
|
107
|
+
return "Japan"
|
|
108
|
+
if code[3] == "P":
|
|
109
|
+
return "Europe/Elsewhere"
|
|
110
|
+
if code[3] == "D":
|
|
111
|
+
return "German"
|
|
112
|
+
if code[3] == "F":
|
|
113
|
+
return "French"
|
|
114
|
+
if code[3] == "I":
|
|
115
|
+
return "Italian"
|
|
116
|
+
if code[3] == "S":
|
|
117
|
+
return "Spanish"
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
def get_marker_id(self, addr: int) -> str:
|
|
121
|
+
hexa = hex(addr)[2:6]
|
|
122
|
+
n1 = hexa[:2]
|
|
123
|
+
ascii1 = chr((int(n1[0]) ** 1) * 16 + (int(n1[1]) * 16 ** 0))
|
|
124
|
+
n2 = hexa[2:]
|
|
125
|
+
ascii2 = chr((int(n2[0]) ** 1) * 16 + (int(n2[1]) * 16 ** 0))
|
|
126
|
+
return f"{ascii1}{ascii2}"
|
|
127
|
+
|
|
128
|
+
def get_developer(self, addr: int) -> str:
|
|
129
|
+
if self.get_marker_id(addr) == "01":
|
|
130
|
+
return "Nintendo"
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
def get_valid_fixed(self, addr: int) -> str:
|
|
134
|
+
return hex(addr)[6:8]
|
|
135
|
+
|
|
136
|
+
def is_valid_fixed(self, addr: int) -> str:
|
|
137
|
+
if self.get_valid_fixed(addr) == "96":
|
|
138
|
+
return "valid"
|
|
139
|
+
return "invalid"
|
|
140
|
+
|
|
141
|
+
def get_unit_code(self, addr: int) -> str:
|
|
142
|
+
return hex(addr)[8:10]
|
|
143
|
+
|
|
144
|
+
def get_device_type(self, addr: int) -> str:
|
|
145
|
+
return "0" + hex(addr)[2:4]
|
|
146
|
+
|
|
147
|
+
def is_valid_reserved_area(self, prev_addr: int, curr_addr: int) -> str:
|
|
148
|
+
if (prev_addr & 0xFFFFFF) == 0 and (curr_addr & 0xFFFFFFFF) == 0:
|
|
149
|
+
return "valid"
|
|
150
|
+
return "invalid"
|
|
151
|
+
|
|
152
|
+
def get_software_version(self, addr: int) -> str:
|
|
153
|
+
hexa = hex(addr)[2:]
|
|
154
|
+
|
|
155
|
+
if len(str(hexa)) <= 6:
|
|
156
|
+
return "00"
|
|
157
|
+
return f"{hexa[0:2]}"
|
|
158
|
+
|
|
159
|
+
def get_rom_sum(self, addr: int) -> str:
|
|
160
|
+
hexa = hex(addr)[2:]
|
|
161
|
+
|
|
162
|
+
if len(str(hexa)) <= 6:
|
|
163
|
+
hexa = "00" + hexa
|
|
164
|
+
return hexa[2:4]
|
|
165
|
+
|
|
166
|
+
def calc_sum(self) -> str:
|
|
167
|
+
chk = 0
|
|
168
|
+
|
|
169
|
+
for i in range(0xA0, 0xBD):
|
|
170
|
+
chk = (chk - self.raw[i]) & 0xFF
|
|
171
|
+
chk = (chk - 0x19) & 0xFF
|
|
172
|
+
return f"{chk:02x}"
|
|
173
|
+
|
|
174
|
+
def display_header(self):
|
|
175
|
+
is_valid_entry = self.is_valid_entry(self.address[self.pc])
|
|
176
|
+
raw_entry = self.to_little_endian(self.address[self.pc])
|
|
177
|
+
op_code_entry = self.get_opcode_branch(self.address[self.pc])
|
|
178
|
+
self.pc += int(BYTE_LEN / BYTE_LEN)
|
|
179
|
+
is_valid_nintendo_logo = self.is_valid_nintendo_logo(self.address[self.pc:self.pc + int(LOGO_BYTE_LEN / BYTE_LEN)])
|
|
180
|
+
self.pc += int(LOGO_BYTE_LEN / BYTE_LEN)
|
|
181
|
+
is_debuging = self.is_debugging(self.address[self.pc - 1])
|
|
182
|
+
title_game = self.get_game_title(self.address[self.pc:self.pc + 3])
|
|
183
|
+
self.pc += int(GAME_TITLE_BYTE_LEN / BYTE_LEN)
|
|
184
|
+
game_code = self.get_code(self.address[self.pc])
|
|
185
|
+
game_release = self.get_date(self.address[self.pc])
|
|
186
|
+
game_language = self.get_language(self.address[self.pc])
|
|
187
|
+
self.pc += int(BYTE_LEN / BYTE_LEN)
|
|
188
|
+
marker_id = self.get_marker_id(self.address[self.pc])
|
|
189
|
+
developer = self.get_developer(self.address[self.pc])
|
|
190
|
+
is_valid_fixed = self.is_valid_fixed(self.address[self.pc])
|
|
191
|
+
valid_fixed = self.get_valid_fixed(self.address[self.pc])
|
|
192
|
+
unit_code = self.get_unit_code(self.address[self.pc])
|
|
193
|
+
self.pc += int(BYTE_LEN / BYTE_LEN)
|
|
194
|
+
device_type = self.get_device_type(self.address[self.pc])
|
|
195
|
+
self.pc += int(BYTE_LEN / BYTE_LEN)
|
|
196
|
+
is_valid_reserved_area = self.is_valid_reserved_area(self.address[self.pc - 1], self.address[self.pc])
|
|
197
|
+
self.pc += int(BYTE_LEN / BYTE_LEN)
|
|
198
|
+
software_version = self.get_software_version(self.address[self.pc])
|
|
199
|
+
rom_sum = self.get_rom_sum(self.address[self.pc])
|
|
200
|
+
sum = self.calc_sum()
|
|
201
|
+
is_valid_sum = rom_sum == sum
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
click.echo(self.gba_file + ":")
|
|
205
|
+
click.echo("|-- entry")
|
|
206
|
+
click.echo(f"| |-- valid: {is_valid_entry}")
|
|
207
|
+
click.echo(f"| |-- raw: 0x{raw_entry:08x}")
|
|
208
|
+
click.echo(f"| `-- opcode: {op_code_entry}")
|
|
209
|
+
click.echo("|-- nintendo logo:")
|
|
210
|
+
click.echo(f"| |-- status: {is_valid_nintendo_logo}")
|
|
211
|
+
click.echo(f"| `-- debugging: {is_debuging}")
|
|
212
|
+
click.echo(f"|-- game title: {title_game}")
|
|
213
|
+
click.echo("|-- game code:")
|
|
214
|
+
click.echo(f"| |-- code: {game_code}")
|
|
215
|
+
click.echo(f"| |-- date: {game_release}")
|
|
216
|
+
click.echo(f"| `-- language: {game_language}")
|
|
217
|
+
click.echo("|-- marker code:")
|
|
218
|
+
click.echo(f"| |-- id: {marker_id}")
|
|
219
|
+
click.echo(f"| `-- developer: {developer}")
|
|
220
|
+
click.echo(f"|-- fixed value: {is_valid_fixed} ({valid_fixed}h)")
|
|
221
|
+
click.echo(f"|-- unit code: {unit_code}h")
|
|
222
|
+
click.echo(f"|-- device type: {device_type}h")
|
|
223
|
+
click.echo(f"|-- reserved: {is_valid_reserved_area}")
|
|
224
|
+
click.echo(f"|-- software_ver: {software_version}h")
|
|
225
|
+
click.echo("`-- checksum:")
|
|
226
|
+
click.echo(f" |-- valid: {is_valid_sum}")
|
|
227
|
+
click.echo(f" |-- rom: {rom_sum}")
|
|
228
|
+
click.echo(f" `-- our: {sum}")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cartridge-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: GBA SDK — build tool for Game Boy Advance
|
|
5
|
+
Author-email: Lukas Soigneux <lukas.soigneux@epitech.eu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: GBA,Game Boy,GameBoy Advance,Cartridge
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: click
|
|
11
|
+
Requires-Dist: Pillow
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
<a href="https://github.com/lukas-sgx/">
|
|
16
|
+
<img src="https://github.com/lukas-sgx/GBA-sdk/blob/main/assets/gba-logo.png?raw=true" alt="Logo" height="180" style="border-radius: 10px">
|
|
17
|
+
</a>
|
|
18
|
+
|
|
19
|
+
<h3 align="center">GameBoy Advance - SDK</h3>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
A Software Development Kit for developers who want to build GameBoy Advance games.
|
|
23
|
+
<br />
|
|
24
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk"><strong>Explore the docs »</strong></a>
|
|
25
|
+
<br />
|
|
26
|
+
<br />
|
|
27
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk">View Demo</a>
|
|
28
|
+
·
|
|
29
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
|
|
30
|
+
·
|
|
31
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<details>
|
|
36
|
+
<summary>Table of Contents</summary>
|
|
37
|
+
<ol>
|
|
38
|
+
<li>
|
|
39
|
+
<a href="#about-the-project">About The Project</a>
|
|
40
|
+
<ul>
|
|
41
|
+
<li><a href="#built-with">Built With</a></li>
|
|
42
|
+
</ul>
|
|
43
|
+
</li>
|
|
44
|
+
<li>
|
|
45
|
+
<a href="#getting-started">Getting Started</a>
|
|
46
|
+
<ul>
|
|
47
|
+
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
48
|
+
<li><a href="#installation">Installation</a></li>
|
|
49
|
+
</ul>
|
|
50
|
+
</li>
|
|
51
|
+
<li><a href="#usage">Usage</a></li>
|
|
52
|
+
<li><a href="#roadmap">Roadmap</a></li>
|
|
53
|
+
<li><a href="#contributing">Contributing</a></li>
|
|
54
|
+
<li><a href="#license">License</a></li>
|
|
55
|
+
<li><a href="#contact">Contact</a></li>
|
|
56
|
+
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
|
57
|
+
</ol>
|
|
58
|
+
</details>
|
|
59
|
+
|
|
60
|
+
## About The Project
|
|
61
|
+
|
|
62
|
+
This SDK aims to simplify GameBoy Advance homebrew development by allowing developers to write game logic in Python. It handles the underlying compilation, asset conversion, and bindings to interface efficiently with the GBA hardware.
|
|
63
|
+
|
|
64
|
+
### Built With
|
|
65
|
+
|
|
66
|
+
[![Python][Python-shield]][Python-url]
|
|
67
|
+
[![C][C-shield]][C-url]
|
|
68
|
+
[![Assembly][ASM-shield]][ASM-url]
|
|
69
|
+
[![CMake][CMake-shield]][CMake-url]
|
|
70
|
+
|
|
71
|
+
## Getting Started
|
|
72
|
+
|
|
73
|
+
To get a local copy up and running, follow these simple steps.
|
|
74
|
+
|
|
75
|
+
### Prerequisites
|
|
76
|
+
|
|
77
|
+
You need Python 3.x installed on your system
|
|
78
|
+
|
|
79
|
+
### Installation
|
|
80
|
+
|
|
81
|
+
#### Development mode (clone the repo, with local changes)
|
|
82
|
+
1. Clone the repo
|
|
83
|
+
```sh
|
|
84
|
+
git clone https://github.com/lukas-sgx/GBA-sdk.git
|
|
85
|
+
cd GBA-sdk
|
|
86
|
+
```
|
|
87
|
+
2. Install the SDK in development mode
|
|
88
|
+
```sh
|
|
89
|
+
pip install -e .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Release mode (stable version from PyPI)
|
|
93
|
+
```sh
|
|
94
|
+
pip install cartridge-sdk
|
|
95
|
+
```
|
|
96
|
+
## Usage
|
|
97
|
+
|
|
98
|
+
Here is a quick example of how to dump header of ROM:
|
|
99
|
+
```sh
|
|
100
|
+
$ cartridge-sdk hdr dump bin/ExampleGBA.gba
|
|
101
|
+
bin/ExampleGBA.gba:
|
|
102
|
+
|-- entry
|
|
103
|
+
| |-- valid: True
|
|
104
|
+
| |-- raw: 0xea00002e
|
|
105
|
+
| `-- opcode: b 0xc0
|
|
106
|
+
|-- nintendo logo:
|
|
107
|
+
| |-- status: True
|
|
108
|
+
| `-- debugging: True
|
|
109
|
+
|-- game title: EXAMPLEGBA
|
|
110
|
+
|-- game code:
|
|
111
|
+
| |-- code: BXXE
|
|
112
|
+
| |-- date: 2003.. (new)
|
|
113
|
+
| `-- language: USA/English
|
|
114
|
+
|-- marker code:
|
|
115
|
+
| |-- id: 01
|
|
116
|
+
| `-- developer: Nintendo
|
|
117
|
+
|-- fixed value: valid (96h)
|
|
118
|
+
|-- unit code: 00h
|
|
119
|
+
|-- device type: 00h
|
|
120
|
+
|-- reserved: valid
|
|
121
|
+
|-- software_ver: 00h
|
|
122
|
+
`-- checksum:
|
|
123
|
+
|-- valid: True
|
|
124
|
+
|-- rom: e3
|
|
125
|
+
`-- our: e3
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
*For more advanced examples, please refer to the [Documentation](https://github.com/lukas-sgx/GBA-sdk).*
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Roadmap
|
|
132
|
+
|
|
133
|
+
- [x] Automated header checker `.gba` ROM
|
|
134
|
+
- [x] Automated compilation to `.gba` ROM
|
|
135
|
+
- [x] Font asset pipeline (PNG to C file converter)
|
|
136
|
+
- [ ] Core GBA bindings (Video, Audio, Inputs)
|
|
137
|
+
- [ ] Asset pipeline (PNG to GBA sprite palette converter)
|
|
138
|
+
|
|
139
|
+
See the [open issues](https://github.com/lukas-sgx/GBA-sdk/issues) for a full list of proposed features (and known issues).
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Contributing
|
|
143
|
+
|
|
144
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
145
|
+
|
|
146
|
+
1. Fork the Project
|
|
147
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
148
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
149
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
150
|
+
5. Open a Pull Request
|
|
151
|
+
|
|
152
|
+
### Top contributors:
|
|
153
|
+
|
|
154
|
+
<a href="https://github.com/lukas-sgx/GBA-sdk/graphs/contributors">
|
|
155
|
+
<img src="https://contrib.rocks/image?repo=lukas-sgx/GBA-sdk" alt="contrib.rocks image" />
|
|
156
|
+
</a>
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Distributed under the MIT License. See [LICENSE](./LICENSE) for more information.
|
|
161
|
+
|
|
162
|
+
## Contact
|
|
163
|
+
|
|
164
|
+
Lukas Soigneux - lukas.soigneux@epitech.eu
|
|
165
|
+
|
|
166
|
+
## Acknowledgments
|
|
167
|
+
|
|
168
|
+
* [GBATEK](https://mgba-emu.github.io/gbatek/) - GameBoy Advance Technical Info
|
|
169
|
+
* [Ayyboy-Advance](https://github.com/YannMagnin/ayyboy-advance) - Great emulator for testing
|
|
170
|
+
|
|
171
|
+
[Python-shield]: https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white
|
|
172
|
+
[Python-url]: https://www.python.org/
|
|
173
|
+
[C-shield]: https://img.shields.io/badge/C-005895?style=for-the-badge&logo=c&logoColor=white
|
|
174
|
+
[C-url]: https://www.c-language.org/
|
|
175
|
+
[ASM-shield]: https://img.shields.io/badge/-Assembly-f2921d?style=for-the-badge&logo=assemblyscript&logoColor=white
|
|
176
|
+
[ASM-url]: https://developer.arm.com/
|
|
177
|
+
[CMake-shield]: https://img.shields.io/badge/-CMake-064F8C?style=for-the-badge&logo=cmake&logoColor=white
|
|
178
|
+
[CMake-url]: https://cmake.org/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
cartridge/__init__.py
|
|
5
|
+
cartridge/cli.py
|
|
6
|
+
cartridge/modules/build.py
|
|
7
|
+
cartridge/modules/conv.py
|
|
8
|
+
cartridge/modules/init.py
|
|
9
|
+
cartridge/modules/hdr/__init__.py
|
|
10
|
+
cartridge/modules/hdr/dump.py
|
|
11
|
+
cartridge/modules/hdr/gen.py
|
|
12
|
+
cartridge/services/conversion.py
|
|
13
|
+
cartridge/services/generation.py
|
|
14
|
+
cartridge/services/header.py
|
|
15
|
+
cartridge_sdk.egg-info/PKG-INFO
|
|
16
|
+
cartridge_sdk.egg-info/SOURCES.txt
|
|
17
|
+
cartridge_sdk.egg-info/dependency_links.txt
|
|
18
|
+
cartridge_sdk.egg-info/entry_points.txt
|
|
19
|
+
cartridge_sdk.egg-info/requires.txt
|
|
20
|
+
cartridge_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cartridge
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cartridge-sdk"
|
|
7
|
+
description = "GBA SDK — build tool for Game Boy Advance"
|
|
8
|
+
|
|
9
|
+
version = "1.0.0"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"click",
|
|
12
|
+
"Pillow",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
authors = [
|
|
16
|
+
{name = "Lukas Soigneux", email = "lukas.soigneux@epitech.eu"},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = ["LICENSE"]
|
|
22
|
+
keywords = ["GBA", "Game Boy", "GameBoy Advance", "Cartridge"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
include = ["cartridge*"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
cartridge-sdk = "cartridge.cli:main"
|