A poorly documented script for RNG manipulation in Golvellius (SMS).
-- Configuration
alwaysHP = true;
enableDamageTimer = true;
enableDragAndDrop = false;
enableLagDetection = false;
showList = false;
showHitbox = true;
currArr = 1
calcArray = {}
local simulating = false
local anyValid = false
local testFrames = 1
local inputs = {}
local changeInputs = {}
local currentUp = false
local currentDown = true
local currentLeft = false
local currentRight = false
local modify = {}
print("Settings:");
print();
print("alwaysHP = "..tostring(alwaysHP));
print("enableDamageTimer = "..tostring(enableDamageTimer));
print("enableDragAndDrop = "..tostring(enableDragAndDrop));
print("enableLagDetection = "..tostring(enableLagDetection));
print("showHitbox = "..tostring(showHitbox));
print("showList = "..tostring(showList));
local red = 0xFFFF0000;
local yellow = 0xFFFFFF00;
local gold = 0xFFFFD700;
local green = 0xFF00AA00; -- Hair Color
local pink = 0xFFFF00FF;
local black = 0xFF000000;
local white = 0xFFFFFFFF;
-- Game state
local gamemode = "overworld"; -- overworld, vertical, horizontal, shop
local object_array_base = 0x100;
local object_size = 0x20;
local object_array_capacity = 16;
local object_fields = {
["object_type"] = 0x00, -- Byte
["object_types"] = {
[0x4C] = {name = "Player", color = red}, -- Damaged, Vertical Dungeon
--
[0x59] = {name = "Player", color = red}, -- Damaged, Side Scroller
--
[0x5C] = {name = "Dying Boss?", particle = true},
--
[0x5E] = {name = "Player", color = red}, -- Damaged, Overworld
--
[0x60] = {name = "Despa Particle Spawning", particle = true},
--
[0x81] = {name = "Player"}, -- Overworld
[0x82] = {name = "Player"}, -- Dungeon, Side Scroller
[0x83] = {name = "Player"}, -- Dungeon, Vertical
[0x84] = {name = "Sword", color = yellow}, -- Player Sword
--
[0x86] = {name = "Dying Enemy", particle = true},
[0x87] = {name = "Snakelet", gold = 10, color = red, max_hp = 1},
[0x88] = {name = "Fire Spirit", gold = 40, color = red, max_hp = 2},
[0x89] = {name = "Flea", gold = 30, color = red, max_hp = 2},
[0x8A] = {name = "Basketworm", gold = 20, color = red, max_hp = 2},
[0x8B] = {name = "Spider", gold = 100, color = red, max_hp = 3},
[0x8C] = {name = "Health", color = pink, particle = true},
--
[0x90] = {name = "Fly", gold = 80, color = red, max_hp = 1},
[0x91] = {name = "Tick", gold = 60, color = red, max_hp = 1},
[0x92] = {name = "Dark Blue Bat", gold = 30, color = red, max_hp = 1},
[0x93] = {name = "Little Big Bat", gold = 0, color = red, max_hp = 0},
[0x94] = {name = "Big Bat", gold = 0, color = red, max_hp = 16},
[0x95] = {name = "Spawning Enemy"},
--
[0x99] = {name = "Black Crow", gold = 40, color = red, max_hp = 1},
[0x9A] = {name = "Blue Crow", gold = 90, color = red, max_hp = 2},
[0x9B] = {name = "Red Crow", gold = 210, color = red, max_hp = 3},
--
[0x9D] = {name = "Dark Blue Bat", gold = 30, color = red, max_hp = 1},
[0x9E] = {name = "Light Blue Bat", gold = 50, color = red, max_hp = 2},
[0x9F] = {name = "Red Bat", gold = 200, color = red, max_hp = 2},
[0xA0] = {name = "White Bat", gold = 300, color = red, max_hp = 6},
[0xA1] = {name = "Red Bat", gold = 200, color = red, max_hp = 6},
[0xA2] = {name = "Yellow Bee", gold = 100, color = red, max_hp = 1},
[0xA3] = {name = "Red Bee", gold = 200, color = red, max_hp = 2},
[0xA4] = {name = "Light Blue Spider", gold = 80, color = red, max_hp = 2},
[0xA5] = {name = "Dark Blue Spider", gold = 180, color = red, max_hp = 5},
[0xA6] = {name = "Red Spider", gold = 280, color = red, max_hp = 5},
[0xA7] = {name = "Health", color = pink, particle = true},
[0xA8] = {name = "Green Frog", gold = 40, color = red, max_hp = 2},
[0xA9] = {name = "Red Frog", gold = 200, color = red, max_hp = 3},
[0xAA] = {name = "Red Snake", gold = 10, color = red, max_hp = 1},
[0xAB] = {name = "Blue Snake", gold = 40, color = red, max_hp = 2},
[0xAC] = {name = "Green Snake", gold = 180, color = red, max_hp = 3},
[0xAD] = {name = "White Snake", gold = 220, color = red, max_hp = 6},
[0xAE] = {name = "Red Jellyfish", gold = 300, color = red, max_hp = 9},
--
[0xB0] = {name = "Green Potato Bug", gold = 100, color = red, max_hp = 4},
[0xB1] = {name = "White Potato Bug", gold = 240, color = red, max_hp = 9},
[0xB2] = {name = "Red Porcupig", gold = 30, color = red, max_hp = 2},
[0xB3] = {name = "Blue Porcupig", gold = 100, color = red, max_hp = 4},
--
[0xB5] = {name = "Red Troll", gold = 120, color = red, max_hp = 6},
[0xB6] = {name = "Blue Troll", gold = 330, color = red, max_hp = 6},
--
[0xB8] = {name = "Blue Knight", gold = 100, color = red, max_hp = 6},
[0xB9] = {name = "Red Knight", gold = 200, color = red, max_hp = 9},
--
[0xBB] = {name = "Skeleton", gold = 120, color = red, max_hp = 4},
[0xBC] = {name = "Black Skeleton", gold = 330, color = red, max_hp = 9},
[0xBD] = {name = "Blue Mouse", gold = 200, color = red, max_hp = 6},
--
[0xBF] = {name = "Red Mole", gold = 60, color = red, max_hp = 2},
[0xC0] = {name = "Blue Mole", gold = 120, color = red, max_hp = 5},
--
[0xD0] = {name = "Despa", color = red, max_hp = 20},
[0xD1] = {name = "Rolick", color = red, max_hp = 36},
[0xD2] = {name = "Bachular", color = red, max_hp = 40},
[0xD3] = {name = "Fosbus", color = red, max_hp = 66},
[0xD4] = {name = "Warlic", color = red, max_hp = 56},
-- 0xD5 Crawky
-- 0xD6 Haidee
-- 0xD7 Golvellius
[0xD8] = {name = "Dying Boss?", particle = true},
--
[0xDB] = {name = "Projectile", color = yellow, particle = true},
[0xDC] = {name = "Dying Boss?", particle = true},
--
[0xE0] = {name = "Despa Projectile", color = yellow, particle = true},
[0xE1] = {name = "Bachular Projectile", color = yellow, particle = true},
[0xE2] = {name = "Fosbus Projectile", color = yellow, particle = true},
--
[0xE5] = {name = "Giant Snake", gold = 0, color = red, max_hp = 5},
--
[0xEF] = {name = "Projectile", color = yellow, particle = true},
},
["y_position"] = 0x01, -- u8
["x_position"] = 0x02, -- u8
["spawn_timer"] = 0x08, -- u8
["sword_timer"] = 0x0D, -- u8
["health"] = 0x15, -- u8
["damage_timer"] = 0x1F, -- u8
};
-- Map data
local map_base = 0xA00;
local map_width = 0x0F;
local map_height = 0x0C;
function toBinaryString(num, bits) -- TODO: Properly define behavior for negative numbers
if type(num) ~= "number" then
return "0";
end
bits = bits or select(2, math.frexp(num));
local t = {};
for b = bits, 1, -1 do
t[b] = math.fmod(num, 2);
num = (num - t[b]) / 2;
end
return table.concat(t);
end
local floor,insert = math.floor, table.insert
function basen(n,b)
n = floor(n)
if not b or b == 10 then return tostring(n) end
local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local t = {}
local sign = ""
if n < 0 then
sign = "-"
n = -n
end
repeat
local d = (n % b) + 1
n = floor(n / b)
insert(t, 1, digits:sub(d,d))
until n == 0
--return sign .. table.concat(t,"")
return t
end
function getHoleTile()
return mainmemory.readbyte(0xAD2);
end
function getHolePosition()
local holeTile = getHoleTile();
local xTile = holeTile % map_width;
local yTile = math.floor(holeTile / map_width);
return {xTile * 16 + 8, yTile * 16};
end
function renderHolePosition()
if gamemode == "overworld" then -- Don't render hole position in dungeons
local holePosition = getHolePosition();
gui.drawRectangle(holePosition[1], holePosition[2], 16, 16, green, 0x7F000000);
gui.drawText(holePosition[1] + 3, holePosition[2], "H", white, 0x00000000);
end
end
-- Lag Detection
local prevLag = -1;
function detectLag()
local currentLag = mainmemory.readbyte(0x808);
if enableLagDetection and gamemode == "vertical" then -- Only detect lag for vertical dungeons
if currentLag == prevLag then
tastudio.setlag(emu.framecount(), true);
else
tastudio.setlag(emu.framecount(), false);
end
end
prevLag = currentLag;
end
function toHexString(value, desiredLength, prefix)
value = string.format("%X", value or 0);
prefix = prefix or "0x";
desiredLength = desiredLength or string.len(value);
while string.len(value) < desiredLength do
value = "0"..value;
end
return prefix..value;
end
function round(num, idp)
return tonumber(string.format("%."..(idp or 0).."f", num));
end
local mouseClickedLastFrame = false;
local startDragPosition = {0,0};
local draggedObjects = {};
function drawObjects()
local height = 16; -- Text row height
local width = 8; -- Text column width
local mouse = input.getmouse();
local startDrag = false;
local dragging = false;
local dragTransform = {0, 0};
if mouse.Left then
if not mouseClickedLastFrame then
startDrag = true;
startDragPosition = {mouse.X, mouse.Y};
end
mouseClickedLastFrame = true;
dragging = true;
dragTransform = {mouse.X - startDragPosition[1], mouse.Y - startDragPosition[2]};
else
draggedObjects = {};
mouseClickedLastFrame = false;
dragging = false;
end
local row = 0;
for i = object_array_capacity, 0, -1 do
local objectBase = object_array_base + (i * object_size);
local objectType = mainmemory.readbyte(objectBase + object_fields.object_type);
local objectTypeNumeric = objectType;
local objectTypeTable = nil;
local color = nil;
if objectType ~= 0 then
-- Default to 16 width/height for hitbox
local hitboxWidth = 16;
local hitboxHeight = 16;
-- Get the X and Y position of the object
local xPosition = mainmemory.readbyte(objectBase + object_fields.x_position);
local yPosition = mainmemory.readbyte(objectBase + object_fields.y_position);
local hp = mainmemory.readbyte(objectBase + object_fields.health);
local maxHP = "?";
local goldOnKill = -1;
local isParticle = false;
if type(object_fields.object_types[objectType]) == "table" then
objectTypeTable = object_fields.object_types[objectType];
if type(objectTypeTable.name) == "string" then
objectType = object_fields.object_types[objectType].name.." "..toHexString(objectType);
else
objectType = "Unknown ("..toHexString(objectType)..")";
end
if type(objectTypeTable["color"]) == "number" then
color = objectTypeTable["color"];
end
if type(objectTypeTable.hitbox_x_offset) == "number" then
hitboxXOffset = objectTypeTable.hitbox_x_offset;
end
if type(objectTypeTable.hitbox_y_offset) == "number" then
hitboxYOffset = objectTypeTable.hitbox_y_offset;
end
if type(objectTypeTable.hitbox_width) == "number" then
hitboxWidth = objectTypeTable.hitbox_width;
end
if type(objectTypeTable.hitbox_height) == "number" then
hitboxHeight = objectTypeTable.hitbox_height;
end
if type(objectTypeTable.gold) == "number" then
goldOnKill = objectTypeTable.gold;
end
if type(objectTypeTable.max_hp) == "number" then
maxHP = objectTypeTable.max_hp;
end
isParticle = type(objectTypeTable.particle) ~= "nil" and objectTypeTable.particle;
else
color = white;
objectType = "Unknown ("..toHexString(objectType)..")";
end
local hitboxXOffset = -(hitboxWidth / 2);
local hitboxYOffset = -(hitboxHeight / 2);
if showHitbox then
if enableDragAndDrop and dragging then
for d = 1, #draggedObjects do
if draggedObjects[d][1] == objectBase then
xPosition = draggedObjects[d][2] + dragTransform[1];
yPosition = draggedObjects[d][3] + dragTransform[2];
mainmemory.writebyte(objectBase + object_fields.x_position, xPosition);
mainmemory.writebyte(objectBase + object_fields.y_position, yPosition);
break;
end
end
end
if (mouse.X >= xPosition + hitboxXOffset and mouse.X <= xPosition + hitboxXOffset + hitboxWidth) and (mouse.Y >= yPosition + hitboxYOffset and mouse.Y <= yPosition + hitboxYOffset + hitboxHeight) then
if startDrag then
table.insert(draggedObjects, {objectBase, xPosition, yPosition});
end
local goldString = "";
if goldOnKill > 0 then
goldString = " "..goldOnKill.."G";
end
local mouseOverText = {
objectType.." "..hp.."/"..maxHP.." HP",
toHexString(objectBase).." "..xPosition..","..yPosition..goldString,
};
local maxLength = -math.huge;
for t = 1, #mouseOverText do
maxLength = math.max(maxLength, string.len(mouseOverText[t]));
end
local safeX = math.min(xPosition + hitboxXOffset, 256 - (maxLength * width));
local safeY = math.min(yPosition + hitboxYOffset, 192 - (#mouseOverText * height));
for t = 1, #mouseOverText do
gui.drawText(safeX, safeY + ((t - 1) * height), mouseOverText[t], color);
end
else
if objectTypeNumeric == 0x95 then -- Spawning enemy should show countdown to spawn
gui.drawText(xPosition + hitboxXOffset, yPosition + hitboxYOffset, mainmemory.readbyte(objectBase + object_fields.spawn_timer), gold);
elseif objectTypeNumeric == 0x84 then -- Sword should show Sword Timer
gui.drawText(xPosition + hitboxXOffset, yPosition + hitboxYOffset, mainmemory.readbyte(objectBase + object_fields.sword_timer), gold);
elseif (not alwaysHP) and goldOnKill > 0 then
gui.drawText(xPosition + hitboxXOffset, yPosition + hitboxYOffset, ""..goldOnKill, gold);
elseif objectBase ~= 0x100 and not isParticle then -- Everyone without a gold value should show their current/max HP (except the player)
local damageTimerString = "";
if enableDamageTimer then
local damageTimer = mainmemory.readbyte(objectBase + object_fields.damage_timer);
if damageTimer > 0 then
damageTimerString = " "..damageTimer;
end
end
gui.drawText(xPosition + hitboxXOffset, yPosition + hitboxYOffset, hp.."/"..maxHP..damageTimerString, gold);
end
end
gui.drawRectangle(xPosition + hitboxXOffset, yPosition + hitboxYOffset, hitboxWidth, hitboxHeight, color); -- Draw the object's hitbox
end
if showList then
local goldString = " ";
if goldOnKill > 0 then
goldString = " - "..goldOnKill.."G - ";
end
gui.text(2, 2 + height * row, xPosition..", "..yPosition.." - "..hp.."/"..maxHP.." HP - "..objectType..goldString..toHexString(objectBase), color, 'bottomright');
row = row + 1;
end
end
end
end
function getGold()
local hundred_thousands = toHexString(mainmemory.readbyte(0x83F), 2, ""); -- 100000s
local thousands = toHexString(mainmemory.readbyte(0x840), 2, ""); -- 1000s
local tens = toHexString(mainmemory.readbyte(0x841), 2, ""); -- 10s
return hundred_thousands..thousands..tens.."0";
end
function getScreen()
if gamemode == "vertical" then
return toHexString(mainmemory.readbyte(0x808), 2, "");
end
return toHexString(mainmemory.readbyte(0x809), 2, "");
end
function drawOSD()
-- Detect game mode
checkEndFrame()
local playerType = mainmemory.readbyte(0x100);
if playerType == 0x5E or playerType == 0x81 then
gamemode = "overworld";
elseif playerType == 0x4C or playerType == 0x83 then
gamemode = "vertical";
elseif playerType == 0x59 or playerType == 0x82 then
gamemode = "horizontal";
end
if gamemode == "vertical" then
gui.drawText(197, 1, getScreen().." ScrY", 0xFF000000, 0);
else
if gamemode == "horizontal" then
gui.drawRectangle(156, 15, 92, 13, 0, 0x7F000000);
gui.drawText(156, 14, getScreen().." Screen X", gold, 0);
end
gui.drawRectangle(156, 2, 92, 13, 0, 0x7F000000);
gui.drawText(156, 1, getGold().." Gold", gold, 0);
end
detectLag();
renderHolePosition();
drawObjects();
end
function table.copy(orig)
local copy = {};
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value;
end
return copy;
end
xspacing = 60
yspacing = 25
inity = 10
initx = 50
width = 50
height= 20
buttonWidth = 100
buttonHeight = 20
buttonX = initx+2*xspacing
buttonY = inity
local formhandle = forms.newform(320, 200, "GAW");
local enemy_address_1 = forms.textbox(formhandle, "", width, height, 1, initx, inity)
local enemy_address_2 = forms.textbox(formhandle, "", width, height, 1, initx, inity+yspacing)
local enemy_address_3 = forms.textbox(formhandle, "", width, height, 1, initx, inity+2*yspacing)
local enemy_address_4 = forms.textbox(formhandle, "", width, height, 1, initx, inity+3*yspacing)
local enemy_address_5 = forms.textbox(formhandle, "", width, height, 1, initx, inity+4*yspacing)
local enemy_address_6 = forms.textbox(formhandle, "", width, height, 1, initx+1*xspacing, inity+0*yspacing)
local enemy_address_7 = forms.textbox(formhandle, "", width, height, 1, initx+1*xspacing, inity+1*yspacing)
local enemy_address_8 = forms.textbox(formhandle, "", width, height, 1, initx+1*xspacing, inity+2*yspacing)
local enemy_address_9 = forms.textbox(formhandle, "", width, height, 1, initx+1*xspacing, inity+3*yspacing)
local enemy_address_10 = forms.textbox(formhandle, "", width, height, 1, initx+1*xspacing, inity+4*yspacing)
local startFrameText = forms.textbox(formhandle, "", width, height, 1, initx+2*xspacing, inity+3*yspacing)
local endFrameText = forms.textbox(formhandle, "", width, height, 1, initx+3*xspacing, inity+3*yspacing)
function testObjects()
local obj = {};
obj.address = {}
obj.tile = {}
obj.address[1] = toHexString(tonumber(forms.gettext(enemy_address_1),16))
obj.address[2] = toHexString(tonumber(forms.gettext(enemy_address_2),16))
obj.address[3] = toHexString(tonumber(forms.gettext(enemy_address_3),16))
obj.address[4] = toHexString(tonumber(forms.gettext(enemy_address_4),16))
obj.address[5] = toHexString(tonumber(forms.gettext(enemy_address_5),16))
obj.address[6] = toHexString(tonumber(forms.gettext(enemy_address_6),16))
obj.address[7] = toHexString(tonumber(forms.gettext(enemy_address_7),16))
obj.address[8] = toHexString(tonumber(forms.gettext(enemy_address_8),16))
obj.address[9] = toHexString(tonumber(forms.gettext(enemy_address_9),16))
obj.address[10] = toHexString(tonumber(forms.gettext(enemy_address_10),16))
for j = 1,1 do -- change to 10 for multiple
if tonumber(obj.address[j],16) ~= 0 then
local xObj = mainmemory.readbyte(obj.address[j] + object_fields.x_position);
local yObj = mainmemory.readbyte(obj.address[j] + object_fields.y_position)
if (xObj == 176 and yObj == 152) then
anyValid = true
end
end
end
--print(validArrays)
--print(anyValid)
end
function checkEndFrame()
startFrame = tonumber(forms.gettext(startFrameText))
endFrame = tonumber(forms.gettext(endFrameText))
currentFrame = emu.framecount()
if currentFrame == startFrame and simulating == false then
savestate.saveslot(9)
end
if currentFrame == endFrame and simulating == false then
simulating = true
savestate.saveslot(0)
playerX = mainmemory.read_u8(0x0102)
playerY = mainmemory.read_u8(0x0101)
if startFrame ~= nil then
inputs = {}
noInputs = 0
for frames = 1,endFrame-startFrame+1 do
inputs[frames] = movie.getinput(startFrame+frames-1)
if inputs[frames]["P2 Up"] == true then
changeInputs[frames] = true
noInputs = noInputs+1
else
changeInputs[frames] = false
end
end
print(changeInputs)
z = {}
for u = 1,noInputs do
z[u] = 0
end
end
currentX = mainmemory.read_u8(0x0313)
currentY = mainmemory.read_u8(0x0311)
movie.setreadonly(false)
savestate.loadslot(9)
testFrames = 1
noChanged = 1
if currentUp == true then
modify[1] = "P1 Up"
modify[2] = "P1 Left"
modify[3] = "P1 Right"
print("up")
elseif currentDown == true then
modify[1] = "P1 Down"
modify[2] = "P1 Left"
modify[3] = "P1 Right"
print("down")
elseif currentLeft == true then
modify[1] = "P1 Left"
modify[2] = "P1 Up"
modify[3] = "P1 Down"
print("left")
else
modify[1] = "P1 Right"
modify[2] = "P1 Up"
modify[3] = "P1 Down"
print("right")
end
end
if simulating == true then
startOffset = currentFrame-startFrame+1
currentInput = table.copy(inputs[startOffset])
t = basen(testFrames,3)
zc = table.copy(z)
diff = table.getn(zc)-table.getn(t)
for u = 1,table.getn(t) do
zc[diff+u] = t[u]
end
if changeInputs[startOffset] == true then
if zc[noChanged] == "1" then
currentInput[modify[1]] = false
currentInput[modify[2]] = true
elseif zc[noChanged] == "2" then
currentInput[modify[1]] = false
currentInput[modify[3]] = true
else
currentInput[modify[1]] = true
end
noChanged = noChanged+1
end
joypad.set(currentInput)
if currentFrame == endFrame then
testObjects()
if anyValid == true then
print("Success!")
simulating = false
movie.save()
movie.setreadonly(true)
else
if testFrames >= 3^noChanged then
print("Fail")
simulating = false
movie.setreadonly(true)
else
testFrames = testFrames+1
--print("testframes"..testFrames)
savestate.loadslot(9)
noChanged = 1
movie.setrerecordcount(movie.getrerecordcount()+1)
end
end
end
end
end
local button_testObjects = forms.button(formhandle, "Test", testObjects, buttonX, buttonY+4*yspacing, buttonWidth, buttonHeight);
event.onframestart(drawOSD);
event.onloadstate(drawOSD);