Skip to content

Commit 519da4f

Browse files
authored
Merge pull request #104 from VelocityRa/velocity/dvp-overlays-etc
Import VU overlays | etc
2 parents ff997eb + c1d1c49 commit 519da4f

File tree

11 files changed

+1076
-10
lines changed

11 files changed

+1076
-10
lines changed

data/languages/r5900.pspec

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,28 @@
7676
</register_data>
7777

7878
<default_memory_blocks>
79-
<memory_block name="registers.io" start_address="0x10000000" length="0x10000" initialized="false" mode="rwv"/>
80-
<memory_block name="vu0.code" start_address="0x11000000" length="0x1000" initialized="false"/>
81-
<memory_block name="vu0.data" start_address="0x11004000" length="0x1000" initialized="false"/>
82-
<memory_block name="vu1.code" start_address="0x11008000" length="0x4000" initialized="false"/>
83-
<memory_block name="vu1.data" start_address="0x1100C000" length="0x4000" initialized="false"/>
84-
<memory_block name="registers.gs" start_address="0x12000000" length="0x2000" initialized="false" mode="rwv"/>
85-
<memory_block name="scratchpad" start_address="0x70000000" length="0x4000" initialized="false"/>
79+
<!--
80+
// start end bytes description
81+
// 0x00000000 - 0x01E84800 32000000 (main ram cached)
82+
// 0x20000000 - 0x21E84800 32000000 (main ram uncached)
83+
// 0x30100000 - 0x31E905C0 31000000 (main ram uncached & accelerated)
84+
// 0x1C000000 - 0x1E000000 02000000 (iop ram)
85+
// 0x1FC00000 - 0x1FFD0900 04000000 (BIOS/rom0 uncached)
86+
// 0x9FC00000 - 0x9FFD0900 04000000 (BIOS/rom09 cached)
87+
// 0xBFC00000 - 0xBFFD0900 04000000 (BIOS/rom0b uncached)
88+
89+
// KUSEG: 00000000h-7FFFFFFFh User segment
90+
// KSEG0: 80000000h-9FFFFFFFh Kernel segment 0
91+
// KSEG1: A0000000h-BFFFFFFFh Kernel segment 1
92+
-->
93+
<memory_block name="registers.io" start_address="0x10000000" length="0x00010000" initialized="false" mode="rwv"/>
94+
<memory_block name="vu0.code" start_address="0x11000000" length="0x00001000" initialized="false" mode="rwx"/>
95+
<memory_block name="vu0.data" start_address="0x11004000" length="0x00001000" initialized="false" mode="rw"/>
96+
<memory_block name="vu1.code" start_address="0x11008000" length="0x00004000" initialized="false" mode="rwx"/>
97+
<memory_block name="vu1.data" start_address="0x1100C000" length="0x00004000" initialized="false" mode="rw"/>
98+
<memory_block name="registers.gs" start_address="0x12000000" length="0x00002000" initialized="false" mode="rwv"/>
99+
<memory_block name="scratchpad" start_address="0x70000000" length="0x00004000" initialized="false" mode="rw"/>
100+
<memory_block name="iop_ram" start_address="0x1C000000" length="0x02000000" initialized="false" mode="rwx"/>
101+
<memory_block name="rom0" start_address="0x1FC00000" length="0x04000000" initialized="false" mode="r"/>
86102
</default_memory_blocks>
87103
</processor_spec>

data/languages/r5900.slaspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# 5. COP2 (VU) Macro Instruction Set
99

1010
define endian=little;
11-
define alignment=2;
11+
define alignment=4;
1212

1313
define space ram type=ram_space size=4 default;
1414
define space register type=register_space size=4;

data/patterns/r5900_LE_patterns.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
<funcstart validcode="function" thunk="true"/>
4444
</pattern>
4545

46+
<pattern>
47+
<data> 0x30000f3c 0x.. 0x.. 10111101 00100111 0x.. 0x.. 0xef 0x25 </data>
48+
<funcstart validcode="function"/>
49+
</pattern>
50+
51+
<pattern>
52+
<data>0x.. 0x.. 0x03 0x24 0x0c 0x00 0x00 0x00 0x08 0x00 0xe0 0x03</data>
53+
<funcstart validcode="function"/>
54+
</pattern>
4655

4756
<pattern>
4857
<data>0x00 0x00 0x03 0x24 0x0c 0x00 0x00 0x00 0x08 0x00 0xe0 0x03 0x00 0x00 0x00 0x00</data>
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# Export VU (DVP) overlay data: binary data (.bin) and symbols (.sym)
2+
# @category ghidra-emotionengine
3+
# @runtime PyGhidra
4+
5+
import typing
6+
if typing.TYPE_CHECKING:
7+
from ghidra.ghidra_builtins import *
8+
9+
import os
10+
import re
11+
from collections import defaultdict
12+
from ghidra.program.model.symbol import SourceType
13+
import jpype
14+
15+
# Constants from DvpOverlayTable.java
16+
VU1_TEXT_ADDRESS = 0x11008000
17+
DVP_OVERLAY_PREFIX = ".DVP.overlay.."
18+
VU_INSTRUCTION_SIZE = 8 # VU instructions are 8 bytes (64-bit)
19+
20+
class OverlayData:
21+
"""Container for overlay block data grouped by filename hash."""
22+
23+
def __init__(self, filename_hash):
24+
self.filename_hash = filename_hash
25+
self.symbols = [] # List of (vu_address, symbol_name) tuples
26+
self.blocks = [] # List of (vaddr, block) tuples for binary export
27+
28+
def add_symbol(self, vu_address, symbol_name):
29+
"""Add a symbol to this overlay."""
30+
self.symbols.append((vu_address, symbol_name))
31+
32+
def add_block(self, vaddr, block):
33+
"""Add a memory block to this overlay."""
34+
self.blocks.append((vaddr, block))
35+
36+
def get_sorted_symbols(self):
37+
"""Get symbols sorted by address."""
38+
return sorted(self.symbols, key=lambda x: x[0])
39+
40+
def get_sorted_blocks(self):
41+
"""Get blocks sorted by vaddr."""
42+
return sorted(self.blocks, key=lambda x: x[0])
43+
44+
def parse_overlay_block_name(block_name):
45+
"""
46+
Parse DVP overlay block name to extract components.
47+
48+
Format: .DVP.overlay..<overlay_name_offset>.<filename_hash>.<vaddr>.<line_of_split>.<counter>
49+
Example: .DVP.overlay..0x0000.a1b2c3d4.0x0100.0042.1
50+
51+
Returns:
52+
dict with keys: overlay_name_offset, filename_hash, vaddr, line_of_split, counter
53+
or None if parsing fails
54+
"""
55+
if not block_name.startswith(DVP_OVERLAY_PREFIX):
56+
return None
57+
58+
# Remove prefix and split by dots
59+
remainder = block_name[len(DVP_OVERLAY_PREFIX):]
60+
parts = remainder.split('.')
61+
62+
if len(parts) < 5:
63+
return None
64+
65+
try:
66+
return {
67+
'overlay_name_offset': parts[0], # e.g., "0x0000"
68+
'filename_hash': parts[1], # e.g., "a1b2c3d4"
69+
'vaddr': parts[2], # e.g., "0x0100"
70+
'line_of_split': parts[3], # e.g., "0042"
71+
'counter': parts[4] # e.g., "1"
72+
}
73+
except (IndexError, ValueError):
74+
return None
75+
76+
def convert_vu_address(address_offset):
77+
"""
78+
Convert Ghidra address to VU instruction address.
79+
80+
Subtracts VU1_TEXT_ADDRESS base and divides by 8 (instruction word size).
81+
82+
Args:
83+
address_offset: The address offset as a long
84+
85+
Returns:
86+
VU instruction address as integer
87+
"""
88+
relative_offset = address_offset - VU1_TEXT_ADDRESS
89+
vu_address = relative_offset // VU_INSTRUCTION_SIZE
90+
return int(vu_address)
91+
92+
def collect_overlay_data(memory, symbol_table):
93+
"""
94+
Collect all overlay data from memory blocks.
95+
96+
Returns:
97+
dict: filename_hash -> OverlayData mapping
98+
"""
99+
overlays = defaultdict(lambda: OverlayData(None))
100+
overlay_blocks_found = 0
101+
total_symbols = 0
102+
103+
# Iterate through all memory blocks
104+
for block in memory.blocks:
105+
block_name = block.name
106+
107+
# Check if this is a DVP overlay block
108+
if not block_name.startswith(DVP_OVERLAY_PREFIX):
109+
continue
110+
111+
# Parse the block name
112+
parsed = parse_overlay_block_name(block_name)
113+
if parsed is None:
114+
printerr(f"Warning: Could not parse overlay block name: {block_name}")
115+
continue
116+
117+
filename_hash = parsed['filename_hash']
118+
vaddr = parsed['vaddr']
119+
overlay_blocks_found += 1
120+
121+
println(f"Processing overlay block: {block_name}")
122+
println(f" Filename hash: {filename_hash}")
123+
124+
# Initialize overlay data if needed
125+
if overlays[filename_hash].filename_hash is None:
126+
overlays[filename_hash].filename_hash = filename_hash
127+
128+
# Parse vaddr for sorting blocks
129+
try:
130+
vaddr_int = int(vaddr, 16) if vaddr.startswith('0x') else int(vaddr, 16)
131+
except ValueError:
132+
vaddr_int = 0
133+
134+
# Add block for binary export
135+
overlays[filename_hash].add_block(vaddr_int, block)
136+
137+
# Collect symbols from this block
138+
block_start = block.start
139+
block_end = block.end
140+
block_symbol_count = 0
141+
142+
symbol_iter = symbol_table.getSymbolIterator(block_start, True)
143+
for symbol in symbol_iter:
144+
symbol_addr = symbol.address
145+
146+
# Check if symbol is within this block
147+
if symbol_addr.compareTo(block_end) > 0:
148+
break
149+
if symbol_addr.compareTo(block_start) < 0:
150+
continue
151+
152+
# Skip default symbols and VU temporary symbols
153+
if symbol.source == SourceType.DEFAULT:
154+
continue
155+
156+
symbol_name = symbol.name
157+
if re.match(r'\.?vu\.\d+', symbol_name):
158+
continue
159+
160+
# Convert address and add symbol
161+
vu_address = convert_vu_address(symbol_addr.offset)
162+
overlays[filename_hash].add_symbol(vu_address, symbol_name)
163+
block_symbol_count += 1
164+
total_symbols += 1
165+
166+
println(f" Found {block_symbol_count} symbols")
167+
168+
return dict(overlays), overlay_blocks_found, total_symbols
169+
170+
def export_symbol_file(overlay_data, output_path):
171+
"""
172+
Export symbols to a .sym file.
173+
174+
Args:
175+
overlay_data: OverlayData instance
176+
output_path: Directory path for output
177+
178+
Returns:
179+
bool: True if successful
180+
"""
181+
filename = f"{overlay_data.filename_hash}.sym"
182+
filepath = os.path.join(output_path, filename)
183+
184+
try:
185+
symbols = overlay_data.get_sorted_symbols()
186+
with open(filepath, 'w', encoding='utf-8') as f:
187+
for vu_address, symbol_name in symbols:
188+
# Format: 4 hex digits (no 0x prefix), space, symbol name
189+
f.write(f"{vu_address:04x} {symbol_name}\n")
190+
191+
println(f"Wrote {len(symbols)} symbols to: {filename}")
192+
return True
193+
194+
except Exception as e:
195+
printerr(f"Error writing symbol file {filename}: {str(e)}")
196+
return False
197+
198+
def export_binary_file(overlay_data, output_path):
199+
"""
200+
Export raw binary data to a .bin file.
201+
202+
Args:
203+
overlay_data: OverlayData instance
204+
output_path: Directory path for output
205+
206+
Returns:
207+
bool: True if successful
208+
"""
209+
filename = f"{overlay_data.filename_hash}.bin"
210+
filepath = os.path.join(output_path, filename)
211+
212+
try:
213+
blocks = overlay_data.get_sorted_blocks()
214+
total_bytes = 0
215+
216+
with open(filepath, 'wb') as f:
217+
for vaddr_int, block in blocks:
218+
# Get block size and create byte array
219+
block_size = int(block.size)
220+
byte_array = jpype.JByte[block_size]
221+
222+
# Read bytes from the block
223+
bytes_read = block.getBytes(block.start, byte_array)
224+
225+
# Convert Java bytes (signed) to Python bytes (unsigned)
226+
python_bytes = bytes([(b & 0xFF) for b in byte_array[:bytes_read]])
227+
228+
# Write to file
229+
f.write(python_bytes)
230+
total_bytes += bytes_read
231+
232+
println(f"Wrote {total_bytes} bytes to: {filename}")
233+
return True
234+
235+
except Exception as e:
236+
printerr(f"Error writing binary file {filename}: {str(e)}")
237+
import traceback
238+
printerr(traceback.format_exc())
239+
return False
240+
241+
def export_dvp_overlays():
242+
"""Main function to export DVP overlay data."""
243+
244+
# Validate that a program is loaded
245+
if currentProgram is None:
246+
printerr("No program is currently loaded")
247+
return
248+
249+
println("Exporting DVP overlay data...")
250+
println("-" * 60)
251+
252+
# Get output directory from user
253+
try:
254+
output_dir = askDirectory("Select Output Directory for DVP Overlay Files", "Select")
255+
except:
256+
println("No directory selected, export cancelled")
257+
return
258+
259+
if output_dir is None:
260+
println("No directory selected, export cancelled")
261+
return
262+
263+
output_path = output_dir.absolutePath
264+
println(f"Output directory: {output_path}")
265+
println("-" * 60)
266+
267+
# Collect overlay data
268+
memory = currentProgram.memory
269+
symbol_table = currentProgram.symbolTable
270+
271+
overlays, overlay_blocks_found, total_symbols = collect_overlay_data(memory, symbol_table)
272+
273+
println("-" * 60)
274+
println(f"Found {overlay_blocks_found} DVP overlay blocks")
275+
println(f"Total symbols collected: {total_symbols}")
276+
println(f"Unique overlays (by filename hash): {len(overlays)}")
277+
println("-" * 60)
278+
279+
if overlay_blocks_found == 0:
280+
println("No DVP overlay blocks found in program")
281+
return
282+
283+
# Export files for each overlay
284+
sym_files_written = 0
285+
bin_files_written = 0
286+
287+
for filename_hash, overlay_data in overlays.items():
288+
# Export symbol file
289+
if export_symbol_file(overlay_data, output_path):
290+
sym_files_written += 1
291+
292+
# Export binary file
293+
if export_binary_file(overlay_data, output_path):
294+
bin_files_written += 1
295+
296+
# Print summary
297+
println("-" * 60)
298+
println("Export complete!")
299+
println(f" Symbol files (.sym): {sym_files_written}")
300+
println(f" Binary files (.bin): {bin_files_written}")
301+
println(f" Total symbols exported: {total_symbols}")
302+
println(f" Output directory: {output_path}")
303+
println("-" * 60)
304+
305+
# Run the export
306+
export_dvp_overlays()

0 commit comments

Comments
 (0)