mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00

Certain multiworld settings (bug observed with item link settings) can cause the total item count in TLoZ world to exceed 255. This causes an overflow in the loop to receive all pending items. This adds an additional byte to be used as a high byte for the items obtained counter. This approach was taken due to the surrounding bytes being occupied, preventing a direct 16-bit number from being used without moving to a different location and leaving more empty bytes in the memory block for save data.
618 lines
18 KiB
Lua
618 lines
18 KiB
Lua
--Shamelessly based off the FF1 lua
|
|
|
|
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 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 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
|
|
local itemsObtainedHigh = 0x067B
|
|
|
|
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 getItemsObtained()
|
|
return bit.bor(bit.lshift(u8(itemsObtainedHigh), 8), u8(itemsObtained))
|
|
end
|
|
|
|
local function setItemsObtained(value)
|
|
wU8(itemsObtainedHigh, bit.rshift(value, 8))
|
|
wU8(itemsObtained, bit.band(value, 0xFF))
|
|
end
|
|
|
|
local function determineItem(array)
|
|
memdomain.ram()
|
|
currentItemsObtained = getItemsObtained()
|
|
|
|
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 = getItemsObtained() + 1
|
|
setItemsObtained(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 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)
|
|
setItemsObtained(getItemsObtained() - 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 > getItemsObtained() 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 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, 0x11)
|
|
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"] = getItemsObtained()
|
|
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 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 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()
|