Introduction of the dll

45 views
Skip to first unread message

ROTA

unread,
Sep 11, 2011, 9:47:31 AM9/11/11
to TexMod development
1) General introduction
2) dll injection
3) OTM_DX9 introduction


1) General introduction
A game will load the d3d9.dll and call the Direct3DCreate9() function,
which returns a IDirect3D9 interface. This function can be regarded as
the first directx call of the game. Afterwards the game creates an
IDirect3DDevice9 device by calling interface->CreateDevice().
With this device the basic directx function are called by the game.

What we need is to intercept some of these call, to modify them a
little bit, and call afterwards the original functions.

How do we get their?
We will write our own OTM_IDirect3DDevice9 class, which is inherited
from the original IDirect3DDevice9 class. We implement all of the
original function, which does nothing else than calling the original
functions (first testing step)

How to we get the game to use our fake_device instead of the real one?
We create OTM_IDirect3D9 class, inherited from the original IDirect3D9
class, implement all functions....
but the fake_interface->CreateDevice() function will create an
original IDirect3DDevice9 device and a OTM_IDirect3DDevice9
fake_device. The fake_device will hold a pointer to the original
device, the fake_device is returned.

How to we get the game to use our fake_interface instead of the real
one?
We need the game to load our dll and to call our Direct3DCreate9()
function (see 2) dll injection)
Our Direct3DCreate9() function will create an OTM_IDirect3D9
fake_interface and an original IDirect3D9 interface. The
fake_interface will hold a pointer to the original interface. The
fake_interface will be returned to the game.

you guess what happens. Yes, the game will only deal with our fake
objects and we intercept all needed directx functions automatically.




2) dll injection

Currently 2 methods are possible.
1) Some games don't load the d3d9.dll from the system directory. For
them it is fine, if we place our dll (renamed to d3d9.dll) in the game
directory, and the game will load our library. You just need to run
"make NO_INJECTION=1". But since I have used the second method the
last time during development, this method is not up to date and thus
won't work at the moment.

2) We hook all Programs. Each time a program is started, while our
hook is active, our dll is loaded automatically into this process (and
gracefully before the program can initialize it self). In our dll
initialization routine, we ask for the name of this executable and if
this is the target process, we go further.

We load the original d3d9.dll and now we detour the original
Direct3DCreate9 function to our Direct3DCreate9 fake function. That is
all!
But there's one hitch, we can't unhook our dll, before all hooked
games are closed, otherwise the game will immediately crash!
But the advantage is, we don't have to care about the command line
parameters of the game or how the game is started. It can started by
double clicking on a desktop icon, through command line or even
through steam (you will need to hook the game.exe, not the steam.exe).
The only requirement is, that the OTM_GUI must be started before the
game.




3) OTM_DX9 introduction
will follow soon in the next post

ROTA

unread,
Sep 12, 2011, 7:57:16 AM9/12/11
to TexMod development
3) OTM_DX9 introduction

Communication between the game and the GUI:

The dll is loaded into the game, thus it has its own memory space. To
interchange data with the GUI it uses two pipes. One for outgoing
messages (error state etc, which is not used at the moment) and an
incoming pipe to receive the path of the textures, etc.
A Server (OTM_TextureServer) is listening on the incoming pipe in the
Mainloop(). Because the ReadFile is blocking (it waits till something
is written on the opposite site of the pipe) it must run in an extra
thread. If The Server receive some messages it prepare an update and
give it to all Clients. (Note all these things are done by the server-
thread)

A Client (OTM_TextureClient) Object is hold by each
OTM_IDirect3DDevice9 object. Somehow there can exist more than one
device object, thus the OTM_TextureServer can handle with more than
one OTM_TextureClient.

At the beginning of each fake_device->BeginScene the MergUpdate() of
the Client is called. Afterwards the original device->BeginScene() is
called. If the Server has add an update the MergUpdate() will remove
unused fake texture and load/reload new fake textures. (Note: this
will be called from the game rendering thread, during the render
process of one frame)

Note: Because the server does not know when and if a client has merged
the update, it can't delete the allocated memory. This is done by the
Client.
Note: To guarantee a stable run, one has to use a mutex, if the server
adds an update or the client merge the update. But the corresponding
functions are commented out.




How to distinguish the texture:

How does one detect which texture to mod or to save? Characterization
by the pointer address is a bad idea, cause the address range always
change (also the relative addresses). One has to use hash values. The
hash is computed over the raw texture data. With LockRect() one gets
the pointer to this data. But the size of the data has to be calculate
by the width and hight of the texture as well as the format. This has
to be done with care! Although one doesn't change the data (while
computing the hash), an access violation during the read can also
crash the game.

