275 lines
9.0 KiB
Python
275 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
# Persona2EPEventCompiler
|
|
# Author: SergeyShemet
|
|
# Version: 1.0
|
|
|
|
import sys
|
|
import json
|
|
import re
|
|
import os
|
|
|
|
def main():
|
|
# Check input parameters
|
|
if len(sys.argv) < 2:
|
|
print("Usage: event_compiler.py input_file [output_file]")
|
|
return
|
|
|
|
input_file = sys.argv[1]
|
|
|
|
# Set output filename
|
|
if len(sys.argv) >= 3:
|
|
output_file = sys.argv[2]
|
|
else:
|
|
output_file = input_file + ".TRANSL"
|
|
|
|
# Read script lines
|
|
with open(input_file, 'r', encoding='utf-8') as f:
|
|
script_lines = f.readlines()
|
|
|
|
# Load command codes from JSON
|
|
with open('ep_scr_cmd.json', 'r') as f:
|
|
cmd_codes_list = json.load(f)
|
|
|
|
# Convert command codes to dictionary
|
|
cmd_codes = {}
|
|
for item in cmd_codes_list:
|
|
for key, value in item.items():
|
|
cmd_codes[key] = int(value, 16)
|
|
|
|
# Initialize arrays
|
|
cmds = [] # Will store dict objects
|
|
subs = [] # Subroutines {name: cpuAddr}
|
|
branches = [] # Branches {name: cpuAddr}
|
|
text = bytearray() # Text data
|
|
outputparams = [] # Final parameters
|
|
outputfile = bytearray() # Output file
|
|
|
|
tempBr1 = tempBr2 = tempBr3 = 0
|
|
cpu = 0 # Command counter
|
|
|
|
# FIRST PASS - Parse script
|
|
for line in script_lines:
|
|
line = line.rstrip('\n')
|
|
line_trimmed = line.strip()
|
|
|
|
# Skip empty lines and comments
|
|
if not line_trimmed or line_trimmed.startswith('//'):
|
|
continue
|
|
|
|
# Check for subroutine
|
|
if line_trimmed.endswith(':'):
|
|
name = line_trimmed[:-1].strip()
|
|
subs.append({'name': name, 'addr': cpu})
|
|
continue
|
|
|
|
# Check for branch
|
|
if line_trimmed.startswith('&'):
|
|
name = line_trimmed[1:].strip()
|
|
branches.append({'name': name, 'addr': cpu})
|
|
continue
|
|
|
|
# Parse command
|
|
match = re.match(r'^(\S+)\s+(.*)$', line_trimmed)
|
|
if match:
|
|
cmd_name = match.group(1)
|
|
cmd_data = match.group(2)
|
|
|
|
cmd_obj = {
|
|
'cpu': cpu,
|
|
'cpuCmd': cmd_name,
|
|
'scriptLine': line_trimmed,
|
|
'params': []
|
|
}
|
|
|
|
# Check for TextShow command
|
|
if 'TextShow' in cmd_name:
|
|
cmd_obj['text'] = cmd_data
|
|
else:
|
|
# Split params by whitespace
|
|
params = cmd_data.split()
|
|
cmd_obj['params'] = params
|
|
|
|
cmds.append(cmd_obj)
|
|
cpu += 1
|
|
else:
|
|
cmd_obj = {
|
|
'cpu': cpu,
|
|
'cpuCmd': line_trimmed,
|
|
'scriptLine': line_trimmed,
|
|
'params': []
|
|
}
|
|
cmds.append(cmd_obj)
|
|
cpu += 1
|
|
|
|
# SECOND PASS - Link and parse command codes
|
|
for cmd in cmds:
|
|
# Handle special markers
|
|
if '!' in cmd['cpuCmd']:
|
|
tempBr1 = cmd['cpu']
|
|
cmd['cpuCmd'] = cmd['cpuCmd'].replace('!', '')
|
|
if '@' in cmd['cpuCmd']:
|
|
tempBr2 = cmd['cpu']
|
|
cmd['cpuCmd'] = cmd['cpuCmd'].replace('@', '')
|
|
if '#' in cmd['cpuCmd']:
|
|
tempBr3 = cmd['cpu']
|
|
cmd['cpuCmd'] = cmd['cpuCmd'].replace('#', '')
|
|
|
|
# Parse command code
|
|
if cmd['cpuCmd'] in cmd_codes:
|
|
cmd['cpuCode'] = cmd_codes[cmd['cpuCmd']]
|
|
else:
|
|
try:
|
|
cmd['cpuCode'] = int(cmd['cpuCmd'], 16)
|
|
except:
|
|
print(f"Command not recognized: {cmd['scriptLine']}")
|
|
cmd['cpuCode'] = 0
|
|
|
|
if cmd['cpuCmd'] == 'TextShow':
|
|
text_start = len(text)
|
|
text_data = cmd['text']
|
|
|
|
i = 0
|
|
text_len = len(text_data)
|
|
|
|
while i < text_len:
|
|
if text_data[i] == '[':
|
|
j = text_data.find(']', i + 1)
|
|
if j != -1:
|
|
hex_str = text_data[i+1:j].replace(' ', '')
|
|
hex_bytes = bytes.fromhex(hex_str)
|
|
# hex_bytes = hex_bytes[::-1]
|
|
text.extend(hex_bytes)
|
|
i = j + 1
|
|
continue
|
|
|
|
if text_data[i] == '\t':
|
|
text.extend([0x31, 0x11])
|
|
i += 1
|
|
continue
|
|
|
|
# Accumulate regular text (including Russian)
|
|
accum = bytearray()
|
|
while i < text_len and text_data[i] != '[' and text_data[i] != '\t':
|
|
# Encode in cp1251
|
|
accum.extend(text_data[i].encode('cp1251'))
|
|
i += 1
|
|
|
|
if len(accum) > 0:
|
|
text.append(len(accum))
|
|
text.append(0x20)
|
|
text.extend(accum)
|
|
if len(text) & 1:
|
|
text.append(0)
|
|
|
|
cmd['params'] = [text_start]
|
|
|
|
else:
|
|
# Parse parameters
|
|
for i, param in enumerate(cmd['params']):
|
|
# Handle special markers
|
|
if param == '!':
|
|
cmd['params'][i] = tempBr1
|
|
elif param == '@':
|
|
cmd['params'][i] = tempBr2
|
|
elif param == '#':
|
|
cmd['params'][i] = tempBr3
|
|
else:
|
|
# Try to parse as hex
|
|
try:
|
|
# if param.startswith('0x'):
|
|
# cmd['params'][i] = int(param, 16)
|
|
# else:
|
|
# # Try to parse as decimal
|
|
cmd['params'][i] = int(param, 16)
|
|
except:
|
|
# Look in branches
|
|
found = False
|
|
for branch in branches:
|
|
if branch['name'] == param:
|
|
cmd['params'][i] = branch['addr']
|
|
found = True
|
|
break
|
|
|
|
if not found:
|
|
# Look in subs (store index, not address)
|
|
for sub_idx, sub in enumerate(subs):
|
|
if sub['name'] == param:
|
|
cmd['params'][i] = sub_idx
|
|
found = True
|
|
break
|
|
|
|
if not found:
|
|
print(f"Could not parse parameter '{param}' in command: {cmd['scriptLine']}")
|
|
cmd['params'][i] = 0
|
|
|
|
# THIRD PASS - Build output file
|
|
# Header
|
|
subroutines_offset = 0x18
|
|
subs_count = len(subs)
|
|
cmd_offset = subs_count * 72 + 0x18
|
|
params_offset = cmd_offset + len(cmds) * 8
|
|
|
|
# Write header
|
|
outputfile.extend(subroutines_offset.to_bytes(4, 'little')) # subroutines offset
|
|
outputfile.extend(subroutines_offset.to_bytes(4, 'little')) # again
|
|
outputfile.extend(subs_count.to_bytes(4, 'little')) # subs count
|
|
outputfile.extend(cmd_offset.to_bytes(4, 'little')) # cmd offset
|
|
outputfile.extend(params_offset.to_bytes(4, 'little')) # params offset
|
|
outputfile.extend(b'\x00\x00\x00\x00') # text offset (placeholder)
|
|
|
|
# Write subroutines
|
|
for sub in subs:
|
|
# Name (64 bytes)
|
|
name_bytes = sub['name'].encode('ascii')
|
|
outputfile.extend(name_bytes)
|
|
outputfile.extend(b'\x00' * (64 - len(name_bytes)))
|
|
|
|
# cpuAddr
|
|
outputfile.extend(sub['addr'].to_bytes(4, 'little'))
|
|
|
|
# Padding 0
|
|
outputfile.extend((0).to_bytes(4, 'little'))
|
|
|
|
# Write commands
|
|
params_offset_current = params_offset
|
|
for cmd in cmds:
|
|
print(cmd)
|
|
# Command code
|
|
outputfile.extend(cmd['cpuCode'].to_bytes(4, 'little'))
|
|
|
|
# Params offset
|
|
outputfile.extend(params_offset_current.to_bytes(4, 'little'))
|
|
|
|
# Add params to outputparams
|
|
for param in cmd['params']:
|
|
if isinstance(param, str):
|
|
try:
|
|
param_val = int(param)
|
|
except:
|
|
param_val = 0
|
|
else:
|
|
param_val = param
|
|
|
|
outputparams.append(param_val)
|
|
params_offset_current += 4
|
|
|
|
# Write outputparams
|
|
for param in outputparams:
|
|
outputfile.extend(param.to_bytes(4, 'little'))
|
|
|
|
# Write text
|
|
text_offset = len(outputfile)
|
|
outputfile.extend(text)
|
|
|
|
# Update text offset in header
|
|
outputfile[0x14:0x18] = text_offset.to_bytes(4, 'little')
|
|
|
|
# Write to file
|
|
with open(output_file, 'wb') as f:
|
|
f.write(outputfile)
|
|
|
|
print(f"Compilation completed successfully!")
|
|
print(f"Output file: {output_file}")
|
|
|
|
if __name__ == "__main__":
|
|
main() |