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
-
+
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)