develop
Timothy Collett 2012-06-19 14:48:40 -04:00
commit 4ca3aa878a
6 changed files with 345 additions and 82 deletions

@ -1172,9 +1172,9 @@ and are only documented here for completeness:
Returns the pre-extracted vtable address ``name``, or *nil*.
* ``dfhack.internal.getBase()``
* ``dfhack.internal.getRebaseDelta()``
Returns the base address of the process.
Returns the ASLR rebase offset of the DF executable.
* ``dfhack.internal.getMemRanges()``

@ -1329,8 +1329,8 @@ global environment, persistent between calls to the script.</p>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getVTable(name)</tt></p>
<p>Returns the pre-extracted vtable address <tt class="docutils literal">name</tt>, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getBase()</tt></p>
<p>Returns the base address of the process.</p>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getRebaseDelta()</tt></p>
<p>Returns the ASLR rebase offset of the DF executable.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getMemRanges()</tt></p>
<p>Returns a sequence of tables describing virtual memory ranges of the process.</p>

@ -1036,10 +1036,10 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
return rv;
}
static uint32_t getBase() { return Core::getInstance().p->getBase(); }
static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getBase),
WRAP(getRebaseDelta),
{ NULL, NULL }
};
@ -1074,6 +1074,7 @@ static int internal_setAddress(lua_State *L)
}
// Print via printerr, so that it is definitely logged to stderr.log.
addr -= Core::getInstance().vinfo->getRebaseDelta();
std::string msg = stl_sprintf("<global-address name='%s' value='0x%x'/>", name.c_str(), addr);
dfhack_printerr(L, msg);

