Research
This commit is contained in:
parent
28cbbe3470
commit
a9153546ae
56 changed files with 6731 additions and 258 deletions
8
tools/create_focused_crop.py
Normal file
8
tools/create_focused_crop.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from PIL import Image
|
||||
IN = r"K:\ghidra\Crusader_Decomp\binary\vram_weapons.png"
|
||||
OUT = r"K:\ghidra\Crusader_Decomp\binary\crop_weapon_row.png"
|
||||
box = (80,44,360,92)
|
||||
img = Image.open(IN)
|
||||
crop = img.crop(box)
|
||||
crop.save(OUT)
|
||||
print('wrote', OUT)
|
||||
27
tools/find_bytes.py
Normal file
27
tools/find_bytes.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import argparse
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--file', required=True)
|
||||
p.add_argument('--byte', required=True, help='byte value in hex (e.g. 0x0c)')
|
||||
p.add_argument('--base', type=lambda x: int(x,0), default=0x80000000)
|
||||
p.add_argument('--start', type=lambda x: int(x,0), default=0x80064000)
|
||||
p.add_argument('--end', type=lambda x: int(x,0), default=0x80064800)
|
||||
args = p.parse_args()
|
||||
bval = int(args.byte, 0)
|
||||
with open(args.file, 'rb') as f:
|
||||
f.seek(0,2)
|
||||
size = f.tell()
|
||||
s = args.start - args.base
|
||||
e = args.end - args.base
|
||||
s = max(0, s)
|
||||
e = min(size, e)
|
||||
f.seek(s)
|
||||
data = f.read(e-s)
|
||||
for i, b in enumerate(data):
|
||||
if b == bval:
|
||||
addr = args.base + s + i
|
||||
print(hex(addr), hex(s+i))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
10
tools/find_cd_bytes2.py
Normal file
10
tools/find_cd_bytes2.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import sys
|
||||
fname = sys.argv[1]
|
||||
start = int(sys.argv[2], 0)
|
||||
length = int(sys.argv[3], 0)
|
||||
with open(fname, 'rb') as f:
|
||||
f.seek(start)
|
||||
d = f.read(length)
|
||||
for i, b in enumerate(d):
|
||||
if b in (0x0c, 0x0d):
|
||||
print(hex(0x80000000 + start + i), hex(start + i), hex(b))
|
||||
45
tools/hexdump_region.py
Normal file
45
tools/hexdump_region.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
def hexdump(data, base=0, width=16):
|
||||
for i in range(0, len(data), width):
|
||||
chunk = data[i:i+width]
|
||||
hex_bytes = ' '.join(f"{b:02x}" for b in chunk)
|
||||
ascii_repr = ''.join((chr(b) if 32 <= b < 127 else '.') for b in chunk)
|
||||
print(f"{base+i:08x}: {hex_bytes:<48} {ascii_repr}")
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--file', required=True)
|
||||
p.add_argument('--addr', required=True, help='PSX virtual address (e.g. 0x80064355)')
|
||||
p.add_argument('--before', type=int, default=64)
|
||||
p.add_argument('--after', type=int, default=256)
|
||||
p.add_argument('--base', type=lambda x: int(x,0), default=0x80000000, help='PSX RAM base address used for dump offset')
|
||||
args = p.parse_args()
|
||||
|
||||
addr = int(args.addr, 0)
|
||||
base = args.base
|
||||
offset = addr - base
|
||||
if offset < 0:
|
||||
print(f"Computed negative offset {offset} for addr {hex(addr)} base {hex(base)}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
with open(args.file, 'rb') as f:
|
||||
f.seek(0, 2)
|
||||
size = f.tell()
|
||||
if offset >= size:
|
||||
print(f"Offset {offset:#x} beyond file size {size:#x}")
|
||||
sys.exit(3)
|
||||
start = max(0, offset - args.before)
|
||||
f.seek(start)
|
||||
data = f.read(args.before + args.after)
|
||||
print(f"File: {args.file}")
|
||||
print(f"PSX addr: {hex(addr)}, file offset: {hex(offset)}, dump start: {hex(start)}, len: {len(data)}")
|
||||
hexdump(data, base=start)
|
||||
except FileNotFoundError:
|
||||
print(f"File not found: {args.file}")
|
||||
sys.exit(4)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
107
tools/hud_icon_match.py
Normal file
107
tools/hud_icon_match.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
IN_BIN = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
W,H = 1024,512
|
||||
# regions
|
||||
HUD_BOX = (80,44,360,92) # left,top,right,bot
|
||||
VSTRIP_X0, VSTRIP_X1 = 956, 1023
|
||||
import os, sys
|
||||
with open(IN_BIN,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# build RGB rows
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = []
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5) & 0x1F) << 3
|
||||
r = ((val >>10) & 0x1F) << 3
|
||||
else:
|
||||
r=g=b=0
|
||||
row.append((r,g,b))
|
||||
rows.append(row)
|
||||
# extract HUD crop
|
||||
lx,ty,rx,by = HUD_BOX
|
||||
w = rx-lx; h = by-ty
|
||||
hud = [[rows[y][x] for x in range(lx,rx)] for y in range(ty,by)]
|
||||
# build mask for HUD
|
||||
hud_mask = [[(1 if any(ch!=0 for ch in hud[y][x]) else 0) for x in range(w)] for y in range(h)]
|
||||
# extract vstrip area and find blobs
|
||||
vx0,vx1 = VSTRIP_X0, VSTRIP_X1
|
||||
vw = vx1-vx0+1
|
||||
vrows = [[rows[y][x] for x in range(vx0,vx1+1)] for y in range(H)]
|
||||
# mask and flood-fill
|
||||
mask = [[1 if any(ch!=0 for ch in vrows[y][x]) else 0 for x in range(vw)] for y in range(H)]
|
||||
visited = [[0]*vw for _ in range(H)]
|
||||
from collections import deque
|
||||
blobs = []
|
||||
for y in range(H):
|
||||
for x in range(vw):
|
||||
if mask[y][x] and not visited[y][x]:
|
||||
q=deque([(x,y)])
|
||||
visited[y][x]=1
|
||||
xs=[]; ys=[]
|
||||
while q:
|
||||
cx,cy=q.popleft()
|
||||
xs.append(cx); ys.append(cy)
|
||||
for dx,dy in ((1,0),(-1,0),(0,1),(0,-1)):
|
||||
nx,ny = cx+dx, cy+dy
|
||||
if 0<=nx<vw and 0<=ny<H and mask[ny][nx] and not visited[ny][nx]:
|
||||
visited[ny][nx]=1; q.append((nx,ny))
|
||||
x0,x1 = min(xs), max(xs)
|
||||
y0,y1 = min(ys), max(ys)
|
||||
area = len(xs)
|
||||
blobs.append((x0,y0,x1,y1,area))
|
||||
# sort blobs by y (top to bottom)
|
||||
blobs.sort(key=lambda b: b[1])
|
||||
print('Found', len(blobs), 'blobs in vstrip')
|
||||
for i,b in enumerate(blobs):
|
||||
x0,y0,x1,y1,area = b
|
||||
print(i, 'blob box (vstrip coords)=', (x0,y0,x1,y1), 'area=',area)
|
||||
# extract blob images
|
||||
def extract_from_rows(rr, x0,y0,x1,y1):
|
||||
w = x1-x0+1; h = y1-y0+1
|
||||
img = [[rr[y+y0][x+x0] for x in range(w)] for y in range(h)]
|
||||
return img
|
||||
blob_imgs = [extract_from_rows(vrows, *b[:4]) for b in blobs]
|
||||
# template match each blob against hud with sliding window
|
||||
import math
|
||||
results = []
|
||||
for bi, img in enumerate(blob_imgs):
|
||||
bh = len(img); bw = len(img[0])
|
||||
if bh<4 or bw<4: continue
|
||||
best = (1e12, -1,-1)
|
||||
# convert flattened arrays for speed
|
||||
tmpl = [c for row in img for px in row for c in px]
|
||||
for y in range(0, h-bh+1):
|
||||
for x in range(0, w-bw+1):
|
||||
ssd=0
|
||||
for j in range(bh):
|
||||
for i in range(bw):
|
||||
r1,g1,b1 = img[j][i]
|
||||
r2,g2,b2 = hud[j+y][i+x]
|
||||
dr=r1-r2; dg=g1-g2; db=b1-b2
|
||||
ssd += dr*dr + dg*dg + db*db
|
||||
if ssd>best[0]: break
|
||||
if ssd>best[0]: break
|
||||
if ssd < best[0]: best = (ssd,x,y)
|
||||
results.append((bi,bw,bh,best[0],best[1],best[2]))
|
||||
# sort by score
|
||||
results.sort(key=lambda x: x[3])
|
||||
print('\nTop matches:')
|
||||
for bi,bw,bh,ssd,x,y in results[:10]:
|
||||
# compute HUD pixel coordinates and vram offsets
|
||||
hud_x = lx + x; hud_y = ty + y
|
||||
start_idx = hud_y*W + hud_x
|
||||
end_idx = (hud_y+bh-1)*W + (hud_x + bw -1)
|
||||
so = start_idx*2; eo = (end_idx+1)*2 -1
|
||||
vb = blobs[bi]
|
||||
v_x0 = vx0 + vb[0]; v_y0 = vb[1]; v_x1 = vx0 + vb[2]; v_y1 = vb[3]
|
||||
print(f'blob#{bi} vbox=({v_x0},{v_y0})-({v_x1},{v_y1}) size={bw}x{bh} bestssd={ssd} hudpos=({hud_x},{hud_y}) bytes=0x{so:06x}-0x{eo:06x}')
|
||||
|
||||
# If results empty, report
|
||||
if not results:
|
||||
print('No matches found')
|
||||
89
tools/hud_row_scan.py
Normal file
89
tools/hud_row_scan.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
IN_BIN = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
W,H = 1024,512
|
||||
TOP, BOT = 44, 92 # y range (inclusive start, exclusive end)
|
||||
import os
|
||||
with open(IN_BIN,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# build mask for this row range
|
||||
col_counts = [0]*W
|
||||
for y in range(TOP, BOT):
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
if val & 0x7fff:
|
||||
col_counts[x] += 1
|
||||
# find runs where col_counts exceeds threshold
|
||||
maxc = max(col_counts)
|
||||
thr = max(3, int(maxc*0.2))
|
||||
runs = []
|
||||
inside=False
|
||||
for x,c in enumerate(col_counts):
|
||||
if c>thr and not inside:
|
||||
sx = x; inside=True
|
||||
if c<=thr and inside:
|
||||
ex = x-1; inside=False; runs.append((sx,ex))
|
||||
if inside: runs.append((sx,W-1))
|
||||
print('max per-col count', maxc, 'threshold', thr)
|
||||
print('runs:', runs)
|
||||
# compute byte offsets and produce crops (no PIL)
|
||||
import struct, zlib
|
||||
OUT_DIR = r"K:\ghidra\Crusader_Decomp\binary\hud_candidates"
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
with open(IN_BIN,'rb') as f:
|
||||
bin_data = f.read()
|
||||
# build full rows RGB
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = bin_data[off] | (bin_data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5)&0x1F) << 3
|
||||
r = ((val >>10)&0x1F) << 3
|
||||
else:
|
||||
r=g=b=0
|
||||
row.extend([r,g,b])
|
||||
rows.append(bytes(row))
|
||||
# for each run, create a crop with padding
|
||||
for i,(sx,ex) in enumerate(runs):
|
||||
pad = 8
|
||||
x0 = max(0, sx-pad)
|
||||
x1 = min(W-1, ex+pad)
|
||||
y0 = TOP; y1 = BOT-1
|
||||
w = x1 - x0 + 1
|
||||
h = y1 - y0 + 1
|
||||
rawrows = []
|
||||
for y in range(y0, y1+1):
|
||||
rawrows.append(b"\x00" + rows[y][x0*3:(x0+w)*3])
|
||||
raw = b"".join(rawrows)
|
||||
comp = zlib.compress(raw,9)
|
||||
def chunk(t,d):
|
||||
out = struct.pack('>I', len(d)) + t + d
|
||||
import zlib
|
||||
crc = zlib.crc32(t + d) & 0xffffffff
|
||||
out += struct.pack('>I', crc)
|
||||
return out
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
|
||||
png += chunk(b'IDAT', comp)
|
||||
png += chunk(b'IEND', b'')
|
||||
out = os.path.join(OUT_DIR, f'cand_{i}_{x0}_{y0}.png')
|
||||
with open(out,'wb') as f:
|
||||
f.write(png)
|
||||
start_idx = y0*W + x0
|
||||
end_idx = y1*W + x1
|
||||
so = start_idx*2
|
||||
eo = (end_idx+1)*2 -1
|
||||
print(f'wrote {out} box=({x0},{y0})-({x1},{y1}) bytes=0x{so:06x}-0x{eo:06x} col_counts_max={max(col_counts[x0:x1+1])}')
|
||||
|
||||
# also print top columns with counts
|
||||
top_cols = sorted(((c,x) for x,c in enumerate(col_counts)), reverse=True)[:30]
|
||||
print('\nTop columns (count,x):')
|
||||
for c,x in top_cols:
|
||||
print(c,x)
|
||||
52
tools/make_crop_no_pil.py
Normal file
52
tools/make_crop_no_pil.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import struct, zlib, os
|
||||
IN_BIN = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
OUT_PNG = r"K:\ghidra\Crusader_Decomp\binary\crop_weapon_row_nopil.png"
|
||||
W,H = 1024,512
|
||||
# box: left,top,right,bot (exclusive right/bot matching PIL convention)
|
||||
left,top,right,bot = 80,44,360,92
|
||||
w = right - left
|
||||
h = bot - top
|
||||
with open(IN_BIN,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# build rows
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5) & 0x1F) << 3
|
||||
r = ((val >>10) & 0x1F) << 3
|
||||
else:
|
||||
r=g=b=0
|
||||
row.extend([r,g,b])
|
||||
rows.append(bytes(row))
|
||||
# compose crop raw (PNG filter 0 per row)
|
||||
rawrows = []
|
||||
for y in range(top, top+h):
|
||||
rawrows.append(b"\x00" + rows[y][left*3:(left+w)*3])
|
||||
raw = b"".join(rawrows)
|
||||
comp = zlib.compress(raw, level=9)
|
||||
def chunk(t,d):
|
||||
out = struct.pack('>I', len(d)) + t + d
|
||||
crc = zlib.crc32(t + d) & 0xffffffff
|
||||
out += struct.pack('>I', crc)
|
||||
return out
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
|
||||
png += chunk(b'IDAT', comp)
|
||||
png += chunk(b'IEND', b'')
|
||||
with open(OUT_PNG, 'wb') as f:
|
||||
f.write(png)
|
||||
# compute file offsets
|
||||
start_idx = top*W + left
|
||||
end_idx = (top+h-1)*W + (left + w -1)
|
||||
so = start_idx * 2
|
||||
eo = ((end_idx)+1)*2 -1
|
||||
print('wrote', OUT_PNG)
|
||||
print(f'pixel box=({left},{top})-({left+w-1},{top+h-1}) size={w}x{h}')
|
||||
print(f'vram byte offsets: 0x{so:06x}-0x{eo:06x} (inclusive)')
|
||||
26
tools/read_commit_table.py
Normal file
26
tools/read_commit_table.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import argparse
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--file', required=True)
|
||||
p.add_argument('--addr', required=True)
|
||||
p.add_argument('--base', type=lambda x: int(x,0), default=0x80000000)
|
||||
p.add_argument('--stride', type=int, default=10)
|
||||
p.add_argument('--field_idx', type=int, default=9)
|
||||
p.add_argument('--count', type=int, default=20)
|
||||
args = p.parse_args()
|
||||
|
||||
addr = int(args.addr, 0)
|
||||
offset = addr - args.base
|
||||
with open(args.file, 'rb') as f:
|
||||
for ch in range(args.count):
|
||||
idx = offset + ch*args.stride + args.field_idx
|
||||
f.seek(idx)
|
||||
b = f.read(1)
|
||||
if not b:
|
||||
print(f"ch {ch:02d}: EOF")
|
||||
break
|
||||
print(f"ch {ch:02d}: addr {hex(addr + ch*args.stride + args.field_idx)} offset {hex(idx)} value {b[0]:02x} ({b[0]})")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
57
tools/vertical_strip_crops.py
Normal file
57
tools/vertical_strip_crops.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import struct, zlib, os
|
||||
IN_BIN = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
OUT_DIR = r"K:\ghidra\Crusader_Decomp\binary\vstrip_crops"
|
||||
W,H = 1024,512
|
||||
x0 = 956
|
||||
x1 = 1023
|
||||
w = x1 - x0 + 1
|
||||
step_h = 48
|
||||
import math
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
with open(IN_BIN,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# build full RGB rows
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5) & 0x1F) << 3
|
||||
r = ((val >>10) & 0x1F) << 3
|
||||
else:
|
||||
r=g=b=0
|
||||
row.extend([r,g,b])
|
||||
rows.append(bytes(row))
|
||||
# produce crops
|
||||
import struct, zlib
|
||||
def chunk(t,d):
|
||||
out = struct.pack('>I', len(d)) + t + d
|
||||
crc = zlib.crc32(t + d) & 0xffffffff
|
||||
out += struct.pack('>I', crc)
|
||||
return out
|
||||
for i in range(0, H, step_h):
|
||||
y0 = i
|
||||
y1 = min(H-1, i+step_h-1)
|
||||
h = y1 - y0 + 1
|
||||
rawrows = []
|
||||
for y in range(y0, y1+1):
|
||||
rawrows.append(b"\x00" + rows[y][x0*3:(x0+w)*3])
|
||||
raw = b"".join(rawrows)
|
||||
comp = zlib.compress(raw,9)
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
|
||||
png += chunk(b'IDAT', comp)
|
||||
png += chunk(b'IEND', b'')
|
||||
out = os.path.join(OUT_DIR, f'vstrip_{y0:03d}_{y1:03d}.png')
|
||||
with open(out,'wb') as f:
|
||||
f.write(png)
|
||||
start_idx = y0*W + x0
|
||||
end_idx = y1*W + x1
|
||||
so = start_idx*2
|
||||
eo = (end_idx+1)*2 -1
|
||||
print(out, f'box=({x0},{y0})-({x1},{y1}) bytes=0x{so:06x}-0x{eo:06x}')
|
||||
60
tools/vram_analyze.py
Normal file
60
tools/vram_analyze.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from collections import deque
|
||||
IN_PATH = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
W,H = 1024,512
|
||||
import os
|
||||
with open(IN_PATH,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# build mask of non-black pixels
|
||||
mask = bytearray(W*H)
|
||||
for i in range(count):
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5) & 0x1F) << 3
|
||||
r = ((val >>10) & 0x1F) << 3
|
||||
if r|g|b:
|
||||
mask[i] = 1
|
||||
# flood-fill connected components (4-neigh)
|
||||
visited = bytearray(W*H)
|
||||
bbs = []
|
||||
for idx in range(W*H):
|
||||
if mask[idx] and not visited[idx]:
|
||||
q = deque([idx])
|
||||
visited[idx]=1
|
||||
xs = []
|
||||
ys = []
|
||||
while q:
|
||||
v = q.popleft()
|
||||
y = v // W
|
||||
x = v % W
|
||||
xs.append(x); ys.append(y)
|
||||
# neighbors
|
||||
for dx,dy in ((1,0),(-1,0),(0,1),(0,-1)):
|
||||
nx = x+dx; ny = y+dy
|
||||
if 0<=nx<W and 0<=ny<H:
|
||||
ni = ny*W+nx
|
||||
if mask[ni] and not visited[ni]:
|
||||
visited[ni]=1
|
||||
q.append(ni)
|
||||
x0,x1 = min(xs), max(xs)
|
||||
y0,y1 = min(ys), max(ys)
|
||||
area = len(xs)
|
||||
start_idx = y0*W + x0
|
||||
end_idx = y1*W + x1
|
||||
start_off = start_idx*2
|
||||
end_off = (end_idx+1)*2 - 1
|
||||
bbs.append((x0,y0,x1,y1,area,start_off,end_off))
|
||||
# sort by area descending
|
||||
bbs.sort(key=lambda x: -x[4])
|
||||
print('Found', len(bbs), 'components')
|
||||
for i,(x0,y0,x1,y1,area,so,eo) in enumerate(bbs[:20]):
|
||||
print(f'[{i}] box=({x0},{y0})-({x1},{y1}) area={area} bytes=0x{so:06x}-0x{eo:06x}')
|
||||
# Save the top few as small BMP crops for inspection
|
||||
from PIL import Image
|
||||
img = Image.open(r"K:\ghidra\Crusader_Decomp\binary\vram_weapons.png")
|
||||
for i,(x0,y0,x1,y1,area,so,eo) in enumerate(bbs[:12]):
|
||||
crop = img.crop((x0,y0,x1+1,y1+1))
|
||||
out = r"K:\ghidra\Crusader_Decomp\binary\crop_%02d.png"%i
|
||||
crop.save(out)
|
||||
print('wrote', out)
|
||||
60
tools/vram_crop_grid.py
Normal file
60
tools/vram_crop_grid.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import struct, zlib, os
|
||||
IN_BIN = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
IN_PNG = r"K:\ghidra\Crusader_Decomp\binary\vram_weapons.png"
|
||||
W,H = 1024,512
|
||||
# simple PNG crop writer: read full PNG rows from our previous generated PNG file
|
||||
# read raw image bytes (we will load via simple approach using original binary->RGB conversion again)
|
||||
with open(IN_BIN,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
# make full RGB array
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
i = y*W + x
|
||||
if i < count:
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >>5) & 0x1F) << 3
|
||||
r = ((val >>10) & 0x1F) << 3
|
||||
else:
|
||||
r=g=b=0
|
||||
row.extend([r,g,b])
|
||||
rows.append(bytes(row))
|
||||
# crop grid params
|
||||
xs = list(range(0, W, 128))
|
||||
ys = list(range(0, 192, 64))
|
||||
out_dir = r"K:\ghidra\Crusader_Decomp\binary\crops_grid"
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
import zlib, struct
|
||||
for y0 in ys:
|
||||
for x0 in xs:
|
||||
w = min(256, W-x0)
|
||||
h = min(128, H-y0)
|
||||
# build raw rows top-to-bottom
|
||||
rawrows = []
|
||||
for y in range(y0, y0+h):
|
||||
rawrows.append(b"\x00" + rows[y][x0*3:(x0+w)*3])
|
||||
raw = b"".join(rawrows)
|
||||
comp = zlib.compress(raw, level=9)
|
||||
def chunk(t,d):
|
||||
out = struct.pack('>I', len(d)) + t + d
|
||||
import zlib
|
||||
crc = zlib.crc32(t + d) & 0xffffffff
|
||||
out += struct.pack('>I', crc)
|
||||
return out
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
|
||||
png += chunk(b'IDAT', comp)
|
||||
png += chunk(b'IEND', b'')
|
||||
out = os.path.join(out_dir, f'crop_{x0}_{y0}.png')
|
||||
with open(out,'wb') as f:
|
||||
f.write(png)
|
||||
# compute file-offset range
|
||||
start_idx = y0*W + x0
|
||||
end_idx = (y0+h-1)*W + (x0+w-1)
|
||||
so = start_idx*2
|
||||
eo = (end_idx+1)*2 -1
|
||||
print(out, f'box=({x0},{y0})-({x0+w-1},{y0+h-1}) bytes=0x{so:06x}-0x{eo:06x}')
|
||||
61
tools/vram_density_scan.py
Normal file
61
tools/vram_density_scan.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
IN_PATH = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
W,H = 1024,512
|
||||
with open(IN_PATH,'rb') as f:
|
||||
data = f.read()
|
||||
count = min(len(data)//2, W*H)
|
||||
mask = [0]*(W*H)
|
||||
for i in range(count):
|
||||
off = i*2
|
||||
val = data[off] | (data[off+1]<<8)
|
||||
if val & 0x7fff:
|
||||
mask[i]=1
|
||||
# row sums
|
||||
row_sums = [sum(mask[y*W:(y+1)*W]) for y in range(H)]
|
||||
col_sums = [sum(mask[x::W]) for x in range(W)]
|
||||
# find row bands where row_sum > threshold
|
||||
thr_row = max(10, int(max(row_sums)*0.05))
|
||||
runs = []
|
||||
inside = False
|
||||
for y, s in enumerate(row_sums):
|
||||
if s>thr_row and not inside:
|
||||
start = y; inside=True
|
||||
if s<=thr_row and inside:
|
||||
end = y-1; inside=False; runs.append((start,end))
|
||||
if inside: runs.append((start,H-1))
|
||||
print('row runs (threshold=%d):'%thr_row, runs)
|
||||
# print top runs with sums
|
||||
for (s,e) in runs:
|
||||
tot = sum(row_sums[s:e+1])
|
||||
print('run',s,e,'rows=',e-s+1,'sum=',tot)
|
||||
# find column runs similarly
|
||||
thr_col = max(5, int(max(col_sums)*0.05))
|
||||
cruns=[]; inside=False
|
||||
for x,s in enumerate(col_sums):
|
||||
if s>thr_col and not inside:
|
||||
sx=x; inside=True
|
||||
if s<=thr_col and inside:
|
||||
ex=x-1; inside=False; cruns.append((sx,ex))
|
||||
if inside: cruns.append((sx,W-1))
|
||||
print('col runs (threshold=%d):'%thr_col, cruns[:10])
|
||||
# Identify intersection boxes by combining top few row runs and col runs
|
||||
candidates=[]
|
||||
for (ry0,ry1) in runs:
|
||||
for (cx0,cx1) in cruns:
|
||||
# compute density
|
||||
area = (ry1-ry0+1)*(cx1-cx0+1)
|
||||
s = 0
|
||||
for y in range(ry0,ry1+1):
|
||||
s += sum(mask[y*W+cx0 : y*W+cx1+1])
|
||||
if s > max(200, area*0.02):
|
||||
candidates.append((cx0,ry0,cx1,ry1,s,area))
|
||||
candidates.sort(key=lambda x:-x[4])
|
||||
print('\nCandidates:')
|
||||
for i,(x0,y0,x1,y1,s,area) in enumerate(candidates[:12]):
|
||||
so = (y0*W + x0)*2
|
||||
eo = ((y1*W + x1)+1)*2 -1
|
||||
print(f'[{i}] box=({x0},{y0})-({x1},{y1}) area_pixels={area} nonzero={s} bytes=0x{so:06x}-0x{eo:06x}')
|
||||
# print a few rows around top to locate HUD band
|
||||
print('\nTop rows with nonzero counts (y:count)')
|
||||
for y in range(0,160):
|
||||
if row_sums[y]>0:
|
||||
print(y, row_sums[y])
|
||||
60
tools/vram_to_bmp.py
Normal file
60
tools/vram_to_bmp.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import struct, os
|
||||
IN_PATH = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
OUT_PATH = r"K:\ghidra\Crusader_Decomp\binary\vram_weapons.bmp"
|
||||
W, H = 1024, 512
|
||||
with open(IN_PATH, 'rb') as f:
|
||||
data = f.read()
|
||||
exp = W * H * 2
|
||||
if len(data) < exp:
|
||||
print(f"Warning: expected {exp} bytes, got {len(data)} bytes")
|
||||
# read pixels (little-endian 16bpp)
|
||||
count = min(len(data) // 2, W * H)
|
||||
pixels = [None] * (W * H)
|
||||
for i in range(count):
|
||||
off = i * 2
|
||||
val = data[off] | (data[off+1] << 8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >> 5) & 0x1F) << 3
|
||||
r = ((val >> 10) & 0x1F) << 3
|
||||
pixels[i] = (r, g, b)
|
||||
# fill remaining if any
|
||||
for i in range(count, W*H):
|
||||
pixels[i] = (0,0,0)
|
||||
# BMP row padding
|
||||
row_bytes_unpadded = 3 * W
|
||||
row_size = (row_bytes_unpadded + 3) // 4 * 4
|
||||
pixel_data = bytearray()
|
||||
for y in range(H-1, -1, -1):
|
||||
row_start = y * W
|
||||
for x in range(W):
|
||||
r,g,b = pixels[row_start + x]
|
||||
pixel_data.extend(bytes((b, g, r)))
|
||||
pad = row_size - row_bytes_unpadded
|
||||
if pad:
|
||||
pixel_data.extend(b"\x00" * pad)
|
||||
# headers
|
||||
bfType = b'BM'
|
||||
bfSize = 14 + 40 + len(pixel_data)
|
||||
bfReserved1 = 0
|
||||
bfReserved2 = 0
|
||||
bfOffBits = 14 + 40
|
||||
bmp = bytearray()
|
||||
bmp.extend(bfType)
|
||||
bmp.extend(struct.pack('<IHHI', bfSize, bfReserved1, bfReserved2, bfOffBits))
|
||||
# DIB header (BITMAPINFOHEADER)
|
||||
biSize = 40
|
||||
biWidth = W
|
||||
biHeight = H
|
||||
biPlanes = 1
|
||||
biBitCount = 24
|
||||
biCompression = 0
|
||||
biSizeImage = len(pixel_data)
|
||||
biXPelsPerMeter = 2835
|
||||
biYPelsPerMeter = 2835
|
||||
biClrUsed = 0
|
||||
biClrImportant = 0
|
||||
bmp.extend(struct.pack('<IIIHHIIIIII', biSize, biWidth, biHeight, biPlanes, biBitCount, biCompression, biSizeImage, biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant))
|
||||
bmp.extend(pixel_data)
|
||||
with open(OUT_PATH, 'wb') as f:
|
||||
f.write(bmp)
|
||||
print('Wrote', OUT_PATH, 'bytes=', len(bmp))
|
||||
46
tools/vram_to_png.py
Normal file
46
tools/vram_to_png.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import struct, zlib
|
||||
IN_PATH = r"K:\ghidra\Crusader_Decomp\binary\Crusader - No Remorse Memdump Weapons.bin"
|
||||
OUT_PATH = r"K:\ghidra\Crusader_Decomp\binary\vram_weapons.png"
|
||||
W, H = 1024, 512
|
||||
with open(IN_PATH, 'rb') as f:
|
||||
data = f.read()
|
||||
exp = W * H * 2
|
||||
if len(data) < exp:
|
||||
print(f"Warning: expected {exp} bytes, got {len(data)} bytes")
|
||||
count = min(len(data) // 2, W * H)
|
||||
# build raw RGB rows top-to-bottom
|
||||
rows = []
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
i = y * W + x
|
||||
if i < count:
|
||||
off = i * 2
|
||||
val = data[off] | (data[off+1] << 8)
|
||||
b = (val & 0x1F) << 3
|
||||
g = ((val >> 5) & 0x1F) << 3
|
||||
r = ((val >> 10) & 0x1F) << 3
|
||||
else:
|
||||
r = g = b = 0
|
||||
row.extend([r, g, b])
|
||||
rows.append(b"\x00" + bytes(row))
|
||||
raw = b"".join(rows)
|
||||
comp = zlib.compress(raw, level=9)
|
||||
# PNG helpers
|
||||
def chunk(ch_type, data):
|
||||
out = struct.pack('>I', len(data)) + ch_type + data
|
||||
import zlib
|
||||
crc = zlib.crc32(ch_type + data) & 0xffffffff
|
||||
out += struct.pack('>I', crc)
|
||||
return out
|
||||
png = b"\x89PNG\r\n\x1a\n"
|
||||
# IHDR
|
||||
ihdr = struct.pack('>IIBBBBB', W, H, 8, 2, 0, 0, 0) # 8-bit, truecolor, no interlace
|
||||
png += chunk(b'IHDR', ihdr)
|
||||
# IDAT
|
||||
png += chunk(b'IDAT', comp)
|
||||
# IEND
|
||||
png += chunk(b'IEND', b'')
|
||||
with open(OUT_PATH, 'wb') as f:
|
||||
f.write(png)
|
||||
print('Wrote', OUT_PATH, 'size=', len(png))
|
||||
Loading…
Add table
Add a link
Reference in a new issue