Mercurial > touhou
diff pytouhou/formats/anm0.py @ 429:40d5f3083ebc
Implement PCB’s ANM2 format and vm.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sat, 03 Aug 2013 15:48:57 +0200 |
parents | 88e2a2485b2b |
children |
line wrap: on
line diff
--- a/pytouhou/formats/anm0.py +++ b/pytouhou/formats/anm0.py @@ -24,6 +24,7 @@ from struct import pack, unpack from pytouhou.utils.helpers import read_string, get_logger from pytouhou.formats import WrongFormatError +from pytouhou.formats.thtx import Texture logger = get_logger(__name__) @@ -39,36 +40,84 @@ class Script(list): class ANM0(object): - _instructions = {0: ('', 'delete'), - 1: ('I', 'set_sprite'), - 2: ('ff', 'set_scale'), - 3: ('I', 'set_alpha'), - 4: ('BBBx', 'set_color'), - 5: ('I', 'jump'), - 7: ('', 'toggle_mirrored'), - 9: ('fff', 'set_3d_rotations'), - 10: ('fff', 'set_3d_rotations_speed'), - 11: ('ff', 'set_scale_speed'), - 12: ('ii', 'fade'), - 13: ('', 'set_blendmode_add'), - 14: ('', 'set_blendmode_alphablend'), - 15: ('', 'keep_still'), - 16: ('ii', 'set_random_sprite'), - 17: ('fff', 'set_3d_translation'), - 18: ('fffi', 'move_to_linear'), - 19: ('fffi', 'move_to_decel'), - 20: ('fffi', 'move_to_accel'), - 21: ('', 'wait'), - 22: ('i', 'interrupt_label'), - 23: ('', 'set_corner_relative_placement'), - 24: ('', 'wait_ex'), - 25: ('i', 'set_allow_offset'), #TODO: better name - 26: ('i', 'set_automatic_orientation'), - 27: ('f', 'shift_texture_x'), - 28: ('f', 'shift_texture_y'), - 29: ('i', 'set_visible'), - 30: ('ffi', 'scale_in'), - 31: ('i', None)} + _instructions = {0: {0: ('', 'delete'), + 1: ('I', 'set_sprite'), + 2: ('ff', 'set_scale'), + 3: ('I', 'set_alpha'), + 4: ('BBBx', 'set_color'), + 5: ('I', 'jump'), + 7: ('', 'toggle_mirrored'), + 9: ('fff', 'set_3d_rotations'), + 10: ('fff', 'set_3d_rotations_speed'), + 11: ('ff', 'set_scale_speed'), + 12: ('ii', 'fade'), + 13: ('', 'set_blendmode_add'), + 14: ('', 'set_blendmode_alphablend'), + 15: ('', 'keep_still'), + 16: ('ii', 'set_random_sprite'), + 17: ('fff', 'set_3d_translation'), + 18: ('fffi', 'move_to_linear'), + 19: ('fffi', 'move_to_decel'), + 20: ('fffi', 'move_to_accel'), + 21: ('', 'wait'), + 22: ('i', 'interrupt_label'), + 23: ('', 'set_corner_relative_placement'), + 24: ('', 'wait_ex'), + 25: ('i', 'set_allow_offset'), #TODO: better name + 26: ('i', 'set_automatic_orientation'), + 27: ('f', 'shift_texture_x'), + 28: ('f', 'shift_texture_y'), + 29: ('i', 'set_visible'), + 30: ('ffi', 'scale_in'), + 31: ('i', None)}, + + 2: {0: ('', 'noop'), + 1: ('', 'delete'), + 2: ('', 'keep_still'), + 3: ('I', 'set_sprite'), + 4: ('II', 'jump_bis'), + 5: ('III', 'jump_ex'), + 6: ('fff', 'set_3d_translation'), + 7: ('ff', 'set_scale'), + 8: ('I', 'set_alpha'), + 9: ('BBBx', 'set_color'), + 10: ('', 'toggle_mirrored'), + 12: ('fff', 'set_3d_rotations'), + 13: ('fff', 'set_3d_rotations_speed'), + 14: ('ff', 'set_scale_speed'), + 15: ('ii', 'fade'), + 16: ('I', 'set_blendmode'), + 17: ('fffi', 'move_to_linear'), + 18: ('fffi', 'move_to_decel'), + 19: ('fffi', 'move_to_accel'), + 20: ('', 'wait'), + 21: ('i', 'interrupt_label'), + 22: ('', 'set_corner_relative_placement'), + 23: ('', 'wait_ex'), + 24: ('i', 'set_allow_offset'), #TODO: better name + 25: ('i', 'set_automatic_orientation'), + 26: ('f', 'shift_texture_x'), + 27: ('f', 'shift_texture_y'), + 28: ('i', 'set_visible'), + 29: ('ffi', 'scale_in'), + 30: ('i', None), + 31: ('I', None), + 32: ('IIfff', 'move_in_linear_bis'), + 33: ('IIBBBx', 'change_color_in'), + 34: ('III', 'fade_bis'), + 35: ('IIfff', 'rotate_in_bis'), + 36: ('IIff', 'scale_in_bis'), + 37: ('II', 'set_int'), + 38: ('ff', 'set_float'), + 42: ('ff', 'decrement_float'), + 50: ('fff', 'add_float'), + 52: ('fff', 'substract_float'), + 55: ('III', 'divide_int'), + 59: ('II', 'set_random_int'), + 60: ('ff', 'set_random_float'), + 69: ('IIII', 'branch_if_not_equal'), + 79: ('I', 'wait_duration'), + 80: ('I', None)}} def __init__(self): @@ -82,67 +131,113 @@ class ANM0(object): @classmethod def read(cls, file): - nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) - width, height, format, unknown1 = unpack('<IIII', file.read(16)) - first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) - version, unknown2, thtxoffset, hasdata, nextoffset, zero2 = unpack('<IIIIII', file.read(24)) - if version != 0: - raise WrongFormatError(version) - assert (zero1, zero2) == (0, 0) + anm_list = [] + start_offset = 0 + while True: + file.seek(start_offset) + nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) + width, height, fmt, unknown1 = unpack('<IIII', file.read(16)) + first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) + version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24)) - sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] - script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] + if version == 0: + assert zero1 == 0 + assert unknown3 == 0 + assert has_data == 0 + elif version == 2: + assert zero1 == 0 + assert secondary_name_offset == 0 + assert has_data == 1 # Can be false but we don’t support that yet. + else: + raise WrongFormatError(version) + + instructions = cls._instructions[version] - self = cls() + sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] + script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] - self.size = (width, height) + self = cls() + + self.size = (width, height) + self.version = version - # Names - if first_name_offset: - file.seek(first_name_offset) - self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? - if secondary_name_offset: - file.seek(secondary_name_offset) - self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? + # Names + if first_name_offset: + file.seek(start_offset + first_name_offset) + self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? + if secondary_name_offset: + file.seek(start_offset + secondary_name_offset) + self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? + + + # Sprites + for offset in sprite_offsets: + file.seek(start_offset + offset) + idx, x, y, width, height = unpack('<Iffff', file.read(20)) + self.sprites[idx] = x, y, width, height - # Sprites - file.seek(64) - self.sprites = {} - for offset in sprite_offsets: - file.seek(offset) - idx, x, y, width, height = unpack('<Iffff', file.read(20)) - self.sprites[idx] = x, y, width, height + # Scripts + for i, offset in script_offsets: + self.scripts[i] = Script() + instruction_offsets = [] + file.seek(start_offset + offset) + while True: + instruction_offsets.append(file.tell() - (start_offset + offset)) + if version == 0: + time, opcode, size = unpack('<HBB', file.read(4)) + elif version == 2: + opcode, size, time, mask = unpack('<HHHH', file.read(8)) + if opcode == 0xffff: + break + size -= 8 + data = file.read(size) + if opcode in instructions: + args = unpack('<%s' % instructions[opcode][0], data) + else: + args = (data,) + logger.warn('unknown opcode %d', opcode) + self.scripts[i].append((time, opcode, args)) + if version == 0 and opcode == 0: + break - # Scripts - self.scripts = {} - for i, offset in script_offsets: - self.scripts[i] = Script() - instruction_offsets = [] - file.seek(offset) - while True: - instruction_offsets.append(file.tell() - offset) - time, opcode, size = unpack('<HBB', file.read(4)) + # Translate offsets to instruction pointers and register interrupts + for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): + time, opcode, args = instr + if version == 0: + if opcode == 5: + args = (instruction_offsets.index(args[0]),) + elif opcode == 22: + interrupt = args[0] + self.scripts[i].interrupts[interrupt] = j + 1 + elif version == 2: + if opcode == 4: + args = (instruction_offsets.index(args[0]), args[1]) + elif opcode == 5: + args = (args[0], instruction_offsets.index(args[1]), args[2]) + elif opcode == 21: + interrupt = args[0] + self.scripts[i].interrupts[interrupt] = j + 1 + elif opcode == 69: + args = (args[0], args[1], instruction_offsets.index(args[2]), args[3]) + self.scripts[i][j] = time, opcode, args + + # Texture + if has_data: + file.seek(start_offset + texture_offset) + magic = file.read(4) + assert magic == b'THTX' + zero, fmt, width, height, size = unpack('<HHHHI', file.read(12)) + assert zero == 0 data = file.read(size) - if opcode in cls._instructions: - args = unpack('<%s' % cls._instructions[opcode][0], data) - else: - args = (data,) - logger.warn('unknown opcode %d', opcode) + self.texture = Texture(width, height, fmt, data) - self.scripts[i].append((time, opcode, args)) - if opcode == 0: - break + anm_list.append(self) - # Translate offsets to instruction pointers and register interrupts - for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): - time, opcode, args = instr - if opcode == 5: - args = (instruction_offsets.index(args[0]),) - elif opcode == 22: - interrupt = args[0] - self.scripts[i].interrupts[interrupt] = j + 1 - self.scripts[i][j] = time, opcode, args + if next_offset: + start_offset += next_offset + else: + break - return self + return anm_list