Mercurial > touhou
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) |