需要帮助对视频游戏中的相机相关信息进行逆向工程

逆向工程 部件 作弊引擎 路亚
2021-06-29 00:01:26

我已经在游戏开发 StackExchange 上提出了一个类似的问题,但我将对其进行改进、详细说明和零碎一个更特定于 RE 的上下文,希望那里的人有这方面的经验!

首先,我希望创建一个自由飞行的凸轮黑客,我可以在任何地方移动,向前方向始终是我的鼠标指向的地方。

我正在破解的游戏(仅限单人游戏)在任务完成后自动移动玩家;除了通过鼠标旋转外,没有任何指令绑定到玩家移动。从某种意义上说,这意味着我自己正在构建 WASD 功能。我找到了一个注入指令,它允许我获取相机的 XYZ 坐标(当游戏自动移动玩家时会改变),以及它的旋转数据。因此,我有以下几点:

  • camX (Plane) -- 值是一个从负到正的浮点数
  • camY (Plane) -- 值是一个从负到正的浮点数
  • camZ (Up/Down) -- 值是一个从负到正的浮点数
  • camH(水平/偏航)——值是一个从 +180 到 -180 度的浮点数
  • camV (Vertical/Pitch) -- 值是一个以度为单位的浮点数,固定为 +89 到 -89

有了这些信息,我用 Lua 编写了一个脚本(用于作弊引擎),它允许我将方向(向前、向后、向左、向右、向上和向下)绑定到键(W、A、S、D、 Q, 和 E); 然而,那些只是简单地添加/减去 XYZ 值以及速度修饰符,这意味着如果我面向前方并向前移动,一切都很好。但是,用鼠标旋转 180 度实际上意味着向后向前,向左向右等。

我想要实现的是让这些方向键适用于我瞄准鼠标指针的任何地方。所以,前进总是我面对的地方,等等。

到目前为止我所做的是以下的一些组合:

  • 从其他游戏下载flycam/freecam脚本并替换相关说明/数据/等。用我自己的。
  • 尝试根据我收集的各种信息(此处的示例)以及与游戏开发相关的三角学和向量微积分的一般研究来创建我自己的

似乎无论我认为我明白发生了什么,我还没有完成这项工作。

我收集到的一点是,我真的应该找到游戏存储/引用sincos信息的位置,这样我就不必通过诸如 math.cos()/math 之类的东西自己计算这些值。 sin() 并可能转换度数/弧度等。即使我尝试过的那些手动计算也没有解决,所以我不确定我是否完全掌握了我需要的东西。

考虑到上述所有内容,以下是我的问题:

问题 1:sin/cos 值往往是什么样的,我希望在什么时候通过与相机/坐标相关的子程序看到它们(无论是在堆栈上,还是在 FPU/XMM 寄存器中,等等)?我认为这些应该接近我在内存或子程序中涉足的地方,但我不太确定我在寻找什么。

问题2:是否有通过逆向识别游戏坐标系的通用方法?就我破解的游戏而言,它是在虚幻引擎 4 中开发的,所以我可以简单地查找引擎的坐标系但是,如果我想验证这些发现(如果游戏使用自定义坐标系而不是内置坐标系,可能会充实)或在处理自定义引擎时重新发现,我该怎么做?它们是倾向于驻留在虚拟形象/相机数据附近的内存地址中的值,还是可能驻留在相机相关子例程中的寄存器/堆栈中的值?

问题 3:如果我知道游戏中使用的坐标系,以及 XYZ 和 Pitch/Yaw 摄像机值,是否有公式方法可以计算正弦/余弦,然后使用速度修改器成功实现自由摄像机? 如果我无法在游戏中找到要在我的脚本中使用的 sin/cos。

问题 4:我对我在试图理解与 sin/cos 相关的计算时查找的三角函数/计算函数只有一个粗略的理解。给定一个左手右手坐标系,Z 向上,连同 XYZ、俯仰/偏航数据和速度修改器,有人可能会引导我完成必要的计算,以便在两个坐标系中进行向前工作,无论在哪里用户指向鼠标?例如,在任何给定时间,sin/cos 值究竟由什么组成,它们如何/为什么重要,以及为什么要添加/sub/mul 以适当修改 XYZ 等?

