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 | #!/bin/bash | ||||||
| rst2html  README.rst > Readme.html | # regenerate documentation after editing the .rst files. Requires python and docutils. | ||||||
| rst2html  COMPILE.rst > Compile.html | 
 | ||||||
| rst2html  DEVEL.rst > Devel.html | cd `dirname $0` | ||||||
| rst2html  LUA_API.rst > Lua\ API.html | 
 | ||||||
|  | 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