242 lines
8.0 KiB
Python
242 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Parser dla plików .mar (MultiArray) z gier Reksio
|
||
"""
|
||
|
||
import struct
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
|
||
class MultiArrayParser:
|
||
def __init__(self, filepath):
|
||
self.filepath = Path(filepath)
|
||
self.dimensions = []
|
||
self.total_elements = 0
|
||
self.data = {} # sparse dictionary: index -> value
|
||
|
||
def read_int(self, f):
|
||
"""Czyta int32 little-endian"""
|
||
return struct.unpack('<i', f.read(4))[0]
|
||
|
||
def read_double(self, f):
|
||
"""Czyta double jako int32/10000"""
|
||
raw = struct.unpack('<i', f.read(4))[0]
|
||
return raw / 10000.0
|
||
|
||
def read_bool(self, f):
|
||
"""Czyta bool jako byte"""
|
||
return struct.unpack('<?', f.read(1))[0]
|
||
|
||
def read_string(self, f):
|
||
"""Czyta string: [int32 length][bytes data]"""
|
||
length = self.read_int(f)
|
||
if length <= 0:
|
||
return ""
|
||
data = f.read(length)
|
||
# Usuń null terminatory
|
||
return data.decode('utf-8', errors='ignore').rstrip('\x00')
|
||
|
||
def read_variable(self, f):
|
||
"""Czyta zmienną: [int32 type][data]"""
|
||
data_type = self.read_int(f)
|
||
|
||
if data_type == 1: # INTEGER
|
||
value = self.read_int(f)
|
||
return ('INTEGER', value)
|
||
elif data_type == 2: # STRING
|
||
value = self.read_string(f)
|
||
return ('STRING', value)
|
||
elif data_type == 3: # BOOL
|
||
value = self.read_bool(f)
|
||
return ('BOOL', value)
|
||
elif data_type == 4: # DOUBLE
|
||
value = self.read_double(f)
|
||
return ('DOUBLE', value)
|
||
else:
|
||
raise ValueError(f"Unknown data type: {data_type}")
|
||
|
||
def flat_to_indices(self, flat_index):
|
||
"""Konwertuje flat index na wielowymiarowe indeksy"""
|
||
indices = []
|
||
remaining = flat_index
|
||
|
||
for i in range(len(self.dimensions) - 1, -1, -1):
|
||
indices.insert(0, remaining % self.dimensions[i])
|
||
remaining //= self.dimensions[i]
|
||
|
||
return indices
|
||
|
||
def indices_to_flat(self, indices):
|
||
"""Konwertuje wielowymiarowe indeksy na flat index"""
|
||
flat_index = 0
|
||
multiplier = 1
|
||
|
||
for i in range(len(self.dimensions) - 1, -1, -1):
|
||
flat_index += indices[i] * multiplier
|
||
multiplier *= self.dimensions[i]
|
||
|
||
return flat_index
|
||
|
||
def parse(self):
|
||
"""Parsuje plik .mar"""
|
||
with open(self.filepath, 'rb') as f:
|
||
# Czytaj liczbę wymiarów
|
||
dimensions_count = self.read_int(f)
|
||
print(f"Dimensions count: {dimensions_count}")
|
||
|
||
# Czytaj rozmiary wymiarów
|
||
self.total_elements = 1
|
||
for i in range(dimensions_count):
|
||
dim_size = self.read_int(f)
|
||
self.dimensions.append(dim_size)
|
||
self.total_elements *= dim_size
|
||
print(f" Dimension {i}: {dim_size}")
|
||
|
||
print(f"Total elements: {self.total_elements}")
|
||
print(f"Array shape: {self.dimensions}")
|
||
print()
|
||
|
||
# Czytaj elementy (sparse format)
|
||
loaded_count = 0
|
||
try:
|
||
while True:
|
||
# Sprawdź czy są jeszcze dane
|
||
pos = f.tell()
|
||
if f.read(1) == b'':
|
||
break
|
||
f.seek(pos)
|
||
|
||
# Czytaj indeks
|
||
flat_index = self.read_int(f)
|
||
|
||
if flat_index < 0 or flat_index >= self.total_elements:
|
||
print(f"WARNING: Index out of bounds: {flat_index}")
|
||
break
|
||
|
||
# Czytaj zmienną
|
||
var_type, var_value = self.read_variable(f)
|
||
|
||
# Zapisz w sparse dictionary
|
||
self.data[flat_index] = (var_type, var_value)
|
||
loaded_count += 1
|
||
|
||
except struct.error:
|
||
pass # Koniec pliku
|
||
|
||
print(f"Loaded {loaded_count}/{self.total_elements} elements ({100*loaded_count/self.total_elements:.1f}% filled)")
|
||
|
||
def print_summary(self):
|
||
"""Wyświetla podsumowanie"""
|
||
print("\n" + "="*80)
|
||
print(f"File: {self.filepath.name}")
|
||
print(f"Dimensions: {len(self.dimensions)}D array")
|
||
print(f"Shape: {' × '.join(map(str, self.dimensions))}")
|
||
print(f"Total slots: {self.total_elements}")
|
||
print(f"Filled slots: {len(self.data)}")
|
||
print(f"Empty slots: {self.total_elements - len(self.data)}")
|
||
print(f"Fill rate: {100*len(self.data)/self.total_elements:.1f}%")
|
||
print("="*80)
|
||
|
||
def print_data(self, max_items=50):
|
||
"""Wyświetla dane"""
|
||
print("\nData:")
|
||
print("-" * 80)
|
||
|
||
if not self.data:
|
||
print(" (empty)")
|
||
return
|
||
|
||
for i, (flat_index, (var_type, var_value)) in enumerate(sorted(self.data.items())):
|
||
if i >= max_items:
|
||
remaining = len(self.data) - max_items
|
||
print(f" ... and {remaining} more items")
|
||
break
|
||
|
||
indices = self.flat_to_indices(flat_index)
|
||
indices_str = '[' + ']['.join(map(str, indices)) + ']'
|
||
|
||
# Formatuj wartość
|
||
if var_type == 'STRING':
|
||
value_str = f'"{var_value}"'
|
||
elif var_type == 'BOOL':
|
||
value_str = 'true' if var_value else 'false'
|
||
else:
|
||
value_str = str(var_value)
|
||
|
||
print(f" {indices_str:20s} (flat: {flat_index:5d}) = {var_type:8s} {value_str}")
|
||
|
||
def export_to_python(self, output_file=None):
|
||
"""Eksportuje do Pythona jako nested lists"""
|
||
if output_file is None:
|
||
output_file = self.filepath.with_suffix('.py')
|
||
|
||
def create_nested_structure(dims):
|
||
"""Tworzy zagnieżdżoną strukturę list"""
|
||
if len(dims) == 1:
|
||
return [None] * dims[0]
|
||
else:
|
||
return [create_nested_structure(dims[1:]) for _ in range(dims[0])]
|
||
|
||
def set_value(arr, indices, value):
|
||
"""Ustawia wartość w zagnieżdżonej strukturze"""
|
||
for idx in indices[:-1]:
|
||
arr = arr[idx]
|
||
arr[indices[-1]] = value
|
||
|
||
# Stwórz strukturę
|
||
nested = create_nested_structure(self.dimensions)
|
||
|
||
# Wypełnij danymi
|
||
for flat_index, (var_type, var_value) in self.data.items():
|
||
indices = self.flat_to_indices(flat_index)
|
||
set_value(nested, indices, (var_type, var_value))
|
||
|
||
# Zapisz do pliku
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write(f"# Generated from {self.filepath.name}\n")
|
||
f.write(f"# Dimensions: {self.dimensions}\n\n")
|
||
f.write(f"data = {nested!r}\n")
|
||
|
||
print(f"\nExported to: {output_file}")
|
||
|
||
|
||
def main():
|
||
if len(sys.argv) < 2:
|
||
print("Usage: mar_parser.py <file.mar> [--export] [--verbose] [--max-items N]")
|
||
print()
|
||
print("Options:")
|
||
print(" --export Export to Python file")
|
||
print(" --verbose Show all data items")
|
||
print(" --max-items N Show max N items (default: 50)")
|
||
sys.exit(1)
|
||
|
||
filepath = sys.argv[1]
|
||
export = '--export' in sys.argv
|
||
verbose = '--verbose' in sys.argv
|
||
|
||
max_items = 50
|
||
if '--max-items' in sys.argv:
|
||
idx = sys.argv.index('--max-items')
|
||
if idx + 1 < len(sys.argv):
|
||
max_items = int(sys.argv[idx + 1])
|
||
|
||
if verbose:
|
||
max_items = 999999
|
||
|
||
# Parsuj plik
|
||
parser = MultiArrayParser(filepath)
|
||
parser.parse()
|
||
|
||
# Pokaż wyniki
|
||
parser.print_summary()
|
||
parser.print_data(max_items=max_items)
|
||
|
||
# Eksportuj jeśli trzeba
|
||
if export:
|
||
parser.export_to_python()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|