Skip to content
This repository has been archived by the owner on Jul 13, 2020. It is now read-only.

Technical Details

mrfearless edited this page Sep 8, 2019 · 6 revisions

Summary

The loader EEex.exe will inject the EEex.dll into the Enhanced Edition (EE) game.

The EEex.dll searches the EE game for known EE game functions and EE game global variables by matching byte patterns defined in the EEex.db pattern database. EEex.dll also looks for a specific address to patch a redirection to a EEexLuaInit function located within itself.

The EEexLuaInit function, when called by the EE game engine, registers the Lua EEex_Init function with the Lua state and sets it to globally visible.

The Lua EEexInit function in turn registers and sets global visibility to other Lua functions defined in EEex.dll. These Lua functions are then available for usage from within EEex Lua files and game script files.

EEex.exe Detail

EEex.exe is the loader. It loads the game executable and injects the EEex.dll. It can be ran from a command console or by double clicking on the EEex.exe file.

Check game executable exists

EEex.exe checks if a known EE game executable can be located in the current directory. It looks for one of the following: Baldur.exe, SiegeOfDragonspear.exe, Icewind.exe or Torment.exe. If none are found a message is displayed to the user and the program exits.

Verify game executable

Once the EE game executable is located, it's File Version is compared to determine if it is the latest known version. If it is not a message is displayed to the user and the program exits. The File Version can be viewed by right clicking on the EE game executable, selecting properties and the details tab.

Check other files exists

Additional checks are done to verify if EEex.dll, EEex.db and M__EEex.lua files exists. If any are not found a message is displayed to the user and the program exits.

Launch game executable

The EE game executable that was previously found, is launched using the CreateProcess, but with the dwCreationFlags parameter set to CREATE_SUSPENDED.

CREATE_SUSPENDED - The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.

By creating the EE game process in a suspended state we easily obtain the process handle for the EE game process and prevent it from running until we are finished with it.

Inject EEex.dll

A small size of memory is allocated inside the suspended EE game process using VirtualAllocEx - exactly enough is allocated for the length of the full path and filename to the EEex.dll file. The address of this allocated memory is stored for later use. The full path and filename is written into the EE game process memory (at the location allocated) using WriteProcessMemory. For example: C:\BGIIEE\Data\00783\EEex.dll

The address of the LoadLibraryA function is obtained and stored for later use.

A new remote thread is created to run inside the EE game process using CreateRemoteThread with the lpStartAddress parameter being set to the address of the LoadLibraryA function and the lpParameter parameter being set to the address of the full path and filename to the EEex.dll file (which was allocated and written to the EE game process earlier).

When the remote thread is executed in the EE game process it will allow for the LoadLibraryA function to be called with it's parameter being the address of the full path and filename to the EEex.dll file. This will then load the EEex.dll into the EE game process.

ResumeThread is then called to continue the EE game process that was suspended earlier (via CreateProcess with dwCreationFlags set to CREATE_SUSPENDED)

The EE game continues as normal, calling the DllMain in EEex.dll via the LoadLibraryA call created by the CreateRemoteThread

EEex.dll Detail

Initialization

Execution begins once the EE game process calls DllMain in EEex.dll.

Options are read from the EEex.ini. If no EEex.ini file exists a new one is created with default values for the options it contains. The EE game type is detected and the addresses of some exported functions in the EE game are obtained and stored for later. Basic game information is logged to the EEex.log file.

Game module information

Some detailed information about the EE game executable is collected and logged (for debugging purposes). Information is also collected relating to the address of and size of the .TEXT section in the EE game, which is used later on for searching and comparing byte patterns for functions and variables used in the EE game.

Import patterns

The EEex.db pattern database file is read to collect all the section names and to count how many patterns are to be imported. See the EEex.db Pattern Database file for details.

An array (table) of PATTERN entries (records), multiplied by the total patterns to import, is created in memory for storing all the imported patterns in a structured format, this table is pointed to by the PatternsDatabase variable. Storing the patterns in a structured format allows for easier processing of the patterns later on. Each pattern entry (record) is stored in the following structure:

PATTERN         STRUCT
    bFound      DD 0 ; Found flag
    PatType     DD 0 ; Pattern type
    PatBytes    DD 0 ; Pointer to pattern byte
    PatLength   DD 0 ; Length of PatBytes
    VerBytes    DD 0 ; Pointer to verify bytes
    VerLength   DD 0 ; Length of VerLength
    PatAdj      DD 0 ; +/- if PatBytes matched
    VerAdj      DD 0 ; +/- from PatBytes matched
    PatName     DD 0 ; Pointer to function/variable name
    PatAddress  DD 0 ; Address of function/variable
PATTERN         ENDS

Each pattern name (ini section name [<PatName>]) is read to get the pattern bytes (PatBytes) key value. This is converted from the text representation of hexadecimal characters to raw byte values and stored in an allocated block of memory. The address of this block of memory is stored in the PatBytesfield of the current PATTERN entry (record). The length of the pattern bytes is stored in the PatLength field.

If the verify bytes (VerBytes) key value exists this is converted from text to raw bytes, stored in allocated memory and the address to this is stored in the VerBytes field, along with the length of the verify bytes in the VerLength field.

The PatAdj, VerAdj and PatType key value's are also read and stored in the similarly named fields in the current PATTERN entry.

The EEex.ini file is read to obtain the address of previously found patterns. The key value pair is in the format of <PatName>=<address>. If successfully read, the address is stored in the PatAddressfield in the the current PATTERN entry. The exception to this is for patterns that are Type 2 (PatType= 2) patterns.

Type 2 patterns are stored in the EEex.ini as an ini section name [<PatName>] with a Count=<x> key value pair followed by multiple entries in the format of <n>=<address> where n is 0 - (x-1). For example:

[CreStructExpand]
Count=42
0=0x0051C21E
1=0x0051E0A4
...
n=0x01234ABC

The Count value is stored in the VerAdj field of the current PATTERN entry. The PatAddressfield is not set for Type 2 patterns and these Type 2 patterns are skipped for the verification process. The multiple entries in Type 2 patterns are processed separately later on.

The bFound in PATTERN is not set at this stage, this is used later on.

The total patterns imported is logged in the EEex.log file

Verify imported patterns

EEex.dll logs found patterns to EEex.ini with the address of the found pattern as key value pairs in the format of <PatName>=<address>. <PatName> is typically the name of the function or global variable. Examples:

PatchLocation=0x005167E0
_lua_pushnumber=0x004B5960
CGameSprite::~CGameSprite=0x006D5D90
_g_lua=0x005167DC
g_pChitin=0x00785207

Note: PatchLocation is a special case as it is the location used to hook the EE game to implement the Lua support for EEex.

Searching for patterns is time consuming and as such is is preferable to verify that a logged pattern is found at the address specified.

Each PATTERN entry in the PatternsDatabase table is processed. For entries with PatAddress that are not 0(new patterns and Type 2 patterns have PatAddress as 0) these are checked to verify that the raw bytes at this location matches the pattern bytes pointed to by the PatBytes field. The PatAdj field value is used to increment/decrement the starting offset of the address to begin the comparison at.

If the byte comparison matches a further verification can occur if the VerBytes field is not 0. Using the verify bytes pointed to by theVerBytes field, and using VerAdj to adjust the starting offset from the found address, these bytes are compared to verify that the pattern is indeed correct.

Once a pattern has been successfully verified the bFound field of the PATTERN entry is set to TRUE (except for Type 2 patterns).

Type 2 (PatType= 2) patterns are not processed or verified at this stage.

On a first run there will not be any found patterns logged, which will force a search for patterns.

Verify Type 2 patterns

After the imported patterns are verified, Type 2 (PatType= 2) patterns are then processed next for verification.

Each PATTERN entry in the PatternsDatabase table is processed. Only entries with PatType= 2are processed.

The Count value as stored in the VerAdj field of the current PATTERN entry is read and used to create an array (pType2Array) to store addresses in.

Type 2 patterns are stored in the EEex.ini as an ini section name [<PatName>] with a Count=<x> key value pair followed by multiple entries in the format of <n>=<address> where n is 0 - (x-1). For example:

