Author Topic: Trigger Screenshot in nwmain  (Read 2083 times)

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« on: May 03, 2014, 10:27:46 pm »


               Hi Guys,
In my PW, I use a piece of Software that the players can optionally have running to sync the online resources with their local machines.

Eg: Optional content like music etc


I have created new functionality that allows it to detect when screenshots are taken in-game (on the server), and then post them to our facebook community page.


I am now working on some more functionality, that will allow the PW Server to request the Client to take a screenshot.

Eg: it sends a message over tcp, and the client when they receive it, will trigger a screenshot, then the screenshot is sent to the facebook page.

Uses for this would be along the lines of immortalizing boss defeats, or epic battles etc, or cutscene moments.


The problem I am having, is that the Windows API does provide methods for triggering/simulating the Print Screen key is being pressed, but they dont seem to be interacting well with nwmain.
Eg: Nothing happens - when the code to trigger a print screen fires, no screenshot is being taken by nwn.

I've also tried using the clipboard, but any image stored there is just a black screen.
OpenGL and DirectX Games do not make it easy to capture screenshots, because the graphics exist in a different memory portion than the OS.

I was wondering if anyone has any ideas or clues about how one might trigger NWN to actually take a screenshot?
It seems that the windows API refuses to let me simulate a user pressing the print screen key, so I need to take another avenue : maybe unsafe code, and actually fire a method inside the nwmain process?

Does anyone have a symbols file for nwmain?
I'd be interested to see if they have a method inside that handles the screenshots, and if it can be hooked externally?
               
               

               
            

Legacy_virusman

  • Sr. Member
  • ****
  • Posts: 448
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #1 on: May 04, 2014, 10:13:46 pm »


               

Call __thiscall 0x004269A0 (void * this, int, int, int)


with params: *(**(0x0092DC50)+0x4), 0x5E, 0x80, 0



               
               

               
            

Legacy_virusman

  • Sr. Member
  • ****
  • Posts: 448
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #2 on: May 04, 2014, 10:37:13 pm »


               

Or just use nwnapi from NWNCX:


https://github.com/v.../include/nwnapi


Define a new function: void (*HandleInputEvent)(CClientExoAppInternal *, int, int, int) = 0x004269A0


And call it: HandleInputEvent(g_pAppManager->ClientExoApp->Internal, 0x5E, 0x80, 0);


See nwncx_connect and nwncx_serverlist sources to figure out how to link with nwnapi.


 


Alternatively, you can look at the disassembly at 0x0042765B and try to replicate that: set CClientExoAppInternal+0x170 to 1, write &char * with destination filename to CClientExoAppInternal+0x178. This will make a screenshot on the next cycle.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #3 on: May 06, 2014, 01:15:27 pm »


               

Thanks Virusman for posting the offset information, and method signature.


 


I am currently researching dll injection techniques that are viable for .net (c#)


 


I believe that I might be able to inject a dll into the nwmain process, and then have that dll (a c++ bootstrapper) execute a method contained in a c# dll, for triggering the method discussed above.  


 


I've already got it working to the point where a dll can be injected into the process, however, I soon learned that I need to inject a c++ dll, which initiates the .net framework (CLR) within the NWN Process, and then have the bootstrapper make a further call to my 'actual' dll, which will contain the instructions to execute the delegate for the method pointer/address above.


 


Don't suppose you have any experience with c++ bootstrapping and .net?


 


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Bootstrap();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
 

 


which then calls


 


#include "stdafx.h"
#include <stdio.h>
#include "objbase.h"
#include "MSCorEE.h"
#import "C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb" raw_interfaces_only
using namespace mscorlib;

void Bootstrap() {
OutputDebugString(L"MceFM Bootstrap Started");

CoInitializeEx(0, COINIT_MULTITHREADED );
ICorRuntimeHost* pICorRuntimeHost = 0;
HRESULT st = CoCreateInstance(CLSID_CorRuntimeHost, 0, CLSCTX_ALL,
IID_ICorRuntimeHost, (void**)&pICorRuntimeHost);
if(!pICorRuntimeHost) return; // Clean up and log errror ...

HDOMAINENUM hEnum = NULL;
pICorRuntimeHost->EnumDomains(&hEnum);

if(!hEnum) return; // Clean up and log errror ...

IUnknown* pUunk = 0;
st = pICorRuntimeHost->NextDomain(hEnum, &pUunk);
if(!pUunk) return; // Clean up and log errror ...

_AppDomain * pCurDomain = NULL;
st = pUunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCurDomain);
if(!pCurDomain) return; // Clean up and log errror ...

_bstr_t assemblyName =
"Last, Version=1.1.0.0, Culture=neutral, PublicKeyToken=792d614cdf38e9ce";
_bstr_t typeName = "MceFM.Last.Inject.Injectee";

_ObjectHandle* pObjectHandle = 0;

pCurDomain->CreateInstance(assemblyName, typeName, &pObjectHandle);

}

 


 


