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
|
#!/usr/bin/env python3
"""stitch_overworld.py <sock> <out.png> — drive EXPLORE mode, DFS the map
connection graph, screenshot every reachable overworld map, and composite them
into one big overworld image positioned by connection direction.
Assumes EXPLORE is ALREADY open (on the origin map) when this starts."""
import socket, sys, time
from PIL import Image, ImageDraw
sock, out = sys.argv[1], sys.argv[2]
c = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); c.settimeout(8); c.connect(sock); c.recv(200)
def cmd(l):
c.sendall((l+"\n").encode()); b=b""
while b"\n" not in b: b+=c.recv(65536)
return b.split(b"\n",1)[0].decode()
def rd(a,n):
o=[]
while n>0:
k=min(4096,n); h=cmd(f"read 0x{a:04X} {k}").split()[-1]
o+=[int(h[i*2:i*2+2],16) for i in range(k)]; a+=k; n-=k
return o
reg=lambda a: rd(a,1)[0]
def capture():
LCDC=reg(0xFF40);SCY=reg(0xFF42);SCX=reg(0xFF43)
cmd("write 0xFF4F 00"); v0=rd(0x8000,0x2000)
cmd("write 0xFF4F 01"); v1=rd(0x8000,0x2000)
cmd("write 0xFF4F 00")
vb=lambda bank,addr:(v0 if bank==0 else v1)[addr-0x8000]
bgmap=0x9C00 if (LCDC>>3)&1 else 0x9800
uns=(LCDC>>4)&1
ta=lambda idx:0x8000+idx*16 if uns else 0x9000+((idx^0x80)-0x80)*16
shade=[255,170,85,0]
fb=[[255]*160 for _ in range(144)]
def fetch(mx,my):
ent=bgmap+(my//8)*32+(mx//8)
idx=vb(0,ent);attr=vb(1,ent)
bank=(attr>>3)&1;xf=(attr>>5)&1;yf=(attr>>6)&1
a=ta(idx); ry=(7-(my%8)) if yf else (my%8); rx=(7-(mx%8)) if xf else (mx%8)
b1=vb(bank,a+ry*2);b2=vb(bank,a+ry*2+1)
return shade[((b1>>(7-rx))&1)|(((b2>>(7-rx))&1)<<1)]
for sy in range(144):
for sx in range(160):
if LCDC&1: fb[sy][sx]=fetch((sx+SCX)&0xFF,(sy+SCY)&0xFF)
img=Image.new("L",(160,144))
img.putdata([fb[y][x] for y in range(144) for x in range(160)])
return img.convert("RGB")
def curmap(): return reg(0xD35D)
def conns():
cn=reg(0xD36F)
return {'N':reg(0xD370) if cn&8 else None,
'S':reg(0xD37B) if cn&4 else None,
'W':reg(0xD386) if cn&2 else None,
'E':reg(0xD391) if cn&1 else None}
def press(k, settle=1.4):
cmd("+"+k); time.sleep(0.26); cmd("-"+k); time.sleep(settle)
def move(k, src, target):
"""press k to go src->target, with retries; only re-press while still on src."""
for _ in range(4):
press(k)
m=curmap()
if m==target: return True
if m!=src: return False # ended somewhere unexpected; don't hammer
return False
OFF={'N':(0,-1),'S':(0,1),'W':(-1,0),'E':(1,0)}
KEY={'N':'up','S':'down','W':'left','E':'right'}
OPP={'N':'S','S':'N','W':'E','E':'W'}
cmd("release")
shots={} # map_id -> RGB image
grid={} # map_id -> (col,row)
cells={} # (col,row) -> map_id (collision detect)
order=[]
def visit(mid,col,row):
shots[mid]=capture(); grid[mid]=(col,row); order.append(mid)
if (col,row) in cells and cells[(col,row)]!=mid:
print(f" COLLISION at ({col},{row}): 0x{cells[(col,row)]:02X} vs 0x{mid:02X}")
cells[(col,row)]=mid
cn=conns()
print(f"map 0x{mid:02X} @({col},{row}) conn "+" ".join(f"{d}=0x{v:02X}" for d,v in cn.items() if v is not None))
for d in ('N','S','W','E'):
nb=cn[d]
if nb is None or nb in grid: continue
if not move(KEY[d], mid, nb):
print(f" !! {d} 0x{mid:02X}->0x{nb:02X} failed (on 0x{curmap():02X}); resync")
# try to get back to mid before continuing
if curmap()!=mid: move(KEY[OPP[d]], curmap(), mid)
continue
dc,dr=OFF[d]; visit(nb,col+dc,row+dr)
if not move(KEY[OPP[d]], nb, mid):
print(f" !! backtrack 0x{nb:02X}->0x{mid:02X} failed (on 0x{curmap():02X})")
start=curmap()
print(f"start map=0x{start:02X}")
visit(start,0,0)
cols=[p[0] for p in grid.values()]; rows=[p[1] for p in grid.values()]
minc,maxc=min(cols),max(cols); minr,maxr=min(rows),max(rows)
W=(maxc-minc+1)*160; H=(maxr-minr+1)*144
canvas=Image.new("RGB",(W,H),(40,40,40))
dr=ImageDraw.Draw(canvas)
for mid,(col,row) in grid.items():
x=(col-minc)*160; y=(row-minr)*144
canvas.paste(shots[mid],(x,y))
dr.rectangle([x,y,x+159,y+143],outline=(255,0,0))
dr.text((x+2,y+2),f"{mid:02X}",fill=(255,0,0))
canvas.save(out)
print(f"\nstitched {len(shots)} maps -> {out} ({W}x{H})")
|