Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Author: Suuper
- -- If you make any significant improvements, please share.
- local addrPtrToFirstObjectByType = 0x208FB38
- local offsetNodeByType = 0x38
- -- General stuff
- local typeOffset = 0x44
- local XOffset = 0x60
- local YOffset = 0x64
- local XVelOffset = 0xD0
- local YVelOffset = 0xD4
- local XVelTargetOffset = 0xE0
- local YVelTargetOffset = 0xE4
- local scaleXOffset = 0xF0
- local widthOffset = 0x13C
- local heightOffset = 0x140
- local someFlagsOffset = 0x146
- -- Specific object types
- local MarioXVelOffset = 0xB4
- local MarioMaxSpeedOffset = 0xB8
- function read_u32(addr)
- return memory.read_u32_le(addr)
- end
- function write_u32(addr, value)
- return memory.write_u32_le(addr, value, "ARM9 System Bus")
- end
- function read_s32(addr)
- return memory.read_s32_le(addr)
- end
- function write_s32(addr, value)
- return memory.write_s32_le(addr, value, "ARM9 System Bus")
- end
- function read_u16(addr)
- return memory.read_u16_le(addr)
- end
- function write_u16(addr, value)
- return memory.write_u16_le(addr, value, "ARM9 System Bus")
- end
- function read_u8(addr)
- return memory.read_u8(addr)
- end
- function write_u8(addr, value)
- memory.write_u8(addr, value, "ARM9 System Bus")
- end
- function read_s8(addr)
- return memory.read_s8(addr)
- end
- function write_s8(addr, value)
- return memory.write_s8(addr, value, "ARM9 System Bus")
- end
- function drawText(x, y, str)
- gui.drawText(x, y + 192, str)
- end
- function isTangible(objAddr)
- local flags = read_s8(objAddr + someFlagsOffset)
- return bit.check(flags, 0)
- end
- local objectTypes = {}
- objectTypes[0x1C] = "Mario"
- objectTypes[0x1F] = "Piranha Plant"
- objectTypes[0x28] = "Cheep Cheep"
- objectTypes[0x2B] = "Bumped Block"
- objectTypes[0x2C] = "Powerup" -- Fireflower and mini are same
- objectTypes[0x31] = "Flag Pole"
- objectTypes[0x32] = "Spring"
- objectTypes[0x35] = "Lakitu"
- objectTypes[0x36] = "Spiney Beetle"
- objectTypes[0x37] = "Boo"
- objectTypes[0x44] = "Buzzy Beetle"
- objectTypes[0x45] = "Dry Bones"
- objectTypes[0x47] = "Fireball Enemy"
- objectTypes[0x4C] = "Mega GP Drop"
- objectTypes[0x51] = "Hammer Bro"
- objectTypes[0x56] = "Shark"
- objectTypes[0x5A] = "Bowser's Fireball"
- objectTypes[0x5B] = "Spinny Spring"
- objectTypes[0x5F] = "Red Coin"
- objectTypes[0x60] = "P Switch"
- objectTypes[0x62] = "Blue P Switch"
- objectTypes[0x64] = "Red ! Switch"
- objectTypes[0x68] = "Expandable Block"
- objectTypes[0x7E] = "Swooper"
- objectTypes[0x84] = "Bowser Junior"
- objectTypes[0x85] = "Princess Peach"
- objectTypes[0x89] = "Roulette Block"
- objectTypes[0x8B] = "Basher"
- objectTypes[0x8F] = "Bounce Bubble"
- objectTypes[0x95] = "Bowser"
- objectTypes[0x9B] = "Boss Switch"
- objectTypes[0x9D] = "Respawnable Hidden Block"
- objectTypes[0x9E] = "Coin"
- objectTypes[0x9F] = "Star Coin"
- objectTypes[0xA0] = "Goomba"
- objectTypes[0xA3] = "Koopa" -- green or red
- objectTypes[0xA4] = "Paratroopa"
- objectTypes[0xAE] = "Giant Spike Fish"
- objectTypes[0xAF] = "Red Coin Ring"
- objectTypes[0xBB] = "Checkpoint"
- objectTypes[0xB4] = "Snowball Thrower"
- objectTypes[0xB5] = "Snowball"
- objectTypes[0xC3] = "Tightrope"
- objectTypes[0xC8] = "Moving Platform"
- objectTypes[0xCF] = "Moving Slate?"
- objectTypes[0xD2] = "See-saw"
- objectTypes[0xE3] = "Chomping Piranha Plant"
- objectTypes[0xE8] = "Pokey"
- objectTypes[0xE9] = "Manhole"
- objectTypes[0xEA] = "Bumpable Platform"
- objectTypes[0xEB] = "Tilting Mushroom"
- objectTypes[0xF7] = "Quicksnow"
- objectTypes[0x102] = "Falling Snow"
- objectTypes[0x103] = "One-Way Gate"
- objectTypes[0x105] = "Red ? Block"
- objectTypes[0x107] = "Boo Elevator Platform"
- objectTypes[0x115] = "Broken Brick"
- objectTypes[0x133] = "Temp Projectile Slot"
- objectTypes[0x135] = "Water"
- function getObjectTypeName(obj)
- local objType = read_u16(obj + typeOffset)
- if objectTypes[objType] ~= nil then
- return objectTypes[objType]
- else
- return "Unknown"
- end
- end
- local allObjects = {}
- local objectsByDropDown = {}
- local objectCount = 0
- local spawnActivity = false
- local shouldExit = false
- local detailForms = {}
- function GetFirstObject()
- local nodePtr = read_u32(addrPtrToFirstObjectByType)
- if nodePtr == 0 then
- return 0
- else
- --return read_u32(nodePtr + 8) -- Works, but why bother reading it?
- return nodePtr - offsetNodeByType
- end
- end
- function GetNextObject(obj)
- local nextNodePtr = read_u32(obj + offsetNodeByType + 4)
- if nextNodePtr == 0 then
- return 0
- else
- --return read_u32(nextNodePtr + 8)
- return nextNodePtr - offsetNodeByType
- end
- end
- function GetPreviousObject(obj)
- local nextNodePtr = read_u32(obj + offsetNodeByType)
- if nextNodePtr == 0 then
- return 0
- else
- return read_u32(nextNodePtr + 8)
- end
- end
- function hex(v, padLength)
- padLength = padLength or "1"
- return string.format("%0" .. padLength .. "x", v)
- end
- function formatPosition(v)
- local sign = " "
- if v < 0 then
- sign = "-"
- v = -v
- end
- local subpixel = v % 0x1000
- v = math.floor(v / 0x1000)
- local pixel = v % 0x10
- local block = math.floor(v / 0x10)
- return sign .. string.format("%04d", block) .. "|" .. string.format("%02d", pixel) .. "|" .. string.format("%04d", subpixel)
- end
- function formatSpeed(v)
- local sign = " "
- if v < 0 then
- sign = "-"
- v = -v
- end
- local subpixel = v % 0x1000
- local pixel = math.floor(v / 0x1000)
- return sign .. string.format("%02d", pixel) .. "|" .. string.format("%04d", subpixel)
- end
- function parseBarFormat(str)
- local i, _ = string.find(str, "|")
- if i == nil then
- -- floor because we might have decimal, e.g. 1.51947
- return math.floor(tonumber(str) * 0x1000)
- else
- local first = string.sub(str, 0, i - 1)
- local second = string.sub(str, i + 1, nil)
- i, _ = string.find(second, "|")
- if i == nil then
- -- pixel|subpixel
- i, _ = string.find(first, "-")
- if i == nil then
- return tonumber(first) * 0x1000 + tonumber(second)
- else
- return tonumber(first) * 0x1000 - tonumber(second)
- end
- else
- -- block|pixel|subpixel
- local third = string.sub(second, i + 1, nil)
- second = string.sub(second, 0, i - 1)
- i, _ = string.find(first, "-")
- if i == nil then
- return tonumber(first) * 0x10000 + tonumber(second * 0x1000) + tonumber(third)
- else
- return tonumber(first) * 0x10000 - tonumber(second * 0x1000) - tonumber(third)
- end
- end
- end
- end
- function formatNone(v)
- return v
- end
- -- Lua class for RAM Watch controls
- function GetAddress(pointerList)
- local address = pointerList[1]
- for i = 2, #pointerList do
- address = read_u32(address) + pointerList[i]
- end
- return address
- end
- WatchControls = {}
- WatchControls.x = 0
- WatchControls.y = 0
- WatchControls.value = 0
- WatchControls.editable = true
- function WatchControls:new(parentForm, x, y, width, ptr, name, readFunc, formatFunc, writeFunc, parseFunc)
- -- Validate and set variables
- if type(ptr) == "number" then
- ptr = { ptr }
- end
- if (name ~= nil) then name = name .. ":" else name = "?:" end
- if (readFunc == nil) then readFunc = read_u32 end
- if (formatFunc == nil) then readFunc = formatNone end
- if (parseFunc == nil) then parseFunc = tonumber end
- local newWatchControls = {}
- newWatchControls.form = form
- newWatchControls.ptr = ptr
- newWatchControls.name = name
- newWatchControls.readFunc = readFunc
- newWatchControls.formatFunc = formatFunc
- newWatchControls.writeFunc = writeFunc
- newWatchControls.parseFunc = parseFunc
- -- Create controls
- newWatchControls.label = forms.label(parentForm.handle, name, x, y, 0, 0, true)
- forms.setproperty(newWatchControls.label, "AutoSize", true)
- if writeFunc ~= nil then -- should be editable
- x = x + forms.getproperty(newWatchControls.label, "Width")
- newWatchControls.box = forms.textbox(parentForm.handle, "0", width, 18, nil, x, y, false, true)
- end
- setmetatable(newWatchControls, self);
- self.__index = self;
- return newWatchControls
- end
- function WatchControls:updateDisplay()
- local displayStr = self.formatFunc(self.readFunc(GetAddress(self.ptr)))
- if self.writeFunc == nil then
- forms.settext(self.label, self.name .. displayStr)
- else
- forms.settext(self.box, displayStr)
- end
- end
- function WatchControls:applyEdits()
- if self.writeFunc == nil then
- return
- end
- self.writeFunc(GetAddress(self.ptr), self.parseFunc(forms.gettext(self.box)))
- end
- -- Set up form
- function createMainForm()
- local form = {}
- form.handle = forms.newform(210, 90, "NSMB Objects", function()
- shouldExit = true
- for k in pairs(detailForms) do
- forms.destroy(detailForms[k].handle)
- end
- end)
- forms.setDefaultTextBackground(form.handle, "Transparent")
- -- BizHawk is stupid and requires at least one element present.
- -- It also requires that all elements already be strings.
- form.objectDropDown = forms.dropdown(form.handle, {"placeholder"}, 10, 10, 180, 10)
- form.countLabel = forms.label(form.handle, "label", 10, 64)
- forms.setproperty(form.countLabel, "AutoSize", true)
- form.getDetailsButton = forms.button(form.handle, "Get Details", function()
- local index = tonumber(forms.getproperty(form.objectDropDown, "SelectedIndex"))
- if index == -1 then
- return
- end
- local df = createObjectDetailsForm(objectsByDropDown[index + 1])
- if objectsByDropDown[index + 1] == nil then
- error("obj was nil, index: " .. index)
- end
- updateDetails(df)
- end, 10, 35, 80, 24)
- return form
- end
- local mainForm = createMainForm()
- function createObjectDetailsForm(objAddress)
- local form = {}
- form.obj = objAddress
- local listIndex = #detailForms + 1
- detailForms[#detailForms + 1] = form
- form.handle = forms.newform(300, 240, "Details for " .. getObjectTypeName(objAddress), function()
- detailForms[listIndex] = nil
- end)
- forms.setDefaultTextBackground(form.handle, "Transparent")
- form.objType = read_u16(objAddress + typeOffset)
- form.addressLabel = forms.label(form.handle, "Address: 0", 10, 10)
- forms.setproperty(form.addressLabel, "AutoSize", true)
- -- Not all objects have the same properties
- local _xVelOffset = XVelOffset
- local _xVelTargetOffset = XVelTargetOffset
- if form.objType == 0x1C then -- Mario
- _xVelOffset = MarioXVelOffset
- _xVelTargetOffset = MarioMaxSpeedOffset
- end
- local verticalSeparation = 23
- local y = 30
- form.watches = {}
- -- location
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 10, y + verticalSeparation * 0, 100,
- { form.obj + XOffset }, "X ",
- read_s32, formatPosition, write_s32, parseBarFormat
- )
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 10, y + verticalSeparation * 1, 100,
- { form.obj + YOffset }, "Y ",
- read_s32, formatPosition, write_s32, parseBarFormat
- )
- -- velocity
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 10, y + verticalSeparation * 2, 65,
- { form.obj + _xVelOffset }, "X Speed",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 10, y + verticalSeparation * 3, 65,
- { form.obj + YVelOffset }, "Y Speed",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- -- size
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 160, y + verticalSeparation * 0, 65,
- { form.obj + widthOffset }, "Width ",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 160, y + verticalSeparation * 1, 65,
- { form.obj + heightOffset }, "Height",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- -- target velocity
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 140, y + verticalSeparation * 2, 65,
- { form.obj + _xVelTargetOffset }, "-> target",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- form.watches[#form.watches + 1] = WatchControls:new(
- form, 140, y + verticalSeparation * 3, 65,
- { form.obj + YVelTargetOffset }, "-> target",
- read_s32, formatSpeed, write_s32, parseBarFormat
- )
- form.tangibleCheckbox = forms.checkbox(form.handle, "Tangible", 10, y + verticalSeparation * 4)
- local applyButton = forms.button(form.handle, "Apply", function()
- apply(form)
- end, 200, y + verticalSeparation * 4)
- -- collision checking
- local y = y + verticalSeparation * 5 + 10
- form.collisionSelectLabel = forms.label(form.handle, "Check collision:", 10, y + 2)
- forms.setproperty(form.collisionSelectLabel, "AutoSize", true)
- form.objectDropDown = forms.dropdown(form.handle, { "placeholder" }, 96, y, 178, 10)
- refreshObjectDropDown(form.objectDropDown, 0)
- forms.setproperty(form.objectDropDown, "SelectedIndex", -1)
- form.collisionDistanceLabel = forms.label(form.handle, "Distance to collide:", 10, y + 25)
- form.collisionDistanceHLabel = forms.label(form.handle, "0", 30, y + 40, 0, 0, true)
- form.collisionDistanceVLabel = forms.label(form.handle, "0", 30, y + 55, 0, 0, true)
- forms.setproperty(form.collisionDistanceLabel, "AutoSize", true)
- forms.setproperty(form.collisionDistanceHLabel, "AutoSize", true)
- forms.setproperty(form.collisionDistanceVLabel, "AutoSize", true)
- return form
- end
- function updateDetails(detailsForm)
- local obj = detailsForm.obj
- if read_u32(obj) == 0 or read_u16(obj + typeOffset) ~= detailsForm.objType then
- forms.settext(detailsForm.addressLabel, "Address: Despawned")
- for i = 1, #detailsForm.watches do
- if detailsForm.watches[i].box ~= nil then
- forms.settext(detailsForm.watches[i].box, "Despawned")
- else
- forms.settext(detailsForm.watches[i].label, "Despawned")
- end
- end
- else
- forms.settext(detailsForm.addressLabel, "Address: 0x" .. hex(obj, 8))
- forms.setproperty(detailsForm.tangibleCheckbox, "Checked", isTangible(obj))
- for i = 1, #detailsForm.watches do
- detailsForm.watches[i]:updateDisplay()
- end
- local index = tonumber(forms.getproperty(detailsForm.objectDropDown, "SelectedIndex"))
- if spawnActivity then
- refreshObjectDropDown(detailsForm.objectDropDown, detailsForm.selectedCollisionObj)
- index = tonumber(forms.getproperty(detailsForm.objectDropDown, "SelectedIndex"))
- end
- if index ~= -1 then
- detailsForm.selectedCollisionObj = objectsByDropDown[index + 1]
- local this = {
- X = read_s32(obj + XOffset),
- Y = read_s32(obj + YOffset),
- Width = read_s32(obj + widthOffset),
- Height = read_s32(obj + heightOffset),
- }
- local otherObj = detailsForm.selectedCollisionObj
- local other = {
- X = read_s32(otherObj + XOffset),
- Y = read_s32(otherObj + YOffset),
- Width = read_s32(otherObj + widthOffset),
- Height = read_s32(otherObj + heightOffset),
- }
- local distanceRight = math.max(0, (other.X - other.Width) - (this.X + this.Width))
- local distanceLeft = math.max(0, (this.X - this.Width) - (other.X + other.Width))
- local distanceH = math.max(distanceRight, distanceLeft)
- forms.settext(detailsForm.collisionDistanceHLabel, formatPosition(distanceH))
- local distanceUp = math.max(0, (other.Y) - (this.Y + this.Height))
- local distanceDown = math.max(0, (this.Y) - (other.Y + other.Height))
- local distanceV = math.max(distanceUp, distanceDown)
- forms.settext(detailsForm.collisionDistanceVLabel, formatPosition(distanceV))
- end
- end
- end
- function apply(detailsForm)
- function inner()
- local obj = detailsForm.obj
- if read_u32(obj) == 0 then
- return
- end
- local flags = read_u8(obj + someFlagsOffset)
- if forms.ischecked(detailsForm.tangibleCheckbox) then
- flags = bit.set(flags, 0)
- else
- flags = bit.clear(flags, 0)
- end
- write_u8(obj + someFlagsOffset, flags)
- for i = 1, #detailsForm.watches do
- detailsForm.watches[i]:applyEdits()
- end
- end
- local result = pcall(inner)
- if not result then
- console.log("Failed to apply changes. Ensure your inputs are in the correct format.")
- end
- end
- function getNewObjects(first, second)
- local list = {}
- for key in pairs(first) do
- if second[key] == nil then
- list[#list + 1] = key
- end
- end
- return list
- end
- function refreshObjectsList()
- -- Update objects list
- local currentObjects = {}
- local addrCurrentNode = GetFirstObject()
- objectCount = 0
- while true do
- addrCurrentNode = GetNextObject(addrCurrentNode)
- if addrCurrentNode == 0 then
- break
- end
- if currentObjects[addrCurrentNode] ~= nil then
- error("Encountered repeat object. This indicates memory is corrupt or the script thinks you're in a level when you're not.")
- end
- currentObjects[addrCurrentNode] = true
- objectCount = objectCount + 1
- if objectCount > 200 then
- error("Error: Found over 200 objects. This probably means there is a problem, because there shouldn't be that many objects.")
- end
- end
- -- Which objects are new, which despawned?
- local despawned = getNewObjects(allObjects, currentObjects)
- local newObjs = getNewObjects(currentObjects, allObjects)
- spawnActivity = #newObjs > 0 or #despawned > 0
- for i = 1, #newObjs do
- local objType = read_u16(newObjs[i] + typeOffset)
- local text = objectTypes[objType]
- if text == nil then
- text = "Unknown (Type 0x" .. hex(objType) .. ")"
- end
- console.log("Spawned " .. text)
- end
- for i = 1, #despawned do
- local objType = read_u16(despawned[i] + typeOffset)
- local text = objectTypes[objType]
- if text == nil then
- text = "Unknown (Type 0x" .. hex(objType) .. ")"
- end
- console.log("Despawned " .. text)
- end
- allObjects = currentObjects
- end
- local dropDownItems = nil
- function refreshObjectDropDownList()
- dropDownItems = {}
- objectsByDropDown = {}
- for key in pairs(allObjects) do
- local objType = read_u16(key + typeOffset)
- local text = objectTypes[objType]
- if text == nil then
- text = "Unknown (Type 0x" .. hex(objType) .. ")"
- end
- text = text
- dropDownItems[#dropDownItems + 1] = text
- objectsByDropDown[#objectsByDropDown + 1] = key
- end
- end
- function refreshObjectDropDown(dropDown, previousSelectedObj)
- -- BizHawk is stupid and attempts to set SelectedIndex to 0 after updating the list
- if #dropDownItems == 0 then
- forms.setdropdownitems(dropDown, { "No objects" }, false)
- else
- forms.setdropdownitems(dropDown, dropDownItems, false)
- end
- forms.setproperty(dropDown, "SelectedIndex", -1)
- for i = 1, #objectsByDropDown do
- if objectsByDropDown[i] == previousSelectedObj then
- forms.setproperty(dropDown, "SelectedIndex", i - 1)
- end
- end
- end
- while not shouldExit do
- refreshObjectsList()
- if spawnActivity then
- forms.settext(mainForm.countLabel, "Number of objects: " .. objectCount)
- local selectedIndex = forms.getproperty(mainForm.objectDropDown, "SelectedIndex")
- local selectedObject = objectsByDropDown[selectedIndex + 1]
- refreshObjectDropDownList()
- refreshObjectDropDown(mainForm.objectDropDown, selectedObject)
- end
- for key in pairs(detailForms) do
- updateDetails(detailForms[key])
- end
- emu.frameadvance()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement