develop
commit
39a25856cd
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,314 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||
|
||||
A thread-safe logging console with a line editor for windows.
|
||||
|
||||
Based on linenoise win32 port,
|
||||
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||
All rights reserved.
|
||||
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||
|
||||
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.
|
||||
* Neither the name of Redis nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
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 OWNER 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 <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <md5wrapper.h>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
typedef unsigned char patch_byte;
|
||||
|
||||
struct BinaryPatch {
|
||||
struct Byte {
|
||||
unsigned offset;
|
||||
patch_byte old_val, new_val;
|
||||
};
|
||||
enum State {
|
||||
Conflict = 0,
|
||||
Unapplied = 1,
|
||||
Applied = 2,
|
||||
Partial = 3
|
||||
};
|
||||
|
||||
std::vector<Byte> entries;
|
||||
|
||||
bool loadDIF(std::string name);
|
||||
State checkState(const patch_byte *ptr, size_t len);
|
||||
|
||||
void apply(patch_byte *ptr, size_t len, bool newv);
|
||||
};
|
||||
|
||||
inline bool is_hex(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
bool BinaryPatch::loadDIF(std::string name)
|
||||
{
|
||||
entries.clear();
|
||||
|
||||
std::ifstream infile(name);
|
||||
if(infile.bad())
|
||||
{
|
||||
cerr << "Cannot open file: " << name << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string s;
|
||||
while(std::getline(infile, s))
|
||||
{
|
||||
// Parse lines that begin with "[0-9a-f]+:"
|
||||
size_t idx = s.find(':');
|
||||
if (idx == std::string::npos || idx == 0 || idx > 8)
|
||||
continue;
|
||||
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < idx; i++)
|
||||
if (!is_hex(s[i]))
|
||||
ok = false;
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
unsigned off, oval, nval;
|
||||
int nchar = 0;
|
||||
int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar);
|
||||
|
||||
if (cnt < 3)
|
||||
{
|
||||
cerr << "Could not parse: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = nchar; i < s.size(); i++)
|
||||
{
|
||||
if (!isspace(s[i]))
|
||||
{
|
||||
cerr << "Garbage at end of line: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (oval >= 256 || nval >= 256)
|
||||
{
|
||||
cerr << "Invalid byte values: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Byte bv = { off, patch_byte(oval), patch_byte(nval) };
|
||||
entries.push_back(bv);
|
||||
}
|
||||
|
||||
if (entries.empty())
|
||||
{
|
||||
cerr << "No lines recognized." << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
|
||||
{
|
||||
int state = 0;
|
||||
|
||||
for (size_t i = 0; i < entries.size(); i++)
|
||||
{
|
||||
Byte &bv = entries[i];
|
||||
|
||||
if (bv.offset >= len)
|
||||
{
|
||||
cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl;
|
||||
return Conflict;
|
||||
}
|
||||
|
||||
patch_byte cv = ptr[bv.offset];
|
||||
if (bv.old_val == cv)
|
||||
state |= Unapplied;
|
||||
else if (bv.new_val == cv)
|
||||
state |= Applied;
|
||||
else
|
||||
{
|
||||
cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val
|
||||
<< ", but currently " << cv << std::dec << endl;
|
||||
return Conflict;
|
||||
}
|
||||
}
|
||||
|
||||
return State(state);
|
||||
}
|
||||
|
||||
void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv)
|
||||
{
|
||||
for (size_t i = 0; i < entries.size(); i++)
|
||||
{
|
||||
Byte &bv = entries[i];
|
||||
assert (bv.offset < len);
|
||||
|
||||
ptr[bv.offset] = (newv ? bv.new_val : bv.old_val);
|
||||
}
|
||||
}
|
||||
|
||||
bool load_file(std::vector<patch_byte> *pvec, std::string fname)
|
||||
{
|
||||
FILE *f = fopen(fname.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
cerr << "Cannot open file: " << fname << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
pvec->resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
size_t cnt = fread(pvec->data(), 1, pvec->size(), f);
|
||||
fclose(f);
|
||||
|
||||
return cnt == pvec->size();
|
||||
}
|
||||
|
||||
bool save_file(const std::vector<patch_byte> &pvec, std::string fname)
|
||||
{
|
||||
FILE *f = fopen(fname.c_str(), "wb");
|
||||
if (!f)
|
||||
{
|
||||
cerr << "Cannot open file: " << fname << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f);
|
||||
fclose(f);
|
||||
|
||||
return cnt == pvec.size();
|
||||
}
|
||||
|
||||
std::string compute_hash(const std::vector<patch_byte> &pvec)
|
||||
{
|
||||
md5wrapper md5;
|
||||
return md5.getHashFromBytes(pvec.data(), pvec.size());
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
if (argc <= 3)
|
||||
{
|
||||
cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::string cmd = argv[1];
|
||||
|
||||
if (cmd != "check" && cmd != "apply" && cmd != "remove")
|
||||
{
|
||||
cerr << "Invalid command: " << cmd << endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::string exe_file = argv[2];
|
||||
std::vector<patch_byte> bindata;
|
||||
if (!load_file(&bindata, exe_file))
|
||||
return 2;
|
||||
|
||||
BinaryPatch patch;
|
||||
if (!patch.loadDIF(argv[3]))
|
||||
return 2;
|
||||
|
||||
BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size());
|
||||
if (state == BinaryPatch::Conflict)
|
||||
return 1;
|
||||
|
||||
if (cmd == "check")
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case BinaryPatch::Unapplied:
|
||||
cout << "Currently not applied." << endl;
|
||||
break;
|
||||
case BinaryPatch::Applied:
|
||||
cout << "Currently applied." << endl;
|
||||
break;
|
||||
case BinaryPatch::Partial:
|
||||
cout << "Currently partially applied." << endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (cmd == "apply")
|
||||
{
|
||||
if (state == BinaryPatch::Applied)
|
||||
{
|
||||
cout << "Already applied." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
patch.apply(bindata.data(), bindata.size(), true);
|
||||
}
|
||||
else if (cmd == "remove")
|
||||
{
|
||||
if (state == BinaryPatch::Unapplied)
|
||||
{
|
||||
cout << "Already removed." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
patch.apply(bindata.data(), bindata.size(), false);
|
||||
}
|
||||
|
||||
if (!save_file(bindata, exe_file + ".bak"))
|
||||
{
|
||||
cerr << "Could not create backup." << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!save_file(bindata, exe_file))
|
||||
return 1;
|
||||
|
||||
cout << "Patched " << patch.entries.size()
|
||||
<< " bytes, new hash: " << compute_hash(bindata) << endl;
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
-- Support for scripted interaction sequences via coroutines.
|
||||
|
||||
local _ENV = mkmodule('gui.script')
|
||||
|
||||
local dlg = require('gui.dialogs')
|
||||
|
||||
--[[
|
||||
Example:
|
||||
|
||||
start(function()
|
||||
sleep(100, 'frames')
|
||||
print(showYesNoPrompt('test', 'print true?'))
|
||||
end)
|
||||
]]
|
||||
|
||||
-- Table of running background scripts.
|
||||
if not scripts then
|
||||
scripts = {}
|
||||
setmetatable(scripts, { __mode = 'k' })
|
||||
end
|
||||
|
||||
local function do_resume(inst, ...)
|
||||
inst.gen = inst.gen + 1
|
||||
return (dfhack.saferesume(inst.coro, ...))
|
||||
end
|
||||
|
||||
-- Starts a new background script by calling the function.
|
||||
function start(fn,...)
|
||||
local coro = coroutine.create(fn)
|
||||
local inst = {
|
||||
coro = coro,
|
||||
gen = 0,
|
||||
}
|
||||
scripts[coro] = inst
|
||||
return do_resume(inst, ...)
|
||||
end
|
||||
|
||||
-- Checks if called from a background script
|
||||
function in_script()
|
||||
return scripts[coroutine.running()] ~= nil
|
||||
end
|
||||
|
||||
local function getinst()
|
||||
local inst = scripts[coroutine.running()]
|
||||
if not inst then
|
||||
error('Not in a gui script coroutine.')
|
||||
end
|
||||
return inst
|
||||
end
|
||||
|
||||
local function invoke_resume(inst,gen,quiet,...)
|
||||
local state = coroutine.status(inst.coro)
|
||||
if state ~= 'suspended' then
|
||||
if state ~= 'dead' then
|
||||
dfhack.printerr(debug.traceback('resuming a non-waiting continuation'))
|
||||
end
|
||||
elseif inst.gen > gen then
|
||||
if not quiet then
|
||||
dfhack.printerr(debug.traceback('resuming an expired continuation'))
|
||||
end
|
||||
else
|
||||
do_resume(inst, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns a callback that resumes the script from wait with given return values
|
||||
function mkresume(...)
|
||||
local inst = getinst()
|
||||
return curry(invoke_resume, inst, inst.gen, false, ...)
|
||||
end
|
||||
|
||||
-- Like mkresume, but does not complain if already resumed from this wait
|
||||
function qresume(...)
|
||||
local inst = getinst()
|
||||
return curry(invoke_resume, inst, inst.gen, true, ...)
|
||||
end
|
||||
|
||||
-- Wait until a mkresume callback is called, then return its arguments.
|
||||
-- Once it returns, all mkresume callbacks created before are invalidated.
|
||||
function wait()
|
||||
getinst() -- check that the context is right
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
-- Wraps dfhack.timeout for coroutines.
|
||||
function sleep(time, quantity)
|
||||
if dfhack.timeout(time, quantity, mkresume()) then
|
||||
wait()
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Some dialog wrappers:
|
||||
|
||||
function showMessage(title, text, tcolor)
|
||||
dlg.MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showYesNoPrompt(title, text, tcolor)
|
||||
dlg.MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_accept = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showInputPrompt(title, text, tcolor, input, min_width)
|
||||
dlg.InputBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
input = input,
|
||||
frame_width = min_width,
|
||||
on_input = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showListPrompt(title, text, tcolor, choices, min_width)
|
||||
dlg.ListBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
choices = choices,
|
||||
frame_width = min_width,
|
||||
on_select = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,22 @@
|
||||
building_spatter
|
||||
|
||||
[OBJECT:BUILDING]
|
||||
|
||||
[BUILDING_WORKSHOP:GREASING_STATION]
|
||||
[NAME:Greasing Station]
|
||||
[NAME_COLOR:2:0:1]
|
||||
[DIM:1:1]
|
||||
[WORK_LOCATION:1:1]
|
||||
[BUILD_LABOR:DYER]
|
||||
[BUILD_KEY:CUSTOM_ALT_G]
|
||||
[BLOCK:1:0]
|
||||
[TILE:0:1:150]
|
||||
[COLOR:0:1:0:0:1]
|
||||
[TILE:1:1:150]
|
||||
[COLOR:1:1:MAT]
|
||||
[TILE:2:1:8]
|
||||
[COLOR:2:1:MAT]
|
||||
[TILE:3:1:8]
|
||||
[COLOR:3:1:7:5:0]
|
||||
[BUILD_ITEM:1:BUCKET:NONE:NONE:NONE][CAN_USE_ARTIFACT]
|
||||
[BUILD_ITEM:1:NONE:NONE:NONE:NONE][BUILDMAT]
|
@ -1,27 +1,43 @@
|
||||
# remove bad thoughts for the selected unit or the whole fort
|
||||
|
||||
# with removebadthoughts -v, dump the bad thoughts types we removed
|
||||
verbose = $script_args.delete('-v')
|
||||
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
|
||||
|
||||
if u = df.unit_find(:selected)
|
||||
targets = [u]
|
||||
else
|
||||
targets = df.unit_citizens
|
||||
end
|
||||
$script_args << 'all' if dry_run and $script_args.empty?
|
||||
|
||||
seenbad = Hash.new(0)
|
||||
|
||||
targets.each { |u|
|
||||
clear_mind = lambda { |u|
|
||||
u.status.recent_events.each { |e|
|
||||
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
|
||||
seenbad[e.type] += 1
|
||||
e.age = 0x1000_0000
|
||||
e.age = 0x1000_0000 unless dry_run
|
||||
}
|
||||
}
|
||||
|
||||
summary = lambda {
|
||||
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
|
||||
puts " #{thought} #{cnt}"
|
||||
}
|
||||
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
|
||||
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
|
||||
}
|
||||
|
||||
if verbose
|
||||
seenbad.sort_by { |k, v| v }.each { |k, v| puts " #{v} #{k}" }
|
||||
case $script_args[0]
|
||||
when 'him'
|
||||
if u = df.unit_find
|
||||
clear_mind[u]
|
||||
summary[]
|
||||
else
|
||||
puts 'Please select a dwarf ingame'
|
||||
end
|
||||
|
||||
count = seenbad.values.inject(0) { |s, v| s+v }
|
||||
puts "removed #{count} bad thought#{'s' if count != 1}"
|
||||
when 'all'
|
||||
df.unit_citizens.each { |uu|
|
||||
clear_mind[uu]
|
||||
}
|
||||
summary[]
|
||||
|
||||
else
|
||||
puts "Usage: removebadthoughts [--dry-run] <him|all>"
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue