Skip to content

Instantly share code, notes, and snippets.

@Sanqui
Last active February 16, 2019 19:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Sanqui/5295479 to your computer and use it in GitHub Desktop.
Save Sanqui/5295479 to your computer and use it in GitHub Desktop.
A Lua script for corrupting Gameboy and Gameboy Advance games live. Supply to VBA-RR. Consult your doctor before use.
--
-- ░█▀▄░█▀█░█▀█░█▄█░░░░█░░░█░█░█▀█
-- ░█▀▄░█░█░█░█░█░█░░░░█░░░█░█░█▀█
-- ░▀▀░░▀▀▀░▀▀▀░▀░▀░▀░░▀▀▀░▀▀▀░▀░▀
--
--
-- Fair warning: this code is about as bad as the things it does to games.
-- GBA support experimental and frankly quite boring.
--Original NES Braidulator by Antony Lavelle 2009 got_wot@hotmail.com http://www.the-exp.net
--Change these settings to adjust options
--Which key you would like to function as the "rewind key"
local rewindKey = 'W' -- Hold this to go back in time!
-- Keys. Feel free to change them to something convenient, because I have a weird keyboard layout
randomkey = "A" -- Just your standard fare. One random WRAM byte.
plusminuskey = "S" -- +/- on one random WRAM byte. Probably the safest you can do which still has some effect.
powerkey = "M" -- Power corruption! 16 random bytes. The most fun key you can press.
highkey = "K" -- On GB, random byte from HRAM, may have drastic consequences. On GBA, power corruption on IRAM
vramkey = "Q" -- Messes up vram (graphics). Purely visual - totally safe.
freezekey = "B" -- Freezes a random byte in memory indefinedly. May have totally different effects from regular corruption.
unfreezekey = "N" -- Unfreezes every frozen byte. This also happens automatically when rewinding.
savetileskey = "I" -- Save current tiles.
restoretileskey = "O" -- Restore saved tiles. Purely visual, can be used to copy tiles from game to game etc.
wrammap = "L" -- Display an interactive memory map, one pixel per byte.
-- Opacity = value. Blue = byte changed this frame. Green = HRAM.
-- Left: +1 Right: -1 Middle: set to $FF
-- Warning: SLOW! PARTLY BROKEN FOR GBA!
rewindon = "T" -- Switches rewinding on and off. May save some 15%, depending on your computer.
debugkey = "`" -- Displays some boring CPU info.
--How much rewind power would you like? (The higher the number the further back in time you can go, but more computer memory is used up)
--Do not set to 0!
local saveMax = 2000;
funtimes = 0;
txttime = 0;
--The stuff below is for more advanced users, enter at your own peril!
print("Boom loaded!")
function rainbowcolor(ri)
freq = 0.15
r = math.sin(freq*ri) * 127 + 128
g = math.sin(freq*ri+2) * 127 + 128
b = math.sin(freq*ri+4) * 127 + 128
return {r, g, b}
end
function randmemory(from, to) -- gets a random memory offset, avoiding the stack
if gb then
while (true) do
bang = math.random(from, to)
if bang < sp or bang > stack then
break
end
end
return bang
else
return math.random(from, to)
end
end
frozen = {}
crashi = 0
vram = {}
wram = {}
prevwram = {}
s = {0xff, 0xff, 0x00, 0x00, 0xbb, 0xbb, 0x8b, 0x8b, 0x68, 0x68, 0x8b, 0x8b, 0xeb, 0xeb, 0x18, 0x18}
stack = -1
-- dumb format for speed/convenience
invalid_opcodes = {[0xd3]= true, [0xdb]= true, [0xdd]= true, [0xe3]= true, [0xe4]= true, [0xeb]= true, [0xec]= true, [0xed]= true, [0xfd]= true, [0xf4]= true}
local saveArray = {};--the Array in which the save states are stored
local saveCount = 1;--used for finding which array position to cycle through
local save; -- the variable used for storing the save state
local rewindCount = 0;--this stops you looping back around the array if theres nothing at the end
local savePreventBuffer = 1;--Used for more control over when save states will be saved, not really used in this version much.
local saveFrequency = 10; -- look at me mom, I'm editing LIVE!
local timer = -1;
local rewind = true
while (true) do
keys = input.get();
timer = timer + 1;
savePreventBuffer = savePreventBuffer-1;
if savePreventBuffer==0 then
savePreventBuffer = 1;
end;
keys = input.get();
if keys[rewindKey] then
savePreventBuffer = 5;
if rewindCount==0 then
--makes sure you can't go back too far could also include other things in here, left empty for now.
else
savestate.load(saveArray[saveCount]);
saveCount = saveCount-1;
rewindCount = rewindCount-1;
if saveCount==0 then
saveCount = saveMax-1;
end;
end;
local HUDMATH = rewindCount / saveMax;--Making the rewind time a percentage.
gui.text(1,0, string.format("%.2f%%", HUDMATH * 100));--Displaying the time onscreen.
if math.fmod(timer, 30) <= 15 then
gui.text(138,8*2, "<<"); -- picky, picky
end;
end;
if rewind and savePreventBuffer==1 and math.fmod(timer, saveFrequency) == 0 then
-- gui.text(80,15,"");
saveCount=saveCount+1;
if saveCount==saveMax then
saveCount = 1;
end
rewindCount = rewindCount+1;
if rewindCount==saveMax-1 then
rewindCount = saveMax-2;
end;
save = savestate.create();
savestate.save(save);
saveArray[saveCount] = save;
end;
if math.fmod(timer, 32) == 0 then
header = memory.readbyte(0x133) -- last byte of logo on GB
if header == 0x3e then
gb = true
gba = false
else
gb = false
gba = true
end
end
if gb then
pc = memory.getregister('pc')
op = memory.readbyte(pc)
is_invalid = invalid_opcodes[op]
sp = memory.getregister('sp')
if crashi <= 1 then
if stack == -1 and sp < 0xe000 and sp >= 0xc000 then
stack = (math.ceil(sp / 0x10) * 0x10)-1
print(string.format("sp=%04X, stack detected at %04X", sp, stack))
elseif sp > 0xFF00 then
print("Stack detected in HRAM, restart?")
stack = -1
end
end
--if sp <=
end
if keys[debugkey] then
if gb then
debugtext = string.format("pc=%04X sp=%04X", pc, sp)
gui.text(160-(string.len(debugtext)*4),0, debugtext, 'green');
debugtext = string.format("opcode %02X", op)
gui.text(160-(string.len(debugtext)*4),8, debugtext, 'green');
else
debugtext = string.format("GBA")
gui.text(240-(string.len(debugtext)*4),0, debugtext, 'green');
end
end
if gb then
if pc >= 0x8000 or sp < 0x8000 or is_invalid or pc == 0x39 then
crashi = crashi + 1
if crashi >= 2 then
gui.text(1,8, crashi, 'red');
if crashi > 10 then info = "Crashed" else info = "Clinical death" end
crashtext = string.format(info.." pc=%04X sp=%04X", pc, sp)
gui.text(160-(string.len(crashtext)*4),0, crashtext, 'red');
crashtext = ""
if is_invalid then
crashtext = string.format("Invalid opcode %02X", op)
elseif pc == 0x39 then
crashtext = string.format("Stuck in rst 38", op)
end
gui.text(160-(string.len(crashtext)*4),8, crashtext, 'red');
end
else
crashi = 0
end
-- print(string.format('%04X', memory.getregister('pc')))
end
if keys[randomkey] then
gui.text(1,137, "Random bytes"); -- picky, picky
if gb then
bang1 = randmemory(0x9800, 0xFEFF)
elseif gba then
bang1 = randmemory(0x2000000, 0x0203FFFF)
end
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
--print(string.format("%04X => %02X", bang1, bang2));
end;
if keys[plusminuskey] then
if gb then
bang1 = randmemory(0x9800, 0xFEFF)
elseif gba then
bang1 = randmemory(0x2000000, 0x0203FFFF)
end
bang2 = AND(memory.readbyte(bang1) + math.random(-1, 1), 0xFF);
memory.writebyte(bang1, bang2);
gui.text(1,137, string.format("%04X +/- 1", bang1)); -- picky, picky
--print(string.format("%04X => %02X", bang1, bang2));
end;
-- KABLOOEY
if keys[powerkey] then
gui.text(1,137, "Power corruption!", rainbowcolor(timer)); -- picky, picky
for i = 0, 0xF do
if gb then
bang1 = randmemory(0xA000, 0xFEFF)
elseif gba then
--if math.random(1) then
bang1 = randmemory(0x2000000, 0x0203FFFF)
--else
-- bang1 = randmemory(0x3000000, 0x0303FFFF)
--end
end
bang2 = math.random(0x00, 0xFF);
memory.writebyte(bang1, bang2);
--print(string.format("%04X => %02X", bang1, bang2));
end;
end;
-- KABLOOEY
if keys[highkey] then
if gb then
gui.text(1,137, "HRAM corruption!"); -- picky, picky
bang1 = randmemory(0xFF8C, 0xFFFE)
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
end
if gba then
gui.text(1,137, "Power corruption INT!", rainbowcolor(timer)); -- picky, picky
for i = 0, 0xF do
bang1 = randmemory(0x3000000, 0x0300FFFF)
bang2 = math.random(0x00, 0xFF);
memory.writebyte(bang1, bang2);
end;
end
end;
if keys[vramkey] then
rand = math.random(0, 16)
if gb then
if rand >= 3 then
gui.text(1,137, "Trashing VRAM");
bang1 = math.random(0x8000, 0x9FFF)
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
--elseif rand >= 3 then -- I hate you vba
-- gui.text(1,137, "Trashing palettes");
-- bang1 = math.random(0, 0xFF);
-- bang2 = math.random(0, 0xFF);
-- memory.writebyte(0xFF68, bang1);
-- memory.writebyte(0xFF69, bang2);
elseif rand >= 1 then
gui.text(1,137, "Mixing up tiles");
bang1 = math.random(0x8000, 0x9700)
tile = memory.readbyterange(bang1, 0x10)
bang2 = math.random(0x8000, 0x9700)
for i,byte in pairs(tile) do
memory.writebyte(bang2+i, byte)
end
memory.writebyte(bang1, bang2);
else
gui.text(1,137, "Trashing OAM");
bang1 = math.random(0xFE00, 0xFE90)
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
end
rand2 = math.random(0, 10000)
if rand2 == 0 then
bang = math.random(0x8000, 0x9700)
bang = AND(bang, 0xfff0)
for i,byte in pairs(s) do
memory.writebyte(bang-1+i, byte)
end
gui.text(1,137, "4");
end
elseif gba then
for i = 0, 0x8 do -- larger screen, larger corruption
if rand >= 3 then
gui.text(1,137, "Trashing VRAM");
bang1 = math.random(0x6000000, 0x6017fff)
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
else
gui.text(1,137, "Trashing palettes");
bang1 = math.random(0x5000000, 0x5000400)
bang2 = math.random(0, 0xFF);
memory.writebyte(bang1, bang2);
end
end
end
--print(string.format("%04X => %02X", bang1, bang2));
end;
if keys[freezekey] then
gui.text(1,137, "Freeze"); -- picky, picky
for i = 0, 0xF do
if gb then
bang1 = randmemory(0xC000, 0xDFFF)
elseif gba then
bang1 = randmemory(0x2000000, 0x0203FFFF)
end
--bang2 = math.random(0x00, 0xFF);
frozen[bang1] = memory.readbyte(bang1);
--print(string.format("%04X => %02X", bang1, bang2));
end;
end;
if keys[unfreezekey] or keys[rewindKey] then
gui.text(1,137, "Unfreeze")
frozen = {}
end
if keys[savetileskey] then
if gb then
gui.text(1,137, "Saved tiles"); -- picky, picky
vram = memory.readbyterange(0x8000, 0x1700)
end
end;
if keys[restoretileskey] then
if gb then
gui.text(1,137, "Restored tiles")
for i,v in pairs(vram) do
memory.writebyte(0x8000-1+i, v);
end
end
end
if keys[rewindon] and not lastkeys[rewindon] then rewind = not rewind end
if keys[rewindon] then
if rewind then text = "Rewind ON!" else text = "Rewind OFF!" end
gui.text(1,137, text)
end
if keys[wrammap] and not lastkeys[wrammap] then showwram = not showwram; end
if showwram then
--gui.text(1,137, "Live WRAM")
if gb then
wram = memory.readbyterange(0xC000, 0x2000)
for k,v in pairs(memory.readbyterange(0xFF00, 0xFF)) do wram[0x2000+k] = v end
width = 64
elseif gba then
wram = memory.readbyterange(0x2000000, 0x3FFFF)
width = 64*3
end
for i,v in pairs(wram) do
--memory.writebyte(0x8000-1+i, v);
if prevwram[i] ~= wram[i] then
color = {0, 0, 255, v}
elseif frozen[0xC000+i-1] then
color = {0, 255, 255, v}
elseif i > 0x2000 then
color = {0, 255, 0, v}
else
color = {255, 0, 0, v}
end
gui.pixel((i-1)%width, math.floor((i-1)/width), color)
end
prevwram = wram
xmouse, ymouse, left, right, middle = keys['xmouse'], keys['ymouse'], keys['leftclick'], keys['rightclick'], keys['middleclick']
if left or right or middle and xmouse <= width then
offset = 0xC000 + (xmouse%width) + (ymouse*width)
if left then
memory.writebyte(offset, AND(memory.readbyte(offset) + 1, 0xFF))
elseif right then
memory.writebyte(offset, AND(memory.readbyte(offset) - 1, 0xFF))
else
memory.writebyte(offset, 0xff)
end
end
end
for i,v in pairs(frozen) do
memory.writebyte(i, v);
end
lastkeys = keys
emu.frameadvance();
end;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment