-- Gargoyles, Genesis (BizHawk) -- feos, 2015-2017 --== Shortcuts ==-- local rb = memory.read_u8 local rw = memory.read_u16_be local rws = memory.read_s16_be local r24 = memory.read_u24_be local rl = memory.read_u32_be local box = gui.drawBox local text = gui.text local ptext = gui.pixelText local line = gui.drawLine --== RAM addresses ==-- local levnum = 0xff00ba local LevelFlr = 0xff00c0 local LevelCon = 0xff00c4 local mapline_tab = 0xff0244 local GlobalBase = 0xff1c76 local GolBase = 0xff2c76 local MapA_Buff = 0xff4af0 --== Camera Hack ==-- local camhack = false local div = 1 -- scale local size = 16/div -- block size --== Block cache ==-- local col = 0 -- block color local opout = 0x33000000 -- outer opacity local opin = 0x66000000 -- inner opacity local op = 0xff000000 local cache = {} --== Other stuff ==-- local MsgCutoff = 20 local MsgTime = 1 local MsgStep = 14 local MsgOffs = 2 local MsgTable = {} local XposLast = 0 local YposLast = 0 local room = 0 local workinglast = 0 local wSize = client.getwindowsize() local lagcount = emu.lagcount() gui.defaultTextBackground(0xff000000) --== Object types ==-- local types = { "Goliath","Orb","Health1PLR","Health2PLR","Health1NME","Health2NME","Numeral", "BigExplode","SmallExplode","GlassDebris","MetalDebris","WoodDebris", "WallDebris","SignPiece","SteamVent","BreakWall","SkyLight","BreakLight", "ThrowCrate","BreakEdgeLeft","BreakEdgeRight","Spark","Spark2","Sparks", "Sparks2","Fireball","HomingProj1","HorzProj1","VertProj1","DirProj1", "DirProj2","DropMine","Scratch","Icon","RaptorBot","SniperBot","SpiderBot", "WaspBot","Xanatos","PlasmaBot","RabidHH","MorningStar","Archer","Arrow", "Valkyrie","Axe","WeaponExp","Couldron","SpittingCouldron","FireballHead", "FireballTrail","BigFireballHead","BigFireballTrail","Oil","OilGenerator", "Claw","Stump","StumpBubble","StumpFire","ClawStump","StumpFireGen","Vent", "VentSparks","Chain","FlameLick","Floor","MutVikBody","MutVikHead", "MutVikHammer","EyeOfOdin","EyeOfOdinTrail","L1BreakWall","Catapult", "L1BreakFloor","Gate","GateCrusher","Weight","WeightCrusher","WallFire", "Balista","BalistaLog","PasteWall","FlameBoulder","CastlePiece", "MutantSpiderBot","MutSpiLegs","MutSpiHead","MutSpiHeadFlame","MutSpiProj", "MutSpiElecV","MutSpiElecH","PlasmaBall","PlasmaBallTail","PlasmaDeadHead", "VertFlame","WallFlame","FloorFlame","OPPlatform","OPLink","OPOrb", "Furnace","RobotGenerator","RockGenerator","BigRock","MediumRock", "SmallRock","BigCouldronGen","BigCouldron","Trough","TroughGen","Energizer", "Demona","TrajectoryProj","WallPaste","EdgePaste","Tentacle","Infuser", "BigGuns","BigGunsProj","HighSignPole","HighSign","LowLight","L5Skylight", "L5Wall","ElecGenerator","Electricity","WaspGenerator","TunnelEdge", "ForegroundPost","Sorcerer","LightningTop","LightningBot","MDemonaWallFire", "MDemonaFloorFire","EyeRooftopUp","EyeRooftopDn","EyeRaptor" } local function RoomTime() local start11 = 894--767 local start12 = 2294 local start13 = 5468 -- 4254 -- 4101 local startl4 = 5506 local startl5 = 7117 local startl6 = 8412 local startl7 = 17117 local timer = emu.framecount() if timer < start11 then room = timer elseif timer < start12 then room = timer - start11 elseif timer < start13 then room = timer - start12 elseif timer < startl4 then room = timer - start13 elseif timer < startl5 then room = timer - startl4 elseif timer < startl6 then room = timer - startl5 elseif timer < startl7 then room = timer - startl6 end text(2, 2, string.format("cx:%5d\ncy:%5d\nroom:%d", camx, camy, room), "white", "bottomright") end local function HUD() --if working > 0 then return end local rndcol = "white" if rndlast ~= rnd1 then rndcol = "red" end text(0, 2, string.format("RNG:%08X %04X", rnd1, rnd2), rndcol, "bottomleft") text(2, 0, string.format( "x: %4d\ny: %4d\ndx: %3d\ndy: %3d\nhp: %3d\nrun:%3d\ninv:%3d", Xpos, Ypos, Xspd, Yspd, health, run, inv), "white", "topright") end local function CamhackHUD() if working == 0 then -- screen edge box((backx-camx- 1)/div, (backy-camy- 1)/div, (backx-camx+320)/div, (backy-camy+224)/div, 0xff0000ff, 0) -- map edge box( 0-camx/div+size, 0-camy/div+size, mapw/div-camx/div, maph/div-camy/div, 0xff0000ff, 0) end if camhack or div > 1 then text(0, 0, string.format("div:%d", div), "white", "topleft") end end local function PosToIndex(x, y) return (x // 16)+(y // 16)*xblocks end local function IndexToPos(i) return { x=(i%xblocks)*16, y=(i // xblocks)*16 } end local function InBounds(x, minimum, maximum) if x >= minimum and x <= maximum then return true else return false end end local function GetBlock(x, y) if working > 0 then return nil end local final = { contour={}, block=0 } if x > 0 and x < mapw and y > 0 and y < maph then local pixels = 0 local x1 = x/div-camx/div local x2 = x1+size-1 local y1 = y/div-camy/div local y2 = y1+size-1 local d4 = rw(mapline_tab+(y >> 4)*2) local a1 = r24(LevelFlr+1) local d1 = rw(MapA_Buff+d4+(x >> 4)*2) >> 1 final.block = rb(a1+d1+2) d1 = rw(a1+d1) a1 = r24(LevelCon+1)+d1 if rb(a1) > 0 or rb(a1+8) > 0 then for pixel=0, 15 do final.contour[pixel] = rb(a1+pixel) end else final.contour = nil end else return nil end return final end local DrawBlock = { [0x80] = function(x1, y1, x2, y2) -- WALL col = 0x00ffffff -- white line(x1, y1, x1, y2, col+op) -- left line(x2, y1, x2, y2, col+op) -- right end, [0x81] = function(x1, y1, x2, y2) -- CEILING col = 0x00ffffff -- white line(x1, y2, x2, y2, col+op) -- bottom end, [0x82] = function(x1, y1, x2, y2) -- CLIMB_U col = 0x0000ffff -- cyan line(x1, y2, x2, y2, col+op) -- bottom end, [0x83] = function(x1, y1, x2, y2) -- CLIMB_R col = 0x0000ffff -- cyan line(x1, y1, x1, y2, col+op) -- left end, [0x84] = function(x1, y1, x2, y2) -- CLIMB_L col = 0x0000ffff -- cyan line(x2, y1, x2, y2, col+op) -- right end, [0x85] = function(x1, y1, x2, y2) -- CLIMB_LR col = 0x0000ffff -- cyan line(x1, y1, x1, y2, col+op) -- left line(x2, y1, x2, y2, col+op) -- right end, [0x86] = function(x1, y1, x2, y2) -- CLIMB_R_STAND_R col = 0x00ffffff -- white line(x1, y1, x2, y1, col+op) -- top col = 0x0000ffff -- cyan line(x1, y1, x1, y2, col+op) -- left end, [0x87] = function(x1, y1, x2, y2) -- CLIMB_L_STAND_L col = 0x00ffffff -- white line(x1, y1, x2, y1, col+op) -- top col = 0x0000ffff -- cyan line(x2, y1, x2, y2, col+op) -- right end, [0x88] = function(x1, y1, x2, y2) -- CLIMB_LR_STAND_LR col = 0x00ffffff -- white line(x1, y1, x2, y1, col+op) -- top col = 0x00ff00ff -- cyan line(x1, y1, x1, y2, col+op) -- left col = 0x0000ffff -- cyan line(x2, y1, x2, y2, col+op) -- right end, [0x70] = function(x1, y1, x2, y2) -- GRAB_SWING col = 0x0000ff00 -- green box(x1, y1, x2, y2, col, col+opout) end, [0x7f] = function(x1, y1, x2, y2) -- EXIT col = 0x00ffff00 -- yellow end, [0xd0] = function(x1, y1, x2, y2) -- SPIKES col = 0x00ff0000 -- red box(x1, y1, x2, y2, col, col+opout) end, [0xd1] = function(x1, y1, x2, y2) -- SPIKES col = 0x00ff0000 -- red box(x1, y1, x2, y2, col, col+opout) end } local function DrawBlockDefault(x1, y1, x2, y2) -- LEVEL_SPECIFIC col = 0x00ff8800 -- orange box(x1, y1, x2, y2, col+opin, col+opout) end local function DrawBG(unit, x, y) local val= 0 local x1 = x/div-camx/div-(camx%16)/div local x2 = x1+size-1 local y1 = y/div-camy/div-(camy%16)/div local y2 = y1+size-1 if unit.contour ~= nil then box(x1, y1, x2, y2, 0x5500ff00, 0x5500ff00) for pixel=0, 15 do val = unit.contour[pixel] --[ [-- if val > 0 then gui.drawPixel( x1+pixel/div, y1+val/div-1/div, 0xffffff00) end --]]-- end end if unit.block > 0 then local Fn = DrawBlock[unit.block] or DrawBlockDefault Fn(x1, y1, x2, y2) box(x1, y1, x2, y2, col+opin, col+opout) end end local function Background() if working > 0 then cache = {} return end if camhack then camx = Xpos-320/2*div camy = Ypos-224/2*div box(0, 0, 320, 240, 0, 0x66000000) end local border = 0 local offset = 32 local basex = camx+border local basey = camy+border local basei = PosToIndex(basex-offset, basey-offset) local boundx = 320*div-border local boundy = 224*div-border local xblockstockeck = ((camx+boundx+offset)-(basex-offset))/size/div local yblockstockeck = ((camy+boundy+offset)-(basey-offset))/size/div for yblock = 0, yblockstockeck do for xblock = 0, xblockstockeck do local i = yblock*xblocks+xblock+basei local x = basex+xblock*size*div local y = basey+yblock*size*div if InBounds(x, basex-offset, camx+boundx+offset) then local unit = cache[i] if unit == nil or workinglast > 0 then if InBounds(x, basex, camx+boundx) and InBounds(y, basey, camy+boundy) then cache[i] = GetBlock(x, y) end else if not InBounds(x, basex, camx+boundx) and not InBounds(y, basey, camy+boundy) then cache[i] = nil end end if unit ~= nil then DrawBG(unit, x, y) end elseif cache[i] ~= nil then cache[i] = nil end end end CamhackHUD() end local function Clamp(v, vmin, vmax) if v < vmin then v = vmin end if v > vmax then v = vmax end return v end local function Objects() if working > 0 then return end for i=0, 63 do local base = GlobalBase+i*128 local flag2 = rb(base+0x49) & 0x10 -- active if flag2 == 0x10 then local xpos = rw (base+0x00) local ypos = rw (base+0x02) local state = rw (base+0x0c) local dmg = rb (base+0x10) local id = rw (base+0x40) local hp = rw (base+0x50) local cRAM = r24(base+0x75) -- pointer to 4 collision boxes per object local xscr = (xpos-camx)/div local yscr = (ypos-camy)/div local num = id/6 local name = types[num] local col = 0 -- collision color for boxx=0, 4 do local x0 = rw (cRAM+boxx*8) local x1 = (rws(cRAM+boxx*8+0)-camx)/div local y1 = (rws(cRAM+boxx*8+2)-camy)/div local x2 = (rws(cRAM+boxx*8+4)-camx)/div local y2 = (rws(cRAM+boxx*8+6)-camy)/div if boxx == 0 then col = 0xff00ff00 -- body -- archer hp doesn't matter if id == 282 or id == 258 then hp = 1 end if hp > 0 and id > 0 and x0 ~= 0x8888 then local xx = Clamp(xscr, 0, 318-string.len(name)*4) local yy = Clamp(yscr, 0, 214) ptext(xx, yy+2, string.format("%d", hp), col) end elseif boxx == 1 then col = 0xffffff00 -- floor elseif boxx == 2 then if dmg > 0 then col = 0xffff0000 -- projectile else col = 0xff8800ff -- item end if dmg > 0 then text(x1*wSize+2, y2*wSize+1, string.format("%d", dmg), col, 0x88000000) end else col = 0xffffffff -- other end if x1 ~= 0x8888 and x1 <= 320 and x2 >= 0 and y1 <= 224 and y2 >= 0 then box(x1, y1, x2, y2, col, 0) end end end end end local function PostRndRoll() for i = 1,#MsgTable do if MsgTable[i] and MsgTable[i].index == i then local base = MsgTable[i].base local xpos = rw(base+0x00) local ypos = rw(base+0x02) local id = rw(base+0x40) local x = (xpos-camx)/div local y = (ypos-camy)/div local num = id/6 local ymsg = 0 local yoffs = ((i-1) // MsgCutoff)*14 local name = types[num] local color = 0xffffff00 if base == GolBase then name = "Goliath" elseif not name then name = string.format("%X", base) color = 0xff00ffff end if y < 224/2 then yoffs = -yoffs ymsg = 210 end x = Clamp(x, 2, 320-string.len(name)*4) y = Clamp(y, 20, 214) line ((i-1)%MsgCutoff*MsgStep+3 +MsgOffs, ymsg+yoffs+4, x, y, color-0x88000000) ptext((i-1)%MsgCutoff*MsgStep*wSize+MsgOffs, ymsg+yoffs, i, color) MsgTable[i].timer = MsgTable[i].timer-1 if MsgTable[i].timer <= 0 then MsgTable[i] = nil end end end end local function PlayerBoxes() if working > 0 then return end local xx = (Xpos-camx)/div local yy = (Ypos-camy)/div local col = 0xff00ffff local swcol = col -- usual detection if Yspd > 0 then -- gimme swings to grab! swcol = 0xff00ff00 elseif Yspd == 0 then -- can tell that too swcol = 0xffffffff end if facing == 2 then box(xx-0xf /div-2, yy-0x2c/div-1, xx-0xf/div+0, yy-0x2c/div+1, swcol, 0) -- lefttop else box(xx+0xf /div , yy-0x2c/div-1, xx+0xf/div+2, yy-0x2c/div+1, swcol, 0) -- rightttop end box(xx -1, yy-0x2c/div-1, xx +1, yy-0x2c/div+1, col, 0) -- top box(xx-0xf /div-2, yy-0x1f/div-1, xx-0xf /div+0, yy-0x1f/div+1, col, 0) -- left box(xx+0x10/div-1, yy-0x1f/div-1, xx+0x10/div+1, yy-0x1f/div+1, col, 0) -- right -- box(xx -1, yy-0x1f/div-1, xx +1, yy-0x1f/div+1, col, 0) -- center box(xx -1, yy-0x0f/div-1, xx +1, yy-0x0f/div+1, col, 0) -- bottom box(xx -1, yy -1, xx +1, yy +1,0xffffff00, 0) -- feet -- box(xx -1, yy+0x10/div-1, xx +1, yy+0x10/div+1, col, 0) -- ground end local function Input() local i, u, d, l, r, a, b, c, s if movie.isloaded() then i = movie.getinput(emu.framecount()-1) else i = joypad.getimmediate() end if i["P1 Up" ] then u = "U" else u = " " end if i["P1 Down" ] then d = "D" else d = " " end if i["P1 Left" ] then l = "L" else l = " " end if i["P1 Right"] then r = "R" else r = " " end if i["P1 A" ] then a = "A" else a = " " end if i["P1 B" ] then b = "B" else b = " " end if i["P1 C" ] then c = "C" else c = " " end if i["P1 Start"] then s = "S" else s = " " end text(1, 10, u..d..l..r..a..b..c..s, "yellow") end event.onframeend(function() emu.setislagged(rb(0xfff6d4) == 0) if rb(0xfff6d4) == 0 then lagcount = lagcount+1 framecol = "red" else framecol = "white" end emu.setlagcount(lagcount) wSize = client.getwindowsize() rndlast = rnd1 workinglast = working XposLast = Xpos YposLast = Ypos end) event.onmemoryexecute(function() local a0 = emu.getregister("M68K A0") & 0xffffff if a0 ~= 0xff4044 then for i = 1, 200 do if MsgTable[i] == nil then MsgTable[i] = { index = i, timer = MsgTime, base = a0 } break end end end end, 0x257A, "RNGseed") local function main() rnd1 = rl (0xff001c) rnd2 = rw (0xff0020) working = rb (0xff0073) xblocks = rw (0xff00d4) mapw = rw (0xff00d4)*8 maph = rw (0xff00d6)*8 Xpos = rws(0xff0106) Ypos = rws(0xff0108) camx = rws(0xff010c)+16 camy = rws(0xff010e)+16 run = rb (0xff1699) inv = rw (0xff16d2) health = rws(0xff2cc6) backx = camx backy = camy Xspd = Xpos-XposLast Yspd = Ypos-YposLast facing = rb(GolBase+0x48) & 2 -- object flag 1 if working > 0 then MsgTable = {} end Background() PlayerBoxes() Objects() PostRndRoll() HUD() RoomTime() end while true do main() emu.frameadvance() gui.clearGraphics() end