Others Trying to edit an ESCUDE exe file

Oct 8, 2017
98
78
Hi,
I am trying to edit the main executable of one of the ESCUDE games, since the game saves its configuration to the Documents folder, and I wanna avoid that and make it save in the root folder of the main executable.

Thus far, I have managed to find and interpret the function (mind you, I am dealing with x86 assembly) where is trying to read the original configuration.cfg (it uses a standard Windows API for initialization files) and then it creates a folder in Documents folder called ESCUDE, and then another folder called HimeYuki (both are variables stored in the configuration.cfg).

Original code:

C:
void FUN_004ef860(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4)

{
  int createConfigFile;
  BOOL BVar1;
  uchar *puVar2;
  DWORD DVar3;
  HWND gameWindow;
  HRESULT HVar4;
  char *lpString2;
  char acStack_428 [4];
  tagMSG tStack_424;
  CHAR configPath [260];
  char acStack_304 [256];
  char acStack_204 [256];
  char acStack_104 [256];
  uint local_4;

  local_4 = DAT_0058c730 ^ (uint)acStack_428;
  DAT_00c31b24 = param_1;
  _setlocale(0,"Japanese_Japan.932");
  GetModuleFileNameA((HMODULE)0x0,&iniFile,0x104);
  __splitpath(&iniFile,acStack_428,acStack_304,acStack_204,acStack_104);
  __makepath(&iniFile,acStack_428,acStack_304,(char *)0x0,(char *)0x0);
  FUN_004eded0();
  __makepath_s(&DAT_00c31a20,0x104,acStack_428,acStack_304,acStack_204,".cfg");
  createConfigFile = findExistingConfigFile(&DAT_00c31a20);
  if (createConfigFile != 0) {
    FUN_004edfa0(&DAT_00c31a20);
  }
  if (inheritance != 0) {
    __makepath_s(&DAT_00c31a20,0x104,acStack_428,acStack_304,"configure",".cfg");
    createConfigFile = findExistingConfigFile(&DAT_00c31a20);
    if (createConfigFile != 0) {
      FUN_004edfa0(&DAT_00c31a20);
    }
    if ((((inheritance != 0) && (companyString != '\0')) && (productString != '\0')) &&
       (createConfigFile = checkExistsConfigFile(0,5,configPath), createConfigFile != 0)) {
      lstrcatA(configPath,"\\");
      lstrcatA(configPath,&companyString);
      createConfigFile = findExistingConfigFile(configPath);
      if ((createConfigFile != 0) ||
         (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
        lstrcatA(configPath,"\\");
        lstrcatA(configPath,&productString);
        createConfigFile = findExistingConfigFile(configPath);
        if ((createConfigFile != 0) ||
           (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
          lstrcatA(configPath,"\\configure.cfg");
          lstrcpyA(&DAT_00c31a20,configPath);
          createConfigFile = findExistingConfigFile(configPath);
          if (createConfigFile != 0) {
            FUN_004edfa0(&DAT_00c31a20);
          }
        }
      }
    }
  }
This is the modified snippet:
C:
    if ((((inheritance != 0) && (companyString != '\0')) && (productString != '\0')) &&
       (createConfigFile = checkExistsConfigFile(0,5,configPath), createConfigFile != 0)) {
      getCorrectConfigPath(configPath); --> For now I am trying to recreate this function
      createConfigFile = findExistingConfigFile(configPath);
My idea was to create this function:

C:
void getCorrectConfigPath(char *configPath)
{
    char executablePath[MAX_PATH];  // Buffer to store the path of the executable
    char rootFolder[MAX_PATH];      // Buffer to store the root folder (directory)
   
    // Clear the configPath (set it to an empty string)
    memset(configPath, 0, MAX_PATH);

    // Get the full path of the executable
    GetModuleFileNameA(NULL, executablePath, MAX_PATH);
   
    // Extract the root directory from the executable path
    __splitpath(executablePath, rootFolder, NULL, NULL, NULL);

    // Set the configPath to the root folder
    strcpy(configPath, rootFolder);
}
and thus far I have this:

C:
/* WARNING: Unknown calling convention -- yet parameter storage is locked */

void getCorrectConfigPath(char *configPath)

{
  char rootFolder [260];

  _memset(configPath,0,260);
  GetModuleFileNameA((HMODULE)0x0,&stack0xfffffdf4,0x104);
  __splitpath(&stack0xfffffdf4,rootFolder,(char *)0x0,(char *)0x0,(char *)0x0);
  lstrcpyA(configPath,rootFolder);
  return;
}
Do you have any recommendations on how to approach this? I can pass the entire Ghidra project if needed.
 
Last edited:
Oct 8, 2017
98
78
Forgot to mention, but variables like inheritance, companyString and productString are just variables taken from the already existing configuration.cfg file that comes with the game already, just for clarification purposes.
 

roguelyfe

New Member
Feb 22, 2025
3
2
I'm not an assembly expert but here's my best guess. It looks like findExistingConfigFile is expecting a complete path, not just the folder. The first time it's called on &DAT_00c31a20 is right after a makepath call. And it's called repeatedly on configPath, so you probably need to use makepath right after you use splitpath. Also your rootfolder argument is currently in the drive parameter not the folder parameter. So something like this maybe?:


C:
...
__splitpath(&stack0xfffffdf4, exeDrive, exeDir, exeFname, exeExt);
__makepath(configPath, exeDrive, exeDir,  (char *)0x0, (char *)0x0);
return;
 
Oct 8, 2017
98
78
I'm not an assembly expert but here's my best guess. It looks like findExistingConfigFile is expecting a complete path, not just the folder. The first time it's called on &DAT_00c31a20 is right after a makepath call. And it's called repeatedly on configPath, so you probably need to use makepath right after you use splitpath. Also your rootfolder argument is currently in the drive parameter not the folder parameter. So something like this maybe?:


C:
...
__splitpath(&stack0xfffffdf4, exeDrive, exeDir, exeFname, exeExt);
__makepath(configPath, exeDrive, exeDir,  (char *)0x0, (char *)0x0);
return;
Fwiw, the original function still needs modified even further, I just needed to know first if the new function I made was somewhat correct.

Right now, the game just simply crashes (I mean, the new function should only report back the game's root folder path without the double slash at the end, so obviously I knew from the get-go that my code is far from finished), but apparently is because of a stack overflow. So what I wanna do first is try to see if the game crashes while calling the new function or even before that point, just to see if my new function has not been implemented correctly.

And yes, that weird &stack0xfffffdf4 variable is the configPath variable, I just don't know why it's not being interpreted that way by Ghidra. In fact I assume that this must be the point where the app might crash?
 
Last edited:

n00bi

Active Member
Nov 24, 2022
626
676
Hi there, I am not so active coding C as i once used to.
and i am not familiar with Ghidra, i know a tiny bit ollydbg, but that thing is ancient now.
but i see issues with your function.

My idea was to create this function:

C:
void getCorrectConfigPath(char *configPath)
{
char executablePath[MAX_PATH]; // Buffer to store the path of the executable
char rootFolder[MAX_PATH]; // Buffer to store the root folder (directory)

// Clear the configPath (set it to an empty string)
memset(configPath, 0, MAX_PATH);

// Get the full path of the executable
GetModuleFileNameA(NULL, executablePath, MAX_PATH);

// Extract the root directory from the executable path
__splitpath(executablePath, rootFolder, NULL, NULL, NULL);

// Set the configPath to the root folder
strcpy(configPath, rootFolder);
}
and thus far I have this:

This will not give you the root directory, this will give you the drive. ie. C: or D: etc.
you can check out the doc .
Code:
   // Extract the root directory from the executable path
    __splitpath(executablePath, rootFolder, NULL, NULL, NULL);
And please don't do this (char *)0x0 unless you really have too, just use NULL.
Your function should be more like this:

C:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>


// Compile with, mingw.
// gcc main.c -o exepath

//#define MAX_PATH 256
void getCorrectConfigPath(char *pCfgPath) {

    // get the path to the exe file.
    char exepath[256];
    int szExePath = sizeof(exepath);
    GetModuleFileNameA(NULL, exepath, szExePath);


    printf("Old config path: %s\n", pCfgPath);
    // Do you know the size of configpath?
    // if not just use strlen and clear whats set.
    // clear the config path.
    int slen = strlen(pCfgPath)+1; // +1 since strlen dosent include the null term.
    memset(pCfgPath, 0, slen);


    char drive[54];
    char dir[128];
    _splitpath(exepath, drive, dir, NULL, NULL);

    strcat(pCfgPath, drive);
    strcat(pCfgPath, dir);
    strcat(pCfgPath, "Saves");
    printf("New config path: %s\n", pCfgPath);
}

// ------------------------------------------------------------------------------------
// Main Entry For Testing
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR pCmdLine, int nCmdShow ) {

    // A path to some save location that needs to be changed.
    char configpath[256] = "H:\\Games\\Foo-Bar\\Saves";
    getCorrectConfigPath( configpath);
    return 0;
}
INI:
Old config path: H:\Games\Foo-Bar\Saves
New config path: D:\Best Games\Banna game\Saves
Also. you should try and use the safe functions instead of the vanilla ones.
ie. memset_s, and _splitpath_s_ strcat_s and so forth.
 
Last edited:
Oct 8, 2017
98
78
n00bi regarding the (char *)0x0, is just what Ghidra interprets of me using NULL, read the function above the last one, is the one that I am trying to really create, is just that... maybe Ghidra is not interpreting correctly my assembly code or maybe I am doing something wrong, but who knows...

Mmm, well, using memset_s could be possible, but first I wanted to try using the ones the game already uses by default instead of trying to insert new imports when it is not needed.
Still though, I will check your newly done function whenever I can, it's just a pretty time-consuming task for such a minute thing... (honestly, if the game already reads the configure.cfg from the executable path, so why doesn't it want to write into it directly...)
 

n00bi

Active Member
Nov 24, 2022
626
676
if the game already reads the configure.cfg from the executable path, so why doesn't it want to write into it directly...)
Well, welcome to the world of hacking where you will discover stupid code.
I took a look at your initial post a bit more and there is so many questions to ask.
There seams to be a lot of missing stuff.

i did clean up it a bit.
C:
void FUN_004ef860(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4) {
  // what are the parameters used for?, only one is assigned to a variable: DAT_00c31b24 which again is never used..
  int createConfigFile;
  BOOL BVar1;
  uchar *puVar2;
  DWORD DVar3;
  HWND gameWindow;
  HRESULT HVar4;
  char *lpString2;

  tagMSG tStack_424;

  CHAR configPath[260];
  char drive[4];
  char dir[256];
  char filename[256];
  char ext [256];
  uint local_4;

  // what the heck is this? casting a char array "pointer" to an int and doing xor
  // and what is DAT_0058c730 ?
  local_4 = DAT_0058c730 ^ (uint)drive;
  DAT_00c31b24 = param_1;
  _setlocale(0,"Japanese_Japan.932");


  // store the path of the exe file into &iniFile
  GetModuleFileNameA(NULL, &iniFile, 260);

  __splitpath(&iniFile, drive, dir,filename,ext);

  /* construct a path and store it in iniFile Ie: C:\mygames\apple\ */
  __makepath(&iniFile, drive, dir, NULL, NULL);

  FUN_004eded0(); // what is this function ?

  /* construct a path and store it in DAT_00c31a20, Ie: C:\mygames\apple\apple.cfg */
  __makepath_s(&DAT_00c31a20, 260, drive, dir, filename,".cfg");

  // What does findExistingConfigFile do. just check if a file exists or creates it too ??
  createConfigFile = findExistingConfigFile( &DAT_00c31a20 );

  if (createConfigFile != 0) {
    FUN_004edfa0(&DAT_00c31a20); // Error Msg???
  }

  // what is inheritance ??
  if (inheritance != 0) {

    /* construct a path and store it in DAT_00c31a20, Ie: C:\mygames\apple\configure.cfg */
    __makepath_s( &DAT_00c31a20, 260, drive, dir, "configure",".cfg");

    createConfigFile = findExistingConfigFile(&DAT_00c31a20); // create the file ??

    if (createConfigFile != 0) {
      FUN_004edfa0(&DAT_00c31a20); // Error ??
    }

    // where does companyString, productString come from, globar vars?, content created from the resource.rc ?
    // And checkExistsConfigFile function does what? create a new if config one doesnt exists??
    // Does it read a hard coded path ? because.. configPath is not set to anything at this point.
    if ((((inheritance != 0) && (companyString != '\0')) && (productString != '\0')) && (createConfigFile = checkExistsConfigFile(0, 5, configPath), createConfigFile != 0)) {
 
      // Concatinates configPath, "build up a string"
      // configPath = "whatever\companyString" at this point.. as we dont know if checkExistsConfigFile did anything to it.
      lstrcatA(configPath,"\\");
      lstrcatA(configPath,&companyString);
 
      createConfigFile = findExistingConfigFile(configPath);
 
      if ((createConfigFile != 0) || (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
        // concatinates configPath more.
        // At this point configPath should be.
        // configPath = "whatever\companyString\productString"
        lstrcatA(configPath,"\\");
        lstrcatA(configPath,&productString);
   
        createConfigFile = findExistingConfigFile(configPath);
   
        if ((createConfigFile != 0) || (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
     
          // configPath = "whatever\companyString\productString"\configure.cfg"
          lstrcatA(configPath,"\\configure.cfg");
     
          // copy configPath into DAT_00c31a20
          lstrcpyA(&DAT_00c31a20, configPath);
     
     
          createConfigFile = findExistingConfigFile(configPath);
     
          if (createConfigFile != 0) {
            FUN_004edfa0(&DAT_00c31a20); // Error ?
          }
        }
      }
    }
  }
}
The purpose of the FUN_004ef860 function seem to be to create the config file.
And DAT_00c31a20 is the variable the game uses. not 100% sure tho.

i guess you can simplify the hole thing and always make it read the game dir for a file called configure.cfg
something like this.

C:
void FUN_004ef860(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4) {

  char drive[4];
  char dir[256];
  char filename[256];
  char ext [256];

  _setlocale(0,"Japanese_Japan.932");

  // store the path of the exe file into &iniFile
  GetModuleFileNameA(NULL, &iniFile, 260);

  // store the sub components of the path into drive, dir etc.
  __splitpath(&iniFile, drive, dir, filename, ext);

  // construct a path and store it in DAT_00c31a20
  // Ie: C:\mygames\apple\configure.cfg
  __makepath_s( &DAT_00c31a20, 260, drive, dir, "configure",".cfg");

  createConfigFile = findExistingConfigFile(&DAT_00c31a20);
}
I dont know how easy it is to patch a exe file in the tool your using.
but in general "Hacking" is not easy.

Patching a exe with new code i have never done.
Often when modifying a exe its about bypassing misc functiosn to avoid ie. registrations etc.
and that can often just be to nop our the entire funcion call or just use jmp, whatever is needed.

If new code to be added i think doing a dll injection is good enough.
while fully possible to patch a exe with new code, from old day it was a tediouse task and avoided.
never the less. it requires you do disassembly to find the memory location of a function.
It looks like you are already doing some disassebly anyway.
And you have to consider if ASLR is enabled or not. "if it puts the function address a random location".

Here is a simple example of how to do dll injection and replace a function in progA.exe.:
Assume you program is called progA.exe and you want to replace the function called 'foobar' in it.
For the sake of simplicity i will provide the source code for progA.exe

C:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

// compile with gcc
// The -g flag includes debug symbols so we can locate function names in the disassembly.
// gcc -g -o progA.exe progA.c
// gcc -g -o progA.exe progA.c -Wl,--disable-dynamicbase  ; disable aslr

// Disassemble using objdump to find address of foobar.
// objdump -d -M intel progA.exe > progA_disasm.txt

// increment the value n by 1.
void foobar(int *n) {
    *n+=1;
}


// simple print.
void printing(void) {
    printf("\nsoo cool\n");
}


INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR pCmdLine, int nCmdShow ) {
    int number;
    while (1) {
        printf("Enter a number above 0: ");
        scanf("%d", &number);
   
        if (number == 0){ break;}
   
        foobar(&number);
        printf("modded: %d\n", number);
    }
}
This does nothing fancy.
The next step is to create the Dll file that is to be injected into progA.exe
This step involves a bit stuff to do.
1st you need to disasseble the progA.exe.

i am using gcc tool chain so i will use its tools as example.
objdump -d -M intel progA.exe > progA_disasm.txt

Next we open up and look for foobar and optional printing if you want to modify it aswell.
or use printing inside your replacement foobar function.

Code:
0000000140001460 <foobar>:
   140001460:    55                       push   rbp
   140001461:    48 89 e5                 mov    rbp,rsp
   140001464:    48 89 4d 10              mov    QWORD PTR [rbp+0x10],
....
....
000000014000147a <printing>:
   14000147a:    55                       push   rbp
   14000147b:    48 89 e5                 mov    rbp,rsp
   14000147e:    48 83 ec 20              sub    rsp,0x20
....
....
ok so now you have the mem locations and the byte pattern. we can build the Dll file.

C:
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

// -----------------------------------------------
// Compile with:
// gcc -shared -o hook.dll hook.c -m64 -fPIC
// -----------------------------------------------


typedef void (*PrintFn)();
static PrintFn cachedPrinting = NULL;


// --- Pattern to identify the start of `foobar()` in memory ---
// Based on disassembly of `foobar` in progA.exe.
//0000000140001460 <foobar>:
//   140001460:    55                       push   rbp
//   140001461:    48 89 e5                 mov    rbp,rsp
//   140001464:    48 89 4d 10              mov    QWORD PTR [rbp+0x10],rcx
//   140001468:    48 8b 45 10              mov    rax,QWORD PTR [rbp+0x10]
//   14000146c:    8b 00                    mov    eax,DWORD PTR [rax]
//   14000146e:    8d 50 01                 lea    edx,[rax+0x1]
//   140001471:    48 8b 45 10              mov    rax,QWORD PTR [rbp+0x10]
//   140001475:    89 10                    mov    DWORD PTR [rax],edx
//   140001477:    90                       nop
//   140001478:    5d                       pop    rbp
//   140001479:    c3                       ret
//
// Be sure this signature is stable across builds.
BYTE pattern_foobar[] = {
    0x55,                   // push rbp
    0x48, 0x89, 0xE5,       // mov rbp, rsp
    0x48, 0x89, 0x4D, 0x10, // mov [rbp+0x10], rcx
    0x48, 0x8B, 0x45, 0x10, // mov rax, [rbp+0x10]
    0x8B, 0x00,             // mov eax, [rax]
    0x8D, 0x50, 0x01        // lea edx, [rax+1]
};
const char *mask_foobar = "xxxxxxxxxxxxxxxxxxx"; // 'x' = exact match (no wildcards)
size_t pattern_foobarLen = sizeof(pattern_foobar);


//000000014000147a <printing>:
   // 14000147a:    55                       push   rbp
   // 14000147b:    48 89 e5                 mov    rbp,rsp
   // 14000147e:    48 83 ec 20              sub    rsp,0x20
   // 140001482:    48 8d 05 77 1b 01 00     lea    rax,[rip+0x11b77]        # 140013000 <.rdata>
   // 140001489:    48 89 c1                 mov    rcx,rax
   // 14000148c:    e8 c7 f9 00 00           call   140010e58 <puts>
   // 140001491:    90                       nop
   // 140001492:    48 83 c4 20              add    rsp,0x20
   // 140001496:    5d                       pop    rbp
   // 140001497:    c3                       ret
BYTE pattern_printing[] = {
    0x55,
    0x48, 0x89, 0xe5,
    0x48, 0x83, 0xec, 0x20,
    0x48, 0x8d, 0x05, 0x77, 0x1b, 0x01, 0x00,
    0x48, 0x89, 0xc1,
    0xe8, 0xc7, 0xf9, 0x00, 0x00,
    0x90
};
//const char *mask_printing = "xxxxxxxxxxx????xxx????xx";
const char *mask_printing = "xxxxxxxxxxxxxxxxxxxxxxxx";
size_t pattern_printingLen = sizeof(pattern_printing);


// Utility to scan memory for the byte pattern
void* find_pattern(BYTE *base, DWORD size, BYTE *pattern, const char *mask, size_t len) {
    for (DWORD i = 0; i <= size - len; i++) {
        bool found = true;
        for (DWORD j = 0; j < len; j++) {
            if (mask[j] == 'x' && base[i + j] != pattern[j]) {
                found = false;
                break;
            }
        }
        if (found) {
            //printf("\nPattern matched at offset: 0x%llx\n", (uintptr_t)(base + i) - (uintptr_t)base);
            return base + i;
        }
    }

    printf("Pattern NOT found!\n");
    return NULL;
}

// --- Find function in .text section ---
void* find_function(BYTE *pattern, const char *mask, size_t len) {
    HMODULE hModule = GetModuleHandle(NULL);
    if (!hModule) return NULL;

    BYTE *base = (BYTE*)hModule;
    IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER*)base;
    IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew);

    IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nt);
    for (int i = 0; i < nt->FileHeader.NumberOfSections; i++, section++) {
        if (memcmp(section->Name, ".text", 5) == 0) {
            BYTE *start = base + section->VirtualAddress;
            DWORD size = section->Misc.VirtualSize;
            return find_pattern(start, size, pattern, mask, len);
        }
    }

    return NULL;
}



void *find_foobar() { return find_function(pattern_foobar, mask_foobar, pattern_foobarLen); }
void *find_printing() { return find_function(pattern_printing, mask_printing, pattern_printingLen); }


// --- Hook replacement for `foobar` Set n to 42 ---
void myfoobar(int *n) {
    *n = 42;

    if (!cachedPrinting) {
        cachedPrinting = (PrintFn)find_printing();
        if (!cachedPrinting) {
            printf("cachedPrinting is NULL!\n");
        } else {
            printf("cachedPrinting found at %p\n", cachedPrinting);
        }
    }

    if (cachedPrinting) {
        cachedPrinting();
    }
}



// Dll entry point. Installs hook on `foobar()` after locating it dynamically.
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
   
        // 1. Set up a pointer to the function we are hooking.
        void *pTarget = find_foobar();
        if (!pTarget) {
            MessageBoxA(NULL, "Failed to find foobar pattern!", "Hook Error", MB_OK);
            return FALSE;
        }

        DWORD oldProtect;
        if (VirtualProtect(pTarget, 14, PAGE_EXECUTE_READWRITE, &oldProtect)) {
            // Absolute jump via RAX: MOV RAX, addr; JMP RAX
            BYTE *code = (BYTE*)pTarget;
            code[0] = 0x48;  // REX.W prefix
            code[1] = 0xB8;  // MOV RAX, imm64
            *((uintptr_t*)(code + 2)) = (uintptr_t)&myfoobar;
            code[10] = 0xFF; // JMP RAX
            code[11] = 0xE0;
            code[12] = 0x90; // NOP padding
            code[13] = 0x90;

            VirtualProtect(pTarget, 14, oldProtect, &oldProtect);
        } else {
            char msg[256];
            sprintf(msg, "Failed to change memory protection! Error: %lu", GetLastError());
            MessageBoxA(NULL, msg, "Hook Error", MB_OK);
        }
    }

    return TRUE;
}
You don't have permission to view the spoiler content. Log in or register now.


