comparison pytouhou/formats/std.py @ 204:88361534c77e

Add some documentation (argh, so much left to document!)
author Thibaut Girka <thib@sitedethib.com>
date Tue, 01 Nov 2011 13:46:03 +0100
parents c596a1a69402
children 61adb5453e46
comparison
equal deleted inserted replaced
203:df8b2ab54639 204:88361534c77e
9 ## This program is distributed in the hope that it will be useful, 9 ## This program is distributed in the hope that it will be useful,
10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of 10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ## GNU General Public License for more details. 12 ## GNU General Public License for more details.
13 ## 13 ##
14
15 """Stage Definition (STD) files handling.
16
17 This module provides classes for handling the Stage Definition file format.
18 The STD file format is a format used in Touhou 6: EoSD to describe non-gameplay
19 aspects of a stage: its name, its music, 3D models composing its background,
20 and various scripted events such as camera movement.
21 """
14 22
15 23
16 from struct import pack, unpack, calcsize 24 from struct import pack, unpack, calcsize
17 from pytouhou.utils.helpers import read_string, get_logger 25 from pytouhou.utils.helpers import read_string, get_logger
18 26
27 self.quads = quads or [] 35 self.quads = quads or []
28 36
29 37
30 38
31 class Stage(object): 39 class Stage(object):
40 """Handle Touhou 6 Stage Definition files.
41
42 Stage Definition files are structured files describing non-gameplay aspects
43 aspects of a stage. They are split in a header an 3 additional sections.
44
45 The header contains the name of the stage, the background musics (BGM) used,
46 as well as the number of quads and objects composing the background.
47 The first section describes the models composing the background, whereas
48 the second section dictates how they are used.
49 The last section describes the changes to the camera, fog, and other things.
50
51 Instance variables:
52 name -- name of the stage
53 bgms -- list of (name, path) describing the different background musics used
54 models -- list of Model objects
55 object_instances -- list of instances of the aforementioned models
56 script -- stage script (camera, fog, etc.)
57 """
58
32 _instructions = {0: ('fff', 'set_viewpos'), 59 _instructions = {0: ('fff', 'set_viewpos'),
33 1: ('BBBxff', 'set_fog'), 60 1: ('BBBxff', 'set_fog'),
34 2: ('fff', 'set_viewpos2'), 61 2: ('fff', 'set_viewpos2'),
35 3: ('Ixxxxxxxx', 'start_interpolating_viewpos2'), 62 3: ('Ixxxxxxxx', 'start_interpolating_viewpos2'),
36 4: ('Ixxxxxxxx', 'start_interpolating_fog')} 63 4: ('Ixxxxxxxx', 'start_interpolating_fog')}
43 self.script = [] 70 self.script = []
44 71
45 72
46 @classmethod 73 @classmethod
47 def read(cls, file): 74 def read(cls, file):
75 """Read a Stage Definition file.
76
77 Raise an exception if the file is invalid.
78 Return a STD instance otherwise.
79 """
80
48 stage = Stage() 81 stage = Stage()
49 82
50 nb_models, nb_faces = unpack('<HH', file.read(4)) 83 nb_models, nb_faces = unpack('<HH', file.read(4))
51 object_instances_offset, script_offset = unpack('<II', file.read(8)) 84 object_instances_offset, script_offset = unpack('<II', file.read(8))
52 if file.read(4) != b'\x00\x00\x00\x00': 85 if file.read(4) != b'\x00\x00\x00\x00':
69 # Read model definitions 102 # Read model definitions
70 offsets = unpack('<%s' % ('I' * nb_models), file.read(4 * nb_models)) 103 offsets = unpack('<%s' % ('I' * nb_models), file.read(4 * nb_models))
71 for offset in offsets: 104 for offset in offsets:
72 model = Model() 105 model = Model()
73 file.seek(offset) 106 file.seek(offset)
107
108 # Read model header
74 id_, unknown, x, y, z, width, height, depth = unpack('<HHffffff', file.read(28)) 109 id_, unknown, x, y, z, width, height, depth = unpack('<HHffffff', file.read(28))
75 model.unknown = unknown 110 model.unknown = unknown
76 model.bounding_box = x, y, z, width, height, depth #TODO: check 111 model.bounding_box = x, y, z, width, height, depth #TODO: check
112
113 # Read model quads
77 while True: 114 while True:
78 unknown, size = unpack('<HH', file.read(4)) 115 unknown, size = unpack('<HH', file.read(4))
79 if unknown == 0xffff: 116 if unknown == 0xffff:
80 break 117 break
81 if size != 0x1c: 118 if size != 0x1c:
114 151
115 return stage 152 return stage
116 153
117 154
118 def write(self, file): 155 def write(self, file):
156 """Write to a Stage Definition file."""
119 model_offsets = [] 157 model_offsets = []
120 second_section_offset = 0 158 second_section_offset = 0
121 third_section_offset = 0 159 third_section_offset = 0
122 160
123 nb_faces = sum(len(model.quads) for model in self.models) 161 nb_faces = sum(len(model.quads) for model in self.models)
124 162
125 # Write header 163 # Write header (offsets, number of quads, name and background musics)
126 file.write(pack('<HH', len(self.models), nb_faces)) #TODO: nb_faces 164 file.write(pack('<HH', len(self.models), nb_faces))
127 file.write(pack('<II', 0, 0)) 165 file.write(pack('<II', 0, 0))
128 file.write(pack('<I', 0)) 166 file.write(pack('<I', 0))
129 file.write(pack('<128s', self.name.encode('shift_jis'))) 167 file.write(pack('<128s', self.name.encode('shift_jis')))
130 for bgm_name, bgm_path in self.bgms: 168 for bgm_name, bgm_path in self.bgms:
131 file.write(pack('<128s', bgm_name.encode('shift_jis'))) 169 file.write(pack('<128s', bgm_name.encode('shift_jis')))
132 for bgm_name, bgm_path in self.bgms: 170 for bgm_name, bgm_path in self.bgms:
133 file.write(pack('<128s', bgm_path.encode('ascii'))) 171 file.write(pack('<128s', bgm_path.encode('ascii')))
134 file.write(b'\x00\x00\x00\x00' * len(self.models)) 172 file.write(b'\x00\x00\x00\x00' * len(self.models))
135 173
136 # Write first section 174 # Write first section (models)
137 for i, model in enumerate(self.models): 175 for i, model in enumerate(self.models):
138 model_offsets.append(file.tell()) 176 model_offsets.append(file.tell())
139 file.write(pack('<HHffffff', i, model.unknown, *model.bounding_box)) 177 file.write(pack('<HHffffff', i, model.unknown, *model.bounding_box))
140 for quad in model.quads: 178 for quad in model.quads:
141 file.write(pack('<HH', 0x00, 0x1c)) 179 file.write(pack('<HH', 0x00, 0x1c))
142 file.write(pack('<Hxxfffff', *quad)) 180 file.write(pack('<Hxxfffff', *quad))
143 file.write(pack('<HH', 0xffff, 4)) 181 file.write(pack('<HH', 0xffff, 4))
144 182
145 # Write second section 183 # Write second section (object instances)
146 second_section_offset = file.tell() 184 second_section_offset = file.tell()
147 for obj_id, x, y, z in self.object_instances: 185 for obj_id, x, y, z in self.object_instances:
148 file.write(pack('<HHfff', obj_id, 256, x, y, z)) 186 file.write(pack('<HHfff', obj_id, 256, x, y, z))
149 file.write(b'\xff' * 16) 187 file.write(b'\xff' * 16)
150 188
151 # Write third section 189 # Write third section (script)
152 third_section_offset = file.tell() 190 third_section_offset = file.tell()
153 for frame, opcode, args in self.script: 191 for frame, opcode, args in self.script:
154 size = calcsize(self._instructions[opcode][0]) 192 size = calcsize(self._instructions[opcode][0])
155 file.write(pack('<IHH%s' % self._instructions[opcode][0], frame, opcode, size, *args)) 193 file.write(pack('<IHH%s' % self._instructions[opcode][0], frame, opcode, size, *args))
156 file.write(b'\xff' * 20) 194 file.write(b'\xff' * 20)