Download the latest version here!
Quick instructions
As per DarkKobold's request, I'm including a basic guide at the top of this post. These are just bare-bones instructions to get you started. The full technical description and detailed instructions are after all these purdy pictures.
Introduction
If you're anything like me, you've encountered some difficulties with emulators' native RAM Search functions. Although the built-in RAM Search offers precision and comparative ease when you know just what to look for, oftentimes the RAM address you seek is somewhat ill-defined. Everyone has experience narrowing down a RAM address until the search has to be reset or, even worse, making some small mistake that cannot be undone. That is why FatRatKnight and I are proud to present Smart RAM Searching. This Lua script aims to make RAM searching intuitive, internally intelligent, and externally idiot-proof.
The mathematical premise is simple if you have some vector algebra under your belt. We take a user's mouse movements with each frame as the input vector, let's call it
v. This vector and vectors representing all RAM addresses (denote them by
w) for each frame are then detrended (their average value subtracted) and normalized (divided by their magnitude). The program then takes the dot product between the normalized input vector and the normalized RAM address vectors. Its output is a list of RAM addresses that maximize the magnitude of this dot product, since
v.w=|v|*|w|*cos(theta)
=cos(theta) (for unit vectors)
where the period denotes the dot product. The closer cos^2(theta) is to 1, the closer the user's input is to being just a multiple of the RAM address's value. Incidentally, this method
also minimizes the "residual sum of squares" (the sum of the squares of the differences between the two vectors).
Why use this program?
This program has several advantages over the default RAM Search function:
•
It is insensitive to the address's range. It does not matter whether the address's value varies between 0 and 10, 197 and 235, or 0 and 256. As long as the user faithfully tracks its contour, the script will pick out the best match.
•
It is insensitive to sign differences. This is especially useful for tracking things like the y coordinate. Oftentimes, the y coordinate
decreases with increasing height.
•
It is relatively insensitive to small human errors. Because the user's input is qualitative, small errors (such as misplacing the mouse for a single frame) will not appreciably affect the program's output. Note that the same applies to the RAM address itself, which may (for whatever reason) fluctuate in a manner inconsistent with what the user perceives.
This program also has some disadvantages compared with the default RAM Search function:
•
It cannot find RAM addresses whose values are constant. This is because the vectors are detrended. On the other hand, if the value is constant, why would you even attempt to use this script instead of using the default RAM Search or, better yet, just writing the value down?
•
It offers no real advantages if the value in question is known. For example, if enemy HP is displayed on the screen, that value can simply be input into RAM Search to efficiently find its address.
•
It is slow. In its current implementation, this script typically takes a few seconds to execute a RAM search in FCEUX if the user's input is about 100 frames. That is because it must compare the user's input with every RAM address for every frame. The script is best used with FCEUX or Game Boy games in VBA, both of which have comparatively less RAM than other systems. FatRatKnight and I are working on some pre-filtering options that will make this program run much faster and allow it to be used efficiently in other emulators.
Current features
• A vertical bar to accept user input. Clicking the bar while frame advancing generates the input vector.
• Shift-clicking to move the bar.
• Resize boxes above and below the bar.
• GUI output that displays top-candidate RAM addresses, their "score" (cos
2(theta)), and their current value.
• Cross-compatibility with several emulators. The script has been tested to work in VBA, FCEUX, and SNES9x. It is probably also compatible with Gens.
• Excluded mirror values. Many RAM addresses have mirrored values. Including them wastes processing power and clutters up the GUI output.
• Pretty rainbow colors in the input bar. (Does not work in FCEUX.)
Features to be implemented
Because FatRatKnight and I are working on different aspects of the script, I will split the improvements into technical and GUI categories.
Technical improvements:
• Pre-filtering. This currently falls under two categories:
- Change pre-filtering. This would be similar to the default RAM Search's change count feature. It would allow the user to exclude addresses that have or haven't changed, according to what they expect from the address they're searching for.
- Dot product pre-filtering. This would work exactly like the program's basic function except that addresses that aren't a sufficiently good match would be strictly excluded from future searches. This could be set by a number of different methods: keep the top
x% of matches, keep the top
n matches, or keep only matches whose score is above a certain threshold.
• Downsampling. This means recording the RAM addresses only once every
n frames.
• Derivatives. Sometimes you do not know whether the value you are looking for is a function or its derivative. An example of this (and one of the things that inspired me to work on this script) is the hilly terrain in the racing game Skitchin': Is it stored as a height for graphical purposes or as a slope, which affects the player's acceleration directly?
• Time lag. Sometimes there is a delay between what the user sees on the screen and what the RAM address is doing internally. In these instances, we want to compare the two vectors offset by some time delay (with a normalization factor thrown in). This is known as the cross-correlation. It is very computationally intensive, however, and cannot reasonably be implemented without pre-filtering. Also, the cross-correlation is most quickly computed by taking the discrete Fourier transform of the vectors, although it may only be faster to do so if there is an especially large time lag.
• Words and other series of bytes. Sometimes we're not looking for a single byte. Endianness should also be considered.
• Wrap-around. For periodic variables (e.g., the position of fire bars in Super Mario Bros. or the progression of the music), the value can jump from at or near 255 back to 0 (or vice versa). The user would have to set a threshold below which wrap-around would be applied.
• Signed values. Currently, all values are presumed unsigned.
• Expanded compatibility. This script only works with 8- and 16-bit consoles, as we do not yet know the memory map of other consoles.
• An improved sorting algorithm. For ease of programming and getting this script published promptly, an insertion sort was implemented. This should be changed to a merge sort or other sort that executes in O(
n*log(n)).
GUI improvements:
• Graphical tie-in of the above-mentioned improvements.
• Options to change the vertical input bar into a horizontal bar. A circle could also be used.
• A menu system that will complement keyboard shortcuts for features.
• Arrows signifying the resize buttons.
• Context-sensitive cursor icons.
• Text boxes for user input.
• Customizable output, including less or more information and fewer or more matches. For long lists of matches, the user should also be able to scroll through results.
• Continuing to gather user input after the cursor leaves the bar.
• Manual override of game system and/or screen resolution. GBx, SGB, and GBA games all use different screen resolutions and there seems to be no easy way to differentiate between which game is which in Lua. The same goes for PSX games, which use several different resolutions. Sega games may use different resolutions and they also have different memory maps depending on the system, according to marzojr. The user should be able to toggle between pre-set screen resolutions.
• Fix the slowdown that occurs when results are displayed in Gens (and other emulators?).
We also wish to further break the script up into smaller functions for ease of editing.
How to use the script
Although the script is designed to be very intuitive, there are a few basic instructions. First, download all the scripts posted below and place them in the same directory. Run the script SmartRAMSearch.lua in the emulator of your choice (preferably FCEUX or VBA). Either immediately or after frame advancing once, a green rectangle (the user input bar) should appear. This bar can be moved by holding the shift key and then clicking and dragging it. It can also be resized by clicking and dragging the boxes above and below it.
Do something in the game that will affect the RAM address you are looking for in a predictable way. (It is suggested you make a movie and work from that.) While the RAM address is changing, click and hold the bar and then frame advance. Continue to frame advance while the mouse is still held. As you frame advance, move the mouse up and down with the contour you think the RAM address's value is following. For example, if you wish to know the RAM address corresponding to Mario's height in Super Mario Bros., just follow him with your cursor while you execute a jump. Input will not be accepted if your cursor leaves the bar.
Once you have collected data for many frames (a few dozen to a few hundred), press the spacebar to have the program calculate the closest-matching RAM addresses. To display these addresses, press the "home" key. The first column contains the RAM addresses, sorted by how closely they matched with your input, the second column contains their "score", which is just cos^2(theta) times 10,000, and the third column contains their current values.
What we need from you
• Your encouragement.
• Your constructive criticism.
• Your suggestions.
• Your success stories.
If this topic stagnates, then we will assume there is no demand for this script and stop updating it.
The scripts
As mentioned above, you should download these scripts to the same directory. Open the game of your choice and run "SmartRAMSearch.lua".
Download BasicFunctions.luaLanguage: lua
function boolto10(boole)
if boole then
return 1
else
return 0
end
end
Download VectorOps.luaLanguage: lua
function getaverage(vector)
local total=0
local n=0
for k,v in pairs(vector) do
total=total+v
n=n+1
end
local average=total/n
return average
end
function detrend(vector)
local average=getaverage(vector)
for k,v in pairs(vector) do
vector[k]=v-average
end
return vector
end
function dotproduct(v1,v2)
local total=0
for k,v in pairs(v1) do
total=total+v*v2[k]
end
return total
end
function getmag(vector)
local magsquared=dotproduct(vector,vector)
local magnitude=math.sqrt(magsquared)
return magnitude
end
function normalize(vector)
local magnitude=getmag(vector)
if magnitude>0 then
for k,v in pairs(vector) do
vector[k]=v/magnitude
end
end
return vector
end
Download Sorts.luaLanguage: lua
--This function executes a selection sort on a vector, finding the best N values and organizing them into a table.
--Note that the computation time is O(n^2). For efficiency, consider merge sort, whose speed is O(n*log(n)).
--The advantage of selection sort is that it's easy to program and is probably still quite efficient as long as N is small.
function selectionsort(vector,N)
local sorted={}
for i=1,N do
sorted[i]={-1,0}
for k,v in pairs(vector) do
if v>sorted[i][2] then
sorted[i]={k,v}
end
end
vector[sorted[i][1]]=0
end
return sorted
end
--Insertion sort provided by FatRatKnight.
SortedTable={}
function InsertVal(v)
local n=#SortedTable+1
while n>1 do
if v<SortedTable[n-1] then break end
SortedTable[n]=SortedTable[n-1]
n=n-1
end
SortedTable[n]=v
end
Download YummyButton.luaLanguage: lua
--[[
Welcome to my Buttons script!
To start, put in the following lines:
require("thisScript")
NewBtn{x= 1, y= 1, fn= function()
((Various code here))
end}
And all of a sudden, you have this bluish button on the top left corner of
the emulator window, once you run the script. Use the mouse to click the
button. The code you put in will happen.
Now, there are ways to configure these buttons to your desires. For one thing,
make as many NewBtn objects as you like. This script will keep track of 'em.
For another, change that x and y up there if you don't like the NewBtn being
stuck in the top-left corner. Finally, there's various options you can use to
tweak your NewBtn desires.
REQUIRED:
x - X position. Where is the button horizontally?
y - Y position. Where do we put it vertically?
fn - Function. The button had better do something!!
Optional:
text - Cute text to display on button. May size button automatically.
x1 - Identical to the otherwise required x. If both present, x has priority
x2 - The right side of the button
y1 - Identical to y. y1 can take y's place, but if you make both, y is used
y2 - The bottom side of the button
width - Width of button. Would interfere with x2, but x2 takes priority.
height- Height of button. Use if you don't feel like using y2.
color - General color scheme of the button. This affects all aspects of color.
MBC - Main Border Color. "Main" is when your mouse isn't over the button.
MFC - Main Fill Color. ... I use "Main" for lack of better words...
SBC - Sub Border Color. "Sub" is used when you're mousing over the button.
SFC - Sub Fill Color. Anyone have better words for me?
tag - Spare variable.
Included functions:
draw - Draws the button. Don't bother using it, it's automatically called.
recolor- Modifies the color scheme. Takes in one value depicting base color.
move - Moves the button so that the top-left corner matches the x,y input.
While it is packaged for simple use as is, the more savvy programmers may want
to read on.
I pass the button object to the clicked button function as the only parameter.
I can't think of any real way of passing variables you want for it, but I
added the "tag" variable for this purpose. Technically, after the button is
constructed, you can add new parameters to the button object itself, but the
"tag" variable seemed like a convenient thing to add to the constructor.
In addition, I put the HandleBtns function in gui.register by default, but go
ahead and call it in another function and replace the gui.register callback.
This function will return whatever the button function returns, so if it is in
your interest to have the button function return something, it will do so
through _FRK_Btn.HandleBtns.
The NewBtn constructor returns the button object it created as a table for you
to mess around with, should you have a reason to. Maybe you want to move it
around or change its colors, who knows.
Finally, _FRK_Btn.MB holds all my buttons. You can, in the middle of creating
new buttons, set another variable (MB2, perhaps?), set _FRK_Btn.MB = {}, then
create more buttons. You'll have two different sets of buttons which you can
switch between, but you'll have to make your own functions to do the switch.
But if you're not experienced in lua programming, none of the previous four
paragraphs are of any interest to you. The simple use of these buttons are
good enough for many uses.
]]--
_FRK_Btn= {}
local EMUCHWID= 6
local EMUCHHGT= 8
if stylus then EMUCHWID= 6; EMUCHHGT= 8
elseif snes9x then EMUCHWID= 4; EMUCHHGT= 6
elseif VBA then EMUCHWID= 4; EMUCHHGT= 6
end
_FRK_Btn.ClickType= "leftclick"
_FRK_Btn.MB= {}
keys, lastkeys= {}, {}
function UpdateKeys() lastkeys= keys; keys= input.get() end
function Press(k) return keys[k] and not lastkeys[k] end
--*****************************************************************************
function _FRK_Btn.FindBtn()
--*****************************************************************************
if (keys.xmouse == lastkeys.xmouse) and
(keys.ymouse == lastkeys.ymouse) then return end
local X,Y= keys.xmouse, keys.ymouse
for i= 1, #_FRK_Btn.MB do -- linear scan, darn it!
local n= _FRK_Btn.MB[i]
if (Y >= n.y1) and (Y <= n.y2)
and (X >= n.x1) and (X <= n.x2) then return i end
end
return -1
end
--*****************************************************************************
function _FRK_Btn.ClickBtn(id)
--*****************************************************************************
if id and _FRK_Btn.MB[id] then
return _FRK_Btn.MB[id]:fn()
end
end
--*****************************************************************************
function _FRK_Btn.DrawUnsel(B)
--*****************************************************************************
gui.box(B.x1, B.y1, B.x2, B.y2, B.MFC, B.MBC)
gui.text(B.xt, B.yt, B.text)
end
--*****************************************************************************
function _FRK_Btn.DrawSel(B)
--*****************************************************************************
gui.box(B.x1, B.y1, B.x2, B.y2, B.SFC, B.SBC)
gui.text(B.xt, B.yt, B.text)
end
--*****************************************************************************
function _FRK_Btn.ShowBtns()
--*****************************************************************************
B= _FRK_Btn.MB
for i= 1, #B do
B[i]:draw()
end
end
--*****************************************************************************
function _FRK_Btn.Recolor(B,color)
--*****************************************************************************
-- Does not like strings. Still will accept them, however.
local t= type(color)
if t == "string" then -- Uh, no. No tricky color stuff. Sorry.
B.MBC= Cns.color
B.SBC= Cns.color
B.MFC= 0x00000040
B.SFC= 0xC0C0C0C0
--Oh, not a string! Better be table or number, please.
else
if t == "table" then
color= 0
local q= color.r or color[1]
if q then color= bit.lshift(q,24) end
q= color.g or color[2]
if q then color= bit.bor(color,bit.lshift(q,16)) end
q= color.b or color[3]
if q then color= bit.bor(color,bit.lshift(q, 8)) end
q= color.a or color[4]
if q then color= bit.bor(color,q)
else color= bit.bor(color,0xFF) end
end
B.MBC= color
B.MFC= bit.rshift(bit.band(color,0xFEFEFEFE),1)
B.SBC= bit.bor(color+bit.band(0xFFFFFFFF-color,0xFEFEFEFE)/2,0xFF)
B.SFC= color
end
end
--*****************************************************************************
function _FRK_Btn.Move(B, x,y)
--*****************************************************************************
-- Moves the whole button for you!
if x then
local XDiff= x - B.x1
B.x1= x
B.x2= B.x2 + XDiff
B.xt= B.xt + XDiff
end
if y then
local YDiff= y - B.y1
B.y1= y
B.y2= B.y2 + YDiff
B.yt= B.yt + YDiff
end
end
--*****************************************************************************
function _FRK_Btn.NewBtn(Cns)
--*****************************************************************************
-- the button constructor!
--Insert universal function pointers while creating a new table.
local NewTbl= {
draw= _FRK_Btn.DrawUnsel,
recolor= _FRK_Btn.Recolor,
move= _FRK_Btn.Move
}
--Insert presumed information.
NewTbl.fn= Cns.fn
NewTbl.x1= Cns.x or Cns.x1
NewTbl.y1= Cns.y or Cns.y1
NewTbl.tag= Cns.tag
--Check if user defined the right side.
if Cns.x2 then
NewTbl.x2= math.max(NewTbl.x1,Cns.x2)
NewTbl.x1= math.min(NewTbl.x1,Cns.x2)
elseif Cns.width then
NewTbl.x2= NewTbl.x1 + Cns.width
end
--Check if user defined the bottom side.
if Cns.y2 then
NewTbl.y2= math.max(NewTbl.y1,Cns.y2)
NewTbl.y1= math.min(NewTbl.y1,Cns.y2)
elseif Cns.height then
NewTbl.y2= NewTbl.y1 + Cns.height
end
--Look for text. Do tricky stuff based on where to put the darn text or
--how to resize the button.
if Cns.text then
NewTbl.text= tostring(Cns.text)
local len= #NewTbl.text * EMUCHWID
if NewTbl.x2 then
NewTbl.xt= math.floor((NewTbl.x1 + NewTbl.x2 - len)/2)
else
NewTbl.x2= NewTbl.x1 + len + 3
NewTbl.xt= NewTbl.x1 + 2
end
if NewTbl.y2 then
NewTbl.yt= math.floor((NewTbl.y1 + NewTbl.y2 - 8)/2)
else
NewTbl.y2= NewTbl.y1 + EMUCHHGT + 4
NewTbl.yt= NewTbl.y1 + 2
end
else
NewTbl.text= ""
NewTbl.xt= NewTbl.x1
NewTbl.yt= NewTbl.y1
if not NewTbl.x2 then NewTbl.x2= NewTbl.x1 + 8 end
if not NewTbl.y2 then NewTbl.y2= NewTbl.y1 + 8 end
end
--Mess with colors!
if Cns.color then
NewTbl:recolor(Cns.color)
else
NewTbl.MBC= Cns.MBC or 0x0020FFC0
NewTbl.MFC= Cns.MFC or 0x00000040
NewTbl.SBC= Cns.SBC or 0x0080FFFF
NewTbl.SFC= Cns.SFC or 0x2000C0C0
end
--Finally add the button to our MB array.
local index= #(_FRK_Btn.MB)+1
_FRK_Btn.MB[index]= NewTbl
--Here's the button object in case you want to handle it yourself.
return NewTbl
end
local ID= -1
--*****************************************************************************
function _FRK_Btn.HandleBtns()
--*****************************************************************************
UpdateKeys()
local id= _FRK_Btn.FindBtn()
if id then
if ID ~= -1 then
_FRK_Btn.MB[ID].draw= _FRK_Btn.DrawUnsel
end
if id ~= -1 then
_FRK_Btn.MB[id].draw= _FRK_Btn.DrawSel
end
ID= id
end
_FRK_Btn.ShowBtns()
if Press(_FRK_Btn.ClickType) and (ID ~= -1) then
return _FRK_Btn.ClickBtn(ID)
end
end
--=============================================================================
if not FRK_NoAuto then
--=============================================================================
gui.register(_FRK_Btn.HandleBtns)
NewBtn= _FRK_Btn.NewBtn
end
Download bit.luaLanguage: lua
--[[---------------
LuaBit v0.4
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
How to use:
-------------------
bit.bnot(n) -- bitwise not (~n)
bit.band(m, n) -- bitwise and (m & n)
bit.bor(m, n) -- bitwise or (m | n)
bit.bxor(m, n) -- bitwise xor (m ^ n)
bit.brshift(n, bits) -- right shift (n >> bits)
bit.blshift(n, bits) -- left shift (n << bits)
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
Please note that bit.brshift and bit.blshift only support number within
32 bits.
2 utility functions are provided too:
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
-- high bits first
bit.tonumb(bit_tbl) -- convert a bit table into a number
-------------------
Under the MIT license.
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
--]]---------------
do
------------------------
-- bit lib implementions
local function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) then
error("trying to use bitwise operation on non-integer!")
end
end
local function to_bits(n)
check_int(n)
if(n < 0) then
-- negative
return to_bits(bit.bnot(math.abs(n)) + 1)
end
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = math.mod(n,2)
if(last == 1) then
tbl[cnt] = 1
else
tbl[cnt] = 0
end
n = (n-last)/2
cnt = cnt + 1
end
return tbl
end
local function tbl_to_number(tbl)
local n = table.getn(tbl)
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*power
power = power*2
end
return rslt
end
local function expand(tbl_m, tbl_n)
local big = {}
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_m
small = tbl_n
else
big = tbl_n
small = tbl_m
end
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0
end
end
local function bit_or(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_and(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_not(n)
local tbl = to_bits(n)
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_xor(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) then
tbl[i] = 1
else
tbl[i] = 0
end
end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
end
local function bit_rshift(n, bits)
check_int(n)
local high_bit = 0
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
end
for i=1, bits do
n = n/2
n = bit_or(math.floor(n), high_bit)
end
return math.floor(n)
end
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n/2
end
return math.floor(n)
end
local function bit_lshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n*2
end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
end
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
end
--------------------
-- bit lib interface
bit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
rshift = bit_rshift,
lshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,
-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}
end
--[[
for i = 1, 100 do
for j = 1, 100 do
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
error("bit.xor failed.")
end
end
end
--]]
Download SmartRAMSearch.luaLanguage: lua
--Certain parameters are emulator-specific.
--In particular, we need to know the range of relevant RAM addresses and place the bar where it is visible within the window.
local function getsystem()
--These are the default values if the emulator isn't specified. Watch out! It will take a very long time to execute and you may scroll the bar off the screen!
byterange={ {0x000000,0x1FFFFF}}
L,U,R,D= 180,50,189,149
SCRBOTTOM,SCRRIGHT=480,640
if FCEU then --NES
byterange={ {0x0000,0x07FF},
{0x2000,0x2008},
{0x4000,0x7FFF}}
L,U,R,D= 180,50,189,149
SCRBOTTOM,SCRRIGHT=239,255
elseif vba then --GBx
byterange={ {0x8000,0xDFFF},
{0xFF80,0xFFFF}}
L,U,R,D= 140,10,149,109
SCRBOTTOM,SCRRIGHT=143,159
elseif snes9x then --SNES
byterange={ {0x7E0000,0x7FFFFF}}
L,U,R,D= 180,50,189,149
SCRBOTTOM,SCRRIGHT=222,255
elseif gens then --Genesis (and similar)
byterange={ {0xFF0000,0xFFFFFF}}
L,U,R,D= 180,50,189,149
SCRBOTTOM,SCRRIGHT=223,319
emu=gens
dofile("bit.lua")
elseif pcsx then --PlayStation
byterange={ {0x000000,0x1FFFFF}}
L,U,R,D= 180,50,189,149
SCRBOTTOM,SCRRIGHT=480,640
elseif stylus then --DS
print("Compatibility pending...")
else --Emulator not recognized
print("Error: What emulator are you using?")
end
end
--Initialize this stuff...
dofile("BasicFunctions.lua")
dofile("VectorOps.lua")
dofile("Sorts.lua")
getsystem()
local mindex=0
range={}
for i=1,#byterange do
for j=byterange[i][1],byterange[i][2] do
range[mindex]=j
mindex=mindex+1
end
end
--range=0x7FFF
addresses={}
for i = 1,#range do
addresses[range[i]]={}
end
X={}
costhetasq={}
--Converts a hue angle to RGB. Normalized to the range [0,255].
local function huetoRGB(hueangle,alpha)
local R,G,B=0,0,0
if not(alpha) then alpha=0x7F end --Alpha defaults to 0x7F.
hueangle=hueangle%360
local huefraction=hueangle/60-math.floor(hueangle/60) --This is a sawtooth function
if hueangle>=0 and hueangle<60 then
R,G,B=1,huefraction,0
elseif hueangle>=60 and hueangle<120 then
R,G,B=1-huefraction,1,0
elseif hueangle>=120 and hueangle<180 then
R,G,B=0,1,huefraction
elseif hueangle>=180 and hueangle<240 then
R,G,B=0,1-huefraction,1
elseif hueangle>=240 and hueangle<300 then
R,G,B=huefraction,0,1
elseif hueangle>=300 and hueangle<360 then
R,G,B=1,0,1-huefraction
end
R,G,B=math.floor(0xFF*R),math.floor(0xFF*G),math.floor(0xFF*B)
return {R,G,B,alpha} --Output is in table form.
end
--#############################################################################
--#############################################################################
local BestTable= false
local function CalcNow()
for i,v in pairs(addresses) do
addresses[i]=detrend(addresses[i])
addresses[i]=normalize(addresses[i])
end
X=detrend(X)
X=normalize(X)
for i,v in pairs(addresses) do
costhetasq[i]=dotproduct(X,addresses[i])^2
end
BestTable=selectionsort(costhetasq,20)
for i=1,#BestTable do
BestTable[i][3]=memory.readbyte(BestTable[i][1])
end
end
local function GetData(UserInput)
for i,v in pairs(addresses) do
addresses[i][emu.framecount()]=memory.readbyte(i)
end
X[emu.framecount()]=UserInput
end
local function NullFn() end -- The "Do Nothing" function.
--#############################################################################
--#############################################################################
--*****************************************************************************
local function InBox(Lf,Up,Rt,Dn , x,y)
--*****************************************************************************
-- Just seeing if the chosen pixel resides within the chosen boundaries.
-- Does not like it if the "box" you specify has reversed x- or y-axis.
return (x >= Lf) and (x <= Rt) and (y >= Up) and (y <= Dn)
end
--*****************************************************************************
local Draw= {}
--*****************************************************************************
--Coordinates is the top-left pixel of the 3x5 digit.
--Used for drawing compact, colored numbers.
local Px,Li= gui.pixel, gui.line
Draw[0]= function(x,y,c) -- ###
Li(x ,y ,x ,y+4,c)-- # #
Li(x+2,y ,x+2,y+4,c)-- # #
Px(x+1,y ,c) -- # #
Px(x+1,y+4,c) -- ###
end
Draw[1]= function(x,y,c) -- #
Li(x ,y+4,x+2,y+4,c)-- ##
Li(x+1,y ,x+1,y+3,c)-- #
Px(x ,y+1,c) -- #
end -- ###
Draw[2]= function(x,y,c) -- ###
Li(x ,y ,x+2,y ,c)-- #
Li(x ,y+3,x+2,y+1,c)-- ###
Li(x ,y+4,x+2,y+4,c)-- #
Px(x ,y+2,c) -- ###
Px(x+2,y+2,c)
end
Draw[3]= function(x,y,c) -- ###
Li(x ,y ,x+1,y ,c)-- #
Li(x ,y+2,x+1,y+2,c)-- ###
Li(x ,y+4,x+1,y+4,c)-- #
Li(x+2,y ,x+2,y+4,c)-- ###
end
Draw[4]= function(x,y,c) -- # #
Li(x ,y ,x ,y+2,c)-- # #
Li(x+2,y ,x+2,y+4,c)-- ###
Px(x+1,y+2,c) -- #
end -- #
Draw[5]= function(x,y,c) -- ###
Li(x ,y ,x+2,y ,c)-- #
Li(x ,y+1,x+2,y+3,c)-- ###
Li(x ,y+4,x+2,y+4,c)-- #
Px(x ,y+2,c) -- ###
Px(x+2,y+2,c)
end
Draw[6]= function(x,y,c) -- ###
Li(x ,y ,x+2,y ,c)-- #
Li(x ,y+1,x ,y+4,c)-- ###
Li(x+2,y+2,x+2,y+4,c)-- # #
Px(x+1,y+2,c) -- ###
Px(x+1,y+4,c)
end
-- ###
Draw[7]= function(x,y,c) -- #
Li(x ,y ,x+1,y ,c)-- ##
Li(x+2,y ,x+1,y+4,c)-- #
end -- #
Draw[8]= function(x,y,c) -- ###
Li(x ,y ,x ,y+4,c)-- # #
Li(x+2,y ,x+2,y+4,c)-- ###
Px(x+1,y ,c) -- # #
Px(x+1,y+2,c) -- ###
Px(x+1,y+4,c)
end
Draw[9]= function(x,y,c) -- ###
Li(x ,y ,x ,y+2,c)-- # #
Li(x+2,y ,x+2,y+3,c)-- ###
Li(x ,y+4,x+2,y+4,c)-- #
Px(x+1,y ,c) -- ###
Px(x+1,y+2,c)
end
Draw[10]=function(x,y,c) -- #
Li(x ,y+1,x ,y+4,c)-- # #
Li(x+2,y+1,x+2,y+4,c)-- # #
Px(x+1,y ,c) -- ###
Px(x+1,y+3,c) -- # #
end
Draw[11]=function(x,y,c) -- ##
Li(x ,y ,x ,y+4,c)-- # #
Li(x+1,y ,x+2,y+1,c)-- ##
Li(x+1,y+4,x+2,y+3,c)-- # #
Px(x+1,y+2,c) -- ##
end
Draw[12]=function(x,y,c) -- #
Li(x ,y+1,x ,y+3,c)-- # #
Li(x+1,y ,x+2,y+1,c)-- #
Li(x+1,y+4,x+2,y+3,c)-- # #
end -- #
Draw[13]=function(x,y,c) -- ##
Li(x ,y ,x ,y+4,c)-- # #
Li(x+2,y+1,x+2,y+3,c)-- # #
Px(x+1,y ,c) -- # #
Px(x+1,y+4,c) -- ##
end
Draw[14]=function(x,y,c) -- ###
Li(x ,y ,x ,y+4,c)-- #
Li(x+1,y ,x+2,y ,c)-- ##
Li(x+1,y+4,x+2,y+4,c)-- #
Px(x+1,y+2,c) -- ###
end
Draw[15]=function(x,y,c) -- ###
Li(x ,y ,x ,y+4,c)-- #
Li(x+1,y ,x+2,y ,c)-- ##
Px(x+1,y+2,c) -- #
end -- #
--*****************************************************************************
local function __DN_AnyBase(right, y, Number, c, bkgnd, div)
--*****************************************************************************
-- Works with any base from 2 to 16. Paints the input number.
-- Returns the x position where it would paint another digit.
-- It only works with integers. Rounds fractions toward zero.
if div < 2 then return end -- Prevents the function from never returning.
local Digit= {}
local Negative= false
if Number < 0 then
Number= -Number
Negative= true
end
Number= math.floor(Number)
c= c or "white"
bkgnd= bkgnd or "clear"
local i= 0
if Number < 1 then
Digit[1]= 0
i= 1
end
while (Number >= 1) do
i= i+1
Digit[i]= Number % div
Number= math.floor(Number/div)
end
if Negative then i= i+1 end
local x= right - i*4
gui.box(x+1, y-1, right+1, y+5,bkgnd,bkgnd)
i= 1
while Draw[Digit[i]] do
Draw[Digit[i]](right-2,y,c)
right= right-4
i=i+1
end
if Negative then
gui.line(right, y+2,right-2,y+2,c)
right= right-4
end
return right
end
--#############################################################################
--#############################################################################
--Let's construct buttons! Yay!
local Flag_DrawRainbow= false -- Bah, hate flags. Any workarounds?
local ValFindFn, MoveMe
FRK_NoAuto= true
dofile("YummyButton.lua")
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
local InputBar= _FRK_Btn.NewBtn{
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
x1= 5, y1= 15, x2= 14, y2= 99,
MBC= 0x00FF00FF, MFC= 0,
SBC= 0x7FFF7FFF, SFC= 0,
-------------------------------------------------------------------------------
fn= function(B)
-------------------------------------------------------------------------------
if keys.shift then return MoveMe end
return ValFindFn
end
}
local function rt(B) return B.tag end
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
local StretchBoxUp= _FRK_Btn.NewBtn{
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
x1= InputBar.x1-5, y1= InputBar.y1-15, x2= InputBar.x2+5, y2= InputBar.y1+5,
MBC= "#FFFFFF40", MFC= 0,
SBC= "#FFFFFFC0", SFC= 0,
fn= function(B) return B.tag end
}
-------------------------------------------------------------------------------
StretchBoxUp.tag= function()
-------------------------------------------------------------------------------
local DeltaY= keys.ymouse - lastkeys.ymouse
DeltaY= math.max(DeltaY, -InputBar.y1) --Don't put beyond screen edge!
DeltaY= math.min(DeltaY, InputBar.y2-InputBar.y1-10)
InputBar.y1= InputBar.y1+DeltaY
StretchBoxUp.y1= StretchBoxUp.y1+DeltaY
StretchBoxUp.y2= StretchBoxUp.y2+DeltaY
end
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
local StretchBoxDn= _FRK_Btn.NewBtn{
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
x1= InputBar.x1-5, y1= InputBar.y2-5, x2= InputBar.x2+5, y2= InputBar.y2+15,
MBC= "#FFFFFF40", MFC= 0,
SBC= "#FFFFFFC0", SFC= 0,
fn= function(B) return B.tag end
}
-------------------------------------------------------------------------------
StretchBoxDn.tag= function()
-------------------------------------------------------------------------------
local DeltaY= keys.ymouse - lastkeys.ymouse
DeltaY= math.max(DeltaY, InputBar.y1-InputBar.y2+10)
DeltaY= math.min(DeltaY, SCRBOTTOM - InputBar.y2) --Screen edge
InputBar.y2= InputBar.y2+DeltaY
StretchBoxDn.y1= StretchBoxDn.y1+DeltaY
StretchBoxDn.y2= StretchBoxDn.y2+DeltaY
end
--*****************************************************************************
ValFindFn= function()
--*****************************************************************************
Flag_DrawRainbow= true
end
--*****************************************************************************
MoveMe= function()
--*****************************************************************************
local DeltaX= keys.xmouse - lastkeys.xmouse
local DeltaY= keys.ymouse - lastkeys.ymouse
DeltaX= math.min(DeltaX, SCRRIGHT - InputBar.x2)
DeltaY= math.min(DeltaY, SCRBOTTOM - InputBar.y2)
DeltaX= math.max(DeltaX, -InputBar.x1)
DeltaY= math.max(DeltaY, -InputBar.y1)
InputBar:move(InputBar.x1+DeltaX, InputBar.y1+DeltaY)
StretchBoxUp:move(StretchBoxUp.x1+DeltaX, StretchBoxUp.y1+DeltaY)
StretchBoxDn:move(StretchBoxDn.x1+DeltaX, StretchBoxDn.y1+DeltaY)
end
-------------------------------------------------------------------------------
InputBar.GetVal= function(B,k)
-------------------------------------------------------------------------------
local h= B.y2 - B.y1
local n= k.ymouse - B.y1
return (1 - n/h) * 1024 -- The multiplication is fairly arbitrary
end
-------------------------------------------------------------------------------
InputBar.DrawSel= function(B) --Override standard button code
-------------------------------------------------------------------------------
gui.box(B.x1, B.y1, B.x2, B.y2, B.SFC, B.SBC) -- May as well box ourself
Flag_DrawRainbow= true -- Would rather make only one call, not thousands.
end
-------------------------------------------------------------------------------
InputBar.DrawRainbow= function(B,y)
-------------------------------------------------------------------------------
y=B.y1*boolto10(y<=B.y1) + y*boolto10(B.y1<y and y<=B.y2) + B.y2*boolto10(B.y2<y)
for i=y,B.y2 do
local hueangle=240 - 240/(B.y1-B.y2)*(i-B.y2)
local RGBTable=huetoRGB(hueangle,0x7F)
gui.line(B.x1,i,B.x2,i,RGBTable)
end
gui.line(B.x1,y,B.x2,y,"white")
end
-------------------------------------------------------------------------------
InputBar.MouseHit= function(B,k)
-------------------------------------------------------------------------------
if InBox(B.x1,B.y1,B.x2,B.y2, k.xmouse,k.ymouse) then
return InputBar:GetVal(k)
end
return nil
end
--#############################################################################
--#############################################################################
local ShowTable= false
--*****************************************************************************
local function PaintStat(T,y)
--*****************************************************************************
gui.box(2,y-1,70,y+5,0x000000FF,0x000000FF)
local temp= T[1] -- Address
for i= 0, 3 do
local n= bit.band(bit.rshift(temp,i*4),0xF)
Draw[n](16-4*i, y, -1)
end
temp= T[2]*10000 -- Certainty
local color= "red"
if temp >= 900 then color= "white"
elseif temp >= 800 then color= "green"
elseif temp >= 700 then color= "yellow"
end
__DN_AnyBase(44, y, temp, color, 0, 10)
temp= memory.readbyte(T[1]) -- Memory value
__DN_AnyBase(68, y, temp, "white", 0, 10)
end
--#############################################################################
--#############################################################################
--Keyboard (and indirectly, location where mouse is stored)
--local keys, lastkeys= input.get(), input.get()
--local function UpdateKeys() lastkeys= keys; keys= input.get() end
--local function Press(k) return keys[k] and (not lastkeys[k]) end
-- Functions are supplied by YummyButtons.lua. Neato.
local KF= {}
--*****************************************************************************
local function KeyFunctions()
--*****************************************************************************
for key,v in pairs(keys) do
if not lastkeys[key] then
if KF[key] then KF[key]() end
end
end
end
-------------------------------------------------------------------------------
KF["space"]= function() -- The CALCULATE NOW function.
-------------------------------------------------------------------------------
CalcNow()
end
-------------------------------------------------------------------------------
KF["home"]= function() -- Display toggle
-------------------------------------------------------------------------------
ShowTable= not ShowTable
end
--#############################################################################
--#############################################################################
local Val= nil
--*****************************************************************************
local function ControlHUD()
--*****************************************************************************
gui.box(L,U,R,D,0,0x00FF00FF)
-- See if we click the all-important box
if InBox(L,U,R,D , keys.xmouse , keys.ymouse) then
gui.line(L,keys.ymouse,R,keys.ymouse, -1)
local h= D - U -- Get height of box
local n= (keys.ymouse - U) -- Figure out where mouse is relative to box
Val= (1 - n/h) * 999 -- Linear scoring of mouse position.
gui.text(200,210,math.floor(Val))
else
Val= nil
end
if keys.shift and keys.leftclick then
L= L + keys.xmouse - lastkeys.xmouse
R= R + keys.xmouse - lastkeys.xmouse
U= U + keys.ymouse - lastkeys.ymouse
D= D + keys.ymouse - lastkeys.ymouse
end
end
local HoldFn
--*****************************************************************************
local function HandleGui()
--*****************************************************************************
--Main handler. Things to do, yes.
local Fn= _FRK_Btn.HandleBtns()
HoldFn= Fn or HoldFn
if not keys.leftclick then HoldFn= nil end
if HoldFn then HoldFn() end
KeyFunctions()
-- Maybe display list of results. This is only a glorified print() so far.
-- There are planned alterations to do...
if ShowTable and BestTable then
for i= 1, #BestTable do
PaintStat(BestTable[i],9*i)
end
end
-- Paint the rainbow!
if Flag_DrawRainbow then
InputBar:DrawRainbow(keys.ymouse)
Flag_DrawRainbow= false
end
end
gui.register(HandleGui)
--*****************************************************************************
local function CheckImmediateInput()
--*****************************************************************************
local ImmediateInput= input.get()
if ImmediateInput.leftclick and not ImmediateInput.shift then
return InputBar:MouseHit(ImmediateInput)
end
end
--*****************************************************************************
local function HandleBefore()
--*****************************************************************************
--Handle frame-advancey stuff.
local Val= CheckImmediateInput()
if Val then
GetData(Val)
end
end
emu.registerbefore(HandleBefore)
A little extra thanks
Although I have tried to emphasize that this is a joint project, I would still like to extend my thanks to FatRatKnight, who kindly volunteered his time to create the GUI. I do not have the programming know-how to efficiently make a GUI, so this project would have gone nowhere without him. Even though I am in charge of the technical aspects of the program, I still feel FatRatKnight has created most of its true "content". If anything in the scripts seems to be programmed elegantly, chances are good that FatRatKnight wrote it.