@ -233,19 +233,55 @@ struct HeapBlock
ULONG reserved;
};
*/
// FIXME: NEEDS TESTING!
// FIXME: <warmist> i noticed that if you enumerate it twice, second time it returns wrong .text region size
static void GetDosNames(std::map<string, string> &table)
{
// Partially based on example from msdn:
// Translate path with device name to drive letters.
TCHAR szTemp[512];
szTemp[0] = '\0';
if (GetLogicalDriveStrings(sizeof(szTemp)-1, szTemp))
{
TCHAR szName[MAX_PATH];
TCHAR szDrive[3] = " :";
BOOL bFound = FALSE;
TCHAR* p = szTemp;
do
{
// Copy the drive letter to the template string
*szDrive = *p;
// Look up each device name
if (QueryDosDevice(szDrive, szName, MAX_PATH))
table[szName] = szDrive;
// Go to the next NULL character.
while (*p++);
} while (*p); // end of string
}
}
void Process::getMemRanges( vector<t_memrange> & ranges )
{
MEMORY_BASIC_INFORMATION MBI;
//map<char *, unsigned int> heaps;
uint64_t movingStart = 0;
PVOID LastAllocationBase = 0;
map <char *, string> nameMap;
map <string,string> dosDrives;
// get page size
SYSTEM_INFO si;
GetSystemInfo(&si);
uint64_t PageSize = si.dwPageSize;
// get dos drive names
GetDosNames(dosDrives);
ranges.clear();
// enumerate heaps
// HeapNodes(d->my_pid, heaps);
// go through all the VM regions, convert them to our internal format
@ -254,52 +290,106 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
movingStart = ((uint64_t)MBI.BaseAddress + MBI.RegionSize);
if(movingStart % PageSize != 0)
movingStart = (movingStart / PageSize + 1) * PageSize;
// skip empty regions and regions we share with other processes (DLLs)
if( !(MBI.State & MEM_COMMIT) /*|| !(MBI.Type & MEM_PRIVATE)*/ )
// Skip unallocated address space
if (MBI.State & MEM_FREE)
continue;
// Find range and permissions
t_memrange temp;
memset(&temp, 0, sizeof(temp));
temp.start = (char *) MBI.BaseAddress;
temp.end = ((char *)MBI.BaseAddress + (uint64_t)MBI.RegionSize);
temp.read = MBI.Protect & PAGE_EXECUTE_READ || MBI.Protect & PAGE_EXECUTE_READWRITE || MBI.Protect & PAGE_READONLY || MBI.Protect & PAGE_READWRITE;
temp.write = MBI.Protect & PAGE_EXECUTE_READWRITE || MBI.Protect & PAGE_READWRITE;
temp.execute = MBI.Protect & PAGE_EXECUTE_READ || MBI.Protect & PAGE_EXECUTE_READWRITE || MBI.Protect & PAGE_EXECUTE;
temp.valid = true;
if(!GetModuleBaseName(d->my_handle, (HMODULE) temp.start, temp.name, 1024))
temp.valid = true;
if (!(MBI.State & MEM_COMMIT))
temp.valid = false; // reserved address space
else if (MBI.Protect & PAGE_EXECUTE)
temp.execute = true;
else if (MBI.Protect & PAGE_EXECUTE_READ)
temp.execute = temp.read = true;
else if (MBI.Protect & PAGE_EXECUTE_READWRITE)
temp.execute = temp.read = temp.write = true;
else if (MBI.Protect & PAGE_EXECUTE_WRITECOPY)
temp.execute = temp.read = temp.write = true;
else if (MBI.Protect & PAGE_READONLY)
temp.read = true;
else if (MBI.Protect & PAGE_READWRITE)
temp.read = temp.write = true;
else if (MBI.Protect & PAGE_WRITECOPY)
temp.read = temp.write = true;
// Merge areas with the same properties
if (!ranges.empty() && LastAllocationBase == MBI.AllocationBase)
{
if(nameMap.count((char *)temp.start))
auto &last = ranges.back();
if (last.end == temp.start &&
last.valid == temp.valid && last.execute == temp.execute &&
last.read == temp.read && last.write == temp.write)
{
// potential buffer overflow...
strcpy(temp.name, nameMap[(char *)temp.start].c_str());
last.end = temp.end;
continue;
}
else
}
#if 1
// Find the mapped file name
if (GetMappedFileName(d->my_handle, temp.start, temp.name, 1024))
{
int vsize = strlen(temp.name);
// Translate NT name to DOS name
for (auto it = dosDrives.begin(); it != dosDrives.end(); ++it)
{
// filter away shared segments without a name.
if( !(MBI.Type & MEM_PRIVATE) )
int ksize = it->first.size();
if (strncmp(temp.name, it->first.data(), ksize) != 0)
continue;
else
temp.name[0]=0;
memcpy(temp.name, it->second.data(), it->second.size());
memmove(temp.name + it->second.size(), temp.name + ksize, vsize + 1 - ksize);
break;
}
}
else
temp.name[0] = 0;
#else
// Find the executable name
char *base = (char*)MBI.AllocationBase;
if(nameMap.count(base))
{
strncpy(temp.name, nameMap[base].c_str(), 1023);
}
else if(GetModuleBaseName(d->my_handle, (HMODULE)base, temp.name, 1024))
{
std::string nm(temp.name);
nameMap[base] = nm;
// this is our executable! (could be generalized to pull segments from libs, but whatever)
if(d->base == temp.start)
if(d->base == base)
{
for(int i = 0; i < d->pe_header.FileHeader.NumberOfSections; i++)
{
char sectionName[9];
/*char sectionName[9];
memcpy(sectionName,d->sections[i].Name,8);
sectionName[8] = 0;
string nm;
nm.append(temp.name);
nm.append(" : ");
nm.append(sectionName);
nameMap[(char *)temp.start + d->sections[i].VirtualAddress] = nm;
nm.append(sectionName);*/
nameMap[base + d->sections[i].VirtualAddress] = nm;
}
}
else
continue;
}
else
temp.name[0] = 0;
#endif
// Push the entry
LastAllocationBase = MBI.AllocationBase;
ranges.push_back(temp);
}
}

@ -208,7 +208,8 @@ local function find_data_segment()
end
elseif mem.read and mem.write
and (string.match(mem.name,'/dwarfort%.exe$')
or string.match(mem.name,'/Dwarf_Fortress$'))
or string.match(mem.name,'/Dwarf_Fortress$')
or string.match(mem.name,'Dwarf Fortress%.exe'))
then
data_start = mem.start_addr
data_end = mem.end_addr

