--GBA Zook Man ZX4 -- Tile Overlay script
--Loads all those pretty maps, then displays tiles as you wander aimlessly.
--FatRatKnight
--memory.usememorydomain("ROM")
local R4u , R4s= memory.read_u32_le , memory.read_s32_le
local R2u , R2s= memory.read_u16_le , memory.read_s16_le
local R1u = memory.read_u8
local ROMmin,ROMmax= 0x08000000, 0x08000000+memory.getmemorydomainsize("ROM")
--*****************************************************************************
local function FetchAddrDomainGBA(a)
--*****************************************************************************
--I am furious at the design away from the bus. It used to exist! Why remove?
--I do not want to code in removing offsets to pointers every time I read one.
--This function was made because I insist on full pointers. Has only what I know.
if (a >= 0x02000000) and (a < (0x02000000+memory.getmemorydomainsize("EWRAM"))) then
return a-0x02000000, "EWRAM"
elseif (a >= 0x03000000) and (a < (0x03000000+memory.getmemorydomainsize("IWRAM"))) then
return a-0x03000000, "IWRAM"
elseif (a >= 0x08000000) and (a < (0x08000000+memory.getmemorydomainsize("ROM"))) then
return a-0x08000000, "ROM"
else
error(string.format("Unknown address %08X", a),1)
end
end
--*****************************************************************************
local function LoadMaps()
--*****************************************************************************
--This function tracks down every stage, every segment in each stage, every
--block in each segment, and every tile in each block. Then it stitches
--together the tiles as it would fit in the block.
local Maps= {}
for Stage= 0, 16 do
local StageMaps= {} --Construct new table
local BlockSetPtr= R4u(0x3CD86C + 4*Stage,"ROM")
local SegmentBase= R4u(0x3CD8B0 + 4*Stage,"ROM")
local Segment= 0
while true do
local SegmentPtr= R4u(FetchAddrDomainGBA(SegmentBase + Segment*4))
if (SegmentPtr < ROMmin) or (SegmentPtr >= ROMmax) then break end
local SegX, SegY= R2s(FetchAddrDomainGBA(SegmentPtr)), R2s(FetchAddrDomainGBA(SegmentPtr+2))
SegmentPtr= SegmentPtr+4
local SegmentMap= {}
for y= 0, SegY-1 do
for x= 0, SegX-1 do
local BlockIndex= R2u(FetchAddrDomainGBA(SegmentPtr + (y*SegX + x)*2))
local BlockPtr= R4u(FetchAddrDomainGBA(BlockSetPtr + 4*BlockIndex))
for yy= 0, 31 do
for xx= 0, 31 do
local i= xx + x*32 + yy*SegX*32 + y*SegX*1024
SegmentMap[i]= R1u(FetchAddrDomainGBA(BlockPtr + 2 + xx + 32*yy))
end
end
end
end
StageMaps[Segment]= SegmentMap
Segment= Segment+1
end
Maps[Stage]= StageMaps --Stash what we've got!
print("Loaded Stage " .. Stage)
end
return Maps
end
if not Maps then --Globalize the Maps table.
Maps= LoadMaps() --This way it stays loaded after script expires.
end --Core reboots (such as starting a new movie) still resets.
--x - X location on screen. Might be negative./
--y - Y location on screen.
--i - Block index, in case you put the function in multiple places
local function NullFn() return end
local DefaultColors= {
[0x00]= 0xFFFFFFFF,
[0x10]= 0xFF00FF00,
[0x60]= 0xFFFFFF00,
[0x70]= 0xFF00FFFF
}
--*****************************************************************************
local function Tile_Default(x,y,i)
--*****************************************************************************
local clr= DefaultColors[i - i%0x10] or 0xFFFF00FF
gui.pixelText(x,y,string.format("%X",i%0x10),clr)
end
local BlockColors= {
[0]=0x00000000, -- Open air
0xC0FFFFFF, -- Floor
0xC0FF2000, -- Spikes
0xC0000000, -- Walls (basic)
0x00000000, -- Unused
0xC000FFFF, -- Walljump
[0x0A]=0x80FFFF00, --Slope magnet downward from above
[0x0B]=0x80FFFF00, --Slope magnet upward from below
[0x14]=0x8040A000, --Ladder top
[0x15]=0x8040FF40, --Ladder mid
[0x16]=0x8040A000, --Ladder bottom
[0x17]=0xC00000FF, --Save Platform mid
[0x18]=0xC00000FF, --Save Platform end
[0x19]=0xC08000FF, --Capsule Head
[0x1A]=0xC08000FF, --Capsule Body
[0x1B]=0xC08000FF, --Capsule Leg
[0x1C]=0xC08000FF, --Capsule Arm
[0x1F]=0xC08000FF, --Autoscroll ender tiles
}
--*****************************************************************************
local function Tile_Blocks(x,y,i)
--*****************************************************************************
local clr= BlockColors[i] or 0xFFFF00FF
local bclr= bit.band(clr/2,0xFF000000) + bit.band(clr,0x00FFFFFF)
gui.drawRectangle(x,y,7,7,clr,bclr)
end
local BreakColors= {
[0x60]=0x60FF0000, [0x61]=0x60FFFF00, [0x62]=0x6000FF00, [0x63]=0x6000FFFF,
[0x64]=0x600000FF, [0x65]=0x60FF00FF, [0x66]=0x60FFFFFF, [0x67]=0x60000000,
[0x68]=0x6080FF00, [0x69]=0x6000FF80, [0x6A]=0x600080FF, [0x6B]=0x608000FF,
[0x6C]=0x60FF0080, [0x6D]=0x60FF8000, [0x6E]=0x60808080, [0x6F]=0x60800080,
[0x70]=0x60808000, [0x71]=0x60008080, [0x72]=0x60FF80FF, [0x73]=0x60FFFF80,
[0x74]=0x6080FFFF, [0x75]=0x6080FF80, [0x76]=0x608080FF
}
--*****************************************************************************
local function Tile_Break(x,y,i)
--*****************************************************************************
local clr= BreakColors[i] or 0xC0FF00FF
gui.drawRectangle(x,y,7,7,0,clr)
end
local SlopeHeights= {
[0x06]={L=7,R=4},[0x07]={L=3,R=0},[0x08]={L=0,R=3},[0x09]={L=4,R=7},
[0x0C]={L=7,R=6},[0x0D]={L=5,R=4},[0x0E]={L=3,R=2},[0x0F]={L=1,R=0},
[0x10]={L=0,R=1},[0x11]={L=2,R=3},[0x12]={L=4,R=5},[0x13]={L=6,R=7}
}
--*****************************************************************************
local function Tile_Slope(x,y,i)
--*****************************************************************************
gui.drawRectangle(x,y,7,7,0x40808000,0x40808000)
local h= SlopeHeights[i]; if not h then return end
local y1,y2= h.L+y,h.R+y
gui.drawLine(x,y1,x+7,y2,0xFFFFFFFF)
end
--[[
00 Air 01 Floor 02 Spikes 03 Solid
04 (unused) 05 Walljump 06 / Steep1 07 / Steep2
08 \ Steep1 09 \ Steep2 0A Magnet down 0B Magnet up
0C / Gentle1 0D / Gentle2 0E / Gentle3 0F / Gentle4
10 \ Gentle1 11 \ Gentle2 12 \ Gentle3 13 \ Gentle4
14 Ladder Top 15 Ladder 16 Ladder Bottom 17 Save platform
18 Save platform 19 Capsule Head 1A Capsule Body 1B Capsule Leg
1C Capsule Arm 1D (unused) 1E (unused) 1F Autoscroll end
60 - 76 Breakable blocks
]]--
local Patterns= {
[0]=NullFn ,Tile_Blocks,Tile_Blocks,Tile_Blocks, --Air,Floor,Spike,Solid
nil ,Tile_Blocks,Tile_Slope ,Tile_Slope , --nil,Wj,Sslope /
Tile_Slope ,Tile_Slope ,Tile_Blocks,Tile_Blocks, --Sslope \,magnet
Tile_Slope ,Tile_Slope ,Tile_Slope ,Tile_Slope , --Gslope /
Tile_Slope ,Tile_Slope ,Tile_Slope ,Tile_Slope , --Gslope \
Tile_Blocks,Tile_Blocks,Tile_Blocks,Tile_Blocks, --Ladder, sPlatform
Tile_Blocks,Tile_Blocks,Tile_Blocks,Tile_Blocks, --sPlatform, Capsule
Tile_Blocks,nil ,nil ,Tile_Blocks, --Capsule,nil,nil,AS end
[0x60]=Tile_Break, [0x61]=Tile_Break, [0x62]=Tile_Break, [0x63]=Tile_Break,
[0x64]=Tile_Break, [0x65]=Tile_Break, [0x66]=Tile_Break, [0x67]=Tile_Break,
[0x68]=Tile_Break, [0x69]=Tile_Break, [0x6A]=Tile_Break, [0x6B]=Tile_Break,
[0x6C]=Tile_Break, [0x6D]=Tile_Break, [0x6E]=Tile_Break, [0x6F]=Tile_Break,
[0x70]=Tile_Break, [0x71]=Tile_Break, [0x72]=Tile_Break, [0x73]=Tile_Break,
[0x74]=Tile_Break, [0x75]=Tile_Break, [0x76]=Tile_Break
}
--*****************************************************************************
local function TileOverlay2()
--*****************************************************************************
local Stage, Segment= R4s(0x152C,"IWRAM"),R4s(0x1530,"IWRAM")
local Map= Maps[Stage]; if not Map then return end
Map= Map[Segment] ; if not Map then return end
--Conveniently, the game has width and height loaded in RAM.
local SegX, SegY= R4s(0x153C,"IWRAM"), R4s(0x1540,"IWRAM")
if (SegX < 1) or (SegY < 1) or (SegX*SegY > 0x1000) then return end
local CamX, CamY= R4s(0x14F0,"IWRAM"), R4s(0x14F4,"IWRAM")
local xp, yp= -(CamX%8), -(CamY%8)
for y= 0,20 do
local Top= y*8 + yp
for x= 0,30 do
local Left= x*8 + xp
local xt,yt= (math.floor(CamX/8)+x),(math.floor(CamY/8)+y)
local Tile= Map[yt*32*SegX + xt]
if Tile then
local Fn= Patterns[Tile] or Tile_Default
Fn(Left,Top,Tile)
end
end
end
end
--*****************************************************************************
local function TileOverlay()
--*****************************************************************************
local Stage= R4u( 0x152C,"IWRAM")
if Stage > 16 then
gui.pixelText(5,5,"Bad Stage")
return
end
local SegmentPtr= R4u(0x3CD8B0 + 4*Stage,"ROM")
SegmentPtr= R4u(FetchAddrDomainGBA(SegmentPtr + R4u(0x1530,"IWRAM")*4))
if (SegmentPtr < ROMmin) or (SegmentPtr >= ROMmax) then
gui.pixelText(5,5,"Bad Segment index")
return
end
local SegX, SegY= R2s(FetchAddrDomainGBA(SegmentPtr)), R2s(FetchAddrDomainGBA(SegmentPtr+2))
SegmentPtr= SegmentPtr+4
if (SegX < 1) or (SegY < 1) or (SegX*SegY > 0x1000) then
gui.pixelText(5,5,"Bad Segment (wait, how?)")
return
end
local BlockSetPtr= R4u(0x3CD86C + 4*Stage,"ROM")
local CamX,CamY= R4s(0x14F0,"IWRAM"),R4s(0x14F4,"IWRAM")
local xp,yp= -(CamX%8), -(CamY%8)
--I will do things inefficiently: For every tile, ask what block.
for y= 0,20 do
local Top= y*8 + yp
for x= 0,30 do
local Left= x*8 + xp
--PaintBlock
local xx,yy= math.floor((CamX + x*8)/256), math.floor((CamY + y*8)/256)
if (xx < SegX) and (yy < SegY) then
local BlockIndex= R2u(FetchAddrDomainGBA(SegmentPtr + (yy*SegX + xx)*2))
local Block= R4u(FetchAddrDomainGBA(BlockSetPtr + BlockIndex*4))
local xt,yt= (math.floor(CamX/8)+x)%32,(math.floor(CamY/8)+y)%32
local Tile= R1u(FetchAddrDomainGBA(Block + 2 + xt + yt*32))
local Fn= Patterns[Tile] or Tile_Default
Fn(Left,Top,Tile)
end
end
end
end
--*****************************************************************************
local function ImportantPlayerPixels(PlX,PlY)
--*****************************************************************************
local PlAir= R4u(0x1554,"IWRAM") == 1
local PlLeft=R4u(0x1E64,"IWRAM") == 1
local PlJump=R4u(0x1E6C,"IWRAM")
-- local PlAir= R4u(0x1500,"IWRAM")
--(16,48) Left fall check
if (not PlAir) or ((PlJump >= 20) and not PlLeft) then
gui.drawPixel(PlX+16,PlY+48,0xFFFFFFFF)
gui.drawPixel(PlX+15,PlY+47,0xFFFFFFFF)
gui.drawPixel(PlX+16,PlY+47,0xFFFFFFFF)
gui.drawPixel(PlX+17,PlY+47,0xFFFFFFFF)
end
--(24,48) Right fall check
if (not PlAir) or ((PlJump >= 20) and PlLeft) then
gui.drawPixel(PlX+24,PlY+48,0xFFFFFFFF)
gui.drawPixel(PlX+23,PlY+47,0xFFFFFFFF)
gui.drawPixel(PlX+24,PlY+47,0xFFFFFFFF)
gui.drawPixel(PlX+25,PlY+47,0xFFFFFFFF)
end
--(16, 8) Left head check
if PlAir and (PlJump < 20) and (not PlLeft) then
gui.drawPixel(PlX+16,PlY+ 8,0xFFFF80FF)
gui.drawPixel(PlX+15,PlY+ 9,0xFFFF80FF)
gui.drawPixel(PlX+16,PlY+ 9,0xFFFF80FF)
gui.drawPixel(PlX+17,PlY+ 9,0xFFFF80FF)
end
--(24, 8) Right head check
if PlAir and (PlJump < 20) and PlLeft then
gui.drawPixel(PlX+24,PlY+ 8,0xFFFF80FF)
gui.drawPixel(PlX+23,PlY+ 9,0xFFFF80FF)
gui.drawPixel(PlX+24,PlY+ 9,0xFFFF80FF)
gui.drawPixel(PlX+25,PlY+ 9,0xFFFF80FF)
end
--( 8,47) Left horizontal check
if PlLeft then
gui.drawPixel(PlX+ 8,PlY+47,0xFFFFFF00)
gui.drawPixel(PlX+ 9,PlY+46,0xFFFFFF00)
gui.drawPixel(PlX+ 9,PlY+47,0xFFFFFF00)
gui.drawPixel(PlX+ 9,PlY+48,0xFFFFFF00)
end
--(32,47) Right horizontal check
if not PlLeft then
gui.drawPixel(PlX+32,PlY+47,0xFFFFFF00)
gui.drawPixel(PlX+31,PlY+46,0xFFFFFF00)
gui.drawPixel(PlX+31,PlY+47,0xFFFFFF00)
gui.drawPixel(PlX+31,PlY+48,0xFFFFFF00)
end
--( 8,32) Left walljump check
if PlAir and (PlJump >= 20) and PlLeft then
gui.drawPixel(PlX+ 8,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+ 9,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+10,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+ 9,PlY+31,0xFF00FFFF)
gui.drawPixel(PlX+10,PlY+30,0xFF00FFFF)
end
--(32,32) Right walljump check
if PlAir and (PlJump >= 20) and (not PlLeft) then
gui.drawPixel(PlX+32,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+31,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+30,PlY+32,0xFF00FFFF)
gui.drawPixel(PlX+31,PlY+31,0xFF00FFFF)
gui.drawPixel(PlX+30,PlY+30,0xFF00FFFF)
end
end
--*****************************************************************************
while true do
--*****************************************************************************
--Our overhead.
TileOverlay2()
ImportantPlayerPixels(R2s(0x1E58,"IWRAM"),R2s(0x1E5C,"IWRAM"))
emu.frameadvance()
end