Link to home
Start Free TrialLog in
Avatar of Edocecrous
Edocecrous

asked on

DirectX screen capture

If someone knows,
how to access a thread's (program's) (secondary) surfaces from another thread (program).

I think the DirectX surface capturing is a well kept secret. /at least i could not find ANY info on the topic/

I need just the idea, i can implement it...

THX,
Edocecrous
Avatar of qbasa
qbasa

Hey Edocecrous,
I had this same dilema you are having, I could not find anything about directX screen capture either, so I just started trying everything. The thought I had was that all I had to do was make a copy of the primary display surface and save it as a .bmp file. So I eventualy found that using the windows GDI allowed me to gain access to the windows handle(hWnd) of the primary display surface. I would start looking into that, I am sure there are better ways but this methode has worked for me.
I hope this helps you get on the right track. :)
Avatar of Edocecrous

ASKER

Did you used the GDI-compatible surface and GDI to make copy of it, or you could get the lpDDsurface pointer, and accessed the memory?
(Because i need to access the secondary surfaces, and GDI has no access to that.)

ps.:I'm reversing a screen-grabber's dll right now, but it moves slowly, so any idea welcomed.

Edocecrous
I didn't attempt to gain access to the secondary surface, I used the GDI to create a device context for the "Display", which allowed me to basicaly take what you see on the monitor and use it wherever i wanted. I tried it with a couple directX games and it worked. But you want to get access to the secondary surface, and I do as well. But I have yet to find anyone that can help me there.

Here is the address of the code example I used to make my
screen cap program. It is VB code so if you don't have vb
then this won't help much.
http://www.vbcode.com/asp/code.asp?lstCategory=Graphics
Right now i'm working on a model, how to get to those surfaces. First, i created a program which creates the surfaces.
-than i made code to enumerate those surfaces,
-i'll try if it works from different thread,
-if it does, i'll try it from different process.

As far as i could get into the details with the directX capturer program i'm dissecting, first if finds where the ddraw.dll is mapped in the current process's address space.
Than it gets the address of the DirectDrawCreate() function in it. THan it gets the currentprocessID. Than it tries to enumerate the surfaces by poking around in memory...

So it's basically not hard stuff, but i'll have to work on it...

I think these DX-grabber programs use a techinc called "code infusion", where you copy your code into the address space of the program you want the surfaces from.

Code infusion should raise no problems, but to write code that can call API or DirectX functions inside a different context will be interesting...

So i'll get back to it....

(I'm also working on 'low level' version, writing a VxD which can poke around in the other process....)
Hmm, interesting... That is something I should look into
as well. What I have been trying to do for a while now is to get a small graphic to display at all times on the screen. Even over a directX application. So maby I could
instead of writing to the primary display memory area,
I could write my image data in the secondary memory area set by directX itself to reduce screen flicker. Well that
is only if it isn't locked in some way. Time to do more
research, hopefully I can figure this out as this has been
haunting me for some time now. hehe

Good luck to you
Time to go learn! :)
In DirectX, to display something over the screen, they use overlay surfaces, maybe that's waht you need. /DVD players use that technic/
Also, if the flipping chain is locked, DirectX can't use it, so there should be plenty of time to catch it.

What i want to do is to get access to the offsecreen surfaces of games, and learn how they do the animation, and how thaey build up the levels, etc...

Without using Ring-0, there are functions like CreateRemoteThreadEx, and WriteProcessMemory, etc. to access other processes. Also, i did a test, and in 2 different DirectX processes, the directdraw interface was mapped to the same (shared memory) region. The shared region is: 0x80000000 to 0xBFFFFFFF).

Edocecrous
Did you ever figure this one out?  Know that you probably havent checked this for a while, but it's a unlocked q, so I'm hoping you'll check the email ;)
Hi there!
Yes, i checked my mail. I have 5 mail accounts to check, but i checked this one. hehe.

So, I did figure out, but not all. what i realy want to do, is to get inside the game diablo, and check out the animation, and background arrangement of the sprites, because i'll write a similar one, and it's a good oportunity to learn.

I wrote the program to inject code inside diablo's process, it's working. Also, it's running in a separate thread, so after that, the hooking is released.

The only problem so far is the COM object of directx.

When a thread creates a surface object, it gets back an interface pointer. When the program is already running, where the hell the DDRAW object keeps the interface list, and how to get there....

My only problem is, i'm maximalistic. I want a code wich works with any running process.

Otherwise, i could start up diablo in a process at suspended state, modify the ddraw dll loaded in the process, or modify the Import Address Table ot the diablo.exe, so when it calls ddraw.create etc, it uns through my code, which logs it.

As soon as i have the pointer, beeing in the same process, i can access all the surfaces... ...i tried that part, it works.

So far i spent $150 on books to get the info i need, but couldn't find it.

I guess i just have to play with the code... right now i have no time to do it, but i'm determined to solve the problem the mentioned way if it's not working out othervise.

Edocecrous



I'm trying to do the exact same thing myself, with ultima online though, not diablo (directdraw).

could you tell me which api calls your hooking?  I was thinking about getting handles from the directdrawcreate function or something like that, though that's quick thinking and I haven't really done any research on it yet.
You know, it's a pain in the b*tt to log in to EE each time i need to answer. Please just send a letter to edocecrous@angelfire.com OK? or my ICQ:27494791

First of all, it has been an idea so far.
I have a running thread inside Diablo's process, so i can do whatever i want in the Diablo address space.

But i did not hook any functions yet, i didn't do the IAT patching yet. I would start with ddcreate, however.

Right now i have a different approach, and i suggest to you, to do the same thing, so we can help each-other, and share;-)

Here it comes:
-What is the problem: We have no idea how to get the interface of the surface created by another thread.
-What we know: If we have the interface address, we CAN access the interface from a different thread than the creator thread of the surface.

The IAT patching will be our (at least main) last trick, which must work. BUT, first i like to try some other tricky way to get the interface pointers.

To do that: I created a program, which has 2 threads, and and 1 thread creates a surface. The other thread tries to get it...

This way the situation is the same as we would be inside a program with our spy-thread, and trying to get the interface. But, we have the advantage of KNOWING the interface pointer, and all the creation parameters, so we can actualy check, if we are successful or not.

I would try to create many surfaces, and then compare the interface's addresses, maybe they follow each other? Or something like that.

Edocecrous
ps.:If this approach fails, we have to do the IAT patching. I have a book which explains it. (not the IAT patching, but how to patch directx)
I just simply not want to do it before i'm not sure that's the only way. Also, i never tried to start another program in suspended state, so i need to look into that either...

So drop me an email....

pps.:I'm doing my finals this month, that's why i can't check out things right now.
Hey Edocecrous,
  I analyzed a directx program wrote by myself and found sth. really interesting.Here's what i got.
  Whenever we call DirectDrawCreate in a process,we'll get the same IDirectDraw object and it points to a undocumented structure.The 2nd DWORD value points to another structure and the value of that structure at offset 44 is the pointer toward primary surface.
  Ooh!
  Sorry for my poor english.I think I should speak in "code" instead.
  the following is sample code.hope it can works on ur PC.
  BTW,the code can also get the back surfaces,but i can't express clearly how to get it  :(
**Delphi code ONLY**
unit test1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DirectDraw, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Timer1: TTimer;
    lb: TListBox;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    FDirectDraw:IDirectDraw;
    FPSurface,FBSurface:IDirectDrawSurface;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  DC:HDC;
  SurfaceDesc:TDDSURFACEDESC;
  DDSCaps:TDDSCAPS;
  a,b,c:dword;
begin
  if DirectDrawCreate(nil,FDirectDraw,nil)=DD_OK then
    if FDirectDraw.SetCooperativeLevel(Application.Handle,
       DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
       or DDSCL_ALLOWREBOOT)=DD_OK then
      if FDirectDraw.SetDisplayMode(640,480,8)=DD_OK then
      begin
        FillChar(SurfaceDesc,SizeOf(SurfaceDesc),0);
        SurfaceDesc.dwSize:=Sizeof(SurfaceDesc);
        SurfaceDesc.dwFlags:=DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
        SurfaceDesc.ddsCaps.dwCaps:=DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP
                                     or DDSCAPS_COMPLEX;
        SurfaceDesc.dwBackBufferCount:=1;
        if FDirectDraw.CreateSurface(SurfaceDesc,FPSurface,nil)=DD_OK then
        begin
          ddsCaps.dwCaps:=DDSCAPS_BACKBUFFER;
          if FPSurface.GetAttachedSurface(DDSCaps,FBSurface)=DD_OK then
          begin
            if FPSurface.GetDC(DC)=DD_OK then
            begin
              FPSurface.ReleaseDC(DC);
            end;
            if FBSurface.GetDC(DC)=DD_OK then
            begin
              FBSurface.ReleaseDC(DC);
            end;
            Timer1.Enabled:=true;
        a:=dword(pointer(@FDirectDraw)^);
        b:=dword(pointer(@FPSurface)^);
        c:=dword(pointer(@FbSurface)^);
        lb.Items.Add('iDirectDraw:'+inttohex(a,8));
        lb.Items.Add('FPSurface:'+inttohex(b,8));
        lb.Items.Add('FbSurface:'+inttohex(c,8));
        lb.Items.Add('');
          Exit;
          end;
        end;
      end;
  MessageBox(Handle,PChar('Error!'),PChar('Error!'),MB_OK);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  hr:HRESULT;