为冗长而道歉,如果我在不知不觉中提出了需要更多理解才能充分回答的问题,但我只是想彻底解释我的研究和迄今为止的努力。

感谢您提供的任何指导!

2个回答

问题 3 和 4 答案:

要沿您面对的方向移动相机/播放器,您可以使用此功能适用于 eular 角度。您需要找到的唯一地址是您当前的视角和 3d 坐标。

您可以使用它来覆盖播放器/相机 3d 坐标。我在 GetaSyncKeyState 调用中执行此操作,因此在按下 NUMPAD_1 时它会起作用。

该函数假设:

  • X = 俯仰,Y = 偏航,Z = 滚转
  • 3D 坐标的 Z 是垂直平面
  • 角度以度为单位
#define PI 3.1415927f

vec3 { float x,y,z; };

vec3 Add(vec3 src, vec3 dst)
{
    vec3 sum;
    sum.x = src.x + dst.x;
    sum.y = src.y + dst.y;
    sum.z = src.z + dst.z;
    return sum;
}

float DegreeToRadian(float degree)
{
    return degree * (PI / 180);
}

vec3 DegreeToRadian(vec3 degrees)
{
    vec3 radians;
    radians.x = degrees.x * (PI / 180);
    radians.y = degrees.y * (PI / 180);
    radians.z = degrees.z * (PI / 180);
    return radians;
}

void MoveInCameraDirection(vec3 currAngle, vec3& src, float dist)
{
    vec3 d;

    d.x = cosf(DegreeToRadian(currAngle.x - 90)) * dist;
    d.y = sinf(DegreeToRadian(currAngle.x - 90)) * dist;
    d.z = sinf(DegreeToRadian(currAngle.y)) * dist;

    src = Add(src, d);
}

GetAsyncKetState 循环中的示例用法:

while (1)
{
    if (GetAsyncKeyState(VK_NUMPAD1) & 1)
    {
        MoveInCameraDirection(localPlayer->angle, localPlayer->pos, 3);
        Sleep(20);
    }
}
  • currAngle = 您当前的视角
  • src = 您当前的位置,作为参考传递,以便函数可以修改它
  • dist = 每次执行时要移动的距离,1 到 5 之间是好的

当按下一个键来飞来飞去时调用该函数,像往常一样移动鼠标瞄准你的家伙。我有一个工作项目,我在其中注入了这个项目,它工作得很好。Gravity 获得控制权,您每次滴答都会略微下降,但您也可以禁用重力客户端,然后您就拥有完全的自由。

经过大量的反复试验,以及来自许多不同来源和人们的帮助的拼图的零碎碎片,我想在我认为能够回答的范围内回答我自己的问题。我希望这可以帮助遇到这个问题的其他人。

问题 1:sin/cos 值往往是什么样的,我希望在什么时候通过与相机/坐标相关的子程序看到它们(无论是在堆栈上,还是在 FPU/XMM 寄存器中,等等)?我认为这些应该接近我在内存或子程序中涉足的地方,但我不太确定我在寻找什么。

这些值将是相机俯仰(垂直运动)和偏航(水平运动)的正弦和余弦。它们可能驻留在相机俯仰/偏航地址的附近内存中的某个位置,或者在子程序中计算俯仰/偏航时的寄存器中。游戏可以以度数或弧度表示俯仰和偏航。

音高的例子:

Range in degrees: -89 to 89 (Example of a clamped range, to avoid flipping)

Same range in radians: -1.55334 to 1.55334

偏航的例子:

Range in degrees: -180 to 180 (Equaling 360 degrees total)

Same range in radians: -3.14159 to 3.14159

如果您正在反转的游戏将其值存储为度数,取决于您计划如何计算正弦/余弦,您可能需要将度数转换为弧度。由于我使用的是 Lua,所以它的度数到弧度函数是math.rad(). 所以, math.rad(180) 会给我们一个从 180 度到 3.14159 弧度的转换。

有了这个值,我们现在可以使用 Lua 的math.cos()math.sin()函数了。假设我们正在寻找相机在 63 度时偏航的 sin 和 cos。我们将在内存、堆栈或寄存器中查找的值分别为 0.4539(余弦或math.cos(63))和 0.891007(正弦或math.sin(63))。

