mirror of
https://github.com/isledecomp/isle.git
synced 2025-10-22 16:04:17 +00:00
Add datacmp to CI (#746)
This commit is contained in:
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -156,6 +156,13 @@ jobs:
|
|||||||
python3 tools/vtable/vtable.py legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB .
|
python3 tools/vtable/vtable.py legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB .
|
||||||
python3 tools/vtable/vtable.py legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .
|
python3 tools/vtable/vtable.py legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .
|
||||||
|
|
||||||
|
- name: Check Variables
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python3 tools/datacmp.py legobin/CONFIG.EXE build/CONFIG.EXE build/CONFIG.PDB .
|
||||||
|
python3 tools/datacmp.py legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB .
|
||||||
|
python3 tools/datacmp.py legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@master
|
||||||
with:
|
with:
|
||||||
|
@@ -297,7 +297,7 @@
|
|||||||
// GLOBAL: ISLE 0x4105b0
|
// GLOBAL: ISLE 0x4105b0
|
||||||
// __shi_TaskRecord
|
// __shi_TaskRecord
|
||||||
|
|
||||||
// GLOBAL: ISLE 0x4125f8
|
// ~GLOBAL: ISLE 0x4125f8
|
||||||
// ?_pnhHeap@@3P6AHI@ZA
|
// ?_pnhHeap@@3P6AHI@ZA
|
||||||
|
|
||||||
// GLOBAL: ISLE 0x412830
|
// GLOBAL: ISLE 0x412830
|
||||||
|
@@ -47,6 +47,13 @@ def parse_args() -> argparse.Namespace:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no-color", "-n", action="store_true", help="Do not color the output"
|
"--no-color", "-n", action="store_true", help="Do not color the output"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--all",
|
||||||
|
"-a",
|
||||||
|
dest="show_all",
|
||||||
|
action="store_true",
|
||||||
|
help="Only show variables with a problem",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--print-rec-addr",
|
"--print-rec-addr",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -236,7 +243,7 @@ def do_the_comparison(args: argparse.Namespace) -> Iterable[ComparisonItem]:
|
|||||||
|
|
||||||
# If we are here, we can do the type-aware comparison.
|
# If we are here, we can do the type-aware comparison.
|
||||||
compared = []
|
compared = []
|
||||||
compare_items = mini_cvdump.types.get_scalars(type_name)
|
compare_items = mini_cvdump.types.get_scalars_gapless(type_name)
|
||||||
format_str = mini_cvdump.types.get_format_string(type_name)
|
format_str = mini_cvdump.types.get_format_string(type_name)
|
||||||
|
|
||||||
orig_data = unpack(format_str, orig_raw)
|
orig_data = unpack(format_str, orig_raw)
|
||||||
@@ -308,8 +315,15 @@ def main():
|
|||||||
)
|
)
|
||||||
return f"{match_color}{result.name}{colorama.Style.RESET_ALL}"
|
return f"{match_color}{result.name}{colorama.Style.RESET_ALL}"
|
||||||
|
|
||||||
|
var_count = 0
|
||||||
|
problems = 0
|
||||||
|
|
||||||
for item in do_the_comparison(args):
|
for item in do_the_comparison(args):
|
||||||
if not args.verbose and item.result == CompareResult.MATCH:
|
var_count += 1
|
||||||
|
if item.result in (CompareResult.DIFF, CompareResult.ERROR):
|
||||||
|
problems += 1
|
||||||
|
|
||||||
|
if not args.show_all and item.result == CompareResult.MATCH:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
address_display = (
|
address_display = (
|
||||||
@@ -334,8 +348,14 @@ def main():
|
|||||||
f" {c.offset:5} {value_get(c.name, '(value)'):30} {value_a} : {value_b}"
|
f" {c.offset:5} {value_get(c.name, '(value)'):30} {value_a} : {value_b}"
|
||||||
)
|
)
|
||||||
|
|
||||||
print()
|
if args.verbose:
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"{os.path.basename(args.original)} - Variables: {var_count}. Issues: {problems}"
|
||||||
|
)
|
||||||
|
return 0 if problems == 0 else 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
raise SystemExit(main())
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, Iterator, List, NamedTuple, Optional
|
from typing import Dict, List, NamedTuple, Optional
|
||||||
|
|
||||||
|
|
||||||
class CvdumpTypeError(Exception):
|
class CvdumpTypeError(Exception):
|
||||||
@@ -109,38 +109,10 @@ def scalar_type_format_char(type_name: str) -> str:
|
|||||||
return char if scalar_type_signed(type_name) else char.upper()
|
return char if scalar_type_signed(type_name) else char.upper()
|
||||||
|
|
||||||
|
|
||||||
def member_string_iter(
|
def member_list_to_struct_string(members: List[ScalarType]) -> str:
|
||||||
members: List[ScalarType], size: Optional[int] = None
|
"""Create a string for use with struct.unpack"""
|
||||||
) -> Iterator[str]:
|
|
||||||
if len(members) == 0:
|
|
||||||
yield "x" * (size or 0)
|
|
||||||
|
|
||||||
last_offset = 0
|
format_string = "".join(m.format_char for m in members)
|
||||||
last_size = 0
|
|
||||||
for m in members:
|
|
||||||
padding = m.offset - last_offset - last_size
|
|
||||||
if padding > 0:
|
|
||||||
yield "x" * padding
|
|
||||||
|
|
||||||
yield m.format_char
|
|
||||||
last_offset = m.offset
|
|
||||||
last_size = m.size
|
|
||||||
|
|
||||||
if size is not None:
|
|
||||||
padding = size - (last_offset + last_size)
|
|
||||||
if padding > 0:
|
|
||||||
yield "x" * padding
|
|
||||||
|
|
||||||
|
|
||||||
def member_list_to_struct_string(
|
|
||||||
members: List[ScalarType], size: Optional[int] = None
|
|
||||||
) -> str:
|
|
||||||
"""Create a string for use with struct.unpack
|
|
||||||
Will pad to `size` bytes if present."""
|
|
||||||
if len(members) == 0:
|
|
||||||
return "x" * (size or 0)
|
|
||||||
|
|
||||||
format_string = "".join(list(member_string_iter(members, size)))
|
|
||||||
if len(format_string) > 0:
|
if len(format_string) > 0:
|
||||||
return "<" + format_string
|
return "<" + format_string
|
||||||
|
|
||||||
@@ -372,11 +344,43 @@ class CvdumpTypesParser:
|
|||||||
for cm in self.get_scalars(m.type)
|
for cm in self.get_scalars(m.type)
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_format_string(self, type_key: str) -> str:
|
def get_scalars_gapless(self, type_key: str) -> List[ScalarType]:
|
||||||
|
"""Reduce the given type to a list of scalars so we can
|
||||||
|
compare each component value."""
|
||||||
|
|
||||||
obj = self.get(type_key)
|
obj = self.get(type_key)
|
||||||
members = self.get_scalars(type_key)
|
total_size = obj.size
|
||||||
# We need both to pad the data to size
|
|
||||||
return member_list_to_struct_string(members, obj.size)
|
scalars = self.get_scalars(type_key)
|
||||||
|
|
||||||
|
output = []
|
||||||
|
last_extent = total_size
|
||||||
|
|
||||||
|
# Walk the scalar list in reverse; we assume a gap could not
|
||||||
|
# come at the start of the struct.
|
||||||
|
for scalar in scalars[::-1]:
|
||||||
|
this_extent = scalar.offset + scalar_type_size(scalar.type)
|
||||||
|
size_diff = last_extent - this_extent
|
||||||
|
# We need to add the gap fillers in reverse here
|
||||||
|
for i in range(size_diff - 1, -1, -1):
|
||||||
|
# Push to front
|
||||||
|
output.insert(
|
||||||
|
0,
|
||||||
|
ScalarType(
|
||||||
|
offset=this_extent + i,
|
||||||
|
name="(padding)",
|
||||||
|
type="T_UCHAR",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
output.insert(0, scalar)
|
||||||
|
last_extent = scalar.offset
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_format_string(self, type_key: str) -> str:
|
||||||
|
members = self.get_scalars_gapless(type_key)
|
||||||
|
return member_list_to_struct_string(members)
|
||||||
|
|
||||||
def read_line(self, line: str):
|
def read_line(self, line: str):
|
||||||
if (match := self.INDEX_RE.match(line)) is not None:
|
if (match := self.INDEX_RE.match(line)) is not None:
|
||||||
|
@@ -313,14 +313,27 @@ def test_struct(parser):
|
|||||||
|
|
||||||
|
|
||||||
def test_struct_padding(parser):
|
def test_struct_padding(parser):
|
||||||
"""Struct format string should insert padding characters 'x'
|
"""For data comparison purposes, make sure we have no gaps in the
|
||||||
where a value is padded to alignment size (probably 4 bytes)"""
|
list of scalar types. Any gap is filled by an unsigned char."""
|
||||||
|
|
||||||
|
# MxString, padded to 16 bytes. 4 actual members. 2 bytes of padding.
|
||||||
|
assert len(parser.get_scalars("0x4db6")) == 4
|
||||||
|
assert len(parser.get_scalars_gapless("0x4db6")) == 6
|
||||||
|
|
||||||
|
# MxVariable, with two MxStrings (and a vtable)
|
||||||
|
# Fill in the middle gap and the outer gap.
|
||||||
|
assert len(parser.get_scalars("0x22d5")) == 9
|
||||||
|
assert len(parser.get_scalars_gapless("0x22d5")) == 13
|
||||||
|
|
||||||
|
|
||||||
|
def test_struct_format_string(parser):
|
||||||
|
"""Generate the struct.unpack format string using the
|
||||||
|
list of scalars with padding filled in."""
|
||||||
# MxString, padded to 16 bytes.
|
# MxString, padded to 16 bytes.
|
||||||
assert parser.get_format_string("0x4db6") == "<LLLHxx"
|
assert parser.get_format_string("0x4db6") == "<LLLHBB"
|
||||||
|
|
||||||
# MxVariable, with two MxString members.
|
# MxVariable, with two MxString members.
|
||||||
assert parser.get_format_string("0x22d5") == "<LLLLHxxLLLHxx"
|
assert parser.get_format_string("0x22d5") == "<LLLLHBBLLLHBB"
|
||||||
|
|
||||||
|
|
||||||
def test_array(parser):
|
def test_array(parser):
|
||||||
|
Reference in New Issue
Block a user