mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
TLoZ: Implementing The Legend of Zelda (#1354)
Co-authored-by: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
This commit is contained in:
702
data/lua/TLoZ/TheLegendOfZeldaConnector.lua
Normal file
702
data/lua/TLoZ/TheLegendOfZeldaConnector.lua
Normal file
@@ -0,0 +1,702 @@
|
||||
--Shamelessly based off the FF1 lua
|
||||
|
||||
local socket = require("socket")
|
||||
local json = require('json')
|
||||
local math = require('math')
|
||||
|
||||
local STATE_OK = "Ok"
|
||||
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
|
||||
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
|
||||
local STATE_UNINITIALIZED = "Uninitialized"
|
||||
|
||||
local itemMessages = {}
|
||||
local consumableStacks = nil
|
||||
local prevstate = ""
|
||||
local curstate = STATE_UNINITIALIZED
|
||||
local zeldaSocket = nil
|
||||
local frame = 0
|
||||
local gameMode = 0
|
||||
|
||||
local cave_index
|
||||
local triforce_byte
|
||||
local game_state
|
||||
|
||||
local u8 = nil
|
||||
local wU8 = nil
|
||||
local isNesHawk = false
|
||||
|
||||
local shopsChecked = {}
|
||||
local shopSlotLeft = 0x0628
|
||||
local shopSlotMiddle = 0x0629
|
||||
local shopSlotRight = 0x062A
|
||||
|
||||
--N.B.: you won't find these in a RAM map. They're flag values that the base patch derives from the cave ID.
|
||||
local blueRingShopBit = 0x40
|
||||
local potionShopBit = 0x02
|
||||
local arrowShopBit = 0x08
|
||||
local candleShopBit = 0x10
|
||||
local shieldShopBit = 0x20
|
||||
local takeAnyCaveBit = 0x01
|
||||
|
||||
|
||||
local sword = 0x0657
|
||||
local bombs = 0x0658
|
||||
local maxBombs = 0x067C
|
||||
local keys = 0x066E
|
||||
local arrow = 0x0659
|
||||
local bow = 0x065A
|
||||
local candle = 0x065B
|
||||
local recorder = 0x065C
|
||||
local food = 0x065D
|
||||
local waterOfLife = 0x065E
|
||||
local magicalRod = 0x065F
|
||||
local raft = 0x0660
|
||||
local bookOfMagic = 0x0661
|
||||
local ring = 0x0662
|
||||
local stepladder = 0x0663
|
||||
local magicalKey = 0x0664
|
||||
local powerBracelet = 0x0665
|
||||
local letter = 0x0666
|
||||
local clockItem = 0x066C
|
||||
local heartContainers = 0x066F
|
||||
local partialHearts = 0x0670
|
||||
local triforceFragments = 0x0671
|
||||
local boomerang = 0x0674
|
||||
local magicalBoomerang = 0x0675
|
||||
local magicalShield = 0x0676
|
||||
local rupeesToAdd = 0x067D
|
||||
local rupeesToSubtract = 0x067E
|
||||
local itemsObtained = 0x0677
|
||||
local takeAnyCavesChecked = 0x0678
|
||||
local localTriforce = 0x0679
|
||||
local bonusItemsObtained = 0x067A
|
||||
|
||||
itemAPids = {
|
||||
["Boomerang"] = 7100,
|
||||
["Bow"] = 7101,
|
||||
["Magical Boomerang"] = 7102,
|
||||
["Raft"] = 7103,
|
||||
["Stepladder"] = 7104,
|
||||
["Recorder"] = 7105,
|
||||
["Magical Rod"] = 7106,
|
||||
["Red Candle"] = 7107,
|
||||
["Book of Magic"] = 7108,
|
||||
["Magical Key"] = 7109,
|
||||
["Red Ring"] = 7110,
|
||||
["Silver Arrow"] = 7111,
|
||||
["Sword"] = 7112,
|
||||
["White Sword"] = 7113,
|
||||
["Magical Sword"] = 7114,
|
||||
["Heart Container"] = 7115,
|
||||
["Letter"] = 7116,
|
||||
["Magical Shield"] = 7117,
|
||||
["Candle"] = 7118,
|
||||
["Arrow"] = 7119,
|
||||
["Food"] = 7120,
|
||||
["Water of Life (Blue)"] = 7121,
|
||||
["Water of Life (Red)"] = 7122,
|
||||
["Blue Ring"] = 7123,
|
||||
["Triforce Fragment"] = 7124,
|
||||
["Power Bracelet"] = 7125,
|
||||
["Small Key"] = 7126,
|
||||
["Bomb"] = 7127,
|
||||
["Recovery Heart"] = 7128,
|
||||
["Five Rupees"] = 7129,
|
||||
["Rupee"] = 7130,
|
||||
["Clock"] = 7131,
|
||||
["Fairy"] = 7132
|
||||
}
|
||||
|
||||
itemCodes = {
|
||||
["Boomerang"] = 0x1D,
|
||||
["Bow"] = 0x0A,
|
||||
["Magical Boomerang"] = 0x1E,
|
||||
["Raft"] = 0x0C,
|
||||
["Stepladder"] = 0x0D,
|
||||
["Recorder"] = 0x05,
|
||||
["Magical Rod"] = 0x10,
|
||||
["Red Candle"] = 0x07,
|
||||
["Book of Magic"] = 0x11,
|
||||
["Magical Key"] = 0x0B,
|
||||
["Red Ring"] = 0x13,
|
||||
["Silver Arrow"] = 0x09,
|
||||
["Sword"] = 0x01,
|
||||
["White Sword"] = 0x02,
|
||||
["Magical Sword"] = 0x03,
|
||||
["Heart Container"] = 0x1A,
|
||||
["Letter"] = 0x15,
|
||||
["Magical Shield"] = 0x1C,
|
||||
["Candle"] = 0x06,
|
||||
["Arrow"] = 0x08,
|
||||
["Food"] = 0x04,
|
||||
["Water of Life (Blue)"] = 0x1F,
|
||||
["Water of Life (Red)"] = 0x20,
|
||||
["Blue Ring"] = 0x12,
|
||||
["Triforce Fragment"] = 0x1B,
|
||||
["Power Bracelet"] = 0x14,
|
||||
["Small Key"] = 0x19,
|
||||
["Bomb"] = 0x00,
|
||||
["Recovery Heart"] = 0x22,
|
||||
["Five Rupees"] = 0x0F,
|
||||
["Rupee"] = 0x18,
|
||||
["Clock"] = 0x21,
|
||||
["Fairy"] = 0x23
|
||||
}
|
||||
|
||||
|
||||
--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["ram"] = function() memory.usememorydomain("RAM") 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["ram"] = function() memory.usememorydomain("RAM") end
|
||||
memDomain["saveram"] = function() memory.usememorydomain("WRAM") end
|
||||
memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
|
||||
end
|
||||
return memDomain
|
||||
end
|
||||
|
||||
local memDomain = defineMemoryFunctions()
|
||||
u8 = memory.read_u8
|
||||
wU8 = memory.write_u8
|
||||
uRange = memory.readbyterange
|
||||
|
||||
itemIDNames = {}
|
||||
|
||||
for key, value in pairs(itemAPids) do
|
||||
itemIDNames[value] = key
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function determineItem(array)
|
||||
memdomain.ram()
|
||||
currentItemsObtained = u8(itemsObtained)
|
||||
|
||||
end
|
||||
|
||||
local function gotSword()
|
||||
local currentSword = u8(sword)
|
||||
wU8(sword, math.max(currentSword, 1))
|
||||
end
|
||||
|
||||
local function gotWhiteSword()
|
||||
local currentSword = u8(sword)
|
||||
wU8(sword, math.max(currentSword, 2))
|
||||
end
|
||||
|
||||
local function gotMagicalSword()
|
||||
wU8(sword, 3)
|
||||
end
|
||||
|
||||
local function gotBomb()
|
||||
local currentBombs = u8(bombs)
|
||||
local currentMaxBombs = u8(maxBombs)
|
||||
wU8(bombs, math.min(currentBombs + 4, currentMaxBombs))
|
||||
wU8(0x505, 0x29) -- Fake bomb to show item get.
|
||||
end
|
||||
|
||||
local function gotArrow()
|
||||
local currentArrow = u8(arrow)
|
||||
wU8(arrow, math.max(currentArrow, 1))
|
||||
end
|
||||
|
||||
local function gotSilverArrow()
|
||||
wU8(arrow, 2)
|
||||
end
|
||||
|
||||
local function gotBow()
|
||||
wU8(bow, 1)
|
||||
end
|
||||
|
||||
local function gotCandle()
|
||||
local currentCandle = u8(candle)
|
||||
wU8(candle, math.max(currentCandle, 1))
|
||||
end
|
||||
|
||||
local function gotRedCandle()
|
||||
wU8(candle, 2)
|
||||
end
|
||||
|
||||
local function gotRecorder()
|
||||
wU8(recorder, 1)
|
||||
end
|
||||
|
||||
local function gotFood()
|
||||
wU8(food, 1)
|
||||
end
|
||||
|
||||
local function gotWaterOfLifeBlue()
|
||||
local currentWaterOfLife = u8(waterOfLife)
|
||||
wU8(waterOfLife, math.max(currentWaterOfLife, 1))
|
||||
end
|
||||
|
||||
local function gotWaterOfLifeRed()
|
||||
wU8(waterOfLife, 2)
|
||||
end
|
||||
|
||||
local function gotMagicalRod()
|
||||
wU8(magicalRod, 1)
|
||||
end
|
||||
|
||||
local function gotBookOfMagic()
|
||||
wU8(bookOfMagic, 1)
|
||||
end
|
||||
|
||||
local function gotRaft()
|
||||
wU8(raft, 1)
|
||||
end
|
||||
|
||||
local function gotBlueRing()
|
||||
local currentRing = u8(ring)
|
||||
wU8(ring, math.max(currentRing, 1))
|
||||
memDomain.saveram()
|
||||
local currentTunicColor = u8(0x0B92)
|
||||
if currentTunicColor == 0x29 then
|
||||
wU8(0x0B92, 0x32)
|
||||
wU8(0x0804, 0x32)
|
||||
end
|
||||
end
|
||||
|
||||
local function gotRedRing()
|
||||
wU8(ring, 2)
|
||||
memDomain.saveram()
|
||||
wU8(0x0B92, 0x16)
|
||||
wU8(0x0804, 0x16)
|
||||
end
|
||||
|
||||
local function gotStepladder()
|
||||
wU8(stepladder, 1)
|
||||
end
|
||||
|
||||
local function gotMagicalKey()
|
||||
wU8(magicalKey, 1)
|
||||
end
|
||||
|
||||
local function gotPowerBracelet()
|
||||
wU8(powerBracelet, 1)
|
||||
end
|
||||
|
||||
local function gotLetter()
|
||||
wU8(letter, 1)
|
||||
end
|
||||
|
||||
local function gotHeartContainer()
|
||||
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
|
||||
if currentHeartContainers < 16 then
|
||||
currentHeartContainers = math.min(currentHeartContainers + 1, 16)
|
||||
local currentHearts = bit.band(u8(heartContainers), 0x0F) + 1
|
||||
wU8(heartContainers, bit.lshift(currentHeartContainers, 4) + currentHearts)
|
||||
end
|
||||
end
|
||||
|
||||
local function gotTriforceFragment()
|
||||
local triforceByte = 0xFF
|
||||
local newTriforceCount = u8(localTriforce) + 1
|
||||
wU8(localTriforce, newTriforceCount)
|
||||
end
|
||||
|
||||
local function gotBoomerang()
|
||||
wU8(boomerang, 1)
|
||||
end
|
||||
|
||||
local function gotMagicalBoomerang()
|
||||
wU8(magicalBoomerang, 1)
|
||||
end
|
||||
|
||||
local function gotMagicalShield()
|
||||
wU8(magicalShield, 1)
|
||||
end
|
||||
|
||||
local function gotRecoveryHeart()
|
||||
local currentHearts = bit.band(u8(heartContainers), 0x0F)
|
||||
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
|
||||
if currentHearts < currentHeartContainers then
|
||||
currentHearts = currentHearts + 1
|
||||
else
|
||||
wU8(partialHearts, 0xFF)
|
||||
end
|
||||
currentHearts = bit.bor(bit.band(u8(heartContainers), 0xF0), currentHearts)
|
||||
wU8(heartContainers, currentHearts)
|
||||
end
|
||||
|
||||
local function gotFairy()
|
||||
local currentHearts = bit.band(u8(heartContainers), 0x0F)
|
||||
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
|
||||
if currentHearts < currentHeartContainers then
|
||||
currentHearts = currentHearts + 3
|
||||
if currentHearts > currentHeartContainers then
|
||||
currentHearts = currentHeartContainers
|
||||
wU8(partialHearts, 0xFF)
|
||||
end
|
||||
else
|
||||
wU8(partialHearts, 0xFF)
|
||||
end
|
||||
currentHearts = bit.bor(bit.band(u8(heartContainers), 0xF0), currentHearts)
|
||||
wU8(heartContainers, currentHearts)
|
||||
end
|
||||
|
||||
local function gotClock()
|
||||
wU8(clockItem, 1)
|
||||
end
|
||||
|
||||
local function gotFiveRupees()
|
||||
local currentRupeesToAdd = u8(rupeesToAdd)
|
||||
wU8(rupeesToAdd, math.min(currentRupeesToAdd + 5, 255))
|
||||
end
|
||||
|
||||
local function gotSmallKey()
|
||||
wU8(keys, math.min(u8(keys) + 1, 9))
|
||||
end
|
||||
|
||||
local function gotItem(item)
|
||||
--Write itemCode to itemToLift
|
||||
--Write 128 to itemLiftTimer
|
||||
--Write 4 to sound effect queue
|
||||
itemName = itemIDNames[item]
|
||||
itemCode = itemCodes[itemName]
|
||||
wU8(0x505, itemCode)
|
||||
wU8(0x506, 128)
|
||||
wU8(0x602, 4)
|
||||
numberObtained = u8(itemsObtained) + 1
|
||||
wU8(itemsObtained, numberObtained)
|
||||
if itemName == "Boomerang" then gotBoomerang() end
|
||||
if itemName == "Bow" then gotBow() end
|
||||
if itemName == "Magical Boomerang" then gotMagicalBoomerang() end
|
||||
if itemName == "Raft" then gotRaft() end
|
||||
if itemName == "Stepladder" then gotStepladder() end
|
||||
if itemName == "Recorder" then gotRecorder() end
|
||||
if itemName == "Magical Rod" then gotMagicalRod() end
|
||||
if itemName == "Red Candle" then gotRedCandle() end
|
||||
if itemName == "Book of Magic" then gotBookOfMagic() end
|
||||
if itemName == "Magical Key" then gotMagicalKey() end
|
||||
if itemName == "Red Ring" then gotRedRing() end
|
||||
if itemName == "Silver Arrow" then gotSilverArrow() end
|
||||
if itemName == "Sword" then gotSword() end
|
||||
if itemName == "White Sword" then gotWhiteSword() end
|
||||
if itemName == "Magical Sword" then gotMagicalSword() end
|
||||
if itemName == "Heart Container" then gotHeartContainer() end
|
||||
if itemName == "Letter" then gotLetter() end
|
||||
if itemName == "Magical Shield" then gotMagicalShield() end
|
||||
if itemName == "Candle" then gotCandle() end
|
||||
if itemName == "Arrow" then gotArrow() end
|
||||
if itemName == "Food" then gotFood() end
|
||||
if itemName == "Water of Life (Blue)" then gotWaterOfLifeBlue() end
|
||||
if itemName == "Water of Life (Red)" then gotWaterOfLifeRed() end
|
||||
if itemName == "Blue Ring" then gotBlueRing() end
|
||||
if itemName == "Triforce Fragment" then gotTriforceFragment() end
|
||||
if itemName == "Power Bracelet" then gotPowerBracelet() end
|
||||
if itemName == "Small Key" then gotSmallKey() end
|
||||
if itemName == "Bomb" then gotBomb() end
|
||||
if itemName == "Recovery Heart" then gotRecoveryHeart() end
|
||||
if itemName == "Five Rupees" then gotFiveRupees() end
|
||||
if itemName == "Fairy" then gotFairy() end
|
||||
if itemName == "Clock" then gotClock() end
|
||||
end
|
||||
|
||||
|
||||
local function StateOKForMainLoop()
|
||||
memDomain.ram()
|
||||
local gameMode = u8(0x12)
|
||||
return gameMode == 5
|
||||
end
|
||||
|
||||
local function checkCaveItemObtained()
|
||||
memDomain.ram()
|
||||
local returnTable = {}
|
||||
returnTable["slot1"] = u8(shopSlotLeft)
|
||||
returnTable["slot2"] = u8(shopSlotMiddle)
|
||||
returnTable["slot3"] = u8(shopSlotRight)
|
||||
returnTable["takeAnys"] = u8(takeAnyCavesChecked)
|
||||
return returnTable
|
||||
end
|
||||
|
||||
function table.empty (self)
|
||||
for _, _ in pairs(self) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local bizhawk_version = client.getversion()
|
||||
local is23Or24Or25 = (bizhawk_version=="2.3.1") or (bizhawk_version:sub(1,3)=="2.4") or (bizhawk_version:sub(1,3)=="2.5")
|
||||
local is26To28 = (bizhawk_version:sub(1,3)=="2.6") or (bizhawk_version:sub(1,3)=="2.7") or (bizhawk_version:sub(1,3)=="2.8")
|
||||
|
||||
local function getMaxMessageLength()
|
||||
if is23Or24Or25 then
|
||||
return client.screenwidth()/11
|
||||
elseif is26To28 then
|
||||
return client.screenwidth()/12
|
||||
end
|
||||
end
|
||||
|
||||
local function drawText(x, y, message, color)
|
||||
if is23Or24Or25 then
|
||||
gui.addmessage(message)
|
||||
elseif is26To28 then
|
||||
gui.drawText(x, y, message, color, 0xB0000000, 18, "Courier New", "middle", "bottom", nil, "client")
|
||||
end
|
||||
end
|
||||
|
||||
local function clearScreen()
|
||||
if is23Or24Or25 then
|
||||
return
|
||||
elseif is26To28 then
|
||||
drawText(0, 0, "", "black")
|
||||
end
|
||||
end
|
||||
|
||||
local function drawMessages()
|
||||
if table.empty(itemMessages) then
|
||||
clearScreen()
|
||||
return
|
||||
end
|
||||
local y = 10
|
||||
found = false
|
||||
maxMessageLength = getMaxMessageLength()
|
||||
for k, v in pairs(itemMessages) do
|
||||
if v["TTL"] > 0 then
|
||||
message = v["message"]
|
||||
while true do
|
||||
drawText(5, y, message:sub(1, maxMessageLength), v["color"])
|
||||
y = y + 16
|
||||
|
||||
message = message:sub(maxMessageLength + 1, message:len())
|
||||
if message:len() == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
newTTL = 0
|
||||
if is26To28 then
|
||||
newTTL = itemMessages[k]["TTL"] - 1
|
||||
end
|
||||
itemMessages[k]["TTL"] = newTTL
|
||||
found = true
|
||||
end
|
||||
end
|
||||
if found == false then
|
||||
clearScreen()
|
||||
end
|
||||
end
|
||||
|
||||
function generateOverworldLocationChecked()
|
||||
memDomain.ram()
|
||||
data = uRange(0x067E, 0x81)
|
||||
data[0] = nil
|
||||
return data
|
||||
end
|
||||
|
||||
function getHCLocation()
|
||||
memDomain.rom()
|
||||
data = u8(0x1789A)
|
||||
return data
|
||||
end
|
||||
|
||||
function getPBLocation()
|
||||
memDomain.rom()
|
||||
data = u8(0x10CB2)
|
||||
return data
|
||||
end
|
||||
|
||||
function generateUnderworld16LocationChecked()
|
||||
memDomain.ram()
|
||||
data = uRange(0x06FE, 0x81)
|
||||
data[0] = nil
|
||||
return data
|
||||
end
|
||||
|
||||
function generateUnderworld79LocationChecked()
|
||||
memDomain.ram()
|
||||
data = uRange(0x077E, 0x81)
|
||||
data[0] = nil
|
||||
return data
|
||||
end
|
||||
|
||||
function updateTriforceFragments()
|
||||
memDomain.ram()
|
||||
local triforceByte = 0xFF
|
||||
totalTriforceCount = u8(localTriforce)
|
||||
local currentPieces = bit.rshift(triforceByte, 8 - math.min(8, totalTriforceCount))
|
||||
wU8(triforceFragments, currentPieces)
|
||||
end
|
||||
|
||||
function processBlock(block)
|
||||
if block ~= nil then
|
||||
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 bonusItems = block["bonusItems"]
|
||||
if bonusItems ~= nil and isInGame then
|
||||
for i, item in ipairs(bonusItems) do
|
||||
memDomain.ram()
|
||||
if i > u8(bonusItemsObtained) then
|
||||
if u8(0x505) == 0 then
|
||||
gotItem(item)
|
||||
wU8(itemsObtained, u8(itemsObtained) - 1)
|
||||
wU8(bonusItemsObtained, u8(bonusItemsObtained) + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local itemsBlock = block["items"]
|
||||
memDomain.saveram()
|
||||
isInGame = StateOKForMainLoop()
|
||||
updateTriforceFragments()
|
||||
if itemsBlock ~= nil and isInGame then
|
||||
memDomain.ram()
|
||||
--get item from item code
|
||||
--get function from item
|
||||
--do function
|
||||
for i, item in ipairs(itemsBlock) do
|
||||
memDomain.ram()
|
||||
if u8(0x505) == 0 then
|
||||
if i > u8(itemsObtained) then
|
||||
gotItem(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local shopsBlock = block["shops"]
|
||||
if shopsBlock ~= nil then
|
||||
wU8(shopSlotLeft, bit.bor(u8(shopSlotLeft), shopsBlock["left"]))
|
||||
wU8(shopSlotMiddle, bit.bor(u8(shopSlotMiddle), shopsBlock["middle"]))
|
||||
wU8(shopSlotRight, bit.bor(u8(shopSlotRight), shopsBlock["right"]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function difference(a, b)
|
||||
local aa = {}
|
||||
for k,v in pairs(a) do aa[v]=true end
|
||||
for k,v in pairs(b) do aa[v]=nil end
|
||||
local ret = {}
|
||||
local n = 0
|
||||
for k,v in pairs(a) do
|
||||
if aa[v] then n=n+1 ret[n]=v end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function receive()
|
||||
l, e = zeldaSocket: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(0x1F, 0x10)
|
||||
playerName[0] = nil
|
||||
local retTable = {}
|
||||
retTable["playerName"] = playerName
|
||||
if StateOKForMainLoop() then
|
||||
retTable["overworld"] = generateOverworldLocationChecked()
|
||||
retTable["underworld1"] = generateUnderworld16LocationChecked()
|
||||
retTable["underworld2"] = generateUnderworld79LocationChecked()
|
||||
end
|
||||
retTable["caves"] = checkCaveItemObtained()
|
||||
memDomain.ram()
|
||||
if gameMode ~= 19 then
|
||||
gameMode = u8(0x12)
|
||||
end
|
||||
retTable["gameMode"] = gameMode
|
||||
retTable["overworldHC"] = getHCLocation()
|
||||
retTable["overworldPB"] = getPBLocation()
|
||||
retTable["itemsObtained"] = u8(itemsObtained)
|
||||
msg = json.encode(retTable).."\n"
|
||||
local ret, error = zeldaSocket: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 (is23Or24Or25 or is26To28) == false then
|
||||
print("Must use a version of bizhawk 2.3.1 or higher")
|
||||
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 Zelda1Client.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
|
||||
zeldaSocket = client
|
||||
zeldaSocket:settimeout(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
BIN
data/lua/TLoZ/core.dll
Normal file
BIN
data/lua/TLoZ/core.dll
Normal file
Binary file not shown.
380
data/lua/TLoZ/json.lua
Normal file
380
data/lua/TLoZ/json.lua
Normal file
@@ -0,0 +1,380 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2015 rxi
|
||||
--
|
||||
-- This library is free software; you can redistribute it and/or modify it
|
||||
-- under the terms of the MIT license. See LICENSE for details.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.0" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if val[1] ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
--local line_count = 1
|
||||
--local col_count = 1
|
||||
--for i = 1, idx - 1 do
|
||||
-- col_count = col_count + 1
|
||||
-- if str:sub(i, i) == "\n" then
|
||||
-- line_count = line_count + 1
|
||||
-- col_count = 1
|
||||
-- end
|
||||
-- end
|
||||
-- emu.message( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
return ( parse(str, next_char(str, 1, space_chars, true)) )
|
||||
end
|
||||
|
||||
|
||||
return json
|
132
data/lua/TLoZ/socket.lua
Normal file
132
data/lua/TLoZ/socket.lua
Normal file
@@ -0,0 +1,132 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- LuaSocket helper module
|
||||
-- Author: Diego Nehab
|
||||
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Declare module and import dependencies
|
||||
-----------------------------------------------------------------------------
|
||||
local base = _G
|
||||
local string = require("string")
|
||||
local math = require("math")
|
||||
local socket = require("socket.core")
|
||||
module("socket")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Exported auxiliar functions
|
||||
-----------------------------------------------------------------------------
|
||||
function connect(address, port, laddress, lport)
|
||||
local sock, err = socket.tcp()
|
||||
if not sock then return nil, err end
|
||||
if laddress then
|
||||
local res, err = sock:bind(laddress, lport, -1)
|
||||
if not res then return nil, err end
|
||||
end
|
||||
local res, err = sock:connect(address, port)
|
||||
if not res then return nil, err end
|
||||
return sock
|
||||
end
|
||||
|
||||
function bind(host, port, backlog)
|
||||
local sock, err = socket.tcp()
|
||||
if not sock then return nil, err end
|
||||
sock:setoption("reuseaddr", true)
|
||||
local res, err = sock:bind(host, port)
|
||||
if not res then return nil, err end
|
||||
res, err = sock:listen(backlog)
|
||||
if not res then return nil, err end
|
||||
return sock
|
||||
end
|
||||
|
||||
try = newtry()
|
||||
|
||||
function choose(table)
|
||||
return function(name, opt1, opt2)
|
||||
if base.type(name) ~= "string" then
|
||||
name, opt1, opt2 = "default", name, opt1
|
||||
end
|
||||
local f = table[name or "nil"]
|
||||
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
|
||||
else return f(opt1, opt2) end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Socket sources and sinks, conforming to LTN12
|
||||
-----------------------------------------------------------------------------
|
||||
-- create namespaces inside LuaSocket namespace
|
||||
sourcet = {}
|
||||
sinkt = {}
|
||||
|
||||
BLOCKSIZE = 2048
|
||||
|
||||
sinkt["close-when-done"] = function(sock)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function(self, chunk, err)
|
||||
if not chunk then
|
||||
sock:close()
|
||||
return 1
|
||||
else return sock:send(chunk) end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sinkt["keep-open"] = function(sock)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function(self, chunk, err)
|
||||
if chunk then return sock:send(chunk)
|
||||
else return 1 end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sinkt["default"] = sinkt["keep-open"]
|
||||
|
||||
sink = choose(sinkt)
|
||||
|
||||
sourcet["by-length"] = function(sock, length)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function()
|
||||
if length <= 0 then return nil end
|
||||
local size = math.min(socket.BLOCKSIZE, length)
|
||||
local chunk, err = sock:receive(size)
|
||||
if err then return nil, err end
|
||||
length = length - string.len(chunk)
|
||||
return chunk
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sourcet["until-closed"] = function(sock)
|
||||
local done
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function()
|
||||
if done then return nil end
|
||||
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
|
||||
if not err then return chunk
|
||||
elseif err == "closed" then
|
||||
sock:close()
|
||||
done = 1
|
||||
return partial
|
||||
else return nil, err end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
sourcet["default"] = sourcet["until-closed"]
|
||||
|
||||
source = choose(sourcet)
|
Reference in New Issue
Block a user