1+ #define _CRT_SECURE_NO_WARNINGS
2+
13#include <Windows.h>
24
5+ #include <stdbool.h>
36#include <stdio.h>
47#include <string.h>
58#include <time.h>
69
7- void Log (const char * fmt , ...) {
8- va_list va ;
9- va_start (va , fmt );
10+ struct IsaacOptions {
11+ // Repentogon options
12+ int updates ;
13+ int console ;
14+
15+ // Game options
16+ int lua_debug ;
17+ int level_stage ;
18+ int stage_type ;
19+ const char * lua_heap_size ;
20+ };
1021
11- FILE * f = fopen ("injector.log" , "a" );
12- if (!f ) {
13- f = stderr ;
22+ /* Perform the early setup for the injection: create the Isaac process,
23+ * allocate memory for the remote thread function etc.
24+ *
25+ * No extra thread is created in the process. ImGui should be initialized
26+ * afterwards to setup the injector, and then the remote thread can be
27+ * created.
28+ *
29+ * Return true of the initialization was sucessful, false otherwise.
30+ */
31+ static int FirstStageInit (struct IsaacOptions const * options , HANDLE * process , void * * page , size_t * functionOffset , PROCESS_INFORMATION * processInfo );
32+
33+ static void Log (const char * fmt , ...);
34+
35+ static void GenerateCLI (const struct IsaacOptions * options , char cli [256 ]);
36+
37+ void GenerateCLI (const struct IsaacOptions * options , char cli [256 ]) {
38+ memset (cli , 0 , sizeof (cli ));
39+ if (options -> console ) {
40+ strcat (cli , "--console " );
1441 }
1542
16- char buffer [4096 ];
17- time_t now = time (NULL );
18- struct tm * tm = localtime (& now );
19- strftime (buffer , 4095 , "%Y-%m-%d %H:%M:%S" , tm );
20- fprintf (f , "[%s] " , buffer );
21- vfprintf (f , fmt , va );
22- va_end (va );
23- }
43+ if (!options -> updates ) {
44+ strcat (cli , "--skipupdates " );
45+ }
2446
25- int main () {
26- {
27- FILE * f = fopen ("injector.log" , "w" );
28- if (f ) {
29- fclose (f );
30- }
47+ if (options -> lua_debug ) {
48+ strcat (cli , "--luadebug " );
3149 }
3250
33- Log ("Starting injector\n" );
51+ if (options -> level_stage ) {
52+ strcat (cli , "--set-stage=" );
53+ char buffer [13 ]; // 11 chars for a max int (including sign) + 1 char for space + 1 char for '\0'
54+ sprintf (buffer , "%d " , options -> level_stage );
55+ strcat (cli , buffer );
56+ }
57+
58+ if (options -> stage_type ) {
59+ strcat (cli , "--set-stage-type=" );
60+ char buffer [13 ]; // 11 chars for a max int (including sign) + 1 char for space + 1 char for '\0'
61+ sprintf (buffer , "%d " , options -> stage_type );
62+ strcat (cli , buffer );
63+ }
64+
65+ if (options -> lua_heap_size ) {
66+ strcat (cli , "--luaheapsize=" );
67+ strcat (cli , options -> lua_heap_size );
68+ }
69+ }
3470
71+ DWORD CreateIsaac (struct IsaacOptions const * options , PROCESS_INFORMATION * processInfo ) {
3572 STARTUPINFOA startupInfo ;
3673 memset (& startupInfo , 0 , sizeof (startupInfo ));
3774
38- PROCESS_INFORMATION processInfo ;
39- memset (& processInfo , 0 , sizeof (processInfo ));
75+ memset (processInfo , 0 , sizeof (* processInfo ));
76+
77+ char cli [256 ];
78+ GenerateCLI (options , cli );
4079
41- DWORD result = CreateProcess ("isaac-ng.exe" , NULL , NULL , NULL , FALSE, CREATE_SUSPENDED , NULL , NULL , & startupInfo , & processInfo );
80+ DWORD result = CreateProcess ("isaac-ng.exe" , cli , NULL , NULL , FALSE, CREATE_SUSPENDED , NULL , NULL , & startupInfo , processInfo );
4281 if (result == 0 ) {
4382 Log ("Failed to create process: %d\n" , GetLastError ());
4483 return -1 ;
4584 }
4685 else {
47- Log ("Started isaac-ng.exe in suspended state, processID = %d\n" , processInfo . dwProcessId );
86+ Log ("Started isaac-ng.exe in suspended state, processID = %d\n" , processInfo -> dwProcessId );
4887 }
4988
50- HANDLE process = OpenProcess (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ ,
51- FALSE, processInfo .dwProcessId );
52- if (!process ) {
53- Log ("Failed to open process: %d\n" , GetLastError ());
54- return -1 ;
55- }
56- else {
57- Log ("Acquired handle to isaac-ng.exe, process ID = %d\n" , processInfo .dwProcessId );
58- }
89+ return result ;
90+ }
5991
92+ int UpdateMemory (HANDLE process , PROCESS_INFORMATION const * processInfo , void * * page , size_t * functionOffset ) {
6093 void * remotePage = VirtualAllocEx (process , NULL , 4096 , MEM_COMMIT , PAGE_EXECUTE_READWRITE );
6194 if (!remotePage ) {
6295 Log ("Failed to allocate memory in isaac-ng.exe to load the dsound DLL: %d\n" , GetLastError ());
@@ -66,7 +99,7 @@ int main() {
6699 Log ("Allocated memory for remote thread at %p\n" , remotePage );
67100 }
68101
69- size_t bytesWritten = 0 ;
102+ SIZE_T bytesWritten = 0 ;
70103 char zeroBuffer [4096 ];
71104 memset (zeroBuffer , 0 , sizeof (zeroBuffer ));
72105 WriteProcessMemory (process , remotePage , zeroBuffer , 4096 , & bytesWritten );
@@ -110,15 +143,17 @@ int main() {
110143 // 0x16 (0x15 is a '\0')
111144 WriteProcessMemory (process , (char * )remotePage + offset , functionName , strlen (functionName ), & bytesWritten );
112145 offset += (bytesWritten + 1 );
113- size_t functionOffset = offset ;
146+
147+ * functionOffset = offset ;
148+
114149 /* 0x21 (0x20 is a '\0')
115150 * Call LoadLibraryA in the remote thread.
116151 * The thread will push the name of the DLL from its stack.
117152 * It will then call LoadLibraryA.
118- *
153+ *
119154 * This function is a THREAD_START_ROUTINE, with the following signature :
120155 * DWORD WINAPI(LPVOID);
121- *
156+ *
122157 * WINAPI is __stdcall: arguments are pushed in reverse order on the stack and callee cleans the stack.
123158 */
124159 char hook [128 ] = {
@@ -148,7 +183,42 @@ int main() {
148183 };
149184 WriteProcessMemory (process , (char * )remotePage + offset , hook , 128 , & bytesWritten );
150185
151- HANDLE remoteThread = CreateRemoteThread (process , NULL , 0 , (char * )remotePage + functionOffset , remotePage , 0 , NULL );
186+ * page = remotePage ;
187+ return 0 ;
188+ }
189+
190+ int FirstStageInit (struct IsaacOptions const * options , HANDLE * outProcess , void * * page , size_t * functionOffset , PROCESS_INFORMATION * processInfo ) {
191+ {
192+ FILE * f = fopen ("injector.log" , "w" );
193+ if (f ) {
194+ fclose (f );
195+ }
196+ }
197+
198+ Log ("Starting injector\n" );
199+ DWORD processId = CreateIsaac (options , processInfo );
200+
201+ HANDLE process = OpenProcess (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ ,
202+ FALSE, processInfo -> dwProcessId );
203+ if (!process ) {
204+ Log ("Failed to open process: %d\n" , GetLastError ());
205+ return -1 ;
206+ }
207+ else {
208+ Log ("Acquired handle to isaac-ng.exe, process ID = %d\n" , processInfo -> dwProcessId );
209+ }
210+
211+ if (UpdateMemory (process , processInfo , page , functionOffset )) {
212+ return -1 ;
213+ }
214+
215+ * outProcess = process ;
216+
217+ return 0 ;
218+ }
219+
220+ int CreateAndWait (HANDLE process , void * remotePage , size_t functionOffset ) {
221+ HANDLE remoteThread = CreateRemoteThread (process , NULL , 0 , (LPTHREAD_START_ROUTINE )((char * )remotePage + functionOffset ), remotePage , 0 , NULL );
152222 if (!remoteThread ) {
153223 Log ("Error while creating remote thread: %d\n" , GetLastError ());
154224 return -1 ;
@@ -158,26 +228,69 @@ int main() {
158228 }
159229
160230 Log ("Waiting for remote thread to complete\n" );
161- result = WaitForSingleObject (remoteThread , 60 * 1000 );
231+ DWORD result = WaitForSingleObject (remoteThread , 60 * 1000 );
162232 switch (result ) {
163233 case WAIT_OBJECT_0 :
164234 Log ("RemoteThread completed\n" );
165235 break ;
166236
167237 case WAIT_ABANDONED :
168238 Log ("This shouldn't happened: RemoteThread returned WAIT_ABANDONNED\n" );
169- break ;
239+ return -1 ;
170240
171241 case WAIT_TIMEOUT :
172242 Log ("RemoteThread timed out\n" );
173- break ;
243+ return -1 ;
174244
175245 case WAIT_FAILED :
176246 Log ("WaitForSingleObject on RemoteThread failed: %d\n" , GetLastError ());
177- break ;
247+ return -1 ;
178248 }
179249
180- result = ResumeThread (processInfo .hThread );
250+ return 0 ;
251+ }
252+
253+ void Log (const char * fmt , ...) {
254+ va_list va ;
255+ va_start (va , fmt );
256+
257+ FILE * f = fopen ("injector.log" , "a" );
258+ if (!f ) {
259+ f = stderr ;
260+ }
261+
262+ char buffer [4096 ];
263+ time_t now = time (NULL );
264+ struct tm * tm = localtime (& now );
265+ strftime (buffer , 4095 , "%Y-%m-%d %H:%M:%S" , tm );
266+ fprintf (f , "[%s] " , buffer );
267+ vfprintf (f , fmt , va );
268+ va_end (va );
269+ }
270+
271+ int __declspec(dllexport ) InjectIsaac (int updates , int console , int lua_debug , int level_stage , int stage_type , const char * lua_heap_size ) {
272+ HANDLE process ;
273+ void * remotePage ;
274+ size_t functionOffset ;
275+ PROCESS_INFORMATION processInfo ;
276+
277+ struct IsaacOptions options ;
278+ options .updates = updates ;
279+ options .console = console ;
280+ options .lua_debug = lua_debug ;
281+ options .level_stage = level_stage ;
282+ options .stage_type = stage_type ;
283+ options .lua_heap_size = lua_heap_size ;
284+
285+ if (FirstStageInit (& options , & process , & remotePage , & functionOffset , & processInfo )) {
286+ return -1 ;
287+ }
288+
289+ if (CreateAndWait (process , remotePage , functionOffset )) {
290+ return -1 ;
291+ }
292+
293+ DWORD result = ResumeThread (processInfo .hThread );
181294 if (result == -1 ) {
182295 Log ("Failed to resume isaac-ng.exe main thread: %d\n" , GetLastError ());
183296 return -1 ;
@@ -193,5 +306,10 @@ int main() {
193306 CloseHandle (processInfo .hProcess );
194307 CloseHandle (processInfo .hThread );
195308
309+ return 0 ;
310+ }
311+
312+ int main (int argc , char * * argv ) {
313+ InjectIsaac (1 , 1 , 0 , 0 , 0 , NULL );
196314 return 0 ;
197315}
0 commit comments