-- 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 -> <browse to file>)
-- 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: <saved> <killed> <remaining> <total kill count>
-- 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
Comments
0 B
|👍
/👎