Skip to content

Instantly share code, notes, and snippets.

@wareya
Created March 6, 2017 12:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wareya/4ccfd0b0190c6e8deeb4b8020875976a to your computer and use it in GitHub Desktop.
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)
#!/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