view pytouhou/formats/exe.py @ 229:5afc75f71fed

Add “SHT” support to EoSD, and do a little cleanup.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 30 Dec 2011 18:37:06 +0100
parents
children 1c24a6d93c1b
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
##
## 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.
##

from struct import Struct, unpack
from pytouhou.utils.pe import PEFile

from pytouhou.utils.helpers import get_logger

logger = get_logger(__name__)


SQ2 = 2. ** 0.5 / 2.


class Shot(object):
    def __init__(self):
        self.interval = 0
        self.delay = 0
        self.pos = (0., 0.)
        self.hitbox = (0., 0.)
        self.angle = 0.
        self.speed = 0.
        self.damage = 0
        self.orb = 0
        self.shot_type = 0
        self.sprite = 0
        self.unknown1 = None

    def __repr__(self):
        return '(%d, %d, %f, %f, %d, %d, %d, %d, %d)' % (self.interval, self.delay, self.angle, self.speed, self.damage, self.orb, self.shot_type, self.sprite, self.unknown1)


class SHT(object):
    def __init__(self):
        #self.unknown1 = None
        #self.bombs = 0.
        #self.unknown2 = None
        self.hitbox = 4.
        self.graze_hitbox = 42.
        self.autocollection_speed = 8.
        self.item_hitbox = 38.
        # No percentage_of_cherry_loss_on_die
        self.point_of_collection = 128 #TODO: find the real default.
        self.horizontal_vertical_speed = 0.
        self.horizontal_vertical_focused_speed = 0.
        self.diagonal_speed = 0.
        self.diagonal_focused_speed = 0.
        self.shots = {}


    @classmethod
    def find_character_records(self, file, pe_file):
        format = Struct('<4f2I')
        data_section = [section for section in pe_file.sections if section.Name.startswith('.data')][0]
        text_section = [section for section in pe_file.sections if section.Name.startswith('.text')][0]
        data_va = pe_file.image_base + data_section.VirtualAddress
        text_va = pe_file.image_base + text_section.VirtualAddress

        for addr in xrange(data_va, data_va + data_section.SizeOfRawData, 4):
            for character_id in xrange(4):
                pe_file.seek_to_va(addr + character_id * 24)
                speed1, speed2, speed3, speed4, ptr1, ptr2 = format.unpack(file.read(format.size))

                if not (all(0. < x < 8. for x in (speed1, speed2, speed3, speed4))
                        and speed2 <= speed1
                        and 0 <= ptr1 - text_va < text_section.SizeOfRawData - 8
                        and 0 <= ptr2 - text_va < text_section.SizeOfRawData - 8):
                    break

                pe_file.seek_to_va(ptr1 + 4)
                shtptr1, = unpack('<I', file.read(4))
                pe_file.seek_to_va(ptr2 + 4)
                shtptr2, = unpack('<I', file.read(4))

                if not (0 <= shtptr1 - data_va < data_section.SizeOfRawData
                        and 0 <= shtptr2 - data_va < data_section.SizeOfRawData):
                    break
            else: # XXX: Obscure python feature! This gets executed if there were no break!
                yield addr


    @classmethod
    def read(cls, file):
        pe_file = PEFile(file)

        character_records_va = list(cls.find_character_records(file, pe_file))[0]

        characters = []
        shots_offsets = []
        for character in xrange(4):
            sht = cls()

            pe_file.seek_to_va(character_records_va + 6*4*character)

            data = unpack('<4f2I', file.read(6*4))
            (speed, speed_focused, speed_unknown1, speed_unknown2,
             shots_func_offset, shots_func_offset_focused) = data

            sht.horizontal_vertical_speed = speed
            sht.horizontal_vertical_focused_speed = speed_focused
            sht.diagonal_speed = speed * SQ2
            sht.diagonal_focused_speed = speed_focused * SQ2

            # Read from “push” operand
            pe_file.seek_to_va(shots_func_offset + 4)
            offset = unpack('<I', file.read(4))[0]
            shots_offsets.append(offset)

            characters.append(sht)

        character = 0
        for shots_offset in shots_offsets:
            pe_file.seek_to_va(shots_offset)

            level_count = 9
            levels = []
            for i in xrange(level_count):
                shots_count, power, offset = unpack('<III', file.read(3*4))
                levels.append((shots_count, power, offset))

            sht = characters[character]
            sht.shots = {}

            for shots_count, power, offset in levels:
                sht.shots[power] = []
                pe_file.seek_to_va(offset)

                for i in xrange(shots_count):
                    shot = Shot()

                    data = unpack('<HH6fHBBhh', file.read(36))
                    (shot.interval, shot.delay, x, y, hitbox_x, hitbox_y,
                     shot.angle, shot.speed, shot.damage, shot.orb, shot.shot_type,
                     shot.sprite, shot.unknown1) = data

                    shot.pos = (x, y)
                    shot.hitbox = (hitbox_x, hitbox_y)

                    sht.shots[power].append(shot)

            character += 1


        return characters