Initial work on ROE. Still in progress.
This commit is contained in:
parent
5e3de6e8ff
commit
308df75423
6 changed files with 89 additions and 0 deletions
2
poetry.toml
Normal file
2
poetry.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[virtualenvs]
|
||||
in-project = true
|
19
pyproject.toml
Normal file
19
pyproject.toml
Normal 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
0
src/elfish/__init__.py
Normal file
15
src/elfish/exceptions.py
Normal file
15
src/elfish/exceptions.py
Normal 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
48
src/elfish/roe.py
Normal 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
5
src/elfish/utils.py
Normal 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
|
Loading…
Reference in a new issue