This procedure has to be done with each texture loaded by the game.
(if save all texture is set to true, the texture is afterwards saved)

Because we don't have the code of the original TexMod we don't know
the exact hash function or over which parts of the texture the hash is
computed. Thus we will probably not be able to support mods from the
original TexMod.

But there is the next problem. On load a Texture with function
D3DXCreateTextureFromFile(), D3DXCreateTextureFromFileInMemory(), ...
but these function can be static linked by the game, thus one can't
intercept them. The only function we can intercept (and we already
have) is the IDirect3DDevice9::CreateTexture(). This function
generates an empty texture and is called by the
D3DXCreateTextureFromFile(), D3DXCreateTextureFromFileInMemory(),...
functions.

One gets the pointer to the created texture, when the texture is
empty. So one stores this texture to LastCreatedTexture. The next time
a texture is created and LastCreatedTexture is unequal NULL, we can
calculate the hash (hopefully now this texture is loaded completely).

Note: LockRect() sometimes fails. Never mind, we just return..
Note: Somehow texture can be loaded and immediately released, thus
CreateTexture() is not called in between. The problem is, that
LastCreatedTexture now points to an non existing texture. The next
estimation of the hash would crash the game. If a texture is released,
one has to compare the this pointer with LastCreatedTexture and if
they are the same, one set LastCreatedTexture to NULL.
Note: If loading a fake texture, also the CreateTexture() will be
called, thus one must set the fake-bit to true, otherwise during the
next call of CreateTexture() our fake texture would be treated as
original texture (one could also set the LastCreatedTexture to NULL,
but we need the fake-bit at least if a texture is released).




How to switch the texture on the fly:

If one likes to switch the texture, one need the save point of the
pointer to the texture, thus one can write to the save point the
pointer to the new texture. Unfortunately one only knows the pointer
to the texture, but not the memory place, where the game stores the
pointer.
The trick is again the use of a fake texture object.
OTM_IDirect3DTexture9 is inherited from the original IDirect3DTexture9
class. After the original IDirect3DDevice9::CreateTexture() is
called, we create our fake texture object and return this to the game.
But we must take care, if a texture is loaded with
IDirect3DDevice9::SetTexture, we must always pass the original texture
object to the original device!!

Note: There exist also some standard texture, which are not loaded
through our fake device. These texture must not be handled as
OTM_IDirect3DTexture9!!

The OTM_IDirect3DTexture9 holds a Pointer to the original texture, the
hash of the texture and a Cross reference to an other
OTM_IDirect3DTexture9. Switching OTM_IDirect3DTexture9 texture is
simple, one interchange the pointer of the original texture and
generate the cross reference.
The cross reference is needed for unloading or deleting a texture.

Next, one has to keep in mind all the textures which are loaded from
the game (OTM_TextureHandler OriginalTextures) and all textures which
are loaded as fake textures (OTM_TextureHandler FakeTextures). The
reference value is the index of these arrays and is only need for a
fast deleting. (dammed, while writing this line I found an error in
the code at this point)

We must store all original texture, because we want to mod them on the
fly, and we must store all fake textures, because they must be deleted
when destructing the directx device.
Maybe we should comment the (OTM_TextureHandler FakeTextures) out and
delete the fake texture, if the original texture is deleted (or it is
unmoded through an update).




Speed up:

To do not misspend texture resources, I load fake textures only if
needed. The fake textures are loaded if the matching original texture
was loaded by the gamer or if an update was send (the last point will
executed from the render thread and should be fast). For that I
decided to hold the file content of the texture in the ram. The
loading of the file from disk and the corresponding errors is done by
the server thread. The final load of a fake texture is done by the
client (a thread of the game) by calling
D3DXCreateTextureFromFileInMemory().





Threads, Threads, and Threads and a stable run...

we only know that there exist one server thread, which we start at the
beginning. But we don't know, how many threads the game use to handle
with the device (well we could ask for the TID and count....). The
problem is a stable run. E.g. if more than one thread of the game is
loading the original textures, we will get in serious trouble, not
only LastCreatedTexture is not thread safe but also the add of the of
the texture to (OTM_TextureHandler OriginalTextures).

At the moment no mutex are in use, but the functions are coded and
even called at the right position for guarantee a save flow between
the server and the client. But if the game use more than one thread,
we need to add more mutex.




How to select single texture (save single texture):

One can ask for the key state of a single key (if
(GetAsyncKeyState( Key_Number ) &1 )). This is done at the moment
before the original BeginScene() is called. To inking the selected
texture, one create a new (e.g. green) fake texture and switch it with
the selected original texture. That is all.
Reply all
Reply to author
Forward
0 new messages