Rewrite __main__ code to use stream-based resource reading
This commit is contained in:
parent
5c96baea29
commit
2907d9f9e8
|
@ -1,6 +1,8 @@
|
||||||
import argparse
|
import argparse
|
||||||
import enum
|
import enum
|
||||||
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
@ -183,11 +185,12 @@ def filter_resources(rf: api.ResourceFile, filters: typing.Sequence[str]) -> typ
|
||||||
yield res
|
yield res
|
||||||
|
|
||||||
|
|
||||||
def hexdump(data: bytes) -> typing.Iterable[str]:
|
def hexdump_stream(stream: typing.BinaryIO) -> typing.Iterable[str]:
|
||||||
last_line = None
|
last_line = None
|
||||||
asterisk_shown = False
|
asterisk_shown = False
|
||||||
for i in range(0, len(data), 16):
|
line = stream.read(16)
|
||||||
line = data[i:i + 16]
|
i = 0
|
||||||
|
while line:
|
||||||
# If the same 16-byte lines appear multiple times, print only the first one, and replace all further lines with a single line with an asterisk.
|
# If the same 16-byte lines appear multiple times, print only the first one, and replace all further lines with a single line with an asterisk.
|
||||||
# This is unambiguous - to find out how many lines were collapsed this way, the user can compare the addresses of the lines before and after the asterisk.
|
# This is unambiguous - to find out how many lines were collapsed this way, the user can compare the addresses of the lines before and after the asterisk.
|
||||||
if line == last_line:
|
if line == last_line:
|
||||||
|
@ -201,14 +204,26 @@ def hexdump(data: bytes) -> typing.Iterable[str]:
|
||||||
yield f"{i:08x} {line_hex_left:<{8*2+7}} {line_hex_right:<{8*2+7}} |{line_char}|"
|
yield f"{i:08x} {line_hex_left:<{8*2+7}} {line_hex_right:<{8*2+7}} |{line_char}|"
|
||||||
asterisk_shown = False
|
asterisk_shown = False
|
||||||
last_line = line
|
last_line = line
|
||||||
|
i += len(line)
|
||||||
|
line = stream.read(16)
|
||||||
|
|
||||||
if data:
|
if i:
|
||||||
yield f"{len(data):08x}"
|
yield f"{i:08x}"
|
||||||
|
|
||||||
|
|
||||||
|
def hexdump(data: bytes) -> typing.Iterable[str]:
|
||||||
|
yield from hexdump_stream(io.BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
|
def raw_hexdump_stream(stream: typing.BinaryIO) -> typing.Iterable[str]:
|
||||||
|
line = stream.read(16)
|
||||||
|
while line:
|
||||||
|
yield " ".join(f"{byte:02x}" for byte in line)
|
||||||
|
line = stream.read(16)
|
||||||
|
|
||||||
|
|
||||||
def raw_hexdump(data: bytes) -> typing.Iterable[str]:
|
def raw_hexdump(data: bytes) -> typing.Iterable[str]:
|
||||||
for i in range(0, len(data), 16):
|
yield from raw_hexdump_stream(io.BytesIO(data))
|
||||||
yield " ".join(f"{byte:02x}" for byte in data[i:i + 16])
|
|
||||||
|
|
||||||
|
|
||||||
def translate_text(data: bytes) -> str:
|
def translate_text(data: bytes) -> str:
|
||||||
|
@ -267,77 +282,80 @@ def show_filtered_resources(resources: typing.Sequence[api.Resource], format: st
|
||||||
|
|
||||||
for res in resources:
|
for res in resources:
|
||||||
if decompress:
|
if decompress:
|
||||||
data = res.data
|
open_func = res.open
|
||||||
else:
|
else:
|
||||||
data = res.data_raw
|
open_func = res.open_raw
|
||||||
|
|
||||||
if format in ("dump", "dump-text"):
|
with open_func() as f:
|
||||||
# Human-readable info and hex or text dump
|
if format in ("dump", "dump-text"):
|
||||||
desc = describe_resource(res, include_type=True, decompress=decompress)
|
# Human-readable info and hex or text dump
|
||||||
print(f"Resource {desc}:")
|
desc = describe_resource(res, include_type=True, decompress=decompress)
|
||||||
if format == "dump":
|
print(f"Resource {desc}:")
|
||||||
for line in hexdump(data):
|
if format == "dump":
|
||||||
print(line)
|
for line in hexdump_stream(f):
|
||||||
elif format == "dump-text":
|
print(line)
|
||||||
print(translate_text(data))
|
elif format == "dump-text":
|
||||||
else:
|
print(translate_text(f.read()))
|
||||||
raise AssertionError(f"Unhandled format: {format!r}")
|
else:
|
||||||
print()
|
raise AssertionError(f"Unhandled format: {format!r}")
|
||||||
elif format == "hex":
|
print()
|
||||||
# Data only as hex
|
elif format == "hex":
|
||||||
|
# Data only as hex
|
||||||
for line in raw_hexdump(data):
|
|
||||||
print(line)
|
|
||||||
elif format == "raw":
|
|
||||||
# Data only as raw bytes
|
|
||||||
|
|
||||||
sys.stdout.buffer.write(data)
|
|
||||||
elif format == "derez":
|
|
||||||
# Like DeRez with no resource definitions
|
|
||||||
|
|
||||||
attrs = list(decompose_flags(res.attributes))
|
|
||||||
|
|
||||||
if decompress and api.ResourceAttrs.resCompressed in attrs:
|
|
||||||
attrs.remove(api.ResourceAttrs.resCompressed)
|
|
||||||
attrs_comment = " /* was compressed */"
|
|
||||||
else:
|
|
||||||
attrs_comment = ""
|
|
||||||
|
|
||||||
attr_descs_with_none = [_REZ_ATTR_NAMES[attr] for attr in attrs]
|
|
||||||
if None in attr_descs_with_none:
|
|
||||||
attr_descs = [f"${res.attributes.value:02X}"]
|
|
||||||
else:
|
|
||||||
attr_descs = typing.cast(typing.List[str], attr_descs_with_none)
|
|
||||||
|
|
||||||
parts = [str(res.id)]
|
|
||||||
|
|
||||||
if res.name is not None:
|
|
||||||
parts.append(bytes_quote(res.name, '"'))
|
|
||||||
|
|
||||||
parts += attr_descs
|
|
||||||
|
|
||||||
quoted_restype = bytes_quote(res.type, "'")
|
|
||||||
print(f"data {quoted_restype} ({', '.join(parts)}{attrs_comment}) {{")
|
|
||||||
|
|
||||||
for i in range(0, len(data), 16):
|
|
||||||
# Two-byte grouping is really annoying to implement.
|
|
||||||
groups = []
|
|
||||||
for j in range(0, 16, 2):
|
|
||||||
if i+j >= len(data):
|
|
||||||
break
|
|
||||||
elif i+j+1 >= len(data):
|
|
||||||
groups.append(f"{data[i+j]:02X}")
|
|
||||||
else:
|
|
||||||
groups.append(f"{data[i+j]:02X}{data[i+j+1]:02X}")
|
|
||||||
|
|
||||||
s = f'$"{" ".join(groups)}"'
|
for line in raw_hexdump_stream(f):
|
||||||
comment = "/* " + data[i:i + 16].decode(_TEXT_ENCODING).translate(_TRANSLATE_NONPRINTABLES) + " */"
|
print(line)
|
||||||
print(f"\t{s:<54s}{comment}")
|
elif format == "raw":
|
||||||
|
# Data only as raw bytes
|
||||||
print("};")
|
|
||||||
print()
|
shutil.copyfileobj(f, sys.stdout.buffer)
|
||||||
else:
|
elif format == "derez":
|
||||||
raise ValueError(f"Unhandled output format: {format}")
|
# Like DeRez with no resource definitions
|
||||||
|
|
||||||
|
attrs = list(decompose_flags(res.attributes))
|
||||||
|
|
||||||
|
if decompress and api.ResourceAttrs.resCompressed in attrs:
|
||||||
|
attrs.remove(api.ResourceAttrs.resCompressed)
|
||||||
|
attrs_comment = " /* was compressed */"
|
||||||
|
else:
|
||||||
|
attrs_comment = ""
|
||||||
|
|
||||||
|
attr_descs_with_none = [_REZ_ATTR_NAMES[attr] for attr in attrs]
|
||||||
|
if None in attr_descs_with_none:
|
||||||
|
attr_descs = [f"${res.attributes.value:02X}"]
|
||||||
|
else:
|
||||||
|
attr_descs = typing.cast(typing.List[str], attr_descs_with_none)
|
||||||
|
|
||||||
|
parts = [str(res.id)]
|
||||||
|
|
||||||
|
if res.name is not None:
|
||||||
|
parts.append(bytes_quote(res.name, '"'))
|
||||||
|
|
||||||
|
parts += attr_descs
|
||||||
|
|
||||||
|
quoted_restype = bytes_quote(res.type, "'")
|
||||||
|
print(f"data {quoted_restype} ({', '.join(parts)}{attrs_comment}) {{")
|
||||||
|
|
||||||
|
line = f.read(16)
|
||||||
|
while line:
|
||||||
|
# Two-byte grouping is really annoying to implement.
|
||||||
|
groups = []
|
||||||
|
for j in range(0, 16, 2):
|
||||||
|
if j >= len(line):
|
||||||
|
break
|
||||||
|
elif j+1 >= len(line):
|
||||||
|
groups.append(f"{line[j]:02X}")
|
||||||
|
else:
|
||||||
|
groups.append(f"{line[j]:02X}{line[j+1]:02X}")
|
||||||
|
|
||||||
|
s = f'$"{" ".join(groups)}"'
|
||||||
|
comment = "/* " + line.decode(_TEXT_ENCODING).translate(_TRANSLATE_NONPRINTABLES) + " */"
|
||||||
|
print(f"\t{s:<54s}{comment}")
|
||||||
|
line = f.read(16)
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unhandled output format: {format}")
|
||||||
|
|
||||||
|
|
||||||
def list_resources(resources: typing.List[api.Resource], *, sort: bool, group: str, decompress: bool) -> None:
|
def list_resources(resources: typing.List[api.Resource], *, sort: bool, group: str, decompress: bool) -> None:
|
||||||
|
|
Loading…
Reference in New Issue