-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
474 additions
and
1 deletion.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .asffile import ASFFile | ||
from .asfparser import ASFParser, MemChunk, MemType |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import os | ||
import io | ||
import struct | ||
import zlib | ||
from dataclasses import dataclass | ||
|
||
|
||
class BadASFFile(Exception): | ||
pass | ||
|
||
|
||
@dataclass | ||
class Chunk: | ||
tag: str = None | ||
size: int = 0 | ||
flags: int = 0 | ||
offset: int = 0 | ||
total_size: int = 0 | ||
uncompressed_size: int = 0 | ||
|
||
def read(self, fp): | ||
self.fp = fp | ||
buf = fp.read(8) | ||
raw_tag, size = struct.unpack(">4sI", buf) | ||
self.offset = fp.tell() | ||
self.tag = raw_tag.decode("ascii") | ||
self.size = size - 8 | ||
self.uncompressed_size = size | ||
self.flags = 0 | ||
if size > 8: | ||
buf = fp.read(4) | ||
self.flags = struct.unpack(">I", buf)[0] | ||
self.offset += 4 | ||
self.size -= 4 | ||
if self.flags & 1 == 1: | ||
buf = fp.read(4) | ||
self.uncompressed_size = struct.unpack(">I", buf)[0] | ||
self.offset += 4 | ||
self.size -= 4 | ||
|
||
# bogus END: no align? | ||
if self.tag == "END ": | ||
self.total_size = 8 | ||
else: | ||
self.total_size = self._align(size) + size | ||
|
||
def read_data(self): | ||
if self.size == 0: | ||
return None | ||
if self.fp.tell() != self.offset: | ||
self.fp.seek(self.offset, 0) | ||
data = self.fp.read(self.size) | ||
# align? | ||
align = self._align(self.size) | ||
self.fp.read(align) | ||
# uncompress data? | ||
if self.flags & 1 == 1: | ||
data = zlib.decompress(data) | ||
assert len(data) == self.uncompressed_size | ||
return data | ||
|
||
def skip_data(self): | ||
if self.size == 0: | ||
return | ||
# move forward without header | ||
size = self.size + self._align(self.size) | ||
self.fp.seek(size, os.SEEK_CUR) | ||
|
||
def _align(self, size): | ||
# bogus alignment: if already aligned on long it adds 4 dummy bytes??? | ||
align = 4 - (size & 3) | ||
return align | ||
|
||
|
||
class ASFFile: | ||
def __init__(self, file, mode="r"): | ||
if mode not in ("r", "w"): | ||
raise ValueError("ASSFile mode must be 'r' or 'w'") | ||
self.file = file | ||
self.fp = None | ||
if isinstance(file, os.PathLike): | ||
file = os.fspath(file) | ||
if isinstance(file, str): | ||
self.filename = file | ||
self.fp = io.open(file, mode + "+b") | ||
else: | ||
self.fp = file | ||
self.filename = getattr(file, "name", None) | ||
|
||
if mode == "r": | ||
self.fp.seek(0, os.SEEK_END) | ||
self.total_size = self.fp.tell() | ||
self.fp.seek(0, os.SEEK_SET) | ||
self._read_header() | ||
self.chunks = None | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, type, value, traceback): | ||
self.close() | ||
|
||
def __repr__(self): | ||
result = ["<%s.%s" % (self.__class__.__module__, self.__class__.__qualname__)] | ||
if self.fp is not None: | ||
if self._filePassed: | ||
result.append(" file=%r" % self.fp) | ||
elif self.filename is not None: | ||
result.append(" filename=%r" % self.filename) | ||
result.append(" mode=%r" % self.mode) | ||
else: | ||
result.append(" [closed]") | ||
result.append(">") | ||
return "".join(result) | ||
|
||
def __del__(self): | ||
self.close() | ||
|
||
def close(self): | ||
if self.fp: | ||
self.fp.close() | ||
self.fp = None | ||
|
||
def chunklist(self): | ||
"""return list of chunks found in file""" | ||
if not self.chunks: | ||
self._read_chunklist() | ||
return self.chunks | ||
|
||
def get_chunk(self, tag): | ||
chunks = self.chunklist() | ||
for chunk in chunks: | ||
if chunk.tag == tag: | ||
return chunk | ||
|
||
def get_all_chunks(self, tag): | ||
result = [] | ||
chunks = self.chunklist() | ||
for chunk in chunks: | ||
if chunk.tag == tag: | ||
result.append(chunk) | ||
return result | ||
|
||
def load_chunk(self, tag): | ||
chunk = self.get_chunk(tag) | ||
if chunk: | ||
return chunk.read_data() | ||
|
||
def load_all_chunks(self, tag): | ||
chunks = self.get_all_chunks(tag) | ||
if chunks: | ||
return list(map(lambda x: x.read_data(), chunks)) | ||
|
||
def _read_chunklist(self): | ||
chunks = [] | ||
left = self.total_size | ||
self.fp.seek(self.main_offset, 0) | ||
while left > 0: | ||
chunk = Chunk() | ||
chunk.read(self.fp) | ||
chunk.skip_data() | ||
left -= chunk.total_size | ||
chunks.append(chunk) | ||
self.chunks = chunks | ||
|
||
def _read_header(self): | ||
hdr = Chunk() | ||
hdr.read(self.fp) | ||
if hdr.tag != "ASF ": | ||
raise BadASFFile() | ||
data = hdr.read_data() | ||
# skip long | ||
offset = 4 | ||
# header | ||
self.emu_name, offset = self._read_string(data, offset) | ||
self.emu_version, offset = self._read_string(data, offset) | ||
self.desc, offset = self._read_string(data, offset) | ||
assert offset == hdr.size | ||
self.total_size -= hdr.total_size | ||
self.main_offset = hdr.total_size | ||
|
||
def _read_string(self, buf, off): | ||
data = bytearray() | ||
while buf[off] != 0: | ||
data.append(buf[off]) | ||
off += 1 | ||
txt = data.decode("utf-8") | ||
return txt, off + 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from dataclasses import dataclass | ||
from enum import IntEnum | ||
import struct | ||
|
||
|
||
class MemType(IntEnum): | ||
CHIP = 0 | ||
BOGO = 1 | ||
Z2RAM = 2 | ||
Z3RAM = 3 | ||
Z3CHIP = 4 | ||
A3KLO = 5 | ||
A3KHI = 6 | ||
|
||
|
||
@dataclass | ||
class MemChunk: | ||
address: int | ||
size: int | ||
type: MemType | ||
data: bytes = None | ||
|
||
|
||
@dataclass | ||
class Expansion: | ||
z2ram_base: int | ||
z3ram_base: int | ||
gfx_base: int | ||
boro_base: int | ||
z2ram_base2: int | ||
|
||
|
||
class ASFParser: | ||
def __init__(self, asf_file): | ||
self.asf_file = asf_file | ||
|
||
def get_expansion(self): | ||
data = self.asf_file.load_chunk("EXPA") | ||
if not data: | ||
return None | ||
assert len(data) >= 5 * 4 | ||
z2b, z3b, gfx, boro, z2b2 = struct.unpack(">IIIII", data) | ||
return Expansion(z2b, z3b, gfx, boro, z2b2) | ||
|
||
def get_ram_layout(self, load_ram=False): | ||
layout = [] | ||
# chip | ||
chip = self._get_mem_chunk("CRAM", MemType.CHIP, load_ram, 0) | ||
if chip: | ||
layout.append(chip) | ||
bogo = self._get_mem_chunk("BRAM", MemType.BOGO, load_ram, 0xC00000) | ||
# slow/bogo ram | ||
if bogo: | ||
layout.append(bogo) | ||
# a3k | ||
a3klo = self._get_mem_chunk("A3K1", MemType.A3KLO, load_ram, 0x08000000) | ||
if a3klo: | ||
# fixup lo address | ||
a3klo.address -= a3klo.size | ||
layout.append(a3klo) | ||
a3khi = self._get_mem_chunk("A3K2", MemType.A3KHI, load_ram, 0x08000000) | ||
if a3khi: | ||
layout.append(a3khi) | ||
# z2ram/z3ram | ||
expansion = self.get_expansion() | ||
if expansion: | ||
# z2ram | ||
z2rams = self._get_all_mem_chunks("FRAM", MemType.Z2RAM, load_ram) | ||
n = len(z2rams) | ||
if n > 0: | ||
z2ram = z2rams[0] | ||
z2ram.address = expansion.z2ram_base | ||
layout.append(z2ram) | ||
if n > 1: | ||
z2ram = z2rams[1] | ||
z2ram.address = expansion.z2ram_base2 | ||
layout.append(z2ram) | ||
# z2ram | ||
z3rams = self._get_all_mem_chunks("ZRAM", MemType.Z3RAM, load_ram) | ||
n = len(z3rams) | ||
if n > 0: | ||
z3ram = z3rams[0] | ||
z3ram.address = expansion.z3ram_base | ||
layout.append(z3ram) | ||
return layout | ||
|
||
def _get_all_mem_chunks(self, tag, type, load_ram): | ||
result = [] | ||
if load_ram: | ||
datas = self.asf_file.load_all_chunks(tag) | ||
if datas: | ||
for data in datas: | ||
size = len(data) | ||
result.append(MemChunk(0, size, type, data)) | ||
else: | ||
chunks = self.asf_file.get_all_chunks(tag) | ||
if chunks: | ||
for chunk in chunks: | ||
size = chunk.uncompressed_size | ||
result.append(MemChunk(0, size, type)) | ||
return result | ||
|
||
def _get_mem_chunk(self, tag, type, load_ram, addr=0): | ||
if load_ram: | ||
data = self.asf_file.load_chunk(tag) | ||
if not data: | ||
return None | ||
size = len(data) | ||
else: | ||
data = None | ||
chunk = self.asf_file.get_chunk(tag) | ||
if not chunk: | ||
return None | ||
size = chunk.uncompressed_size | ||
return MemChunk(addr, size, type, data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import sys | ||
|
||
from .tool import Tool | ||
from amitools.vamos.astructs import AmigaStructTypes, TypeDumper | ||
from amitools.state import ASFFile, ASFParser | ||
|
||
|
||
class StateTool(Tool): | ||
def __init__(self): | ||
Tool.__init__(self, "state", "inspect system state") | ||
|
||
def add_args(self, arg_parser): | ||
sub = arg_parser.add_subparsers(dest="type_cmd") | ||
# info | ||
parser = sub.add_parser("info", help="give info on state file") | ||
parser.add_argument("file", help="UAE state file (.uss)") | ||
parser.add_argument( | ||
"-d", "--dump", default=False, action="store_true", help="dump state file" | ||
) | ||
parser.add_argument("-s", "--save", help="save RAM to files with prefix") | ||
|
||
def run(self, args): | ||
type_cmd = args.type_cmd | ||
# info | ||
if type_cmd == "info": | ||
asf = ASFFile(args.file) | ||
chunks = asf.chunklist() | ||
|
||
# dump state file? | ||
if args.dump: | ||
for chunk in chunks: | ||
print(chunk) | ||
|
||
# parse state file | ||
parser = ASFParser(asf) | ||
|
||
# dump expansion | ||
if args.dump: | ||
expansion = parser.get_expansion() | ||
if expansion: | ||
print(expansion) | ||
|
||
# show ram layout | ||
print("RAM:") | ||
load_ram = args.save is not None | ||
ram_layout = parser.get_ram_layout(load_ram) | ||
for ram in ram_layout: | ||
if args.save: | ||
file = args.save + f".{ram.type.name}.{ram.address:08x}.dump" | ||
with open(file, "wb") as fh: | ||
fh.write(ram.data) | ||
else: | ||
file = "" | ||
print( | ||
f"@{ram.address:08x} +{ram.size:08x} {ram.type.name:6s} {file}" | ||
) | ||
|
||
return 0 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Oops, something went wrong.