Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Used with "BizHawk" emulator - load ROM -> Tools -> Lua Console -> Script -> Open Script -> double-click until green play sign
- -- Enable or disable the overlay with [R+SELECT]. Haven't played with this yet, so
- -- you may end up seeing overlay popups during the normal game at odd times.
- -- mod type for offsets, works off of the 'rom name' (what it says in title bar/saves games as)
- -- if your rom isn't detected just add it in this format (including the , and any " marks):
- -- ["ROM NAME"] = romtype,
- -- where romtype is one of these: nil, "ROTDS", "BNW"
- -- any other romtype will use nil (vanilla) settings
- local MODS = {
- ["Final Fantasy III (USA)"] = nil, -- vanilla, default. -- means comments
- ["Return of the Dark Sorcerer"] = "ROTDS", -- uses rotds extended item/spell names
- ["Brave New World 2.0"] = "BNW", -- mod'd dance chance
- ["FFVI T Edition"] = "japan", -- nothing yet, the version is stripped further down
- }
- --- =======================
- --- all settings are above this
- --- =======================
- local romname = gameinfo.getromname()
- -- t-edition has ever-changing patch number on the end
- if (romname:sub(1,14) == "FFVI T Edition") then romname = "FFVI T Edition" end
- local MOD_TYPE = MODS[romname]
- function checkIfHelpButton()
- -- X is at 0x0A as the 7th bit (0x40), Y is at 0x0B same
- local x = bit.band(memory.readbyte(0x0A,"WRAM"),0x40)
- if (x > 0) then return true end
- local y = bit.band(memory.readbyte(0x0B,"WRAM"),0x40)
- if (y > 0) then return true end
- return false
- end
- function initialize()
- gui.defaultPixelFont("fceux")
- -- vanilla
- -- offset start, text length, index start
- offNames = {
- spells = { 0x26F567, 7, 0 },
- espers = { 0x26F6E1, 8, 54 },
- attacks = { 0x26F7B9, 10, 81 },
- espattacks = { 0x26FE8F, 10, 256 },
- dances = { 0x26FF9D, 12, 283 },
- }
- offMonsterData = 0xF0000
- lenMonsterData = 32
- offItemNames = 0x12B300
- lenItemNames = 13
- if (MOD_TYPE == "ROTDS") then
- offNames.spells = { 0x316100, 9, 0 }
- offNames.espers = { 0x3162E6, 12, 54 }
- offNames.attacks = { 0x31645E, 16, 81 }
- offNames.espattacks = { 0x316DDE, 16, 256 }
- offNames.dances = { 0x3170FE, 13, 283 }
- lenItemNames = 13
- end
- end
- initialize()
- -- maps each font byte to the appropriate character, FF should break loop, space for anything else
- local fontMap = { [0x80] = "A", [0x81] = "B", [0x82] = "C", [0x83] = "D", [0x84] = "E", [0x85] = "F", [0x86] = "G", [0x87] = "H", [0x88] = "I", [0x89] = "J", [0x8A] = "K", [0x8B] = "L", [0x8C] = "M", [0x8D] = "N", [0x8E] = "O", [0x8F] = "P", [0x90] = "Q", [0x91] = "R", [0x92] = "S", [0x93] = "T", [0x94] = "U", [0x95] = "V", [0x96] = "W", [0x97] = "X", [0x98] = "Y", [0x99] = "Z", [0x9A] = "a", [0x9B] = "b", [0x9C] = "c", [0x9D] = "d", [0x9E] = "e", [0x9F] = "f", [0xA0] = "g", [0xA1] = "h", [0xA2] = "i", [0xA3] = "j", [0xA4] = "k", [0xA5] = "l", [0xA6] = "m", [0xA7] = "n", [0xA8] = "o", [0xA9] = "p", [0xAA] = "q", [0xAB] = "r", [0xAC] = "s", [0xAD] = "t", [0xAE] = "u", [0xAF] = "v", [0xB0] = "w", [0xB1] = "x", [0xB2] = "y", [0xB3] = "z", [0xB4] = "0", [0xB5] = "1", [0xB6] = "2", [0xB7] = "3", [0xB8] = "4", [0xB9] = "5", [0xBA] = "6", [0xBB] = "7", [0xBC] = "8", [0xBD] = "9", [0xBE] = "!", [0xBF] = "?", [0xC3] = "'", [0xC4] = "-", [0xC5] = ".", }
- local monSpecials = {
- [0x00]="+Darkness", [0x01]="+Zombie", [0x02]="+Poison", [0x03]="+Magitek", [0x04]="+Invis", [0x05]="+Imp", [0x06]="+Stone", [0x07]="+Death", [0x08]="+Doom", [0x09]="+Critical", [0x0A]="+Blink", [0x0B]="+Silence", [0x0C]="+Berserk", [0x0D]="+Confuse", [0x0E]="+Sap", [0x0F]="+Sleep", [0x10]="+Dance", [0x11]="+Regen", [0x12]="+Slow", [0x13]="+Haste", [0x14]="+Stop", [0x15]="+Shell", [0x16]="+Protect", [0x17]="+Reflect", [0x18]="+Rage", [0x19]="+Frozen", [0x1A]="+Reraise", [0x1B]="+Morph", [0x1C]="+Spellcast", [0x1D]="+Flee", [0x1E]="+Interceptor", [0x1F]="+Float", [0x20]=" Damage + 50%", [0x21]=" Damage +100%", [0x22]=" Damage +150%", [0x23]=" Damage +200%", [0x24]=" Damage +250%", [0x25]=" Damage +300%", [0x26]=" Damage +350%", [0x27]=" Damage +400%", [0x28]=" Damage +450%", [0x29]=" Damage +500%", [0x2A]=" Damage +550%", [0x2B]=" Damage +600%", [0x2C]=" Damage +650%", [0x2D]=" Damage +700%", [0x2E]=" Damage +750%", [0x2F]=" Damage +800%",
- }
- function hexformat(input) return string.format("%02x", input):upper() end
- function trim(out) return (out:gsub("^%s*(.-)%s*$", "%1")) end
- -- skipFF to anything to prevent breaking on FF
- function translate(byteTable,skipFF)
- local out = ""
- local start = 0
- if (not byteTable[start]) then start = 1 end
- for i=start,#byteTable do
- local hex = byteTable[i]
- if ((hex == 0xFF or not hex) and not skipFF) then break end
- if (fontMap[hex]) then
- out = out .. fontMap[hex]
- else
- out = out .. " "
- end
- end
- out = trim(out)
- return out
- end
- function getStatuses(byteA,byteB,byteC)
- local statuses = { {"Blind", "Zombi", "Poisn", "M-Tek", "Invis", "Imp ", "Stone", "Death"},
- {"Doom ", "Hurt ", "Image", "Mute ", "Brsrk", "Muddl", "Sap ", "Sleep"},
- {"Float", "Regen", "Slow ", "Haste", "Stop ", "Shell", "Safe ", "Rflct"} }
- local outStatuses = {}
- for _,byte in pairs({ byteA, byteB, byteC }) do
- for i=1,#statuses[_] do
- if (bit.band(byte,bit.lshift(1,(i-1))) > 0) then
- outStatuses[#outStatuses+1] = statuses[_][i]
- end
- end
- end
- local out = ""
- for _,v in pairs(outStatuses) do
- out = out .. v .. " "
- if (_ % 6 == 0) then out = out .. "\n " end
- end
- return trim(out)
- end
- function getElements(byte)
- local elements = { "FIR", "ICE", "THN", "POI", "WND", "HLY", "ERT", "WTR" }
- local outElements = {}
- for i=1,#elements do
- if (bit.band(byte,bit.lshift(1,(i-1))) > 0) then
- outElements[#outElements+1] = elements[i]
- end
- end
- local out = ""
- for _,v in pairs(outElements) do
- out = out .. v .. " "
- --if (_ % 5 == 0) then out = out .. "\n" end
- end
- return trim(out)
- end
- function getSpellName(index)
- local type = nil
- if (index >= offNames.dances[3]) then type = offNames.dances
- elseif (index >= offNames.espattacks[3]) then type = offNames.espattacks
- elseif (index >= offNames.attacks[3]) then type = offNames.attacks
- elseif (index >= offNames.espers[3]) then type = offNames.espers
- else type = offNames.spells
- end
- local beginOffset = type[1]
- local textLength = type[2]
- local startSkillIndex = type[3]
- local firstByte = beginOffset + ((index-startSkillIndex)*textLength)
- local out = memory.readbyterange(firstByte,textLength,"CARTROM")
- out = translate(out)
- return out
- end
- function getItemName(index)
- local out = memory.readbyterange(offItemNames + (index*lenItemNames),lenItemNames,"CARTROM")
- out = translate(out,"don't-break-on-FF")
- if (out == "") then out = "(None)" end
- return out
- end
- local cursor_state = nil
- local inputs = { { nil, nil, nil, nil, "R", "L", "X", "A", },
- { "Right", "Left", "Down", "Up", "Start", "Select", "Y", "B" } }
- local keymap = {}
- local keymapLastFrame = {}
- function buttonHandler()
- -- X is at 0x0A as the 7th bit (0x40), Y is at 0x0B same
- local input1 = memory.readbyte(0x04,"WRAM")
- local input2 = memory.readbyte(0x05,"WRAM")
- if (input1 == 0x00 and input2 == 0x00) then
- input1 = memory.readbyte(0x0A)
- input2 = memory.readbyte(0x0B)
- end
- keymapLastFrame = keymap
- keymap = {}
- for _,byte in pairs({ input1, input2 }) do
- for i=1,#inputs[_] do
- if (inputs[_][i] ~= nil) then
- if (bit.band(byte,bit.lshift(1,(i-1))) > 0) then
- keymap[inputs[_][i]] = true
- end
- end
- end
- end
- end
- local enabledToggle = true
- local messageStartTime = 0
- local messageText = ""
- local messageDuration = 0
- function disabledCheck()
- if (keymap["Select"] and keymap["R"] and not (keymapLastFrame["R"] and keymapLastFrame["Select"])) then
- enabledToggle = not enabledToggle
- if (enabledToggle) then message("Overlay enabled.",4)
- else message("Overlay disabled.", 4)
- end
- end
- end
- function message(msg,duration)
- messageStartTime = os.time()
- messageText = msg
- messageDuration = duration
- end
- function messageCheck()
- messageTime = os.time()
- if (messageTime < (messageStartTime + messageDuration)) then
- gui.drawText(0,210,messageText,"white","black")
- end
- end
- function processEnemySpecial(idxTargetMonster)
- local monStats = memory.readbyterange(offMonsterData + (lenMonsterData * idxTargetMonster),lenMonsterData,"CARTROM")
- local specialAttack = monStats[0x1F]
- local noEvade = (bit.band(specialAttack,0x80) > 0)
- local noDamage = (bit.band(specialAttack,0x40) > 0)
- specialAttack = bit.band(specialAttack,0x3F)
- specialAttack = monSpecials[specialAttack]
- if (specialAttack) then
- if (noEvade or noDamage) then
- specialAttack = specialAttack..")\n(Special:"
- if (noEvade) then specialAttack = specialAttack .. "Can't dodge" end
- if (noEvade and noDamage) then specialAttack = specialAttack .. " & " end
- if (noDamage) then specialAttack = specialAttack .. "No damage" end
- end
- else
- specialAttack = "+???"
- end
- return "(Special:Attack"..specialAttack..")"
- end
- local hintLoc = { x = 8, y = 142, height = 9 }
- function printHint(msg)
- local _, extraLineCount = msg:gsub('\n', '\n')
- gui.pixelText(hintLoc.x,hintLoc.y-(hintLoc.height * extraLineCount),msg,"white")
- end
- function doStealStuff(targetted_monster)
- local stealLoc = 0x3310 + (2*targetted_monster)
- local rare_steal = memory.read_u8(stealLoc)
- local common_steal = memory.read_u8(stealLoc+1)
- local itemNames = { getItemName(common_steal), getItemName(rare_steal) }
- if (common_steal == 0xFF) then itemNames[1] = "---" end
- if (rare_steal == 0xFF) then itemNames[2] = "---" end
- local out = "Nothing to steal!"
- if (itemNames[1] ~= "---" or itemNames[2] ~= "---") then
- out = "Common Steal (7/8): "..itemNames[1].."\n Rare Steal (1/8): "..itemNames[2]
- end
- return out
- end
- function main()
- disabledCheck()
- messageCheck()
- if (not enabledToggle) then return end
- local paused = memory.read_u8(0x62ab)
- if (paused > 0) then
- -- should probably get a check so it ONLY works in battle
- --[[
- -- show enemy health if paused
- for i=0,5 do
- local hp = memory.read_u16_le(0x3bfc + (2*i))
- if (hp > 0) then
- local x = memory.read_u16_le(0x80c3 + (2*i)) -- monster loc x in pixels
- local y = memory.read_u16_le(0x80cf + (2*i)) -- monster loc y in pixels
- gui.drawText(x,y,hp)
- end
- end
- ]]--
- end
- -- reads memory of 'cursor state' which appears to be the type of command being hovered over
- local last_cursor_state = cursor_state
- cursor_state = memory.read_u8(0x7bc2)
- local which_character = memory.read_u8(0x62ca) -- offsets cursor info
- local idxRecentCommand = memory.read_u8(0x890F + which_character) -- the command we're hovering on main menu
- local recentCommandType = memory.read_u8(0x202E + (12*which_character) + (3*idxRecentCommand))
- --23 = scrolling page up or down
- if (cursor_state == 23 or cursor_state == 24) then cursor_state = last_cursor_state end
- --30 = rage
- if (cursor_state == 30) then
- local rages = memory.readbyterange(0x257E,256)
- local battleMenuColumn = 0x892F -- row and scrolled are derived from this (+4/-4)
- local column = memory.read_u8(battleMenuColumn + which_character)
- local row = memory.read_u8(battleMenuColumn + which_character + 4)
- local scrolled = memory.read_u8(battleMenuColumn + which_character - 4)
- local hoverRage = rages[((row+scrolled)*2) + column]
- local menuClosing = (memory.read_u8(0x7BCB) > 0) -- get rid of little text-blink as rage menu closes
- if (hoverRage < 255 and not menuClosing) then
- local rageLoc = 0xF4600 + (2*hoverRage)
- local rageAttacks = { memory.readbyte(rageLoc,"CARTROM"), memory.readbyte(rageLoc+1,"CARTROM") }
- for i=1,2 do rageAttacks[i] = getSpellName(rageAttacks[i]) end --.."("..rageAttacks[i]..")" end
- local monStats = memory.readbyterange(offMonsterData + (lenMonsterData * hoverRage),lenMonsterData,"CARTROM")
- local out = "Rage: "..rageAttacks[1] .. "/" .. rageAttacks[2]
- if (rageAttacks[2] == "Special" or rageAttacks[1] == "Special") then
- specialAttack = processEnemySpecial(hoverRage)
- out = specialAttack .. "\n"..out
- end
- printHint(out)
- if (checkIfHelpButton()) then
- local blockedStatuses = { monStats[0x14], monStats[0x15], monStats[0x16] }
- local appliedStatuses = { monStats[0x1B], monStats[0x1C], monStats[0x1D] }
- local absorbElements = monStats[0x17]
- local immuneElements = monStats[0x18]
- local weakElements = monStats[0x19]
- local bytes = ""
- for i=0,#monStats do
- bytes = bytes .. " " .. hexformat(monStats[i])
- if ((i+1) % 8 == 0) then bytes = bytes .. "\n" end
- end
- local infoText = ""
- blockedStatuses = getStatuses(blockedStatuses[1],blockedStatuses[2],blockedStatuses[3])
- appliedStatuses = getStatuses(appliedStatuses[1],appliedStatuses[2],appliedStatuses[3])
- if (#appliedStatuses > 0) then infoText = infoText.."GAINS:"..appliedStatuses.."\n" end
- if (#blockedStatuses > 0) then infoText = infoText.. "BLOCK:"..blockedStatuses.."\n" end
- absorbElements = getElements(absorbElements)
- immuneElements = getElements(immuneElements)
- weakElements = getElements(weakElements)
- if (#absorbElements > 0) then infoText = infoText.. "ABS:"..absorbElements.."\n" end
- if (#immuneElements > 0) then infoText = infoText.. "IMM:"..immuneElements.."\n" end
- if (#weakElements > 0) then infoText = infoText.. "WEAK:"..weakElements.."\n" end
- infoText = trim(infoText)
- gui.pixelText(0,0,string.format("%-99s",infoText),"white")
- end
- end
- -- dances
- elseif (cursor_state == 33) then
- local dances = memory.readbyterange(0x267E,8)
- local battleMenuColumn = 0x8937 -- row and scrolled are derived from this (+4/-4)
- local column = memory.read_u8(battleMenuColumn + which_character)
- local row = memory.read_u8(battleMenuColumn + which_character + 4)
- local scrolled = memory.read_u8(battleMenuColumn + which_character - 4)
- local hoverDance = dances[((row+scrolled)*2) + column]
- local menuClosing = (memory.read_u8(0x7BCB) > 0) -- get rid of little text-blink as dance menu closes
- if (hoverDance < 255 and not menuClosing) then
- local danceLoc = 0xFFE80 + (4*hoverDance)
- local danceAttacks = { memory.readbyte(danceLoc,"CARTROM"), memory.readbyte(danceLoc+1,"CARTROM"),
- memory.readbyte(danceLoc+2,"CARTROM"), memory.readbyte(danceLoc+3,"CARTROM") }
- for i=1,4 do danceAttacks[i] = getSpellName(danceAttacks[i]) end
- local danceChances = { 7, 6, 2, 1 }
- if (MOD_TYPE == "BNW") then danceChances = { 7, 5, 3, 1 } end
- local out = string.format("Dance: (%s/16) %s\n (%s/16) %s\n (%s/16) %s\n (%s/16) %s", danceChances[1], danceAttacks[1], danceChances[2], danceAttacks[2], danceChances[3], danceAttacks[3], danceChances[4], danceAttacks[4])
- printHint(out)
- end
- -- targetting enemies
- elseif (cursor_state == 0x38) then
- local targetted_monster = memory.read_u8(0x7B7E)
- -- only bother if we're actually targetting a monster (ie not an ally)
- if (targetted_monster > 0) then
- targetted_monster = (math.log(targetted_monster) / math.log(2))
- if (recentCommandType == 0x00) then -- fight
- local weaponPropLoc = 0x3CBC --main, then off
- weaponPropLoc = weaponPropLoc + (2*which_character)
- local my_weapons = { memory.read_u8(weaponPropLoc), memory.read_u8(weaponPropLoc + 1) }
- -- the offhand will be shortcircuited if first passes
- my_weapons[1] = (bit.band(my_weapons[1],0x10) > 0 and my_weapons[1] < 32)
- if (my_weapons[1] or (bit.band(my_weapons[2],0x10) > 0 and my_weapons[2] < 32)) then
- local out = doStealStuff(targetted_monster)
- local weaponLoc = 0x3CA8
- weaponLoc = weaponLoc + (2*which_character)
- local providingWeapon = "ThiefKnife"
- if (my_weapons[1]) then
- providingWeapon = getItemName(memory.read_u8(weaponLoc))
- else -- use off-hand since it wasn't from mainhand
- providingWeapon = getItemName(memory.read_u8(weaponLoc + 1))
- end
- printHint("* Shown due to "..providingWeapon..":\n"..out)
- end
- elseif (recentCommandType == 0x05 or recentCommandType == 0x06) then -- we're stealing and targetting enemies
- printHint(doStealStuff(targetted_monster))
- elseif (recentCommandType == 0x0D) then --sketch
- local idxTargetMonster = memory.read_u16_le(0x2001 + (2*targetted_monster))
- local cantSketchByte = memory.readbyte(offMonsterData + (lenMonsterData * idxTargetMonster) + 0x13,"CARTROM")
- if (bit.band(cantSketchByte,0x20) > 0) then
- printHint("Can't sketch!")
- else
- local sketchLoc = 0xF4300 + (2*idxTargetMonster)
- local sketchAttacks = { memory.readbyte(sketchLoc,"CARTROM"), memory.readbyte(sketchLoc+1,"CARTROM") }
- for i=1,2 do sketchAttacks[i] = getSpellName(sketchAttacks[i]) end
- local out = "Sketch: (75%) "..sketchAttacks[2] .. "\n (25%) " .. sketchAttacks[1] ..""
- if (sketchAttacks[2] == "Special" or sketchAttacks[1] == "Special") then
- specialAttack = processEnemySpecial(idxTargetMonster)
- out = specialAttack.."\n"..out
- end
- printHint(out)
- end
- elseif (recentCommandType == 0x0E) then -- chaos control
- local idxTargetMonster = memory.read_u16_le(0x2001 + (2*targetted_monster))
- local cantControlByte = memory.readbyte(offMonsterData + (lenMonsterData * idxTargetMonster) + 0x13,"CARTROM")
- if (bit.band(cantControlByte,0x80) > 0) then
- printHint("Can't control!")
- else
- local controlLoc = 0xF3D00 + (4*idxTargetMonster)
- local controlAttacks = { memory.readbyte(controlLoc,"CARTROM"), memory.readbyte(controlLoc+1,"CARTROM"), memory.readbyte(controlLoc+2,"CARTROM"), memory.readbyte(controlLoc+3,"CARTROM") }
- local hasSpecial = false
- local namesControlAttacks = {}
- for i=1,4 do
- if (controlAttacks[i] ~= 0xFF) then
- namesControlAttacks[#namesControlAttacks + 1] = getSpellName(controlAttacks[i])
- if (namesControlAttacks[#namesControlAttacks] == "Special") then hasSpecial = true end
- if (#namesControlAttacks == 3) then namesControlAttacks[3] = "\n "..namesControlAttacks[3] end
- end
- end
- local out = "Control: "..table.concat(namesControlAttacks,", ")
- if (hasSpecial) then
- specialAttack = processEnemySpecial(idxTargetMonster)
- out = specialAttack.."\n"..out
- end
- printHint(out)
- end
- end
- end
- end
- end
- while true do
- emu.frameadvance();
- -- prevent it from doing dumb things while rom isn't even loaded
- if (romname ~= "Null") then
- buttonHandler()
- main()
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement