DLL hook/performance questions lazarus / 34-Windows ---------------------------------------------------------------------- Mike_HDF · 2019-04-09 11:29 Hi, everyone. I have an old game and I'm trying to tweak it a little with the help of free pascal. The game reads some files in it's EXE folder using the FindFirstFileA() and FindNextFileA() functions of the kernel32.dll. The problem is that I need to sort in alphabetical order that list of files so I can make a useful mod for it and the functions don't do it (depends on Windows version or partition system, not sure). I want to make my own dll to rewrite only those functions but I'm not sure about the best approach. Some people seems to override some old dll and put theirs own version (letting, for example, to play the soundtrack in MP3 format instead of the Audio CD), but then I would have to re-link a lot of functions to the original dll, right? It could affect to game performance? Should I try some dll hooking method? Its relatively important that the EXE keeps it's original size and don't want antivirus alerts if is needed some external application. Thanks in advance. ---------------------------------------------------------------------- howardpc · 2019-04-09 14:05 An obvious approach would be to read the filenames into a TStringList, and set Sorted to True. You might need to write a simple TStringList.CustomSort method, depending on the encoding of the filenames. ---------------------------------------------------------------------- Mike_HDF · 2019-04-09 15:51 Yes, that's the way I made it. The dll is working well, my question is more related to the hook of the kernel32.dll call. I've seen people with questions about dll injection here, but maybe it's not the appropriate forum. Thanks for your response anyway. I'll test the dll by replacing all the functions the exe uses. ---------------------------------------------------------------------- 440bx · 2019-04-09 16:13 Quote from: Mike_HDF on April 09, 2019, 05:51:16 pmThe dll is working well, my question is more related to the hook of the kernel32.dll call. I've seen people with questions about dll injection here, but maybe it's not the appropriate forum. I'll test the dll by replacing all the functions the exe uses. First, I want to make sure I understand the problem correctly.  You want to trap API calls made by another process and, for this you want to cause the other process to load your dll (or alternatively inject it into the other process), is this correct ? ---------------------------------------------------------------------- Mike_HDF · 2019-04-09 19:13 Quote from: 440bx on April 09, 2019, 06:13:47 pmQuote from: Mike_HDF on April 09, 2019, 05:51:16 pmThe dll is working well, my question is more related to the hook of the kernel32.dll call. I've seen people with questions about dll injection here, but maybe it's not the appropriate forum. I'll test the dll by replacing all the functions the exe uses. First, I want to make sure I understand the problem correctly.  You want to trap API calls made by another process and, for this you want to cause the other process to load your dll (or alternatively inject it into the other process), is this correct ? Yes, I would prefer not to need a third program but yes: I want to override some of the functions (FindFirstFileA, FindNextFileA, FindClose) with the cleanest solution. I'm testing right now and trying to figure out why it crashes. What I have done is rewrite the game EXE to search for my dll instead of kernel32.dll. My english is limited, sorry if it's hard to understand me and thanks again. ---------------------------------------------------------------------- 440bx · 2019-04-09 20:31 Quote from: Mike_HDF on April 09, 2019, 09:13:00 pmYes, I would prefer not to need a third program but yes: I want to override some of the functions (FindFirstFileA, FindNextFileA, FindClose) with the cleanest solution. I'm testing right now and trying to figure out why it crashes. What I have done is rewrite the game EXE to search for my dll instead of kernel32.dll. I'm going to suggest something that you won't find anywhere mentioned but, it is a variation on techniques used to analyze malware. As you know, the typical/"normal" way of solving the problem you want to solve is to inject a dll into the other process (that what MS wants you to do.)  The other way of solving that problem - which has its pros and cons - is to load the exe as if it were a DLL into your process and executing it there. This is roughly how it's done: 1. Use LoadLibraryEx to load the exe into your process specifying LOAD_LIBRARY_AS_IMAGE_RESOURCE.  This will cause LoadLibraryEx to map the exe with the same layout as when it is loaded to be executed.  However, when mapped this way, the loader does not do any fixups, including relocations and updating the Import Address Table (IAT) with the actual address of the functions the exe imports. 2. Apply the relocations and update the IAT.  That's fairly easy (same process as when rebasing an executable followed by a loop over the IAT calling GetProcAddress and updating all the IAT slots.) Be careful in this step, some exe's put the IAT in a read only section of the PE file, in those cases, it only becomes read only after the loader has patched the IAT but, when you're doing the loader's job, you either change the protection (VirtualProtect) or use WriteProcessMemory which will work around that "inconvenience". Steps 3 and 4 that follow should not be necessary but they are a good precaution to have in place. 3. Somewhere in your program have a function that is the same as the typical DLLMAIN.  You'll tell Windows that's the "guest" exe's entry point which it may call (it usually won't) when attach/detach events occur.  This step may not be necessary since Windows didn't load the exe for execution but, better to play it safe, just in case Windows has a change of heart. 4. change the exe's entry point to point to your "DLLMAIN".  (of course save the exe's original entry point since you'll need it to start the exe with a call to CreateThread.  NOTE: DisableThreadLibraryCalls is not a way to get around having to do this because even when disabled, the DLLs still get a process attach and detach, if there is no handler for that and, Windows has decided to call the "dll" entry point, it can cause your process to go south when ending. Once those are done, you have a lot of advantages over injecting a dll into another process.  Among them: 1. you have complete control over what the exe can and cannot do as well as when it gets to do it. 2. you can track and modify its TLS callbacks 3. you can debug that process easily since it is in your process space instead of another. 4. you can implement a "poor man's" debugger.  Slap an int 3 anywhere in the exe if you need it to break and the exception will be routed to your exception handler(s). 5. You can attach a console to your process and use writeln in the traps you've installed to see what's going on in real time. One thing you need to watch out for is thread local storage.  If the exe uses static thread variables and your program does too then you need to ensure that your program doesn't overwrite any of the "guest" exe's thread variables.  Easy way to solve that problem is not to use static thread variables instead use TlsAlloc. You may also need to execute the exe's thread callbacks since those are usually meant to initialize the thread variables as the program needs them. Another thing to watch out for when doing that is, Windows will see your program as the process owner (instead of being owned by the "guest" exe) and there are some assumptions it will make as a result of that.  Some API calls made by the "guest" will return addresses that won't be "ideal".  The usual culprits are APIs that default to a process' resource (such as GetProcessHeap).  Those can be a source of undesirable side effects.  Use CreateHeap, trap GetProcessHeap and hand it the heap handle of the heap (edited) you created.  That way, it stays out of your program's way and your program doesn't get in its way either.   Go through the APIs the "guest" exe imports, any API that returns a default is a potential source of problems. It takes some work to get it setup but, once you've gotten steps 1 and 2 done right, any additional steps can be determined with the use of a debugger.  Once it's set up, everything is so much easier when the executable is in your own process.  If you decide to give it a shot that way, I recommend you download ProcessHacker (free open source utility), it will allow you to inspect your process memory layout with ease (you'll need that!.)  Essentially, it's just a "poor man's" sandbox. ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 07:39 Wow, 440bx, thanks a lot for your detailed response. Since I want to investigate the game's exe for fixing and tweaking, I'll take a deep look into it. I have a working DLL by now. This is an excerpt of the working code of the DLL (the game runs perfectly): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i library sortfile; {$mode objfpc}{$H+}{$R sortfile.res} uses  Classes,windows  { you can add units after this }; function FindFirstFileA(lpFileName: LPCSTR; var lpFindFileData: TWIN32FindDataA): THandle; external 'kernel32' name 'FindFirstFileA';function FindNextFileA(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; external 'kernel32' name 'FindNextFileA';function FindClose(hFindFile : THandle): BOOL; external 'kernel32' name 'FindClose'; exports  FindFirstFileA,  FindNextFileA,  FindClose;beginend.  But when I try to personalize the functions, got different SIGSEGV errors. I'll have different versions of Lazarus, both with a cross platform compiler: 1.6.2 in a laptop (the one I'm using right now) and it does compile and work with a test Exe I made, but can't produce an i386-win32 DLL, the compiler says it's not supported and the game is a 32 bits app. On home I have 1.9.x and can create the i386-win32 DLL but crashes on a lot of lines. I have Win7 x64 on both. This simple comparison crash: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  Classes,windows...function FindFirstFileA(lpFileName: LPCSTR; var lpFindFileData: TWIN32FindDataA): THandle; stdcall;var  myhandle : THandle;...  myhandle := FindFirstFile(lpFileName,lpFindFileData);  if myhandle <> INVALID_HANDLE_VALUE then begin...  There must be a problem with using an x64 build of Lazarus when creating a x32 DLL or EXE that uses DLLs, Am I right? ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 11:34 This is insane! Even installing Lazarus 2.0.0 (32 bits) and recompiling, it keeps crashing  . ---------------------------------------------------------------------- 440bx · 2019-04-10 12:31 Quote from: Mike_HDF on April 10, 2019, 09:39:19 amWow, 440bx, thanks a lot for your detailed response. Since I want to investigate the game's exe for fixing and tweaking, I'll take a deep look into it. You'll find that learning that technique is definitely worth it. Quote from: Mike_HDF on April 10, 2019, 09:39:19 amThere must be a problem with using an x64 build of Lazarus when creating a x32 DLL or EXE that uses DLLs, Am I right? As far as the exception you're getting, it's really difficult to debug things that are not in the same process.   If I were doing it the way you're doing it, I'd put an INT 3 (a call to DebugBreak()) at the entrance of every trap I've coded (and hope there is a way for the debugger you're using to activate itself on those.) I'd pay close attention to the stack upon entry and exit.  Very often the problems occur because the stack frame is not properly restored.    That's always the first thing to check, is the stack handled correctly and, in the specific case of FindFirstFile, FindNextFile, it's possible, even likely, that the exe is using recursion.  If you have any static variables being set in your traps, recursion will usually mess up the values. One thing you can do, if you place calls to DebugBreak() at the entrance of your traps and the debugger you're using won't automatically activate and trap them, you can use the Visual Studio debugger.  When the exception occurs, Windows will present a box giving you the option to debug the program, tell it you want to debug it and it will activate a new instance of the VS debugger.  You'll get that too if you have calls to DebugBreak() in your traps.  Of course, you'll be debugging in raw (no symbols) assembler. My first impression is that the 64bit Lazarus, while it may be causing the problem, it's more likely that the problem is caused by something in your code. Without having the entire ball of wax to look at, I can only give you general ideas. HTH. ---------------------------------------------------------------------- engkin · 2019-04-10 12:43 Quote from: Mike_HDF on April 10, 2019, 09:39:19 amI have a working DLL by now. [snip] There must be a problem with using an x64 build of Lazarus when creating a x32 DLL or EXE that uses DLLs, Am I right? You are not using stdcall. Try at the top of your functions to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$calling stdcall} ---------------------------------------------------------------------- 440bx · 2019-04-10 13:04 Quote from: engkin on April 10, 2019, 02:43:13 pmYou are not using stdcall. Try at the top of your functions to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$calling stdcall}Good observation.  Without it the stack got screwed up. ---------------------------------------------------------------------- engkin · 2019-04-10 13:09 Quote from: 440bx on April 10, 2019, 03:04:35 pmQuote from: engkin on April 10, 2019, 02:43:13 pmYou are not using stdcall. Try at the top of your functions to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$calling stdcall}Good observation.  Without it the stack got screwed up. You pointed at it. I checked his code after reading your previous post. We still don't see the method he used to replace these functions. ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 13:36 Quote from: 440bx on April 10, 2019, 02:31:40 pmAs far as the exception you're getting, it's really difficult to debug things that are not in the same process.   If I were doing it the way you're doing it, I'd put an INT 3 (a call to DebugBreak()) at the entrance of every trap I've coded (and hope there is a way for the debugger you're using to activate itself on those.) I'd pay close attention to the stack upon entry and exit.  Very often the problems occur because the stack frame is not properly restored.    That's always the first thing to check, is the stack handled correctly and, in the specific case of FindFirstFile, FindNextFile, it's possible, even likely, that the exe is using recursion.  If you have any static variables being set in your traps, recursion will usually mess up the values. One thing you can do, if you place calls to DebugBreak() at the entrance of your traps and the debugger you're using won't automatically activate and trap them, you can use the Visual Studio debugger.  When the exception occurs, Windows will present a box giving you the option to debug the program, tell it you want to debug it and it will activate a new instance of the VS debugger.  You'll get that too if you have calls to DebugBreak() in your traps.  Of course, you'll be debugging in raw (no symbols) assembler. Without having the entire ball of wax to look at, I can only give you general ideas. HTH. I'm not that good, I'm still learning about assembly, sorry  Quote from: 440bx on April 10, 2019, 02:31:40 pmMy first impression is that the 64bit Lazarus, while it may be causing the problem, it's more likely that the problem is caused by something in your code. I found something weird: When I leave the default option for OS and CPU (then it compiles a 64 bits executable), the variable myhandle of THandle type says it's a QWord. When I choose Win32 and i386 it says it's a LongWord, I'm doing something wrong with the project configuration? Quote from: engkin on April 10, 2019, 02:43:13 pmYou are not using stdcall. Try at the top of your functions to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$calling stdcall} Thanks for your response. I defined the functions with stdcall inside DLL. Tried what you suggested too but it fails. QuoteWithout having the entire ball of wax to look at, I can only give you general ideas. This is a bigger excerpt of the DLL: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i library sortfile; {$mode objfpc}{$H+}{$R sortfile.res} uses  Classes,Windows,sysutils; var  fileLst : TStringList;  idxLst : Integer; type  PTOP_LEVEL_EXCEPTION_FILTER = function(ExceptionInfo: PEXCEPTION_POINTERS): LONG; stdcall;  {$EXTERNALSYM PTOP_LEVEL_EXCEPTION_FILTER}  LPTOP_LEVEL_EXCEPTION_FILTER = PTOP_LEVEL_EXCEPTION_FILTER; //Funciones propias de kernel32.dll que son usadas por Total Annihilationfunction GetVolumeInformationA(lpRootPathName: LPCSTR; lpVolumeNameBuffer: LPSTR;         nVolumeNameSize: DWORD; lpVolumeSerialNumber: LPDWORD;         var lpMaximumComponentLength, lpFileSystemFlags: DWORD;         lpFileSystemNameBuffer: LPSTR; nFileSystemNameSize: DWORD): BOOL; external 'kernel32' name 'GetVolumeInformationA';function GetDriveTypeA(lpRootPathName: LPCSTR): UINT; external 'kernel32' name 'GetDriveTypeA';function InterlockedExchange(var Target: LONG; Value: LONG): LONG; external 'kernel32' name 'InterlockedExchange';function SetEvent(hEvent: HANDLE): BOOL; external 'kernel32' name 'SetEvent';function GetVersionExA(lpVersionInformation: LPOSVERSIONINFOA): BOOL; external 'kernel32' name 'GetVersionExA';function GlobalSize(hMem: HGLOBAL): SIZE_T; external 'kernel32' name 'GlobalSize';function GlobalLock(hMem: HGLOBAL): LPVOID; external 'kernel32' name 'GlobalLock';function GetEnvironmentStrings: LPWSTR; external 'kernel32' name 'GetEnvironmentStrings';function GetEnvironmentStringsW: LPWSTR; external 'kernel32' name 'GetEnvironmentStringsW';function SetHandleCount(uNumber: UINT): UINT; external 'kernel32' name 'SetHandleCount';function GetStdHandle(nStdHandle: DWORD): HANDLE; external 'kernel32' name 'GetStdHandle';function GetFileType(hFile: HANDLE): DWORD; external 'kernel32' name 'GetFileType';function HeapDestroy(hHeap: HANDLE): BOOL; external 'kernel32' name 'HeapDestroy';function HeapCreate(flOptions: DWORD; dwInitialSize: SIZE_T;         dwMaximumSize: SIZE_T): HANDLE; external 'kernel32' name 'HeapCreate';function SetStdHandle(nStdHandle: DWORD; hHandle: HANDLE): BOOL; external 'kernel32' name 'SetStdHandle';function GetStringTypeA(Locale:LCID; dwInfoType:DWORD; lpSrcStr:LPCSTR;         cchSrc:longint; lpCharType:LPWORD):WINBOOL; external 'kernel32' name 'GetStringTypeA';function GetStringTypeW(dwInfoType: DWORD; const lpSrcStr: PWideChar;         cchSrc: BOOL; var lpCharType: Word): BOOL; external 'kernel32' name 'GetStringTypeW';function FlushFileBuffers(hFile: HANDLE): BOOL; external 'kernel32' name 'FlushFileBuffers';function IsBadReadPtr(lp: LPVOID; ucb: UINT_PTR): BOOL; external 'kernel32' name 'IsBadReadPtr';function IsBadWritePtr(lp: LPVOID; ucb: UINT_PTR): BOOL; external 'kernel32' name 'IsBadWritePtr';function IsBadCodePtr(lpfn: FARPROC): BOOL; external 'kernel32' name 'IsBadCodePtr';function IsValidLocale(Locale:LCID; dwFlags:DWORD):WINBOOL; external 'kernel32' name 'IsValidLocale';function IsValidCodePage(CodePage:UINT):WINBOOL; external 'kernel32' name 'IsValidCodePage';function GetLocaleInfoA(Locale:LCID; LCType:LCTYPE; lpLCData:LPSTR; cchData:longint):longint; external 'kernel32' name 'GetLocaleInfoA';function EnumSystemLocalesA(lpLocaleEnumProc:LOCALE_ENUMPROCA; dwFlags:DWORD):WINBOOL; external 'kernel32' name 'EnumSystemLocalesA';function GetUserDefaultLCID:LCID; external 'kernel32' name 'GetUserDefaultLCID';function CompareStringA(Locale:LCID; dwCmpFlags:DWORD; lpString1:LPCSTR;       cchCount1:longint; lpString2:LPCSTR;cchCount2:longint):longint; external 'kernel32' name 'CompareStringA';function CompareStringW(Locale:LCID; dwCmpFlags:DWORD; lpString1:LPCWSTR;       cchCount1:longint; lpString2:LPCWSTR;cchCount2:longint):longint; external 'kernel32' name 'CompareStringW';function GetLocaleInfoW(Locale:LCID; LCType:LCTYPE; lpLCData:LPWSTR; cchData:longint):longint; external 'kernel32' name 'GetLocaleInfoW';function TlsGetValue(dwTlsIndex: DWORD): LPVOID; external 'kernel32' name 'TlsGetValue';function MultiByteToWideChar(CodePage:UINT; dwFlags:DWORD; lpMultiByteStr:LPCSTR;         cchMultiByte:longint; lpWideCharStr:LPWSTR;cchWideChar:longint):longint; external 'kernel32' name 'MultiByteToWideChar';procedure ExitProcess(uExitCode:UINT); external 'kernel32' name 'ExitProcess';function GlobalHandle(pMem: LPCVOID): HGLOBAL; external 'kernel32' name 'GlobalHandle';function lstrlenA(lpString: LPCSTR): Integer; external 'kernel32' name 'lstrlenA';function lstrcpyA(lpString1: LPSTR; lpString2: LPCSTR): LPSTR; external 'kernel32' name 'lstrcpyA';function lstrcpynA(lpString1: LPSTR; lpString2: LPCSTR; iMaxLength: Integer): LPSTR; external 'kernel32' name 'lstrcpynA';function CloseHandle(hObject: HANDLE): BOOL; external 'kernel32' name 'CloseHandle';[...]function GetThreadPriority(hThread: HANDLE): Integer; external 'kernel32' name 'GetThreadPriority';function SetUnhandledExceptionFilter(lpTopLevelExceptionFilter : LPTOP_LEVEL_EXCEPTION_FILTER) : LPTOP_LEVEL_EXCEPTION_FILTER; external 'kernel32' name 'SetUnhandledExceptionFilter'; //Funciones que vamos a personalizar, aunque también las usaremos internamentefunction FindFirstFile(lpFileName: LPCSTR; var lpFindFileData: TWIN32FindDataA): THandle; external 'kernel32' name 'FindFirstFileA';function FindNextFile(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; external 'kernel32' name 'FindNextFileA';function FindCloseMe(hFindFile : THandle): BOOL; external 'kernel32' name 'FindClose'; { *** Aquí empieza la fiesta *** }{procedure asigna_estructura_de_1_a_2(p1 : TWIN32FindDataA; var p2 : TWIN32FindDataA);begin  p2.cAlternateFileName := p1.cAlternateFileName;  p2.cFileName := p1.cFileName;  p2.dwFileAttributes:= p1.dwFileAttributes;  p2.dwReserved0:= p1.dwReserved0;  p2.dwReserved1:= p1.dwReserved1;  p2.ftCreationTime := p1.ftCreationTime;  p2.ftLastAccessTime := p1.ftLastAccessTime;  p2.ftLastWriteTime := p1.ftLastWriteTime;  p2.nFileSizeHigh := p1.nFileSizeHigh;  p2.nFileSizeLow := p1.nFileSizeLow;end; //asigna_estructura_de_1_a_2}function FindFirstFileA(lpFileName: LPCSTR; var lpFindFileData: TWIN32FindDataA): THandle; stdcall;var  myhandle : THandle;  firstdata : TWIN32FindDataA;begin  fileLst := TStringList.Create();  idxLst := 0;   myhandle := FindFirstFile(lpFileName,lpFindFileData);  //asigna_estructura_de_1_a_2(lpFindFileData,firstdata);   if myhandle <> INVALID_HANDLE_VALUE then begin     repeat         fileLst.Add(lpFindFileData.cFileName);     until FindNextFile(myhandle,lpFindFileData) = FALSE;     FindCloseMe(myhandle);     fileLst.SaveToFile('ztmp.txt');      fileLst.Sort();     //asigna_estructura_de_1_a_2(firstdata,lpFindFileData);     lpFindFileData.cFileName := fileLst.Strings[0];     idxLst := 1;  end;   result := myhandle;end; //FindFirstFileA function FindNextFileA(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; stdcall;begin  if idxLst < fileLst.Count then begin     lpFindFileData.cFileName := fileLst.Strings[idxLst];     idxLst := idxLst + 1;     result := TRUE;  end else begin     result := FALSE;  end;end; //FindNextFileA function FindClose(hFindFile : THandle): BOOL; stdcall;begin  fileLst.Clear();  fileLst := nil;  result := TRUE;end; //FindClose exports  GetVolumeInformationA,  GetDriveTypeA,  InterlockedExchange,  SetEvent,  GetVersionExA,  GlobalSize,  GlobalLock,  GetEnvironmentStrings,  GetEnvironmentStringsW,  SetHandleCount,  GetStdHandle,  GetFileType,  HeapDestroy,  HeapCreate,  SetStdHandle,  GetStringTypeA,  GetStringTypeW,  FlushFileBuffers,  IsBadReadPtr,  IsBadWritePtr,  IsBadCodePtr,  IsValidLocale,  IsValidCodePage,  GetLocaleInfoA,  EnumSystemLocalesA,  GetUserDefaultLCID,  CompareStringA,  CompareStringW,  GetLocaleInfoW,  TlsGetValue,  MultiByteToWideChar,  ExitProcess,  GlobalHandle,  lstrlenA,  lstrcpyA,  lstrcpynA,  CloseHandle,  CreateFileA,  DebugBreak,  Sleep,  GetTickCount,  GetPrivateProfileIntA,  LoadLibraryA,  SetThreadPriority,  GetCurrentThread,  WaitForSingleObject,  GetPrivateProfileStringA,  FreeLibrary,  QueryPerformanceCounter,  WriteFile,  GlobalMemoryStatus,  CreateSemaphoreA,  OpenSemaphoreA,  GetModuleFileNameA,  SetFilePointer,  SetCurrentDirectoryA,  GlobalFree,  GlobalUnlock,  GetProcAddress,  GetFileSize,  GetOEMCP,  GetACP,  GetCPInfo,  UnhandledExceptionFilter,  LCMapStringW,  LCMapStringA,  WideCharToMultiByte,  FreeEnvironmentStringsW,  ReadFile,  SetLastError,  TlsFree,  TlsAlloc,  FatalAppExitA,  HeapReAlloc,  HeapSize,  HeapAlloc,  HeapFree,  RaiseException,  CreateDirectoryA,  FileTimeToSystemTime,  SetEnvironmentVariableA,  GetCurrentDirectoryA,  DeleteFileA,  MoveFileA,  ResumeThread,  SetEndOfFile,  GetVersion,  GetStartupInfoA,  SetConsoleCtrlHandler,  GetLocalTime,  GetTimeZoneInformation,  ExitThread,  TlsSetValue,  InterlockedIncrement,  InterlockedDecrement,  RtlUnwind,  TerminateProcess,  DeviceIoControl,  GetPriorityClass,  SetPriorityClass,  QueryPerformanceFrequency,  GetSystemTime,  SystemTimeToFileTime,  UnmapViewOfFile,  CreateFileMappingA,  MapViewOfFile,  DeleteCriticalSection,  FreeEnvironmentStringsA,  GetFileTime,  FileTimeToLocalFileTime,  FileTimeToDosDateTime,  GetSystemInfo,  GetLastError,  GetCurrentProcess,  GetModuleHandleA,  VirtualFree,  VirtualAlloc,  InitializeCriticalSection,  GlobalAlloc,  GetThreadPriority,  SetUnhandledExceptionFilter,  CreateThread,  GetCommandLineA,  VirtualQuery,  VirtualProtect,  EnterCriticalSection,  LeaveCriticalSection,  GetCurrentThreadId,  OutputDebugStringA,  ResetEvent,  CreateEventA,  GetFullPathNameA,  FindFirstFileA,  FindNextFileA,  FindClose;beginend.  This is the test program code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program TesteoDll; {$mode objfpc}{$H+}{$calling stdcall} uses  Windows; var  myhandle : THandle;  mydata : TWIN32FindDataA; function FindFirstFile(lpFileName: LPCSTR; var lpFindFileData: TWIN32FindDataA): THandle; external 'sortfile' name 'FindFirstFileA';function FindNextFile(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; external 'sortfile' name 'FindNextFileA';function FindClose(hFindFile : THandle): BOOL; external 'sortfile' name 'FindClose'; begin  myhandle := FindFirstFile('*.ccx',mydata);   while (myhandle <> INVALID_HANDLE_VALUE) do begin    writeln(mydata.cFileName);    if (FindNextFile(myhandle,mydata) = FALSE) then begin      break;    end;  end;  FindClose(myhandle);end.  Sure I made some mistake ---------------------------------------------------------------------- 440bx · 2019-04-10 14:14 Quote from: Mike_HDF on April 10, 2019, 03:36:22 pmI'm not that good, I'm still learning about assembly, sorry  For what you're trying to do that will be a significant handicap.  Google "iczelion tutorials" that will be a good place to start.  For a programmer Assembler is like salt to a chef.  Quote from: Mike_HDF on April 10, 2019, 03:36:22 pmI found something weird: When I leave the default option for OS and CPU (then it compiles a 64 bits executable), the variable myhandle of THandle type says it's a QWord. When I choose Win32 and i386 it says it's a LongWord, I'm doing something wrong with the project configuration? That's completely normal.  In 32bit, handles are 32bit wide, in 64bit they are 64bit wide.  The Lazarus/FPC are using the unit versions that have the correct size for the compile target.  A nice feature. Quote from: Mike_HDF on April 10, 2019, 03:36:22 pmI defined the functions with stdcall inside DLL. Tried what you suggested too but it fails. From what I see, I get the impression you copied some headers into your Sortfile library.  That one also needs {$calling stdcall} otherwise the API calls it makes will leave the stack unbalanced.  As a rule of thumb, use your editor to change all occurrences of "external" to "stdcall; external" that's the safe way of declaring an API header (unless, of course, it is a cdecl function then you don't want "stdcall" in there.) Quote from: Mike_HDF on April 10, 2019, 03:36:22 pmThis is a bigger excerpt of the DLL: That helped.  Sortfile, as mentioned above, seems to be missing the "stdcall" calling convention. Quote from: Mike_HDF on April 10, 2019, 03:36:22 pmSure I made some mistake  That is a reasonable (and likely correct) conclusion ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 14:44 Quote from: 440bx on April 10, 2019, 04:14:50 pmFor what you're trying to do that will be a significant handicap.  Google "iczelion tutorials" that will be a good place to start.  For a programmer Assembler is like salt to a chef.  I tried something easy to start and see what I got  , not very experienced with free pascal neither, is obvious. Thanks for the info, I'm searching those tutorials. Quote from: 440bx on April 10, 2019, 04:14:50 pmThat's completely normal.  In 32bit, handles are 32bit wide, in 64bit they are 64bit wide.  The Lazarus/FPC are using the unit versions that have the correct size for the compile target.  A nice feature. You're right. Don't know what I've been thinking about. Quote from: 440bx on April 10, 2019, 04:14:50 pmFrom what I see, I get the impression you copied some headers into your Sortfile library.  That one also needs {$calling stdcall} otherwise the API calls it makes will leave the stack unbalanced.  As a rule of thumb, use your editor to change all occurrences of "external" to "stdcall; external" that's the safe way of declaring an API header (unless, of course, it is a cdecl function then you don't want "stdcall" in there.) That worked! Quote from: 440bx on April 10, 2019, 04:14:50 pmThat is a reasonable (and likely correct) conclusion You're right  . Thanks a lot to everyone! ---------------------------------------------------------------------- engkin · 2019-04-10 14:51 Inside your dll you also need {$calling stdcall} You do not need to import functions from Kernel32/User32. They are already imported for you inside unit Windows. Just use the proper way to refer to them, like: Windows.FindFirstFile. ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 15:14 Quote from: engkin on April 10, 2019, 04:51:50 pmInside your dll you also need {$calling stdcall} I did, thanks! Quote from: engkin on April 10, 2019, 04:51:50 pmYou do not need to import functions from Kernel32/User32. They are already imported for you inside unit Windows. Just use the proper way to refer to them, like: Windows.FindFirstFile. I need the kernel32 functions to export them, that's the trick so the game works as espected (need to test with the modified Find* functions, can wait!). ---------------------------------------------------------------------- 440bx · 2019-04-10 16:00 Quote from: Mike_HDF on April 10, 2019, 05:14:17 pmI need the kernel32 functions to export them, that's the trick so the game works as espected (need to test with the modified Find* functions, can wait!). Not only you shouldn't need to do that but, that won't cause the exe program to call the functions in your dll. In the exe file, there is an import descriptor (one or more per import library) and, the descriptor tells the loader the name of the library where to find the functions.  In the case of kernel32, the descriptor says "kernel32.dll", the loader will completely ignore your dll.   The only time your technique works is when your dll is a replacement for the original dll.  Somewhere you have to patch the exe's Import Address Table (IAT) so that the IAT has the address of YOUR functions in your dll instead of the address of those functions in kernel32. I don't see anywhere in your code that you are patching the exe's IAT. ---------------------------------------------------------------------- Mike_HDF · 2019-04-10 17:50 Quote from: 440bx on April 10, 2019, 06:00:06 pmIn the exe file, there is an import descriptor (one or more per import library) and, the descriptor tells the loader the name of the library where to find the functions.  In the case of kernel32, the descriptor says "kernel32.dll", the loader will completely ignore your dll. Yes, I modified the exe so kernel32.dll is now sortfile.dll. For what you told me I know is not the best practice, but that's the way the community has been upgrading the game. The game is running with the new DLL and I verified that it loads the files in alphabetical order. The hack is working well! Thanks for your help. ---------------------------------------------------------------------- 440bx · 2019-04-10 18:53 Quote from: Mike_HDF on April 10, 2019, 07:50:38 pmYes, I modified the exe so kernel32.dll is now sortfile.dll. For what you told me I know is not the best practice, but that's the way the community has been upgrading the game. I wholeheartedly agree that isn't the "best practice" but, I like it.  My concern with that method is that Windows is very protective of its system dlls and when you modify them, it restores the original from copies it keeps at hand (I know because I've used that method too... ) edit: never mind, you modified the exe not kernel32. Quote from: Mike_HDF on April 10, 2019, 07:50:38 pmThe game is running with the new DLL and I verified that it loads the files in alphabetical order. The hack is working well! Thanks for your help. You're welcome.  I'm pleased you got it working.