mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
FF1: Bizhawk Client and APWorld Support (#4448)
Co-authored-by: beauxq <beauxq@yahoo.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
@@ -1,462 +0,0 @@
|
||||
local socket = require("socket")
|
||||
local json = require('json')
|
||||
local math = require('math')
|
||||
require("common")
|
||||
|
||||
local STATE_OK = "Ok"
|
||||
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
|
||||
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
|
||||
local STATE_UNINITIALIZED = "Uninitialized"
|
||||
|
||||
local ITEM_INDEX = 0x03
|
||||
local WEAPON_INDEX = 0x07
|
||||
local ARMOR_INDEX = 0x0B
|
||||
|
||||
local goldLookup = {
|
||||
[0x16C] = 10,
|
||||
[0x16D] = 20,
|
||||
[0x16E] = 25,
|
||||
[0x16F] = 30,
|
||||
[0x170] = 55,
|
||||
[0x171] = 70,
|
||||
[0x172] = 85,
|
||||
[0x173] = 110,
|
||||
[0x174] = 135,
|
||||
[0x175] = 155,
|
||||
[0x176] = 160,
|
||||
[0x177] = 180,
|
||||
[0x178] = 240,
|
||||
[0x179] = 255,
|
||||
[0x17A] = 260,
|
||||
[0x17B] = 295,
|
||||
[0x17C] = 300,
|
||||
[0x17D] = 315,
|
||||
[0x17E] = 330,
|
||||
[0x17F] = 350,
|
||||
[0x180] = 385,
|
||||
[0x181] = 400,
|
||||
[0x182] = 450,
|
||||
[0x183] = 500,
|
||||
[0x184] = 530,
|
||||
[0x185] = 575,
|
||||
[0x186] = 620,
|
||||
[0x187] = 680,
|
||||
[0x188] = 750,
|
||||
[0x189] = 795,
|
||||
[0x18A] = 880,
|
||||
[0x18B] = 1020,
|
||||
[0x18C] = 1250,
|
||||
[0x18D] = 1455,
|
||||
[0x18E] = 1520,
|
||||
[0x18F] = 1760,
|
||||
[0x190] = 1975,
|
||||
[0x191] = 2000,
|
||||
[0x192] = 2750,
|
||||
[0x193] = 3400,
|
||||
[0x194] = 4150,
|
||||
[0x195] = 5000,
|
||||
[0x196] = 5450,
|
||||
[0x197] = 6400,
|
||||
[0x198] = 6720,
|
||||
[0x199] = 7340,
|
||||
[0x19A] = 7690,
|
||||
[0x19B] = 7900,
|
||||
[0x19C] = 8135,
|
||||
[0x19D] = 9000,
|
||||
[0x19E] = 9300,
|
||||
[0x19F] = 9500,
|
||||
[0x1A0] = 9900,
|
||||
[0x1A1] = 10000,
|
||||
[0x1A2] = 12350,
|
||||
[0x1A3] = 13000,
|
||||
[0x1A4] = 13450,
|
||||
[0x1A5] = 14050,
|
||||
[0x1A6] = 14720,
|
||||
[0x1A7] = 15000,
|
||||
[0x1A8] = 17490,
|
||||
[0x1A9] = 18010,
|
||||
[0x1AA] = 19990,
|
||||
[0x1AB] = 20000,
|
||||
[0x1AC] = 20010,
|
||||
[0x1AD] = 26000,
|
||||
[0x1AE] = 45000,
|
||||
[0x1AF] = 65000
|
||||
}
|
||||
|
||||
local extensionConsumableLookup = {
|
||||
[432] = 0x3C,
|
||||
[436] = 0x3C,
|
||||
[440] = 0x3C,
|
||||
[433] = 0x3D,
|
||||
[437] = 0x3D,
|
||||
[441] = 0x3D,
|
||||
[434] = 0x3E,
|
||||
[438] = 0x3E,
|
||||
[442] = 0x3E,
|
||||
[435] = 0x3F,
|
||||
[439] = 0x3F,
|
||||
[443] = 0x3F
|
||||
}
|
||||
|
||||
local noOverworldItemsLookup = {
|
||||
[499] = 0x2B,
|
||||
[500] = 0x12,
|
||||
}
|
||||
|
||||
local consumableStacks = nil
|
||||
local prevstate = ""
|
||||
local curstate = STATE_UNINITIALIZED
|
||||
local ff1Socket = nil
|
||||
local frame = 0
|
||||
|
||||
local isNesHawk = false
|
||||
|
||||
|
||||
--Sets correct memory access functions based on whether NesHawk or QuickNES is loaded
|
||||
local function defineMemoryFunctions()
|
||||
local memDomain = {}
|
||||
local domains = memory.getmemorydomainlist()
|
||||
if domains[1] == "System Bus" then
|
||||
--NesHawk
|
||||
isNesHawk = true
|
||||
memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
|
||||
memDomain["saveram"] = function() memory.usememorydomain("Battery RAM") end
|
||||
memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
|
||||
elseif domains[1] == "WRAM" then
|
||||
--QuickNES
|
||||
memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
|
||||
memDomain["saveram"] = function() memory.usememorydomain("WRAM") end
|
||||
memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
|
||||
end
|
||||
return memDomain
|
||||
end
|
||||
|
||||
local memDomain = defineMemoryFunctions()
|
||||
|
||||
local function StateOKForMainLoop()
|
||||
memDomain.saveram()
|
||||
local A = u8(0x102) -- Party Made
|
||||
local B = u8(0x0FC)
|
||||
local C = u8(0x0A3)
|
||||
return A ~= 0x00 and not (A== 0xF2 and B == 0xF2 and C == 0xF2)
|
||||
end
|
||||
|
||||
function generateLocationChecked()
|
||||
memDomain.saveram()
|
||||
data = uRange(0x01FF, 0x101)
|
||||
data[0] = nil
|
||||
return data
|
||||
end
|
||||
|
||||
function setConsumableStacks()
|
||||
memDomain.rom()
|
||||
consumableStacks = {}
|
||||
-- In order shards, tent, cabin, house, heal, pure, soft, ext1, ext2, ext3, ex4
|
||||
consumableStacks[0x35] = 1
|
||||
consumableStacks[0x36] = u8(0x47400) + 1
|
||||
consumableStacks[0x37] = u8(0x47401) + 1
|
||||
consumableStacks[0x38] = u8(0x47402) + 1
|
||||
consumableStacks[0x39] = u8(0x47403) + 1
|
||||
consumableStacks[0x3A] = u8(0x47404) + 1
|
||||
consumableStacks[0x3B] = u8(0x47405) + 1
|
||||
consumableStacks[0x3C] = u8(0x47406) + 1
|
||||
consumableStacks[0x3D] = u8(0x47407) + 1
|
||||
consumableStacks[0x3E] = u8(0x47408) + 1
|
||||
consumableStacks[0x3F] = u8(0x47409) + 1
|
||||
end
|
||||
|
||||
function getEmptyWeaponSlots()
|
||||
memDomain.saveram()
|
||||
ret = {}
|
||||
count = 1
|
||||
slot1 = uRange(0x118, 0x4)
|
||||
slot2 = uRange(0x158, 0x4)
|
||||
slot3 = uRange(0x198, 0x4)
|
||||
slot4 = uRange(0x1D8, 0x4)
|
||||
for i,v in pairs(slot1) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x118 + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot2) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x158 + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot3) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x198 + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot4) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x1D8 + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function getEmptyArmorSlots()
|
||||
memDomain.saveram()
|
||||
ret = {}
|
||||
count = 1
|
||||
slot1 = uRange(0x11C, 0x4)
|
||||
slot2 = uRange(0x15C, 0x4)
|
||||
slot3 = uRange(0x19C, 0x4)
|
||||
slot4 = uRange(0x1DC, 0x4)
|
||||
for i,v in pairs(slot1) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x11C + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot2) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x15C + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot3) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x19C + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for i,v in pairs(slot4) do
|
||||
if v == 0 then
|
||||
ret[count] = 0x1DC + i
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local function slice (tbl, s, e)
|
||||
local pos, new = 1, {}
|
||||
for i = s + 1, e do
|
||||
new[pos] = tbl[i]
|
||||
pos = pos + 1
|
||||
end
|
||||
return new
|
||||
end
|
||||
function processBlock(block)
|
||||
local msgBlock = block['messages']
|
||||
if msgBlock ~= nil then
|
||||
for i, v in pairs(msgBlock) do
|
||||
if itemMessages[i] == nil then
|
||||
local msg = {TTL=450, message=v, color=0xFFFF0000}
|
||||
itemMessages[i] = msg
|
||||
end
|
||||
end
|
||||
end
|
||||
local itemsBlock = block["items"]
|
||||
memDomain.saveram()
|
||||
isInGame = u8(0x102)
|
||||
if itemsBlock ~= nil and isInGame ~= 0x00 then
|
||||
if consumableStacks == nil then
|
||||
setConsumableStacks()
|
||||
end
|
||||
memDomain.saveram()
|
||||
-- print('ITEMBLOCK: ')
|
||||
-- print(itemsBlock)
|
||||
itemIndex = u8(ITEM_INDEX)
|
||||
-- print('ITEMINDEX: '..itemIndex)
|
||||
for i, v in pairs(slice(itemsBlock, itemIndex, #itemsBlock)) do
|
||||
-- Minus the offset and add to the correct domain
|
||||
local memoryLocation = v
|
||||
if v >= 0x100 and v <= 0x114 then
|
||||
-- This is a key item
|
||||
memoryLocation = memoryLocation - 0x0E0
|
||||
wU8(memoryLocation, 0x01)
|
||||
elseif v >= 0x1E0 and v <= 0x1F2 then
|
||||
-- This is a movement item
|
||||
-- Minus Offset (0x100) - movement offset (0xE0)
|
||||
memoryLocation = memoryLocation - 0x1E0
|
||||
-- Canal is a flipped bit
|
||||
if memoryLocation == 0x0C then
|
||||
wU8(memoryLocation, 0x00)
|
||||
else
|
||||
wU8(memoryLocation, 0x01)
|
||||
end
|
||||
elseif v >= 0x1F3 and v <= 0x1F4 then
|
||||
-- NoOverworld special items
|
||||
memoryLocation = noOverworldItemsLookup[v]
|
||||
wU8(memoryLocation, 0x01)
|
||||
elseif v >= 0x16C and v <= 0x1AF then
|
||||
-- This is a gold item
|
||||
amountToAdd = goldLookup[v]
|
||||
biggest = u8(0x01E)
|
||||
medium = u8(0x01D)
|
||||
smallest = u8(0x01C)
|
||||
currentValue = 0x10000 * biggest + 0x100 * medium + smallest
|
||||
newValue = currentValue + amountToAdd
|
||||
newBiggest = math.floor(newValue / 0x10000)
|
||||
newMedium = math.floor(math.fmod(newValue, 0x10000) / 0x100)
|
||||
newSmallest = math.floor(math.fmod(newValue, 0x100))
|
||||
wU8(0x01E, newBiggest)
|
||||
wU8(0x01D, newMedium)
|
||||
wU8(0x01C, newSmallest)
|
||||
elseif v >= 0x115 and v <= 0x11B then
|
||||
-- This is a regular consumable OR a shard
|
||||
-- Minus Offset (0x100) + item offset (0x20)
|
||||
memoryLocation = memoryLocation - 0x0E0
|
||||
currentValue = u8(memoryLocation)
|
||||
amountToAdd = consumableStacks[memoryLocation]
|
||||
if currentValue < 99 then
|
||||
wU8(memoryLocation, currentValue + amountToAdd)
|
||||
end
|
||||
elseif v >= 0x1B0 and v <= 0x1BB then
|
||||
-- This is an extension consumable
|
||||
memoryLocation = extensionConsumableLookup[v]
|
||||
currentValue = u8(memoryLocation)
|
||||
amountToAdd = consumableStacks[memoryLocation]
|
||||
if currentValue < 99 then
|
||||
value = currentValue + amountToAdd
|
||||
if value > 99 then
|
||||
value = 99
|
||||
end
|
||||
wU8(memoryLocation, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #itemsBlock > itemIndex then
|
||||
wU8(ITEM_INDEX, #itemsBlock)
|
||||
end
|
||||
|
||||
memDomain.saveram()
|
||||
weaponIndex = u8(WEAPON_INDEX)
|
||||
emptyWeaponSlots = getEmptyWeaponSlots()
|
||||
lastUsedWeaponIndex = weaponIndex
|
||||
-- print('WEAPON_INDEX: '.. weaponIndex)
|
||||
memDomain.saveram()
|
||||
for i, v in pairs(slice(itemsBlock, weaponIndex, #itemsBlock)) do
|
||||
if v >= 0x11C and v <= 0x143 then
|
||||
-- Minus the offset and add to the correct domain
|
||||
local itemValue = v - 0x11B
|
||||
if #emptyWeaponSlots > 0 then
|
||||
slot = table.remove(emptyWeaponSlots, 1)
|
||||
wU8(slot, itemValue)
|
||||
lastUsedWeaponIndex = weaponIndex + i
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if lastUsedWeaponIndex ~= weaponIndex then
|
||||
wU8(WEAPON_INDEX, lastUsedWeaponIndex)
|
||||
end
|
||||
memDomain.saveram()
|
||||
armorIndex = u8(ARMOR_INDEX)
|
||||
emptyArmorSlots = getEmptyArmorSlots()
|
||||
lastUsedArmorIndex = armorIndex
|
||||
-- print('ARMOR_INDEX: '.. armorIndex)
|
||||
memDomain.saveram()
|
||||
for i, v in pairs(slice(itemsBlock, armorIndex, #itemsBlock)) do
|
||||
if v >= 0x144 and v <= 0x16B then
|
||||
-- Minus the offset and add to the correct domain
|
||||
local itemValue = v - 0x143
|
||||
if #emptyArmorSlots > 0 then
|
||||
slot = table.remove(emptyArmorSlots, 1)
|
||||
wU8(slot, itemValue)
|
||||
lastUsedArmorIndex = armorIndex + i
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if lastUsedArmorIndex ~= armorIndex then
|
||||
wU8(ARMOR_INDEX, lastUsedArmorIndex)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function receive()
|
||||
l, e = ff1Socket:receive()
|
||||
if e == 'closed' then
|
||||
if curstate == STATE_OK then
|
||||
print("Connection closed")
|
||||
end
|
||||
curstate = STATE_UNINITIALIZED
|
||||
return
|
||||
elseif e == 'timeout' then
|
||||
print("timeout")
|
||||
return
|
||||
elseif e ~= nil then
|
||||
print(e)
|
||||
curstate = STATE_UNINITIALIZED
|
||||
return
|
||||
end
|
||||
processBlock(json.decode(l))
|
||||
|
||||
-- Determine Message to send back
|
||||
memDomain.rom()
|
||||
local playerName = uRange(0x7BCBF, 0x41)
|
||||
playerName[0] = nil
|
||||
local retTable = {}
|
||||
retTable["playerName"] = playerName
|
||||
if StateOKForMainLoop() then
|
||||
retTable["locations"] = generateLocationChecked()
|
||||
end
|
||||
msg = json.encode(retTable).."\n"
|
||||
local ret, error = ff1Socket:send(msg)
|
||||
if ret == nil then
|
||||
print(error)
|
||||
elseif curstate == STATE_INITIAL_CONNECTION_MADE then
|
||||
curstate = STATE_TENTATIVELY_CONNECTED
|
||||
elseif curstate == STATE_TENTATIVELY_CONNECTED then
|
||||
print("Connected!")
|
||||
itemMessages["(0,0)"] = {TTL=240, message="Connected", color="green"}
|
||||
curstate = STATE_OK
|
||||
end
|
||||
end
|
||||
|
||||
function main()
|
||||
if not checkBizHawkVersion() then
|
||||
return
|
||||
end
|
||||
server, error = socket.bind('localhost', 52980)
|
||||
|
||||
while true do
|
||||
gui.drawEllipse(248, 9, 6, 6, "Black", "Yellow")
|
||||
frame = frame + 1
|
||||
drawMessages()
|
||||
if not (curstate == prevstate) then
|
||||
-- console.log("Current state: "..curstate)
|
||||
prevstate = curstate
|
||||
end
|
||||
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
|
||||
if (frame % 60 == 0) then
|
||||
gui.drawEllipse(248, 9, 6, 6, "Black", "Blue")
|
||||
receive()
|
||||
else
|
||||
gui.drawEllipse(248, 9, 6, 6, "Black", "Green")
|
||||
end
|
||||
elseif (curstate == STATE_UNINITIALIZED) then
|
||||
gui.drawEllipse(248, 9, 6, 6, "Black", "White")
|
||||
if (frame % 60 == 0) then
|
||||
gui.drawEllipse(248, 9, 6, 6, "Black", "Yellow")
|
||||
|
||||
drawText(5, 8, "Waiting for client", 0xFFFF0000)
|
||||
drawText(5, 32, "Please start FF1Client.exe", 0xFFFF0000)
|
||||
|
||||
-- Advance so the messages are drawn
|
||||
emu.frameadvance()
|
||||
server:settimeout(2)
|
||||
print("Attempting to connect")
|
||||
local client, timeout = server:accept()
|
||||
if timeout == nil then
|
||||
-- print('Initial Connection Made')
|
||||
curstate = STATE_INITIAL_CONNECTION_MADE
|
||||
ff1Socket = client
|
||||
ff1Socket:settimeout(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
Reference in New Issue
Block a user