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)
|
OPTION(DL_RUBY "download libruby from the internet" ON)
|
||||||
if(RUBY_FOUND)
|
IF (DL_RUBY)
|
||||||
ADD_CUSTOM_COMMAND(
|
IF (UNIX)
|
||||||
OUTPUT ruby-autogen.cpp
|
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
|
||||||
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
|
EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
|
||||||
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl
|
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
|
||||||
)
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp)
|
FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
|
||||||
if(CMAKE_COMPILER_IS_GNUCC)
|
SET(RUBYLIB libruby.so)
|
||||||
set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof")
|
ELSE (UNIX)
|
||||||
endif(CMAKE_COMPILER_IS_GNUCC)
|
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
|
||||||
ADD_CUSTOM_COMMAND(
|
EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
|
||||||
OUTPUT ruby-autogen.offsets
|
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
|
||||||
COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
DEPENDS ruby-autogen
|
FILE(RENAME msvcrt-ruby18.dll libruby.dll)
|
||||||
)
|
SET(RUBYLIB libruby.dll)
|
||||||
ADD_CUSTOM_COMMAND(
|
ENDIF(UNIX)
|
||||||
OUTPUT ruby-autogen.rb
|
ENDIF(DL_RUBY)
|
||||||
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_COMMAND(
|
||||||
)
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
|
||||||
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS 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
|
||||||
include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH})
|
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
|
||||||
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
COMMENT ruby-autogen.rb
|
||||||
target_link_libraries(ruby ${RUBY_LIBRARY})
|
)
|
||||||
install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION})
|
ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
|
||||||
else(RUBY_FOUND)
|
|
||||||
MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.")
|
INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
|
||||||
endif(RUBY_FOUND)
|
|
||||||
|
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