diff --git a/docs/guides/lua.md b/docs/guides/lua.md index c602682f..3877d3f3 100644 --- a/docs/guides/lua.md +++ b/docs/guides/lua.md @@ -343,6 +343,91 @@ Sleep for `length` milliseconds. Pause the game if playing, resume otherwise. +### Ramsearch functions + +Ramsearch functions configure a ram search similar to the one of the Ramsearch window. The parameters correspond to what can be found there. + +| value_type | Number | +| -------------- | ------ | +| unsigned char | 0 | +| char | 1 | +| unsigned short | 2 | +| short | 3 | +| unsigned int | 4 | +| int | 5 | +| unsigned int64 | 6 | +| int64 | 7 | +| float | 8 | +| double | 9 | +| byte array | 10 | +| string | 11 | + +| alignment | Number | +| -------------- | ------ | +| default | 0 | +| 1 | 1 | +| 2 | 2 | +| 4 | 3 | + +| compare_type | Number | +| ---------------------- | ------ | +| Unknown/Previous Value | 0 | +| Specific Value | 1 | + +In the `Specific Value` case, the value must be passed in the `compare_value` parameter. + +| compare_operator | String | +| ------------------------ | ------ | +| Equal To | == | +| Not Equal To | != | +| Less Than | < | +| Greater Than | > | +| Less Than Or Equal To | <= | +| Greater Than Or Equal To | >= | +| Different By | ! | + +In the `Different By` case, the value must be passed in the `different value` parameter. + +MemFlags uses three bitwise options with values 1, 2, and 4: Exclude special regions, Exclude read-only regions, Exclude executable regions. +They combine by adding their values using a bitwise OR. +No options = 0, and all three selected = 7 (default). + +#### ramsearch.newsearch + + Number ramsearch.newsearch([Number value_type = 0], [Number alignment = 0], [Number compare_type = 0], [Number compare_value = 0], [String compare_operator = "=="], [Number different_value = 0], [Number memflags = 7], [String begin_address = "0000000000000000"], [String end_address = "00007fffffffffff"]) + +Start a new ramsearch. Returns number of results. + +#### ramsearch.search + + Number ramsearch.search([Number compare_type], [Number compare_value], [String compare_operator], [Number different_value]) + +Perform a new step on previously started search. Parameters that are not passed will default to their previous value set up in former calls to newsearch() or search() or using the `ramsearch:set_*` functions. Returns number of results. + +#### ramsearch.get_current_value + + Number ramsearch.get_current_value(Number index) + +Get current value of address at a given index of the search results. + +#### ramsearch.get_address + + String ramsearch.get_address(Number index) + +Get address at a given index of the search results + +#### ramsearch.set_compare_operator + + none ramsearch.set_compare_operator(String operator, [Number different_value]) + +Set comparison operator for next search. It is only necessary to pass the different value in the `Different By` case. + +#### ramsearch.set_comparison_type + + none ramsearch.set_comparison_type(Number type, [Number compare_value]) + +Set comparison type for next search. It is only necessary to pass the compare_value in the `Specific Value` case. + ### Callbacks #### callback.onStartup @@ -393,3 +478,51 @@ Display a simple text on top of the game: end callback.onPaint(hello_world) + +#### Using Ramsearch to automate finding x;y position in memory + + local memx = "" + local x_position_frame_10 = 580 + + function onFrame() + local f = movie.currentFrame() + if f == 10 then + -- look for doubles of value 580 on frame 10 + local nb_results= ramsearch.newsearch(9, 0, 1, x_position_frame_10, "==") + print(string.format("number of results on newsearch: %d", nb_results)) + end + if f == 11 then + -- filter out addresses that changed on frame 11 (we didn't move) + local nb_results = ramsearch.search() + print(string.format("number of results on frame 11: %d", nb_results)) + end + if f == 12 then + -- filter out addresses that didn't change on frame 12 (after we moved) + local nb_results = ramsearch.search(0, 0, "!=") + print(string.format("number of results on frame 12: %d", nb_results)) + if nb_results == 1 then + -- We found our value, let's store it to display it in the + -- onPaint() callback + memx = ramsearch.get_address(0) + else + print("Error: found too many values") + for i = 0,nb_results-1,1 + do + local v = ramsearch.get_current_value(i) + local addr = ramsearch.get_address(i) + print(string.format("current value: %f @ %s", v, addr)) + end + end + end + end + + function onPaint() + if memx ~= "" then + local x_num = tonumber(memx, 16) + local x = memory.readd(x_num) + local y_num = x_num + 56 + local y = memory.readd(y_num) + gui.ellipse(x, y, 10, 10) + gui.text(100, 100, string.format("%f ; %f", x, y)) + end + end diff --git a/src/program/GameLoop.cpp b/src/program/GameLoop.cpp index 8fb3a35f..45bd8169 100644 --- a/src/program/GameLoop.cpp +++ b/src/program/GameLoop.cpp @@ -809,10 +809,8 @@ void GameLoop::processInputs(AllInputs &ai) ai.clear(); /* Allow lua to modify inputs past the end of the movie */ - Lua::Input::registerInputs(&ai, &modified); + Lua::Input::registerInputs(&ai, &modified_by_lua); Lua::Callbacks::call(Lua::NamedLuaFunction::CallbackInput); - if (modified) - movie.inputs->wasModified(); } /* Update controller inputs if controller window is shown */ diff --git a/src/program/Makefile.am b/src/program/Makefile.am index e5d387bd..d233283b 100644 --- a/src/program/Makefile.am +++ b/src/program/Makefile.am @@ -102,6 +102,7 @@ libTAS_SOURCES = \ lua/Movie.cpp \ lua/Print.cpp \ lua/Runtime.cpp \ + lua/Ramsearch.cpp \ movie/InputSerialization.cpp \ movie/MovieActionEditFrames.cpp \ movie/MovieActionInsertFrames.cpp \ diff --git a/src/program/lua/Main.cpp b/src/program/lua/Main.cpp index 8de6dba3..55d04920 100644 --- a/src/program/lua/Main.cpp +++ b/src/program/lua/Main.cpp @@ -26,6 +26,7 @@ #include "Print.h" #include "Runtime.h" #include "Callbacks.h" +#include "Ramsearch.h" #include extern "C" { @@ -56,6 +57,7 @@ lua_State* Lua::Main::new_state() Lua::Callbacks::registerFunctions(lua_state); Lua::Print::init(lua_state); Lua::Runtime::registerFunctions(lua_state, context); + Lua::Ramsearch::registerFunctions(lua_state, context); return lua_state; } diff --git a/src/program/lua/Ramsearch.cpp b/src/program/lua/Ramsearch.cpp new file mode 100644 index 00000000..c8729171 --- /dev/null +++ b/src/program/lua/Ramsearch.cpp @@ -0,0 +1,259 @@ +#include "Ramsearch.h" + +#include "ramsearch/MemAccess.h" +#include "ramsearch/MemScanner.h" +#include "ramsearch/MemScannerThread.h" +#include "ui/RamSearchModel.h" + + +#include +extern "C" { +#include +#include +} + +static Context* context; +static RamSearchModel *ramSearchModel = nullptr; + +/* search variables definitions with default values */ +static int memflags = 0 | MemSection::MemNoSpecial | MemSection::MemNoRO | MemSection::MemNoExec; // 7 +static int value_type = 0; +static int alignment = 0; +static int compare_type_int = 0; // CompareType::Previous; +static CompareType compare_type; +static CompareOperator compare_operator = CompareOperator::Equal; +static const char* compare_value_str = ""; +static MemValueType compare_value; +static const char* different_value_str = ""; +static MemValueType different_value; +static const char* begin_address_default = "0000000000000000"; +static uintptr_t begin_address; +static const char* end_address_default = "00007fffffffffff"; +static uintptr_t end_address; + + +/* List of functions to register */ +static const luaL_Reg ramsearch_functions[] = +{ + { "newsearch", Lua::Ramsearch::newsearch}, + { "search", Lua::Ramsearch::search}, + { "get_current_value", Lua::Ramsearch::get_current_value}, + { "set_comparison_operator", Lua::Ramsearch::set_compare_operator}, + { "set_comparison_type", Lua::Ramsearch::set_comparison_type}, + { "get_address", Lua::Ramsearch::get_address}, + { NULL, NULL } +}; + +void Lua::Ramsearch::registerFunctions(lua_State *L, Context* c) +{ + context = c; + luaL_newlib(L, ramsearch_functions); + lua_setglobal(L, "ramsearch"); +} + +int Lua::Ramsearch::newsearch(lua_State *L) +{ + ramSearchModel = new RamSearchModel(context); + + value_type = luaL_optinteger(L, 1, value_type); + alignment = luaL_optinteger(L, 2, alignment); + compare_type_int = luaL_optinteger(L, 3, compare_type_int); + compare_value_str = luaL_optstring(L, 4, compare_value_str); + _set_compare_to(compare_type_int, compare_value_str); + const char* compare_operator_str = luaL_optstring(L, 5, nullptr); + if (compare_operator_str != nullptr) { + different_value_str = luaL_optstring(L, 6, different_value_str); + _set_compareop(compare_operator_str, different_value_str); + } + memflags = luaL_optinteger(L, 7, memflags); + begin_address = std::strtoul(luaL_optstring(L, 8, begin_address_default), nullptr, 16); + end_address = std::strtoul(luaL_optstring(L, 9, end_address_default), nullptr, 16); + + int err = ramSearchModel->memscanner.first_scan(memflags, value_type, alignment, compare_type, compare_operator, compare_value, different_value, begin_address, end_address); + + switch (err) { + case MemScannerThread::ESTOPPED: + std::cerr << "The search was interupted by the user"; + break; + case MemScannerThread::EOUTPUT: + std::cerr << "The search results could not be written to disk"; + break; + case MemScannerThread::EINPUT: + std::cerr << "The previous search results could not be read correctly"; + break; + case MemScannerThread::EPROCESS: + std::cerr << "There was an error in the search process"; + break; + } + + // return number of results + lua_pushinteger(L, static_cast(ramSearchModel->scanCount())); + return 1; +} + +int Lua::Ramsearch::search(lua_State *L) +{ + if (ramSearchModel == nullptr) { + std::cerr << "Lua ramsearch: Error: attempting to perforum a search without first creating a newsearch"; + lua_pushinteger(L, static_cast(0)); + return 1; + } + + if (ramSearchModel->scanCount() == 0) { + lua_pushinteger(L, static_cast(0)); + return 1; + } + + compare_type_int = luaL_optinteger(L, 1, compare_type_int); + compare_value_str = luaL_optstring(L, 2, compare_value_str); + _set_compare_to(compare_type_int, compare_value_str); + const char* compare_operator_str = luaL_optstring(L, 3, nullptr); + if (compare_operator_str != nullptr) { + const char* value = luaL_optstring(L, 4, different_value_str); + _set_compareop(compare_operator_str, value); + } + + int err = ramSearchModel->memscanner.scan(false, compare_type, compare_operator, compare_value, different_value); + + switch (err) { + case MemScannerThread::ESTOPPED: + std::cerr << "The search was interupted by the user"; + break; + case MemScannerThread::EOUTPUT: + std::cerr << "The search results could not be written to disk"; + break; + case MemScannerThread::EINPUT: + std::cerr << "The previous search results could not be read correctly"; + break; + case MemScannerThread::EPROCESS: + std::cerr << "There was an error in the search process"; + break; + } + + // return number of results + lua_pushinteger(L, static_cast(ramSearchModel->scanCount())); + return 1; +} + +int Lua::Ramsearch::get_current_value(lua_State *L) +{ + if (ramSearchModel == nullptr) { + std::cerr << "Lua ramsearch: Error: attempting to access search results first creating a newsearch"; + lua_pushinteger(L, static_cast(0)); + return 1; + } + + int index = static_cast(lua_tointeger(L, 1)); + MemValueType current_value = ramSearchModel->memscanner.get_current_value(index); + + switch (value_type) { + case RamChar: + lua_pushinteger(L, static_cast(current_value.v_int8_t)); + break; + case RamUnsignedChar: + lua_pushinteger(L, static_cast(current_value.v_uint8_t)); + break; + case RamShort: + lua_pushinteger(L, static_cast(current_value.v_int16_t)); + break; + case RamUnsignedShort: + lua_pushinteger(L, static_cast(current_value.v_uint16_t)); + break; + case RamInt: + lua_pushinteger(L, static_cast(current_value.v_int32_t)); + break; + case RamUnsignedInt: + lua_pushinteger(L, static_cast(current_value.v_uint32_t)); + break; + case RamLong: + lua_pushinteger(L, static_cast(current_value.v_int64_t)); + break; + case RamUnsignedLong: + lua_pushinteger(L, static_cast(current_value.v_uint64_t)); + break; + case RamFloat: + lua_pushnumber(L, static_cast(current_value.v_float)); + break; + case RamDouble: + lua_pushnumber(L, static_cast(current_value.v_double)); + break; + case RamCString: + lua_pushstring(L, current_value.v_cstr); + break; + case RamArray: { + lua_newtable(L); + int size = current_value.v_array[RAM_ARRAY_MAX_SIZE]; + for (int i = 0; i < size; i++) { + lua_pushinteger(L, current_value.v_array[i]); + lua_rawseti(L, -2, i + 1); + } + break; + } + default: + std::cerr << "Lua ramsearch: Error: value type not recognized\n"; + break; + } + return 1; +} + +void Lua::Ramsearch::_set_compareop(const char *op, const char* value) +{ + if (strcmp(op, "==") == 0) + compare_operator = CompareOperator::Equal; + else if (strcmp(op, "!=") == 0) + compare_operator = CompareOperator::NotEqual; + else if (strcmp(op, "<") == 0) + compare_operator = CompareOperator::Less; + else if (strcmp(op, ">") == 0) + compare_operator = CompareOperator::Greater; + else if (strcmp(op, "<=") == 0) + compare_operator = CompareOperator::LessEqual; + else if (strcmp(op, ">=") == 0) + compare_operator = CompareOperator::GreaterEqual; + else if (strcmp(op, "!") == 0) { + compare_operator = CompareOperator::Different; + different_value = MemValue::from_string(value, value_type, false); + } +} + +int Lua::Ramsearch::set_compare_operator(lua_State *L) +{ + const char* op = lua_tostring(L, 1); + different_value_str = luaL_optstring(L, 2, different_value_str); + _set_compareop(op, different_value_str); + return 0; +} + +void Lua::Ramsearch::_set_compare_to(int type, const char* value) +{ + if (type == 0) { + compare_type = CompareType::Previous; + } else if (type == 1) { + compare_type = CompareType::Value; + compare_value = MemValue::from_string(value, value_type, false); + } +} + +int Lua::Ramsearch::set_comparison_type(lua_State *L) +{ + compare_type_int = static_cast(lua_tointeger(L, 1)); + compare_value_str = luaL_optstring(L, 2, compare_value_str); + _set_compare_to(compare_type_int, compare_value_str); + return 0; +} + +int Lua::Ramsearch::get_address(lua_State *L) +{ + if (ramSearchModel == nullptr) { + std::cerr << "Lua ramsearch: Error: attempting to access search results first creating a newsearch"; + lua_pushinteger(L, static_cast(0)); + return 1; + } + + int index = static_cast(lua_tointeger(L, 1)); + uintptr_t address = ramSearchModel->memscanner.get_address(index); + std::ostringstream addr_hex; + addr_hex << std::hex << address; + lua_pushstring(L, addr_hex.str().c_str()); + return 1; +} diff --git a/src/program/lua/Ramsearch.h b/src/program/lua/Ramsearch.h new file mode 100644 index 00000000..804fdf11 --- /dev/null +++ b/src/program/lua/Ramsearch.h @@ -0,0 +1,46 @@ +#ifndef LIBTAS_LUARAMSEARCH_H_INCLUDED +#define LIBTAS_LUARAMSEARCH_H_INCLUDED + +#include +extern "C" { +#include +} + +struct Context; + +namespace Lua { + +namespace Ramsearch { + + /* Register all functions */ + void registerFunctions(lua_State *L, Context* c); + + /* Start a new ramsearch */ + int newsearch(lua_State *L); + + /* Perform a new step on previously started search */ + int search(lua_State *L); + + /* Get current value of address at a given index of the search results */ + int get_current_value(lua_State *L); + + /* Get address at a given index of the search results */ + int get_address(lua_State *L); + + /* Set comparison operator for next search */ + int set_compare_operator(lua_State *L); + + /* Set comparison type for next search */ + int set_comparison_type(lua_State *L); + + /* Set comparison operator, and value in the "different" operator + * case */ + void _set_compareop(const char *op, const char* value); + + /* Set "compare to" case, and value in the "specific value" case */ + void _set_compare_to(int type, const char* value); + +} +} + +#endif