@ -5,6 +5,8 @@ local ms = require 'memscan'
local is_known = dfhack.internal.getAddress
local os_type = dfhack.getOSType()
local force_scan = {}
for _,v in ipairs({...}) do
force_scan[v] = true
@ -20,10 +22,14 @@ MAKE IT RUN CORRECTLY if any data structures
changed, thus possibly leading to CRASHES AND/OR
PERMANENT SAVE CORRUPTION.
This script should be initially started immediately
after loading the game, WITHOUT first loading a world.
It expects vanilla game configuration, without any
custom tilesets or init file changes.
Finding the first few globals requires this script to be
started immediately after loading the game, WITHOUT
first loading a world.
The script expects vanilla game configuration, without
any custom tilesets or init file changes. Never unpause
the game unless instructed. When done, quit the game
without saving using 'die'.
]]
if not utils.prompt_yes_no('Proceed?') then
@ -52,6 +58,34 @@ local function validate_offset(name,validator,addr,tname,...)
ms.found_offset(name,obj)
end
local function zoomed_searcher(startn, end_or_sz)
if force_scan.nozoom then
return nil
end
local sv = is_known(startn)
if not sv then
return nil
end
local ev
if type(end_or_sz) == 'number' then
ev = sv + end_or_sz
if end_or_sz < 0 then
sv, ev = ev, sv
end
else
ev = is_known(end_or_sz)
if not ev then
return nil
end
end
sv = sv - (sv % 4)
ev = ev + 3
ev = ev - (ev % 4)
if data:contains_range(sv, ev-sv) then
return ms.DiffSearcher.new(ms.MemoryArea.new(sv,ev))
end
end
local function exec_finder(finder, names)
if type(names) ~= 'table' then
names = { names }
@ -76,7 +110,8 @@ end
local ordinal_names = {
[0] = '1st entry',
[1] = '2nd entry'
[1] = '2nd entry',
[2] = '3rd entry'
}
setmetatable(ordinal_names, {
__index = function(self,idx) return (idx+1)..'th entry' end
@ -140,8 +175,6 @@ local function find_cursor()
return false
end
exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
--
-- Announcements
--
@ -158,8 +191,6 @@ local function find_announcements()
dfhack.printerr('Could not find announcements.')
end
exec_finder(find_announcements, 'announcements')
--
-- d_init
--
@ -198,8 +229,6 @@ local function find_d_init()
dfhack.printerr('Could not find d_init')
end
exec_finder(find_d_init, 'd_init')
--
-- gview
--
@ -220,8 +249,6 @@ local function find_gview()
dfhack.printerr('Could not find gview')
end
exec_finder(find_gview, 'gview')
--
-- World
--
@ -257,8 +284,6 @@ menu, and select different types as instructed below:]],
validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type')
end
exec_finder(find_world, 'world')
--
-- UI
--
@ -291,8 +316,6 @@ menu, and switch modes as instructed below:]],
validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode')
end
exec_finder(find_ui, 'ui')
--
-- ui_sidebar_menus
--
@ -319,9 +342,9 @@ end
local function find_ui_sidebar_menus()
local addr = searcher:find_menu_cursor([[
Searching for ui_sidebar_menus. Please open the add job
ui of Mason, Craftsdwarfs, or Carpenters workshop, and
select entries in the list:]],
Searching for ui_sidebar_menus. Please switch to 'q' mode,
select a Mason, Craftsdwarfs, or Carpenters workshop, open
the Add Job menu, and move the cursor within:]],
'int32_t',
{ 0, 1, 2, 3, 4, 5, 6 },
ordinal_names
@ -330,8 +353,6 @@ select entries in the list:]],
addr, df.ui_sidebar_menus, 'workshop_job', 'cursor')
end
exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus')
--
-- ui_build_selector
--
@ -366,7 +387,25 @@ number, so when it shows "Min (5000df", it means 50000:]],
addr, df.ui_build_selector, 'plate_info', 'unit_min')
end
exec_finder(find_ui_build_selector, 'ui_build_selector')
--
-- ui_menu_width
--
local function find_ui_menu_width()
local addr = searcher:find_menu_cursor([[
Searching for ui_menu_width. Please exit to the main
dwarfmode menu, then use Tab to do as instructed below:]],
'int8_t',
{ 2, 3, 1 },
{ [2] = 'switch to the most usual [mapmap][menu] layout',
[3] = 'hide the menu completely',
[1] = 'switch to the default [map][menu][map] layout' }
)
ms.found_offset('ui_menu_width', addr)
-- NOTE: Assume that the vars are adjacent, as always
ms.found_offset('ui_area_map_width', addr+1)
end
--
-- ui_selected_unit
@ -395,8 +434,6 @@ into the prompts below:]],
ms.found_offset('ui_selected_unit', addr)
end
exec_finder(find_ui_selected_unit, 'ui_selected_unit')
--
-- ui_unit_view_mode
--
@ -412,8 +449,6 @@ with 'v', switch the pages as requested:]],
ms.found_offset('ui_unit_view_mode', addr)
end
exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode')
--
-- ui_look_cursor
--
@ -434,8 +469,6 @@ and select list entries as instructed:]],
ms.found_offset('ui_look_cursor', addr)
end
exec_finder(find_ui_look_cursor, 'ui_look_cursor')
--
-- ui_building_item_cursor
--
@ -456,8 +489,6 @@ with many contained items, and select as instructed:]],
ms.found_offset('ui_building_item_cursor', addr)
end
exec_finder(find_ui_building_item_cursor, 'ui_building_item_cursor')
--
-- ui_workshop_in_add
--
@ -468,7 +499,7 @@ Searching for ui_workshop_in_add. Please activate the 'q'
mode, find a workshop without jobs (or delete jobs),
and do as instructed below.
NOTE: After first 3 steps resize the game window.]],
NOTE: If not done after first 3-4 steps, resize the game window.]],
'int8_t',
{ 1, 0 },
{ [1] = 'enter the add job menu',
@ -477,8 +508,6 @@ NOTE: After first 3 steps resize the game window.]],
ms.found_offset('ui_workshop_in_add', addr)
end
exec_finder(find_ui_workshop_in_add, 'ui_workshop_in_add')
--
-- ui_workshop_job_cursor
--
@ -498,8 +527,6 @@ mode, find a workshop with many jobs, and select as instructed:]],
ms.found_offset('ui_workshop_job_cursor', addr)
end
exec_finder(find_ui_workshop_job_cursor, 'ui_workshop_job_cursor')
--
-- ui_building_in_assign
--
@ -510,7 +537,7 @@ Searching for ui_building_in_assign. Please activate
the 'q' mode, select a room building (e.g. a bedroom)
and do as instructed below.
NOTE: After first 3 steps resize the game window.]],
NOTE: If not done after first 3-4 steps, resize the game window.]],
'int8_t',
{ 1, 0 },
{ [1] = 'enter the Assign owner menu',
@ -519,8 +546,6 @@ NOTE: After first 3 steps resize the game window.]],
ms.found_offset('ui_building_in_assign', addr)
end
exec_finder(find_ui_building_in_assign, 'ui_building_in_assign')
--
-- ui_building_in_resize
--
@ -531,7 +556,7 @@ Searching for ui_building_in_resize. Please activate
the 'q' mode, select a room building (e.g. a bedroom)
and do as instructed below.
NOTE: After first 3 steps resize the game window.]],
NOTE: If not done after first 3-4 steps, resize the game window.]],
'int8_t',
{ 1, 0 },
{ [1] = 'enter the Resize room mode',
@ -540,9 +565,6 @@ NOTE: After first 3 steps resize the game window.]],
ms.found_offset('ui_building_in_resize', addr)
end
exec_finder(find_ui_building_in_resize, 'ui_building_in_resize')
--
-- window_x
--
@ -557,8 +579,6 @@ scroll to the LEFT edge, then do as instructed:]],
ms.found_offset('window_x', addr)
end
exec_finder(find_window_x, 'window_x')
--
-- window_y
--
@ -573,8 +593,6 @@ scroll to the TOP edge, then do as instructed:]],
ms.found_offset('window_y', addr)
end
exec_finder(find_window_y, 'window_y')
--
-- window_z
--
@ -582,20 +600,173 @@ exec_finder(find_window_y, 'window_y')
local function find_window_z()
local addr = searcher:find_counter([[
Searching for window_z. Please exit to main dwarfmode menu,
scroll to ground level, then do as instructed below.
scroll to a Z level near surface, then do as instructed below.
NOTE: After first 3 steps resize the game window.]],
NOTE: If not done after first 3-4 steps, resize the game window.]],
'int32_t', -1,
"Please press '>' to scroll one Z level down."
)
ms.found_offset('window_z', addr)
end
exec_finder(find_window_z, 'window_z')
--
-- cur_year
--
local function find_cur_year()
local zone
if os_type == 'windows' then
zone = zoomed_searcher('formation_next_id', 32)
elseif os_type == 'darwin' then
zone = zoomed_searcher('cursor', -32)
elseif os_type == 'linux' then
zone = zoomed_searcher('ui_building_assign_type', -512)
end
if not zone then
dfhack.printerr('Cannot search for cur_year - prerequisites missing.')
return
end
local yvalue = utils.prompt_input('Please enter current in-game year: ', utils.check_number)
local idx, addr = zone.area.int32_t:find_one{yvalue}
if idx then
ms.found_offset('cur_year', addr)
return
end
dfhack.printerr('Could not find cur_year')
end
--
-- cur_year_tick
--
local function find_cur_year_tick()
local zone
if os_type == 'windows' then
zone = zoomed_searcher('artifact_next_id', -32)
else
zone = zoomed_searcher('cur_year', 128)
end
if not zone then
dfhack.printerr('Cannot search for cur_year_tick - prerequisites missing.')
return
end
local addr = zone:find_counter([[
Searching for cur_year_tick. Please exit to main dwarfmode
menu, then do as instructed below:]],
'int32_t', 1,
"Please press '.' to step the game one frame."
)
ms.found_offset('cur_year_tick', addr)
end
--
-- THE END
-- process_jobs
--
print('Done.')
local function get_process_zone()
if os_type == 'windows' then
return zoomed_searcher('ui_workshop_job_cursor', 'ui_building_in_resize')
else
return zoomed_searcher('cur_year', 'cur_year_tick')
end
end
local function find_process_jobs()
local zone = get_process_zone() or searcher
local addr = zone:find_menu_cursor([[
Searching for process_jobs. Please do as instructed below:]],
'int8_t',
{ 1, 0 },
{ [1] = 'designate a building to be constructed, e.g a bed',
[0] = 'step or unpause the game to reset the flag' }
)
ms.found_offset('process_jobs', addr)
end
--
-- process_dig
--
local function find_process_dig()
local zone = get_process_zone() or searcher
local addr = zone:find_menu_cursor([[
Searching for process_dig. Please do as instructed below:]],
'int8_t',
{ 1, 0 },
{ [1] = 'designate a tile to be mined out',
[0] = 'step or unpause the game to reset the flag' }
)
ms.found_offset('process_dig', addr)
end
--
-- pause_state
--
local function find_pause_state()
local zone
if os_type == 'linux' then
zone = zoomed_searcher('ui_look_cursor', 32)
elseif os_type == 'windows' then
zone = zoomed_searcher('ui_workshop_job_cursor', 64)
end
zone = zone or searcher
local addr = zone:find_menu_cursor([[
Searching for pause_state. Please do as instructed below:]],
'int8_t',
{ 1, 0 },
{ [1] = 'PAUSE the game',
[0] = 'UNPAUSE the game' }
)
ms.found_offset('pause_state', addr)
end
--
-- MAIN FLOW
--
print('\nInitial globals (need title screen):\n')
exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
exec_finder(find_announcements, 'announcements')
exec_finder(find_d_init, 'd_init')
exec_finder(find_gview, 'gview')
print('\nCompound globals (need loaded world):\n')
exec_finder(find_world, 'world')
exec_finder(find_ui, 'ui')
exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus')
exec_finder(find_ui_build_selector, 'ui_build_selector')
print('\nPrimitive globals:\n')
exec_finder(find_ui_menu_width, { 'ui_menu_width', 'ui_area_map_width' })
exec_finder(find_ui_selected_unit, 'ui_selected_unit')
exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode')
exec_finder(find_ui_look_cursor, 'ui_look_cursor')
exec_finder(find_ui_building_item_cursor, 'ui_building_item_cursor')
exec_finder(find_ui_workshop_in_add, 'ui_workshop_in_add')
exec_finder(find_ui_workshop_job_cursor, 'ui_workshop_job_cursor')
exec_finder(find_ui_building_in_assign, 'ui_building_in_assign')
exec_finder(find_ui_building_in_resize, 'ui_building_in_resize')
exec_finder(find_window_x, 'window_x')
exec_finder(find_window_y, 'window_y')
exec_finder(find_window_z, 'window_z')
print('\nUnpausing globals:\n')
exec_finder(find_cur_year, 'cur_year')
exec_finder(find_cur_year_tick, 'cur_year_tick')
exec_finder(find_process_jobs, 'process_jobs')
exec_finder(find_process_dig, 'process_dig')
exec_finder(find_pause_state, 'pause_state')
print('\nDone. Now add newly-found globals to symbols.xml.')
searcher:reset()