I've never worked on making a c++ dll from scratch before, so the bootstrapper is my current challenge.


 


 


From what I understand, I should be able to get the .net DLL to execute a delegate for the memory location you posted above... if I can get the code to execute under the context of the nwmain process.


To do that, I need to inject into the process, so its the Process firing the method, and not my external application. (although it will be my external application that triggers it)


Am I right that when the dll is injected, DLLMain is executed automatically for the c++ dll, and then I need to use that to spawn off my .net dll, and then execute methods within it?


 


Even if I don't get it finished, its still interesting, cause I am learning more about Injection etc



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #4 on: May 06, 2014, 01:19:32 pm »


               


Alternatively, you can look at the disassembly at 0x0042765B and try to replicate that: set CClientExoAppInternal+0x170 to 1, write &char * with destination filename to CClientExoAppInternal+0x178. This will make a screenshot on the next cycle.



 


Does this mean a screenshout could be achieved by simple memory editing?


Eg: not needing to inject any dll?


 


Eg:


If I find the memory offset of CClientExoAppInternal, then add 0x170 :  and set that to 1, while at the same time Writing a string (filepath) - converted to bytes to the 0x178 : this would trigger a screenshot?



               
               

               
            

Legacy_virusman

  • Sr. Member
  • ****
  • Posts: 448
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #5 on: May 06, 2014, 06:01:30 pm »


               

Yes. I'd write the string first and the flag later to avoid a race condition, though.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #6 on: May 06, 2014, 07:52:14 pm »


               What is the memory offset of CClientExoAppInternal?

Is it 0042765B or 0x004269A0 ?
               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #7 on: May 06, 2014, 08:25:06 pm »


               Im trying to do this manually through cheat-engines memory viewer.
So far not having much luck.

Tried going to

0042765B + 178 - wrote c:\mypic.tga as well as mypic.tga and mypic - in the hopes that one of them would work.
then I did 0042765B + 170 : and wrote a 01 value to that location.

The 01 didnt seem to vanish or anything, and no pictures appeared at either the screenshot directory, or my c:\

When I went in-game, and proceeded to press print screen, it did then crash my game.... :-(
               
               

               
            

Legacy_virusman

  • Sr. Member
  • ****
  • Posts: 448
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #8 on: May 07, 2014, 09:31:04 pm »


               

You won't get far without static or dynamic analysis - use IDA to get addresses and/or a debugger (IDA debugger is very good for that) to examine memory layout at runtime.


0x0042765B is in the code segment, it's a part of the function.


0x0092DC50 is the global variable you need, but again, read NWNCX sources and use IDA debugger: 99% of the time these things don't work at the first attempt, and without a debugger you won't know what's happening.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #9 on: May 07, 2014, 10:00:57 pm »


               Does a symbols file exist for the client?
Eg: like nwserver?

I will load it up in IDA now, and have a nosy.
               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #10 on: May 07, 2014, 10:57:35 pm »


               Im in IDA, and I think I have found a method that runs every cycle: it seems that when the print screen button is pressed, it triggers a specific branch in a switch statement to be fired.

jumptable 00426ADC case 94


Logic would suggest that I need to trigger this case statement?
Either by directly firing it, or by changing the value of the variable being used in the switch statement, to point to case 94.

.text:00426AD4 ; ---------------------------------------------------------------------------
.text:00426AD4
.text:00426AD4 loc_426AD4: ; CODE XREF: sub_4269A0+A6j
.text:00426AD4 ; sub_4269A0+DFj
.text:00426AD4 xor ecx, ecx
.text:00426AD6 mov cl, ds:byte_428810[ebx]
.text:00426ADC
.text:00426ADC loc_426ADC: ; CODE XREF: sub_4269A0+12Dj
.text:00426ADC jmp ds:off_42870C[ecx*4


This is the switch statement.
I have no idea how to interpret this...
Is ecx the value that is being switched on?
If so: it has a value of 30 in a screenshot instance
               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #11 on: May 14, 2014, 12:41:05 am »


               Im making somewhat slow progress, and running out of avenues to explore.
Any assistance would be appreciated.

I am injecting my own custom dll into the nwmain.exe process, (a c# bootstrapper dll, wrote in c++)

The c# DLL contents are as follows:


namespace Inject
{
    public class MainClass
    {
        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        public delegate void Screenshot(uint uThis, int u1, int u2, int u3);

        //      00426ADC

        public static int DLLMain(string arg)
        {
            MessageBox.Show("Start");
            try
            {
               
                Screenshot screenshot = WhiteMagic.Magic.Instance.RegisterDelegate<Screenshot>(0x004269A0);
                screenshot(0x0092DC50 + 4, 0x5E, 0x80, 0);
                ////MessageBox.Show("Loaded DLL");

            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }

            return 1;
        }
    }
}

I am using a somewhat open source library called WhiteMagic which handles execution of methods from inside other programs - I believe people wrote it for the purpose of making bots and such for games like World of Warcraft.

I have ran nwmain with IDA attached, and I can confirm that this does fire the branch of the switch statement that should be making a screenshot, but for some reason, nothings appearing.

With regards to this line


screenshot(0x0092DC50 + 4, 0x5E, 0x80, 0);
It being a thiscall convention, am I right in saying that 0x0092DC50 is the location of CAppManager, and the Internal is +4 into it?
So the this call should be right? right?

I think the reason its not writing the screenshot is because its missing a filename: stepping through in IDA revealed that it didnt have a filename to append onto the screenshots directory.

I am going to try writing to Internal+0x178 to see if I can give a fake name.
I will report back with my success or failure story.
               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #12 on: May 14, 2014, 01:28:40 am »


               Not going so well...
Even writing the filename to memory as an array of bytes doesnt get it to save the screenshot.
Its like it is saying the file path is invalid or something, which then causes it to fail to do fopen?
               
               

               
            

Legacy_virusman

  • Sr. Member
  • ****
  • Posts: 448
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #13 on: May 14, 2014, 08:32:51 pm »


               

No, 0x0092DC50 is the location of a pointer to AppManager, AppManager+0 is a pointer to CClientExoApp, and offset +0x4 in ClientExoApp is a pointer to CClientExoAppInternal, and that is the pointer you need to pass as "this" (ecx, not stack) into the function, according to thiscall calling convention.


I think it'd be easier to do this low-level stuff in C and call that C method from C#. Easier to debug C code from IDA, too.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Trigger Screenshot in nwmain
« Reply #14 on: May 14, 2014, 09:20:27 pm »


               Ah...
I get it now
I didn't realize that +0 was a pointer, I thought it was an offset.
I had it in my head that the AppManager +0 was the start of the ExoApp, and then +4 from that was the Internal.
Now that I know its a pointer, I can read the pointer address, and feed that in instead.

I will give it a go now: i think I was calling the right method, with the right arguments with exception to the 'this'.