Each address entry in the ini section name [<PatName>] for the Type 2 pattern is read and these are checked to verify that the raw bytes at this location matches the pattern bytes pointed to by the PatBytes field. The PatAdj field value is used to increment/decrement the starting offset of the address to begin the comparison at. Note: the same PatBytes and PatAdj field values are used to compare each address entry as there is expected to be multiple addresses that match.

If all the addresses in the Type 2 pattern match successfully then the bFound field of the PATTERN entry is set to TRUE. The PatAddress field of the PATTERN entry is set to the array of addresses pointed to by pType2Array.

If any of the addresses of the Type 2 pattern fail to verify or the Count / PatAdj value is 0 (first run) then verification will fail, forcing a search for patterns.

Search for patterns

Each PATTERN entry in the PatternsDatabase table is processed. For entries that have the bFound field of the PATTERN entry is set to TRUE these are skipped, having previously been verified.

The EE game memory is searched for a pattern, starting at the address of the .TEXT section (found previously during the [Game module information](#Game module information) stage). The bytes in memory are checked against the bytes pointed to byPatBytes in the current PATTERN entry.

If the byte comparison matches, a further verification can occur if the VerBytes field is not 0. Using the verify bytes pointed to by theVerBytes field, and using VerAdj to adjust the starting offset from the found address, these bytes are compared to verify that the pattern is indeed correct.

Once a pattern has been found the bFound field of the PATTERN entry is set to TRUE (except for Type 2 patterns).

Type 2 (PatType= 2) patterns don't use the VerBytes as they are expected to have multiple occurrences of the same pattern. For Type 2 patterns that are found, a dynamic array is increased on each iteration found, (the pointer to this array is stored in the PatAddress field), the address at which the iteration is matched is stored in the array and the count of the total matches is stored in the VerAdj field of the current PATTERN entry.

Once the entire EE game memory has been searched for the current PATTERN entries PatBytes the pointer is moved to the next PATTERN entry in the PatternDatabase table and the whole looped process occurs again searching for and comparing bytes in memory.

Once all PATTERN entries have been searched, all Type 2 patterns are marked as found, and some checking is done to see if any patterns previously imported and not verified have been missed. If so a message is displayed to user that an error has occurred. Any missing patterns may result in strange and unpredictable behavior by the EE game using EEex.

If all patterns have been found, then the addresses of the found patterns are written to the EEex.ini file to speed up subsequent runs, by just verifying memory addresses for PatBytes instead of having to re-search the entire EE game memory each time.

New patterns added to the EEex.db pattern database will trigger the full search, but will be limited to only those PATTERN entries that do not have the bFound field set to TRUE after verification.

Patch luaL_loadstring

The PatchLocation pattern is defined as:

[PatchLocation]
PatBytes=83C43085C075145050506AFF50FF35
PatAdj=-5
VerBytes=83C4186A00
VerAdj=24

The matching bytes 83C43085C075145050506AFF50FF35 are highlighted in red in the image above. Adjusting the address at which this pattern is found by the PatAdj value (-5) gives us the location to patch the call to _luaL_loadstring.

Note: The same pattern can actually be found in another location in the EE game, and thus we use the VerBytes and VerAdj values to further refine the matching to a specific location, by matching the verify bytes (VerBytes) at this additional location (adjusting found pattern address by the VerAdj value).

The bytes at the PatchLocation address shown above in blue are modified in the EE game executable to change the following function call:

FF35 0C019400 | push dword ptr ds:[<_g_lua>]   
E8 0B1AFAFF   | call <baldur._luaL_loadstring> 

to:

FF35 0C019400 | push dword ptr ds:[<_g_lua>]
E8 3BB4CA68   | call eeex.EEexLuaInit

Note: if disassembled it will actually show a specific address, for example: E8 3BB4CA68 | call eeex.691C1C20 (replaced in example above with EEexLuaInit to help with understanding). Additionally the reference to the modulebaldur in the example above may be different depending on the EE game that is using EEex.

When the EE game continues its normal execution, after EEex.dll has finished loading and processing, it will at a later time come to the PatchLocation address which is now redirecting execution to the EEexLuaInit function located within EEex.dll

Patch SDL_LogMessageV

To allow for console output if EEex.exe is started via a command line, and also to log output to the EEex.log file, the call SDL_LogMessageV in the SDL_Log exported function in the EE game is patched to redirect to functions in EEex.dll to provide that additional logging support.

The address of the exported SDL_Log function is obtained via GetProcAddress early on during Initialization. This address is taken and adjusted to point to the location where the call SDL_LogMessageV in the SDL_Log function is.

The bytes at the SDL_LogMessageV location are modified in the EE game executable to change the following function call:

E8 2D010000   | call <baldur.SDL_LogMessageV> 

to:

E8 6DE3C674   | call <eeex.EEexSDL_LogMessageV> 

Note: if disassembled it will actually show a specific address, for example: E8 6DE3C674 | call eeex.75422190 (replaced in example above with EEexSDL_LogMessageV to help with understanding). Additionally the reference to the modulebaldur in the example above may be different depending on the EE game that is using EEex.

Function addresses

Each PATTERN entry in the PatternsDatabase table is processed. The pattern name (PatName) string is first checked to see if it begins with _lua and if it does it is then compared against specific Lua function name strings. If a match occurs the PatAddress value is saved to a global variable.

These global variables are prefixed with F_ for function. Prototypes for each of the functions are previously defined to allow for calling these functions directly in the EE game via the use of Invoke or with the call instruction. For example the address of the _lua_pushnumber function is stored in a F_Lua_pushnumber global variable, and can be called as:

push eax
fild dword ptr [esp]
sub esp, 4h
fstp qword ptr [esp]
push dword ptr [ebp+8h]
call F_Lua_pushnumber

or via Invoke like so:

fild dwPatternAddress
fstp qword ptr [qwAddress]
Invoke F_Lua_pushnumber, lua_State, qwAddress

Variable values

Type 1 (PatType= 1), Type 3 (PatType= 3) and Type 4 (PatType= 4) patterns are processed after all patterns have been imported, verified and/or searched for.

These pattern types are used to read a value at the address of the pattern found (stored in the PatAddress field of the PATTERN entry).

For Type 1 patterns, a DWORD is read at the PatAddress address and this value is stored in the PatAddress field, replacing the previous value.

For Type 3 patterns, a BYTE is read at the PatAddress address and this value is stored in the PatAddress field, replacing the previous value.

For Type 4 patterns, a WORD is read at the PatAddress address and this value is stored in the PatAddress field, replacing the previous value.

Note: the original PatAddress value is not required after reading the address, thus it is save to re-use the same field in the PATTERN entry to store the new content

Additionally the _g_lua Type 1 pattern when found during this process is stored in a global variable for later use with using the Lua state in the EE game and using the Lua functions stored in the EEex.dll. It is stored in a p_lua variable, which is itself a pointer to the g_lua Lua state.

The EEexLuaInit function

EEexLuaInit is a cdecl function that is stored in the EEex.dll and is called by the EE game due to the redirection placed earlier in the EE game process memory, which changed the call _luaL_loadstring to a call EEexLuaInit instead.

EEexLuaInit resolves the p_lua variable to obtain the g_lua EE game Lua state.

EEexLuaInit registers with the EE game's global Lua state a new Lua function: EEex_Init, which is located in the EEex.dll. It then calls the _luaL_loadstring that was replaced by the patch (hook) and resumes execution back to just after the PatchLocation to continue as normal.

When the EE game processes its own Lua files as normal and encounters an EEex_Init function, it will call the EEex_Init Lua function - which was previously registered and is located in the EEex.dll

The Lua EEex_Init function

EEex_Init is a cdecl function that registers additional Lua helper cdecl functions for the EEex project: EEexExposeToLua, EEexWriteByte, EEexCall and EEex_AddressList. In addition EEex_Init also allocates an additional block of memory via VirtualAlloc and returns the address pointing to this block of memory on the Lua stack. This memory is used by the EEex project for various means.