Stack bytecode. Heavily modified based on someone else's BGI script tools. See also [[BGI (old)]] {{{#!highlight python #!/usr/bin/env python from sys import argv, exit, stdout from mmap import mmap from struct import unpack, iter_unpack def print(s, end="\n"): stdout.buffer.write((str(s) + end).encode('utf-8')) #stdout = open(stdout.name, 'w', newline='\n', encoding='utf-8') #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": 0}, 0x0011:{"arguments":0, "istext": 0, "islabel": 0, "push": 0, "pull": 0}, #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}, 0x007E:{"arguments":1, "istext": 1, "islabel": 0, "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(" 0xFFFF: #print("looks like we hit the string table") break 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["pull"] > 0: # stack.pop() #if operation == 0x0018: # jump #f.seek(stack.pop()) #print("Jumped") #if operation == 0x00F4: # return from script #print("returning from script") #print(long(f.tell())) #break #if operation == 0x003F: #numargs = arguments[0] #newstack = [] #for i in range(numargs): # newstack.append(stack.pop()) #print(newstack) if operation == 0x0000: stack.append(arguments[0]) if operation == 0x0001: stack.append(arguments[0]) if operation == 0x0002: stack.append(arguments[0]) 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", errors="ignore") if string_addr not in string_addresses: string_addresses.add(string_addr) strings[string_addr] = string if debug: print(string + "(" + long(string_addr) + ")") stack.append(string) if operation == 0x007E: string_addr = arguments[0]+real_beginning 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", errors="ignore") if string_addr not in string_addresses: string_addresses.add(string_addr) strings[string_addr] = string if operation == 0x0140: if debug: print(stack) string1 = stack.pop() string2 = stack.pop() flag1 = stack.pop() flag2 = stack.pop() flag3 = stack.pop() # following newline line = string1.replace("《", "«").replace("》", "»") #if speaker > real_beginning: #print("> " + strings[speaker], end=": ") #if speaker > real_beginning: # print("> " + strings[speaker] + ": " + strings[line]) #else: # print(strings[line]) #print(strings[line])strings[line]) if flag3 == 1: print(line, end="\n") else: print(line, end="") #is_spoken = False if operation == 0x0141: print("\n\n\n", end="") if operation == 0x0142: print("\n", end="") # 0x014B - furigana #elif operation > 0x0140 and operation < 0x0160: #print("OPERATOR") #if operation == 0x0141: #print(stringstack) #line = stringstack.pop()+real_beginning #speaker = stringstack.pop()+real_beginning #if speaker > real_beginning: #print("> " + strings[speaker], end=": ") #if is_spoken and speaker > real_beginning: #print("> " + strings[speaker] + ": " + #is_spoken = False #if operation == 0x01A9: #line = stack.pop()+real_beginning #print("♪" + strings[line]) #is_spoken = True # 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 if debug: 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") }}}