legacy-puyo-tools 0.0.1__tar.gz → 0.1.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.
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/workflows/ci.yaml +5 -2
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/workflows/release.yaml +3 -11
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.markdownlint.yaml +1 -1
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/CHANGELOG.md +14 -0
- legacy_puyo_tools-0.1.0/CODE_OF_CONDUCT.md +128 -0
- legacy_puyo_tools-0.1.0/CONTRIBUTING.md +60 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSE +1 -1
- legacy_puyo_tools-0.1.0/PKG-INFO +119 -0
- legacy_puyo_tools-0.1.0/README.md +91 -0
- legacy_puyo_tools-0.1.0/REUSE.toml +20 -0
- legacy_puyo_tools-0.1.0/formats.md +49 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/pyproject.toml +27 -7
- legacy_puyo_tools-0.1.0/src/legacy_puyo_tools/cli.py +157 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/exceptions.py +4 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/fpd.py +29 -35
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/mtx.py +5 -12
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/uv.lock +16 -0
- legacy_puyo_tools-0.0.1/PKG-INFO +0 -34
- legacy_puyo_tools-0.0.1/README.md +0 -22
- legacy_puyo_tools-0.0.1/REUSE.toml +0 -15
- legacy_puyo_tools-0.0.1/docs/formats.md +0 -26
- legacy_puyo_tools-0.0.1/src/legacy_puyo_tools/cli.py +0 -177
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.editorconfig +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/dependabot.yml +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.gitignore +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.pre-commit-config.yaml +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSES/MIT-0.txt +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSES/MIT.txt +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/__init__.py +0 -0
- {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/py.typed +0 -0
@@ -20,9 +20,12 @@ jobs:
|
|
20
20
|
- uses: actions/checkout@v4
|
21
21
|
- uses: astral-sh/setup-uv@v6
|
22
22
|
- uses: actions/setup-python@v5
|
23
|
+
with:
|
24
|
+
python-version-file: "pyproject.toml"
|
23
25
|
|
24
|
-
# Disable rules about documentation and TODOs in CI
|
25
|
-
#
|
26
|
+
# Disable rules about documentation and TODOs in CI.
|
27
|
+
# Instead yell at the developer and let them decide when to add
|
28
|
+
# documentation or remove TODOs.
|
26
29
|
- name: Ruff lint
|
27
30
|
run: uv run ruff check --fix --ignore=D101,D102,FIX002
|
28
31
|
- name: Lint using pylint
|
@@ -17,21 +17,13 @@ jobs:
|
|
17
17
|
permissions:
|
18
18
|
id-token: write
|
19
19
|
steps:
|
20
|
+
# Setup environment
|
20
21
|
- uses: actions/checkout@v4
|
21
|
-
|
22
|
-
-
|
23
|
-
uses: astral-sh/setup-uv@v6
|
24
|
-
with:
|
25
|
-
enable-cache: true
|
26
|
-
|
27
|
-
- name: Set up Python
|
28
|
-
uses: actions/setup-python@v5
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
23
|
+
- uses: actions/setup-python@v5
|
29
24
|
with:
|
30
25
|
python-version-file: "pyproject.toml"
|
31
26
|
|
32
|
-
- name: Install package
|
33
|
-
run: uv sync --locked --all-extras --dev
|
34
|
-
|
35
27
|
- name: Build package
|
36
28
|
run: uv build
|
37
29
|
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to
|
7
7
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
8
8
|
|
9
|
+
## 0.1.0 - 2025-07-16
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Installation, usage, and supported games sections in README.md.
|
14
|
+
- Information about the `fmp` format (#2).
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- Rewrite the command line interface to use [`cloup`] instead of `argparse`.
|
19
|
+
- Update formats.md with current support progress.
|
20
|
+
|
21
|
+
[`cloup`]: https://cloup.readthedocs.io
|
22
|
+
|
9
23
|
## 0.0.1 - 2025-07-15
|
10
24
|
|
11
25
|
### Added
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity
|
10
|
+
and orientation.
|
11
|
+
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
13
|
+
diverse, inclusive, and healthy community.
|
14
|
+
|
15
|
+
## Our Standards
|
16
|
+
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
18
|
+
community include:
|
19
|
+
|
20
|
+
- Demonstrating empathy and kindness toward other people
|
21
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
22
|
+
- Giving and gracefully accepting constructive feedback
|
23
|
+
- Accepting responsibility and apologizing to those affected by our mistakes,
|
24
|
+
and learning from the experience
|
25
|
+
- Focusing on what is best not just for us as individuals, but for the
|
26
|
+
overall community
|
27
|
+
|
28
|
+
Examples of unacceptable behavior include:
|
29
|
+
|
30
|
+
- The use of sexualized language or imagery, and sexual attention or
|
31
|
+
advances of any kind
|
32
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
33
|
+
- Public or private harassment
|
34
|
+
- Publishing others' private information, such as a physical or email
|
35
|
+
address, without their explicit permission
|
36
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
37
|
+
professional setting
|
38
|
+
|
39
|
+
## Enforcement Responsibilities
|
40
|
+
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
44
|
+
or harmful.
|
45
|
+
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
49
|
+
decisions when appropriate.
|
50
|
+
|
51
|
+
## Scope
|
52
|
+
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
54
|
+
an individual is officially representing the community in public spaces.
|
55
|
+
Examples of representing our community include using an official e-mail address,
|
56
|
+
posting via an official social media account, or acting as an appointed
|
57
|
+
representative at an online or offline event.
|
58
|
+
|
59
|
+
## Enforcement
|
60
|
+
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
62
|
+
reported to the community leaders responsible for enforcement at
|
63
|
+
<twopizza9621536@gmail.com>.
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
65
|
+
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
67
|
+
reporter of any incident.
|
68
|
+
|
69
|
+
## Enforcement Guidelines
|
70
|
+
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
73
|
+
|
74
|
+
### 1. Correction
|
75
|
+
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
77
|
+
unprofessional or unwelcome in the community.
|
78
|
+
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
82
|
+
|
83
|
+
### 2. Warning
|
84
|
+
|
85
|
+
**Community Impact**: A violation through a single incident or series
|
86
|
+
of actions.
|
87
|
+
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
92
|
+
like social media. Violating these terms may lead to a temporary or
|
93
|
+
permanent ban.
|
94
|
+
|
95
|
+
### 3. Temporary Ban
|
96
|
+
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
98
|
+
sustained inappropriate behavior.
|
99
|
+
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
101
|
+
communication with the community for a specified period of time. No public or
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
104
|
+
Violating these terms may lead to a permanent ban.
|
105
|
+
|
106
|
+
### 4. Permanent Ban
|
107
|
+
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
111
|
+
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within
|
113
|
+
the community.
|
114
|
+
|
115
|
+
## Attribution
|
116
|
+
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
118
|
+
version 2.0, available at
|
119
|
+
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
|
120
|
+
|
121
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
122
|
+
enforcement ladder](https://github.com/mozilla/diversity).
|
123
|
+
|
124
|
+
[homepage]: https://www.contributor-covenant.org
|
125
|
+
|
126
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
127
|
+
<https://www.contributor-covenant.org/faq>. Translations are available at
|
128
|
+
<https://www.contributor-covenant.org/translations>.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# How to contribute
|
2
|
+
|
3
|
+
Want a feature to be added to `legacy-puyo-tools` or found a bug that needs to
|
4
|
+
be fixed. Great! Create an issue at
|
5
|
+
<https://github.com/wushenrong/legacy-puyo-tools/issues> and add a description
|
6
|
+
on why a feature should be added or how the bug occurred.
|
7
|
+
|
8
|
+
If you are contributing code, be sure to add a signoff to your commits with the
|
9
|
+
`-s` or `--signoff` flag as this project uses the
|
10
|
+
[Developer Certificate of Origin][dco] to resolve licensing issues between
|
11
|
+
contributors.
|
12
|
+
|
13
|
+
[dco]: https://developercertificate.org/
|
14
|
+
|
15
|
+
Do not forget to read and follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
16
|
+
|
17
|
+
## Contributors
|
18
|
+
|
19
|
+
- @52871299hzy for contributing information about the `fmp` format.
|
20
|
+
|
21
|
+
## Developer Certificate of Origin
|
22
|
+
|
23
|
+
Below is a reproduction of the [DCO][dco] for reference.
|
24
|
+
|
25
|
+
```txt
|
26
|
+
Developer Certificate of Origin
|
27
|
+
Version 1.1
|
28
|
+
|
29
|
+
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
30
|
+
|
31
|
+
Everyone is permitted to copy and distribute verbatim copies of this
|
32
|
+
license document, but changing it is not allowed.
|
33
|
+
|
34
|
+
|
35
|
+
Developer's Certificate of Origin 1.1
|
36
|
+
|
37
|
+
By making a contribution to this project, I certify that:
|
38
|
+
|
39
|
+
(a) The contribution was created in whole or in part by me and I
|
40
|
+
have the right to submit it under the open source license
|
41
|
+
indicated in the file; or
|
42
|
+
|
43
|
+
(b) The contribution is based upon previous work that, to the best
|
44
|
+
of my knowledge, is covered under an appropriate open source
|
45
|
+
license and I have the right under that license to submit that
|
46
|
+
work with modifications, whether created in whole or in part
|
47
|
+
by me, under the same open source license (unless I am
|
48
|
+
permitted to submit under a different license), as indicated
|
49
|
+
in the file; or
|
50
|
+
|
51
|
+
(c) The contribution was provided directly to me by some other
|
52
|
+
person who certified (a), (b) or (c) and I have not modified
|
53
|
+
it.
|
54
|
+
|
55
|
+
(d) I understand and agree that this project and the contribution
|
56
|
+
are public and that a record of the contribution (including all
|
57
|
+
personal information I submit with it, including my sign-off) is
|
58
|
+
maintained indefinitely and may be redistributed consistent with
|
59
|
+
this project or the open source license(s) involved.
|
60
|
+
```
|
@@ -0,0 +1,119 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: legacy-puyo-tools
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A tool to edit text for older Puyo Puyo games.
|
5
|
+
Project-URL: changelog, https://github.com/wushenrong/legacy-puyo-tools/blob/main/CHANGELOG.md
|
6
|
+
Project-URL: homepage, https://github.com/wushenrong/legacy-puyo-tools
|
7
|
+
Project-URL: issues, https://github.com/wushenrong/legacy-puyo-tools/issues
|
8
|
+
Project-URL: source, https://github.com/wushenrong/legacy-puyo-tools.git
|
9
|
+
Author-email: Samuel Wu <twopizza9621536@gmail.com>
|
10
|
+
License-Expression: MIT
|
11
|
+
License-File: LICENSE
|
12
|
+
Keywords: puyopuyo
|
13
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
14
|
+
Classifier: Environment :: Console
|
15
|
+
Classifier: Intended Audience :: Other Audience
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
19
|
+
Classifier: Topic :: File Formats
|
20
|
+
Classifier: Topic :: Games/Entertainment :: Puzzle Games
|
21
|
+
Classifier: Topic :: Utilities
|
22
|
+
Requires-Python: >=3.13
|
23
|
+
Requires-Dist: attrs>=25.3.0
|
24
|
+
Requires-Dist: click>=8.2.1
|
25
|
+
Requires-Dist: cloup>=3.0.7
|
26
|
+
Requires-Dist: lxml>=6.0.0
|
27
|
+
Description-Content-Type: text/markdown
|
28
|
+
|
29
|
+
# Legacy Puyo Tools
|
30
|
+
|
31
|
+
A command line tool for modding older Puyo Puyo games. (Yes, the name is using a
|
32
|
+
[reversed naming scheme](https://github.com/microsoft/WSL).)
|
33
|
+
|
34
|
+
## Installation
|
35
|
+
|
36
|
+
Install the latest version [Python](https://www.python.org/).
|
37
|
+
|
38
|
+
`legacy-python-tools` is published to
|
39
|
+
[PyPI](https://pypi.org/project/legacy-puyo-tools/). It is recommended to
|
40
|
+
install tools from PyPI into an isolated Python environment.
|
41
|
+
|
42
|
+
You can use [`pipx`](https://pipx.pypa.io):
|
43
|
+
|
44
|
+
```bash
|
45
|
+
pipx install legacy-python-tools
|
46
|
+
# Or to run the cli without installing legacy-python-tools
|
47
|
+
pipx run legacy-python-tools
|
48
|
+
```
|
49
|
+
|
50
|
+
Or [`uv`](https://docs.astral.sh/uv):
|
51
|
+
|
52
|
+
```bash
|
53
|
+
uv tool install legacy-python-tools
|
54
|
+
# Or to run the cli without installing legacy-python-tools
|
55
|
+
uv tool run legacy-python-tools
|
56
|
+
# Or the shorter uvx
|
57
|
+
uvx legacy-python-tools
|
58
|
+
```
|
59
|
+
|
60
|
+
And of course, you can use good old pip in a virtual Python environment using
|
61
|
+
[`virualenv`](https://virtualenv.pypa.io) or the built-in `venv` library:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
# Create a virtual python environment
|
65
|
+
virualenv .venv
|
66
|
+
# Or with the venv library
|
67
|
+
python -m venv .venv
|
68
|
+
# Activate the virtual environment
|
69
|
+
./.venv/Scripts/activate
|
70
|
+
# Install legacy-python-tools
|
71
|
+
pip install legacy-python-tools
|
72
|
+
# Or using the pip module
|
73
|
+
python -m pip install legacy-python-tools
|
74
|
+
```
|
75
|
+
|
76
|
+
## Usage
|
77
|
+
|
78
|
+
Create a `fpd` file from a UTF-16 little-endian encoded text file.
|
79
|
+
|
80
|
+
```bash
|
81
|
+
legacy-puyo-tools create fpd puyo14.txt
|
82
|
+
```
|
83
|
+
|
84
|
+
Or convert a `mtx` file to an editable XML file using a `fpd` file.
|
85
|
+
|
86
|
+
```bash
|
87
|
+
legacy-puyo-tools convert mtx --output custom_als.mtx --fpd puyo14.fpd als.xml
|
88
|
+
```
|
89
|
+
|
90
|
+
You can use the `--help` flag to see what sub-commands and options are
|
91
|
+
available.
|
92
|
+
|
93
|
+
## Supported Games
|
94
|
+
|
95
|
+
This tool will try to support formats from the following Puyo games:
|
96
|
+
|
97
|
+
- Puyo Puyo! 15th Annversivery
|
98
|
+
- Puyo Puyo 7
|
99
|
+
- Puyo Puyo!! 20th Annversivery (If there is demand)
|
100
|
+
|
101
|
+
See [Formats](formats.md) for detailed information about those formats, and the
|
102
|
+
current progress on creating and converting them.
|
103
|
+
|
104
|
+
## Why
|
105
|
+
|
106
|
+
The [Puyo Text Editor][puyo-text-editor] can already do what `legacy-puyo-tools`
|
107
|
+
does and is the inspiration of this tool, but there are advantages to rewriting
|
108
|
+
it in Python:
|
109
|
+
|
110
|
+
[puyo-text-editor]: https://github.com/nickworonekin/puyo-text-editor
|
111
|
+
|
112
|
+
- Better cross compatibility with Linux.
|
113
|
+
- Don't have to update the language version every time it becomes End of Life.
|
114
|
+
- Avoids the rigidness of using a pure object-oriented design.
|
115
|
+
|
116
|
+
## License
|
117
|
+
|
118
|
+
Under the MIT License. Based on [Puyo Text Editor][puyo-text-editor] which is
|
119
|
+
also under the MIT License.
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Legacy Puyo Tools
|
2
|
+
|
3
|
+
A command line tool for modding older Puyo Puyo games. (Yes, the name is using a
|
4
|
+
[reversed naming scheme](https://github.com/microsoft/WSL).)
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Install the latest version [Python](https://www.python.org/).
|
9
|
+
|
10
|
+
`legacy-python-tools` is published to
|
11
|
+
[PyPI](https://pypi.org/project/legacy-puyo-tools/). It is recommended to
|
12
|
+
install tools from PyPI into an isolated Python environment.
|
13
|
+
|
14
|
+
You can use [`pipx`](https://pipx.pypa.io):
|
15
|
+
|
16
|
+
```bash
|
17
|
+
pipx install legacy-python-tools
|
18
|
+
# Or to run the cli without installing legacy-python-tools
|
19
|
+
pipx run legacy-python-tools
|
20
|
+
```
|
21
|
+
|
22
|
+
Or [`uv`](https://docs.astral.sh/uv):
|
23
|
+
|
24
|
+
```bash
|
25
|
+
uv tool install legacy-python-tools
|
26
|
+
# Or to run the cli without installing legacy-python-tools
|
27
|
+
uv tool run legacy-python-tools
|
28
|
+
# Or the shorter uvx
|
29
|
+
uvx legacy-python-tools
|
30
|
+
```
|
31
|
+
|
32
|
+
And of course, you can use good old pip in a virtual Python environment using
|
33
|
+
[`virualenv`](https://virtualenv.pypa.io) or the built-in `venv` library:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
# Create a virtual python environment
|
37
|
+
virualenv .venv
|
38
|
+
# Or with the venv library
|
39
|
+
python -m venv .venv
|
40
|
+
# Activate the virtual environment
|
41
|
+
./.venv/Scripts/activate
|
42
|
+
# Install legacy-python-tools
|
43
|
+
pip install legacy-python-tools
|
44
|
+
# Or using the pip module
|
45
|
+
python -m pip install legacy-python-tools
|
46
|
+
```
|
47
|
+
|
48
|
+
## Usage
|
49
|
+
|
50
|
+
Create a `fpd` file from a UTF-16 little-endian encoded text file.
|
51
|
+
|
52
|
+
```bash
|
53
|
+
legacy-puyo-tools create fpd puyo14.txt
|
54
|
+
```
|
55
|
+
|
56
|
+
Or convert a `mtx` file to an editable XML file using a `fpd` file.
|
57
|
+
|
58
|
+
```bash
|
59
|
+
legacy-puyo-tools convert mtx --output custom_als.mtx --fpd puyo14.fpd als.xml
|
60
|
+
```
|
61
|
+
|
62
|
+
You can use the `--help` flag to see what sub-commands and options are
|
63
|
+
available.
|
64
|
+
|
65
|
+
## Supported Games
|
66
|
+
|
67
|
+
This tool will try to support formats from the following Puyo games:
|
68
|
+
|
69
|
+
- Puyo Puyo! 15th Annversivery
|
70
|
+
- Puyo Puyo 7
|
71
|
+
- Puyo Puyo!! 20th Annversivery (If there is demand)
|
72
|
+
|
73
|
+
See [Formats](formats.md) for detailed information about those formats, and the
|
74
|
+
current progress on creating and converting them.
|
75
|
+
|
76
|
+
## Why
|
77
|
+
|
78
|
+
The [Puyo Text Editor][puyo-text-editor] can already do what `legacy-puyo-tools`
|
79
|
+
does and is the inspiration of this tool, but there are advantages to rewriting
|
80
|
+
it in Python:
|
81
|
+
|
82
|
+
[puyo-text-editor]: https://github.com/nickworonekin/puyo-text-editor
|
83
|
+
|
84
|
+
- Better cross compatibility with Linux.
|
85
|
+
- Don't have to update the language version every time it becomes End of Life.
|
86
|
+
- Avoids the rigidness of using a pure object-oriented design.
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
Under the MIT License. Based on [Puyo Text Editor][puyo-text-editor] which is
|
91
|
+
also under the MIT License.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Samuel Wu
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT-0
|
4
|
+
|
5
|
+
version = 1
|
6
|
+
|
7
|
+
[[annotations]]
|
8
|
+
SPDX-FileCopyrightText = "2025 Samuel Wu and contributors"
|
9
|
+
SPDX-License-Identifier = "MIT"
|
10
|
+
path = ["formats.md"]
|
11
|
+
|
12
|
+
[[annotations]]
|
13
|
+
SPDX-FileCopyrightText = "2025 Samuel Wu"
|
14
|
+
SPDX-License-Identifier = "MIT-0"
|
15
|
+
path = ["CHANGELOG.md", "CONTRIBUTING.md", "README.md", "uv.lock"]
|
16
|
+
|
17
|
+
[[annotations]]
|
18
|
+
SPDX-FileCopyrightText = "2014 Coraline Ada Ehmke"
|
19
|
+
SPDX-License-Identifier = "CC-BY-4.0"
|
20
|
+
path = ["CODE_OF_CONDUCT.md"]
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Formats
|
2
|
+
|
3
|
+
Below are the formats that are used by the older Puyo games. This includes how
|
4
|
+
each format is structured and the current progress on creating and converting
|
5
|
+
them.
|
6
|
+
|
7
|
+
## The `fpd` format
|
8
|
+
|
9
|
+
Both the creation and conversion of `fpd` are fully implemented.
|
10
|
+
|
11
|
+
The `fpd` format is a binary character table format used by the developers of
|
12
|
+
Puyo Puyo! 15th Anniversary and Puyo Puyo 7 to convert characters from UTF-16
|
13
|
+
little-endian into an index that can be used by the `mtx` for text. Each
|
14
|
+
character entry in the `fpd` is 3 bytes long and formatted as follows:
|
15
|
+
`XX XX YY`. Where `XX XX` is the character encoded in UTF-16 little-endian and
|
16
|
+
`YY` is the width of the character. The entries are placed next to each other,
|
17
|
+
creating a zero-based index that is offset by multiples of `0x03`. I.e. the 1st
|
18
|
+
character is at index `0x00`, the 2nd character is at index `0x03`, the 3rd
|
19
|
+
character is at index `0x06`, etc.
|
20
|
+
|
21
|
+
The `fpd` is not used by the games internally except for the Nintendo DS version
|
22
|
+
of the games.
|
23
|
+
|
24
|
+
## The `fmp` format
|
25
|
+
|
26
|
+
The `fmp` format is a bitmap format used by the Nintendo DS versions of Puyo
|
27
|
+
Puyo! 15th Anniversary and Puyo Puyo 7 to store the pixel data of the font
|
28
|
+
corresponding to the `fpd` file. Characters are stored in the same order as the
|
29
|
+
`fpd` file. Each character is either 14x14 pixels (in `puyo14.fmp` and
|
30
|
+
`test.fmp`) or 8x8 pixels (in `puyo8.fmp`). The image data is stored in a packed
|
31
|
+
format where each byte encodes two horizontal pixels, where the lower nibble
|
32
|
+
(4 bits) represent the left pixel and the higher nibble represent the right
|
33
|
+
one:
|
34
|
+
|
35
|
+
- `0x1` represents a visible pixel (on pixel)
|
36
|
+
- `0x0` represents an empty pixel (off pixel)
|
37
|
+
|
38
|
+
Pixels are stored row by row, in top-to-bottom and left-to-right order. There
|
39
|
+
are no headers or padding bytes in the file.
|
40
|
+
|
41
|
+
## The `mtx` format
|
42
|
+
|
43
|
+
The creation of `mtx` has not been implemented yet while conversion only has
|
44
|
+
partial support.
|
45
|
+
|
46
|
+
<!-- TODO: Finish the mtx format for PP15 and PP7 -->
|
47
|
+
<!-- TODO: Look at the mtx format for PP20 -->
|
48
|
+
The `mtx` format is a binary-encoded format used by older Puyo games for
|
49
|
+
storing character dialog and text.
|
@@ -4,20 +4,41 @@
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
authors = [{ name = "Samuel Wu", email = "twopizza9621536@gmail.com" }]
|
7
|
-
|
7
|
+
classifiers = [
|
8
|
+
"Development Status :: 2 - Pre-Alpha",
|
9
|
+
"Environment :: Console",
|
10
|
+
"License :: OSI Approved :: MIT License",
|
11
|
+
|
12
|
+
# Intended Audience
|
13
|
+
"Intended Audience :: Other Audience",
|
14
|
+
"Topic :: File Formats",
|
15
|
+
"Topic :: Games/Entertainment :: Puzzle Games",
|
16
|
+
"Topic :: Utilities",
|
17
|
+
|
18
|
+
# Python Versions
|
19
|
+
"Programming Language :: Python :: 3",
|
20
|
+
"Programming Language :: Python :: 3.13",
|
21
|
+
]
|
22
|
+
dependencies = ["attrs>=25.3.0", "click>=8.2.1", "cloup>=3.0.7", "lxml>=6.0.0"]
|
8
23
|
description = "A tool to edit text for older Puyo Puyo games."
|
24
|
+
keywords = ["puyopuyo"]
|
9
25
|
license = "MIT"
|
10
26
|
license-files = ["LICENSE"]
|
11
27
|
name = "legacy-puyo-tools"
|
12
28
|
readme = "README.md"
|
13
29
|
requires-python = ">=3.13"
|
14
|
-
version = "0.0
|
30
|
+
version = "0.1.0"
|
31
|
+
|
32
|
+
[project.urls]
|
33
|
+
changelog = "https://github.com/wushenrong/legacy-puyo-tools/blob/main/CHANGELOG.md"
|
34
|
+
homepage = "https://github.com/wushenrong/legacy-puyo-tools"
|
35
|
+
issues = "https://github.com/wushenrong/legacy-puyo-tools/issues"
|
36
|
+
source = "https://github.com/wushenrong/legacy-puyo-tools.git"
|
15
37
|
|
16
38
|
[project.scripts]
|
17
39
|
legacy-puyo-tools = "legacy_puyo_tools.cli:main"
|
18
40
|
|
19
41
|
[tool.ruff]
|
20
|
-
line-length = 79
|
21
42
|
output-format = "concise"
|
22
43
|
# Required for some rules
|
23
44
|
preview = true
|
@@ -56,17 +77,16 @@ typing-modules = ["types-lxml"]
|
|
56
77
|
# Disable rules that conflict with the formatter
|
57
78
|
ignore = ["COM812"]
|
58
79
|
|
59
|
-
[tool.ruff.lint.
|
60
|
-
|
80
|
+
[tool.ruff.lint.per-file-ignores]
|
81
|
+
"src/legacy_puyo_tools/cli.py" = ["DOC501"]
|
61
82
|
|
62
83
|
[tool.ruff.lint.pydocstyle]
|
63
84
|
convention = "google"
|
64
85
|
|
65
86
|
[tool.pylint]
|
66
87
|
extension-pkg-allow-list = ["lxml"]
|
67
|
-
max-line-length =
|
88
|
+
max-line-length = 88
|
68
89
|
output-format = "colorized"
|
69
|
-
source-roots = ["src"]
|
70
90
|
|
71
91
|
[tool.pyright]
|
72
92
|
include = ["src"]
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"""A commandline interface for the conversion tools.
|
2
|
+
|
3
|
+
SPDX-FileCopyrightText: 2025 Samuel Wu
|
4
|
+
SPDX-License-Identifier: MIT
|
5
|
+
"""
|
6
|
+
|
7
|
+
from codecs import BOM_UTF16_LE
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import BinaryIO
|
10
|
+
|
11
|
+
import cloup
|
12
|
+
from cloup import option, option_group
|
13
|
+
from cloup.constraints import require_one
|
14
|
+
|
15
|
+
from legacy_puyo_tools.exceptions import ArgumentError, FileFormatError
|
16
|
+
from legacy_puyo_tools.fpd import Fpd
|
17
|
+
from legacy_puyo_tools.mtx import Mtx
|
18
|
+
|
19
|
+
output_option = option(
|
20
|
+
"--output",
|
21
|
+
"-o",
|
22
|
+
help="Output file. Defaults to an appropriate filename and extension.",
|
23
|
+
type=cloup.File("wb"),
|
24
|
+
)
|
25
|
+
|
26
|
+
mtx_options = option_group(
|
27
|
+
"Character table options",
|
28
|
+
option(
|
29
|
+
"--fpd",
|
30
|
+
help="Use a fpd file as the character table.",
|
31
|
+
type=cloup.Path(exists=True, dir_okay=False, path_type=Path),
|
32
|
+
),
|
33
|
+
option(
|
34
|
+
"--unicode",
|
35
|
+
help="Use a unicode text file as the character table.",
|
36
|
+
type=cloup.Path(exists=True, dir_okay=False, path_type=Path),
|
37
|
+
),
|
38
|
+
constraint=require_one.rephrased(
|
39
|
+
"exactly 1 character table required for mtx files",
|
40
|
+
"exactly 1 character table must be specified",
|
41
|
+
),
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
@cloup.group()
|
46
|
+
@cloup.version_option()
|
47
|
+
def main() -> None:
|
48
|
+
"""A conversion tool for files used by older Puyo games."""
|
49
|
+
|
50
|
+
|
51
|
+
@main.group()
|
52
|
+
def create() -> None:
|
53
|
+
"""Create files to used by older Puyo games."""
|
54
|
+
|
55
|
+
|
56
|
+
@create.command(name="fpd")
|
57
|
+
@cloup.argument(
|
58
|
+
"input_file",
|
59
|
+
help="Unicode text file encoded in UTF-16 little-endian.",
|
60
|
+
type=cloup.File("rb"),
|
61
|
+
)
|
62
|
+
@output_option
|
63
|
+
def create_fpd(input_file: BinaryIO, output: BinaryIO | None) -> None:
|
64
|
+
"""Create a fpd file from a unicode text file."""
|
65
|
+
if input_file.read(2) != BOM_UTF16_LE:
|
66
|
+
raise FileFormatError(
|
67
|
+
f"{input_file.name} is not a UTF-16 little-endian encoded text file."
|
68
|
+
)
|
69
|
+
|
70
|
+
if output:
|
71
|
+
Fpd.read_unicode(input_file).write_fpd(output)
|
72
|
+
return
|
73
|
+
|
74
|
+
path = Path(input_file.name).with_suffix("")
|
75
|
+
|
76
|
+
if path.suffix != ".fpd":
|
77
|
+
path = path.with_suffix(".fpd")
|
78
|
+
|
79
|
+
Fpd.read_unicode(input_file).write_fpd_to_path(path)
|
80
|
+
|
81
|
+
|
82
|
+
@create.command(name="mtx", show_constraints=True)
|
83
|
+
@cloup.argument(
|
84
|
+
"input_file",
|
85
|
+
help="XML file that contains markup text or dialog.",
|
86
|
+
type=cloup.File("rb"),
|
87
|
+
)
|
88
|
+
@output_option
|
89
|
+
@mtx_options
|
90
|
+
def create_mtx(
|
91
|
+
input_file: BinaryIO, output: BinaryIO, fpd: Path | None, unicode: Path | None
|
92
|
+
) -> None:
|
93
|
+
"""Create a mtx file from a XML file."""
|
94
|
+
raise NotImplementedError("Creating MTX files is currently not implemented yet.")
|
95
|
+
|
96
|
+
|
97
|
+
@main.group()
|
98
|
+
def convert() -> None:
|
99
|
+
"""Convert files used by older Puyo games to an editable format."""
|
100
|
+
|
101
|
+
|
102
|
+
@convert.command(name="fpd")
|
103
|
+
@cloup.argument(
|
104
|
+
"input_file", help="Fpd file containing character data.", type=cloup.File("rb")
|
105
|
+
)
|
106
|
+
@output_option
|
107
|
+
def convert_fpd(input_file: BinaryIO, output_file: BinaryIO | None) -> None:
|
108
|
+
"""Convert a fpd file to a UTF-16 little-endian unicode text file."""
|
109
|
+
if output_file:
|
110
|
+
output_file.write(BOM_UTF16_LE)
|
111
|
+
Fpd.read_fpd(input_file).write_fpd(output_file)
|
112
|
+
return
|
113
|
+
|
114
|
+
path = Path(input_file.name).with_suffix("")
|
115
|
+
|
116
|
+
if path.suffix != ".fpd":
|
117
|
+
path = path.with_suffix(".fpd")
|
118
|
+
|
119
|
+
Fpd.read_fpd(input_file).write_unicode_to_path(path)
|
120
|
+
|
121
|
+
|
122
|
+
@convert.command(name="mtx", show_constraints=True)
|
123
|
+
@cloup.argument(
|
124
|
+
"input_file", help="Mtx file containing Manzai text.", type=cloup.File("rb")
|
125
|
+
)
|
126
|
+
@output_option
|
127
|
+
@mtx_options
|
128
|
+
def convert_mtx(
|
129
|
+
input_file: BinaryIO,
|
130
|
+
output: BinaryIO | None,
|
131
|
+
fpd: Path | None,
|
132
|
+
unicode: Path | None,
|
133
|
+
) -> None:
|
134
|
+
"""Convert a mtx file to a XML file."""
|
135
|
+
if fpd:
|
136
|
+
fpd_data = Fpd.read_fpd_from_path(fpd)
|
137
|
+
elif unicode:
|
138
|
+
fpd_data = Fpd.read_unicode_from_path(unicode)
|
139
|
+
else:
|
140
|
+
raise ArgumentError(
|
141
|
+
"You must specify a character table using --fpd or --unicode."
|
142
|
+
)
|
143
|
+
|
144
|
+
if output:
|
145
|
+
Mtx.read_mtx(input_file).write_xml(output, fpd_data)
|
146
|
+
return
|
147
|
+
|
148
|
+
path = Path(input_file.name).with_suffix("")
|
149
|
+
|
150
|
+
if path.suffix != ".xml":
|
151
|
+
path = path.with_suffix(".xml")
|
152
|
+
|
153
|
+
Mtx.read_mtx(input_file).write_xml_to_file(path, fpd_data)
|
154
|
+
|
155
|
+
|
156
|
+
if __name__ == "__main__":
|
157
|
+
main()
|
@@ -1,7 +1,7 @@
|
|
1
|
-
"""
|
1
|
+
"""Fpd conversion tool for older Puyo games.
|
2
2
|
|
3
|
-
This module supports the encoding and decoding of the fpd format used by
|
4
|
-
|
3
|
+
This module supports the encoding and decoding of the fpd format used by Puyo Puyo! 15th
|
4
|
+
Anniversary and Puyo Puyo 7.
|
5
5
|
|
6
6
|
SPDX-FileCopyrightText: 2025 Samuel Wu
|
7
7
|
SPDX-License-Identifier: MIT
|
@@ -26,16 +26,16 @@ WIDTH_ENTRY_OFFSET = 2
|
|
26
26
|
class FpdCharacter:
|
27
27
|
"""A fpd character entry.
|
28
28
|
|
29
|
-
A fpd character is a binary entry that is 3 bytes long and formatted
|
30
|
-
|
31
|
-
|
29
|
+
A fpd character is a binary entry that is 3 bytes long and formatted as follows:
|
30
|
+
`XX XX YY`. Where `XX XX` is the character encoded in UTF-16 little-endian and `YY`
|
31
|
+
is the width of the character.
|
32
32
|
|
33
33
|
Attributes:
|
34
34
|
code_point:
|
35
35
|
A string that stores a single character.
|
36
36
|
width:
|
37
|
-
How wide should the character be, only used in the Nintendo
|
38
|
-
|
37
|
+
How wide should the character be, only used in the Nintendo DS versions of
|
38
|
+
the games.
|
39
39
|
"""
|
40
40
|
|
41
41
|
code_point: str
|
@@ -71,16 +71,13 @@ class FpdCharacter:
|
|
71
71
|
|
72
72
|
Raises:
|
73
73
|
FormatError:
|
74
|
-
The entry given does not conform to the fpd character
|
75
|
-
format.
|
74
|
+
The entry given does not conform to the fpd character format.
|
76
75
|
|
77
76
|
Returns:
|
78
77
|
A fpd character entry containing its code point and width.
|
79
78
|
"""
|
80
79
|
if len(fpd_entry) != FPD_ENTRY_LENGTH:
|
81
|
-
raise FormatError(
|
82
|
-
f"{fpd_entry} does not matches size {FPD_ENTRY_LENGTH}"
|
83
|
-
)
|
80
|
+
raise FormatError(f"{fpd_entry} does not matches size {FPD_ENTRY_LENGTH}")
|
84
81
|
|
85
82
|
return cls(fpd_entry[:UTF16_LENGTH], fpd_entry[WIDTH_ENTRY_OFFSET])
|
86
83
|
|
@@ -89,10 +86,10 @@ class FpdCharacter:
|
|
89
86
|
class Fpd:
|
90
87
|
"""A fpd character table.
|
91
88
|
|
92
|
-
The fpd stores character table in which each entry is placed right
|
93
|
-
|
94
|
-
|
95
|
-
|
89
|
+
The fpd stores character table in which each entry is placed right next to each
|
90
|
+
other and the indices is offset by multiples of `0x03`. I.e. The 1st character is at
|
91
|
+
index `0x00`, the 2nd character is at index `0x03`, the 3rd character is at index
|
92
|
+
`0x06`, etc.
|
96
93
|
|
97
94
|
Attributes:
|
98
95
|
entries:
|
@@ -119,13 +116,12 @@ class Fpd:
|
|
119
116
|
|
120
117
|
Args:
|
121
118
|
path:
|
122
|
-
A path to a fpd encoded file that contains a fpd
|
123
|
-
character table.
|
119
|
+
A path to a fpd encoded file that contains a fpd character table.
|
124
120
|
|
125
121
|
Raises:
|
126
122
|
FileFormatError:
|
127
|
-
The fpd file contain a entry that does not conform
|
128
|
-
|
123
|
+
The fpd file contain a entry that does not conform to the fpd character
|
124
|
+
format.
|
129
125
|
|
130
126
|
Returns:
|
131
127
|
A fpd character table.
|
@@ -155,8 +151,7 @@ class Fpd:
|
|
155
151
|
|
156
152
|
Args:
|
157
153
|
data:
|
158
|
-
A fpd encoded stream that contains a fpd character
|
159
|
-
table.
|
154
|
+
A fpd encoded stream that contains a fpd character table.
|
160
155
|
|
161
156
|
Returns:
|
162
157
|
A fpd character table.
|
@@ -199,25 +194,26 @@ class Fpd:
|
|
199
194
|
|
200
195
|
@classmethod
|
201
196
|
def read_unicode_from_path(cls, path: Path) -> Self:
|
202
|
-
"""Reads and convert characters from a UTF-16
|
197
|
+
"""Reads and convert characters from a UTF-16 little-endian text file.
|
203
198
|
|
204
199
|
Arguments:
|
205
|
-
path:
|
206
|
-
A path to a UTF-16 LE text file.
|
200
|
+
path: A path to a UTF-16 LE text file.
|
207
201
|
|
208
202
|
Raises:
|
209
203
|
FileFormatError:
|
210
|
-
The file is not a UTF-16
|
211
|
-
|
204
|
+
The file is not a UTF-16 little-endian encoded text file or is missing
|
205
|
+
the Byte Order Mark for UTF-16 little-endian.
|
212
206
|
|
213
207
|
Returns:
|
214
208
|
A fpd character table.
|
215
209
|
"""
|
216
210
|
with Path(path).open("rb") as fp:
|
217
|
-
# Check the Byte Order Mark (BOM) to see if it is really a
|
218
|
-
#
|
211
|
+
# Check the Byte Order Mark (BOM) to see if it is really a UTF-16 LE text
|
212
|
+
# file
|
219
213
|
if fp.read(2) != BOM_UTF16_LE:
|
220
|
-
raise FileFormatError(
|
214
|
+
raise FileFormatError(
|
215
|
+
f"{path} is not a UTF-16 little-endian text file."
|
216
|
+
)
|
221
217
|
|
222
218
|
return cls.read_unicode(fp)
|
223
219
|
|
@@ -234,8 +230,7 @@ class Fpd:
|
|
234
230
|
"""
|
235
231
|
return cls.decode_unicode(fp.read())
|
236
232
|
|
237
|
-
# TODO: Somehow allow people to specify the width of the character
|
238
|
-
# during decoding
|
233
|
+
# TODO: Somehow allow people to specify the width of the character during decoding
|
239
234
|
@classmethod
|
240
235
|
def decode_unicode(cls, unicode: bytes) -> Self:
|
241
236
|
"""Converts a UTF-16 LE stream into a fpd character table.
|
@@ -278,8 +273,7 @@ class Fpd:
|
|
278
273
|
"""Encodes the fpd character table into a UTF-16 LE text stream.
|
279
274
|
|
280
275
|
Returns:
|
281
|
-
A UTF-16 LE encoded text stream with characters from the
|
282
|
-
fpd.
|
276
|
+
A UTF-16 LE encoded text stream with characters from the fpd.
|
283
277
|
"""
|
284
278
|
with BytesIO() as bytes_buffer:
|
285
279
|
for character in self.entries:
|
@@ -1,8 +1,7 @@
|
|
1
1
|
"""Manzai text conversion tool for older Puyo Puyo games.
|
2
2
|
|
3
|
-
This module converts
|
4
|
-
|
5
|
-
Anniversary.
|
3
|
+
This module converts mtx files to and from XML for modding Puyo games. Currently
|
4
|
+
supports Puyo Puyo 7 and might support Puyo Puyo! 15th Anniversary.
|
6
5
|
|
7
6
|
SPDX-FileCopyrightText: 2025 Samuel Wu
|
8
7
|
SPDX-License-Identifier: MIT
|
@@ -62,9 +61,7 @@ class Mtx:
|
|
62
61
|
try:
|
63
62
|
return cls.read_mtx(fp)
|
64
63
|
except FormatError as e:
|
65
|
-
raise FileFormatError(
|
66
|
-
f"{path} is not a valid `mtx` file"
|
67
|
-
) from e
|
64
|
+
raise FileFormatError(f"{path} is not a valid `mtx` file") from e
|
68
65
|
|
69
66
|
@classmethod
|
70
67
|
def read_mtx(cls, fp: BinaryIO) -> Self:
|
@@ -85,9 +82,7 @@ class Mtx:
|
|
85
82
|
|
86
83
|
sections = [
|
87
84
|
read_offset(data, section_table_offset + (i * int_width))
|
88
|
-
for i in range(
|
89
|
-
(string_table_offset - section_table_offset) // int_width
|
90
|
-
)
|
85
|
+
for i in range((string_table_offset - section_table_offset) // int_width)
|
91
86
|
]
|
92
87
|
|
93
88
|
# Add the length to the sections so we can read to end of stream
|
@@ -97,9 +92,7 @@ class Mtx:
|
|
97
92
|
|
98
93
|
for current_string_offset, next_string_offset in pairwise(sections):
|
99
94
|
strings.append([
|
100
|
-
_read_character(
|
101
|
-
data, current_string_offset + (i * CHARACTER_WIDTH)
|
102
|
-
)
|
95
|
+
_read_character(data, current_string_offset + (i * CHARACTER_WIDTH))
|
103
96
|
for i in range(next_string_offset - current_string_offset)
|
104
97
|
])
|
105
98
|
|
@@ -106,6 +106,18 @@ wheels = [
|
|
106
106
|
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
|
107
107
|
]
|
108
108
|
|
109
|
+
[[package]]
|
110
|
+
name = "cloup"
|
111
|
+
version = "3.0.7"
|
112
|
+
source = { registry = "https://pypi.org/simple" }
|
113
|
+
dependencies = [
|
114
|
+
{ name = "click" },
|
115
|
+
]
|
116
|
+
sdist = { url = "https://files.pythonhosted.org/packages/86/c9/3c621e0b7898403556e807244104095df1132a6094384f80c272bba4e4e4/cloup-3.0.7.tar.gz", hash = "sha256:c852e0a0541aa433c6ab31a9b8b503f63d9881e91ddaf0384d6927965f2b421c", size = 229613, upload-time = "2025-03-15T00:34:10.224Z" }
|
117
|
+
wheels = [
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/45/2e/ccd754e473b972155f7e8ca60af373a8bc13ae951a5cb76363c43363e69d/cloup-3.0.7-py2.py3-none-any.whl", hash = "sha256:d3a431cf1e14d8835fe85608e02cc6ff7216f0a39e30d5e28618aa938003ddb4", size = 54602, upload-time = "2025-03-15T00:34:09.165Z" },
|
119
|
+
]
|
120
|
+
|
109
121
|
[[package]]
|
110
122
|
name = "colorama"
|
111
123
|
version = "0.4.6"
|
@@ -187,6 +199,8 @@ version = "0.0.1"
|
|
187
199
|
source = { editable = "." }
|
188
200
|
dependencies = [
|
189
201
|
{ name = "attrs" },
|
202
|
+
{ name = "click" },
|
203
|
+
{ name = "cloup" },
|
190
204
|
{ name = "lxml" },
|
191
205
|
]
|
192
206
|
|
@@ -203,6 +217,8 @@ dev = [
|
|
203
217
|
[package.metadata]
|
204
218
|
requires-dist = [
|
205
219
|
{ name = "attrs", specifier = ">=25.3.0" },
|
220
|
+
{ name = "click", specifier = ">=8.2.1" },
|
221
|
+
{ name = "cloup", specifier = ">=3.0.7" },
|
206
222
|
{ name = "lxml", specifier = ">=6.0.0" },
|
207
223
|
]
|
208
224
|
|
legacy_puyo_tools-0.0.1/PKG-INFO
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: legacy-puyo-tools
|
3
|
-
Version: 0.0.1
|
4
|
-
Summary: A tool to edit text for older Puyo Puyo games.
|
5
|
-
Author-email: Samuel Wu <twopizza9621536@gmail.com>
|
6
|
-
License-Expression: MIT
|
7
|
-
License-File: LICENSE
|
8
|
-
Requires-Python: >=3.13
|
9
|
-
Requires-Dist: attrs>=25.3.0
|
10
|
-
Requires-Dist: lxml>=6.0.0
|
11
|
-
Description-Content-Type: text/markdown
|
12
|
-
|
13
|
-
# Legacy Puyo Tools
|
14
|
-
|
15
|
-
Supports Puyo Puyo 7 and possibly Puyo Puyo! 15th Anniversary `mtx` and `fpd`
|
16
|
-
files. Puyo Puyo!! 20th Anniversary is still not supported yet. (Create an issue
|
17
|
-
or pull request to support `fnt` and additional `mtx` controls). Also, legacy as
|
18
|
-
in older Puyo Puyo games, not that this tool is decapitated.
|
19
|
-
|
20
|
-
## Why
|
21
|
-
|
22
|
-
The [Puyo Text Editor][1] can already do what Legacy Puyo Tools does and is the
|
23
|
-
inspiration of this tool, but there are advantages to rewrite it in Python:
|
24
|
-
|
25
|
-
- Better cross compatibility with Linux.
|
26
|
-
- Don't have to update the language version every time it becomes End of Life.
|
27
|
-
- Avoids the rigidness of using a pure object-oriented design.
|
28
|
-
|
29
|
-
## License
|
30
|
-
|
31
|
-
Under the MIT License. Based on [Puyo Text Editor][1] which is also under the
|
32
|
-
MIT License.
|
33
|
-
|
34
|
-
[1]: https://github.com/nickworonekin/puyo-text-editor
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# Legacy Puyo Tools
|
2
|
-
|
3
|
-
Supports Puyo Puyo 7 and possibly Puyo Puyo! 15th Anniversary `mtx` and `fpd`
|
4
|
-
files. Puyo Puyo!! 20th Anniversary is still not supported yet. (Create an issue
|
5
|
-
or pull request to support `fnt` and additional `mtx` controls). Also, legacy as
|
6
|
-
in older Puyo Puyo games, not that this tool is decapitated.
|
7
|
-
|
8
|
-
## Why
|
9
|
-
|
10
|
-
The [Puyo Text Editor][1] can already do what Legacy Puyo Tools does and is the
|
11
|
-
inspiration of this tool, but there are advantages to rewrite it in Python:
|
12
|
-
|
13
|
-
- Better cross compatibility with Linux.
|
14
|
-
- Don't have to update the language version every time it becomes End of Life.
|
15
|
-
- Avoids the rigidness of using a pure object-oriented design.
|
16
|
-
|
17
|
-
## License
|
18
|
-
|
19
|
-
Under the MIT License. Based on [Puyo Text Editor][1] which is also under the
|
20
|
-
MIT License.
|
21
|
-
|
22
|
-
[1]: https://github.com/nickworonekin/puyo-text-editor
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Samuel Wu
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: MIT-0
|
4
|
-
|
5
|
-
version = 1
|
6
|
-
|
7
|
-
[[annotations]]
|
8
|
-
SPDX-FileCopyrightText = "2025 Samuel Wu"
|
9
|
-
SPDX-License-Identifier = "MIT"
|
10
|
-
path = ["docs/formats.md"]
|
11
|
-
|
12
|
-
[[annotations]]
|
13
|
-
SPDX-FileCopyrightText = "2025 Samuel Wu"
|
14
|
-
SPDX-License-Identifier = "MIT-0"
|
15
|
-
path = ["README.md", "uv.lock"]
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# Formats
|
2
|
-
|
3
|
-
Older Puyo Puyo games used multiple formats to store manzai text and character
|
4
|
-
data. This document contains how each format is structured and encoded.
|
5
|
-
|
6
|
-
## The `fpd` format
|
7
|
-
|
8
|
-
The `fpd` format is a binary character table format used by the developers for
|
9
|
-
Puyo Puyo! 15th Anniversary and Puyo Puyo 7 to convert characters from UTF-16
|
10
|
-
little-endian into an index that is used by the `mtx` for text. Each character
|
11
|
-
entry in the `fpd` is 3 bytes long and formatted as follows: `XX XX YY`. Where
|
12
|
-
`XX XX` is the character encoded in UTF-16 little-endian and `YY` is the width
|
13
|
-
of the character. The entries are placed next to each other, creating a
|
14
|
-
zero-based index that is offset by multiples of `0x03`. I.e. the 1st character
|
15
|
-
is at index `0x00`, the 2nd character is at index `0x03`, the 3rd character is
|
16
|
-
at index `0x06`, etc.
|
17
|
-
|
18
|
-
The `fpd` is not used by the games internally except for the Nintendo DS version
|
19
|
-
of Puyo Puyo 7.
|
20
|
-
|
21
|
-
## The `mtx` format
|
22
|
-
|
23
|
-
<!-- TODO: Finish the mtx format for PP15 and PP7 -->
|
24
|
-
<!-- TODO: Look at the mtx format for PP20 -->
|
25
|
-
The `mtx` format is a binary-encoded format used by older Puyo games for
|
26
|
-
storing character dialog and text.
|
@@ -1,177 +0,0 @@
|
|
1
|
-
"""A commandline application that interfaces with conversion tools.
|
2
|
-
|
3
|
-
SPDX-FileCopyrightText: 2025 Samuel Wu
|
4
|
-
SPDX-License-Identifier: MIT
|
5
|
-
"""
|
6
|
-
|
7
|
-
from __future__ import annotations
|
8
|
-
|
9
|
-
import argparse
|
10
|
-
import sys
|
11
|
-
from codecs import BOM_UTF16_LE
|
12
|
-
from collections.abc import Callable
|
13
|
-
from importlib import metadata
|
14
|
-
from pathlib import Path
|
15
|
-
from typing import BinaryIO
|
16
|
-
|
17
|
-
from attrs import define
|
18
|
-
|
19
|
-
from legacy_puyo_tools.exceptions import FileFormatError
|
20
|
-
from legacy_puyo_tools.fpd import Fpd
|
21
|
-
from legacy_puyo_tools.mtx import Mtx
|
22
|
-
|
23
|
-
|
24
|
-
@define
|
25
|
-
class _CliNamespace(argparse.Namespace):
|
26
|
-
func: Callable[[type[_CliNamespace]], None]
|
27
|
-
input: BinaryIO
|
28
|
-
output: BinaryIO
|
29
|
-
unicode: BinaryIO
|
30
|
-
fpd: BinaryIO
|
31
|
-
version: bool
|
32
|
-
|
33
|
-
|
34
|
-
def _create_fpd(args: _CliNamespace) -> None:
|
35
|
-
if args.input.read(2) != BOM_UTF16_LE:
|
36
|
-
raise FileFormatError(
|
37
|
-
f"{args.input.name} is not a UTF-16 little-endian encoded text"
|
38
|
-
"file."
|
39
|
-
)
|
40
|
-
|
41
|
-
if args.output:
|
42
|
-
Fpd.read_unicode(args.input).write_fpd(args.output)
|
43
|
-
return
|
44
|
-
|
45
|
-
path = Path(args.input.name).with_suffix("")
|
46
|
-
|
47
|
-
if path.suffix != ".fpd":
|
48
|
-
path = path.with_suffix(".fpd")
|
49
|
-
|
50
|
-
Fpd.read_unicode(args.input).write_fpd_to_path(path)
|
51
|
-
|
52
|
-
|
53
|
-
def _create_mtx(args: _CliNamespace) -> None:
|
54
|
-
raise NotImplementedError()
|
55
|
-
|
56
|
-
|
57
|
-
def _convert_fpd(args: _CliNamespace) -> None:
|
58
|
-
if args.output:
|
59
|
-
args.output.write(BOM_UTF16_LE)
|
60
|
-
Fpd.read_fpd(args.input).write_fpd(args.output)
|
61
|
-
return
|
62
|
-
|
63
|
-
path = Path(args.input.name).with_suffix("")
|
64
|
-
|
65
|
-
if path.suffix != ".fpd":
|
66
|
-
path = path.with_suffix(".fpd")
|
67
|
-
|
68
|
-
Fpd.read_fpd(args.input).write_unicode_to_path(path)
|
69
|
-
|
70
|
-
|
71
|
-
def _convert_mtx(args: _CliNamespace) -> None:
|
72
|
-
if args.fpd:
|
73
|
-
fpd_data = Fpd.read_fpd(args.fpd)
|
74
|
-
else:
|
75
|
-
if args.unicode.read(2) != BOM_UTF16_LE:
|
76
|
-
raise FileFormatError(
|
77
|
-
f"{args.input.name} is not a UTF-16 little-endian encoded text"
|
78
|
-
"file."
|
79
|
-
)
|
80
|
-
|
81
|
-
fpd_data = Fpd.read_unicode(args.unicode)
|
82
|
-
|
83
|
-
if args.output:
|
84
|
-
Mtx.read_mtx(args.input).write_xml(args.output, fpd_data)
|
85
|
-
return
|
86
|
-
|
87
|
-
path = Path(args.input.name).with_suffix("")
|
88
|
-
|
89
|
-
if path.suffix != ".xml":
|
90
|
-
path = path.with_suffix(".xml")
|
91
|
-
|
92
|
-
Mtx.read_mtx(args.input).write_xml_to_file(path, fpd_data)
|
93
|
-
|
94
|
-
|
95
|
-
def _create_parsers(main_parser: argparse.ArgumentParser) -> None:
|
96
|
-
shared_options = argparse.ArgumentParser(add_help=False)
|
97
|
-
shared_options.add_argument(
|
98
|
-
"input", type=argparse.FileType("rb"), help="input file"
|
99
|
-
)
|
100
|
-
shared_options.add_argument(
|
101
|
-
"-o", "--output", type=argparse.FileType("wb"), help="output file"
|
102
|
-
)
|
103
|
-
|
104
|
-
mtx_options = argparse.ArgumentParser(
|
105
|
-
add_help=False, parents=[shared_options]
|
106
|
-
)
|
107
|
-
mtx_options_group = mtx_options.add_mutually_exclusive_group(required=True)
|
108
|
-
mtx_options_group.add_argument(
|
109
|
-
"--fpd", type=argparse.FileType("rb"), help="fpd file"
|
110
|
-
)
|
111
|
-
mtx_options_group.add_argument(
|
112
|
-
"--unicode", type=argparse.FileType("rb"), help="unicode text file"
|
113
|
-
)
|
114
|
-
|
115
|
-
sub_parser = main_parser.add_subparsers()
|
116
|
-
|
117
|
-
create_parser = sub_parser.add_parser("create")
|
118
|
-
create_sub_parser = create_parser.add_subparsers(required=True)
|
119
|
-
|
120
|
-
create_fpd_parser = create_sub_parser.add_parser(
|
121
|
-
"fpd",
|
122
|
-
help="create a fpd file from a unicode text file",
|
123
|
-
parents=[shared_options],
|
124
|
-
)
|
125
|
-
create_fpd_parser.set_defaults(func=_create_fpd)
|
126
|
-
|
127
|
-
create_mtx_parser = create_sub_parser.add_parser(
|
128
|
-
"mtx", help="create a mtx file from a XML file", parents=[mtx_options]
|
129
|
-
)
|
130
|
-
create_mtx_parser.set_defaults(func=_create_mtx)
|
131
|
-
|
132
|
-
convert_parser = sub_parser.add_parser("convert")
|
133
|
-
convert_sub_parser = convert_parser.add_subparsers(required=True)
|
134
|
-
|
135
|
-
convert_fpd_parser = convert_sub_parser.add_parser(
|
136
|
-
"fpd",
|
137
|
-
help="convert a fpd file to a unicode file",
|
138
|
-
parents=[shared_options],
|
139
|
-
)
|
140
|
-
convert_fpd_parser.set_defaults(func=_convert_fpd)
|
141
|
-
|
142
|
-
convert_mtx_parser = convert_sub_parser.add_parser(
|
143
|
-
"mtx", help="convert a mtx file to XML file", parents=[mtx_options]
|
144
|
-
)
|
145
|
-
convert_mtx_parser.set_defaults(func=_convert_mtx)
|
146
|
-
|
147
|
-
|
148
|
-
def main() -> None:
|
149
|
-
"""Entry point for the commandline application."""
|
150
|
-
main_parser = argparse.ArgumentParser(
|
151
|
-
description="A conversion tool for files used by older Puyo games."
|
152
|
-
)
|
153
|
-
main_parser.add_argument(
|
154
|
-
"-v", "--version", help="show version", action="store_true"
|
155
|
-
)
|
156
|
-
|
157
|
-
_create_parsers(main_parser)
|
158
|
-
|
159
|
-
args = main_parser.parse_args(namespace=_CliNamespace)
|
160
|
-
|
161
|
-
if args.version is True:
|
162
|
-
package_name = "legacy-puyo-tools"
|
163
|
-
version = metadata.version(package_name)
|
164
|
-
|
165
|
-
print(f"{package_name} {version}")
|
166
|
-
|
167
|
-
sys.exit(0)
|
168
|
-
|
169
|
-
if not hasattr(args, "func"):
|
170
|
-
main_parser.print_help(sys.stderr)
|
171
|
-
sys.exit(1)
|
172
|
-
|
173
|
-
args.func(args)
|
174
|
-
|
175
|
-
|
176
|
-
if __name__ == "__main__":
|
177
|
-
main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|