dfhack/tools/playground/hexsearch2.c

1356 lines
40 KiB
C

/*
* Author: Silas Dunsmore aka 0x517A5D vim:ts=4:sw=4
*
* Released under the MIT X11 license; feel free to use some or all of this
* code, as long as you include the copyright and license statement (below)
* in all copies of the source code. In fact, I truly encourage reuse.
*
* If you do use large portions of this code, I suggest but do not require
* that you keep this code in a seperate file (such as this hexsearch.c file)
* so that it is clear that the terms of the license do not also apply to
* your code.
*
* Should you make fundamental changes, or bugfixes, to this code, I would
* appreciate it if you would give me a copy of your changes.
*
*
* Be advised that I use several advanced idioms of the C language:
* macro expansion, stringification, and variable argument functions.
* You do not need to understand them. Usage should be obvious.
*
*
* Lots of logging output is sent to OutputDebugString().
* The Sysinternals' DebugView program is very useful in monitering this.
*
*
* Copyright (C) 2007-2008 Silas Dunsmore
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* See also: http://www.opensource.org/licenses/mit-license.php
* and http://en.wikipedia.org/wiki/MIT_License
*/
#include <stdarg.h> // va_list and friends
#include <stdio.h> // vsnprintf()
#include <stdlib.h> // atexit()
#include <signal.h>
#define WINVER 0x0500 // OpenThread()
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.h>
#include "hexsearch2.h"
#define SKIPME 0xFEDCBA98 // token for internal use only.
// exported globals
HANDLE df_h_process, df_h_thread;
DWORD df_pid, df_main_win_tid;
DWORD here[16];
DWORD target[16];
char *errormessage;
DWORD df_memory_base, df_memory_start, df_memory_end;
// local globals
static BOOL change_page_permissions;
static BOOL suspended;
static BYTE *copy_of_df_memory;
static int nexthere;
static int nexttarget;
static DWORD searchmemstart, searchmemend;
#define dump(x) d_printf("%-32s == %08X\n", #x, (x));
#define dumps(x) d_printf("%-32s == '%s'\n", #x, (x));
// ============================================================================
// send info to KERNEL32:OutputDebugString(), useful for non-console programs.
// you can watch this with SysInternals' DebugView.
// http://www.microsoft.com/technet/sysinternals/Miscellaneous/DebugView.mspx
//
void d_printf(const char *format, ...)
{
va_list va;
char debugstring[4096]; // debug strings can be up to 4K.
va_start(va, format);
_vsnprintf(debugstring, sizeof(debugstring) - 2, format, va);
va_end(va);
OutputDebugString(debugstring);
}
// ============================================================================
BOOL isvalidaddress(DWORD address)
{
MEMORY_BASIC_INFORMATION mbi;
return (VirtualQueryEx(df_h_process, (void *)address, &mbi, sizeof(mbi))
== sizeof(mbi) && mbi.State == MEM_COMMIT);
}
// ----------------------------------------------------------------------------
BOOL peekarb(DWORD address, OUT void *data, DWORD len)
{
BOOL ok = FALSE;
DWORD succ = 0;
DWORD OldProtect;
MEMORY_BASIC_INFORMATION mbi = {0, 0, 0, 0, 0, 0, 0};
errormessage = "";
// do {} while(0) is a construct that lets you abort a piece of code
// in the middle without using gotos.
do
{
if ((succ = VirtualQueryEx(df_h_process, (BYTE *)address + len - 1,
&mbi, sizeof(mbi))) != sizeof(mbi))
{
dump(address + len - 1);
dump(succ);
errormessage = "peekarb(): VirtualQueryEx() on end address failed";
break;
}
if (mbi.State != MEM_COMMIT)
{
dump(mbi.State);
errormessage = "peekarb(): VirtualQueryEx() says end address "
"is not MEM_COMMIT";
break;
}
if ((succ = VirtualQueryEx(df_h_process, (void *)address, &mbi,
sizeof(mbi))) != sizeof(mbi))
{
dump(address);
dump(succ);
errormessage ="peekarb(): VirtualQueryEx() on start address failed";
break;
}
if (mbi.State != MEM_COMMIT)
{
dump(mbi.State);
errormessage = "peekarb(): VirtualQueryEx() says start address is "
"not MEM_COMMIT";
break;
}
if (change_page_permissions)
{
if (!VirtualProtectEx(df_h_process, mbi.AllocationBase,
mbi.RegionSize, PAGE_READWRITE, &OldProtect))
{
errormessage = "peekarb(): VirtualProtectEx() failed";
break;
}
}
if (!ReadProcessMemory(df_h_process, (void *)address, data, len, &succ))
{
errormessage = "peekarb(): ReadProcessMemory() failed";
// note that we do NOT break here, as we want to restore the
// page protection.
}
else if (len != succ)
{
errormessage = "peekarb(): ReadProcessMemory() returned "
"partial read";
}
else ok = TRUE;
if (change_page_permissions)
{
if (!VirtualProtectEx(df_h_process, mbi.AllocationBase,
mbi.RegionSize, OldProtect, &OldProtect))
{
errormessage = "peekarb(): undo VirtualProtectEx() failed";
break;
}
}
} while (0);
if (errormessage != NULL && strlen(errormessage) != 0)
{
d_printf("%s\n", errormessage);
dump(address);
//dump(len);
//dump(succ);
//dump(mbi.AllocationBase);
}
return(ok);
}
// ----------------------------------------------------------------------------
BYTE peekb(DWORD address)
{
BYTE data;
return(peekarb(address, &data, sizeof(data)) ? data : 0);
// pop quiz: why don't we set errormessage?
}
// ----------------------------------------------------------------------------
WORD peekw(DWORD address)
{
WORD data;
return(peekarb(address, &data, sizeof(data)) ? data : 0);
}
// ----------------------------------------------------------------------------
DWORD peekd(DWORD address)
{
DWORD data;
return(peekarb(address, &data, sizeof(data)) ? data : 0);
}
// ----------------------------------------------------------------------------
char *peekstr(DWORD address, OUT char *data, DWORD maxlen)
{
BYTE c;
int i = 0;
data[0] = '\0';
if (!isvalidaddress(address)) return(data);
while (--maxlen && (c = peekb(address++)) >= ' ' && c <= '~')
{
if (!isvalidaddress(address)) return(data);
data[i++] = c;
data[i] = '\0';
}
// for convenience
return(data);
}
// ----------------------------------------------------------------------------
char *peekwstr(DWORD address, OUT char *data, DWORD maxlen)
{
BYTE c;
int i = 0;
data[0] = '\0';
if (!isvalidaddress(address)) return(data);
while (--maxlen && (c = peekb(address++)) >= ' ' && c <= '~' && peekb(address++) == 0)
{
if (!isvalidaddress(address))
{
return(data);
}
data[i++] = c;
data[i] = '\0';
}
// for convenience
return(data);
}
// ----------------------------------------------------------------------------
BOOL pokearb(DWORD address, const void *data, DWORD len)
{
BOOL ok = FALSE;
DWORD succ = 0;
DWORD OldProtect;
MEMORY_BASIC_INFORMATION mbi;
errormessage = "";
do
{
if (!isvalidaddress(address))
{
errormessage = "pokearb() failed: invalid address";
break;
}
if (!isvalidaddress(address + len - 1))
{
errormessage = "pokearb() failed: invalid end address";
break;
}
if (change_page_permissions)
{
if (VirtualQueryEx(df_h_process, (void *)address, &mbi, sizeof(mbi)) != sizeof(mbi))
{
errormessage = "pokearb(): VirtualQueryEx() failed";
break;
}
if (!VirtualProtectEx(df_h_process, mbi.AllocationBase, mbi.RegionSize, PAGE_READWRITE, &OldProtect))
{
errormessage = "pokearb(): VirtualProtectEx() failed";
break;
}
}
if (!WriteProcessMemory(df_h_process, (void *)address, data, len, &succ))
{
errormessage = "pokearb(): WriteProcessMemory() failed";
// note that we do NOT break here, as we want to restore the
// page protection.
}
else if (len != succ)
{
errormessage = "pokearb(): WriteProcessMemory() did partial write";
}
else
{
ok = TRUE;
}
if (change_page_permissions)
{
if (!VirtualProtectEx(df_h_process, mbi.AllocationBase, mbi.RegionSize, OldProtect, &OldProtect))
{
errormessage = "pokearb(): undo VirtualProtectEx() failed";
break;
}
}
} while (0);
if (errormessage != NULL && strlen(errormessage) != 0)
{
d_printf("%s\n", errormessage);
dump(address);
//dump(len);
//dump(succ);
//dump(mbi.AllocationBase);
}
return(ok);
}
// ----------------------------------------------------------------------------
BOOL pokeb(DWORD address, BYTE data)
{
return(pokearb(address, &data, sizeof(data)));
}
// ----------------------------------------------------------------------------
BOOL pokew(DWORD address, WORD data)
{
return(pokearb(address, &data, sizeof(data)));
}
// ----------------------------------------------------------------------------
BOOL poked(DWORD address, DWORD data)
{
return(pokearb(address, &data, sizeof(data)));
}
// ----------------------------------------------------------------------------
BOOL pokestr(DWORD address, const BYTE *data)
{
// can't include a "\x00" in the string, obviously.
return(pokearb(address, data, strlen((const char *)data)));
}
// ----------------------------------------------------------------------------
// helper function for hexsearch. recursive, with backtracking.
// this checks if a particular memory offset matches the given pattern.
// returns location of start of match (in the cached copy of DF memory).
// returns NULL on mismatch.
//
// TODO: there is a harmless bug in the recursion related to the very first
// recursive call, probably on each level of recursion.
static BYTE *hexsearch_match2(BYTE *p, DWORD token1, va_list va)
{
static DWORD recursion_level = 0;
DWORD tokensprocessed = 0;
DWORD token = 0x4DECADE5, b1, b2, lo, hi;
BYTE *retval = p;
BOOL ok = FALSE, lookedahead;
int savenexthere = nexthere;
int savenexttarget = nexttarget;
// TODO token is being used without being inited on recursion.
//if (recursion_level) dump(recursion_level);
//if (recursion_level) dump(p);
//if (recursion_level) dump(tokensprocessed);
if (token1 != SKIPME)
{
lookedahead = TRUE;
token = token1;
tokensprocessed = 1;
}
while (1)
{
// if the previous argument looked ahead, token is already set.
// peekahead is currently unused.
if (!lookedahead)
{
token = va_arg(va, unsigned int);
tokensprocessed++;
}
lookedahead = FALSE;
// exact-match a byte, advance.
if (token <= 0xFF)
{
if (token != *p++) break;
}
// the remaining tokens (the metas) ought to be a switch.
// but that would make it hard to break out of the while(1).
// if we hit an EOL, the match succeeded.
else if (token == EOL)
{
ok = TRUE;
break;
}
// match any byte, advance.
else if (token == ANYBYTE)
{
p++;
}
// return the address of the next matching byte instead of the
// address of the start of the pattern. don't advance.
else if (token == HERE)
{
retval = p;
here[nexthere++] = (DWORD)p; // needs postprocessing.
}
// accept either of the next two parameters as a match. advance.
// note that this does not count as peeking.
else if (token == EITHER)
{
if ((b1 = va_arg(va, unsigned int)) > 0xFF)
{
d_printf("EITHER: not followed by a legal token (byte1): %08X\n", b1);
break;
}
tokensprocessed++;
if ((b2 = va_arg(va, unsigned int)) > 0xFF)
{
d_printf("EITHER: not followed by a legal token (byte2): %08X\n", b2);
break;
}
tokensprocessed++;
if (!(*p == b1 || *p == b2)) break;
p++;
}
#ifdef FF_OR_00 //DEPRECATED
// accept either 0x00 or 0xFF. advance.
else if (token == FF_OR_00)
{
if (!(*p == 0x00 || *p == 0xFF))
{
break;
}
p++;
}
#endif
// set low value for range comparison. don't advance. DEPRECATED.
else if (token == RANGE_LO)
{
if ((lo = va_arg(va, unsigned int)) > 0xFF)
{
d_printf("RANGE_LO: not followed by a legal token: %08X\n", lo);
break;
}
tokensprocessed++;
// Q: peek here to ensure next token is RANGE_HI ?
}
// set high value for range comparison, and do comparison. advance.
// DEPRECATED.
else if (token == RANGE_HI)
{
if ((hi = va_arg(va, unsigned int)) > 0xFF) {
d_printf("RANGE_HI: not followed by a legal token: %08X\n", hi);
break;
}
if (*p < lo || *p > hi)
{
break;
}
p++;
tokensprocessed++;
}
// do a byte-size range comparison
else if (token == BYTERANGE)
{
if ((lo = va_arg(va, unsigned int)) > 0xFF)
{
d_printf("BYTERANGE: not followed by a legal token: %08X\n", lo);
break;
}
tokensprocessed++;
if ((hi = va_arg(va, unsigned int)) > 0xFF)
{
d_printf("BYTERANGE: not followed by a legal token: %08X\n", hi);
break;
}
if (*p < lo || *p > hi)
{
break;
}
p++;
tokensprocessed++;
}
// do a dword-size range comparison
else if (token == DWORDRANGE)
{
lo = va_arg(va, unsigned int);
tokensprocessed++;
hi = va_arg(va, unsigned int);
if (*(DWORD *)p < lo || *(DWORD *)p > hi)
{
break;
}
p++;
tokensprocessed++;
}
// this is the fun one. this is where we recurse.
else if (token == SKIP_UP_TO)
{
DWORD len;
BYTE * subretval;
len = va_arg(va, unsigned int) + 1;
tokensprocessed++;
while (len)
{
// um. This is a kludge. It ought to work to not set any
// heres or targets if we're in a recursion. (not tested)
int savenexthere;
int savenexttarget;
// I think it's not technically legal to copy va_lists;
// but it should work on any stack-based machine, which is
// everything except old Crays and weird dataflow processors.
savenexthere = nexthere;
savenexttarget = nexttarget;
//dump(tokensprocessed);
//dump(recursion_level);
//dumps("-->");
recursion_level++;
subretval = hexsearch_match2(p, SKIPME, va);
recursion_level--;
//dumps("<--");
//dump(recursion_level);
//dump(tokensprocessed);
nexthere = savenexthere;
nexttarget = savenexttarget;
if (subretval != NULL) {
// okay, we now know that the bytes starting at p
// match the remainder of the pattern.
// Nonetheless, for ease of programming, we will
// go through the motions instead of trying to
// early-out. (Basically done for HERE tokens.)
break;
}
p++;
len--;
}
if (subretval != NULL)
{
continue;
}
// no match within nn bytes, abort.
break;
}
// exact-match a dword. advance 4.
else if (token == DWORD_)
{
DWORD d = va_arg(va, unsigned int);
if (*(DWORD *)p != d) break;
p += 4;
tokensprocessed++;
}
// match any dword. advance 4.
else if (token == ANYDWORD)
{
p += 4;
}
// match any legal address in
else if (token == ADDRESS)
{
// program text. advance 4.
if (*(DWORD *)p < df_memory_start)
{
break;
}
if (*(DWORD *)p > df_memory_end)
{
break;
}
p += 4;
}
// match any call. advance 5.
else if (token == CALL)
{
if (*p++ != 0xE8)
{
break;
}
target[nexttarget++] = *(DWORD *)p + (DWORD)p + 4;
#if 0
if (*(DWORD *)p > DF_CODE_SIZE
&& *(DWORD *)p < (DWORD)-DF_CODE_SIZE)
break;
#endif
p += 4;
}
// match any short or near jump.
else if (token == JUMP)
{
// advance 2 or 5 respectively.
if (*p == 0xEB)
{
target[nexttarget++] = *(signed char *)(p+1) + (DWORD)p + 1;
p += 2;
continue;
}
if (*p++ != 0xE9)
{
break;
}
target[nexttarget++] = *(DWORD *)p + (DWORD)p + 4;
#if 0
if (*(DWORD *)p > DF_CODE_SIZE
&& *(DWORD *)p < (DWORD)-DF_CODE_SIZE)
break;
#endif
p += 4;
}
// match a JZ instruction
else if (token == JZ)
{
if (*p == 0x74)
{
target[nexttarget++] = *(signed char *)(p+1) + (DWORD)p + 2;
p += 2;
continue;
}
else if (*p == 0x0F && *(p+1) == 0x84
&& (*(p+4) == 0x00 || *(p+4) == 0xFF) // assume dist < 64k
&& (*(p+5) == 0x00 || *(p+5) == 0xFF))
{
target[nexttarget++] = *(DWORD *)(p+2) + (DWORD)p + 6;
p += 6;
continue;
}
else break;
}
// match a JNZ instruction
else if (token == JNZ)
{
if (*p == 0x75) {
target[nexttarget++] = *(signed char *)(p+1) + (DWORD)p + 2;
p += 2;
continue;
}
else if (*p == 0x0F && *(p+1) == 0x85
&& (*(p+4) == 0x00 || *(p+4) == 0xFF) // assume dist < 64k
&& (*(p+5) == 0x00 || *(p+5) == 0xFF)
) {
target[nexttarget++] = *(DWORD *)(p+2) + (DWORD)p + 6;
p += 6;
continue;
}
else break;
}
// match any conditional jump
else if (token == JCC)
{
if (*p >= 0x70 && *p <= 0x7F)
{
target[nexttarget++] = *(signed char *)(p+1) + (DWORD)p + 2;
p += 2;
continue;
}
else if (*p == 0x0F && *(p+1) >= 0x80 && *(p+1) <= 0x8F
&& (*(p+4) == 0x00 || *(p+4) == 0xFF) // assume dist < 64k
&& (*(p+5) == 0x00 || *(p+5) == 0xFF))
{
target[nexttarget++] = *(DWORD *)(p+2) + (DWORD)p + 6;
p += 6;
continue;
}
else break;
}
// unknown token, abort
else
{
d_printf("unknown token: %08X\n", token);
dump(p);
dump(recursion_level);
break;
} // end of huge if-else
} // end of while(1)
if (!ok)
{
retval = NULL;
nexthere = savenexthere;
nexttarget = savenexttarget;
}
return (retval);
}
// ============================================================================
// The name has changed because the API HAS CHANGED!
//
// Now instead of returning HERE, it always returns the start of the match.
//
// However, there is an array, here[], that is filled with the locations of
// the HERE tokens.
// (Starting at 1. here[0] is a copy of the start of the match.)
//
// The here[] array, starting at 1, is filled with the locations of the
// HERE token.
// here[0] is a copy of the start of the match.
//
// Also, the target[] array, starting at 1, is filled with the target
// addresses of all CALL, JMP, JZ, JNZ, and JCC tokens.
//
// Finally, you no longer pass it a search length. Instead, each set of
// search terms must end with an EOL.
//
//
//
// Okay, I admit this one is complicated. Treat it as a black box.
//
// Search Dwarf Fortress's code and initialized data segments for a pattern.
// (Does not search stack, heap, or thread-local memory.)
//
// Parameters: any number of search tokens, all unsigned ints.
// The last token must be EOL.
//
// 0x00 - 0xFF: Match this byte.
// EOL: End-of-list. The match succeeds when this token is reached.
// ANYBYTE: Match any byte.
// DWORD_: Followed by a dword. Exact-match the dword.
// Equivalent to 4 match-this-byte tokens.
// ANYDWORD: Match any dword. Equivalant to 4 ANYBYTEs.
// HERE: Put the current address into the here[] array.
// SKIP_UP_TO, nn: Allow up to nn bytes between the previous match
// and the next match. The next token must be a match-this-byte
// token. There is sweet, sweet backtracking.
// EITHER: Accept either of the next two tokens as a match.
// Both must be match-this-byte tokens.
// RANGE_LO, nn: Set low byte for a range comparison. DEPRECATED.
// RANGE_HI, nn: Set high byte for a range comparison, and do the
// comparison. Should immediately follow a RANGE_LO. DEPRECATED.
// BYTERANGE, nn, mm: followed by two bytes, the low and high limits
// of the range. This is the new way to do ranges.
// DWORDRANGE, nnnnnnnn, mmmmmmmm: works like BYTERANGE.
// ADDRESS: Accept any legal address in the program's text.
// DOES NOT accept pointers into heap space and such.
// CALL: Match a near call instruction to a reasonable address.
// JUMP: Match a short or near unconditional jump to a reasonable
// address.
// JZ: Match a short or long jz (jump if zero) instruction.
// JNZ: Match a short or long jnz (jump if not zero) instruction.
// JCC: Match any short or long conditional jump instruction.
// More tokens can easily be added.
//
// Returns the offset in Dwarf Fortress of the first match of the pattern.
// Also sets global variables here[] and target[].
//
// Note: starting a pattern with ANYBYTE, ANYDWORD, SKIP_UP_TO, or EOL
// is explicitly illegal.
//
//
// implementation detail: search() uses a cached copy of the memory, so
// poke()s and patch()s will not be reflected in the searches.
//
// implementation detail: Q: why don't we discard count and always use EOL?
// A: because we need a fixed parameter to start the va_list.
// (Hmm, could we grab address of a local variable, walk the stack
// until we see &hexsearch, and use that address to init va_list?)
// (No, dummy, because &hexsearch is not on the stack. The stack
// holds the return address.)
// (Could we grab the EBP register and treat it as a stack frame?)
// (Maybe, but the compiler decides if EBP really is a stack frame,
// so that would be an implementation dependency. Don't do it.)
//
// TODO: should be a way to repeat a search in case we want the second or
// eleventh occurance.
//
// TODO: allow EITHER to accept arbitrary (sets of) tokens.
// (How would that work? Have two sub-patterns, each terminating
// with EOL?)
//
DWORD hexsearch2(DWORD token1, ...)
{
DWORD size;
BYTE *nextoffset, *foundit;
DWORD token;
int i;
va_list va;
for (i = 0; i < 16; i++) here[i] = 0;
for (i = 0; i < 16; i++) target[i] = 0;
nexthere = 1;
nexttarget = 1;
if ( token1 == ANYBYTE
|| token1 == ANYDWORD
|| token1 == SKIP_UP_TO
|| token1 == HERE
|| token1 == EOL )
{
return 0;
}
dump(searchmemstart);
dump(searchmemend);
dump(df_memory_start);
dump(copy_of_df_memory);
nextoffset = copy_of_df_memory;
nextoffset += (searchmemstart - df_memory_start);
dump(nextoffset);
size = searchmemend - searchmemstart;
dump(size);
while (1)
{
// for speed, if we start with a raw byte, use a builtin function
// to skip ahead to the next actual occurance.
if (token1 <= 0xFF)
{
nextoffset = (BYTE *)memchr(nextoffset, token1,
size - (nextoffset - copy_of_df_memory));
if (nextoffset == NULL) break;
}
va_start(va, token1);
foundit = hexsearch_match2(nextoffset, token1, va);
va_end(va);
if (foundit)
{
break;
}
if ((DWORD)(++nextoffset - copy_of_df_memory - (searchmemstart - df_memory_start)) >= size)
{
break;
}
}
if (!foundit)
{
d_printf("hexsearch2(%X", token1);
i = 0;
va_start(va, token1);
do
{
d_printf(",%X", token = va_arg(va, DWORD));
} while (EOL != token && i++ < 64);
va_end(va);
if (i >= 64) d_printf("...");
d_printf(")\n");
d_printf("search failed!\n");
for (i = 0; i < 16; i++)
{
here[i] = 0;
target[i] = 0;
}
return 0;
}
here[0] = (DWORD)foundit;
for (i = 0; i < 16; i++)
{
if (i < nexthere && here[i] != 0)
{
here[i] += df_memory_start - (DWORD)copy_of_df_memory;
}
else here[i] = 0;
}
for (i = 0; i < 16; i++)
{
if (i < nexttarget && target[i] != 0)
{
target[i] += df_memory_start - (DWORD)copy_of_df_memory;
}
else target[i] = 0;
}
return df_memory_start + (foundit - copy_of_df_memory);
}
// ----------------------------------------------------------------------------
void set_hexsearch2_limits(DWORD start, DWORD end)
{
if (end < start) end = start;
searchmemstart = start >= df_memory_start ? start : df_memory_start;
searchmemend = end >= df_memory_start && end <= df_memory_end ?
end : df_memory_end;
}
// ============================================================================
// helper function for patch and verify. does the actual work.
static BOOL vpatchengine(BOOL mode, DWORD address, va_list va)
{
// mode: TRUE == patch, FALSE == verify
DWORD next = address, a, c;
// a is token, c is helper value, b is asm instruction byte to use.
BYTE b;
BOOL ok = FALSE;
while (1)
{
//if (length == 0) { ok = TRUE; break; }
//length--;
a = va_arg(va, unsigned int);
if (a == EOL)
{
ok = TRUE;
break;
}
if (a == DWORD_)
{
//if (length-- == 0) break;
c = va_arg(va, unsigned int);
if (mode ? !poked(next, c) : c != peekd(next)) break;
next += 4;
continue;
}
if (a == CALL || a == JUMP)
{
b = (a == CALL ? 0xE8 : 0xE9);
//if (length-- == 0) break;
c = va_arg(va, unsigned int) - (next + 5);
if (mode ? !pokeb(next, b) : b != peekb(next)) break;
// do NOT merge the next++ into the previous if statement.
next++;
if (mode ? !poked(next, c) : c != peekd(next)) break;
next += 4;
continue;
}
if (a == JZ || a == JNZ)
{
b = (a == JZ ? 0x84 : 0x85);
//if (length-- == 0) break;
c = va_arg(va, unsigned int) - (next + 6);
if (mode ? !pokeb(next, 0x0F) : 0x0F != peekb(next)) break;
next++;
if (mode ? !pokeb(next, b) : b != peekb(next)) break;
next++;
if (mode ? !poked(next, c) : c != peekd(next)) break;
next += 4;
continue;
}
if (a <= 0xFF)
{
if (mode ? !pokeb(next, a) : a != peekb(next)) break;
next++;
continue;
}
d_printf("vpatchengine: unsupported token: %08X\n", a);
break; // unsupported token
}
// d_printf("vpatchengine returning %d, length is %d, next is %08X\n",
// ok, length, next);
return(ok);
}
// ----------------------------------------------------------------------------
// patch() and verify() support a modified subset of hex_search() tokens:
//
// 0x00 - 0xFF: poke the byte.
// EOL: end-of-list. terminate when this token is reached.
// DWORD_: Followed by a DWORD, not a byte. Poke the DWORD.
// CALL: given an _address_; pokes near call with the proper _delta_.
// JUMP: given an _address_; pokes near jump with the proper _delta_.
// JZ: given an _address_; assembles a near (not short) jz & delta.
// JNZ: given an _address_; assembles a near jnz & delta.
//
// Particularly note that, unlike hex_search(), CALL, JUMP, JZ, and JNZ
// are followed by a dword-sized target address.
//
// Note that patch() does its own verify(), so you don't have to.
//
// TODO: doing so many individual pokes and peeks is slow. Consider
// building a copy, then doing a pokearb/peekarb.
// Make an offset in Dwarf Fortress have certain bytes.
BOOL patch2(DWORD address, ...)
{
va_list va;
BOOL ok;
va_start(va, address);
ok = vpatchengine(TRUE, address, va);
va_end(va);
va_start(va, address);
if (ok) ok = vpatchengine(FALSE, address, va);
va_end(va);
return(ok);
}
// ----------------------------------------------------------------------------
// Check that an offset in Dwarf Fortress has certain bytes.
//
// See patch() documentation.
BOOL verify2(DWORD address, ...)
{
BOOL ok;
va_list va;
va_start(va, address);
ok = vpatchengine(FALSE, address, va);
va_end(va);
return(ok);
}
// ----------------------------------------------------------------------------
// Get the exe name of the DF executable
static DWORD get_exe_base(DWORD pid)
{
HANDLE snap = INVALID_HANDLE_VALUE;
PROCESSENTRY32 process = {0};
MODULEENTRY32 module = {0};
do
{
snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE)
{
errormessage = "CreateToolhelp32Snapshot(Process) failed.";
break;
}
// Get the process exe name
process.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(snap, &process))
{
errormessage = "Process32First(snapshot) failed.";
break;
}
do
{
if (process.th32ProcessID == pid)
{
break;
}
else
{
//d_printf("Process: %d \"%.*s\"", process.th32ProcessID, MAX_PATH, process.szExeFile);
}
} while (Process32Next(snap, &process));
if (process.th32ProcessID != pid)
{
errormessage = "Process32List(snapshot) Couldn't find the target process in the snapshot?";
break;
}
d_printf("Target Process: %d \"%.*s\"", process.th32ProcessID, MAX_PATH, process.szExeFile);
CloseHandle(snap);
snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
if (snap == INVALID_HANDLE_VALUE)
{
errormessage = "CreateToolhelp32Snapshot(Modules) failed.";
break;
}
// Now find the module
module.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(snap, &module))
{
errormessage = "Module32First(snapshot) failed.";
break;
}
do
{
if (!stricmp(module.szModule, process.szExeFile))
{
break;
}
else
{
//d_printf("Module: %08X \"%.*s\" \"%.*s\"", (DWORD)(SIZE_T)module.modBaseAddr, MAX_MODULE_NAME32 + 1, module.szModule, MAX_PATH, module.szExePath);
}
} while (Module32Next(snap, &module));
if (stricmp(module.szModule, process.szExeFile))
{
errormessage = "Module32List(snapshot) Couldn't find the target module in the snapshot?";
break;
}
d_printf("Target Module: %08X \"%.*s\" \"%.*s\"", (DWORD)(SIZE_T)module.modBaseAddr, MAX_MODULE_NAME32 + 1, module.szModule, MAX_PATH, module.szExePath);
} while (0);
if (snap != INVALID_HANDLE_VALUE)
{
CloseHandle(snap);
}
if (errormessage != NULL && strlen(errormessage) != 0)
{
d_printf("%s\n", errormessage);
return 0;
}
return (DWORD)(SIZE_T)module.modBaseAddr;
}
// ============================================================================
void cleanup(void)
{
if (copy_of_df_memory != NULL)
{
GlobalFree(copy_of_df_memory);
copy_of_df_memory = NULL;
}
if (suspended)
{
if ((DWORD)-1 == ResumeThread(df_h_thread))
{
// do what? apologise?
}
else suspended = FALSE;
}
#if 0
// do this incase earlier runs crashed without executing the atexit handler.
// TODO: trap exceptions?
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
ResumeThread(df_h_thread);
#endif
if (df_h_thread != NULL)
{
CloseHandle(df_h_thread);
df_h_thread = NULL;
}
if (df_h_process != NULL)
{
CloseHandle(df_h_process);
df_h_process = NULL;
}
}
// ----------------------------------------------------------------------------
BOOL open_dwarf_fortress(void)
{
HANDLE df_main_wh;
char *state;
change_page_permissions = FALSE;
atexit(cleanup);
df_memory_base = 0x400000; // executables start here unless
// explicitly relocated.
errormessage = "";
// trigger a GPF
//*(char *)(0) = '!';
// trigger an invalid instruction (or DEP on newer processors).
// 0F 0B is the UD1 pseudo-instruction, explicitly reserved as an invalid
// instruction to trigger faults.
//asm(".fill 1, 1, 0x0F; .fill 1, 1, 0x0B");
state = "calling FindWindow()... ";
d_printf(state);
df_main_wh = FindWindow("OpenGL","Dwarf Fortress");
d_printf("done\n");
dump(df_main_wh);
if (df_main_wh == 0)
{
df_main_wh = FindWindow("SDL_app","Dwarf Fortress");
if (df_main_wh == 0)
{
errormessage = "FindWindow(Dwarf Fortress) failed.\nIs the game running?";
return FALSE;
}
}
state = "calling GetWindowThreadProcessId()... ";
d_printf(state);
df_main_win_tid = GetWindowThreadProcessId(df_main_wh, &df_pid);
d_printf("done\n");
dump(df_pid);
dump(df_main_win_tid);
if (df_main_win_tid == 0) {
errormessage =
"GetWindowThreadProcessId(Dwarf Fortress) failed.\n"
"That should not have happened!";
return FALSE;
}
state = "calling OpenProcess()... ";
d_printf(state);
df_h_process = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_VM_OPERATION | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION, FALSE, df_pid);
d_printf("done\n");
dump(df_h_process);
if (df_h_process == NULL)
{
errormessage = "OpenProcess(Dwarf Fortress) failed.\n"
"Are you the same user that started the game?";
return(FALSE);
}
state = "calling OpenThread()... ";
d_printf(state);
df_h_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, df_main_win_tid);
d_printf("done\n");
dump(df_h_thread);
if (df_h_thread == NULL)
{
errormessage = "OpenThread(Dwarf Fortress) failed.";
return(FALSE);
}
// TODO enum and suspend all threads?
state = "calling SuspendThread()... ";
d_printf(state);
if ((DWORD)-1 == SuspendThread(df_h_thread))
{
errormessage = "SuspendThread(Dwarf Fortress) failed.";
return(FALSE);
}
d_printf("done\n");
suspended = TRUE;
// get the base of the df executable
df_memory_base = get_exe_base(df_pid);
if (!df_memory_base)
{
errormessage = "GetModuleBase(Dwarf Fortress) failed.";
return(FALSE);
}
dump(df_memory_base);
// we could get this from the PE header, but why bother?
df_memory_start = df_memory_base + 0x1000;
dump(df_memory_start);
// df_memory_end is based on the SizeOfImage field from the in-memory
// copy of the PE header.
df_memory_end = df_memory_base + peekd(df_memory_base+peekd(df_memory_base+0x3C)+0x50)-1;
dump(df_memory_end);
state = "calling GlobalAlloc(huge)... ";
d_printf(state);
dump(df_memory_end - df_memory_start + 0x100);
if (NULL == (copy_of_df_memory = (BYTE *)GlobalAlloc(GPTR, df_memory_end - df_memory_start + 0x100)))
{
errormessage = "GlobalAlloc() of copy_of_df_memory failed";
return FALSE;
}
d_printf("done\n");
state = "copying memory... ";
if (!peekarb(df_memory_start, copy_of_df_memory, df_memory_end-df_memory_start))
{
d_printf("peekarb(entire program) for search()'s use failed\n");
return FALSE;
}
set_hexsearch2_limits(0, 0);
return(TRUE);
}
// not (normally) necessary because it is done at exit time by atexit().
// however you can call this to resume DF before putting up a dialog box.
void close_dwarf_fortress(void)
{
cleanup();
}