Skip to content

Commit

Permalink
Add support for PC version
Browse files Browse the repository at this point in the history
- Added support for GXT files from PC version
- Added new option `--platform`
- Changed version numbering to date format
- Removed old code in a docstring
- Rewrote part of the CLI interface
  • Loading branch information
santiago046 committed May 24, 2023
1 parent 8eee21c commit 8813d51
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 71 deletions.
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Manhunt 2 GXT Tool

This is a Python CLI program that can pack and unpack GXT files from Manhunt 2. Currently, it supports the PSP and PS2 versions.
This is a Python CLI program that can pack and unpack GXT files from Manhunt 2. Currently, it supports the PSP, PS2 and PC versions.

The tool is based on the [GXT](https://github.com/Sor3nt/manhunt-toolkit/blob/5c3b56d237b0ead7f1ce633a5c22cf6996f77c57/Application/App/Service/Archive/Gxt.php) implementation from [Manhunt Toolkit](https://github.com/Sor3nt/manhunt-toolkit) by Sor3nt.

Expand Down Expand Up @@ -39,12 +39,14 @@ Commands:
```bash
Usage: mh2-gxttool pack [OPTIONS] SRC_FILE

Pack a TOML document to a GXT file
Pack a TOML document to a GXT file

Options:
-f, --force Force destination file overwriting (default: False)
-o, --output FILE Output file name (default: change src_file extension to .toml)
-h, --help Show this message and exit.
-f, --force force overwrite existing file
-o, --output FILE output file name (default: change src_file
extension to .gxt)
-p, --platform [PC|PSP|PS2] GXT game platform (default: PSP)
-h, --help Show this message and exit.
```

The `pack` command converts a TOML document (`SRC_FILE`) into a GXT file. The TOML document must follow the syntax described below.
Expand All @@ -54,12 +56,14 @@ The `pack` command converts a TOML document (`SRC_FILE`) into a GXT file. The TO
```bash
Usage: mh2-gxttool unpack [OPTIONS] SRC_FILE

Unpack a GXT file to a TOML document
Unpack a GXT file to a TOML document

Options:
-f, --force Force destination file overwriting (default: False)
-o, --output FILE Output file name (default: change src_file extension to .gxt)
-h, --help Show this message and exit.
-f, --force force overwrite existing file
-o, --output FILE output file name (default: change src_file
extension to .toml)
-p, --platform [PC|PSP|PS2] GXT game platform (default: PSP)
-h, --help Show this message and exit.
```

The `unpack` command extracts the text data from a GXT file (`SRC_FILE`) and saves it as a TOML document. The TOML document can be edited and packed back into a GXT file.
Expand All @@ -78,7 +82,7 @@ duration = 192 # Integer, duration
string = "Hello, world!" # Key string
```

- `KEYNAME`: Maximum of 8 characters.
- `KEYNAME`: Maximum of 8 characters for PSP/PS2, 12 for PC.
- `console`: Tells the packer to encode the string in UTF-16 charset, useful for strings handled by the console and not the game.
- `duration`: Duration of the key in milliseconds.
- `string`: The key string, always inside double quotes. If the string contains a double quote, escape it using `\"`.
Expand Down Expand Up @@ -106,7 +110,7 @@ mh2-gxttool pack ~/resources/GAME.toml
```

## To do
- Add support for the PC and Wii versions of Manhunt 2
- Add support for the Wii version of Manhunt 2
- Maybe add support for Manhunt (2003)

Happy modding/translating!
3 changes: 1 addition & 2 deletions mh2_gxttool/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from .gxtfile import GXTFile
from .mh2utils import MH2Utils
from .main import cli
27 changes: 12 additions & 15 deletions mh2_gxttool/gxtfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@ class GXTFileFormatError(Exception):
class GXTFile:

@staticmethod
def pack(src_file: Path, dst_file: Path):
def pack(src_file: Path, dst_file: Path, platform="PSP"):
"""
Pack a TOML document to a GXT file.
Args:
src_file: A pathlib.Path object to the TOML document to pack
dst_file: A pathlib.Path object to the GXT file
platform: A string to tell the game platform, default: PSP
"""

keys_info = MH2Utils._from_toml(src_file)
key_str_fmt = "<I12sI" if platform == "PC" else "<I8sI"

with io.BytesIO() as tkey_buffer, io.BytesIO() as tdat_buffer:
tdat_size = 0 # Used to key_data_offset too
Expand All @@ -51,7 +53,7 @@ def pack(src_file: Path, dst_file: Path):

tkey_buffer.write(
struct.pack(
"<I8sI", key_data_offset, key_name, key_duration
key_str_fmt, key_data_offset, key_name, key_duration
)
)

Expand All @@ -60,16 +62,6 @@ def pack(src_file: Path, dst_file: Path):
if value.get("console", False)
else MH2Utils._encode_string(value["string"])
)
"""
if value.get("console", False):
# If it is true, then encode using UTF-16LE charset. Else, MH2 charset
if value["console"]:
key_string_bytes = value["string"].encode("UTF-16LE") + b"\x00\x00"
else:
key_string_bytes = MH2Utils._encode_string(value["string"])
else:
key_string_bytes = MH2Utils._encode_string(value["string"])
"""

tdat_buffer.write(key_string_bytes)
# Update tdat size
Expand All @@ -79,7 +71,9 @@ def pack(src_file: Path, dst_file: Path):
with dst_file.open("wb") as gxt_file:
# Write TKEY signature and its size
gxt_file.write(
struct.pack("<4sI", b"TKEY", len(keys_info) * 16)
struct.pack(
"<4sI", b"TKEY", tkey_buffer.getbuffer().nbytes
)
)
# Write TKEY data
gxt_file.write(tkey_buffer.getvalue())
Expand All @@ -90,18 +84,21 @@ def pack(src_file: Path, dst_file: Path):
gxt_file.write(tdat_buffer.getvalue())

@staticmethod
def unpack(src_file: Path, dst_file: Path):
def unpack(src_file: Path, dst_file: Path, platform="PSP"):
"""
Unpack a GXT file to a TOML document.
Args:
src_file: A pathlib.Path object to the GXT file to unpack
dst_file: A pathlib.Path object to the TOML document file
platform: A string to tell the game platform, default: PSP
Raises:
InvalidGXTFileError: when src_file is not a valid GXT file
"""

key_str_fmt = struct_fmt = "<I12sI" if platform == "PC" else "<I8sI"

with src_file.open("rb") as gxt_file:
# Check TKEY
if gxt_file.read(4) != b"TKEY":
Expand All @@ -119,7 +116,7 @@ def unpack(src_file: Path, dst_file: Path):
"duration": key_duration,
}
for key_offset, key_name, key_duration in struct.iter_unpack(
"<I8sI", gxt_file.read(tkey_size)
key_str_fmt, gxt_file.read(tkey_size)
)
]

Expand Down
64 changes: 37 additions & 27 deletions mh2_gxttool/__main__.py → mh2_gxttool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,51 @@
import click
import pathlib

from mh2_gxttool import GXTFile
from mh2_gxttool.gxtfile import GXTFile


# Common options
force_option = click.option(
"-f",
"--force",
help="force overwrite existing file",
is_flag=True,
)
platform_opt = click.option(
"-p",
"--platform",
default="PSP",
help="GXT game platform (default: PSP)",
type=click.Choice(["PC", "PSP", "PS2"], case_sensitive=False),
)
src_argument = click.argument(
"src_file", type=click.Path(exists=True, dir_okay=False, readable=True)
)


# Start of CLI
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
def cli():
"""A CLI tool to pack/unpack GXT files from Manhunt 2."""
pass


# Pack command
@cli.command(help="Pack a TOML document to a GXT file")
@click.option(
"-f",
"--force",
is_flag=True,
help="force destination file overwriting (default: False)",
)
@force_option
@click.option(
"-o",
"--output",
"dst_file",
help="output file name (default: change src_file extension to .gxt)",
type=click.Path(dir_okay=False),
help="output file name (default: change src_file extension to .toml)",
)
@click.argument(
"src_file", type=click.Path(exists=True, dir_okay=False, readable=True)
)
def pack(force, dst_file, src_file):
@platform_opt
@src_argument
def pack(platform, force, dst_file, src_file):
src_file = pathlib.Path(src_file)

if dst_file == None:
if dst_file is None:
dst_file = src_file.with_suffix(".gxt")
else:
dst_file = pathlib.Path(dst_file)
Expand All @@ -57,30 +72,25 @@ def pack(force, dst_file, src_file):
f"{dst_file} already exist. Use -f to overwrite"
)

GXTFile.pack(src_file, dst_file)
GXTFile.pack(src_file, dst_file, platform)


# Unpack command
@cli.command(help="Unpack a GXT file to a TOML document")
@click.option(
"-f",
"--force",
is_flag=True,
help="force destination file overwriting (default: False)",
)
@force_option
@click.option(
"-o",
"--output",
"dst_file",
help="output file name (default: change src_file extension to .toml)",
type=click.Path(dir_okay=False),
help="output file name (default: change src_file extension to .gxt)",
)
@click.argument(
"src_file", type=click.Path(exists=True, dir_okay=False, readable=True)
)
def unpack(force, dst_file, src_file):
@platform_opt
@src_argument
def unpack(platform, force, dst_file, src_file):
src_file = pathlib.Path(src_file)

if dst_file == None:
if dst_file is None:
dst_file = src_file.with_suffix(".toml")
else:
dst_file = pathlib.Path(dst_file)
Expand All @@ -90,7 +100,7 @@ def unpack(force, dst_file, src_file):
f"{dst_file} already exists. Use -f to overwrite"
)

GXTFile.unpack(src_file, dst_file)
GXTFile.unpack(src_file, dst_file, platform)


if __name__ == "__main__":
Expand Down
3 changes: 0 additions & 3 deletions requirements.txt

This file was deleted.

23 changes: 10 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
from setuptools import setup, find_packages

with open("requirements.txt") as f, open("README.md") as fh:
required = f.read().splitlines()
long_description = fh.read()
with open("README.md") as rf:
readme_file = rf.read()

setup(
name="mh2-gxttool",
version="1.0.0",
description="CLI tool to unpack/pack GXT files from Manhunt 2 PSP/PS2",
long_description=long_description,
version="2023.05.24",
description="CLI tool to unpack/pack GXT files from Manhunt 2 PSP/PS2/PC",
long_description=readme_file,
long_description_content_type="text/markdown",
author="santiago046",
license="GPLv3",
url="https://github.com/santiago046/mh2-gxtttool",
packages=find_packages(),
python_requires=">=3.8",
install_requires=required,
entry_points={
"console_scripts": ["mh2-gxttool = mh2_gxttool.__main__:cli"]
},
project_urls={"Source code": "https://github.com/santiago046/mh2-gxttool"},
author="santiago046",
python_requires=">=3.8",
packages=find_packages(),
install_requires=["click>=8.1.3", "tomlkit>=0.11.8"],
entry_points={"console_scripts": ["mh2-gxttool = mh2_gxttool.main:cli"]},
)

0 comments on commit 8813d51

Please sign in to comment.