Stack bytecode. Heavily modified based on someone else's BGI script tools. See also BGI (new)

   1 #!/usr/bin/env python
   2 
   3 from sys import argv, exit, stdout
   4 from mmap import mmap
   5 from struct import unpack, iter_unpack
   6 
   7 def print(s, end="\n"):
   8     stdout.buffer.write((str(s) + end).encode('utf-8'))
   9 
  10 #stdout = open(stdout.name, 'w', newline='\n', encoding='utf-8')
  11 
  12 #documented (poorly) by bgiop.py
  13 opcodes = {
  14 #-1 = unknown
  15 # push : number of elements pushed to the stack
  16 # pull: number of elements pulled from the stack
  17 # all numbers for push/pull are currently guesses
  18 0x0000:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0},
  19 0x0001:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0},
  20 0x0002:{"arguments":1, "istext": 0, "islabel":-1, "push": 1, "pull": 0},
  21 0x0003:{"arguments":1, "istext": 1, "islabel": 0, "push": 1, "pull": 0},
  22 #0x0004:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  23 #0x0005:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  24 #0x0006:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  25 #0x0007:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  26 0x0008:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0},
  27 0x0009:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0},
  28 0x000A:{"arguments":1, "istext": 0, "islabel": 0, "push": 0, "pull": 0},
  29 #0x000B:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  30 #0x000C:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  31 #0x000D:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  32 #0x000E:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  33 #0x000F:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  34 0x0010:{"arguments":0, "istext": 0, "islabel": 0, "push": 0, "pull": 0},
  35 0x0011:{"arguments":0, "istext": 0, "islabel": 0, "push": 0, "pull": 0},
  36 #0x0012:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  37 #0x0013:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  38 #0x0014:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  39 #0x0015:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  40 #0x0016:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  41 #0x0017:{"arguments":1, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  42 0x0018:{"arguments":0, "istext": 0, "islabel": 1, "push": 0, "pull": 1},
  43 0x0019:{"arguments":1, "istext": 0, "islabel":-1, "push":-1, "pull": 0},
  44 0x001A:{"arguments":0, "istext": 0, "islabel": 1, "push":-1, "pull": 1},
  45 0x001B:{"arguments":0, "istext": 0, "islabel": 1, "push":-1, "pull":-1},
  46 #0x001C:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # executes something from the stack
  47 # 001D
  48 0x001E:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  49 0x001F:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  50 0x0020:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  51 0x0021:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  52 0x0022:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  53 0x0023:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  54 0x0024:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  55 0x0025:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  56 0x0026:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  57 0x0027:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  58 0x0028:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  59 0x0029:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  60 0x002A:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  61 0x002B:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  62 #002C...002F
  63 0x0030:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  64 0x0031:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  65 0x0032:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  66 0x0033:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  67 0x0034:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  68 0x0035:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  69 0x0038:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  70 0x0039:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  71 0x003A:{"arguments":0, "istext": 0, "islabel":-1, "push":-1, "pull":-1},
  72 #003B...003E
  73 0x003F:{"arguments":1, "istext": 0, "islabel":-1, "push":-1, "pull":-1},    
  74 0x007F:{"arguments":2, "istext": 1, "islabel": 0, "push":-1, "pull":-1},
  75 
  76 # unsorted new
  77 #0x0140:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for dialogue, narration
  78 #0x01A9:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for voiceover
  79 
  80 # used in CardSelect.dat after a 0x0002 operation, definitely real operations, number of arguments uncertain
  81 #0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  82 #0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  83 #0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  84 
  85 # bogus?
  86 0x007B:{"arguments":3, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # certainly wrong but fixes a lot of problems without adding shitloads of operators
  87 #0x00FE:{"arguments":2, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  88 #0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  89 #0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  90 #0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  91 #0x011B:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  92 #0x0127:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  93 0xDBDB:{} # bogus ending operation to keep the list clean
  94 }
  95 
  96 # clipped from bgiop.py
  97 def make_ops():
  98     for op in range(0x800):
  99         if op not in opcodes:
 100             opcodes[op] = {"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}
 101 #        if op < 0x18:
 102 #            opcodes[op]["arguments"] = 1
 103 
 104 make_ops()
 105 
 106 def uint(x):
 107     return unpack("<L", x)[0]
 108 
 109 def short(x):
 110     return "0x%04X" % x
 111 def long(x):
 112     return "0x%08X" % x
 113 
 114 if len(argv) < 2:
 115     print("No input given")
 116     exit()
 117 
 118 #print(argv[1])
 119 
 120 first = True
 121 for fn in argv:
 122     if first:
 123         first = False
 124         continue
 125     else:
 126         #print("\nFile:")
 127         #print(fn)
 128         with open(fn, "r+b") as f:
 129             memory = mmap(f.fileno(), 0)
 130             
 131             #data = f.read(0x1C)
 132             #if data.decode("sjis") != "BurikoCompiledScriptVer1.00\0":
 133             #    print("Error: input is not a valid supported script file")
 134             #    exit()
 135             #data = f.read(0x04)
 136             
 137             #header_length = uint(data)-0x04 # ?
 138             
 139             #num_defines = uint(f.read(0x04))
 140             
 141             #header = f.read(header_length-0x04)
 142             #defines = []
 143             
 144             #last_end = 0
 145             #for i in range(num_defines):
 146             #    end = header.find(b'\x00', last_end)
 147             #    string = header[last_end:end].decode("sjis")
 148             #    defines.append(string)
 149             #    last_end = end+1
 150             
 151             #real_beginning = header_length+0x04+0x1C # beginning in terms of address indexing of string constants
 152             real_beginning = 0
 153             
 154             earliest_string = -1
 155             string_addresses = set()
 156             strings = {}
 157             stack = []
 158             
 159             newstack = []
 160             
 161             debug = 0
 162             is_spoken = False
 163             
 164             stack.append(0)
 165             
 166             while 1:
 167                 if debug: print(long(f.tell()), end=" ")
 168                 temp = f.read(0x04)
 169                 if temp == b'': break
 170                 operation = uint(temp)
 171                 if operation <= 0xFFFF and operation not in opcodes:
 172                     print(short(operation) + " unknown operation")
 173                     print("Location: "+long(f.tell()))
 174                     print("First string: " + long(earliest_string))
 175                     exit()
 176                 elif operation > 0xFFFF:
 177                     #print("looks like we hit the string table")
 178                     break
 179                 else:
 180                     op = opcodes[operation]
 181                     arguments = []
 182                     for i in range(op["arguments"]):
 183                         arguments.append(uint(f.read(0x04)))
 184                     if debug: 
 185                         print(short(operation), end=': ')
 186                         for i in arguments:
 187                             print(long(i), end=', ')
 188                         print("")
 189                     #if op["pull"] > 0:
 190                     #    stack.pop()
 191                     #if operation == 0x0018: # jump
 192                         #f.seek(stack.pop())
 193                         #print("Jumped")
 194                     #if operation == 0x00F4: # return from script
 195                         #print("returning from script")
 196                         #print(long(f.tell()))
 197                         #break
 198                     if operation == 0x003F:
 199                         numargs = arguments[0]
 200                         newstack = []
 201                         for i in range(numargs):
 202                             newstack.append(stack.pop())
 203                         #print(newstack)
 204                     if operation == 0x0000:
 205                         stack.append(arguments[0])
 206                     if operation == 0x0001:
 207                         stack.append(arguments[0])
 208                     if operation == 0x0002:
 209                         stack.append(arguments[0])
 210                     if operation == 0x0003:
 211                         string_addr = arguments[0]+real_beginning
 212                         if string_addr >= memory.size():
 213                             print("Bogus string address (too large)")
 214                             exit()
 215                         if string_addr < f.tell():
 216                             print("Bogus string address (before us)")
 217                             exit()
 218                         if memory[string_addr-1] != 0:
 219                             print("Probably a bogus string address (comes before a non-null character)")
 220                             exit()
 221                         #print("found real op")
 222                         if earliest_string < 0:
 223                             earliest_string = string_addr
 224                         else:
 225                             earliest_string = min(earliest_string, string_addr)
 226                         startpos = string_addr
 227                         endpos = startpos
 228                         while 1:
 229                             if memory[endpos] == 0: break
 230                             else: endpos += 1
 231                         string = memory[startpos:endpos].decode("sjis", errors="ignore")
 232                         if string_addr not in string_addresses:
 233                             string_addresses.add(string_addr)
 234                             strings[string_addr] = string
 235                             
 236                         if debug: print(string + "(" + long(string_addr) + ")")
 237                         
 238                         stack.append(string)
 239                     if operation == 0x0140:
 240                         #if debug: print(newstack)
 241                         string1 = newstack.pop()
 242                         string2 = newstack.pop()
 243                         flag1 = newstack.pop()
 244                         flag2 = newstack.pop()
 245                         flag3 = newstack.pop() # following newline
 246                         
 247                         line = string1.replace("", "«").replace("", "»")
 248                         
 249                         #if speaker > real_beginning:
 250                             #print("> " + strings[speaker], end=": ")
 251                         
 252                         #if speaker > real_beginning:
 253                         #    print("> " + strings[speaker] + ": " + strings[line])
 254                         #else:
 255                         #    print(strings[line])
 256                         #print(strings[line])strings[line])
 257                         if flag3 == 1:
 258                             print(line, end="\n")
 259                         else:
 260                             print(line, end="")
 261                         #is_spoken = False
 262                     if operation == 0x0141:
 263                         print("\n\n\n", end="")
 264                     if operation == 0x0142:
 265                         print("\n", end="")
 266                     # 0x014B - furigana
 267                     #elif operation > 0x0140 and operation < 0x0160:
 268                         #print("OPERATOR")
 269                         
 270                     #if operation == 0x0141:
 271                         #print(stringstack)
 272                         #line = stringstack.pop()+real_beginning
 273                         #speaker = stringstack.pop()+real_beginning
 274                         #if speaker > real_beginning:
 275                             #print("> " + strings[speaker], end=": ")
 276                         
 277                         #if is_spoken and speaker > real_beginning:
 278                             #print("> " + strings[speaker] + ": " + 
 279                         #is_spoken = False
 280                     #if operation == 0x01A9:
 281                         #line = stack.pop()+real_beginning
 282                         #print("♪" + strings[line])
 283                         #is_spoken = True
 284                         
 285             
 286             # count number of available strings and compare to number of found strings
 287             i = earliest_string
 288             available_strings = 0
 289             while i < memory.size():
 290                 if memory[i] == 0:
 291                     available_strings += 1
 292                 i += 1
 293             
 294             #print("First: " + long(earliest_string))
 295             #print("Available: " + str(available_strings))
 296             #print("Found: " + str(len(string_addresses)))
 297             #if available_strings > len(string_addresses):
 298             #    print("Did not find all strings")
 299             #    exit()
 300             #
 301             #print("Finished cleanly")
 302                 
 303             

BGI (old) (last edited 2017-08-28 05:18:53 by weh)