Merge branch 'master' into diggingInvaders
commit
8a242b3c0d
@ -0,0 +1,3 @@
|
||||
FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py)
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE)
|
@ -0,0 +1,402 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
|
||||
<title>Contributors</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { /* line numbers */
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: #eeeeee
|
||||
}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="contributors">
|
||||
<h1 class="title">Contributors</h1>
|
||||
|
||||
<p>If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)</p>
|
||||
<p>The following is a list of people who have contributed to <strong>DFHack</strong>.</p>
|
||||
<ul class="simple">
|
||||
<li>Petr Mrázek <<a class="reference external" href="mailto:peterix@gmail.com">peterix@gmail.com</a>></li>
|
||||
<li>Alexander Gavrilov <<a class="reference external" href="mailto:angavrilov@gmail.com">angavrilov@gmail.com</a>></li>
|
||||
<li>doomchild <<a class="reference external" href="mailto:lee.crabtree@gmail.com">lee.crabtree@gmail.com</a>></li>
|
||||
<li>Quietust <<a class="reference external" href="mailto:quietust@gmail.com">quietust@gmail.com</a>></li>
|
||||
<li>jj <<a class="reference external" href="mailto:john-git@ofjj.net">john-git@ofjj.net</a>></li>
|
||||
<li>Warmist <<a class="reference external" href="mailto:warmist@gmail.com">warmist@gmail.com</a>></li>
|
||||
<li>Robert Heinrich <<a class="reference external" href="mailto:robertheinrich73@googlemail.com">robertheinrich73@googlemail.com</a>></li>
|
||||
<li>simon <<a class="reference external" href="mailto:simon@banquise.net">simon@banquise.net</a>></li>
|
||||
<li>Kelly Martin <<a class="reference external" href="mailto:kelly.lynn.martin@gmail.com">kelly.lynn.martin@gmail.com</a>></li>
|
||||
<li>mizipzor <<a class="reference external" href="mailto:mizipzor@gmail.com">mizipzor@gmail.com</a>></li>
|
||||
<li>Simon Jackson <<a class="reference external" href="mailto:sizeak@hotmail.com">sizeak@hotmail.com</a>></li>
|
||||
<li>belal <<a class="reference external" href="mailto:jimbelal@gmail.com">jimbelal@gmail.com</a>></li>
|
||||
<li>RusAnon <<a class="reference external" href="mailto:rusanon@dollchan.ru">rusanon@dollchan.ru</a>></li>
|
||||
<li>Raoul XQ <<a class="reference external" href="mailto:raoulxq@gmail.com">raoulxq@gmail.com</a>></li>
|
||||
<li>Matthew Cline <<a class="reference external" href="mailto:zelgadis@sourceforge.net">zelgadis@sourceforge.net</a>></li>
|
||||
<li>Mike Stewart <<a class="reference external" href="mailto:thewonderidiot@gmail.com">thewonderidiot@gmail.com</a>></li>
|
||||
<li>Timothy Collett <<a class="reference external" href="mailto:tcollett+github@topazgryphon.org">tcollett+github@topazgryphon.org</a>></li>
|
||||
<li>RossM <<a class="reference external" href="mailto:Ross@Gnome">Ross@Gnome</a>></li>
|
||||
<li>Tom Prince <<a class="reference external" href="mailto:tom.prince@ualberta.net">tom.prince@ualberta.net</a>></li>
|
||||
<li>Jared Adams <<a class="reference external" href="mailto:jaxad0127@gmail.com">jaxad0127@gmail.com</a>></li>
|
||||
<li>expwnent <<a class="reference external" href="mailto:q309185@gmail.com">q309185@gmail.com</a>></li>
|
||||
<li>Erik Youngren <<a class="reference external" href="mailto:artanis.00@gmail.com">artanis.00@gmail.com</a>></li>
|
||||
<li>Espen Wiborg <<a class="reference external" href="mailto:espen.wiborg@telio.no">espen.wiborg@telio.no</a>></li>
|
||||
<li>Tim Walberg <<a class="reference external" href="mailto:twalberg@comcast.net">twalberg@comcast.net</a>></li>
|
||||
<li>Mikko Juola <<a class="reference external" href="mailto:mikko.juola@kolumbus.fi">mikko.juola@kolumbus.fi</a>></li>
|
||||
<li>rampaging-poet <<a class="reference external" href="mailto:yrudoingthis@hotmail.com">yrudoingthis@hotmail.com</a>></li>
|
||||
<li>U-glouglou\simon</li>
|
||||
<li>Clayton Hughes <<a class="reference external" href="mailto:clayton.hughes@gmail.com">clayton.hughes@gmail.com</a>></li>
|
||||
<li>zilpin <<a class="reference external" href="mailto:ziLpin@gmail.com">ziLpin@gmail.com</a>></li>
|
||||
<li>Will Rogers <<a class="reference external" href="mailto:wjrogers@gmail.com">wjrogers@gmail.com</a>></li>
|
||||
<li>NMLittle <<a class="reference external" href="mailto:nmlittle@gmail.com">nmlittle@gmail.com</a>></li>
|
||||
<li>root</li>
|
||||
<li>reverb</li>
|
||||
<li>Zhentar <<a class="reference external" href="mailto:Zhentar@gmail.com">Zhentar@gmail.com</a>></li>
|
||||
<li>Valentin Ochs <<a class="reference external" href="mailto:a@0au.de">a@0au.de</a>></li>
|
||||
<li>Priit Laes <<a class="reference external" href="mailto:plaes@plaes.org">plaes@plaes.org</a>></li>
|
||||
<li>kmartin</li>
|
||||
<li>Neil Little</li>
|
||||
<li>rout <<a class="reference external" href="mailto:rout.mail+github@gmail.com">rout.mail+github@gmail.com</a>></li>
|
||||
<li>rofl0r <<a class="reference external" href="mailto:retnyg@gmx.net">retnyg@gmx.net</a>></li>
|
||||
<li>harlanplayford <<a class="reference external" href="mailto:harlanplayford@gmail.com">harlanplayford@gmail.com</a>></li>
|
||||
<li>gsvslto <<a class="reference external" href="mailto:gsvslto@gmail.com">gsvslto@gmail.com</a>></li>
|
||||
<li>sami</li>
|
||||
<li>potato</li>
|
||||
<li>playfordh <<a class="reference external" href="mailto:harlanplayford@gmail.com">harlanplayford@gmail.com</a>></li>
|
||||
<li>feng1st <<a class="reference external" href="mailto:nf_xp@hotmail.com">nf_xp@hotmail.com</a>></li>
|
||||
<li>comestible <<a class="reference external" href="mailto:nickolas.g.russell@gmail.com">nickolas.g.russell@gmail.com</a>></li>
|
||||
<li>Rumrusher <<a class="reference external" href="mailto:Anuleakage@yahoo.com">Anuleakage@yahoo.com</a>></li>
|
||||
<li>Rinin <<a class="reference external" href="mailto:RininS@Gmail.com">RininS@Gmail.com</a>></li>
|
||||
<li>Raoul van Putten</li>
|
||||
<li>John Shade <<a class="reference external" href="mailto:gsvslto@gmail.com">gsvslto@gmail.com</a>></li>
|
||||
<li>John Beisley <<a class="reference external" href="mailto:greatred@gmail.com">greatred@gmail.com</a>></li>
|
||||
<li>Feng <<a class="reference external" href="mailto:nf_xp@hotmail.com">nf_xp@hotmail.com</a>></li>
|
||||
<li>Donald Ruegsegger <<a class="reference external" href="mailto:druegsegger@gmail.com">druegsegger@gmail.com</a>></li>
|
||||
<li>Caldfir <<a class="reference external" href="mailto:caldfir@hotmail.com">caldfir@hotmail.com</a>></li>
|
||||
<li>Antalia <<a class="reference external" href="mailto:tamarakorr@gmail.com">tamarakorr@gmail.com</a>></li>
|
||||
<li>Angus Mezick <<a class="reference external" href="mailto:amezick@gmail.com">amezick@gmail.com</a>></li>
|
||||
</ul>
|
||||
<p>And those are the cool people who made <strong>stonesense</strong>.</p>
|
||||
<ul class="simple">
|
||||
<li>Kris Parker <kaypy></li>
|
||||
<li>Japa <<a class="reference external" href="mailto:japa.mala.illo@gmail.com">japa.mala.illo@gmail.com</a>></li>
|
||||
<li>Jonas Ask <<a class="reference external" href="mailto:jonask84@gmail.com">jonask84@gmail.com</a>></li>
|
||||
<li>Petr Mrázek <<a class="reference external" href="mailto:peterix@gmail.com">peterix@gmail.com</a>></li>
|
||||
<li>Caldfir <<a class="reference external" href="mailto:aitken.tim@gmail.com">aitken.tim@gmail.com</a>></li>
|
||||
<li>8Z <<a class="reference external" href="mailto:git8z@ya.ru">git8z@ya.ru</a>></li>
|
||||
<li>Alexander Gavrilov <<a class="reference external" href="mailto:angavrilov@gmail.com">angavrilov@gmail.com</a>></li>
|
||||
<li>Timothy Collett <<a class="reference external" href="mailto:tcollett+github@topazgryphon.org">tcollett+github@topazgryphon.org</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,74 @@
|
||||
Contributors
|
||||
============
|
||||
If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)
|
||||
|
||||
The following is a list of people who have contributed to **DFHack**.
|
||||
|
||||
- Petr Mrázek <peterix@gmail.com>
|
||||
- Alexander Gavrilov <angavrilov@gmail.com>
|
||||
- doomchild <lee.crabtree@gmail.com>
|
||||
- Quietust <quietust@gmail.com>
|
||||
- jj <john-git@ofjj.net>
|
||||
- Warmist <warmist@gmail.com>
|
||||
- Robert Heinrich <robertheinrich73@googlemail.com>
|
||||
- simon <simon@banquise.net>
|
||||
- Kelly Martin <kelly.lynn.martin@gmail.com>
|
||||
- mizipzor <mizipzor@gmail.com>
|
||||
- Simon Jackson <sizeak@hotmail.com>
|
||||
- belal <jimbelal@gmail.com>
|
||||
- RusAnon <rusanon@dollchan.ru>
|
||||
- Raoul XQ <raoulxq@gmail.com>
|
||||
- Matthew Cline <zelgadis@sourceforge.net>
|
||||
- Mike Stewart <thewonderidiot@gmail.com>
|
||||
- Timothy Collett <tcollett+github@topazgryphon.org>
|
||||
- RossM <Ross@Gnome>
|
||||
- Tom Prince <tom.prince@ualberta.net>
|
||||
- Jared Adams <jaxad0127@gmail.com>
|
||||
- expwnent <q309185@gmail.com>
|
||||
- Erik Youngren <artanis.00@gmail.com>
|
||||
- Espen Wiborg <espen.wiborg@telio.no>
|
||||
- Tim Walberg <twalberg@comcast.net>
|
||||
- Mikko Juola <mikko.juola@kolumbus.fi>
|
||||
- rampaging-poet <yrudoingthis@hotmail.com>
|
||||
- U-glouglou\\simon
|
||||
- Clayton Hughes <clayton.hughes@gmail.com>
|
||||
- zilpin <ziLpin@gmail.com>
|
||||
- Will Rogers <wjrogers@gmail.com>
|
||||
- NMLittle <nmlittle@gmail.com>
|
||||
- root
|
||||
- reverb
|
||||
- Zhentar <Zhentar@gmail.com>
|
||||
- Valentin Ochs <a@0au.de>
|
||||
- Priit Laes <plaes@plaes.org>
|
||||
- kmartin
|
||||
- Neil Little
|
||||
- rout <rout.mail+github@gmail.com>
|
||||
- rofl0r <retnyg@gmx.net>
|
||||
- harlanplayford <harlanplayford@gmail.com>
|
||||
- gsvslto <gsvslto@gmail.com>
|
||||
- sami
|
||||
- potato
|
||||
- playfordh <harlanplayford@gmail.com>
|
||||
- feng1st <nf_xp@hotmail.com>
|
||||
- comestible <nickolas.g.russell@gmail.com>
|
||||
- Rumrusher <Anuleakage@yahoo.com>
|
||||
- Rinin <RininS@Gmail.com>
|
||||
- Raoul van Putten
|
||||
- John Shade <gsvslto@gmail.com>
|
||||
- John Beisley <greatred@gmail.com>
|
||||
- Feng <nf_xp@hotmail.com>
|
||||
- Donald Ruegsegger <druegsegger@gmail.com>
|
||||
- Caldfir <caldfir@hotmail.com>
|
||||
- Antalia <tamarakorr@gmail.com>
|
||||
- Angus Mezick <amezick@gmail.com>
|
||||
|
||||
And those are the cool people who made **stonesense**.
|
||||
|
||||
- Kris Parker <kaypy>
|
||||
- Japa <japa.mala.illo@gmail.com>
|
||||
- Jonas Ask <jonask84@gmail.com>
|
||||
- Petr Mrázek <peterix@gmail.com>
|
||||
- Caldfir <aitken.tim@gmail.com>
|
||||
- 8Z <git8z@ya.ru>
|
||||
- Alexander Gavrilov <angavrilov@gmail.com>
|
||||
- Timothy Collett <tcollett+github@topazgryphon.org>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,124 @@
|
||||
DFHack future
|
||||
|
||||
Internals:
|
||||
- support for displaying active keybindings properly.
|
||||
- support for reusable widgets in lua screen library.
|
||||
Notable bugfixes:
|
||||
- autobutcher can be re-enabled again after being stopped.
|
||||
- stopped Dwarf Manipulator from unmasking vampires.
|
||||
Misc improvements:
|
||||
- fastdwarf: new mode using debug flags, and some internal consistency fixes.
|
||||
- added a small stand-alone utility for applying and removing binary patches.
|
||||
- removebadthoughts: add --dry-run option
|
||||
New scripts:
|
||||
- region-pops: displays animal populations of the region and allows tweaking them.
|
||||
New GUI scripts:
|
||||
- gui/guide-path: displays the cached path for minecart Guide orders.
|
||||
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
|
||||
- gui/workflow: a front-end for the workflow plugin.
|
||||
- gui/assign-rack: works together with a binary patch to fix weapon racks.
|
||||
Workflow plugin:
|
||||
- properly considers minecarts assigned to routes busy.
|
||||
- code for deducing job outputs rewritten in lua for flexibility.
|
||||
- logic fix: collecting webs produces silk, and ungathered webs are not thread.
|
||||
New Fix Armory plugin:
|
||||
Together with a couple of binary patches and the gui/assign-rack script,
|
||||
this plugin makes weapon racks, armor stands, chests and cabinets in
|
||||
properly designated barracks be used again for storage of squad equipment.
|
||||
New Search plugin by falconne:
|
||||
Adds an incremental search function to the Stocks, Trading and Unit List screens.
|
||||
|
||||
|
||||
DFHack v0.34.11-r2
|
||||
|
||||
Internals:
|
||||
- full support for Mac OS X.
|
||||
- a plugin that adds scripting in ruby.
|
||||
- support for interposing virtual methods in DF from C++ plugins.
|
||||
- support for creating new interface screens from C++ and lua.
|
||||
- added various other API functions.
|
||||
Notable bugfixes:
|
||||
- better terminal reset after exit on linux.
|
||||
- seedwatch now works on reclaim.
|
||||
- the sort plugin won't crash on cages anymore.
|
||||
Misc improvements:
|
||||
- autodump: can move items to any walkable tile, not just floors.
|
||||
- stripcaged: by default keep armor, new dumparmor option.
|
||||
- zone: allow non-domesticated birds in nestboxes.
|
||||
- workflow: quality range in constraints.
|
||||
- cleanplants: new command to remove rain water from plants.
|
||||
- liquids: can paint permaflow, i.e. what makes rivers power water wheels.
|
||||
- prospect: pre-embark prospector accounts for caves & magma sea in its estimate.
|
||||
- rename: supports renaming stockpiles, workshops, traps, siege engines.
|
||||
- fastdwarf: now has an additional option to make dwarves teleport to their destination.
|
||||
New commands:
|
||||
- misery: multiplies every negative thought gained (2x by default).
|
||||
- digtype: designates every tile of the same type of vein on the map for 'digging' (any dig designation).
|
||||
New tweaks:
|
||||
- tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus.
|
||||
- tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does.
|
||||
- tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui.
|
||||
- tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort.
|
||||
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
|
||||
- tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc.
|
||||
- tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode.
|
||||
- tweak fast-trade: Shift-Enter for selecting items quckly in Trade and Move to Depot screens.
|
||||
- tweak military-stable-assign: Stop rightmost list of military->Positions from jumping to top.
|
||||
- tweak military-color-assigned: In same list, color already assigned units in brown & green.
|
||||
New scripts:
|
||||
- fixnaked: removes thoughts about nakedness.
|
||||
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
|
||||
- siren: wakes up units, stops breaks and parties - but causes bad thoughts.
|
||||
- fix/population-cap: run after every migrant wave to prevent exceeding the cap.
|
||||
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
|
||||
- fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill.
|
||||
- deathcause: shows the circumstances of death for a given body.
|
||||
- digfort: designate areas to dig from a csv file.
|
||||
- drainaquifer: remove aquifers from the map.
|
||||
- growcrops: cheat to make farm crops instantly grow.
|
||||
- magmasource: continuously spawn magma from any map tile.
|
||||
- removebadthoughts: delete all negative thoughts from your dwarves.
|
||||
- slayrace: instakill all units of a given race, optionally with magma.
|
||||
- superdwarf: per-creature fastdwarf.
|
||||
New GUI scripts:
|
||||
- gui/mechanisms: browse mechanism links of the current building.
|
||||
- gui/room-list: browse other rooms owned by the unit when assigning one.
|
||||
- gui/liquids: a GUI front-end for the liquids plugin.
|
||||
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
|
||||
- gui/power-meter: front-end for the Power Meter plugin.
|
||||
- gui/siege-engine: front-end for the Siege Engine plugin.
|
||||
- gui/choose-weapons: auto-choose matching weapons in the military equip screen.
|
||||
Autolabor plugin:
|
||||
- can set nonidle hauler percentage.
|
||||
- broker excluded from all labors when needed at depot.
|
||||
- likewise, anybody with a scheduled diplomat meeting.
|
||||
New Dwarf Manipulator plugin:
|
||||
Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game.
|
||||
New Steam Engine plugin:
|
||||
Dwarven Water Reactors don't make any sense whatsoever and cause lag, so this may be
|
||||
a replacement for those concerned by it. The plugin detects if a workshop with a
|
||||
certain name is in the raws used by the current world, and provides the necessary
|
||||
behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions.
|
||||
Note: Stuff like animal treadmills might be more period, but absolutely can't be
|
||||
done with tools dfhack has access to.
|
||||
New Power Meter plugin:
|
||||
When activated, implements a pressure plate modification that detects power in gear
|
||||
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
|
||||
the necessary build configuration UI.
|
||||
New Siege Engine plugin:
|
||||
When enabled and configured via gui/siege-engine, allows aiming siege engines
|
||||
at a designated rectangular area with 360 degree fire range and across Z levels;
|
||||
this works by rewriting the projectile trajectory immediately after it appears.
|
||||
Also supports loading catapults with non-boulder projectiles, taking from a stockpile,
|
||||
and restricting operator skill range like with ordinary workshops.
|
||||
Disclaimer: not in any way to undermine the future siege update from Toady, but
|
||||
the aiming logic of existing engines hasn't been updated since 2D, and is almost
|
||||
useless above ground :(. Again, things like making siegers bring their own engines
|
||||
is totally out of the scope of dfhack and can only be done by Toady.
|
||||
New Add Spatter plugin:
|
||||
Detects reactions with certain names in the raws, and changes them from adding
|
||||
improvements to adding item contaminants. This allows directly covering items
|
||||
with poisons. The added spatters are immune both to water and 'clean items'.
|
||||
Intended to give some use to all those giant cave spider poison barrels brought
|
||||
by the caravans.
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0
|
||||
Subproject commit 178a4916da838e46e46e769e566e47fff6eff8f8
|
@ -1,5 +1,18 @@
|
||||
#!/bin/bash
|
||||
rst2html README.rst > Readme.html
|
||||
rst2html COMPILE.rst > Compile.html
|
||||
rst2html DEVEL.rst > Devel.html
|
||||
rst2html LUA_API.rst > Lua\ API.html
|
||||
# regenerate documentation after editing the .rst files. Requires python and docutils.
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
function process() {
|
||||
if [ "$1" -nt "$2" ]; then
|
||||
rst2html --no-generator --no-datestamp "$1" "$2"
|
||||
else
|
||||
echo "$2 - up to date."
|
||||
fi
|
||||
}
|
||||
|
||||
process Readme.rst Readme.html
|
||||
process Compile.rst Compile.html
|
||||
process Lua\ API.rst Lua\ API.html
|
||||
process Contributors.rst Contributors.html
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,535 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
#include "MemAccess.h"
|
||||
#include "Core.h"
|
||||
#include "VersionInfo.h"
|
||||
#include "VTableInterpose.h"
|
||||
|
||||
#include "MiscUtils.h"
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
/*
|
||||
* Code for accessing method pointers directly. Very compiler-specific.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
struct MSVC_MPTR {
|
||||
void *method;
|
||||
intptr_t this_shift;
|
||||
};
|
||||
|
||||
static uint32_t *follow_jmp(void *ptr)
|
||||
{
|
||||
uint8_t *p = (uint8_t*)ptr;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
switch (*p)
|
||||
{
|
||||
case 0xE9:
|
||||
p += 5 + *(int32_t*)(p+1);
|
||||
break;
|
||||
case 0xEB:
|
||||
p += 2 + *(int8_t*)(p+1);
|
||||
break;
|
||||
default:
|
||||
return (uint32_t*)p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DFHack::is_vmethod_pointer_(void *pptr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
if (!pobj->method) return false;
|
||||
|
||||
// MSVC implements pointers to vmethods via thunks.
|
||||
// This expects that they all follow a very specific pattern.
|
||||
auto pval = follow_jmp(pobj->method);
|
||||
switch (pval[0]) {
|
||||
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
|
||||
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
|
||||
case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int DFHack::vmethod_pointer_to_idx_(void *pptr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
if (!pobj->method || pobj->this_shift != 0) return -1;
|
||||
|
||||
auto pval = follow_jmp(pobj->method);
|
||||
switch (pval[0]) {
|
||||
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
|
||||
return 0;
|
||||
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
|
||||
return ((int8_t)pval[1])/sizeof(void*);
|
||||
case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
|
||||
return ((int32_t)pval[1])/sizeof(void*);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DFHack::method_pointer_to_addr_(void *pptr)
|
||||
{
|
||||
if (is_vmethod_pointer_(pptr)) return NULL;
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
return pobj->method;
|
||||
}
|
||||
|
||||
void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
pobj->method = addr;
|
||||
pobj->this_shift = 0;
|
||||
}
|
||||
|
||||
#elif defined(__GXX_ABI_VERSION)
|
||||
|
||||
struct GCC_MPTR {
|
||||
intptr_t method;
|
||||
intptr_t this_shift;
|
||||
};
|
||||
|
||||
bool DFHack::is_vmethod_pointer_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
return (pobj->method & 1) != 0;
|
||||
}
|
||||
|
||||
int DFHack::vmethod_pointer_to_idx_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
if ((pobj->method & 1) == 0 || pobj->this_shift != 0)
|
||||
return -1;
|
||||
return (pobj->method-1)/sizeof(void*);
|
||||
}
|
||||
|
||||
void* DFHack::method_pointer_to_addr_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
if ((pobj->method & 1) != 0 || pobj->this_shift != 0)
|
||||
return NULL;
|
||||
return (void*)pobj->method;
|
||||
}
|
||||
|
||||
void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
pobj->method = (intptr_t)addr;
|
||||
pobj->this_shift = 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#error Unknown compiler type
|
||||
#endif
|
||||
|
||||
void *virtual_identity::get_vmethod_ptr(int idx)
|
||||
{
|
||||
assert(idx >= 0);
|
||||
void **vtable = (void**)vtable_ptr;
|
||||
if (!vtable) return NULL;
|
||||
return vtable[idx];
|
||||
}
|
||||
|
||||
bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr)
|
||||
{
|
||||
assert(idx >= 0);
|
||||
void **vtable = (void**)vtable_ptr;
|
||||
if (!vtable) return NULL;
|
||||
return patcher.write(&vtable[idx], &ptr, sizeof(void*));
|
||||
}
|
||||
|
||||
/*
|
||||
VMethod interposing data structures.
|
||||
|
||||
In order to properly support adding and removing hooks,
|
||||
it is necessary to track them. This is what this class
|
||||
is for. The task is further complicated by propagating
|
||||
hooks to child classes that use exactly the same original
|
||||
vmethod implementation.
|
||||
|
||||
Every applied link contains in the saved_chain field a
|
||||
pointer to the next vmethod body that should be called
|
||||
by the hook the link represents. This is the actual
|
||||
control flow structure that needs to be maintained.
|
||||
|
||||
There also are connections between link objects themselves,
|
||||
which constitute the bookkeeping for doing that. Finally,
|
||||
every link is associated with a fixed virtual_identity host,
|
||||
which represents the point in the class hierarchy where
|
||||
the hook is applied.
|
||||
|
||||
When there are no subclasses (i.e. only one host), the
|
||||
structures look like this:
|
||||
|
||||
+--------------+ +------------+
|
||||
| link1 |-next------->| link2 |-next=NULL
|
||||
|s_c: original |<-------prev-|s_c: $link1 |<--+
|
||||
+--------------+ +------------+ |
|
||||
|
|
||||
host->interpose_list[vmethod_idx] ------+
|
||||
vtable: $link2
|
||||
|
||||
The original vtable entry is stored in the saved_chain of the
|
||||
first link. The interpose_list map points to the last one.
|
||||
The hooks are called in order: link2 -> link1 -> original.
|
||||
|
||||
When there are subclasses that use the same vmethod, but don't
|
||||
hook it, the topmost link gets a set of the child_hosts, and
|
||||
the hosts have the link added to their interpose_list:
|
||||
|
||||
+--------------+ +----------------+
|
||||
| link0 @host0 |<--+-interpose_list-| host1 |
|
||||
| |-child_hosts-+----->| vtable: $link |
|
||||
+--------------+ | | +----------------+
|
||||
| |
|
||||
| | +----------------+
|
||||
+-interpose_list-| host2 |
|
||||
+----->| vtable: $link |
|
||||
+----------------+
|
||||
|
||||
When a child defines its own hook, the child_hosts link is
|
||||
severed and replaced with a child_next pointer to the new
|
||||
hook. The hook still points back the chain with prev.
|
||||
All child links to subclasses of host2 are migrated from
|
||||
link1 to link2.
|
||||
|
||||
+--------------+-next=NULL +--------------+-next=NULL
|
||||
| link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses
|
||||
| |<-------------prev-|s_c: $link1 |
|
||||
+--------------+<-------+ +--------------+<-------+
|
||||
| |
|
||||
+--------------+ | +--------------+ |
|
||||
| host1 |-i_list-+ | host2 |-i_list-+
|
||||
|vtable: $link1| |vtable: $link2|
|
||||
+--------------+ +--------------+
|
||||
|
||||
*/
|
||||
|
||||
void VMethodInterposeLinkBase::set_chain(void *chain)
|
||||
{
|
||||
saved_chain = chain;
|
||||
addr_to_method_pointer_(chain_mptr, chain);
|
||||
}
|
||||
|
||||
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority)
|
||||
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method),
|
||||
chain_mptr(chain_mptr), priority(priority),
|
||||
applied(false), saved_chain(NULL), next(NULL), prev(NULL)
|
||||
{
|
||||
if (vmethod_idx < 0 || interpose_method == NULL)
|
||||
{
|
||||
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
|
||||
vmethod_idx, unsigned(interpose_method));
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
|
||||
{
|
||||
if (is_applied())
|
||||
remove();
|
||||
}
|
||||
|
||||
VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id)
|
||||
{
|
||||
auto item = id->interpose_list[vmethod_idx];
|
||||
if (!item)
|
||||
return NULL;
|
||||
|
||||
if (item->host != id)
|
||||
return NULL;
|
||||
while (item->prev && item->prev->host == id)
|
||||
item = item->prev;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
bool VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
|
||||
{
|
||||
auto &children = cur->getChildren();
|
||||
bool found = false;
|
||||
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto child = static_cast<virtual_identity*>(children[i]);
|
||||
auto base = get_first_interpose(child);
|
||||
|
||||
if (base)
|
||||
{
|
||||
assert(base->prev == NULL);
|
||||
|
||||
if (base->saved_chain != vmptr)
|
||||
continue;
|
||||
|
||||
child_next.insert(base);
|
||||
found = true;
|
||||
}
|
||||
else if (child->vtable_ptr)
|
||||
{
|
||||
void *cptr = child->get_vmethod_ptr(vmethod_idx);
|
||||
if (cptr != vmptr)
|
||||
continue;
|
||||
|
||||
child_hosts.insert(child);
|
||||
found = true;
|
||||
|
||||
find_child_hosts(child, vmptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this vtable is not known, but any of the children
|
||||
// have the same vmethod, this one definitely does too
|
||||
if (find_child_hosts(child, vmptr))
|
||||
{
|
||||
child_hosts.insert(child);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
|
||||
{
|
||||
if (from == host)
|
||||
{
|
||||
// When in own host, fully delete
|
||||
remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, drop the link to that child:
|
||||
assert(child_hosts.count(from) != 0 &&
|
||||
from->interpose_list[vmethod_idx] == this);
|
||||
|
||||
// Find and restore the original vmethod ptr
|
||||
auto last = this;
|
||||
while (last->prev) last = last->prev;
|
||||
|
||||
MemoryPatcher patcher;
|
||||
|
||||
from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain);
|
||||
|
||||
// Unlink the chains
|
||||
child_hosts.erase(from);
|
||||
from->interpose_list[vmethod_idx] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool VMethodInterposeLinkBase::apply(bool enable)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_applied())
|
||||
return true;
|
||||
if (!host->vtable_ptr)
|
||||
return false;
|
||||
|
||||
// Retrieve the current vtable entry
|
||||
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
|
||||
VMethodInterposeLinkBase *next_link = NULL;
|
||||
|
||||
while (old_link && old_link->host == host && old_link->priority > priority)
|
||||
{
|
||||
next_link = old_link;
|
||||
old_link = old_link->prev;
|
||||
}
|
||||
|
||||
void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx);
|
||||
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
|
||||
|
||||
// Apply the new method ptr
|
||||
MemoryPatcher patcher;
|
||||
|
||||
set_chain(old_ptr);
|
||||
|
||||
if (next_link)
|
||||
{
|
||||
next_link->set_chain(interpose_method);
|
||||
}
|
||||
else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
|
||||
{
|
||||
set_chain(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Push the current link into the home host
|
||||
applied = true;
|
||||
prev = old_link;
|
||||
next = next_link;
|
||||
|
||||
if (next_link)
|
||||
next_link->prev = this;
|
||||
else
|
||||
host->interpose_list[vmethod_idx] = this;
|
||||
|
||||
child_hosts.clear();
|
||||
child_next.clear();
|
||||
|
||||
if (old_link && old_link->host == host)
|
||||
{
|
||||
// If the old link is home, just push into the plain chain
|
||||
assert(old_link->next == next_link);
|
||||
old_link->next = this;
|
||||
|
||||
// Child links belong to the topmost local entry
|
||||
child_hosts.swap(old_link->child_hosts);
|
||||
child_next.swap(old_link->child_next);
|
||||
}
|
||||
else if (next_link)
|
||||
{
|
||||
if (old_link)
|
||||
{
|
||||
assert(old_link->child_next.count(next_link));
|
||||
old_link->child_next.erase(next_link);
|
||||
old_link->child_next.insert(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If creating a new local chain, find children with same vmethod
|
||||
find_child_hosts(host, old_ptr);
|
||||
|
||||
if (old_link)
|
||||
{
|
||||
// Enter the child chain set
|
||||
assert(old_link->child_hosts.count(host));
|
||||
old_link->child_hosts.erase(host);
|
||||
old_link->child_next.insert(this);
|
||||
|
||||
// Subtract our own children from the parent's sets
|
||||
for (auto it = child_next.begin(); it != child_next.end(); ++it)
|
||||
old_link->child_next.erase(*it);
|
||||
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
|
||||
old_link->child_hosts.erase(*it);
|
||||
}
|
||||
}
|
||||
|
||||
assert (!next_link || (child_next.empty() && child_hosts.empty()));
|
||||
|
||||
// Chain subclass hooks
|
||||
for (auto it = child_next.begin(); it != child_next.end(); ++it)
|
||||
{
|
||||
auto nlink = *it;
|
||||
assert(nlink->saved_chain == old_ptr && nlink->prev == old_link);
|
||||
nlink->set_chain(interpose_method);
|
||||
nlink->prev = this;
|
||||
}
|
||||
|
||||
// Chain passive subclass hosts
|
||||
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
|
||||
{
|
||||
auto nhost = *it;
|
||||
assert(nhost->interpose_list[vmethod_idx] == old_link);
|
||||
nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method);
|
||||
nhost->interpose_list[vmethod_idx] = this;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::remove()
|
||||
{
|
||||
if (!is_applied())
|
||||
return;
|
||||
|
||||
// Remove the link from prev to this
|
||||
if (prev)
|
||||
{
|
||||
if (prev->host == host)
|
||||
prev->next = next;
|
||||
else
|
||||
{
|
||||
prev->child_next.erase(this);
|
||||
|
||||
if (next)
|
||||
prev->child_next.insert(next);
|
||||
else
|
||||
prev->child_hosts.insert(host);
|
||||
}
|
||||
}
|
||||
|
||||
if (next)
|
||||
{
|
||||
next->set_chain(saved_chain);
|
||||
next->prev = prev;
|
||||
|
||||
assert(child_next.empty() && child_hosts.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryPatcher patcher;
|
||||
|
||||
// Remove from the list in the identity and vtable
|
||||
host->interpose_list[vmethod_idx] = prev;
|
||||
host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
|
||||
|
||||
for (auto it = child_next.begin(); it != child_next.end(); ++it)
|
||||
{
|
||||
auto nlink = *it;
|
||||
assert(nlink->saved_chain == interpose_method && nlink->prev == this);
|
||||
nlink->set_chain(saved_chain);
|
||||
nlink->prev = prev;
|
||||
if (prev)
|
||||
prev->child_next.insert(nlink);
|
||||
}
|
||||
|
||||
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
|
||||
{
|
||||
auto nhost = *it;
|
||||
assert(nhost->interpose_list[vmethod_idx] == this);
|
||||
nhost->interpose_list[vmethod_idx] = prev;
|
||||
nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
|
||||
if (prev)
|
||||
prev->child_hosts.insert(nhost);
|
||||
}
|
||||
}
|
||||
|
||||
applied = false;
|
||||
prev = next = NULL;
|
||||
child_next.clear();
|
||||
child_hosts.clear();
|
||||
set_chain(NULL);
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "DataFuncs.h"
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
template<bool> struct StaticAssert;
|
||||
template<> struct StaticAssert<true> {};
|
||||
|
||||
#define STATIC_ASSERT(condition) { StaticAssert<(condition)>(); }
|
||||
|
||||
/* Wrapping around compiler-specific representation of pointers to methods. */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define METHOD_POINTER_SIZE (sizeof(void*)*2)
|
||||
#elif defined(__GXX_ABI_VERSION)
|
||||
#define METHOD_POINTER_SIZE (sizeof(void*)*2)
|
||||
#else
|
||||
#error Unknown compiler type
|
||||
#endif
|
||||
|
||||
#define ASSERT_METHOD_POINTER(type) \
|
||||
STATIC_ASSERT(df::return_type<type>::is_method && sizeof(type)==METHOD_POINTER_SIZE);
|
||||
|
||||
DFHACK_EXPORT bool is_vmethod_pointer_(void*);
|
||||
DFHACK_EXPORT int vmethod_pointer_to_idx_(void*);
|
||||
DFHACK_EXPORT void* method_pointer_to_addr_(void*);
|
||||
DFHACK_EXPORT void addr_to_method_pointer_(void*,void*);
|
||||
|
||||
template<class T> bool is_vmethod_pointer(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return is_vmethod_pointer_(&ptr);
|
||||
}
|
||||
template<class T> int vmethod_pointer_to_idx(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return vmethod_pointer_to_idx_(&ptr);
|
||||
}
|
||||
template<class T> void *method_pointer_to_addr(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return method_pointer_to_addr_(&ptr);
|
||||
}
|
||||
template<class T> T addr_to_method_pointer(void *addr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
T rv;
|
||||
addr_to_method_pointer_(&rv, addr);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Access to vmethod pointers from the vtable. */
|
||||
|
||||
template<class P>
|
||||
P virtual_identity::get_vmethod_ptr(P selector)
|
||||
{
|
||||
typedef typename df::return_type<P>::class_type host_class;
|
||||
virtual_identity &identity = host_class::_identity;
|
||||
int idx = vmethod_pointer_to_idx(selector);
|
||||
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
|
||||
}
|
||||
|
||||
/* VMethod interpose API.
|
||||
|
||||
This API allows replacing an entry in the original vtable
|
||||
with code defined by DFHack, while retaining ability to
|
||||
call the original code. The API can be safely used from
|
||||
plugins, and multiple hooks for the same vmethod are
|
||||
automatically chained (subclass before superclass; at same
|
||||
level highest priority called first; undefined order otherwise).
|
||||
|
||||
Usage:
|
||||
|
||||
struct my_hack : df::someclass {
|
||||
typedef df::someclass interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
|
||||
// If needed by the code, claim the suspend lock.
|
||||
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
|
||||
// CoreSuspendClaimer suspend;
|
||||
...
|
||||
INTERPOSE_NEXT(foo)(arg) // call the original
|
||||
...
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
|
||||
or
|
||||
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
|
||||
|
||||
void init() {
|
||||
if (!INTERPOSE_HOOK(my_hack, foo).apply())
|
||||
error();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
INTERPOSE_HOOK(my_hack, foo).remove();
|
||||
}
|
||||
*/
|
||||
|
||||
#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
|
||||
typedef rtype (interpose_base::*interpose_ptr_##name)args; \
|
||||
static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
|
||||
rtype interpose_fn_##name args
|
||||
|
||||
#define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \
|
||||
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
|
||||
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority);
|
||||
|
||||
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0)
|
||||
|
||||
#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
|
||||
#define INTERPOSE_HOOK(class, name) (class::interpose_##name)
|
||||
|
||||
class DFHACK_EXPORT VMethodInterposeLinkBase {
|
||||
/*
|
||||
These link objects try to:
|
||||
1) Allow multiple hooks into the same vmethod
|
||||
2) Auto-remove hooks when a plugin is unloaded.
|
||||
*/
|
||||
friend class virtual_identity;
|
||||
|
||||
virtual_identity *host; // Class with the vtable
|
||||
int vmethod_idx;
|
||||
void *interpose_method; // Pointer to the code of the interposing method
|
||||
void *chain_mptr; // Pointer to the chain field below
|
||||
int priority;
|
||||
|
||||
bool applied;
|
||||
void *saved_chain; // Previous pointer to the code
|
||||
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
|
||||
|
||||
// inherited vtable members
|
||||
std::set<virtual_identity*> child_hosts;
|
||||
std::set<VMethodInterposeLinkBase*> child_next;
|
||||
|
||||
void set_chain(void *chain);
|
||||
void on_host_delete(virtual_identity *host);
|
||||
|
||||
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
|
||||
bool find_child_hosts(virtual_identity *cur, void *vmptr);
|
||||
public:
|
||||
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority);
|
||||
~VMethodInterposeLinkBase();
|
||||
|
||||
bool is_applied() { return applied; }
|
||||
bool apply(bool enable = true);
|
||||
void remove();
|
||||
};
|
||||
|
||||
template<class Base, class Ptr>
|
||||
class VMethodInterposeLink : public VMethodInterposeLinkBase {
|
||||
public:
|
||||
Ptr chain;
|
||||
|
||||
operator Ptr () { return chain; }
|
||||
|
||||
template<class Ptr2>
|
||||
VMethodInterposeLink(Ptr target, Ptr2 src, int priority)
|
||||
: VMethodInterposeLinkBase(
|
||||
&Base::_identity,
|
||||
vmethod_pointer_to_idx(target),
|
||||
method_pointer_to_addr(src),
|
||||
&chain,
|
||||
priority
|
||||
)
|
||||
{ src = target; /* check compatibility */ }
|
||||
};
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
/*
|
||||
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
|
||||
#include "Export.h"
|
||||
#include "Module.h"
|
||||
#include "BitArray.h"
|
||||
#include "ColorText.h"
|
||||
#include <string>
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/graphic.h"
|
||||
#include "df/viewscreen.h"
|
||||
|
||||
namespace df
|
||||
{
|
||||
struct job;
|
||||
struct item;
|
||||
struct unit;
|
||||
struct building;
|
||||
}
|
||||
|
||||
/**
|
||||
* \defgroup grp_screen utilities for painting to the screen
|
||||
* @ingroup grp_screen
|
||||
*/
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
class Core;
|
||||
|
||||
/**
|
||||
* The Screen module
|
||||
* \ingroup grp_modules
|
||||
* \ingroup grp_screen
|
||||
*/
|
||||
namespace Screen
|
||||
{
|
||||
/// Data structure describing all properties a screen tile can have
|
||||
struct Pen {
|
||||
// Ordinary text symbol
|
||||
char ch;
|
||||
int8_t fg, bg;
|
||||
bool bold;
|
||||
|
||||
// Graphics tile
|
||||
int tile;
|
||||
enum TileMode {
|
||||
AsIs, // Tile colors used without modification
|
||||
CharColor, // The fg/bg pair is used
|
||||
TileColor // The fields below are used
|
||||
} tile_mode;
|
||||
int8_t tile_fg, tile_bg;
|
||||
|
||||
bool valid() const { return tile >= 0; }
|
||||
bool empty() const { return ch == 0 && tile == 0; }
|
||||
|
||||
// NOTE: LuaApi.cpp assumes this struct is plain data and has empty destructor
|
||||
|
||||
Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false)
|
||||
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
|
||||
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile = 0, bool color_tile = false)
|
||||
: ch(ch), fg(fg), bg(bg), bold(bold),
|
||||
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, int tile, int8_t tile_fg, int8_t tile_bg)
|
||||
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
|
||||
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile, int8_t tile_fg, int8_t tile_bg)
|
||||
: ch(ch), fg(fg), bg(bg), bold(bold),
|
||||
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
|
||||
{}
|
||||
};
|
||||
|
||||
DFHACK_EXPORT df::coord2d getMousePos();
|
||||
DFHACK_EXPORT df::coord2d getWindowSize();
|
||||
|
||||
/// Returns the state of [GRAPHICS:YES/NO]
|
||||
DFHACK_EXPORT bool inGraphicsMode();
|
||||
|
||||
/// Paint one screen tile with the given pen
|
||||
DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y);
|
||||
|
||||
/// Retrieves one screen tile from the buffer
|
||||
DFHACK_EXPORT Pen readTile(int x, int y);
|
||||
|
||||
/// Paint a string onto the screen. Ignores ch and tile of pen.
|
||||
DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text);
|
||||
|
||||
/// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile.
|
||||
DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2);
|
||||
|
||||
/// Draws a standard dark gray window border with a title string
|
||||
DFHACK_EXPORT bool drawBorder(const std::string &title);
|
||||
|
||||
/// Wipes the screen to full black
|
||||
DFHACK_EXPORT bool clear();
|
||||
|
||||
/// Requests repaint
|
||||
DFHACK_EXPORT bool invalidate();
|
||||
|
||||
/// Find a loaded graphics tile from graphics raws.
|
||||
DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL);
|
||||
|
||||
// Push and remove viewscreens
|
||||
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
|
||||
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
|
||||
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
|
||||
|
||||
/// Retrieve the string representation of the bound key.
|
||||
DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key);
|
||||
}
|
||||
|
||||
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {
|
||||
df::coord2d last_size;
|
||||
void check_resize();
|
||||
|
||||
protected:
|
||||
bool text_input_mode;
|
||||
|
||||
public:
|
||||
dfhack_viewscreen();
|
||||
virtual ~dfhack_viewscreen();
|
||||
|
||||
static bool is_instance(df::viewscreen *screen);
|
||||
static dfhack_viewscreen *try_cast(df::viewscreen *screen);
|
||||
|
||||
virtual void logic();
|
||||
virtual void render();
|
||||
|
||||
virtual int8_t movies_okay() { return 1; }
|
||||
virtual bool key_conflict(df::interface_key key);
|
||||
|
||||
virtual bool is_lua_screen() { return false; }
|
||||
|
||||
virtual std::string getFocusString() = 0;
|
||||
virtual void onShow() {};
|
||||
virtual void onDismiss() {};
|
||||
virtual df::unit *getSelectedUnit() { return NULL; }
|
||||
virtual df::item *getSelectedItem() { return NULL; }
|
||||
virtual df::job *getSelectedJob() { return NULL; }
|
||||
virtual df::building *getSelectedBuilding() { return NULL; }
|
||||
};
|
||||
|
||||
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
|
||||
std::string focus;
|
||||
|
||||
void update_focus(lua_State *L, int idx);
|
||||
|
||||
bool safe_call_lua(int (*pf)(lua_State *), int args, int rvs);
|
||||
static dfhack_lua_viewscreen *get_self(lua_State *L);
|
||||
|
||||
static int do_destroy(lua_State *L);
|
||||
static int do_render(lua_State *L);
|
||||
static int do_notify(lua_State *L);
|
||||
static int do_input(lua_State *L);
|
||||
|
||||
public:
|
||||
dfhack_lua_viewscreen(lua_State *L, int table_idx);
|
||||
virtual ~dfhack_lua_viewscreen();
|
||||
|
||||
static df::viewscreen *get_pointer(lua_State *L, int idx, bool make);
|
||||
|
||||
virtual bool is_lua_screen() { return true; }
|
||||
virtual std::string getFocusString() { return focus; }
|
||||
|
||||
virtual void render();
|
||||
virtual void logic();
|
||||
virtual void help();
|
||||
virtual void resize(int w, int h);
|
||||
virtual void feed(std::set<df::interface_key> *keys);
|
||||
|
||||
virtual void onShow();
|
||||
virtual void onDismiss();
|
||||
|
||||
virtual df::unit *getSelectedUnit();
|
||||
virtual df::item *getSelectedItem();
|
||||
virtual df::job *getSelectedJob();
|
||||
virtual df::building *getSelectedBuilding();
|
||||
};
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
-- A trivial reloadable class system
|
||||
|
||||
local _ENV = mkmodule('class')
|
||||
|
||||
-- Metatable template for a class
|
||||
class_obj = class_obj or {}
|
||||
|
||||
-- Methods shared by all classes
|
||||
common_methods = common_methods or {}
|
||||
|
||||
-- Forbidden names for class fields and methods.
|
||||
reserved_names = { super = true, ATTRS = true }
|
||||
|
||||
-- Attribute table metatable
|
||||
attrs_meta = attrs_meta or {}
|
||||
|
||||
-- Create or updates a class; a class has metamethods and thus own metatable.
|
||||
function defclass(class,parent)
|
||||
class = class or {}
|
||||
|
||||
local meta = getmetatable(class)
|
||||
if not meta then
|
||||
meta = {}
|
||||
setmetatable(class, meta)
|
||||
end
|
||||
|
||||
for k,v in pairs(class_obj) do meta[k] = v end
|
||||
|
||||
meta.__index = parent or common_methods
|
||||
|
||||
local attrs = rawget(class, 'ATTRS') or {}
|
||||
setmetatable(attrs, attrs_meta)
|
||||
|
||||
rawset(class, 'super', parent)
|
||||
rawset(class, 'ATTRS', attrs)
|
||||
rawset(class, '__index', rawget(class, '__index') or class)
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
-- An instance uses the class as metatable
|
||||
function mkinstance(class,table)
|
||||
table = table or {}
|
||||
setmetatable(table, class)
|
||||
return table
|
||||
end
|
||||
|
||||
-- Patch the stubs in the global environment
|
||||
dfhack.BASE_G.defclass = _ENV.defclass
|
||||
dfhack.BASE_G.mkinstance = _ENV.mkinstance
|
||||
|
||||
-- Just verify the name, and then set.
|
||||
function class_obj:__newindex(name,val)
|
||||
if reserved_names[name] or common_methods[name] then
|
||||
error('Method name '..name..' is reserved.')
|
||||
end
|
||||
rawset(self, name, val)
|
||||
end
|
||||
|
||||
function attrs_meta:__call(attrs)
|
||||
for k,v in pairs(attrs) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_attrs(obj, attrs, init_table)
|
||||
for k,v in pairs(attrs) do
|
||||
local init_v = init_table[k]
|
||||
if init_v ~= nil then
|
||||
obj[k] = init_v
|
||||
elseif v == DEFAULT_NIL then
|
||||
obj[k] = nil
|
||||
else
|
||||
obj[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function invoke_before_rec(self, class, method, ...)
|
||||
local meta = getmetatable(class)
|
||||
if meta then
|
||||
local fun = rawget(class, method)
|
||||
if fun then
|
||||
fun(self, ...)
|
||||
end
|
||||
|
||||
invoke_before_rec(self, meta.__index, method, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local function invoke_after_rec(self, class, method, ...)
|
||||
local meta = getmetatable(class)
|
||||
if meta then
|
||||
invoke_after_rec(self, meta.__index, method, ...)
|
||||
|
||||
local fun = rawget(class, method)
|
||||
if fun then
|
||||
fun(self, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function init_attrs_rec(obj, class, init_table)
|
||||
local meta = getmetatable(class)
|
||||
if meta then
|
||||
init_attrs_rec(obj, meta.__index, init_table)
|
||||
apply_attrs(obj, rawget(class, 'ATTRS'), init_table)
|
||||
end
|
||||
end
|
||||
|
||||
-- Call metamethod constructs the object
|
||||
function class_obj:__call(init_table)
|
||||
-- The table is assumed to be a scratch temporary.
|
||||
-- If it is not, copy it yourself before calling.
|
||||
init_table = init_table or {}
|
||||
|
||||
local obj = mkinstance(self)
|
||||
|
||||
-- This initialization sequence is broadly based on how the
|
||||
-- Common Lisp initialize-instance generic function works.
|
||||
|
||||
-- preinit screens input arguments in subclass to superclass order
|
||||
invoke_before_rec(obj, self, 'preinit', init_table)
|
||||
-- initialize the instance table from init table
|
||||
init_attrs_rec(obj, self, init_table)
|
||||
-- init in superclass -> subclass
|
||||
invoke_after_rec(obj, self, 'init', init_table)
|
||||
-- postinit in superclass -> subclass
|
||||
invoke_after_rec(obj, self, 'postinit', init_table)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
-- Common methods for all instances:
|
||||
|
||||
function common_methods:callback(method, ...)
|
||||
return dfhack.curry(self[method], self, ...)
|
||||
end
|
||||
|
||||
function common_methods:cb_getfield(field)
|
||||
return function() return self[field] end
|
||||
end
|
||||
|
||||
function common_methods:cb_setfield(field)
|
||||
return function(val) self[field] = val end
|
||||
end
|
||||
|
||||
function common_methods:assign(data)
|
||||
for k,v in pairs(data) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function common_methods:invoke_before(method, ...)
|
||||
return invoke_before_rec(self, getmetatable(self), method, ...)
|
||||
end
|
||||
|
||||
function common_methods:invoke_after(method, ...)
|
||||
return invoke_after_rec(self, getmetatable(self), method, ...)
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,636 @@
|
||||
-- Viewscreen implementation utility collection.
|
||||
|
||||
local _ENV = mkmodule('gui')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
USE_GRAPHICS = dscreen.inGraphicsMode()
|
||||
|
||||
local to_pen = dfhack.pen.parse
|
||||
|
||||
CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
|
||||
|
||||
function simulateInput(screen,...)
|
||||
local keys = {}
|
||||
local function push_key(arg)
|
||||
local kv = arg
|
||||
if type(arg) == 'string' then
|
||||
kv = df.interface_key[arg]
|
||||
if kv == nil then
|
||||
error('Invalid keycode: '..arg)
|
||||
end
|
||||
end
|
||||
if type(kv) == 'number' then
|
||||
keys[#keys+1] = kv
|
||||
end
|
||||
end
|
||||
for i = 1,select('#',...) do
|
||||
local arg = select(i,...)
|
||||
if arg ~= nil then
|
||||
local t = type(arg)
|
||||
if type(arg) == 'table' then
|
||||
for k,v in pairs(arg) do
|
||||
if v == true then
|
||||
push_key(k)
|
||||
else
|
||||
push_key(v)
|
||||
end
|
||||
end
|
||||
else
|
||||
push_key(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
dscreen._doSimulateInput(screen, keys)
|
||||
end
|
||||
|
||||
function mkdims_xy(x1,y1,x2,y2)
|
||||
return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 }
|
||||
end
|
||||
function mkdims_wh(x1,y1,w,h)
|
||||
return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h }
|
||||
end
|
||||
function is_in_rect(rect,x,y)
|
||||
return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
|
||||
end
|
||||
|
||||
local function align_coord(gap,align,lv,rv)
|
||||
if gap <= 0 then
|
||||
return 0
|
||||
end
|
||||
if not align then
|
||||
if rv and not lv then
|
||||
align = 1.0
|
||||
elseif lv and not rv then
|
||||
align = 0.0
|
||||
else
|
||||
align = 0.5
|
||||
end
|
||||
end
|
||||
return math.floor(gap*align)
|
||||
end
|
||||
|
||||
function compute_frame_rect(wavail,havail,spec,xgap,ygap)
|
||||
if not spec then
|
||||
return mkdims_wh(0,0,wavail,havail)
|
||||
end
|
||||
|
||||
local sw = wavail - (spec.l or 0) - (spec.r or 0)
|
||||
local sh = havail - (spec.t or 0) - (spec.b or 0)
|
||||
local rqw = math.min(sw, (spec.w or sw)+xgap)
|
||||
local rqh = math.min(sh, (spec.h or sh)+ygap)
|
||||
local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r)
|
||||
local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b)
|
||||
|
||||
local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh)
|
||||
rect.wgap = sw - rqw
|
||||
rect.hgap = sh - rqh
|
||||
return rect
|
||||
end
|
||||
|
||||
local function parse_inset(inset)
|
||||
local l,r,t,b
|
||||
if type(inset) == 'table' then
|
||||
l,r = inset.l or inset.x, inset.r or inset.x
|
||||
t,b = inset.t or inset.y, inset.b or inset.y
|
||||
else
|
||||
l = inset or 0
|
||||
t,r,b = l,l,l
|
||||
end
|
||||
return l,r,t,b
|
||||
end
|
||||
|
||||
function inset_frame(rect, inset, gap)
|
||||
gap = gap or 0
|
||||
local l,t,r,b = parse_inset(inset)
|
||||
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
|
||||
end
|
||||
|
||||
function compute_frame_body(wavail, havail, spec, inset, gap)
|
||||
gap = gap or 0
|
||||
local l,t,r,b = parse_inset(inset)
|
||||
local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b)
|
||||
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
|
||||
return rect, body
|
||||
end
|
||||
|
||||
function blink_visible(delay)
|
||||
return math.floor(dfhack.getTickCount()/delay) % 2 == 0
|
||||
end
|
||||
|
||||
function getKeyDisplay(code)
|
||||
if type(code) == 'string' then
|
||||
code = df.interface_key[code]
|
||||
end
|
||||
return dscreen.getKeyDisplay(code)
|
||||
end
|
||||
|
||||
-----------------------------------
|
||||
-- Clipped view rectangle object --
|
||||
-----------------------------------
|
||||
|
||||
ViewRect = defclass(ViewRect, nil)
|
||||
|
||||
function ViewRect:init(args)
|
||||
if args.view_rect then
|
||||
self:assign(args.view_rect)
|
||||
else
|
||||
local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
|
||||
local crect = args.clip_rect or rect
|
||||
self:assign{
|
||||
x1 = rect.x1, clip_x1 = crect.x1,
|
||||
y1 = rect.y1, clip_y1 = crect.y1,
|
||||
x2 = rect.x2, clip_x2 = crect.x2,
|
||||
y2 = rect.y2, clip_y2 = crect.y2,
|
||||
width = rect.x2-rect.x1+1,
|
||||
height = rect.y2-rect.y1+1,
|
||||
}
|
||||
end
|
||||
if args.clip_view then
|
||||
local cr = args.clip_view
|
||||
self:assign{
|
||||
clip_x1 = math.max(self.clip_x1, cr.clip_x1),
|
||||
clip_y1 = math.max(self.clip_y1, cr.clip_y1),
|
||||
clip_x2 = math.min(self.clip_x2, cr.clip_x2),
|
||||
clip_y2 = math.min(self.clip_y2, cr.clip_y2),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function ViewRect:isDefunct()
|
||||
return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2)
|
||||
end
|
||||
|
||||
function ViewRect:inClipGlobalXY(x,y)
|
||||
return x >= self.clip_x1 and x <= self.clip_x2
|
||||
and y >= self.clip_y1 and y <= self.clip_y2
|
||||
end
|
||||
|
||||
function ViewRect:inClipLocalXY(x,y)
|
||||
return (x+self.x1) >= self.clip_x1 and (x+self.x1) <= self.clip_x2
|
||||
and (y+self.y1) >= self.clip_y1 and (y+self.y1) <= self.clip_y2
|
||||
end
|
||||
|
||||
function ViewRect:localXY(x,y)
|
||||
return x-self.x1, y-self.y1
|
||||
end
|
||||
|
||||
function ViewRect:globalXY(x,y)
|
||||
return x+self.x1, y+self.y1
|
||||
end
|
||||
|
||||
function ViewRect:viewport(x,y,w,h)
|
||||
if type(x) == 'table' then
|
||||
x,y,w,h = x.x1, x.y1, x.width, x.height
|
||||
end
|
||||
local x1,y1 = self.x1+x, self.y1+y
|
||||
local x2,y2 = x1+w-1, y1+h-1
|
||||
local vp = {
|
||||
-- Logical viewport
|
||||
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
|
||||
width = w, height = h,
|
||||
-- Actual clipping rect
|
||||
clip_x1 = math.max(self.clip_x1, x1),
|
||||
clip_y1 = math.max(self.clip_y1, y1),
|
||||
clip_x2 = math.min(self.clip_x2, x2),
|
||||
clip_y2 = math.min(self.clip_y2, y2),
|
||||
}
|
||||
return mkinstance(ViewRect, vp)
|
||||
end
|
||||
|
||||
----------------------------
|
||||
-- Clipped painter object --
|
||||
----------------------------
|
||||
|
||||
Painter = defclass(Painter, ViewRect)
|
||||
|
||||
function Painter:init(args)
|
||||
self.x = self.x1
|
||||
self.y = self.y1
|
||||
self.cur_pen = to_pen(args.pen or COLOR_GREY)
|
||||
self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN)
|
||||
end
|
||||
|
||||
function Painter.new(rect, pen)
|
||||
return Painter{ rect = rect, pen = pen }
|
||||
end
|
||||
|
||||
function Painter.new_view(view_rect, pen)
|
||||
return Painter{ view_rect = view_rect, pen = pen }
|
||||
end
|
||||
|
||||
function Painter.new_xy(x1,y1,x2,y2,pen)
|
||||
return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen }
|
||||
end
|
||||
|
||||
function Painter.new_wh(x,y,w,h,pen)
|
||||
return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen }
|
||||
end
|
||||
|
||||
function Painter:isValidPos()
|
||||
return self:inClipGlobalXY(self.x, self.y)
|
||||
end
|
||||
|
||||
function Painter:viewport(x,y,w,h)
|
||||
local vp = ViewRect.viewport(x,y,w,h)
|
||||
vp.cur_pen = self.cur_pen
|
||||
vp.cur_key_pen = self.cur_key_pen
|
||||
return mkinstance(Painter, vp):seek(0,0)
|
||||
end
|
||||
|
||||
function Painter:cursor()
|
||||
return self.x - self.x1, self.y - self.y1
|
||||
end
|
||||
|
||||
function Painter:cursorX()
|
||||
return self.x - self.x1
|
||||
end
|
||||
|
||||
function Painter:cursorY()
|
||||
return self.y - self.y1
|
||||
end
|
||||
|
||||
function Painter:seek(x,y)
|
||||
if x then self.x = self.x1 + x end
|
||||
if y then self.y = self.y1 + y end
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:advance(dx,dy)
|
||||
if dx then self.x = self.x + dx end
|
||||
if dy then self.y = self.y + dy end
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:newline(dx)
|
||||
self.y = self.y + 1
|
||||
self.x = self.x1 + (dx or 0)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:pen(pen,...)
|
||||
self.cur_pen = to_pen(self.cur_pen, pen, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:color(fg,bold,bg)
|
||||
self.cur_pen = to_pen(self.cur_pen, fg, bg, bold)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:key_pen(pen,...)
|
||||
self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:clear()
|
||||
dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:fill(x1,y1,x2,y2,pen,bg,bold)
|
||||
if type(x1) == 'table' then
|
||||
x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2
|
||||
end
|
||||
x1 = math.max(x1+self.x1,self.clip_x1)
|
||||
y1 = math.max(y1+self.y1,self.clip_y1)
|
||||
x2 = math.min(x2+self.x1,self.clip_x2)
|
||||
y2 = math.min(y2+self.y1,self.clip_y2)
|
||||
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:char(char,pen,...)
|
||||
if self:isValidPos() then
|
||||
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char)
|
||||
end
|
||||
return self:advance(1, nil)
|
||||
end
|
||||
|
||||
function Painter:tile(char,tile,pen,...)
|
||||
if self:isValidPos() then
|
||||
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile)
|
||||
end
|
||||
return self:advance(1, nil)
|
||||
end
|
||||
|
||||
function Painter:string(text,pen,...)
|
||||
if self.y >= self.clip_y1 and self.y <= self.clip_y2 then
|
||||
local dx = 0
|
||||
if self.x < self.clip_x1 then
|
||||
dx = self.clip_x1 - self.x
|
||||
end
|
||||
local len = #text
|
||||
if self.x + len - 1 > self.clip_x2 then
|
||||
len = self.clip_x2 - self.x + 1
|
||||
end
|
||||
if len > dx then
|
||||
dscreen.paintString(
|
||||
to_pen(self.cur_pen, pen, ...),
|
||||
self.x+dx, self.y,
|
||||
string.sub(text,dx+1,len)
|
||||
)
|
||||
end
|
||||
end
|
||||
return self:advance(#text, nil)
|
||||
end
|
||||
|
||||
function Painter:key(code,pen,...)
|
||||
return self:string(
|
||||
getKeyDisplay(code),
|
||||
to_pen(self.cur_key_pen, pen, ...)
|
||||
)
|
||||
end
|
||||
|
||||
--------------------------
|
||||
-- Abstract view object --
|
||||
--------------------------
|
||||
|
||||
View = defclass(View)
|
||||
|
||||
View.ATTRS {
|
||||
active = true,
|
||||
visible = true,
|
||||
view_id = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function View:init(args)
|
||||
self.subviews = {}
|
||||
end
|
||||
|
||||
function View:addviews(list)
|
||||
if not list then return end
|
||||
|
||||
local sv = self.subviews
|
||||
|
||||
for _,obj in ipairs(list) do
|
||||
table.insert(sv, obj)
|
||||
|
||||
local id = obj.view_id
|
||||
if id and type(id) ~= 'number' and sv[id] == nil then
|
||||
sv[id] = obj
|
||||
end
|
||||
end
|
||||
|
||||
for _,dir in ipairs(list) do
|
||||
for id,obj in pairs(dir.subviews) do
|
||||
if id and type(id) ~= 'number' and sv[id] == nil then
|
||||
sv[id] = obj
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View:getWindowSize()
|
||||
local rect = self.frame_body
|
||||
return rect.width, rect.height
|
||||
end
|
||||
|
||||
function View:getMousePos()
|
||||
local rect = self.frame_body
|
||||
local x,y = dscreen.getMousePos()
|
||||
if rect and rect:inClipGlobalXY(x,y) then
|
||||
return rect:localXY(x,y)
|
||||
end
|
||||
end
|
||||
|
||||
function View:computeFrame(parent_rect)
|
||||
return mkdims_wh(0,0,parent_rect.width,parent_rect.height)
|
||||
end
|
||||
|
||||
function View:updateSubviewLayout(frame_body)
|
||||
for _,child in ipairs(self.subviews) do
|
||||
child:updateLayout(frame_body)
|
||||
end
|
||||
end
|
||||
|
||||
function View:updateLayout(parent_rect)
|
||||
if not parent_rect then
|
||||
parent_rect = self.frame_parent_rect
|
||||
else
|
||||
self.frame_parent_rect = parent_rect
|
||||
end
|
||||
|
||||
self:invoke_before('preUpdateLayout', parent_rect)
|
||||
|
||||
local frame_rect,body_rect = self:computeFrame(parent_rect)
|
||||
|
||||
self.frame_rect = frame_rect
|
||||
self.frame_body = parent_rect:viewport(body_rect or frame_rect)
|
||||
|
||||
self:invoke_after('postComputeFrame', self.frame_body)
|
||||
|
||||
self:updateSubviewLayout(self.frame_body)
|
||||
|
||||
self:invoke_after('postUpdateLayout', self.frame_body)
|
||||
end
|
||||
|
||||
function View:renderSubviews(dc)
|
||||
for _,child in ipairs(self.subviews) do
|
||||
if child.visible then
|
||||
child:render(dc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function View:render(dc)
|
||||
self:onRenderFrame(dc, self.frame_rect)
|
||||
|
||||
local sub_dc = Painter{
|
||||
view_rect = self.frame_body,
|
||||
clip_view = dc
|
||||
}
|
||||
|
||||
self:onRenderBody(sub_dc)
|
||||
|
||||
self:renderSubviews(sub_dc)
|
||||
end
|
||||
|
||||
function View:onRenderFrame(dc,rect)
|
||||
end
|
||||
|
||||
function View:onRenderBody(dc)
|
||||
end
|
||||
|
||||
function View:inputToSubviews(keys)
|
||||
local children = self.subviews
|
||||
|
||||
for i=#children,1,-1 do
|
||||
local child = children[i]
|
||||
if child.visible and child.active and child:onInput(keys) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function View:onInput(keys)
|
||||
return self:inputToSubviews(keys)
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Base screen object --
|
||||
------------------------
|
||||
|
||||
Screen = defclass(Screen, View)
|
||||
|
||||
Screen.text_input_mode = false
|
||||
|
||||
function Screen:postinit()
|
||||
self:onResize(dscreen.getWindowSize())
|
||||
end
|
||||
|
||||
Screen.isDismissed = dscreen.isDismissed
|
||||
|
||||
function Screen:isShown()
|
||||
return self._native ~= nil
|
||||
end
|
||||
|
||||
function Screen:isActive()
|
||||
return self:isShown() and not self:isDismissed()
|
||||
end
|
||||
|
||||
function Screen:invalidate()
|
||||
dscreen.invalidate()
|
||||
end
|
||||
|
||||
function Screen:renderParent()
|
||||
if self._native and self._native.parent then
|
||||
self._native.parent:render()
|
||||
else
|
||||
dscreen.clear()
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:sendInputToParent(...)
|
||||
if self._native and self._native.parent then
|
||||
simulateInput(self._native.parent, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:show(parent)
|
||||
if self._native then
|
||||
error("This screen is already on display")
|
||||
end
|
||||
parent = parent or dfhack.gui.getCurViewscreen(true)
|
||||
self:onAboutToShow(parent)
|
||||
if not dscreen.show(self, parent.child) then
|
||||
error('Could not show screen')
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:onAboutToShow(parent)
|
||||
end
|
||||
|
||||
function Screen:onShow()
|
||||
self:onResize(dscreen.getWindowSize())
|
||||
end
|
||||
|
||||
function Screen:dismiss()
|
||||
if self._native then
|
||||
dscreen.dismiss(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:onDismiss()
|
||||
end
|
||||
|
||||
function Screen:onDestroy()
|
||||
end
|
||||
|
||||
function Screen:onResize(w,h)
|
||||
self:updateLayout(ViewRect{ rect = mkdims_wh(0,0,w,h) })
|
||||
end
|
||||
|
||||
function Screen:onRender()
|
||||
self:render(Painter.new())
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Framed screen object --
|
||||
------------------------
|
||||
|
||||
-- Plain grey-colored frame.
|
||||
GREY_FRAME = {
|
||||
frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE },
|
||||
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
}
|
||||
|
||||
-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets.
|
||||
BOUNDARY_FRAME = {
|
||||
frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
||||
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY },
|
||||
}
|
||||
|
||||
GREY_LINE_FRAME = {
|
||||
frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
||||
}
|
||||
|
||||
function paint_frame(x1,y1,x2,y2,style,title)
|
||||
local pen = style.frame_pen
|
||||
dscreen.paintTile(style.lt_frame_pen or pen, x1, y1)
|
||||
dscreen.paintTile(style.rt_frame_pen or pen, x2, y1)
|
||||
dscreen.paintTile(style.lb_frame_pen or pen, x1, y2)
|
||||
dscreen.paintTile(style.rb_frame_pen or pen, x2, y2)
|
||||
dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1)
|
||||
dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2)
|
||||
dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1)
|
||||
dscreen.fillRect(style.r_frame_pen or style.v_frame_pen or pen,x2,y1+1,x2,y2-1)
|
||||
dscreen.paintString(style.signature_pen or style.title_pen or pen,x2-7,y2,"DFHack")
|
||||
|
||||
if title then
|
||||
local x = math.max(0,math.floor((x2-x1-3-#title)/2)) + x1
|
||||
local tstr = ' '..title..' '
|
||||
if #tstr > x2-x1-1 then
|
||||
tstr = string.sub(tstr,1,x2-x1-1)
|
||||
end
|
||||
dscreen.paintString(style.title_pen or pen, x, y1, tstr)
|
||||
end
|
||||
end
|
||||
|
||||
FramedScreen = defclass(FramedScreen, Screen)
|
||||
|
||||
FramedScreen.ATTRS{
|
||||
frame_style = BOUNDARY_FRAME,
|
||||
frame_title = DEFAULT_NIL,
|
||||
frame_width = DEFAULT_NIL,
|
||||
frame_height = DEFAULT_NIL,
|
||||
frame_inset = 0,
|
||||
frame_background = CLEAR_PEN,
|
||||
}
|
||||
|
||||
function FramedScreen:getWantedFrameSize()
|
||||
return self.frame_width, self.frame_height
|
||||
end
|
||||
|
||||
function FramedScreen:computeFrame(parent_rect)
|
||||
local sw, sh = parent_rect.width, parent_rect.height
|
||||
local fw, fh = self:getWantedFrameSize(parent_rect)
|
||||
return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1)
|
||||
end
|
||||
|
||||
function FramedScreen:onRenderFrame(dc, rect)
|
||||
local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2
|
||||
|
||||
if rect.wgap <= 0 and rect.hgap <= 0 then
|
||||
dc:clear()
|
||||
else
|
||||
self:renderParent()
|
||||
dc:fill(rect, self.frame_background)
|
||||
end
|
||||
|
||||
paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title)
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,221 @@
|
||||
-- Some simple dialog screens
|
||||
|
||||
local _ENV = mkmodule('gui.dialogs')
|
||||
|
||||
local gui = require('gui')
|
||||
local widgets = require('gui.widgets')
|
||||
local utils = require('utils')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
MessageBox = defclass(MessageBox, gui.FramedScreen)
|
||||
|
||||
MessageBox.focus_path = 'MessageBox'
|
||||
|
||||
MessageBox.ATTRS{
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 1,
|
||||
-- new attrs
|
||||
on_accept = DEFAULT_NIL,
|
||||
on_cancel = DEFAULT_NIL,
|
||||
on_close = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function MessageBox:init(info)
|
||||
self:addviews{
|
||||
widgets.Label{
|
||||
view_id = 'label',
|
||||
text = info.text,
|
||||
text_pen = info.text_pen,
|
||||
frame = { l = 0, t = 0 },
|
||||
auto_height = true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function MessageBox:getWantedFrameSize()
|
||||
local label = self.subviews.label
|
||||
local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4)
|
||||
return math.max(width, label:getTextWidth()), label:getTextHeight()
|
||||
end
|
||||
|
||||
function MessageBox:onRenderFrame(dc,rect)
|
||||
MessageBox.super.onRenderFrame(self,dc,rect)
|
||||
if self.on_accept then
|
||||
dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM')
|
||||
end
|
||||
end
|
||||
|
||||
function MessageBox:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function MessageBox:onInput(keys)
|
||||
if keys.MENU_CONFIRM then
|
||||
self:dismiss()
|
||||
if self.on_accept then
|
||||
self.on_accept()
|
||||
end
|
||||
elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showMessage(title, text, tcolor, on_close)
|
||||
MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_close = on_close
|
||||
}:show()
|
||||
end
|
||||
|
||||
function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
|
||||
MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_accept = on_accept,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
InputBox = defclass(InputBox, MessageBox)
|
||||
|
||||
InputBox.focus_path = 'InputBox'
|
||||
|
||||
InputBox.ATTRS{
|
||||
on_input = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function InputBox:preinit(info)
|
||||
info.on_accept = nil
|
||||
end
|
||||
|
||||
function InputBox:init(info)
|
||||
self:addviews{
|
||||
widgets.EditField{
|
||||
view_id = 'edit',
|
||||
text = info.input,
|
||||
text_pen = info.input_pen,
|
||||
frame = { l = 0, r = 0, h = 1 },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function InputBox:getWantedFrameSize()
|
||||
local mw, mh = InputBox.super.getWantedFrameSize(self)
|
||||
self.subviews.edit.frame.t = mh+1
|
||||
return mw, mh+2
|
||||
end
|
||||
|
||||
function InputBox:onInput(keys)
|
||||
if keys.SELECT then
|
||||
self:dismiss()
|
||||
if self.on_input then
|
||||
self.on_input(self.subviews.edit.text)
|
||||
end
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
|
||||
InputBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
input = input,
|
||||
on_input = on_input,
|
||||
on_cancel = on_cancel,
|
||||
frame_width = min_width,
|
||||
}:show()
|
||||
end
|
||||
|
||||
ListBox = defclass(ListBox, MessageBox)
|
||||
|
||||
ListBox.focus_path = 'ListBox'
|
||||
|
||||
ListBox.ATTRS{
|
||||
with_filter = false,
|
||||
cursor_pen = DEFAULT_NIL,
|
||||
select_pen = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL
|
||||
}
|
||||
|
||||
function ListBox:preinit(info)
|
||||
info.on_accept = nil
|
||||
end
|
||||
|
||||
function ListBox:init(info)
|
||||
local spen = dfhack.pen.parse(COLOR_CYAN, self.select_pen, nil, false)
|
||||
local cpen = dfhack.pen.parse(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true)
|
||||
|
||||
local list_widget = widgets.List
|
||||
if self.with_filter then
|
||||
list_widget = widgets.FilteredList
|
||||
end
|
||||
|
||||
self:addviews{
|
||||
list_widget{
|
||||
view_id = 'list',
|
||||
selected = info.selected,
|
||||
choices = info.choices,
|
||||
icon_width = info.icon_width,
|
||||
text_pen = spen,
|
||||
cursor_pen = cpen,
|
||||
on_submit = function(sel,obj)
|
||||
self:dismiss()
|
||||
if self.on_select then self.on_select(sel, obj) end
|
||||
local cb = obj.on_select or obj[2]
|
||||
if cb then cb(obj, sel) end
|
||||
end,
|
||||
frame = { l = 0, r = 0 },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function ListBox:getWantedFrameSize()
|
||||
local mw, mh = InputBox.super.getWantedFrameSize(self)
|
||||
local list = self.subviews.list
|
||||
list.frame.t = mh+1
|
||||
return math.max(mw, list:getContentWidth()), mh+1+math.min(20,list:getContentHeight())
|
||||
end
|
||||
|
||||
function ListBox:onInput(keys)
|
||||
if keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter)
|
||||
ListBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
choices = choices,
|
||||
on_select = on_select,
|
||||
on_cancel = on_cancel,
|
||||
frame_width = min_width,
|
||||
with_filter = filter,
|
||||
}:show()
|
||||
end
|
||||
|
||||
return _ENV
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue