Created
March 6, 2017 12:14
-
-
Save wareya/4ccfd0b0190c6e8deeb4b8020875976a to your computer and use it in GitHub Desktop.
Dumps Magical Charming's compiled script files (incomplete but works for trivial uses)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from sys import argv, exit | |
from mmap import mmap | |
from struct import unpack, iter_unpack | |
#documented (poorly) by bgiop.py | |
opcodes = { | |
#-1 = unknown | |
# push : number of elements pushed to the stack | |
# pull: number of elements pulled from the stack | |
# all numbers for push/pull are currently guesses | |
0x0000:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0}, | |
0x0001:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0}, | |
0x0002:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0}, | |
0x0003:{"arguments":1, "istext": 1, "islabel": 0, "push": 1, "pull": 0}, | |
#0x0004:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0005:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0006:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0007:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
0x0008:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0}, | |
0x0009:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0}, | |
0x000A:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0}, | |
#0x000B:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x000C:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x000D:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x000E:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x000F:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
0x0010:{"arguments":0, "istext": 0, "islabel": 0, "push": 0, "pull": 1}, | |
0x0011:{"arguments":0, "istext": 0, "islabel": 0, "push": 0, "pull": 1}, | |
#0x0012:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0013:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0014:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0015:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0016:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0017:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
0x0018:{"arguments":0, "istext": 0, "islabel": 1, "push": 0, "pull": 1}, | |
0x0019:{"arguments":1, "istext": 0, "islabel":-1, "push":-1, "pull": 0}, | |
0x001A:{"arguments":0, "istext": 0, "islabel": 1, "push":-1, "pull": 1}, | |
0x001B:{"arguments":0, "istext": 0, "islabel": 1, "push":-1, "pull":-1}, | |
#0x001C:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # executes something from the stack | |
# 001D | |
0x001E:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x001F:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0020:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0021:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0022:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0023:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0024:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0025:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0026:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0027:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0028:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0029:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x002A:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x002B:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
#002C...002F | |
0x0030:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0031:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0032:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0033:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0034:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0035:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0038:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x0039:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x003A:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
#003B...003E | |
0x003F:{"arguments":1, "istext": 0, "islabel":-1, "push":-1, "pull":-1}, | |
0x007F:{"arguments":2, "istext": 1, "islabel": 0, "push":-1, "pull":-1}, | |
# unsorted new | |
0x0140:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for dialogue, narration | |
0x01A9:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for voiceover | |
# used in CardSelect.dat after a 0x0002 operation, definitely real operations, number of arguments uncertain | |
#0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
# bogus? | |
0x007B:{"arguments":3, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # certainly wrong but fixes a lot of problems without adding shitloads of operators | |
#0x00FE:{"arguments":2, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation | |
#0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x011B:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
#0x0127:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, | |
0xDBDB:{} # bogus ending operation to keep the list clean | |
} | |
# clipped from bgiop.py | |
def make_ops(): | |
for op in range(0x800): | |
if op not in opcodes: | |
opcodes[op] = {"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1} | |
# if op < 0x18: | |
# opcodes[op]["arguments"] = 1 | |
make_ops() | |
def uint(x): | |
return unpack("<L", x)[0] | |
def short(x): | |
return "0x%04X" % x | |
def long(x): | |
return "0x%08X" % x | |
if len(argv) < 1: | |
print("No input given") | |
exit() | |
with open(argv[1], "r+b") as f: | |
memory = mmap(f.fileno(), 0) | |
data = f.read(0x1C) | |
if data.decode("sjis") != "BurikoCompiledScriptVer1.00\0": | |
print("Error: input is not a valid supported script file") | |
exit() | |
data = f.read(0x04) | |
header_length = uint(data)-0x04 # ? | |
num_defines = uint(f.read(0x04)) | |
header = f.read(header_length-0x04) | |
defines = [] | |
last_end = 0 | |
for i in range(num_defines): | |
end = header.find(b'\x00', last_end) | |
string = header[last_end:end].decode("sjis") | |
defines.append(string) | |
last_end = end+1 | |
real_beginning = header_length+0x04+0x1C # beginning in terms of address indexing of string constants | |
earliest_string = -1 | |
string_addresses = set() | |
strings = {} | |
stack = [] | |
debug = 0 | |
while 1: | |
if debug: print(long(f.tell()), end=" ") | |
operation = uint(f.read(0x04)) | |
if operation not in opcodes: | |
print(short(operation) + " unknown operation") | |
print("Location: "+long(f.tell())) | |
print("First string: " + long(earliest_string)) | |
exit() | |
else: | |
op = opcodes[operation] | |
arguments = [] | |
for i in range(op["arguments"]): | |
arguments.append(uint(f.read(0x04))) | |
if debug: | |
print(short(operation), end=': ') | |
for i in arguments: | |
print(long(i), end=', ') | |
print() | |
if op["push"] > 0: | |
if op["arguments"] != 0: | |
stack.append(arguments[0]) | |
else: | |
print("Bogus push operation") | |
exit() | |
if op["pull"] > 0: | |
stack.pop() | |
#if operation == 0x0018: # jump | |
#f.seek(stack.pop()) | |
#print("Jumped") | |
if operation == 0x00F4: # return from script | |
break | |
if operation == 0x0003: | |
string_addr = arguments[0]+real_beginning | |
if string_addr >= memory.size(): | |
print("Bogus string address (too large)") | |
exit() | |
if string_addr < f.tell(): | |
print("Bogus string address (before us)") | |
exit() | |
if memory[string_addr-1] != 0: | |
print("Probably a bogus string address (comes before a non-null character)") | |
exit() | |
#print("found real op") | |
if earliest_string < 0: | |
earliest_string = string_addr | |
else: | |
earliest_string = min(earliest_string, string_addr) | |
startpos = string_addr | |
endpos = startpos | |
while 1: | |
if memory[endpos] == 0: break | |
else: endpos += 1 | |
string = memory[startpos:endpos].decode("sjis") | |
if string_addr not in string_addresses: | |
string_addresses.add(string_addr) | |
strings[string_addr] = string | |
if debug: print(string + "(" + long(string_addr) + ")") | |
if operation == 0x0140: | |
line = stack.pop()+real_beginning | |
speaker = stack.pop()+real_beginning | |
if speaker > real_beginning: | |
print("> " + strings[speaker], end=": ") | |
print(strings[line]) | |
if operation == 0x01A9: | |
line = stack.pop()+real_beginning | |
print("♪" + strings[line]) | |
# count number of available strings and compare to number of found strings | |
i = earliest_string | |
available_strings = 0 | |
while i < memory.size(): | |
if memory[i] == 0: | |
available_strings += 1 | |
i += 1 | |
print("First: " + long(earliest_string)) | |
print("Available: " + str(available_strings)) | |
print("Found: " + str(len(string_addresses))) | |
if available_strings > len(string_addresses): | |
print("Did not find all strings") | |
exit() | |
print("Finished cleanly") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment