Merge branch 'develop' into lua-ref-target
commit
a44a2e2298
@ -0,0 +1,6 @@
|
|||||||
|
Thank you for your interest in contributing to DFHack! If you're reading this
|
||||||
|
document, you're probably viewing it on GitHub. The DFHack docs are hosted
|
||||||
|
on [ReadTheDocs](https://dfhack.readthedocs.io/) - in particular, contributing
|
||||||
|
guidelines are [here](https://docs.dfhack.org/en/latest/docs/Contributing.html).
|
||||||
|
Double-checking the style guidelines before submitting a pull request is
|
||||||
|
always appreciated.
|
@ -1,229 +0,0 @@
|
|||||||
###########################
|
|
||||||
How to contribute to DFHack
|
|
||||||
###########################
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
|
|
||||||
.. _contributing-code:
|
|
||||||
|
|
||||||
Contributing Code
|
|
||||||
=================
|
|
||||||
Several things should be kept in mind when contributing code to DFHack.
|
|
||||||
|
|
||||||
Code Format
|
|
||||||
-----------
|
|
||||||
* Four space indents for C++. Never use tabs for indentation in any language.
|
|
||||||
* LF (Unix style) line terminators
|
|
||||||
* Avoid trailing whitespace
|
|
||||||
* UTF-8 encoding
|
|
||||||
* For C++:
|
|
||||||
|
|
||||||
* Opening and closing braces on their own lines or opening brace at the end of the previous line
|
|
||||||
* Braces placed at original indent level if on their own lines
|
|
||||||
* #includes should be sorted. C++ libraries first, then dfhack modules, then df structures,
|
|
||||||
then local includes. Within each category they should be sorted alphabetically.
|
|
||||||
|
|
||||||
How to get new code into DFHack
|
|
||||||
-------------------------------
|
|
||||||
* Submit pull requests to the ``develop`` branch, not the ``master`` branch.
|
|
||||||
(The ``master`` branch always points at the most recent release)
|
|
||||||
* Use a new branch for each feature or bugfix so that your changes can be merged independently
|
|
||||||
(i.e. not the master or develop branch of your fork).
|
|
||||||
* If possible, compile on multiple platforms when changing anything that compiles
|
|
||||||
* It must pass CI - run ``python travis/all.py`` to check this.
|
|
||||||
* Update ``changelog.txt`` and ``docs/Authors.rst`` when applicable. See
|
|
||||||
`build-changelog` for more information on the changelog format.
|
|
||||||
* Create a GitHub pull request once finished
|
|
||||||
* Submit ideas and bug reports as :issue:`issues on GitHub <>`.
|
|
||||||
Posts in the forum thread can easily get missed or forgotten.
|
|
||||||
* Work on :issue:`reported problems <?q=is:open+-label:idea>`
|
|
||||||
will take priority over ideas or suggestions.
|
|
||||||
|
|
||||||
.. _contributing-memory-research:
|
|
||||||
|
|
||||||
Memory research
|
|
||||||
---------------
|
|
||||||
If you want to do memory research, you'll need some tools and some knowledge.
|
|
||||||
In general, you'll need a good memory viewer and optionally something
|
|
||||||
to look at machine code without getting crazy :)
|
|
||||||
Using publicly known information and analyzing the game's data is preferred.
|
|
||||||
|
|
||||||
Good Windows tools include:
|
|
||||||
|
|
||||||
* IDA Freeware 7.0 (for non-commercial use, supports 32-bit and 64-bit)
|
|
||||||
* Cheat Engine
|
|
||||||
|
|
||||||
Good Linux tools:
|
|
||||||
|
|
||||||
* angavrilov's df-structures gui (32-bit only, visit us on IRC for details)
|
|
||||||
* IDA Freeware 7.0 (see above)
|
|
||||||
* edb (Evan's Debugger)
|
|
||||||
* Some of the tools residing in the ``legacy`` dfhack branch.
|
|
||||||
|
|
||||||
Using the library as a developer
|
|
||||||
================================
|
|
||||||
Currently, the most direct way to use the library is to write a script or plugin that can be loaded by it.
|
|
||||||
All the plugins can be found in the 'plugins' folder. There's no in-depth documentation
|
|
||||||
on how to write one yet, but it should be easy enough to copy one and just follow the pattern.
|
|
||||||
``plugins/skeleton/skeleton.cpp`` is provided for this purpose.
|
|
||||||
|
|
||||||
Other than through plugins, it is possible to use DFHack via remote access interface,
|
|
||||||
or by writing scripts in Lua or Ruby. There are plenty of examples in the scripts folder.
|
|
||||||
The `lua-api` is quite well documented.
|
|
||||||
|
|
||||||
The most important parts of DFHack are the Core, Console, Modules and Plugins.
|
|
||||||
|
|
||||||
* Core acts as the centerpiece of DFHack - it acts as a filter between DF and
|
|
||||||
SDL and synchronizes the various plugins with DF.
|
|
||||||
* Console is a thread-safe console that can be used to invoke commands exported by Plugins.
|
|
||||||
* Modules actually describe the way to access information in DF's memory. You
|
|
||||||
can get them from the Core. Most modules are split into two parts: high-level
|
|
||||||
and low-level. High-level is mostly method calls, low-level publicly visible
|
|
||||||
pointers to DF's data structures.
|
|
||||||
* Plugins are the tools that use all the other stuff to make things happen.
|
|
||||||
A plugin can have a list of commands that it exports and an onupdate function
|
|
||||||
that will be called each DF game tick.
|
|
||||||
|
|
||||||
Rudimentary API documentation can be built using doxygen (see build options
|
|
||||||
in ``CMakeCache.txt`` or with ``ccmake`` or ``cmake-gui``). The full DFHack
|
|
||||||
documentation is built with Sphinx_, which runs automatically at compile time.
|
|
||||||
|
|
||||||
.. _Sphinx: http://www.sphinx-doc.org
|
|
||||||
|
|
||||||
DFHack consists of variously licensed code, but invariably weak copyleft.
|
|
||||||
The main license is zlib/libpng, some bits are MIT licensed, and some are
|
|
||||||
BSD licensed. See the `license` for more information.
|
|
||||||
|
|
||||||
Feel free to add your own extensions and plugins. Contributing back to
|
|
||||||
the DFHack repository is welcome and the right thing to do :)
|
|
||||||
|
|
||||||
DF data structure definitions
|
|
||||||
-----------------------------
|
|
||||||
DFHack uses information about the game data structures, represented via xml files
|
|
||||||
in the ``library/xml/`` submodule.
|
|
||||||
|
|
||||||
See https://github.com/DFHack/df-structures, and the documentation linked in the index.
|
|
||||||
|
|
||||||
Data structure layouts are described in files following the ``df.*.xml`` name pattern.
|
|
||||||
This information is transformed by a perl script into C++ headers describing the
|
|
||||||
structures, and associated metadata for the Lua wrapper. These headers and data
|
|
||||||
are then compiled into the DFHack libraries, thus necessitating a compatibility
|
|
||||||
break every time layouts change; in return it significantly boosts the efficiency
|
|
||||||
and capabilities of DFHack code.
|
|
||||||
|
|
||||||
Global object addresses are stored in :file:`symbols.xml`, which is copied to the dfhack
|
|
||||||
release package and loaded as data at runtime.
|
|
||||||
|
|
||||||
Remote access interface
|
|
||||||
-----------------------
|
|
||||||
DFHack supports remote access by exchanging Google protobuf messages via a TCP
|
|
||||||
socket. Both the core and plugins can define remotely accessible methods. The
|
|
||||||
``dfhack-run`` command uses this interface to invoke ordinary console commands.
|
|
||||||
|
|
||||||
Currently the supported set of requests is limited, because the developers don't
|
|
||||||
know what exactly is most useful. `remotefortressreader` provides a fairly
|
|
||||||
comprehensive interface for visualisers such as :forums:`Armok Vision <146473>`.
|
|
||||||
|
|
||||||
|
|
||||||
Documentation Standards
|
|
||||||
=======================
|
|
||||||
DFHack documentation is built with Sphinx_, and configured automatically
|
|
||||||
through CMake. If you want to build the docs *only*, use this command::
|
|
||||||
|
|
||||||
sphinx-build . docs/html
|
|
||||||
|
|
||||||
Whether you're adding new code or just fixing old documentation (and there's plenty),
|
|
||||||
there are a few important standards for completeness and consistent style. Treat
|
|
||||||
this section as a guide rather than iron law, match the surrounding text, and you'll
|
|
||||||
be fine.
|
|
||||||
|
|
||||||
Each command should have a short (~54 character) help string, which is shown
|
|
||||||
by the `ls` command. For scripts, this is a comment on the first line
|
|
||||||
(the comment marker and whitespace is stripped). For plugins it's the second
|
|
||||||
argument to ``PluginCommand``. Please make this brief but descriptive!
|
|
||||||
|
|
||||||
Everything should be documented! If it's not clear *where* a particular
|
|
||||||
thing should be documented, ask on IRC or in the DFHack thread on Bay12 -
|
|
||||||
as well as getting help, you'll be providing valuable feedback that
|
|
||||||
makes it easier for future readers!
|
|
||||||
|
|
||||||
Scripts can use a custom autodoc function, based on the Sphinx ``include``
|
|
||||||
directive - anything between the tokens is copied into the appropriate scripts
|
|
||||||
documentation page. For Ruby, we follow the built-in docstring convention
|
|
||||||
(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]``
|
|
||||||
- ordinary multi-line strings. It is highly encouraged to reuse this string
|
|
||||||
as the in-console documentation by (eg.) printing it when a ``-help`` argument
|
|
||||||
is given.
|
|
||||||
|
|
||||||
The docs **must** have a heading which exactly matches the command, underlined
|
|
||||||
with ``=====`` to the same length. For example, a lua file would have::
|
|
||||||
|
|
||||||
local helpstr = [====[
|
|
||||||
|
|
||||||
add-thought
|
|
||||||
===========
|
|
||||||
Adds a thought or emotion to the selected unit. Can be used by other scripts,
|
|
||||||
or the gui invoked by running ``add-thought gui`` with a unit selected.
|
|
||||||
|
|
||||||
]====]
|
|
||||||
|
|
||||||
|
|
||||||
Where the heading for a section is also the name of a command, the spelling
|
|
||||||
and case should exactly match the command to enter in the DFHack command line.
|
|
||||||
|
|
||||||
Try to keep lines within 80-100 characters, so it's readable in plain text
|
|
||||||
in the terminal - Sphinx (our documentation system) will make sure
|
|
||||||
paragraphs flow.
|
|
||||||
|
|
||||||
If there aren't many options or examples to show, they can go in a paragraph of
|
|
||||||
text. Use double-backticks to put commands in monospaced font, like this::
|
|
||||||
|
|
||||||
You can use ``cleanowned scattered x`` to dump tattered or abandoned items.
|
|
||||||
|
|
||||||
If the command takes more than three arguments, format the list as a table
|
|
||||||
called Usage. The table *only* lists arguments, not full commands.
|
|
||||||
Input values are specified in angle brackets. Example::
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
:arg1: A simple argument.
|
|
||||||
:arg2 <input>: Does something based on the input value.
|
|
||||||
:Very long argument:
|
|
||||||
Is very specific.
|
|
||||||
|
|
||||||
To demonstrate usage - useful mainly when the syntax is complicated, list the
|
|
||||||
full command with arguments in monospaced font, then indent the next line and
|
|
||||||
describe the effect::
|
|
||||||
|
|
||||||
``resume all``
|
|
||||||
Resumes all suspended constructions.
|
|
||||||
|
|
||||||
If it would be helpful to mention another DFHack command, don't just type the
|
|
||||||
name - add a hyperlink! Specify the link target in backticks, and it will be
|
|
||||||
replaced with the corresponding title and linked: eg ```autolabor```
|
|
||||||
=> `autolabor`. Link targets should be equivalent to the command
|
|
||||||
described (without file extension), and placed above the heading of that
|
|
||||||
section like this::
|
|
||||||
|
|
||||||
.. _autolabor:
|
|
||||||
|
|
||||||
autolabor
|
|
||||||
=========
|
|
||||||
|
|
||||||
Add link targets if you need them, but otherwise plain headings are preferred.
|
|
||||||
Scripts have link targets created automatically.
|
|
||||||
|
|
||||||
Other ways to help
|
|
||||||
==================
|
|
||||||
DFHack is a software project, but there's a lot more to it than programming.
|
|
||||||
If you're not comfortable programming, you can help by:
|
|
||||||
|
|
||||||
* reporting bugs and incomplete documentation
|
|
||||||
* improving the documentation
|
|
||||||
* finding third-party scripts to add
|
|
||||||
* writing tutorials for newbies
|
|
||||||
|
|
||||||
All those things are crucial, and often under-represented. So if that's
|
|
||||||
your thing, go get started!
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
|||||||
/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
|
|
||||||
Copyright (c) 2010-2012 Marcus Geelnard
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _FAST_MUTEX_H_
|
|
||||||
#define _FAST_MUTEX_H_
|
|
||||||
|
|
||||||
/// @file
|
|
||||||
|
|
||||||
// Which platform are we on?
|
|
||||||
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
|
|
||||||
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
|
|
||||||
#define _TTHREAD_WIN32_
|
|
||||||
#else
|
|
||||||
#define _TTHREAD_POSIX_
|
|
||||||
#endif
|
|
||||||
#define _TTHREAD_PLATFORM_DEFINED_
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check if we can support the assembly language level implementation (otherwise
|
|
||||||
// revert to the system API)
|
|
||||||
#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \
|
|
||||||
(defined(_MSC_VER) && (defined(_M_IX86) /*|| defined(_M_X64)*/)) || \
|
|
||||||
(defined(__GNUC__) && (defined(__ppc__)))
|
|
||||||
#define _FAST_MUTEX_ASM_
|
|
||||||
#else
|
|
||||||
#define _FAST_MUTEX_SYS_
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
#define NOMINMAX
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#define __UNDEF_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#ifdef __UNDEF_LEAN_AND_MEAN
|
|
||||||
#undef WIN32_LEAN_AND_MEAN
|
|
||||||
#undef __UNDEF_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#ifdef _FAST_MUTEX_ASM_
|
|
||||||
#include <sched.h>
|
|
||||||
#else
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace tthread {
|
|
||||||
|
|
||||||
/// Fast mutex class.
|
|
||||||
/// This is a mutual exclusion object for synchronizing access to shared
|
|
||||||
/// memory areas for several threads. It is similar to the tthread::mutex class,
|
|
||||||
/// but instead of using system level functions, it is implemented as an atomic
|
|
||||||
/// spin lock with very low CPU overhead.
|
|
||||||
///
|
|
||||||
/// The \c fast_mutex class is NOT compatible with the \c condition_variable
|
|
||||||
/// class (however, it IS compatible with the \c lock_guard class). It should
|
|
||||||
/// also be noted that the \c fast_mutex class typically does not provide
|
|
||||||
/// as accurate thread scheduling as a the standard \c mutex class does.
|
|
||||||
///
|
|
||||||
/// Because of the limitations of the class, it should only be used in
|
|
||||||
/// situations where the mutex needs to be locked/unlocked very frequently.
|
|
||||||
///
|
|
||||||
/// @note The "fast" version of this class relies on inline assembler language,
|
|
||||||
/// which is currently only supported for 32/64-bit Intel x86/AMD64 and
|
|
||||||
/// PowerPC architectures on a limited number of compilers (GNU g++ and MS
|
|
||||||
/// Visual C++).
|
|
||||||
/// For other architectures/compilers, system functions are used instead.
|
|
||||||
class fast_mutex {
|
|
||||||
public:
|
|
||||||
/// Constructor.
|
|
||||||
#if defined(_FAST_MUTEX_ASM_)
|
|
||||||
fast_mutex() : mLock(0) {}
|
|
||||||
#else
|
|
||||||
fast_mutex()
|
|
||||||
{
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
InitializeCriticalSection(&mHandle);
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
pthread_mutex_init(&mHandle, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(_FAST_MUTEX_ASM_)
|
|
||||||
/// Destructor.
|
|
||||||
~fast_mutex()
|
|
||||||
{
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
DeleteCriticalSection(&mHandle);
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
pthread_mutex_destroy(&mHandle);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Lock the mutex.
|
|
||||||
/// The method will block the calling thread until a lock on the mutex can
|
|
||||||
/// be obtained. The mutex remains locked until \c unlock() is called.
|
|
||||||
/// @see lock_guard
|
|
||||||
inline void lock()
|
|
||||||
{
|
|
||||||
#if defined(_FAST_MUTEX_ASM_)
|
|
||||||
bool gotLock;
|
|
||||||
do {
|
|
||||||
gotLock = try_lock();
|
|
||||||
if(!gotLock)
|
|
||||||
{
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
Sleep(0);
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
sched_yield();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} while(!gotLock);
|
|
||||||
#else
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
EnterCriticalSection(&mHandle);
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
pthread_mutex_lock(&mHandle);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to lock the mutex.
|
|
||||||
/// The method will try to lock the mutex. If it fails, the function will
|
|
||||||
/// return immediately (non-blocking).
|
|
||||||
/// @return \c true if the lock was acquired, or \c false if the lock could
|
|
||||||
/// not be acquired.
|
|
||||||
inline bool try_lock()
|
|
||||||
{
|
|
||||||
#if defined(_FAST_MUTEX_ASM_)
|
|
||||||
int oldLock;
|
|
||||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
|
||||||
asm volatile (
|
|
||||||
"movl $1,%%eax\n\t"
|
|
||||||
"xchg %%eax,%0\n\t"
|
|
||||||
"movl %%eax,%1\n\t"
|
|
||||||
: "=m" (mLock), "=m" (oldLock)
|
|
||||||
:
|
|
||||||
: "%eax", "memory"
|
|
||||||
);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
|
||||||
int *ptrLock = &mLock;
|
|
||||||
__asm {
|
|
||||||
mov eax,1
|
|
||||||
mov ecx,ptrLock
|
|
||||||
xchg eax,[ecx]
|
|
||||||
mov oldLock,eax
|
|
||||||
}
|
|
||||||
#elif defined(__GNUC__) && (defined(__ppc__))
|
|
||||||
int newLock = 1;
|
|
||||||
asm volatile (
|
|
||||||
"\n1:\n\t"
|
|
||||||
"lwarx %0,0,%1\n\t"
|
|
||||||
"cmpwi 0,%0,0\n\t"
|
|
||||||
"bne- 2f\n\t"
|
|
||||||
"stwcx. %2,0,%1\n\t"
|
|
||||||
"bne- 1b\n\t"
|
|
||||||
"isync\n"
|
|
||||||
"2:\n\t"
|
|
||||||
: "=&r" (oldLock)
|
|
||||||
: "r" (&mLock), "r" (newLock)
|
|
||||||
: "cr0", "memory"
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
return (oldLock == 0);
|
|
||||||
#else
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
return TryEnterCriticalSection(&mHandle) ? true : false;
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unlock the mutex.
|
|
||||||
/// If any threads are waiting for the lock on this mutex, one of them will
|
|
||||||
/// be unblocked.
|
|
||||||
inline void unlock()
|
|
||||||
{
|
|
||||||
#if defined(_FAST_MUTEX_ASM_)
|
|
||||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
|
||||||
asm volatile (
|
|
||||||
"movl $0,%%eax\n\t"
|
|
||||||
"xchg %%eax,%0\n\t"
|
|
||||||
: "=m" (mLock)
|
|
||||||
:
|
|
||||||
: "%eax", "memory"
|
|
||||||
);
|
|
||||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
|
||||||
int *ptrLock = &mLock;
|
|
||||||
__asm {
|
|
||||||
mov eax,0
|
|
||||||
mov ecx,ptrLock
|
|
||||||
xchg eax,[ecx]
|
|
||||||
}
|
|
||||||
#elif defined(__GNUC__) && (defined(__ppc__))
|
|
||||||
asm volatile (
|
|
||||||
"sync\n\t" // Replace with lwsync where possible?
|
|
||||||
: : : "memory"
|
|
||||||
);
|
|
||||||
mLock = 0;
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
LeaveCriticalSection(&mHandle);
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
pthread_mutex_unlock(&mHandle);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
#if defined(_FAST_MUTEX_ASM_)
|
|
||||||
int mLock;
|
|
||||||
#else
|
|
||||||
#if defined(_TTHREAD_WIN32_)
|
|
||||||
CRITICAL_SECTION mHandle;
|
|
||||||
#elif defined(_TTHREAD_POSIX_)
|
|
||||||
pthread_mutex_t mHandle;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // _FAST_MUTEX_H_
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
|||||||
|
.. _contributing:
|
||||||
|
|
||||||
|
###########################
|
||||||
|
How to contribute to DFHack
|
||||||
|
###########################
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:local:
|
||||||
|
|
||||||
|
.. _contributing-code:
|
||||||
|
|
||||||
|
Contributing Code
|
||||||
|
=================
|
||||||
|
Several things should be kept in mind when contributing code to DFHack.
|
||||||
|
|
||||||
|
Code Format
|
||||||
|
-----------
|
||||||
|
* Four space indents for C++. Never use tabs for indentation in any language.
|
||||||
|
* LF (Unix style) line terminators
|
||||||
|
* Avoid trailing whitespace
|
||||||
|
* UTF-8 encoding
|
||||||
|
* For C++:
|
||||||
|
|
||||||
|
* Opening and closing braces on their own lines or opening brace at the end of the previous line
|
||||||
|
* Braces placed at original indent level if on their own lines
|
||||||
|
* #includes should be sorted. C++ libraries first, then dfhack modules, then df structures,
|
||||||
|
then local includes. Within each category they should be sorted alphabetically.
|
||||||
|
|
||||||
|
How to get new code into DFHack
|
||||||
|
-------------------------------
|
||||||
|
* Submit pull requests to the ``develop`` branch, not the ``master`` branch.
|
||||||
|
(The ``master`` branch always points at the most recent release)
|
||||||
|
* Use a new branch for each feature or bugfix so that your changes can be merged independently
|
||||||
|
(i.e. not the master or develop branch of your fork).
|
||||||
|
* If possible, compile on multiple platforms when changing anything that compiles
|
||||||
|
* It must pass CI - run ``python travis/all.py`` to check this.
|
||||||
|
* Update documentation when applicable - see `docs-standards` for details.
|
||||||
|
* Update ``changelog.txt`` and ``docs/Authors.rst`` when applicable. See
|
||||||
|
`build-changelog` for more information on the changelog format.
|
||||||
|
* Create a GitHub pull request once finished
|
||||||
|
* Submit ideas and bug reports as :issue:`issues on GitHub <>`.
|
||||||
|
Posts in the forum thread can easily get missed or forgotten.
|
||||||
|
* Work on :issue:`reported problems <?q=is:open+-label:idea>`
|
||||||
|
will take priority over ideas or suggestions.
|
||||||
|
|
||||||
|
|
||||||
|
Other ways to help
|
||||||
|
==================
|
||||||
|
DFHack is a software project, but there's a lot more to it than programming.
|
||||||
|
If you're not comfortable programming, you can help by:
|
||||||
|
|
||||||
|
* reporting bugs and incomplete documentation
|
||||||
|
* improving the documentation
|
||||||
|
* finding third-party scripts to add
|
||||||
|
* writing tutorials for newbies
|
||||||
|
|
||||||
|
All those things are crucial, and often under-represented. So if that's
|
||||||
|
your thing, go get started!
|
||||||
|
|
@ -0,0 +1,87 @@
|
|||||||
|
===========================
|
||||||
|
DFHack development overview
|
||||||
|
===========================
|
||||||
|
|
||||||
|
DFHack has various components; this page provides an overview of some. If you
|
||||||
|
are looking to develop a tool for DFHack, developing a script or plugin is
|
||||||
|
likely the most straightforward choice.
|
||||||
|
|
||||||
|
Other pages that may be relevant include:
|
||||||
|
|
||||||
|
- `contributing`
|
||||||
|
- `documentation`
|
||||||
|
- `license`
|
||||||
|
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
-------
|
||||||
|
|
||||||
|
DFHack plugins are written in C++ and located in the ``plugins`` folder.
|
||||||
|
Currently, documentation on how to write plugins is somewhat sparse. There are
|
||||||
|
templates that you can get use to get started in the ``plugins/skeleton``
|
||||||
|
folder, and the source code of existing plugins can also be helpful.
|
||||||
|
|
||||||
|
If you want to compile a plugin that you have just added, you will need to add a
|
||||||
|
call to ``DFHACK_PLUGIN`` in ``plugins/CMakeLists.txt``.
|
||||||
|
|
||||||
|
Plugins have the ability to make one or more commands available to users of the
|
||||||
|
DFHack console. Examples include `3dveins` (which implements the ``3dveins``
|
||||||
|
command) and `reveal` (which implements ``reveal``, ``unreveal``, and several
|
||||||
|
other commands).
|
||||||
|
|
||||||
|
Plugins can also register handlers to run on every tick, and can interface with
|
||||||
|
the built-in `enable` and `disable` commands. For the full plugin API, see the
|
||||||
|
skeleton plugins or ``PluginManager.cpp``.
|
||||||
|
|
||||||
|
Installed plugins live in the ``hack/plugins`` folder of a DFHack installation,
|
||||||
|
and the `load` family of commands can be used to load a recompiled plugin
|
||||||
|
without restarting DF.
|
||||||
|
|
||||||
|
See `plugins-index` for a list of all plugins included in DFHack.
|
||||||
|
|
||||||
|
Scripts
|
||||||
|
-------
|
||||||
|
|
||||||
|
DFHack scripts can currently be written in Lua or Ruby. The `Lua API <lua-api>`
|
||||||
|
is more complete and currently better-documented, however. Referring to existing
|
||||||
|
scripts as well as the API documentation can be helpful when developing new
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
`Scripts included in DFHack <scripts-index>` live in a separate `scripts repository <https://github.com/dfhack/scripts>`_.
|
||||||
|
This can be found in the ``scripts`` submodule if you have
|
||||||
|
`cloned DFHack <compile-how-to-get-the-code>`, or the ``hack/scripts`` folder
|
||||||
|
of an installed copy of DFHack.
|
||||||
|
|
||||||
|
Core
|
||||||
|
----
|
||||||
|
|
||||||
|
The `DFHack core <dfhack-core>` has a variety of low-level functions. It is
|
||||||
|
responsible for hooking into DF (via SDL), providing a console, and providing an
|
||||||
|
interface for plugins and scripts to interact with DF.
|
||||||
|
|
||||||
|
Modules
|
||||||
|
-------
|
||||||
|
|
||||||
|
A lot of shared code to interact with DF in more complicated ways is contained
|
||||||
|
in **modules**. For example, the Units module contains functions for checking
|
||||||
|
various traits of units, changing nicknames properly, and more. Generally, code
|
||||||
|
that is useful to multiple plugins and scripts should go in the appropriate
|
||||||
|
module, if there is one.
|
||||||
|
|
||||||
|
Several modules are also `exposed to Lua <lua-cpp-func-wrappers>`, although
|
||||||
|
some functions (and some entire modules) are currently only available in C++.
|
||||||
|
|
||||||
|
Remote access interface
|
||||||
|
-----------------------
|
||||||
|
DFHack supports remote access by exchanging Google protobuf messages via a TCP
|
||||||
|
socket. Both the core and plugins can define remotely accessible methods. The
|
||||||
|
``dfhack-run`` command uses this interface to invoke ordinary console commands.
|
||||||
|
|
||||||
|
Currently the supported set of requests is limited, because the developers don't
|
||||||
|
know what exactly is most useful. `remotefortressreader` provides a fairly
|
||||||
|
comprehensive interface for visualisers such as :forums:`Armok Vision <146473>`.
|
||||||
|
|
@ -0,0 +1,285 @@
|
|||||||
|
.. _documentation:
|
||||||
|
|
||||||
|
###########################
|
||||||
|
DFHack Documentation System
|
||||||
|
###########################
|
||||||
|
|
||||||
|
|
||||||
|
DFHack documentation, like the file you are reading now, is created as ``.rst`` files,
|
||||||
|
which are in `reStructuredText (reST) <http://sphinx-doc.org/rest.html>`_ format.
|
||||||
|
This is a documentation format common in the Python community. It is very
|
||||||
|
similar in concept - and in syntax - to Markdown, as found on GitHub and many other
|
||||||
|
places. However it is more advanced than Markdown, with more features available when
|
||||||
|
compiled to HTML, such as automatic tables of contents, cross-linking, special
|
||||||
|
external links (forum, wiki, etc) and more. The documentation is compiled by a
|
||||||
|
Python tool, `Sphinx <http://sphinx-doc.org>`_.
|
||||||
|
|
||||||
|
The DFHack build process will compile the documentation, but this is disabled
|
||||||
|
by default due to the additional Python and Sphinx requirements. You typically
|
||||||
|
only need to build the docs if you're changing them, or perhaps
|
||||||
|
if you want a local HTML copy; otherwise, you can read an
|
||||||
|
`online version hosted by ReadTheDocs <https://dfhack.readthedocs.org>`_.
|
||||||
|
|
||||||
|
(Note that even if you do want a local copy, it is certainly not necessary to
|
||||||
|
compile the documentation in order to read it. Like Markdown, reST documents are
|
||||||
|
designed to be just as readable in a plain-text editor as they are in HTML format.
|
||||||
|
The main thing you lose in plain text format is hyperlinking.)
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:local:
|
||||||
|
|
||||||
|
.. _docs-standards:
|
||||||
|
|
||||||
|
Documentation standards
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Whether you're adding new code or just fixing old documentation (and there's plenty),
|
||||||
|
there are a few important standards for completeness and consistent style. Treat
|
||||||
|
this section as a guide rather than iron law, match the surrounding text, and you'll
|
||||||
|
be fine.
|
||||||
|
|
||||||
|
Each command should have a short (~54 character) help string, which is shown
|
||||||
|
by the `ls` command. For scripts, this is a comment on the first line
|
||||||
|
(the comment marker and whitespace is stripped). For plugins it's the second
|
||||||
|
argument to ``PluginCommand``. Please make this brief but descriptive!
|
||||||
|
|
||||||
|
Everything should be documented! If it's not clear *where* a particular
|
||||||
|
thing should be documented, ask on IRC or in the DFHack thread on Bay12 -
|
||||||
|
as well as getting help, you'll be providing valuable feedback that
|
||||||
|
makes it easier for future readers!
|
||||||
|
|
||||||
|
Scripts can use a custom autodoc function, based on the Sphinx ``include``
|
||||||
|
directive - anything between the tokens is copied into the appropriate scripts
|
||||||
|
documentation page. For Ruby, we follow the built-in docstring convention
|
||||||
|
(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]``
|
||||||
|
- ordinary multi-line strings. It is highly encouraged to reuse this string
|
||||||
|
as the in-console documentation by (eg.) printing it when a ``-help`` argument
|
||||||
|
is given.
|
||||||
|
|
||||||
|
The docs **must** have a heading which exactly matches the command, underlined
|
||||||
|
with ``=====`` to the same length. For example, a lua file would have::
|
||||||
|
|
||||||
|
local helpstr = [====[
|
||||||
|
|
||||||
|
add-thought
|
||||||
|
===========
|
||||||
|
Adds a thought or emotion to the selected unit. Can be used by other scripts,
|
||||||
|
or the gui invoked by running ``add-thought gui`` with a unit selected.
|
||||||
|
|
||||||
|
]====]
|
||||||
|
|
||||||
|
|
||||||
|
Where the heading for a section is also the name of a command, the spelling
|
||||||
|
and case should exactly match the command to enter in the DFHack command line.
|
||||||
|
|
||||||
|
Try to keep lines within 80-100 characters, so it's readable in plain text
|
||||||
|
in the terminal - Sphinx (our documentation system) will make sure
|
||||||
|
paragraphs flow.
|
||||||
|
|
||||||
|
If there aren't many options or examples to show, they can go in a paragraph of
|
||||||
|
text. Use double-backticks to put commands in monospaced font, like this::
|
||||||
|
|
||||||
|
You can use ``cleanowned scattered x`` to dump tattered or abandoned items.
|
||||||
|
|
||||||
|
If the command takes more than three arguments, format the list as a table
|
||||||
|
called Usage. The table *only* lists arguments, not full commands.
|
||||||
|
Input values are specified in angle brackets. Example::
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
:arg1: A simple argument.
|
||||||
|
:arg2 <input>: Does something based on the input value.
|
||||||
|
:Very long argument:
|
||||||
|
Is very specific.
|
||||||
|
|
||||||
|
To demonstrate usage - useful mainly when the syntax is complicated, list the
|
||||||
|
full command with arguments in monospaced font, then indent the next line and
|
||||||
|
describe the effect::
|
||||||
|
|
||||||
|
``resume all``
|
||||||
|
Resumes all suspended constructions.
|
||||||
|
|
||||||
|
If it would be helpful to mention another DFHack command, don't just type the
|
||||||
|
name - add a hyperlink! Specify the link target in backticks, and it will be
|
||||||
|
replaced with the corresponding title and linked: eg ```autolabor```
|
||||||
|
=> `autolabor`. Link targets should be equivalent to the command
|
||||||
|
described (without file extension), and placed above the heading of that
|
||||||
|
section like this::
|
||||||
|
|
||||||
|
.. _autolabor:
|
||||||
|
|
||||||
|
autolabor
|
||||||
|
=========
|
||||||
|
|
||||||
|
Add link targets if you need them, but otherwise plain headings are preferred.
|
||||||
|
Scripts have link targets created automatically.
|
||||||
|
|
||||||
|
|
||||||
|
Building the documentation
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Required dependencies
|
||||||
|
---------------------
|
||||||
|
In order to build the documentation, you must have Python with Sphinx
|
||||||
|
version |sphinx_min_version| or later. Both Python 2.x and 3.x are supported.
|
||||||
|
|
||||||
|
When installing Sphinx from OS package managers, be aware that there is
|
||||||
|
another program called Sphinx, completely unrelated to documentation management.
|
||||||
|
Be sure you are installing the right Sphinx; it may be called ``python-sphinx``,
|
||||||
|
for example. To avoid doubt, ``pip`` can be used instead as detailed below.
|
||||||
|
|
||||||
|
Once you have installed Sphinx, ``sphinx-build --version`` should report the
|
||||||
|
version of Sphinx that you have installed. If this works, CMake should also be
|
||||||
|
able to find Sphinx.
|
||||||
|
|
||||||
|
For more detailed platform-specific instructions, see the sections below:
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
:backlinks: none
|
||||||
|
|
||||||
|
|
||||||
|
Linux
|
||||||
|
~~~~~
|
||||||
|
Most Linux distributions will include Python by default. If not, start by
|
||||||
|
installing Python (preferably Python 3). On Debian-based distros::
|
||||||
|
|
||||||
|
sudo apt install python3
|
||||||
|
|
||||||
|
Check your package manager to see if Sphinx |sphinx_min_version| or later is
|
||||||
|
available. On Debian-based distros, this package is named ``python3-sphinx``.
|
||||||
|
If this package is new enough, you can install it directly. If not, or if you
|
||||||
|
want to use a newer Sphinx version (which may result in faster builds), you
|
||||||
|
can install Sphinx through the ``pip`` package manager instead. On Debian-based
|
||||||
|
distros, you can install pip with::
|
||||||
|
|
||||||
|
sudo apt install python3-pip
|
||||||
|
|
||||||
|
Once pip is available, you can then install Sphinx with::
|
||||||
|
|
||||||
|
pip3 install sphinx
|
||||||
|
|
||||||
|
If you run this as an unpriviliged user, it may install a local copy of Sphinx
|
||||||
|
for your user only. The ``sphinx-build`` executable will typically end up in
|
||||||
|
``~/.local/bin/`` in this case. Alternatively, you can install Sphinx
|
||||||
|
system-wide by running pip with ``sudo``. In any case, you will need the folder
|
||||||
|
containing ``sphinx-build`` to be in your ``$PATH``.
|
||||||
|
|
||||||
|
macOS
|
||||||
|
~~~~~
|
||||||
|
macOS has Python 2.7 installed by default, but it does not have the pip package manager.
|
||||||
|
|
||||||
|
You can install Homebrew's Python 3, which includes pip, and then install the
|
||||||
|
latest Sphinx using pip::
|
||||||
|
|
||||||
|
brew install python3
|
||||||
|
pip3 install sphinx
|
||||||
|
|
||||||
|
Alternatively, you can simply install Sphinx directly from Homebrew::
|
||||||
|
|
||||||
|
brew install sphinx-doc
|
||||||
|
|
||||||
|
This will install Sphinx for macOS's system Python 2.7, without needing pip.
|
||||||
|
|
||||||
|
Either method works; if you plan to use Python for other purposes, it might best
|
||||||
|
to install Homebrew's Python 3 so that you have the latest Python as well as pip.
|
||||||
|
If not, just installing sphinx-doc for macOS's system Python 2.7 is fine.
|
||||||
|
|
||||||
|
|
||||||
|
Windows
|
||||||
|
~~~~~~~
|
||||||
|
Python for Windows can be downloaded `from python.org <https://www.python.org/downloads/>`_.
|
||||||
|
The latest version of Python 3 is recommended, as it includes pip already.
|
||||||
|
|
||||||
|
You can also install Python and pip through the Chocolatey package manager.
|
||||||
|
After installing Chocolatey as outlined in the `Windows compilation instructions <compile-windows>`,
|
||||||
|
run the following command from an elevated (admin) command prompt (e.g. ``cmd.exe``)::
|
||||||
|
|
||||||
|
choco install python pip -y
|
||||||
|
|
||||||
|
Once you have pip available, you can install Sphinx with the following command::
|
||||||
|
|
||||||
|
pip install sphinx
|
||||||
|
|
||||||
|
Note that this may require opening a new (admin) command prompt if you just
|
||||||
|
installed pip from the same command prompt.
|
||||||
|
|
||||||
|
|
||||||
|
Using CMake
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Enabling the ``BUILD_DOCS`` CMake option will cause the documentation to be built
|
||||||
|
whenever it changes as part of the normal DFHack build process. There are several
|
||||||
|
ways to do this:
|
||||||
|
|
||||||
|
* When initially running CMake, add ``-DBUILD_DOCS:bool=ON`` to your ``cmake``
|
||||||
|
command. For example::
|
||||||
|
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DBUILD_DOCS:bool=ON -DCMAKE_INSTALL_PREFIX=<path to DF>
|
||||||
|
|
||||||
|
* If you have already run CMake, you can simply run it again from your build
|
||||||
|
folder to update your configuration::
|
||||||
|
|
||||||
|
cmake .. -DBUILD_DOCS:bool=ON
|
||||||
|
|
||||||
|
* You can edit the ``BUILD_DOCS`` setting in CMakeCache.txt directly
|
||||||
|
|
||||||
|
* You can use the CMake GUI to change ``BUILD_DOCS``
|
||||||
|
|
||||||
|
* On Windows, if you prefer to use the batch scripts, you can run
|
||||||
|
``generate-msvc-gui.bat`` and set ``BUILD_DOCS`` through the GUI. If you are
|
||||||
|
running another file, such as ``generate-msvc-all.bat``, you will need to edit
|
||||||
|
it to add the flag. You can also run ``cmake`` on the command line, similar to
|
||||||
|
other platforms.
|
||||||
|
|
||||||
|
Running Sphinx manually
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
You can also build the documentation without going through CMake, which may be
|
||||||
|
faster. There is a ``docs/build.sh`` script available for Linux and macOS that
|
||||||
|
will run essentially the same command that CMake runs - see the script for
|
||||||
|
options.
|
||||||
|
|
||||||
|
To build the documentation with default options, run the following command from
|
||||||
|
the root DFHack folder::
|
||||||
|
|
||||||
|
sphinx-build . docs/html
|
||||||
|
|
||||||
|
Sphinx has many options to enable clean builds, parallel builds, logging, and
|
||||||
|
more - run ``sphinx-build --help`` for details.
|
||||||
|
|
||||||
|
|
||||||
|
.. _build-changelog:
|
||||||
|
|
||||||
|
Building the changelogs
|
||||||
|
=======================
|
||||||
|
If you have Python installed, but do not want to build all of the documentation,
|
||||||
|
you can build the changelogs with the ``docs/gen_changelog.py`` script. This
|
||||||
|
script provides additional options, including one to build individual changelogs
|
||||||
|
for all DFHack versions - run ``python docs/gen_changelog.py --help`` for details.
|
||||||
|
|
||||||
|
Changelog entries are obtained from ``changelog.txt`` files in multiple repos.
|
||||||
|
This allows changes to be listed in the same repo where they were made. These
|
||||||
|
changelogs are combined as part of the changelog build process:
|
||||||
|
|
||||||
|
* ``docs/changelog.txt`` for changes in the main ``dfhack`` repo
|
||||||
|
* ``scripts/changelog.txt`` for changes made to scripts in the ``scripts`` repo
|
||||||
|
* ``library/xml/changelog.txt`` for changes made in the ``df-structures`` repo
|
||||||
|
|
||||||
|
Building the changelogs generates two files: ``docs/_auto/news.rst`` and
|
||||||
|
``docs/_auto/news-dev.rst``. These correspond to `changelog` and `dev-changelog`
|
||||||
|
and contain changes organized by stable and development DFHack releases,
|
||||||
|
respectively. For example, an entry listed under "0.44.05-alpha1" in
|
||||||
|
changelog.txt will be listed under that version in the development changelog as
|
||||||
|
well, but under "0.44.05-r1" in the stable changelog (assuming that is the
|
||||||
|
closest stable release after 0.44.05-alpha1). An entry listed under a stable
|
||||||
|
release like "0.44.05-r1" in changelog.txt will be listed under that release in
|
||||||
|
both the stable changelog and the development changelog.
|
||||||
|
|
||||||
|
|
||||||
|
Changelog syntax
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. include:: /docs/changelog.txt
|
||||||
|
:start-after: ===help
|
||||||
|
:end-before: ===end
|
@ -0,0 +1,149 @@
|
|||||||
|
.. _memory-research:
|
||||||
|
|
||||||
|
###############
|
||||||
|
Memory research
|
||||||
|
###############
|
||||||
|
|
||||||
|
There are a variety of tools that can be used to analyze DF memory - some are
|
||||||
|
listed here. Note that some of these may be old and unmaintained. If you aren't
|
||||||
|
sure what tool would be best for your purposes, feel free to ask for advice (on
|
||||||
|
IRC, Bay12, etc.).
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
Cross-platform tools
|
||||||
|
====================
|
||||||
|
|
||||||
|
Ghidra
|
||||||
|
------
|
||||||
|
|
||||||
|
Ghidra is a cross-platform reverse-engineering framework (written in Java)
|
||||||
|
available at https://ghidra-sre.org. It supports analyzing both 32-bit and
|
||||||
|
64-bit executables for all supported DF platforms. There are some custom DFHack
|
||||||
|
Ghidra scripts available in the `df_misc`_ repo (look for ``.java`` files).
|
||||||
|
|
||||||
|
|
||||||
|
IDA Freeware 7.0
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Available from `Hex-Rays <https://www.hex-rays.com/products/ida/support/download_freeware/>`_.
|
||||||
|
Supports analyzing both 32-bit and 64-bit executables for all supported DF platforms.
|
||||||
|
Some ``.idc`` scripts for IDA are available in the `df_misc`_ repo.
|
||||||
|
|
||||||
|
.. _df_misc: https://github.com/DFHack/df_misc
|
||||||
|
|
||||||
|
|
||||||
|
Hopper
|
||||||
|
------
|
||||||
|
|
||||||
|
Runs on macOS and some Linux distributions; available from https://www.hopperapp.com/.
|
||||||
|
`TWBT <https://github.com/mifki/df-twbt/blob/master/PATCHES.md>`_ uses this to produce some patches.
|
||||||
|
|
||||||
|
|
||||||
|
DFHack tools
|
||||||
|
------------
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
There are a few development plugins useful for low-level memory research. They
|
||||||
|
are not built by default, but can be built by setting the ``BUILD_DEVEL``
|
||||||
|
`CMake option <compile-build-options>`. These include:
|
||||||
|
|
||||||
|
- ``check-structures-sanity``, which performs sanity checks on the given DF
|
||||||
|
object. Note that this will crash in several cases, some intentional, so using
|
||||||
|
this with `GDB <linux-gdb>` is recommended.
|
||||||
|
- ``memview``, which produces a hex dump of a given memory range. It also
|
||||||
|
highlights valid pointers, and can be configured to work with `sizecheck`
|
||||||
|
to auto-detect object sizes.
|
||||||
|
- ``vectors``, which can identify instances of ``std::vector`` in a given memory range.
|
||||||
|
|
||||||
|
Scripts
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Several `development scripts <scripts-devel>` can be useful for memory research.
|
||||||
|
These include (but are not limited to):
|
||||||
|
|
||||||
|
- `devel/dump-offsets`
|
||||||
|
- `devel/find-offsets`
|
||||||
|
- `devel/lsmem`
|
||||||
|
- `devel/sc` (requires `sizecheck`)
|
||||||
|
- `devel/visualize-structure`
|
||||||
|
- Generally, any script starting with ``devel/find``
|
||||||
|
|
||||||
|
.. _sizecheck:
|
||||||
|
|
||||||
|
Sizecheck
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Sizecheck is a custom tool that hooks into the memory allocator and inserts a
|
||||||
|
header indicating the size of every object. The corresponding logic to check for
|
||||||
|
this header when freeing memory usually works, but is inherently not foolproof.
|
||||||
|
You should not count on DF being stable when using this.
|
||||||
|
|
||||||
|
DFHack's implementation of sizecheck is currently only tested on Linux, although
|
||||||
|
it probably also works on macOS. It can be built with the ``BUILD_SIZECHECK``
|
||||||
|
`CMake option <compile-build-options>`, which produces a ``libsizecheck``
|
||||||
|
library installed in the ``hack`` folder. You will need to preload this library
|
||||||
|
manually, by setting ``PRELOAD_LIB`` on Linux (or ``LD_PRELOAD`` if editing
|
||||||
|
the ``dfhack`` launcher script directly), or by editing the ``dfhack``
|
||||||
|
launcher script and adding the library to ``DYLD_INSERT_LIBRARIES`` on macOS.
|
||||||
|
|
||||||
|
There is also an older sizecheck implementation by Mifki available on
|
||||||
|
`GitHub <https://github.com/mifki/df-sizecheck>`__ (``b.cpp`` is the main
|
||||||
|
sizecheck library, and ``win_patch.cpp`` is used for Windows support). To use
|
||||||
|
this with other DFHack tools, you will likely need to edit the header's
|
||||||
|
magic number to match what is used in `devel/sc` (search for a hexadecimal
|
||||||
|
constant starting with ``0x``).
|
||||||
|
|
||||||
|
Legacy tools
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some very old DFHack tools are available in the `legacy branch on GitHub <https://github.com/dfhack/dfhack/tree/legacy/tools>`_.
|
||||||
|
No attempt is made to support these.
|
||||||
|
|
||||||
|
|
||||||
|
Linux-specific tools
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. _linux-gdb:
|
||||||
|
|
||||||
|
GDB
|
||||||
|
---
|
||||||
|
|
||||||
|
`GDB <https://www.gnu.org/software/gdb/>`_ is technically cross-platform, but
|
||||||
|
tends to work best on Linux, and DFHack currently only offers support for using
|
||||||
|
GDB on 64-bit Linux. To start with GDB, pass ``-g`` to the DFHack launcher
|
||||||
|
script::
|
||||||
|
|
||||||
|
./dfhack -g
|
||||||
|
|
||||||
|
Some basic GDB commands:
|
||||||
|
|
||||||
|
- ``run``: starts DF from the GDB prompt. Any arguments will be passed as
|
||||||
|
command-line arguments to DF (e.g. `load-save` may be useful).
|
||||||
|
- ``bt`` will produce a backtrace if DF crashes.
|
||||||
|
|
||||||
|
See the `official GDB documentation <https://www.gnu.org/software/gdb/documentation/>`_
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
|
||||||
|
df-structures GUI
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
This is a tool written by Angavrilov and available on `GitHub <https://github.com/angavrilov/cl-linux-debug>`__.
|
||||||
|
It only supports 32-bit DF. Some assistance may be available on IRC.
|
||||||
|
|
||||||
|
|
||||||
|
EDB (Evan's debugger)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Available on `GitHub <https://github.com/eteran/edb-debugger>`__.
|
||||||
|
|
||||||
|
|
||||||
|
Windows-specific tools
|
||||||
|
======================
|
||||||
|
|
||||||
|
Some people have used `Cheat Engine <https://www.cheatengine.org/>`__ for research in the past.
|
@ -0,0 +1,17 @@
|
|||||||
|
#############
|
||||||
|
Removed tools
|
||||||
|
#############
|
||||||
|
|
||||||
|
This page lists tools (plugins or scripts) that were previously included in
|
||||||
|
DFHack but have been removed. It exists primarily so that internal links still
|
||||||
|
work (e.g. links from the `changelog`).
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:local:
|
||||||
|
:depth: 1
|
||||||
|
|
||||||
|
.. _warn-stuck-trees:
|
||||||
|
|
||||||
|
warn-stuck-trees
|
||||||
|
================
|
||||||
|
The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01.
|
@ -1,14 +0,0 @@
|
|||||||
###############
|
|
||||||
Removed scripts
|
|
||||||
###############
|
|
||||||
|
|
||||||
The following scripts were removed for various reasons.
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:depth: 2
|
|
||||||
|
|
||||||
.. _warn-stuck-trees:
|
|
||||||
|
|
||||||
warn-stuck-trees
|
|
||||||
================
|
|
||||||
The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01.
|
|
@ -0,0 +1,34 @@
|
|||||||
|
===================================
|
||||||
|
DF data definitions (DF-structures)
|
||||||
|
===================================
|
||||||
|
|
||||||
|
DFHack's information about DF's data structures is stored in XML files in the
|
||||||
|
`df-structures repository <https://github.com/DFHack/df-structures>`_. If you
|
||||||
|
have `obtained a local copy of the DFHack source <compile-how-to-get-the-code>`,
|
||||||
|
DF-structures is included as a submodule in ``library/xml``.
|
||||||
|
|
||||||
|
Data structure layouts are described in files named with the ``df.*.xml``
|
||||||
|
pattern. This information is transformed by a Perl script (``codegen.pl``) into
|
||||||
|
C++ headers, as well as metadata for the Lua wrapper. This ultimately allows
|
||||||
|
DFHack code to access DF data directly, with the same speed and capabilities as
|
||||||
|
DF itself, which is an advantage over the older out-of-process approach (used
|
||||||
|
by debuggers and utilities like Dwarf Therapist). The main disadvantage of this
|
||||||
|
approach is that any compiled code relying on these layouts will break when DF's
|
||||||
|
layout changes, and will need to be recompiled for every new DF version.
|
||||||
|
|
||||||
|
Addresses of DF global objects and vtables are stored in a separate file,
|
||||||
|
:file:`symbols.xml`. Since these are only absolute addresses, they do not need
|
||||||
|
to be compiled in to DFHack code, and are instead loaded at runtime. This makes
|
||||||
|
fixes and additions to global addresses possible without recompiling DFHack.
|
||||||
|
In an installed copy of DFHack, this file can be found at the root of the
|
||||||
|
``hack`` folder.
|
||||||
|
|
||||||
|
The following pages contain more detailed information about various aspects
|
||||||
|
of DF-structures:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:titlesonly:
|
||||||
|
|
||||||
|
/library/xml/SYNTAX
|
||||||
|
/library/xml/how-to-update
|
||||||
|
|
@ -1,285 +1,5 @@
|
|||||||
import collections
|
import os, sys
|
||||||
import copy
|
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sphinx_extensions'))
|
||||||
import itertools
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
CHANGELOG_PATHS = (
|
from dfhack.changelog import cli_entrypoint
|
||||||
'docs/changelog.txt',
|
cli_entrypoint()
|
||||||
'scripts/changelog.txt',
|
|
||||||
'library/xml/changelog.txt',
|
|
||||||
)
|
|
||||||
|
|
||||||
CHANGELOG_SECTIONS = [
|
|
||||||
'New Plugins',
|
|
||||||
'New Scripts',
|
|
||||||
'New Tweaks',
|
|
||||||
'New Features',
|
|
||||||
'New Internal Commands',
|
|
||||||
'Fixes',
|
|
||||||
'Misc Improvements',
|
|
||||||
'Removed',
|
|
||||||
'API',
|
|
||||||
'Internals',
|
|
||||||
'Lua',
|
|
||||||
'Ruby',
|
|
||||||
'Structures',
|
|
||||||
]
|
|
||||||
|
|
||||||
REPLACEMENTS = {
|
|
||||||
'`search`': '`search-plugin`',
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_title_case(word):
|
|
||||||
if word == word.upper():
|
|
||||||
# Preserve acronyms
|
|
||||||
return word
|
|
||||||
return word[0].upper() + word[1:].lower()
|
|
||||||
|
|
||||||
def find_all_indices(string, substr):
|
|
||||||
start = 0
|
|
||||||
while True:
|
|
||||||
i = string.find(substr, start)
|
|
||||||
if i == -1:
|
|
||||||
return
|
|
||||||
yield i
|
|
||||||
start = i + 1
|
|
||||||
|
|
||||||
def replace_text(string, replacements):
|
|
||||||
for old_text, new_text in replacements.items():
|
|
||||||
new_string = ''
|
|
||||||
new_string_end = 0 # number of characters from string in new_string
|
|
||||||
for i in find_all_indices(string, old_text):
|
|
||||||
if i > 0 and string[i - 1] == '!':
|
|
||||||
# exempt if preceded by '!'
|
|
||||||
new_string += string[new_string_end:i - 1]
|
|
||||||
new_string += old_text
|
|
||||||
else:
|
|
||||||
# copy until this occurrence
|
|
||||||
new_string += string[new_string_end:i]
|
|
||||||
new_string += new_text
|
|
||||||
new_string_end = i + len(old_text)
|
|
||||||
new_string += string[new_string_end:]
|
|
||||||
string = new_string
|
|
||||||
return string
|
|
||||||
|
|
||||||
class ChangelogEntry(object):
|
|
||||||
def __init__(self, text, section, stable_version, dev_version):
|
|
||||||
text = text.lstrip('- ')
|
|
||||||
# normalize section to title case
|
|
||||||
self.section = ' '.join(map(to_title_case, section.strip().split()))
|
|
||||||
self.stable_version = stable_version
|
|
||||||
self.dev_version = dev_version
|
|
||||||
self.dev_only = text.startswith('@')
|
|
||||||
text = text.lstrip('@ ')
|
|
||||||
self.children = []
|
|
||||||
|
|
||||||
split_index = text.find(': ')
|
|
||||||
if split_index != -1:
|
|
||||||
self.feature, description = text[:split_index], text[split_index+1:]
|
|
||||||
if description.strip():
|
|
||||||
self.children.insert(0, description.strip())
|
|
||||||
else:
|
|
||||||
self.feature = text
|
|
||||||
self.feature = self.feature.replace(':\\', ':').rstrip(':')
|
|
||||||
|
|
||||||
self.sort_key = self.feature.upper()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'ChangelogEntry(%r, %r)' % (self.feature, self.children)
|
|
||||||
|
|
||||||
def parse_changelog():
|
|
||||||
entries = []
|
|
||||||
|
|
||||||
for fpath in CHANGELOG_PATHS:
|
|
||||||
if not os.path.isfile(fpath):
|
|
||||||
continue
|
|
||||||
with open(fpath) as f:
|
|
||||||
cur_stable = None
|
|
||||||
cur_dev = None
|
|
||||||
cur_section = None
|
|
||||||
last_entry = None
|
|
||||||
multiline = ''
|
|
||||||
for line_id, line in enumerate(f.readlines()):
|
|
||||||
line_id += 1
|
|
||||||
|
|
||||||
if multiline:
|
|
||||||
multiline += line
|
|
||||||
elif '[[[' in line:
|
|
||||||
multiline = line.replace('[[[', '')
|
|
||||||
|
|
||||||
if ']]]' in multiline:
|
|
||||||
line = multiline.replace(']]]', '')
|
|
||||||
multiline = ''
|
|
||||||
elif multiline:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not line.strip() or line.startswith('==='):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.startswith('##'):
|
|
||||||
cur_section = line.lstrip('#').strip()
|
|
||||||
elif line.startswith('#'):
|
|
||||||
cur_dev = line.lstrip('#').strip().lower()
|
|
||||||
if ('alpha' not in cur_dev and 'beta' not in cur_dev and
|
|
||||||
'rc' not in cur_dev):
|
|
||||||
cur_stable = cur_dev
|
|
||||||
elif line.startswith('-'):
|
|
||||||
if not cur_stable or not cur_dev or not cur_section:
|
|
||||||
raise ValueError(
|
|
||||||
'%s:%i: Entry without section' % (fpath, line_id))
|
|
||||||
last_entry = ChangelogEntry(line.strip(), cur_section,
|
|
||||||
cur_stable, cur_dev)
|
|
||||||
entries.append(last_entry)
|
|
||||||
elif line.lstrip().startswith('-'):
|
|
||||||
if not cur_stable or not cur_dev:
|
|
||||||
raise ValueError(
|
|
||||||
'%s:%i: Sub-entry without section' % (fpath, line_id))
|
|
||||||
if not last_entry:
|
|
||||||
raise ValueError(
|
|
||||||
'%s:%i: Sub-entry without parent' % (fpath, line_id))
|
|
||||||
last_entry.children.append(line.strip('- \n'))
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid line: ' + line)
|
|
||||||
|
|
||||||
if not entries:
|
|
||||||
raise RuntimeError('No changelog files with contents found')
|
|
||||||
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def consolidate_changelog(all_entries):
|
|
||||||
for sections in all_entries.values():
|
|
||||||
for section, entries in sections.items():
|
|
||||||
# sort() is stable, so reverse entries so that older entries for the
|
|
||||||
# same feature are on top
|
|
||||||
entries.reverse()
|
|
||||||
entries.sort(key=lambda entry: entry.sort_key)
|
|
||||||
new_entries = []
|
|
||||||
for feature, group in itertools.groupby(entries,
|
|
||||||
lambda e: e.feature):
|
|
||||||
old_entries = list(group)
|
|
||||||
children = list(itertools.chain(*[entry.children
|
|
||||||
for entry in old_entries]))
|
|
||||||
new_entry = copy.deepcopy(old_entries[0])
|
|
||||||
new_entry.children = children
|
|
||||||
new_entries.append(new_entry)
|
|
||||||
entries[:] = new_entries
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def print_changelog(versions, all_entries, path, replace=True, prefix=''):
|
|
||||||
# all_entries: version -> section -> entry
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
def write(line):
|
|
||||||
if replace:
|
|
||||||
line = replace_text(line, REPLACEMENTS)
|
|
||||||
f.write(prefix + line + '\n')
|
|
||||||
for version in versions:
|
|
||||||
sections = all_entries[version]
|
|
||||||
if not sections:
|
|
||||||
continue
|
|
||||||
version = 'DFHack ' + version
|
|
||||||
write(version)
|
|
||||||
write('=' * len(version))
|
|
||||||
write('')
|
|
||||||
for section in CHANGELOG_SECTIONS:
|
|
||||||
entries = sections[section]
|
|
||||||
if not entries:
|
|
||||||
continue
|
|
||||||
write(section)
|
|
||||||
write('-' * len(section))
|
|
||||||
for entry in entries:
|
|
||||||
if len(entry.children) == 1:
|
|
||||||
write('- ' + entry.feature + ': ' +
|
|
||||||
entry.children[0].strip('- '))
|
|
||||||
continue
|
|
||||||
elif entry.children:
|
|
||||||
write('- ' + entry.feature + ':')
|
|
||||||
write('')
|
|
||||||
for child in entry.children:
|
|
||||||
write(' - ' + child)
|
|
||||||
write('')
|
|
||||||
else:
|
|
||||||
write('- ' + entry.feature)
|
|
||||||
write('')
|
|
||||||
write('')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_changelog(all=False):
|
|
||||||
entries = parse_changelog()
|
|
||||||
|
|
||||||
# scan for unrecognized sections
|
|
||||||
for entry in entries:
|
|
||||||
if entry.section not in CHANGELOG_SECTIONS:
|
|
||||||
raise RuntimeWarning('Unknown section: ' + entry.section)
|
|
||||||
|
|
||||||
# ordered versions
|
|
||||||
versions = ['future']
|
|
||||||
# map versions to stable versions
|
|
||||||
stable_version_map = {}
|
|
||||||
# version -> section -> entry
|
|
||||||
stable_entries = collections.defaultdict(lambda:
|
|
||||||
collections.defaultdict(list))
|
|
||||||
dev_entries = collections.defaultdict(lambda:
|
|
||||||
collections.defaultdict(list))
|
|
||||||
|
|
||||||
for entry in entries:
|
|
||||||
# build list of all versions
|
|
||||||
if entry.dev_version not in versions:
|
|
||||||
versions.append(entry.dev_version)
|
|
||||||
stable_version_map.setdefault(entry.dev_version, entry.stable_version)
|
|
||||||
|
|
||||||
if not entry.dev_only:
|
|
||||||
# add non-dev-only entries to both changelogs
|
|
||||||
stable_entries[entry.stable_version][entry.section].append(entry)
|
|
||||||
dev_entries[entry.dev_version][entry.section].append(entry)
|
|
||||||
|
|
||||||
consolidate_changelog(stable_entries)
|
|
||||||
consolidate_changelog(dev_entries)
|
|
||||||
|
|
||||||
print_changelog(versions, stable_entries, 'docs/_auto/news.rst')
|
|
||||||
print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst')
|
|
||||||
|
|
||||||
if all:
|
|
||||||
for version in versions:
|
|
||||||
if version not in stable_version_map:
|
|
||||||
print('warn: skipping ' + version)
|
|
||||||
continue
|
|
||||||
if stable_version_map[version] == version:
|
|
||||||
version_entries = {version: stable_entries[version]}
|
|
||||||
else:
|
|
||||||
version_entries = {version: dev_entries[version]}
|
|
||||||
print_changelog([version], version_entries,
|
|
||||||
'docs/_changelogs/%s-github.txt' % version,
|
|
||||||
replace=False)
|
|
||||||
print_changelog([version], version_entries,
|
|
||||||
'docs/_changelogs/%s-reddit.txt' % version,
|
|
||||||
replace=False,
|
|
||||||
prefix='> ')
|
|
||||||
|
|
||||||
return entries
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import argparse
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('-a', '--all', action='store_true',
|
|
||||||
help='Print changelogs for all versions to docs/_changelogs')
|
|
||||||
parser.add_argument('-c', '--check', action='store_true',
|
|
||||||
help='Check that all entries are printed')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
|
||||||
os.chdir('..')
|
|
||||||
entries = generate_changelog(all=args.all)
|
|
||||||
|
|
||||||
if args.check:
|
|
||||||
with open('docs/_auto/news.rst') as f:
|
|
||||||
content_stable = f.read()
|
|
||||||
with open('docs/_auto/news-dev.rst') as f:
|
|
||||||
content_dev = f.read()
|
|
||||||
for entry in entries:
|
|
||||||
for description in entry.children:
|
|
||||||
if not entry.dev_only and description not in content_stable:
|
|
||||||
print('stable missing: ' + description)
|
|
||||||
if description not in content_dev:
|
|
||||||
print('dev missing: ' + description)
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
============
|
||||||
|
About DFHack
|
||||||
|
============
|
||||||
|
|
||||||
|
These pages contain information about the general DFHack project.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
/docs/NEWS
|
||||||
|
/docs/Authors
|
||||||
|
/LICENSE
|
||||||
|
/docs/Removed
|
@ -0,0 +1,19 @@
|
|||||||
|
========================
|
||||||
|
DFHack Development Guide
|
||||||
|
========================
|
||||||
|
|
||||||
|
These are pages relevant to people developing for DFHack.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
/docs/Dev-intro
|
||||||
|
/docs/Contributing
|
||||||
|
/docs/Compile
|
||||||
|
/docs/NEWS-dev
|
||||||
|
/docs/Lua API
|
||||||
|
/docs/Documentation
|
||||||
|
/docs/Structures-intro
|
||||||
|
/docs/Memory-research
|
||||||
|
/docs/Binpatches
|
||||||
|
|
@ -0,0 +1,307 @@
|
|||||||
|
import collections
|
||||||
|
import copy
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from sphinx.errors import ExtensionError, SphinxError, SphinxWarning
|
||||||
|
|
||||||
|
from dfhack.util import DFHACK_ROOT, DOCS_ROOT
|
||||||
|
|
||||||
|
CHANGELOG_PATHS = (
|
||||||
|
'docs/changelog.txt',
|
||||||
|
'scripts/changelog.txt',
|
||||||
|
'library/xml/changelog.txt',
|
||||||
|
)
|
||||||
|
CHANGELOG_PATHS = (os.path.join(DFHACK_ROOT, p) for p in CHANGELOG_PATHS)
|
||||||
|
|
||||||
|
CHANGELOG_SECTIONS = [
|
||||||
|
'New Plugins',
|
||||||
|
'New Scripts',
|
||||||
|
'New Tweaks',
|
||||||
|
'New Features',
|
||||||
|
'New Internal Commands',
|
||||||
|
'Fixes',
|
||||||
|
'Misc Improvements',
|
||||||
|
'Removed',
|
||||||
|
'API',
|
||||||
|
'Internals',
|
||||||
|
'Lua',
|
||||||
|
'Ruby',
|
||||||
|
'Structures',
|
||||||
|
]
|
||||||
|
|
||||||
|
REPLACEMENTS = {
|
||||||
|
'`search`': '`search-plugin`',
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_title_case(word):
|
||||||
|
if word == word.upper():
|
||||||
|
# Preserve acronyms
|
||||||
|
return word
|
||||||
|
return word[0].upper() + word[1:].lower()
|
||||||
|
|
||||||
|
def find_all_indices(string, substr):
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
i = string.find(substr, start)
|
||||||
|
if i == -1:
|
||||||
|
return
|
||||||
|
yield i
|
||||||
|
start = i + 1
|
||||||
|
|
||||||
|
def replace_text(string, replacements):
|
||||||
|
for old_text, new_text in replacements.items():
|
||||||
|
new_string = ''
|
||||||
|
new_string_end = 0 # number of characters from string in new_string
|
||||||
|
for i in find_all_indices(string, old_text):
|
||||||
|
if i > 0 and string[i - 1] == '!':
|
||||||
|
# exempt if preceded by '!'
|
||||||
|
new_string += string[new_string_end:i - 1]
|
||||||
|
new_string += old_text
|
||||||
|
else:
|
||||||
|
# copy until this occurrence
|
||||||
|
new_string += string[new_string_end:i]
|
||||||
|
new_string += new_text
|
||||||
|
new_string_end = i + len(old_text)
|
||||||
|
new_string += string[new_string_end:]
|
||||||
|
string = new_string
|
||||||
|
return string
|
||||||
|
|
||||||
|
class ChangelogEntry(object):
|
||||||
|
def __init__(self, text, section, stable_version, dev_version):
|
||||||
|
text = text.lstrip('- ')
|
||||||
|
# normalize section to title case
|
||||||
|
self.section = ' '.join(map(to_title_case, section.strip().split()))
|
||||||
|
self.stable_version = stable_version
|
||||||
|
self.dev_version = dev_version
|
||||||
|
self.dev_only = text.startswith('@')
|
||||||
|
text = text.lstrip('@ ')
|
||||||
|
self.children = []
|
||||||
|
|
||||||
|
split_index = text.find(': ')
|
||||||
|
if split_index != -1:
|
||||||
|
self.feature, description = text[:split_index], text[split_index+1:]
|
||||||
|
if description.strip():
|
||||||
|
self.children.insert(0, description.strip())
|
||||||
|
else:
|
||||||
|
self.feature = text
|
||||||
|
self.feature = self.feature.replace(':\\', ':').rstrip(':')
|
||||||
|
|
||||||
|
self.sort_key = self.feature.upper()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'ChangelogEntry(%r, %r)' % (self.feature, self.children)
|
||||||
|
|
||||||
|
def parse_changelog():
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
for fpath in CHANGELOG_PATHS:
|
||||||
|
if not os.path.isfile(fpath):
|
||||||
|
continue
|
||||||
|
with open(fpath) as f:
|
||||||
|
cur_stable = None
|
||||||
|
cur_dev = None
|
||||||
|
cur_section = None
|
||||||
|
last_entry = None
|
||||||
|
multiline = ''
|
||||||
|
for line_id, line in enumerate(f.readlines()):
|
||||||
|
line_id += 1
|
||||||
|
|
||||||
|
if multiline:
|
||||||
|
multiline += line
|
||||||
|
elif '[[[' in line:
|
||||||
|
multiline = line.replace('[[[', '')
|
||||||
|
|
||||||
|
if ']]]' in multiline:
|
||||||
|
line = multiline.replace(']]]', '')
|
||||||
|
multiline = ''
|
||||||
|
elif multiline:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not line.strip() or line.startswith('==='):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith('##'):
|
||||||
|
cur_section = line.lstrip('#').strip()
|
||||||
|
elif line.startswith('#'):
|
||||||
|
cur_dev = line.lstrip('#').strip().lower()
|
||||||
|
if ('alpha' not in cur_dev and 'beta' not in cur_dev and
|
||||||
|
'rc' not in cur_dev):
|
||||||
|
cur_stable = cur_dev
|
||||||
|
elif line.startswith('-'):
|
||||||
|
if not cur_stable or not cur_dev or not cur_section:
|
||||||
|
raise ValueError(
|
||||||
|
'%s:%i: Entry without section' % (fpath, line_id))
|
||||||
|
last_entry = ChangelogEntry(line.strip(), cur_section,
|
||||||
|
cur_stable, cur_dev)
|
||||||
|
entries.append(last_entry)
|
||||||
|
elif line.lstrip().startswith('-'):
|
||||||
|
if not cur_stable or not cur_dev:
|
||||||
|
raise ValueError(
|
||||||
|
'%s:%i: Sub-entry without section' % (fpath, line_id))
|
||||||
|
if not last_entry:
|
||||||
|
raise ValueError(
|
||||||
|
'%s:%i: Sub-entry without parent' % (fpath, line_id))
|
||||||
|
last_entry.children.append(line.strip('- \n'))
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid line: ' + line)
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
raise RuntimeError('No changelog files with contents found')
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def consolidate_changelog(all_entries):
|
||||||
|
for sections in all_entries.values():
|
||||||
|
for section, entries in sections.items():
|
||||||
|
# sort() is stable, so reverse entries so that older entries for the
|
||||||
|
# same feature are on top
|
||||||
|
entries.reverse()
|
||||||
|
entries.sort(key=lambda entry: entry.sort_key)
|
||||||
|
new_entries = []
|
||||||
|
for feature, group in itertools.groupby(entries,
|
||||||
|
lambda e: e.feature):
|
||||||
|
old_entries = list(group)
|
||||||
|
children = list(itertools.chain(*[entry.children
|
||||||
|
for entry in old_entries]))
|
||||||
|
new_entry = copy.deepcopy(old_entries[0])
|
||||||
|
new_entry.children = children
|
||||||
|
new_entries.append(new_entry)
|
||||||
|
entries[:] = new_entries
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def print_changelog(versions, all_entries, path, replace=True, prefix=''):
|
||||||
|
# all_entries: version -> section -> entry
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
def write(line):
|
||||||
|
if replace:
|
||||||
|
line = replace_text(line, REPLACEMENTS)
|
||||||
|
f.write(prefix + line + '\n')
|
||||||
|
for version in versions:
|
||||||
|
sections = all_entries[version]
|
||||||
|
if not sections:
|
||||||
|
continue
|
||||||
|
version = 'DFHack ' + version
|
||||||
|
write(version)
|
||||||
|
write('=' * len(version))
|
||||||
|
write('')
|
||||||
|
for section in CHANGELOG_SECTIONS:
|
||||||
|
entries = sections[section]
|
||||||
|
if not entries:
|
||||||
|
continue
|
||||||
|
write(section)
|
||||||
|
write('-' * len(section))
|
||||||
|
for entry in entries:
|
||||||
|
if len(entry.children) == 1:
|
||||||
|
write('- ' + entry.feature + ': ' +
|
||||||
|
entry.children[0].strip('- '))
|
||||||
|
continue
|
||||||
|
elif entry.children:
|
||||||
|
write('- ' + entry.feature + ':')
|
||||||
|
write('')
|
||||||
|
for child in entry.children:
|
||||||
|
write(' - ' + child)
|
||||||
|
write('')
|
||||||
|
else:
|
||||||
|
write('- ' + entry.feature)
|
||||||
|
write('')
|
||||||
|
write('')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_changelog(all=False):
|
||||||
|
entries = parse_changelog()
|
||||||
|
|
||||||
|
# scan for unrecognized sections
|
||||||
|
for entry in entries:
|
||||||
|
if entry.section not in CHANGELOG_SECTIONS:
|
||||||
|
raise SphinxWarning('Unknown section: ' + entry.section)
|
||||||
|
|
||||||
|
# ordered versions
|
||||||
|
versions = ['future']
|
||||||
|
# map versions to stable versions
|
||||||
|
stable_version_map = {}
|
||||||
|
# version -> section -> entry
|
||||||
|
stable_entries = collections.defaultdict(lambda:
|
||||||
|
collections.defaultdict(list))
|
||||||
|
dev_entries = collections.defaultdict(lambda:
|
||||||
|
collections.defaultdict(list))
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
# build list of all versions
|
||||||
|
if entry.dev_version not in versions:
|
||||||
|
versions.append(entry.dev_version)
|
||||||
|
stable_version_map.setdefault(entry.dev_version, entry.stable_version)
|
||||||
|
|
||||||
|
if not entry.dev_only:
|
||||||
|
# add non-dev-only entries to both changelogs
|
||||||
|
stable_entries[entry.stable_version][entry.section].append(entry)
|
||||||
|
dev_entries[entry.dev_version][entry.section].append(entry)
|
||||||
|
|
||||||
|
consolidate_changelog(stable_entries)
|
||||||
|
consolidate_changelog(dev_entries)
|
||||||
|
|
||||||
|
print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, '_auto/news.rst'))
|
||||||
|
print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, '_auto/news-dev.rst'))
|
||||||
|
|
||||||
|
if all:
|
||||||
|
for version in versions:
|
||||||
|
if version not in stable_version_map:
|
||||||
|
print('warn: skipping ' + version)
|
||||||
|
continue
|
||||||
|
if stable_version_map[version] == version:
|
||||||
|
version_entries = {version: stable_entries[version]}
|
||||||
|
else:
|
||||||
|
version_entries = {version: dev_entries[version]}
|
||||||
|
print_changelog([version], version_entries,
|
||||||
|
os.path.join(DOCS_ROOT, '_changelogs/%s-github.txt' % version),
|
||||||
|
replace=False)
|
||||||
|
print_changelog([version], version_entries,
|
||||||
|
os.path.join(DOCS_ROOT, '_changelogs/%s-reddit.txt' % version),
|
||||||
|
replace=False,
|
||||||
|
prefix='> ')
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def cli_entrypoint():
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-a', '--all', action='store_true',
|
||||||
|
help='Print changelogs for all versions to docs/_changelogs')
|
||||||
|
parser.add_argument('-c', '--check', action='store_true',
|
||||||
|
help='Check that all entries are printed')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
entries = generate_changelog(all=args.all)
|
||||||
|
|
||||||
|
if args.check:
|
||||||
|
with open(os.path.join(DOCS_ROOT, '_auto/news.rst')) as f:
|
||||||
|
content_stable = f.read()
|
||||||
|
with open(os.path.join(DOCS_ROOT, '_auto/news-dev.rst')) as f:
|
||||||
|
content_dev = f.read()
|
||||||
|
for entry in entries:
|
||||||
|
for description in entry.children:
|
||||||
|
if not entry.dev_only and description not in content_stable:
|
||||||
|
print('stable missing: ' + description)
|
||||||
|
if description not in content_dev:
|
||||||
|
print('dev missing: ' + description)
|
||||||
|
|
||||||
|
|
||||||
|
def sphinx_entrypoint(app, config):
|
||||||
|
try:
|
||||||
|
generate_changelog()
|
||||||
|
except SphinxError:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise ExtensionError(str(e), e)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.connect('config-inited', sphinx_entrypoint)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'version': '0.1',
|
||||||
|
'parallel_read_safe': True,
|
||||||
|
'parallel_write_safe': True,
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
DFHACK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
|
DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs')
|
||||||
|
|
||||||
|
if not os.path.isdir(DOCS_ROOT):
|
||||||
|
raise ReferenceError('docs root not found: %s' % DOCS_ROOT)
|
@ -0,0 +1,6 @@
|
|||||||
|
<p class="logo">
|
||||||
|
<a href="{{ pathto(master_doc) }}">
|
||||||
|
<img class="logo" src="{{ pathto('_static/' ~ theme_logo, 1) }}" alt="Logo"/>
|
||||||
|
<h3 class="logo logo-name">Home</h3>
|
||||||
|
</a>
|
||||||
|
</p>
|
@ -1 +1 @@
|
|||||||
Subproject commit 0792fc0202fb6a04bfdaa262bc36a3b14c8581e5
|
Subproject commit b9028b0bb9ad40d3ad4dc3f10934bb61aa16629b
|
@ -1 +1 @@
|
|||||||
Subproject commit b9fc28836f34f7ce1be64de94afd90184a341c7d
|
Subproject commit bdab71c99a0a7cc268ca517a0cd3f0a5fb41042a
|
@ -1 +1 @@
|
|||||||
Subproject commit 2079b9fb69b8b4db48aa35ec54a96f5cca7cc8ef
|
Subproject commit e8de92efb73d5ef4d0b52df000d60d3350f07a37
|
Loading…
Reference in New Issue