1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
|
# coding: utf-8
import os
from new import classobj
import configuration
conf = configuration.Config()
from crystal import (
SingleByteParam,
PointerLabelParam,
DecimalParam,
BigEndianParam,
Command,
load_rom
)
from gbz80disasm import get_local_address, get_global_address
from audio import sort_asms
from wram import read_constants
rom = bytearray(load_rom())
sfx_constants = read_constants(os.path.join(conf.path, 'constants/sfx_constants.asm'))
class SoundEffectParam(SingleByteParam):
def to_asm(self):
if self.byte in sfx_constants.keys():
sfx_constant = sfx_constants[self.byte]
return sfx_constant
return SingleByteParam.to_asm(self)
anim_gfx_constants = read_constants(os.path.join(conf.path, 'constants/gfx_constants.asm'))
class AnimGFXParam(SingleByteParam):
def to_asm(self):
if self.byte in anim_gfx_constants.keys():
return anim_gfx_constants[self.byte]
return SingleByteParam.to_asm(self)
anims = read_constants(os.path.join(conf.path, 'constants/animation_constants.asm'))
objs = { k: v for k, v in anims.items() if 'ANIM_OBJ' in v }
bgs = { k: v for k, v in anims.items() if 'ANIM_BG' in v }
anims = { k: v.replace('ANIM_','') for k, v in anims.items() }
from move_constants import moves
anims.update(moves)
class AnimObjParam(SingleByteParam):
def to_asm(self):
if self.byte in objs.keys():
return objs[self.byte]
return SingleByteParam.to_asm(self)
class BGEffectParam(SingleByteParam):
def to_asm(self):
if self.byte in bgs.keys():
return bgs[self.byte]
return SingleByteParam.to_asm(self)
battle_animation_commands = {
0xd0: ['anim_obj', ['obj', AnimObjParam], ['x', DecimalParam], ['y', DecimalParam], ['param', SingleByteParam]],
0xd1: ['anim_1gfx', ['gfx1', AnimGFXParam]],
0xd2: ['anim_2gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam]],
0xd3: ['anim_3gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam]],
0xd4: ['anim_4gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam]],
0xd5: ['anim_5gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam], ['gfx5', AnimGFXParam]],
0xd6: ['anim_incobj', ['id', SingleByteParam]],
0xd7: ['anim_setobj', ['id', SingleByteParam], ['obj', AnimObjParam]], # bug: second param is interpreted as a command if not found in the object array
0xd8: ['anim_incbgeffect', ['effect', BGEffectParam]],
0xd9: ['anim_enemyfeetobj'],
0xda: ['anim_playerheadobj'],
0xdb: ['anim_checkpokeball'],
0xdc: ['anim_transform'],
0xdd: ['anim_raisesub'],
0xde: ['anim_dropsub'],
0xdf: ['anim_resetobp0'],
0xe0: ['anim_sound', ['tracks', SingleByteParam], ['id', SoundEffectParam]],
0xe1: ['anim_cry', ['pitch', SingleByteParam]],
0xe2: ['anim_minimizeopp'], # unused
0xe3: ['anim_oamon'],
0xe4: ['anim_oamoff'],
0xe5: ['anim_clearobjs'],
0xe6: ['anim_beatup'],
0xe7: ['anim_0xe7'], # nothing
0xe8: ['anim_updateactorpic'],
0xe9: ['anim_minimize'],
0xea: ['anim_0xea'], # nothing
0xeb: ['anim_0xeb'], # nothing
0xec: ['anim_0xec'], # nothing
0xed: ['anim_0xed'], # nothing
0xee: ['anim_jumpand', ['value', SingleByteParam], ['address', PointerLabelParam]],
0xef: ['anim_jumpuntil', ['address', PointerLabelParam]],
0xf0: ['anim_bgeffect', ['effect', BGEffectParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam]],
0xf1: ['anim_bgp', ['colors', SingleByteParam]],
0xf2: ['anim_obp0', ['colors', SingleByteParam]],
0xf3: ['anim_obp1', ['colors', SingleByteParam]],
0xf4: ['anim_clearsprites'],
0xf5: ['anim_0xf5'], # nothing
0xf6: ['anim_0xf6'], # nothing
0xf7: ['anim_0xf7'], # nothing
0xf8: ['anim_jumpif', ['value', SingleByteParam], ['address', PointerLabelParam]],
0xf9: ['anim_setvar', ['value', SingleByteParam]],
0xfa: ['anim_incvar'],
0xfb: ['anim_jumpvar', ['value', SingleByteParam], ['address', PointerLabelParam]],
0xfc: ['anim_jump', ['address', PointerLabelParam]],
0xfd: ['anim_loop', ['count', SingleByteParam], ['address', PointerLabelParam]],
0xfe: ['anim_call', ['address', PointerLabelParam]],
0xff: ['anim_ret'],
}
battle_animation_enders = [
'anim_jump',
'anim_ret',
]
def create_battle_animation_classes():
classes = []
for cmd, command in battle_animation_commands.items():
cmd_name = command[0]
params = {
'id': cmd,
'size': 1,
'end': cmd_name in battle_animation_enders,
'macro_name': cmd_name,
'param_types': {},
}
for i, (name, class_) in enumerate(command[1:]):
params['param_types'][i] = {'name': name, 'class': class_}
params['size'] += class_.size
class_name = cmd_name + 'Command'
class_ = classobj(class_name, (Command,), params)
globals()[class_name] = class_
classes += [class_]
return classes
battle_animation_classes = create_battle_animation_classes()
class BattleAnimWait(Command):
macro_name = 'anim_wait'
size = 1
end = macro_name in battle_animation_enders
param_types = {
0: {'name': 'duration', 'class': DecimalParam},
}
override_byte_check = True
class BattleAnim:
"""
A list of battle animation commands read from a given address.
Results in a list of commands (self.output) and a list of labels (self.labels).
Format is (address, asm, last_address). Includes any subroutines and their output.
To convert to text, use self.to_asm().
For combining multiple BattleAnims, take self.output + self.labels from each
and sort with sort_asms.
"""
def __init__(self, address, base_label=None, label=None, used_labels=[], macros=[]):
self.start_address = address
self.address = address
self.base_label = base_label
if self.base_label == None:
self.base_label = 'BattleAnim_' + hex(self.start_address)
self.label = label
if self.label == None:
self.label = self.base_label
self.used_labels = used_labels
self.output = []
self.labels = []
self.label_asm = (
self.start_address,
'%s: ; %x' % (self.label, self.start_address),
self.start_address
)
self.labels += [self.label_asm]
self.used_labels += [self.label_asm]
self.macros = macros
self.parse()
def parse(self):
done = False
while not done:
cmd = rom[self.address]
class_ = self.get_command_class(cmd)(address=self.address)
asm = class_.to_asm()
# label jumps/calls
for key, param in class_.param_types.items():
if param['class'] == PointerLabelParam:
label_address = class_.params[key].parsed_address
label = '%s_branch_%x' % (self.base_label, label_address)
label_def = '%s: ; %x' % (label, label_address)
label_asm = (label_address, label_def, label_address)
if label_asm not in self.used_labels:
self.labels += [label_asm]
asm = asm.replace('$%x' % get_local_address(label_address), label)
self.output += [(self.address, '\t' + asm, self.address + class_.size)]
self.address += class_.size
done = class_.end
# infinite loops are enders
if class_.macro_name == 'anim_loop':
if class_.params[0].byte == 0:
done = True
# last_address comment
self.output += [(self.address, '; %x\n' % self.address, self.address)]
# parse any other branches too
self.labels = list(set(self.labels))
for address, asm, last_address in self.labels:
if not (self.start_address <= address < self.address) and (address, asm, last_address) not in self.used_labels:
self.used_labels += [(address, asm, last_address)]
sub = BattleAnim(
address=address,
base_label=self.base_label,
label=asm.split(':')[0],
used_labels=self.used_labels,
macros=self.macros
)
self.output += sub.output
self.labels += sub.labels
self.output = list(set(self.output))
self.labels = list(set(self.labels))
def to_asm(self):
output = sort_asms(self.output + self.labels)
text = ''
for (address, asm, last_address) in output:
text += asm + '\n'
return text
def get_command_class(self, cmd):
if cmd < 0xd0:
return BattleAnimWait
for class_ in self.macros:
if class_.id == cmd:
return class_
return None
def battle_anim_label(i):
"""
Return a label matching the name of a battle animation by id.
"""
if i in anims.keys():
base_label = 'BattleAnim_%s' % anims[i].title().replace('_','')
else:
base_label = 'BattleAnim_%d' % i
return base_label
def dump_battle_anims(table_address=0xc906f, num_anims=278, macros=battle_animation_classes):
"""
Dump each battle animation from a pointer table.
"""
asms = []
asms += [(table_address, 'BattleAnimations: ; %x' % table_address, table_address)]
address = table_address
bank = address / 0x4000
for i in xrange(num_anims):
pointer_address = address
anim_address = rom[pointer_address] + rom[pointer_address + 1] * 0x100
anim_address = get_global_address(anim_address, bank)
base_label = battle_anim_label(i)
address += 2
# anim pointer
asms += [(pointer_address, '\tdw %s' % base_label, address)]
# anim script
anim = BattleAnim(
address=anim_address,
base_label=base_label,
macros=macros
)
asms += anim.output + anim.labels
asms += [(address, '; %x\n' % address, address)]
# jp sonicboom
anim = BattleAnim(
address=0xc9c00,
base_label='BattleAnim_Sonicboom_JP',
macros=macros
)
asms += anim.output + anim.labels
asms = sort_asms(asms)
return asms
def asm_list_to_text(asms):
output = ''
last = asms[0][0]
for addr, asm, last_addr in asms:
if addr > last:
# incbin any unknown areas
output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n\n' % (last, addr, last)
if addr >= last:
output += asm + '\n'
last = last_addr
return output
if __name__ == '__main__':
asms = dump_battle_anims()
print asm_list_to_text(asms)
|