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
|
# 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 Wait(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:
def __init__(self, address, base_label=None, label=None, used_labels=[]):
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.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)
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 = sorted(self.output + self.labels, key = lambda (x, y, z): (x, z))
text = ''
for (address, asm, last_address) in output:
text += asm + '\n'
#text += '; %x\n' % last_address
return text
def get_command_class(self, cmd):
if cmd < 0xd0:
return Wait
for class_ in battle_animation_classes:
if class_.id == cmd:
return class_
return None
def battle_anim_label(i):
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):
"""
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)
asms += anim.output + anim.labels
asms += [(address, '; %x\n' % address, address)]
# jp sonicboom
anim = BattleAnim(address=0xc9c00, base_label='BattleAnim_Sonicboom_JP')
asms += anim.output + anim.labels
asms = sort_asms(asms)
return asms
def print_asm_list(asms):
# incbin any unknown areas
# not really needed since there are no gaps
last = asms[0][0]
for addr, asm, last_addr in asms:
if addr > last:
print '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n' % (last, addr, last)
if addr >= last:
print asm
last = last_addr
if __name__ == '__main__':
asms = dump_battle_anims()
print_asm_list(asms)
|