The next step is you need a way to inject the dll into progA.exe.
so you need to write a simple injector program or use an existing one.

C:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// gcc -o injector.exe injector.c -m64


DWORD FindTargetProcess(const char *procName) {
    PROCESSENTRY32 pe = { 0 };
    pe.dwSize = sizeof(PROCESSENTRY32);
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE) {
        return 0;
    }

    if (Process32First(hSnap, &pe)) {
        do {
            if (_stricmp(pe.szExeFile, procName) == 0) {
                CloseHandle(hSnap);
                return pe.th32ProcessID;
            }
        } while (Process32Next(hSnap, &pe));
    }
    CloseHandle(hSnap);
    return 0;
}


int main() {
    const char *targetProc = "progA.exe";
    const char *dllName = "hook.dll";

    DWORD pid = FindTargetProcess(targetProc);
    if (pid == 0) {
        printf("Could not find process: %s\n", targetProc);
        return 1;
    }

    // Get full path to DLL in same directory
    char dllFullPath[MAX_PATH];
    GetFullPathNameA(dllName, MAX_PATH, dllFullPath, NULL);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) {
        printf("Failed to open process. Error: %lu\n", GetLastError());
        return 1;
    }

    LPVOID remotePath = VirtualAllocEx(hProcess, NULL, strlen(dllFullPath) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!remotePath) {
        printf("VirtualAllocEx failed. Error: %lu\n", GetLastError());
        CloseHandle(hProcess);
        return 1;
    }

    if (!WriteProcessMemory(hProcess, remotePath, dllFullPath, strlen(dllFullPath) + 1, NULL)) {
        printf("WriteProcessMemory failed. Error: %lu\n", GetLastError());
        VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    LPVOID loadLibAddr = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
    if (!loadLibAddr) {
        printf("GetProcAddress failed.\n");
        VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibAddr, remotePath, 0, NULL);
    if (!hThread) {
        printf("CreateRemoteThread failed. Error: %lu\n", GetLastError());
        VirtualFreeEx(hProcess, remotePath, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    printf("DLL injected into %s (PID %lu)\n", targetProc, pid);

    CloseHandle(hThread);
    CloseHandle(hProcess);
    return 0;
}
You can ofcource modify this so it actualy loads the progA.exe itself.
that way you can rename progA to progA.data and have the injector as the progA.
but you can figure out that youself as it should be a easy task..

Just make sure that the hook.dll file and that the injector.exe is in the same directory as progA.exe

NVIDIA_Overlay_F8vVf2NdUA.gif

Hacking is not the simplest thing out there.
 
Last edited:
Oct 8, 2017
98
78
n00bi wowy, that was a lengthy answer (well at least I know that not every user here is a braindead computer user as I initially expected honestly...).

Regarding the code I provided from the original devs, let me clarify some things to you so you can understand it a bit better, although I did not mention such things because in all honestly, they are not really needed purely for my purposes.

The main function called FUN_004ef860 is the one that reads the configuration file called configuration.cfg from the executable's path, creates one in the Documents folder, and updates it after the game closes down. (This last bit was omitted from the code I showed here since it just does not matter at all, it only takes the configPath variable to know where is the newly created configuration.cfg file, and I obviously don't need to show here part of the function that is completely independent from where I need to tackle my modifications).

So what is DAT_0058c730? Good question because I have no fucking clue and it does not seem to be used again... Keep in mind that ESCUDE games come in CDs in Japan, so maybe that is used for a variable for the disc path elsewhere? I don't know nor I care for it because again, it's not important to understand it.

What is FUN_004eded0? It's a function that hardcodes from the get go certain variables of the game that might not be present in the configuration.cfg file because they are not expected to be there.
C:
void FUN_004eded0(void)


{

  inheritance = 1;

  debugMode = 0;

  error = 1;

  multiInstance = 0;

  titleName = 0;

  screenWidth = 0x500;

  screenHeight = 0x2d0;

  sceenDepth = 0x18;

  DAT_0058c654 = 1;

  systemMultiThreading = 1;

  systemDirect3D = 1;

  systemDirectSound = 1;

  systemDirectMusic = 0;

  systemDirectInput = 0;

  DAT_00c31b7c = 0;

  driveCD = 0;

  driveCDROM = 0;

  driveCDDIR = 0;

  screenFullscreen = 0;

  screenFixedAspectRatio = 1;

  windowResize = 0;

  screenSmoothing = 1;

  DAT_0058c634 = 1;

  DAT_0058c638 = 1;

  DAT_0058c63c = 1;

  DAT_00c30508 = 0;

  DAT_00c30a64 = 0;

  DAT_00c30858 = 0x40000;

  DAT_00c30e74 = 0x800000;

  companyString = 0;

  productString = 0;

  return;

}
After that, we go into the GetIniValues function, which in this one the game reads the configuration.cfg file from the executable's path, and here you have said function, which is where I could infer most of the weird strings you might get that you don't understand:

C:
undefined4 GetIniValues(LPCSTR iniFile)

{
  char cVar1;
  int iVar2;
 
  GetPrivateProfileStringA("General","Company",&companyString,&companyString,0x104,iniFile);
  GetPrivateProfileStringA("General","Product",&productString,&productString,0x104,iniFile);
  inheritance = GetPrivateProfileIntA("General","Inheritance",inheritance,iniFile);
  debugMode = GetPrivateProfileIntA("General","DebugMode",debugMode,iniFile);
  error = GetPrivateProfileIntA("General","Error",error,iniFile);
  multiInstance = GetPrivateProfileIntA("General","MultiInstance",multiInstance,iniFile);
  GetPrivateProfileStringA("General","Title",&titleName,&titleName,0x100,iniFile);
  fileSystem = GetPrivateProfileIntA("General","FileSystem",fileSystem,iniFile);
  if ((fileSystem & 3) == 0) {
    fileSystem = 3;
  }
  windowResize = GetPrivateProfileIntA("Window","Resize",windowResize,iniFile);
  windowRestoreBounds = GetPrivateProfileIntA("Window","RestoreBounds",windowRestoreBounds,iniFil e);
  windowLeft = GetPrivateProfileIntA("Window","WindowLeft",windowLeft,iniFile);
  windowTop = GetPrivateProfileIntA("Window","WindowTop",windowTop,iniFile);
  windowWidth = GetPrivateProfileIntA("Window","WindowWidth",windowWidth,iniFile);
  windowHeight = GetPrivateProfileIntA("Window","WindowHeight",windowHeight,iniFile);
  screenWidth = GetPrivateProfileIntA("Screen","Width",screenWidth,iniFile);
  screenHeight = GetPrivateProfileIntA("Screen","Height",screenHeight,iniFile);
  sceenDepth = GetPrivateProfileIntA("Screen","Depth",sceenDepth,iniFile);
  screenFullscreen = GetPrivateProfileIntA("Screen","FullScreen",screenFullscreen,iniFile);
  screenFixedAspectRatio =
       GetPrivateProfileIntA("Screen","FixedAspectRatio",screenFixedAspectRatio,iniFile);
  screenSmoothing = GetPrivateProfileIntA("Screen","Smoothing",screenSmoothing,iniFile);
  DAT_0058c654 = GetPrivateProfileIntA("System",(LPCSTR)&PTR_DAT_0055f930,DAT_0058c654,iniFile);
  systemMultiThreading =
       GetPrivateProfileIntA("System","MultiThreading",systemMultiThreading,iniFile);
  systemDirect3D = GetPrivateProfileIntA("System","Direct3D",systemDirect3D,iniFile);
  systemDirectSound = GetPrivateProfileIntA("System","DirectSound",systemDirectSound,iniFile);
  systemDirectMusic = GetPrivateProfileIntA("System","DirectMusic",systemDirectMusic,iniFile);
  systemDirectInput = GetPrivateProfileIntA("System","DirectInput",systemDirectInput,iniFile);
  DAT_00c31b7c = GetPrivateProfileIntA("System",(LPCSTR)&PTR_DAT_0055f8e4,DAT_00c31b7c,iniFile);
  driveCD = GetPrivateProfileIntA("Drive","CD",driveCD,iniFile);
  GetPrivateProfileStringA("Drive","CD-ROM",&driveCDROM,&driveCDROM,0x100,iniFile);
  GetPrivateProfileStringA("Drive","CD-DIR",&driveCDDIR,&driveCDDIR,0x100,iniFile);
  iVar2 = lstrlenA(&driveCDDIR);
  if (((0 < iVar2) && (cVar1 = *(char *)((int)&DAT_00c3085c + iVar2 + 3), cVar1 != '\\')) &&
     (cVar1 != '/')) {
    lstrcatA(&driveCDDIR,"\\");
  }
  DAT_0058c634 = GetPrivateProfileIntA("Option","GlobalFocus",DAT_0058c634,iniFile);
  DAT_0058c638 = GetPrivateProfileIntA("Option","GlobalSound",DAT_0058c638,iniFile);
  DAT_0058c63c = GetPrivateProfileIntA("Option","Confirmation",DAT_0058c63c,iniFile);
  GetPrivateProfileStringA("Script","Load",&DAT_00c30508,&DAT_00c30508,0x104,iniFile);
  DAT_00c30a64 = GetPrivateProfileIntA("Script","ProgramSize",DAT_00c30a64,iniFile);
  DAT_00c30858 = GetPrivateProfileIntA("Script","StackSize",DAT_00c30858,iniFile);
  DAT_00c30e74 = GetPrivateProfileIntA("Script","HeapSize",DAT_00c30e74,iniFile);
  return 1;
}
As you can see, this just reads the config file like an initialization file, and this is how it looks for my copy of the game so you get an idea:

Code:
[General]
Company=ESCUDE
Product=HimeYoku
Title=HimeYoku -A Sacrifice of Lust and Grace-
Now we go onto findExistingConfigFile:

C:
void findExistingConfigFile(LPCSTR configPath)

{
  HANDLE hFindFile;
  _WIN32_FIND_DATAA local_144;
  uint local_4;
 
  local_4 = DAT_0058c730 ^ (uint)&local_144;
  if (configPath != (LPCSTR)0x0) {
    hFindFile = FindFirstFileA(configPath,&local_144);
    if (hFindFile != (HANDLE)0xffffffff) {
      FindClose(hFindFile);
      __security_check_cookie(local_4 ^ (uint)&local_144);
      return;
    }
  }
  __security_check_cookie(local_4 ^ (uint)&local_144);
  return;
}
If you recall the original game's function (FUN_004eded0), you'll see that the game just keeps checking to see if the cfg file is the ESCUDE folder even though it knows that it should first create the subfolder first and then put the cfg inside it! (Unless I am missunderstanding something pretty obvious)


So, regarding the injection or modification process entirely, I am kinda unsure on what to do yet. I generally don't like the approach of injection through a new dll whenever is possible, it just overalls has a less cleaner result imho.

My approach at the moment has been to use a region that in its entirety looks like this (even way below where the new function ends):

1746487976194.png

If you think that this way of doing things is not ideal, let me know.
 
Oct 8, 2017
98
78
Oh, and if you wanna see the ENTIRE original game's function... here it is: (Posted on another message due to how LONG it is)

C:
void FUN_004ef860(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4)

{
  BOOL BVar1;
  int createConfigFile;
  uchar *puVar2;
  DWORD DVar3;
  HWND gameWindow;
  HRESULT HVar4;
  char *lpString2;
  char acStack_428 [4];
  tagMSG tStack_424;
  char configPath [260];
  char acStack_304 [256];
  char acStack_204 [256];
  char acStack_104 [256];
  uint local_4;
 
  local_4 = DAT_0058c730 ^ (uint)acStack_428;
  DAT_00c31b24 = param_1;
  _setlocale(0,"Japanese_Japan.932");
  GetModuleFileNameA((HMODULE)0x0,&iniFile,0x104);
  __splitpath(&iniFile,acStack_428,acStack_304,acStack_204,acStack_104);
  __makepath(&iniFile,acStack_428,acStack_304,(char *)0x0,(char *)0x0);
  FUN_004eded0();
  __makepath_s(&configFilePath,0x104,acStack_428,acStack_304,acStack_204,".cfg");
  createConfigFile = findExistingConfigFile(&configFilePath);
  if (createConfigFile != 0) {
    GetIniValues(&configFilePath);
  }
  if (inheritance != 0) {
    __makepath_s(&configFilePath,0x104,acStack_428,acStack_304,"configure",".cfg");
    createConfigFile = findExistingConfigFile(&configFilePath);
    if (createConfigFile != 0) {
      GetIniValues(&configFilePath);
    }
    if ((((inheritance != 0) && (companyString != '\0')) && (productString != '\0')) &&
       (createConfigFile = checkExistsConfigFile(0,5,configPath), createConfigFile != 0)) {
      getCorrectConfigPath(configPath);
      createConfigFile = findExistingConfigFile(configPath);
      if ((createConfigFile != 0) ||
         (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
        lstrcatA(configPath,"\\");
        lstrcatA(configPath,&productString);
        createConfigFile = findExistingConfigFile(configPath);
        if ((createConfigFile != 0) ||
           (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
          lstrcatA(configPath,"\\configure.cfg");
          lstrcpyA(&configFilePath,configPath);
          createConfigFile = findExistingConfigFile(configPath);
          if (createConfigFile != 0) {
            GetIniValues(&configFilePath);
          }
        }
      }
    }
  }
  createConfigFile = FUN_00412b60();
  if (createConfigFile == 0) {
    DAT_0058c654 = 0;
  }
  lstrcpyA(&DAT_00c30718,"ESCUDE_GAMES_MUTEX");
  lstrcpyA(&DAT_00c30400,"ESCUDE_GAMES_FRAME");
  lstrcpyA(&DAT_00c30610,"ESCUDE_GAMES_VIEW");
  if (DAT_00c30508 == '\0') {
    lstrcpyA(&DAT_00c30960,acStack_204);
    lstrcatA(&DAT_00c30960,"\\");
    lpString2 = "main.c";
LAB_004efb0d:
    lstrcpyA(&DAT_00c30508,lpString2);
  }
  else {
    puVar2 = __mbsrchr((uchar *)&DAT_00c30508,0x5c);
    if (puVar2 != (uchar *)0x0) {
      lstrcpynA(&DAT_00c30960,&DAT_00c30508,(int)(puVar2 + -0xc30506));
      lpString2 = (char *)(puVar2 + 1);
      goto LAB_004efb0d;
    }
  }
  if (multiInstance == 0) {
    CreateMutexA((LPSECURITY_ATTRIBUTES)0x0,1,&DAT_00c30718);
    DVar3 = GetLastError();
    if (DVar3 == 0xb7) {
      gameWindow = FindWindowA(&DAT_00c30400,(LPCSTR)0x0);
      if (gameWindow != (HWND)0x0) {
        BVar1 = IsIconic(gameWindow);
        if (BVar1 != 0) {
          ShowWindow(gameWindow,9);
        }
        SetForegroundWindow(gameWindow);
      }
      goto LAB_004efd16;
    }
  }
  SetCurrentDirectoryA(&iniFile);
  DAT_00c31a18 = 0;
  createConfigFile = lstrlenA(&driveCDROM);
  if (createConfigFile != 0) {
    createConfigFile = FUN_004edd60(&driveCDROM,0);
    while (createConfigFile == 0) {
      createConfigFile = MessageBoxA((HWND)0x0,"There is no disk in the drive.",&titleName,5);
      if (createConfigFile == 2) goto LAB_004efd16;
      createConfigFile = FUN_004edd60(&driveCDROM,0);
    }
    DAT_00c31a1a = 0;
    createConfigFile = lstrcmpiA(&DAT_00c31a18,acStack_428);
    if (createConfigFile == 0) {
      DAT_00c31a18 = 0;
    }
  }
  HVar4 = CoInitialize((LPVOID)0x0);
  if (HVar4 == 0) {
    createConfigFile = FUN_004ef630(param_1,param_4);
    if ((createConfigFile == 0) || (createConfigFile = FUN_004ee550(), createConfigFile == 0)) {
      CoUninitialize();
    }
    else {
      if (screenFullscreen != 0) {
        FUN_004ee6f0(1);
      }
      PostMessageA(DAT_00c31b28,0x401,0,0xc30508);
      while( true ) {
        while (BVar1 = PeekMessageA(&tStack_424,(HWND)0x0,0,0,0), BVar1 == 0) {
          WaitMessage();
        }
        BVar1 = GetMessageA(&tStack_424,(HWND)0x0,0,0);
        if (BVar1 == 0) break;
        if ((DAT_00c31b30 == (HWND)0x0) ||
           (BVar1 = IsDialogMessageA(DAT_00c31b30,&tStack_424), BVar1 == 0)) {
          TranslateMessage(&tStack_424);
          DispatchMessageA(&tStack_424);
        }
        else if ((tStack_424.message == 0x100) && (tStack_424.wParam == 0x77)) {
          SendMessageA(DAT_00c31b28,0x100,0x77,tStack_424.lParam);
        }
      }
      CoUninitialize();
      writeConfigFile(&configFilePath);
    }
  }
LAB_004efd16:
  __security_check_cookie(local_4 ^ (uint)acStack_428);
  return;
}
 

n00bi

Active Member
Nov 24, 2022
626
676
If you think that this way of doing things is not ideal, let me know.
I dont know what the correct way. its kind of hard without the exe and messing around a bit myself.
but from the looks of it. you have done a lot already.

but the FUN_004ef860 is the main function if i got this correct.?
so the main itself does the config checking so to speak.

if we look at you last post. it look like the variable that is used to hold the path to the config file is the configFilePath
unless you plan on running multiple instances or something you can just store the config in you gamedir and do a "jump"

C:
void FUN_004ef860(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4 {
  BOOL BVar1;
  int createConfigFile;
  uchar *puVar2;
  DWORD DVar3;
  HWND gameWindow;
  HRESULT HVar4;
  char *lpString2;
  char acStack_428 [4];  // drive
  tagMSG tStack_424;
  char configPath [260];
  char acStack_304 [256]; // dir
  char acStack_204 [256]; // filename
  char acStack_104 [256];
  uint local_4;

  local_4 = DAT_0058c730 ^ (uint)acStack_428;
  DAT_00c31b24 = param_1;
  _setlocale(0,"Japanese_Japan.932");

  // store the path of the exe file into &iniFile
  // example: C:\mygames\apple\applegame.exe
  GetModuleFileNameA((HMODULE)0x0,&iniFile,260);

  // split the path in &iniFile..
  __splitpath(&iniFile,acStack_428, acStack_304, acStack_204, acStack_104);

  // construct a new sting in &iniFile like:
  // C:\mygame\apple
  __makepath(&iniFile, acStack_428, acStack_304, NULL, NULL);

  FUN_004eded0(); // init values..


  /*
    "configFilePath" is declared somewhere else.
    construct a new sting in &configFilePath like:
    C:\mygame\apple\applegame.cfg
  */
  __makepath_s(&configFilePath, 260, acStack_428, acStack_304, acStack_204,".cfg");

  // check if file exists.
  createConfigFile = findExistingConfigFile(&configFilePath);

  if (createConfigFile != 0) {
    // read the values from the config file.
    // C:\mygame\apple\applegame.cfg
    GetIniValues(&configFilePath);
  }

// ---------------------
goto SKIP;
// ---------------------

  if (inheritance != 0) {
      /*
      construct a new sting in &configFilePath like:
      C:\mygame\apple\configure.cfg
      */
    __makepath_s(&configFilePath, 260 ,acStack_428, acStack_304,"configure",".cfg");
    createConfigFile = findExistingConfigFile(&configFilePath);

    if (createConfigFile != 0) {
      GetIniValues(&configFilePath);
    }

    if ((((inheritance != 0) && (companyString != '\0')) && (productString != '\0')) && (createConfigFile = checkExistsConfigFile(0,5,configPath), createConfigFile != 0)) {

      getCorrectConfigPath(configPath);
      createConfigFile = findExistingConfigFile(configPath);

      // create a dir
      if ((createConfigFile != 0) || (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
        lstrcatA(configPath,"\\");
        lstrcatA(configPath,&productString);
        createConfigFile = findExistingConfigFile(configPath);

        if ((createConfigFile != 0) || (BVar1 = CreateDirectoryA(configPath,(LPSECURITY_ATTRIBUTES)0x0), BVar1 != 0)) {
          lstrcatA(configPath,"\\configure.cfg");
          lstrcpyA(&configFilePath, configPath);
          createConfigFile = findExistingConfigFile(configPath);
          if (createConfigFile != 0) {
            GetIniValues(&configFilePath);
          }
        }
      }
    }
  }

// ---------------------
SKIP:;
// ---------------------


  createConfigFile = FUN_00412b60();
  if (createConfigFile == 0) {
    DAT_0058c654 = 0;
  }
  ...
  ...
in short, you can skip all the ifs and buts.

I have no idea what this does tho if this will create a config based on configFilePath earlier up in the code.
createConfigFile = FUN_00412b60();
your Ghidra is funny, naming and assigning almost everything as/to createConfigFile even void functions :p

So how do you modify you function. the way i know of.. maybe your Ghidra is more up to date.
you dissemble the program locate in the text for where the function does it stuff.
use a hex editor and replace thouse bytes.

And you replace every byte with a 0x90 byte. A "No Operation Instruction" to the cpu.
so you want to find the code in the asm that is between the skip labels and replace every byte the with 0x90.

A simple example.
assume we have code like this.
You don't have permission to view the spoiler content. Log in or register now.
 
Last edited:
  • Like
Reactions: The VN Connoisseur
Oct 8, 2017
98
78
n00bi my Ghidra is the most current release by the way. It is pretty good, since it does let you directly insert the assembly instruction at whatever instruction that you want, and it will reposition everything automatically if whatever instruction requires more or less bytes than the original instruction, so you don't need to hex edit it (for the most part). As for most of the names, those were assigned by me, considering how I assumed that these functions/variables where used for.

And yes, FUN_004ef860 is basically the main function that deals with the configuration creation/updating process, unless I have missed something? But the main executable has no more mentions of the .cfg file, and considering that this function seems to do everything I wanna avoid the game doing, it is fair to say that this is the correct place to put my efforts into.

Attached in this post you can get the game's executable, so you check it out by yourself if you so wish.
 

n00bi

Active Member
Nov 24, 2022
626
676
I havent tested Ghidra, but if it can do that, its sounds good.
Since your exe is marked with a virus, i will not be downloading it. I hope you can understand that.

From the looks of it. all you need to do is insert a jump at the position shown in my prev post where the goto command is.
In your debugger you probably need to use breakpoints and step though the code to figure out where it is and how long of a jump is needed.
unless you want to "decode asm directly".
if it actually allows you to modify it with C code i will be amazed.
 
Last edited:
Oct 8, 2017
98
78
I havent tested Ghidra, but if it can do that, its sounds good.
Since your exe is marked with a virus, i will not be downloading it. I hope you can understand that.

From the looks of it. all you need to do is insert a jump at the position shown in my prev post where the goto command is.
In your debugger you probably need to use breakpoints and step though the code to figure out where it is and how long of a jump is needed.
unless you want to "decode asm directly".
if it actually allows you to modify it with C code i will be amazed.
Oh, just get the exe from this thread https://f95zone.to/threads/himeyoku-a-sacrifice-of-lust-and-grace-final-escu-de.130362/, the virus detector is as basic as it is.
Sadly you cannot edit the code as C, but you can see the changes done to the function interpreted as C in real time.
 

n00bi

Active Member
Nov 24, 2022
626
676
Sadly you cannot edit the code as C, but you can see the changes done to the function interpreted as C in real time.
I did download the both ghidra and the exe. but i have not run the exe,
however i did take a look at it.

And it looks like my initial thought by doing a jump is doable.
Take a note of the address the if statement is on.
jrH4YIrzFZ.png

Next scroll down til the iVar1 = FUN_00412b60();
And take a note of its address aswell.

javaw_Zw41b1ou8D.png

Next is replace the opcode on the 1st address with a jmp.
So at address 4ef94a you want a jmp 0x4efa80


Asm snipet.
...
4ef94a: 0f 84 2b 01 00 00 je 0x4efa7b ---> jump 0x136 bytes ahead. "jmp 0x4efa80"
4ef950: 68 f0 fa 55 00 push 0x55faf0
4ef955: 68 e4 fa 55 00 push 0x55fae4
....
....
4efa80: e8 db 30 f2 ff call 0x412b60 <---- to here.
...
...


you should replace
4ef94a: 0f 84 2b 01 00 00 je 0x4efa7b
with something like
4ef94a: eb 86 jmp 0x4efa80
or
4ef94a: e9 88000000

Disclaimer tho. i have not tested this, i have only looked at the code.
nor do i know how to patch up something in ghidra.
 
Last edited:
Oct 8, 2017
98
78
I did download the both ghidra and the exe. but i have not run the exe,
however i did take a look at it.

And it looks like my initial thought by doing a jump is doable.
Take a note of the address the if statement is on.
View attachment 4816688

Next scroll down til the iVar1 = FUN_00412b60();
And take a note of its address aswell.

View attachment 4816690

Next is replace the opcode on the 1st address with a jmp.
So at address 4ef94a you want a jmp 0x4efa80


Asm snipet.
...
4ef94a: 0f 84 2b 01 00 00 je 0x4efa7b ---> jump 0x136 bytes ahead. "jmp 0x4efa80"
4ef950: 68 f0 fa 55 00 push 0x55faf0
4ef955: 68 e4 fa 55 00 push 0x55fae4
....
....
4efa80: e8 db 30 f2 ff call 0x412b60 <---- to here.
...
...


you should replace
4ef94a: 0f 84 2b 01 00 00 je 0x4efa7b
with something like
4ef94a: eb 44 1e jmp 0x4efa80
or
4ef94a: db 0xEB, 0x44, 0x1E

Disclaimer tho. i have not tested this, i have only looked at the code.
nor do i know how to patch up something in ghidra.
That looks to be doable, I just need to make sure that adding the new function just does not make the game go ape shit. Whenever I can I'll insert the function you have recommended to me and see if the game is even capable of just running without a hitch.

If it does, I'll just then patch it out and do some tests in-game...
 

n00bi

Active Member
Nov 24, 2022
626
676
The VN Connoisseur Yeah. only way to see if it works is to test it.
vq1jLvb08P.png

I didnt use ghidra to modify the file.
i just used objdump to get an asm file then i wrote a simple py script to get the opcodes from the output of that file
for the address range that was needed. ugly but simple script.

objdump -d -M intel princess.exe > princess_asm.txt


Python:
import string
def search_and_replace_in_exe(search_string, replacement_string, input_file_path, output_file_path):
    # Convert hex strings to bytes
    search_bytes = bytes.fromhex(search_string)
    replacement_bytes = bytes.fromhex(replacement_string)

    # Read the contents of the executable file
    with open(input_file_path, 'rb') as f:
        content = bytearray(f.read())

    # Replace the search string with the replacement string
    original_content_length = len(content)
    replaced_content_length = 0

    while True:
        start_idx = content.find(search_bytes, replaced_content_length)
        if start_idx == -1:
            break

        end_idx = start_idx + len(search_bytes)
        content[start_idx:end_idx] = replacement_bytes
        replaced_content_length = end_idx

    # Check if the replacement was successful
    assert len(content) == original_content_length, "Content length mismatch after replacement"

    # Write the modified content to a new file
    with open(output_file_path, 'wb') as f:
        f.write(content)


def extract_address_range(file_path, start_addr, end_addr):

    in_section = False
    org_string=""
    with open(file_path, 'r') as file:
        for line in file:
            line = line.strip() # strip leading whitespaces.
            data = line.split(":")  # split the line at :
            addr = data[0]          # the address as string in hex. its string...

            # Simple checks.
            if len(addr) == 0:
                continue
             
            if all(c in string.hexdigits for c in addr) == False:
                continue
         
            # convert it to a int..
            addr = int(addr, 16)
         
            # dont include the end addr
            if ((addr >= start_addr) and (addr < end_addr)):
             
                #exctract the machine code fomr data[1].
                bstring = ""
                for c in data[1].strip():
                    if c.lower() in [" ","0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]:
                        bstring = bstring + c
                    else:
                        break
                org_string = org_string + bstring
 
    # return the bytes  to be replaced.
    return (org_string.replace(" ",""))
 


if __name__ == "__main__":

    filename = "princess_asm.txt"
    start_address = 0x4ef94a
    end_address = 0x4efa80

    orginal_string = extract_address_range(filename, start_address, end_address)

    # Convert the hex string to a bytes object
    orginal_bytes = bytearray.fromhex(orginal_string)

    # Convert the bytes object to a list of integers (each representing a byte)
    byte_list = [b for b in orginal_bytes]

    # replace all bytes with 0x90. a nop instruction
    replacement_bytes  = [0x90 for b in byte_list]
    replacement_string = ''.join([format(byte, '02x') for byte in replacement_bytes])
 

    input_file_path = "princess.exe"
    output_file_path = "princess_modified.exe"
 
    search_and_replace_in_exe(orginal_string, replacement_string, input_file_path, output_file_path)

And if you compare the asm you will see its fill with nop's from 0x4ef94 to 0x4efa7b

if you just want to edit yourself by hexedit and not use objdump and the py script,
search for this:
0f842b01000068f0fa550068e4fa55008d84243c010000508d4c241c51680401000068201ac300e8af80000068201ac300e870ddffff83c41c85c0740d68201ac300e80fe6ffff83c404833dfc03c300000f84da000000803d680cc300000f84cd000000803d1018c300000f84c00000008d542430526a056a00e897ddffff83c40c85c00f84a700000068c4f855008d44243450ffd768680cc3008d4c243451ffd78d54243052e8fadcffff8b2d28d1550083c40485c0750d6a008d44243450ffd585c0746b68c4f855008d4c243451ffd7681018c3008d54243452ffd78d44243050e8bedcffff83c40485c0750c508d4c243451ffd585c0743668d4fa55008d54243452ffd78d4424305068201ac300ffd68d4c243051e889dcffff83c40485c0740d68201ac300e828e5ffff83c404b948905800

and replace it with:
90909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090
 
Last edited:
  • Heart
Reactions: The VN Connoisseur