From bf2f4a2b3a5d2d7b3ae21be41d5a0da30a40cd1e Mon Sep 17 00:00:00 2001 From: monster860 Date: Sat, 30 Jul 2022 14:11:54 -0400 Subject: [PATCH] demo-writer --- .gitignore | 2 + CMakeLists.txt | 44 +++ cmake/i686-w64-mingw32.cmake | 18 + src/core/byond_constants.h | 76 ++++ src/core/byond_functions.cpp | 20 + src/core/byond_functions.h | 39 ++ src/core/byond_structures.h | 310 +++++++++++++++ src/core/core.cpp | 34 ++ src/core/core.h | 30 ++ src/core/find_functions.cpp | 64 +++ src/core/find_functions.h | 7 + src/core/hooking.cpp | 30 ++ src/core/hooking.h | 16 + src/core/sigscan/sigscan.cpp | 88 +++++ src/core/sigscan/sigscan.h | 13 + src/core/subhook/subhook.c | 77 ++++ src/core/subhook/subhook.h | 270 +++++++++++++ src/core/subhook/subhook_private.h | 53 +++ src/core/subhook/subhook_unix.c | 42 ++ src/core/subhook/subhook_windows.c | 38 ++ src/core/subhook/subhook_x86.c | 556 +++++++++++++++++++++++++++ src/demo_writer/demo_writer.cpp | 212 ++++++++++ src/demo_writer/demo_writer.h | 6 + src/demo_writer/state_tracking.cpp | 8 + src/demo_writer/state_tracking.h | 30 ++ src/demo_writer/write_appearance.cpp | 240 ++++++++++++ src/demo_writer/write_appearance.h | 4 + src/demo_writer/write_objects.cpp | 272 +++++++++++++ src/demo_writer/write_objects.h | 5 + src/demo_writer/write_primitive.cpp | 142 +++++++ src/demo_writer/write_primitive.h | 35 ++ src/dllmain.cpp | 23 ++ 32 files changed, 2804 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/i686-w64-mingw32.cmake create mode 100644 src/core/byond_constants.h create mode 100644 src/core/byond_functions.cpp create mode 100644 src/core/byond_functions.h create mode 100644 src/core/byond_structures.h create mode 100644 src/core/core.cpp create mode 100644 src/core/core.h create mode 100644 src/core/find_functions.cpp create mode 100644 src/core/find_functions.h create mode 100644 src/core/hooking.cpp create mode 100644 src/core/hooking.h create mode 100644 src/core/sigscan/sigscan.cpp create mode 100644 src/core/sigscan/sigscan.h create mode 100644 src/core/subhook/subhook.c create mode 100644 src/core/subhook/subhook.h create mode 100644 src/core/subhook/subhook_private.h create mode 100644 src/core/subhook/subhook_unix.c create mode 100644 src/core/subhook/subhook_windows.c create mode 100644 src/core/subhook/subhook_x86.c create mode 100644 src/demo_writer/demo_writer.cpp create mode 100644 src/demo_writer/demo_writer.h create mode 100644 src/demo_writer/state_tracking.cpp create mode 100644 src/demo_writer/state_tracking.h create mode 100644 src/demo_writer/write_appearance.cpp create mode 100644 src/demo_writer/write_appearance.h create mode 100644 src/demo_writer/write_objects.cpp create mode 100644 src/demo_writer/write_objects.h create mode 100644 src/demo_writer/write_primitive.cpp create mode 100644 src/demo_writer/write_primitive.h create mode 100644 src/dllmain.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..841c035 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dmtest/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..93a613c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +if (WIN32) + # Needed for MSVC_RUNTIME_LIBRARY + cmake_minimum_required(VERSION 3.15.0) +else() + cmake_minimum_required(VERSION 3.10.0) +endif() + +project(demo-writer) +if (WIN32) + enable_language(ASM) +endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) +if (WIN32) + file(GLOB_RECURSE SRC_FILES + "${SRC_DIR}/*.cpp" + "${SRC_DIR}/*.h" + "${SRC_DIR}/*.hpp" + "${SRC_DIR}/*.s" + ) +else() + file(GLOB_RECURSE SRC_FILES + "${SRC_DIR}/*.cpp" + "${SRC_DIR}/*.h" + "${SRC_DIR}/*.hpp" + ) +endif() +set(SRC_FILES ${SRC_FILES} "${SRC_DIR}/core/subhook/subhook.c") + +add_library(demo-writer SHARED ${SRC_FILES}) +source_group(TREE ${SRC_DIR} FILES ${SRC_FILES}) +if (WIN32) + set_property(TARGET demo-writer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + if (MSVC) + target_compile_options(demo-writer PRIVATE "/MP") + else() + target_link_libraries(demo-writer PRIVATE "ws2_32" "psapi" "-static-libgcc" "-static-libstdc++" "-static") + endif() + target_compile_definitions(demo-writer PRIVATE WIN32_LEAN_AND_MEAN SUBHOOK_IMPLEMENTATION) +else() + set_target_properties(demo-writer PROPERTIES COMPILE_OPTIONS "-m32" LINK_FLAGS "-m32") +endif() diff --git a/cmake/i686-w64-mingw32.cmake b/cmake/i686-w64-mingw32.cmake new file mode 100644 index 0000000..47f6203 --- /dev/null +++ b/cmake/i686-w64-mingw32.cmake @@ -0,0 +1,18 @@ +set(GNU_HOST i686-w64-mingw32) +set(CMAKE_SYSTEM_PROCESSOR "i686") + +set(COMPILER_PREFIX "${GNU_HOST}-") +set(COMPILER_SUFFIX "-posix") + +set(CMAKE_SYSTEM_NAME "Windows") +set(CMAKE_CROSSCOMPILING TRUE) +set(WIN32 TRUE) +set(MINGW TRUE) + +set(CMAKE_C_COMPILER ${COMPILER_PREFIX}gcc${COMPILER_SUFFIX}) +set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}g++${COMPILER_SUFFIX}) +set(CMAKE_RC_COMPILER ${COMPILER_PREFIX}windres) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/src/core/byond_constants.h b/src/core/byond_constants.h new file mode 100644 index 0000000..efdae3d --- /dev/null +++ b/src/core/byond_constants.h @@ -0,0 +1,76 @@ +#pragma once + +enum DataType : unsigned char +{ + NULL_D = 0x00, + TURF = 0x01, + OBJ = 0x02, + MOB = 0x03, + AREA = 0x04, + CLIENT = 0x05, + STRING = 0x06, + MOB_TYPEPATH = 0x08, + OBJ_TYPEPATH = 0x09, + TURF_TYPEPATH = 0x0A, + AREA_TYPEPATH = 0x0B, + RESOURCE = 0x0C, + IMAGE = 0x0D, + WORLD_D = 0x0E, + LIST = 0x0F, + LIST_ARGS = 0x10, + LIST_MOB_VERBS = 0x11, + LIST_VERBS = 0x12, + LIST_TURF_VERBS = 0x13, + LIST_AREA_VERBS = 0x14, + LIST_CLIENT_VERBS = 0x15, + LIST_SAVEFILE_DIR = 0x16, + LIST_MOB_CONTENTS = 0x17, + LIST_TURF_CONTENTS = 0x18, + LIST_AREA_CONTENTS = 0x19, + LIST_WORLD_CONTENTS = 0x1A, + LIST_GROUP = 0x1B, + LIST_CONTENTS = 0x1C, + DATUM_TYPEPATH = 0x20, + DATUM = 0x21, + SAVEFILE = 0x23, + SAVEFILE_TYPEPATH = 0x24, + PROCPATH = 0x26, + FILE_ = 0x27, + LIST_TYPEPATH = 0x28, + PREFAB = 0x29, + NUMBER = 0x2A, + LIST_MOB_VARS = 0x2C, + LIST_OBJ_VARS = 0x2D, + LIST_TURF_VARS = 0x2E, + LIST_AREA_VARS = 0x2F, + LIST_CLIENT_VARS = 0x30, + LIST_VARS = 0x31, //maybe? + LIST_MOB_OVERLAYS = 0x32, + LIST_MOB_UNDERLAYS = 0x33, + LIST_OVERLAYS = 0x34, + LIST_UNDERLAYS = 0x35, + LIST_TURF_OVERLAYS = 0x36, + LIST_TURF_UNDERLAYS = 0x37, + LIST_AREA_OVERLAYS = 0x38, + LIST_AREA_UNDERLAYS = 0x39, + APPEARANCE = 0x3A, + CLIENT_TYPEPATH = 0x3B, + IMAGE_TYPEPATH = 0x3F, + LIST_IMAGE_OVERLAYS = 0x40, + LIST_IMAGE_UNDERLAYS = 0x41, + LIST_IMAGE_VARS = 0x42, + LIST_IMAGE_VERBS = 0x43, + LIST_IMAGE_CONTENTS = 0x44, // wait wtf + LIST_CLIENT_IMAGES = 0x46, + LIST_CLIENT_SCREEN = 0x47, + LIST_TURF_VIS_CONTENTS = 0x4B, + LIST_VIS_CONTENTS = 0x4C, + LIST_MOB_VIS_CONTENTS = 0x4D, + LIST_TURF_VIS_LOCS = 0x4E, + LIST_VIS_LOCS = 0x4F, + LIST_MOB_VIS_LOCS = 0x50, + LIST_WORLD_VARS = 0x51, + LIST_GLOBAL_VARS = 0x52, + FILTERS = 0x53, + LIST_IMAGE_VIS_CONTENTS = 0x54, +}; diff --git a/src/core/byond_functions.cpp b/src/core/byond_functions.cpp new file mode 100644 index 0000000..b0d7332 --- /dev/null +++ b/src/core/byond_functions.cpp @@ -0,0 +1,20 @@ +#include "byond_functions.h" + +GetStringTableEntryPtr GetStringTableEntry; +SetTurfAppearancePtr SetTurfAppearance; +SetAppearancePtr SetAppearance; +SpliceAppearancePtr SpliceAppearance; +SpliceStringPtr SpliceString; +SetPixelXPtr SetPixelX; +SetPixelYPtr SetPixelY; +SetPixelWPtr SetPixelW; +SetPixelZPtr SetPixelZ; +SetMovableDirPtr SetMovableDir; +SetLocPtr SetLoc; +ToStringPtr ToString; +GetMobAppearancePtr GetMobAppearance; +GetObjAppearancePtr GetObjAppearance; +GetVariablePtr GetVariable; +AllocObjIdPtr AllocObjId; +AllocMobIdPtr AllocMobId; +ChangeTurfPtr ChangeTurf; diff --git a/src/core/byond_functions.h b/src/core/byond_functions.h new file mode 100644 index 0000000..648e080 --- /dev/null +++ b/src/core/byond_functions.h @@ -0,0 +1,39 @@ +#include "byond_structures.h" + +typedef String* (*GetStringTableEntryPtr)(int stringId); +typedef void(*SetTurfAppearancePtr)(int appearance, int turf); +typedef void(*SetAppearancePtr)(Value atom, int appearance); +typedef void(__fastcall *SpliceAppearancePtr)(void* this_, int edx, int appearance); // it's actually __thiscall but c++ compilers are stupid +typedef void(*SpliceStringPtr)(unsigned int id); +typedef void(*SetPixelXPtr)(Value atom, short pixel_x); +typedef SetPixelXPtr SetPixelYPtr; +typedef SetPixelXPtr SetPixelZPtr; +typedef SetPixelXPtr SetPixelWPtr; +typedef void(*SetMovableDirPtr)(Value atom, unsigned char dir); +typedef void(*SetLocPtr)(Value atom, Value loc); +typedef unsigned int(*ToStringPtr)(int type, int value); +typedef unsigned int(*GetObjAppearancePtr)(unsigned int id); +typedef unsigned int(*GetMobAppearancePtr)(unsigned int id); +typedef Value(*GetVariablePtr)(Value datum, unsigned int varNameId); +typedef int(*AllocObjIdPtr)(); +typedef int(*AllocMobIdPtr)(); +typedef void(*ChangeTurfPtr)(int a, int b, int type, Value turf); + +extern GetStringTableEntryPtr GetStringTableEntry; +extern SetTurfAppearancePtr SetTurfAppearance; +extern SetAppearancePtr SetAppearance; +extern SpliceAppearancePtr SpliceAppearance; +extern SpliceStringPtr SpliceString; +extern SetPixelXPtr SetPixelX; +extern SetPixelYPtr SetPixelY; +extern SetPixelWPtr SetPixelW; +extern SetPixelZPtr SetPixelZ; +extern SetMovableDirPtr SetMovableDir; +extern SetLocPtr SetLoc; +extern ToStringPtr ToString; +extern GetMobAppearancePtr GetMobAppearance; +extern GetObjAppearancePtr GetObjAppearance; +extern GetVariablePtr GetVariable; +extern AllocObjIdPtr AllocObjId; +extern AllocMobIdPtr AllocMobId; +extern ChangeTurfPtr ChangeTurf; diff --git a/src/core/byond_structures.h b/src/core/byond_structures.h new file mode 100644 index 0000000..3a44669 --- /dev/null +++ b/src/core/byond_structures.h @@ -0,0 +1,310 @@ +#pragma once + +#include +#include "byond_constants.h" + +#ifdef _WIN32 +#define REGPARM3 +#define REGPARM2 +#else +#define REGPARM3 __attribute__((regparm(3))) +#define REGPARM2 __attribute__((regparm(2))) +#endif + +struct String +{ + char* stringData; + int unk1; + int unk2; + unsigned int refcount; +}; + +struct Value +{ + char type; + union + { + int value; + float valuef; + }; +}; + +struct TableHolder2 +{ + void** elements; + unsigned int length; +}; + +template +struct RefTable +{ + RefTable(T*** e, unsigned int* l) : elements(*e), length(*l) {} + RefTable(TableHolder2* th) : elements(*(T***)&th->elements), length(th->length) {} + RefTable(void* base, int elements_offset, int length_offset) : elements(**(T****)((int)base+elements_offset)), length(**(unsigned int**)((int)base + length_offset)) {} + RefTable() : elements(dummy_elements), length(dummy_length) {} + T**& elements; + unsigned int& length; +private: + T** dummy_elements = nullptr; + unsigned int dummy_length = 0; +}; + +struct TableHolder3 +{ + void* elements; + std::uint32_t size; + std::uint32_t capacity; + TableHolder3* next; //probably? + char unknown[8]; +}; + +struct VarListEntry +{ + std::uint32_t unknown; + std::uint32_t name_id; + Value value; +}; + +struct Obj +{ + Value loc; // 0 + char unknown[8]; // 8 + short bound_x; // 10 + short bound_y; // 12 + short bound_width; // 14 + short bound_height; // 16 + float step_x; // 18 + float step_y; // 1c + float step_size; // 20 + short pixel_x; // 24 + short pixel_y; // 26 + short pixel_w; // 28 + short pixel_z; // 2a + void* some_other_linked_list; + VarListEntry* modified_vars; // 30 + std::uint16_t modified_vars_count; + std::uint16_t modified_vars_capacity; + char unknown2[4]; // 38 + void* some_linked_list; //3c + char unknown3[16]; + TableHolder3* vis_contents; // 50 + TableHolder3* vis_locs; // 54 + char unknown4[12]; + int appearance; // 64 + int appearance2; // 68 + int appearance3; // 6c + char unknown5[64]; +}; + +struct Datum +{ + std::uint32_t type; + VarListEntry* modified_vars; + std::uint16_t modifier_vars_count; + std::uint16_t modified_vars_capacity; + std::uint32_t flags; + std::uint32_t refcount; +}; + +struct Mob +{ + Value loc; // 0 + char unknown[8]; // 8 + short bound_x; // 10 + short bound_y; // 12 + short bound_width; // 14 + short bound_height; // 16 + float step_x; // 18 + float step_y; // 1c + float step_size; // 20 + short pixel_x; // 24 + short pixel_y; // 26 + short pixel_w; // 28 + short pixel_z; // 2a + void* some_other_linked_list; + VarListEntry* modified_vars; // 30 + std::uint16_t modified_vars_count; + std::uint16_t modified_vars_capacity; + char unknown2[4]; // 38 + void* some_linked_list; //3c + char unknown3[16]; + TableHolder3* vis_contents; // 50 + TableHolder3* vis_locs; // 54 + char unknown4[12]; + int appearance; // 64 + int appearance2; // 68 + int appearance3; // 6c + char unknown5[0x4C]; + void* unknown_list3; + char unknown6[0x10]; +}; + +struct Turf { // According to lummox, this struct also includes info about atoms overhanging and animations too + int id; + Turf* next; + int obj_contents; + int mob_contents; + int unk_10; + TableHolder3* vis_contents; // vis_contents + TableHolder3* vis_locs; + int unk_1c; + int unk_20; + int unk_24; + int unk_28; + int unk_2c; +}; +struct TurfVars { + int id; + TurfVars* next; + VarListEntry* modified_vars; + std::uint16_t modified_vars_count; + std::uint16_t modified_vars_capacity; +}; + +struct TurfSharedInfo { + int typepath_id; + int appearance; + int area; + int unk_0c; + short unk_10; + short unk_12; + int unk_14; + short unk_18; + short unk_1a; + int unk_1c; +}; + +struct TurfTableHolder { + int* shared_info_id_table; + unsigned char* existence_table; + int turf_count; + int maxx; + int maxy; + int maxz; +}; +struct TurfHashtableHolder { + Turf** elements; + int size; + int mask; +}; + +enum class AppearanceRbtColor : bool +{ + Black = true, + Red = false, +}; + +struct Appearance +{ + Appearance* left_node; // 0 + Appearance* right_node; // 4 + Appearance* parent_node; // 8 + Appearance* prev_node; // c + Appearance* next_node; // 10 + int id; // 14 + AppearanceRbtColor node_color; // 18 + int name_str; // 1c + int desc_str; // 20 + int suffix_str; // 24 + int screen_loc_str; // 28 + int text_str; // 2c + int icon_res; // 30 + int icon_state_str; // 34 + int overlays_list; // 38 + int underlays_list; // 3c + int verbs_list; // 40 + int unk_44; + + unsigned long long opacity : 1; // 48 + unsigned long long density : 1; + unsigned long long unk_48_4 : 4; + unsigned long long gender : 2; // 0=neuter, 0=male, 0=female, 0=plural + + unsigned long long mouse_drop_zone : 1; // 49 + unsigned long long dir_override : 1; // internal flag for whether dir is inherited or not + unsigned long long unk_49_4 : 2; + unsigned long long mouse_opacity : 2; + unsigned long long animate_movement : 2; // add one and bitwise-and 3 to get actual value + + unsigned long long unk_4a : 2; // 4a + unsigned long long override : 1; + unsigned long long unk_4a_8 : 2; + + unsigned long long appearance_flags : 12; + + unsigned char dir; // 50 + unsigned char invisibility; + unsigned char infra_luminosity; + unsigned char luminosity; + + short pixel_x; // 54 + short pixel_y; + + short pixel_w; // 58 + short pixel_z; + + float glide_size; // 5c + float layer; // 60? + int maptext_str; // 64? + + short maptext_x; // 68? + short maptext_y; + + short maptext_width; // 6c? + short maptext_height; + + Value mouse_over_pointer; // 70 + Value mouse_drag_pointer; // 78 + Value mouse_drop_pointer; // 80 + int unk_88; // an appearance + int unk_8c; // an appearance + int unk_90; // an appearance + + float transform[6]; // 94 + + union { // ac + struct { + unsigned char color_r; + unsigned char color_g; + unsigned char color_b; + unsigned char alpha; + }; + unsigned int color_alpha; + }; + + + unsigned char blend_mode; // b0 + unsigned char matrix_flag; // b1 + short plane; // b2 + + int unk_b4; // probably filters imo + int render_source_str; // b8 + int render_target_str; // bc + + unsigned short vis_flags; // c0 + short unk_c2; + + int unk_c4; + int unk_c8; + int unk_cc; + int unk_d0; + int unk_d4; + int unk_d8; + int unk_dc; + float *color_matrix; // e0 + int refcount; // e4 +}; + +struct AppearanceTable +{ + char unk[0x40]; + Appearance** elements; + int length; +}; + +struct AppearanceList // used for overlays, underlays, etc +{ + short len; + int unk; + int* ids; +}; \ No newline at end of file diff --git a/src/core/core.cpp b/src/core/core.cpp new file mode 100644 index 0000000..229f20a --- /dev/null +++ b/src/core/core.cpp @@ -0,0 +1,34 @@ +#include "core.h" +#ifdef _WIN32 +#include +#endif +#include + +std::unique_ptr> Core::obj_table; +//TableHolder2* Core::datum_table = nullptr; +//TableHolder2* Core::list_table = nullptr; + +std::unique_ptr> Core::mob_table; +TableHolder2* Core::appearance_list_table = nullptr; +AppearanceTable** Core::appearance_table = nullptr; +TurfTableHolder* Core::turf_table = nullptr; +TurfHashtableHolder* Core::turf_hashtable = nullptr; +TurfSharedInfo*** Core::turf_shared_info_table = nullptr; + +void Core::Alert(const std::string& what) { +#ifdef _WIN32 + MessageBoxA(NULL, what.c_str(), "Ouch!", MB_OK); +#else + printf("Ouch!: %s\n", what.c_str()); +#endif +} + +void Core::Alert(int what) +{ + Alert(std::to_string(what)); +} + +std::string Core::GetStringFromId(unsigned int id) +{ + return GetStringTableEntry(id)->stringData; +} diff --git a/src/core/core.h b/src/core/core.h new file mode 100644 index 0000000..5c6f1f7 --- /dev/null +++ b/src/core/core.h @@ -0,0 +1,30 @@ +#pragma once + +#include "byond_structures.h" +#include +#include "find_functions.h" +#include "hooking.h" +#include "byond_functions.h" + +#ifdef _WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __attribute__((visibility("default"))) +#endif + +namespace Core { + void Alert(const std::string& what); + void Alert(int what); + + std::string GetStringFromId(unsigned int id); + + extern std::unique_ptr> obj_table; + //extern TableHolder2* datum_table; + //extern TableHolder2* list_table; //list list honk + extern std::unique_ptr> mob_table; + extern TableHolder2* appearance_list_table; + extern AppearanceTable** appearance_table; + extern TurfTableHolder* turf_table; + extern TurfHashtableHolder* turf_hashtable; + extern TurfSharedInfo*** turf_shared_info_table; +} \ No newline at end of file diff --git a/src/core/find_functions.cpp b/src/core/find_functions.cpp new file mode 100644 index 0000000..5e62270 --- /dev/null +++ b/src/core/find_functions.cpp @@ -0,0 +1,64 @@ +#include "find_functions.h" +#include "sigscan/sigscan.h" +#include "byond_functions.h" +#include "core.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef _WIN32 +#define BYONDCORE "byondcore.dll" +#else +#define BYONDCORE "libbyond.so" +#endif + +#define FIND_OR_DIE(name, sig) name = (name##Ptr)Pocket::Sigscan::FindPattern(BYONDCORE, sig); if(!name) { Core::Alert("demo-writer: Failed to locate " #name); return false; } +#define TRUE_OR_DIE(thing) if(!thing) { Core::Alert("demo-writer: Failed to locate " #thing); return false; } + +bool Core::find_functions() +{ +#ifdef _WIN32 + FIND_OR_DIE(GetStringTableEntry, "55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 10 A1"); + FIND_OR_DIE(ToString, "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ?? 0F B6 C1"); + FIND_OR_DIE(SetAppearance, "55 8b ec 8b 45 08 83 ec 08 0f b6 c8 53 8d 41 ff 56 57 83 f8 53 0f 87 ?? ?? ?? ?? 0f b6 80 ?? ?? ?? ?? ff 24 85 ?? ?? ?? ?? ff 75 0c ff 75 10 e8 ?? ?? ?? ?? 83 c4 08 5f 5e 5b 8b e5 5d c3"); + FIND_OR_DIE(SetPixelX, "55 8b ec 8b 45 08 56 3c 03 75 16 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ad 00 00 00 a1 ?? ?? ?? ?? eb 18 3c 02 75 78 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ?? ?? ?? ?? a1 ?? ?? ?? ?? 8b 34 b0 85 f6 0f 84 ?? ?? ?? ?? 8b 45 10 66 3b 46 24 74 7a 66 89 46 24"); + FIND_OR_DIE(SetPixelY, "55 8b ec 8b 45 08 56 3c 03 75 16 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ad 00 00 00 a1 ?? ?? ?? ?? eb 18 3c 02 75 78 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ?? ?? ?? ?? a1 ?? ?? ?? ?? 8b 34 b0 85 f6 0f 84 ?? ?? ?? ?? 8b 45 10 66 3b 46 26 74 7a 66 89 46 26"); + FIND_OR_DIE(SetPixelW, "55 8b ec 8b 45 08 56 3c 03 75 16 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ad 00 00 00 a1 ?? ?? ?? ?? eb 18 3c 02 75 78 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ?? ?? ?? ?? a1 ?? ?? ?? ?? 8b 34 b0 85 f6 0f 84 ?? ?? ?? ?? 8b 45 10 66 3b 46 28 74 7a 66 89 46 28"); + FIND_OR_DIE(SetPixelZ, "55 8b ec 8b 45 08 56 3c 03 75 16 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ad 00 00 00 a1 ?? ?? ?? ?? eb 18 3c 02 75 78 8b 75 0c 3b 35 ?? ?? ?? ?? 0f 83 ?? ?? ?? ?? a1 ?? ?? ?? ?? 8b 34 b0 85 f6 0f 84 ?? ?? ?? ?? 8b 45 10 66 3b 46 2a 74 7a 66 89 46 2a"); + FIND_OR_DIE(SetMovableDir, "55 8b ec 6a ff 68 ?? ?? ?? ?? 64 a1 00 00 00 00 50 81 ec ?? ?? ?? ?? a1 ?? ?? ?? ?? 33 c5 89 45 f0 53 56 57 50 8d 45 f4 64 a3 00 00 00 00 8b 45 08 8b 7d 0c 89 85 00 ff ff ff 0f b6 c0"); + FIND_OR_DIE(SetLoc, "55 8b ec ff 75 14 ff 75 10 e8 ?? ?? ?? ?? 83 c4 08 84 c0 75 04 32 c0 5d c3"); + FIND_OR_DIE(SpliceAppearance, "55 8b ec 53 56 8b d9 57 8b 7d 08 8b f7 8b 4b 38 d3 ee 3b 73 28"); + FIND_OR_DIE(SpliceString, "55 8b ec 83 ec 08 56 57 8b 7d 08 57 e8 ?? ?? ?? ?? 8b f0 83 c4 04 85 f6 0f 84 ?? ?? ?? ?? 57 e8 ?? ?? ?? ?? 83 c4 04"); + FIND_OR_DIE(GetVariable, "55 8B EC 8B 4D ?? 0F B6 C1 48 83 F8 ?? 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 ?? FF 75 ?? E8 ?? ?? ?? ??"); + FIND_OR_DIE(AllocObjId, "56 57 8b 3d ?? ?? ?? ?? 85 ff 74 22 a1 ?? ?? ?? ?? 4f 89 3d ?? ?? ?? ?? 8b 3c b8 81 ff ff ff 00 00 74 0b 8b 35 ?? ?? ?? ?? e9 fb 00 00 00 8b 0d ?? ?? ?? ?? 8b f9 81 f9 ff ff ff 00 75 18 51 6a 08 68"); + FIND_OR_DIE(AllocMobId, "56 57 8b 3d ?? ?? ?? ?? 85 ff 74 22 a1 ?? ?? ?? ?? 4f 89 3d ?? ?? ?? ?? 8b 3c b8 81 ff ff ff 00 00 74 0b 8b 35 ?? ?? ?? ?? e9 fb 00 00 00 8b 0d ?? ?? ?? ?? 8b f9 81 f9 ff ff ff 00 75 18 51 6a 10 68"); + FIND_OR_DIE(ChangeTurf, "55 8b ec 83 ec 1c ff 75 18 ff 75 14 e8 ?? ?? ?? ?? 83 c4 08") + + char* get_appearance_ptr = (char*)Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec 8b 4d 08 0f b6 c1 83 c0 fe 83 f8 4e 0f 87 ?? ?? ?? ?? 0f b6 80 ?? ?? ?? ?? ff 24 85 ?? ?? ?? ?? ff 75 0c e8 ?? ?? ?? ?? 83 c4 04 5d c3 ff 75 0c e8 ?? ?? ?? ?? 83 c4 04 5d c3", 0); + TRUE_OR_DIE(get_appearance_ptr) + GetObjAppearance = (GetObjAppearancePtr)(get_appearance_ptr + *(int*)(get_appearance_ptr + 39) + 43); + GetMobAppearance = (GetMobAppearancePtr)(get_appearance_ptr + *(int*)(get_appearance_ptr + 52) + 56); + + //obj_table = std::make_unique>(Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec 56 8b 75 08 6a 02 56 e8 ?? ?? ?? ?? ff 75 10 ff 75 0c 52 50 e8 ?? ?? ?? ?? 83 c4 18 84 c0 75 5a 3b 35 ?? ?? ?? ?? 73 52 a1 ?? ?? ?? ??"), 44, 37); + //mob_table = std::make_unique>(Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec 56 8b 75 08 6a 03 56 e8 ?? ?? ?? ?? ff 75 10 ff 75 0c 52 50 e8 ?? ?? ?? ?? 83 c4 18 84 c0 75 5a 3b 35 ?? ?? ?? ?? 73 52 a1 ?? ?? ?? ??"), 44, 37); + obj_table = std::make_unique>((void*)GetObjAppearance, 16, 9); + mob_table = std::make_unique>((void*)GetMobAppearance, 16, 9); + appearance_table = *(AppearanceTable***)Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec a1 ?? ?? ?? ?? 8b 4d 08 3b 48 44 73 08 8b 40 40 8b 04 88 5d c3", 4); + void* get_id_list_ptr = Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec 83 ec 0c 53 56 57 ff 75 08 e8 ?? ?? ?? ?? 83 c4 04 85 c0", 13); + TRUE_OR_DIE(get_id_list_ptr) + void* get_id_list = (void*)(*(int*)get_id_list_ptr + (int)get_id_list_ptr + 4); + appearance_list_table = *(TableHolder2**)((int)get_id_list + 19); + char* get_shared_turf_ptr = (char*)Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec 8b 4d 08 3b 0d ?? ?? ?? ?? 73 12 a1 ?? ?? ?? ?? 8b 0c 88 a1 ?? ?? ?? ?? 8b 04 88 5d c3 33 c0 5d c3"); + TRUE_OR_DIE(get_shared_turf_ptr) + turf_table = *(TurfTableHolder**)(get_shared_turf_ptr + 15); + turf_shared_info_table = *(TurfSharedInfo****)(get_shared_turf_ptr + 23); + turf_hashtable = *(TurfHashtableHolder**)Pocket::Sigscan::FindPattern(BYONDCORE, "55 8b ec a1 ?? ?? ?? ?? 8b 55 08 23 c2 0f b7 c8 a1 ?? ?? ?? ??", 17); + TRUE_OR_DIE(turf_hashtable); +#else + FIND_OR_DIE(GetStringTableEntry, "55 89 E5 83 EC 18 8B 45 ?? 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 ??"); + FIND_OR_DIE(ToString, "55 89 E5 83 EC 68 A1 ?? ?? ?? ?? 8B 15 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 89 5D ??"); + FIND_OR_DIE(GetVariable, "55 89 E5 81 EC ?? ?? ?? ?? 8B 55 ?? 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ??"); +#endif + return true; +} \ No newline at end of file diff --git a/src/core/find_functions.h b/src/core/find_functions.h new file mode 100644 index 0000000..2059f7b --- /dev/null +++ b/src/core/find_functions.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Core +{ + bool find_functions(); + //bool verify_compat(); +} \ No newline at end of file diff --git a/src/core/hooking.cpp b/src/core/hooking.cpp new file mode 100644 index 0000000..1dd78b9 --- /dev/null +++ b/src/core/hooking.cpp @@ -0,0 +1,30 @@ +#include "hooking.h" +#include "subhook/subhook.h" +#include + +std::unordered_map hooks; + +void* Core::untyped_install_hook(void* original, void* hook) +{ + subhook::Hook* /*I am*/ shook = new subhook::Hook; + shook->Install(original, hook); + hooks[original] = shook; + return shook->GetTrampoline(); +} + +void Core::remove_hook(void* func) +{ + hooks[func]->Remove(); + delete hooks[func]; + hooks.erase(func); +} + +void Core::remove_all_hooks() +{ + for (auto iter = hooks.begin(); iter != hooks.end(); ) + { + iter->second->Remove(); + delete iter->second; + iter = hooks.erase(iter); + } +} diff --git a/src/core/hooking.h b/src/core/hooking.h new file mode 100644 index 0000000..e762d75 --- /dev/null +++ b/src/core/hooking.h @@ -0,0 +1,16 @@ +#pragma once + +namespace Core +{ + void* untyped_install_hook(void* original, void* hook); + + // Used to ensure everything is the same function pointer type. + template + FnPtr install_hook(FnPtr original, FnPtr hook) + { + return (FnPtr)untyped_install_hook((void*)original, (void*)hook); + } + + void remove_hook(void* func); + void remove_all_hooks(); +} \ No newline at end of file diff --git a/src/core/sigscan/sigscan.cpp b/src/core/sigscan/sigscan.cpp new file mode 100644 index 0000000..c35a41b --- /dev/null +++ b/src/core/sigscan/sigscan.cpp @@ -0,0 +1,88 @@ +#include "sigscan.h" + +#ifdef _WIN32 +#include +#include +#else +// lol +#include +#include +#include +#include +#include +#include +#include + +#endif +#define INRANGE(x,a,b) (x >= a && x <= b) +#define getBits( x ) (INRANGE((x&(~0x20)),'A','F') ? ((x&(~0x20)) - 'A' + 0xa) : (INRANGE(x,'0','9') ? x - '0' : 0)) +#define getByte( x ) (getBits(x[0]) << 4 | getBits(x[1])) + +inline bool Pocket::Sigscan::DataCompare(const unsigned char* base, const char* pattern) +{ + for (; *(pattern + 2); ++base, pattern += *(pattern + 1) == ' ' ? 2 : 3) + { + if (*pattern != '?') + if (*base != getByte(pattern)) + return false; + } + + return *(pattern + 2) == 0; +} + +void* Pocket::Sigscan::FindPattern(std::uintptr_t address, size_t size, const char* pattern, const short offset) +{ + for (size_t i = 0; i < size; ++i, ++address) + if (DataCompare(reinterpret_cast(address), pattern)) + return reinterpret_cast(address + offset); + + return nullptr; +} +#ifndef _WIN32 +static void* disgusting; +size_t disgustingSz; +static int +callback(struct dl_phdr_info* info, size_t size, void* data) +{ + int j; + //printf("name: %s vs %s\n", info->dlpi_name, (const char*)data); + if (!strstr(info->dlpi_name, (const char*)data)) return 0; + for (j = 0; j < info->dlpi_phnum; j++) + { + + if (info->dlpi_phdr[j].p_type == PT_LOAD) + { + char* beg = (char*)info->dlpi_addr + info->dlpi_phdr[j].p_vaddr; + char* end = beg + info->dlpi_phdr[j].p_memsz; + disgusting = beg; + disgustingSz = info->dlpi_phdr[j].p_memsz; + return 0; + } + } + return 0; +} +#endif +void* Pocket::Sigscan::FindPattern(const char* moduleName, const char* pattern, const short offset) +{ + + size_t rangeStart; + size_t size; +#ifdef _WIN32 + if (!(rangeStart = reinterpret_cast(GetModuleHandleA(moduleName)))) + return nullptr; + MODULEINFO miModInfo; GetModuleInformation(GetCurrentProcess(), reinterpret_cast(rangeStart), &miModInfo, sizeof(MODULEINFO)); + size = miModInfo.SizeOfImage; +#else + + disgusting = nullptr; + dl_iterate_phdr(callback, (void*)moduleName); + if (!disgusting) + { + return nullptr; + } + + rangeStart = reinterpret_cast(disgusting); + size = disgustingSz; +#endif + return FindPattern(rangeStart, size, pattern, offset); +} \ No newline at end of file diff --git a/src/core/sigscan/sigscan.h b/src/core/sigscan/sigscan.h new file mode 100644 index 0000000..3efea13 --- /dev/null +++ b/src/core/sigscan/sigscan.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +namespace Pocket +{ + namespace Sigscan + { + bool DataCompare(const unsigned char* base, const char* pattern); + void* FindPattern(std::uintptr_t address, size_t size, const char* pattern, short offset = 0); + void* FindPattern(const char* moduleName, const char* pattern, short offset = 0); + } +} \ No newline at end of file diff --git a/src/core/subhook/subhook.c b/src/core/subhook/subhook.c new file mode 100644 index 0000000..8ea7d51 --- /dev/null +++ b/src/core/subhook/subhook.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "subhook.h" +#include "subhook_private.h" + +subhook_disasm_handler_t subhook_disasm_handler = NULL; + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_src(subhook_t hook) { + if (hook == NULL) { + return NULL; + } + return hook->src; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_dst(subhook_t hook) { + if (hook == NULL) { + return NULL; + } + return hook->dst; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_trampoline(subhook_t hook) { + if (hook == NULL) { + return NULL; + } + return hook->trampoline; +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook) { + if (hook == NULL) { + return false; + } + return hook->installed; +} + +SUBHOOK_EXPORT void SUBHOOK_API subhook_set_disasm_handler( + subhook_disasm_handler_t handler) { + subhook_disasm_handler = handler; +} + +#ifndef SUBHOOK_SEPARATE_SOURCE_FILES + +#if defined SUBHOOK_WINDOWS + #include "subhook_windows.c" +#elif defined SUBHOOK_UNIX + #include "subhook_unix.c" +#endif + +#if defined SUBHOOK_X86 || defined SUBHOOK_X86_64 + #include "subhook_x86.c" +#endif + +#endif diff --git a/src/core/subhook/subhook.h b/src/core/subhook/subhook.h new file mode 100644 index 0000000..3fe102c --- /dev/null +++ b/src/core/subhook/subhook.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SUBHOOK_H +#define SUBHOOK_H + +#if defined _M_IX86 || defined __i386__ + #define SUBHOOK_X86 + #define SUBHOOK_BITS 32 +#elif defined _M_AMD64 || __amd64__ + #define SUBHOOK_X86_64 + #define SUBHOOK_BITS 64 +#else + #error Unsupported architecture +#endif + +#if defined _WIN32 || defined __CYGWIN__ + #define SUBHOOK_WINDOWS +#elif defined __linux__ + #define SUBHOOK_LINUX + #define SUBHOOK_UNIX +#elif defined __APPLE__ + #define SUBHOOK_MACOS + #define SUBHOOK_UNIX +#else + #error Unsupported operating system +#endif + +#if !defined SUBHOOK_EXTERN + #if defined __cplusplus + #define SUBHOOK_EXTERN extern "C" + #else + #define SUBHOOK_EXTERN extern + #endif +#endif + +#if defined SUBHOOK_STATIC + #define SUBHOOK_API + #define SUBHOOK_EXPORT SUBHOOK_EXTERN +#endif + +#if !defined SUBHOOK_API + #if defined SUBHOOK_X86 + #if defined SUBHOOK_WINDOWS + #define SUBHOOK_API __cdecl + #elif defined SUBHOOK_UNIX + #define SUBHOOK_API __attribute__((cdecl)) + #endif + #else + #define SUBHOOK_API + #endif +#endif + +#if !defined SUBHOOK_EXPORT + #if defined SUBHOOK_WINDOWS + #if defined SUBHOOK_IMPLEMENTATION + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __declspec(dllexport) + #else + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __declspec(dllimport) + #endif + #elif defined SUBHOOK_UNIX + #if defined SUBHOOK_IMPLEMENTATION + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __attribute__((visibility("default"))) + #else + #define SUBHOOK_EXPORT SUBHOOK_EXTERN + #endif + #endif +#endif + +typedef enum subhook_flags { + /* Use the 64-bit jump method on x86-64 (requires more space). */ + SUBHOOK_64BIT_OFFSET = 1 +} subhook_flags_t; + +struct subhook_struct; +typedef struct subhook_struct *subhook_t; + +typedef int (SUBHOOK_API *subhook_disasm_handler_t)( + void *src, + int *reloc_op_offset); + +SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new( + void *src, + void *dst, + subhook_flags_t flags); +SUBHOOK_EXPORT void SUBHOOK_API subhook_free(subhook_t hook); + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_src(subhook_t hook); +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_dst(subhook_t hook); +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_trampoline(subhook_t hook); + +SUBHOOK_EXPORT int SUBHOOK_API subhook_install(subhook_t hook); +SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook); +SUBHOOK_EXPORT int SUBHOOK_API subhook_remove(subhook_t hook); + +/* + * Reads hook destination address from code. + * + * This function may be useful when you don't know the address or want to + * check whether src is already hooked. + */ +SUBHOOK_EXPORT void *SUBHOOK_API subhook_read_dst(void *src); + +/* + * Sets a custom disassmbler function to use in place of the default one + * (subhook_disasm). + * + * The default function recognized a small st of x86 instructiosn commonly + * in prologues. If it fails in your situation you might want to use a more + * advanced disassembler library. + */ +SUBHOOK_EXPORT void SUBHOOK_API subhook_set_disasm_handler( + subhook_disasm_handler_t handler); + +#ifdef __cplusplus + +namespace subhook { + +enum HookFlags { + HookNoFlags = 0, + HookFlag64BitOffset = SUBHOOK_64BIT_OFFSET +}; + +inline HookFlags operator|(HookFlags o1, HookFlags o2) { + return static_cast( + static_cast(o1) | static_cast(o2)); +} + +inline HookFlags operator&(HookFlags o1, HookFlags o2) { + return static_cast( + static_cast(o1) & static_cast(o2)); +} + +inline void *ReadHookDst(void *src) { + return subhook_read_dst(src); +} + +inline void SetDisasmHandler(subhook_disasm_handler_t handler) { + subhook_set_disasm_handler(handler); +} + +class Hook { + public: + Hook() : hook_(0) {} + Hook(void *src, void *dst, HookFlags flags = HookNoFlags) + : hook_(subhook_new(src, dst, (subhook_flags_t)flags)) + { + } + + ~Hook() { + subhook_remove(hook_); + subhook_free(hook_); + } + + void *GetSrc() const { return subhook_get_src(hook_); } + void *GetDst() const { return subhook_get_dst(hook_); } + void *GetTrampoline() const { return subhook_get_trampoline(hook_); } + + bool Install() { + return subhook_install(hook_) >= 0; + } + + bool Install(void *src, + void *dst, + HookFlags flags = HookNoFlags) { + if (hook_ == 0) { + hook_ = subhook_new(src, dst, (subhook_flags_t)flags); + } + return Install(); + } + + bool Remove() { + return subhook_remove(hook_) >= 0; + } + + bool IsInstalled() const { + return !!subhook_is_installed(hook_); + } + + private: + Hook(const Hook &); + void operator=(const Hook &); + + private: + subhook_t hook_; +}; + +class ScopedHookRemove { + public: + ScopedHookRemove(Hook *hook) + : hook_(hook) + , removed_(hook_->Remove()) + { + } + + ~ScopedHookRemove() { + if (removed_) { + hook_->Install(); + } + } + + private: + ScopedHookRemove(const ScopedHookRemove &); + void operator=(const ScopedHookRemove &); + + private: + Hook *hook_; + bool removed_; +}; + +class ScopedHookInstall { + public: + ScopedHookInstall(Hook *hook) + : hook_(hook) + , installed_(hook_->Install()) + { + } + + ScopedHookInstall(Hook *hook, + void *src, + void *dst, + HookFlags flags = HookNoFlags) + : hook_(hook) + , installed_(hook_->Install(src, dst, flags)) + { + } + + ~ScopedHookInstall() { + if (installed_) { + hook_->Remove(); + } + } + + private: + ScopedHookInstall(const ScopedHookInstall &); + void operator=(const ScopedHookInstall &); + + private: + Hook *hook_; + bool installed_; +}; + +} // namespace subhook + +#endif /* __cplusplus */ + +#endif /* SUBHOOK_H */ diff --git a/src/core/subhook/subhook_private.h b/src/core/subhook/subhook_private.h new file mode 100644 index 0000000..9d54781 --- /dev/null +++ b/src/core/subhook/subhook_private.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SUBHOOK_PRIVATE_H +#define SUBHOOK_PRIVATE_H + +#include + +#ifndef true + #define true 1 +#endif +#ifndef false + #define false 0 +#endif + +struct subhook_struct { + int installed; + void *src; + void *dst; + subhook_flags_t flags; + void *code; + void *trampoline; + size_t jmp_size; + size_t trampoline_size; + size_t trampoline_len; +}; + +void *subhook_unprotect(void *address, size_t size); + +#endif /* SUBHOOK_PRIVATE_H */ diff --git a/src/core/subhook/subhook_unix.c b/src/core/subhook/subhook_unix.c new file mode 100644 index 0000000..31f927e --- /dev/null +++ b/src/core/subhook/subhook_unix.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +void *subhook_unprotect(void *address, size_t size) { + long pagesize; + + pagesize = sysconf(_SC_PAGESIZE); + address = (void *)((long)address & ~(pagesize - 1)); + + if (mprotect(address, size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0) { + return address; + } else { + return NULL; + } +} diff --git a/src/core/subhook/subhook_windows.c b/src/core/subhook/subhook_windows.c new file mode 100644 index 0000000..b1f0be6 --- /dev/null +++ b/src/core/subhook/subhook_windows.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +void *subhook_unprotect(void *address, size_t size) { + DWORD old; + + if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old) != 0) { + return address; + } else { + return NULL; + } +} diff --git a/src/core/subhook/subhook_x86.c b/src/core/subhook/subhook_x86.c new file mode 100644 index 0000000..7c36b82 --- /dev/null +++ b/src/core/subhook/subhook_x86.c @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2012-2018 Zeex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "subhook.h" +#include "subhook_private.h" + +#ifdef SUBHOOK_WINDOWS + #define INT32_MAX 0x7fffffff + #define INT32_MIN (-INT32_MAX - 1) + typedef unsigned __int8 uint8_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef __int64 int64_t; + #ifdef SUBHOOK_X86_64 + typedef __int64 intptr_t; + typedef unsigned __int64 uintptr_t; + #else + typedef __int32 intptr_t; + typedef unsigned __int32 uintptr_t; + #endif +#else + #include +#endif + +#define ABS(x) ((x) >= 0 ? (x) : -(x)) +#define MAX_INSN_LEN 15 /* maximum length of x86 instruction */ + +#define JMP_OPCODE 0xE9 +#define PUSH_OPCODE 0x68 +#define MOV_OPCODE 0xC7 +#define RET_OPCODE 0xC3 + +#define JMP64_MOV_MODRM 0x44 /* write to address + 1 byte displacement */ +#define JMP64_MOV_SIB 0x24 /* write to [rsp] */ +#define JMP64_MOV_OFFSET 0x04 + +#pragma pack(push, 1) + +struct subhook_jmp32 { + uint8_t opcode; + int32_t offset; +}; + +/* Since AMD64 doesn't support 64-bit direct jumps, we'll push the address + * onto the stack, then call RET. + */ +struct subhook_jmp64 { + uint8_t push_opcode; + uint32_t push_addr; /* lower 32-bits of the address to jump to */ + uint8_t mov_opcode; + uint8_t mov_modrm; + uint8_t mov_sib; + uint8_t mov_offset; + uint32_t mov_addr; /* upper 32-bits of the address to jump to */ + uint8_t ret_opcode; +}; + +#pragma pack(pop) + +extern subhook_disasm_handler_t subhook_disasm_handler; + +static int subhook_disasm(void *src, int *reloc_op_offset) { + enum flags { + MODRM = 1, + PLUS_R = 1 << 1, + REG_OPCODE = 1 << 2, + IMM8 = 1 << 3, + IMM16 = 1 << 4, + IMM32 = 1 << 5, + RELOC = 1 << 6 + }; + + static uint8_t prefixes[] = { + 0xF0, 0xF2, 0xF3, + 0x2E, 0x36, 0x3E, 0x26, 0x64, 0x65, + 0x66, /* operand size override */ + 0x67 /* address size override */ + }; + + struct opcode_info { + uint8_t opcode; + uint8_t reg_opcode; + unsigned int flags; + }; + + /* + * See the Intel Developer Manual volumes 2a and 2b for more information + * about instruction format and encoding: + * + * https://www-ssl.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html + */ + static struct opcode_info opcodes[] = { + /* ADD AL, imm8 */ {0x04, 0, IMM8}, + /* ADD EAX, imm32 */ {0x05, 0, IMM32}, + /* ADD r/m8, imm8 */ {0x80, 0, MODRM | REG_OPCODE | IMM8}, + /* ADD r/m32, imm32 */ {0x81, 0, MODRM | REG_OPCODE | IMM32}, + /* ADD r/m32, imm8 */ {0x83, 0, MODRM | REG_OPCODE | IMM8}, + /* ADD r/m8, r8 */ {0x00, 0, MODRM}, + /* ADD r/m32, r32 */ {0x01, 0, MODRM}, + /* ADD r8, r/m8 */ {0x02, 0, MODRM}, + /* ADD r32, r/m32 */ {0x03, 0, MODRM}, + /* AND AL, imm8 */ {0x24, 0, IMM8}, + /* AND EAX, imm32 */ {0x25, 0, IMM32}, + /* AND r/m8, imm8 */ {0x80, 4, MODRM | REG_OPCODE | IMM8}, + /* AND r/m32, imm32 */ {0x81, 4, MODRM | REG_OPCODE | IMM32}, + /* AND r/m32, imm8 */ {0x83, 4, MODRM | REG_OPCODE | IMM8}, + /* AND r/m8, r8 */ {0x20, 0, MODRM}, + /* AND r/m32, r32 */ {0x21, 0, MODRM}, + /* AND r8, r/m8 */ {0x22, 0, MODRM}, + /* AND r32, r/m32 */ {0x23, 0, MODRM}, + /* CALL rel32 */ {0xE8, 0, IMM32 | RELOC}, + /* CALL r/m32 */ {0xFF, 2, MODRM | REG_OPCODE}, + /* CMP r/m16/32, imm8*/ {0x83, 7, MODRM | REG_OPCODE | IMM8 }, + /* DEC r/m16/32 */ {0xFF, 1, MODRM | REG_OPCODE }, + /* ENTER imm16, imm8 */ {0xC8, 0, IMM16 | IMM8}, + /* INT 3 */ {0xCC, 0, 0}, + /* JMP rel32 */ {0xE9, 0, IMM32 | RELOC}, + /* JMP r/m32 */ {0xFF, 4, MODRM | REG_OPCODE}, + /* LEA r32,m */ {0x8D, 0, MODRM}, + /* LEAVE */ {0xC9, 0, 0}, + /* MOV r/m8,r8 */ {0x88, 0, MODRM}, + /* MOV r/m32,r32 */ {0x89, 0, MODRM}, + /* MOV r8,r/m8 */ {0x8A, 0, MODRM}, + /* MOV r32,r/m32 */ {0x8B, 0, MODRM}, + /* MOV r/m16,Sreg */ {0x8C, 0, MODRM}, + /* MOV Sreg,r/m16 */ {0x8E, 0, MODRM}, + /* MOV AL,moffs8 */ {0xA0, 0, IMM8}, + /* MOV EAX,moffs32 */ {0xA1, 0, IMM32}, + /* MOV moffs8,AL */ {0xA2, 0, IMM8}, + /* MOV moffs32,EAX */ {0xA3, 0, IMM32}, + /* MOV r8, imm8 */ {0xB0, 0, PLUS_R | IMM8}, + /* MOV r32, imm32 */ {0xB8, 0, PLUS_R | IMM32}, + /* MOV r/m8, imm8 */ {0xC6, 0, MODRM | REG_OPCODE | IMM8}, + /* MOV r/m32, imm32 */ {0xC7, 0, MODRM | REG_OPCODE | IMM32}, + /* NOP */ {0x90, 0, 0}, + /* OR AL, imm8 */ {0x0C, 0, IMM8}, + /* OR EAX, imm32 */ {0x0D, 0, IMM32}, + /* OR r/m8, imm8 */ {0x80, 1, MODRM | REG_OPCODE | IMM8}, + /* OR r/m32, imm32 */ {0x81, 1, MODRM | REG_OPCODE | IMM32}, + /* OR r/m32, imm8 */ {0x83, 1, MODRM | REG_OPCODE | IMM8}, + /* OR r/m8, r8 */ {0x08, 0, MODRM}, + /* OR r/m32, r32 */ {0x09, 0, MODRM}, + /* OR r8, r/m8 */ {0x0A, 0, MODRM}, + /* OR r32, r/m32 */ {0x0B, 0, MODRM}, + /* POP r/m32 */ {0x8F, 0, MODRM | REG_OPCODE}, + /* POP r32 */ {0x58, 0, PLUS_R}, + /* PUSH r/m32 */ {0xFF, 6, MODRM | REG_OPCODE}, + /* PUSH r32 */ {0x50, 0, PLUS_R}, + /* PUSH imm8 */ {0x6A, 0, IMM8}, + /* PUSH imm32 */ {0x68, 0, IMM32}, + /* RET */ {0xC3, 0, 0}, + /* RET imm16 */ {0xC2, 0, IMM16}, + /* SUB AL, imm8 */ {0x2C, 0, IMM8}, + /* SUB EAX, imm32 */ {0x2D, 0, IMM32}, + /* SUB r/m8, imm8 */ {0x80, 5, MODRM | REG_OPCODE | IMM8}, + /* SUB r/m32, imm32 */ {0x81, 5, MODRM | REG_OPCODE | IMM32}, + /* SUB r/m32, imm8 */ {0x83, 5, MODRM | REG_OPCODE | IMM8}, + /* SUB r/m8, r8 */ {0x28, 0, MODRM}, + /* SUB r/m32, r32 */ {0x29, 0, MODRM}, + /* SUB r8, r/m8 */ {0x2A, 0, MODRM}, + /* SUB r32, r/m32 */ {0x2B, 0, MODRM}, + /* TEST AL, imm8 */ {0xA8, 0, IMM8}, + /* TEST EAX, imm32 */ {0xA9, 0, IMM32}, + /* TEST r/m8, imm8 */ {0xF6, 0, MODRM | REG_OPCODE | IMM8}, + /* TEST r/m32, imm32 */ {0xF7, 0, MODRM | REG_OPCODE | IMM32}, + /* TEST r/m8, r8 */ {0x84, 0, MODRM}, + /* TEST r/m32, r32 */ {0x85, 0, MODRM}, + /* XOR AL, imm8 */ {0x34, 0, IMM8}, + /* XOR EAX, imm32 */ {0x35, 0, IMM32}, + /* XOR r/m8, imm8 */ {0x80, 6, MODRM | REG_OPCODE | IMM8}, + /* XOR r/m32, imm32 */ {0x81, 6, MODRM | REG_OPCODE | IMM32}, + /* XOR r/m32, imm8 */ {0x83, 6, MODRM | REG_OPCODE | IMM8}, + /* XOR r/m8, r8 */ {0x30, 0, MODRM}, + /* XOR r/m32, r32 */ {0x31, 0, MODRM}, + /* XOR r8, r/m8 */ {0x32, 0, MODRM}, + /* XOR r32, r/m32 */ {0x33, 0, MODRM} + }; + + uint8_t *code = src; + size_t i; + int len = 0; + int operand_size = 4; + uint8_t opcode = 0; + int found_opcode = false; + + for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++) { + if (code[len] == prefixes[i]) { + len++; + if (prefixes[i] == 0x66) { + operand_size = 2; + } + } + } + +#ifdef SUBHOOK_X86_64 + if ((code[len] & 0xF0) == 0x40) { + /* This is a REX prefix (40H - 4FH). REX prefixes are valid only in + * 64-bit mode. + */ + uint8_t rex = code[len++]; + + if (rex & 8) { + /* REX.W changes size of immediate operand to 64 bits. */ + operand_size = 8; + } + } +#endif + + for (i = 0; i < sizeof(opcodes) / sizeof(*opcodes); i++) { + if (code[len] == opcodes[i].opcode) { + if (opcodes[i].flags & REG_OPCODE) { + found_opcode = ((code[len + 1] >> 3) & 7) == opcodes[i].reg_opcode; + } else { + found_opcode = true; + } + } + + if ((opcodes[i].flags & PLUS_R) + && (code[len] & 0xF8) == opcodes[i].opcode) { + found_opcode = true; + } + + if (found_opcode) { + opcode = code[len++]; + break; + } + } + + if (!found_opcode) { + return 0; + } + + if (reloc_op_offset != NULL && opcodes[i].flags & RELOC) { + *reloc_op_offset = len; /* relative call or jump */ + } + + if (opcodes[i].flags & MODRM) { + uint8_t modrm = code[len++]; /* +1 for Mod/RM byte */ + uint8_t mod = modrm >> 6; + uint8_t rm = modrm & 7; + + if (mod != 3 && rm == 4) { + uint8_t sib = code[len++]; /* +1 for SIB byte */ + uint8_t base = sib & 7; + + if (base == 5) { + /* The SIB is followed by a disp32 with no base if the MOD is 00B. + * Otherwise, disp8 or disp32 + [EBP]. + */ + if (mod == 1) { + len += 1; /* for disp8 */ + } else { + len += 4; /* for disp32 */ + } + } + } + +#ifdef SUBHOOK_X86_64 + if (reloc_op_offset != NULL && rm == 5) { + *reloc_op_offset = (int32_t)len; /* RIP-relative addressing */ + } +#endif + + if (mod == 1) { + len += 1; /* for disp8 */ + } + if (mod == 2 || (mod == 0 && rm == 5)) { + len += 4; /* for disp32 */ + } + } + + if (opcodes[i].flags & IMM8) { + len += 1; + } + if (opcodes[i].flags & IMM16) { + len += 2; + } + if (opcodes[i].flags & IMM32) { + len += operand_size; + } + + return len; +} + +static size_t subhook_get_jmp_size(subhook_flags_t flags) { +#ifdef SUBHOOK_X86_64 + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { + return sizeof(struct subhook_jmp64); + } +#else + (void)flags; +#endif + return sizeof(struct subhook_jmp32); +} + +static int subhook_make_jmp32(void *src, void *dst) { + struct subhook_jmp32 *jmp = (struct subhook_jmp32 *)src; + intptr_t src_addr = (intptr_t)src; + intptr_t dst_addr = (intptr_t)dst; +#ifdef SUBHOOK_X86_64 + int64_t distance = ABS(src_addr - dst_addr); +#endif + +#ifdef SUBHOOK_X86_64 + if (distance < INT32_MIN || distance > INT32_MAX) { + return -EOVERFLOW; + } +#endif + + jmp->opcode = JMP_OPCODE; + jmp->offset = (int32_t)(dst_addr - (src_addr + sizeof(*jmp))); + + return 0; +} + +#ifdef SUBHOOK_X86_64 + +static int subhook_make_jmp64(void *src, void *dst) { + struct subhook_jmp64 *jmp = (struct subhook_jmp64 *)src; + + jmp->push_opcode = PUSH_OPCODE; + jmp->push_addr = (uint32_t)(uintptr_t)dst; /* truncate */ + jmp->mov_opcode = MOV_OPCODE; + jmp->mov_modrm = JMP64_MOV_MODRM; + jmp->mov_sib = JMP64_MOV_SIB; + jmp->mov_offset = JMP64_MOV_OFFSET; + jmp->mov_addr = (uint32_t)(((uintptr_t)dst) >> 32); + jmp->ret_opcode = RET_OPCODE; + + return 0; +} + +#endif + +static int subhook_make_jmp(void *src, + void *dst, + subhook_flags_t flags) { +#ifdef SUBHOOK_X86_64 + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { + return subhook_make_jmp64(src, dst); + } +#else + (void)flags; +#endif + return subhook_make_jmp32(src, dst); +} + +static int subhook_make_trampoline(void *trampoline, + void *src, + size_t jmp_size, + size_t *trampoline_len, + subhook_flags_t flags) { + size_t orig_size = 0; + size_t insn_len; + intptr_t trampoline_addr = (intptr_t)trampoline; + intptr_t src_addr = (intptr_t)src; + subhook_disasm_handler_t disasm_handler = + subhook_disasm_handler != NULL ? subhook_disasm_handler : subhook_disasm; + + assert(trampoline_len != NULL); + + /* Determine how many bytes of original code needs to be copied over + * to the trampoline. + */ + while (orig_size < jmp_size) { + int reloc_op_offset = 0; + + insn_len = + disasm_handler((void *)(src_addr + orig_size), &reloc_op_offset); + + if (insn_len == 0) { + return -EINVAL; + } + + /* Copy this instruction to the trampoline. + */ + memcpy((void *)(trampoline_addr + orig_size), + (void *)(src_addr + orig_size), + insn_len); + + /* If the operand is a relative address, such as found in calls or + * jumps, it needs to be relocated because the original code and the + * trampoline reside at different locations in memory. + */ + if (reloc_op_offset > 0) { + /* Calculate how far our trampoline is from the source and change + * the address accordingly. + */ + int32_t offset = (int32_t)(trampoline_addr - src_addr); + int32_t *op = (int32_t *)(trampoline_addr + orig_size + reloc_op_offset); + *op -= offset; + } + + orig_size += insn_len; + } + + *trampoline_len = orig_size + jmp_size; + + /* Insert the final jump. It goes back to the original code at + * src + orig_size. + */ + return subhook_make_jmp((void *)(trampoline_addr + orig_size), + (void *)(src_addr + orig_size), + flags); +} + +SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, + void *dst, + subhook_flags_t flags) { + subhook_t hook; + + if ((hook = malloc(sizeof(*hook))) == NULL) { + return NULL; + } + + hook->installed = 0; + hook->src = src; + hook->dst = dst; + hook->flags = flags; + hook->jmp_size = subhook_get_jmp_size(hook->flags); + hook->trampoline_size = hook->jmp_size * 2 + MAX_INSN_LEN; + hook->trampoline_len = 0; + + if ((hook->code = malloc(hook->jmp_size)) == NULL) { + free(hook); + return NULL; + } + + memcpy(hook->code, hook->src, hook->jmp_size); + + if ((hook->trampoline = calloc(1, hook->trampoline_size)) == NULL) { + free(hook->code); + free(hook); + return NULL; + } + + if (subhook_unprotect(hook->src, hook->jmp_size) == NULL + || subhook_unprotect(hook->trampoline, hook->trampoline_size) == NULL) + { + free(hook->trampoline); + free(hook->code); + free(hook); + return NULL; + } + + subhook_make_trampoline( + hook->trampoline, + hook->src, + hook->jmp_size, + &hook->trampoline_len, + hook->flags); + + if (hook->trampoline_len == 0) { + free(hook->trampoline); + hook->trampoline = NULL; + } + + return hook; +} + +SUBHOOK_EXPORT void SUBHOOK_API subhook_free(subhook_t hook) { + if (hook == NULL) { + return; + } + free(hook->trampoline); + free(hook->code); + free(hook); +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_install(subhook_t hook) { + int error; + + if (hook == NULL) { + return -EINVAL; + } + if (hook->installed) { + return -EINVAL; + } + + error = subhook_make_jmp(hook->src, hook->dst, hook->flags); + if (error >= 0) { + hook->installed = true; + return 0; + } + + return error; +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_remove(subhook_t hook) { + if (hook == NULL) { + return -EINVAL; + } + if (!hook->installed) { + return -EINVAL; + } + + memcpy(hook->src, hook->code, hook->jmp_size); + hook->installed = 0; + + return 0; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_read_dst(void *src) { + struct subhook_jmp32 *maybe_jmp32 = (struct subhook_jmp32 *)src; +#ifdef SUBHOOK_X86_64 + struct subhook_jmp64 *maybe_jmp64 = (struct subhook_jmp64 *)src; +#endif + + if (maybe_jmp32->opcode == JMP_OPCODE) { + return (void *)( + maybe_jmp32->offset + (uintptr_t)src + sizeof(*maybe_jmp32)); + } + +#ifdef SUBHOOK_X86_64 + if (maybe_jmp64->push_opcode == PUSH_OPCODE + && maybe_jmp64->mov_opcode == MOV_OPCODE + && maybe_jmp64->mov_modrm == JMP64_MOV_MODRM + && maybe_jmp64->mov_sib == JMP64_MOV_SIB + && maybe_jmp64->mov_offset == JMP64_MOV_OFFSET + && maybe_jmp64->ret_opcode == RET_OPCODE) { + return (void *)( + maybe_jmp64->push_addr & ((uintptr_t)maybe_jmp64->mov_addr << 32)); + } +#endif + + return NULL; +} diff --git a/src/demo_writer/demo_writer.cpp b/src/demo_writer/demo_writer.cpp new file mode 100644 index 0000000..ef7ed54 --- /dev/null +++ b/src/demo_writer/demo_writer.cpp @@ -0,0 +1,212 @@ +#include "demo_writer.h" +#include "write_primitive.h" +#include "write_objects.h" +#include "state_tracking.h" +#include "../core/core.h" +#include + +std::ofstream demo_file_handle; + +Value parse_ref(const char* ref) { + Value val; + val.type = 0; + val.value = 0; + + unsigned int refy_bit = 0; + if(ref[0] != '[' || ref[1] != '0' || ref[2] != 'x') return val; + ref += 3; + while(*ref != ']' && *ref != 0) { + if(*ref >= '0' && *ref <= '9') { + refy_bit = (refy_bit << 4) | (*ref - '0'); + } else if(*ref >= 'a' && *ref <= 'f') { + refy_bit = (refy_bit << 4) | (*ref - 'a' + 0xa); + } else if(*ref >= 'A' && *ref <= 'F') { + refy_bit = (refy_bit << 4) | (*ref - 'A' + 0xa); + } + ref++; + } + if(ref[0] != ']' || ref[1] != 0) return val; + val.type = (refy_bit >> 24); + val.value = (refy_bit & 0xFFFFFF); + return val; +} + + +const char* pixel_things[] = { "pixel_x", "pixel_y", "pixel_w", "pixel_z" }; + +#define SET_PIXEL_HOOK(X) SetPixel##X##Ptr oSetPixel##X; void hSetPixel##X(Value atom, short pixel_x) {mark_atom_dirty(atom); oSetPixel##X (atom, pixel_x);} +/*SetPixelXPtr oSetPixelX; +void hSetPixel(Value atom, short pixel_x) { + mark_atom_dirty(atom); + oSetPixelX(atom, pixel_x); +}*/ +SET_PIXEL_HOOK(X) +SET_PIXEL_HOOK(Y) +SET_PIXEL_HOOK(W) +SET_PIXEL_HOOK(Z) + +SetMovableDirPtr oSetMovableDir; +void hSetMovableDir(Value atom, unsigned char dir) { + mark_atom_dirty(atom); + oSetMovableDir(atom, dir); +} + +SetLocPtr oSetLoc; +void hSetLoc(Value atom, Value loc) { + mark_atom_dirty(atom); + oSetLoc(atom, loc); +} + +SetAppearancePtr oSetAppearance; +void hSetAppearance(Value atom, int appearance) { + oSetAppearance(atom, appearance); + mark_atom_dirty(atom); +} + +SpliceAppearancePtr oSpliceAppearance; +void __fastcall hSpliceAppearance(void* this_, int edx, int appearance) { + get_demo_id_flags(appearance).appearance_written = false; + oSpliceAppearance(this_, edx, appearance); +} + +SpliceStringPtr oSpliceString; +void hSpliceString(unsigned int id) { + get_demo_id_flags(id).string_written = false; + oSpliceString(id); +} + +AllocObjIdPtr oAllocObjId; +int hAllocObjId() { + int id = oAllocObjId(); + mark_atom_dirty({ OBJ, {id} }); + return id; +} + +AllocMobIdPtr oAllocMobId; +int hAllocMobId() { + int id = oAllocMobId(); + mark_atom_dirty({ MOB, {id} }); + return id; +} + +ChangeTurfPtr oChangeTurf; +void hChangeTurf(int a, int b, int type, Value turf) { + oChangeTurf(a, b, type, turf); + mark_atom_dirty(turf); +} + +char return_buf[64]; + +bool demo_started = false; +extern "C" EXPORT const char* demo_start(int n_args, const char** args) +{ + if (demo_started) return "Demo already started"; + if (n_args < 2) return "Missing arguments"; + + const char* out_file = args[0]; + const char* revdata = args[1]; + + demo_file_handle.open(out_file, std::ios::binary | std::ios::trunc); + if (demo_file_handle.fail()) { + return "Failed to open file"; + } + + if (!(Core::find_functions())) + { + return "Couldn't find functions"; + } + + oSetAppearance = Core::install_hook(SetAppearance, hSetAppearance); + oSpliceAppearance = Core::install_hook(SpliceAppearance, hSpliceAppearance); + oSpliceString = Core::install_hook(SpliceString, hSpliceString); + //oFlick = Core::install_hook(Flick, hFlick); + //oAnimate = Core::install_hook(Animate, hAnimate); + oSetPixelX = Core::install_hook(SetPixelX, hSetPixelX); + oSetPixelY = Core::install_hook(SetPixelY, hSetPixelY); + oSetPixelW = Core::install_hook(SetPixelW, hSetPixelW); + oSetPixelZ = Core::install_hook(SetPixelZ, hSetPixelZ); + oSetMovableDir = Core::install_hook(SetMovableDir, hSetMovableDir); + oSetLoc = Core::install_hook(SetLoc, hSetLoc); + oAllocObjId = Core::install_hook(AllocObjId, hAllocObjId); + oAllocMobId = Core::install_hook(AllocMobId, hAllocMobId); + oChangeTurf = Core::install_hook(ChangeTurf, hChangeTurf); + + demo_file_handle.put(0xCB); + demo_file_handle.put(0x0); // version number + demo_file_handle.put(0x0); + + while (*revdata != 0) { + demo_file_handle.put(*revdata); + revdata++; + } + demo_file_handle.put(0x0); + std::streampos demo_loadsize_ptr = demo_file_handle.tellp(); + demo_file_handle.put(0); + demo_file_handle.put(0); + demo_file_handle.put(0); + demo_file_handle.put(0); + + demo_time_override_enabled = true; + demo_time_override = 0; + + write_world_size(); + flush_atom_updates(); + + demo_time_override_enabled = false; + + std::streampos end_ptr = demo_file_handle.tellp(); + unsigned int loadsize = end_ptr; + demo_file_handle.seekp(demo_loadsize_ptr); + demo_file_handle.write((const char*)&loadsize, 4); + demo_file_handle.seekp(end_ptr); + demo_file_handle.flush(); + + demo_started = true; + return "SUCCESS"; +} + +extern "C" EXPORT const char* demo_embed_resource(int n_args, const char** args) { + if (n_args < 2 || !demo_started) return "FAIL"; + Value resource = parse_ref(args[0]); + if (resource.type != RESOURCE || resource.value == 0xFFFF) return "FAIL"; + std::ifstream file(args[1], std::ios::binary | std::ios::ate); + unsigned int size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buf(size); + file.read(buf.data(), size); + if (file.good()) { + file.close(); + demo_file_handle.put(0xA); + write_vlq(4 + buf.size()); + demo_file_handle.write((char*)&resource.value, 4); + demo_file_handle.write((char*)&buf[0], buf.size()); + return "SUCCESS"; + } + file.close(); + return "FAIL"; +} + +extern "C" EXPORT const char* demo_end(int n_args, const char** args) { + if (!demo_started) return ""; + flush_atom_updates(); + demo_file_handle.close(); + demo_started = false; + Core::remove_all_hooks(); + return ""; +} + +extern "C" EXPORT const char* demo_flush(int n_args, const char** args) { + if (!demo_started) return "0"; + flush_atom_updates(); + demo_file_handle.flush(); + int size = demo_file_handle.tellp(); + snprintf(return_buf, sizeof(return_buf), "%d", size); + return return_buf; +} + +extern "C" EXPORT const char* demo_get_size(int n_args, const char** args) { + if (!demo_started) return "0"; + int size = demo_file_handle.tellp(); + snprintf(return_buf, sizeof(return_buf), "%d", size); + return return_buf; +} diff --git a/src/demo_writer/demo_writer.h b/src/demo_writer/demo_writer.h new file mode 100644 index 0000000..9ccbd17 --- /dev/null +++ b/src/demo_writer/demo_writer.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +extern std::ofstream demo_file_handle; diff --git a/src/demo_writer/state_tracking.cpp b/src/demo_writer/state_tracking.cpp new file mode 100644 index 0000000..6942d75 --- /dev/null +++ b/src/demo_writer/state_tracking.cpp @@ -0,0 +1,8 @@ +#include "state_tracking.h" + +std::vector demo_id_flags; +DemoWriterIdFlags& get_demo_id_flags(int id) { + if (demo_id_flags.size() <= id) + demo_id_flags.resize(id + 1); + return demo_id_flags[id]; +} \ No newline at end of file diff --git a/src/demo_writer/state_tracking.h b/src/demo_writer/state_tracking.h new file mode 100644 index 0000000..0925f89 --- /dev/null +++ b/src/demo_writer/state_tracking.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include "../core/core.h" + +struct alignas(1) DemoWriterIdFlags { + union { + struct { + // as you can see I care about memory usage... lol + unsigned char string_written : 1; + unsigned char appearance_written : 1; + unsigned char obj_written : 1; + unsigned char mob_written : 1; + unsigned char turf_written : 1; + unsigned char resource_written : 1; + }; + unsigned char byte = 0; + }; + template inline bool get_written(); + template<> inline bool get_written() { return obj_written; } + template<> inline bool get_written() { return mob_written; } + template<> inline bool get_written() { return turf_written; } + + template inline void set_written(bool f); + template<> inline void set_written(bool f) { obj_written = f; } + template<> inline void set_written(bool f) { mob_written = f; } + template<> inline void set_written(bool f) { turf_written = f; } +}; + +extern std::vector demo_id_flags; +DemoWriterIdFlags& get_demo_id_flags(int id); \ No newline at end of file diff --git a/src/demo_writer/write_appearance.cpp b/src/demo_writer/write_appearance.cpp new file mode 100644 index 0000000..8c708de --- /dev/null +++ b/src/demo_writer/write_appearance.cpp @@ -0,0 +1,240 @@ +#include "write_appearance.h" +#include "write_primitive.h" +#include "state_tracking.h" +#include "../core/core.h" + +struct alignas(1) DemoAppearanceFlags { + union { + struct { + unsigned char dir : 3; + unsigned char opacity : 1; + unsigned char density : 1; + unsigned char gender : 2; + unsigned char dir_override : 1; + + unsigned char include_name : 1; + unsigned char include_desc : 1; + unsigned char include_screen_loc : 1; + unsigned char include_maptext : 1; + unsigned char include_overlays_and_underlays : 1; + unsigned char include_layer : 1; + unsigned char include_invisibility : 1; + unsigned char include_glide_size : 1; + + unsigned char include_pixel_xy : 1; + unsigned char include_pixel_wz : 1; + unsigned char include_plane : 1; + unsigned char include_transform : 1; + unsigned char include_color_alpha : 1; + unsigned char include_color_matrix : 1; + unsigned char animate_movement : 2; + + unsigned char blend_mode : 3; + unsigned char mouse_opacity : 2; + }; + unsigned char bytes[4] = { 0,0,0,0 }; + }; +}; + +struct alignas(1) ColorMatrixFormat { + union { + struct { + unsigned char is_float : 1; + unsigned char include_cr_cg_cb : 1; + unsigned char include_ca : 1; + unsigned char ca_one : 1; + unsigned char include_ar_ag_ab : 1; + unsigned char include_aa : 1; + unsigned char include_ra_ga_ba : 1; + unsigned char include_color : 1; + }; + unsigned char byte = 0; + }; +}; + +int dir_encode_lut[] = { + 0, 1, 0, 0, + 2, 6, 4, 0, + 3, 7, 5, 0, + 0, 0, 0, 0, +}; + +int write_appearance(std::vector &buf, int appearance_id) { + if (appearance_id == 0xFFFF || appearance_id >= (**Core::appearance_table).length) { + write_primitive(buf, 0xFFFF); + return 0xFFFF; + } + Appearance* appearance = (**Core::appearance_table).elements[appearance_id]; + if (!appearance) { + write_primitive(buf, 0xFFFF); + return 0xFFFF; + } + DemoWriterIdFlags& dif = get_demo_id_flags(appearance_id); + bool do_write = !dif.appearance_written; + dif.appearance_written = 1; + write_primitive(buf, (appearance_id & 0xFFFFFF) | (do_write << 24)); + if (!do_write) { + return appearance_id; + } + auto daf = write_primitive(buf, DemoAppearanceFlags()); + daf->dir = dir_encode_lut[appearance->dir & 0xF]; + daf->opacity = appearance->opacity; + daf->density = appearance->density; + daf->gender = appearance->gender; + daf->dir_override = appearance->dir_override; + daf->animate_movement = (appearance->animate_movement + 1) & 3; + daf->blend_mode = appearance->blend_mode; + daf->mouse_opacity = appearance->mouse_opacity; + write_primitive(buf, (unsigned short)appearance->appearance_flags); + if (appearance->name_str != 0xFFFF) { + daf->include_name = true; + write_byond_string(buf, appearance->name_str); + } + if (appearance->desc_str != 0xFFFF) { + daf->include_desc = true; + write_byond_string(buf, appearance->desc_str); + } + if (appearance->screen_loc_str != 0xFFFF) { + daf->include_screen_loc = true; + write_byond_string(buf, appearance->screen_loc_str); + } + write_byond_resourceid(buf, appearance->icon_res); + write_byond_string(buf, appearance->icon_state_str); + if (appearance->maptext_str != 0xFFFF) { + daf->include_maptext = true; + write_byond_string(buf, appearance->maptext_str); + write_primitive(buf, appearance->maptext_x); + write_primitive(buf, appearance->maptext_y); + write_primitive(buf, appearance->maptext_width); + write_primitive(buf, appearance->maptext_height); + } + if (appearance->overlays_list != 0xFFFF || appearance->underlays_list != 0xFFFF) { + daf->include_overlays_and_underlays = true; + int o_id = appearance->overlays_list; + int u_id = appearance->underlays_list; + TableHolder2 *id_lists = Core::appearance_list_table; + if (o_id != 0xFFFF && id_lists->length > o_id && id_lists->elements[o_id]) { + AppearanceList* l = (AppearanceList*)id_lists->elements[o_id]; + write_vlq(buf, l->len); + for (int i = 0; i < l->len; i++) write_appearance(buf, l->ids[i]); + } + else { + buf.push_back(0); + } + if (u_id != 0xFFFF && id_lists->length > u_id&& id_lists->elements[u_id]) { + AppearanceList* l = (AppearanceList*)id_lists->elements[u_id]; + write_vlq(buf, l->len); + for (int i = 0; i < l->len; i++) write_appearance(buf, l->ids[i]); + } + else { + buf.push_back(0); + } + } + if (appearance->invisibility > 0) { + daf->include_invisibility = true; + buf.push_back(appearance->invisibility); + } + if (appearance->pixel_x != 0 || appearance->pixel_y != 0) { + daf->include_pixel_xy = true; + write_primitive(buf, appearance->pixel_x); + write_primitive(buf, appearance->pixel_y); + } + if (appearance->pixel_w != 0 || appearance->pixel_z != 0) { + daf->include_pixel_wz = true; + write_primitive(buf, appearance->pixel_w); + write_primitive(buf, appearance->pixel_z); + } + if (appearance->glide_size != 0) { + daf->include_glide_size = true; + write_primitive(buf, appearance->glide_size); + } + if (appearance->layer != 3) { + daf->include_layer = true; + write_primitive(buf, appearance->layer); + } + if (appearance->plane != -32767) { + daf->include_plane = true; + write_primitive(buf, appearance->plane); + } + if ( + !appearance->matrix_flag + || appearance->transform[0] != 1 + || appearance->transform[1] != 0 + || appearance->transform[2] != 0 + || appearance->transform[3] != 0 + || appearance->transform[4] != 1 + || appearance->transform[5] != 0) { + daf->include_transform = true; + for (int i = 0; i < 6; i++) { + write_primitive(buf, appearance->transform[i]); + } + } + if (appearance->alpha != 255 || (appearance->color_matrix == nullptr && (appearance->color_r != 255 || appearance->color_g != 255 || appearance->color_b != 255))) { + daf->include_color_alpha = true; + write_primitive(buf, appearance->color_alpha); + } + if (appearance->color_matrix != nullptr) { + daf->include_color_matrix = true; + float* cm = appearance->color_matrix; + ColorMatrixFormat cmf; + for (int i = 0; i < 20; i++) { + if (cm[i] < 0.0f || cm[i] > 1.0f) { + cmf.is_float = true; + break; + } + } + if (cm[16] != 0 || cm[17] != 0 || cm[18] != 0) cmf.include_cr_cg_cb = true; + if (cm[19] != 0) { + if (cm[19] == 1) cmf.ca_one = true; + else cmf.include_ca = true; + } + if (cm[12] != 0 || cm[13] != 0 || cm[14] != 0) cmf.include_ar_ag_ab = true; + if (cm[15] != 1) cmf.include_aa = true; + if (cm[3] != 0 || cm[7] != 0 || cm[11] != 0) cmf.include_ra_ga_ba = true; + if (cm[0] != cm[1] || cm[1] != cm[2] || cm[4] != cm[5] || cm[5] != cm[6] || cm[8] != cm[9] || cm[9] != cm[10] || cm[12] != cm[13] || cm[13] != cm[14]) cmf.include_color = true; + write_primitive(buf, cmf); + if (cmf.is_float) { + write_primitive(buf, cm[0]); + if (cmf.include_color) { write_primitive(buf, cm[1]); write_primitive(buf, cm[2]); } + if (cmf.include_ra_ga_ba) write_primitive(buf, cm[3]); + write_primitive(buf, cm[4]); + if (cmf.include_color) { write_primitive(buf, cm[5]); write_primitive(buf, cm[6]); } + if (cmf.include_ra_ga_ba) write_primitive(buf, cm[7]); + write_primitive(buf, cm[8]); + if (cmf.include_color) { write_primitive(buf, cm[9]); write_primitive(buf, cm[10]); } + if (cmf.include_ra_ga_ba) write_primitive(buf, cm[11]); + if (cmf.include_ar_ag_ab) { write_primitive(buf, cm[12]); } + if (cmf.include_ar_ag_ab && cmf.include_color) { write_primitive(buf, cm[13]); write_primitive(buf, cm[14]); } + if (cmf.include_aa) write_primitive(buf, cm[15]); + if (cmf.include_cr_cg_cb) { + write_primitive(buf, cm[16]); + write_primitive(buf, cm[17]); + write_primitive(buf, cm[18]); + } + if (cmf.include_ca) write_primitive(buf, cm[19]); + } + else { + unsigned char cmb[20]; + for (int i = 0; i < 20; i++) cmb[i] = (unsigned char)(cm[i] * 255); + buf.push_back(cmb[0]); + if (cmf.include_color) { buf.push_back(cmb[1]); buf.push_back(cmb[2]); } + if (cmf.include_ra_ga_ba) buf.push_back(cmb[3]); + buf.push_back(cmb[4]); + if (cmf.include_color) { buf.push_back(cmb[5]); buf.push_back(cmb[6]); } + if (cmf.include_ra_ga_ba) buf.push_back(cmb[7]); + buf.push_back(cmb[8]); + if (cmf.include_color) { buf.push_back(cmb[9]); buf.push_back(cmb[10]); } + if (cmf.include_ra_ga_ba) buf.push_back(cmb[11]); + if (cmf.include_ar_ag_ab) { buf.push_back(cmb[12]); } + if (cmf.include_ar_ag_ab && cmf.include_color) { buf.push_back(cmb[13]); buf.push_back(cmb[14]); } + if (cmf.include_aa) buf.push_back(cmb[15]); + if (cmf.include_cr_cg_cb) { + buf.push_back(cmb[16]); + buf.push_back(cmb[17]); + buf.push_back(cmb[18]); + } + if (cmf.include_ca) buf.push_back(cmb[19]); + } + } + return appearance_id; +} diff --git a/src/demo_writer/write_appearance.h b/src/demo_writer/write_appearance.h new file mode 100644 index 0000000..4f5683c --- /dev/null +++ b/src/demo_writer/write_appearance.h @@ -0,0 +1,4 @@ +#pragma once +#include + +int write_appearance(std::vector &buf, int appearance_id); diff --git a/src/demo_writer/write_objects.cpp b/src/demo_writer/write_objects.cpp new file mode 100644 index 0000000..3f174ef --- /dev/null +++ b/src/demo_writer/write_objects.cpp @@ -0,0 +1,272 @@ +#include "write_objects.h" +#include "../core/core.h" +#include "demo_writer.h" +#include "state_tracking.h" +#include "write_primitive.h" +#include "write_appearance.h" +#include + +struct alignas(1) DemoAtomFlags { + union { + struct { + unsigned char include_appearance : 1; + unsigned char include_loc : 1; + unsigned char include_vis_contents : 1; + unsigned char include_step_xy : 1; + unsigned char copy_loc : 1; + unsigned char copy_vis_contents : 1; + }; + unsigned char byte = 0; + }; +}; + +template struct LastListItem { + int last_loc = 0; + int last_appearance = 0xFFFF; + int last_vis_contents_hash = 0; + short last_step_x = 0; + short last_step_y = 0; +}; +template<> struct LastListItem { + int last_appearance = 0xFFFF; + int last_vis_contents_hash = 0; +}; +/*template<> struct LastListItem { + int last_loc = 0; + int last_appearance = 0xFFFF; + int last_vis_contents_hash = 0; +};*/ + +inline unsigned int ref_int(Value val) { + if (!val.type) return 0; + return (val.type << 24) | (val.value & 0xFFFFFF); +} +inline unsigned int ref_int_relative(Value val, unsigned int parent) { + if (!val.type) return 0; + return (val.type << 24) | 0x80000000 | ((val.value - parent) & 0xFFFFFF); +} + +template +class AtomUpdateBuffer { +private: + int dirty_floor = 0; + int last_atom_loc_ref = 0; + int last_atom_abs_loc_ref = 0; + int last_atom_rel_loc_ref = 0; + std::priority_queue, std::greater> dirty_list; + std::vector> last_list; + void write_update(std::vector& buf, int id) { + DemoWriterIdFlags& dif = get_demo_id_flags(id); + dif.set_written(true); + Atom* atom = get_element(id); + ByteVecRef daf = write_primitive(buf, DemoAtomFlags()); + if (last_list.size() <= id) { + last_list.resize(id + 1); + } + LastListItem& lli = last_list[id]; + int appearance = get_appearance(atom, id); + if (appearance != lli.last_appearance || !(get_demo_id_flags(appearance).appearance_written)) { + daf->include_appearance = true; + lli.last_appearance = write_appearance(buf, appearance); + } + if constexpr (includes_loc) { + Value loc_val = { NULL_D, { 0 } }; + if(atom)loc_val = atom->loc; + int abs_loc = ref_int(loc_val); + if (abs_loc != lli.last_loc) { + int relative_loc = ref_int_relative(loc_val, id); + int loc = (abs_loc != 0 && relative_loc == last_atom_rel_loc_ref) ? relative_loc : abs_loc; + if (loc == last_atom_loc_ref) { + daf->copy_loc = true; + } else { + daf->include_loc = true; + write_primitive(buf, loc); + } + lli.last_loc = abs_loc; + last_atom_abs_loc_ref = abs_loc; + last_atom_loc_ref = loc; + last_atom_rel_loc_ref = relative_loc; + } + } + if constexpr (true) { + unsigned int vis_contents_hash = 0; + TableHolder3 *vis_contents = atom ? atom->vis_contents : nullptr; + if (vis_contents) { + for (int i = 0; i < vis_contents->size; i++) { + Value thing = ((Value*)vis_contents->elements)[i]; + unsigned int ref = ref_int(thing); + unsigned long long extended = ((unsigned long long)ref + (unsigned long long)(vis_contents_hash * 31)); + vis_contents_hash = (extended) + (extended >> 32); + } + } + if (vis_contents_hash != lli.last_vis_contents_hash) { + daf->include_vis_contents = true; + lli.last_vis_contents_hash = vis_contents_hash; + if (vis_contents) { + write_vlq(buf, vis_contents->size); + for (int i = 0; i < vis_contents->size; i++) { + Value thing = ((Value*)vis_contents->elements)[i]; + unsigned int ref = ref_int(thing); + write_primitive(buf, ref); + } + } + else { + write_vlq(buf, 0); + } + } + } + if constexpr (includes_stepxy) { + if (atom) { + short step_x = (short)(atom->step_x * 256); + short step_y = (short)(atom->step_y * 256); + if (step_x != lli.last_step_x || step_y != lli.last_step_y) { + daf->include_step_xy = true; + write_primitive(buf, step_x); + write_primitive(buf, step_y); + lli.last_step_x = step_x; + lli.last_step_y = step_y; + } + } + } + } + + int next_item() { + if (!dirty_list.empty()) { + int popped = dirty_list.top(); + dirty_list.pop(); + return popped; + } + while (dirty_floor < get_table_length()) { + int r = dirty_floor++; + if (!get_demo_id_flags(r).get_written() && does_element_exist(r)) { + return r; + } else { + DemoWriterIdFlags& dif = get_demo_id_flags(r); + dif.set_written(true); + } + } + return -1; + } + + inline int get_table_length(); + inline Atom *get_element(int id); + inline int get_appearance(Atom* atom, int id) { return atom ? atom->appearance : 0xFFFF; } + inline bool does_element_exist(int id) { return get_element(id); } +public: + void flush() { + int curr_ref = next_item(); + if (curr_ref < 0) return; + std::vector vec; + write_vlq(vec, curr_ref); + int len_pos = vec.size(); + int amt_in = 0;; + while (curr_ref >= 0) { + write_update(vec, curr_ref); + amt_in++; + int ideal_next = curr_ref + 1; + curr_ref = next_item(); + while (curr_ref < ideal_next && curr_ref >= 0) { + DemoWriterIdFlags& dif = get_demo_id_flags(curr_ref); + dif.set_written(true); + curr_ref = next_item(); // dang it going down is bad + } + if (curr_ref != ideal_next) { + std::vector vlq_vec; + write_vlq(vlq_vec, amt_in); + amt_in = 0; + vec.insert(vec.begin() + len_pos, vlq_vec.begin(), vlq_vec.end()); + if (curr_ref > ideal_next) { + write_vlq(vec, curr_ref - ideal_next); + } + len_pos = vec.size(); + } + } + vec.push_back(0); + update_demo_time(); + demo_file_handle.put(update_chunk_id); + write_vlq(vec.size()); + demo_file_handle.write((char*)&vec[0], vec.size()); + } + + void mark_dirty(int id) { + if (id >= dirty_floor) return; + DemoWriterIdFlags& dif = get_demo_id_flags(id); + if (dif.get_written()) { + dif.set_written(false); + dirty_list.push(id); + } + } +}; + +inline int AtomUpdateBuffer::get_table_length() { + return Core::turf_table->turf_count; +} +inline int AtomUpdateBuffer::get_table_length() { + return Core::obj_table->length; +} +inline int AtomUpdateBuffer::get_table_length() { + return Core::mob_table->length; +} +inline Turf *AtomUpdateBuffer::get_element(int id) { + if (id < Core::turf_table->turf_count || Core::turf_table->existence_table[id] == 0) { + return nullptr; + } + Turf* ref = Core::turf_hashtable->elements[id & Core::turf_hashtable->mask]; + while (ref && ref->id != id) { + ref = ref->next; + } + return ref; +} +inline Obj* AtomUpdateBuffer::get_element(int id) { + return id < Core::obj_table->length ? Core::obj_table->elements[id] : nullptr; +} +inline Mob* AtomUpdateBuffer::get_element(int id) { + return id < Core::mob_table->length ? Core::mob_table->elements[id] : nullptr; +} +inline int AtomUpdateBuffer::get_appearance(Turf* turf, int id) { + if (id >= Core::turf_table->turf_count) return 0xFFFF; + int shared_id = Core::turf_table->shared_info_id_table[id]; + return (*Core::turf_shared_info_table)[shared_id]->appearance; +} +inline int AtomUpdateBuffer::get_appearance(Obj* obj, int id) { + return obj ? GetObjAppearance(id) : 0xFFFF; +} +inline int AtomUpdateBuffer::get_appearance(Mob* mob, int id) { + return mob ? GetMobAppearance(id) : 0xFFFF; +} +inline bool AtomUpdateBuffer::does_element_exist(int id) { + return true; +} + +AtomUpdateBuffer turf_update_buffer; +AtomUpdateBuffer obj_update_buffer; +AtomUpdateBuffer mob_update_buffer; + +void flush_atom_updates() { + turf_update_buffer.flush(); + obj_update_buffer.flush(); + mob_update_buffer.flush(); +} + +void mark_atom_dirty(Value atom) { + switch (atom.type) { + case TURF: + case LIST_TURF_VERBS: + case LIST_TURF_OVERLAYS: + case LIST_TURF_UNDERLAYS: + turf_update_buffer.mark_dirty(atom.value); + break; + case OBJ: + case LIST_VERBS: + case LIST_OVERLAYS: + case LIST_UNDERLAYS: + obj_update_buffer.mark_dirty(atom.value); + break; + case MOB: + case LIST_MOB_VERBS: + case LIST_MOB_OVERLAYS: + case LIST_MOB_UNDERLAYS: + mob_update_buffer.mark_dirty(atom.value); + break; + } +} diff --git a/src/demo_writer/write_objects.h b/src/demo_writer/write_objects.h new file mode 100644 index 0000000..5ec3a28 --- /dev/null +++ b/src/demo_writer/write_objects.h @@ -0,0 +1,5 @@ +#pragma once +#include "../core/core.h" + +void flush_atom_updates(); +void mark_atom_dirty(Value atom); \ No newline at end of file diff --git a/src/demo_writer/write_primitive.cpp b/src/demo_writer/write_primitive.cpp new file mode 100644 index 0000000..9ac896f --- /dev/null +++ b/src/demo_writer/write_primitive.cpp @@ -0,0 +1,142 @@ +#pragma once +#include "write_primitive.h" +#include "demo_writer.h" +#include "state_tracking.h" +#include "../core/core.h" + +void write_vlq(std::vector &buf, int value) { + int val_buf; + val_buf = value & 0x7f; + while ((value >>= 7) > 0) + { + val_buf <<= 8; + val_buf |= 0x80; + val_buf += (value & 0x7f); + } + + while (true) + { + buf.push_back(val_buf); + if (val_buf & 0x80) val_buf >>= 8; + else + break; + } +} + +void write_vlq(int value) { + int val_buf; + val_buf = value & 0x7f; + while ((value >>= 7) > 0) + { + val_buf <<= 8; + val_buf |= 0x80; + val_buf += (value & 0x7f); + } + + while (true) + { + demo_file_handle.put(val_buf); + if (val_buf & 0x80) val_buf >>= 8; + else + break; + } +} + +void write_byond_string(std::vector &buf, int string_id) { + if (string_id == 0 || string_id == 0xFFFF) { + buf.push_back(0); + return; + } + String *str = GetStringTableEntry(string_id); + if (!str) { + buf.push_back(0); + return; + } + + DemoWriterIdFlags& dif = get_demo_id_flags(string_id); + bool do_write = !dif.string_written; + dif.string_written = true; + if (string_id < 256) { + buf.push_back((int)do_write | 2); + buf.push_back(string_id); + } + else if (string_id < 65536) { + buf.push_back((int)do_write | 4); + buf.push_back((string_id >> 8) & 0xFF); + buf.push_back(string_id & 0xFF); + } + else { + buf.push_back((int)do_write | 6); + buf.push_back((string_id >> 16) & 0xFF); + buf.push_back((string_id >> 8) & 0xFF); + buf.push_back(string_id & 0xFF); + } + if (do_write) { + char* chars = str->stringData; + while (*chars != 0) { + buf.push_back(*chars); + chars++; + } + buf.push_back(0); + } +} + +void write_byond_resourceid(std::vector& buf, int resource_id) { + if (resource_id == 0xFFFF) { + buf.push_back(0); + return; + } + int string_id = ToString(RESOURCE, resource_id); + String* str = GetStringTableEntry(string_id); + + DemoWriterIdFlags& dif = get_demo_id_flags(resource_id); + bool do_write = string_id && !dif.resource_written; + dif.resource_written = true; + if (resource_id < 256) { + buf.push_back((int)do_write | 2); + buf.push_back(resource_id); + } + else if (resource_id < 65536) { + buf.push_back((int)do_write | 4); + buf.push_back((resource_id >> 8) & 0xFF); + buf.push_back(resource_id & 0xFF); + } + else { + buf.push_back((int)do_write | 6); + buf.push_back((resource_id >> 16) & 0xFF); + buf.push_back((resource_id >> 8) & 0xFF); + buf.push_back(resource_id & 0xFF); + } + if (do_write) { + char* chars = str->stringData; + while (*chars != 0) { + buf.push_back(*chars); + chars++; + } + buf.push_back(0); + } +} + +float last_world_time = 0; +bool demo_time_override_enabled = false; +float demo_time_override = 0; + +void update_demo_time() { + float time = demo_time_override_enabled ? demo_time_override : GetVariable({ WORLD_D, {0} }, 0x4f).valuef; // world.time + if (last_world_time >= time) return; + last_world_time = time; + demo_file_handle.put(0x00); // Chunk ID + demo_file_handle.put(0x04); // Chunk Length + demo_file_handle.write((char*)&time, sizeof(time)); +} + +void write_world_size() { + struct { + char chunk_id = 1; + char chunk_length = 0x6; + short maxx = Core::turf_table->maxx; + short maxy = Core::turf_table->maxy; + short maxz = Core::turf_table->maxz; + } data; + demo_file_handle.write((char*)&data, sizeof(data)); +} diff --git a/src/demo_writer/write_primitive.h b/src/demo_writer/write_primitive.h new file mode 100644 index 0000000..97f0c15 --- /dev/null +++ b/src/demo_writer/write_primitive.h @@ -0,0 +1,35 @@ +#pragma once +#include + +void write_vlq(std::vector& buf, int value); +void write_vlq(int value); +void write_byond_string(std::vector& buf, int string_id); +void write_byond_resourceid(std::vector& buf, int resource_id); +void update_demo_time(); +void write_world_size(); + +extern bool demo_time_override_enabled; +extern float demo_time_override; + +template +class ByteVecRef { +public: + ByteVecRef(std::vector* vec, int num) { + this->vec = vec; + this->num = num; + } + T* operator->() { + return (T*)&(*vec)[num]; + } +private: + std::vector *vec; + int num; +}; + +template +ByteVecRef write_primitive(std::vector& buf, const T &value) { + int n = buf.size(); + buf.resize(n + sizeof(T)); + memcpy(&buf[n], &value, sizeof(T)); + return ByteVecRef(&buf, n); +} diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..1c8f7d7 --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#ifdef _WIN32 +#include +#include "core/core.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + Core::remove_all_hooks(); + break; + } + return TRUE; +} +#endif \ No newline at end of file