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.
Files changed (30) hide show
  1. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/workflows/ci.yaml +5 -2
  2. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/workflows/release.yaml +3 -11
  3. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.markdownlint.yaml +1 -1
  4. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/CHANGELOG.md +14 -0
  5. legacy_puyo_tools-0.1.0/CODE_OF_CONDUCT.md +128 -0
  6. legacy_puyo_tools-0.1.0/CONTRIBUTING.md +60 -0
  7. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSE +1 -1
  8. legacy_puyo_tools-0.1.0/PKG-INFO +119 -0
  9. legacy_puyo_tools-0.1.0/README.md +91 -0
  10. legacy_puyo_tools-0.1.0/REUSE.toml +20 -0
  11. legacy_puyo_tools-0.1.0/formats.md +49 -0
  12. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/pyproject.toml +27 -7
  13. legacy_puyo_tools-0.1.0/src/legacy_puyo_tools/cli.py +157 -0
  14. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/exceptions.py +4 -0
  15. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/fpd.py +29 -35
  16. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/mtx.py +5 -12
  17. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/uv.lock +16 -0
  18. legacy_puyo_tools-0.0.1/PKG-INFO +0 -34
  19. legacy_puyo_tools-0.0.1/README.md +0 -22
  20. legacy_puyo_tools-0.0.1/REUSE.toml +0 -15
  21. legacy_puyo_tools-0.0.1/docs/formats.md +0 -26
  22. legacy_puyo_tools-0.0.1/src/legacy_puyo_tools/cli.py +0 -177
  23. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.editorconfig +0 -0
  24. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.github/dependabot.yml +0 -0
  25. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.gitignore +0 -0
  26. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/.pre-commit-config.yaml +0 -0
  27. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSES/MIT-0.txt +0 -0
  28. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/LICENSES/MIT.txt +0 -0
  29. {legacy_puyo_tools-0.0.1 → legacy_puyo_tools-0.1.0}/src/legacy_puyo_tools/__init__.py +0 -0
  30. {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
- # Let the developer decide when to fix them
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
- - name: Install uv
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
 
@@ -59,4 +59,4 @@ link-image-style:
59
59
  url_inline: false
60
60
 
61
61
  table-pipe-style:
62
- style: no_leading_or_trailing
62
+ style: leading_and_trailing
@@ -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
+ ```
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Samuel Wu
3
+ Copyright (c) 2025 Samuel Wu and contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
@@ -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
- dependencies = ["attrs>=25.3.0", "lxml>=6.0.0"]
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.1"
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.pycodestyle]
60
- max-doc-length = 72
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 = 79
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()
@@ -11,3 +11,7 @@ class FormatError(Exception):
11
11
 
12
12
  class FileFormatError(FormatError):
13
13
  """The file does not conform to a file format or is malformed."""
14
+
15
+
16
+ class ArgumentError(Exception):
17
+ """One of the argument is invalid or missing."""
@@ -1,7 +1,7 @@
1
- """fpd conversion tool for older puyo games.
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
- Puyo Puyo! 15th Anniversary and Puyo Puyo 7.
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
- as follows: `XX XX YY`. Where `XX XX` is the character encoded in
31
- UTF-16 LE and `YY` is the width of the character.
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
- DS versions of the games.
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
- next to each other and the indices is offset by multiples of `0x03`.
94
- I.e. The 1st character is at index `0x00`, the 2nd character is at
95
- index `0x03`, the 3rd character is at index `0x06`, etc.
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
- to the fpd character format.
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 LE text file.
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 LE encoded text file or is
211
- missing the Byte Order Mark for UTF-16 LE.
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
- # UTF-16 LE text file
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(f"{path} is not a UTF-16 LE text file.")
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 `mtx` files to and from XML for modding Puyo games.
4
- Currently supports Puyo Puyo 7 and might support Puyo Puyo! 15th
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
 
@@ -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()