diff --git a/docs/changelog.txt b/docs/changelog.txt index 91c50db23a..f7d146dc45 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -61,6 +61,7 @@ Template for new versions: ## Fixes ## Misc Improvements +- The ``fpause`` console command can now be used to force world generation to pause (as it did prior to version 50). ## Documentation diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index fa5c482bf6..d10978e97b 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -48,6 +48,7 @@ set(MAIN_HEADERS include/DFHackVersion.h include/BitArray.h include/ColorText.h + include/Commands.h include/Console.h include/Core.h include/CoreDefs.h @@ -66,6 +67,7 @@ set(MAIN_HEADERS include/MiscUtils.h include/Module.h include/MemAccess.h + include/MemoryPatcher.h include/ModuleFactory.h include/PluginLua.h include/PluginManager.h @@ -88,6 +90,7 @@ set(MAIN_HEADERS_WINDOWS set(MAIN_SOURCES Core.cpp ColorText.cpp + Commands.cpp CompilerWorkAround.cpp DataDefs.cpp DataIdentity.cpp @@ -100,6 +103,7 @@ set(MAIN_SOURCES LuaApi.cpp DataStatics.cpp DataStaticsCtor.cpp + MemoryPatcher.cpp MiscUtils.cpp Types.cpp PluginManager.cpp diff --git a/library/Commands.cpp b/library/Commands.cpp new file mode 100644 index 0000000000..796a1f2701 --- /dev/null +++ b/library/Commands.cpp @@ -0,0 +1,581 @@ + +#include "Commands.h" + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "RemoteTools.h" + +#include "modules/Gui.h" +#include "modules/World.h" + +#include "df/viewscreen_new_regionst.h" + +#include +#include +#include + + +namespace DFHack +{ + command_result Commands::help(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!parts.size()) + { + if (con.is_console()) + { + con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" + "Some basic editing capabilities are included (single-line text editing).\n" + "The console also has a command history - you can navigate it with Up and Down keys.\n" + "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" + "by clicking on the program icon in the top bar of the window.\n\n"); + } + con.print("Here are some basic commands to get you started:\n" + " help|?|man - This text.\n" + " help - Usage help for the given plugin, command, or script.\n" + " tags - List the tags that the DFHack tools are grouped by.\n" + " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" + " Optional parameters:\n" + " --notags: skip printing tags for each command.\n" + " --dev: include commands intended for developers and modders.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately, without saving.\n" + " keybinding - Modify bindings of commands to in-game key shortcuts.\n" + "\n" + "See more commands by running 'ls'.\n\n" + ); + + con.print("DFHack version %s\n", dfhack_version_desc().c_str()); + } + else + { + DFHack::help_helper(con, parts[0]); + } + return CR_OK; + } + + command_result Commands::load(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + bool all = false; + bool load = (first == "load"); + bool unload = (first == "unload"); + bool reload = (first == "reload"); + auto plug_mgr = core.getPluginManager(); + if (parts.size()) + { + for (const auto& p : parts) + { + if (p.size() && p[0] == '-') + { + if (p.find('a') != std::string::npos) + all = true; + } + } + auto ret = CR_OK; + if (all) + { + if (load && !plug_mgr->loadAll()) + ret = CR_FAILURE; + else if (unload && !plug_mgr->unloadAll()) + ret = CR_FAILURE; + else if (reload && !plug_mgr->reloadAll()) + ret = CR_FAILURE; + } + else + { + for (auto& p : parts) + { + if (p.empty() || p[0] == '-') + continue; + if (load && !plug_mgr->load(p)) + ret = CR_FAILURE; + else if (unload && !plug_mgr->unload(p)) + ret = CR_FAILURE; + else if (reload && !plug_mgr->reload(p)) + ret = CR_FAILURE; + } + } + if (ret != CR_OK) + con.printerr("%s failed\n", first.c_str()); + return ret; + } + else + { + con.printerr("%s: no arguments\n", first.c_str()); + return CR_FAILURE; + } + + } + + command_result Commands::enable(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + CoreSuspender suspend; + bool enable = (first == "enable"); + auto plug_mgr = core.getPluginManager(); + + if (parts.size()) + { + command_result res{CR_FAILURE}; + + for (auto& part_ : parts) + { + // have to copy to modify as passed argument is const + std::string part(part_); + + if (has_backslashes(part)) + { + con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); + replace_backslashes_with_forwardslashes(part); + } + + auto alias = core.GetAliasCommand(part, true); + + Plugin* plug = (*plug_mgr)[alias]; + + if (!plug) + { + std::filesystem::path lua = core.findScript(part + ".lua"); + if (!lua.empty()) + { + res = core.enableLuaScript(con, part, enable); + } + else + { + res = CR_NOT_FOUND; + con.printerr("No such plugin or Lua script: %s\n", part.c_str()); + } + } + else if (!plug->can_set_enabled()) + { + res = CR_NOT_IMPLEMENTED; + con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); + } + else + { + res = plug->set_enabled(con, enable); + + if (res != CR_OK || plug->is_enabled() != enable) + con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); + } + } + + return res; + } + else + { + for (auto& [key, plug] : *plug_mgr) + { + if (!plug) + continue; + if (!plug->can_be_enabled()) continue; + + con.print( + "%21s %-3s%s\n", + (key + ":").c_str(), + plug->is_enabled() ? "on" : "off", + plug->can_set_enabled() ? "" : " (controlled internally)" + ); + } + + Lua::CallLuaModuleFunction(con, "script-manager", "list"); + + return CR_OK; + } + } + + command_result Commands::plug(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + constexpr auto header_format = "%30s %10s %4s %8s\n"; + constexpr auto row_format = "%30s %10s %4zu %8s\n"; + + con.print(header_format, "Name", "State", "Cmds", "Enabled"); + + auto plug_mgr = core.getPluginManager(); + + plug_mgr->refresh(); + for (auto& [key, plug] : *plug_mgr) + { + if (!plug) + continue; + + if (parts.size() && std::ranges::find(parts, key) == parts.end()) + continue; + + color_value color; + switch (plug->getState()) + { + case Plugin::PS_LOADED: + color = COLOR_RESET; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; + } + con.color(color); + con.print(row_format, + plug->getName().c_str(), + Plugin::getStateDescription(plug->getState()), + plug->size(), + (plug->can_be_enabled() + ? (plug->is_enabled() ? "enabled" : "disabled") + : "n/a") + ); + con.color(COLOR_RESET); + } + + return CR_OK; + } + + command_result Commands::type(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + auto plug_mgr = core.getPluginManager(); + + if (!parts.size()) + { + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; + } + con << parts[0]; + bool builtin = is_builtin(con, parts[0]); + std::filesystem::path lua_path = core.findScript(parts[0] + ".lua"); + Plugin* plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin) + { + con << " is a built-in command"; + con << std::endl; + } + else if (core.IsAlias(parts[0])) + { + con << " is an alias: " << core.GetAliasCommand(parts[0]) << std::endl; + } + else if (plug) + { + con << " is a command implemented by the plugin " << plug->getName() << std::endl; + } + else if (!lua_path.empty()) + { + con << " is a Lua script: " << lua_path << std::endl; + } + else + { + con << " is not a recognized command." << std::endl; + plug = plug_mgr->getPluginByName(parts[0]); + if (plug) + con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::keybinding(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) + { + std::string keystr = parts[1]; + if (parts[0] == "set") + core.ClearKeyBindings(keystr); + // for (int i = parts.size()-1; i >= 2; i--) + for (const auto& part : parts | std::views::drop(2) | std::views::reverse) + { + if (!core.AddKeyBinding(keystr, part)) + { + con.printerr("Invalid key spec: %s\n", keystr.c_str()); + return CR_FAILURE; + } + } + } + else if (parts.size() >= 2 && parts[0] == "clear") + { + // for (size_t i = 1; i < parts.size(); i++) + for (const auto& part : parts | std::views::drop(1)) + { + if (!core.ClearKeyBindings(part)) + { + con.printerr("Invalid key spec: %s\n", part.c_str()); + return CR_FAILURE; + } + } + } + else if (parts.size() == 2 && parts[0] == "list") + { + std::vector list = core.ListKeyBindings(parts[1]); + if (list.empty()) + con << "No bindings." << std::endl; + for (const auto& kb : list) + con << " " << kb << std::endl; + } + else + { + con << "Usage:" << std::endl + << " keybinding list " << std::endl + << " keybinding clear [@context]..." << std::endl + << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << std::endl + << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << std::endl + << "Later adds, and earlier items within one command have priority." << std::endl + << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << std::endl + << "Context may be used to limit the scope of the binding, by" << std::endl + << "requiring the current context to have a certain prefix." << std::endl + << "Current UI context is: " << std::endl + << join_strings("\n", Gui::getCurFocus(true)) << std::endl; + } + + return CR_OK; + } + + command_result Commands::alias(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const std::string& name = parts[1]; + std::vector cmd(parts.begin() + 2, parts.end()); + if (!core.AddAlias(name, cmd, parts[0] == "replace")) + { + con.printerr("Could not add alias %s - already exists\n", name.c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + { + if (!core.RemoveAlias(parts[1])) + { + con.printerr("Could not remove alias %s\n", parts[1].c_str()); + return CR_FAILURE; + } + } + else if (parts.size() >= 1 && (parts[0] == "list")) + { + auto aliases = core.ListAliases(); + for (auto p : aliases) + { + con << p.first << ": " << join_strings(" ", p.second) << std::endl; + } + } + else + { + con << "Usage: " << std::endl + << " alias add|replace " << std::endl + << " alias delete|clear " << std::endl + << " alias list" << std::endl; + } + + return CR_OK; + } + + command_result Commands::fpause(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (auto scr = Gui::getViewscreenByType()) + { + if (scr->doing_mods || scr->doing_simple_params || scr->doing_params) + { + con.printerr("Cannot pause now.\n"); + return CR_FAILURE; + } + scr->abort_world_gen_dialogue = true; + } + else + { + World::SetPauseState(true); + } + con.print("The game was forced to pause!\n"); + return CR_OK; + } + + command_result Commands::clear(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (con.can_clear()) + { + con.clear(); + return CR_OK; + } + else + { + con.printerr("No console to clear, or this console does not support clearing.\n"); + return CR_NEEDS_CONSOLE; + } + } + + command_result Commands::kill_lua(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + bool force = std::ranges::any_of(parts, [] (const std::string& part) { return part == "force"; }); + if (!Lua::Interrupt(force)) + { + con.printerr( + "Failed to register hook. This can happen if you have" + " lua profiling or coverage monitoring enabled. Use" + " 'kill-lua force' to force, but this may disable" + " profiling and coverage monitoring.\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() == 1) + { + core.loadScriptFile(con, std::filesystem::weakly_canonical(std::filesystem::path{parts[0]}), false); + return CR_OK; + } + else + { + con << "Usage:" << std::endl + << " script " << std::endl; + return CR_WRONG_USAGE; + } + } + + command_result Commands::show(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!core.getConsole().show()) + { + con.printerr("Could not show console\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::hide(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!core.getConsole().hide()) + { + con.printerr("Could not hide console\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::sc_script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.empty() || parts[0] == "help" || parts[0] == "?") + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; + con << "Valid event names (SC_ prefix is optional):" << std::endl; + for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) + { + std::string name = sc_event_name((state_change_event)i); + if (name != "SC_UNKNOWN") + con << " " << name << std::endl; + } + return CR_OK; + } + else if (parts[0] == "list") + { + std::string event_name = parts.size() >= 2 ? parts[1] : ""; + if (event_name.size() && sc_event_id(event_name) == SC_UNKNOWN) + { + con << "Unrecognized event name: " << parts[1] << std::endl; + return CR_WRONG_USAGE; + } + for (const auto& state_script : core.getStateChangeScripts()) + { + if (!parts[1].size() || (state_script.event == sc_event_id(parts[1]))) + { + con.print("%s (%s): %s%s\n", sc_event_name(state_script.event).c_str(), + state_script.save_specific ? "save-specific" : "global", + state_script.save_specific ? "/raw/" : "/", + state_script.path.c_str()); + } + } + return CR_OK; + } + else if (parts[0] == "add") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) + { + con << "Usage: sc-script add EVENT path-to-script [-save]" << std::endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) + { + con << "Unrecognized event: " << parts[1] << std::endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript script(evt, parts[2], save_specific); + for (const auto& state_script : core.getStateChangeScripts()) + { + if (script == state_script) + { + con << "Script already registered" << std::endl; + return CR_FAILURE; + } + } + core.addStateChangeScript(script); + return CR_OK; + } + else if (parts[0] == "remove") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) + { + con << "Usage: sc-script remove EVENT path-to-script [-save]" << std::endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) + { + con << "Unrecognized event: " << parts[1] << std::endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript tmp(evt, parts[2], save_specific); + if (core.removeStateChangeScript(tmp)) + { + return CR_OK; + } + else + { + con << "Unrecognized script" << std::endl; + return CR_FAILURE; + } + } + else + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; + return CR_WRONG_USAGE; + } + } + + command_result Commands::dump_rpc(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService coreSvc; + coreSvc.dumpMethods(file); + + for (auto& it : *core.getPluginManager()) + { + Plugin* plug = it.second; + if (!plug) + continue; + + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; + + file << "// Plugin: " << plug->getName() << std::endl; + svc->dumpMethods(file); + } + } + else + { + con << "Usage: devel/dump-rpc \"filename\"" << std::endl; + return CR_WRONG_USAGE; + } + return CR_OK; + } +} diff --git a/library/Core.cpp b/library/Core.cpp index bb631414c6..298bb1edfa 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -22,14 +22,16 @@ must not be misrepresented as being the original software. distribution. */ +#include "Core.h" + #include "Internal.h" #include "Error.h" #include "MemAccess.h" -#include "Core.h" #include "DataDefs.h" #include "Debug.h" #include "Console.h" +#include "MemoryPatcher.h" #include "MiscUtils.h" #include "Module.h" #include "VersionInfoFactory.h" @@ -42,6 +44,8 @@ distribution. #include "DFHackVersion.h" #include "md5wrapper.h" +#include "Commands.h" + #include "modules/DFSDL.h" #include "modules/DFSteam.h" #include "modules/EventManager.h" @@ -302,7 +306,7 @@ static void fHKthread(IODATA * iodata) } } -static std::string dfhack_version_desc() +std::string DFHack::dfhack_version_desc() { std::stringstream s; s << Version::dfhack_version() << " "; @@ -352,7 +356,7 @@ static bool init_enable_script(color_ostream &out, lua_State *state, const std:: return true; } -static command_result enableLuaScript(color_ostream &out, const std::string_view name, bool enabled) +command_result Core::enableLuaScript(color_ostream &out, const std::string_view name, bool enabled) { auto init_fn = [name, enabled](color_ostream& out, lua_State* state) -> bool { return init_enable_script(out, state, name, enabled); @@ -363,13 +367,13 @@ static command_result enableLuaScript(color_ostream &out, const std::string_view return ok ? CR_OK : CR_FAILURE; } -command_result Core::runCommand(color_ostream &out, const std::string &command) +command_result Core::runCommand(color_ostream& out, const std::string& command) { if (!command.empty()) { std::vector parts; - Core::cheap_tokenise(command,parts); - if(parts.size() == 0) + Core::cheap_tokenise(command, parts); + if (parts.size() == 0) return CR_NOT_IMPLEMENTED; std::string first = parts[0]; @@ -385,20 +389,23 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } -bool is_builtin(color_ostream &con, const std::string &command) { +bool DFHack::is_builtin(color_ostream& con, const std::string& command) +{ CoreSuspender suspend; auto L = DFHack::Core::getInstance().getLuaState(); Lua::StackUnwinder top(L); if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) { + !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) + { con.printerr("Failed to load helpdb Lua code\n"); return false; } Lua::Push(L, command); - if (!Lua::SafeCall(con, L, 1, 1)) { + if (!Lua::SafeCall(con, L, 1, 1)) + { con.printerr("Failed Lua call to helpdb.is_builtin.\n"); return false; } @@ -605,7 +612,7 @@ static void sc_event_map_init() { } } -static state_change_event sc_event_id (std::string name) { +state_change_event DFHack::sc_event_id (std::string name) { sc_event_map_init(); auto it = state_change_event_map.find(name); if (it != state_change_event_map.end()) @@ -615,7 +622,7 @@ static state_change_event sc_event_id (std::string name) { return SC_UNKNOWN; } -static std::string sc_event_name (state_change_event id) { +std::string DFHack::sc_event_name (state_change_event id) { sc_event_map_init(); for (auto it = state_change_event_map.begin(); it != state_change_event_map.end(); ++it) { @@ -625,7 +632,7 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } -void help_helper(color_ostream &con, const std::string &entry_name) { +void DFHack::help_helper(color_ostream &con, const std::string &entry_name) { ConditionalCoreSuspender suspend{}; if (!suspend) { @@ -743,38 +750,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s command_result res; if (first == "help" || first == "man" || first == "?") { - if(!parts.size()) - { - if (con.is_console()) - { - con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" - "Some basic editing capabilities are included (single-line text editing).\n" - "The console also has a command history - you can navigate it with Up and Down keys.\n" - "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" - "by clicking on the program icon in the top bar of the window.\n\n"); - } - con.print("Here are some basic commands to get you started:\n" - " help|?|man - This text.\n" - " help - Usage help for the given plugin, command, or script.\n" - " tags - List the tags that the DFHack tools are grouped by.\n" - " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" - " Optional parameters:\n" - " --notags: skip printing tags for each command.\n" - " --dev: include commands intended for developers and modders.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately, without saving.\n" - " keybinding - Modify bindings of commands to in-game key shortcuts.\n" - "\n" - "See more commands by running 'ls'.\n\n" - ); - - con.print("DFHack version %s\n", dfhack_version_desc().c_str()); - } - else - { - help_helper(con, parts[0]); - } + return Commands::help(con, *this, first, parts); } else if (first == "tags") { @@ -782,119 +758,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "load" || first == "unload" || first == "reload") { - bool all = false; - bool load = (first == "load"); - bool unload = (first == "unload"); - bool reload = (first == "reload"); - if (parts.size()) - { - for (const auto& p : parts) - { - if (p.size() && p[0] == '-') - { - if (p.find('a') != std::string::npos) - all = true; - } - } - auto ret = CR_OK; - if (all) - { - if (load && !plug_mgr->loadAll()) - ret = CR_FAILURE; - else if (unload && !plug_mgr->unloadAll()) - ret = CR_FAILURE; - else if (reload && !plug_mgr->reloadAll()) - ret = CR_FAILURE; - } - else - { - for (auto& p : parts) - { - if (p.empty() || p[0] == '-') - continue; - if (load && !plug_mgr->load(p)) - ret = CR_FAILURE; - else if (unload && !plug_mgr->unload(p)) - ret = CR_FAILURE; - else if (reload && !plug_mgr->reload(p)) - ret = CR_FAILURE; - } - } - if (ret != CR_OK) - con.printerr("%s failed\n", first.c_str()); - return ret; - } - else { - con.printerr("%s: no arguments\n", first.c_str()); - return CR_FAILURE; - } + return Commands::load(con, *this, first, parts); } else if( first == "enable" || first == "disable" ) { - CoreSuspender suspend; - bool enable = (first == "enable"); - - if(parts.size()) - { - for (auto& part : parts) - { - if (has_backslashes(part)) - { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); - replace_backslashes_with_forwardslashes(part); - } - - part = GetAliasCommand(part, true); - - Plugin * plug = (*plug_mgr)[part]; - - if(!plug) - { - std::filesystem::path lua = findScript(part + ".lua"); - if (!lua.empty()) - { - res = enableLuaScript(con, part, enable); - } - else - { - res = CR_NOT_FOUND; - con.printerr("No such plugin or Lua script: %s\n", part.c_str()); - } - } - else if (!plug->can_set_enabled()) - { - res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); - } - else - { - res = plug->set_enabled(con, enable); - - if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); - } - } - - return res; - } - else - { - for (auto& [key, plug] : *plug_mgr) - { - if (!plug) - continue; - if (!plug->can_be_enabled()) continue; - - con.print( - "%21s %-3s%s\n", - (key+":").c_str(), - plug->is_enabled() ? "on" : "off", - plug->can_set_enabled() ? "" : " (controlled internally)" - ); - } - - Lua::CallLuaModuleFunction(con, "script-manager", "list"); - } + return Commands::enable(con, *this, first, parts); } else if (first == "ls" || first == "dir") { @@ -902,194 +770,27 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "plug") { - constexpr auto header_format = "%30s %10s %4s %8s\n"; - constexpr auto row_format = "%30s %10s %4zu %8s\n"; - con.print(header_format, "Name", "State", "Cmds", "Enabled"); - - plug_mgr->refresh(); - for (auto& [key, plug] : *plug_mgr) - { - if (!plug) - continue; - - if (parts.size() && std::ranges::find(parts, key) == parts.end()) - continue; - - color_value color; - switch (plug->getState()) - { - case Plugin::PS_LOADED: - color = COLOR_RESET; - break; - case Plugin::PS_UNLOADED: - case Plugin::PS_UNLOADING: - color = COLOR_YELLOW; - break; - case Plugin::PS_LOADING: - color = COLOR_LIGHTBLUE; - break; - case Plugin::PS_BROKEN: - color = COLOR_LIGHTRED; - break; - default: - color = COLOR_LIGHTMAGENTA; - break; - } - con.color(color); - con.print(row_format, - plug->getName().c_str(), - Plugin::getStateDescription(plug->getState()), - plug->size(), - (plug->can_be_enabled() - ? (plug->is_enabled() ? "enabled" : "disabled") - : "n/a") - ); - con.color(COLOR_RESET); - } + return Commands::plug(con, *this, first, parts); } else if (first == "type") { - if (!parts.size()) - { - con.printerr("type: no argument\n"); - return CR_WRONG_USAGE; - } - con << parts[0]; - bool builtin = is_builtin(con, parts[0]); - std::filesystem::path lua_path = findScript(parts[0] + ".lua"); - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (builtin) - { - con << " is a built-in command"; - con << std::endl; - } - else if (IsAlias(parts[0])) - { - con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; - } - else if (plug) - { - con << " is a command implemented by the plugin " << plug->getName() << std::endl; - } - else if (!lua_path.empty()) - { - con << " is a Lua script: " << lua_path << std::endl; - } - else - { - con << " is not a recognized command." << std::endl; - plug = plug_mgr->getPluginByName(parts[0]); - if (plug) - con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; - return CR_FAILURE; - } + return Commands::type(con, *this, first, parts); } else if (first == "keybinding") { - if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) - { - std::string keystr = parts[1]; - if (parts[0] == "set") - ClearKeyBindings(keystr); - // for (int i = parts.size()-1; i >= 2; i--) - for (const auto& part : parts | std::views::drop(2) | std::views::reverse) - { - if (!AddKeyBinding(keystr, part)) { - con.printerr("Invalid key spec: %s\n", keystr.c_str()); - break; - } - } - } - else if (parts.size() >= 2 && parts[0] == "clear") - { - // for (size_t i = 1; i < parts.size(); i++) - for (const auto& part : parts | std::views::drop(1)) - { - if (!ClearKeyBindings(part)) { - con.printerr("Invalid key spec: %s\n", part.c_str()); - break; - } - } - } - else if (parts.size() == 2 && parts[0] == "list") - { - std::vector list = ListKeyBindings(parts[1]); - if (list.empty()) - con << "No bindings." << std::endl; - for (const auto& kb : list) - con << " " << kb << std::endl; - } - else - { - con << "Usage:" << std::endl - << " keybinding list " << std::endl - << " keybinding clear [@context]..." << std::endl - << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << std::endl - << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << std::endl - << "Later adds, and earlier items within one command have priority." << std::endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << std::endl - << "Context may be used to limit the scope of the binding, by" << std::endl - << "requiring the current context to have a certain prefix." << std::endl - << "Current UI context is: " << std::endl - << join_strings("\n", Gui::getCurFocus(true)) << std::endl; - } + return Commands::keybinding(con, *this, first, parts); } else if (first == "alias") { - if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) - { - const std::string &name = parts[1]; - std::vector cmd(parts.begin() + 2, parts.end()); - if (!AddAlias(name, cmd, parts[0] == "replace")) - { - con.printerr("Could not add alias %s - already exists\n", name.c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) - { - if (!RemoveAlias(parts[1])) - { - con.printerr("Could not remove alias %s\n", parts[1].c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 1 && (parts[0] == "list")) - { - auto aliases = ListAliases(); - for (auto p : aliases) - { - con << p.first << ": " << join_strings(" ", p.second) << std::endl; - } - } - else - { - con << "Usage: " << std::endl - << " alias add|replace " << std::endl - << " alias delete|clear " << std::endl - << " alias list" << std::endl; - } + return Commands::alias(con, *this, first, parts); } else if (first == "fpause") { - World::SetPauseState(true); -/* TODO: understand how this changes for v50 - if (auto scr = Gui::getViewscreenByType()) - { - scr->worldgen_paused = true; - } -*/ - con.print("The game was forced to pause!\n"); + return Commands::fpause(con, *this, first, parts); } else if (first == "cls" || first == "clear") { - if (con.is_console()) - ((Console&)con).clear(); - else - { - con.printerr("No console to clear.\n"); - return CR_NEEDS_CONSOLE; - } + return Commands::clear(con, *this, first, parts); } else if (first == "die") { @@ -1101,173 +802,27 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "kill-lua") { - bool force = false; - for (const auto& part : parts) - { - if (part == "force") - force = true; - } - if (!Lua::Interrupt(force)) - { - con.printerr( - "Failed to register hook. This can happen if you have" - " lua profiling or coverage monitoring enabled. Use" - " 'kill-lua force' to force, but this may disable" - " profiling and coverage monitoring.\n"); - } + return Commands::kill_lua(con, *this, first, parts); } else if (first == "script") { - if(parts.size() == 1) - { - loadScriptFile(con, std::filesystem::weakly_canonical(std::filesystem::path{parts[0]}), false); - } - else - { - con << "Usage:" << std::endl - << " script " << std::endl; - return CR_WRONG_USAGE; - } + return Commands::script(con, *this, first, parts); } else if (first == "hide") { - if (!getConsole().hide()) - { - con.printerr("Could not hide console\n"); - return CR_FAILURE; - } - return CR_OK; + return Commands::hide(con, *this, first, parts); } else if (first == "show") { - if (!getConsole().show()) - { - con.printerr("Could not show console\n"); - return CR_FAILURE; - } - return CR_OK; + return Commands::show(con, *this, first, parts); } else if (first == "sc-script") { - if (parts.empty() || parts[0] == "help" || parts[0] == "?") - { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; - con << "Valid event names (SC_ prefix is optional):" << std::endl; - for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) - { - std::string name = sc_event_name((state_change_event)i); - if (name != "SC_UNKNOWN") - con << " " << name << std::endl; - } - return CR_OK; - } - else if (parts[0] == "list") - { - if(parts.size() < 2) - parts.push_back(""); - if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) - { - con << "Unrecognized event name: " << parts[1] << std::endl; - return CR_WRONG_USAGE; - } - for (const auto& state_script : state_change_scripts) - { - if (!parts[1].size() || (state_script.event == sc_event_id(parts[1]))) - { - con.print("%s (%s): %s%s\n", sc_event_name(state_script.event).c_str(), - state_script.save_specific ? "save-specific" : "global", - state_script.save_specific ? "/raw/" : "/", - state_script.path.c_str()); - } - } - return CR_OK; - } - else if (parts[0] == "add") - { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script add EVENT path-to-script [-save]" << std::endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << std::endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript script(evt, parts[2], save_specific); - for (const auto& state_script : state_change_scripts) - { - if (script == state_script) - { - con << "Script already registered" << std::endl; - return CR_FAILURE; - } - } - state_change_scripts.push_back(script); - return CR_OK; - } - else if (parts[0] == "remove") - { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script remove EVENT path-to-script [-save]" << std::endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << std::endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript tmp(evt, parts[2], save_specific); - auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); - if (it != state_change_scripts.end()) - { - state_change_scripts.erase(it); - return CR_OK; - } - else - { - con << "Unrecognized script" << std::endl; - return CR_FAILURE; - } - } - else - { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; - return CR_WRONG_USAGE; - } + return Commands::sc_script(con, *this, first, parts); } else if (first == "devel/dump-rpc") { - if (parts.size() == 1) - { - std::ofstream file(parts[0]); - CoreService core; - core.dumpMethods(file); - - for (auto & it : *plug_mgr) - { - Plugin * plug = it.second; - if (!plug) - continue; - - std::unique_ptr svc(plug->rpc_connect(con)); - if (!svc) - continue; - - file << "// Plugin: " << plug->getName() << std::endl; - svc->dumpMethods(file); - } - } - else - { - con << "Usage: devel/dump-rpc \"filename\"" << std::endl; - return CR_WRONG_USAGE; - } + return Commands::dump_rpc(con, *this, first, parts); } else if (RunAlias(con, first, parts, res)) { @@ -1278,7 +833,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s res = plug_mgr->InvokeCommand(con, first, parts); if (res == CR_WRONG_USAGE) { - help_helper(con, first); + DFHack::help_helper(con, first); } else if (res == CR_NOT_IMPLEMENTED) { @@ -2872,127 +2427,6 @@ std::string Core::GetAliasCommand(const std::string &name, bool ignore_params) return join_strings(" ", aliases[name]); } -///////////////// -// ClassNameCheck -///////////////// - -// Since there is no Process.cpp, put ClassNameCheck stuff in Core.cpp - -static std::set known_class_names; -static std::map known_vptrs; - -ClassNameCheck::ClassNameCheck(std::string _name) : name(_name), vptr(0) -{ - known_class_names.insert(name); -} - -ClassNameCheck &ClassNameCheck::operator= (const ClassNameCheck &b) -{ - name = b.name; vptr = b.vptr; return *this; -} - -bool ClassNameCheck::operator() (Process *p, void * ptr) const { - if (vptr == 0 && p->readClassName(ptr) == name) - { - vptr = ptr; - known_vptrs[name] = ptr; - } - return (vptr && vptr == ptr); -} - -void ClassNameCheck::getKnownClassNames(std::vector &names) -{ - for(const auto& kcn : known_class_names) { - names.push_back(kcn); - } -} - -MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) -{ - if (!p) - p = Core::getInstance().p.get(); -} - -MemoryPatcher::~MemoryPatcher() -{ - close(); -} - -bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) -{ - auto *sptr = (uint8_t*)target; - uint8_t *eptr = sptr + count; - - // Find the valid memory ranges - if (ranges.empty()) - p->getMemRanges(ranges); - - // Find the ranges that this area spans - unsigned start = 0; - while (start < ranges.size() && ranges[start].end <= sptr) - start++; - if (start >= ranges.size() || ranges[start].start > sptr) - return false; - - unsigned end = start+1; - while (end < ranges.size() && ranges[end].start < eptr) - { - if (ranges[end].start != ranges[end-1].end) - return false; - end++; - } - if (ranges[end-1].end < eptr) - return false; - - // Verify current permissions - for (unsigned i = start; i < end; i++) - if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) - return false; - - // Apply writable permissions & update - for (unsigned i = start; i < end; i++) - { - auto &perms = ranges[i]; - if ((perms.write || !write) && perms.read) - continue; - - save.push_back(perms); - perms.write = perms.read = true; - if (!p->setPermissions(perms, perms)) - return false; - } - - return true; -} - -bool MemoryPatcher::write(void *target, const void *src, size_t size) -{ - if (!makeWritable(target, size)) - return false; - - memmove(target, src, size); - - p->flushCache(target, size); - return true; -} - -void MemoryPatcher::close() -{ - for (size_t i = 0; i < save.size(); i++) - p->setPermissions(save[i], save[i]); - - save.clear(); - ranges.clear(); -}; - - -bool Process::patchMemory(void *target, const void* src, size_t count) -{ - MemoryPatcher patcher(this); - - return patcher.write(target, src, count); -} - /******************************************************************************* M O D U L E S *******************************************************************************/ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4edf60000a..450d03e95e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -36,6 +36,7 @@ distribution. #include "LuaTools.h" #include "LuaWrapper.h" #include "md5wrapper.h" +#include "MemoryPatcher.h" #include "MiscUtils.h" #include "PluginManager.h" diff --git a/library/MemoryPatcher.cpp b/library/MemoryPatcher.cpp new file mode 100644 index 0000000000..44aa865c42 --- /dev/null +++ b/library/MemoryPatcher.cpp @@ -0,0 +1,83 @@ +#include "Core.h" +#include "MemoryPatcher.h" + +namespace DFHack +{ + MemoryPatcher::MemoryPatcher(Process* p_) : p(p_) + { + if (!p) + p = Core::getInstance().p.get(); + } + + MemoryPatcher::~MemoryPatcher() + { + close(); + } + + bool MemoryPatcher::verifyAccess(void* target, size_t count, bool write) + { + auto* sptr = (uint8_t*)target; + uint8_t* eptr = sptr + count; + + // Find the valid memory ranges + if (ranges.empty()) + p->getMemRanges(ranges); + + // Find the ranges that this area spans + unsigned start = 0; + while (start < ranges.size() && ranges[start].end <= sptr) + start++; + if (start >= ranges.size() || ranges[start].start > sptr) + return false; + + unsigned end = start + 1; + while (end < ranges.size() && ranges[end].start < eptr) + { + if (ranges[end].start != ranges[end - 1].end) + return false; + end++; + } + if (ranges[end - 1].end < eptr) + return false; + + // Verify current permissions + for (unsigned i = start; i < end; i++) + if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) + return false; + + // Apply writable permissions & update + for (unsigned i = start; i < end; i++) + { + auto& perms = ranges[i]; + if ((perms.write || !write) && perms.read) + continue; + + save.push_back(perms); + perms.write = perms.read = true; + if (!p->setPermissions(perms, perms)) + return false; + } + + return true; + } + + bool MemoryPatcher::write(void* target, const void* src, size_t size) + { + if (!makeWritable(target, size)) + return false; + + memmove(target, src, size); + + p->flushCache(target, size); + return true; + } + + void MemoryPatcher::close() + { + for (size_t i = 0; i < save.size(); i++) + p->setPermissions(save[i], save[i]); + + save.clear(); + ranges.clear(); + }; +} diff --git a/library/Process.cpp b/library/Process.cpp index 101eecbc71..beec599ef8 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -57,6 +57,7 @@ distribution. #include "Internal.h" #include "MemAccess.h" #include "Memory.h" +#include "MemoryPatcher.h" #include "MiscUtils.h" #include "VersionInfo.h" #include "VersionInfoFactory.h" @@ -796,3 +797,10 @@ int Process::memProtect(void *ptr, const int length, const int prot) return !VirtualProtect(ptr, length, prot_native, &old_prot); #endif /* WIN32 */ } + +bool Process::patchMemory(void* target, const void* src, size_t count) +{ + MemoryPatcher patcher(this); + + return patcher.write(target, src, count); +} diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 3c2a4a50fc..212d6e7dca 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -32,6 +32,7 @@ distribution. #include "Core.h" #include "DataFuncs.h" #include "MemAccess.h" +#include "MemoryPatcher.h" #include "VersionInfo.h" #include "VTableInterpose.h" diff --git a/library/include/ColorText.h b/library/include/ColorText.h index f5768c0ffa..d8cd23c480 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -122,6 +122,9 @@ namespace DFHack virtual bool is_console() { return false; } virtual color_ostream *proxy_target() { return NULL; } + virtual bool can_clear() const { return false; } + virtual void clear() {} + static bool log_errors_to_stderr; }; diff --git a/library/include/Commands.h b/library/include/Commands.h new file mode 100644 index 0000000000..163d2e164f --- /dev/null +++ b/library/include/Commands.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ColorText.h" +#include "CoreDefs.h" +#include "Core.h" + +#include +#include + +namespace DFHack +{ + namespace Commands + { + command_result help(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result load(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result enable(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result plug(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result type(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result keybinding(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result alias(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result fpause(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result clear(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result kill_lua(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result show(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result hide(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result sc_script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result dump_rpc(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + } +} diff --git a/library/include/Console.h b/library/include/Console.h index 4397c75954..cbf7d58511 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -141,8 +141,9 @@ namespace DFHack /// shutdown the console. NOT thread-safe bool shutdown( void ); + bool can_clear() const { return true; } /// Clear the console, along with its scrollback - void clear(); + void clear() override; /// Position cursor at x,y. 1,1 = top left corner void gotoxy(int x, int y); /// Enable or disable the caret/cursor diff --git a/library/include/Core.h b/library/include/Core.h index 3ea8f68ef1..5b85d97252 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -31,6 +31,7 @@ distribution. #include "modules/Graphic.h" +#include #include #include #include @@ -170,7 +171,8 @@ namespace DFHack std::string getHotkeyCmd( bool &keep_going ); command_result runCommand(color_ostream &out, const std::string &command, std::vector ¶meters, bool no_autocomplete = false); - command_result runCommand(color_ostream &out, const std::string &command); + command_result runCommand(color_ostream& out, const std::string& command); + bool loadScriptFile(color_ostream &out, std::filesystem::path fname, bool silent = false); bool addScriptPath(std::filesystem::path path, bool search_before = false); @@ -212,7 +214,7 @@ namespace DFHack static void print(const char *format, ...) Wformat(printf,1,2); static void printerr(const char *format, ...) Wformat(printf,1,2); - PluginManager *getPluginManager() { return plug_mgr; } + PluginManager* getPluginManager() const { return plug_mgr; } static void cheap_tokenise(std::string const& input, std::vector &output); @@ -224,6 +226,29 @@ namespace DFHack return State; } + static command_result enableLuaScript(color_ostream& out, const std::string_view name, bool enabled); + + const std::vector getStateChangeScripts() const + { + return state_change_scripts; + } + + void addStateChangeScript(const StateChangeScript& script) + { + state_change_scripts.push_back(script); + } + + bool removeStateChangeScript(const StateChangeScript& script) + { + auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), script); + if (it != state_change_scripts.end()) + { + state_change_scripts.erase(it); + return true; + } + return false; + } + private: DFHack::Console con; @@ -247,8 +272,8 @@ namespace DFHack void onStateChange(color_ostream &out, state_change_event event); void handleLoadAndUnloadScripts(color_ostream &out, state_change_event event); - Core(Core const&); // Don't Implement - void operator=(Core const&); // Don't implement + Core(Core const&) = delete; + void operator=(Core const&) = delete; // report error to user while failing void fatal (std::string output, const char * title = nullptr); @@ -502,4 +527,12 @@ namespace DFHack operator bool() const { return owns_lock(); } }; + // unclassified functions related to core + + void help_helper(color_ostream& con, const std::string& entry_name); + std::string dfhack_version_desc(); + bool is_builtin(color_ostream& con, const std::string& command); + std::string sc_event_name(state_change_event id); + state_change_event sc_event_id(std::string name); + } diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 5ef352a99e..19d65468ed 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -50,9 +50,9 @@ namespace DFHack */ struct DFHACK_EXPORT t_memrange { - void * base; - void * start; - void * end; + void* base; + void* start; + void* end; // memory range name (if any) char name[1024]; // permission to read @@ -63,7 +63,7 @@ namespace DFHack bool execute : 1; // is a shared region bool shared : 1; - inline bool isInRange( void * address) + inline bool isInRange(void* address) { if (address >= start && address < end) return true; return false; @@ -77,237 +77,239 @@ namespace DFHack */ class DFHACK_EXPORT Process { - public: - /// this is the single most important destructor ever. ~px - Process(const VersionInfoFactory& known_versions); - ~Process(); - /// read a 8-byte integer - uint64_t readQuad(const void * address) - { - return *(uint64_t *)address; - } - /// read a 8-byte integer - void readQuad(const void * address, uint64_t & value) - { - value = *(uint64_t *)address; - }; - /// write a 8-byte integer - void writeQuad(const void * address, const uint64_t value) - { - (*(uint64_t *)address) = value; - }; - - /// read a 4-byte integer - uint32_t readDWord(const void * address) - { - return *(uint32_t *)address; - } - /// read a 4-byte integer - void readDWord(const void * address, uint32_t & value) - { - value = *(uint32_t *)address; - }; - /// write a 4-byte integer - void writeDWord(const void * address, const uint32_t value) - { - (*(uint32_t *)address) = value; - }; - - /// read a pointer - char * readPtr(const void * address) - { - return *(char **)address; - } - /// read a pointer - void readPtr(const void * address, char * & value) - { - value = *(char **)address; - }; - - /// read a float - float readFloat(const void * address) - { - return *(float*)address; - } - /// write a float - void readFloat(const void * address, float & value) - { - value = *(float*)address; - }; - - /// read a 2-byte integer - uint16_t readWord(const void * address) - { - return *(uint16_t *)address; - } - /// read a 2-byte integer - void readWord(const void * address, uint16_t & value) - { - value = *(uint16_t *)address; - }; - /// write a 2-byte integer - void writeWord(const void * address, const uint16_t value) - { - (*(uint16_t *)address) = value; - }; - - /// read a byte - uint8_t readByte(const void * address) - { - return *(uint8_t *)address; - } - /// read a byte - void readByte(const void * address, uint8_t & value) - { - value = *(uint8_t *)address; - }; - /// write a byte - void writeByte(const void * address, const uint8_t value) - { - (*(uint8_t *)address) = value; - }; - - /// read an arbitrary amount of bytes - void read(void * address, uint32_t length, uint8_t* buffer) - { - memcpy(buffer, (void *) address, length); - }; - /// write an arbitrary amount of bytes - void write(void * address, uint32_t length, uint8_t* buffer) - { - memcpy((void *) address, buffer, length); - }; - - /// read an STL string - const std::string readSTLString (void * offset) - { - std::string * str = (std::string *) offset; - return *str; - }; - /// read an STL string - size_t readSTLString (void * offset, char * buffer, size_t bufcapacity) - { - if(!bufcapacity || bufcapacity == 1) - return 0; - std::string * str = (std::string *) offset; - size_t copied = str->copy(buffer,bufcapacity-1); - buffer[copied] = 0; - return copied; - }; - /** - * write an STL string - * @return length written - */ - size_t writeSTLString(const void * address, const std::string writeString) - { - std::string * str = (std::string *) address; - str->assign(writeString); - return writeString.size(); - }; - /** - * attempt to copy a string from source address to target address. may truncate or leak, depending on platform - * @return length copied - */ - size_t copySTLString(const void * address, const uintptr_t target) - { - std::string * strsrc = (std::string *) address; - std::string * str = (std::string *) target; - str->assign(*strsrc); - return str->size(); - } - - /// get class name of an object with rtti/type info - std::string doReadClassName(void * vptr); - - std::string readClassName(void * vptr) - { - std::map::iterator it = classNameCache.find(vptr); - if (it != classNameCache.end()) - return it->second; - return classNameCache[vptr] = doReadClassName(vptr); - } - - /// read a null-terminated C string - const std::string readCString (void * offset) - { - return std::string((char *) offset); - }; - - /// @return true if the process is suspended - bool isSuspended() - { - return true; - }; - /// @return true if the process is identified -- has a symbol table extension - bool isIdentified() - { - return identified; - }; - - /// get virtual memory ranges of the process (what is mapped where) - static void getMemRanges(std::vector & ranges); - - /// get the symbol table extension of this process - std::shared_ptr getDescriptor() - { - return my_descriptor; - }; - - void ValidateDescriptionOS() { - if (my_descriptor) - my_descriptor->ValidateOS(); - }; - - uintptr_t getBase(); - /// get the DF Process ID - int getPID(); - /// get the DF Process FilePath - std::filesystem::path getPath(); - /// Adjust between in-memory and in-file image offset - int adjustOffset(int offset, bool to_file = false); - - /// millisecond tick count, exactly as DF uses - uint32_t getTickCount(); - - /// modify permisions of memory range - bool setPermissions(const t_memrange & range,const t_memrange &trgrange); - - /// write a possibly read-only memory area - bool patchMemory(void *target, const void* src, size_t count); - - /// flush cache - bool flushCache(const void* target, size_t count); - - /// allocate new memory pages for code or stuff - /// returns -1 on error (0 is a valid address) - void* memAlloc(const int length); - - /// free memory pages from memAlloc - /// should have length = alloced length for portability - /// returns 0 on success - int memDealloc(void *ptr, const int length); - - /// change memory page permissions - /// prot is a bitwise OR of the MemProt enum - /// returns 0 on success - int memProtect(void *ptr, const int length, const int prot); - - enum MemProt { - READ = 1, - WRITE = 2, - EXEC = 4 - }; - - uint32_t getPE() { return my_pe; } - std::string getMD5() { return my_md5; } + public: + /// this is the single most important destructor ever. ~px + Process(const VersionInfoFactory& known_versions); + ~Process(); + /// read a 8-byte integer + uint64_t readQuad(const void* address) + { + return *(uint64_t*)address; + } + /// read a 8-byte integer + void readQuad(const void* address, uint64_t& value) + { + value = *(uint64_t*)address; + }; + /// write a 8-byte integer + void writeQuad(const void* address, const uint64_t value) + { + (*(uint64_t*)address) = value; + }; + + /// read a 4-byte integer + uint32_t readDWord(const void* address) + { + return *(uint32_t*)address; + } + /// read a 4-byte integer + void readDWord(const void* address, uint32_t& value) + { + value = *(uint32_t*)address; + }; + /// write a 4-byte integer + void writeDWord(const void* address, const uint32_t value) + { + (*(uint32_t*)address) = value; + }; + + /// read a pointer + char* readPtr(const void* address) + { + return *(char**)address; + } + /// read a pointer + void readPtr(const void* address, char*& value) + { + value = *(char**)address; + }; + + /// read a float + float readFloat(const void* address) + { + return *(float*)address; + } + /// write a float + void readFloat(const void* address, float& value) + { + value = *(float*)address; + }; + + /// read a 2-byte integer + uint16_t readWord(const void* address) + { + return *(uint16_t*)address; + } + /// read a 2-byte integer + void readWord(const void* address, uint16_t& value) + { + value = *(uint16_t*)address; + }; + /// write a 2-byte integer + void writeWord(const void* address, const uint16_t value) + { + (*(uint16_t*)address) = value; + }; + + /// read a byte + uint8_t readByte(const void* address) + { + return *(uint8_t*)address; + } + /// read a byte + void readByte(const void* address, uint8_t& value) + { + value = *(uint8_t*)address; + }; + /// write a byte + void writeByte(const void* address, const uint8_t value) + { + (*(uint8_t*)address) = value; + }; + + /// read an arbitrary amount of bytes + void read(void* address, uint32_t length, uint8_t* buffer) + { + memcpy(buffer, (void*)address, length); + }; + /// write an arbitrary amount of bytes + void write(void* address, uint32_t length, uint8_t* buffer) + { + memcpy((void*)address, buffer, length); + }; + + /// read an STL string + const std::string readSTLString(void* offset) + { + std::string* str = (std::string*)offset; + return *str; + }; + /// read an STL string + size_t readSTLString(void* offset, char* buffer, size_t bufcapacity) + { + if (!bufcapacity || bufcapacity == 1) + return 0; + std::string* str = (std::string*)offset; + size_t copied = str->copy(buffer, bufcapacity - 1); + buffer[copied] = 0; + return copied; + }; + /** + * write an STL string + * @return length written + */ + size_t writeSTLString(const void* address, const std::string writeString) + { + std::string* str = (std::string*)address; + str->assign(writeString); + return writeString.size(); + }; + /** + * attempt to copy a string from source address to target address. may truncate or leak, depending on platform + * @return length copied + */ + size_t copySTLString(const void* address, const uintptr_t target) + { + std::string* strsrc = (std::string*)address; + std::string* str = (std::string*)target; + str->assign(*strsrc); + return str->size(); + } + + /// get class name of an object with rtti/type info + std::string doReadClassName(void* vptr); + + std::string readClassName(void* vptr) + { + std::map::iterator it = classNameCache.find(vptr); + if (it != classNameCache.end()) + return it->second; + return classNameCache[vptr] = doReadClassName(vptr); + } + + /// read a null-terminated C string + const std::string readCString(void* offset) + { + return std::string((char*)offset); + }; + + /// @return true if the process is suspended + bool isSuspended() + { + return true; + }; + /// @return true if the process is identified -- has a symbol table extension + bool isIdentified() + { + return identified; + }; + + /// get virtual memory ranges of the process (what is mapped where) + static void getMemRanges(std::vector& ranges); + + /// get the symbol table extension of this process + std::shared_ptr getDescriptor() + { + return my_descriptor; + }; + + void ValidateDescriptionOS() + { + if (my_descriptor) + my_descriptor->ValidateOS(); + }; + + uintptr_t getBase(); + /// get the DF Process ID + int getPID(); + /// get the DF Process FilePath + std::filesystem::path getPath(); + /// Adjust between in-memory and in-file image offset + int adjustOffset(int offset, bool to_file = false); + + /// millisecond tick count, exactly as DF uses + uint32_t getTickCount(); + + /// modify permisions of memory range + bool setPermissions(const t_memrange& range, const t_memrange& trgrange); + + /// write a possibly read-only memory area + bool patchMemory(void* target, const void* src, size_t count); + + /// flush cache + bool flushCache(const void* target, size_t count); + + /// allocate new memory pages for code or stuff + /// returns -1 on error (0 is a valid address) + void* memAlloc(const int length); + + /// free memory pages from memAlloc + /// should have length = alloced length for portability + /// returns 0 on success + int memDealloc(void* ptr, const int length); + + /// change memory page permissions + /// prot is a bitwise OR of the MemProt enum + /// returns 0 on success + int memProtect(void* ptr, const int length, const int prot); + + enum MemProt + { + READ = 1, + WRITE = 2, + EXEC = 4 + }; + + uint32_t getPE() { return my_pe; } + std::string getMD5() { return my_md5; } private: std::shared_ptr my_descriptor; - PlatformSpecific *d; + PlatformSpecific* d; bool identified; uint32_t my_pid; uint32_t base; - std::map classNameCache; + std::map classNameCache; uint32_t my_pe; std::string my_md5; }; @@ -315,36 +317,21 @@ namespace DFHack class DFHACK_EXPORT ClassNameCheck { std::string name; - mutable void * vptr; + mutable void* vptr; public: ClassNameCheck() : vptr(0) {} ClassNameCheck(std::string _name); - ClassNameCheck &operator= (const ClassNameCheck &b); + ClassNameCheck& operator= (const ClassNameCheck& b); // Is the class name of the given virtual table pointer the same as the // name for thei ClassNameCheck object? - bool operator() (Process *p, void * ptr) const; + bool operator() (Process* p, void* ptr) const; // Get list of names given to ClassNameCheck constructors. - static void getKnownClassNames(std::vector &names); + static void getKnownClassNames(std::vector& names); }; - class DFHACK_EXPORT MemoryPatcher - { - Process *p; - std::vector ranges, save; - public: - MemoryPatcher(Process *p = NULL); - ~MemoryPatcher(); - - bool verifyAccess(void *target, size_t size, bool write = false); - bool makeWritable(void *target, size_t size) { - return verifyAccess(target, size, true); - } - bool write(void *target, const void *src, size_t size); - - void close(); - }; } + #endif diff --git a/library/include/MemoryPatcher.h b/library/include/MemoryPatcher.h new file mode 100644 index 0000000000..8577a762ef --- /dev/null +++ b/library/include/MemoryPatcher.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include "MemAccess.h" + +namespace DFHack { + + class Process; + + class MemoryPatcher { + public: + explicit MemoryPatcher(Process *p_ = nullptr); + ~MemoryPatcher(); + + // Ensure the target memory region is accessible and optionally writable. + // If `write` is true this will attempt to make the pages writable. + bool verifyAccess(void *target, size_t count, bool write); + + // Write `size` bytes from `src` to `target`. Returns true on success. + bool write(void *target, const void *src, size_t size); + + // Restore any modified permissions and clear internal state. + void close(); + + bool makeWritable(void* target, size_t size) + { + return verifyAccess(target, size, true); + } + private: + Process* p; + std::vector ranges, save; + }; + +} diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 8a5bee86f7..bade25b521 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -211,6 +211,7 @@ namespace DFHack functions.push_back(new VoidServerMethod(this, name, flags, fptr)); } + public: void dumpMethods(std::ostream & out) const; };