view pytouhou/formats/anm0.py @ 377:70e2ed71b09c

Add meaningful exceptions in format parsing.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 29 Aug 2012 18:34:28 +0200
parents da53bc29b94a
children 88e2a2485b2b
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##

"""ANM0 files handling.

This module provides classes for handling the ANM0 file format.
The ANM0 format is a format used in Touhou 6: EoSD to describe sprites
and animations.
Almost everything rendered in the game is described by an ANM0 file.
"""

from struct import pack, unpack
from pytouhou.utils.helpers import read_string, get_logger

from pytouhou.formats import WrongFormatError


logger = get_logger(__name__)

#TODO: refactor/clean up


class Script(list):
    def __init__(self):
        list.__init__(self)
        self.interrupts = {}



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: ('', None),
                     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)}


    def __init__(self):
        self.size = (0, 0)
        self.first_name = None
        self.secondary_name = None
        self.sprites = {}
        self.scripts = {}


    @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)

        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 = cls()

        self.size = (width, height)

        # 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?


        # 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
        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))
                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.scripts[i].append((time, opcode, args))
                if opcode == 0:
                    break

            # 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

        return self