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