Capture the rest of the file that I know about in parsed structures, track when a file needs to be read-only.
This commit is contained in:
parent
59b93e6931
commit
7fcca97ae4
@ -15,7 +15,13 @@ from bemani.protocol.node import Node
|
||||
|
||||
|
||||
class PMAN:
|
||||
def __init__(self, entries: List[str] = []) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
entries: List[str] = [],
|
||||
flags1: int = 0,
|
||||
flags2: int = 0,
|
||||
flags3: int = 0,
|
||||
) -> None:
|
||||
self.entries = entries
|
||||
|
||||
|
||||
@ -72,6 +78,16 @@ class Animation:
|
||||
self.header = header
|
||||
|
||||
|
||||
class Shape:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
data: bytes,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.data = data
|
||||
|
||||
|
||||
class AFPFile:
|
||||
def __init__(self, contents: bytes, verbose: bool = False) -> None:
|
||||
# Initialize coverage. This is used to help find missed/hidden file
|
||||
@ -88,6 +104,11 @@ class AFPFile:
|
||||
self.legacy_lz: bool = False
|
||||
self.modern_lz: bool = False
|
||||
|
||||
# If we encounter parts of the file that we don't know how to read
|
||||
# or save, we drop into read-only mode and throw if somebody tries
|
||||
# to update the file.
|
||||
self.read_only: bool = False
|
||||
|
||||
# List of all textures in this file. This is unordered, textures should
|
||||
# be looked up by name.
|
||||
self.textures: List[Texture] = []
|
||||
@ -118,6 +139,16 @@ class AFPFile:
|
||||
# a particular font texture.
|
||||
self.fontdata: Optional[Node] = None
|
||||
|
||||
# Shapes(?) with their raw data.
|
||||
self.shapes: List[Shape] = []
|
||||
|
||||
# Shape(?) mapping, not understood or used.
|
||||
self.shapemap: PMAN = PMAN()
|
||||
|
||||
# Unknown PMAN structures that we have to roundtrip.
|
||||
self.unk_pman1: PMAN = PMAN()
|
||||
self.unk_pman2: PMAN = PMAN()
|
||||
|
||||
# Parse out the file structure.
|
||||
self.__parse(verbose)
|
||||
|
||||
@ -229,7 +260,10 @@ class AFPFile:
|
||||
raise Exception(f"Didn't get mapping for entry {i + 1}")
|
||||
|
||||
return PMAN(
|
||||
entries=names
|
||||
entries=names,
|
||||
flags1=flags1,
|
||||
flags2=flags2,
|
||||
flags3=flags3,
|
||||
)
|
||||
|
||||
def __parse(
|
||||
@ -567,6 +601,8 @@ class AFPFile:
|
||||
|
||||
# TODO: 0x40 has some weird offset calculations, gotta look into
|
||||
# this further. Also, gotta actually parse this structure.
|
||||
if length != 0:
|
||||
self.read_only = True
|
||||
else:
|
||||
vprint("Bit 0x000040 - NOT PRESENT")
|
||||
|
||||
@ -578,11 +614,10 @@ class AFPFile:
|
||||
|
||||
vprint(f"Bit 0x000080 - offset: {hex(offset)}")
|
||||
|
||||
# TODO: We don't save this PMAN structure, I have no idea what it's for, but if
|
||||
# we find files with a nonzero value here and update textures, we're hosed.
|
||||
# TODO: I have no idea what this is for.
|
||||
if offset != 0:
|
||||
pman = self.descramble_pman(offset)
|
||||
for i, name in enumerate(pman.entries):
|
||||
self.unk_pman1 = self.descramble_pman(offset)
|
||||
for i, name in enumerate(self.unk_pman1.entries):
|
||||
vprint(f" {i}: {name}")
|
||||
else:
|
||||
vprint("Bit 0x000080 - NOT PRESENT")
|
||||
@ -599,6 +634,8 @@ class AFPFile:
|
||||
# TODO: We do something if length is > 0, we use the magic flag
|
||||
# from above in this case to optionally transform each thing we
|
||||
# extract. This is possibly names of some other type of struture?
|
||||
if length != 0:
|
||||
self.read_only = True
|
||||
else:
|
||||
vprint("Bit 0x000100 - NOT PRESENT")
|
||||
|
||||
@ -613,8 +650,8 @@ class AFPFile:
|
||||
# TODO: We don't save this PMAN structure, I have no idea what it's for, but if
|
||||
# we find files with a nonzero value here and update textures, we're hosed.
|
||||
if offset != 0:
|
||||
pman = self.descramble_pman(offset)
|
||||
for i, name in enumerate(pman.entries):
|
||||
self.unk_pman2 = self.descramble_pman(offset)
|
||||
for i, name in enumerate(self.unk_pman2.entries):
|
||||
vprint(f" {i}: {name}")
|
||||
else:
|
||||
vprint("Bit 0x000200 - NOT PRESENT")
|
||||
@ -703,7 +740,6 @@ class AFPFile:
|
||||
self.data[shape_base_offset:(shape_base_offset + 12)],
|
||||
)
|
||||
self.add_coverage(shape_base_offset, 12)
|
||||
self.add_coverage(shape_offset, shape_length)
|
||||
|
||||
# TODO: At the shape offset is a "D2EG" structure of some sort.
|
||||
# I have no idea what these do. I would have to look into it
|
||||
@ -716,6 +752,15 @@ class AFPFile:
|
||||
name = AFPFile.descramble_text(bytedata, self.text_obfuscated)
|
||||
shapenames.append(name)
|
||||
|
||||
if shape_offset != 0:
|
||||
self.add_coverage(shape_offset, shape_length)
|
||||
self.shapes.append(
|
||||
Shape(
|
||||
name,
|
||||
self.data[shape_offset:(shape_offset + shape_length)],
|
||||
)
|
||||
)
|
||||
|
||||
for name in shapenames:
|
||||
vprint(f" {name}")
|
||||
else:
|
||||
@ -730,18 +775,23 @@ class AFPFile:
|
||||
vprint(f"Bit 0x004000 - offset: {hex(offset)}")
|
||||
|
||||
if offset != 0:
|
||||
pman = self.descramble_pman(offset)
|
||||
for i, name in enumerate(pman.entries):
|
||||
self.shapemap = self.descramble_pman(offset)
|
||||
for i, name in enumerate(self.shapemap.entries):
|
||||
vprint(f" {i}: {name}")
|
||||
else:
|
||||
vprint("Bit 0x004000 - NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x8000:
|
||||
# One unknown byte, treated as an offset.
|
||||
# One unknown byte, treated as an offset. I have no idea what this is because
|
||||
# the games I've looked at don't include this bit.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
self.add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
|
||||
# Since I've never seen this, I'm going to assume that it showing up is
|
||||
# bad and make things read only.
|
||||
self.read_only = True
|
||||
|
||||
vprint(f"Bit 0x008000 - offset: {hex(offset)}")
|
||||
else:
|
||||
vprint("Bit 0x008000 - NOT PRESENT")
|
||||
@ -810,6 +860,10 @@ class AFPFile:
|
||||
else:
|
||||
vprint("Bit 0x040000 - modern lz mode off")
|
||||
|
||||
if feature_mask & 0xFFF80000:
|
||||
# We don't know these bits at all!
|
||||
raise Exception("Invalid bits set in feature mask!")
|
||||
|
||||
if header_offset != header_length:
|
||||
raise Exception("Failed to parse bitfield of header correctly!")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user