Files
P2EP_Export/_Utils/Event/event_compiler.py
2026-03-19 17:13:28 +05:00

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()