Add a mostly-complete parser for shapes, which are the missing link between object placement in AP2 files and textures/regions elsewhere.
This commit is contained in:
parent
98197641c9
commit
887d4dc657
@ -1,5 +1,6 @@
|
||||
import io
|
||||
from hashlib import md5
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from PIL import Image # type: ignore
|
||||
@ -98,6 +99,17 @@ class TextureRegion:
|
||||
'bottom': self.bottom,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"texture: {self.textureno}, " +
|
||||
f"left: {self.left / 2}, " +
|
||||
f"top: {self.top / 2}, " +
|
||||
f"right: {self.right / 2}, " +
|
||||
f"bottom: {self.bottom / 2}, " +
|
||||
f"width: {(self.right - self.left) / 2}, " +
|
||||
f"height: {(self.bottom - self.top) / 2}"
|
||||
)
|
||||
|
||||
|
||||
class Matrix:
|
||||
def __init__(self, a: float, b: float, c: float, d: float, tx: float, ty: float) -> None:
|
||||
@ -112,6 +124,9 @@ class Matrix:
|
||||
def identity() -> "Matrix":
|
||||
return Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"a: {round(self.a, 5)}, b: {round(self.b, 5)}, c: {round(self.c, 5)}, d: {round(self.d, 5)}, tx: {round(self.tx, 5)}, ty: {round(self.ty, 5)}"
|
||||
|
||||
|
||||
class Color:
|
||||
def __init__(self, r: float, g: float, b: float, a: float) -> None:
|
||||
@ -120,8 +135,32 @@ class Color:
|
||||
self.b = b
|
||||
self.a = a
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'r': self.r,
|
||||
'g': self.g,
|
||||
'b': self.b,
|
||||
'a': self.a,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{round(self.r, 5)}, {round(self.g, 5)}, {round(self.b, 5)}, {round(self.a, 5)}"
|
||||
return f"r: {round(self.r, 5)}, g: {round(self.g, 5)}, b: {round(self.b, 5)}, a: {round(self.a, 5)}"
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self, x: float, y: float) -> None:
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'x': self.x,
|
||||
'y': self.y,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"x: {round(self.x, 5)}, y: {round(self.y, 5)}"
|
||||
|
||||
|
||||
class Tag:
|
||||
END = 0x0
|
||||
@ -874,7 +913,7 @@ class SWF:
|
||||
add_coverage(28, 4)
|
||||
|
||||
if flags & 0x2:
|
||||
# I think this is FPS given the output of this bit of code.
|
||||
# FPS can be either an integer or a float.
|
||||
fps = struct.unpack("<i", data[24:28])[0] * 0.0009765625
|
||||
else:
|
||||
fps = struct.unpack("<f", data[24:28])[0]
|
||||
@ -899,7 +938,7 @@ class SWF:
|
||||
# Get exported SWF name.
|
||||
self.exported_name = self.__get_string(nameoffset)
|
||||
add_coverage(nameoffset + stringtable_offset, len(self.exported_name) + 1, unique=False)
|
||||
vprint(f"\nAFP name: {self.name}")
|
||||
vprint(f"{os.linesep}AFP name: {self.name}")
|
||||
vprint(f"Container Version: {hex(ap2_data_version)}")
|
||||
vprint(f"Version: {hex(version)}")
|
||||
vprint(f"Exported Name: {self.exported_name}")
|
||||
@ -993,6 +1032,52 @@ class SWF:
|
||||
self.print_coverage()
|
||||
|
||||
|
||||
class DrawParams:
|
||||
def __init__(
|
||||
self,
|
||||
flags: int,
|
||||
region: Optional[str] = None,
|
||||
vertexes: List[int] = [],
|
||||
blend: Optional[Color] = None,
|
||||
) -> None:
|
||||
self.flags = flags
|
||||
self.region = region
|
||||
self.vertexes = vertexes
|
||||
self.blend = blend
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'flags': self.flags,
|
||||
'region': self.region,
|
||||
'vertexes': self.vertexes,
|
||||
'blend': self.blend.as_dict() if self.blend else None,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
flagbits: List[str] = []
|
||||
if self.flags & 0x1:
|
||||
flagbits.append("(Instantiable)")
|
||||
if self.flags & 0x2:
|
||||
flagbits.append("(Includes Texture)")
|
||||
if self.flags & 0x8:
|
||||
flagbits.append("(Includes Blend Color)")
|
||||
if self.flags & 0x40:
|
||||
flagbits.append("(Needs Tex Point Normalization)")
|
||||
|
||||
flagspart = f"flags: {hex(self.flags)} {' '.join(flagbits)}"
|
||||
if self.flags & 0x2:
|
||||
texpart = f", region: {self.region}, vertexes: {', '.join(str(x) for x in self.vertexes)}"
|
||||
else:
|
||||
texpart = ""
|
||||
|
||||
if self.flags & 0x8:
|
||||
blendpart = f", blend: {self.blend}"
|
||||
else:
|
||||
blendpart = ""
|
||||
|
||||
return f"{flagspart}{texpart}{blendpart}"
|
||||
|
||||
|
||||
class Shape:
|
||||
def __init__(
|
||||
self,
|
||||
@ -1002,12 +1087,144 @@ class Shape:
|
||||
self.name = name
|
||||
self.data = data
|
||||
|
||||
# Rectangle points outlining this shape.
|
||||
self.rect_points: List[Point] = []
|
||||
|
||||
# Texture points, as used alongside vertex chunks when the shape contains a texture.
|
||||
self.tex_points: List[Point] = []
|
||||
|
||||
# Actual shape drawing parameters.
|
||||
self.draw_params: List[DrawParams] = []
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'name': self.name,
|
||||
'data': "".join(_hex(x) for x in self.data),
|
||||
'rect_points': [p.as_dict() for p in self.rect_points],
|
||||
'tex_points': [p.as_dict() for p in self.tex_points],
|
||||
'draw_params': [d.as_dict() for d in self.draw_params],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return os.linesep.join([
|
||||
*[f"rect point: {rect}" for rect in self.rect_points],
|
||||
*[f"tex point: {tex}" for tex in self.tex_points],
|
||||
*[f"draw params: {params}" for params in self.draw_params],
|
||||
])
|
||||
|
||||
def get_until_null(self, offset: int) -> bytes:
|
||||
out = b""
|
||||
while self.data[offset] != 0:
|
||||
out += self.data[offset:(offset + 1)]
|
||||
offset += 1
|
||||
return out
|
||||
|
||||
def parse(self, text_obfuscated: bool = True) -> None:
|
||||
# First, grab the header bytes.
|
||||
magic = self.data[0:4]
|
||||
|
||||
if magic == b"D2EG":
|
||||
endian = "<"
|
||||
elif magic == b"GE2D":
|
||||
endian = ">"
|
||||
else:
|
||||
raise Exception("Invalid magic value in GE2D structure!")
|
||||
|
||||
filesize = struct.unpack(f"{endian}I", self.data[12:16])[0]
|
||||
if filesize != len(self.data):
|
||||
raise Exception("Unexpected file size for GE2D structure!")
|
||||
|
||||
rect_count, tex_count, unk1_count, label_count, render_params_count, _ = struct.unpack(
|
||||
f"{endian}HHHHHH",
|
||||
self.data[20:32],
|
||||
)
|
||||
|
||||
rect_offset, tex_offset, unk1_offset, label_offset, render_params_offset = struct.unpack(
|
||||
f"{endian}IIIII",
|
||||
self.data[32:52],
|
||||
)
|
||||
|
||||
rect_points: List[Point] = []
|
||||
if rect_offset != 0:
|
||||
for rectno in range(rect_count):
|
||||
rectno_offset = rect_offset + (8 * rectno)
|
||||
x, y = struct.unpack(f"{endian}ff", self.data[rectno_offset:rectno_offset + 8])
|
||||
rect_points.append(Point(x, y))
|
||||
self.rect_points = rect_points
|
||||
|
||||
tex_points: List[Point] = []
|
||||
if tex_offset != 0:
|
||||
for texno in range(tex_count):
|
||||
texno_offset = tex_offset + (8 * texno)
|
||||
x, y = struct.unpack(f"{endian}ff", self.data[texno_offset:texno_offset + 8])
|
||||
tex_points.append(Point(x, y))
|
||||
self.tex_points = tex_points
|
||||
|
||||
if unk1_offset != 0:
|
||||
raise Exception("Unknown offset pointer data present!")
|
||||
|
||||
labels: List[str] = []
|
||||
if label_offset != 0:
|
||||
for labelno in range(label_count):
|
||||
labelno_offset = label_offset + (4 * labelno)
|
||||
labelptr = struct.unpack(f"{endian}I", self.data[labelno_offset:labelno_offset + 4])[0]
|
||||
|
||||
bytedata = self.get_until_null(labelptr)
|
||||
labels.append(AFPFile.descramble_text(bytedata, text_obfuscated))
|
||||
|
||||
draw_params: List[DrawParams] = []
|
||||
if render_params_offset != 0:
|
||||
# The actual render parameters for the shape. This dictates how the texture values
|
||||
# are used when drawing shapes, whether to use a blend value or draw a primitive, etc.
|
||||
for render_paramsno in range(render_params_count):
|
||||
render_paramsno_offset = render_params_offset + (16 * render_paramsno)
|
||||
points, flags, label, _, trianglecount, _, rgba, triangleoffset = struct.unpack(
|
||||
f"{endian}BBBBHHII",
|
||||
self.data[(render_paramsno_offset):(render_paramsno_offset + 16)]
|
||||
)
|
||||
|
||||
if points != 4:
|
||||
raise Exception("Unexpected number of points in GE2D structure!")
|
||||
if (flags & 0x2) and len(labels) == 0:
|
||||
raise Exception("GE2D structure has a texture, but no region labels present!")
|
||||
|
||||
color = Color(
|
||||
r=(rgba & 0xFF) / 255.0,
|
||||
g=((rgba >> 8) & 0xFF) / 255.0,
|
||||
b=((rgba >> 16) & 0xFF) / 255.0,
|
||||
a=((rgba >> 24) & 0xFF) / 255.0,
|
||||
)
|
||||
|
||||
verticies: List[int] = []
|
||||
for render_paramstriangleno in range(trianglecount):
|
||||
render_paramstriangleno_offset = triangleoffset + (2 * render_paramstriangleno)
|
||||
tex_offset = struct.unpack(f"{endian}H", self.data[render_paramstriangleno_offset:(render_paramstriangleno_offset + 2)])[0]
|
||||
verticies.append(tex_offset)
|
||||
|
||||
# Seen bits are 0x1, 0x2, 0x8 so far.
|
||||
# 0x1 Is a "this shape is instantiable/drawable" bit.
|
||||
# 0x2 Is the shape having a texture.
|
||||
# 0x8 Is "draw background color/blend" flag.
|
||||
# 0x40 Is a "normalize texture coordinates" flag. It performs the below algorithm.
|
||||
|
||||
if (flags & (0x2 | 0x40)) == (0x2 | 0x40):
|
||||
# The tex offsets point at the tex vals parsed above, and are used in conjunction with
|
||||
# texture/region metrics to calcuate some offsets. First, the region left/right/top/bottom
|
||||
# is divided by 2 (looks like a scaling of 2 for regions to textures is hardcoded) and then
|
||||
# divided by the texture width/height (as relevant). The returned metrics are in texture space
|
||||
# where 0.0 is the origin and 1.0 is the furthest right/down. The metrics are then multiplied
|
||||
# by the texture point pairs that appear above, meaning they should be treated as percentages.
|
||||
pass
|
||||
|
||||
draw_params.append(
|
||||
DrawParams(
|
||||
flags=flags,
|
||||
region=labels[label] if (flags & 0x2) else None,
|
||||
vertexes=verticies if (flags & 0x2) else [],
|
||||
blend=color if (flags & 0x8) else None,
|
||||
)
|
||||
)
|
||||
self.draw_params = draw_params
|
||||
|
||||
|
||||
class Unknown1:
|
||||
def __init__(
|
||||
@ -1320,7 +1537,7 @@ class AFPFile:
|
||||
if length != len(self.data):
|
||||
raise Exception(f"Invalid graphic file length, expecting {length} bytes!")
|
||||
|
||||
# I think that offset 16-20 are the file data offset, but I'm not sure?
|
||||
# This is always the header length, or the offset of the data payload.
|
||||
header_length = struct.unpack(f"{self.endian}I", self.data[16:20])[0]
|
||||
add_coverage(16, 4)
|
||||
|
||||
@ -1435,8 +1652,6 @@ class AFPFile:
|
||||
# flags1 = (fmtflags >> 24) & 0xFF
|
||||
# flags2 = (fmtflags >> 16) & 0xFF
|
||||
|
||||
# These flags may have some significance, such as
|
||||
# the unk3/unk4 possibly indicating texture doubling?
|
||||
# unk1 = 3 if (flags1 & 0xF == 1) else 1
|
||||
# unk2 = 3 if ((flags1 >> 4) & 0xF == 1) else 1
|
||||
# unk3 = 1 if (flags2 & 0xF == 1) else 2
|
||||
@ -1612,8 +1827,7 @@ class AFPFile:
|
||||
|
||||
# Mapping between texture index and the name of the texture.
|
||||
if feature_mask & 0x02:
|
||||
# Seems to be a structure that duplicates texture names? I am pretty
|
||||
# sure this is used to map texture names to file indexes used elsewhere.
|
||||
# Mapping of texture name to texture index. This is used by regions to look up textures.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
@ -1653,18 +1867,19 @@ class AFPFile:
|
||||
|
||||
if texture_no < 0 or texture_no >= len(self.texturemap.entries):
|
||||
raise Exception(f"Out of bounds texture {texture_no}")
|
||||
vprint(f" length: 10, offset: {hex(offset + (10 * i))}")
|
||||
|
||||
# TODO: The offsets here seem to be off by a power of 2, there
|
||||
# might be more flags in the above texture format that specify
|
||||
# device scaling and such?
|
||||
self.texture_to_region.append(TextureRegion(texture_no, left, top, right, bottom))
|
||||
# Texture regions are multiplied by a power of 2. Not sure why, but the games I
|
||||
# looked at hardcode a divide by 2 when loading regions.
|
||||
region = TextureRegion(texture_no, left, top, right, bottom)
|
||||
self.texture_to_region.append(region)
|
||||
|
||||
vprint(f" {region}, offset: {hex(descriptor_offset)}")
|
||||
else:
|
||||
vprint("Bit 0x000008 - regions; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x10:
|
||||
# Names of the graphics regions, so we can look into the texture_to_region
|
||||
# mapping above.
|
||||
# mapping above. Used by shapes to find the right region offset given a name.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
@ -1779,7 +1994,8 @@ class AFPFile:
|
||||
vprint("Bit 0x000400 - unknown; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x800:
|
||||
# This is the names of the SWF data as far as I can tell.
|
||||
# SWF raw data that is loaded and passed to AFP core. It is equivalent to the
|
||||
# afp files in an IFS container.
|
||||
length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)])
|
||||
add_coverage(header_offset, 8)
|
||||
header_offset += 8
|
||||
@ -1813,7 +2029,7 @@ class AFPFile:
|
||||
vprint("Bit 0x000800 - swfdata; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x1000:
|
||||
# Seems to be a secondary structure mirroring the above.
|
||||
# A mapping structure that allows looking up SWF data by name.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
@ -1826,16 +2042,15 @@ class AFPFile:
|
||||
vprint("Bit 0x001000 - swfmapping; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x2000:
|
||||
# I am making a very preliminary guess that these are shapes used along
|
||||
# with SWF data specified below. The names in these sections tend to
|
||||
# have the word "shape" in them.
|
||||
# These are shapes as used with the SWF data above. They contain mappings between a
|
||||
# loaded texture shape and the region that contains data. They are equivalent to the
|
||||
# geo files found in an IFS container.
|
||||
length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)])
|
||||
add_coverage(header_offset, 8)
|
||||
header_offset += 8
|
||||
|
||||
vprint(f"Bit 0x002000 - shapes; count: {length}, offset: {hex(offset)}")
|
||||
|
||||
# TODO: We do a LOT of extra stuff with this one, if count > 0...
|
||||
for x in range(length):
|
||||
shape_base_offset = offset + (x * 12)
|
||||
if shape_base_offset != 0:
|
||||
@ -1845,85 +2060,32 @@ class AFPFile:
|
||||
)
|
||||
add_coverage(shape_base_offset, 12)
|
||||
|
||||
# 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
|
||||
# more if its important.
|
||||
|
||||
if name_offset != 0:
|
||||
# Let's decode this until the first null.
|
||||
bytedata = self.get_until_null(name_offset)
|
||||
add_coverage(name_offset, len(bytedata) + 1, unique=False)
|
||||
name = AFPFile.descramble_text(bytedata, self.text_obfuscated)
|
||||
vprint(f" {name}, length: {shape_length}, offset: {hex(shape_offset)}")
|
||||
else:
|
||||
name = "<unnamed>"
|
||||
|
||||
if shape_offset != 0:
|
||||
shape_data = self.data[shape_offset:(shape_offset + shape_length)]
|
||||
shape = Shape(
|
||||
name,
|
||||
self.data[shape_offset:(shape_offset + shape_length)],
|
||||
)
|
||||
shape.parse(text_obfuscated=self.text_obfuscated)
|
||||
self.shapes.append(shape)
|
||||
add_coverage(shape_offset, shape_length)
|
||||
|
||||
magic, header1, header2, filesize, header3 = struct.unpack(
|
||||
f"{self.endian}4sIIII",
|
||||
shape_data[0:20],
|
||||
)
|
||||
vprint(f" {name}, length: {shape_length}, offset: {hex(shape_offset)}")
|
||||
for line in str(shape).split(os.linesep):
|
||||
vprint(f" {line}")
|
||||
|
||||
if self.endian == "<" and magic != b"D2EG":
|
||||
raise Exception("Invalid magic value in D2EG structure!")
|
||||
if self.endian == ">" and magic != b"GE2D":
|
||||
raise Exception("Invalid magic value in D2EG structure!")
|
||||
if filesize != len(shape_data):
|
||||
raise Exception("Unexpected file size for D2EG structure!")
|
||||
|
||||
# Get width/height
|
||||
endian = "<" if self.endian == ">" else ">"
|
||||
width, height = struct.unpack(f"{endian}HH", shape_data[20:24])
|
||||
|
||||
header4, header5 = struct.unpack(
|
||||
f"{self.endian}II",
|
||||
shape_data[24:32],
|
||||
)
|
||||
|
||||
rect_offset, tex_offset, unk1_offset, label_offset, unk2_offset = struct.unpack(
|
||||
f"{self.endian}IIIII",
|
||||
shape_data[32:52],
|
||||
)
|
||||
|
||||
label = None
|
||||
if label_offset != 0:
|
||||
labelptr = struct.unpack(f"{self.endian}I", shape_data[label_offset:label_offset + 4])[0]
|
||||
if labelptr is not None:
|
||||
bytedata = self.get_until_null(shape_offset + labelptr)
|
||||
label = AFPFile.descramble_text(bytedata, self.text_obfuscated) # NOQA: F841
|
||||
|
||||
if rect_offset != 0:
|
||||
floats = struct.unpack(
|
||||
f"{self.endian}ffffffff",
|
||||
shape_data[(rect_offset):(rect_offset + 32)]
|
||||
)
|
||||
_rect_offsets = [x for x in floats] # NOQA: F841
|
||||
if tex_offset != 0:
|
||||
floats = struct.unpack(
|
||||
f"{self.endian}ffffffff",
|
||||
shape_data[(tex_offset):(tex_offset + 32)]
|
||||
)
|
||||
tex_offsets = []
|
||||
for i, flt in enumerate(floats):
|
||||
tex_offsets.append(flt * (width if ((i & 1) == 0) else height))
|
||||
if unk2_offset != 0:
|
||||
test = struct.unpack( # NOQA: F841
|
||||
f"{endian}iii",
|
||||
shape_data[(unk2_offset):(unk2_offset + 12)]
|
||||
)
|
||||
|
||||
self.shapes.append(
|
||||
Shape(
|
||||
name,
|
||||
self.data[shape_offset:(shape_offset + shape_length)],
|
||||
)
|
||||
)
|
||||
else:
|
||||
vprint("Bit 0x002000 - shapes; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x4000:
|
||||
# Seems to be a secondary section mirroring the names from above.
|
||||
# Mapping so that shapes can be looked up by name to get their offset.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
@ -1951,7 +2113,8 @@ class AFPFile:
|
||||
vprint("Bit 0x008000 - unknown; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x10000:
|
||||
# Included font package, BINXRPC encoded.
|
||||
# Included font package, BINXRPC encoded. This is basically a texture sheet with an XML
|
||||
# pointing at the region in the texture sheet for every renderable character.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
@ -1977,8 +2140,8 @@ class AFPFile:
|
||||
vprint("Bit 0x010000 - fontinfo; NOT PRESENT")
|
||||
|
||||
if feature_mask & 0x20000:
|
||||
# I am beginning to suspect that this is SWF data/level data. I have
|
||||
# no idea what "afp" is. Games refer to these as "afp streams".
|
||||
# This is the byteswapping headers that allow us to byteswap the SWF data before passing it
|
||||
# to AFP core. It is equivalent to the bsi files in an IFS container.
|
||||
offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0]
|
||||
add_coverage(header_offset, 4)
|
||||
header_offset += 4
|
||||
|
@ -8,7 +8,7 @@ import textwrap
|
||||
from PIL import Image, ImageDraw # type: ignore
|
||||
from typing import Any, Dict
|
||||
|
||||
from bemani.format.afp import AFPFile, SWF
|
||||
from bemani.format.afp import AFPFile, Shape, SWF
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@ -100,18 +100,31 @@ def main() -> int:
|
||||
help="Display verbuse debugging output",
|
||||
)
|
||||
|
||||
parse_parser = subparsers.add_parser('parse', help='Parse a raw AFP/BSI file pair from an IFS container')
|
||||
parse_parser.add_argument(
|
||||
parseafp_parser = subparsers.add_parser('parseafp', help='Parse a raw AFP/BSI file pair extracted from an IFS container')
|
||||
parseafp_parser.add_argument(
|
||||
"afp",
|
||||
metavar="AFPFILE",
|
||||
help="The AFP file to parse",
|
||||
)
|
||||
parse_parser.add_argument(
|
||||
parseafp_parser.add_argument(
|
||||
"bsi",
|
||||
metavar="BSIFILE",
|
||||
help="The BSI file to parse",
|
||||
)
|
||||
parse_parser.add_argument(
|
||||
parseafp_parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Display verbuse debugging output",
|
||||
)
|
||||
|
||||
parsegeo_parser = subparsers.add_parser('parsegeo', help='Parse a raw GEO file extracted from an IFS container')
|
||||
parsegeo_parser.add_argument(
|
||||
"geo",
|
||||
metavar="GEOFILE",
|
||||
help="The GEO file to parse",
|
||||
)
|
||||
parsegeo_parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
@ -331,7 +344,7 @@ def main() -> int:
|
||||
# Now, print it
|
||||
print(json.dumps(afpfile.as_dict(), sort_keys=True, indent=4))
|
||||
|
||||
if args.action == "parse":
|
||||
if args.action == "parseafp":
|
||||
# First, load the AFP and BSI files
|
||||
with open(args.afp, "rb") as bafp:
|
||||
with open(args.bsi, "rb") as bbsi:
|
||||
@ -341,6 +354,17 @@ def main() -> int:
|
||||
swf.parse(verbose=args.verbose)
|
||||
print(json.dumps(swf.as_dict(), sort_keys=True, indent=4))
|
||||
|
||||
if args.action == "parsegeo":
|
||||
# First, load the AFP and BSI files
|
||||
with open(args.geo, "rb") as bfp:
|
||||
geo = Shape("<unnamed>", bfp.read())
|
||||
|
||||
# Now, print it
|
||||
geo.parse()
|
||||
if args.verbose:
|
||||
print(geo, file=sys.stderr)
|
||||
print(json.dumps(geo.as_dict(), sort_keys=True, indent=4))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user