-- feos, 2025 -- CONSTANTS local NULL_OBJECT = 0x88888888 -- no object at that index local OUT_OF_BOUNDS = 0xFFFFFFFF -- no such index local MINIMAL_ZOOM = 0.0001 -- ??? local ZOOM_FACTOR = 0.02 local PAN_FACTOR = 10 local CHAR_WIDTH = 10 local CHAR_HEIGHT = 16 local NEGATIVE_MAXIMUM = 1 << 63 local POSITIVE_MAXIMUM = ~NEGATIVE_MAXIMUM -- sizes in bytes local SHORT = 2 local INT = 4 local POINTER = 8 local LINE_SIZE = 256 -- sizeof(line_t) is 232, but we padded it for niceness local MOBJ_SIZE = 512 -- sizeof(mobj_t) is 464, but we padded it for niceness -- shortcuts local rl = memory.read_u32_le local rw = memory.read_u16_le local rb = memory.read_u8_le local rls = memory.read_s32_le local rws = memory.read_s16_le local rbs = memory.read_s8_le local text = gui.text local box = gui.drawBox local line = gui.drawLine --local text = gui.pixelText -- INSANELY SLOW -- TOP LEVEL VARIABLES local Zoom = 1 local LastSize = 0 local Init = true -- tables -- view offset local Pan = { x = 0, y = 0 } -- object positions bounds local OB = { top = POSITIVE_MAXIMUM, left = POSITIVE_MAXIMUM, bottom = NEGATIVE_MAXIMUM, right = NEGATIVE_MAXIMUM } local LastScreenSize = { w = client.screenwidth(), h = client.screenheight() } -- forward declarations local Offsets = {} -- mobj member offsets in bytes local MobjType = {} local SpriteNumber = {} local Objects = {} --gui.defaultPixelFont("fceux") gui.use_surface("client") local function mapify_x(coord) return math.floor(((coord / 0xffff)+Pan.x)*Zoom) end local function mapify_y(coord) return math.floor(((coord / 0xffff)+Pan.y)*Zoom) end local function in_range(var, minimum, maximum) return var >= minimum and var <= maximum end local function reset_view() Init = true update_zoom() end local function zoom_out() local newZoom = Zoom * (1 - ZOOM_FACTOR) if newZoom < MINIMAL_ZOOM then return end Zoom = newZoom end local function zoom_in() Zoom = Zoom * (1 + ZOOM_FACTOR) end local function pan_left() Pan.x = Pan.x + PAN_FACTOR/Zoom/2 end local function pan_right() Pan.x = Pan.x - PAN_FACTOR/Zoom/2 end local function pan_up() Pan.y = Pan.y + PAN_FACTOR/Zoom/2 end local function pan_down() Pan.y = Pan.y - PAN_FACTOR/Zoom/2 end local function add_offset(size, name) Offsets[name] = LastSize -- print(name, string.format("%X \t\t %X", size, LastSize)) LastSize = size + LastSize end function maybe_swap(left, right) if left > right then local smallest = right right = left left = smallest end end local function get_line_count(str) local lines = 1 local longest = 0 local size = 0 for i = 1, #str do local c = str:sub(i, i) if c == '\n' then lines = lines + 1 if size > longest then longest = size size = -1 end end size = size + 1 end if size > longest then longest = size end return lines, longest end local function iterate() if Init then return end for _, addr in ipairs(Objects) do local x = rls(addr + Offsets.x) local y = rls(addr + Offsets.y) * -1 local health = rls(addr + Offsets.health) local radius = math.floor((rls(addr + Offsets.radius) >> 16) * Zoom) local sprite = SpriteNumber[rls(addr + Offsets.sprite)] local type = rl(addr + Offsets.type) local pos = { x = mapify_x(x), y = mapify_y(y) } local color = "white" if type == 0 then type = "PLAYER" print("PLAYER") else type = MobjType[type] end if health <= 0 then color = "red" end --[[-- local z = rls(addr + Offsets.z) / 0xffff local index = rl (addr + Offsets.index) local tics = rl (addr + Offsets.tics) --]]-- if in_range(pos.x, 0, client.screenwidth()) and in_range(pos.y, 0, client.screenheight()) then text(pos.x, pos.y, string.format("%s", type), color) box(pos.x - radius, pos.y - radius, pos.x + radius, pos.y + radius, color) end end for i = 0, 100000 do local addr = i * LINE_SIZE if addr > 0xFFFFFF then break end local id = rl(addr, "Lines") & 0xFFFFFFFF if id == OUT_OF_BOUNDS then break end if id ~= NULL_OBJECT then local vertices_offset = 0xe8 local v1 = { x = rls(addr+vertices_offset , "Lines"), y = -rls(addr+vertices_offset+ 4, "Lines") } local v2 = { x = rls(addr+vertices_offset+ 8, "Lines"), y = -rls(addr+vertices_offset+12, "Lines") } line( mapify_x(v1.x), mapify_y(v1.y), mapify_x(v2.x), mapify_y(v2.y), 0xffcccccc) end end end local function init_objects() for i = 0, 100000 do local addr = i * MOBJ_SIZE if addr > 0xFFFFFF then break end local thinker = rl(addr) & 0xFFFFFFFF -- just to check if mobj is there if thinker == OUT_OF_BOUNDS then break end if thinker ~= NULL_OBJECT then local x = rls(addr + Offsets.x) / 0xffff local y = rls(addr + Offsets.y) / 0xffff * -1 local type = rl (addr + Offsets.type) if type == 0 then type = "PLAYER" print("PLAYER") else type = MobjType[type] end -- print(string.format("%d %f %f %02X", index, x, y, type)) if type and not string.find(type, "MISC") then if x < OB.left then OB.left = x end if x > OB.right then OB.right = x end if y < OB.top then OB.top = y end if y > OB.bottom then OB.bottom = y end -- cache the Objects we need table.insert(Objects, addr) end end end end function update_zoom() if not Init and LastScreenSize.w == client.screenwidth() and LastScreenSize.h == client.screenheight() then return end if OB.top ~= POSITIVE_MAXIMUM and OB.left ~= POSITIVE_MAXIMUM and OB.right ~= NEGATIVE_MAXIMUM and OB.bottom ~= NEGATIVE_MAXIMUM and not emu.islagged() then maybe_swap(OB.right, OB.left) maybe_swap(OB.top, OB.bottom) local span = { x = OB.right-OB.left+200, y = OB.bottom-OB.top+200 } local scale = { x = client.screenwidth()/span.x, y = client.screenheight()/span.y } local spanCenter = { x = OB.left+span.x/2, y = OB.top+span.y/2 } Zoom = math.min(scale.x, scale.y) local sreenCenter = { x = client.screenwidth()/Zoom/2, y = client.screenheight()/Zoom/2 } Pan.x = -math.floor(spanCenter.x - sreenCenter.x) Pan.y = -math.floor(spanCenter.y - sreenCenter.y) Init = false end end local function make_button(x, y, name, func) local boxWidth = CHAR_WIDTH local boxHeight = CHAR_HEIGHT local lineCount, longest = get_line_count(name) local textWidth = longest *CHAR_WIDTH local textHeight = lineCount*CHAR_HEIGHT local colors = { 0x66bbddff, 0xaabbddff, 0xaa88aaff } local colorIndex = 1 if textWidth + 10 > boxWidth then boxWidth = textWidth + 10 end if textHeight + 10 > boxHeight then boxHeight = textHeight + 10 end local textX = x + boxWidth /2 - textWidth /2 local textY = y + boxHeight/2 - textHeight/2 - boxHeight local mouse = input.getmouse() local mousePos = client.transformPoint(mouse.X, mouse.Y) if in_range(mousePos.x, x, x+boxWidth) and in_range(mousePos.y, y-boxHeight, y ) then if mouse.Left then colorIndex = 3 func() else colorIndex = 2 end end box(x, y, x+boxWidth, y-boxHeight, 0xaaffffff, colors[colorIndex]) text(textX, textY, name, colors[colorIndex] | 0xff000000) -- full alpha end --[[-- thinker 30 0 x 4 30 y 4 34 z 4 38 snext 8 3C sprev 8 44 angle 4 4C sprite 4 50 frame 4 54 bnext 8 58 bprev 8 60 subsector 8 68 floorz 4 70 ceilingz 4 74 dropoffz 4 78 radius 4 7C height 4 80 momx 4 84 momy 4 88 momz 4 8C validcount 4 90 type 4 94 info 8 98 tics 4 A0 state 8 A4 flags 8 AC intflags 4 B4 health 4 B8 movedir 2 BC movecount 2 BE strafecount 2 C0 target 8 C2 reactiontime 2 CA threshold 2 CC pursuecount 2 CE gear 2 D0 player 8 D2 lastlook 2 DA spawnpoint 3A DC tracer 8 116 lastenemy 8 11E friction 4 126 movefactor 4 12A touching_sectorlist 8 12E PrevX 4 136 PrevY 4 13A PrevZ 4 13E pitch 4 142 index 4 146 patch_width 2 14A iden_nums 4 14C --]]-- add_offset(48, "thinker") add_offset(INT, "x") add_offset(INT, "y") add_offset(INT, "z") add_offset(INT, "padding1") add_offset(POINTER, "snext") add_offset(POINTER, "sprev") add_offset(INT, "angle") add_offset(INT, "sprite") add_offset(INT, "frame") add_offset(INT, "padding2") add_offset(POINTER, "bnext") add_offset(POINTER, "bprev") add_offset(POINTER, "subsector") add_offset(INT, "floorz") add_offset(INT, "ceilingz") add_offset(INT, "dropoffz") add_offset(INT, "radius") add_offset(INT, "height") add_offset(INT, "momx") add_offset(INT, "momy") add_offset(INT, "momz") add_offset(INT, "validcount") add_offset(INT, "type") add_offset(POINTER, "info") add_offset(INT, "tics") add_offset(INT, "padding3") add_offset(POINTER, "state") add_offset(8, "flags") add_offset(INT, "intflags") add_offset(INT, "health") add_offset(SHORT, "movedir") add_offset(SHORT, "movecount") add_offset(SHORT, "strafecount") add_offset(SHORT, "padding4") add_offset(POINTER, "target") add_offset(SHORT, "reactiontime") add_offset(SHORT, "threshold") add_offset(SHORT, "pursuecount") add_offset(SHORT, "gear") add_offset(POINTER, "player") add_offset(SHORT, "lastlook") add_offset(58, "spawnpoint") add_offset(INT, "padding5") -- unsure where this one should be exactly add_offset(POINTER, "tracer") add_offset(POINTER, "lastenemy") add_offset(INT, "friction") add_offset(INT, "movefactor") add_offset(POINTER, "touching_sectorlist") add_offset(INT, "PrevX") add_offset(INT, "PrevY") add_offset(INT, "PrevZ") add_offset(INT, "pitch") add_offset(INT, "index") add_offset(SHORT, "patch_width") add_offset(INT, "iden_nums") -- the rest are non-doom -- print(Offsets) MobjType = { -- "NULL" = -1, -- "ZERO", -- "PLAYER = ZERO", "POSSESSED", "SHOTGUY", "VILE", "FIRE", "UNDEAD", "TRACER", "SMOKE", "FATSO", "FATSHOT", "CHAINGUY", "TROOP", "SERGEANT", "SHADOWS", "HEAD", "BRUISER", "BRUISERSHOT", "KNIGHT", "SKULL", "SPIDER", "BABY", "CYBORG", "PAIN", "WOLFSS", "KEEN", "BOSSBRAIN", "BOSSSPIT", "BOSSTARGET", "SPAWNSHOT", "SPAWNFIRE", "BARREL", "TROOPSHOT", "HEADSHOT", "ROCKET", "PLASMA", "BFG", "ARACHPLAZ", "PUFF", "BLOOD", "TFOG", "IFOG", "TELEPORTMAN", "EXTRABFG", "MISC0", "MISC1", "MISC2", "MISC3", "MISC4", "MISC5", "MISC6", "MISC7", "MISC8", "MISC9", "MISC10", "MISC11", "MISC12", "INV", "MISC13", "INS", "MISC14", "MISC15", "MISC16", "MEGA", "CLIP", "MISC17", "MISC18", "MISC19", "MISC20", "MISC21", "MISC22", "MISC23", "MISC24", "MISC25", "CHAINGUN", "MISC26", "MISC27", "MISC28", "SHOTGUN", "SUPERSHOTGUN", "MISC29", "MISC30", "MISC31", "MISC32", "MISC33", "MISC34", "MISC35", "MISC36", "MISC37", "MISC38", "MISC39", "MISC40", "MISC41", "MISC42", "MISC43", "MISC44", "MISC45", "MISC46", "MISC47", "MISC48", "MISC49", "MISC50", "MISC51", "MISC52", "MISC53", "MISC54", "MISC55", "MISC56", "MISC57", "MISC58", "MISC59", "MISC60", "MISC61", "MISC62", "MISC63", "MISC64", "MISC65", "MISC66", "MISC67", "MISC68", "MISC69", "MISC70", "MISC71", "MISC72", "MISC73", "MISC74", "MISC75", "MISC76", "MISC77", "MISC78", "MISC79", "MISC80", "MISC81", "MISC82", "MISC83", "MISC84", "MISC85", "MISC86", "PUSH", "PULL", "DOGS", "PLASMA1", "PLASMA2" } SpriteNumber = { -- "TROO", "SHTG", "PUNG", "PISG", "PISF", "SHTF", "SHT2", "CHGG", "CHGF", "MISG", "MISF", "SAWG", "PLSG", "PLSF", "BFGG", "BFGF", "BLUD", "PUFF", "BAL1", "BAL2", "PLSS", "PLSE", "MISL", "BFS1", "BFE1", "BFE2", "TFOG", "IFOG", "PLAY", "POSS", "SPOS", "VILE", "FIRE", "FATB", "FBXP", "SKEL", "MANF", "FATT", "CPOS", "SARG", "HEAD", "BAL7", "BOSS", "BOS2", "SKUL", "SPID", "BSPI", "APLS", "APBX", "CYBR", "PAIN", "SSWV", "KEEN", "BBRN", "BOSF", "ARM1", "ARM2", "BAR1", "BEXP", "FCAN", "BON1", "BON2", "BKEY", "RKEY", "YKEY", "BSKU", "RSKU", "YSKU", "STIM", "MEDI", "SOUL", "PINV", "PSTR", "PINS", "MEGA", "SUIT", "PMAP", "PVIS", "CLIP", "AMMO", "ROCK", "BROK", "CELL", "CELP", "SHEL", "SBOX", "BPAK", "BFUG", "MGUN", "CSAW", "LAUN", "PLAS", "SHOT", "SGN2", "COLU", "SMT2", "GOR1", "POL2", "POL5", "POL4", "POL3", "POL1", "POL6", "GOR2", "GOR3", "GOR4", "GOR5", "SMIT", "COL1", "COL2", "COL3", "COL4", "CAND", "CBRA", "COL6", "TRE1", "TRE2", "ELEC", "CEYE", "FSKU", "COL5", "TBLU", "TGRN", "TRED", "SMBT", "SMGT", "SMRT", "HDB1", "HDB2", "HDB3", "HDB4", "HDB5", "HDB6", "POB1", "POB2", "BRS1", "TLMP", "TLP2", "TNT1", "DOGS", "PLS1", "PLS2", "BON3", "BON4", "BLD2" } while true do if Init then init_objects() end iterate() update_zoom() make_button( 10, client.screenheight()-70, "Zoom\nIn", zoom_in ) make_button( 10, client.screenheight()-10, "Zoom\nOut", zoom_out ) make_button( 80, client.screenheight()-40, "Pan\nLeft", pan_left ) make_button(150, client.screenheight()-70, "Pan \nUp", pan_up ) make_button(150, client.screenheight()-10, "Pan\nDown", pan_down ) make_button(220, client.screenheight()-40, "Pan\nRight", pan_right ) make_button(300, client.screenheight()-10, "Reset\nView", reset_view) text(10, client.screenheight()-170, string.format( "Zoom: %.4f\nPanX: %s\nPanY: %s", Zoom, Pan.x, Pan.y), 0xffbbddff) LastScreenSize.w = client.screenwidth() LastScreenSize.h = client.screenheight() emu.frameadvance() end