从公式的角度来看,以下是我们如何设置变量以在 Lua 中为我们提供相机偏航的 sin 和 cos,前提是我们知道该值的存储地址:

--Read and store value from camera yaw memory address local camYaw = readFloat("[cameraBase+1D0]")

--Convert yaw to radians, then calculate cosine local camYawCos = math.cos(math.rad(camYaw))

--Convert yaw to radians, then calculate sine local camYawSin = math.sin(math.rad(camYaw))

请记住,如果您正在反转的游戏已经将其值存储为弧度,则您不必担心将度数转换为弧度的步骤!

上面的公式允许您自己计算值,然后在内存、堆栈或寄存器中查找它们,从而帮助您找出特定值所在的位置。

问题2:是否有通过逆向识别游戏坐标系的通用方法?

目前我对此仍然不确定。一旦发现 XYZ 俯仰/偏航,您可以通过查看计算的行为来弄清楚它,但这并不是我想知道的答案。如果/当我弄清楚这一点时,我会更新这个答案。

问题 3:如果我知道游戏中使用的坐标系,以及 XYZ 和 Pitch/Yaw 摄像机值,是否有公式方法可以计算正弦/余弦,然后使用速度修改器成功实现自由摄像机? 如果我无法在游戏中找到要在我的脚本中使用的 sin/cos。

问题 4:我对我在试图理解与 sin/cos 相关的计算时查找的三角函数/计算函数只有一个粗略的理解。给定一个左手和右手坐标系,Z 向上,连同 XYZ、俯仰/偏航数据和速度修改器,有人可能会引导我完成必要的计算,以便在两个坐标系中进行向前工作,无论在哪里用户指向鼠标?例如,在任何给定时间,sin/cos 值究竟由什么组成,它们如何/为什么重要,以及为什么要添加/sub/mul 以适当修改 XYZ 等?

我将把这两个与我最终创建的脚本联系在一起。

首先,信用到期的信用。我使用SunBeam 的刺客信条:起源脚本作为初始模板。此外,我发现对这篇文章做出贡献的优秀个人的例子在整个过程中都非常有启发性——以及GuidedHacking在这里的答案,以及我最初发布查询的DMGregory 的答案最后,这种巨大的工作由弗朗斯鲍马游戏相机上的反转是任何人都希望在讨论这个话题精彩的参考。

现在,在左手坐标系中,您需要插入此脚本的只是相机的 XYZ 坐标,以及相机的俯仰和偏航。您可以将速度修改器更改为您想要的任何内容。我有一些键可以让相机移动得越来越快,而且我还实现了扫射,所以你可以在旋转时对角移动。

这是一个 Lua 脚本,创建用于在Cheat Engine 中运行脚本的顶部使用 Cheat Engine 函数分配内存来存储速度修饰符值。我已经对所有内容进行了大量评论,因此如果需要,您应该会发现将其转换为您选择的高级语言相对容易!

globalalloc(speedModifier,8) --Allocate 8 bytes memory
speedModifier:
dd (float)2 --Store a float of 2

{$lua} --Tells Cheat Engine to treat everything beneath as Lua

[ENABLE] --Everything beneath this happens when this script is enabled

function checkKeys(timer) --Check keys for input (check on a timer)

--Speed modifier value, read from memory location we allocated earlier
local speed = readFloat("speedModifier")

--Camera coordinates stored in variables
local camx = readFloat("[camBase]+1A0") -- Camera X
local camy = readFloat("[camBase]+1A8") -- Camera Y
local camz = readFloat("[camBase]+1A4") -- Camera Z

--Mouse rotation in radians
--Use math.rad() to convert from degrees if necessary
local rotv = math.rad(readFloat("[camBase]+1D0")) -- Vertical (Pitch)
local roth = math.rad(readFloat("[camBase]+1D4")) -- Horizontal (Yaw)

