After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,315 @@
|
||||
/*
|
||||
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 << ": "
|
||||
<< unsigned(bv.old_val) << " " << unsigned(bv.new_val)
|
||||
<< ", but currently " << unsigned(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;
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef CL_MOD_VEGETATION
|
||||
#define CL_MOD_VEGETATION
|
||||
/**
|
||||
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
|
||||
* @ingroup grp_modules
|
||||
*/
|
||||
|
||||
#include "Export.h"
|
||||
#include "DataDefs.h"
|
||||
#include "df/plant.h"
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
namespace Vegetation
|
||||
{
|
||||
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
|
||||
|
||||
// "Simplified" copy of plant
|
||||
struct t_plant {
|
||||
df::language_name name;
|
||||
df::plant_flags flags;
|
||||
int16_t material;
|
||||
df::coord pos;
|
||||
int32_t grow_counter;
|
||||
uint16_t temperature_1;
|
||||
uint16_t temperature_2;
|
||||
int32_t is_burning;
|
||||
int32_t hitpoints;
|
||||
int16_t update_order;
|
||||
//std::vector<void *> unk1;
|
||||
//int32_t unk2;
|
||||
//uint16_t temperature_3;
|
||||
//uint16_t temperature_4;
|
||||
//uint16_t temperature_5;
|
||||
// Pointer to original object, in case you want to modify it
|
||||
df::plant *origin;
|
||||
};
|
||||
|
||||
DFHACK_EXPORT bool isValid();
|
||||
DFHACK_EXPORT uint32_t getCount();
|
||||
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
|
||||
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,121 @@
|
||||
-- Simple binary patch with IDA dif file support.
|
||||
|
||||
local function load_patch(name)
|
||||
local filename = name
|
||||
if not string.match(filename, '[./\\]') then
|
||||
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
|
||||
end
|
||||
|
||||
local file, err = io.open(filename, 'r')
|
||||
if not file then
|
||||
if string.match(err, ': No such file or directory') then
|
||||
return nil, 'patch not found'
|
||||
end
|
||||
end
|
||||
|
||||
local old_bytes = {}
|
||||
local new_bytes = {}
|
||||
|
||||
for line in file:lines() do
|
||||
if string.match(line, '^%x+:') then
|
||||
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
|
||||
if not offset then
|
||||
file:close()
|
||||
return nil, 'could not parse: '..line
|
||||
end
|
||||
|
||||
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
|
||||
if oldv > 255 or newv > 255 then
|
||||
file:close()
|
||||
return nil, 'invalid byte values: '..line
|
||||
end
|
||||
|
||||
old_bytes[offset] = oldv
|
||||
new_bytes[offset] = newv
|
||||
end
|
||||
end
|
||||
|
||||
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
|
||||
end
|
||||
|
||||
local function rebase_table(input)
|
||||
local output = {}
|
||||
local base = dfhack.internal.getImageBase()
|
||||
for k,v in pairs(input) do
|
||||
local offset = dfhack.internal.adjustOffset(k)
|
||||
if not offset then
|
||||
return nil, string.format('invalid offset: %x', k)
|
||||
end
|
||||
output[base + offset] = v
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
local function rebase_patch(patch)
|
||||
local nold, err = rebase_table(patch.old_bytes)
|
||||
if not nold then return nil, err end
|
||||
local nnew, err = rebase_table(patch.new_bytes)
|
||||
if not nnew then return nil, err end
|
||||
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
|
||||
end
|
||||
|
||||
BinaryPatch = defclass(BinaryPatch)
|
||||
|
||||
BinaryPatch.ATTRS {
|
||||
name = DEFAULT_NIL,
|
||||
old_bytes = DEFAULT_NIL,
|
||||
new_bytes = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function load_dif_file(name)
|
||||
local patch, err = load_patch(name)
|
||||
if not patch then return nil, err end
|
||||
|
||||
local rpatch, err = rebase_patch(patch)
|
||||
if not rpatch then return nil, err end
|
||||
|
||||
return BinaryPatch(rpatch)
|
||||
end
|
||||
|
||||
function BinaryPatch:status()
|
||||
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
|
||||
if old_ok then
|
||||
return 'removed'
|
||||
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||
return 'applied'
|
||||
else
|
||||
return 'conflict', addr
|
||||
end
|
||||
end
|
||||
|
||||
function BinaryPatch:isApplied()
|
||||
return dfhack.internal.patchBytes({}, self.new_bytes)
|
||||
end
|
||||
|
||||
function BinaryPatch:apply()
|
||||
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
|
||||
if ok then
|
||||
return true, 'applied the patch'
|
||||
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||
return true, 'patch is already applied'
|
||||
else
|
||||
return false, string.format('conflict at address %x', addr)
|
||||
end
|
||||
end
|
||||
|
||||
function BinaryPatch:isRemoved()
|
||||
return dfhack.internal.patchBytes({}, self.old_bytes)
|
||||
end
|
||||
|
||||
function BinaryPatch:remove()
|
||||
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
|
||||
if ok then
|
||||
return true, 'removed the patch'
|
||||
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
|
||||
return true, 'patch is already removed'
|
||||
else
|
||||
return false, string.format('conflict at address %x', addr)
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,337 @@
|
||||
-- Stock dialog for selecting materials
|
||||
|
||||
local _ENV = mkmodule('gui.materials')
|
||||
|
||||
local gui = require('gui')
|
||||
local widgets = require('gui.widgets')
|
||||
local dlg = require('gui.dialogs')
|
||||
local utils = require('utils')
|
||||
|
||||
ARROW = string.char(26)
|
||||
|
||||
CREATURE_BASE = 19
|
||||
PLANT_BASE = 419
|
||||
|
||||
MaterialDialog = defclass(MaterialDialog, gui.FramedScreen)
|
||||
|
||||
MaterialDialog.focus_path = 'MaterialDialog'
|
||||
|
||||
MaterialDialog.ATTRS{
|
||||
prompt = 'Type or select a material from this list',
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 1,
|
||||
frame_title = 'Select Material',
|
||||
-- new attrs
|
||||
none_caption = 'none',
|
||||
hide_none = false,
|
||||
use_inorganic = true,
|
||||
use_creature = true,
|
||||
use_plant = true,
|
||||
mat_filter = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_cancel = DEFAULT_NIL,
|
||||
on_close = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function MaterialDialog:init(info)
|
||||
self:addviews{
|
||||
widgets.Label{
|
||||
text = {
|
||||
self.prompt, '\n\n',
|
||||
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
|
||||
},
|
||||
text_pen = COLOR_WHITE,
|
||||
frame = { l = 0, t = 0 },
|
||||
},
|
||||
widgets.Label{
|
||||
view_id = 'back',
|
||||
visible = false,
|
||||
text = { { key = 'LEAVESCREEN', text = ': Back' } },
|
||||
frame = { r = 0, b = 0 },
|
||||
auto_width = true,
|
||||
},
|
||||
widgets.FilteredList{
|
||||
view_id = 'list',
|
||||
not_found_label = 'No matching materials',
|
||||
frame = { l = 0, r = 0, t = 4, b = 2 },
|
||||
icon_width = 2,
|
||||
on_submit = self:callback('onSubmitItem'),
|
||||
},
|
||||
widgets.Label{
|
||||
text = { {
|
||||
key = 'SELECT', text = ': Select',
|
||||
disabled = function() return not self.subviews.list:canSubmit() end
|
||||
} },
|
||||
frame = { l = 0, b = 0 },
|
||||
}
|
||||
}
|
||||
self:initBuiltinMode()
|
||||
end
|
||||
|
||||
function MaterialDialog:getWantedFrameSize(rect)
|
||||
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
|
||||
end
|
||||
|
||||
function MaterialDialog:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:initBuiltinMode()
|
||||
local choices = {}
|
||||
if not self.hide_none then
|
||||
table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
|
||||
end
|
||||
|
||||
if self.use_inorganic then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I',
|
||||
cb = self:callback('initInorganicMode')
|
||||
})
|
||||
end
|
||||
if self.use_creature then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C',
|
||||
cb = self:callback('initCreatureMode')
|
||||
})
|
||||
end
|
||||
if self.use_plant then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P',
|
||||
cb = self:callback('initPlantMode')
|
||||
})
|
||||
end
|
||||
|
||||
local table = df.global.world.raws.mat_table.builtin
|
||||
for i=0,df.builtin_mats._last_item do
|
||||
self:addMaterial(choices, table[i], i, -1, false, nil)
|
||||
end
|
||||
|
||||
self:pushContext('Any material', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initInorganicMode()
|
||||
local choices = {}
|
||||
|
||||
for i,mat in ipairs(df.global.world.raws.inorganics) do
|
||||
self:addMaterial(choices, mat.material, 0, i, false, mat)
|
||||
end
|
||||
|
||||
self:pushContext('Inorganic materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initCreatureMode()
|
||||
local choices = {}
|
||||
|
||||
for i,v in ipairs(df.global.world.raws.creatures.all) do
|
||||
self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i)
|
||||
end
|
||||
|
||||
self:pushContext('Creature materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initPlantMode()
|
||||
local choices = {}
|
||||
|
||||
for i,v in ipairs(df.global.world.raws.plants.all) do
|
||||
self:addObjectChoice(choices, v, v.name, PLANT_BASE, i)
|
||||
end
|
||||
|
||||
self:pushContext('Plant materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:addObjectChoice(choices, obj, name, typ, index)
|
||||
-- Check if any eligible children
|
||||
local count = #obj.material
|
||||
local idx = 0
|
||||
|
||||
if self.mat_filter then
|
||||
count = 0
|
||||
for i,v in ipairs(obj.material) do
|
||||
if self.mat_filter(v, obj, typ+i, index) then
|
||||
count = count + 1
|
||||
if count > 1 then break end
|
||||
idx = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create an entry
|
||||
if count < 1 then
|
||||
return
|
||||
elseif count == 1 then
|
||||
self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj)
|
||||
else
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = name, mat_type = typ, mat_index = index,
|
||||
obj = obj, cb = self:callback('onSelectObj')
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onSelectObj(item)
|
||||
local choices = {}
|
||||
for i,v in ipairs(item.obj.material) do
|
||||
self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj)
|
||||
end
|
||||
self:pushContext(item.text, choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent)
|
||||
-- Check the filter
|
||||
if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Find the material name
|
||||
local state = 0
|
||||
if mat.heat.melting_point <= 10015 then
|
||||
state = 1
|
||||
end
|
||||
local name = mat.state_name[state]
|
||||
name = string.gsub(name, '^frozen ','')
|
||||
name = string.gsub(name, '^molten ','')
|
||||
name = string.gsub(name, '^condensed ','')
|
||||
|
||||
-- Add prefix if requested
|
||||
local key
|
||||
if pfix and mat.prefix ~= '' then
|
||||
name = mat.prefix .. ' ' .. name
|
||||
key = mat.prefix
|
||||
end
|
||||
|
||||
table.insert(choices, {
|
||||
text = name,
|
||||
search_key = key,
|
||||
material = mat,
|
||||
mat_type = typ, mat_index = idx
|
||||
})
|
||||
end
|
||||
|
||||
function MaterialDialog:pushContext(name, choices)
|
||||
if not self.back_stack then
|
||||
self.back_stack = {}
|
||||
self.subviews.back.visible = false
|
||||
else
|
||||
table.insert(self.back_stack, {
|
||||
context_str = self.context_str,
|
||||
all_choices = self.subviews.list:getChoices(),
|
||||
edit_text = self.subviews.list:getFilter(),
|
||||
selected = self.subviews.list:getSelected(),
|
||||
})
|
||||
self.subviews.back.visible = true
|
||||
end
|
||||
|
||||
self.context_str = name
|
||||
self.subviews.list:setChoices(choices, 1)
|
||||
end
|
||||
|
||||
function MaterialDialog:onGoBack()
|
||||
local save = table.remove(self.back_stack)
|
||||
self.subviews.back.visible = (#self.back_stack > 0)
|
||||
|
||||
self.context_str = save.context_str
|
||||
self.subviews.list:setChoices(save.all_choices)
|
||||
self.subviews.list:setFilter(save.edit_text, save.selected)
|
||||
end
|
||||
|
||||
function MaterialDialog:submitMaterial(typ, index)
|
||||
self:dismiss()
|
||||
|
||||
if self.on_select then
|
||||
self.on_select(typ, index)
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onSubmitItem(idx, item)
|
||||
if item.cb then
|
||||
item:cb(idx)
|
||||
else
|
||||
self:submitMaterial(item.mat_type, item.mat_index)
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onInput(keys)
|
||||
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
|
||||
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
|
||||
self:onGoBack()
|
||||
else
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter)
|
||||
MaterialDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
mat_filter = mat_filter,
|
||||
on_select = on_select,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
function ItemTypeDialog(args)
|
||||
args.text = args.prompt or 'Type or select an item type'
|
||||
args.text_pen = COLOR_WHITE
|
||||
args.with_filter = true
|
||||
args.icon_width = 2
|
||||
|
||||
local choices = {}
|
||||
|
||||
if not args.hide_none then
|
||||
table.insert(choices, {
|
||||
icon = '?', text = args.none_caption or 'none',
|
||||
item_type = -1, item_subtype = -1
|
||||
})
|
||||
end
|
||||
|
||||
local filter = args.item_filter
|
||||
|
||||
for itype = 0,df.item_type._last_item do
|
||||
local attrs = df.item_type.attrs[itype]
|
||||
local defcnt = dfhack.items.getSubtypeCount(itype)
|
||||
|
||||
if not filter or filter(itype,-1) then
|
||||
local name = attrs.caption or df.item_type[itype]
|
||||
local icon
|
||||
if defcnt >= 0 then
|
||||
name = 'any '..name
|
||||
icon = '+'
|
||||
end
|
||||
table.insert(choices, {
|
||||
icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1
|
||||
})
|
||||
end
|
||||
|
||||
if defcnt > 0 then
|
||||
for subtype = 0,defcnt-1 do
|
||||
local def = dfhack.items.getSubtypeDef(itype, subtype)
|
||||
if not filter or filter(itype,subtype,def) then
|
||||
table.insert(choices, {
|
||||
icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
args.choices = choices
|
||||
|
||||
if args.on_select then
|
||||
local cb = args.on_select
|
||||
args.on_select = function(idx, obj)
|
||||
return cb(obj.item_type, obj.item_subtype)
|
||||
end
|
||||
end
|
||||
|
||||
return dlg.ListBox(args)
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,164 @@
|
||||
-- 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, filter)
|
||||
dlg.ListBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
choices = choices,
|
||||
frame_width = min_width,
|
||||
with_filter = filter,
|
||||
on_select = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showMaterialPrompt(title, prompt)
|
||||
require('gui.materials').MaterialDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
on_select = mkresume(true,
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,783 @@
|
||||
-- Simple widgets for screens
|
||||
|
||||
local _ENV = mkmodule('gui.widgets')
|
||||
|
||||
local gui = require('gui')
|
||||
local utils = require('utils')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
local function show_view(view,vis)
|
||||
if view then
|
||||
view.visible = vis
|
||||
end
|
||||
end
|
||||
|
||||
local function getval(obj)
|
||||
if type(obj) == 'function' then
|
||||
return obj()
|
||||
else
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
local function map_opttab(tab,idx)
|
||||
if tab then
|
||||
return tab[idx]
|
||||
else
|
||||
return idx
|
||||
end
|
||||
end
|
||||
|
||||
------------
|
||||
-- Widget --
|
||||
------------
|
||||
|
||||
Widget = defclass(Widget, gui.View)
|
||||
|
||||
Widget.ATTRS {
|
||||
frame = DEFAULT_NIL,
|
||||
frame_inset = DEFAULT_NIL,
|
||||
frame_background = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function Widget:computeFrame(parent_rect)
|
||||
local sw, sh = parent_rect.width, parent_rect.height
|
||||
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset)
|
||||
end
|
||||
|
||||
function Widget:onRenderFrame(dc, rect)
|
||||
if self.frame_background then
|
||||
dc:fill(rect, self.frame_background)
|
||||
end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Panel --
|
||||
-----------
|
||||
|
||||
Panel = defclass(Panel, Widget)
|
||||
|
||||
Panel.ATTRS {
|
||||
on_render = DEFAULT_NIL,
|
||||
on_layout = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function Panel:init(args)
|
||||
self:addviews(args.subviews)
|
||||
end
|
||||
|
||||
function Panel:onRenderBody(dc)
|
||||
if self.on_render then self.on_render(dc) end
|
||||
end
|
||||
|
||||
function Panel:postComputeFrame(body)
|
||||
if self.on_layout then self.on_layout(body) end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Pages --
|
||||
-----------
|
||||
|
||||
Pages = defclass(Pages, Panel)
|
||||
|
||||
function Pages:init(args)
|
||||
for _,v in ipairs(self.subviews) do
|
||||
v.visible = false
|
||||
end
|
||||
self:setSelected(args.selected or 1)
|
||||
end
|
||||
|
||||
function Pages:setSelected(idx)
|
||||
if type(idx) ~= 'number' then
|
||||
local key = idx
|
||||
if type(idx) == 'string' then
|
||||
key = self.subviews[key]
|
||||
end
|
||||
idx = utils.linear_index(self.subviews, key)
|
||||
if not idx then
|
||||
error('Unknown page: '..key)
|
||||
end
|
||||
end
|
||||
|
||||
show_view(self.subviews[self.selected], false)
|
||||
self.selected = math.min(math.max(1, idx), #self.subviews)
|
||||
show_view(self.subviews[self.selected], true)
|
||||
end
|
||||
|
||||
function Pages:getSelected()
|
||||
return self.selected, self.subviews[self.selected]
|
||||
end
|
||||
|
||||
----------------
|
||||
-- Edit field --
|
||||
----------------
|
||||
|
||||
EditField = defclass(EditField, Widget)
|
||||
|
||||
EditField.ATTRS{
|
||||
text = '',
|
||||
text_pen = DEFAULT_NIL,
|
||||
on_char = DEFAULT_NIL,
|
||||
on_change = DEFAULT_NIL,
|
||||
on_submit = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function EditField:onRenderBody(dc)
|
||||
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0)
|
||||
|
||||
local cursor = '_'
|
||||
if not self.active or gui.blink_visible(300) then
|
||||
cursor = ' '
|
||||
end
|
||||
local txt = self.text .. cursor
|
||||
if #txt > dc.width then
|
||||
txt = string.char(27)..string.sub(txt, #txt-dc.width+2)
|
||||
end
|
||||
dc:string(txt)
|
||||
end
|
||||
|
||||
function EditField:onInput(keys)
|
||||
if self.on_submit and keys.SELECT then
|
||||
self.on_submit(self.text)
|
||||
return true
|
||||
elseif keys._STRING then
|
||||
local old = self.text
|
||||
if keys._STRING == 0 then
|
||||
self.text = string.sub(old, 1, #old-1)
|
||||
else
|
||||
local cv = string.char(keys._STRING)
|
||||
if not self.on_char or self.on_char(cv, old) then
|
||||
self.text = old .. cv
|
||||
end
|
||||
end
|
||||
if self.on_change and self.text ~= old then
|
||||
self.on_change(self.text, old)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Label --
|
||||
-----------
|
||||
|
||||
function parse_label_text(obj)
|
||||
local text = obj.text or {}
|
||||
if type(text) ~= 'table' then
|
||||
text = { text }
|
||||
end
|
||||
local curline = nil
|
||||
local out = { }
|
||||
local active = nil
|
||||
local idtab = nil
|
||||
for _,v in ipairs(text) do
|
||||
local vv
|
||||
if type(v) == 'string' then
|
||||
vv = utils.split_string(v, NEWLINE)
|
||||
else
|
||||
vv = { v }
|
||||
end
|
||||
|
||||
for i = 1,#vv do
|
||||
local cv = vv[i]
|
||||
if i > 1 then
|
||||
if not curline then
|
||||
table.insert(out, {})
|
||||
end
|
||||
curline = nil
|
||||
end
|
||||
if cv ~= '' then
|
||||
if not curline then
|
||||
curline = {}
|
||||
table.insert(out, curline)
|
||||
end
|
||||
|
||||
if type(cv) == 'string' then
|
||||
table.insert(curline, { text = cv })
|
||||
else
|
||||
table.insert(curline, cv)
|
||||
|
||||
if cv.on_activate then
|
||||
active = active or {}
|
||||
table.insert(active, cv)
|
||||
end
|
||||
|
||||
if cv.id then
|
||||
idtab = idtab or {}
|
||||
idtab[cv.id] = cv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
obj.text_lines = out
|
||||
obj.text_active = active
|
||||
obj.text_ids = idtab
|
||||
end
|
||||
|
||||
local function is_disabled(token)
|
||||
return (token.disabled ~= nil and getval(token.disabled)) or
|
||||
(token.enabled ~= nil and not getval(token.enabled))
|
||||
end
|
||||
|
||||
function render_text(obj,dc,x0,y0,pen,dpen,disabled)
|
||||
local width = 0
|
||||
for iline,line in ipairs(obj.text_lines) do
|
||||
local x = 0
|
||||
if dc then
|
||||
dc:seek(x+x0,y0+iline-1)
|
||||
end
|
||||
for _,token in ipairs(line) do
|
||||
token.line = iline
|
||||
token.x1 = x
|
||||
|
||||
if token.gap then
|
||||
x = x + token.gap
|
||||
if dc then
|
||||
dc:advance(token.gap)
|
||||
end
|
||||
end
|
||||
|
||||
if token.tile then
|
||||
x = x + 1
|
||||
if dc then
|
||||
dc:char(nil, token.tile)
|
||||
end
|
||||
end
|
||||
|
||||
if token.text or token.key then
|
||||
local text = ''..(getval(token.text) or '')
|
||||
local keypen
|
||||
|
||||
if dc then
|
||||
local tpen = getval(token.pen)
|
||||
if disabled or is_disabled(token) then
|
||||
dc:pen(getval(token.dpen) or tpen or dpen)
|
||||
keypen = COLOR_GREEN
|
||||
else
|
||||
dc:pen(tpen or pen)
|
||||
keypen = COLOR_LIGHTGREEN
|
||||
end
|
||||
end
|
||||
|
||||
local width = getval(token.width)
|
||||
local padstr
|
||||
if width then
|
||||
x = x + width
|
||||
if #text > width then
|
||||
text = string.sub(text,1,width)
|
||||
else
|
||||
if token.pad_char then
|
||||
padstr = string.rep(token.pad_char,width-#text)
|
||||
end
|
||||
if dc and token.rjustify then
|
||||
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||
end
|
||||
end
|
||||
else
|
||||
x = x + #text
|
||||
end
|
||||
|
||||
if token.key then
|
||||
local keystr = gui.getKeyDisplay(token.key)
|
||||
local sep = token.key_sep or ''
|
||||
|
||||
x = x + #keystr
|
||||
|
||||
if sep == '()' then
|
||||
if dc then
|
||||
dc:string(text)
|
||||
dc:string(' ('):string(keystr,keypen):string(')')
|
||||
end
|
||||
x = x + 3
|
||||
else
|
||||
if dc then
|
||||
dc:string(keystr,keypen):string(sep):string(text)
|
||||
end
|
||||
x = x + #sep
|
||||
end
|
||||
else
|
||||
if dc then
|
||||
dc:string(text)
|
||||
end
|
||||
end
|
||||
|
||||
if width and dc and not token.rjustify then
|
||||
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||
end
|
||||
end
|
||||
|
||||
token.x2 = x
|
||||
end
|
||||
width = math.max(width, x)
|
||||
end
|
||||
obj.text_width = width
|
||||
end
|
||||
|
||||
function check_text_keys(self, keys)
|
||||
if self.text_active then
|
||||
for _,item in ipairs(self.text_active) do
|
||||
if item.key and keys[item.key] and not is_disabled(item) then
|
||||
item.on_activate()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Label = defclass(Label, Widget)
|
||||
|
||||
Label.ATTRS{
|
||||
text_pen = COLOR_WHITE,
|
||||
text_dpen = COLOR_DARKGREY,
|
||||
disabled = DEFAULT_NIL,
|
||||
enabled = DEFAULT_NIL,
|
||||
auto_height = true,
|
||||
auto_width = false,
|
||||
}
|
||||
|
||||
function Label:init(args)
|
||||
self:setText(args.text)
|
||||
end
|
||||
|
||||
function Label:setText(text)
|
||||
self.text = text
|
||||
parse_label_text(self)
|
||||
|
||||
if self.auto_height then
|
||||
self.frame = self.frame or {}
|
||||
self.frame.h = self:getTextHeight()
|
||||
end
|
||||
end
|
||||
|
||||
function Label:preUpdateLayout()
|
||||
if self.auto_width then
|
||||
self.frame = self.frame or {}
|
||||
self.frame.w = self:getTextWidth()
|
||||
end
|
||||
end
|
||||
|
||||
function Label:itemById(id)
|
||||
if self.text_ids then
|
||||
return self.text_ids[id]
|
||||
end
|
||||
end
|
||||
|
||||
function Label:getTextHeight()
|
||||
return #self.text_lines
|
||||
end
|
||||
|
||||
function Label:getTextWidth()
|
||||
render_text(self)
|
||||
return self.text_width
|
||||
end
|
||||
|
||||
function Label:onRenderBody(dc)
|
||||
render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self))
|
||||
end
|
||||
|
||||
function Label:onInput(keys)
|
||||
if not is_disabled(self) then
|
||||
return check_text_keys(self, keys)
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
-- List --
|
||||
----------
|
||||
|
||||
List = defclass(List, Widget)
|
||||
|
||||
STANDARDSCROLL = {
|
||||
STANDARDSCROLL_UP = -1,
|
||||
STANDARDSCROLL_DOWN = 1,
|
||||
STANDARDSCROLL_PAGEUP = '-page',
|
||||
STANDARDSCROLL_PAGEDOWN = '+page',
|
||||
}
|
||||
|
||||
SECONDSCROLL = {
|
||||
SECONDSCROLL_UP = -1,
|
||||
SECONDSCROLL_DOWN = 1,
|
||||
SECONDSCROLL_PAGEUP = '-page',
|
||||
SECONDSCROLL_PAGEDOWN = '+page',
|
||||
}
|
||||
|
||||
List.ATTRS{
|
||||
text_pen = COLOR_CYAN,
|
||||
cursor_pen = COLOR_LIGHTCYAN,
|
||||
inactive_pen = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_submit = DEFAULT_NIL,
|
||||
on_submit2 = DEFAULT_NIL,
|
||||
row_height = 1,
|
||||
scroll_keys = STANDARDSCROLL,
|
||||
icon_width = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function List:init(info)
|
||||
self.page_top = 1
|
||||
self.page_size = 1
|
||||
|
||||
if info.choices then
|
||||
self:setChoices(info.choices, info.selected)
|
||||
else
|
||||
self.choices = {}
|
||||
self.selected = 1
|
||||
end
|
||||
end
|
||||
|
||||
function List:setChoices(choices, selected)
|
||||
self.choices = choices or {}
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
if type(v) ~= 'table' then
|
||||
v = { text = v }
|
||||
self.choices[i] = v
|
||||
end
|
||||
v.text = v.text or v.caption or v[1]
|
||||
parse_label_text(v)
|
||||
end
|
||||
|
||||
self:setSelected(selected)
|
||||
end
|
||||
|
||||
function List:setSelected(selected)
|
||||
self.selected = selected or self.selected or 1
|
||||
self:moveCursor(0, true)
|
||||
return self.selected
|
||||
end
|
||||
|
||||
function List:getChoices()
|
||||
return self.choices
|
||||
end
|
||||
|
||||
function List:getSelected()
|
||||
if #self.choices > 0 then
|
||||
return self.selected, self.choices[self.selected]
|
||||
end
|
||||
end
|
||||
|
||||
function List:getContentWidth()
|
||||
local width = 0
|
||||
for i,v in ipairs(self.choices) do
|
||||
render_text(v)
|
||||
local roww = v.text_width
|
||||
if v.key then
|
||||
roww = roww + 3 + #gui.getKeyDisplay(v.key)
|
||||
end
|
||||
width = math.max(width, roww)
|
||||
end
|
||||
return width + (self.icon_width or 0)
|
||||
end
|
||||
|
||||
function List:getContentHeight()
|
||||
return #self.choices * self.row_height
|
||||
end
|
||||
|
||||
function List:postComputeFrame(body)
|
||||
self.page_size = math.max(1, math.floor(body.height / self.row_height))
|
||||
self:moveCursor(0)
|
||||
end
|
||||
|
||||
function List:moveCursor(delta, force_cb)
|
||||
local page = math.max(1, self.page_size)
|
||||
local cnt = #self.choices
|
||||
|
||||
if cnt < 1 then
|
||||
self.page_top = 1
|
||||
self.selected = 1
|
||||
if force_cb and self.on_select then
|
||||
self.on_select(nil,nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local off = self.selected+delta-1
|
||||
local ds = math.abs(delta)
|
||||
|
||||
if ds > 1 then
|
||||
if off >= cnt+ds-1 then
|
||||
off = 0
|
||||
else
|
||||
off = math.min(cnt-1, off)
|
||||
end
|
||||
if off <= -ds then
|
||||
off = cnt-1
|
||||
else
|
||||
off = math.max(0, off)
|
||||
end
|
||||
end
|
||||
|
||||
self.selected = 1 + off % cnt
|
||||
self.page_top = 1 + page * math.floor((self.selected-1) / page)
|
||||
|
||||
if (force_cb or delta ~= 0) and self.on_select then
|
||||
self.on_select(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:onRenderBody(dc)
|
||||
local choices = self.choices
|
||||
local top = self.page_top
|
||||
local iend = math.min(#choices, top+self.page_size-1)
|
||||
local iw = self.icon_width
|
||||
|
||||
local function paint_icon(icon, obj)
|
||||
if type(icon) ~= 'string' then
|
||||
dc:char(nil,icon)
|
||||
else
|
||||
if current then
|
||||
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
|
||||
else
|
||||
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = top,iend do
|
||||
local obj = choices[i]
|
||||
local current = (i == self.selected)
|
||||
local cur_pen = self.cursor_pen
|
||||
local cur_dpen = self.text_pen
|
||||
|
||||
if not self.active then
|
||||
cur_pen = self.inactive_pen or self.cursor_pen
|
||||
end
|
||||
|
||||
local y = (i - top)*self.row_height
|
||||
local icon = getval(obj.icon)
|
||||
|
||||
if iw and icon then
|
||||
dc:seek(0, y)
|
||||
paint_icon(icon, obj)
|
||||
end
|
||||
|
||||
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
|
||||
|
||||
local ip = dc.width
|
||||
|
||||
if obj.key then
|
||||
local keystr = gui.getKeyDisplay(obj.key)
|
||||
ip = ip-2-#keystr
|
||||
dc:seek(ip,y):pen(self.text_pen)
|
||||
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
|
||||
end
|
||||
|
||||
if icon and not iw then
|
||||
dc:seek(ip-1,y)
|
||||
paint_icon(icon, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function List:submit()
|
||||
if self.on_submit and #self.choices > 0 then
|
||||
self.on_submit(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:submit2()
|
||||
if self.on_submit2 and #self.choices > 0 then
|
||||
self.on_submit2(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:onInput(keys)
|
||||
if self.on_submit and keys.SELECT then
|
||||
self:submit()
|
||||
return true
|
||||
elseif self.on_submit2 and keys.SEC_SELECT then
|
||||
self:submit2()
|
||||
return true
|
||||
else
|
||||
for k,v in pairs(self.scroll_keys) do
|
||||
if keys[k] then
|
||||
if v == '+page' then
|
||||
v = self.page_size
|
||||
elseif v == '-page' then
|
||||
v = -self.page_size
|
||||
end
|
||||
|
||||
self:moveCursor(v)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
if v.key and keys[v.key] then
|
||||
self:setSelected(i)
|
||||
self:submit()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local current = self.choices[self.selected]
|
||||
if current then
|
||||
return check_text_keys(current, keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Filtered List --
|
||||
-------------------
|
||||
|
||||
FilteredList = defclass(FilteredList, Widget)
|
||||
|
||||
FilteredList.ATTRS {
|
||||
edit_below = false,
|
||||
}
|
||||
|
||||
function FilteredList:init(info)
|
||||
self.edit = EditField{
|
||||
text_pen = info.edit_pen or info.cursor_pen,
|
||||
frame = { l = info.icon_width, t = 0, h = 1 },
|
||||
on_change = self:callback('onFilterChange'),
|
||||
on_char = self:callback('onFilterChar'),
|
||||
}
|
||||
self.list = List{
|
||||
frame = { t = 2 },
|
||||
text_pen = info.text_pen,
|
||||
cursor_pen = info.cursor_pen,
|
||||
inactive_pen = info.inactive_pen,
|
||||
icon_pen = info.icon_pen,
|
||||
row_height = info.row_height,
|
||||
scroll_keys = info.scroll_keys,
|
||||
icon_width = info.icon_width,
|
||||
}
|
||||
if self.edit_below then
|
||||
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
|
||||
self.list.frame = { t = 0, b = 2 }
|
||||
end
|
||||
if info.on_select then
|
||||
self.list.on_select = function()
|
||||
return info.on_select(self:getSelected())
|
||||
end
|
||||
end
|
||||
if info.on_submit then
|
||||
self.list.on_submit = function()
|
||||
return info.on_submit(self:getSelected())
|
||||
end
|
||||
end
|
||||
if info.on_submit2 then
|
||||
self.list.on_submit2 = function()
|
||||
return info.on_submit2(self:getSelected())
|
||||
end
|
||||
end
|
||||
self.not_found = Label{
|
||||
visible = true,
|
||||
text = info.not_found_label or 'No matches',
|
||||
text_pen = COLOR_LIGHTRED,
|
||||
frame = { l = info.icon_width, t = self.list.frame.t },
|
||||
}
|
||||
self:addviews{ self.edit, self.list, self.not_found }
|
||||
if info.choices then
|
||||
self:setChoices(info.choices, info.selected)
|
||||
else
|
||||
self.choices = {}
|
||||
end
|
||||
end
|
||||
|
||||
function FilteredList:getChoices()
|
||||
return self.choices
|
||||
end
|
||||
|
||||
function FilteredList:setChoices(choices, pos)
|
||||
choices = choices or {}
|
||||
self.choices = choices
|
||||
self.edit.text = ''
|
||||
self.list:setChoices(choices, pos)
|
||||
self.not_found.visible = (#choices == 0)
|
||||
end
|
||||
|
||||
function FilteredList:submit()
|
||||
return self.list:submit()
|
||||
end
|
||||
|
||||
function FilteredList:submit2()
|
||||
return self.list:submit2()
|
||||
end
|
||||
|
||||
function FilteredList:canSubmit()
|
||||
return not self.not_found.visible
|
||||
end
|
||||
|
||||
function FilteredList:getSelected()
|
||||
local i,v = self.list:getSelected()
|
||||
if i then
|
||||
return map_opttab(self.choice_index, i), v
|
||||
end
|
||||
end
|
||||
|
||||
function FilteredList:getContentWidth()
|
||||
return self.list:getContentWidth()
|
||||
end
|
||||
|
||||
function FilteredList:getContentHeight()
|
||||
return self.list:getContentHeight() + 2
|
||||
end
|
||||
|
||||
function FilteredList:getFilter()
|
||||
return self.edit.text, self.list.choices
|
||||
end
|
||||
|
||||
function FilteredList:setFilter(filter, pos)
|
||||
local choices = self.choices
|
||||
local cidx = nil
|
||||
|
||||
filter = filter or ''
|
||||
self.edit.text = filter
|
||||
|
||||
if filter ~= '' then
|
||||
local tokens = utils.split_string(filter, ' ')
|
||||
local ipos = pos
|
||||
|
||||
choices = {}
|
||||
cidx = {}
|
||||
pos = nil
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
local ok = true
|
||||
local search_key = v.search_key or v.text
|
||||
for _,key in ipairs(tokens) do
|
||||
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
table.insert(choices, v)
|
||||
cidx[#choices] = i
|
||||
if ipos == i then
|
||||
pos = #choices
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.choice_index = cidx
|
||||
self.list:setChoices(choices, pos)
|
||||
self.not_found.visible = (#choices == 0)
|
||||
end
|
||||
|
||||
function FilteredList:onFilterChange(text)
|
||||
self:setFilter(text)
|
||||
end
|
||||
|
||||
local bad_chars = {
|
||||
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
|
||||
['['] = true, [']'] = true, ['('] = true, [')'] = true,
|
||||
}
|
||||
|
||||
function FilteredList:onFilterChar(char, text)
|
||||
if bad_chars[char] then
|
||||
return false
|
||||
end
|
||||
if char == ' ' then
|
||||
return string.match(text, '%S$')
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return _ENV
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 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 <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
using namespace std;
|
||||
|
||||
#include "VersionInfo.h"
|
||||
#include "MemAccess.h"
|
||||
#include "Types.h"
|
||||
#include "Core.h"
|
||||
using namespace DFHack;
|
||||
|
||||
#include "modules/Vegetation.h"
|
||||
#include "df/world.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using df::global::world;
|
||||
|
||||
bool Vegetation::isValid()
|
||||
{
|
||||
return (world != NULL);
|
||||
}
|
||||
|
||||
uint32_t Vegetation::getCount()
|
||||
{
|
||||
return world->plants.all.size();
|
||||
}
|
||||
|
||||
df::plant * Vegetation::getPlant(const int32_t index)
|
||||
{
|
||||
if (uint32_t(index) >= getCount())
|
||||
return NULL;
|
||||
return world->plants.all[index];
|
||||
}
|
||||
|
||||
bool Vegetation::copyPlant(const int32_t index, t_plant &out)
|
||||
{
|
||||
if (uint32_t(index) >= getCount())
|
||||
return false;
|
||||
|
||||
out.origin = world->plants.all[index];
|
||||
|
||||
out.name = out.origin->name;
|
||||
out.flags = out.origin->flags;
|
||||
out.material = out.origin->material;
|
||||
out.pos = out.origin->pos;
|
||||
out.grow_counter = out.origin->grow_counter;
|
||||
out.temperature_1 = out.origin->temperature_1;
|
||||
out.temperature_2 = out.origin->temperature_2;
|
||||
out.is_burning = out.origin->is_burning;
|
||||
out.hitpoints = out.origin->hitpoints;
|
||||
out.update_order = out.origin->update_order;
|
||||
//out.unk1 = out.origin->anon_1;
|
||||
//out.unk2 = out.origin->anon_2;
|
||||
//out.temperature_3 = out.origin->temperature_3;
|
||||
//out.temperature_4 = out.origin->temperature_4;
|
||||
//out.temperature_5 = out.origin->temperature_5;
|
||||
return true;
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b
|
||||
Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be
|
@ -0,0 +1,142 @@
|
||||
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
|
||||
|
||||
0x2ac6b
|
||||
CC CC CC CC CC
|
||||
66 39 E8 EB 53
|
||||
|
||||
.text:0042B86B loc_42B86B:
|
||||
.text:0042B86B cmp ax, bp
|
||||
.text:0042B86E jmp short loc_42B8C3
|
||||
|
||||
0x2ac7b
|
||||
CC CC CC CC CC
|
||||
E9 96 A2 00 00
|
||||
|
||||
.text:0042B87B loc_42B87B:
|
||||
.text:0042B87B jmp loc_435B16
|
||||
|
||||
0x2acc3
|
||||
CC CC CC CC CC CC CC CC CC CC CC CC CC
|
||||
75 0A 66 FF 4C 24 16 79 03 58 EB AC C3
|
||||
|
||||
.text:0042B8C3 loc_42B8C3:
|
||||
.text:0042B8C3 jnz short locret_42B8CF
|
||||
.text:0042B8C5 dec word ptr [esp+16h] ; 4+8+8+2
|
||||
.text:0042B8CA jns short locret_42B8CF
|
||||
.text:0042B8CC pop eax
|
||||
.text:0042B8CD jmp short loc_42B87B
|
||||
.text:0042B8CF locret_42B8CF:
|
||||
.text:0042B8CF retn
|
||||
|
||||
0x2b2a1
|
||||
CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
|
||||
66 C7 44 24 0E 01 00 8B 90 44 01 00 00 C3 CC
|
||||
|
||||
.text:0042BEA1 loc_42BEA1:
|
||||
.text:0042BEA1 mov word ptr [esp+0Eh], 1 ; 4+8+2
|
||||
.text:0042BEA8 mov edx, [eax+144h]
|
||||
.text:0042BEAE retn
|
||||
|
||||
0x34d91
|
||||
8B 90 44 01 00 00
|
||||
E8 0B 65 FF FF 90
|
||||
|
||||
<<<<
|
||||
.text:00435991 mov edx, [eax+144h]
|
||||
====
|
||||
.text:00435991 call loc_42BEA1
|
||||
.text:00435996 nop
|
||||
>>>>
|
||||
|
||||
0x34e53
|
||||
0F 84 BD 00 00 00
|
||||
E8 6B 5E FF FF 90
|
||||
|
||||
<<<<
|
||||
.text:00435A53 jz loc_435B16
|
||||
====
|
||||
.text:00435A53 call loc_42B8C3
|
||||
.text:00435A58 nop
|
||||
>>>>
|
||||
|
||||
0x34ef3
|
||||
66 3B C5 74 1E
|
||||
E8 73 5D FF FF
|
||||
|
||||
<<<<
|
||||
.text:00435AF3 cmp ax, bp
|
||||
.text:00435AF6 jz short loc_435B16
|
||||
====
|
||||
.text:00435AF3 call loc_42B86B
|
||||
>>>>
|
||||
|
||||
|
||||
basically:
|
||||
|
||||
+ int allowed_count = 1; // to mean 2
|
||||
...
|
||||
- if (type(item) == new_type)
|
||||
+ if (type(item) == new_type && --allowed_count < 0)
|
||||
return false;
|
||||
|
||||
to allow up to two items of the same type at the same time
|
||||
|
||||
|
||||
---8<---
|
||||
This difference file is created by The Interactive Disassembler
|
||||
|
||||
Dwarf Fortress.exe
|
||||
0002AC6B: CC 66
|
||||
0002AC6C: CC 39
|
||||
0002AC6D: CC E8
|
||||
0002AC6E: CC EB
|
||||
0002AC6F: CC 53
|
||||
0002AC7B: CC E9
|
||||
0002AC7C: CC 96
|
||||
0002AC7D: CC A2
|
||||
0002AC7E: CC 00
|
||||
0002AC7F: CC 00
|
||||
0002ACC3: CC 75
|
||||
0002ACC4: CC 0A
|
||||
0002ACC5: CC 66
|
||||
0002ACC6: CC FF
|
||||
0002ACC7: CC 4C
|
||||
0002ACC8: CC 24
|
||||
0002ACC9: CC 16
|
||||
0002ACCA: CC 79
|
||||
0002ACCB: CC 03
|
||||
0002ACCC: CC 58
|
||||
0002ACCD: CC EB
|
||||
0002ACCE: CC AC
|
||||
0002ACCF: CC C3
|
||||
0002B2A1: CC 66
|
||||
0002B2A2: CC C7
|
||||
0002B2A3: CC 44
|
||||
0002B2A4: CC 24
|
||||
0002B2A5: CC 0E
|
||||
0002B2A6: CC 01
|
||||
0002B2A7: CC 00
|
||||
0002B2A8: CC 8B
|
||||
0002B2A9: CC 90
|
||||
0002B2AA: CC 44
|
||||
0002B2AB: CC 01
|
||||
0002B2AC: CC 00
|
||||
0002B2AD: CC 00
|
||||
0002B2AE: CC C3
|
||||
00034D91: 8B E8
|
||||
00034D92: 90 0B
|
||||
00034D93: 44 65
|
||||
00034D94: 01 FF
|
||||
00034D95: 00 FF
|
||||
00034D96: 00 90
|
||||
00034E53: 0F E8
|
||||
00034E54: 84 6B
|
||||
00034E55: BD 5E
|
||||
00034E56: 00 FF
|
||||
00034E57: 00 FF
|
||||
00034E58: 00 90
|
||||
00034EF3: 66 E8
|
||||
00034EF4: 3B 73
|
||||
00034EF5: C5 5D
|
||||
00034EF6: 74 FF
|
||||
00034EF7: 1E FF
|
@ -0,0 +1,91 @@
|
||||
http://www.bay12games.com/dwarves/mantisbt/view.php?id=808
|
||||
|
||||
Original code:
|
||||
|
||||
.text:00916BCE mov edi, ebp
|
||||
.text:00916BD0 call eax
|
||||
.text:00916BD2 test eax, eax
|
||||
.text:00916BD4 jnz short loc_916C1C
|
||||
|
||||
.text:00916C0A mov edi, ebp
|
||||
|
||||
.text:00916C14 mov edi, ebp
|
||||
|
||||
Patch:
|
||||
|
||||
0x2ac34:
|
||||
CC CC CC CC CC CC CC CC CC CC CC CC
|
||||
8B 7C 24 78 8B 3C B7 FF D0 EB 25 CC
|
||||
|
||||
.text:0042B834 loc_42B834:
|
||||
.text:0042B834 mov edi, [esp+78h]
|
||||
.text:0042B838 mov edi, [edi+esi*4]
|
||||
.text:0042B83B call eax
|
||||
.text:0042B83D jmp short unk_42B864
|
||||
|
||||
0x2ac64
|
||||
CC CC CC CC CC CC CC CC CC CC CC CC
|
||||
85 C0 E9 69 B3 4E 00 CC CC CC CC CC
|
||||
|
||||
.text:0042B864 loc_42B864:
|
||||
.text:0042B864 test eax, eax
|
||||
.text:0042B866 jmp loc_916BD4
|
||||
|
||||
0x515fce
|
||||
8B FD FF D0 85 C0
|
||||
E9 61 4C B1 FF 90
|
||||
|
||||
.text:00916BCE jmp loc_42B834
|
||||
.text:00916BD3 nop
|
||||
.text:00916BD4 loc_916BD4:
|
||||
|
||||
0x51600a
|
||||
8B FD
|
||||
90 90
|
||||
|
||||
.text:00916C0A nop
|
||||
.text:00916C0B nop
|
||||
|
||||
0x516014
|
||||
8B FD
|
||||
90 90
|
||||
|
||||
.text:00916C14 nop
|
||||
.text:00916C15 nop
|
||||
|
||||
|
||||
You can use this script to apply the generated patch below:
|
||||
http://stalkr.net/files/ida/idadif.py
|
||||
|
||||
----8<----
|
||||
This difference file is created by The Interactive Disassembler
|
||||
|
||||
Dwarf Fortress.exe
|
||||
0002AC34: CC 8B
|
||||
0002AC35: CC 7C
|
||||
0002AC36: CC 24
|
||||
0002AC37: CC 78
|
||||
0002AC38: CC 8B
|
||||
0002AC39: CC 3C
|
||||
0002AC3A: CC B7
|
||||
0002AC3B: CC FF
|
||||
0002AC3C: CC D0
|
||||
0002AC3D: CC EB
|
||||
0002AC3E: CC 25
|
||||
0002AC64: CC 85
|
||||
0002AC65: CC C0
|
||||
0002AC66: CC E9
|
||||
0002AC67: CC 69
|
||||
0002AC68: CC B3
|
||||
0002AC69: CC 4E
|
||||
0002AC6A: CC 00
|
||||
00515FCE: 8B E9
|
||||
00515FCF: FD 61
|
||||
00515FD0: FF 4C
|
||||
00515FD1: D0 B1
|
||||
00515FD2: 85 FF
|
||||
00515FD3: C0 90
|
||||
0051600A: 8B 90
|
||||
0051600B: FD 90
|
||||
00516014: 8B 90
|
||||
00516015: FD 90
|
@ -0,0 +1,61 @@
|
||||
http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994
|
||||
|
||||
Original code:
|
||||
|
||||
.text:008629BD mov edi, [eax+38h]
|
||||
.text:008629C0 mov eax, [eax+3Ch]
|
||||
.text:008629C3 mov [esp+1Ch], eax
|
||||
.text:008629C7 cmp edi, eax
|
||||
.text:008629C9 jnb short loc_862A22
|
||||
.text:008629CB jmp short loc_8629D0
|
||||
.text:008629CD lea ecx, [ecx+0]
|
||||
...
|
||||
.text:00862A19 add edi, 4
|
||||
.text:00862A1C cmp edi, [esp+1Ch]
|
||||
.text:00862A20 jb short loc_8629D0
|
||||
|
||||
Patch:
|
||||
|
||||
0x461dbd
|
||||
8B 78 38 8B 40 3C 89 44 24 1C 3B F8
|
||||
8B 78 3C 8B 40 38 89 44 24 1C 39 F8
|
||||
|
||||
.text:008629BD mov edi, [eax+3Ch]
|
||||
.text:008629C0 mov eax, [eax+38h]
|
||||
.text:008629C3 mov [esp+1Ch], eax
|
||||
.text:008629C7 cmp eax, edi
|
||||
|
||||
0x461dcb
|
||||
EB 03 8D 49 00
|
||||
83 EF 04 90 90
|
||||
|
||||
.text:008629CB sub edi, 4
|
||||
.text:008629CE nop
|
||||
.text:008629CF nop
|
||||
|
||||
0x461e19
|
||||
83 C7 04 3B 7C 24 1C 72 AE
|
||||
83 EF 04 3B 7C 24 1C 73 AE
|
||||
|
||||
.text:00862A19 sub edi, 4
|
||||
.text:00862A1C cmp edi, [esp+1Ch]
|
||||
.text:00862A20 jnb short loc_8629D0
|
||||
|
||||
|
||||
You can use this script to apply the generated patch below:
|
||||
http://stalkr.net/files/ida/idadif.py
|
||||
|
||||
----8<----
|
||||
This difference file is created by The Interactive Disassembler
|
||||
|
||||
Dwarf_Fortress
|
||||
00461DBF: 38 3C
|
||||
00461DC2: 3C 38
|
||||
00461DC7: 3B 39
|
||||
00461DCB: EB 83
|
||||
00461DCC: 03 EF
|
||||
00461DCD: 8D 04
|
||||
00461DCE: 49 90
|
||||
00461DCF: 00 90
|
||||
00461E1A: C7 EF
|
||||
00461E20: 72 73
|