Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- This script has been superseded by:
- -- https://github.com/yoshifan/ram-watch-cheat-engine
- -- That repository's organization should make it easier to find all the steps
- -- you need to run the code.
- -- Dolphin RAM display script for use with Cheat Engine.
- -- 2013/08/26 - Created (Masterjun) (http://pastebin.com/vUCmhwMQ)
- -- 2013/09/17 - Modified to have permanent addresses for "Textures
- -- created" and the frame counter, and to tolerate game start addresses
- -- greater than 0x7FFFFFFF (yoshifan) (http://pastebin.com/4MsADD5W)
- -- 2013/09/29 - Modified to track SMG2 addresses, and optionally output
- -- stats to a file (yoshifan)
- -- 2014/10/15 - Now works in Dolphin versions as late as 4.0-2826; the
- -- display now uses a Cheat Engine window GUI rather than replacing
- -- Dolphin's statistics text (yoshifan)
- -- IF YOU ARE USING THIS SCRIPT:
- -- You need to set the variables directly below.
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- --------------------
- -- First of all:
- -- Specify your Dolphin version here.
- -- If you use a version of Dolphin listed here (recommended if you want to
- -- set up this script as easily as possible), uncomment the line for that
- -- version, and comment out the rest of the version lines. Then you
- -- should be good to go!
- -- If you use a different version of Dolphin, add a line for your Dolphin
- -- version here, and uncomment the rest of the version lines. Then, below,
- -- follow the instructions to get GameRAMStartPointerAddress,
- -- OncePerFrameAddress, and (optionally) FrameCounterAddress; and for each
- -- of these, add an elseif clause for your version.
- -- A line is "commented out" if it has two dashes at the start. This means
- -- the Lua engine will ignore the line. If you are looking at this script
- -- in Cheat Engine, make sure you have View -> Syntax Highlighting checked,
- -- and the comments should show with a gray color and italics.
- -- To "uncomment" a commented-out line, just remove the two dashes from the
- -- start of the line.
- --local DolphinVersion = "3.5-2302-x64"
- local DolphinVersion = "4.0-2826"
- --------------------
- --------------------
- -- If you have 64-bit Dolphin, set this to true. If 32-bit, set this to false.
- -- This is only used for the FrameCounterAddress, which is optional.
- local DolphinIs64bit = true
- --------------------
- -- The following variables are memory addresses that depend on the Dolphin
- -- version. Since addresses are written in hex, make sure you have the "0x"
- -- before the actual number.
- --------------------
- -- Follow this: http://tasvideos.org/forum/t/13462 tutorial to get a
- -- "pointerscan result". Doubleclick on the Address column and enter the address
- -- after the ' "Dolphin.exe"+ ' (don't forget the 0x).
- local GameRAMStartPointerAddress = nil
- if DolphinVersion == "3.5-2302-x64" then
- GameRAMStartPointerAddress = 0x04961818
- elseif DolphinVersion == "4.0-2826" then
- GameRAMStartPointerAddress = 0x00D19988
- end
- --------------------
- --------------------
- -- Next, we want to find a Dolphin instruction that is run exactly once per
- -- frame. Depending on your version of Dolphin, there's a harder or easier way
- -- to do this.
- --
- -- (A) Harder way that should work in any Dolphin version, including 4.0-2826:
- --
- -- Start your game in Dolphin, then pause the emulation. Now in Cheat Engine,
- -- start a new scan with a Scan Type of "Unknown initial value", and a Value
- -- Type of "4 Bytes". You probably won't see any scan results yet; that's
- -- normal.
- --
- -- Now go to Dolphin and advance your game by 5 frames. Go back to
- -- Cheat Engine, change the Scan Type to "Increased value by ...", type 5
- -- in the Value box, and click Next Scan. Repeat this process a couple
- -- more times, possibly using larger numbers of frames as well. Eventually the
- -- results should be narrowed down a fair bit.
- --
- -- Now pick an address from the results list. Try to get a green address, as
- -- that is a static address which should not change as long as you're using
- -- the same Dolphin version. Double-click this address to add it to the bottom
- -- box. Then right-click the address in the bottom box and choose "Find out
- -- what writes to this address". Choose Yes if you get a Confirmation dialog.
- --
- -- Now advance your Dolphin game by 5 frames again. Hopefully an entry will
- -- appear in the dialog that just popped up, with the number 5 in the Count
- -- column (if not, try a different address). Click the Stop button.
- --
- -- Right-click that entry and choose "Show this address in the disassembler".
- -- Look near the top of the Memory Viewer dialog and you should see
- -- "Dolphin.exe+" followed by a hex address. Use that hex address as the
- -- OncePerFrameAddress. (If you don't see Dolphin.exe, but rather some kind
- -- of DLL, try a different address from the scan you did previously.)
- --
- -- If you can't fully read the hex address because the bottom is cut off,
- -- look back at the previous dialog; the first thing in the "Instruction"
- -- column is a hex address. Hopefully, the last 5 digits of this
- -- address should match the last 5 digits of the OncePerFrameAddress!
- --
- -- (B) Easier way that works in earlier versions such as Dolphin 3.5-2302:
- --
- -- Set the Value Type to "Array of byte" and uncheck "Writable" under Memory
- -- Scan Options. Make sure the "Hex" box is checked and search for:
- --
- -- 48 63 D8 48 03 DF 48 83 7D C0 10
- -- if you have 64 bit Dolphin
- --
- -- 83 C4 0C 83 7C 24 1C 10 8D 3C 06
- -- if you have 32 bit Dolphin
- --
- -- There should be one result, right-click that result and click "Disassemble
- -- this memory region". Enter the number after the "Dolphin.exe+" at the top.
- --
- -- Make sure to check "Writable" again, and really check it, because it has
- -- three states.
- local OncePerFrameAddress = nil
- if DolphinVersion == "3.5-2302-x64" then
- OncePerFrameAddress = 0x00425671
- elseif DolphinVersion == "4.0-2826" then
- OncePerFrameAddress = 0x004AD770
- end
- --------------------
- --------------------
- -- "local FrameCounterAddress = nil" if you don't want it.
- -- Not necessary but it adds a neat Frame- and Movecounter to the screen.
- -- When playing or recording a movie scan for the VI-Count+1 (on the top of the
- -- game window) and setting the Value Type to 4 Bytes. The first result should
- -- be a green address. Rightclick that result and click "Disassemble this
- -- memory region". Enter the number after the "Dolphin.exe+" at the top.
- local FrameCounterAddress = nil
- if DolphinVersion == "3.5-2302-x64" then
- FrameCounterAddress = 0x0496B6A0
- elseif DolphinVersion == "4.0-2826" then
- -- Actually, out of luck here. The scan doesn't seem to work in this version.
- FrameCounterAddress = nil
- end
- --------------------
- --------------------
- -- When you want to output some stats to a file, set this to the number
- -- of frames you want to take stats for, and then click "Execute Script"
- -- again.
- -- If you don't want to output stats to a file, set this to 0.
- local stats_frames_to_take = 0
- --------------------
- -- End of variables that users need to care about. (Unless you want to change
- -- what is being displayed / what is being written to a stats file.)
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Developer notes, mainly ideas for improvements to this script.
- -- TODO: Try to decouple the stuff that depends on Dolphin versions, versus the
- -- stuff that depends on the game, into separate Lua files.
- -- Perhaps one file (this one) for the main script and stuff related to
- -- Dolphin versions, and one other file for each game (e.g. SMG2.lua,
- -- MetroidPrime.lua) that one wants to do RAM watching with.
- -- One possible issue is where to store the separate Lua files so that
- -- Cheat Engine can include them.
- -- TODO: Find out how to get an equivalent of FrameCounterAddress in later
- -- Dolphin versions.
- -- TODO: Have GUI controls (a start button, number field, and maybe even
- -- dropdown of which thing to track) to control the writing to stats.txt.
- -- Re-clicking "Execute script" is annoying because you have to keep that
- -- Lua script window open, and re-executing the script
- -- causes another trainer window to open.
- -- TODO: Have a GUI control to turn the RAM watching off/on, for times when
- -- the values are expected to become invalid (and may crash the script).
- -- For example, restarting the game, going into the Mario/Luigi change room
- -- in SMG2, etc.
- -- TODO: Make each name/value a different label so that we can play with
- -- different font styles/colors? At least try it, and perhaps revert if it
- -- makes things too complicated / hurts performance.
- -- TODO: See if you can make the window semi-transparent (and thus friendlier
- -- to overlap with the game screen maybe) by using Alphablend. At least try it
- -- and see what it looks like.
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- Some functions.
- -- Append to the end of a string, and add a newline char at the end.
- local function appendline(s, new_line)
- new_s = s .. new_line .. "\n"
- return new_s
- end
- -- Convert a number into float
- local function tofloat(x)
- if x==0 then return 0 end
- local s = 1
- if x>=0x80000000 then
- s = -1
- x=x-0x80000000
- end
- local e = math.floor(x/0x800000)-127
- local m=(x%0x800000)/0x800000+1
- return s*(2^e)*m
- end
- -- table of bytes (big endian) -> a string
- -- GC/Wii games use big endian.
- local function tabletostr(x)
- local s = ""
- for k,v in pairs(x) do s=s..string.format("%02X",v) end
- return s
- end
- -- table of bytes (little endian) -> string
- -- Windows programs use little endian.
- local function tableLittleEndianToStr(x)
- local s = ""
- for k,v in pairs(x) do s=string.format("%02X",v)..s end
- return s
- end
- -- table of bytes (little endian) -> number
- local function tableLittleEndianToNumber(x)
- local littleEndianStr = tableLittleEndianToStr(x)
- return tonumber(littleEndianStr, 16)
- end
- -- table -> float
- local function tabletofloat(x)
- return tofloat(tonumber(tabletostr(x), 16))
- end
- -- float -> string. The number of decimal places can be optionally specified.
- local function floattostr(x,precision)
- if not precision then
- precision = 3
- end
- format_str = "%." .. precision .. "f"
- local s = string.format(format_str, x)
- return s
- end
- -- 4byte table representing a float -> string representing that float.
- -- The number of decimal places can be optionally specified.
- local function toflstr(x,precision)
- local str_x = tabletostr(x)
- local hex_x = tonumber(str_x,16)
- return floattostr(tofloat(hex_x),precision)
- end
- -- Same as toflstr, except for integer numbers, not floats.
- local function tonumstr(x)
- local str_x = tabletostr(x)
- local x = tonumber(str_x,16)
- local s = string.format("%d", x)
- return s
- end
- -- Scan for a string and return the address of the first result.
- -- If there is no result, it returns nil.
- local function scanstr(str)
- local startaddr=0
- local stopaddr=0x7fffffffffffffff
- local scan = createMemScan()
- scan.OnlyOneResult=true
- scan.firstScan(soExactValue,vtString,rtTruncated,str,"",startaddr,stopaddr,
- "+W-C",fsmNotAligned,"",false,false,false,true)
- scan.waitTillDone()
- return scan.getOnlyResult()
- end
- -- Initialize a GUI label.
- -- Based on: http://forum.cheatengine.org/viewtopic.php?t=530121
- local function InitLabel( form, x, y, text )
- local label = createLabel(form)
- if( label == nil ) then return nil end
- control_setCaption( label, text )
- control_setPosition( label, x, y )
- return label
- end
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -----
- -- Just initializing variables here.
- local stats_current_frame = 1
- local stats = {} -- array
- local pos_x_initial = nil
- local pos_y_initial = nil
- local pos_z_initial = nil
- local pos_x_prev = nil
- local pos_y_prev = nil
- local pos_z_prev = nil
- local pos_x = 0
- local pos_y = 0
- local pos_z = 0
- -----
- -----
- -- Initializing the GUI window.
- local window = createForm(true)
- -- Put it in the center of the screen.
- form_centerScreen(window)
- -- Set the window title.
- control_setCaption(window, "RAM Display")
- -- Customize the font.
- local font = control_getFont(window)
- font_setName(font, "Calibri")
- font_setSize(font, 16)
- -- Add a blank label to the window. Later, in the every-frame loop,
- -- we'll add text to it.
- local label1 = InitLabel(window, 5, 5, "")
- -----
- -- This sets a breakpoint at a particular instruction which should be
- -- called exactly once every frame.
- debug_removeBreakpoint(getAddress("Dolphin.exe")+OncePerFrameAddress)
- debug_setBreakpoint(getAddress("Dolphin.exe")+OncePerFrameAddress)
- -- If all goes well, everything in the following function should run
- -- exactly once every frame.
- function debugger_onBreakpoint()
- local mode=""
- if FrameCounterAddress then
- local fcaddr=getAddress("Dolphin.exe")+FrameCounterAddress
- local curfc=readInteger(fcaddr)
- local fc=readInteger(fcaddr+0x08)
- local readonly=readBytes(fcaddr-((DolphinIs64bit and 0x30) or 0x28))
- mode=curfc.." [recording]"
- if readonly==2 then mode=curfc.."/"..fc.." [playback]"
- elseif readonly==0 then mode=curfc.."/"..fc.." [inactive]" end
- end
- -- Get the game's start address. We'll use this as a base for all
- -- other addresses.
- --
- -- We use readBytes() instead of readInteger() here, because readInteger()
- -- can't handle values greater than the max integer-type value, which
- -- is 0x7FFFFFFF.
- --
- -- Example value: In Dolphin 3.5-2302 x64, usually o = 0x7FFF0000
- -- if it's the first game you've run since starting up Dolphin.
- local oBytes = readBytes(getAddress("Dolphin.exe")+GameRAMStartPointerAddress,4,true)
- local o = tableLittleEndianToNumber(oBytes)
- -- Timer values.
- local stage_time_address = 0x80A75D10
- local stage_time_bytes = readBytes(stage_time_address+o,4,true)
- local stage_time_frames = tonumber(tabletostr(stage_time_bytes),16)
- local stage_time_centis = math.floor((stage_time_frames % 60) * (100/60))
- local stage_time_secs = math.floor(stage_time_frames / 60) % 60
- local stage_time_mins = math.floor(math.floor(stage_time_frames / 60) / 60)
- local stage_time_str = string.format("%d:%02d.%02d",
- stage_time_mins, stage_time_secs, stage_time_centis
- )
- --[[
- local file_time_address = 0x80E40E4C
- local file_time_low_bytes = readBytes(file_time_address+o,2,true)
- local file_time_high_bytes = readBytes(file_time_address+o+2,2,true)
- local file_time_frames = (
- (tonumber(tabletostr(file_time_high_bytes),16) * 65536)
- + tonumber(tabletostr(file_time_low_bytes),16)
- )
- local file_time_centis = math.floor((file_time_frames % 60) * (100/60))
- local file_time_secs = math.floor(file_time_frames / 60) % 60
- local file_time_mins = math.floor(math.floor(file_time_frames / 60) / 60) % 60
- local file_time_hrs = math.floor(math.floor(math.floor(file_time_frames / 60) / 60) / 60)
- local file_time_str = string.format("%d:%02d:%02d.%02d",
- file_time_hrs, file_time_mins, file_time_secs, file_time_centis
- )
- ]]
- -- Level-2 pointer to a reference value for locating pos and vel.
- local ref_pointer2_address = 0x80C7A2C8
- local ref_pointer2_bytes = readBytes(ref_pointer2_address+o,4,true)
- -- Level-1 pointer to the reference value.
- local ref_pointer1_address = tonumber(tabletostr(ref_pointer2_bytes),16) + 0x750
- local ref_pointer1_bytes = readBytes(ref_pointer1_address+o,4,true)
- local ref_pointer = tonumber(tabletostr(ref_pointer1_bytes),16)
- local pos_start = o + ref_pointer - 0x8670
- pos_x_prev = pos_x
- pos_y_prev = pos_y
- pos_z_prev = pos_z
- pos_x = tabletofloat(readBytes(pos_start,4,true))
- pos_y = tabletofloat(readBytes(pos_start+0x4,4,true))
- pos_z = tabletofloat(readBytes(pos_start+0x8,4,true))
- local vel_start = o + ref_pointer - 0x8C20
- local vel_x = readBytes(vel_start,4,true)
- local vel_y = readBytes(vel_start+0x4,4,true)
- local vel_z = readBytes(vel_start+0x8,4,true)
- if pos_x == nil then
- -- We're reading a non-readable address. Must be switching between
- -- Mario and Luigi, or just starting up the game.
- return 1
- end
- local vel_x_float = tabletofloat(vel_x)
- local vel_y_float = tabletofloat(vel_y)
- local vel_z_float = tabletofloat(vel_z)
- local speed_xz_float = math.sqrt(
- vel_x_float*vel_x_float + vel_z_float*vel_z_float
- )
- local disp_y_float = 0
- local disp_xz_float = 0
- local disp_xyz_float = 0
- if pos_x ~= nil and pos_x_prev ~= nil then
- local disp_x_float = pos_x - pos_x_prev
- local disp_z_float = pos_z - pos_z_prev
- disp_y_float = pos_y - pos_y_prev
- disp_xz_float = math.sqrt(
- disp_x_float*disp_x_float + disp_z_float*disp_z_float
- )
- disp_xyz_float = math.sqrt(
- disp_x_float*disp_x_float + disp_y_float*disp_y_float + disp_z_float*disp_z_float
- )
- end
- s = ""
- if mode ~= "" then s = appendline(s, mode) end
- s = appendline(s, string.format("Time: %s | %d", stage_time_str, stage_time_frames))
- s = appendline(s, "Y Vel: " .. floattostr(disp_y_float))
- s = appendline(s, "XZ Speed: " .. floattostr(disp_xz_float))
- s = appendline(s, "XYZ Speed: " .. floattostr(disp_xyz_float))
- --s = s .. string.format("XYZ Pos: %s | %s | %s", floattostr(pos_x,1), floattostr(pos_y,1), floattostr(pos_z,1)) .. "\n"
- control_setCaption(label1, s)
- -- (Optional) Take some statistics on each frame. If
- -- stats_frames_to_take (defined above) is 0, then this doesn't do anything.
- if stats_current_frame <= stats_frames_to_take then
- -- XZ Speed
- stats[stats_current_frame] = floattostr(speed_xz_float)
- -- XZ Distance
- --if not pos_x_initial then
- -- pos_x_initial = pos_x
- -- pos_z_initial = pos_z
- --end
- --distance_xz_float = math.sqrt(
- -- (pos_x-pos_x_initial)*(pos_x-pos_x_initial) + (pos_z-pos_z_initial)*(pos_z-pos_z_initial)
- --)
- --stats[stats_current_frame] = floattostr(distance_xz_float,6)
- -- Y Velocity
- --stats[stats_current_frame] = toflstr(vel_y)
- -- Y Position Change
- --if not pos_y_initial then
- -- pos_y_initial = pos_y
- --end
- --local y_pos_change = pos_y - pos_y_initial
- --stats[stats_current_frame] = floattostr(y_pos_change,6)
- -- XYZ displacement
- --stats[stats_current_frame] = floattostr(disp_xyz_float,6)
- if stats_current_frame == stats_frames_to_take then
- -- Done taking stats. Collect them in string form and write them
- -- to a file.
- stats_str = table.concat(stats, "\n")
- -- This file will be created in either:
- -- (A) The same directory as the cheat table you have open.
- -- (B) The same directory as the Cheat Engine .exe file, it you don't
- -- have a cheat table open.
- local stats_file = io.open("stats.txt", "w")
- stats_file:write(stats_str)
- stats_file:close()
- end
- stats_current_frame = stats_current_frame + 1
- end
- return 1
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement