注入帧缓冲区

时间:2013-04-05 17:41:19

标签: opengl framebuffer code-injection

从我年轻的时候起,我一直在“黑客攻击”我最喜欢的游戏之一,并且我已经设法能够拦截OpenGL调用并使用opengl32.dll包装器注入C ++代码。我已经能够为此提出一些很酷的用途,但我目前的目标是在这款旧游戏中实现后处理glsl着色器,使其看起来更现代。该游戏最初是在90年代后期发布的,因此它对OpenGL及其渲染代码的使用是有限的/原始的/过时的。

我已经能够通过注入代码来初始化和使用glsl着色器程序来成功生成顶点和片段着色器,但我的问题在于生成一个帧缓冲区/将我的场景渲染到纹理以作为采样器发送到我的片段着色器。据我所知,我制作的纹理通常都是黑色的。

很难找到与我正在做的事情相关的资源并检查原始的opengl调用(我一直试图通过读取单帧游戏玩法的opengl跟踪来确定插入代码的位置),所以我没有能够绕过我做错的事情。

我已尝试将代码放在很多地方,但目前,我在游戏调用wglSwapBuffers()之后绑定了帧缓冲区,并且在下一次glFlush()调用之前我立即解除绑定,但我不确定多个视口设置,矩阵操作,或者在这些时间之间创建的任何其他内容是否会以某种方式混淆帧缓冲。

这是初始化帧缓冲区的代码:

void initFB()
{
  /* Texture */
  glActiveTexture(GL_TEXTURE0);
  glGenTextures(1, &fbo_texture);
  glBindTexture(GL_TEXTURE_2D, fbo_texture);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glBindTexture(GL_TEXTURE_2D, 0);

    /* Depth buffer */
  glGenRenderbuffers(1, &fbo_depth);
  glBindRenderbuffer(GL_RENDERBUFFER, fbo_depth);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 1920, 1080);  
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth);
  glBindRenderbuffer(GL_RENDERBUFFER, 0);

  /* Framebuffer to link everything together */
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);

  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture, 0);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth);

  GLenum status;
  if ((status = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) {
    fprintf(stderr, "glCheckFramebufferStatus: error %p", status);
  }

  glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

这是wglSwapBuffers之后调用的代码:

GLenum fboBuff[] =  { GL_COLOR_ATTACHMENT0 };
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(1, fboBuff);
glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT);

这是glFlush()之前调用的代码:

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glPopAttrib(); // Restore our glEnable and glViewport states  

... Draw an Overlay containing with fbo texture binded ...

Here是单帧的gl轨迹。

这是将近800线,但训练有素的opengl眼睛应该能够看到什么时候进行的通话。我删除了数百行绘图调用,这是我至少理解的。

3 个答案:

答案 0 :(得分:2)

我不完全确定你想做什么。运行游戏渲染给你fbo?在顶部渲染东西? 首先,尝试挂钩所有wgl和gl调用。 http://research.microsoft.com/en-us/projects/detours/是一个很好的起点。

但是再次阅读你的帖子,我想你已经知道了。知道你想做什么会有所帮助。

  • 渲染叠加层:Hook SwapBuffers,在那里做你的东西
  • 捕获:再次,挂钩SwapBuffers
  • 渲染纹理和制作后期效果:Hook BindFramebuffer。但是有一个问题:帧缓冲区0是启动时的默认值,因此不使用它的代码不会调用BindFramebuffer启动,只能将其设置为0.尝试挂钩glClear或任何先来调用。

随时向我提供更多细节。听起来像一个有趣的教育项目,我很乐意帮助:)

