├── Mods ├── levitateL.lua ├── onePunchMan.lua ├── teleport.lua ├── displayBlueWarpTimer.lua ├── changeLanguage.lua ├── bombTornado.lua ├── speedMod.lua ├── displayBomb.lua ├── changeName.lua ├── changeTunic.lua ├── linkAttractor.lua ├── controlActor.lua ├── scale.lua ├── displayActors.lua └── minimap.lua ├── TODO.txt ├── main.lua ├── ModLoader.lua ├── .gitattributes ├── .gitignore ├── MM ├── Addr.wch ├── ActorModel.lua └── Cst.lua ├── OOT ├── ActorModel.lua ├── Addr.wch └── Cst.lua ├── Cst.lua ├── Mod.lua ├── UnitTests.lua ├── README.md ├── Actor.lua ├── Utils.lua ├── Addr.lua └── lib └── json.lua /Mods/levitateL.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | Mod.new("levitateL","Press L to Levitate",function() 6 | Utils.onButtonHold("levitateL-key",CST.INPUT.L,function() 7 | Addr.getById("Move.ZVelocity").set(4) 8 | end) 9 | end,function() 10 | Utils.clearOnButtonHold("levitateL-key",CST.INPUT.L) 11 | end) 12 | 13 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | 2 | Add new addresses in `Addr.wch` via Tools->RAM Watch 3 | Dungeon map/key/compass/flags. 4 | Permanent flags. 5 | Addresses to Actor Overlay Table 6 | http://wiki.cloudmodding.com/oot/Global_Context 7 | 8 | Create new mods following examples in `/Mods`. 9 | -throw boomerang that attracts everything 10 | 11 | Add Enums in Cst.lua 12 | 13 | 14 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | require("Cst") 2 | require("Utils") 3 | require("Addr") 4 | require("Actor") --before ActorModel 5 | require(CST.GAME..".ActorModel") 6 | require("Mod") 7 | require("ModLoader") --must be after Mod 8 | 9 | Mod.openMainForm() 10 | 11 | -- 0x2200 higher than pj64 gameshark 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ModLoader.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local MOD_DIRECTORY = Utils.getLuaDir()..[[\Mods]] 4 | 5 | 6 | local init = function() 7 | console.log("Loading mods from directory: " .. MOD_DIRECTORY) 8 | local modFolder = io.popen("dir \"" .. MOD_DIRECTORY .. "\" /b") 9 | 10 | for file in modFolder:lines() do 11 | local modId = bizstring.replace(file,".lua","") 12 | require("Mods." .. modId) 13 | end 14 | end 15 | 16 | init() -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /Mods/onePunchMan.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local cats = {[CST.ACTOR_CATEGORY.Enemy] = true,[CST.ACTOR_CATEGORY.Boss] = true} 6 | 7 | mod = Mod.new("onePunchMan","One Punch Man",function() 8 | Utils.onLoop("onePunchMan-loop",function() 9 | for cat,t in pairs(cats) do 10 | local actList = Actor.getActorsByCategory(cat) 11 | for i=1,actList.length do 12 | actList[i].health.set(1) --if 0, stalfos dont die... 13 | end 14 | end 15 | end,25) 16 | end,function() 17 | Utils.clearOnLoop("scale-loop") 18 | end) 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /temp/* 2 | 3 | # Windows image file caches 4 | Thumbs.db 5 | ehthumbs.db 6 | 7 | # Folder config file 8 | Desktop.ini 9 | 10 | # Recycle Bin used on file shares 11 | $RECYCLE.BIN/ 12 | 13 | # Windows Installer files 14 | *.cab 15 | *.msi 16 | *.msm 17 | *.msp 18 | 19 | # Windows shortcuts 20 | *.lnk 21 | 22 | # ========================= 23 | # Operating System Files 24 | # ========================= 25 | 26 | # OSX 27 | # ========================= 28 | 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear on external disk 37 | .Spotlight-V100 38 | .Trashes 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | -------------------------------------------------------------------------------- /MM/Addr.wch: -------------------------------------------------------------------------------- 1 | Domain ROM 2 | SystemID N64 3 | 001EF69C b u 1 RDRAM [8]Misc.Name 4 | 003E87D0 d u 1 RDRAM [12,3]Actor.List 5 | 003FFDD4 d f 1 RDRAM Move.X 6 | 003FFDD8 d f 1 RDRAM Move.Z 7 | 003FFDDC d f 1 RDRAM Move.Y 8 | 003FFE14 d f 1 RDRAM Move.XVelocity 9 | 003FFE18 d f 1 RDRAM Move.ZVelocity 10 | 003FFE1C d f 1 RDRAM Move.YVelocity 11 | 00400880 d f 1 RDRAM Move.Speed 12 | 00400884 w s 1 RDRAM Move.MovementAngle 13 | 003E6B20 b u 1 RDRAM GlobalContext.Start 14 | 003E6DD0 d h 1 RDRAM Misc.CameraActor 15 | 003FFDB0 d h 1 RDRAM Misc.CameraLink 16 | 001EF6A4 w u 1 RDRAM Value.MaxHp 17 | 001EF6A6 w u 1 RDRAM Value.HP 18 | 001EF6A8 b u 1 RDRAM Value.MagicMeterSize 19 | 001EF6A9 b u 1 RDRAM Value.CurrentMagic 20 | 001EF6AA w u 1 RDRAM Value.Rupee 21 | 001EF6B0 b u 1 RDRAM Value.CanUseMagic 22 | 001F054E w u 1 RDRAM Value.RupeeBank 23 | -------------------------------------------------------------------------------- /MM/ActorModel.lua: -------------------------------------------------------------------------------- 1 | local SIZE = Addr.SIZE 2 | local TYPE = Addr.TYPE 3 | 4 | ActorModel.new(ActorModel.BASE_MODEL,function(self) 5 | self.variant = Addr.new(self.address + 0x02,SIZE.byte).get() --unknown if mm 6 | self.roomNo = Addr.new(self.address + 0x03,SIZE.byte).get() --unknown if mm 7 | 8 | self.isLink = self.type == CST.ACTOR_TYPE.Link 9 | 10 | --based on http://wiki.cloudmodding.com/oot/Actors#Actor_Instances 11 | 12 | self.scaleX = Addr.new(self.address + 0x54,SIZE.float,TYPE.float) --+0x04 from oot 13 | self.scaleZ = Addr.new(self.address + 0x58,SIZE.float,TYPE.float) 14 | self.scaleY = Addr.new(self.address + 0x5C,SIZE.float,TYPE.float) 15 | 16 | self.prev = Addr.new(self.address + 0x128,SIZE.pointer) --+0x08 from oot 17 | self.next = Addr.new(self.address + 0x12C,SIZE.pointer) 18 | 19 | 20 | end) 21 | -------------------------------------------------------------------------------- /Mods/teleport.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local dropdown 7 | local checkbox 8 | local mod 9 | 10 | local Entrance = Addr.getById("Misc.EntranceIndex") 11 | 12 | mod = Mod.new("teleport","Teleport",function() 13 | form = forms.newform(250,200,"Teleport",function() 14 | form = nil 15 | mod.deactivate() 16 | end) 17 | 18 | dropdown = forms.dropdown(form,CST.EXIT_TO_MAP,0,0,200,20) 19 | forms.label(form,"Freeze Destination",0,30) 20 | checkbox = forms.checkbox(form,"",100,25) 21 | 22 | Utils.onLoop("teleport-",function() 23 | if(forms.ischecked(checkbox)) then 24 | Entrance.set(CST.MAP_TO_EXIT[forms.gettext(dropdown)]) 25 | end 26 | end,1) 27 | 28 | end,function() 29 | Utils.clearOnLoop("teleport-") 30 | if(form) then 31 | forms.destroy(form) 32 | end 33 | form = nil 34 | end) 35 | -------------------------------------------------------------------------------- /Mods/displayBlueWarpTimer.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local label = nil 7 | local mod 8 | 9 | mod = Mod.new("displayBlueWarpTimer","Display Blue Warp Timer",function() 10 | form = forms.newform(320,200,"Blue Warp",function() 11 | form = nil 12 | mod.deactivate() 13 | end) 14 | label = forms.label(form,"",0,0,200,25,true), 15 | 16 | Utils.onLoop("displayBlueWarpTimer-",function() 17 | local warp = Actor.getActorByType(CST.ACTOR_TYPE.Warpportals) 18 | if(warp) then 19 | forms.settext(label,"Timer = " .. warp.timer.get() .. " 0x" .. Utils.decToHex(warp.timer.address)) 20 | else 21 | forms.settext(label,"No Blue Warp in map.") 22 | end 23 | end,1) 24 | end,function() 25 | if(form) then 26 | forms.destroy(form) 27 | end 28 | form = nil 29 | Utils.clearOnLoop("displayBlueWarpTimer-") 30 | end) 31 | -------------------------------------------------------------------------------- /Mods/changeLanguage.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local checkbox 7 | local mod 8 | local Language = Addr.getById("Misc.Language") 9 | 10 | mod = Mod.new("changeLanguage","Change Language",function() 11 | form = forms.newform(200,150,"Blue Warp",function() 12 | form = nil 13 | mod.deactivate() 14 | end) 15 | checkbox = forms.checkbox(form,"Japanese?",0,0) 16 | Utils.setChecked(checkbox,Language.get() == CST.LANGUAGE.japanese) 17 | 18 | forms.label(form,"Active on Soft Reset.",0,25,200,25,true) 19 | 20 | forms.addclick(checkbox,function() 21 | Utils.setTimeout("changeLanguage-",function() 22 | if(forms.ischecked(checkbox)) then 23 | Language.set(CST.LANGUAGE.japanese) 24 | else 25 | Language.set(CST.LANGUAGE.english) 26 | end 27 | end,5) 28 | end) 29 | 30 | end,function() 31 | if(form) then 32 | forms.destroy(form) 33 | end 34 | form = nil 35 | Utils.clearOnLoop("changeLanguage-") 36 | end) 37 | -------------------------------------------------------------------------------- /Mods/bombTornado.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local LinkX = Addr.getById("Move.X") 7 | local LinkY = Addr.getById("Move.Y") 8 | 9 | --Mod.activate("displayBomb") 10 | 11 | local rotSpd = math.pi/15 12 | local rot = { 13 | [1] = 0/6 * math.pi, 14 | [2] = 4/6 * math.pi, 15 | [3] = 8/6 * math.pi, 16 | [4] = 12/6 * math.pi, 17 | [5] = 16/6 * math.pi, 18 | [6] = 20/6 * math.pi, 19 | } 20 | Mod.new("bombTornado","Bomb Tornado",function() 21 | Utils.onLoop("bombTornado-",function() 22 | for r=1,6 do 23 | rot[r] = rot[r] + rotSpd 24 | end 25 | 26 | local bombs = Actor.getActorsByCategory(CST.ACTOR_CATEGORY.Bomb) 27 | 28 | for i=1,6 do 29 | if(bombs[i]) then 30 | local bomb = bombs[i] 31 | 32 | if(bomb.timer.get() < 15) then --prevent explosion 33 | bomb.timer.set(40) 34 | end 35 | bomb.x.set(LinkX.get() + 75*math.cos(rot[i])) 36 | bomb.y.set(LinkY.get() + 75*math.sin(rot[i])) 37 | end 38 | end 39 | end,3) 40 | end,function() 41 | Utils.clearOnLoop("bombTornado-") 42 | end) 43 | -------------------------------------------------------------------------------- /Mods/speedMod.lua: -------------------------------------------------------------------------------- 1 | --Made by SoundBlitz 2 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 3 | return 4 | end 5 | 6 | local mod 7 | local form 8 | local tbspeed 9 | local framePressed = 0 10 | 11 | mod = Mod.new("speedMod","Link Speed Mod",function() 12 | form = forms.newform(250,90,"Link Speed Modifier",function() 13 | form = nil 14 | mod.deactivate() 15 | end) 16 | forms.label(form,"Links Speed (Hold A)",0,0,70,20) 17 | 18 | tbspeed = forms.textbox(form,"25",70,20,nil,0,20) 19 | 20 | Utils.onLoop("speedMod-loop",function() 21 | local speedmod = tonumber(forms.gettext(tbspeed)) or 1 22 | 23 | Utils.onButtonHold("speedMod-key",CST.INPUT.A,function() 24 | framePressed = framePressed + 1 25 | if(framePressed > 15) then 26 | Addr.getById("Move.Speed").set(speedmod) 27 | end 28 | end) 29 | 30 | Utils.onButtonRelease("speedMod-keyUp",CST.INPUT.A,function() 31 | framePressed = 0 32 | end) 33 | 34 | end,3) 35 | end,function() 36 | Utils.clearOnLoop("speedMod-loop") 37 | Utils.clearOnButtonHold("speedMod-key",CST.INPUT.A) 38 | Utils.clearOnButtonRelease("speedMod-keyUp",CST.INPUT.A) 39 | if(form) then 40 | forms.destroy(form) 41 | end 42 | form = nil 43 | end) -------------------------------------------------------------------------------- /Mods/displayBomb.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local bombForm = nil 7 | local mod 8 | --Mod.activate("displayBomb") 9 | 10 | mod = Mod.new("displayBomb","Display Bombs",function() 11 | form = forms.newform(320,200,"Bombs",function() 12 | form = nil 13 | mod.deactivate() 14 | end) 15 | bombForm = { 16 | [1]=forms.label(form,"",0,0,300,25,true), 17 | [2]=forms.label(form,"",0,25,300,25,true), 18 | [3]=forms.label(form,"",0,50,300,25,true), 19 | [4]=forms.label(form,"",0,75,300,25,true), 20 | [5]=forms.label(form,"",0,100,300,25,true), 21 | [6]=forms.label(form,"",0,125,300,25,true), 22 | } 23 | 24 | Utils.onLoop("displayBomb-timer",function() 25 | local bombs = Actor.getActorsByCategory(CST.ACTOR_CATEGORY.Bomb) 26 | 27 | local i 28 | for i=1,6 do 29 | if(not bombs[i]) then 30 | forms.settext(bombForm[i],"") 31 | else 32 | local text = "Bomb" .. i .. " = " 33 | .. bombs[i].timer.get() 34 | .. " | x=" .. math.floor(bombs[i].x.get()) 35 | .. " y=" .. math.floor(bombs[i].y.get()) 36 | .. " 0x" .. bombs[i].addressHex 37 | forms.settext(bombForm[i],text) 38 | end 39 | end 40 | end,3) 41 | end,function() 42 | if(form) then 43 | forms.destroy(form) 44 | end 45 | form = nil 46 | Utils.clearOnLoop("displayBomb-timer") 47 | end) 48 | -------------------------------------------------------------------------------- /Mods/changeName.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 2 | return 3 | end 4 | 5 | local form 6 | local mod 7 | local Name = Addr.getById("Misc.Name") 8 | 9 | local isOot = CST.GAME == CST.GAMES.OOT 10 | 11 | --oot JP: A=171 a=197 1=1 .=234 -=228 12 | --MM U: A=0x0A a=0x24 1=1 .=0x40 -=0x3f space=0x3e 13 | local off_a = isOot and (197 - string.byte("a")) or (0x24 - string.byte("a")) 14 | local off_A = isOot and (171 - string.byte("A")) or (0x0A - string.byte("A")) 15 | local off_0 = 0 - string.byte("0") 16 | local dot = isOot and 234 or 0x40 17 | local hyphen = isOot and 228 or 0x3f 18 | local space = isOot and 223 or 0x3e 19 | 20 | 21 | mod = Mod.new("changeName","Change Name",function() 22 | form = forms.newform(200,150,"Change Name",function() 23 | form = nil 24 | mod.deactivate() 25 | end) 26 | 27 | forms.label(form,"New Name: ") 28 | local textbox = forms.textbox(form,"",100,20,nil,25,25) 29 | 30 | forms.button(form,"Go",function() 31 | local str = forms.gettext(textbox) 32 | local i 33 | for i=1,8 do 34 | local c = Utils.charAt(str,i) 35 | if(not c) then 36 | Name.set(i-1,space) 37 | else 38 | local v = string.byte(c) 39 | if(c >= "a" and c <= "z") then 40 | Name.set(i-1,v + off_a) 41 | elseif(c >= "A" and c <= "Z") then 42 | Name.set(i-1,v + off_A) 43 | elseif(c >= "0" and c <= "9") then 44 | Name.set(i-1,v - off_0) 45 | elseif(c == ".") then 46 | Name.set(i-1,dot) 47 | elseif(c == "-") then 48 | Name.set(i-1,hyphen) 49 | else 50 | Name.set(i-1,space) 51 | end 52 | end 53 | end 54 | mod.deactivate() 55 | end,25,50) 56 | end,function() 57 | if(form) then 58 | forms.destroy(form) 59 | end 60 | form = nil 61 | end) 62 | -------------------------------------------------------------------------------- /OOT/ActorModel.lua: -------------------------------------------------------------------------------- 1 | local SIZE = Addr.SIZE 2 | local TYPE = Addr.TYPE 3 | 4 | ActorModel.new(ActorModel.BASE_MODEL,function(self) 5 | self.variant = Addr.new(self.address + 0x02,SIZE.byte).get() 6 | self.roomNo = Addr.new(self.address + 0x03,SIZE.byte).get() 7 | 8 | self.isLink = self.type == CST.ACTOR_TYPE.Link 9 | 10 | --based on http://wiki.cloudmodding.com/oot/Actors#Actor_Instances 11 | --self.collisionX = Addr.new(self.address + 0x08,SIZE.float,TYPE.float) 12 | --self.collisionZ = Addr.new(self.address + 0x0C,SIZE.float,TYPE.float) 13 | --self.collisionY = Addr.new(self.address + 0x10,SIZE.float,TYPE.float) 14 | 15 | --self.initRotX = Addr.new(self.address + 0x14,SIZE.word) 16 | --self.initRotZ = Addr.new(self.address + 0x16,SIZE.word) 17 | --self.initRotY = Addr.new(self.address + 0x18,SIZE.word) 18 | 19 | self.scaleX = Addr.new(self.address + 0x50,SIZE.float,TYPE.float) 20 | self.scaleZ = Addr.new(self.address + 0x54,SIZE.float,TYPE.float) 21 | self.scaleY = Addr.new(self.address + 0x58,SIZE.float,TYPE.float) 22 | 23 | self.prev = Addr.new(self.address + 0x120,SIZE.pointer) 24 | self.next = Addr.new(self.address + 0x124,SIZE.pointer) 25 | 26 | --only for damagable enemies 27 | self.damageChart = Addr.new(self.address + 0x98,SIZE.pointer) 28 | self.health = Addr.new(self.address + 0xAF,SIZE.byte) 29 | self.damageEffect = Addr.new(self.address + 0xB1,SIZE.byte) 30 | end) 31 | 32 | 33 | 34 | ActorModel.new(CST.ACTOR_TYPE.Bomb,function(self) 35 | self.timer = Addr.new(self.address + 0x1E9) 36 | end) 37 | 38 | ActorModel.new(CST.ACTOR_TYPE.Bombchu,function(self) 39 | self.timer = Addr.new(self.address + 0x141) 40 | end) 41 | 42 | ActorModel.new(CST.ACTOR_TYPE.Warpportals,function(self) 43 | self.timer = Addr.new(self.address + 0x183) 44 | end) 45 | -------------------------------------------------------------------------------- /Cst.lua: -------------------------------------------------------------------------------- 1 | CST = CST or {} 2 | 3 | CST.GAMES = { 4 | OOT="OOT", 5 | MM="MM", 6 | } 7 | 8 | CST.GAME = CST.GAMES.OOT 9 | if(bizstring.contains(bizstring.tolower(gameinfo.getromname()),"mask")) then --kinda bad... 10 | CST.GAME = CST.GAMES.MM 11 | console.log("Majora's Mask detected.") 12 | end 13 | --OOT is 941952000 14 | 15 | require(CST.GAME..".Cst") 16 | 17 | CST.GLOBAL_OFFSET = 0x80000000 18 | 19 | CST.INPUT = { 20 | ["A"] = "P1 A", 21 | ["B"] = "P1 B", 22 | ["Z"] = "P1 Z", 23 | ["R"] = "P1 R", 24 | ["L"] = "P1 L", 25 | ["Start"] = "P1 Start", 26 | ["CLeft"] = "P1 C Left", 27 | ["CRight"] = "P1 C Right", 28 | ["CUp"] = "P1 C Up", 29 | ["CDown"] = "P1 C Down", 30 | ["DLeft"] = "P1 DPad L", 31 | ["DRight"] = "P1 DPad R", 32 | ["DUp"] = "P1 DPad U", 33 | ["DDown"] = "P1 DPad D", 34 | ["YAxis"] = "P1 Y Axis", 35 | ["XAxis"] = "P1 X Axis" 36 | } 37 | 38 | CST.KEYS = { --Utils.displayInput() 39 | A = "A",B = "B",C = "C",D = "D",E = "E",F = "F", 40 | G = "G",H = "H",I = "I",J = "J",K = "K", 41 | L = "L",M = "M",N = "N",O = "O",P = "P", 42 | Q = "Q",R = "R",S = "S",T = "T",U = "U", 43 | V = "V",W = "W",X = "X",Y = "Y",Z = "Z", 44 | NumberPad1="NumberPad1",NumberPad2="NumberPad2",NumberPad3="NumberPad3",NumberPad4="NumberPad4", 45 | NumberPad5="NumberPad5",NumberPad6="NumberPad6",NumberPad7="NumberPad7",NumberPad8="NumberPad8", 46 | NumberPad9="NumberPad9",NumberPad0="NumberPad0", 47 | LeftArrow="LeftArrow",RightArrow="RightArrow",UpArrow="UpArrow",DownArrow="DownArrow", 48 | D1="D1",D2="D2",D3="D3",D4="D4",D5="D5",D6="D6",D7="D7",D8="D8",D9="D9",D0="D0", 49 | Grave="Grave",LeftBracket="LeftBracket",RightBracket="RightBracket", 50 | } 51 | 52 | CST.UI = { 53 | textbox = "textbox", 54 | label = "label", 55 | checkbox = "checkbox", 56 | dropdown = "dropdown" 57 | } 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Mods/changeTunic.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local form 6 | local canvas 7 | local Tunic = Addr.getById("Misc.TunicColorHex") 8 | local mod 9 | local textboxR 10 | local textboxG 11 | local textboxB 12 | local canvas 13 | 14 | local getTextboxText = function(tb) --returns length=2 15 | return bizstring.substring(forms.gettext(tb).."00",0,2) 16 | end 17 | 18 | mod = Mod.new("changeTunicColor","Change Tunic Color",function() 19 | form = forms.newform(200,200,"Change Tunic Color",function() 20 | form = nil 21 | mod.deactivate() 22 | end) 23 | 24 | forms.label(form,"R:",0,0,30,20) 25 | textboxR = forms.textbox(form,"",50,20,"HEX",50,0) 26 | forms.label(form,"G:",0,25,30,20) 27 | textboxG = forms.textbox(form,"",50,20,"HEX",50,25) 28 | forms.label(form,"B:",0,50,30,20) 29 | textboxB = forms.textbox(form,"",50,20,"HEX",50,50) 30 | 31 | forms.label(form,"Change applied on Soft Reset.",0,75) 32 | 33 | forms.button(form,"Go",function() 34 | local r = tonumber(getTextboxText(textboxR),16) 35 | local g = tonumber(getTextboxText(textboxG),16) 36 | local b = tonumber(getTextboxText(textboxB),16) 37 | 38 | Tunic.set(0,r) 39 | Tunic.set(1,g) 40 | Tunic.set(2,b) 41 | mod.deactivate() 42 | end,0,100) 43 | 44 | canvas = gui.createcanvas(200,200) 45 | 46 | Utils.onLoop("changeTunicColor-draw",function() 47 | local str = "0xFF" 48 | .. getTextboxText(textboxR) 49 | .. getTextboxText(textboxG) 50 | .. getTextboxText(textboxB) 51 | canvas.DrawRectangle(0,0,200,200,0,tonumber(str,16)) 52 | canvas.Refresh() 53 | end,10) 54 | 55 | 56 | end,function() 57 | Utils.clearOnLoop("changeTunicColor-draw") 58 | if(form) then 59 | forms.destroy(form) 60 | end 61 | if(canvas) then 62 | canvas.Dispose() 63 | end 64 | canvas = nil 65 | form = nil 66 | end) 67 | -------------------------------------------------------------------------------- /Mods/linkAttractor.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 2 | return 3 | end 4 | 5 | --local LinkX = Addr.getById("Move.X") 6 | --local LinkY = Addr.getById("Move.Y") 7 | --local LinkZ = Addr.getById("Move.Z") 8 | local rot = {} 9 | 10 | Mod.new("linkAttractor","Attractor",function() 11 | Utils.onLoop("linkAttractor-loop",function() 12 | local link = Actor.getActorByCategory(2) 13 | if(not link) then return end 14 | --local linkX = LinkX.get() 15 | --local linkY = LinkY.get() 16 | --local linkZ = LinkZ.get() 17 | local linkX = link.x.get() 18 | local linkY = link.y.get() 19 | local linkZ = link.z.get() 20 | 21 | 22 | 23 | for name,cat in pairs(CST.ACTOR_CATEGORY) do 24 | local actList = Actor.getActorsByCategory(cat) 25 | if(cat == CST.ACTOR_CATEGORY.Player) then 26 | actList.length = 0 27 | end 28 | 29 | for i=1,actList.length do 30 | local actor = actList[i] 31 | rot[actor.id] = rot[actor.id] or { 32 | value=0, 33 | inc=(math.random()-0.5)*math.pi/10, 34 | dist=25 + 75 * math.random(), 35 | z=80 + math.random()*80 36 | } 37 | local r = rot[actor.id] 38 | local x = actor.x.get() 39 | local y = actor.y.get() 40 | local z = actor.z.get() 41 | 42 | r.value = r.value + r.inc 43 | 44 | local dist = Utils.getDistance(linkX,linkY,x,y) 45 | if(dist < 500) then 46 | local tx = linkX + math.cos(r.value) * r.dist 47 | local ty = linkY + math.sin(r.value) * r.dist 48 | 49 | actor.x.set(x + (tx - x)/10) 50 | actor.y.set(y + (ty - y)/10) 51 | 52 | if(z < linkZ + r.z) then 53 | actor.z.set(z+5) 54 | else 55 | actor.z.set(linkZ + r.z) 56 | end 57 | end 58 | end 59 | end 60 | 61 | end,3) 62 | end,function() 63 | Utils.clearOnLoop("linkAttractor-loop") 64 | end) 65 | -------------------------------------------------------------------------------- /Mod.lua: -------------------------------------------------------------------------------- 1 | local formCheckbox = {} 2 | 3 | Mod = {} 4 | 5 | Mod.new = function(id,name,onActivate,onDeactivate) 6 | local self = {} 7 | self.id = id 8 | self.name = name 9 | self.onActivate = onActivate 10 | self.onDeactivate = onDeactivate 11 | self.active = false 12 | 13 | self.activate = function(updateCheckbox) 14 | if(not self.active) then 15 | self.onActivate(self) 16 | end 17 | 18 | self.active = true 19 | 20 | if(updateCheckbox ~= false and formCheckbox[self.id]) then 21 | Utils.setChecked(formCheckbox[self.id],true) 22 | end 23 | end 24 | 25 | self.deactivate = function(updateCheckbox) 26 | if(self.active and self.onDeactivate) then 27 | self.onDeactivate(self) 28 | end 29 | self.active = false 30 | 31 | if(updateCheckbox ~= false and formCheckbox[self.id]) then 32 | Utils.setChecked(formCheckbox[self.id],false) 33 | end 34 | end 35 | 36 | Mod.LIST[id] = self 37 | console.log("Mod loaded: " .. id) 38 | return self 39 | end 40 | 41 | Mod.LIST = {} 42 | 43 | Mod.getById = function(id) 44 | return Mod.LIST[id] 45 | end 46 | 47 | Mod.activate = function(id) 48 | Mod.LIST[id].activate() 49 | end 50 | 51 | Mod.deactivate = function(id) 52 | Mod.LIST[id].deactivate() 53 | end 54 | 55 | 56 | Mod.isGame = function(games) 57 | return Utils.contains(games,CST.GAME) 58 | 59 | end 60 | 61 | 62 | Mod.openMainForm = function() 63 | local form = forms.newform(300,450,"Mods",function() 64 | for key,m in pairs(Mod.LIST) do 65 | m.deactivate() 66 | end 67 | end) 68 | 69 | local y = 0 70 | for key,m in pairs(Mod.LIST) do 71 | forms.label(form,m.name,0,y,200,25,true) 72 | 73 | local checkbox = forms.checkbox(form,"",225,y) 74 | formCheckbox[m.id] = checkbox 75 | 76 | forms.addclick(checkbox,function() 77 | if(not forms.ischecked(checkbox)) then --not cuz stupid bizhawk 78 | Mod.LIST[key].activate(false) --[key] so can overwrite 79 | else 80 | Mod.LIST[key].deactivate(false) 81 | end 82 | end) 83 | y = y + 25 84 | end 85 | end 86 | 87 | 88 | return Mod 89 | 90 | -------------------------------------------------------------------------------- /UnitTests.lua: -------------------------------------------------------------------------------- 1 | local clearTimeout 2 | Utils.setTimeout("a",function() 3 | console.log("setTimeout 100") 4 | clearTimeout() --prevent b 5 | end,100) 6 | 7 | clearTimeout = Utils.setTimeout("b",function() 8 | console.log("should NOT be displayed") 9 | end,200) 10 | 11 | console.log("Pressing A should display message.") 12 | console.log("Pressing B should remove Button A events.") 13 | 14 | Utils.onButtonRelease("c",CST.INPUT.A,function() 15 | console.log("onButtonRelease A") 16 | end) 17 | 18 | Utils.onButtonPress("d",CST.INPUT.A,function() 19 | console.log("onButtonPress A") 20 | end) 21 | 22 | Utils.onButtonHold("e",CST.INPUT.A,function() 23 | console.log("onButtonHold A") 24 | end) 25 | 26 | 27 | Utils.onButtonRelease("f",CST.INPUT.B,function() 28 | Utils.clearOnButtonRelease("c",CST.INPUT.A) 29 | Utils.onButtonPress("d",CST.INPUT.A) 30 | Utils.onButtonHold("e",CST.INPUT.A) 31 | console.log("event with button A should now be removed") 32 | end) 33 | 34 | 35 | assert(Utils.arrayToString({length=2,[1]=23,[2]="ga"}) == "[23,ga]") 36 | assert(Utils.arrayToString({length=2,[1]=12,[2]=8},true) == "[0C,08]") 37 | assert(Utils.decToHex(100) == "64") 38 | assert(Utils.decToHex(100,4) == "0064") 39 | assert(Utils.decToBin(100) == "1100100") 40 | assert(Utils.decToBin(100,10) == "0001100100") 41 | assert(Utils.decToHex(0x803FFDB0) == "803FFDB0)") 42 | assert(tonumber(Utils.decToHex(0x803FFDB0),16) == 0x803FFDB0) 43 | assert(Utils.invertTable({bob="a"}).a == "bob") 44 | assert(Utils.addPadding("bob",5) == "bob ") 45 | assert(Utils.addPadding("bob",5,"y") == "bobyy") 46 | assert(Utils.addPadding("bob",5,"y",true) == "yybob") 47 | assert(Utils.contains({"12","43"},"43")) 48 | assert(not Utils.contains({"12","43"},"433")) 49 | 50 | --#################### 51 | console.log("#########") 52 | 53 | Addr.getById("Amount.Bomb").set(10) 54 | console.log("Bomb amount should be 10",Addr.getById("Amount.Bomb").get()) 55 | 56 | console.log("Kokiri Tunic removed from pause menu") 57 | Addr.getById("Equip.KokiriTunic").set("0") 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Mods/controlActor.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 2 | return 3 | end 4 | 5 | local Camera = Addr.getById("Misc.CameraActor") 6 | local CameraLinkPt = Utils.addGlobalOffset(Addr.getById("Misc.CameraLink").address) 7 | local form 8 | 9 | local selectedPt = 0 10 | local selectedPt_tb 11 | local mod 12 | 13 | mod = Mod.new("controlActor","Control Actor",function() 14 | form = forms.newform(300,200,"",function() 15 | form = nil 16 | mod.deactivate() 17 | end) 18 | 19 | Utils.addUI(form,"label",{text="Actor Pointer:",x=0,y=0,width=70}) 20 | selectedPt_tb = Utils.addUI(form,"textbox",{text="",x=75,y=0,width=125,type="HEX"}) 21 | 22 | Utils.addUI(form,"label",{text="Control with NumberPad",x=0,y=25,width=150}) 23 | 24 | Utils.addUI(form,"checkbox",{text="Focus Camera",x=0,y=50,onclick=function(checked) 25 | if(checked) then 26 | if(Actor.isPointerActor(selectedPt)) then 27 | Camera.set(Utils.addGlobalOffset(selectedPt)) 28 | else 29 | console.log("pointer doesnt pointer to an actor") 30 | end 31 | else 32 | Camera.set(CameraLinkPt) 33 | end 34 | end}) 35 | 36 | Utils.onLoop("controlActor-updateAct",function() 37 | selectedPt = tonumber(forms.gettext(selectedPt_tb),16) or 0 38 | 39 | if(selectedPt == 0 or not Actor.isPointerActor(selectedPt)) then 40 | return 41 | end 42 | local act = Actor.new(selectedPt) 43 | if(Utils.isButtonHeld(CST.KEYS.NumberPad4)) then 44 | act.x.set(act.x.get() - 20) 45 | end 46 | if(Utils.isButtonHeld(CST.KEYS.NumberPad6)) then 47 | act.x.set(act.x.get() + 20) 48 | end 49 | if(Utils.isButtonHeld(CST.KEYS.NumberPad8)) then 50 | act.y.set(act.y.get() - 20) 51 | end 52 | if(Utils.isButtonHeld(CST.KEYS.NumberPad2)) then 53 | act.y.set(act.y.get() + 20) 54 | end 55 | if(Utils.isButtonHeld(CST.KEYS.NumberPad5)) then 56 | act.z.set(act.z.get() + 50) 57 | end 58 | if(Utils.isButtonHeld(CST.KEYS.NumberPad9)) then 59 | act.z.set(act.z.get() - 50) 60 | end 61 | end,5) 62 | 63 | end,function() 64 | Utils.clearOnLoop("controlActor-updateAct") 65 | if(form) then 66 | forms.destroy(form) 67 | end 68 | form = nil 69 | end) 70 | -------------------------------------------------------------------------------- /Mods/scale.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT})) then 2 | return 3 | end 4 | 5 | local mod 6 | local form 7 | local tbX 8 | local tbY 9 | local tbZ 10 | local tbLX 11 | local tbLY 12 | local tbLZ 13 | 14 | mod = Mod.new("scale","Scale Modifier",function() 15 | form = forms.newform(200,200,"Scale",function() 16 | form = nil 17 | mod.deactivate() 18 | end) 19 | 20 | forms.label(form,"X:",0,0,50,20) 21 | tbX = forms.textbox(form,"1.0",50,20,nil,70,0) 22 | forms.label(form,"Y:",0,25,50,20) 23 | tbY = forms.textbox(form,"1.0",50,20,nil,70,25) 24 | forms.label(form,"Z:",0,50,50,20) 25 | tbZ = forms.textbox(form,"1.0",50,20,nil,70,50) 26 | 27 | 28 | forms.label(form,"Link X:",0,75,50,20) 29 | tbLX = forms.textbox(form,"1.0",50,20,nil,70,75) 30 | forms.label(form,"Link Y:",0,100,50,20) 31 | tbLY = forms.textbox(form,"1.0",50,20,nil,70,100) 32 | forms.label(form,"Link Z:",0,125,50,20) 33 | tbLZ = forms.textbox(form,"1.0",50,20,nil,70,125) 34 | 35 | Utils.onLoop("scale-loop",function() 36 | local scaleX = tonumber(forms.gettext(tbX)) or 1 37 | local scaleY = tonumber(forms.gettext(tbY)) or 1 38 | local scaleZ = tonumber(forms.gettext(tbZ)) or 1 39 | 40 | local scaleLX = tonumber(forms.gettext(tbLX)) or 1 41 | local scaleLY = tonumber(forms.gettext(tbLY)) or 1 42 | local scaleLZ = tonumber(forms.gettext(tbLZ)) or 1 43 | 44 | local actList = Actor.getActors() 45 | for i=1,actList.length do 46 | local act = actList[i] 47 | if(not act.custom["scale"]) then 48 | act.custom["scale"] = true 49 | Utils.setTimeout(math.random(),function() --must change scale after init 50 | if(act.isLink) then 51 | act.scaleX.set(scaleLX*act.scaleX.get()) 52 | act.scaleY.set(scaleLY*act.scaleY.get()) 53 | act.scaleZ.set(scaleLZ*act.scaleZ.get()) 54 | else 55 | act.scaleX.set(scaleX*act.scaleX.get()) 56 | act.scaleY.set(scaleY*act.scaleY.get()) 57 | act.scaleZ.set(scaleZ*act.scaleZ.get()) 58 | end 59 | end,25) 60 | end 61 | end 62 | end,3) 63 | end,function() 64 | Utils.clearOnLoop("scale-loop") 65 | if(form) then 66 | forms.destroy(form) 67 | end 68 | form = nil 69 | end) 70 | -------------------------------------------------------------------------------- /Mods/displayActors.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 2 | return 3 | end 4 | 5 | local form 6 | local myForms = {} 7 | local mod 8 | local catDd 9 | local typeDd 10 | local max = 28 11 | local hexCb 12 | 13 | local NOFILTER = "0--No Filter--" 14 | local catList = Utils.clone(CST.ACTOR_CATEGORY_TO_NAME) 15 | catList.whatever = NOFILTER 16 | 17 | local typeList = Utils.clone(CST.ACTOR_TYPE_TO_NAME) 18 | typeList.whatever = NOFILTER 19 | 20 | mod = Mod.new("displayActors","Display Actors",function() 21 | form = forms.newform(620,600,"Actors",function() 22 | form = nil 23 | mod.deactivate() 24 | end) 25 | 26 | catDd = forms.dropdown(form,catList,150,0,120,20) 27 | typeDd = forms.dropdown(form,typeList,300,0,180,20) 28 | 29 | hexCb = forms.checkbox(form,"Hex",510,0) 30 | 31 | for i=1,max do 32 | myForms[i] = Utils.addUI(form,"label",{text="",x=20,y=i * 20,width=600,height=20,fixedWidth=true}) 33 | Utils.addUI(form,"button",{x=0,y=i * 20,text=" ",width=20,height=20,onclick=function() 34 | console.log(forms.gettext(myForms[i])) 35 | end}) 36 | 37 | end 38 | 39 | Utils.onLoop("displayActors-",function() 40 | local catSel = forms.gettext(catDd) 41 | local typeSel = forms.gettext(typeDd) 42 | local hex = forms.ischecked(hexCb) 43 | 44 | local count = 1 45 | for name,cat in pairs(CST.ACTOR_CATEGORY) do 46 | if(catSel == NOFILTER or catSel == name) then 47 | local actList = Actor.getActorsByCategory(cat) 48 | 49 | for i=1,actList.length do 50 | local act = actList[i] 51 | if(typeSel == NOFILTER or CST.ACTOR_TYPE[typeSel] == act.type) then 52 | if(myForms[count]) then --only xx forms available 53 | local text = "" 54 | if(not hex) then 55 | text = Utils.addPadding(act.typeName,20) 56 | .. " 0x" .. act.addressHex 57 | .. " 0x" .. act.typeHex 58 | .. " x=" .. Utils.addPadding(""..math.floor(act.x.get()),5) 59 | .. " y=" .. Utils.addPadding(""..math.floor(act.y.get()),5) 60 | .. " z=" .. Utils.addPadding(""..math.floor(act.z.get()),5) 61 | if(act.timer) then 62 | text = text .. " timer=" .. act.timer.get() 63 | end 64 | else 65 | for j=0,10 do 66 | text = text .. Addr.new(act.address + j*4,Addr.SIZE.double,Addr.TYPE.hex).toString() .. " " 67 | end 68 | end 69 | 70 | forms.settext(myForms[count],text) 71 | count = count + 1 72 | end 73 | end 74 | end 75 | end 76 | end 77 | for j=count,max do 78 | forms.settext(myForms[j],"") 79 | end 80 | end,5) 81 | end,function() 82 | if(form) then 83 | forms.destroy(form) 84 | end 85 | form = nil 86 | Utils.clearOnLoop("displayActors-") 87 | end) 88 | -------------------------------------------------------------------------------- /Mods/minimap.lua: -------------------------------------------------------------------------------- 1 | if(not Mod.isGame({CST.GAMES.OOT,CST.GAMES.MM})) then 2 | return 3 | end 4 | 5 | local canvas 6 | local width = 400 7 | local height = 400 8 | local LinkX = Addr.getById("Move.X") 9 | local LinkY = Addr.getById("Move.Y") 10 | local form 11 | local zoom = 10 12 | local zoom_tb 13 | local mod 14 | local size = 100 15 | local size_tb 16 | local onlyDisplay = 0 17 | local onlyDisplay_tb 18 | local displayPt_cb 19 | 20 | local catColor = { 21 | [CST.ACTOR_CATEGORY.Bomb]={color=0xFF000000,size=6,cb=nil}, 22 | [CST.ACTOR_CATEGORY.Chest]={color=0xFFFF6600,size=6,cb=nil}, 23 | [CST.ACTOR_CATEGORY.Door]={color=0xFF888888,size=6,cb=nil}, 24 | [CST.ACTOR_CATEGORY.Enemy]={color=0xFFFF0000,size=6,cb=nil}, 25 | [CST.ACTOR_CATEGORY.Npc]={color=0xFF00FF00,size=6,cb=nil}, 26 | [CST.ACTOR_CATEGORY.Boss]={color=0xFF00FF00,size=8,cb=nil}, 27 | [CST.ACTOR_CATEGORY.Player]={color=0xFF0000FF,size=6,cb=nil}, 28 | [CST.ACTOR_CATEGORY.ItemAction]={color=0xFF00FFFF,size=6,cb=nil}, 29 | [CST.ACTOR_CATEGORY.Misc]={color=0xFFAA00AA,size=3,cb=nil}, 30 | [CST.ACTOR_CATEGORY.Prop]={color=0xFF005555,size=3,cb=nil}, 31 | [CST.ACTOR_CATEGORY.Prop1]={color=0xFF555500,size=3,cb=nil}, 32 | [CST.ACTOR_CATEGORY.Switch]={color=0xFF555500,size=3,cb=nil}, 33 | } 34 | 35 | 36 | --Addr.getById("Misc.CameraActor").set(0x8040C6D0) 37 | 38 | 39 | mod = Mod.new("minimap","Dynamic Minimap",function() 40 | canvas = gui.createcanvas(width,height) 41 | form = forms.newform(300,300,"",function() 42 | form = nil 43 | mod.deactivate() 44 | end) 45 | 46 | Utils.addUI(form,"label",{text="Zoom:",x=0,y=0,width=75}) 47 | zoom_tb = Utils.addUI(form,"textbox",{text="10",x=80,y=0,width=50,type="UNSIGNED"}) 48 | 49 | Utils.addUI(form,"label",{text="Square Size:",x=0,y=25,width=75}) 50 | size_tb = Utils.addUI(form,"textbox",{text="100",x=80,y=25,width=50,type="UNSIGNED"}) 51 | 52 | Utils.addUI(form,"label",{text="Only Display Pt:",x=0,y=50,width=75}) 53 | onlyDisplay_tb = Utils.addUI(form,"textbox",{text="000000",x=80,y=50,width=50,type="HEX"}) 54 | 55 | displayPt_cb = Utils.addUI(form,"checkbox",{text="Display Pt",x=0,y=75,width=100}) 56 | 57 | local c = 0 58 | for i,j in pairs(catColor) do 59 | catColor[i].cb = Utils.addUI(form,"checkbox",{ 60 | text=CST.ACTOR_CATEGORY_TO_NAME[i], 61 | x=125*(c%2),y=100 + 25*math.floor(c/2),checked=true,width=100, 62 | }) 63 | c = c + 1 64 | end 65 | 66 | 67 | Utils.onLoop("minimap-draw",function() 68 | zoom = tonumber(forms.gettext(zoom_tb)) or 10 69 | size = tonumber(forms.gettext(size_tb)) or 100 70 | onlyDisplay = tonumber(forms.gettext(onlyDisplay_tb),16) or 0 71 | local displayPt = forms.ischecked(displayPt_cb) 72 | canvas.Clear(0x00000000); 73 | 74 | local linkX = LinkX.get(); 75 | local linkY = LinkY.get(); 76 | for cat,cs in pairs(catColor) do 77 | if(forms.ischecked(catColor[cat].cb)) then 78 | local actList = Actor.getActorsByCategory(cat) 79 | 80 | for i=1,actList.length do 81 | local act = actList[i] 82 | local x = (act.x.get()-linkX)/zoom + width/2 83 | local y = (act.y.get()-linkY)/zoom + height/2 84 | local s = cs.size * 15 / zoom * size / 100 85 | if(onlyDisplay == 0 or onlyDisplay == act.address or act.isLink) then 86 | canvas.DrawRectangle(x-s/2,y-s/2,s,s,0,cs.color) 87 | if(displayPt) then 88 | canvas.DrawText(x-s/2,y-s/2-10, act.addressHex,0xFF000000,10); 89 | end 90 | end 91 | end 92 | end 93 | end 94 | 95 | canvas.Refresh() 96 | end,10) 97 | 98 | end,function() 99 | Utils.clearOnLoop("minimap-draw") 100 | if(canvas) then 101 | canvas.Dispose() 102 | end 103 | if(form) then 104 | forms.destroy(form) 105 | end 106 | form = nil 107 | canvas = nil 108 | end) 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Z64 Lua Hooks 2 | ============== 3 | 4 | This project provides an environment to create hooks and mods for Ocarina of Time 1.0 and Majora's Mask US. 5 | 6 | All you need is the [Bizhawk emulator](http://tasvideos.org/BizHawk.html) and the ROM. 7 | 8 | No setup is required. It works right out of the box. 9 | 10 | ####Features: 11 | - Read/write address support by id. Ex: `Addr.getById("Amount.Bomb").set(10)` 12 | - Actor RAM map. Ex: `Actor.new(pointerToBomb).x.set(10)` 13 | - Actor Finder. Ex: `Actor.getActorsByType(CST.ACTOR_TYPE.Bomb)[1].timer.set(11)` 14 | - Enum. Ex: `CST.ACTOR_TYPE.Bomb == 0x0010` 15 | - Modular Mod support. Ex: See `/Mods` folder. 16 | - Event support. Ex: `Utils.onButtonPress` `Utils.setTimeout` 17 | 18 | ####How to use: 19 | - Open Bizhawk and load the ROM. 20 | - Open the Lua Console via Tools->Lua Console 21 | - Open the script `main.lua` 22 | - Click to checkbox to apply the mods you want. 23 | - NOTICE: Always close the Mod Window before closing the Lua Console. 24 | 25 | ####How to contribute: 26 | - Extend the Actor RAM map in `Actor.lua`. 27 | - Add Enums in `Cst.lua`. 28 | - Add new addresses in `Addr.wch` via Tools->RAM Watch 29 | - Dungeon map/key/compass/flags. 30 | - Permanent flags. 31 | - Create new mods following examples in `/Mods`. 32 | - Check `TODO.txt` 33 | 34 | ####Mods made so far: 35 | - [Control Any Actors](https://youtu.be/Jbghgt4i22c) 36 | - [Minimap Displaying All Actors](https://youtu.be/1x5szVqoyuU) 37 | - [Attracts all Actors towards Link](https://www.youtube.com/watch?v=wQbrlCaYlx0) 38 | - [Scale Link and Actors](https://www.youtube.com/watch?v=Oczgt9Ib9KI) 39 | - [Display Actor Data](https://youtu.be/bcX-8PJ1yzA) 40 | - [Teleport](https://youtu.be/PzrsyLTpNb8) 41 | - Change Name 42 | - Bomb Tornado (Causes bombs to loop in circles around you) 43 | - Display Bomb (Display all bombs x,y,z,timer and address) 44 | - Press L to levitate 45 | - Teleport 46 | - Change Tunic Hex Color (Modifies ROM) 47 | 48 | [More Mods Video](https://www.youtube.com/watch?v=kUZ-sWL7h0Q) 49 | 50 | 51 | #### API 52 | 53 | ######Utils 54 | 55 | static 56 | string charAt(number pos) 57 | void onButtonPress(string id, CST.INPUT input, Function func) 58 | void onButtonRelease(string id, CST.INPUT input, Function func) 59 | void onButtonHold(string id, CST.INPUT input, Function func) 60 | void clearOnButtonHold(string id, CST.INPUT input) 61 | void clearOnButtonPress(string id, CST.INPUT input) 62 | void clearOnButtonRelease(string id, CST.INPUT input) 63 | Function setTimeout(string id, Function func, number time) 64 | Call the function returned to prevent the function call 65 | string readFile(string path) 66 | string decToHex(number num,[number length) 67 | Add 0 left padding to match length if provided 68 | string decToBin(number num,[number length) 69 | Add 0 left padding to match length if provided 70 | void onLoop(string id, Function func,number interval) 71 | void clearOnLoop(string id) 72 | string getLuaDir() 73 | Return path to folder containing "main.lua" 74 | boolean contains(Object list, Any element) 75 | int addUI(Form form,CST.UI type,Object prop) 76 | prop:{x,y,width,height,text,onclick,checked,multiline,fixedWidth} 77 | void setChecked(checkbox,val) 78 | 79 | 80 | ######Addr 81 | 82 | On initialization, every address in `Addr.wch` will create its corresponding Addr accessible via `Addr.getById(id)`. 83 | 84 | constructor(number address,Addr.SIZE size,Addr.TYPE type,string id) 85 | 86 | instance 87 | void set(number value) 88 | number get() 89 | string toString() 90 | 91 | static 92 | Addr getById(string id) 93 | 94 | 95 | ######Mod 96 | 97 | constructor(string id,string name,Function onActivate,[Function onDeactivate]) 98 | 99 | instance 100 | void activate() 101 | void deactivate() 102 | 103 | static 104 | Mod getById(string id) 105 | void activate(string id) 106 | void deactivate(string id) 107 | void openMainForm() 108 | 109 | 110 | ######Actor 111 | 112 | constructor(number address) 113 | 114 | static 115 | int* getActorByCategory(CST.ACTOR_CATEGORY cat) 116 | int*[] getActorsByCategory(CST.ACTOR_CATEGORY cat) 117 | int* getActorByType(CST.ACTOR_TYPE type) 118 | int*[] getActorsByType(CST.ACTOR_TYPE type) 119 | int* getActorById(number id) 120 | int*[] getActors() 121 | boolean isPointerActor(addr) 122 | return true if addr points to an actor 123 | Actor getLink() 124 | 125 | instance 126 | CST.ACTOR_TYPE type 127 | string addressHex 128 | number address 129 | number id 130 | Addr x 131 | Addr y 132 | Addr z 133 | Addr next 134 | Addr prev 135 | Addr scaleX 136 | Addr scaleY 137 | Addr scaleZ 138 | Object custom 139 | boolean isLink 140 | 141 | -------------------------------------------------------------------------------- /OOT/Addr.wch: -------------------------------------------------------------------------------- 1 | Domain ROM 2 | SystemID N64 3 | 00105440 d h 1 RDRAM Misc.RNG 4 | 0011A5D2 w h 1 RDRAM Misc.EntranceIndex 5 | 0011A5DA w h 1 RDRAM Misc.CutsceneNumber 6 | 0011A5DC w u 1 RDRAM Misc.TimeofDay 7 | 0011A5E0 b u 1 RDRAM Misc.Scene 8 | 0011A5F3 b u 1 RDRAM Misc.DeathCount 9 | 0011A5F4 b u 1 RDRAM [8]Misc.Name 10 | 0011A600 w u 1 RDRAM Value.HP 11 | 0011A602 b u 1 RDRAM Value.MagicMeterSize 12 | 0011A603 b u 1 RDRAM Value.CurrentMagic 13 | 0011A604 w u 1 RDRAM Value.Rupee 14 | 0011A60A b u 1 RDRAM Value.CanUseMagic 15 | 0011A638 b b 1 RDRAM Button.CRight 16 | 0011A639 b b 1 RDRAM Button.CDown 17 | 0011A63A b b 1 RDRAM Button.CLeft 18 | 0011A63B b b 1 RDRAM Button.B 19 | 0011A640 b h 1 RDRAM Misc.TunicColor 20 | 0011A644 b u 1 RDRAM Slot.Stick 21 | 0011A645 b u 1 RDRAM Slot.Nuts 22 | 0011A646 b u 1 RDRAM Slot.Bomb 23 | 0011A647 b u 1 RDRAM Slot.Bow 24 | 0011A648 b u 1 RDRAM Slot.FireArrow 25 | 0011A649 b u 1 RDRAM Slot.DinsFire 26 | 0011A64A b u 1 RDRAM Slot.Slingshot 27 | 0011A64B b u 1 RDRAM Slot.Ocarina 28 | 0011A64C b u 1 RDRAM Slot.Bombchu 29 | 0011A64D b u 1 RDRAM Slot.Hookshot 30 | 0011A64E b u 1 RDRAM Slot.IceArrow 31 | 0011A64F b u 1 RDRAM Slot.FaroresWind 32 | 0011A650 b u 1 RDRAM Slot.Boomerang 33 | 0011A651 b u 1 RDRAM Slot.LensofTruth 34 | 0011A652 b u 1 RDRAM Slot.Beans 35 | 0011A653 b u 1 RDRAM Slot.Hammer 36 | 0011A654 b u 1 RDRAM Slot.IceArrow2 37 | 0011A655 b u 1 RDRAM Slot.NaryusLove 38 | 0011A656 b u 1 RDRAM Slot.Bottle1 39 | 0011A657 b u 1 RDRAM Slot.Bottle2 40 | 0011A658 b u 1 RDRAM Slot.Bottle3 41 | 0011A659 b u 1 RDRAM Slot.Bottle4 42 | 0011A65A b u 1 RDRAM Slot.AdultTrade 43 | 0011A65B b u 1 RDRAM Slot.ChildTrade 44 | 0011A65C b u 1 RDRAM Amount.Stick 45 | 0011A65D b u 1 RDRAM Amount.Nuts 46 | 0011A65E b u 1 RDRAM Amount.Bomb 47 | 0011A65F b u 1 RDRAM Amount.Arrow 48 | 0011A662 b u 1 RDRAM Amount.Seed 49 | 0011A664 b u 1 RDRAM Amount.Bombchu 50 | 0011A66A b u 1 RDRAM Amount.Beans 51 | 0011A66B b u 1 RDRAM Amount.BeansAvailable 52 | 0011A66C b b 1 RDRAM (7,1)Equip.KokiriTunic 53 | 0011A66C b b 1 RDRAM (6,1)Equip.GoronTunic 54 | 0011A66C b b 1 RDRAM (5,1)Equip.ZoraTunic 55 | 0011A66C b b 1 RDRAM (3,1)Equip.KokiriBoots 56 | 0011A66C b b 1 RDRAM (2,1)Equip.IronBoots 57 | 0011A66C b b 1 RDRAM (1,1)Equip.HoverBoots 58 | 0011A66D b b 1 RDRAM (7,1)Equip.KokiriSword 59 | 0011A66D b b 1 RDRAM (6,1)Equip.MasterSword 60 | 0011A66D b b 1 RDRAM (4,2)Equip.BiggoronSword 61 | 0011A66D b b 1 RDRAM (3,1)Equip.DekuShield 62 | 0011A66D b b 1 RDRAM (2,1)Equip.HylianShield 63 | 0011A66D b b 1 RDRAM (1,1)Equip.MirrorShield 64 | 0011A671 b b 1 RDRAM (1,3)Capacity.Stick 65 | 0011A671 b b 1 RDRAM (3,3)Capacity.Nuts 66 | 0011A671 b b 1 RDRAM (7,3)Capacity.Seed 67 | 0011A672 b b 1 RDRAM (2,2)Capacity.Wallet 68 | 0011A672 b b 1 RDRAM (4,3)Capacity.Scale 69 | 0011A672 b b 1 RDRAM (7,3)Capacity.Strength 70 | 0011A673 b b 1 RDRAM (2,3)Capacity.Bomb 71 | 0011A673 b b 1 RDRAM (5,3)Capacity.Quiver 72 | 0011A674 b b 1 RDRAM (0,4)Quest.HeartPieces 73 | 0011A675 b b 1 RDRAM (7,1)Quest.SongOfTime 74 | 0011A675 b b 1 RDRAM (6,1)Quest.SongOfStorms 75 | 0011A675 b b 1 RDRAM (5,1)Quest.KokirisEmerald 76 | 0011A675 b b 1 RDRAM (4,1)Quest.GoronsRuby 77 | 0011A675 b b 1 RDRAM (3,1)Quest.ZorasSapphire 78 | 0011A675 b b 1 RDRAM (2,1)Quest.StoneOfAgony 79 | 0011A675 b b 1 RDRAM (1,1)Quest.GerudosCard 80 | 0011A675 b b 1 RDRAM (0,1)Quest.SkulltulaShown 81 | 0011A676 b b 1 RDRAM (7,1)Quest.SerenadeOfWater 82 | 0011A676 b b 1 RDRAM (6,1)Quest.RequiemOfSpirit 83 | 0011A676 b b 1 RDRAM (5,1)Quest.NocturneOfShadow 84 | 0011A676 b b 1 RDRAM (4,1)Quest.PreludeOfLight 85 | 0011A676 b b 1 RDRAM (3,1)Quest.ZeldasLullaby 86 | 0011A676 b b 1 RDRAM (2,1)Quest.EponasSong 87 | 0011A676 b b 1 RDRAM (1,1)Quest.SariasSong 88 | 0011A676 b b 1 RDRAM (0,1)Quest.SunsSong 89 | 0011A677 b b 1 RDRAM (7,1)Quest.ForestMedallion 90 | 0011A677 b b 1 RDRAM (6,1)Quest.FireMedallion 91 | 0011A677 b b 1 RDRAM (5,1)Quest.WaterMedallion 92 | 0011A677 b b 1 RDRAM (4,1)Quest.SpiritMedallion 93 | 0011A677 b b 1 RDRAM (3,1)Quest.ShadowMedallion 94 | 0011A677 b b 1 RDRAM (2,1)Quest.LightMedallion 95 | 0011A677 b b 1 RDRAM (1,1)Quest.MinuetOfForest 96 | 0011A677 b b 1 RDRAM (0,1)Quest.BoleroOfFire 97 | 0011A6A1 b u 1 RDRAM Quest.Skulltulas 98 | 0011B490 b u 1 RDRAM Quest.FishRecord 99 | 0011B4FE b b 1 RDRAM Misc.NeverEquippedSword 100 | 0011B964 w h 1 RDRAM Misc.GrottoReturnEntranceIndex 101 | 0011B9E2 w u 1 RDRAM Misc.CutsceneOffset 102 | 001CA0D0 d u 1 RDRAM [12,2]Actor.List 103 | 001C8710 d h 1 RDRAM Misc.CameraActor 104 | 001DAA30 d h 1 RDRAM Misc.CameraLink 105 | 001DA304 b b 1 RDRAM (0,1)Misc.InvulnerableChild 106 | 001DA671 b u 1 RDRAM Misc.FadeoutTimer 107 | 001DAA54 d f 1 RDRAM Move.X 108 | 001DAA58 d f 1 RDRAM Move.Z 109 | 001DAA5C d f 1 RDRAM Move.Y 110 | 001DAA90 d f 1 RDRAM Move.ZVelocity 111 | 001DAAE6 w s 1 RDRAM Move.FacingAngle 112 | 001DAB7F b u 1 RDRAM State.MaskEquipped 113 | 001DB09C d h 1 RDRAM State.Link 114 | 001DB258 d f 1 RDRAM Move.Speed 115 | 001DB25C w s 1 RDRAM Move.MoveAngle 116 | 003A9E3C w s 1 RDRAM Move.AnalogueAngle 117 | 003A9E3C w s 1 RDRAM Move.AnalogueAngle 118 | 00AED2D6 b h 1 ROM [3]Misc.TunicColorHex 119 | 0000003E b h 1 ROM Misc.Language--45=EN 4A=JP 120 | 121 | -------------------------------------------------------------------------------- /Actor.lua: -------------------------------------------------------------------------------- 1 | local ActorList = Addr.getById("Actor.List") 2 | local SIZE = Addr.SIZE 3 | local TYPE = Addr.TYPE 4 | 5 | local ID_ADDR = 26 6 | 7 | local USE_ID = false 8 | 9 | Actor = Actor or {} 10 | Actor.LIST = {} 11 | Actor.NEXT = 1 12 | Actor.COUNT = 0 13 | 14 | ActorModel = ActorModel or {} 15 | ActorModel.LIST = {} 16 | ActorModel.BASE_MODEL = "BASE_MODEL" 17 | ActorModel.new = function(id,constructor) 18 | if(not id) then 19 | Utils.onError("id is null, make sure you typed CST.ACTOR_TYPE correctly.") 20 | end 21 | ActorModel.LIST[id] = constructor 22 | end 23 | 24 | 25 | 26 | Utils.onLoop("Actor_garbageCollector",function() 27 | for i,act in pairs(Actor.LIST) do 28 | --changed type => no longer exists 29 | if(act.type ~= mainmemory.read_u16_be(act.address)) then 30 | Actor.LIST[i] = nil 31 | Actor.COUNT = Actor.COUNT - 1 32 | end 33 | end 34 | end,500) 35 | 36 | event.onloadstate(function() 37 | Utils.setTimeout("Actor_clear",function() 38 | Actor.NEXT = 1 39 | Actor.COUNT = 0 40 | Actor.LIST = {} 41 | local list = Actor.getActors() 42 | for i=1,list.length do 43 | mainmemory.write_u16_be(list[i].address + ID_ADDR,0) 44 | end 45 | end,1) 46 | end,"Actor_clearList") 47 | 48 | Actor.new = function(address) 49 | address = address % CST.GLOBAL_OFFSET 50 | 51 | local self = {} 52 | 53 | if(USE_ID) then 54 | local id = mainmemory.read_u16_be(address + ID_ADDR) 55 | if(id ~= 0 and Actor.LIST[id]) then 56 | return Actor.LIST[id] 57 | end 58 | 59 | id = Actor.NEXT 60 | mainmemory.write_u16_be(address + ID_ADDR,Actor.NEXT) 61 | Actor.NEXT = Actor.NEXT + 1 62 | Actor.COUNT = Actor.COUNT + 1 63 | Actor.LIST[id] = self 64 | 65 | self.id = id 66 | else 67 | self.id = address --address..self.type..self.roomNo..self.variant --relatively unique 68 | end 69 | 70 | self.custom = {} 71 | 72 | self.address = address 73 | self.addressHex = Utils.decToHex(address) 74 | 75 | self.x = Addr.new(self.address + 0x24,SIZE.float,TYPE.float) 76 | self.z = Addr.new(self.address + 0x28,SIZE.float,TYPE.float) 77 | self.y = Addr.new(self.address + 0x2C,SIZE.float,TYPE.float) 78 | 79 | 80 | self.type = Addr.new(self.address,SIZE.word).get() 81 | self.typeHex = Utils.decToHex(self.type,4) 82 | self.typeName = CST.ACTOR_TYPE_TO_NAME[self.type] or "" 83 | ActorModel.LIST[ActorModel.BASE_MODEL](self) 84 | 85 | if(ActorModel.LIST[self.type]) then 86 | ActorModel.LIST[self.type](self) 87 | end 88 | 89 | return self 90 | end 91 | 92 | Actor.getDistance = function(act0,act1) 93 | return Utils.getDistance(act0.x.get(),act0.y.get(),act1.x.get(),act1.y.get()) 94 | end 95 | 96 | Actor.QUERY = { 97 | address="address", 98 | actor="actor", 99 | addressHex="addressHex", 100 | type="type", 101 | typeHex="typeHex", 102 | typeName="typeName", 103 | } 104 | 105 | Actor.getActorByCategory = function(cat) --first one 106 | local count = ActorList.get(cat,0) 107 | if(count) then 108 | return Actor.new(ActorList.get(cat,1)) 109 | end 110 | return nil 111 | end 112 | 113 | Actor.getActorByType = function(type,categories) 114 | categories = categories or CST.ACTOR_CATEGORY --restrict to speed up 115 | for i,j in pairs(categories) do 116 | local list = Actor.getActorsByCategory(j) 117 | local k 118 | for k=1,list.length do 119 | if(list[k].type == type) then 120 | return list[k] 121 | end 122 | end 123 | end 124 | return nil 125 | end 126 | 127 | Actor.getActorsByType = function(type) 128 | local res = {length = 0} 129 | for i,j in pairs(CST.ACTOR_CATEGORY) do 130 | local list = Actor.getActorsByCategory(j) 131 | local k 132 | for k=1,list.length do 133 | if(list[k].type == type) then 134 | res.length = res.length + 1 135 | res[res.length] = list[k] 136 | end 137 | end 138 | end 139 | return res 140 | end 141 | 142 | Actor.getActors = function() 143 | local res = {length=0} 144 | for i,j in pairs(CST.ACTOR_CATEGORY) do 145 | local list = Actor.getActorsByCategory(j) 146 | for i=1,list.length do 147 | res.length = res.length + 1 148 | res[res.length] = list[i] 149 | end 150 | end 151 | return res 152 | end 153 | 154 | Actor.isPointerActor = function(pt) 155 | local list = Actor.getActors() 156 | for i = 1,list.length do 157 | if(list[i].address == pt) then 158 | return true 159 | end 160 | end 161 | return false 162 | end 163 | 164 | Actor.getLink = function() 165 | return Actor.getActorByType(CST.ACTOR_TYPE.Link,{CST.ACTOR_CATEGORY.Player}) 166 | end 167 | 168 | Actor.getById = function(id) 169 | return Actor.LIST[id] 170 | end 171 | 172 | Actor.getActorsByCategory = function(cat,query) 173 | local res = {} 174 | local count = ActorList.get(cat,0) 175 | res.length = count 176 | local pt = ActorList.get(cat,1) 177 | local i 178 | for i=1,count do 179 | local act = Actor.new(pt) 180 | if(not query or query == Actor.QUERY.actor) then 181 | res[i] = act 182 | elseif(query == Actor.QUERY.address) then 183 | res[i] = act.address 184 | elseif(query == Actor.QUERY.addressHex) then 185 | res[i] = act.addressHex 186 | elseif(query == Actor.QUERY.typeHex) then 187 | res[i] = act.typeHex 188 | elseif(query == Actor.QUERY.typeName) then 189 | res[i] = act.typeName 190 | elseif(query == Actor.QUERY.type) then 191 | res[i] = act.type 192 | end 193 | pt = act.next.get() 194 | end 195 | return res 196 | end 197 | 198 | Actor.printAll = function(query) 199 | for i,j in pairs(CST.ACTOR_CATEGORY) do 200 | console.log(i,Utils.arrayToString(Actor.getActorsByCategory(j,query))) 201 | end 202 | end 203 | 204 | 205 | 206 | return Actor 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /Utils.lua: -------------------------------------------------------------------------------- 1 | local callOnFrame = {} 2 | 3 | local frameCount = 0 4 | 5 | local lastInput = {} 6 | local lastJoy = {} 7 | local callOnButtonPress = {} 8 | local callOnButtonRelease = {} 9 | local callOnButtonHold = {} 10 | local callOnLoop = {} 11 | 12 | local function callFuncList (list,setToNil) 13 | if(list == nil) then 14 | return 15 | end 16 | 17 | for key,value in pairs(list) do 18 | if(list[key]) then 19 | if not pcall(list[key]) then 20 | Utils.onError("error with func "..key) 21 | end 22 | end 23 | if(setToNil) then 24 | list[key] = nil 25 | end 26 | end 27 | end 28 | 29 | local function timeout_loop() 30 | frameCount = frameCount + 1 31 | callFuncList(callOnFrame[frameCount],true) 32 | end 33 | 34 | 35 | 36 | local function inputLoop() 37 | local inp = input.get(); 38 | 39 | for key,value in pairs(inp) do 40 | if(not lastInput[key] and inp[key]) then 41 | callFuncList(callOnButtonPress[key]) 42 | end 43 | if(lastInput[key] and not inp[key]) then 44 | callFuncList(callOnButtonRelease[key]) 45 | end 46 | if(inp[key]) then 47 | callFuncList(callOnButtonHold[key]) 48 | end 49 | lastInput[key] = inp[key] 50 | end 51 | for key,value in pairs(lastInput) do 52 | lastInput[key] = inp[key] 53 | end 54 | 55 | --################ 56 | 57 | local inp2 = joypad.get(); 58 | for key,value in pairs(inp2) do 59 | if(key ~= CST.INPUT.Y_Axis and key ~= CST.INPUT.X_Axis) then 60 | if(not lastJoy[key] and inp2[key]) then 61 | callFuncList(callOnButtonPress[key]) 62 | end 63 | if(lastJoy[key] and not inp2[key]) then 64 | callFuncList(callOnButtonRelease[key]) 65 | end 66 | if(inp2[key]) then 67 | callFuncList(callOnButtonHold[key]) 68 | end 69 | lastJoy[key] = inp2[key] 70 | end 71 | end 72 | for key,value in pairs(lastJoy) do 73 | lastJoy[key] = inp2[key] 74 | end 75 | end 76 | 77 | 78 | 79 | Utils = {} 80 | 81 | Utils.onButtonPress = function(id,key,func) 82 | callOnButtonPress[key] = callOnButtonPress[key] or {}; 83 | callOnButtonPress[key][id] = func 84 | end 85 | 86 | Utils.onButtonRelease = function(id,key,func) 87 | callOnButtonRelease[key] = callOnButtonRelease[key] or {}; 88 | callOnButtonRelease[key][id] = func 89 | end 90 | 91 | Utils.onButtonHold = function(id,key,func) 92 | callOnButtonHold[key] = callOnButtonHold[key] or {}; 93 | callOnButtonHold[key][id] = func 94 | end 95 | 96 | Utils.clearOnButtonPress = function(id,key) 97 | callOnButtonPress[key] = callOnButtonPress[key] or {} 98 | callOnButtonPress[key][id] = nil 99 | end 100 | 101 | Utils.clearOnButtonRelease = function(id,key) 102 | callOnButtonRelease[key] = callOnButtonRelease[key] or {} 103 | callOnButtonRelease[key][id] = nil 104 | end 105 | 106 | Utils.clearOnButtonHold = function(id,key) 107 | callOnButtonHold[key] = callOnButtonHold[key] or {} 108 | callOnButtonHold[key][id] = nil 109 | end 110 | 111 | Utils.isButtonHeld = function(key) 112 | return input.get()[key] or false 113 | end 114 | 115 | Utils.setTimeout = function(id,func,time2) 116 | local t = frameCount + time2; 117 | callOnFrame[t] = callOnFrame[t] or {}; 118 | callOnFrame[t][id] = func 119 | 120 | return function() 121 | callOnFrame[t][id] = nil 122 | end 123 | end 124 | 125 | Utils.readFile = function(filename) 126 | local file = assert(io.open(filename)) 127 | local contents = file:read('*all') 128 | file:close() 129 | return contents 130 | end 131 | 132 | Utils.decToHex = function(val,length) 133 | --a = ("%08X"):format(number) 134 | local OUT = bizstring.hex(val) 135 | if(length ~= nil) then 136 | return Utils.addPadding(OUT,length,"0",true) 137 | end 138 | return OUT 139 | end 140 | 141 | Utils.decToBin = function(val,length) 142 | local OUT = bizstring.binary(val) 143 | if(length ~= nil) then 144 | return Utils.addPadding(OUT,length,"0",true) 145 | end 146 | return OUT 147 | end 148 | 149 | Utils.onLoop = function(id,func,interval) 150 | local c = 0 151 | interval = interval or 1 152 | event.unregisterbyname(id) 153 | event.onframestart(function() 154 | c = c + 1 155 | if(c % interval == 0) then 156 | if(not pcall(func)) then 157 | Utils.onError("error with onLoop "..id) 158 | end 159 | end 160 | end,id) 161 | end 162 | 163 | Utils.clearOnLoop = function(id) 164 | event.unregisterbyname(id) 165 | end 166 | 167 | Utils.onError = function(myErr,info) 168 | if(info) then 169 | console.log(myErr,info) 170 | else 171 | console.log(myErr) 172 | end 173 | console.log(debug.traceback()) 174 | end 175 | 176 | Utils.charAt = function(str,i) 177 | return string.sub(str,i,i) 178 | end 179 | 180 | Utils.getLuaDir = function() 181 | return io.popen"cd":read'*l' 182 | end 183 | 184 | Utils.arrayToString = function(list,hex) 185 | if(not list.length) then 186 | return Utils.onError("No .length on list provided in Utils.arrayToString") 187 | end 188 | local i 189 | local str = "[" 190 | for i=1,list.length do 191 | if(hex) then 192 | str = str .. Utils.decToHex(list[i]) 193 | else 194 | str = str .. list[i] 195 | end 196 | if(i ~= list.length) then 197 | str = str .. "," 198 | end 199 | end 200 | return str .. "]" 201 | end 202 | 203 | Utils.invertTable = function(list) 204 | local res = {} 205 | for i,j in pairs(list) do 206 | res[j] = i 207 | end 208 | return res 209 | end 210 | 211 | Utils.clone = function(list) 212 | local res = {} 213 | for i,j in pairs(list) do 214 | res[i] = j 215 | end 216 | return res 217 | end 218 | 219 | Utils.addPadding = function(str,length,charr,left) 220 | charr = charr or " " 221 | while string.len(str) < length do 222 | if(left) then 223 | str = charr .. str 224 | else 225 | str = str .. charr 226 | end 227 | end 228 | return str 229 | end 230 | 231 | Utils.getDistance = function(x0,y0,x1,y1) 232 | return ((x0-x1)^2 + (y0-y1)^2)^0.5 233 | end 234 | 235 | Utils.contains = function(list,what) 236 | for i,j in pairs(list) do 237 | if(j == what) then 238 | return true 239 | end 240 | end 241 | return false 242 | end 243 | 244 | Utils.displayInput = function() 245 | Utils.onLoop("Utils.displayInput",function() 246 | console.log("input:",input.get()) 247 | end,100) 248 | end 249 | 250 | Utils.clearDisplayInput = function() 251 | Utils.clearOnLoop("Utils.displayInput") 252 | end 253 | 254 | Utils.addUI = function(form,type,prop) 255 | prop.height = prop.height or 25 256 | prop.width = prop.width or 100 257 | prop.x = prop.x or 0 258 | prop.y = prop.y or 0 259 | prop.text = prop.text or "" 260 | prop.multiline = prop.multiline or false 261 | prop.fixedWidth = prop.fixedWidth or false 262 | 263 | local handle 264 | if(type == "label") then 265 | handle = forms.label(form,prop.text,prop.x, prop.y,prop.width, prop.height,prop.fixedWidth) 266 | elseif(type == "textbox") then 267 | handle = forms.textbox(form,prop.text,prop.width,prop.height,prop.type,prop.x, prop.y,prop.multiline, prop.fixedWidth) 268 | elseif(type == "dropdown") then 269 | handle = forms.dropdown(form,prop.items,prop.x,prop.y,prop.width, prop.height) 270 | elseif(type == "checkbox") then 271 | handle = forms.checkbox(form,prop.text,prop.x,prop.y) 272 | if(prop.checked) then 273 | Utils.setChecked(handle,true) 274 | end 275 | elseif(type == "button") then 276 | handle = forms.button(form,prop.text,prop.onclick,prop.x,prop.y,prop.width, prop.height) 277 | else 278 | return Utils.onError("invalid UI type",type) 279 | end 280 | if(type ~= "button" and prop.onclick) then 281 | forms.addclick(handle,function() 282 | if(type == CST.UI.checkbox) then 283 | prop.onclick(not forms.ischecked(handle)) 284 | else 285 | prop.onclick(handle) 286 | end 287 | end) 288 | end 289 | return handle 290 | end 291 | 292 | Utils.setChecked = function(handle,val) 293 | forms.setproperty(handle,"Checked",val) 294 | end 295 | 296 | Utils.addGlobalOffset = function(val) 297 | return tonumber("80"..Utils.decToHex(val,6),16) 298 | end 299 | 300 | event.unregisterbyname('timeout_loop') 301 | event.onframestart(timeout_loop,'timeout_loop') 302 | 303 | event.unregisterbyname('inputLoop') 304 | event.onframestart(inputLoop,'inputLoop') 305 | 306 | 307 | 308 | 309 | return Utils 310 | 311 | -------------------------------------------------------------------------------- /Addr.lua: -------------------------------------------------------------------------------- 1 | local WCH_PATH = CST.GAME.."\\Addr.wch" 2 | 3 | Addr = {} 4 | 5 | local SIZE = { bit=1/8, byte=8/8,word=16/8,double=32/8,pointer=32/8,float=32/8 } 6 | local TYPE = { unsigned=0,signed=1,hex=2,binary=3,fixedPoint12=4,fixedPoint20=5,fixedPoint16=6,float=7} 7 | Addr.SIZE = SIZE 8 | Addr.TYPE = TYPE 9 | Addr.LIST = {} 10 | 11 | Addr.new = function(address,size,type,id,sbStart,sbLength,isRom) 12 | address = address % CST.GLOBAL_OFFSET 13 | local self = {} 14 | self.address = address 15 | self.addressHex = Utils.decToHex(self.address) 16 | self.size = size or SIZE.byte 17 | self.type = type or TYPE.unsigned 18 | self.id = id or "no id" 19 | self.sbStart = sbStart or 0 20 | self.sbLength = sbLength or 0 21 | self.lastValue = 0 22 | self.isRom = isRom or false 23 | 24 | local mem = mainmemory --aka RAM 25 | if(self.isRom) then 26 | mem = memory --aka ROM 27 | end 28 | 29 | 30 | if(self.sbStart + self.sbLength > 16) then 31 | return Utils.onError("sbStart + sbLength must be < 16") 32 | end 33 | 34 | self.print = function() 35 | console.log(self.id .. " = " .. self.toString()) 36 | end 37 | 38 | self.set = function(val) 39 | if(self.size == SIZE.bit) then 40 | if(string.len(val) ~= self.sbLength) then 41 | return Utils.onError("self.set string.len(val) must == SbLength",self.id) 42 | end 43 | 44 | local v = mem.read_u16_be(self.address) 45 | local str = Utils.decToBin(v,16) 46 | local rightLen = 16 - self.sbStart - self.sbLength; 47 | local str2 = bizstring.substring(str,0,self.sbStart) --self.sbStart 48 | .. val --selfLength 49 | .. bizstring.substring(str,self.sbStart+self.sbLength,rightLen) 50 | local numVal = tonumber(str2, 2) 51 | return mem.write_u16_be(self.address,numVal) 52 | elseif(self.size == SIZE.byte) then 53 | if(self.type == TYPE.signed) then 54 | return mem.write_s8(self.address,val) 55 | else 56 | return mem.write_u8(self.address,val) 57 | end 58 | elseif(self.size == SIZE.word) then 59 | if(self.type == TYPE.signed) then 60 | return mem.write_s16_be(self.address,val) 61 | else 62 | return mem.write_u16_be(self.address,val) 63 | end 64 | elseif(self.size == SIZE.double) then 65 | if(self.type == TYPE.signed) then 66 | return mem.write_s32_be(self.address,val) 67 | end 68 | if(self.type == TYPE.float) then 69 | return mem.writefloat(self.address,val,true) 70 | end 71 | return mem.write_u32_be(self.address,val) 72 | end 73 | end 74 | 75 | self.get = function() 76 | if(self.size == SIZE.bit) then 77 | local v = mem.read_u16_be(self.address) 78 | local str = Utils.decToBin(v,16) 79 | return bizstring.substring(str,1+self.sbStart,self.sbLength) 80 | elseif(self.size == SIZE.byte) then 81 | if(self.type == TYPE.signed) then 82 | return mem.read_s8(self.address) 83 | end 84 | return mem.read_u8(self.address) 85 | elseif(self.size == SIZE.word) then 86 | if(self.type == TYPE.signed) then 87 | return mem.read_s16_be(self.address) 88 | end 89 | return mem.read_u16_be(self.address) 90 | elseif(self.size == SIZE.double) then 91 | if(self.type == TYPE.signed) then 92 | return mem.read_s32_be(self.address) 93 | elseif(self.type == TYPE.float) then 94 | return mem.readfloat(self.address,true) 95 | else 96 | return mem.read_u32_be(self.address) 97 | end 98 | end 99 | end 100 | 101 | self.toString = function() 102 | local val = self.get(); 103 | if(self.type == TYPE.bin) then 104 | return Utils.decToBin(val) 105 | elseif(self.type == TYPE.hex) then 106 | return Utils.decToHex(val,self.size*2) 107 | else 108 | return tostring(val) 109 | end 110 | end 111 | 112 | self.onChange = function(id,func) 113 | self.lastValue = self.get() 114 | Utils.onLoop("Addr-"..self.id..id,function() 115 | local newVal = self.get() 116 | if(self.lastValue ~= newVal) then 117 | local oldVal = self.lastValue 118 | self.lastValue = newVal 119 | func(newVal,oldVal) 120 | end 121 | end) 122 | end 123 | 124 | self.clearOnChange = function() 125 | Utils.clearOnLoop("Addr-"..self.id..id) 126 | end 127 | 128 | return self 129 | end 130 | 131 | Addr.create = function(str) 132 | local a = bizstring.split(str,"\t"); 133 | 134 | local address = tonumber(a[1], 16) 135 | if(address == 0) then --aka separator 136 | return nil 137 | end 138 | 139 | local size; 140 | if(a[2] == "b") then 141 | size = SIZE.byte 142 | elseif(a[2] == "w") then 143 | size = SIZE.word 144 | elseif(a[2] == "d") then 145 | size = SIZE.double 146 | end 147 | 148 | local type; 149 | if(a[3] == "u") then 150 | type = TYPE.unsigned 151 | elseif(a[3] == "s") then 152 | type = TYPE.signed 153 | elseif(a[3] == "h") then 154 | type = TYPE.hex 155 | elseif(a[3] == "b") then 156 | type = TYPE.binary 157 | elseif(a[3] == "1") then 158 | type = TYPE.fixedPoint12 159 | Utils.onError("unsupported type fixedPoint12",id) 160 | elseif(a[3] == "2") then 161 | type = TYPE.fixedPoint20 162 | Utils.onError("unsupported type fixedPoint20",id) 163 | elseif(a[3] == "3") then 164 | type = TYPE.fixedPoint16 165 | Utils.onError("unsupported type fixedPoint16",id) 166 | elseif(a[3] == "f") then 167 | type = TYPE.float 168 | end 169 | 170 | local id = a[6] 171 | id = bizstring.trim(bizstring.split(id,"--")[1]) 172 | 173 | local isRom = a[5] == "ROM" 174 | 175 | if(bizstring.startswith(id,"[")) then --only support array length < 10 176 | local str = bizstring.substring(id,1,string.len(id)-1) --remove [ 177 | local leftRight = bizstring.split(str,"]") 178 | id = leftRight[2] 179 | local split = bizstring.split(leftRight[1],",") 180 | local length1 = tonumber(split[1]) 181 | 182 | if(split[2]) then --2d array 183 | local length2 = tonumber(split[2]) 184 | Addr.LIST[id] = Addr.array2D.new(address,length1,length2,size,type,isRom) 185 | else --1d array 186 | Addr.LIST[id] = Addr.array.new(address,length1,size,type,isRom) 187 | end 188 | elseif(bizstring.startswith(id,"(")) then 189 | local start = tonumber(bizstring.substring(id,1,1)) 190 | local length = tonumber(bizstring.substring(id,3,1)) 191 | id = bizstring.substring(id,5,string.len(id)-5) 192 | Addr.LIST[id] = Addr.new(address,SIZE.bit,TYPE.binary,id,start,length,isRom) 193 | else 194 | Addr.LIST[id] = Addr.new(address,size,type,id,0,0,isRom) 195 | end 196 | return Addr.LIST[id] 197 | end 198 | 199 | Addr.getById = function(id) 200 | return Addr.LIST[id] 201 | end 202 | 203 | Addr.printJSON = function(pathFile) 204 | --local JSON = (loadfile "lib.JSON.lua")() 205 | pathFile = pathFile or "Addr.json" 206 | local file = io.open(pathFile, "w") 207 | local t = JSON:encode_pretty(Addr.LIST) 208 | file:write(t) 209 | file:close() 210 | end 211 | 212 | Addr.array = {} 213 | Addr.array.new = function(address,length,sizeCell,type,isRom) 214 | local self = {} 215 | self.address = address 216 | self.addressHex = Utils.decToHex(self.address) 217 | self.length = length 218 | self.list = {} 219 | local i 220 | for i=0,self.length-1 do 221 | self.list[i] = Addr.new(address + i * sizeCell,sizeCell,type,"",0,0,isRom) 222 | end 223 | 224 | self.get = function(num) 225 | return self.list[num].get() 226 | end 227 | 228 | self.set = function(num,val) 229 | self.list[num].set(val) 230 | end 231 | 232 | self.toString = function() 233 | local str = "[" 234 | local i 235 | for i = 0,self.length-1 do 236 | str = str .. self.get(i) 237 | if(i ~= self.length-1) then 238 | str = str .. "," 239 | end 240 | end 241 | return str .. "]" 242 | end 243 | 244 | self.print = function() 245 | console.log(self.toString()) 246 | end 247 | 248 | return self 249 | end 250 | 251 | Addr.array2D = {} 252 | Addr.array2D.new = function(address,length,length2,sizeCell,type,isRom) 253 | local self = {} 254 | self.address = address 255 | self.addressHex = Utils.decToHex(self.address) 256 | self.length = length 257 | self.list = {} 258 | 259 | self.get = function(num,num2) 260 | return self.list[num].list[num2].get() 261 | end 262 | 263 | self.set = function(num,num2,val) 264 | self.list[num].list[num2].set(val) 265 | end 266 | 267 | self.toString = function() 268 | local str = "[" 269 | local i 270 | for i = 0,self.length-1 do 271 | str = str .. self.list[i].toString() 272 | if(i ~= self.length-1) then 273 | str = str .. "," 274 | end 275 | end 276 | return str .. "]" 277 | end 278 | 279 | self.print = function() 280 | console.log(self.toString()) 281 | end 282 | 283 | --init 284 | local sizeSubArray = length2 * sizeCell 285 | local i 286 | for i=0,self.length-1 do 287 | self.list[i] = Addr.array.new(address + i * sizeSubArray,length2,sizeCell,type,isRom) 288 | end 289 | 290 | 291 | return self 292 | end 293 | 294 | local function loadWch() 295 | local a = bizstring.split(Utils.readFile(WCH_PATH),"\n") 296 | 297 | local i = 3 --skip Domain RDRAM \n SystemID N64 298 | while a[i] do 299 | Addr.create(a[i]) 300 | i = i + 1 301 | end 302 | 303 | end 304 | 305 | loadWch() 306 | memory.usememorydomain("ROM") --for ROM addr, doesnt impact RAM addr as they use mainmemory 307 | 308 | return Addr; -------------------------------------------------------------------------------- /MM/Cst.lua: -------------------------------------------------------------------------------- 1 | 2 | local invertTable = function(list) 3 | local res = {} 4 | for i,j in pairs(list) do res[j] = i end 5 | return res 6 | end 7 | 8 | CST = CST or {} 9 | 10 | CST.ITEM = {--http://wiki.cloudmodding.com/mm/Notes/Deathbasket 11 | Ocarina=0x0, 12 | Bow=0x1, 13 | FireArrow=0x2, 14 | IceArrow=0x3, 15 | LightArrow=0x4, 16 | FairyOcarina=0x5, 17 | Bomb=0x6, 18 | Bombchu=0x7, 19 | DekuStick=0x8, 20 | DekuNut=0x9, 21 | MagicBean=0x0a, 22 | Slingshot=0x0b, 23 | PowderKeg=0x0c, 24 | Pictobox=0x0d, 25 | LensOfTruth=0x0e, 26 | Hookshot=0x0f, 27 | GreatFairySword=0x10, 28 | Hookshot_OoT=0x11, 29 | EmptyBottle=0x12, 30 | RedPotion=0x13, 31 | GreenPotion=0x14, 32 | BluePotion=0x15, 33 | Fairy=0x16, 34 | DekuPrincess=0x17, 35 | Milk=0x18, 36 | Milk_half=0x19, 37 | Fish=0x1a, 38 | Bugs=0x1b, 39 | BlueFire=0x1c, 40 | Poe=0x1d, 41 | BigPoe=0x1e, 42 | Water=0x1f, 43 | HotWater=0x20, 44 | ZoraEgg=0x21, 45 | GoldDust=0x22, 46 | Mushroom=0x23, 47 | Seahorse=0x24, 48 | ChateauRomani=0x25, 49 | Eel=0x26, 50 | EmptyBottle2=0x27, 51 | MoonsTear=0x28, 52 | TownDeed=0x29, 53 | SwampDeed=0x2a, 54 | MountainDeed=0x2b, 55 | OceanDeed=0x2c, 56 | RoomKey=0x2d, 57 | MamasLetter=0x2e, 58 | LetterToKafei=0x2f, 59 | Pendant=0x30, 60 | Map=0x31, 61 | DekuMask=0x32, 62 | GoronMask=0x33, 63 | ZoraMask=0x34, 64 | FierceDeitysMask=0x35, 65 | MaskOfTruth=0x36, 66 | KafeisMask=0x37, 67 | AllnightMask=0x38, 68 | BunnyHood=0x39, 69 | KeatonMask=0x3a, 70 | GarosMask=0x3b, 71 | RomaniMask=0x3c, 72 | CircusLeadersMask=0x3d, 73 | PostmansHat=0x3e, 74 | CouplesMask=0x3f, 75 | GreatFairysMask=0x40, 76 | GibdosMask=0x41, 77 | DongerosMask=0x42, 78 | KamarosMask=0x43, 79 | Captainshat=0x44, 80 | StoneMask=0x45, 81 | BremenMask=0x46, 82 | BlastMask=0x47, 83 | Maskofscents=0x48, 84 | GiantsMask=0x49, 85 | BowFireArrow=0x4a, 86 | BowIceArrow=0x4b, 87 | BowLightArrow=0x4c, 88 | KokiriSword=0x4d, 89 | RazorSword=0x4e, 90 | GildedSword=0x4f, 91 | HelixSword=0x50, 92 | HerosShield=0x51, 93 | MirrorShield=0x52, 94 | Quiver_30=0x53, 95 | Quiver_40=0x54, 96 | Quiver_50=0x55, 97 | BombBag=0x56, 98 | BombBag_30=0x57, 99 | BombBag_40=0x58, 100 | Wallet1=0x59, 101 | Wallet2=0x5a, 102 | Wallet3=0x5b, 103 | FishingRod=0x5c, 104 | OdolwasRemains=0x5d, 105 | GohtsRemains=0x5e, 106 | GyorgsRemains=0x5f, 107 | TwinmoldsRemains=0x60, 108 | SonataOfAwakening=0x61, 109 | GoronLullaby=0x62, 110 | NewWaveBossanova=0x63, 111 | ElegyOfEmptiness=0x64, 112 | OathToOrder=0x65, 113 | SariasSong=0x66, 114 | SongOfTime=0x67, 115 | SongoOfHealing=0x68, 116 | EponasSong=0x69, 117 | SongOfSoaring=0x6a, 118 | SongOfStorms=0x6b, 119 | SunsSong=0x6c, 120 | BombersNotebook=0x6d, 121 | HeartPiece=0x7b, 122 | Heart=0x83, 123 | GreenRupee=0x84, 124 | BlueRupee=0x85, 125 | RedRupee=0x87, 126 | PurpleRupee=0x88, 127 | SilverRupee=0x89, 128 | DekuNutS=0x8e, 129 | Arrows_10=0x93, 130 | Arrows_20=0x94, 131 | 132 | 133 | } 134 | 135 | CST.ITEM_TO_NAME = invertTable(CST.ITEM) 136 | 137 | CST.ACTOR_CATEGORY = { 138 | Switch=0, 139 | Prop1=1, 140 | Player=2, 141 | Bomb=3, 142 | Npc=4, 143 | Enemy=5, 144 | Prop=6, 145 | ItemAction=7, 146 | Misc=8, 147 | Boss=9, 148 | Door=10, 149 | Chest=11, 150 | } 151 | 152 | CST.ACTOR_CATEGORY_TO_NAME = invertTable(CST.ACTOR_CATEGORY) 153 | 154 | CST.ACTOR_TYPE = { --http://wiki.cloudmodding.com/mm/Actor_List_(U) 155 | Link=0x0, 156 | Unknown=0x1, 157 | Unknown=0x2, 158 | Unknown=0x3, 159 | Orangeflame=0x4, 160 | Basicdoor=0x5, 161 | Treasurechest=0x6, 162 | Unknown=0x7, 163 | Unknown=0x8, 164 | Bomb=0x9, 165 | Wallmaster=0x000A, 166 | Dodongo=0x000B, 167 | FireKeese=0x000C, 168 | Unknown=0x000D, 169 | collectables=0x000E, 170 | Smallflamefadesaway=0x000F, 171 | Tatl=0x10, 172 | Cucco=0x11, 173 | Redtektite=0x12, 174 | None=0x13, 175 | Peahat=0x14, 176 | Unknown=0x15, 177 | Bug=0x16, 178 | Fish=0x17, 179 | Blacksquaredisappears=0x18, 180 | Dinofols=0x19, 181 | Woodenpostwithredclth=0x001A, 182 | Bluelockonspot=0x001B, 183 | Unknown=0x001C, 184 | BubbleenemyUNUSED=0x001D, 185 | Studdedliftingdoor=0x001E, 186 | None=0x001F, 187 | Boomerangfin=0x20, 188 | Linksongstatue=0x21, 189 | Unknown=0x22, 190 | None=0x23, 191 | Skulltula=0x24, 192 | None=0x25, 193 | None=0x26, 194 | Unknown=0x27, 195 | Waternoises=0x28, 196 | None=0x29, 197 | Unknown=0x002A, 198 | Unknown=0x002B, 199 | None=0x002C, 200 | DeathArmos=0x002D, 201 | None=0x002E, 202 | Bombflowerbomb=0x002F, 203 | None=0x30, 204 | None=0x31, 205 | Armos=0x32, 206 | DekuBaba=0x33, 207 | Unknown=0x34, 208 | Unknown=0x35, 209 | Unknown=0x36, 210 | None=0x37, 211 | Bluewarp=0x38, 212 | Goldentorchstand=0x39, 213 | Heartcontainer=0x003A, 214 | Madscrub=0x003B, 215 | RedBubble=0x003C, 216 | Unknown=0x003D, 217 | Bluebubble=0x003E, 218 | Fountainwater=0x003F, 219 | None=0x40, 220 | Tree=0x41, 221 | None=0x42, 222 | BatmonsterGomess=0x43, 223 | Unknown=0x44, 224 | None=0x45, 225 | None=0x46, 226 | Beamos=0x47, 227 | Crash=0x48, 228 | Floatingmorisparklies=0x49, 229 | Floormaster=0x004A, 230 | None=0x004B, 231 | ReDead=0x004C, 232 | Lrgjigglystoneelevator=0x004D, 233 | None=0x004E, 234 | Unknown=0x004F, 235 | Skullwalltula=0x50, 236 | Unknown=0x51, 237 | None=0x52, 238 | None=0x53, 239 | Epona=0x54, 240 | Crash=0x55, 241 | None=0x56, 242 | None=0x57, 243 | None=0x58, 244 | None=0x59, 245 | None=0x005A, 246 | Unknown=0x005B, 247 | Unknown=0x005C, 248 | None=0x005D, 249 | None=0x005E, 250 | MajorasMaskballoon=0x005F, 251 | Unknown=0x60, 252 | Twistingcorridor=0x61, 253 | Unknown=0x62, 254 | None=0x63, 255 | ShellBlade=0x64, 256 | Frogthingminiboss=0x65, 257 | DekuBabawithered=0x66, 258 | OrangeGormanBrowalking=0x67, 259 | None=0x68, 260 | RutoZora=0x69, 261 | Bombchuactive=0x006A, 262 | Unknown=0x006B, 263 | LikeLike=0x006C, 264 | None=0x006D, 265 | None=0x006E, 266 | None=0x006F, 267 | None=0x70, 268 | None=0x71, 269 | None=0x72, 270 | Unknown=0x73, 271 | None=0x74, 272 | None=0x75, 273 | None=0x76, 274 | None=0x77, 275 | None=0x78, 276 | None=0x79, 277 | Unknown=0x007A, 278 | Unknown=0x007B, 279 | Unknown=0x007C, 280 | Unknown=0x007D, 281 | Unknown=0x007E, 282 | Unknown=0x007F, 283 | Crash=0x80, 284 | Unknown=0x81, 285 | Unknown=0x82, 286 | None=0x83, 287 | None=0x84, 288 | None=0x85, 289 | None=0x86, 290 | None=0x87, 291 | None=0x88, 292 | Unknown=0x89, 293 | Dekupalacescrub=0x008A, 294 | Tatlinfospotauto=0x008B, 295 | Unknown=0x008C, 296 | Crash=0x008D, 297 | Littlesparkle=0x008E, 298 | Freezard=0x008F, 299 | Unknown=0x90, 300 | Unknown=0x91, 301 | Boulder=0x92, 302 | Unknown=0x93, 303 | None=0x94, 304 | Collapsingstoneplatform=0x95, 305 | Bluehookshotstatue=0x96, 306 | Unknown=0x97, 307 | None=0x98, 308 | RollingboulderBETA=0x99, 309 | None=0x009A, 310 | None=0x009B, 311 | Maninredandblue=0x009C, 312 | Unknown=0x009D, 313 | Lockonablespot=0x009E, 314 | Unknown=0x009F, 315 | Unknown=0x00A0, 316 | Linkcastsweirdshadow=0x00A1, 317 | Explosion=0x00A2, 318 | None=0x00A3, 319 | Unknown=0x00A4, 320 | Magicbeanseller=0x00A5, 321 | Cuccoguy=0x00A6, 322 | Swamptourboat=0x00A7, 323 | Squaresignpost=0x00A8, 324 | None=0x00A9, 325 | Angrycucco=0x00AA, 326 | None=0x00AB, 327 | None=0x00AC, 328 | None=0x00AD, 329 | Madprofessor=0x00AE, 330 | Owl=0x00AF, 331 | Unknown=0x00B0, 332 | Flower=0x00B1, 333 | Sunemblem=0x00B2, 334 | Unknown=0x00B3, 335 | None=0x00B4, 336 | Unknown=0x00B5, 337 | None=0x00B6, 338 | None=0x00B7, 339 | Whirlpooleffect=0x00B8, 340 | Unknown=0x00B9, 341 | None=0x00BA, 342 | None=0x00BB, 343 | Unknown=0x00BC, 344 | Kakarikoroofguy=0x00BD, 345 | None=0x00BE, 346 | Majoramaskedchild=0x00BF, 347 | None=0x00C0, 348 | None=0x00C1, 349 | None=0x00C2, 350 | None=0x00C3, 351 | Rainandlightning=0x00C4, 352 | Pressstartlogo=0x00C5, 353 | Unknown=0x00C6, 354 | Lrgfloatingstonepltfm=0x00C7, 355 | None=0x00C8, 356 | None=0x00C9, 357 | Tatlturnsbluespot=0x00CA, 358 | Unknown=0x00CB, 359 | Sunssongeffect=0x00CC, 360 | None=0x00CD, 361 | Treasurechest=0x00CE, 362 | None=0x00CF, 363 | Unknown=0x00D0, 364 | None=0x00D1, 365 | None=0x00D2, 366 | Onscreentimer=0x00D3, 367 | MaskofTruthSkulltula=0x00D4, 368 | None=0x00D5, 369 | Purplesongeffect=0x00D6, 370 | Songofstormseffect=0x00D7, 371 | Unknown=0x00D8, 372 | Unknown=0x00D9, 373 | FiredDekuNut=0x00DA, 374 | None=0x00DB, 375 | None=0x00DC, 376 | None=0x00DD, 377 | None=0x00DE, 378 | Eponassongeffect=0x00DF, 379 | Sariassongeffect=0x00E0, 380 | None=0x00E1, 381 | Dog=0x00E2, 382 | GoldSkulltulaspirit=0x00E3, 383 | Beehive=0x00E4, 384 | Woodencrate=0x00E5, 385 | None=0x00E6, 386 | BlueNavilockonspot=0x00E7, 387 | Stackofrupees=0x00E8, 388 | HoneyDarling=0x00E9, 389 | None=0x00EA, 390 | None=0x00EB, 391 | Wolfos=0x00EC, 392 | Stalchild=0x00ED, 393 | None=0x00EE, 394 | Gossipstone=0x00EF, 395 | Waternoises=0x00F0, 396 | Guay=0x00F1, 397 | None=0x00F2, 398 | Cow=0x00F3, 399 | None=0x00F4, 400 | None=0x00F5, 401 | Bluesongeffect=0x00F6, 402 | None=0x00F7, 403 | WalkingZora=0x00F8, 404 | Unknown=0x00F9, 405 | WalkingGerudo=0x00FA, 406 | None=0x00FB, 407 | Invisibleroundthing=0x00FC, 408 | Crash=0x00FD, 409 | Postmansletter=0x00FE, 410 | None=0x00FF, 411 | Godowndungnstairdemo=0x100, 412 | None=0x101, 413 | Unknown=0x102, 414 | Unknown=0x103, 415 | None=0x104, 416 | Armosstatue=0x105, 417 | GreenjigglythingBETA=0x106, 418 | None=0x107, 419 | None=0x108, 420 | Dragonfly=0x109, 421 | None=0x010A, 422 | Unknown=0x010B, 423 | Unknown=0x010C, 424 | Randombluepolygons=0x010D, 425 | None=0x010E, 426 | None=0x010F, 427 | Proximityfirewall=0x110, 428 | Unknown=0x111, 429 | Unknown=0x112, 430 | Garo=0x113, 431 | Collapsingstonebridge=0x114, 432 | Windowsskeletons=0x115, 433 | Tatlgreenspot=0x116, 434 | MamamuYanDoglady=0x117, 435 | Lockablebluespot=0x118, 436 | Lockablebluespot=0x119, 437 | Lockablebluespot=0x011A, 438 | Unknown=0x011B, 439 | Unknown=0x011C, 440 | Biguglychap=0x011D, 441 | None=0x011E, 442 | Stalagtite=0x011F, 443 | Unknown=0x120, 444 | Lockablebluespot=0x121, 445 | Unknown=0x122, 446 | Unknown=0x123, 447 | Unknown=0x124, 448 | Unknown=0x125, 449 | None=0x126, 450 | None=0x127, 451 | Unknown=0x128, 452 | Odolwa=0x129, 453 | Twinmold=0x012A, 454 | Gyorg=0x012B, 455 | Eyeballminiboss=0x012C, 456 | BioDekuBaba=0x012D, 457 | Unknown=0x012E, 458 | Unknown=0x012F, 459 | Unknown=0x130, 460 | None=0x131, 461 | Lockablebluespot=0x132, 462 | None=0x133, 463 | None=0x134, 464 | None=0x135, 465 | None=0x136, 466 | None=0x137, 467 | Unknown=0x138, 468 | None=0x139, 469 | Maneatingplant=0x013A, 470 | Unknown=0x013B, 471 | Stoneplatform=0x013C, 472 | Dekutemplewoodenflwr=0x013D, 473 | Pottedgrass=0x013E, 474 | Unknown=0x013F, 475 | Odolwasremains=0x140, 476 | Unknown=0x141, 477 | Unknown=0x142, 478 | Icearrowiceblock=0x143, 479 | Unknown=0x144, 480 | Unknown=0x145, 481 | Tatlinfospotauto=0x146, 482 | Yellowfrog=0x147, 483 | Unknown=0x148, 484 | Crash=0x149, 485 | BlueChuchu=0x014A, 486 | Desbrekoskeletonfish=0x014B, 487 | PartofClocktower=0x014C, 488 | Himneywithsmoke2Dc=0x014D, 489 | Largebell=0x014E, 490 | Unknown=0x014F, 491 | None=0x150, 492 | Woodenwall2d=0x151, 493 | PrincessZelda=0x152, 494 | BrokenGreatFairy=0x153, 495 | Unknown=0x154, 496 | NejironExplosiveround=0x155, 497 | Kaitenspikeylog=0x156, 498 | Smoke=0x157, 499 | Unknown=0x158, 500 | Unknown=0x159, 501 | Unknown=0x015A, 502 | Badbat=0x015B, 503 | Unknown=0x015C, 504 | Unknown=0x015D, 505 | Stoneplatform=0x015E, 506 | Fancyexplosion=0x015F, 507 | Camerafocusesonyou=0x160, 508 | Unknown=0x161, 509 | Flamecircle=0x162, 510 | Unknown=0x163, 511 | BlackBoe=0x164, 512 | Unknown=0x165, 513 | None=0x166, 514 | Unknown=0x167, 515 | Speakingspotboattour=0x168, 516 | Unknown=0x169, 517 | DekuKing=0x016A, 518 | None=0x016B, 519 | Wroughtironfence=0x016C, 520 | LargeboulderwspikeBETA=0x016D, 521 | Enemylockonspot=0x016E, 522 | RealBombchu=0x016F, 523 | Drippingwater=0x170, 524 | Grassbits=0x171, 525 | Proximityfirewall=0x172, 526 | None=0x173, 527 | Unknown=0x174, 528 | Floatingbeamoflight=0x175, 529 | StandingTingle=0x176, 530 | Banker=0x177, 531 | Mountedtelescope=0x178, 532 | Movingiceplatform=0x179, 533 | Dekuguard=0x017A, 534 | Bugs3=0x017B, 535 | Unknown=0x017C, 536 | Postman=0x017D, 537 | Dekudoor=0x017E, 538 | Unknown=0x017F, 539 | Unknown=0x180, 540 | Skeletonfish=0x181, 541 | Makesyouwalkaway=0x182, 542 | Dekuflower=0x183, 543 | Lasereyedmonster=0x184, 544 | Spikedbombtrap=0x185, 545 | Unknown=0x186, 546 | Unknown=0x187, 547 | Kotake=0x188, 548 | None=0x189, 549 | None=0x018A, 550 | Unknown=0x018B, 551 | Nightdaychange=0x018C, 552 | Unknown=0x018D, 553 | Flyingrubble=0x018E, 554 | Unknown=0x018F, 555 | Bigsquareofgrass=0x190, 556 | Skullkid=0x191, 557 | Unknown=0x192, 558 | Poisenwater=0x193, 559 | Unknown=0x194, 560 | Unknown=0x195, 561 | Tatlstayshere=0x196, 562 | Unknown=0x197, 563 | Mtnvillagesnowoverlay=0x198, 564 | Unknown=0x199, 565 | Unknown=0x019A, 566 | Unknown=0x019B, 567 | Clock=0x019C, 568 | None=0x019D, 569 | Unknown=0x019E, 570 | Stoneblock=0x019F, 571 | Dekupalaceentrncguard=0x01A0, 572 | Crackedstonewall=0x01A1, 573 | Clocktowerdoors=0x01A2, 574 | Patternedstone=0x01A3, 575 | Romani=0x01A4, 576 | Unknown=0x01A5, 577 | Unknown=0x01A6, 578 | Unknown=0x01A7, 579 | Unknown=0x01A8, 580 | Icearrowiceplatform=0x01A9, 581 | Triforcestone=0x01AA, 582 | Unknown=0x01AB, 583 | Roundwoodendoor=0x01AC, 584 | Unknown=0x01AD, 585 | HoneyDarlingsplatform=0x01AE, 586 | Pouredoutwater=0x01AF, 587 | Greatfairypart=0x01B0, 588 | DaiFairypartinbubble=0x01B1, 589 | None=0x01B2, 590 | Unknown=0x01B3, 591 | Unknown=0x01B4, 592 | Unknown=0x01B5, 593 | Kaitenlargewoodencog=0x01B6, 594 | Witchonbroom=0x01B7, 595 | Woodenwall=0x01B8, 596 | Lilypad=0x01B9, 597 | Snapper=0x01BA, 598 | Unknown=0x01BB, 599 | Circleofwater=0x01BC, 600 | Dekusalesman=0x01BD, 601 | Tunnelblockingfoliage=0x01BE, 602 | None=0x01BF, 603 | Invisibleplatform=0x01C0, 604 | Mazegamegirl=0x01C1, 605 | Fisherman=0x01C2, 606 | Itemshopfillinguy=0x01C3, 607 | Itemshopguy=0x01C4, 608 | Tinglesfather=0x01C5, 609 | None=0x01C6, 610 | Blockingguard=0x01C7, 611 | Fieldstalagtite=0x01C8, 612 | Dekugamedeku=0x01C9, 613 | Dampe=0x01CA, 614 | None=0x01CB, 615 | Unknown=0x01CC, 616 | Dekugameplatform=0x01CD, 617 | Songofsoaringeffect=0x01CE, 618 | Sunblock=0x01CF, 619 | Unknown=0x01D0, 620 | Dexihand=0x01D1, 621 | Largerupee=0x01D2, 622 | Unknown=0x01D3, 623 | Snowytree=0x01D4, 624 | Unknown=0x01D5, 625 | Dutabuttons2=0x01D6, 626 | Unknown=0x01D7, 627 | Unknown=0x01D8, 628 | Pieceofheart=0x01D9, 629 | Gibdo=0x01DA, 630 | Unknown=0x01DB, 631 | Largesnowball=0x01DC, 632 | Ghot=0x01DD, 633 | Ghostarenaperson=0x01DE, 634 | Invisiblespeakingspot=0x01DF, 635 | Dancinggoronwall=0x01E00, 636 | Unknown=0x01E01, 637 | Stonetowerobject=0x01E02, 638 | Gravestone=0x01E03, 639 | Stonecolumn=0x01E04, 640 | None=0x01E05, 641 | Eeno=0x01E06, 642 | Unknown=0x01E07, 643 | Purplepoe=0x01E08, 644 | Hiploop=0x01E09, 645 | Unknown=0x01EA, 646 | Unknown=0x01EB, 647 | Unknown=0x01EC, 648 | Hotaircolumn=0x01ED, 649 | Racingdog=0x01EE, 650 | Dojomaster=0x01EF, 651 | Stonedoor=0x01F0, 652 | Laboratoryfish=0x01F1, 653 | None=0x01F2, 654 | Poe=0x01F3, 655 | Spidersweb=0x01F4, 656 | Zoraegg=0x01F5, 657 | Blacksmith=0x01F6, 658 | Shadow=0x01F7, 659 | Dojopracticelog=0x01F8, 660 | Smallsnowball=0x01F9, 661 | Unknown=0x01FA, 662 | Darmanisgravestone=0x01FB, 663 | DekuPrincess=0x01FC, 664 | Icywind=0x01FD, 665 | Hotspringwater=0x01FE, 666 | Blacksmithsassistant=0x01FF, 667 | Unknown=0x200, 668 | Goronbaby=0x201, 669 | Unknown=0x202, 670 | None=0x203, 671 | Giantbee=0x204, 672 | Unknown=0x205, 673 | DeepPython=0x206, 674 | Unknown=0x207, 675 | BigPoe=0x208, 676 | Clothwithjapanese=0x209, 677 | Littlecowmodelshead=0x020A, 678 | Secretspotrevealer=0x020B, 679 | Unknown=0x020C, 680 | Unknown=0x020D, 681 | Crackedhousewall=0x020E, 682 | Metalgrate=0x020F, 683 | Skullkidpainting=0x210, 684 | Chestofdrawers=0x211, 685 | Graveguardingstalchldrn=0x212, 686 | FrozenGoronelder=0x213, 687 | Unknown=0x214, 688 | Unknown=0x215, 689 | Leever=0x216, 690 | Chair=0x217, 691 | Unknown=0x218, 692 | Largeornatemirror=0x219, 693 | Rotatingstonetowerrm=0x021A, 694 | Largeseesaw=0x021B, 695 | Fallingwater=0x021C, 696 | Aggresivepirate=0x021D, 697 | Pirateguard=0x021E, 698 | Unknown=0x021F, 699 | Cremia=0x220, 700 | Lowerpartoftownstand=0x221, 701 | GBTempleelevator=0x222, 702 | Owlstatue=0x223, 703 | Unknown=0x224, 704 | KaitenDekuflwrplatfms=0x225, 705 | Unknown=0x226, 706 | Twinmoldbattleenviro=0x227, 707 | Zora=0x228, 708 | Branchedtree3=0x229, 709 | Metalmeshelevator=0x022A, 710 | Unknown=0x022B, 711 | Piratespatrolboat=0x022C, 712 | Woodenbarrel=0x022D, 713 | Invisibleobject=0x022E, 714 | Roundtarget=0x022F, 715 | Unknown=0x230, 716 | Zoraguitarist=0x231, 717 | Unknown=0x232, 718 | Unknown=0x233, 719 | Indigogosmanager=0x234, 720 | CircleofGibdos=0x235, 721 | Oldladywithbag=0x236, 722 | Unknown=0x237, 723 | Zoradrummer=0x238, 724 | Lotteryrotundah=0x239, 725 | Frogmaskgoron=0x023A, 726 | Unknown=0x023B, 727 | Palmtree=0x023C, 728 | Unknown=0x023D, 729 | Unknown=0x023E, 730 | Unknown=0x023F, 731 | Unknown=0x240, 732 | Zorapianist=0x241, 733 | Goron=0x242, 734 | Unknown=0x243, 735 | Unknown=0x244, 736 | Largestoneblock=0x245, 737 | Floorswitch=0x246, 738 | Camerachanges=0x247, 739 | Musicboxman=0x248, 740 | Greensongeffect=0x249, 741 | Navibluespot=0x024A, 742 | Whitesongeffect=0x024B, 743 | Unknown=0x024C, 744 | Unknown=0x024D, 745 | Pinksongeffect=0x024E, 746 | Unknown=0x024F, 747 | Semigibdofather=0x250, 748 | Unknown=0x251, 749 | Zorasinger=0x252, 750 | Unknown=0x253, 751 | Mysteriouspatternedblck=0x254, 752 | Crackedwall=0x255, 753 | Unknown=0x256, 754 | Largemetaldoorthing=0x257, 755 | Crackedwall=0x258, 756 | Songlearninggravestone=0x259, 757 | Largerupee=0x025A, 758 | Ikanaghostcaveoverlay=0x025B, 759 | Ikanawaterwheel=0x025C, 760 | Unknown=0x025D, 761 | Hookshottree=0x025E, 762 | SleepingDekuscrub=0x025F, 763 | SwimmingZora=0x260, 764 | Checkablespot=0x261, 765 | Unknown=0x262, 766 | Unknown=0x263, 767 | Sakonsbag=0x264, 768 | Unknown=0x265, 769 | CirclingGuay=0x266, 770 | Seagull=0x267, 771 | Unknown=0x268, 772 | Yellowlockonspot=0x269, 773 | Manwithpickaxe=0x026A, 774 | Unknown=0x026B, 775 | Unknown=0x026C, 776 | Unknown=0x026D, 777 | Guard=0x026E, 778 | Mayorinchairdebating=0x026F, 779 | Bellonstickwithsign=0x270, 780 | Unknown=0x271, 781 | Unknown=0x272, 782 | Roundstoneplatform=0x273, 783 | Dekusalesman=0x274, 784 | Unknown=0x275, 785 | Unknown=0x276, 786 | Unknown=0x277, 787 | Unknown=0x278, 788 | Unknown=0x279, 789 | Unknown=0x027A, 790 | Dancer=0x027B, 791 | Unknown=0x027C, 792 | Tatlbluespot=0x027D, 793 | Bombersleader=0x027E, 794 | Unknown=0x027F, 795 | Bomber=0x280, 796 | Passwordbomber=0x281, 797 | Notsolidmajoraballoon=0x282, 798 | Moonstear=0x283, 799 | Unknown=0x284, 800 | Unknown=0x285, 801 | Unknown=0x286, 802 | GormanBrossheds=0x287, 803 | Barnroofoverlay=0x288, 804 | Dekubutlersson=0x289, 805 | Largemetalportcullis=0x028A, 806 | Romanimilkjar=0x028B, 807 | Unknown=0x028C, 808 | Crackedwall=0x028D, 809 | Hotcheckeredfloor=0x028E, 810 | Giantstalfos=0x028F, 811 | Mayorsreceptionist=0x290, 812 | Takkuri=0x291, 813 | Jumpinggamefisherman=0x292, 814 | Goldentorchstand=0x293, 815 | Lightcastingwindow=0x294, 816 | Unknown=0x295, 817 | Unknown=0x296, 818 | Hookshotpillar=0x297, 819 | Crackedwall=0x298, 820 | Anjuinweddingdress=0x299, 821 | Unknown=0x029A, 822 | Stoneplatform=0x029B, 823 | Unknown=0x029C, 824 | None=0x029D, 825 | Tatlbluespot=0x029E, 826 | Fatinnwoman=0x029F, 827 | Grandmother=0x02A0, 828 | Unknown=0x02A1, 829 | Mayorchair=0x02A2, 830 | Tingle=0x02A3, 831 | Confettispawner=0x02A4, 832 | Sittingstalchild=0x02A5, 833 | Holeinthewallcheck=0x02A6, 834 | Flyingbird=0x02A7, 835 | Ceremonialguard=0x02A8, 836 | Carpenterboss=0x02A9, 837 | Stonemaskguard=0x02AA, 838 | Maninblueandred=0x02AB, 839 | Unknown=0x02AC, 840 | Lockablebluespot=0x02AD, 841 | Unknown=0x02AE, 842 | Invisiblerupee=0x02AF, 843 | Endgamestuff=0x02B0, 844 | Bombshopman=0x02B1, 845 | } 846 | 847 | CST.ACTOR_TYPE_TO_NAME = invertTable(CST.ACTOR_TYPE) 848 | 849 | CST.MAP_TO_EXIT = { 850 | --ex DekuTree=0x0000, 851 | 852 | } 853 | 854 | CST.EXIT_TO_MAP = invertTable(CST.MAP_TO_EXIT) 855 | 856 | 857 | return CST; -------------------------------------------------------------------------------- /OOT/Cst.lua: -------------------------------------------------------------------------------- 1 | 2 | local invertTable = function(list) 3 | local res = {} 4 | for i,j in pairs(list) do res[j] = i end 5 | return res 6 | end 7 | 8 | CST = CST or {} 9 | 10 | CST.ITEM = { 11 | DekuStick=0, 12 | DekuNut=1, 13 | Bomb=2, 14 | FairyBow=3, 15 | FireArrow=4, 16 | DinsFire=5, 17 | FairySlingshot=6, 18 | FairyOcarina=7, 19 | OcarinaOfTime=8, 20 | Bombchu=9, 21 | Hookshot=10, 22 | Longshot=11, 23 | IceArrow=12, 24 | FaroresWind=13, 25 | Boomerang=14, 26 | LensofTruth=15, 27 | MagicBeans=16, 28 | MegatonHammer=17, 29 | LightArrow=18, 30 | NayrusLove=19, 31 | EmptyBottle=20, 32 | RedPotion=21, 33 | GreenPotion=22, 34 | BluePotion=23, 35 | BottledFairy=24, 36 | Fish=25, 37 | LonLonMilk=26, 38 | Letter=27, 39 | BlueFire=28, 40 | Bug=29, 41 | BigPoe=30, 42 | LonLonMilkHalf=31, 43 | Poe=32, 44 | WeirdEgg=33, 45 | Chicken=34, 46 | ZeldasLetter=35, 47 | KeatonMask=36, 48 | SkullMask=37, 49 | SpookyMask=38, 50 | BunnyHood=39, 51 | GoronMask=40, 52 | ZoraMask=41, 53 | GerudoMask=42, 54 | MaskOfTruth=43, 55 | PocketEgg=45, 56 | PocketCucco=46, 57 | Cojiro=47, 58 | OddMushroom=48, 59 | OddPotion=49, 60 | PoachersSaw=50, 61 | GoronsSwordBroken=51, 62 | Prescription=52, 63 | EyeBallFrog=53, 64 | EyeDrops=54, 65 | ClaimCheck=55, 66 | FairyBowFireArrow=56, 67 | FairyBowIceArrow=57, 68 | FairyBowLightArrow=58, 69 | --unequippable 70 | SOLDOUT=44, 71 | KokiriSword=59, 72 | MasterSword=60, 73 | BiggoronSword=61, 74 | GiantsKnife=61, 75 | DekuShield=62, 76 | HylianShield=63, 77 | MirrorShield=64, 78 | KokiriTunic=65, 79 | GoronTunic=66, 80 | ZoraTunic=67, 81 | KokiriBoots=68, 82 | IronBoots=69, 83 | HoverBoots=70, 84 | BulletBag30=71, 85 | BulletBag40=72, 86 | BulletBag50=73, 87 | Quiver30=74, 88 | Quiver40=75, 89 | Quiver50=76, 90 | BombBag20=77, 91 | BombBag30=78, 92 | BombBag40=79, 93 | GoronsBracelet=80, 94 | SilverGauntlets=81, 95 | GoldenGauntlets=82, 96 | SilverScale=83, 97 | GoldenScale=84, 98 | GiantsKnifeBroken=85, 99 | AdultsWallet=86, 100 | GiantsWallet=87, 101 | DekuSeeds=88, 102 | FishingRod=89, 103 | MinuetOfForest=90, 104 | BoleroOfFire=91, 105 | SerenadeOfWater=92, 106 | RequiemOfSpirit=93, 107 | NocturneOfShadow=94, 108 | PreludeOfLight=95, 109 | ZeldasLullaby=96, 110 | EponasSong=97, 111 | SariasSong=98, 112 | SunsSong=99, 113 | SongOfTime=100, 114 | SongOfStorms=101, 115 | ForestMedallion=102, 116 | FireMedallion=103, 117 | WaterMedallion=104, 118 | SpiritMedallion=105, 119 | ShadowMedallion=106, 120 | LightMedallion=107, 121 | KokiriEmerald=108, 122 | GoronsRuby=109, 123 | ZorasSapphire=110, 124 | StoneOfAgony=111, 125 | GerudosCard=112, 126 | GoldSkulltula=113, 127 | PieceOfHeart=114, 128 | BossKey=116, 129 | Compass=117, 130 | DungeonMap=118, 131 | BiggoronsSword=122, 132 | HauntedWasteland=137, 133 | GerudoFortress=138, 134 | GerudoValley=139, 135 | LakeHylia=140, 136 | LonLonRanch=141, 137 | Market=142, 138 | HyruleField=143, 139 | DeathMountain=144, 140 | KakarikoVillage=145, 141 | LostWoods=146, 142 | KokiriForest=147, 143 | ZorasDomain=148, 144 | EmptyButton=255, 145 | } 146 | 147 | CST.ITEM_TO_NAME = invertTable(CST.ITEM) 148 | 149 | CST.LINK_STATE = { 150 | DisableControl=0x00000001, 151 | SwipingBottle=0x00000002, 152 | ConnectHookshot=0x00000004, 153 | FPSItemInhand=0x00000008, 154 | ResetActionIcon=0x00000010, 155 | FreezeAllObjectAnimations=0x00000080, 156 | SwingSword=0x00000100, 157 | NaviWillRunAway=0x00000400, 158 | Carrying=0x00000800, 159 | LoadingSpinattack=0x00001000, 160 | CameraSideViewLedge=0x00002000, 161 | CameraOverheadView=0x00004000, 162 | ZTargetFocusing=0x00010000, 163 | ZTargetingFocusTarget=0x00018000, 164 | ZTargetingNoFocus=0x00028000, 165 | ExitingWaterWithZTarget=0x00040000, 166 | FirstPersonMode=0x00100000, 167 | Climbing=0x00200000, 168 | Delay=0x003C7080, 169 | Shielding=0x00400000, 170 | Epona=0x00800000, 171 | LockCamera=0x02000000, 172 | FocusCameraInDirectionInWater=0x08000000, 173 | Talking=0x20000000, 174 | OcarinaTradeItem=0x30000000, 175 | EnterGrotto=0x80000000, 176 | } 177 | 178 | CST.ACTOR_CATEGORY = { 179 | Switch=0, 180 | Prop1=1, 181 | Player=2, 182 | Bomb=3, 183 | Npc=4, 184 | Enemy=5, 185 | Prop=6, 186 | ItemAction=7, 187 | Misc=8, 188 | Boss=9, 189 | Door=10, 190 | Chest=11, 191 | } 192 | 193 | CST.ACTOR_CATEGORY_TO_NAME = invertTable(CST.ACTOR_CATEGORY) 194 | 195 | CST.ACTOR_TYPE = { 196 | Link=0x0000, 197 | Stalfos=0x0002, 198 | ShopItems=0x0004, 199 | DissipatingFlames=0x0007, 200 | DecorativeFlames=0x0008, 201 | StandardDoor=0x0009, 202 | Treasurechest=0x000A, 203 | GreatFairy=0x000B, 204 | ProximityActivatedFlameWall=0x000C, 205 | Poe=0x000D, 206 | Octorok=0x000E, 207 | Webs=0x000F, 208 | Bomb=0x0010, 209 | Wallmaster=0x0011, 210 | Dodongo=0x0012, 211 | Keese=0x0013, 212 | Epona=0x0014, 213 | Collectibles=0x0015, 214 | Arrows=0x0016, 215 | Fairies=0x0018, 216 | Cucco=0x0019, 217 | Tektite=0x001B, 218 | Leever=0x001C, 219 | PeahatLarva=0x001D, 220 | Butterfly=0x001E, 221 | Bugs=0x0020, 222 | Fish=0x0021, 223 | RoomChangingPlane=0x0023, 224 | ovl_En_Scene_ChangeBroken=0x0024, 225 | LizalfosDinolfos=0x0025, 226 | WoodenPostWithRedCloth=0x0026, 227 | KingDodongo=0x0027, 228 | Ghoma=0x0028, 229 | PrincessZeldaChildAtWindow=0x0029, 230 | CutsceneActors=0x002A, 231 | GhomaLarva=0x002B, 232 | AlphaCubeCopy=0x002C, 233 | Shabombouncingbubbleenemy=0x002D, 234 | LiftingDungeonDoor=0x002E, 235 | BabyDodongo=0x002F, 236 | ReturningBoomerang=0x0032, 237 | DarkLink=0x0033, 238 | BiriFloatingJellyfish=0x0034, 239 | ElectricTailparisans=0x0035, 240 | Skulltula=0x0037, 241 | TorchSlug=0x0038, 242 | Gameplay_keepitems=0x0039, 243 | Stinger=0x003A, 244 | Ambientsoundeffects=0x003B, 245 | Normalhorse=0x003C, 246 | Shopkeeper=0x003D, 247 | GreatDekuTreesjaw=0x003E, 248 | DodongosCavernMegaDodongo=0x003F, 249 | MegatonStatue=0x0040, 250 | HugestonespikeplatformFireTemple=0x0041, 251 | Ganondorfshorse=0x0042, 252 | StoneblocksFireTemple=0x0043, 253 | SpinningStoneflamethrowerFireTemple=0x0044, 254 | StationaryflamethrowerstatueFireTemple=0x0045, 255 | StoneplatformFireTemple=0x0046, 256 | StoneelevatorFireTemple=0x0047, 257 | Shiek=0x0048, 258 | Flamecircle=0x0049, 259 | DrawbridgeObjects=0x004A, 260 | Moblins=0x004B, 261 | BombflowerBomb=0x004C, 262 | AdultZeldaUsedinCutscenes=0x004D, 263 | RisingElevatorPlatformwithHookshotTarget=0x004E, 264 | Lockonbluespot=0x004F, 265 | DekuTreePuzzleelements=0x0050, 266 | RotatingspikecylinderDekuTree=0x0051, 267 | PhantomGanon=0x0052, 268 | Armosstatue=0x0054, 269 | DekuBabaSnapping=0x0055, 270 | DekuNutTossedbyLink=0x0056, 271 | SpinAttack=0x0057, 272 | RisingstoneplatformDodongosCavern=0x0058, 273 | BombableWall=0x0059, 274 | LordJabuJabu=0x005A, 275 | ZeldasHorse=0x005B, 276 | Stonestairs=0x005C, 277 | Warpportals=0x005D, 278 | Torch=0x005E, 279 | Heartcontainer=0x005F, 280 | MadScrub=0x0060, 281 | Largerotatingstonering=0x0061, 282 | Eyeplatformeyeswitches=0x0062, 283 | BariBigJellyfish=0x0063, 284 | WaterTemplePlatformsDragonHeadStatues=0x0064, 285 | LargesquareofwaterWaterTemple=0x0065, 286 | Hookshot=0x0066, 287 | PhantomGanonsHorse=0x0067, 288 | TwistedHallwayRoom=0x0068, 289 | Bubbleflyingskullenemy=0x0069, 290 | TempleofTimewindows=0x006A, 291 | Flyingfloortile=0x006B, 292 | Masterswordcinema=0x006C, 293 | WarpPad=0x006E, 294 | Metalgate=0x006F, 295 | DoorofTimeCollisionNomodel=0x0070, 296 | StoneStepsandPlatformsFireTemple=0x0071, 297 | BrownbirdAlpha=0x0072, 298 | Greenery=0x0077, 299 | LargenoisystoneAlpha=0x007C, 300 | StonecubeAlpha=0x007D, 301 | Slidingmetalspiketrap=0x0080, 302 | AlphaArrowTrap=0x0081, 303 | OrangepotAlpha=0x0082, 304 | Talon=0x0084, 305 | DampeGraveDiggingGame=0x0085, 306 | ForestTemplelargeroundplatform=0x0086, 307 | ForestTempleelevator=0x0087, 308 | ForestTemplerotatingwallsfromroombeforeboss=0x0088, 309 | ForestTemplefallingceiling=0x0089, 310 | Beamos=0x008A, 311 | Spiritualstones=0x008B, 312 | TempleofTimeObjects=0x008C, 313 | Largeflamewall=0x008D, 314 | Floormaster=0x008E, 315 | CastleCourtyardGuards=0x008F, 316 | Redead=0x0090, 317 | MegPoeSisterfromForestTemple=0x0091, 318 | GoldenGauntletsrock=0x0092, 319 | PoeSistersPuzzleActor=0x0093, 320 | FishBugsButterflies=0x0094, 321 | SkullwalltulaandGoldSkulltulas=0x0095, 322 | FlyingVolvagia=0x0096, 323 | EnvironmentalEffects=0x0097, 324 | Darunia=0x0098, 325 | FlareDancer=0x0099, 326 | YoungEpona=0x009A, 327 | Grotto=0x009B, 328 | GraveyardActors=0x009C, 329 | Gravestone=0x009D, 330 | FaroresWind=0x009E, 331 | DinsFire=0x009F, 332 | ChildPrincessRuto=0x00A1, 333 | HoleVolvagia=0x00A2, 334 | Fireball=0x00A3, 335 | DeadHand=0x00A4, 336 | DeadHandsHand=0x00A5, 337 | Rauru=0x00A6, 338 | Enemyspawner=0x00A7, 339 | SageDaruniaCutscene=0x00A8, 340 | Impa=0x00A9, 341 | TreasureChestLight=0x00AA, 342 | CoreFlareDancercreature=0x00AB, 343 | Volvagiaobjects=0x00AC, 344 | Volvagiarocks=0x00AD, 345 | ShadowTempleFakeWalls=0x00AE, 346 | PlatformsShadowTemple=0x00AF, 347 | ShadowTempleship=0x00B0, 348 | SpinningScytheTrap=0x00B1, 349 | Hyrulianguards=0x00B3, 350 | FallingRockSpawner=0x00B4, 351 | Flyingrubble=0x00B5, 352 | FlobberymuscleblockJabuJabusBelly=0x00B6, 353 | SunBlockSwitch=0x00B7, 354 | GerudoValleyObjects=0x00B8, 355 | StatueDaruniasRoom=0x00B9, 356 | Barinade=0x00BA, 357 | Giantskulljar=0x00BB, 358 | ShadowTempleobjects=0x00BC, 359 | Coffinlid=0x00BD, 360 | StatueandWallShadowTemple=0x00BE, 361 | Cratersmokecone=0x00BF, 362 | ShootingGalleryGame=0x00C0, 363 | ShootingGalleryman=0x00C1, 364 | Shopshelves=0x00C2, 365 | Nabooru=0x00C3, 366 | Morpha=0x00C4, 367 | ShellBlade=0x00C5, 368 | BigOcto=0x00C6, 369 | WitheredDekuBaba=0x00C7, 370 | LordJabuJabuobjects=0x00C8, 371 | SageSariaCutscene=0x00C9, 372 | GoronsCutscene=0x00CA, 373 | Ingo=0x00CB, 374 | KoumeandKotake=0x00CC, 375 | DodongoCavernEntranceRock=0x00CD, 376 | BombablewallsFireTemple=0x00CF, 377 | BombableWall2D=0x00D0, 378 | IcePlatformZorasFountain=0x00D1, 379 | AdultRuto=0x00D2, 380 | DekuTreeSprout=0x00D3, 381 | Waternoise=0x00D4, 382 | LakeHyliaObjects=0x00D5, 383 | Movableiceblock=0x00D6, 384 | BottomoftheWellwaterlevelchanger=0x00D7, 385 | AdultMalon=0x00D9, 386 | Bombchu=0x00DA, 387 | HorsebackMinigames=0x00DB, 388 | Twinrova=0x00DC, 389 | LikeLike=0x00DD, 390 | TentaclefrominsideLordJabuJabu=0x00DE, 391 | JabuJabuElectrifiedTentacle=0x00DF, 392 | AnubisBody=0x00E0, 393 | AnubisFireAttack=0x00E1, 394 | ForestTempleladder=0x00E2, 395 | ForestTempleobjects=0x00E3, 396 | ForestTempleWellWater=0x00E4, 397 | DeathMountaincloudring=0x00E5, 398 | SwitchesInsideLordJabuJabu=0x00E6, 399 | ChildMalon=0x00E7, 400 | GanondorfBoss=0x00E8, 401 | BongoBongoshand=0x00E9, 402 | SpikeenemyWaterTemple=0x00EC, 403 | FrogSongSpotandFrogs=0x00ED, 404 | DekuShield=0x00EE, 405 | RedIce=0x00EF, 406 | BlueFlame=0x00F0, 407 | OcarinaofTime=0x00F1, 408 | NayrusLove=0x00F4, 409 | SagesBallsofLightCutscene=0x00F5, 410 | Anubis=0x00F6, 411 | ShadowTempletruthspinner=0x00F7, 412 | HyruleCastleGate=0x00F8, 413 | GiantRollingBoulder=0x00F9, 414 | SlidingclimbablebrickwallSpiritTemple=0x00FA, 415 | RotatingCobramirror=0x00FC, 416 | ClimbablemetalgratingFireTemple=0x00FD, 417 | FishingpondmanandFish=0x00FE, 418 | MovableBlock=0x00FF, 419 | DeathMountaintrailgate=0x0100, 420 | Sparkliesgatheringtogether=0x0101, 421 | Windmillsails=0x0102, 422 | KakarikoVillagewellcrossbeam=0x0103, 423 | KakarikoVillagewellwater=0x0104, 424 | GoldenTorchStandPoeSisters=0x0105, 425 | FallingPlatformGanondorfFight=0x0106, 426 | Milkcrate=0x0107, 427 | LonLonRanchObstacleFence=0x0108, 428 | FireArrow=0x010A, 429 | IceArrow=0x010B, 430 | LightArrow=0x010C, 431 | Collectibleitems=0x010F, 432 | SmallLiftableCrate=0x0110, 433 | Breakablepot=0x0111, 434 | InvisibleCollectible=0x0112, 435 | IronKnuckle=0x0113, 436 | IronKnucklearmorpiecesSpiritTempleNaboorufight=0x0114, 437 | Skullkid=0x0115, 438 | Skullkidneedleattack=0x0116, 439 | SilverRupee=0x0117, 440 | VortexUsedwhenKoumeKotakekidnapNabooru=0x0118, 441 | BongoBongosShadow=0x0119, 442 | Dekusalesman=0x011A, 443 | Naviinformationspot=0x011B, 444 | StoneEyeFlamethrower=0x011C, 445 | FlyingPot=0x011D, 446 | Icespawnedbyredicefrozenactors=0x011E, 447 | ZorasRiverwaterfall=0x011F, 448 | ZorasDomainwaterfall=0x0120, 449 | Frezzard=0x0121, 450 | DampesGhost=0x0122, 451 | WindmillSetpieces=0x0123, 452 | Zoradivinggame=0x0124, 453 | SingleBushGrass=0x0125, 454 | Beanplantingspot=0x0126, 455 | Brownboulder=0x0127, 456 | Switches=0x012A, 457 | Hugestoneelevator=0x012B, 458 | Squarecollapsingplatform=0x012C, 459 | StoneHookshottarget=0x012D, 460 | MusicStaffOcarinaspot=0x012E, 461 | HorsebackArcherytargetarrowhitbox=0x012F, 462 | RollingBoulder=0x0130, 463 | SparklingRupee=0x0131, 464 | BossCarpenter=0x0132, 465 | Carpenters=0x0133, 466 | NonsolidroundcreaturesAlpha=0x0135, 467 | DarkLinksillusionroom=0x0136, 468 | ZeldasMagictoOpenGates=0x0137, 469 | WhiteclothedGerudo=0x0138, 470 | Blockplacementactor=0x0139, 471 | AlphaDynamicShadowforLink=0x013A, 472 | EnemyArwingAlpha=0x013B, 473 | KakarikoCuccolady=0x013C, 474 | Medigoron=0x013D, 475 | Beanseller=0x013E, 476 | Carpentersson=0x013F, 477 | IngosGatesLonLonRanch=0x0140, 478 | Squaresignpost=0x0141, 479 | HyruleCastleguard=0x0142, 480 | HoppingCucconotsolid=0x0143, 481 | AttackingCucconotsolid=0x0144, 482 | StoneblockingentrancetoBottomoftheWell=0x0145, 483 | Saria=0x0146, 484 | CheckablespotGreenNavi=0x0147, 485 | RainbowBridgetoGanonsCastle=0x0148, 486 | PotionShophag=0x0149, 487 | LakesideProfessor=0x014A, 488 | BombchuBowlingAlleylady=0x014B, 489 | BombchuBowlingAlleypit=0x014C, 490 | KaeporaGaebora=0x014D, 491 | Smallrock=0x014E, 492 | Graveflower=0x014F, 493 | SunEmblemTriggerSpiritTemple=0x0150, 494 | RockBushgroups=0x0151, 495 | Goroncutscenes=0x0152, 496 | Windmillman=0x0153, 497 | TreasureBoxShopChest=0x0155, 498 | LargestonefaceSpiritTemple=0x0156, 499 | ChainPlatformSpiritTemple=0x0157, 500 | LargeCircularMirrorSpirtTemple=0x0158, 501 | LightblockingrockSpiritTemple=0x0159, 502 | CircularmetalgratingblockadeSpiritTemple=0x015A, 503 | Bombablerockwall=0x015B, 504 | GiantthreesidedGoronstatue=0x015C, 505 | GanonsOrganandsurroundings=0x015E, 506 | Waterspout=0x015F, 507 | Whirlpooleffect=0x0160, 508 | RunningmanChildversion=0x0162, 509 | KokiriChildren=0x0163, 510 | KingZora=0x0164, 511 | ProximityRainWeatherEffects=0x0165, 512 | BongoBongosDrum=0x0166, 513 | Kakarikovillagerooftopman=0x0167, 514 | Unknown=0x0168, 515 | IronKnucklesRoomStuffSpiritTemple=0x0169, 516 | Magiccarpetman=0x016A, 517 | DisplaysGIobjectsLostWoodsSlingshotGame=0x016B, 518 | Graveyardboy=0x016C, 519 | Mido=0x016D, 520 | MarketNPCs=0x016E, 521 | GanonsCape=0x016F, 522 | Rainandlightning=0x0170, 523 | TitleScreenActor=0x0171, 524 | Metalgratedoor=0x0172, 525 | NaviInformationSpotTargetableGreen=0x0173, 526 | GanonsTowerCollapsingCutscenesetpieces=0x0174, 527 | BigPoe=0x0175, 528 | LavaparticlefountainDeathMountainpanorama=0x0176, 529 | MetalbarsGanonsCastle=0x0177, 530 | Hyruleguard=0x0178, 531 | AdultZelda=0x0179, 532 | Ganon=0x017A, 533 | PierretheScarecrow=0x017B, 534 | TreasureChestgameman=0x017C, 535 | PushBlockWithownsavepoint=0x017D, 536 | SunsSongeffect=0x017E, 537 | TheEndmessage=0x017F, 538 | GrottoTreasureChest=0x0181, 539 | CreditsRevelersfromLonLon=0x0182, 540 | SongofStormsFairy=0x0183, 541 | SpiralBeamsGreatFairyFountains=0x0184, 542 | DialogSpot=0x0185, 543 | PatrollingGerudo=0x0186, 544 | Timer=0x0187, 545 | CursedSkulltulapeople=0x0188, 546 | UncursedSkulltulapeople=0x0189, 547 | ZeldasLullabyeffect=0x018A, 548 | SongofStormseffect=0x018B, 549 | Stinger2=0x018C, 550 | SacredForestMeadowEntities=0x018D, 551 | StoneElevatorSpiritTemple=0x018E, 552 | ChairandPillarpieceswhendestroyedbyanIronKnuckle=0x018F, 553 | GerudoFortresswoodengate=0x0190, 554 | GerudoFortresstrainingareagate=0x0191, 555 | HintDekuScrubsDekuTree=0x0192, 556 | DekuScrubNutAttack=0x0193, 557 | BrokenDrawbridgeFencing=0x0194, 558 | GroundedSalesScrub=0x0195, 559 | DampesMinigameCollectibles=0x0196, 560 | GerudoFighter=0x0197, 561 | EponasSongeffect=0x0198, 562 | SariasSongeffect=0x0199, 563 | GirlchasingCucco=0x019A, 564 | Dog=0x019B, 565 | GoldenSkulltulatoken=0x019C, 566 | KakarikoVillagesetpieces=0x019D, 567 | Beehive=0x019E, 568 | DestructibleWallDesertColossus=0x019F, 569 | LargeCrate=0x01A0, 570 | DekuMaskPanelTrigger=0x01A1, 571 | DekuMaskPanelHeadJudge=0x01A2, 572 | DekuGame=0x01A3, 573 | Maninwhitetoppurplepants=0x01A4, 574 | BombchuBowlingAlleygame=0x01A5, 575 | CarpentersSonChildLinkversion=0x01A6, 576 | GanonsTowermagicbarrier=0x01A7, 577 | DestructibleWallZorasFountain=0x01A8, 578 | DestructibleWallDeathMountainCrater=0x01A9, 579 | TowerofRupees=0x01AB, 580 | EntwinedloversHoneyDarling=0x01AC, 581 | Hagglingtownspeople=0x01AD, 582 | Gorons=0x01AE, 583 | Wolfos=0x01AF, 584 | HyruleFieldStalchild=0x01B0, 585 | GanonBattlerubble=0x01B1, 586 | Chunkofstone=0x01B2, 587 | SinkinglavaplatformGanonsCastle=0x01B3, 588 | Clearblock=0x01B4, 589 | Webblockedlightwindow=0x01B5, 590 | stonewall2D=0x01B6, 591 | PushablelargesquareiceblockInsideGanonsCastle=0x01B7, 592 | Poecollectorandsurroundings=0x01B8, 593 | Gossipstone=0x01B9, 594 | Bombablestonewall=0x01BA, 595 | Metalgate2=0x01BB, 596 | Carpenters2=0x01BC, 597 | BombchuBowlingalleywall=0x01BD, 598 | BombchuBowlingAlleyWall2=0x01BE, 599 | GuidePoeDesertWasteland=0x01BF, 600 | Guay=0x01C0, 601 | Fakedoor=0x01C1, 602 | RefillingOasisDesertColossus=0x01C2, 603 | LidtoGoronJar=0x01C3, 604 | Goronroomdoor=0x01C4, 605 | AdultMalonRanch=0x01C5, 606 | Cow=0x01C6, 607 | Icicles=0x01C7, 608 | Icebarsforadoor2D=0x01C8, 609 | BonoorutheScarecrowSpawn=0x01C9, 610 | BonoorutheScarecrow=0x01CA, 611 | SongofTimeeffect=0x01CB, 612 | ZeldasPathinGanonCastleEscape=0x01CC, 613 | FALSEStoneWallsGerudoTrainingGrounds=0x01CD, 614 | Zora=0x01CE, 615 | SkulltulaSproutingfromBeanSpot=0x01CF, 616 | Gerudogivingyoumembershipcard=0x01D0, 617 | Timeblock=0x01D1, 618 | Bronzeboulder=0x01D2, 619 | PrincessZeldachild=0x01D3, 620 | RunningManAdult=0x01D4, 621 | YoungLinkSilverBlock=0x01D5, 622 | NaviInfospotGreenTimeBlock=0x01D6 623 | } 624 | 625 | CST.ACTOR_TYPE_TO_NAME = invertTable(CST.ACTOR_TYPE) 626 | 627 | CST.MAP_TO_EXIT = { --only 1 of the possible exits 628 | DekuTree=0x0000, 629 | DodongosCavern=0x0004, 630 | GerudoTrainingGrounds=0x0008, 631 | ForestTempleBoss=0x000C, 632 | WaterTemple=0x0010, 633 | LostScene=0x0014, 634 | ActionTestingRoom=0x0018, 635 | StalfosMiddleRoom=0x001C, 636 | StalfosBossRoom=0x0020, 637 | ItemTestingRoom=0x0024, 638 | InsideJabuJabusBelly=0x0028, 639 | RoyalFamilysTomb=0x002D, 640 | MarketEntranceChildDay=0x0033, 641 | MarketEntranceChildNight=0x0034, 642 | MarketEntranceAdult=0x0035, 643 | ShadowTemple=0x0037, 644 | ShootingGallery=0x003B, 645 | Grottos=0x003F, 646 | LakesideLaboratory=0x0043, 647 | DarkLinkTestingArea=0x0047, 648 | GravewithShield=0x004B, 649 | LonLonRanchBuildings=0x004F, 650 | TempleofTime=0x0053, 651 | TreasureBoxShop=0x0063, 652 | BackAlleyDay=0x0067, 653 | BackAlleyNight=0x0068, 654 | ChamberofSages=0x006B, 655 | GrannysPotionShop=0x0072, 656 | BetaCastleCourtyard=0x0076, 657 | CastleCourtyardDay=0x007A, 658 | LotsoPots=0x007E, 659 | SpiritTemple=0x0082, 660 | IceCavern=0x0088, 661 | SpiritTempleBoss=0x008D, 662 | CollisionTestingArena=0x0094, 663 | BottomoftheWell=0x0098, 664 | HouseofTwins=0x009C, 665 | CutsceneMap=0x00A0, 666 | MarketChildDay=0x00B1, 667 | MarketChildNight=0x00B2, 668 | MarketAdult=0x00B3, 669 | DepthTest=0x00B6, 670 | Bazaar=0x00B7, 671 | LinksHouse=0x00BB, 672 | KokiriShop=0x00C1, 673 | KnowitallBrothers=0x00C9, 674 | HyruleField=0x00CD, 675 | KakarikoVillage=0x00DB, 676 | Graveyard=0x00E4, 677 | ZoraRiver=0x00EA, 678 | KokiriForest=0x00EE, 679 | SacredForestMeadow=0x00FC, 680 | LakeHylia=0x0102, 681 | ZorasDomain=0x0108, 682 | ZorasFountain=0x010E, 683 | GerudoValley=0x0117, 684 | LostWoods=0x011E, 685 | DesertColossus=0x0123, 686 | GerudoFortress=0x0129, 687 | HauntedWasteland=0x0130, 688 | TowerCollapseInterior=0x0134, 689 | HyruleCastle=0x0138, 690 | GanonsCastle=0x013A, 691 | DeathMountainTrail=0x013D, 692 | DeathMountainCrater=0x0147, 693 | GoronCity=0x014D, 694 | LonLonRanch=0x0157, 695 | FireTemple=0x0165, 696 | ForestTemple=0x0169, 697 | OutsideTempleofTimeChildDay=0x0171, 698 | OutsideTempleofTimeChildNight=0x0172, 699 | TowerCollapseExterior=0x01C9, 700 | Stables=0x02F9, 701 | KakarikoVillageHouse=0x02FD, 702 | InsideJabuJabusBellyBoss=0x0301, 703 | FireTempleBoss=0x0305, 704 | GravekeepersHut=0x030D, 705 | GreatFairyFountain=0x0315, 706 | GravewithRedead=0x031C, 707 | SmallFairyFountain=0x036D, 708 | MagicFairyFountain=0x0371, 709 | GoronShop=0x037C, 710 | ZoraShop=0x0380, 711 | KakarikoPotionShop=0x0384, 712 | MarketPotionShop=0x0388, 713 | BombchuShop=0x0390, 714 | PuppyWomansHouse=0x0398, 715 | ImpasHouse=0x039C, 716 | CarpentersTent=0x03A0, 717 | ZeldasCourtyard=0x0400, 718 | DodongosCavernBoss=0x040B, 719 | DekuTreeBoss=0x040F, 720 | ShadowTempleBoss=0x0413, 721 | WaterTempleBoss=0x0417, 722 | GanonsTowerClimb=0x041B, 723 | GanondorfBossRoom=0x041F, 724 | MidosHouse=0x0433, 725 | SariasHouse=0x0437, 726 | BackAlleyVillageHouse=0x043B, 727 | DampesGrave=0x044F, 728 | FishingPond=0x045F, 729 | InsideGanonsCastle=0x0467, 730 | ThievesHideout=0x0486, 731 | BombchuBowlingAlley=0x0507, 732 | GanonBattle=0x0517, 733 | Beshitu=0x0520, 734 | HappyMaskShop=0x0530, 735 | HouseofSkulltula=0x0550, 736 | TowerCollapseInteriorExit=0x056C, 737 | } 738 | 739 | CST.EXIT_TO_MAP = invertTable(CST.MAP_TO_EXIT) 740 | 741 | CST.LANGUAGE = { 742 | english=0x45, 743 | japanese=0x4A 744 | } 745 | 746 | 747 | return CST; -------------------------------------------------------------------------------- /lib/json.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding: utf-8 -*- 2 | -- 3 | -- Simple JSON encoding and decoding in pure Lua. 4 | -- 5 | -- Copyright 2010-2014 Jeffrey Friedl 6 | -- http://regex.info/blog/ 7 | -- 8 | -- Latest version: http://regex.info/blog/lua/json 9 | -- 10 | -- This code is released under a Creative Commons CC-BY "Attribution" License: 11 | -- http://creativecommons.org/licenses/by/3.0/deed.en_US 12 | -- 13 | -- It can be used for any purpose so long as the copyright notice above, 14 | -- the web-page links above, and the 'AUTHOR_NOTE' string below are 15 | -- maintained. Enjoy. 16 | -- 17 | local VERSION = 20141223.14 -- version history at end of file 18 | local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-" 19 | 20 | -- 21 | -- The 'AUTHOR_NOTE' variable exists so that information about the source 22 | -- of the package is maintained even in compiled versions. It's also 23 | -- included in OBJDEF below mostly to quiet warnings about unused variables. 24 | -- 25 | local OBJDEF = { 26 | VERSION = VERSION, 27 | AUTHOR_NOTE = AUTHOR_NOTE, 28 | } 29 | 30 | 31 | -- 32 | -- Simple JSON encoding and decoding in pure Lua. 33 | -- http://www.json.org/ 34 | -- 35 | -- 36 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 37 | -- 38 | -- local lua_value = JSON:decode(raw_json_text) 39 | -- 40 | -- local raw_json_text = JSON:encode(lua_table_or_value) 41 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability 42 | -- 43 | -- 44 | -- 45 | -- DECODING (from a JSON string to a Lua table) 46 | -- 47 | -- 48 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 49 | -- 50 | -- local lua_value = JSON:decode(raw_json_text) 51 | -- 52 | -- If the JSON text is for an object or an array, e.g. 53 | -- { "what": "books", "count": 3 } 54 | -- or 55 | -- [ "Larry", "Curly", "Moe" ] 56 | -- 57 | -- the result is a Lua table, e.g. 58 | -- { what = "books", count = 3 } 59 | -- or 60 | -- { "Larry", "Curly", "Moe" } 61 | -- 62 | -- 63 | -- The encode and decode routines accept an optional second argument, 64 | -- "etc", which is not used during encoding or decoding, but upon error 65 | -- is passed along to error handlers. It can be of any type (including nil). 66 | -- 67 | -- 68 | -- 69 | -- ERROR HANDLING 70 | -- 71 | -- With most errors during decoding, this code calls 72 | -- 73 | -- JSON:onDecodeError(message, text, location, etc) 74 | -- 75 | -- with a message about the error, and if known, the JSON text being 76 | -- parsed and the byte count where the problem was discovered. You can 77 | -- replace the default JSON:onDecodeError() with your own function. 78 | -- 79 | -- The default onDecodeError() merely augments the message with data 80 | -- about the text and the location if known (and if a second 'etc' 81 | -- argument had been provided to decode(), its value is tacked onto the 82 | -- message as well), and then calls JSON.assert(), which itself defaults 83 | -- to Lua's built-in assert(), and can also be overridden. 84 | -- 85 | -- For example, in an Adobe Lightroom plugin, you might use something like 86 | -- 87 | -- function JSON:onDecodeError(message, text, location, etc) 88 | -- LrErrors.throwUserError("Internal Error: invalid JSON data") 89 | -- end 90 | -- 91 | -- or even just 92 | -- 93 | -- function JSON.assert(message) 94 | -- LrErrors.throwUserError("Internal Error: " .. message) 95 | -- end 96 | -- 97 | -- If JSON:decode() is passed a nil, this is called instead: 98 | -- 99 | -- JSON:onDecodeOfNilError(message, nil, nil, etc) 100 | -- 101 | -- and if JSON:decode() is passed HTML instead of JSON, this is called: 102 | -- 103 | -- JSON:onDecodeOfHTMLError(message, text, nil, etc) 104 | -- 105 | -- The use of the fourth 'etc' argument allows stronger coordination 106 | -- between decoding and error reporting, especially when you provide your 107 | -- own error-handling routines. Continuing with the the Adobe Lightroom 108 | -- plugin example: 109 | -- 110 | -- function JSON:onDecodeError(message, text, location, etc) 111 | -- local note = "Internal Error: invalid JSON data" 112 | -- if type(etc) = 'table' and etc.photo then 113 | -- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') 114 | -- end 115 | -- LrErrors.throwUserError(note) 116 | -- end 117 | -- 118 | -- : 119 | -- : 120 | -- 121 | -- for i, photo in ipairs(photosToProcess) do 122 | -- : 123 | -- : 124 | -- local data = JSON:decode(someJsonText, { photo = photo }) 125 | -- : 126 | -- : 127 | -- end 128 | -- 129 | -- 130 | -- 131 | -- 132 | -- 133 | -- DECODING AND STRICT TYPES 134 | -- 135 | -- Because both JSON objects and JSON arrays are converted to Lua tables, 136 | -- it's not normally possible to tell which original JSON type a 137 | -- particular Lua table was derived from, or guarantee decode-encode 138 | -- round-trip equivalency. 139 | -- 140 | -- However, if you enable strictTypes, e.g. 141 | -- 142 | -- JSON = assert(loadfile "JSON.lua")() --load the routines 143 | -- JSON.strictTypes = true 144 | -- 145 | -- then the Lua table resulting from the decoding of a JSON object or 146 | -- JSON array is marked via Lua metatable, so that when re-encoded with 147 | -- JSON:encode() it ends up as the appropriate JSON type. 148 | -- 149 | -- (This is not the default because other routines may not work well with 150 | -- tables that have a metatable set, for example, Lightroom API calls.) 151 | -- 152 | -- 153 | -- ENCODING (from a lua table to a JSON string) 154 | -- 155 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines 156 | -- 157 | -- local raw_json_text = JSON:encode(lua_table_or_value) 158 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability 159 | -- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) 160 | -- 161 | -- On error during encoding, this code calls: 162 | -- 163 | -- JSON:onEncodeError(message, etc) 164 | -- 165 | -- which you can override in your local JSON object. 166 | -- 167 | -- The 'etc' in the error call is the second argument to encode() 168 | -- and encode_pretty(), or nil if it wasn't provided. 169 | -- 170 | -- 171 | -- PRETTY-PRINTING 172 | -- 173 | -- An optional third argument, a table of options, allows a bit of 174 | -- configuration about how the encoding takes place: 175 | -- 176 | -- pretty = JSON:encode(val, etc, { 177 | -- pretty = true, -- if false, no other options matter 178 | -- indent = " ", -- this provides for a three-space indent per nesting level 179 | -- align_keys = false, -- see below 180 | -- }) 181 | -- 182 | -- encode() and encode_pretty() are identical except that encode_pretty() 183 | -- provides a default options table if none given in the call: 184 | -- 185 | -- { pretty = true, align_keys = false, indent = " " } 186 | -- 187 | -- For example, if 188 | -- 189 | -- JSON:encode(data) 190 | -- 191 | -- produces: 192 | -- 193 | -- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} 194 | -- 195 | -- then 196 | -- 197 | -- JSON:encode_pretty(data) 198 | -- 199 | -- produces: 200 | -- 201 | -- { 202 | -- "city": "Kyoto", 203 | -- "climate": { 204 | -- "avg_temp": 16, 205 | -- "humidity": "high", 206 | -- "snowfall": "minimal" 207 | -- }, 208 | -- "country": "Japan", 209 | -- "wards": 11 210 | -- } 211 | -- 212 | -- The following three lines return identical results: 213 | -- JSON:encode_pretty(data) 214 | -- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) 215 | -- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) 216 | -- 217 | -- An example of setting your own indent string: 218 | -- 219 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) 220 | -- 221 | -- produces: 222 | -- 223 | -- { 224 | -- | "city": "Kyoto", 225 | -- | "climate": { 226 | -- | | "avg_temp": 16, 227 | -- | | "humidity": "high", 228 | -- | | "snowfall": "minimal" 229 | -- | }, 230 | -- | "country": "Japan", 231 | -- | "wards": 11 232 | -- } 233 | -- 234 | -- An example of setting align_keys to true: 235 | -- 236 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) 237 | -- 238 | -- produces: 239 | -- 240 | -- { 241 | -- "city": "Kyoto", 242 | -- "climate": { 243 | -- "avg_temp": 16, 244 | -- "humidity": "high", 245 | -- "snowfall": "minimal" 246 | -- }, 247 | -- "country": "Japan", 248 | -- "wards": 11 249 | -- } 250 | -- 251 | -- which I must admit is kinda ugly, sorry. This was the default for 252 | -- encode_pretty() prior to version 20141223.14. 253 | -- 254 | -- 255 | -- AMBIGUOUS SITUATIONS DURING THE ENCODING 256 | -- 257 | -- During the encode, if a Lua table being encoded contains both string 258 | -- and numeric keys, it fits neither JSON's idea of an object, nor its 259 | -- idea of an array. To get around this, when any string key exists (or 260 | -- when non-positive numeric keys exist), numeric keys are converted to 261 | -- strings. 262 | -- 263 | -- For example, 264 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) 265 | -- produces the JSON object 266 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} 267 | -- 268 | -- To prohibit this conversion and instead make it an error condition, set 269 | -- JSON.noKeyConversion = true 270 | -- 271 | 272 | 273 | 274 | 275 | -- 276 | -- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT 277 | -- 278 | -- assert 279 | -- onDecodeError 280 | -- onDecodeOfNilError 281 | -- onDecodeOfHTMLError 282 | -- onEncodeError 283 | -- 284 | -- If you want to create a separate Lua JSON object with its own error handlers, 285 | -- you can reload JSON.lua or use the :new() method. 286 | -- 287 | --------------------------------------------------------------------------- 288 | 289 | local default_pretty_indent = " " 290 | local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } 291 | 292 | local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray 293 | local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject 294 | 295 | 296 | function OBJDEF:newArray(tbl) 297 | return setmetatable(tbl or {}, isArray) 298 | end 299 | 300 | function OBJDEF:newObject(tbl) 301 | return setmetatable(tbl or {}, isObject) 302 | end 303 | 304 | local function unicode_codepoint_as_utf8(codepoint) 305 | -- 306 | -- codepoint is a number 307 | -- 308 | if codepoint <= 127 then 309 | return string.char(codepoint) 310 | 311 | elseif codepoint <= 2047 then 312 | -- 313 | -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 314 | -- 315 | local highpart = math.floor(codepoint / 0x40) 316 | local lowpart = codepoint - (0x40 * highpart) 317 | return string.char(0xC0 + highpart, 318 | 0x80 + lowpart) 319 | 320 | elseif codepoint <= 65535 then 321 | -- 322 | -- 1110yyyy 10yyyyxx 10xxxxxx 323 | -- 324 | local highpart = math.floor(codepoint / 0x1000) 325 | local remainder = codepoint - 0x1000 * highpart 326 | local midpart = math.floor(remainder / 0x40) 327 | local lowpart = remainder - 0x40 * midpart 328 | 329 | highpart = 0xE0 + highpart 330 | midpart = 0x80 + midpart 331 | lowpart = 0x80 + lowpart 332 | 333 | -- 334 | -- Check for an invalid character (thanks Andy R. at Adobe). 335 | -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 336 | -- 337 | if ( highpart == 0xE0 and midpart < 0xA0 ) or 338 | ( highpart == 0xED and midpart > 0x9F ) or 339 | ( highpart == 0xF0 and midpart < 0x90 ) or 340 | ( highpart == 0xF4 and midpart > 0x8F ) 341 | then 342 | return "?" 343 | else 344 | return string.char(highpart, 345 | midpart, 346 | lowpart) 347 | end 348 | 349 | else 350 | -- 351 | -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx 352 | -- 353 | local highpart = math.floor(codepoint / 0x40000) 354 | local remainder = codepoint - 0x40000 * highpart 355 | local midA = math.floor(remainder / 0x1000) 356 | remainder = remainder - 0x1000 * midA 357 | local midB = math.floor(remainder / 0x40) 358 | local lowpart = remainder - 0x40 * midB 359 | 360 | return string.char(0xF0 + highpart, 361 | 0x80 + midA, 362 | 0x80 + midB, 363 | 0x80 + lowpart) 364 | end 365 | end 366 | 367 | function OBJDEF:onDecodeError(message, text, location, etc) 368 | if text then 369 | if location then 370 | message = string.format("%s at char %d of: %s", message, location, text) 371 | else 372 | message = string.format("%s: %s", message, text) 373 | end 374 | end 375 | 376 | if etc ~= nil then 377 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" 378 | end 379 | 380 | if self.assert then 381 | self.assert(false, message) 382 | else 383 | assert(false, message) 384 | end 385 | end 386 | 387 | OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError 388 | OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError 389 | 390 | function OBJDEF:onEncodeError(message, etc) 391 | if etc ~= nil then 392 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" 393 | end 394 | 395 | if self.assert then 396 | self.assert(false, message) 397 | else 398 | assert(false, message) 399 | end 400 | end 401 | 402 | local function grok_number(self, text, start, etc) 403 | -- 404 | -- Grab the integer part 405 | -- 406 | local integer_part = text:match('^-?[1-9]%d*', start) 407 | or text:match("^-?0", start) 408 | 409 | if not integer_part then 410 | self:onDecodeError("expected number", text, start, etc) 411 | end 412 | 413 | local i = start + integer_part:len() 414 | 415 | -- 416 | -- Grab an optional decimal part 417 | -- 418 | local decimal_part = text:match('^%.%d+', i) or "" 419 | 420 | i = i + decimal_part:len() 421 | 422 | -- 423 | -- Grab an optional exponential part 424 | -- 425 | local exponent_part = text:match('^[eE][-+]?%d+', i) or "" 426 | 427 | i = i + exponent_part:len() 428 | 429 | local full_number_text = integer_part .. decimal_part .. exponent_part 430 | local as_number = tonumber(full_number_text) 431 | 432 | if not as_number then 433 | self:onDecodeError("bad number", text, start, etc) 434 | end 435 | 436 | return as_number, i 437 | end 438 | 439 | 440 | local function grok_string(self, text, start, etc) 441 | 442 | if text:sub(start,start) ~= '"' then 443 | self:onDecodeError("expected string's opening quote", text, start, etc) 444 | end 445 | 446 | local i = start + 1 -- +1 to bypass the initial quote 447 | local text_len = text:len() 448 | local VALUE = "" 449 | while i <= text_len do 450 | local c = text:sub(i,i) 451 | if c == '"' then 452 | return VALUE, i + 1 453 | end 454 | if c ~= '\\' then 455 | VALUE = VALUE .. c 456 | i = i + 1 457 | elseif text:match('^\\b', i) then 458 | VALUE = VALUE .. "\b" 459 | i = i + 2 460 | elseif text:match('^\\f', i) then 461 | VALUE = VALUE .. "\f" 462 | i = i + 2 463 | elseif text:match('^\\n', i) then 464 | VALUE = VALUE .. "\n" 465 | i = i + 2 466 | elseif text:match('^\\r', i) then 467 | VALUE = VALUE .. "\r" 468 | i = i + 2 469 | elseif text:match('^\\t', i) then 470 | VALUE = VALUE .. "\t" 471 | i = i + 2 472 | else 473 | local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) 474 | if hex then 475 | i = i + 6 -- bypass what we just read 476 | 477 | -- We have a Unicode codepoint. It could be standalone, or if in the proper range and 478 | -- followed by another in a specific range, it'll be a two-code surrogate pair. 479 | local codepoint = tonumber(hex, 16) 480 | if codepoint >= 0xD800 and codepoint <= 0xDBFF then 481 | -- it's a hi surrogate... see whether we have a following low 482 | local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) 483 | if lo_surrogate then 484 | i = i + 6 -- bypass the low surrogate we just read 485 | codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) 486 | else 487 | -- not a proper low, so we'll just leave the first codepoint as is and spit it out. 488 | end 489 | end 490 | VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) 491 | 492 | else 493 | 494 | -- just pass through what's escaped 495 | VALUE = VALUE .. text:match('^\\(.)', i) 496 | i = i + 2 497 | end 498 | end 499 | end 500 | 501 | self:onDecodeError("unclosed string", text, start, etc) 502 | end 503 | 504 | local function skip_whitespace(text, start) 505 | 506 | local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 507 | if match_end then 508 | return match_end + 1 509 | else 510 | return start 511 | end 512 | end 513 | 514 | local grok_one -- assigned later 515 | 516 | local function grok_object(self, text, start, etc) 517 | if text:sub(start,start) ~= '{' then 518 | self:onDecodeError("expected '{'", text, start, etc) 519 | end 520 | 521 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' 522 | 523 | local VALUE = self.strictTypes and self:newObject { } or { } 524 | 525 | if text:sub(i,i) == '}' then 526 | return VALUE, i + 1 527 | end 528 | local text_len = text:len() 529 | while i <= text_len do 530 | local key, new_i = grok_string(self, text, i, etc) 531 | 532 | i = skip_whitespace(text, new_i) 533 | 534 | if text:sub(i, i) ~= ':' then 535 | self:onDecodeError("expected colon", text, i, etc) 536 | end 537 | 538 | i = skip_whitespace(text, i + 1) 539 | 540 | local new_val, new_i = grok_one(self, text, i) 541 | 542 | VALUE[key] = new_val 543 | 544 | -- 545 | -- Expect now either '}' to end things, or a ',' to allow us to continue. 546 | -- 547 | i = skip_whitespace(text, new_i) 548 | 549 | local c = text:sub(i,i) 550 | 551 | if c == '}' then 552 | return VALUE, i + 1 553 | end 554 | 555 | if text:sub(i, i) ~= ',' then 556 | self:onDecodeError("expected comma or '}'", text, i, etc) 557 | end 558 | 559 | i = skip_whitespace(text, i + 1) 560 | end 561 | 562 | self:onDecodeError("unclosed '{'", text, start, etc) 563 | end 564 | 565 | local function grok_array(self, text, start, etc) 566 | if text:sub(start,start) ~= '[' then 567 | self:onDecodeError("expected '['", text, start, etc) 568 | end 569 | 570 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' 571 | local VALUE = self.strictTypes and self:newArray { } or { } 572 | if text:sub(i,i) == ']' then 573 | return VALUE, i + 1 574 | end 575 | 576 | local VALUE_INDEX = 1 577 | 578 | local text_len = text:len() 579 | while i <= text_len do 580 | local val, new_i = grok_one(self, text, i) 581 | 582 | -- can't table.insert(VALUE, val) here because it's a no-op if val is nil 583 | VALUE[VALUE_INDEX] = val 584 | VALUE_INDEX = VALUE_INDEX + 1 585 | 586 | i = skip_whitespace(text, new_i) 587 | 588 | -- 589 | -- Expect now either ']' to end things, or a ',' to allow us to continue. 590 | -- 591 | local c = text:sub(i,i) 592 | if c == ']' then 593 | return VALUE, i + 1 594 | end 595 | if text:sub(i, i) ~= ',' then 596 | self:onDecodeError("expected comma or '['", text, i, etc) 597 | end 598 | i = skip_whitespace(text, i + 1) 599 | end 600 | self:onDecodeError("unclosed '['", text, start, etc) 601 | end 602 | 603 | 604 | grok_one = function(self, text, start, etc) 605 | -- Skip any whitespace 606 | start = skip_whitespace(text, start) 607 | 608 | if start > text:len() then 609 | self:onDecodeError("unexpected end of string", text, nil, etc) 610 | end 611 | 612 | if text:find('^"', start) then 613 | return grok_string(self, text, start, etc) 614 | 615 | elseif text:find('^[-0123456789 ]', start) then 616 | return grok_number(self, text, start, etc) 617 | 618 | elseif text:find('^%{', start) then 619 | return grok_object(self, text, start, etc) 620 | 621 | elseif text:find('^%[', start) then 622 | return grok_array(self, text, start, etc) 623 | 624 | elseif text:find('^true', start) then 625 | return true, start + 4 626 | 627 | elseif text:find('^false', start) then 628 | return false, start + 5 629 | 630 | elseif text:find('^null', start) then 631 | return nil, start + 4 632 | 633 | else 634 | self:onDecodeError("can't parse JSON", text, start, etc) 635 | end 636 | end 637 | 638 | function OBJDEF:decode(text, etc) 639 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 640 | OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) 641 | end 642 | 643 | if text == nil then 644 | self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) 645 | elseif type(text) ~= 'string' then 646 | self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) 647 | end 648 | 649 | if text:match('^%s*$') then 650 | return nil 651 | end 652 | 653 | if text:match('^%s*<') then 654 | -- Can't be JSON... we'll assume it's HTML 655 | self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) 656 | end 657 | 658 | -- 659 | -- Ensure that it's not UTF-32 or UTF-16. 660 | -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), 661 | -- but this package can't handle them. 662 | -- 663 | if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then 664 | self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) 665 | end 666 | 667 | local success, value = pcall(grok_one, self, text, 1, etc) 668 | 669 | if success then 670 | return value 671 | else 672 | -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert. 673 | if self.assert then 674 | self.assert(false, value) 675 | else 676 | assert(false, value) 677 | end 678 | -- and if we're still here, return a nil and throw the error message on as a second arg 679 | return nil, value 680 | end 681 | end 682 | 683 | local function backslash_replacement_function(c) 684 | if c == "\n" then 685 | return "\\n" 686 | elseif c == "\r" then 687 | return "\\r" 688 | elseif c == "\t" then 689 | return "\\t" 690 | elseif c == "\b" then 691 | return "\\b" 692 | elseif c == "\f" then 693 | return "\\f" 694 | elseif c == '"' then 695 | return '\\"' 696 | elseif c == '\\' then 697 | return '\\\\' 698 | else 699 | return string.format("\\u%04x", c:byte()) 700 | end 701 | end 702 | 703 | local chars_to_be_escaped_in_JSON_string 704 | = '[' 705 | .. '"' -- class sub-pattern to match a double quote 706 | .. '%\\' -- class sub-pattern to match a backslash 707 | .. '%z' -- class sub-pattern to match a null 708 | .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters 709 | .. ']' 710 | 711 | local function json_string_literal(value) 712 | local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) 713 | return '"' .. newval .. '"' 714 | end 715 | 716 | local function object_or_array(self, T, etc) 717 | -- 718 | -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON 719 | -- object. If there are only numbers, it's a JSON array. 720 | -- 721 | -- If we'll be converting to a JSON object, we'll want to sort the keys so that the 722 | -- end result is deterministic. 723 | -- 724 | local string_keys = { } 725 | local number_keys = { } 726 | local number_keys_must_be_strings = false 727 | local maximum_number_key 728 | 729 | for key in pairs(T) do 730 | if type(key) == 'string' then 731 | table.insert(string_keys, key) 732 | elseif type(key) == 'number' then 733 | table.insert(number_keys, key) 734 | if key <= 0 or key >= math.huge then 735 | number_keys_must_be_strings = true 736 | elseif not maximum_number_key or key > maximum_number_key then 737 | maximum_number_key = key 738 | end 739 | else 740 | self:onEncodeError("can't encode table with a key of type " .. type(key), etc) 741 | end 742 | end 743 | 744 | if #string_keys == 0 and not number_keys_must_be_strings then 745 | -- 746 | -- An empty table, or a numeric-only array 747 | -- 748 | if #number_keys > 0 then 749 | return nil, maximum_number_key -- an array 750 | elseif tostring(T) == "JSON array" then 751 | return nil 752 | elseif tostring(T) == "JSON object" then 753 | return { } 754 | else 755 | -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects 756 | return nil 757 | end 758 | end 759 | 760 | table.sort(string_keys) 761 | 762 | local map 763 | if #number_keys > 0 then 764 | -- 765 | -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array 766 | -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. 767 | -- 768 | 769 | if self.noKeyConversion then 770 | self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) 771 | end 772 | 773 | -- 774 | -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings 775 | -- 776 | map = { } 777 | for key, val in pairs(T) do 778 | map[key] = val 779 | end 780 | 781 | table.sort(number_keys) 782 | 783 | -- 784 | -- Throw numeric keys in there as strings 785 | -- 786 | for _, number_key in ipairs(number_keys) do 787 | local string_key = tostring(number_key) 788 | if map[string_key] == nil then 789 | table.insert(string_keys , string_key) 790 | map[string_key] = T[number_key] 791 | else 792 | self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) 793 | end 794 | end 795 | end 796 | 797 | return string_keys, nil, map 798 | end 799 | 800 | -- 801 | -- Encode 802 | -- 803 | -- 'options' is nil, or a table with possible keys: 804 | -- pretty -- if true, return a pretty-printed version 805 | -- indent -- a string (usually of spaces) used to indent each nested level 806 | -- align_keys -- if true, align all the keys when formatting a table 807 | -- 808 | local encode_value -- must predeclare because it calls itself 809 | function encode_value(self, value, parents, etc, options, indent) 810 | 811 | if value == nil then 812 | return 'null' 813 | 814 | elseif type(value) == 'string' then 815 | return json_string_literal(value) 816 | 817 | elseif type(value) == 'number' then 818 | if value ~= value then 819 | -- 820 | -- NaN (Not a Number). 821 | -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. 822 | -- 823 | return "null" 824 | elseif value >= math.huge then 825 | -- 826 | -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should 827 | -- really be a package option. Note: at least with some implementations, positive infinity 828 | -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. 829 | -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" 830 | -- case first. 831 | -- 832 | return "1e+9999" 833 | elseif value <= -math.huge then 834 | -- 835 | -- Negative infinity. 836 | -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. 837 | -- 838 | return "-1e+9999" 839 | else 840 | return tostring(value) 841 | end 842 | 843 | elseif type(value) == 'boolean' then 844 | return tostring(value) 845 | 846 | elseif type(value) ~= 'table' then 847 | self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) 848 | 849 | else 850 | -- 851 | -- A table to be converted to either a JSON object or array. 852 | -- 853 | local T = value 854 | 855 | if type(options) ~= 'table' then 856 | options = {} 857 | end 858 | if type(indent) ~= 'string' then 859 | indent = "" 860 | end 861 | 862 | if parents[T] then 863 | self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) 864 | else 865 | parents[T] = true 866 | end 867 | 868 | local result_value 869 | 870 | local object_keys, maximum_number_key, map = object_or_array(self, T, etc) 871 | if maximum_number_key then 872 | -- 873 | -- An array... 874 | -- 875 | local ITEMS = { } 876 | for i = 1, maximum_number_key do 877 | table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) 878 | end 879 | 880 | if options.pretty then 881 | result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" 882 | else 883 | result_value = "[" .. table.concat(ITEMS, ",") .. "]" 884 | end 885 | 886 | elseif object_keys then 887 | -- 888 | -- An object 889 | -- 890 | local TT = map or T 891 | 892 | if options.pretty then 893 | 894 | local KEYS = { } 895 | local max_key_length = 0 896 | for _, key in ipairs(object_keys) do 897 | local encoded = encode_value(self, tostring(key), parents, etc, options, indent) 898 | if options.align_keys then 899 | max_key_length = math.max(max_key_length, #encoded) 900 | end 901 | table.insert(KEYS, encoded) 902 | end 903 | local key_indent = indent .. tostring(options.indent or "") 904 | local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") 905 | local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" 906 | 907 | local COMBINED_PARTS = { } 908 | for i, key in ipairs(object_keys) do 909 | local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) 910 | table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) 911 | end 912 | result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" 913 | 914 | else 915 | 916 | local PARTS = { } 917 | for _, key in ipairs(object_keys) do 918 | local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) 919 | local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent) 920 | table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) 921 | end 922 | result_value = "{" .. table.concat(PARTS, ",") .. "}" 923 | 924 | end 925 | else 926 | -- 927 | -- An empty array/object... we'll treat it as an array, though it should really be an option 928 | -- 929 | result_value = "[]" 930 | end 931 | 932 | parents[T] = false 933 | return result_value 934 | end 935 | end 936 | 937 | 938 | function OBJDEF:encode(value, etc, options) 939 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 940 | OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) 941 | end 942 | return encode_value(self, value, {}, etc, options or nil) 943 | end 944 | 945 | function OBJDEF:encode_pretty(value, etc, options) 946 | if type(self) ~= 'table' or self.__index ~= OBJDEF then 947 | OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) 948 | end 949 | return encode_value(self, value, {}, etc, options or default_pretty_options) 950 | end 951 | 952 | function OBJDEF.__tostring() 953 | return "JSON encode/decode package" 954 | end 955 | 956 | OBJDEF.__index = OBJDEF 957 | 958 | function OBJDEF:new(args) 959 | local new = { } 960 | 961 | if args then 962 | for key, val in pairs(args) do 963 | new[key] = val 964 | end 965 | end 966 | 967 | return setmetatable(new, OBJDEF) 968 | end 969 | 970 | return OBJDEF:new() 971 | 972 | -- 973 | -- Version history: 974 | -- 975 | -- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really 976 | -- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines 977 | -- more flexible, and changed the default encode_pretty() to be more generally useful. 978 | -- 979 | -- Added a third 'options' argument to the encode() and encode_pretty() routines, to control 980 | -- how the encoding takes place. 981 | -- 982 | -- Updated docs to add assert() call to the loadfile() line, just as good practice so that 983 | -- if there is a problem loading JSON.lua, the appropriate error message will percolate up. 984 | -- 985 | -- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, 986 | -- so that the source of the package, and its version number, are visible in compiled copies. 987 | -- 988 | -- 20140911.12 Minor lua cleanup. 989 | -- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. 990 | -- (Thanks to SmugMug's David Parry for these.) 991 | -- 992 | -- 20140418.11 JSON nulls embedded within an array were being ignored, such that 993 | -- ["1",null,null,null,null,null,"seven"], 994 | -- would return 995 | -- {1,"seven"} 996 | -- It's now fixed to properly return 997 | -- {1, nil, nil, nil, nil, nil, "seven"} 998 | -- Thanks to "haddock" for catching the error. 999 | -- 1000 | -- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. 1001 | -- 1002 | -- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", 1003 | -- and this caused some problems. 1004 | -- 1005 | -- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, 1006 | -- and had of course diverged (encode_pretty didn't get the fixes that encode got, so 1007 | -- sometimes produced incorrect results; thanks to Mattie for the heads up). 1008 | -- 1009 | -- Handle encoding tables with non-positive numeric keys (unlikely, but possible). 1010 | -- 1011 | -- If a table has both numeric and string keys, or its numeric keys are inappropriate 1012 | -- (such as being non-positive or infinite), the numeric keys are turned into 1013 | -- string keys appropriate for a JSON object. So, as before, 1014 | -- JSON:encode({ "one", "two", "three" }) 1015 | -- produces the array 1016 | -- ["one","two","three"] 1017 | -- but now something with mixed key types like 1018 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) 1019 | -- instead of throwing an error produces an object: 1020 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} 1021 | -- 1022 | -- To maintain the prior throw-an-error semantics, set 1023 | -- JSON.noKeyConversion = true 1024 | -- 1025 | -- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. 1026 | -- 1027 | -- 20130120.6 Comment update: added a link to the specific page on my blog where this code can 1028 | -- be found, so that folks who come across the code outside of my blog can find updates 1029 | -- more easily. 1030 | -- 1031 | -- 20111207.5 Added support for the 'etc' arguments, for better error reporting. 1032 | -- 1033 | -- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. 1034 | -- 1035 | -- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: 1036 | -- 1037 | -- * When encoding lua for JSON, Sparse numeric arrays are now handled by 1038 | -- spitting out full arrays, such that 1039 | -- JSON:encode({"one", "two", [10] = "ten"}) 1040 | -- returns 1041 | -- ["one","two",null,null,null,null,null,null,null,"ten"] 1042 | -- 1043 | -- In 20100810.2 and earlier, only up to the first non-null value would have been retained. 1044 | -- 1045 | -- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". 1046 | -- Version 20100810.2 and earlier created invalid JSON in both cases. 1047 | -- 1048 | -- * Unicode surrogate pairs are now detected when decoding JSON. 1049 | -- 1050 | -- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding 1051 | -- 1052 | -- 20100731.1 initial public release 1053 | -- --------------------------------------------------------------------------------