Initial work on ROE. Still in progress.

This commit is contained in:
Josh W 2024-02-12 18:27:21 -05:00
parent 5e3de6e8ff
commit 308df75423
6 changed files with 89 additions and 0 deletions

2
poetry.toml Normal file
View file

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

19
pyproject.toml Normal file
View file

@ -0,0 +1,19 @@
[tool.poetry]
name = "elfish-tools"
version = "0.0.1"
description = "Tools for handling fish data from the 1993 game EL-Fish."
authors = ["Josh Washburne <josh@jodh.us>"]
readme = "README.md"
packages = [{include = "elfish", from = "src"}]
[tool.poetry.dependencies]
python = ">=3.10"
[tool.poetry.group.dev.dependencies]
pylint = ">=3.0.3"
black = ">=24.1.1"
mypy = ">=1.8.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

0
src/elfish/__init__.py Normal file
View file

15
src/elfish/exceptions.py Normal file
View file

@ -0,0 +1,15 @@
"""Custom exceptions for fish parsing."""
class AuthorError(ValueError):
"""
The author must be no more than 15 characters in length and may only
contain a subset of special characters.
"""
class InvalidFSHFileError(ValueError):
"""The FSH file provided is not valid and cannot be parsed safely."""
class InvalidROEFileError(ValueError):
"""The ROE file provided is not valid and cannot be parsed safely."""

48
src/elfish/roe.py Normal file
View file

@ -0,0 +1,48 @@
"""Tools for parsing a ROE file."""
from os import SEEK_CUR, SEEK_SET
from os.path import basename, splitext
from struct import unpack
from .exceptions import InvalidROEFileError
from .utils import nibble_flip
class Roe:
"""The DNA of an electronic fish with an embedded icon."""
def __init__(self, name: str = "", author: str | None = None) -> None:
self.name: str = name
self.author: str | None = author
self.is_mutant: bool = False
self.unknowns: dict[str, bytes] = {}
@classmethod
def from_file(cls, filename: str) -> "Roe":
"""Factory for importing roe from a .ROE file."""
# Unlike .FSH files, the name comes from the filename itself. Since
# DOS was limited to the 8.3 naming convention, a fish's name has a
# forced limit of only eight characters.
name = splitext(basename(filename))[0][0:8]
roe = cls(name)
with open(filename, "rb") as rfile:
# First byte of file determines mutant status. Non-zero = mutant.
# Second byte is padding.
roe.is_mutant = unpack("?x", rfile.read(2))[0]
# Next two bytes are unknown.
roe.unknowns["00000002"] = unpack("2s", rfile.read(2))[0]
# Next sixteen bytes is a nibble-flipped encoding of the original
# author with a zero null termination byte.
author = ""
flipped_author = unpack("16s", rfile.read(16))[0]
for letter in flipped_author:
code = nibble_flip(letter)
if code == 0:
break
author += chr(code)
roe.author = author.upper()
return roe

5
src/elfish/utils.py Normal file
View file

@ -0,0 +1,5 @@
"""Miscellaneous functions commonly used by all classes."""
def nibble_flip(num: int) -> int:
"""Swaps the high and low nibbles in a byte. (Ex: 0x1a -> 0xa1)"""
return (num >> 4) | (num << 4) & 0xFF