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
|
# -*- coding: utf-8 -*-
from __future__ import print_function
from __future__ import absolute_import
import networkx as nx
from .romstr import (
RomStr,
relative_jumps,
call_commands,
relative_unconditional_jumps,
)
class RomGraph(nx.DiGraph):
"""
Graphs various functions pointing to each other.
TODO: Bank switches are nasty. They should be detected. Otherwise,
functions will point to non-functions within the same bank. Another way
to detect bankswitches is retroactively. By disassembling one function
after another within the function banks, it can be roughly assumed that
anything pointing to something else (within the same bank) is really
actually a bankswitch. An even better method to handle bankswitches
would be to just detect those situations in the asm (but I presently
forget how bankswitches are performed in pokecrystal).
"""
# some areas shouldn't be parsed as asm
exclusions = []
# where is the first function located?
start_address = 0x150
# and where is a good place to stop?
end_address = 0x4000 * 0x03 # only do the first bank? sure..
# where is the rom stored?
rompath = "../baserom.gbc"
def __init__(self, rom=None, **kwargs):
"""
Loads and parses the ROM into a function graph.
"""
# continue the initialization
nx.DiGraph.__init__(self, **kwargs)
# load the graph
if rom == None:
self.load_rom()
else:
self.rom = rom
# start parsing the ROM
self.parse()
def load_rom(self):
"""
Creates a RomStr from rompath.
"""
file_handler = open(self.rompath, "r")
self.rom = RomStr(file_handler.read())
file_handler.close()
def parse(self):
"""
Parses the ROM starting with the first function address. Each
function is disassembled and parsed to find where else it leads to.
"""
functions = {}
address = self.start_address
other_addresses = set()
count = 0
while True:
if count > 3000:
break
if address < self.end_address and (address not in functions.keys()) and address >= 0x150:
# address is okay to parse at, keep going
pass
elif len(other_addresses) > 0:
# parse some other address possibly in a remote bank
address = other_addresses.pop()
else:
# no more addresses detected- exit loop
break
# parse the asm
func = self.rom.to_asm(address)
# check if there are any nops (probably not a function)
nops = 0
for (id, command) in func.asm_commands.items():
if "id" in command and command["id"] == 0x0:
nops += 1
# skip this function
if nops > 1:
address = 0
continue
# store this parsed function
functions[address] = func
# where does this function jump to?
used_addresses = set(func.used_addresses())
# add this information to the graph
for used_address in used_addresses:
# only add this remote address if it's not yet parsed
if used_address not in functions.keys():
other_addresses.update([used_address])
# add this other address to the graph
if used_address > 100:
self.add_node(used_address)
# add this as an edge between the two nodes
self.add_edge(address, used_address)
# setup the next function to be parsed
address = func.last_address
count += 1
self.functions = functions
def pretty_printer(self):
"""
Shows some text output describing which nodes point to which other
nodes.
"""
print(self.edges())
def to_d3(self):
"""
Exports to d3.js because we're gangster like that.
"""
import networkx.readwrite.json_graph as json_graph
content = json_graph.dumps(self)
fh = open("crystal/crystal.json", "w")
fh.write(content)
fh.close()
def to_gephi(self):
"""
Generates a gexf file.
"""
nx.write_gexf(self, "graph.gexf")
class RedGraph(RomGraph):
"""
Not implemented. Go away.
"""
rompath = "../pokered-baserom.gbc"
class CryGraph(RomGraph):
exclusions = [
[0x000, 0x149],
]
rompath = "../baserom.gbc"
if __name__ == "__main__":
crygraph = CryGraph()
crygraph.pretty_printer()
crygraph.to_gephi()
|