begin
  while true do
  begin
    hr:=FPSurface.Flip(nil,0);
    case hr of
      DD_OK: exit;
    else
      exit;
    end;
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if FDirectDraw<>nil then
  begin
    FDirectDraw.FlipToGDISurface;
    FDirectDraw.SetCooperativeLevel(Application.Handle,DDSCL_NORMAL);
    if FBSurface<>nil then FBSurface:=nil;
    if FPSurface<>nil then FPSurface:=nil;
    FDirectDraw:=nil;
  end;
end;

procedure x;
var
  DC:HDC;
  a,b,c:dword;
  pp:pointer;
  FD:IDirectDraw;
  sur:array [0..5] of dword;
  i,j:integer;
//  bm:tbitmap;
begin
  if DirectDrawCreate(nil,FD,nil)=DD_OK then
  begin
    pp:=@fd;
    a:=dword(pointer(dword(pp^)+8)^);      //IDirectDraw
    sur[0]:=dword(pointer(dword(pointer(a+4)^)+44)^); //point to PrimarySurface structure
    b:=sur[0];
    for i:=1 to 5 do
    begin                          //point to backSurface structure
      b:=dword(pointer(dword(pointer(dword(pointer(b+4)^)+12)^)+12)^);
      if b<=0 then break;
      if dword(pointer(b+12)^)<dword(pointer(sur[i-1]+12)^) then break;
      sur[i]:=b;
    end;
    for j:=0 to i-1 do  //form the primary surface to the last back surface
//j:=0;  //only process the parimary surface
  begin
    pp:=pointer(sur[j]);
////////////1
asm                             //getdc
    lea edx,dc
    push edx
    mov eax,pp
    push eax
    mov eax,[eax]
    call [eax+$44]
    mov c,eax
end;
    if c<>DD_OK then exit;
    SetBKColor(dc,RGB(0,0,255));
{    bm:=tbitmap.Create;
    bm.Width:=GetDeviceCaps(dc,HORZRES);
    bm.Height:=GetDeviceCaps(dc,VERTRES);
}//Need Lock??
    SetTextColor(DC,RGB(255,255,0));
    TextOut(dc,0,0,PChar('XYZAB'),5);
{    bitblt(bm.Canvas.Handle,0,0,bm.Width,bm.Height,dc,0,0,SRCCOPY);
    bm.SaveToFile('C:\WINDOWS\Desktop\1.bmp');  //save a screenshot
    bm.Free;}
asm                                    //releasedc
@@a:
    lea edx,dc
    push edx
    mov eax,pp
    push eax
    mov eax,[eax]
    call [eax+$68]
    cmp eax,0
    jz @@a
end;
end;
  fd:=nil;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
x;
end;
end.
ASKER CERTIFIED SOLUTION
Avatar of tTUi
tTUi

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi!
I'm sorry for not posting anything for a while, but i had school.
I was about to post the same thing myself, because i found out exactly the same thing, myself... :-)
I have delphi, but i don't like pascal.

I hope that Microsoft will not read this article, because if they will, they will change this goodie, and it will be not available in future directx versions.

I read through your code, and i did nearly exatly the same thing!
(about a week ago, but i had no time to post it)

The delphi code looks lengthy, compared to the C++ one i wrote.

But it works on the same principle.

Also, i went into diablo, and find out something:
They used only Lock() and Unlock(), and they wrote their own rendering code. Thus, there is no double-buffering (back surface) no Blitting to copy strites, nothing.
They render directly to the screen, using repz movsd...

That was disappointing, since that time DirectX 3 was out, whith nice features, like BltFast() etc...

I'm happy you found the solution, and even more, because you found it without (too much) help!

Also, please do not make a news from our findings, since i'm sure Micro$oft will change it if it gets out!
They did it for smaller things...

If someone needs it, can find it on this post.

You know, what's funny, i did all the things:
Inserting a thread into the Process,
Doing Global Hooking,
Loading/freezing the app to do IAT patch,
etc,,,

1.And it turns out, this tweak works cross-process, without doing any illegal stuff... :-)

2.I find out, Diablo has no offscreen surfaces, and use no Blitting...

Edocecrous (Csaba L. Bacskay)

ps.:If you, or anyone else has questions about this post, please write an email to:edocecrous@angelfire.com

pps.:If you want to write a DirectX screen grabber program, HyperSnap DX is out there, and it does Glide and regular screens too.
I'm working on a program, which can display all active DirectDraw interfaces and surfaces, and save them to file, like Primary, back buffer, offscreen, overlay.
When it's finished and out there, you can ask questions. Not before.

Just because i figured it out myself i will not deny the points from the correct answer. Way to go tTUi!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!                                                  !!
!! Since i've got lot's of email about this stuff,  !!
!! please know this: I WILL NOT SEND YOU CODE.      !!
!! So please don't send an email asking for it.     !!
!! I DO HELP YOU FIND THE ANSWER, but you have to   !!
!! work on/for it.                                  !!
!! ps.:Looking for talented people.                 !!
!!                                                  !!
!!                 WWW.MYNERGY.COM                  !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!