diff --git a/readme.md b/readme.md index b64f4fd..4788071 100644 --- a/readme.md +++ b/readme.md @@ -6,20 +6,22 @@ Shroudtopia Creative Mode Mod for Enshrouded Dedicated Servers

-Static Badge +Static Badge

I'm excited to introduce **Shroudtopia**, a mod that brings creative mode functionalities to Enshrouded dedicated servers. Whether you're a builder looking for unlimited resources or an explorer seeking freedom from limitations, Shroudtopia enhances your gameplay experience. ## Features -- **Custom Experience Multiplier:** Adjust experience gain to your preference. -- **Fly Mode with Glider:** Enjoy full flight capabilities with the glider. +- **Fly:** Enjoy full flight capabilities with the glider. No more losing height! +- **Infinite Altars:** Bypass the general altar limit and make the world yours. +- ~~**Custom Experience Multiplier:** Adjust experience gain to your preference.~~ *(Obsolete since official gameSettings implementation)* - **No Fall Damage:** Explore without the fear of taking fall damage. - **No Stamina Loss:** Infinite stamina for uninterrupted gameplay. -- **No Durability Loss:** Gear and items never degrade or break. -- **Item Duplication:** Splitting stacks results in cloned items. -- **Free Crafting & Building:** Resources are required but crafting and building have no resource cost. +- ~~**No Durability Loss:** Gear and items never degrade or break.~~ *(Obsolete since official gameSettings implementation)* +- ~~**Item Duplication:** Splitting stacks results in cloned items.~~ *(Broken)* +- **Free Crafting:** Resources are required but crafting has no cost. +- **Infinite Item Use & No Building Cost:** Resources are required but using and building has no cost. ## Installation @@ -36,17 +38,21 @@ If Shroudtopia is loaded you should see something like this in the server consol [shroudtopia] Wait for server. Configured boot delay is 1000ms. ``` +Upon first launch the file ```shroudtopia.json``` is created. By default all mods are deactivated and you have to manually alter the configuration to your likings. +The file can be changed while the server is running and will be reloaded on-the-fly. + ## Customization Each aspect of Shroudtopia is customizable via the `shroudtopia.json` config file. Adjust settings to tailor the mod to your preferred gameplay style: ```json { - "clone_item_splits": true, - "exp_multiplier": 5, - "free_craft": true, + "active": true, + "boot_delay": 3000, + "bypass_altar_limit": true, "glider_flight": true, - "no_durability_loss": true, + "infinite_item_use": true, + "no_craft_cost": true, "no_fall_damage": true, "no_stamina_loss": true } diff --git a/shroudtopia/config.h b/shroudtopia/config.h index 745a81b..066f00b 100644 --- a/shroudtopia/config.h +++ b/shroudtopia/config.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "pch.h" #define CONFIG_FILE "shroudtopia.json" @@ -9,53 +10,71 @@ using json = nlohmann::json; class Config { public: + Config() { + try { + lastModifiedTime = std::filesystem::last_write_time(CONFIG_FILE); + } catch (std::exception err) { } + } + bool active = true; int boot_delay = 3000; - int exp_multiplier = 5; - bool glider_flight = true; - bool no_fall_damage = true; - bool no_stamina_loss = true; - bool no_durability_loss = true; - bool clone_item_splits = true; - bool free_craft = true; + + bool glider_flight = false; + bool no_stamina_loss = false; + bool no_fall_damage = false; + bool no_craft_cost = false; + bool inf_item_use = false; + bool bypass_world_borders = false; + bool bypass_altar_limit = false; bool read() { - std::ifstream configFile(CONFIG_FILE); - if (configFile.is_open()) - { - json jConfig; - configFile >> jConfig; - active = jConfig.value("active", true); - boot_delay = jConfig.value("boot_delay", 3000); - exp_multiplier = jConfig.value("exp_multiplier", 5); - glider_flight = jConfig.value("glider_flight", true); - no_fall_damage = jConfig.value("no_fall_damage", true); - no_stamina_loss = jConfig.value("no_stamina_loss", true); - no_durability_loss = jConfig.value("no_durability_loss", true); - clone_item_splits = jConfig.value("clone_item_splits", true); - free_craft = jConfig.value("free_craft", true); - return true; + try { + std::ifstream configFile(CONFIG_FILE); + if (configFile.is_open()) + { + json jConfig; + configFile >> jConfig; + + active = jConfig.value("active", false); + boot_delay = jConfig.value("boot_delay", 3000); + glider_flight = jConfig.value("glider_flight", false); + no_stamina_loss = jConfig.value("no_stamina_loss", false); + no_fall_damage = jConfig.value("no_fall_damage", false); + no_craft_cost = jConfig.value("no_craft_cost", false); + inf_item_use = jConfig.value("infinite_item_use", false); + bypass_world_borders = jConfig.value("bypass_world_borders", false); + bypass_altar_limit = jConfig.value("bypass_altar_limit", false); + + lastModifiedTime = std::filesystem::last_write_time(CONFIG_FILE); + return true; + } + return false; + } + catch (std::exception err) { + return false; } - return false; } std::string dump() { json jConfig; - jConfig["exp_multiplier"] = exp_multiplier; + jConfig["active"] = active; + jConfig["boot_delay"] = boot_delay; jConfig["glider_flight"] = glider_flight; jConfig["no_fall_damage"] = no_fall_damage; jConfig["no_stamina_loss"] = no_stamina_loss; - jConfig["no_durability_loss"] = no_durability_loss; - jConfig["clone_item_splits"] = clone_item_splits; - jConfig["free_craft"] = free_craft; + jConfig["no_craft_cost"] = no_craft_cost; + jConfig["infinite_item_use"] = inf_item_use; + jConfig["bypass_world_borders"] = bypass_world_borders; + jConfig["bypass_altar_limit"] = bypass_altar_limit; + return jConfig.dump(4); } bool write() { - std::ofstream configFile("shroudtopia.json"); + std::ofstream configFile(CONFIG_FILE); if (configFile.is_open()) { // Write pretty-printed JSON to file @@ -65,4 +84,30 @@ class Config } return false; } + + + // Check if the configuration file has changed + bool hasFileChanged() + { + try { + auto currentModifiedTime = std::filesystem::last_write_time(CONFIG_FILE); + return currentModifiedTime != lastModifiedTime; + } + catch (std::exception err) { + return false; + } + } + + // Reload configuration if the file has changed + bool reloadIfChanged() + { + if (hasFileChanged()) + { + return read(); // Reload the config if changed + } + return false; // No changes detected + } + +private: + std::filesystem::file_time_type lastModifiedTime; // To track last modified time }; \ No newline at end of file diff --git a/shroudtopia/dllmain.cpp b/shroudtopia/dllmain.cpp index df7f5fc..e6877b4 100644 --- a/shroudtopia/dllmain.cpp +++ b/shroudtopia/dllmain.cpp @@ -2,107 +2,40 @@ #include "pch.h" #include "mem.h" #include +#include +#include -class ExpMultiplier -{ -public: - bool active = false; - - Mem::Detour *expMultiplierMod = nullptr; - - ExpMultiplier(int multiplier = 1) - { - const char *pattern = "\x48\x03\xC3\x01\x34\x88\x48\x8B"; - const char *mask = "xxxxxxxx"; +// Signatures for GameVersion (SVN) 558123 - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); // Scan 16MB +class Mod { +public: + virtual ~Mod() {} // Add a virtual destructor to enable polymorphism - if (address) - { - uint8_t modCode[] = { - 0x0F, 0xAF, 0x35, 0x00, 0x00, 0x00, 0x00, // imul esi, [multipler_exp] - 0x48, 0x03, 0xC3, // add rax, rbx - 0x01, 0x34, 0x88, // add [rax+rcx*4], esi - 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp return - 0x00, 0x00, 0x00, 0x00 - }; - expMultiplierMod = new Mem::Detour(address, modCode, sizeof(modCode), false, 1); - expMultiplierMod->shellcode->updateValue(3, 11); - expMultiplierMod->shellcode->updateValue(14, (uint32_t)(expMultiplierMod->patch->data->address + expMultiplierMod->patch->data->size) - ((uint32_t)((uintptr_t)expMultiplierMod->shellcode->data->address + expMultiplierMod->shellcode->data->size - 4))); - expMultiplierMod->shellcode->updateValue(18, multiplier); - } - } + bool active = false; + Mem::Detour* mod = nullptr; void activate() { - expMultiplierMod->activate(); - active = expMultiplierMod->active; + mod->activate(); + active = mod->active; if (active) - Utils::Log("Activated ExpMultiplier"); + LOG_CLASS("Activated"); else - Utils::Log("Failed to activate ExpMultiplier"); + LOG_CLASS("Failed to activate"); } -}; - -bool ApplyExpMultiplier(const Config &config) -{ - const char *pattern = "\x48\x03\xC3\x01\x34\x88\x48\x8B"; - const char *mask = "xxxxxxxx"; - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); - - if (address) + // Deactivates the mod by restoring the original code. + void deactivate() { - void *newmem = (void *)Mem::AllocateMemoryNearAddress((LPVOID)address, 0x1000); - - if (newmem) - { - uint8_t newCode[] = { - 0x0F, 0xAF, 0x35, 0x00, 0x00, 0x00, 0x00, // imul esi, [multipler_exp] - 0x48, 0x03, 0xC3, // add rax, rbx - 0x01, 0x34, 0x88, // add [rax+rcx*4], esi - 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return - }; - - uint8_t patch[] = { - 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp newmem - 0x90 // nop - }; - - uintptr_t multDataAddress = (uintptr_t)newmem + sizeof(newCode); - - *(int32_t *)(newCode + 3) = (int32_t)(multDataAddress - ((uintptr_t)newmem + 7)); // Offset to multipler_exp - - *(int32_t *)(newCode + 14) = (int32_t)((address + sizeof(patch)) - ((uintptr_t)newmem + sizeof(newCode))); - - int expMultiplier = config.exp_multiplier; - memcpy((void *)multDataAddress, &config.exp_multiplier, sizeof(int)); - - memcpy(newmem, newCode, sizeof(newCode)); - - *(int32_t *)(patch + 1) = (uintptr_t)newmem - (address + 5); - - Mem::Write(address, patch, sizeof(patch)); - - Utils::Log(std::string("Exp multiplier set to: ").append(std::to_string(config.exp_multiplier)).c_str()); - return true; - } + mod->deactivate(); + active = false; + LOG_CLASS("Deactivated"); } +}; - Utils::Log("Failed to set exp multiplier"); - return false; -} - -class GliderFlight +class GliderFlight : public Mod { public: - bool active = false; - - Mem::Detour *flightMod = nullptr; - GliderFlight() { const char *pattern = "\xF3\x0F\x10\x05\x00\x00\x00\x00\x77"; @@ -117,237 +50,309 @@ class GliderFlight 0xF3, 0x0F, 0x10, 0x05, 0x05, 0x00, 0x00, 0x00, // movss xmm0, [GlideData] 0xE9, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xf5, 0xc8, 0xbf}; - flightMod = new Mem::Detour(address, modCode, sizeof(modCode), false, 3); - flightMod->shellcode->updateValue(9, (uint32_t)(flightMod->patch->data->address + flightMod->patch->data->size) - ((uint32_t)((uintptr_t)flightMod->shellcode->data->address + flightMod->shellcode->data->size - 4))); + mod = new Mem::Detour(address, modCode, sizeof(modCode), false, 3); + mod->shellcode->updateValue(9, (uint32_t)(mod->patch->data->address + mod->patch->data->size) - ((uint32_t)((uintptr_t)mod->shellcode->data->address + mod->shellcode->data->size - 4))); } } - - void activate() - { - flightMod->activate(); - active = flightMod->active; - if (active) - Utils::Log("Activated GliderFlight"); - else - Utils::Log("Failed to activate GliderFlight"); - } }; -bool ApplyNoStaminaLoss(const Config &config) +class NoStaminaLoss : public Mod { - if (!config.no_stamina_loss) - return true; - - const char *pattern = "\x8B\x04\x81\x89\x44\x24\x3C"; - const char *mask = "xxxxxxx"; +public: + NoStaminaLoss() + { + // Pattern matching the AOB scan for the original code in the target process. + const char* pattern = "\x8B\x04\x81\x89\x44\x24\x3C"; + const char* mask = "xxxxxxx"; - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - if (address) - { - void *newmem = (void *)Mem::AllocateMemoryNearAddress((LPVOID)address, 0x1000); + // Find the pattern within the game memory (scans within 16MB). + uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); - if (newmem) + // If the address was found, proceed with allocating memory and creating the detour. + if (address) { - uint8_t newCode[] = { - 0x53, // push rbx - 0x8B, 0x5C, 0x81, 0x08, // mov ebx, [rcx+rax*4+8] - 0x89, 0x1C, 0x81, // mov [rcx+rax*4], ebx - 0x5B, // pop rbx - 0x8B, 0x04, 0x81, // mov eax, [rcx+rax*4] - 0x89, 0x44, 0x24, 0x3C, // mov [rsp+3C], eax - 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return + // The modded code that will replace the original instructions at the found address. + uint8_t modCode[] = { + 0x53, // push rbx + 0x8B, 0x5C, 0x81, 0x08, // mov ebx, [rcx+rax*4+8] + 0x89, 0x1C, 0x81, // mov [rcx+rax*4], ebx + 0x5B, // pop rbx + 0x8B, 0x04, 0x81, // mov eax, [rcx+rax*4] + 0x89, 0x44, 0x24, 0x3C, // mov [rsp+3C], eax + 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return }; - uint8_t patch[] = { - 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp newmem - 0x66, 0x90 // nop 2 (x86 nop) - }; + // Creating the detour by replacing the original code with our custom modCode. + mod = new Mem::Detour(address, modCode, sizeof(modCode), false, 2); - int32_t returnAddress = (int32_t)((address + sizeof(patch)) - ((uintptr_t)newmem + sizeof(newCode))); - *(int32_t *)(newCode + sizeof(newCode) - 4) = returnAddress; + // Calculate the jump address and update the shellcode. + mod->shellcode->updateValue( + sizeof(modCode) - 4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + } + } +}; - memcpy(newmem, newCode, sizeof(newCode)); +class NoFallDamage : public Mod +{ +public: + NoFallDamage() + { + // Pattern matching the AOB scan for the original code in the target process. + const char* pattern = "\x89\x04\x91\x48\x8D\x4D\xE0"; + const char* mask = "xxxxxxx"; - *(int32_t *)(patch + 1) = (int32_t)((uintptr_t)newmem - (address + 5)); - Mem::Write(address, patch, sizeof(patch)); + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); + + uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x10000000); + if (address) + { + // add fall damage as health + //uint8_t modCode[] = { + // 0x01, 0x04, 0x91, // add[rcx + rdx * 4], eax + // 0x48, 0x8D, 0x4D, 0xE0, // lea rcx, [rbp-20] + // 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return (dynamic, needs to be calculated) + //}; + + // no fall damage + uint8_t modCode[] = { + 0x48, 0x83, 0xC0, 0x00, // add eax, 0 (equivalent to no operation) + 0x48, 0x8D, 0x4D, 0xE0, // lea rcx, [rbp-20] + 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return (dynamic, needs to be calculated) + }; - Utils::Log("Activated NoStaminaLoss"); + // Creating the detour by replacing the original code with our custom modCode. + mod = new Mem::Detour(address, modCode, sizeof(modCode), false, 2); - return true; + // Calculate the jump address and update the shellcode. + mod->shellcode->updateValue( + sizeof(modCode)-4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + } + else + { + LOG_CLASS(std::string("Address not found: Base Address: ").append(std::to_string(baseAddress)).c_str()); } } +}; - Utils::Log("Failed to activate NoStaminaLoss"); - return false; -} - -bool ApplyNoFallDamage(const Config &config) +class NoCraftCost : public Mod { - if (!config.no_fall_damage) - return true; - - // \x40\x53\x48\x83\xEC\x60\x41\xB8\x20\x00\x00\x00\x48\x8D\x54\x24\x20\x48\x8B\xD9\xE8\x00\x00\x00\x00\x41\xB8\x20\x00\x00\x00\x48\x8D\x54\x24\x20\x48\x8B\xCB\xE8\x00\x00\x00\x00\x84\xC0\x0F\x84\xD9\x00\x00\x00\x48\x89\x7C\x24\x70 - // xxxxxxxxxxxxxxxxxxxxx????xxxxxxxxxxxxxxx????xxxxxxxxxxxxx - const char *pattern = "\x40\x53\x48\x83\xEC\x60\x41\xB8\x20\x00\x00\x00\x48\x8D\x54\x24\x20\x48\x8B\xD9\xE8\x00\x00\x00\x00\x41\xB8\x20\x00\x00\x00\x48\x8D\x54\x24\x20\x48\x8B\xCB\xE8\x00\x00\x00\x00\x84\xC0\x0F\x84\xD9\x00\x00\x00\x48\x89\x7C\x24\x70"; - const char *mask = "xxxxxxxxxxxxxxxxxxxxx????xxxxxxxxxxxxxxx????xxxxxxxxxxxxx"; +public: + NoCraftCost() + { + // Pattern matching the AOB scan for the original code in the target process. + const char* pattern = "\x43\x8B\x74\xF5\x04"; + const char* mask = "xxxxx"; - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - if (address) - { - uint8_t patch[] = { - 0xC3}; + // Find the pattern within the game memory (scans within 16MB). + uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); - Mem::Write(address, patch, sizeof(patch)); + // If the address was found, proceed with allocating memory and creating the detour. + if (address) + { + // The modded code that will replace the original instructions at the found address. + uint8_t modCode[] = { + 0x43, 0x8B, 0x74, 0xF5, 0x04, // - mov esi,[r13 + r14 * 8 + 04] + 0xBE, 0x00, 0x00, 0x00, 0x00, // - mov esi,00000000 + 0xE9, 0x00, 0x00, 0x00, 0x00 // - jmp + }; - Utils::Log("Activated NoFallDamage"); + // Creating the detour by replacing the original code with our custom modCode. + mod = new Mem::Detour(address, modCode, sizeof(modCode), false, 0); - return true; + // Calculate the jump address and update the shellcode. + mod->shellcode->updateValue( + sizeof(modCode) - 4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + } } +}; - Utils::Log("Failed to activate NoFallDamage"); - return false; -} - -bool ApplyNoDurabilityLoss(const Config &config) +class InfiniteItemUse : public Mod { - if (!config.no_durability_loss) - return true; - - const char *pattern = "\x0F\x84\x00\x00\x00\x00\x8B\x00\x00\x48\x03\x00\x01"; - const char *mask = "xx????x??xx?x"; - - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); +public: + Mem::Detour* free_ItemUseMod2 = nullptr; - if (address) + InfiniteItemUse() { + // AOB patterns for the original code in the target process. + const char* pattern1 = "\x45\x29\x7E\x04\x41\x2B\xEF"; // First AOB + const char* pattern2 = "\x33\xC9\x49\x89\x0E"; // Second AOB - void *newmem = (void *)Mem::AllocateMemoryNearAddress((LPVOID)address, 0x1000); + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - if (newmem) + // Find the patterns within the game memory (scans within 16MB). + uintptr_t address1 = Mem::FindPattern(pattern1, "xxxxxxx", baseAddress, 0xF0000000); + uintptr_t address2 = Mem::FindPattern(pattern2, "xxxxx", baseAddress, 0xF0000000); + + // Check if both addresses were found. + if (address1 && address2) { - uint8_t newCode[] = { - 0x85, 0xF6, - 0x0F, 0x89, 0x08, 0x00, 0x00, 0x00, - 0x48, 0x03, 0xC3, - 0xE9, 0x00, 0x00, 0x00, 0x00, // return addr - 0x48, 0x03, 0xC3, - 0x01, 0x34, 0x88, - 0xE9, 0x00, 0x00, 0x00, 0x00, // return addr + // Allocate memory for new instructions to be injected at address1. + uint8_t modCode1[] = { + 0x49, 0x83, 0xFB, 0x01, 0x0F, 0x85, 0x08, 0x00, 0x00, 0x00, 0x44, 0x29, 0xFD, 0xE9, 0x00, 0x00, 0x00, 0x00 }; - uint8_t patch[] = { - 0xE9, 0x00, 0x00, 0x00, 0x00, - 0x90}; - - uintptr_t returnAddress = address + sizeof(patch); - - *(uint32_t *)(newCode + 12) = (uintptr_t)returnAddress - ((uintptr_t)newmem + 16); - *(uint32_t *)(newCode + 23) = (uintptr_t)returnAddress - ((uintptr_t)newmem + sizeof(newCode)); - - memcpy(newmem, newCode, sizeof(newCode)); - - *(uint32_t *)(patch + 1) = (uintptr_t)newmem - ((uintptr_t)address + 5); - - Mem::Write(address, patch, sizeof(patch)); + // Creating the detour for the first address. + mod = new Mem::Detour(address1, modCode1, sizeof(modCode1), false, 2); + + // Calculate the jump address for the first detour. + mod->shellcode->updateValue( + sizeof(modCode1)-4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + + // Allocate memory for new instructions to be injected at address2. + uint8_t modCode2[] = { + 0x31, 0xC9, // xor ecx, ecx + 0x49, 0x83, 0xFB, 0x01, + 0x0F, 0x84, 0x03, 0x00, 0x00, 0x00, + 0x49, 0x89, 0x0E, + 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp + }; - Utils::Log("Activated NoDurabilityLoss"); + // Creating the detour for the second address. + free_ItemUseMod2 = new Mem::Detour(address2, modCode2, sizeof(modCode2), false); - return true; + // Calculate the jump address for the second detour. + free_ItemUseMod2->shellcode->updateValue( + sizeof(modCode2)-4, (uint32_t)(free_ItemUseMod2->patch->data->address + free_ItemUseMod2->patch->data->size) + - ((uint32_t)((uintptr_t)free_ItemUseMod2->shellcode->data->address + + free_ItemUseMod2->shellcode->data->size - 4)) + ); } } - Utils::Log("Failed to activate NoDurabilityLoss"); - return false; -} - -bool ApplyCloneItemSplits(const Config &config) -{ - if (!config.clone_item_splits) - return true; - - const char *pattern = "\x29\x00\x04\xEB\x00\x0F\x57"; - const char *mask = "x?xx?xx"; - - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); - - if (address) + // Activates the Infinite Item Use mod. + void activate() { - uint8_t patch[] = { - 0x90, 0x90, 0x90 // NOP 3 - }; - - Mem::Write(address, patch, sizeof(patch)); - - Utils::Log("Activated CloneItemSplits"); - - return true; + mod->activate(); + free_ItemUseMod2->activate(); + active = mod->active && free_ItemUseMod2->active; + if (active) + LOG_CLASS("Activated"); + else + LOG_CLASS("Failed to activate"); } - Utils::Log("Failed to activate CloneItemSplits"); - return false; -} + // Deactivates the Infinite Item Use mod. + void deactivate() + { + mod->deactivate(); + free_ItemUseMod2->deactivate(); + active = false; + LOG_CLASS("Deactivated"); + } +}; -bool ApplyFreeCraft(const Config &config) +class BypassWorldBorders : public Mod { - if (!config.free_craft) - return true; - - const char *pattern = "\x43\x8B\x74\xF5\x04"; - const char *mask = "xxxxx"; +public: + BypassWorldBorders() + { + // AOB pattern for the original code in the target process. + const char* pattern = "\x89\x88\x00\x00\x00\x0F\x10\x00\xF2\x0F\x10\x48\x10"; // Unique AOB - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - if (address) - { + // Find the pattern within the game memory (scans within 16MB). + uintptr_t address = Mem::FindPattern(pattern, "xxxxxxxxxxxxx", baseAddress, 0xF000000); - uint8_t patch[] = { - 0xBE, 0x00, 0x00, 0x00, 0x00}; + // Check if the address was found. + if (address) + { + // Allocate memory for new instructions to be injected. + uint8_t modCode[] = { + 0xF3, 0x0F, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, // movups xmm0, [_P] + 0xF2, 0x0F, 0x10, 0x48, 0x10, // movsd xmm1, [_P + 10] + 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return (dynamic, needs to be calculated) + }; - Mem::Write(address, patch, sizeof(patch)); + // Creating the detour for the AOB address. + mod = new Mem::Detour(address + 5, modCode, sizeof(modCode), false, 3); - Utils::Log("Activated FreeCraft"); + // Calculate the jump address for the detour. + mod->shellcode->updateValue( + sizeof(modCode)-4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + } + } - return true; + // Activates the Bypass World Borders mod. + void activate() + { + mod->activate(); + active = mod->active; + if (active) + LOG_CLASS("Activated BypassWorldBorders"); + else + LOG_CLASS("Failed to activate BypassWorldBorders"); } - Utils::Log("Failed to activate FreeCraft"); - return false; -} + // Deactivates the Bypass World Borders mod. + void deactivate() + { + mod->deactivate(); + active = false; + LOG_CLASS("Deactivated BypassWorldBorders"); + } +}; -bool ApplyFreeBuild(const Config &config) +class BypassAltarLimit : public Mod { - if (!config.free_craft) - return true; - - const char *pattern = "\x48\x0F\x00\x00\x00\x8B\x00\x00\x8B\x00\x04\x89"; - const char *mask = "xx???x??x?xx"; - - uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uintptr_t address = Mem::FindPattern(pattern, mask, baseAddress, 0x1000000); - - if (address) +public: + BypassAltarLimit() { + // AOB pattern for the original code in the target process. + const char* pattern = "\x49\x8B\x8E\xD0\x00\x00\x00\x3B"; // Unique AOB + + // Base address of the module (the game). + uintptr_t baseAddress = (uintptr_t)GetModuleHandle(NULL); - uint8_t patch[] = { - 0x31, 0xF6, 0x90, 0x90}; + // Find the pattern within the game memory (scans within 16MB). + uintptr_t address = Mem::FindPattern(pattern, "xxxxxxxx", baseAddress, 0x1000000); - Mem::Write(address + 7, patch, sizeof(patch)); + // Check if the address was found. + if (address) + { + // Allocate memory for new instructions to be injected. + uint8_t modCode[] = { + 0x49, 0x8B, 0x8E, 0xD0, 0x00, 0x00, 0x00, // mov rcx, [r14 + 0xD0] + 0xB8, 0xFF, 0x00, 0x00, 0x00, // mov eax, 0xFF + 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp return (dynamic, needs to be calculated) + }; - Utils::Log("Activated FreeBuild"); + // Creating the detour for the AOB address. + mod = new Mem::Detour(address, modCode, sizeof(modCode), false, 2); - return true; + // Calculate the jump address for the detour. + mod->shellcode->updateValue( + sizeof(modCode)-4, (uint32_t)(mod->patch->data->address + mod->patch->data->size) + - ((uint32_t)((uintptr_t)mod->shellcode->data->address + + mod->shellcode->data->size)) + ); + } } - - Utils::Log("Failed to activate FreeBuild"); - return false; -} +}; void PatchMemoryLoop() { @@ -361,43 +366,59 @@ void PatchMemoryLoop() } Utils::Log("Config loaded."); - if (!config.active) - return; - - ExpMultiplier expMultiplier(config.exp_multiplier); GliderFlight gliderFlight; + NoStaminaLoss noStaminaLoss; + NoFallDamage noFallDamage; + NoCraftCost noCraftCost; + InfiniteItemUse infiniteItemUse; + BypassWorldBorders bypassWorldBorders; + BypassAltarLimit bypassAltarLimit; - static bool ActivatedNoFallDamage = false; - static bool ActivatedNoStaminaLoss = false; - static bool ActivatedNoDurabilityLoss = false; - static bool ActivatedCloneItemSplits = false; - static bool ActivatedFreeCraft = false; - static bool ActivatedFreeBuild = false; + Utils::Log("Detours initialized."); Utils::Log(std::string("Wait for server. Configured boot delay is ").append(std::to_string(config.boot_delay)).append("ms.").c_str()); Sleep(config.boot_delay); - // Silent retries if some patch didnt work initally while (true) { - if(config.exp_multiplier != 1 && !expMultiplier.active) - expMultiplier.activate(); - if (config.glider_flight && !gliderFlight.active) - gliderFlight.activate(); - - if (!ActivatedNoStaminaLoss) - ActivatedNoStaminaLoss = ApplyNoStaminaLoss(config); - if (!ActivatedNoFallDamage) - ActivatedNoFallDamage = ApplyNoFallDamage(config); - if (!ActivatedNoDurabilityLoss) - ActivatedNoDurabilityLoss = ApplyNoDurabilityLoss(config); - if (!ActivatedCloneItemSplits) - ActivatedCloneItemSplits = ApplyCloneItemSplits(config); - if (!ActivatedFreeCraft) - ActivatedFreeCraft = ApplyFreeCraft(config); - if (!ActivatedFreeBuild) - ActivatedFreeBuild = ApplyFreeBuild(config); - Sleep(2000); // Wait for 1 second before trying again + if (config.active) { + if (config.glider_flight && !gliderFlight.active) + gliderFlight.activate(); + else if (!config.glider_flight && gliderFlight.active) + gliderFlight.deactivate(); + + if (config.no_stamina_loss && !noStaminaLoss.active) + noStaminaLoss.activate(); + else if (!config.no_stamina_loss && noStaminaLoss.active) + noStaminaLoss.deactivate(); + + if (config.no_fall_damage && !noFallDamage.active) + noFallDamage.activate(); + else if (!config.no_fall_damage && noFallDamage.active) + noFallDamage.deactivate(); + + if (config.no_craft_cost && !noCraftCost.active) + noCraftCost.activate(); + else if (!config.no_craft_cost && noCraftCost.active) + noCraftCost.deactivate(); + + if (config.inf_item_use && !infiniteItemUse.active) + infiniteItemUse.activate(); + else if (!config.inf_item_use && infiniteItemUse.active) + infiniteItemUse.deactivate(); + + if (config.bypass_world_borders && !bypassWorldBorders.active) + bypassWorldBorders.activate(); + else if (!config.bypass_world_borders && bypassWorldBorders.active) + bypassWorldBorders.deactivate(); + + if (config.bypass_altar_limit && !bypassAltarLimit.active) + bypassAltarLimit.activate(); + else if (!config.bypass_altar_limit && bypassAltarLimit.active) + bypassAltarLimit.deactivate(); + } + Sleep(2000); // Wait before trying again + config.reloadIfChanged(); } } diff --git a/shroudtopia/mem.h b/shroudtopia/mem.h index 856ed13..556480c 100644 --- a/shroudtopia/mem.h +++ b/shroudtopia/mem.h @@ -4,23 +4,83 @@ namespace Mem { - uintptr_t FindPattern(const char *pattern, const char *mask, uintptr_t start, size_t length) +#include +#include + + bool WriteToReadOnlyMemory(LPVOID targetAddress, LPVOID data, SIZE_T size) { - for (uintptr_t i = start; i < start + length; i++) + HANDLE hProcess = GetCurrentProcess(); + DWORD oldProtect; + + // Change memory protection to PAGE_EXECUTE_READWRITE + if (!VirtualProtectEx(hProcess, targetAddress, size, PAGE_EXECUTE_READWRITE, &oldProtect)) + { + std::cerr << "Failed to change memory protection: " << GetLastError() << std::endl; + return false; + } + + // Write the data to the target address + SIZE_T bytesWritten; + if (!WriteProcessMemory(hProcess, targetAddress, data, size, &bytesWritten) || bytesWritten != size) + { + std::cerr << "Failed to write to memory: " << GetLastError() << std::endl; + // Restore original protection before exiting + VirtualProtectEx(hProcess, targetAddress, size, oldProtect, &oldProtect); + return false; + } + + // Restore original protection + if (!VirtualProtectEx(hProcess, targetAddress, size, oldProtect, &oldProtect)) { - bool found = true; - for (size_t j = 0; mask[j] != '\0'; j++) + std::cerr << "Failed to restore memory protection: " << GetLastError() << std::endl; + return false; + } + + std::cout << "Successfully wrote to read-only memory at: 0x" << std::hex << (uintptr_t)targetAddress << std::endl; + return true; + } + + uintptr_t FindPattern(const char* pattern, const char* mask, uintptr_t start, size_t length) + { + MEMORY_BASIC_INFORMATION mbi; + uintptr_t currentAddress = start; + + while (currentAddress < start + length) + { + if (VirtualQuery((LPCVOID)currentAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) { - if (mask[j] != '?' && pattern[j] != *(char *)(i + j)) + if (mbi.State == MEM_COMMIT && + (mbi.Protect & (PAGE_EXECUTE_READ | PAGE_EXECUTE | PAGE_READONLY | PAGE_EXECUTE_READWRITE | PAGE_READWRITE))) { - found = false; - break; + uintptr_t regionStart = (uintptr_t)mbi.BaseAddress; + uintptr_t regionEnd = regionStart + mbi.RegionSize; + + for (uintptr_t i = regionStart; i < regionEnd - strlen(mask); i++) + { + bool found = true; + for (size_t j = 0; mask[j] != '\0'; j++) + { + if (mask[j] != '?' && pattern[j] != *(char*)(i + j)) + { + found = false; + break; + } + } + if (found) + { + return i; // Found the pattern + } + } } + currentAddress += mbi.RegionSize; // Move to the next region + } + else + { + break; // Exit if VirtualQuery fails } - if (found) - return i; } - return 0; + + return 0; // Pattern not found } static bool Write(uintptr_t address, const void *buffer, size_t size) @@ -69,17 +129,28 @@ namespace Mem LPVOID lpBaseAddress = nullptr; // Calculate a range near the target address - LPVOID lpMinAddress = (LPVOID)((DWORD_PTR)address - si.dwAllocationGranularity); + LPVOID lpMinAddress = (LPVOID)((DWORD_PTR)address - si.dwAllocationGranularity * 10); + + MEMORY_BASIC_INFORMATION mbi; + VirtualQueryEx(hProcess, lpMinAddress, &mbi, sizeof(mbi)); + + DWORD oldProtect; + VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &oldProtect); // Attempt to allocate memory within the specified range lpBaseAddress = VirtualAllocEx(hProcess, lpMinAddress, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (!lpBaseAddress) + int tries = 0; + static const int maxTries = 10; + while (!lpBaseAddress && tries < maxTries) { // Retry with a wider range if necessary - lpBaseAddress = VirtualAllocEx(hProcess, (LPVOID)((uintptr_t)lpMinAddress - 0x10000000), dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + lpBaseAddress = VirtualAllocEx(hProcess, (LPVOID)((uintptr_t)lpMinAddress - 0x10000000 * (tries + 1)), dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + tries++; } + VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, oldProtect, &oldProtect); + return lpBaseAddress; } @@ -138,6 +209,7 @@ namespace Mem Shellcode(uint8_t *opcode, size_t opcodeSize, uintptr_t nearAddr = 0) { uintptr_t address = (uintptr_t)Mem::AllocateMemoryNearAddress(reinterpret_cast(nearAddr), 0x1000); + if (!address) LOG_CLASS("Could not allocate Memory for shell code."); data = new MemoryData(address, opcode, opcodeSize); } @@ -267,6 +339,11 @@ namespace Mem bool activate() { + auto oss = std::ostringstream() << "Shellcode: " << std::hex << "0x" << shellcode->data->address << "\n"; + oss << "Patch: " << std::hex << "0x" << patch->data->address << "\n"; + std::string logMessage = oss.str(); + LOG_CLASS(logMessage.c_str()); + if (!active && shellcode && patch) return shellcode->inject() && patch->apply() && (active = true); return false; @@ -274,8 +351,10 @@ namespace Mem bool deactivate() { - if (active && shellcode && patch) - return shellcode->inject() && patch->apply() && (active = false); + if (active && patch) { + patch->undo(); + return (active = false); + } return false; } }; diff --git a/shroudtopia/shroudtopia.sln b/shroudtopia/shroudtopia.sln index c540313..4587d0e 100644 --- a/shroudtopia/shroudtopia.sln +++ b/shroudtopia/shroudtopia.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shroudtopia", "shroudtopia.vcxproj", "{7031243F-7A42-4DA4-B1CD-1EF3603463D6}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winmm", "..\winmm\winmm.vcxproj", "{6FCED408-75B2-4EF9-9E5F-3EC58B19249B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,6 +23,14 @@ Global {7031243F-7A42-4DA4-B1CD-1EF3603463D6}.Release|x64.Build.0 = Release|x64 {7031243F-7A42-4DA4-B1CD-1EF3603463D6}.Release|x86.ActiveCfg = Release|Win32 {7031243F-7A42-4DA4-B1CD-1EF3603463D6}.Release|x86.Build.0 = Release|Win32 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Debug|x64.ActiveCfg = Debug|x64 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Debug|x64.Build.0 = Debug|x64 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Debug|x86.ActiveCfg = Debug|Win32 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Debug|x86.Build.0 = Debug|Win32 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Release|x64.ActiveCfg = Release|x64 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Release|x64.Build.0 = Release|x64 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Release|x86.ActiveCfg = Release|Win32 + {6FCED408-75B2-4EF9-9E5F-3EC58B19249B}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/shroudtopia/shroudtopia.vcxproj b/shroudtopia/shroudtopia.vcxproj index 6201006..291ccf1 100644 --- a/shroudtopia/shroudtopia.vcxproj +++ b/shroudtopia/shroudtopia.vcxproj @@ -112,6 +112,7 @@ true Use pch.h + stdcpp17 Windows @@ -129,6 +130,7 @@ true Use pch.h + stdcpp17 Windows diff --git a/shroudtopia/utils.h b/shroudtopia/utils.h index b4d0c81..c9ee8a5 100644 --- a/shroudtopia/utils.h +++ b/shroudtopia/utils.h @@ -5,6 +5,8 @@ #include #define LOG_FILE "shroudtopia.log" +#define LOG_CLASS(msg) Utils::Log(typeid(*this).name(), msg) + namespace Utils { void Log(const char *szInput) @@ -15,6 +17,14 @@ namespace Utils std::cout << "[shroudtopia] " << szInput << std::endl; } + void Log(const char* className, const char* szInput) + { + std::ofstream log(LOG_FILE, std::ios_base::app | std::ios_base::out); + log << "[shroudtopia]" << "[" << className << "] " << szInput; + log << "\n"; + std::cout << "[shroudtopia]" << "[" << className << "] " << szInput << std::endl; + } + void PrintMemory(uintptr_t address, uint8_t* buffer, size_t size) { if (buffer)