我只是在发布之前再次阅读您的帖子,您的问题可能会轻松得多。作为一个Windows程序,你的目标假定fbo 0在开始渲染帧时被绑定。就这样做。在它完成之前,它将切换回fbo 0.不要做推/弹的事情。只要确保你的fbo尺寸相同(在更大的窗户上也可以)。你需要钩住BindFramebuffer。每当你的游戏调用bind 0时,你需要绑定你的游戏。但是没有必要做任何其他事情,比如触摸视口,游戏必须要照顾它。当你最终在swapBuffers钩子中渲染时,只需确保恢复你改变的任何东西。

此外,您确实不需要DrawBuffers调用。它混淆了我的痣,也可能是你的目标。 :)你的目标是GL 1.2代码,你正在做3 ... 4 ...

答案 1 :(得分:1)

根据我的经验,对于无阴影的GL1.x应用程序,使用GL拦截方法可以完成您尝试做的事情。

当我快速破解时,您可以暂时忘记FBO,只需在原始应用程序交换缓冲区时使用glCopyTexSubImage(),将渲染的内容复制到纹理中并对后处理函数进行文本处理。

在进行GL注射方法时,您应该注意一些事项:

  • 可能存在不同的GL上下文(尽管这比CAD典型游戏更有可能用于CAD应用程序)
  • 旧GL允许用户直接管理对象ID - 在需要时不调用glGen*函数(Nice应用程序仍然执行它们)。如果您自己创建新对象,原始应用程序将不会知道并可能重用这些ID。如果你想要做到这一点,你将不得不透明地重新映射对象ID(可能使用一些哈希表) - 这是很多工作,因为涉及许多GL功能。另一个邪恶的黑客就是自己使用一些不太可能的对象ID。
  • GL是状态机,因此您应该非常小心地恢复游戏期望的状态(尽可能多)。 OTOH方面,您还应该准备好,当您获得更改拦截它时,原始应用程序可能有任何GL状态集 - 所以您可能需要重置很多东西。

我可以向你保证,确实有可能将纹理渲染到一些旧的GL应用程序中,就像我自己做的那样。与Quake3和其他着名的时代游戏一起工作非常好。我没有看到您当前的方法存在明显的错误。我做的事情很少:你也应该拦截glDrawBuffer()并将应用程序要做的任何绘图转发到GL_BACK(假设这里是双缓冲应用程序)到你的颜色附件。您发布的日志没有跟踪此功能,因此游戏可能在启动时设置了一次。

我也不明白你使用glFlush的原因。对于任何特别的东西来说,它都不是一个好标记。如果游戏使用双缓冲,SwapBuffers将是你可以得到的“框架”的最佳近似值。只需在创建上下文后直接插入您自己的init对象创建内容(您也可以拦截wglCreateCintext()和朋友),以及 已经为FBO设置了渲染。在SwapBuffers上,只需使用着色器将纹理内容绘制到真实的后台缓冲区,执行实际的缓冲区交换,并将渲染恢复为FBO设置。

我不知道你是否已经意识到这一点,但是开源项目Chromium确实为OpenGL拦截提供了一个很好的框架。虽然这可能是分布式集群渲染的焦点,但它也可以在局部设置中工作,并允许您编写SPU(流处理单元),您可以在其中简单地为要挂钩的任何GL函数指定C函数。该项目已经过时,并且在5年左右的时间内没有积极开发,因为它被设计的GL流操作类型不适用于更现代的可编程管道,但它仍然可以与良好的旧GL1一起使用。 x固定功能管道......

答案 2 :(得分:0)

据我了解,您正在尝试更新旧游戏的图形方面,但您在原始代码中遇到限制,导致您无法改装所需的部件。

虽然我没有相关的游戏编码经验,但当我有一个我试图改造的应用程序时,我会反汇编应用程序来研究它的结构,并选择性地应用内存中的补丁来重定向某些功能和我提供的替代代码段。

我会预加载一个共享库,它会用条件/无条件跳转/调用指令覆盖程序存储器的某些部分,并选择性地进行NOP操作,这样我的代码就成了程序的一个组成部分。无论如何它绝对不优雅,但它让我能够快速解决原始代码中的缺陷。