diff --git a/README.md b/README.md index 802c503..81c9da7 100644 --- a/README.md +++ b/README.md @@ -72,4 +72,6 @@ The primary authors of this GitHub repository are not affiliated with Dolphin, C Masterjun, for writing the Dolphin + Cheat Engine RAM watch script that this project was based on: http://tasvideos.org/forum/viewtopic.php?t=14379 (2013.08.26) +Tales, for providing the Sonic Adventure DX and Sonic Adventure 2: Battle scripts that were added to this repository (with slight modifications). + aldelaro, for the Dolphin + Cheat Engine tutorial I've linked a few times in these docs. diff --git a/docs/tutorial/run.md b/docs/tutorial/run.md index c3059b8..092dcaf 100644 --- a/docs/tutorial/run.md +++ b/docs/tutorial/run.md @@ -34,6 +34,10 @@ - Metroid Prime, North American version (GM8E01) [revision 0-00](http://www.metroid2002.com/version_differences_version_number.php) - If you're not sure whether you have 0-00: Right-click the game in Dolphin, click Properties, and click the Info tab. Revision should be 0. + + - Sonic Adventure DX: Director's Cut, North American version (GXSE8P) + + - Sonic Adventure 2: Battle, North American version (GSNE8P) - Super Mario Galaxy; North American (RMGE01), European (RMGP01), or Japanese (RMGJ01) version @@ -82,6 +86,8 @@ --- | --- | --- | --- | --- F-Zero GX | North America | `'fzerogx'` | `'na'` | `'racerInfo'` Metroid Prime | North America 0-00 | `'metroidprime'` | `'na_0_00'` | `'positionAndVelocity'` + Sonic Adventure DX: Director's Cut | North America | `'sonicadventuredx'` | `'na'` | `'coordsAndInputs'` + Sonic Adventure 2: Battle | North America | `'sonicadventure2battle'` | `'na'` | `'coordsAndInputs'` Super Mario Galaxy | North America | `'supermariogalaxy'` | `'na'` | `'positionAndInputs'` Super Mario Galaxy | Europe | `'supermariogalaxy'` | `'eu'` | `'positionAndInputs'` Super Mario Galaxy | Japan | `'supermariogalaxy'` | `'jp'` | `'positionAndInputs'` diff --git a/games/sonicadventure2battle.lua b/games/sonicadventure2battle.lua new file mode 100644 index 0000000..0fc4906 --- /dev/null +++ b/games/sonicadventure2battle.lua @@ -0,0 +1,167 @@ +package.loaded.utils = nil +local utils = require 'utils' +local subclass = utils.subclass + +package.loaded.dolphin = nil +local dolphin = require 'dolphin' + +local MyGame = subclass(dolphin.DolphinGame) + +MyGame.supportedGameVersions = { + na = 'GSNE8P', +} + +MyGame.layoutModuleNames = {'sonicadventure2battle_layouts'} +MyGame.framerate = 60 + +function MyGame:init(options) + dolphin.DolphinGame.init(self, options) + + self.startAddress = self:getGameStartAddress() +end + + +local valuetypes = require "valuetypes" +local V = valuetypes.V +local GV = MyGame.blockValues +local MV = valuetypes.MV +local Block = valuetypes.Block +local Value = valuetypes.Value +local FloatType = valuetypes.FloatTypeBE +local IntType = valuetypes.IntTypeBE +local ByteType = valuetypes.ByteType +local BinaryType = valuetypes.BinaryType + +package.loaded.layouts = nil +local layoutsModule = require 'layouts' + +local StaticValue = subclass(valuetypes.MemoryValue) +function StaticValue:getAddress() + return self.game.startAddress + self.offset +end + +-- Game addresses + +GV.facingAngle = MV("Facing Angle", 0xC5D5AC, StaticValue, IntType) + +GV.stSpeed = MV( + "StSpeed", 0xC5D704, StaticValue, FloatType) +GV.fSpeed = MV( + "FSpeed", 0xC5D724, StaticValue, FloatType) +GV.vSpeed = MV( + "VSpeed", 0xC5D728, StaticValue, FloatType) +GV.xPos = MV( + "XPos", 0xC5D5B4, StaticValue, FloatType) +GV.yPos = MV( + "YPos", 0xC5D5B8, StaticValue, FloatType) +GV.zPos = MV( + "ZPos", 0xC5D5BC, StaticValue, FloatType) + + +-- Inputs + +GV.ABXYS = MV("ABXY & Start", 0x2BAB78, + StaticValue, BinaryType, {binarySize=8, binaryStartBit=7}) +GV.DZ = MV("D-Pad & Z", 0x2BAB79, + StaticValue, BinaryType, {binarySize=8, binaryStartBit=7}) + +GV.stickX = + MV("X Stick", 0x2BAB7A, StaticValue, ByteType) +GV.stickY = + MV("Y Stick", 0x2BAB7B, StaticValue, ByteType) +GV.xCStick = + MV("X C-Stick", 0x2BAB7C, StaticValue, ByteType) +GV.yCStick = + MV("Y C-Stick", 0x2BAB7D, StaticValue, ByteType) +GV.lShoulder = + MV("L Shoulder", 0x2BAB7E, StaticValue, ByteType) +GV.rShoulder = + MV("R Shoulder", 0x2BAB7F, StaticValue, ByteType) + +function MyGame:displaySpeed() + local stspd = self.stSpeed:get() + local fspd = self.fSpeed:get() + local vspd = self.vSpeed:get() + return string.format("Speed\n St: %f\n F: %f\n V: %f", stspd, fspd, vspd) +end + +function MyGame:displayRotation() + local yrot = self.facingAngle:get() + return string.format("Rotation\n Y: %d", yrot) +end + +function MyGame:displayPosition() + local xpos = self.xPos:get() + local ypos = self.yPos:get() + local zpos = self.zPos:get() + return string.format("Position\n X: %f\n Y: %f\n Z: %f", xpos, ypos, zpos) +end + +function MyGame:displayInputTime() + local address = 0x013458A8 + return string.format(" %d", utils.readIntLE(address)) +end + +function MyGame:getButton(button) + -- Return 1 if button is pressed, 0 otherwise. + local value = nil + if button == "A" then value = self.ABXYS:get()[8] + elseif button == "B" then value = self.ABXYS:get()[7] + elseif button == "X" then value = self.ABXYS:get()[6] + elseif button == "Y" then value = self.ABXYS:get()[5] + elseif button == "S" then value = self.ABXYS:get()[4] + elseif button == "Z" then value = self.DZ:get()[4] + elseif button == "^" then value = self.DZ:get()[5] + elseif button == "v" then value = self.DZ:get()[6] + elseif button == "<" then value = self.DZ:get()[7] + elseif button == ">" then value = self.DZ:get()[8] + elseif button == "L" then value = self.DZ:get()[2] + elseif button == "R" then value = self.DZ:get()[3] + else error("Button code not recognized: " .. tostring(button)) + end + + return value +end + +function MyGame:buttonDisplay(button) + -- Return the button character ("A", "B" etc.) if the button is pressed, + -- or a space character " " otherwise. + local value = self:getButton(button) + if value == 1 then + return button + else + return " " + end +end + +function MyGame:displayAllButtons() + local s = "" + for _, button in pairs{"A", "B", "X", "Y", "S", "Z", "L", "R", "v", "<", ">", "^"} do + s = s..self:buttonDisplay(button) + end + return s +end + + +MyGame.ControllerStickImage = subclass(layoutsModule.StickInputImage) +function MyGame.ControllerStickImage:init(window, game, options) + options = options or {} + options.max = options.max or 255 + options.min = options.min or 0 + options.square = options.square or true + + layoutsModule.StickInputImage.init( + self, window, + game.stickX, game.stickY, options) +end + +MyGame.ControllerLRImage = subclass(layoutsModule.AnalogTriggerInputImage) +function MyGame.ControllerLRImage:init(window, game, options) + options = options or {} + options.max = options.max or 255 + + layoutsModule.AnalogTriggerInputImage.init( + self, window, game.lShoulder, game.rShoulder, options) +end + +return MyGame \ No newline at end of file diff --git a/games/sonicadventure2battle_layouts.lua b/games/sonicadventure2battle_layouts.lua new file mode 100644 index 0000000..de0ecf0 --- /dev/null +++ b/games/sonicadventure2battle_layouts.lua @@ -0,0 +1,93 @@ +package.loaded.utils = nil +local utils = require 'utils' +local subclass = utils.subclass + +package.loaded.layouts = nil +local layoutsModule = require 'layouts' +local Layout = layoutsModule.Layout + +local layouts = {} + +local fixedWidthFontName = "Consolas" + +local inputColor = 0x880000 + +layouts.addressTest = subclass(Layout) +-- Displays some key addresses, as computed by the Lua framework. +-- We can double check these addresses in Cheat Engine's Memory View. +function layouts.addressTest:init() + + self:setUpdatesPerSecond(5) + + self.window:setSize(450, 200) + + self:addLabel{x=6, y=6} + self:addItem( + function() + local lines = {} + table.insert( + lines, "startAddress: "..utils.intToHexStr(self.game.startAddress)) + table.insert( + lines, + "FSpeed addr: "..utils.intToHexStr(self.game.fSpeed:getAddress())) + return table.concat(lines, '\n') + end + ) + +end + + +layouts.coordsAndInputs = subclass(Layout) +-- General use layout for TASing and stuff. +-- Speed, position, rotation, inputs. +-- +-- updatesPerSecond: +-- How often this display should be updated. +-- Set this higher to see more frequent updates. The game runs at +-- 60 FPS, so it doesn't make sense to set this much higher than 60. +-- Set this lower if Dolphin is stuttering too much. +-- Set to 0 to use breakpoint updates (should update on every frame more +-- reliably compared to 60, but may make Dolphin stutter more). +function layouts.coordsAndInputs:init(updatesPerSecond) + + updatesPerSecond = updatesPerSecond or 60 + + local game = self.game + self.margin = 6 + if updatesPerSecond == 0 then + self:setBreakpointUpdateMethod() + else + self:setUpdatesPerSecond(updatesPerSecond) + end + self:activateAutoPositioningY() + + self.window:setSize(220, 460) + self.labelDefaults = {fontSize=fontSize, fontName=fixedWidthFontName} + self.itemDisplayDefaults = {narrow=true} + + self:addLabel() + self:addItem(function(...) return self.game:displaySpeed(...) end) + self:addItem(function(...) return self.game:displayPosition(...) end) + self:addItem(function(...) return self.game:displayRotation(...) end) + + self:addItem("Input Frames Count") + self:addItem(function(...) return self.game:displayInputTime(...) end) + + self:addLabel{fontColor=inputColor} + self:addItem("Buttons") + self:addItem(function(...) return self.game:displayAllButtons(...) end) + + self:addLabel{foregroundColor=inputColor} + self:addImage( + self.game.ControllerLRImage, {game}, {foregroundColor=inputColor}) + + self:addLabel{foregroundColor=inputColor} + self:addImage( + self.game.ControllerStickImage, {game}, {foregroundColor=inputColor}) + +end + + +return { + layouts = layouts, +} diff --git a/games/sonicadventuredx.lua b/games/sonicadventuredx.lua new file mode 100644 index 0000000..76b8b91 --- /dev/null +++ b/games/sonicadventuredx.lua @@ -0,0 +1,212 @@ +package.loaded.utils = nil +local utils = require 'utils' +local subclass = utils.subclass + +package.loaded.dolphin = nil +local dolphin = require 'dolphin' + +local MyGame = subclass(dolphin.DolphinGame) + +MyGame.supportedGameVersions = { + na = 'GXSE8P', +} + +MyGame.layoutModuleNames = {'sonicadventuredx_layouts'} +MyGame.framerate = 60 + +function MyGame:init(options) + dolphin.DolphinGame.init(self, options) + + self.startAddress = self:getGameStartAddress() +end + +function MyGame:updateAddresses() + local pointerAddress = self.startAddress + 0x7a8240 + if pointerAddress == 0 then + self.pointerValue = nil + else + self.pointerValue = self.startAddress + utils.readIntBE(pointerAddress) - 0x80000000 + end +end + + +local valuetypes = require "valuetypes" +local V = valuetypes.V +local GV = MyGame.blockValues +local MV = valuetypes.MV +local Block = valuetypes.Block +local Value = valuetypes.Value +local FloatType = valuetypes.FloatTypeBE +local IntType = valuetypes.IntTypeBE +local ByteType = valuetypes.ByteType +local ShortType = valuetypes.ShortTypeBE +local BinaryType = valuetypes.BinaryType + +package.loaded.layouts = nil +local layoutsModule = require 'layouts' + +local StaticValue = subclass(valuetypes.MemoryValue) +function StaticValue:getAddress() + return self.game.startAddress + self.offset +end + +local PointerBasedValue = subclass(valuetypes.MemoryValue) +function PointerBasedValue:getAddress() + return self.game.pointerValue + self.offset +end + +-- Game addresses + +GV.piece1 = MV("Piece 1", 0x841C5B, StaticValue, ByteType) +GV.piece2 = MV("Piece 2", 0x841C83, StaticValue, ByteType) +GV.piece3 = MV("Piece 3", 0x841CAB, StaticValue, ByteType) +GV.facingAngle = MV("Facing Angle", 0x7E343A, StaticValue, IntType) + +GV.xPos = MV( + "X Position", 0x7E3440, StaticValue, FloatType) +GV.yPos = MV( + "Y Position", 0x7E3444, StaticValue, FloatType) +GV.zPos = MV( + "Z Position", 0x7E3448, StaticValue, FloatType) +GV.xRot = MV( + "X Rotation", 0x7E3436, StaticValue, ShortType) +GV.yRot = MV( + "Y Rotation", 0x7E343A, StaticValue, ShortType) +GV.zRot = MV( + "Z Rotation", 0x7E343E, StaticValue, ShortType) + +GV.stSpeed = MV( + "StSpeed", 0x0, PointerBasedValue, FloatType) +GV.fSpeed = MV( + "FSpeed", 0x38, PointerBasedValue, FloatType) +GV.vSpeed = MV( + "VSpeed", 0x3C, PointerBasedValue, FloatType) + +-- Inputs + +GV.ABXYS = MV("ABXY & Start", 0xA6CE0, + StaticValue, BinaryType, {binarySize=8, binaryStartBit=7}) +GV.DZ = MV("D-Pad & Z", 0xA6CE1, + StaticValue, BinaryType, {binarySize=8, binaryStartBit=7}) + +GV.stickX = + MV("X Stick", 0xA6CE2, StaticValue, ByteType) +GV.stickY = + MV("Y Stick", 0xA6CE3, StaticValue, ByteType) +GV.xCStick = + MV("X C-Stick", 0xA6CE4, StaticValue, ByteType) +GV.yCStick = + MV("Y C-Stick", 0xA6CE5, StaticValue, ByteType) +GV.lShoulder = + MV("L Shoulder", 0xA6CE6, StaticValue, ByteType) +GV.rShoulder = + MV("R Shoulder", 0xA6CE7, StaticValue, ByteType) + +-- Time + +GV.centiseconds = + MV("Centiseconds", 0x74C7AA, StaticValue, ByteType) +GV.seconds = + MV("Seconds", 0x74C7AB, StaticValue, ByteType) +GV.minutes = + MV("Minutes", 0x74C7AC, StaticValue, ByteType) + +function MyGame:displaySpeed() + local stspd = self.stSpeed:get() + local fspd = self.fSpeed:get() + local vspd = self.vSpeed:get() + return string.format("Speed\n St: %f\n F: %f\n V: %f", stspd, fspd, vspd) +end + +function MyGame:displayRotation() + local xrot = self.xRot:get() + local yrot = self.yRot:get() + local zrot = self.zRot:get() + return string.format("Rotation\n X: %d\n Y: %d\n Z: %d", xrot, yrot, zrot) +end + +function MyGame:displayPosition() + local xpos = self.xPos:get() + local ypos = self.yPos:get() + local zpos = self.zPos:get() + return string.format("Position\n X: %f\n Y: %f\n Z: %f", xpos, ypos, zpos) +end + +function MyGame:displayInputTime() + local address = 0x013458A8 + return string.format(" %d", utils.readIntLE(address)) +end + +function MyGame:displayTime() + local centiFrames = self.centiseconds:get() + local secs = self.seconds:get() + local mins = self.minutes:get() + + local centi = math.floor(centiFrames * (100/60)) + + return string.format(" %d:%02d.%02d", mins, secs, centi) +end + +function MyGame:getButton(button) + -- Return 1 if button is pressed, 0 otherwise. + local value = nil + if button == "A" then value = self.ABXYS:get()[8] + elseif button == "B" then value = self.ABXYS:get()[7] + elseif button == "X" then value = self.ABXYS:get()[6] + elseif button == "Y" then value = self.ABXYS:get()[5] + elseif button == "S" then value = self.ABXYS:get()[4] + elseif button == "Z" then value = self.DZ:get()[4] + elseif button == "^" then value = self.DZ:get()[5] + elseif button == "v" then value = self.DZ:get()[6] + elseif button == "<" then value = self.DZ:get()[7] + elseif button == ">" then value = self.DZ:get()[8] + elseif button == "L" then value = self.DZ:get()[2] + elseif button == "R" then value = self.DZ:get()[3] + else error("Button code not recognized: " .. tostring(button)) + end + + return value +end + +function MyGame:buttonDisplay(button) + -- Return the button character ("A", "B" etc.) if the button is pressed, + -- or a space character " " otherwise. + local value = self:getButton(button) + if value == 1 then + return button + else + return " " + end +end + +function MyGame:displayAllButtons() + local s = "" + for _, button in pairs{"A", "B", "X", "Y", "S", "Z", "L", "R", "v", "<", ">", "^"} do + s = s..self:buttonDisplay(button) + end + return s +end + + +MyGame.ControllerStickImage = subclass(layoutsModule.StickInputImage) +function MyGame.ControllerStickImage:init(window, game, options) + options = options or {} + options.max = options.max or 255 + options.min = options.min or 0 + options.square = options.square or true + + layoutsModule.StickInputImage.init( + self, window, + game.stickX, game.stickY, options) +end + +MyGame.ControllerLRImage = subclass(layoutsModule.AnalogTriggerInputImage) +function MyGame.ControllerLRImage:init(window, game, options) + options = options or {} + options.max = options.max or 255 + + layoutsModule.AnalogTriggerInputImage.init( + self, window, game.lShoulder, game.rShoulder, options) +end + +return MyGame \ No newline at end of file diff --git a/games/sonicadventuredx_layouts.lua b/games/sonicadventuredx_layouts.lua new file mode 100644 index 0000000..461ea12 --- /dev/null +++ b/games/sonicadventuredx_layouts.lua @@ -0,0 +1,97 @@ +package.loaded.utils = nil +local utils = require 'utils' +local subclass = utils.subclass + +package.loaded.layouts = nil +local layoutsModule = require 'layouts' +local Layout = layoutsModule.Layout + +local layouts = {} + +local fixedWidthFontName = "Consolas" + +local inputColor = 0x880000 + +layouts.addressTest = subclass(Layout) +-- Displays some key addresses, as computed by the Lua framework. +-- We can double check these addresses in Cheat Engine's Memory View. +function layouts.addressTest:init() + + self:setUpdatesPerSecond(5) + + self.window:setSize(450, 200) + + self:addLabel{x=6, y=6} + self:addItem( + function() + local lines = {} + table.insert( + lines, "startAddress: "..utils.intToHexStr(self.game.startAddress)) + table.insert( + lines, + "pointerValue (StSpeed addr): "..utils.intToHexStr( + self.game.pointerValue)) + return table.concat(lines, '\n') + end + ) + +end + + +layouts.coordsAndInputs = subclass(Layout) +-- General use layout for TASing and stuff. +-- Speed, position, rotation, time, inputs. +-- +-- updatesPerSecond: +-- How often this display should be updated. +-- Set this higher to see more frequent updates. The game runs at +-- 60 FPS, so it doesn't make sense to set this much higher than 60. +-- Set this lower if Dolphin is stuttering too much. +-- Set to 0 to use breakpoint updates (should update on every frame more +-- reliably compared to 60, but may make Dolphin stutter more). +function layouts.coordsAndInputs:init(updatesPerSecond) + + updatesPerSecond = updatesPerSecond or 60 + + local game = self.game + self.margin = 6 + if updatesPerSecond == 0 then + self:setBreakpointUpdateMethod() + else + self:setUpdatesPerSecond(updatesPerSecond) + end + self:activateAutoPositioningY() + + self.window:setSize(220, 550) + self.labelDefaults = {fontSize=fontSize, fontName=fixedWidthFontName} + self.itemDisplayDefaults = {narrow=true} + + self:addLabel() + self:addItem(function(...) return self.game:displaySpeed(...) end) + self:addItem(function(...) return self.game:displayPosition(...) end) + self:addItem(function(...) return self.game:displayRotation(...) end) + + self:addItem("Level time") + self:addItem(function(...) return self.game:displayTime(...) end) + + self:addItem("Input Frames Count") + self:addItem(function(...) return self.game:displayInputTime(...) end) + + self:addLabel{fontColor=inputColor} + self:addItem("Buttons") + self:addItem(function(...) return self.game:displayAllButtons(...) end) + + self:addLabel{foregroundColor=inputColor} + self:addImage( + self.game.ControllerLRImage, {game}, {foregroundColor=inputColor}) + + self:addLabel{foregroundColor=inputColor} + self:addImage( + self.game.ControllerStickImage, {game}, {foregroundColor=inputColor}) + +end + + +return { + layouts = layouts, +}