Advertisement
yoshifan

[Old] Dolphin RAM display script for Cheat Engine 2014/10/16

Oct 16th, 2014
452
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.22 KB | None | 0 0
  1. -- This script has been superseded by:
  2. -- https://github.com/yoshifan/ram-watch-cheat-engine
  3. -- That repository's organization should make it easier to find all the steps
  4. -- you need to run the code.
  5.  
  6.  
  7.  
  8. -- Dolphin RAM display script for use with Cheat Engine.
  9.  
  10. -- 2013/08/26 - Created (Masterjun) (http://pastebin.com/vUCmhwMQ)
  11. -- 2013/09/17 - Modified to have permanent addresses for "Textures
  12. --   created" and the frame counter, and to tolerate game start addresses
  13. --   greater than 0x7FFFFFFF (yoshifan) (http://pastebin.com/4MsADD5W)
  14. -- 2013/09/29 - Modified to track SMG2 addresses, and optionally output
  15. --   stats to a file (yoshifan)
  16. -- 2014/10/15 - Now works in Dolphin versions as late as 4.0-2826; the
  17. --   display now uses a Cheat Engine window GUI rather than replacing
  18. --   Dolphin's statistics text (yoshifan)
  19.  
  20. -- IF YOU ARE USING THIS SCRIPT:
  21. -- You need to set the variables directly below.
  22.  
  23.  
  24. --------------------------------------------------------------------------------
  25. --------------------------------------------------------------------------------
  26.  
  27. --------------------
  28. -- First of all:
  29. -- Specify your Dolphin version here.
  30.  
  31. -- If you use a version of Dolphin listed here (recommended if you want to
  32. -- set up this script as easily as possible), uncomment the line for that
  33. -- version, and comment out the rest of the version lines. Then you
  34. -- should be good to go!
  35.  
  36. -- If you use a different version of Dolphin, add a line for your Dolphin
  37. -- version here, and uncomment the rest of the version lines. Then, below,
  38. -- follow the instructions to get GameRAMStartPointerAddress,
  39. -- OncePerFrameAddress, and (optionally) FrameCounterAddress; and for each
  40. -- of these, add an elseif clause for your version.
  41.  
  42. -- A line is "commented out" if it has two dashes at the start. This means
  43. -- the Lua engine will ignore the line. If you are looking at this script
  44. -- in Cheat Engine, make sure you have View -> Syntax Highlighting checked,
  45. -- and the comments should show with a gray color and italics.
  46. -- To "uncomment" a commented-out line, just remove the two dashes from the
  47. -- start of the line.
  48.  
  49. --local DolphinVersion = "3.5-2302-x64"
  50. local DolphinVersion = "4.0-2826"
  51. --------------------
  52.  
  53. --------------------
  54. -- If you have 64-bit Dolphin, set this to true. If 32-bit, set this to false.
  55. -- This is only used for the FrameCounterAddress, which is optional.
  56. local DolphinIs64bit = true
  57. --------------------
  58.  
  59. -- The following variables are memory addresses that depend on the Dolphin
  60. -- version. Since addresses are written in hex, make sure you have the "0x"
  61. -- before the actual number.
  62.  
  63. --------------------
  64. -- Follow this: http://tasvideos.org/forum/t/13462 tutorial to get a
  65. -- "pointerscan result". Doubleclick on the Address column and enter the address
  66. -- after the ' "Dolphin.exe"+ ' (don't forget the 0x).
  67.  
  68. local GameRAMStartPointerAddress = nil
  69. if DolphinVersion == "3.5-2302-x64" then
  70.   GameRAMStartPointerAddress = 0x04961818
  71. elseif DolphinVersion == "4.0-2826" then
  72.   GameRAMStartPointerAddress = 0x00D19988
  73. end
  74. --------------------
  75.  
  76. --------------------
  77. -- Next, we want to find a Dolphin instruction that is run exactly once per
  78. -- frame. Depending on your version of Dolphin, there's a harder or easier way
  79. -- to do this.
  80. --
  81. -- (A) Harder way that should work in any Dolphin version, including 4.0-2826:
  82. --
  83. -- Start your game in Dolphin, then pause the emulation. Now in Cheat Engine,
  84. -- start a new scan with a Scan Type of "Unknown initial value", and a Value
  85. -- Type of "4 Bytes". You probably won't see any scan results yet; that's
  86. -- normal.
  87. --
  88. -- Now go to Dolphin and advance your game by 5 frames. Go back to
  89. -- Cheat Engine, change the Scan Type to "Increased value by ...", type 5
  90. -- in the Value box, and click Next Scan. Repeat this process a couple
  91. -- more times, possibly using larger numbers of frames as well. Eventually the
  92. -- results should be narrowed down a fair bit.
  93. --
  94. -- Now pick an address from the results list. Try to get a green address, as
  95. -- that is a static address which should not change as long as you're using
  96. -- the same Dolphin version. Double-click this address to add it to the bottom
  97. -- box. Then right-click the address in the bottom box and choose "Find out
  98. -- what writes to this address". Choose Yes if you get a Confirmation dialog.
  99. --
  100. -- Now advance your Dolphin game by 5 frames again. Hopefully an entry will
  101. -- appear in the dialog that just popped up, with the number 5 in the Count
  102. -- column (if not, try a different address). Click the Stop button.
  103. --
  104. -- Right-click that entry and choose "Show this address in the disassembler".
  105. -- Look near the top of the Memory Viewer dialog and you should see
  106. -- "Dolphin.exe+" followed by a hex address. Use that hex address as the
  107. -- OncePerFrameAddress. (If you don't see Dolphin.exe, but rather some kind
  108. -- of DLL, try a different address from the scan you did previously.)
  109. --
  110. -- If you can't fully read the hex address because the bottom is cut off,
  111. -- look back at the previous dialog; the first thing in the "Instruction"
  112. -- column is a hex address. Hopefully, the last 5 digits of this
  113. -- address should match the last 5 digits of the OncePerFrameAddress!
  114. --
  115. -- (B) Easier way that works in earlier versions such as Dolphin 3.5-2302:
  116. --
  117. -- Set the Value Type to "Array of byte" and uncheck "Writable" under Memory
  118. -- Scan Options. Make sure the "Hex" box is checked and search for:
  119. --
  120. -- 48 63 D8 48 03 DF 48 83 7D C0 10
  121. -- if you have 64 bit Dolphin
  122. --
  123. -- 83 C4 0C 83 7C 24 1C 10 8D 3C 06
  124. -- if you have 32 bit Dolphin
  125. --
  126. -- There should be one result, right-click that result and click "Disassemble
  127. -- this memory region". Enter the number after the "Dolphin.exe+" at the top.
  128. --
  129. -- Make sure to check "Writable" again, and really check it, because it has
  130. -- three states.
  131.  
  132. local OncePerFrameAddress = nil
  133. if DolphinVersion == "3.5-2302-x64" then
  134.   OncePerFrameAddress = 0x00425671
  135. elseif DolphinVersion == "4.0-2826" then
  136.   OncePerFrameAddress = 0x004AD770
  137. end
  138. --------------------
  139.  
  140. --------------------
  141. -- "local FrameCounterAddress = nil" if you don't want it.
  142. -- Not necessary but it adds a neat Frame- and Movecounter to the screen.
  143. -- When playing or recording a movie scan for the VI-Count+1 (on the top of the
  144. -- game window) and setting the Value Type to 4 Bytes. The first result should
  145. -- be a green address. Rightclick that result and click "Disassemble this
  146. -- memory region". Enter the number after the "Dolphin.exe+" at the top.
  147.  
  148. local FrameCounterAddress = nil
  149. if DolphinVersion == "3.5-2302-x64" then
  150.   FrameCounterAddress = 0x0496B6A0
  151. elseif DolphinVersion == "4.0-2826" then
  152.   -- Actually, out of luck here. The scan doesn't seem to work in this version.
  153.   FrameCounterAddress = nil
  154. end
  155. --------------------
  156.  
  157. --------------------
  158. -- When you want to output some stats to a file, set this to the number
  159. -- of frames you want to take stats for, and then click "Execute Script"
  160. -- again.
  161. -- If you don't want to output stats to a file, set this to 0.
  162. local stats_frames_to_take = 0
  163. --------------------
  164.  
  165. -- End of variables that users need to care about. (Unless you want to change
  166. -- what is being displayed / what is being written to a stats file.)
  167.  
  168.  
  169. --------------------------------------------------------------------------------
  170. --------------------------------------------------------------------------------
  171.  
  172.  
  173. -- Developer notes, mainly ideas for improvements to this script.
  174.  
  175. -- TODO: Try to decouple the stuff that depends on Dolphin versions, versus the
  176. -- stuff that depends on the game, into separate Lua files.
  177. -- Perhaps one file (this one) for the main script and stuff related to
  178. -- Dolphin versions, and one other file for each game (e.g. SMG2.lua,
  179. -- MetroidPrime.lua) that one wants to do RAM watching with.
  180. -- One possible issue is where to store the separate Lua files so that
  181. -- Cheat Engine can include them.
  182.  
  183. -- TODO: Find out how to get an equivalent of FrameCounterAddress in later
  184. -- Dolphin versions.
  185.  
  186. -- TODO: Have GUI controls (a start button, number field, and maybe even
  187. -- dropdown of which thing to track) to control the writing to stats.txt.
  188. -- Re-clicking "Execute script" is annoying because you have to keep that
  189. -- Lua script window open, and re-executing the script
  190. -- causes another trainer window to open.
  191.  
  192. -- TODO: Have a GUI control to turn the RAM watching off/on, for times when
  193. -- the values are expected to become invalid (and may crash the script).
  194. -- For example, restarting the game, going into the Mario/Luigi change room
  195. -- in SMG2, etc.
  196.  
  197. -- TODO: Make each name/value a different label so that we can play with
  198. -- different font styles/colors? At least try it, and perhaps revert if it
  199. -- makes things too complicated / hurts performance.
  200.  
  201. -- TODO: See if you can make the window semi-transparent (and thus friendlier
  202. -- to overlap with the game screen maybe) by using Alphablend. At least try it
  203. -- and see what it looks like.
  204.  
  205.  
  206. --------------------------------------------------------------------------------
  207. --------------------------------------------------------------------------------
  208.  
  209.  
  210. -- Some functions.
  211.  
  212. -- Append to the end of a string, and add a newline char at the end.
  213. local function appendline(s, new_line)
  214.   new_s = s .. new_line .. "\n"
  215.   return new_s
  216. end
  217.  
  218. -- Convert a number into float
  219. local function tofloat(x)
  220.   if x==0 then return 0 end
  221.   local s = 1
  222.   if x>=0x80000000 then
  223.      s = -1
  224.      x=x-0x80000000
  225.   end
  226.   local e = math.floor(x/0x800000)-127
  227.   local m=(x%0x800000)/0x800000+1
  228.   return s*(2^e)*m
  229. end
  230.  
  231. -- table of bytes (big endian) -> a string
  232. -- GC/Wii games use big endian.
  233. local function tabletostr(x)
  234.   local s = ""
  235.   for k,v in pairs(x) do s=s..string.format("%02X",v) end
  236.   return s
  237. end
  238.  
  239. -- table of bytes (little endian) -> string
  240. -- Windows programs use little endian.
  241. local function tableLittleEndianToStr(x)
  242.   local s = ""
  243.   for k,v in pairs(x) do s=string.format("%02X",v)..s end
  244.   return s
  245. end
  246.  
  247. -- table of bytes (little endian) -> number
  248. local function tableLittleEndianToNumber(x)
  249.   local littleEndianStr = tableLittleEndianToStr(x)
  250.   return tonumber(littleEndianStr, 16)
  251. end
  252.  
  253. -- table -> float
  254. local function tabletofloat(x)
  255.   return tofloat(tonumber(tabletostr(x), 16))
  256. end
  257.  
  258. -- float -> string. The number of decimal places can be optionally specified.
  259. local function floattostr(x,precision)
  260.   if not precision then
  261.     precision = 3
  262.   end
  263.   format_str = "%." .. precision .. "f"
  264.   local s = string.format(format_str, x)
  265.   return s
  266. end
  267.  
  268. -- 4byte table representing a float -> string representing that float.
  269. -- The number of decimal places can be optionally specified.
  270. local function toflstr(x,precision)
  271.   local str_x = tabletostr(x)
  272.   local hex_x = tonumber(str_x,16)
  273.   return floattostr(tofloat(hex_x),precision)
  274. end
  275.  
  276. -- Same as toflstr, except for integer numbers, not floats.
  277. local function tonumstr(x)
  278.   local str_x = tabletostr(x)
  279.   local x = tonumber(str_x,16)
  280.   local s = string.format("%d", x)
  281.   return s
  282. end
  283.  
  284. -- Scan for a string and return the address of the first result.
  285. -- If there is no result, it returns nil.
  286. local function scanstr(str)
  287.   local startaddr=0
  288.   local stopaddr=0x7fffffffffffffff
  289.   local scan = createMemScan()
  290.   scan.OnlyOneResult=true
  291.   scan.firstScan(soExactValue,vtString,rtTruncated,str,"",startaddr,stopaddr,
  292.   "+W-C",fsmNotAligned,"",false,false,false,true)
  293.   scan.waitTillDone()
  294.   return scan.getOnlyResult()
  295. end
  296.  
  297.  
  298. -- Initialize a GUI label.
  299. -- Based on: http://forum.cheatengine.org/viewtopic.php?t=530121
  300. local function InitLabel( form, x, y, text )
  301.   local label = createLabel(form)
  302.   if( label == nil ) then return nil end
  303.   control_setCaption( label, text )
  304.   control_setPosition( label, x, y )
  305.   return label
  306. end
  307.  
  308.  
  309. --------------------------------------------------------------------------------
  310. --------------------------------------------------------------------------------
  311.  
  312.  
  313. -----
  314. -- Just initializing variables here.
  315. local stats_current_frame = 1
  316.  
  317. local stats = {}  -- array
  318. local pos_x_initial = nil
  319. local pos_y_initial = nil
  320. local pos_z_initial = nil
  321.  
  322. local pos_x_prev = nil
  323. local pos_y_prev = nil
  324. local pos_z_prev = nil
  325. local pos_x = 0
  326. local pos_y = 0
  327. local pos_z = 0
  328. -----
  329.  
  330.  
  331. -----
  332. -- Initializing the GUI window.
  333.  
  334. local window = createForm(true)
  335. -- Put it in the center of the screen.
  336. form_centerScreen(window)
  337. -- Set the window title.
  338. control_setCaption(window, "RAM Display")
  339. -- Customize the font.
  340. local font = control_getFont(window)
  341. font_setName(font, "Calibri")
  342. font_setSize(font, 16)
  343.  
  344. -- Add a blank label to the window. Later, in the every-frame loop,
  345. -- we'll add text to it.
  346. local label1 = InitLabel(window, 5, 5, "")
  347. -----
  348.  
  349.  
  350.  
  351.  
  352. -- This sets a breakpoint at a particular instruction which should be
  353. -- called exactly once every frame.
  354.  
  355. debug_removeBreakpoint(getAddress("Dolphin.exe")+OncePerFrameAddress)
  356. debug_setBreakpoint(getAddress("Dolphin.exe")+OncePerFrameAddress)
  357.  
  358. -- If all goes well, everything in the following function should run
  359. -- exactly once every frame.
  360.  
  361. function debugger_onBreakpoint()
  362.  
  363.   local mode=""
  364.   if FrameCounterAddress then
  365.     local fcaddr=getAddress("Dolphin.exe")+FrameCounterAddress
  366.     local curfc=readInteger(fcaddr)
  367.     local fc=readInteger(fcaddr+0x08)
  368.     local readonly=readBytes(fcaddr-((DolphinIs64bit and 0x30) or 0x28))
  369.     mode=curfc.." [recording]"
  370.     if readonly==2 then mode=curfc.."/"..fc.." [playback]"
  371.     elseif readonly==0 then mode=curfc.."/"..fc.." [inactive]" end
  372.   end
  373.  
  374.  
  375.   -- Get the game's start address. We'll use this as a base for all
  376.   -- other addresses.
  377.   --
  378.   -- We use readBytes() instead of readInteger() here, because readInteger()
  379.   -- can't handle values greater than the max integer-type value, which
  380.   -- is 0x7FFFFFFF.
  381.   --
  382.   -- Example value: In Dolphin 3.5-2302 x64, usually o = 0x7FFF0000
  383.   -- if it's the first game you've run since starting up Dolphin.
  384.   local oBytes = readBytes(getAddress("Dolphin.exe")+GameRAMStartPointerAddress,4,true)
  385.   local o = tableLittleEndianToNumber(oBytes)
  386.  
  387.  
  388.   -- Timer values.
  389.   local stage_time_address = 0x80A75D10
  390.   local stage_time_bytes = readBytes(stage_time_address+o,4,true)
  391.   local stage_time_frames = tonumber(tabletostr(stage_time_bytes),16)
  392.  
  393.   local stage_time_centis = math.floor((stage_time_frames % 60) * (100/60))
  394.   local stage_time_secs = math.floor(stage_time_frames / 60) % 60
  395.   local stage_time_mins = math.floor(math.floor(stage_time_frames / 60) / 60)
  396.   local stage_time_str = string.format("%d:%02d.%02d",
  397.     stage_time_mins, stage_time_secs, stage_time_centis
  398.   )
  399.  
  400.   --[[
  401.   local file_time_address = 0x80E40E4C
  402.   local file_time_low_bytes = readBytes(file_time_address+o,2,true)
  403.   local file_time_high_bytes = readBytes(file_time_address+o+2,2,true)
  404.   local file_time_frames = (
  405.     (tonumber(tabletostr(file_time_high_bytes),16) * 65536)
  406.     + tonumber(tabletostr(file_time_low_bytes),16)
  407.   )
  408.  
  409.   local file_time_centis = math.floor((file_time_frames % 60) * (100/60))
  410.   local file_time_secs = math.floor(file_time_frames / 60) % 60
  411.   local file_time_mins = math.floor(math.floor(file_time_frames / 60) / 60) % 60
  412.   local file_time_hrs = math.floor(math.floor(math.floor(file_time_frames / 60) / 60) / 60)
  413.   local file_time_str = string.format("%d:%02d:%02d.%02d",
  414.     file_time_hrs, file_time_mins, file_time_secs, file_time_centis
  415.   )
  416.   ]]
  417.  
  418.  
  419.   -- Level-2 pointer to a reference value for locating pos and vel.
  420.   local ref_pointer2_address = 0x80C7A2C8
  421.   local ref_pointer2_bytes = readBytes(ref_pointer2_address+o,4,true)
  422.  
  423.   -- Level-1 pointer to the reference value.
  424.   local ref_pointer1_address = tonumber(tabletostr(ref_pointer2_bytes),16) + 0x750
  425.   local ref_pointer1_bytes = readBytes(ref_pointer1_address+o,4,true)
  426.   local ref_pointer = tonumber(tabletostr(ref_pointer1_bytes),16)
  427.  
  428.   local pos_start = o + ref_pointer - 0x8670
  429.   pos_x_prev = pos_x
  430.   pos_y_prev = pos_y
  431.   pos_z_prev = pos_z
  432.   pos_x = tabletofloat(readBytes(pos_start,4,true))
  433.   pos_y = tabletofloat(readBytes(pos_start+0x4,4,true))
  434.   pos_z = tabletofloat(readBytes(pos_start+0x8,4,true))
  435.  
  436.   local vel_start = o + ref_pointer - 0x8C20
  437.   local vel_x = readBytes(vel_start,4,true)
  438.   local vel_y = readBytes(vel_start+0x4,4,true)
  439.   local vel_z = readBytes(vel_start+0x8,4,true)
  440.  
  441.   if pos_x == nil then
  442.     -- We're reading a non-readable address. Must be switching between
  443.     -- Mario and Luigi, or just starting up the game.
  444.     return 1
  445.   end
  446.  
  447.   local vel_x_float = tabletofloat(vel_x)
  448.   local vel_y_float = tabletofloat(vel_y)
  449.   local vel_z_float = tabletofloat(vel_z)
  450.   local speed_xz_float = math.sqrt(
  451.     vel_x_float*vel_x_float + vel_z_float*vel_z_float
  452.   )
  453.  
  454.   local disp_y_float = 0
  455.   local disp_xz_float = 0
  456.   local disp_xyz_float = 0
  457.   if pos_x ~= nil and pos_x_prev ~= nil then
  458.     local disp_x_float = pos_x - pos_x_prev
  459.     local disp_z_float = pos_z - pos_z_prev
  460.     disp_y_float = pos_y - pos_y_prev
  461.     disp_xz_float = math.sqrt(
  462.       disp_x_float*disp_x_float + disp_z_float*disp_z_float
  463.     )
  464.     disp_xyz_float = math.sqrt(
  465.       disp_x_float*disp_x_float + disp_y_float*disp_y_float + disp_z_float*disp_z_float
  466.     )
  467.   end
  468.  
  469.  
  470.   s = ""
  471.   if mode ~= "" then s = appendline(s, mode) end
  472.   s = appendline(s, string.format("Time: %s | %d", stage_time_str, stage_time_frames))
  473.   s = appendline(s, "Y Vel: " .. floattostr(disp_y_float))
  474.   s = appendline(s, "XZ Speed: " .. floattostr(disp_xz_float))
  475.   s = appendline(s, "XYZ Speed: " .. floattostr(disp_xyz_float))
  476.   --s = s .. string.format("XYZ Pos: %s | %s | %s", floattostr(pos_x,1), floattostr(pos_y,1), floattostr(pos_z,1)) .. "\n"
  477.  
  478.   control_setCaption(label1, s)
  479.  
  480.  
  481.   -- (Optional) Take some statistics on each frame. If
  482.   -- stats_frames_to_take (defined above) is 0, then this doesn't do anything.
  483.   if stats_current_frame <= stats_frames_to_take then
  484.  
  485.     -- XZ Speed
  486.     stats[stats_current_frame] = floattostr(speed_xz_float)
  487.  
  488.     -- XZ Distance
  489.     --if not pos_x_initial then
  490.     --  pos_x_initial = pos_x
  491.     --  pos_z_initial = pos_z
  492.     --end
  493.     --distance_xz_float = math.sqrt(
  494.     --  (pos_x-pos_x_initial)*(pos_x-pos_x_initial) + (pos_z-pos_z_initial)*(pos_z-pos_z_initial)
  495.     --)
  496.     --stats[stats_current_frame] = floattostr(distance_xz_float,6)
  497.  
  498.     -- Y Velocity
  499.     --stats[stats_current_frame] = toflstr(vel_y)
  500.  
  501.     -- Y Position Change
  502.     --if not pos_y_initial then
  503.     --  pos_y_initial = pos_y
  504.     --end
  505.     --local y_pos_change = pos_y - pos_y_initial
  506.     --stats[stats_current_frame] = floattostr(y_pos_change,6)
  507.  
  508.     -- XYZ displacement
  509.     --stats[stats_current_frame] = floattostr(disp_xyz_float,6)
  510.  
  511.     if stats_current_frame == stats_frames_to_take then
  512.       -- Done taking stats. Collect them in string form and write them
  513.       -- to a file.
  514.  
  515.       stats_str = table.concat(stats, "\n")
  516.  
  517.       -- This file will be created in either:
  518.       -- (A) The same directory as the cheat table you have open.
  519.       -- (B) The same directory as the Cheat Engine .exe file, it you don't
  520.       --   have a cheat table open.
  521.       local stats_file = io.open("stats.txt", "w")
  522.       stats_file:write(stats_str)
  523.       stats_file:close()
  524.     end
  525.  
  526.     stats_current_frame = stats_current_frame + 1
  527.  
  528.   end
  529.  
  530.  
  531.   return 1
  532.  
  533. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement