-- Jet Force Genocide Script -- This script will only work for the English and Japanese Versions. -- What is Jet Force Genocide? ------------------------------------------------------------- -- This is a hack that reverses how Tribals work. When a Tribal is 'saved' it is counted as -- 'killed' and vice versa. This means that in order to be able to beat the game the player -- must go through the game and kill all the Tribals instead of saving them. Reversing how -- they work is necessary to make the game beatable. This is because as far as the game is -- concerned all the Tribals must be saved to do so. -- Emulator: ------------------------------------------------------------------------------- -- Use BizHawk 1.11.6 (or higher) -- After loading the ROM use N64 -> Plugins -- For Global Plugin Settings use: -- Core Type: DynaRec -- Rsp Plugin: Hle -- Active Video Plugin: Jabo 1.6.1 -- Video Resolution: 640x480 (text display is setup for this resolution) -- It seems if the core type is set to interperter (pure or not) the Lurg Fight can crash the game. -- Jabo Video plugin is the best as it shows the manual targetting reticles while the others don't. -- However there are a few errors with it on the Japanese Version. -- The Lurg Boss Fight and Chasm Night Vision Sections are covered by a black screen. -- This prevents you from being able to see. So I disabled them. -- These errors don't seem to happen on the English version but I still disabled to make sure it works. -- How to use this: -- On pastebin.com click the download button on the upper right side of the code frame to download this file as a .lua file. -- In BizHawk open the Lua Console. (Tools -> Lua Console) -- In Lua Console open the .lua file. (Script -> Open Script -> ) -- NOTES: ---------------------------------------------------------------------------------- -- I added an check for invalid tribal counts. -- A message will pop up in the bottle left of the BizHawk window if it happens. -- If this happens the saved and killed counts will reset to 0. -- This could cause an issue with remaining tribals though if it happens in the middle of a region. -- If it happens at the very beggining of the region it should be fine. -- This error shouldn't happen anymore, but I put this in just in case. -- I also added a Tribal Count Display (above the weapon hud). -- The format for it is as follows: -- This will make it easy to tell if the counts are off some how. -- The display is setup for 480p. -- Levitation is enabled since as a tribal you can't use the Jet Pack. -- However it is still based on Jet Fuel. You need to have Jet Fuel to be able to levitate. -- It works the exact same (for the most part). C-Up to accelerate upwards. C-Down to Hover. -- There are 2 displays. A generic one and one that looks like the digital version from in game. -- The digital one requires a specific font to work. To use it you will need "Quartz MS" Font. -- You can download Quartz MS here: https://www.dropbox.com/s/u2np0mk6xmp67u7/QuartzMS.TTF?dl=0 -- On line 59 of this file there is a variable called "font", set this to true if you have Quarts MS Installed. -- Otherwise leave it false for the generic display. -- You should be a tribal when playing as Juno or Vela. -- Sometimes this isn't applied but that't not really an issue. -- For the Mizar's Palace Race you have to be a Drone so it is disabled there. console.clear() local font = false -- Game Hash versions = { ["493CED9008DBE932D6E91179B68E8630CF23A023"] = "E", -- English ["15099233760B36E7AFAD7DA36B9464DA1512C4B1"] = "J" -- Japanese } -- Version Detection local v = versions[gameinfo.getromhash()] if (v == "E") then gui.addmessage("Version: Jet Force Gemini (U)") elseif (v == "J") then gui.addmessage("Version: Star Twins (J)") else gui.addmessage("Version: Unknown") v = nil end local addrs = { ["E"] = { sceneLast = 0x0A323C, sceneSetter = 0x0A3250, entranceSetter = 0x0A3258, loadFlag = 0x0A3294, tribal = { saved = 0x1E6014, killed = 0x1E6015, remaining = 0x1E6016 }, region = 0x0A3269, continues = 0x0A3280, character = 0x1E6010, model = { vela = 0x1E6170, juno = 0x1E61E6 }, pointers = { vela = 0x1D6BAC, juno = 0x1D6BB4, }, rainbow_blood = 0x0A5080, paused = 0x0A06A4, night_vision = 0x0A6D03, lurg_vision = 0x0A333B }, ["J"] = { sceneLast = 0x0A30FC, sceneSetter = 0x0A3110, entranceSetter = 0x0A3118, loadFlag = 0x0A3154, tribal = { saved = 0x1E3584, killed = 0x1E3585, remaining = 0x1E3586 }, region = 0x0A3129, continues = 0x0A3140, character = 0x1E3580, model = { vela = 0x1E36E0, juno = 0x1E3756 }, pointers = { vela = 0x1D40FC, juno = 0x1D4104, }, rainbow_blood = 0x0A4F90, paused = 0x0A0564, night_vision = 0x0A6C13, lurg_vision = 0x0A31FF }, velocity = { vela = 0x0190, juno = 0x0170 }, jet_fuel = { vela = 0x07B4, juno = 0x0794 }, state = { vela = 0x075A, juno = 0x073A } } local playerVelocity = nil local playerFuel = nil local playerState = nil local timer = 0 local jetFuel = 0 local jetFuelSwitch = false local tribals = { saved = 0, killed = 0, remaining = 0, killtotal = 0 } -- Initialize Version Dependant Variables if (v ~= nil) then tribalRemaining = addrs[v].tribal.remaining tribalKilled = addrs[v].tribal.killed tribalSaved = addrs[v].tribal.saved region = addrs[v].region continues = addrs[v].continues sceneLast = addrs[v].sceneLast sceneSetter = addrs[v].sceneSetter entranceSetter = addrs[v].entranceSetter loadFlag = addrs[v].loadFlag rainbowBlood = addrs[v].rainbow_blood paused = addrs[v].paused nightVision = addrs[v].night_vision lurgVision = addrs[v].lurg_vision end -- Encapsulate memory.read/write into functions. function getByte(address) if (address ~= nil) then return memory.readbyte(address) else print("getByte(address): address (a nil value)") end end function getWord(address) if (address ~= nil) then return memory.read_u16_be(address) else print("getWord(address): address (a nil value)") end end function getDWord(address) if (address ~= nil) then return memory.read_u32_be(address) else print("getDWord(address): address (a nil value)") end end function setByte(address, value) if (address ~= nil and value ~= nil) then memory.writebyte(address, value) elseif (address == nil) then print("setByte(address, value): address (a nil value)") elseif (value == nil) then print("setByte(address, value): value (a nil value)") end end function setWord(address, value) if (address ~= nil and value ~= nil) then memory.write_u16_be(address, value) elseif (address == nil) then print("setWord(address, value): address (a nil value)") elseif (value == nil) then print("setWord(address, value): value (a nil value)") end end function setDWord(address, value) if (address ~= nil and value ~= nil) then memory.write_u32_be(address, value) elseif (address == nil) then print("setDWord(address, value): address (a nil value)") elseif (value == nil) then print("setDWord(address, value): value (a nil value)") end end -- This function handles setting up the player actor related data that moves around dynamically in memory. function setupTribal() playerVelocity = nil playerFuel = nil playerState = nil local character = getByte(addrs[v].character) local model -- Check the character and set model to Tribal if (character == 0x00 or character == 0x04) then model = getWord(addrs[v].model.vela) % 0x10 model = getWord(addrs[v].model.vela) - model setWord(addrs[v].model.vela, model + 0x02) elseif (character == 0x01 or character == 0x05) then model = getWord(addrs[v].model.juno) % 0x10 model = getWord(addrs[v].model.juno) - model setWord(addrs[v].model.juno, model + 0x02) end -- Give the scene time to load for i = 0, 240, 1 do -- 240 frames / 4 seconds. textDisplay() updateTribals() storeTribals() fixVision() emu.frameadvance() end -- Check the character and set addresses. if ((character == 0x00 or character == 0x04) and getDWord(addrs[v].pointers.vela) ~= 0x00000000) then playerVelocity = getDWord(addrs[v].pointers.vela) - 0x80000000 + addrs.velocity.vela playerState = getDWord(addrs[v].pointers.vela) - 0x80000000 + addrs.state.vela if (character == 0x04) then playerFuel = getDWord(addrs[v].pointers.vela) - 0x80000000 + addrs.jet_fuel.vela end elseif ((character == 0x01 or character == 0x05) and getDWord(addrs[v].pointers.juno) ~= 0x00000000) then playerVelocity = getDWord(addrs[v].pointers.juno) - 0x80000000 + addrs.velocity.juno playerState = getDWord(addrs[v].pointers.juno) - 0x80000000 + addrs.state.juno if (character == 0x05) then playerFuel = getDWord(addrs[v].pointers.juno) - 0x80000000 + addrs.jet_fuel.juno end end end -- Jet Pack with C-Up/C-Down like normal. function levitate(i) if (playerVelocity ~= nil and playerFuel ~= nil and playerState ~= nil) then local fuel = getWord(playerFuel) if (fuel > 0x0000 and fuel < 0x0E11 and getByte(playerState) ~= 0x01) then jetFuelSwitch = true if (i == true) then setDWord(playerVelocity, 0x40A00000) -- 5.0 if (fuel - 0x0002 == 0xFFFF) then setWord(playerFuel, 0x0000) else setWord(playerFuel, fuel - 0x0002) end elseif (i == false) then setDWord(playerVelocity, 0x3FACCCCC) -- 1.25 setWord(playerFuel, fuel - 0x0001) end end end end -- This function handles updating the tribal counts. function updateTribals() -- Store current counts to check against last frame's counts. local saved = getByte(tribalSaved) local killed = getByte(tribalKilled) local remaining = getByte(tribalRemaining) local savedDiff = 0 local killedDiff = 0 -- Check if the Remaining Tribals has gone down. if (remaining < tribals.remaining) then -- Killed Difference if (killed > tribals.killed) then killedDiff = killed - tribals.killed elseif (killed < tribals.killed) then -- If the count has decreased then the player entered a new region. tribals.killed = 0 end -- Saved Difference if (saved > tribals.saved) then savedDiff = saved - tribals.saved elseif (saved < tribals.saved) then -- If the count has decreased then the player entered a new region. tribals.saved = 0 end -- Apply Differences to opposite count. tribals.saved = tribals.saved + killedDiff tribals.killed = tribals.killed + savedDiff -- Update Kill Total tribals.killtotal = tribals.killtotal + killedDiff -- Validate Tribal counts. (16 is highest) if (tribals.saved > 16 or tribals.killed > 16) then gui.addmessage("Invalid Tribal Counts") gui.addmessage("Reseting Counts...") setByte(tribalSaved, 0) setByte(tribalKilled, 0) setByte(tribalRemaining, tribals.remaining) else setByte(tribalSaved, tribals.saved) setByte(tribalKilled, tribals.killed) end end end -- Store the tribal counts. function storeTribals() tribals.saved = getByte(tribalSaved) tribals.killed = getByte(tribalKilled) tribals.remaining = getByte(tribalRemaining) end function jetFuelDisplayTimer() local fuel = getWord(playerFuel) if (fuel ~= jetFuel) then jetFuelSwitch = true elseif (fuel == 0x0000) then jetFuelSwitch = false end if (jetFuelSwitch == true and timer < 455) then timer = timer + 2 elseif(jetFuelSwitch == false and timer > 0) then timer = timer - 1 end if (timer >= 455) then jetFuelSwitch = false end jetFuel = fuel end function textDisplay() local x = 45 local y = 13 local t = 0 if (timer < 0xFF) then t = timer * 0x01000000 else t = 0xFF000000 end -- Tribal Counts if (getByte(loadFlag) == 0x00 and getByte(region) ~= 0x00 and getByte(paused) == 0x00) then if (getWord(sceneLast) ~= 0x0000 and getWord(sceneLast) ~= 0x0001 and getWord(sceneLast) ~= 0x007F and getWord(sceneLast) ~= 0x015C and getWord(sceneLast) ~= 0x015D and getWord(sceneLast) ~= 0x017D and getWord(sceneLast) ~= 0x017E and getWord(sceneLast) ~= 0x017F and getWord(sceneLast) ~= 0x0180 and getWord(sceneLast) ~= 0x0181) then gui.drawBox(x, y, x + 102, y + 15, 0xFF4FB3C9, 0x80267580) gui.drawText(x - 2, y - 1, string.format("%02d", tribals.saved), 0x60FFFFFF, "TRANSPARENT", 16) gui.drawText(x + 22, y - 1, string.format("%02d", tribals.killed), 0x60FFFFFF, "TRANSPARENT", 16) gui.drawText(x + 46, y - 1, string.format("%02d", tribals.remaining), 0x60FFFFFF, "TRANSPARENT", 16) gui.drawText(x + 70, y - 1, string.format("%03d", tribals.killtotal), 0x60FFFF00, "TRANSPARENT", 16) gui.drawText(x - 2, y - 1, string.format("%2d", tribals.saved), 0xFFFFFFFF, "TRANSPARENT", 16) gui.drawText(x + 22, y - 1, string.format("%2d", tribals.killed), 0xFFFFFFFF, "TRANSPARENT", 16) gui.drawText(x + 46, y - 1, string.format("%2d", tribals.remaining), 0xFFFFFFFF, "TRANSPARENT", 16) gui.drawText(x + 70, y - 1, string.format("%3d", tribals.killtotal), 0xFFFFFF00, "TRANSPARENT", 16) end end -- Jet Fuel if (getByte(loadFlag) == 0x00 and getByte(paused) == 0x00 and playerFuel ~= nil and getWord(playerFuel) < 0x0E11) then if (font == false) then if (timer < 0x80) then gui.drawBox(x + 183, y + 424, x + 407, y + 439, t + 0x4FB3C9, t + 0x267580) else gui.drawBox(x + 183, y + 424, x + 407, y + 439, t + 0x4FB3C9, 0x80267580) end gui.drawText(x + 230, y + 423, string.format("Jet Fuel: %3d", getWord(playerFuel) / 36), t + 0xFFFFFF, "TRANSPARENT", 16) elseif (font == true) then -- Main if (getWord(playerFuel) / 36 == 100) then gui.drawText(470, 386, string.format("%3d", getWord(playerFuel) / 36), t + 0x00FF00, "Transparent", 47, "Quartz MS") elseif (getWord(playerFuel) / 36 >= 10) then gui.drawText(485, 386, string.format("%3d", getWord(playerFuel) / 36), t + 0x00FF00, "Transparent", 47, "Quartz MS") else gui.drawText(502, 386, string.format("%3d", getWord(playerFuel) / 36), t + 0x00FF00, "Transparent", 47, "Quartz MS") end -- Background if (timer < 0x20) then gui.drawText(470, 386, string.format("%3d", 888), t + 0x00FF00, "Transparent", 47, "Quartz MS") else gui.drawText(470, 386, string.format("%3d", 888), 0x2000FF00, "Transparent", 47, "Quartz MS") end end end end function fixVision() -- Disable Lurg Black Screen if (getByte(lurgVision) == 0x01) then setByte(lurgVision, 0x00) end -- 0x0107 -- Disable Night Vision if (getByte(nightVision) ~= 0x00) then setByte(nightVision, 0x00) end -- By pass the Night Vision Room. -- Since it's disabled there's no reason to get it or activate it. if (getByte(loadFlag) ~= 0x00 and getWord(sceneSetter) == 0x013D and getWord(entranceSetter) == 0x0001) then setWord(sceneSetter, 0x0137) elseif (getByte(loadFlag) ~= 0x00 and getWord(sceneSetter) == 0x013C and getWord(entranceSetter) == 0x0000) then setWord(sceneSetter, 0x013B) end end -- Run this when the script is turned on. UNLESS at the title screen/game select menu. if (getWord(sceneLast) ~= 0x0000 and getWord(sceneLast) ~= 0x0001 and getWord(sceneLast) ~= 0x007F and getWord(sceneLast) ~= 0x015C and getWord(sceneLast) ~= 0x015D and getWord(sceneLast) ~= 0x017D and getWord(sceneLast) ~= 0x017E and getWord(sceneLast) ~= 0x017F and getWord(sceneLast) ~= 0x0180 and getWord(sceneLast) ~= 0x0181) then setupTribal() storeTribals() end while true do -- If version is not detected the script won't be able to do anything. if (v ~= nil) then -- Button Activators if joypad.get(1)['C Up'] then levitate(true) elseif joypad.get(1)['C Down'] then levitate(false) end -- On a load. if (getByte(loadFlag) ~= 0x00) then local i = getWord(sceneSetter) -- If the entrance is any of the below this won't run. if (i ~= 0x0000 and -- Game Select i ~= 0x0001 and -- Game Select i ~= 0x007F and -- Character Select i ~= 0x015C and -- Title Screen i ~= 0x015D and -- Title Screen i ~= 0x017D and -- Title Screen i ~= 0x017E and -- Title Screen i ~= 0x017F and -- Title Screen i ~= 0x0180 and -- Title Screen i ~= 0x0181 and -- Title Screen i ~= 0x00FA and -- Mizar's Palace Race Areas i ~= 0x010C and -- Mizar's Palace Race Areas i ~= 0x011D and -- Mizar's Palace Race Areas i ~= 0x0169 and -- Mizar's Palace Race Areas i ~= 0x016B and -- Mizar's Palace Race Areas i ~= 0x0197 and -- Mizar's Palace Race Areas i ~= 0x018E and -- Mizar's Palace Race Areas i ~= 0x018A) then -- Mizar's Palace Race Areas -- Rainbow Blood setDWord(rainbowBlood, 0x01000000) setupTribal() end end if (playerFuel ~= nil) then jetFuelDisplayTimer() end textDisplay() updateTribals() storeTribals() fixVision() end emu.frameadvance() end