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

   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 0x007E:{"arguments":1, "istext": 1, "islabel": 0, "push": 1, "pull":-1},
  75 0x007F:{"arguments":2, "istext": 1, "islabel": 0, "push":-1, "pull":-1},
  76 
  77 # unsorted new
  78 #0x0140:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for dialogue, narration
  79 #0x01A9:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # command for voiceover
  80 
  81 # used in CardSelect.dat after a 0x0002 operation, definitely real operations, number of arguments uncertain
  82 #0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  83 #0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  84 #0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  85 
  86 # bogus?
  87 0x007B:{"arguments":3, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # certainly wrong but fixes a lot of problems without adding shitloads of operators
  88 #0x00FE:{"arguments":2, "istext":-1, "islabel":-1, "push":-1, "pull":-1}, # new operation
  89 #0x0090:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  90 #0x0091:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  91 #0x0098:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  92 #0x011B:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  93 #0x0127:{"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1},
  94 0xDBDB:{} # bogus ending operation to keep the list clean
  95 }
  96 
  97 # clipped from bgiop.py
  98 def make_ops():
  99     for op in range(0x800):
 100         if op not in opcodes:
 101             opcodes[op] = {"arguments":0, "istext":-1, "islabel":-1, "push":-1, "pull":-1}
 102 #        if op < 0x18:
 103 #            opcodes[op]["arguments"] = 1
 104 
 105 make_ops()
 106 
 107 def uint(x):
 108     return unpack("<L", x)[0]
 109 
 110 def short(x):
 111     return "0x%04X" % x
 112 def long(x):
 113     return "0x%08X" % x
 114 
 115 if len(argv) < 2:
 116     print("No input given")
 117     exit()
 118 
 119 #print(argv[1])
 120 
 121 first = True
 122 for fn in argv:
 123     if first:
 124         first = False
 125         continue
 126     else:
 127         #print("\nFile:")
 128         #print(fn)
 129         with open(fn, "r+b") as f:
 130             memory = mmap(f.fileno(), 0)
 131             
 132             data = f.read(0x1C)
 133             if data.decode("sjis") != "BurikoCompiledScriptVer1.00\0":
 134                 print("Error: input is not a valid supported script file")
 135                 exit()
 136             data = f.read(0x04)
 137             
 138             header_length = uint(data)-0x04 # ?
 139             
 140             num_defines = uint(f.read(0x04))
 141             
 142             header = f.read(header_length-0x04)
 143             defines = []
 144             
 145             last_end = 0
 146             for i in range(num_defines):
 147                 end = header.find(b'\x00', last_end)
 148                 string = header[last_end:end].decode("sjis")
 149                 defines.append(string)
 150                 last_end = end+1
 151             
 152             real_beginning = header_length+0x04+0x1C # beginning in terms of address indexing of string constants
 153             #real_beginning = 0
 154             
 155             earliest_string = -1
 156             string_addresses = set()
 157             strings = {}
 158             stack = []
 159             
 160             newstack = []
 161             
 162             debug = 0
 163             is_spoken = False
 164             
 165             stack.append(0)
 166             
 167             if debug:
 168                 print("Header length: " + hex(header_length));
 169                 print("Real beginning: " + hex(real_beginning));
 170             
 171             while 1:
 172                 if debug: print(long(f.tell()), end=" ")
 173                 temp = f.read(0x04)
 174                 if temp == b'': break
 175                 operation = uint(temp)
 176                 if operation <= 0xFFFF and operation not in opcodes:
 177                     print(short(operation) + " unknown operation")
 178                     print("Location: "+long(f.tell()))
 179                     print("First string: " + long(earliest_string))
 180                     exit()
 181                 elif operation > 0xFFFF:
 182                     #print("looks like we hit the string table")
 183                     break
 184                 else:
 185                     op = opcodes[operation]
 186                     arguments = []
 187                     for i in range(op["arguments"]):
 188                         arguments.append(uint(f.read(0x04)))
 189                     if debug: 
 190                         print(short(operation), end=': ')
 191                         for i in arguments:
 192                             print(long(i), end=', ')
 193                         print("")
 194                     #if op["pull"] > 0:
 195                     #    stack.pop()
 196                     #if operation == 0x0018: # jump
 197                         #f.seek(stack.pop())
 198                         #print("Jumped")
 199                     #if operation == 0x00F4: # return from script
 200                         #print("returning from script")
 201                         #print(long(f.tell()))
 202                         #break
 203                     #if operation == 0x003F:
 204                         #numargs = arguments[0]
 205                         #newstack = []
 206                         #for i in range(numargs):
 207                         #    newstack.append(stack.pop())
 208                         #print(newstack)
 209                     if operation == 0x0000:
 210                         stack.append(arguments[0])
 211                     if operation == 0x0001:
 212                         stack.append(arguments[0])
 213                     if operation == 0x0002:
 214                         stack.append(arguments[0])
 215                     if operation == 0x0003:
 216                         string_addr = arguments[0]+real_beginning
 217                         if string_addr >= memory.size():
 218                             print("Bogus string address (too large)")
 219                             exit()
 220                         if string_addr < f.tell():
 221                             print("Bogus string address (before us)")
 222                             exit()
 223                         if memory[string_addr-1] != 0:
 224                             print("Probably a bogus string address (comes before a non-null character)")
 225                             exit()
 226                         #print("found real op")
 227                         if earliest_string < 0:
 228                             earliest_string = string_addr
 229                         else:
 230                             earliest_string = min(earliest_string, string_addr)
 231                         startpos = string_addr
 232                         endpos = startpos
 233                         while 1:
 234                             if memory[endpos] == 0: break
 235                             else: endpos += 1
 236                         string = memory[startpos:endpos].decode("sjis", errors="ignore")
 237                         if string_addr not in string_addresses:
 238                             string_addresses.add(string_addr)
 239                             strings[string_addr] = string
 240                             
 241                         if debug: print(string + "(" + long(string_addr) + ")")
 242                         
 243                         stack.append(string)
 244                         
 245                     if operation == 0x007E:
 246                         string_addr = arguments[0]+real_beginning
 247                         
 248                         if earliest_string < 0:
 249                             earliest_string = string_addr
 250                         else:
 251                             earliest_string = min(earliest_string, string_addr)
 252                         
 253                         startpos = string_addr
 254                         endpos = startpos
 255                         while 1:
 256                             if memory[endpos] == 0: break
 257                             else: endpos += 1
 258                         string = memory[startpos:endpos].decode("sjis", errors="ignore")
 259                         
 260                         if string_addr not in string_addresses:
 261                             string_addresses.add(string_addr)
 262                             strings[string_addr] = string
 263                         
 264                     if operation == 0x0140:
 265                         if debug: print(stack)
 266                         string1 = stack.pop()
 267                         string2 = stack.pop()
 268                         flag1 = stack.pop()
 269                         flag2 = stack.pop()
 270                         flag3 = stack.pop() # following newline
 271                         
 272                         line = string1.replace("", "«").replace("", "»")
 273                         
 274                         #if speaker > real_beginning:
 275                             #print("> " + strings[speaker], end=": ")
 276                         
 277                         #if speaker > real_beginning:
 278                         #    print("> " + strings[speaker] + ": " + strings[line])
 279                         #else:
 280                         #    print(strings[line])
 281                         #print(strings[line])strings[line])
 282                         if flag3 == 1:
 283                             print(line, end="\n")
 284                         else:
 285                             print(line, end="")
 286                         #is_spoken = False
 287                     if operation == 0x0141:
 288                         print("\n\n\n", end="")
 289                     if operation == 0x0142:
 290                         print("\n", end="")
 291                     # 0x014B - furigana
 292                     #elif operation > 0x0140 and operation < 0x0160:
 293                         #print("OPERATOR")
 294                         
 295                     #if operation == 0x0141:
 296                         #print(stringstack)
 297                         #line = stringstack.pop()+real_beginning
 298                         #speaker = stringstack.pop()+real_beginning
 299                         #if speaker > real_beginning:
 300                             #print("> " + strings[speaker], end=": ")
 301                         
 302                         #if is_spoken and speaker > real_beginning:
 303                             #print("> " + strings[speaker] + ": " + 
 304                         #is_spoken = False
 305                     #if operation == 0x01A9:
 306                         #line = stack.pop()+real_beginning
 307                         #print("♪" + strings[line])
 308                         #is_spoken = True
 309                         
 310             
 311             # count number of available strings and compare to number of found strings
 312             i = earliest_string
 313             available_strings = 0
 314             while i < memory.size():
 315                 if memory[i] == 0:
 316                     available_strings += 1
 317                 i += 1
 318             
 319             if debug:
 320                 print("First: " + long(earliest_string))
 321                 print("Available: " + str(available_strings))
 322                 print("Found: " + str(len(string_addresses)))
 323                 if available_strings > len(string_addresses):
 324                     print("Did not find all strings")
 325                     exit()
 326                 
 327                 print("Finished cleanly")
 328                 
 329             

BGI (new) (last edited 2017-08-27 20:43:41 by weh)