mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-02-02 12:37:19 +01:00
166 lines
4.4 KiB
Python
Executable File
166 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
__version__ = "0.3.4"
|
|
__author__ = "spicyjpeg"
|
|
|
|
import sys
|
|
from argparse import ArgumentParser, FileType, Namespace
|
|
from typing import ByteString, Mapping, Sequence, TextIO
|
|
from zlib import decompress
|
|
|
|
from _common import *
|
|
|
|
## Utilities
|
|
|
|
# This encoding is similar to standard base45, but with some problematic
|
|
# characters (' ', '$', '%', '*') excluded.
|
|
_BASE41_CHARSET: str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./:"
|
|
|
|
def decodeBase41(data: str) -> bytearray:
|
|
mapped: map[int] = map(_BASE41_CHARSET.index, data)
|
|
output: bytearray = bytearray()
|
|
|
|
for a, b, c in zip(mapped, mapped, mapped):
|
|
value: int = a + (b * 41) + (c * 1681)
|
|
|
|
output.append(value >> 8)
|
|
output.append(value & 0xff)
|
|
|
|
return output
|
|
|
|
def serialNumberToString(_id: ByteString) -> str:
|
|
value: int = int.from_bytes(_id[1:7], "little")
|
|
|
|
#if value >= 100000000:
|
|
#return "xxxx-xxxx"
|
|
|
|
return f"{(value // 10000) % 10000:04d}-{value % 10000:04d}"
|
|
|
|
def toPrintableChar(value: int):
|
|
if (value < 0x20) or (value > 0x7e):
|
|
return "."
|
|
|
|
return chr(value)
|
|
|
|
def hexdump(data: ByteString | Sequence[int], output: TextIO, width: int = 16):
|
|
for i in range(0, len(data), width):
|
|
hexBytes: map[str] = map(lambda value: f"{value:02x}", data[i:i + width])
|
|
hexLine: str = " ".join(hexBytes).ljust(width * 3 - 1)
|
|
|
|
asciiBytes: map[str] = map(toPrintableChar, data[i:i + width])
|
|
asciiLine: str = "".join(asciiBytes).ljust(width)
|
|
|
|
output.write(f" {i:04x}: {hexLine} |{asciiLine}|\n")
|
|
|
|
## Dump parser
|
|
|
|
_DUMP_START: str = "573::"
|
|
_DUMP_END: str = "::"
|
|
|
|
_CHIP_NAMES: Mapping[ChipType, str] = {
|
|
ChipType.NONE: "None",
|
|
ChipType.X76F041: "Xicor X76F041",
|
|
ChipType.X76F100: "Xicor X76F100",
|
|
ChipType.ZS01: "Konami ZS01 (PIC16CE625)"
|
|
}
|
|
|
|
def parseDumpString(data: str) -> Dump:
|
|
_data: str = data.strip().upper()
|
|
|
|
if not _data.startswith(_DUMP_START) or not _data.endswith(_DUMP_END):
|
|
raise ValueError(f"dump string does not begin with '{_DUMP_START}' and end with '{_DUMP_END}'")
|
|
|
|
_data = _data[len(_DUMP_START):-len(_DUMP_END)]
|
|
|
|
return parseDump(decompress(decodeBase41(_data)))
|
|
|
|
def printDumpInfo(dump: Dump, output: TextIO):
|
|
if dump.flags & DumpFlag.DUMP_SYSTEM_ID_OK:
|
|
output.write(f"Digital I/O ID: {dump.systemID.hex('-')}\n")
|
|
output.write(f"Digital I/O SN: {serialNumberToString(dump.systemID)}\n\n")
|
|
|
|
output.write(f"Cartridge type: {_CHIP_NAMES[dump.chipType]}\n")
|
|
if dump.flags & DumpFlag.DUMP_CART_ID_OK:
|
|
output.write(f"DS2401 identifier: {dump.cartID.hex('-')}\n")
|
|
if dump.flags & DumpFlag.DUMP_ZS_ID_OK:
|
|
output.write(f"ZS01 identifier: {dump.zsID.hex('-')}\n")
|
|
if dump.flags & DumpFlag.DUMP_CONFIG_OK:
|
|
output.write(f"Configuration: {dump.config.hex('-')}\n")
|
|
|
|
output.write("\nEEPROM dump:\n")
|
|
hexdump(dump.data, output)
|
|
output.write("\n")
|
|
|
|
## Main
|
|
|
|
def createParser() -> ArgumentParser:
|
|
parser = ArgumentParser(
|
|
description = "Decodes the contents of a QR code generated by the tool.",
|
|
add_help = False
|
|
)
|
|
|
|
group = parser.add_argument_group("Tool options")
|
|
group.add_argument(
|
|
"-h", "--help",
|
|
action = "help",
|
|
help = "Show this help message and exit"
|
|
)
|
|
|
|
group = parser.add_argument_group("File paths")
|
|
group.add_argument(
|
|
"-i", "--input",
|
|
type = FileType("rb"),
|
|
help = "Read dump (.573 file) or QR string from specified path",
|
|
metavar = "file"
|
|
)
|
|
group.add_argument(
|
|
"-l", "--log",
|
|
type = FileType("at"),
|
|
default = sys.stdout,
|
|
help = "Log cartridge info to specified file (stdout by default)",
|
|
metavar = "file"
|
|
)
|
|
group.add_argument(
|
|
"-e", "--export",
|
|
type = FileType("wb"),
|
|
help = "Export binary dump (.573 file) to specified path",
|
|
metavar = "file"
|
|
)
|
|
|
|
group = parser.add_argument_group("Input data")
|
|
group.add_argument(
|
|
"data",
|
|
type = str,
|
|
nargs = "?",
|
|
help = "QR string to decode (if -i was not passed)"
|
|
)
|
|
|
|
return parser
|
|
|
|
def main():
|
|
parser: ArgumentParser = createParser()
|
|
args: Namespace = parser.parse_args()
|
|
|
|
if args.input:
|
|
with args.input as _file:
|
|
data: bytes = _file.read()
|
|
|
|
try:
|
|
dump: Dump = parseDump(data)
|
|
except:
|
|
dump: Dump = parseDumpString(data.decode("ascii"))
|
|
elif args.data:
|
|
dump: Dump = parseDumpString(args.data)
|
|
else:
|
|
parser.error("a dump must be passed on the command line or using -i")
|
|
|
|
if args.log:
|
|
printDumpInfo(dump, args.log)
|
|
if args.export:
|
|
with args.export as _file:
|
|
_file.write(dump.serialize())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|