Merge remote-tracking branch 'upstream/master'
commit
217ef18aef
@ -0,0 +1,773 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
|
||||
A thread-safe logging console with a line editor.
|
||||
|
||||
Based on linenoise:
|
||||
linenoise -- guerrilla line editing library against the idea that a
|
||||
line editing lib needs to be 20,000 lines of C code.
|
||||
|
||||
You can find the latest source code at:
|
||||
|
||||
http://github.com/antirez/linenoise
|
||||
|
||||
Does a number of crazy assumptions that happen to be true in 99.9999% of
|
||||
the 2010 UNIX computers around.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
Copyright (c) 2011, Petr Mrázek <peterix@gmail.com>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <errno.h>
|
||||
#include <deque>
|
||||
|
||||
// George Vulov for MacOSX
|
||||
#ifndef __LINUX__
|
||||
#define TEMP_FAILURE_RETRY(expr) \
|
||||
({ long int _res; \
|
||||
do _res = (long int) (expr); \
|
||||
while (_res == -1L && errno == EINTR); \
|
||||
_res; })
|
||||
#endif
|
||||
|
||||
#include "Console.h"
|
||||
#include "Hooks.h"
|
||||
using namespace DFHack;
|
||||
|
||||
#include "tinythread.h"
|
||||
using namespace tthread;
|
||||
|
||||
static int isUnsupportedTerm(void)
|
||||
{
|
||||
static const char *unsupported_term[] = {"dumb","cons25",NULL};
|
||||
char *term = getenv("TERM");
|
||||
int j;
|
||||
|
||||
if (term == NULL) return 0;
|
||||
for (j = 0; unsupported_term[j]; j++)
|
||||
if (!strcasecmp(term,unsupported_term[j])) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * ANSI_CLS = "\033[2J";
|
||||
const char * ANSI_BLACK = "\033[22;30m";
|
||||
const char * ANSI_RED = "\033[22;31m";
|
||||
const char * ANSI_GREEN = "\033[22;32m";
|
||||
const char * ANSI_BROWN = "\033[22;33m";
|
||||
const char * ANSI_BLUE = "\033[22;34m";
|
||||
const char * ANSI_MAGENTA = "\033[22;35m";
|
||||
const char * ANSI_CYAN = "\033[22;36m";
|
||||
const char * ANSI_GREY = "\033[22;37m";
|
||||
const char * ANSI_DARKGREY = "\033[01;30m";
|
||||
const char * ANSI_LIGHTRED = "\033[01;31m";
|
||||
const char * ANSI_LIGHTGREEN = "\033[01;32m";
|
||||
const char * ANSI_YELLOW = "\033[01;33m";
|
||||
const char * ANSI_LIGHTBLUE = "\033[01;34m";
|
||||
const char * ANSI_LIGHTMAGENTA = "\033[01;35m";
|
||||
const char * ANSI_LIGHTCYAN = "\033[01;36m";
|
||||
const char * ANSI_WHITE = "\033[01;37m";
|
||||
const char * RESETCOLOR = "\033[0m";
|
||||
|
||||
const char * getANSIColor(const int c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case -1: return RESETCOLOR; // HACK! :P
|
||||
case 0 : return ANSI_BLACK;
|
||||
case 1 : return ANSI_BLUE; // non-ANSI
|
||||
case 2 : return ANSI_GREEN;
|
||||
case 3 : return ANSI_CYAN; // non-ANSI
|
||||
case 4 : return ANSI_RED; // non-ANSI
|
||||
case 5 : return ANSI_MAGENTA;
|
||||
case 6 : return ANSI_BROWN;
|
||||
case 7 : return ANSI_GREY;
|
||||
case 8 : return ANSI_DARKGREY;
|
||||
case 9 : return ANSI_LIGHTBLUE; // non-ANSI
|
||||
case 10: return ANSI_LIGHTGREEN;
|
||||
case 11: return ANSI_LIGHTCYAN; // non-ANSI;
|
||||
case 12: return ANSI_LIGHTRED; // non-ANSI;
|
||||
case 13: return ANSI_LIGHTMAGENTA;
|
||||
case 14: return ANSI_YELLOW; // non-ANSI
|
||||
case 15: return ANSI_WHITE;
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
class Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
{
|
||||
dfout_C = NULL;
|
||||
rawmode = false;
|
||||
in_batch = false;
|
||||
supported_terminal = false;
|
||||
state = con_unclaimed;
|
||||
};
|
||||
virtual ~Private()
|
||||
{
|
||||
//sync();
|
||||
}
|
||||
private:
|
||||
bool read_char(unsigned char & out)
|
||||
{
|
||||
FD_ZERO(&descriptor_set);
|
||||
FD_SET(STDIN_FILENO, &descriptor_set);
|
||||
FD_SET(exit_pipe[0], &descriptor_set);
|
||||
int ret = TEMP_FAILURE_RETRY(
|
||||
select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL)
|
||||
);
|
||||
if(ret == -1)
|
||||
return false;
|
||||
if (FD_ISSET(exit_pipe[0], &descriptor_set))
|
||||
return false;
|
||||
if (FD_ISSET(STDIN_FILENO, &descriptor_set))
|
||||
{
|
||||
// read byte from stdin
|
||||
ret = TEMP_FAILURE_RETRY(
|
||||
read(STDIN_FILENO, &out, 1)
|
||||
);
|
||||
if(ret == -1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
void print(const char *data)
|
||||
{
|
||||
fputs(data, dfout_C);
|
||||
}
|
||||
|
||||
void print_text(color_ostream::color_value clr, const std::string &chunk)
|
||||
{
|
||||
if(!in_batch && state == con_lineedit)
|
||||
{
|
||||
disable_raw();
|
||||
fprintf(dfout_C,"\x1b[1G");
|
||||
fprintf(dfout_C,"\x1b[0K");
|
||||
|
||||
color(clr);
|
||||
print(chunk.c_str());
|
||||
|
||||
reset_color();
|
||||
enable_raw();
|
||||
prompt_refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
color(clr);
|
||||
print(chunk.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void begin_batch()
|
||||
{
|
||||
assert(!in_batch);
|
||||
|
||||
in_batch = true;
|
||||
|
||||
if (state == con_lineedit)
|
||||
{
|
||||
disable_raw();
|
||||
fprintf(dfout_C,"\x1b[1G");
|
||||
fprintf(dfout_C,"\x1b[0K");
|
||||
}
|
||||
}
|
||||
|
||||
void end_batch()
|
||||
{
|
||||
assert(in_batch);
|
||||
|
||||
flush();
|
||||
|
||||
in_batch = false;
|
||||
|
||||
if (state == con_lineedit)
|
||||
{
|
||||
reset_color();
|
||||
enable_raw();
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void flush()
|
||||
{
|
||||
if (!rawmode)
|
||||
fflush(dfout_C);
|
||||
}
|
||||
|
||||
/// Clear the console, along with its scrollback
|
||||
void clear()
|
||||
{
|
||||
if(rawmode)
|
||||
{
|
||||
const char * clr = "\033c\033[3J\033[H";
|
||||
::write(STDIN_FILENO,clr,strlen(clr));
|
||||
}
|
||||
else
|
||||
{
|
||||
print("\033c\033[3J\033[H");
|
||||
fflush(dfout_C);
|
||||
}
|
||||
}
|
||||
/// Position cursor at x,y. 1,1 = top left corner
|
||||
void gotoxy(int x, int y)
|
||||
{
|
||||
char tmp[64];
|
||||
sprintf(tmp,"\033[%d;%dH", y,x);
|
||||
print(tmp);
|
||||
}
|
||||
/// Set color (ANSI color number)
|
||||
void color(Console::color_value index)
|
||||
{
|
||||
if(!rawmode)
|
||||
fprintf(dfout_C,getANSIColor(index));
|
||||
else
|
||||
{
|
||||
const char * colstr = getANSIColor(index);
|
||||
int lstr = strlen(colstr);
|
||||
::write(STDIN_FILENO,colstr,lstr);
|
||||
}
|
||||
}
|
||||
/// Reset color to default
|
||||
void reset_color(void)
|
||||
{
|
||||
color(Console::COLOR_RESET);
|
||||
if(!rawmode)
|
||||
fflush(dfout_C);
|
||||
}
|
||||
/// Enable or disable the caret/cursor
|
||||
void cursor(bool enable = true)
|
||||
{
|
||||
if(enable)
|
||||
print("\033[?25h");
|
||||
else
|
||||
print("\033[?25l");
|
||||
}
|
||||
/// Waits given number of milliseconds before continuing.
|
||||
void msleep(unsigned int msec);
|
||||
/// get the current number of columns
|
||||
int get_columns(void)
|
||||
{
|
||||
winsize ws;
|
||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80;
|
||||
return ws.ws_col;
|
||||
}
|
||||
/// get the current number of rows
|
||||
int get_rows(void)
|
||||
{
|
||||
winsize ws;
|
||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25;
|
||||
return ws.ws_row;
|
||||
}
|
||||
/// beep. maybe?
|
||||
//void beep (void);
|
||||
/// A simple line edit (raw mode)
|
||||
int lineedit(const std::string& prompt, std::string& output, recursive_mutex * lock, CommandHistory & ch)
|
||||
{
|
||||
output.clear();
|
||||
reset_color();
|
||||
this->prompt = prompt;
|
||||
if (!supported_terminal)
|
||||
{
|
||||
print(prompt.c_str());
|
||||
fflush(dfout_C);
|
||||
// FIXME: what do we do here???
|
||||
//SDL_recursive_mutexV(lock);
|
||||
std::getline(std::cin, output);
|
||||
//SDL_recursive_mutexP(lock);
|
||||
return output.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
int count;
|
||||
if (enable_raw() == -1) return 0;
|
||||
if(state == con_lineedit)
|
||||
return -1;
|
||||
state = con_lineedit;
|
||||
count = prompt_loop(lock,ch);
|
||||
state = con_unclaimed;
|
||||
disable_raw();
|
||||
print("\n");
|
||||
if(count != -1)
|
||||
{
|
||||
output = raw_buffer;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
int enable_raw()
|
||||
{
|
||||
struct termios raw;
|
||||
|
||||
if (!supported_terminal)
|
||||
return -1;
|
||||
if (tcgetattr(STDIN_FILENO,&orig_termios) == -1)
|
||||
return -1;
|
||||
|
||||
raw = orig_termios; //modify the original mode
|
||||
// input modes: no break, no CR to NL, no parity check, no strip char,
|
||||
// no start/stop output control.
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
// output modes - disable post processing
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
// control modes - set 8 bit chars
|
||||
raw.c_cflag |= (CS8);
|
||||
// local modes - choing off, canonical off, no extended functions,
|
||||
// no signal chars (^Z,^C)
|
||||
#ifdef CONSOLE_NO_CATCH
|
||||
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN );
|
||||
#else
|
||||
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG );
|
||||
#endif
|
||||
// control chars - set return condition: min number of bytes and timer.
|
||||
// We want read to return every single byte, without timeout.
|
||||
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer
|
||||
// put terminal in raw mode after flushing
|
||||
if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0)
|
||||
return -1;
|
||||
rawmode = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void disable_raw()
|
||||
{
|
||||
/* Don't even check the return value as it's too late. */
|
||||
if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig_termios) != -1)
|
||||
rawmode = 0;
|
||||
}
|
||||
void prompt_refresh()
|
||||
{
|
||||
char seq[64];
|
||||
int cols = get_columns();
|
||||
int plen = prompt.size();
|
||||
const char * buf = raw_buffer.c_str();
|
||||
int len = raw_buffer.size();
|
||||
int cooked_cursor = raw_cursor;
|
||||
// Use math! This is silly.
|
||||
while((plen+cooked_cursor) >= cols)
|
||||
{
|
||||
buf++;
|
||||
len--;
|
||||
cooked_cursor--;
|
||||
}
|
||||
while (plen+len > cols)
|
||||
{
|
||||
len--;
|
||||
}
|
||||
/* Cursor to left edge */
|
||||
snprintf(seq,64,"\x1b[1G");
|
||||
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||
/* Write the prompt and the current buffer content */
|
||||
if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return;
|
||||
if (::write(STDIN_FILENO,buf,len) == -1) return;
|
||||
/* Erase to right */
|
||||
snprintf(seq,64,"\x1b[0K");
|
||||
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||
/* Move cursor to original position. */
|
||||
snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(cooked_cursor+plen));
|
||||
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||
}
|
||||
|
||||
int prompt_loop(recursive_mutex * lock, CommandHistory & history)
|
||||
{
|
||||
int fd = STDIN_FILENO;
|
||||
size_t plen = prompt.size();
|
||||
int history_index = 0;
|
||||
raw_buffer.clear();
|
||||
raw_cursor = 0;
|
||||
/* The latest history entry is always our current buffer, that
|
||||
* initially is just an empty string. */
|
||||
const std::string empty;
|
||||
history.add(empty);
|
||||
if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1;
|
||||
while(1)
|
||||
{
|
||||
unsigned char c;
|
||||
int isok;
|
||||
unsigned char seq[2], seq2;
|
||||
lock->unlock();
|
||||
if(!read_char(c))
|
||||
{
|
||||
lock->lock();
|
||||
return -2;
|
||||
}
|
||||
lock->lock();
|
||||
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||
* there was an error reading from fd. Otherwise it will return the
|
||||
* character that should be handled next. */
|
||||
if (c == 9)
|
||||
{
|
||||
/*
|
||||
if( completionCallback != NULL) {
|
||||
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
|
||||
// Return on errors
|
||||
if (c < 0) return len;
|
||||
// Read next character when 0
|
||||
if (c == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore tab
|
||||
continue;
|
||||
}
|
||||
*/
|
||||
// just ignore tabs
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case 13: // enter
|
||||
history.remove();
|
||||
return raw_buffer.size();
|
||||
case 3: // ctrl-c
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
case 127: // backspace
|
||||
case 8: // ctrl-h
|
||||
if (raw_cursor > 0 && raw_buffer.size() > 0)
|
||||
{
|
||||
raw_buffer.erase(raw_cursor-1,1);
|
||||
raw_cursor--;
|
||||
prompt_refresh();
|
||||
}
|
||||
break;
|
||||
case 27: // escape sequence
|
||||
lock->unlock();
|
||||
if(!read_char(seq[0]) || !read_char(seq[1]))
|
||||
{
|
||||
lock->lock();
|
||||
return -2;
|
||||
}
|
||||
lock->lock();
|
||||
if(seq[0] == '[')
|
||||
{
|
||||
if (seq[1] == 'D')
|
||||
{
|
||||
left_arrow:
|
||||
if (raw_cursor > 0)
|
||||
{
|
||||
raw_cursor--;
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
else if ( seq[1] == 'C')
|
||||
{
|
||||
right_arrow:
|
||||
/* right arrow */
|
||||
if (size_t(raw_cursor) != raw_buffer.size())
|
||||
{
|
||||
raw_cursor++;
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
else if (seq[1] == 'A' || seq[1] == 'B')
|
||||
{
|
||||
/* up and down arrow: history */
|
||||
if (history.size() > 1)
|
||||
{
|
||||
/* Update the current history entry before to
|
||||
* overwrite it with tne next one. */
|
||||
history[history_index] = raw_buffer;
|
||||
/* Show the new entry */
|
||||
history_index += (seq[1] == 'A') ? 1 : -1;
|
||||
if (history_index < 0)
|
||||
{
|
||||
history_index = 0;
|
||||
break;
|
||||
}
|
||||
else if (size_t(history_index) >= history.size())
|
||||
{
|
||||
history_index = history.size()-1;
|
||||
break;
|
||||
}
|
||||
raw_buffer = history[history_index];
|
||||
raw_cursor = raw_buffer.size();
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
else if(seq[1] == 'H')
|
||||
{
|
||||
// home
|
||||
raw_cursor = 0;
|
||||
prompt_refresh();
|
||||
}
|
||||
else if(seq[1] == 'F')
|
||||
{
|
||||
// end
|
||||
raw_cursor = raw_buffer.size();
|
||||
prompt_refresh();
|
||||
}
|
||||
else if (seq[1] > '0' && seq[1] < '7')
|
||||
{
|
||||
// extended escape
|
||||
lock->unlock();
|
||||
if(!read_char(seq2))
|
||||
{
|
||||
lock->lock();
|
||||
return -2;
|
||||
}
|
||||
lock->lock();
|
||||
if (seq[1] == '3' && seq2 == '~' )
|
||||
{
|
||||
// delete
|
||||
if (raw_buffer.size() > 0 && size_t(raw_cursor) < raw_buffer.size())
|
||||
{
|
||||
raw_buffer.erase(raw_cursor,1);
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (raw_buffer.size() == size_t(raw_cursor))
|
||||
{
|
||||
raw_buffer.append(1,c);
|
||||
raw_cursor++;
|
||||
if (plen+raw_buffer.size() < size_t(get_columns()))
|
||||
{
|
||||
/* Avoid a full update of the line in the
|
||||
* trivial case. */
|
||||
if (::write(fd,&c,1) == -1) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
raw_buffer.insert(raw_cursor,1,c);
|
||||
raw_cursor++;
|
||||
prompt_refresh();
|
||||
}
|
||||
break;
|
||||
case 21: // Ctrl+u, delete the whole line.
|
||||
raw_buffer.clear();
|
||||
raw_cursor = 0;
|
||||
prompt_refresh();
|
||||
break;
|
||||
case 11: // Ctrl+k, delete from current to end of line.
|
||||
raw_buffer.erase(raw_cursor);
|
||||
prompt_refresh();
|
||||
break;
|
||||
case 1: // Ctrl+a, go to the start of the line
|
||||
raw_cursor = 0;
|
||||
prompt_refresh();
|
||||
break;
|
||||
case 5: // ctrl+e, go to the end of the line
|
||||
raw_cursor = raw_buffer.size();
|
||||
prompt_refresh();
|
||||
break;
|
||||
case 12: // ctrl+l, clear screen
|
||||
clear();
|
||||
prompt_refresh();
|
||||
}
|
||||
}
|
||||
return raw_buffer.size();
|
||||
}
|
||||
FILE * dfout_C;
|
||||
bool supported_terminal;
|
||||
// state variables
|
||||
bool rawmode; // is raw mode active?
|
||||
termios orig_termios; // saved/restored by raw mode
|
||||
// current state
|
||||
enum console_state
|
||||
{
|
||||
con_unclaimed,
|
||||
con_lineedit
|
||||
} state;
|
||||
bool in_batch;
|
||||
std::string prompt; // current prompt string
|
||||
std::string raw_buffer; // current raw mode buffer
|
||||
int raw_cursor; // cursor position in the buffer
|
||||
// thread exit mechanism
|
||||
int exit_pipe[2];
|
||||
fd_set descriptor_set;
|
||||
};
|
||||
}
|
||||
|
||||
Console::Console()
|
||||
{
|
||||
d = 0;
|
||||
inited = false;
|
||||
// we can't create the mutex at this time. the SDL functions aren't hooked yet.
|
||||
wlock = new recursive_mutex();
|
||||
}
|
||||
Console::~Console()
|
||||
{
|
||||
if(inited)
|
||||
shutdown();
|
||||
if(wlock)
|
||||
delete wlock;
|
||||
if(d)
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool Console::init(bool sharing)
|
||||
{
|
||||
if(sharing)
|
||||
{
|
||||
inited = false;
|
||||
return false;
|
||||
}
|
||||
freopen("stdout.log", "w", stdout);
|
||||
d = new Private();
|
||||
// make our own weird streams so our IO isn't redirected
|
||||
d->dfout_C = fopen("/dev/tty", "w");
|
||||
std::cin.tie(this);
|
||||
clear();
|
||||
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
|
||||
// init the exit mechanism
|
||||
pipe(d->exit_pipe);
|
||||
FD_ZERO(&d->descriptor_set);
|
||||
FD_SET(STDIN_FILENO, &d->descriptor_set);
|
||||
FD_SET(d->exit_pipe[0], &d->descriptor_set);
|
||||
inited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::shutdown(void)
|
||||
{
|
||||
if(!d)
|
||||
return true;
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if(d->rawmode)
|
||||
d->disable_raw();
|
||||
d->print("\n");
|
||||
inited = false;
|
||||
// kill the thing
|
||||
close(d->exit_pipe[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Console::begin_batch()
|
||||
{
|
||||
//color_ostream::begin_batch();
|
||||
|
||||
wlock->lock();
|
||||
|
||||
if (inited)
|
||||
d->begin_batch();
|
||||
}
|
||||
|
||||
void Console::end_batch()
|
||||
{
|
||||
if (inited)
|
||||
d->end_batch();
|
||||
|
||||
wlock->unlock();
|
||||
}
|
||||
|
||||
void Console::flush_proxy()
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if (inited)
|
||||
d->flush();
|
||||
}
|
||||
|
||||
void Console::add_text(color_value color, const std::string &text)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if (inited)
|
||||
d->print_text(color, text);
|
||||
}
|
||||
|
||||
int Console::get_columns(void)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
int ret = -1;
|
||||
if(inited)
|
||||
ret = d->get_columns();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Console::get_rows(void)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
int ret = -1;
|
||||
if(inited)
|
||||
ret = d->get_rows();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Console::clear()
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if(inited)
|
||||
d->clear();
|
||||
}
|
||||
|
||||
void Console::gotoxy(int x, int y)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if(inited)
|
||||
d->gotoxy(x,y);
|
||||
}
|
||||
|
||||
void Console::cursor(bool enable)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
if(inited)
|
||||
d->cursor(enable);
|
||||
}
|
||||
|
||||
int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch)
|
||||
{
|
||||
lock_guard <recursive_mutex> g(*wlock);
|
||||
int ret = -2;
|
||||
if(inited)
|
||||
ret = d->lineedit(prompt,output,wlock,ch);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Console::msleep (unsigned int msec)
|
||||
{
|
||||
if (msec > 1000) sleep(msec/1000000);
|
||||
usleep((msec % 1000000) * 1000);
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
/*typedef struct interpose_s
|
||||
{
|
||||
void *new_func;
|
||||
void *orig_func;
|
||||
} interpose_t;*/
|
||||
|
||||
#include "DFHack.h"
|
||||
#include "Core.h"
|
||||
#include "Hooks.h"
|
||||
#include <iostream>
|
||||
|
||||
/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) =
|
||||
{
|
||||
{ (void *)DFH_SDL_Init, (void *)SDL_Init },
|
||||
{ (void *)DFH_SDL_PollEvent, (void *)SDL_PollEvent },
|
||||
{ (void *)DFH_SDL_Quit, (void *)SDL_Quit },
|
||||
{ (void *)DFH_SDL_NumJoysticks, (void *)SDL_NumJoysticks },
|
||||
|
||||
};*/
|
||||
|
||||
/*******************************************************************************
|
||||
* SDL part starts here *
|
||||
*******************************************************************************/
|
||||
// hook - called for each game tick (or more often)
|
||||
DFhackCExport int SDL_NumJoysticks(void)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
return c.Update();
|
||||
}
|
||||
|
||||
// hook - called at program exit
|
||||
static void (*_SDL_Quit)(void) = 0;
|
||||
DFhackCExport void SDL_Quit(void)
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
c.Shutdown();
|
||||
/*if(_SDL_Quit)
|
||||
{
|
||||
_SDL_Quit();
|
||||
}*/
|
||||
|
||||
_SDL_Quit();
|
||||
}
|
||||
|
||||
// called by DF to check input events
|
||||
static int (*_SDL_PollEvent)(SDL_Event* event) = 0;
|
||||
DFhackCExport int SDL_PollEvent(SDL_Event* event)
|
||||
{
|
||||
pollevent_again:
|
||||
// if SDL returns 0 here, it means there are no more events. return 0
|
||||
int orig_return = _SDL_PollEvent(event);
|
||||
if(!orig_return)
|
||||
return 0;
|
||||
// otherwise we have an event to filter
|
||||
else if( event != 0 )
|
||||
{
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
// if we consume the event, ask SDL for more.
|
||||
if(!c.DFH_SDL_Event(event))
|
||||
goto pollevent_again;
|
||||
}
|
||||
return orig_return;
|
||||
}
|
||||
|
||||
struct WINDOW;
|
||||
DFhackCExport int wgetch(WINDOW *win)
|
||||
{
|
||||
static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch");
|
||||
if(!_wgetch)
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
wgetch_again:
|
||||
int in = _wgetch(win);
|
||||
int out;
|
||||
if(c.ncurses_wgetch(in, out))
|
||||
{
|
||||
// not consumed, give to DF
|
||||
return out;
|
||||
}
|
||||
else
|
||||
{
|
||||
// consumed, repeat
|
||||
goto wgetch_again;
|
||||
}
|
||||
}
|
||||
|
||||
// hook - called at program start, initialize some stuffs we'll use later
|
||||
static int (*_SDL_Init)(uint32_t flags) = 0;
|
||||
DFhackCExport int SDL_Init(uint32_t flags)
|
||||
{
|
||||
// reroute stderr
|
||||
fprintf(stderr,"dfhack: attempting to hook in\n");
|
||||
freopen("stderr.log", "w", stderr);
|
||||
// we don't reroute stdout until we figure out if this should be done at all
|
||||
// See: Console-linux.cpp
|
||||
|
||||
// find real functions
|
||||
fprintf(stderr,"dfhack: saving real SDL functions\n");
|
||||
_SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init");
|
||||
_SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit");
|
||||
_SDL_PollEvent = (int (*)(SDL_Event*))dlsym(RTLD_NEXT,"SDL_PollEvent");
|
||||
|
||||
fprintf(stderr,"dfhack: saved real SDL functions\n");
|
||||
// check if we got them
|
||||
if(_SDL_Init && _SDL_Quit && _SDL_PollEvent)
|
||||
{
|
||||
fprintf(stderr,"dfhack: hooking successful\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// bail, this would be a disaster otherwise
|
||||
fprintf(stderr,"dfhack: something went horribly wrong\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
DFHack::Core & c = DFHack::Core::getInstance();
|
||||
//c.Init();
|
||||
|
||||
int ret = _SDL_Init(flags);
|
||||
return ret;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* MacPool.h
|
||||
* Handles creation and destruction of autorelease pool for DFHack on the Mac
|
||||
*/
|
||||
|
||||
#ifndef MACPOOL_H
|
||||
#define MACPOOL_H
|
||||
|
||||
int create_pool();
|
||||
int destroy_pool();
|
||||
|
||||
#endif
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* MacPool.m
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MacPool.h"
|
||||
|
||||
NSAutoreleasePool *thePool;
|
||||
|
||||
int create_pool() {
|
||||
fprintf(stderr,"Creating autorelease pool\n");
|
||||
thePool = [[NSAutoreleasePool alloc] init];
|
||||
return 1;
|
||||
}
|
||||
|
||||
int destroy_pool() {
|
||||
fprintf(stderr,"Draining and releasing autorelease pool\n");
|
||||
[thePool drain];
|
||||
[thePool release];
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "DFHack.h"
|
||||
#include "PluginManager.h"
|
||||
#include "Hooks.h"
|
||||
#include <iostream>
|
||||
|
||||
/*
|
||||
* Plugin loading functions
|
||||
*/
|
||||
namespace DFHack
|
||||
{
|
||||
DFLibrary * OpenPlugin (const char * filename)
|
||||
{
|
||||
dlerror();
|
||||
DFLibrary * ret = (DFLibrary *) dlopen(filename, RTLD_NOW);
|
||||
if(!ret)
|
||||
{
|
||||
std::cerr << dlerror() << std::endl;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void * LookupPlugin (DFLibrary * plugin ,const char * function)
|
||||
{
|
||||
return (DFLibrary *) dlsym((void *)plugin, function);
|
||||
}
|
||||
void ClosePlugin (DFLibrary * plugin)
|
||||
{
|
||||
dlclose((void *) plugin);
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#include "Internal.h"
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
using namespace std;
|
||||
|
||||
#include <md5wrapper.h>
|
||||
#include "MemAccess.h"
|
||||
#include "VersionInfoFactory.h"
|
||||
#include "VersionInfo.h"
|
||||
#include "Error.h"
|
||||
#include <string.h>
|
||||
using namespace DFHack;
|
||||
|
||||
Process::Process(VersionInfoFactory * known_versions)
|
||||
{
|
||||
int target_result;
|
||||
|
||||
char path[1024];
|
||||
char *real_path;
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
real_path = realpath(path, NULL);
|
||||
}
|
||||
|
||||
identified = false;
|
||||
my_descriptor = 0;
|
||||
|
||||
md5wrapper md5;
|
||||
uint32_t length;
|
||||
uint8_t first_kb [1024];
|
||||
memset(first_kb, 0, sizeof(first_kb));
|
||||
// get hash of the running DF process
|
||||
string hash = md5.getHashFromFile(real_path, length, (char *) first_kb);
|
||||
// create linux process, add it to the vector
|
||||
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash);
|
||||
if(vinfo)
|
||||
{
|
||||
my_descriptor = new VersionInfo(*vinfo);
|
||||
identified = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
char * wd = getcwd(NULL, 0);
|
||||
cerr << "Unable to retrieve version information.\n";
|
||||
cerr << "File: " << real_path << endl;
|
||||
cerr << "MD5: " << hash << endl;
|
||||
cerr << "working dir: " << wd << endl;
|
||||
cerr << "length:" << length << endl;
|
||||
cerr << "1KB hexdump follows:" << endl;
|
||||
for(int i = 0; i < 64; i++)
|
||||
{
|
||||
fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
first_kb[i*16],
|
||||
first_kb[i*16+1],
|
||||
first_kb[i*16+2],
|
||||
first_kb[i*16+3],
|
||||
first_kb[i*16+4],
|
||||
first_kb[i*16+5],
|
||||
first_kb[i*16+6],
|
||||
first_kb[i*16+7],
|
||||
first_kb[i*16+8],
|
||||
first_kb[i*16+9],
|
||||
first_kb[i*16+10],
|
||||
first_kb[i*16+11],
|
||||
first_kb[i*16+12],
|
||||
first_kb[i*16+13],
|
||||
first_kb[i*16+14],
|
||||
first_kb[i*16+15]
|
||||
);
|
||||
}
|
||||
free(wd);
|
||||
}
|
||||
}
|
||||
|
||||
Process::~Process()
|
||||
{
|
||||
// destroy our copy of the memory descriptor
|
||||
delete my_descriptor;
|
||||
}
|
||||
|
||||
string Process::doReadClassName (void * vptr)
|
||||
{
|
||||
//FIXME: BAD!!!!!
|
||||
char * typeinfo = Process::readPtr(((char *)vptr - 0x4));
|
||||
char * typestring = Process::readPtr(typeinfo + 0x4);
|
||||
string raw = readCString(typestring);
|
||||
size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers
|
||||
size_t end = raw.length();
|
||||
return raw.substr(start,end-start);
|
||||
}
|
||||
|
||||
//FIXME: cross-reference with ELF segment entries?
|
||||
void Process::getMemRanges( vector<t_memrange> & ranges )
|
||||
{
|
||||
char buffer[1024];
|
||||
char permissions[5]; // r/-, w/-, x/-, p/s, 0
|
||||
|
||||
FILE *mapFile = ::fopen("/proc/self/maps", "r");
|
||||
size_t start, end, offset, device1, device2, node;
|
||||
|
||||
while (fgets(buffer, 1024, mapFile))
|
||||
{
|
||||
t_memrange temp;
|
||||
temp.name[0] = 0;
|
||||
sscanf(buffer, "%zx-%zx %s %zx %2zx:%2zx %zu %[^\n]",
|
||||
&start,
|
||||
&end,
|
||||
(char*)&permissions,
|
||||
&offset, &device1, &device2, &node,
|
||||
(char*)temp.name);
|
||||
temp.start = (void *) start;
|
||||
temp.end = (void *) end;
|
||||
temp.read = permissions[0] == 'r';
|
||||
temp.write = permissions[1] == 'w';
|
||||
temp.execute = permissions[2] == 'x';
|
||||
temp.shared = permissions[3] == 's';
|
||||
temp.valid = true;
|
||||
ranges.push_back(temp);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Process::getBase()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int getdir (string dir, vector<string> &files)
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *dirp;
|
||||
if((dp = opendir(dir.c_str())) == NULL)
|
||||
{
|
||||
cout << "Error(" << errno << ") opening " << dir << endl;
|
||||
return errno;
|
||||
}
|
||||
while ((dirp = readdir(dp)) != NULL) {
|
||||
files.push_back(string(dirp->d_name));
|
||||
}
|
||||
closedir(dp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Process::getThreadIDs(vector<uint32_t> & threads )
|
||||
{
|
||||
stringstream ss;
|
||||
vector<string> subdirs;
|
||||
if(getdir("/proc/self/task/",subdirs) != 0)
|
||||
{
|
||||
//FIXME: needs exceptions. this is a fatal error
|
||||
cerr << "unable to enumerate threads. This is BAD!" << endl;
|
||||
return false;
|
||||
}
|
||||
threads.clear();
|
||||
for(size_t i = 0; i < subdirs.size();i++)
|
||||
{
|
||||
uint32_t tid;
|
||||
if(sscanf(subdirs[i].c_str(),"%d", &tid))
|
||||
{
|
||||
threads.push_back(tid);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string Process::getPath()
|
||||
{
|
||||
char path[1024];
|
||||
char *real_path;
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
real_path = realpath(path, NULL);
|
||||
}
|
||||
std::string path_string(real_path);
|
||||
int last_slash = path_string.find_last_of("/");
|
||||
std::string directory = path_string.substr(0,last_slash);
|
||||
return directory;
|
||||
}
|
||||
|
||||
int Process::getPID()
|
||||
{
|
||||
return getpid();
|
||||
}
|
||||
|
||||
bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
|
||||
{
|
||||
int result;
|
||||
int protect=0;
|
||||
if(trgrange.read)protect|=PROT_READ;
|
||||
if(trgrange.write)protect|=PROT_WRITE;
|
||||
if(trgrange.execute)protect|=PROT_EXEC;
|
||||
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
|
||||
|
||||
return result==0;
|
||||
}
|
@ -0,0 +1,493 @@
|
||||
-- Utilities for offset scan scripts.
|
||||
|
||||
local _ENV = mkmodule('memscan')
|
||||
|
||||
local utils = require('utils')
|
||||
|
||||
-- A length-checked view on a memory buffer
|
||||
|
||||
CheckedArray = CheckedArray or {}
|
||||
|
||||
function CheckedArray.new(type,saddr,eaddr)
|
||||
local data = df.reinterpret_cast(type,saddr)
|
||||
local esize = data:sizeof()
|
||||
local count = math.floor((eaddr-saddr)/esize)
|
||||
local obj = {
|
||||
type = type, start = saddr, size = count*esize,
|
||||
esize = esize, data = data, count = count
|
||||
}
|
||||
setmetatable(obj, CheckedArray)
|
||||
return obj
|
||||
end
|
||||
|
||||
function CheckedArray:__len()
|
||||
return self.count
|
||||
end
|
||||
function CheckedArray:__index(idx)
|
||||
if type(idx) == number then
|
||||
if idx >= self.count then
|
||||
error('Index out of bounds: '..tostring(idx))
|
||||
end
|
||||
return self.data[idx]
|
||||
else
|
||||
return CheckedArray[idx]
|
||||
end
|
||||
end
|
||||
function CheckedArray:__newindex(idx, val)
|
||||
if idx >= self.count then
|
||||
error('Index out of bounds: '..tostring(idx))
|
||||
end
|
||||
self.data[idx] = val
|
||||
end
|
||||
function CheckedArray:addr2idx(addr, round)
|
||||
local off = addr - self.start
|
||||
if off >= 0 and off < self.size and (round or (off % self.esize) == 0) then
|
||||
return math.floor(off / self.esize), off
|
||||
end
|
||||
end
|
||||
function CheckedArray:idx2addr(idx)
|
||||
if idx >= 0 and idx < self.count then
|
||||
return self.start + idx*self.esize
|
||||
end
|
||||
end
|
||||
|
||||
-- Search methods
|
||||
|
||||
function CheckedArray:find(data,sidx,eidx,reverse)
|
||||
local dcnt = #data
|
||||
sidx = math.max(0, sidx or 0)
|
||||
eidx = math.min(self.count, eidx or self.count)
|
||||
if (eidx - sidx) >= dcnt and dcnt > 0 then
|
||||
return dfhack.with_temp_object(
|
||||
df.new(self.type, dcnt),
|
||||
function(buffer)
|
||||
for i = 1,dcnt do
|
||||
buffer[i-1] = data[i]
|
||||
end
|
||||
local cnt = eidx - sidx - dcnt
|
||||
local step = self.esize
|
||||
local sptr = self.start + sidx*step
|
||||
local ksize = dcnt*step
|
||||
if reverse then
|
||||
local idx, addr = dfhack.internal.memscan(sptr + cnt*step, cnt, -step, buffer, ksize)
|
||||
if idx then
|
||||
return sidx + cnt - idx, addr
|
||||
end
|
||||
else
|
||||
local idx, addr = dfhack.internal.memscan(sptr, cnt, step, buffer, ksize)
|
||||
if idx then
|
||||
return sidx + idx, addr
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
function CheckedArray:find_one(data,sidx,eidx,reverse)
|
||||
local idx, addr = self:find(data,sidx,eidx,reverse)
|
||||
if idx then
|
||||
-- Verify this is the only match
|
||||
if reverse then
|
||||
eidx = idx+#data-1
|
||||
else
|
||||
sidx = idx+1
|
||||
end
|
||||
if self:find(data,sidx,eidx,reverse) then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return idx, addr
|
||||
end
|
||||
function CheckedArray:list_changes(old_arr,old_val,new_val,delta)
|
||||
if old_arr.type ~= self.type or old_arr.count ~= self.count then
|
||||
error('Incompatible arrays')
|
||||
end
|
||||
local eidx = self.count
|
||||
local optr = old_arr.start
|
||||
local nptr = self.start
|
||||
local esize = self.esize
|
||||
local rv
|
||||
local sidx = 0
|
||||
while true do
|
||||
local idx = dfhack.internal.diffscan(optr, nptr, sidx, eidx, esize, old_val, new_val, delta)
|
||||
if not idx then
|
||||
break
|
||||
end
|
||||
rv = rv or {}
|
||||
rv[#rv+1] = idx
|
||||
sidx = idx+1
|
||||
end
|
||||
return rv
|
||||
end
|
||||
function CheckedArray:filter_changes(prev_list,old_arr,old_val,new_val,delta)
|
||||
if old_arr.type ~= self.type or old_arr.count ~= self.count then
|
||||
error('Incompatible arrays')
|
||||
end
|
||||
local eidx = self.count
|
||||
local optr = old_arr.start
|
||||
local nptr = self.start
|
||||
local esize = self.esize
|
||||
local rv
|
||||
for i=1,#prev_list do
|
||||
local idx = prev_list[i]
|
||||
if idx < 0 or idx >= eidx then
|
||||
error('Index out of bounds: '..idx)
|
||||
end
|
||||
if dfhack.internal.diffscan(optr, nptr, idx, idx+1, esize, old_val, new_val, delta) then
|
||||
rv = rv or {}
|
||||
rv[#rv+1] = idx
|
||||
end
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
-- A raw memory area class
|
||||
|
||||
MemoryArea = MemoryArea or {}
|
||||
MemoryArea.__index = MemoryArea
|
||||
|
||||
function MemoryArea.new(astart, aend)
|
||||
local obj = {
|
||||
start_addr = astart, end_addr = aend, size = aend - astart,
|
||||
int8_t = CheckedArray.new('int8_t',astart,aend),
|
||||
uint8_t = CheckedArray.new('uint8_t',astart,aend),
|
||||
int16_t = CheckedArray.new('int16_t',astart,aend),
|
||||
uint16_t = CheckedArray.new('uint16_t',astart,aend),
|
||||
int32_t = CheckedArray.new('int32_t',astart,aend),
|
||||
uint32_t = CheckedArray.new('uint32_t',astart,aend),
|
||||
float = CheckedArray.new('float',astart,aend)
|
||||
}
|
||||
setmetatable(obj, MemoryArea)
|
||||
return obj
|
||||
end
|
||||
|
||||
function MemoryArea:__gc()
|
||||
df.delete(self.buffer)
|
||||
end
|
||||
|
||||
function MemoryArea:__tostring()
|
||||
return string.format('<MemoryArea: %x..%x>', self.start_addr, self.end_addr)
|
||||
end
|
||||
function MemoryArea:contains_range(start,size)
|
||||
return size >= 0 and start >= self.start_addr and (start+size) <= self.end_addr
|
||||
end
|
||||
function MemoryArea:contains_obj(obj,count)
|
||||
local size, base = df.sizeof(obj)
|
||||
return size and base and self:contains_range(base, size*(count or 1))
|
||||
end
|
||||
|
||||
function MemoryArea:clone()
|
||||
local buffer = df.new('int8_t', self.size)
|
||||
local _, base = buffer:sizeof()
|
||||
local rv = MemoryArea.new(base, base+self.size)
|
||||
rv.buffer = buffer
|
||||
return rv
|
||||
end
|
||||
function MemoryArea:copy_from(area2)
|
||||
if area2.size ~= self.size then
|
||||
error('Size mismatch')
|
||||
end
|
||||
dfhack.internal.memmove(self.start_addr, area2.start_addr, self.size)
|
||||
end
|
||||
function MemoryArea:delete()
|
||||
setmetatable(self, nil)
|
||||
df.delete(self.buffer)
|
||||
for k,v in pairs(self) do self[k] = nil end
|
||||
end
|
||||
|
||||
-- Static data segment search
|
||||
|
||||
local function find_data_segment()
|
||||
local data_start, data_end
|
||||
|
||||
for i,mem in ipairs(dfhack.internal.getMemRanges()) do
|
||||
if data_end then
|
||||
if mem.start_addr == data_end and mem.read and mem.write then
|
||||
data_end = mem.end_addr
|
||||
else
|
||||
break
|
||||
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%.exe'))
|
||||
then
|
||||
data_start = mem.start_addr
|
||||
data_end = mem.end_addr
|
||||
end
|
||||
end
|
||||
|
||||
return data_start, data_end
|
||||
end
|
||||
|
||||
function get_data_segment()
|
||||
local s, e = find_data_segment()
|
||||
if s and e then
|
||||
return MemoryArea.new(s, e)
|
||||
end
|
||||
end
|
||||
|
||||
-- Register a found offset, or report an error.
|
||||
|
||||
function found_offset(name,val)
|
||||
local cval = dfhack.internal.getAddress(name)
|
||||
|
||||
if not val then
|
||||
print('Could not find offset '..name)
|
||||
if not cval and not utils.prompt_yes_no('Continue with the script?') then
|
||||
qerror('User quit')
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if df.isvalid(val) then
|
||||
_,val = val:sizeof()
|
||||
end
|
||||
|
||||
print(string.format('Found offset %s: %x', name, val))
|
||||
|
||||
if cval then
|
||||
if cval ~= val then
|
||||
error(string.format('Mismatch with the current value: %x',val))
|
||||
end
|
||||
else
|
||||
dfhack.internal.setAddress(name, val)
|
||||
|
||||
local ival = val - dfhack.internal.getRebaseDelta()
|
||||
local entry = string.format("<global-address name='%s' value='0x%x'/>\n", name, ival)
|
||||
|
||||
local ccolor = dfhack.color(COLOR_LIGHTGREEN)
|
||||
dfhack.print(entry)
|
||||
dfhack.color(ccolor)
|
||||
|
||||
io.stdout:write(entry)
|
||||
io.stdout:flush()
|
||||
end
|
||||
end
|
||||
|
||||
-- Offset of a field within struct
|
||||
|
||||
function field_ref(handle,...)
|
||||
local items = table.pack(...)
|
||||
for i=1,items.n-1 do
|
||||
handle = handle[items[i]]
|
||||
end
|
||||
return handle:_field(items[items.n])
|
||||
end
|
||||
|
||||
function field_offset(type,...)
|
||||
local handle = df.reinterpret_cast(type,1)
|
||||
local _,addr = df.sizeof(field_ref(handle,...))
|
||||
return addr-1
|
||||
end
|
||||
|
||||
function MemoryArea:object_by_field(addr,type,...)
|
||||
if not addr then
|
||||
return nil
|
||||
end
|
||||
local base = addr - field_offset(type,...)
|
||||
local obj = df.reinterpret_cast(type, base)
|
||||
if not self:contains_obj(obj) then
|
||||
obj = nil
|
||||
end
|
||||
return obj, base
|
||||
end
|
||||
|
||||
-- Validation
|
||||
|
||||
function is_valid_vector(ref,size)
|
||||
local ints = df.reinterpret_cast('uint32_t', ref)
|
||||
return ints[0] <= ints[1] and ints[1] <= ints[2]
|
||||
and (size == nil or (ints[1] - ints[0]) % size == 0)
|
||||
end
|
||||
|
||||
-- Difference search helper
|
||||
|
||||
DiffSearcher = DiffSearcher or {}
|
||||
DiffSearcher.__index = DiffSearcher
|
||||
|
||||
function DiffSearcher.new(area)
|
||||
local obj = { area = area }
|
||||
setmetatable(obj, DiffSearcher)
|
||||
return obj
|
||||
end
|
||||
|
||||
function DiffSearcher:begin_search(type)
|
||||
self.type = type
|
||||
self.old_value = nil
|
||||
self.search_sets = nil
|
||||
if not self.save_area then
|
||||
self.save_area = self.area:clone()
|
||||
end
|
||||
end
|
||||
function DiffSearcher:advance_search(new_value,delta)
|
||||
if self.search_sets then
|
||||
local nsets = #self.search_sets
|
||||
local ovec = self.save_area[self.type]
|
||||
local nvec = self.area[self.type]
|
||||
local new_set
|
||||
if nsets > 0 then
|
||||
local last_set = self.search_sets[nsets]
|
||||
new_set = nvec:filter_changes(last_set,ovec,self.old_value,new_value,delta)
|
||||
else
|
||||
new_set = nvec:list_changes(ovec,self.old_value,new_value,delta)
|
||||
end
|
||||
if new_set then
|
||||
self.search_sets[nsets+1] = new_set
|
||||
self.old_value = new_value
|
||||
self.save_area:copy_from(self.area)
|
||||
return #new_set, new_set
|
||||
end
|
||||
else
|
||||
self.old_value = new_value
|
||||
self.search_sets = {}
|
||||
self.save_area:copy_from(self.area)
|
||||
return #self.save_area[self.type], nil
|
||||
end
|
||||
end
|
||||
function DiffSearcher:reset()
|
||||
self.search_sets = nil
|
||||
if self.save_area then
|
||||
self.save_area:delete()
|
||||
self.save_area = nil
|
||||
end
|
||||
end
|
||||
function DiffSearcher:idx2addr(idx)
|
||||
return self.area[self.type]:idx2addr(idx)
|
||||
end
|
||||
|
||||
-- Interactive search utility
|
||||
|
||||
function DiffSearcher:find_interactive(prompt,data_type,condition_cb)
|
||||
enum = enum or {}
|
||||
|
||||
-- Loop for restarting search from scratch
|
||||
while true do
|
||||
print('\n'..prompt)
|
||||
|
||||
self:begin_search(data_type)
|
||||
|
||||
local found = false
|
||||
local ccursor = 0
|
||||
|
||||
-- Loop through choices
|
||||
while true do
|
||||
print('')
|
||||
|
||||
local ok, value, delta = condition_cb(ccursor)
|
||||
|
||||
ccursor = ccursor + 1
|
||||
|
||||
if not ok then
|
||||
break
|
||||
end
|
||||
|
||||
-- Search for it in the memory
|
||||
local cnt, set = self:advance_search(value, delta)
|
||||
if not cnt then
|
||||
dfhack.printerr(' Converged to zero candidates; probably a mistake somewhere.')
|
||||
break
|
||||
elseif set and cnt == 1 then
|
||||
-- To confirm, wait for two 1-candidate results in a row
|
||||
if found then
|
||||
local addr = self:idx2addr(set[1])
|
||||
print(string.format(' Confirmed address: %x\n', addr))
|
||||
return addr, set[1]
|
||||
else
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
print(' '..cnt..' candidates remaining.')
|
||||
end
|
||||
|
||||
if not utils.prompt_yes_no('\nRetry search from the start?') then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DiffSearcher:find_menu_cursor(prompt,data_type,choices,enum)
|
||||
enum = enum or {}
|
||||
|
||||
return self:find_interactive(
|
||||
prompt, data_type,
|
||||
function(ccursor)
|
||||
local choice
|
||||
|
||||
-- Select the next value to search for
|
||||
if type(choices) == 'function' then
|
||||
choice = choices(ccursor)
|
||||
|
||||
if not choice then
|
||||
return false
|
||||
end
|
||||
else
|
||||
choice = choices[(ccursor % #choices) + 1]
|
||||
end
|
||||
|
||||
-- Ask the user to select it
|
||||
if enum ~= 'noprompt' then
|
||||
local cname = enum[choice] or choice
|
||||
if type(choice) == 'string' and type(cname) == 'number' then
|
||||
choice, cname = cname, choice
|
||||
end
|
||||
if cname ~= choice then
|
||||
cname = cname..' ('..choice..')'
|
||||
end
|
||||
|
||||
print(' Please select: '..cname)
|
||||
if not utils.prompt_yes_no(' Continue?', true) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true, choice
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function DiffSearcher:find_counter(prompt,data_type,delta,action_prompt)
|
||||
delta = delta or 1
|
||||
|
||||
return self:find_interactive(
|
||||
prompt, data_type,
|
||||
function(ccursor)
|
||||
if ccursor > 0 then
|
||||
print(" "..(action_prompt or 'Please do the action.'))
|
||||
end
|
||||
if not utils.prompt_yes_no(' Continue?', true) then
|
||||
return false
|
||||
end
|
||||
return true, nil, delta
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- Screen size
|
||||
|
||||
function get_screen_size()
|
||||
-- Use already known globals
|
||||
if dfhack.internal.getAddress('init') then
|
||||
local d = df.global.init.display
|
||||
return d.grid_x, d.grid_y
|
||||
end
|
||||
if dfhack.internal.getAddress('gps') then
|
||||
local g = df.global.gps
|
||||
return g.dimx, g.dimy
|
||||
end
|
||||
|
||||
-- Parse stdout.log for resize notifications
|
||||
io.stdout:flush()
|
||||
|
||||
local w,h = 80,25
|
||||
for line in io.lines('stdout.log') do
|
||||
local cw, ch = string.match(line, '^Resizing grid to (%d+)x(%d+)$')
|
||||
if cw and ch then
|
||||
w, h = tonumber(cw), tonumber(ch)
|
||||
end
|
||||
end
|
||||
return w,h
|
||||
end
|
||||
|
||||
return _ENV
|
@ -1 +1 @@
|
||||
Subproject commit ff87e784a8e274fcc3d1f57e57746735eb7285c2
|
||||
Subproject commit ad38c5e96b05fedf16114fd16bd463e933f13582
|
@ -1,29 +1,33 @@
|
||||
find_package(Ruby)
|
||||
if(RUBY_FOUND)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.cpp
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.cpp
|
||||
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl
|
||||
)
|
||||
ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp)
|
||||
if(CMAKE_COMPILER_IS_GNUCC)
|
||||
set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof")
|
||||
endif(CMAKE_COMPILER_IS_GNUCC)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.offsets
|
||||
COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets
|
||||
DEPENDS ruby-autogen
|
||||
)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ruby-autogen.rb
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb
|
||||
DEPENDS ruby-autogen.offsets ruby-memstruct.rb
|
||||
)
|
||||
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb)
|
||||
include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH})
|
||||
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
||||
target_link_libraries(ruby ${RUBY_LIBRARY})
|
||||
install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION})
|
||||
else(RUBY_FOUND)
|
||||
MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.")
|
||||
endif(RUBY_FOUND)
|
||||
OPTION(DL_RUBY "download libruby from the internet" ON)
|
||||
IF (DL_RUBY)
|
||||
IF (UNIX)
|
||||
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
|
||||
EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
|
||||
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
|
||||
SET(RUBYLIB libruby.so)
|
||||
ELSE (UNIX)
|
||||
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
|
||||
EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
|
||||
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
FILE(RENAME msvcrt-ruby18.dll libruby.dll)
|
||||
SET(RUBYLIB libruby.dll)
|
||||
ENDIF(UNIX)
|
||||
ENDIF(DL_RUBY)
|
||||
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
|
||||
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
|
||||
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
|
||||
COMMENT ruby-autogen.rb
|
||||
)
|
||||
ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
|
||||
|
||||
INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
|
||||
|
||||
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
||||
ADD_DEPENDENCIES(ruby ruby-autogen-rb)
|
||||
|
||||
INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION})
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,747 +0,0 @@
|
||||
module DFHack
|
||||
module MemHack
|
||||
INSPECT_SIZE_LIMIT=16384
|
||||
class MemStruct
|
||||
attr_accessor :_memaddr
|
||||
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
|
||||
def _get ; self ; end
|
||||
def _cpp_init ; end
|
||||
end
|
||||
|
||||
class Compound < MemStruct
|
||||
class << self
|
||||
attr_accessor :_fields, :_rtti_classname, :_sizeof
|
||||
def field(name, offset)
|
||||
struct = yield
|
||||
return if not struct
|
||||
@_fields ||= []
|
||||
@_fields << [name, offset, struct]
|
||||
define_method(name) { struct._at(@_memaddr+offset)._get }
|
||||
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
|
||||
end
|
||||
def _fields_ancestors
|
||||
if superclass.respond_to?(:_fields_ancestors)
|
||||
superclass._fields_ancestors + _fields.to_a
|
||||
else
|
||||
_fields.to_a
|
||||
end
|
||||
end
|
||||
|
||||
def number(bits, signed, initvalue=nil, enum=nil)
|
||||
Number.new(bits, signed, initvalue, enum)
|
||||
end
|
||||
def float
|
||||
Float.new
|
||||
end
|
||||
def bit(shift)
|
||||
BitField.new(shift, 1)
|
||||
end
|
||||
def bits(shift, len, enum=nil)
|
||||
BitField.new(shift, len, enum)
|
||||
end
|
||||
def pointer
|
||||
Pointer.new((yield if block_given?))
|
||||
end
|
||||
def pointer_ary(tglen)
|
||||
PointerAry.new(tglen, yield)
|
||||
end
|
||||
def static_array(len, tglen, indexenum=nil)
|
||||
StaticArray.new(tglen, len, indexenum, yield)
|
||||
end
|
||||
def static_string(len)
|
||||
StaticString.new(len)
|
||||
end
|
||||
|
||||
def stl_vector(tglen=nil)
|
||||
tg = yield if tglen
|
||||
case tglen
|
||||
when 1; StlVector8.new(tg)
|
||||
when 2; StlVector16.new(tg)
|
||||
else StlVector32.new(tg)
|
||||
end
|
||||
end
|
||||
def stl_string
|
||||
StlString.new
|
||||
end
|
||||
def stl_bit_vector
|
||||
StlBitVector.new
|
||||
end
|
||||
def stl_deque(tglen)
|
||||
StlDeque.new(tglen, yield)
|
||||
end
|
||||
|
||||
def df_flagarray(indexenum=nil)
|
||||
DfFlagarray.new(indexenum)
|
||||
end
|
||||
def df_array(tglen)
|
||||
DfArray.new(tglen, yield)
|
||||
end
|
||||
def df_linked_list
|
||||
DfLinkedList.new(yield)
|
||||
end
|
||||
|
||||
def global(glob)
|
||||
Global.new(glob)
|
||||
end
|
||||
def compound(name=nil, &b)
|
||||
m = Class.new(Compound)
|
||||
DFHack.const_set(name, m) if name
|
||||
m.instance_eval(&b)
|
||||
m.new
|
||||
end
|
||||
def rtti_classname(n)
|
||||
DFHack.rtti_register(n, self)
|
||||
@_rtti_classname = n
|
||||
end
|
||||
def sizeof(n)
|
||||
@_sizeof = n
|
||||
end
|
||||
|
||||
# allocate a new c++ object, return its ruby wrapper
|
||||
def cpp_new
|
||||
ptr = DFHack.malloc(_sizeof)
|
||||
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
|
||||
DFHack.memory_write_int32(ptr, vt)
|
||||
# TODO call constructor
|
||||
end
|
||||
o = new._at(ptr)
|
||||
o._cpp_init
|
||||
o
|
||||
end
|
||||
end
|
||||
def _cpp_init
|
||||
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
|
||||
end
|
||||
def _set(h)
|
||||
case h
|
||||
when Hash; h.each { |k, v| send("_#{k}=", v) }
|
||||
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
|
||||
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
|
||||
else raise 'wut?'
|
||||
end
|
||||
end
|
||||
def _fields ; self.class._fields.to_a ; end
|
||||
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
|
||||
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
|
||||
def _rtti_classname ; self.class._rtti_classname ; end
|
||||
def _sizeof ; self.class._sizeof ; end
|
||||
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
|
||||
def inspect
|
||||
cn = self.class.name.sub(/^DFHack::/, '')
|
||||
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
|
||||
out = "#<#{cn}"
|
||||
return out << ' ...>' if @@inspecting[_memaddr]
|
||||
@@inspecting[_memaddr] = true
|
||||
_fields_ancestors.each { |n, o, s|
|
||||
out << ' ' if out.length != 0 and out[-1, 1] != ' '
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << inspect_field(n, o, s)
|
||||
}
|
||||
out.chomp!(' ')
|
||||
@@inspecting.delete _memaddr
|
||||
out << '>'
|
||||
end
|
||||
def inspect_field(n, o, s)
|
||||
if s.kind_of?(BitField) and s._len == 1
|
||||
send(n) ? n.to_s : ''
|
||||
elsif s.kind_of?(Pointer)
|
||||
"#{n}=#{s._at(@_memaddr+o).inspect}"
|
||||
elsif n == :_whole
|
||||
"_whole=0x#{_whole.to_s(16)}"
|
||||
else
|
||||
v = send(n).inspect
|
||||
"#{n}=#{v}"
|
||||
end
|
||||
rescue
|
||||
"#{n}=ERR(#{$!})"
|
||||
end
|
||||
end
|
||||
|
||||
class Enum
|
||||
# number -> symbol
|
||||
def self.enum
|
||||
# ruby weirdness, needed to make the constants 'virtual'
|
||||
@enum ||= const_get(:ENUM)
|
||||
end
|
||||
# symbol -> number
|
||||
def self.nume
|
||||
@nume ||= const_get(:NUME)
|
||||
end
|
||||
|
||||
def self.to_i(i)
|
||||
nume[i] || i
|
||||
end
|
||||
def self.to_sym(i)
|
||||
enum[i] || i
|
||||
end
|
||||
end
|
||||
|
||||
class Number < MemStruct
|
||||
attr_accessor :_bits, :_signed, :_initvalue, :_enum
|
||||
def initialize(bits, signed, initvalue, enum)
|
||||
@_bits = bits
|
||||
@_signed = signed
|
||||
@_initvalue = initvalue
|
||||
@_enum = enum
|
||||
end
|
||||
|
||||
def _get
|
||||
v = case @_bits
|
||||
when 32; DFHack.memory_read_int32(@_memaddr)
|
||||
when 16; DFHack.memory_read_int16(@_memaddr)
|
||||
when 8; DFHack.memory_read_int8( @_memaddr)
|
||||
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
|
||||
end
|
||||
v &= (1 << @_bits) - 1 if not @_signed
|
||||
v = @_enum.to_sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
v = @_enum.to_i(v) if @_enum
|
||||
case @_bits
|
||||
when 32; DFHack.memory_write_int32(@_memaddr, v)
|
||||
when 16; DFHack.memory_write_int16(@_memaddr, v)
|
||||
when 8; DFHack.memory_write_int8( @_memaddr, v)
|
||||
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
|
||||
end
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(@_initvalue) if @_initvalue
|
||||
end
|
||||
end
|
||||
class Float < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_float(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_float(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
_set(0.0)
|
||||
end
|
||||
end
|
||||
class BitField < MemStruct
|
||||
attr_accessor :_shift, :_len, :_enum
|
||||
def initialize(shift, len, enum=nil)
|
||||
@_shift = shift
|
||||
@_len = len
|
||||
@_enum = enum
|
||||
end
|
||||
def _mask
|
||||
(1 << @_len) - 1
|
||||
end
|
||||
|
||||
def _get
|
||||
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
|
||||
if @_len == 1
|
||||
((v & 1) == 0) ? false : true
|
||||
else
|
||||
v &= _mask
|
||||
v = @_enum.to_sym(v) if @_enum
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
if @_len == 1
|
||||
# allow 'bit = 0'
|
||||
v = (v && v != 0 ? 1 : 0)
|
||||
end
|
||||
v = @_enum.to_i(v) if @_enum
|
||||
v = (v & _mask) << @_shift
|
||||
|
||||
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
|
||||
end
|
||||
end
|
||||
|
||||
class Pointer < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp
|
||||
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
# XXX shaky...
|
||||
def _set(v)
|
||||
if v.kind_of?(Pointer)
|
||||
DFHack.memory_write_int32(@_memaddr, v._getp)
|
||||
elsif v.kind_of?(MemStruct)
|
||||
DFHack.memory_write_int32(@_memaddr, v._memaddr)
|
||||
else
|
||||
_get._set(v)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
ptr = _getp
|
||||
if ptr == 0
|
||||
'NULL'
|
||||
else
|
||||
cn = ''
|
||||
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
|
||||
cn = @_tg._glob if cn == 'Global'
|
||||
"#<Pointer #{cn} #{'0x%X' % _getp}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
class PointerAry < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def _getp(i=0)
|
||||
delta = (i != 0 ? i*@_tglen : 0)
|
||||
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
|
||||
end
|
||||
|
||||
def _get
|
||||
addr = _getp
|
||||
return if addr == 0
|
||||
self
|
||||
end
|
||||
|
||||
def [](i)
|
||||
addr = _getp(i)
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
addr = _getp(i)
|
||||
raise 'null pointer' if addr == 0
|
||||
@_tg._at(addr)._set(v)
|
||||
end
|
||||
|
||||
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
|
||||
end
|
||||
module Enumerable
|
||||
include ::Enumerable
|
||||
attr_accessor :_indexenum
|
||||
def each ; (0...length).each { |i| yield self[i] } ; end
|
||||
def inspect
|
||||
out = '['
|
||||
each_with_index { |e, idx|
|
||||
out << ', ' if out.length > 1
|
||||
if out.length > INSPECT_SIZE_LIMIT
|
||||
out << '...'
|
||||
break
|
||||
end
|
||||
out << "#{_indexenum.to_sym(idx)}=" if _indexenum
|
||||
out << e.inspect
|
||||
}
|
||||
out << ']'
|
||||
end
|
||||
def empty? ; length == 0 ; end
|
||||
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
|
||||
end
|
||||
class StaticArray < MemStruct
|
||||
attr_accessor :_tglen, :_length, :_indexenum, :_tg
|
||||
def initialize(tglen, length, indexenum, tg)
|
||||
@_tglen = tglen
|
||||
@_length = length
|
||||
@_indexenum = indexenum
|
||||
@_tg = tg
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
def _cpp_init
|
||||
_length.times { |i| _tgat(i)._cpp_init }
|
||||
end
|
||||
alias length _length
|
||||
alias size _length
|
||||
def _tgat(i)
|
||||
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
|
||||
end
|
||||
def [](i)
|
||||
i = _indexenum.to_i(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i = _indexenum.to_i(i) if _indexenum
|
||||
i += @_length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class StaticString < MemStruct
|
||||
attr_accessor :_length
|
||||
def initialize(length)
|
||||
@_length = length
|
||||
end
|
||||
def _get
|
||||
DFHack.memory_read(@_memaddr, @_length)
|
||||
end
|
||||
def _set(v)
|
||||
DFHack.memory_write(@_memaddr, v[0, @_length])
|
||||
end
|
||||
end
|
||||
|
||||
class StlVector32 < MemStruct
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
def length
|
||||
DFHack.memory_vector32_length(@_memaddr)
|
||||
end
|
||||
def size ; length ; end # alias wouldnt work for subclasses
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector32_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector32_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector32_delete(@_memaddr, idx)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
delete_at(length-1) while length > v.length # match lengthes
|
||||
v.each_with_index { |e, i| self[i] = e } # patch entries
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_vector_init(@_memaddr)
|
||||
end
|
||||
|
||||
def clear
|
||||
delete_at(length-1) while length > 0
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, 0)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
end
|
||||
@_tg._at(valueptr_at(idx))._set(v)
|
||||
end
|
||||
def push(v)
|
||||
self[length] = v
|
||||
self
|
||||
end
|
||||
def <<(v) ; push(v) ; end
|
||||
def pop
|
||||
l = length
|
||||
if l > 0
|
||||
v = self[l-1]
|
||||
delete_at(l-1)
|
||||
end
|
||||
v
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
# do a binary search in an ordered vector for a specific target attribute
|
||||
# ex: world.history.figures.binsearch(unit.hist_figure_id)
|
||||
def binsearch(target, field=:id)
|
||||
o_start = 0
|
||||
o_end = length - 1
|
||||
while o_end >= o_start
|
||||
o_half = o_start + (o_end-o_start)/2
|
||||
obj = self[o_half]
|
||||
oval = obj.send(field)
|
||||
if oval == target
|
||||
return obj
|
||||
elsif oval < target
|
||||
o_start = o_half+1
|
||||
else
|
||||
o_end = o_half-1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlVector16 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector16_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector16_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector16_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector16_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlVector8 < StlVector32
|
||||
def length
|
||||
DFHack.memory_vector8_length(@_memaddr)
|
||||
end
|
||||
def valueptr_at(idx)
|
||||
DFHack.memory_vector8_ptrat(@_memaddr, idx)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vector8_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vector8_delete(@_memaddr, idx)
|
||||
end
|
||||
end
|
||||
class StlBitVector < StlVector32
|
||||
def initialize ; end
|
||||
def length
|
||||
DFHack.memory_vectorbool_length(@_memaddr)
|
||||
end
|
||||
def insert_at(idx, val)
|
||||
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
|
||||
end
|
||||
def delete_at(idx)
|
||||
DFHack.memory_vectorbool_delete(@_memaddr, idx)
|
||||
end
|
||||
def [](idx)
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx += length if idx < 0
|
||||
if idx >= length
|
||||
insert_at(idx, v)
|
||||
elsif idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
class StlString < MemStruct
|
||||
def _get
|
||||
DFHack.memory_read_stlstring(@_memaddr)
|
||||
end
|
||||
|
||||
def _set(v)
|
||||
DFHack.memory_write_stlstring(@_memaddr, v)
|
||||
end
|
||||
|
||||
def _cpp_init
|
||||
DFHack.memory_stlstring_init(@_memaddr)
|
||||
end
|
||||
end
|
||||
class StlDeque < MemStruct
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
|
||||
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
|
||||
def inspect ; "#<StlDeque>" ; end
|
||||
end
|
||||
|
||||
class DfFlagarray < MemStruct
|
||||
attr_accessor :_indexenum
|
||||
def initialize(indexenum)
|
||||
@_indexenum = indexenum
|
||||
end
|
||||
def length
|
||||
DFHack.memory_bitarray_length(@_memaddr)
|
||||
end
|
||||
# TODO _cpp_init
|
||||
def size ; length ; end
|
||||
def resize(len)
|
||||
DFHack.memory_bitarray_resize(@_memaddr, len)
|
||||
end
|
||||
def [](idx)
|
||||
idx = _indexenum.to_i(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
|
||||
end
|
||||
def []=(idx, v)
|
||||
idx = _indexenum.to_i(idx) if _indexenum
|
||||
idx += length if idx < 0
|
||||
if idx >= length or idx < 0
|
||||
raise 'invalid idx'
|
||||
else
|
||||
DFHack.memory_bitarray_set(@_memaddr, idx, v)
|
||||
end
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfArray < Compound
|
||||
attr_accessor :_tglen, :_tg
|
||||
def initialize(tglen, tg)
|
||||
@_tglen = tglen
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_length, 4) { number 16, false }
|
||||
|
||||
def length ; _length ; end
|
||||
def size ; _length ; end
|
||||
# TODO _cpp_init
|
||||
def _tgat(i)
|
||||
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
|
||||
end
|
||||
def [](i)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._get
|
||||
end
|
||||
def []=(i, v)
|
||||
i += _length if i < 0
|
||||
_tgat(i)._set(v)
|
||||
end
|
||||
def _set(a)
|
||||
a.each_with_index { |v, i| self[i] = v }
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
end
|
||||
class DfLinkedList < Compound
|
||||
attr_accessor :_tg
|
||||
def initialize(tg)
|
||||
@_tg = tg
|
||||
end
|
||||
|
||||
field(:_ptr, 0) { number 32, false }
|
||||
field(:_prev, 4) { number 32, false }
|
||||
field(:_next, 8) { number 32, false }
|
||||
|
||||
def item
|
||||
# With the current xml structure, currently _tg designate
|
||||
# the type of the 'next' and 'prev' fields, not 'item'.
|
||||
# List head has item == NULL, so we can safely return nil.
|
||||
|
||||
#addr = _ptr
|
||||
#return if addr == 0
|
||||
#@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def item=(v)
|
||||
#addr = _ptr
|
||||
#raise 'null pointer' if addr == 0
|
||||
#@_tg.at(addr)._set(v)
|
||||
raise 'null pointer'
|
||||
end
|
||||
|
||||
def prev
|
||||
addr = _prev
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
def next
|
||||
addr = _next
|
||||
return if addr == 0
|
||||
@_tg._at(addr)._get
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
def each
|
||||
o = self
|
||||
while o
|
||||
yield o.item if o.item
|
||||
o = o.next
|
||||
end
|
||||
end
|
||||
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
|
||||
end
|
||||
|
||||
class Global < MemStruct
|
||||
attr_accessor :_glob
|
||||
def initialize(glob)
|
||||
@_glob = glob
|
||||
end
|
||||
def _at(addr)
|
||||
g = DFHack.const_get(@_glob)
|
||||
g = DFHack.rtti_getclassat(g, addr)
|
||||
g.new._at(addr)
|
||||
end
|
||||
def inspect ; "#<#{@_glob}>" ; end
|
||||
end
|
||||
end # module MemHack
|
||||
|
||||
|
||||
# cpp rtti name -> rb class
|
||||
@rtti_n2c = {}
|
||||
@rtti_c2n = {}
|
||||
|
||||
# cpp rtti name -> vtable ptr
|
||||
@rtti_n2v = {}
|
||||
@rtti_v2n = {}
|
||||
|
||||
def self.rtti_n2c ; @rtti_n2c ; end
|
||||
def self.rtti_c2n ; @rtti_c2n ; end
|
||||
def self.rtti_n2v ; @rtti_n2v ; end
|
||||
def self.rtti_v2n ; @rtti_v2n ; end
|
||||
|
||||
# register a ruby class with a cpp rtti class name
|
||||
def self.rtti_register(cppname, cls)
|
||||
@rtti_n2c[cppname] = cls
|
||||
@rtti_c2n[cls] = cppname
|
||||
end
|
||||
|
||||
# return the ruby class to use for the cpp object at address if rtti info is available
|
||||
def self.rtti_getclassat(cls, addr)
|
||||
if addr != 0 and @rtti_c2n[cls]
|
||||
# rtti info exist for class => cpp object has a vtable
|
||||
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
|
||||
else
|
||||
cls
|
||||
end
|
||||
end
|
||||
|
||||
# try to read the rtti classname from an object vtable pointer
|
||||
def self.rtti_readclassname(vptr)
|
||||
unless n = @rtti_v2n[vptr]
|
||||
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
|
||||
@rtti_n2v[n] = vptr
|
||||
end
|
||||
n
|
||||
end
|
||||
|
||||
# return the vtable pointer from the cpp rtti name
|
||||
def self.rtti_getvtable(cppname)
|
||||
unless v = @rtti_n2v[cppname]
|
||||
v = get_vtable(cppname.to_s)
|
||||
@rtti_n2v[cppname] = v
|
||||
@rtti_v2n[v] = cppname if v != 0
|
||||
end
|
||||
v if v != 0
|
||||
end
|
||||
|
||||
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
|
||||
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
|
||||
end
|
||||
|
||||
def self.vmethod_arg(arg)
|
||||
case arg
|
||||
when nil, false; 0
|
||||
when true; 1
|
||||
when Integer; arg
|
||||
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
|
||||
when MemHack::Compound; arg._memaddr
|
||||
else raise "bad vmethod arg #{arg.class}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d
|
||||
Subproject commit 17b653665567a5f1df628217820f76bb0b9c70a5
|
@ -0,0 +1,938 @@
|
||||
-- Find some offsets for linux.
|
||||
|
||||
local utils = require 'utils'
|
||||
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
|
||||
end
|
||||
|
||||
collectgarbage()
|
||||
|
||||
print[[
|
||||
WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS.
|
||||
|
||||
Running this script on a new DF version will NOT
|
||||
MAKE IT RUN CORRECTLY if any data structures
|
||||
changed, thus possibly leading to CRASHES AND/OR
|
||||
PERMANENT SAVE CORRUPTION.
|
||||
|
||||
Finding the first few globals requires this script to be
|
||||
started immediately after loading the game, WITHOUT
|
||||
first loading a world. The rest expect a loaded save,
|
||||
not a fresh embark. Finding current_weather requires
|
||||
a special save previously processed with devel/prepare-save
|
||||
on a DF version with working dfhack.
|
||||
|
||||
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
|
||||
return
|
||||
end
|
||||
|
||||
-- Data segment location
|
||||
|
||||
local data = ms.get_data_segment()
|
||||
if not data then
|
||||
qerror('Could not find data segment')
|
||||
end
|
||||
|
||||
print('\nData section: '..tostring(data))
|
||||
if data.size < 5000000 then
|
||||
qerror('Data segment too short.')
|
||||
end
|
||||
|
||||
local searcher = ms.DiffSearcher.new(data)
|
||||
|
||||
local function validate_offset(name,validator,addr,tname,...)
|
||||
local obj = data:object_by_field(addr,tname,...)
|
||||
if obj and not validator(obj) then
|
||||
obj = nil
|
||||
end
|
||||
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 }
|
||||
end
|
||||
local search = force_scan['all']
|
||||
for _,v in ipairs(names) do
|
||||
if force_scan[v] or not is_known(v) then
|
||||
search = true
|
||||
end
|
||||
end
|
||||
if search then
|
||||
if not dfhack.safecall(finder) then
|
||||
if not utils.prompt_yes_no('Proceed with the rest of the script?') then
|
||||
searcher:reset()
|
||||
qerror('Quit')
|
||||
end
|
||||
end
|
||||
else
|
||||
print('Already known: '..table.concat(names,', '))
|
||||
end
|
||||
end
|
||||
|
||||
local ordinal_names = {
|
||||
[0] = '1st entry',
|
||||
[1] = '2nd entry',
|
||||
[2] = '3rd entry'
|
||||
}
|
||||
setmetatable(ordinal_names, {
|
||||
__index = function(self,idx) return (idx+1)..'th entry' end
|
||||
})
|
||||
|
||||
local function list_index_choices(length_func)
|
||||
return function(id)
|
||||
if id > 0 then
|
||||
local ok, len = pcall(length_func)
|
||||
if not ok then
|
||||
len = 5
|
||||
elseif len > 10 then
|
||||
len = 10
|
||||
end
|
||||
return id % len
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Cursor group
|
||||
--
|
||||
|
||||
local function find_cursor()
|
||||
print('\nPlease navigate to the title screen to find cursor.')
|
||||
if not utils.prompt_yes_no('Proceed?', true) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Unpadded version
|
||||
local idx, addr = data.int32_t:find_one{
|
||||
-30000, -30000, -30000,
|
||||
-30000, -30000, -30000, -30000, -30000, -30000,
|
||||
df.game_mode.NONE, df.game_type.NONE
|
||||
}
|
||||
if idx then
|
||||
ms.found_offset('cursor', addr)
|
||||
ms.found_offset('selection_rect', addr + 12)
|
||||
ms.found_offset('gamemode', addr + 12 + 24)
|
||||
ms.found_offset('gametype', addr + 12 + 24 + 4)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Padded version
|
||||
idx, addr = data.int32_t:find_one{
|
||||
-30000, -30000, -30000, 0,
|
||||
-30000, -30000, -30000, -30000, -30000, -30000, 0, 0,
|
||||
df.game_mode.NONE, 0, 0, 0, df.game_type.NONE
|
||||
}
|
||||
if idx then
|
||||
ms.found_offset('cursor', addr)
|
||||
ms.found_offset('selection_rect', addr + 0x10)
|
||||
ms.found_offset('gamemode', addr + 0x30)
|
||||
ms.found_offset('gametype', addr + 0x40)
|
||||
return true
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find cursor.')
|
||||
return false
|
||||
end
|
||||
|
||||
--
|
||||
-- Announcements
|
||||
--
|
||||
|
||||
local function find_announcements()
|
||||
local idx, addr = data.int32_t:find_one{
|
||||
25, 25, 31, 31, 24, 24, 40, 40, 40, 40, 40, 40, 40
|
||||
}
|
||||
if idx then
|
||||
ms.found_offset('announcements', addr)
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find announcements.')
|
||||
end
|
||||
|
||||
--
|
||||
-- d_init
|
||||
--
|
||||
|
||||
local function is_valid_d_init(di)
|
||||
if di.sky_tile ~= 178 then
|
||||
print('Sky tile expected 178, found: '..di.sky_tile)
|
||||
if not utils.prompt_yes_no('Ignore?') then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local ann = is_known 'announcements'
|
||||
local size,ptr = di:sizeof()
|
||||
if ann and ptr+size ~= ann then
|
||||
print('Announcements not immediately after d_init.')
|
||||
if not utils.prompt_yes_no('Ignore?') then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_d_init()
|
||||
local idx, addr = data.int16_t:find_one{
|
||||
1,0, 2,0, 5,0, 25,0, -- path_cost
|
||||
4,4, -- embark_rect
|
||||
20,1000,1000,1000,1000 -- store_dist
|
||||
}
|
||||
if idx then
|
||||
validate_offset('d_init', is_valid_d_init, addr, df.d_init, 'path_cost')
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find d_init')
|
||||
end
|
||||
|
||||
--
|
||||
-- gview
|
||||
--
|
||||
|
||||
local function find_gview()
|
||||
local vs_vtable = dfhack.internal.getVTable('viewscreenst')
|
||||
if not vs_vtable then
|
||||
dfhack.printerr('Cannot search for gview - no viewscreenst vtable.')
|
||||
return
|
||||
end
|
||||
|
||||
local idx, addr = data.uint32_t:find_one{0, vs_vtable}
|
||||
if idx then
|
||||
ms.found_offset('gview', addr)
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find gview')
|
||||
end
|
||||
|
||||
--
|
||||
-- enabler
|
||||
--
|
||||
|
||||
local function is_valid_enabler(e)
|
||||
if not ms.is_valid_vector(e.textures.raws, 4)
|
||||
or not ms.is_valid_vector(e.text_system, 4)
|
||||
then
|
||||
dfhack.printerr('Vector layout check failed.')
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_enabler()
|
||||
-- Data from data/init/colors.txt
|
||||
local colors = {
|
||||
0, 0, 0, 0, 0, 128, 0, 128, 0,
|
||||
0, 128, 128, 128, 0, 0, 128, 0, 128,
|
||||
128, 128, 0, 192, 192, 192, 128, 128, 128,
|
||||
0, 0, 255, 0, 255, 0, 0, 255, 255,
|
||||
255, 0, 0, 255, 0, 255, 255, 255, 0,
|
||||
255, 255, 255
|
||||
}
|
||||
|
||||
for i = 1,#colors do colors[i] = colors[i]/255 end
|
||||
|
||||
local idx, addr = data.float:find_one(colors)
|
||||
if idx then
|
||||
validate_offset('enabler', is_valid_enabler, addr, df.enabler, 'ccolor')
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find enabler')
|
||||
end
|
||||
|
||||
--
|
||||
-- gps
|
||||
--
|
||||
|
||||
local function is_valid_gps(g)
|
||||
if g.clipx[0] < 0 or g.clipx[0] > g.clipx[1] or g.clipx[1] >= g.dimx then
|
||||
dfhack.printerr('Invalid clipx: ', g.clipx[0], g.clipx[1], g.dimx)
|
||||
end
|
||||
if g.clipy[0] < 0 or g.clipy[0] > g.clipy[1] or g.clipy[1] >= g.dimy then
|
||||
dfhack.printerr('Invalid clipy: ', g.clipy[0], g.clipy[1], g.dimy)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_gps()
|
||||
print('\nPlease ensure the mouse cursor is not over the game window.')
|
||||
if not utils.prompt_yes_no('Proceed?', true) then
|
||||
return
|
||||
end
|
||||
|
||||
local zone
|
||||
if os_type == 'windows' or os_type == 'linux' then
|
||||
zone = zoomed_searcher('cursor', 0x1000)
|
||||
elseif os_type == 'darwin' then
|
||||
zone = zoomed_searcher('enabler', 0x1000)
|
||||
end
|
||||
zone = zone or searcher
|
||||
|
||||
local w,h = ms.get_screen_size()
|
||||
|
||||
local idx, addr = zone.area.int32_t:find_one{w, h, -1, -1}
|
||||
if idx then
|
||||
validate_offset('gps', is_valid_gps, addr, df.graphic, 'dimx')
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find gps')
|
||||
end
|
||||
|
||||
--
|
||||
-- World
|
||||
--
|
||||
|
||||
local function is_valid_world(world)
|
||||
if not ms.is_valid_vector(world.units.all, 4)
|
||||
or not ms.is_valid_vector(world.units.bad, 4)
|
||||
or not ms.is_valid_vector(world.history.figures, 4)
|
||||
or not ms.is_valid_vector(world.cur_savegame.map_features, 4)
|
||||
then
|
||||
dfhack.printerr('Vector layout check failed.')
|
||||
return false
|
||||
end
|
||||
|
||||
if #world.units.all == 0 or #world.units.all ~= #world.units.bad then
|
||||
print('Different or zero size of units.all and units.bad:'..#world.units.all..' vs '..#world.units.bad)
|
||||
if not utils.prompt_yes_no('Ignore?') then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_world()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for world. Please open the stockpile creation
|
||||
menu, and select different types as instructed below:]],
|
||||
'int32_t',
|
||||
{ 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' },
|
||||
df.stockpile_category
|
||||
)
|
||||
validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type')
|
||||
end
|
||||
|
||||
--
|
||||
-- UI
|
||||
--
|
||||
|
||||
local function is_valid_ui(ui)
|
||||
if not ms.is_valid_vector(ui.economic_stone, 1)
|
||||
or not ms.is_valid_vector(ui.dipscripts, 4)
|
||||
then
|
||||
dfhack.printerr('Vector layout check failed.')
|
||||
return false
|
||||
end
|
||||
|
||||
if ui.follow_item ~= -1 or ui.follow_unit ~= -1 then
|
||||
print('Invalid follow state: '..ui.follow_item..', '..ui.follow_unit)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_ui()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui. Please open the designation
|
||||
menu, and switch modes as instructed below:]],
|
||||
'int16_t',
|
||||
{ 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', 'DesignateUpStair',
|
||||
'DesignateDownStair', 'DesignateUpDownStair', 'DesignateUpRamp', 'DesignateChopTrees' },
|
||||
df.ui_sidebar_mode
|
||||
)
|
||||
validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode')
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_sidebar_menus
|
||||
--
|
||||
|
||||
local function is_valid_ui_sidebar_menus(usm)
|
||||
if not ms.is_valid_vector(usm.workshop_job.choices_all, 4)
|
||||
or not ms.is_valid_vector(usm.workshop_job.choices_visible, 4)
|
||||
then
|
||||
dfhack.printerr('Vector layout check failed.')
|
||||
return false
|
||||
end
|
||||
|
||||
if #usm.workshop_job.choices_all == 0
|
||||
or #usm.workshop_job.choices_all ~= #usm.workshop_job.choices_visible then
|
||||
print('Different or zero size of visible and all choices:'..
|
||||
#usm.workshop_job.choices_all..' vs '..#usm.workshop_job.choices_visible)
|
||||
if not utils.prompt_yes_no('Ignore?') then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_ui_sidebar_menus()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
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
|
||||
)
|
||||
validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus,
|
||||
addr, df.ui_sidebar_menus, 'workshop_job', 'cursor')
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_build_selector
|
||||
--
|
||||
|
||||
local function is_valid_ui_build_selector(ubs)
|
||||
if not ms.is_valid_vector(ubs.requirements, 4)
|
||||
or not ms.is_valid_vector(ubs.choices, 4)
|
||||
then
|
||||
dfhack.printerr('Vector layout check failed.')
|
||||
return false
|
||||
end
|
||||
|
||||
if ubs.building_type ~= df.building_type.Trap
|
||||
or ubs.building_subtype ~= df.trap_type.PressurePlate then
|
||||
print('Invalid building type and subtype:'..ubs.building_type..','..ubs.building_subtype)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_ui_build_selector()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_build_selector. Please start constructing
|
||||
a pressure plate, and enable creatures. Then change the min
|
||||
weight as requested, remembering that the ui truncates the
|
||||
number, so when it shows "Min (5000df", it means 50000:]],
|
||||
'int32_t',
|
||||
{ 50000, 49000, 48000, 47000, 46000, 45000, 44000 }
|
||||
)
|
||||
validate_offset('ui_build_selector', is_valid_ui_build_selector,
|
||||
addr, df.ui_build_selector, 'plate_info', 'unit_min')
|
||||
end
|
||||
|
||||
--
|
||||
-- init
|
||||
--
|
||||
|
||||
local function is_valid_init(i)
|
||||
-- derived from curses_*.png image sizes presumably
|
||||
if i.font.small_font_dispx ~= 8 or i.font.small_font_dispy ~= 12 or
|
||||
i.font.large_font_dispx ~= 10 or i.font.large_font_dispy ~= 12 then
|
||||
print('Unexpected font sizes: ',
|
||||
i.font.small_font_dispx, i.font.small_font_dispy,
|
||||
i.font.large_font_dispx, i.font.large_font_dispy)
|
||||
if not utils.prompt_yes_no('Ignore?') then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function find_init()
|
||||
local zone
|
||||
if os_type == 'windows' then
|
||||
zone = zoomed_searcher('ui_build_selector', 0x3000)
|
||||
elseif os_type == 'linux' or os_type == 'darwin' then
|
||||
zone = zoomed_searcher('d_init', -0x2000)
|
||||
end
|
||||
zone = zone or searcher
|
||||
|
||||
local idx, addr = zone.area.int32_t:find_one{250, 150, 15, 0}
|
||||
if idx then
|
||||
validate_offset('init', is_valid_init, addr, df.init, 'input', 'hold_time')
|
||||
return
|
||||
end
|
||||
|
||||
local w,h = ms.get_screen_size()
|
||||
|
||||
local idx, addr = zone.area.int32_t:find_one{w, h}
|
||||
if idx then
|
||||
validate_offset('init', is_valid_init, addr, df.init, 'display', 'grid_x')
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find init')
|
||||
end
|
||||
|
||||
--
|
||||
-- current_weather
|
||||
--
|
||||
|
||||
local function find_current_weather()
|
||||
print('\nPlease load the save previously processed with prepare-save.')
|
||||
if not utils.prompt_yes_no('Proceed?', true) then
|
||||
return
|
||||
end
|
||||
|
||||
local zone
|
||||
if os_type == 'windows' then
|
||||
zone = zoomed_searcher('crime_next_id', 512)
|
||||
elseif os_type == 'darwin' then
|
||||
zone = zoomed_searcher('cursor', -64)
|
||||
elseif os_type == 'linux' then
|
||||
zone = zoomed_searcher('ui_building_assign_type', -512)
|
||||
end
|
||||
zone = zone or searcher
|
||||
|
||||
local wbytes = {
|
||||
2, 1, 0, 2, 0,
|
||||
1, 2, 1, 0, 0,
|
||||
2, 0, 2, 1, 2,
|
||||
1, 2, 0, 1, 1,
|
||||
2, 0, 1, 0, 2
|
||||
}
|
||||
|
||||
local idx, addr = zone.area.int8_t:find_one(wbytes)
|
||||
if idx then
|
||||
ms.found_offset('current_weather', addr)
|
||||
return
|
||||
end
|
||||
|
||||
dfhack.printerr('Could not find current_weather - must be a wrong save.')
|
||||
end
|
||||
|
||||
--
|
||||
-- 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
|
||||
--
|
||||
|
||||
local function find_ui_selected_unit()
|
||||
if not is_known 'world' then
|
||||
dfhack.printerr('Cannot search for ui_selected_unit: no world')
|
||||
return
|
||||
end
|
||||
|
||||
for i,unit in ipairs(df.global.world.units.active) do
|
||||
dfhack.units.setNickname(unit, i)
|
||||
end
|
||||
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_selected_unit. Please activate the 'v'
|
||||
mode, point it at units, and enter their numeric nickname
|
||||
into the prompts below:]],
|
||||
'int32_t',
|
||||
function()
|
||||
return utils.prompt_input(' Enter index: ', utils.check_number)
|
||||
end,
|
||||
'noprompt'
|
||||
)
|
||||
ms.found_offset('ui_selected_unit', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_unit_view_mode
|
||||
--
|
||||
|
||||
local function find_ui_unit_view_mode()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_unit_view_mode. Having selected a unit
|
||||
with 'v', switch the pages as requested:]],
|
||||
'int32_t',
|
||||
{ 'General', 'Inventory', 'Preferences', 'Wounds' },
|
||||
df.ui_unit_view_mode.T_value
|
||||
)
|
||||
ms.found_offset('ui_unit_view_mode', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_look_cursor
|
||||
--
|
||||
|
||||
local function look_item_list_count()
|
||||
return #df.global.ui_look_list.items
|
||||
end
|
||||
|
||||
local function find_ui_look_cursor()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_look_cursor. Please activate the 'k'
|
||||
mode, find a tile with many items or units on the ground,
|
||||
and select list entries as instructed:]],
|
||||
'int32_t',
|
||||
list_index_choices(look_item_list_count),
|
||||
ordinal_names
|
||||
)
|
||||
ms.found_offset('ui_look_cursor', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_building_item_cursor
|
||||
--
|
||||
|
||||
local function building_item_list_count()
|
||||
return #df.global.world.selected_building.contained_items
|
||||
end
|
||||
|
||||
local function find_ui_building_item_cursor()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_building_item_cursor. Please activate the 't'
|
||||
mode, find a cluttered workshop, trade depot, or other building
|
||||
with many contained items, and select as instructed:]],
|
||||
'int32_t',
|
||||
list_index_choices(building_item_list_count),
|
||||
ordinal_names
|
||||
)
|
||||
ms.found_offset('ui_building_item_cursor', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_workshop_in_add
|
||||
--
|
||||
|
||||
local function find_ui_workshop_in_add()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
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: If not done after first 3-4 steps, resize the game window.]],
|
||||
'int8_t',
|
||||
{ 1, 0 },
|
||||
{ [1] = 'enter the add job menu',
|
||||
[0] = 'add job, thus exiting the menu' }
|
||||
)
|
||||
ms.found_offset('ui_workshop_in_add', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_workshop_job_cursor
|
||||
--
|
||||
|
||||
local function workshop_job_list_count()
|
||||
return #df.global.world.selected_building.jobs
|
||||
end
|
||||
|
||||
local function find_ui_workshop_job_cursor()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
Searching for ui_workshop_job_cursor. Please activate the 'q'
|
||||
mode, find a workshop with many jobs, and select as instructed:]],
|
||||
'int32_t',
|
||||
list_index_choices(workshop_job_list_count),
|
||||
ordinal_names
|
||||
)
|
||||
ms.found_offset('ui_workshop_job_cursor', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_building_in_assign
|
||||
--
|
||||
|
||||
local function find_ui_building_in_assign()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
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: If not done after first 3-4 steps, resize the game window.]],
|
||||
'int8_t',
|
||||
{ 1, 0 },
|
||||
{ [1] = 'enter the Assign owner menu',
|
||||
[0] = 'press Esc to exit assign' }
|
||||
)
|
||||
ms.found_offset('ui_building_in_assign', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- ui_building_in_resize
|
||||
--
|
||||
|
||||
local function find_ui_building_in_resize()
|
||||
local addr = searcher:find_menu_cursor([[
|
||||
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: If not done after first 3-4 steps, resize the game window.]],
|
||||
'int8_t',
|
||||
{ 1, 0 },
|
||||
{ [1] = 'enter the Resize room mode',
|
||||
[0] = 'press Esc to exit resize' }
|
||||
)
|
||||
ms.found_offset('ui_building_in_resize', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- window_x
|
||||
--
|
||||
|
||||
local function find_window_x()
|
||||
local addr = searcher:find_counter([[
|
||||
Searching for window_x. Please exit to main dwarfmode menu,
|
||||
scroll to the LEFT edge, then do as instructed:]],
|
||||
'int32_t', 10,
|
||||
'Please press Right to scroll right one step.'
|
||||
)
|
||||
ms.found_offset('window_x', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- window_y
|
||||
--
|
||||
|
||||
local function find_window_y()
|
||||
local addr = searcher:find_counter([[
|
||||
Searching for window_y. Please exit to main dwarfmode menu,
|
||||
scroll to the TOP edge, then do as instructed:]],
|
||||
'int32_t', 10,
|
||||
'Please press Down to scroll down one step.'
|
||||
)
|
||||
ms.found_offset('window_y', addr)
|
||||
end
|
||||
|
||||
--
|
||||
-- window_z
|
||||
--
|
||||
|
||||
local function find_window_z()
|
||||
local addr = searcher:find_counter([[
|
||||
Searching for window_z. Please exit to main dwarfmode menu,
|
||||
scroll to a Z level near surface, then do as instructed below.
|
||||
|
||||
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
|
||||
|
||||
--
|
||||
-- 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
|
||||
|
||||
--
|
||||
-- process_jobs
|
||||
--
|
||||
|
||||
local function get_process_zone()
|
||||
if os_type == 'windows' then
|
||||
return zoomed_searcher('ui_workshop_job_cursor', 'ui_building_in_resize')
|
||||
elseif os_type == 'linux' or os_type == 'darwin' then
|
||||
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' or os_type == 'darwin' then
|
||||
zone = zoomed_searcher('ui_look_cursor', 32)
|
||||
elseif os_type == 'windows' then
|
||||
zone = zoomed_searcher('ui_workshop_job_cursor', 80)
|
||||
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')
|
||||
exec_finder(find_enabler, 'enabler')
|
||||
exec_finder(find_gps, 'gps')
|
||||
|
||||
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')
|
||||
exec_finder(find_init, 'init')
|
||||
|
||||
print('\nPrimitive globals:\n')
|
||||
|
||||
exec_finder(find_current_weather, 'current_weather')
|
||||
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()
|
@ -0,0 +1,16 @@
|
||||
-- Deletes ALL items not held by units, buildings or jobs.
|
||||
--
|
||||
-- Intended solely for lag investigation.
|
||||
|
||||
local count = 0
|
||||
|
||||
for _,v in ipairs(df.global.world.items.all) do
|
||||
if not (v.flags.in_building or v.flags.construction or v.flags.in_job
|
||||
or dfhack.items.getGeneralRef(v,df.general_ref_type.UNIT_HOLDER)) then
|
||||
count = count + 1
|
||||
v.flags.forbid = true
|
||||
v.flags.garbage_collect = true
|
||||
end
|
||||
end
|
||||
|
||||
print('Deletion requested: '..count)
|
@ -0,0 +1,71 @@
|
||||
-- Prepare the current save for use with devel/find-offsets.
|
||||
|
||||
df.global.pause_state = true
|
||||
|
||||
--[[print('Placing anchor...')
|
||||
|
||||
do
|
||||
local wp = df.global.ui.waypoints
|
||||
|
||||
for _,pt in ipairs(wp.points) do
|
||||
if pt.name == 'dfhack_anchor' then
|
||||
print('Already placed.')
|
||||
goto found
|
||||
end
|
||||
end
|
||||
|
||||
local x,y,z = pos2xyz(df.global.cursor)
|
||||
|
||||
if not x then
|
||||
error("Place cursor at your preferred anchor point.")
|
||||
end
|
||||
|
||||
local id = wp.next_point_id
|
||||
wp.next_point_id = id + 1
|
||||
|
||||
wp.points:insert('#',{
|
||||
new = true, id = id, name = 'dfhack_anchor',
|
||||
comment=(x..','..y..','..z),
|
||||
tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE,
|
||||
pos = xyz2pos(x,y,z)
|
||||
})
|
||||
|
||||
::found::
|
||||
end]]
|
||||
|
||||
print('Nicknaming units...')
|
||||
|
||||
for i,unit in ipairs(df.global.world.units.active) do
|
||||
dfhack.units.setNickname(unit, i..':'..unit.id)
|
||||
end
|
||||
|
||||
print('Setting weather...')
|
||||
|
||||
local wbytes = {
|
||||
2, 1, 0, 2, 0,
|
||||
1, 2, 1, 0, 0,
|
||||
2, 0, 2, 1, 2,
|
||||
1, 2, 0, 1, 1,
|
||||
2, 0, 1, 0, 2
|
||||
}
|
||||
|
||||
for i=0,4 do
|
||||
for j = 0,4 do
|
||||
df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2)
|
||||
end
|
||||
end
|
||||
|
||||
local yearstr = df.global.cur_year..','..df.global.cur_year_tick
|
||||
|
||||
print('Cur year and tick: '..yearstr)
|
||||
|
||||
dfhack.persistent.save{
|
||||
key='prepare-save/cur_year',
|
||||
value=yearstr,
|
||||
ints={df.global.cur_year, df.global.cur_year_tick}
|
||||
}
|
||||
|
||||
-- Save
|
||||
|
||||
dfhack.run_script('quicksave')
|
||||
|
@ -0,0 +1,29 @@
|
||||
-- Remove uninteresting dead units from the unit list.
|
||||
|
||||
local units = df.global.world.units.active
|
||||
local dwarf_race = df.global.ui.race_id
|
||||
local dwarf_civ = df.global.ui.civ_id
|
||||
local count = 0
|
||||
|
||||
for i=#units-1,0,-1 do
|
||||
local unit = units[i]
|
||||
local flags1 = unit.flags1
|
||||
local flags2 = unit.flags2
|
||||
if flags1.dead and unit.race ~= dwarf_race then
|
||||
local remove = false
|
||||
if flags2.slaughter then
|
||||
remove = true
|
||||
elseif not unit.name.has_name then
|
||||
remove = true
|
||||
elseif unit.civ_id ~= dwarf_civ and
|
||||
not (flags1.merchant or flags1.diplomat) then
|
||||
remove = true
|
||||
end
|
||||
if remove then
|
||||
count = count + 1
|
||||
units:erase(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print('Units removed from active: '..count)
|
@ -0,0 +1,49 @@
|
||||
-- Reset item temperature to the value of their tile.
|
||||
|
||||
local count = 0
|
||||
local types = {}
|
||||
|
||||
local function update_temp(item,btemp)
|
||||
if item.temperature ~= btemp then
|
||||
count = count + 1
|
||||
local tid = item:getType()
|
||||
types[tid] = (types[tid] or 0) + 1
|
||||
end
|
||||
item.temperature = btemp
|
||||
item.temperature_fraction = 0
|
||||
|
||||
if item.contaminants then
|
||||
for _,c in ipairs(item.contaminants) do
|
||||
c.temperature = btemp
|
||||
c.temperature_fraction = 0
|
||||
end
|
||||
end
|
||||
|
||||
for _,sub in ipairs(dfhack.items.getContainedItems(item)) do
|
||||
update_temp(sub,btemp)
|
||||
end
|
||||
|
||||
item:checkTemperatureDamage()
|
||||
end
|
||||
|
||||
local last_frame = df.global.world.frame_counter-1
|
||||
|
||||
for _,item in ipairs(df.global.world.items.all) do
|
||||
if item.flags.on_ground and df.item_actual:is_instance(item) and
|
||||
item.temp_updated_frame == last_frame then
|
||||
local pos = item.pos
|
||||
local block = dfhack.maps.getTileBlock(pos)
|
||||
if block then
|
||||
update_temp(item, block.temperature_1[pos.x%16][pos.y%16])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print('Items updated: '..count)
|
||||
|
||||
local tlist = {}
|
||||
for k,_ in pairs(types) do tlist[#tlist+1] = k end
|
||||
table.sort(tlist, function(a,b) return types[a] > types[b] end)
|
||||
for _,k in ipairs(tlist) do
|
||||
print(' '..df.item_type[k]..':', types[k])
|
||||
end
|
Loading…
Reference in New Issue