--Sine and Cosine of Rotation Values
local sinh = math.sin(roth) -- Sine of Horizontal (Yaw)
local cosh = math.cos(roth) -- Cosine of Horizontal (Yaw)
local sinv = math.sin(rotv) -- Sine of Vertical (Pitch)
local cosv = math.cos(rotv) -- Cosine of Vertical (Pitch)

  --Hotkey Setup
  --[[
        Button Mappings:

        Y - Forward
        G - Left
        H - Backward
        J - Right
        T - Down
        U - Up
        C - Speed Up
        Alt - Slow Down

      --Key combinations for strafing are also defined
      --Keys are oriented to where mouse is pointing
  ]]

  --Forward
  if isKeyPressed(VK_Y) then
    writeFloat("[camBase]+1A0", camx + (cosh * speed))
    writeFloat("[camBase]+1A8", camy + (sinv * speed))
    writeFloat("[camBase]+1A4", camz + (sinh * speed))
  end
  --Left
  if isKeyPressed(VK_G) then
    writeFloat("[camBase]+1A0", camx + (math.cos(roth - math.rad(90)) * speed))
    writeFloat("[camBase]+1A4", camz + (math.sin(roth - math.rad(90)) * speed))
  end
  --Back
  if isKeyPressed(VK_H) then
    writeFloat("[camBase]+1A0", camx - (cosh * speed))
    writeFloat("[camBase]+1A8", camy - (sinv * speed))
    writeFloat("[camBase]+1A4", camz - (sinh * speed))
  end
  --Right
  if isKeyPressed(VK_J) then
    writeFloat("[camBase]+1A0", camx - (math.cos(roth - math.rad(90)) * speed))
    writeFloat("[camBase]+1A4", camz - (math.sin(roth - math.rad(90)) * speed))
  end
  --Forward/Right
  if isKeyPressed(VK_Y) and isKeyPressed(VK_J) then
    writeFloat("[camBase]+1A0", camx + (math.cos(roth + math.rad(45)) * speed))
    writeFloat("[camBase]+1A8", camy + (sinv * speed))
    writeFloat("[camBase]+1A4", camz + (math.sin(roth + math.rad(45)) * speed))
  end
  --Forward/Left
  if isKeyPressed(VK_Y) and isKeyPressed(VK_G) then
    writeFloat("[camBase]+1A0", camx + (math.cos(roth - math.rad(45)) * speed))
    writeFloat("[camBase]+1A8", camy + (sinv * speed))
    writeFloat("[camBase]+1A4", camz + (math.sin(roth - math.rad(45)) * speed))
  end
  --Back/Left
  if isKeyPressed(VK_H) and isKeyPressed(VK_J) then
    writeFloat("[camBase]+1A0", camx - (math.cos(roth - math.rad(45)) * speed))
    writeFloat("[camBase]+1A8", camy - (sinv * speed))
    writeFloat("[camBase]+1A4", camz - (math.sin(roth - math.rad(45)) * speed))
  end
  --Back/Right
  if isKeyPressed(VK_H) and isKeyPressed(VK_G) then
    writeFloat("[camBase]+1A0", camx - (math.cos(roth + math.rad(45)) * speed))
    writeFloat("[camBase]+1A8", camy - (sinv * speed))
    writeFloat("[camBase]+1A4", camz - (math.sin(roth + math.rad(45)) * speed))
  end
  --Up
  if isKeyPressed(VK_U) then
   writeFloat("[camBase]+1A8", camy + (speed * 0.5))
  end
  --Down
  if isKeyPressed(VK_T) then
   writeFloat("[camBase]+1A8", camy - (speed * 0.5))
  end

  --Speed Modifiers
  if isKeyPressed(VK_C) then --If C is pressed, increase speed; max of 100
    if (speed < 100) then
      writeFloat("speedModifier", speed + 1)
    end
  elseif isKeyPressed(VK_MENU) then --If Alt is pressed, slow way down
    writeFloat("speedModifier", .3)
  else --If nothing is pressed, normal speed
    writeFloat("speedModifier", 2)
  end    
end

t=createTimer(nil)
timer_setInterval(t, 10)
timer_onTimer(t, checkKeys)
timer_setEnabled(t, true)

[DISABLE] --Everything beneath this happens when the script is disabled

timer_setEnabled(t, false)