diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29a1e6e..3c6d683 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,11 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v2 - uses: xmake-io/github-action-setup-xmake@v1 - with: - xmake-version: branch@master + - run: | xmake repo -u @@ -21,8 +21,8 @@ jobs: - run: | xmake -w -y - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} path: | - bin/ + bin/ \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d727f8b..852fe38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,11 +7,11 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v2 - uses: xmake-io/github-action-setup-xmake@v1 - with: - xmake-version: branch@master + - run: | xmake repo -u @@ -21,7 +21,7 @@ jobs: - run: | xmake -w -y - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} path: | @@ -34,24 +34,26 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - name: Download Mod + uses: actions/download-artifact@v4 with: name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} - path: release/ + path: artifact - - run: | - cp LICENSE README.md release/ + - name: Copy additional files + run: | + cp -v LICENSE README.md artifact/ - name: Archive release run: | - cd release zip -r ../${{ github.event.repository.name }}-windows-x64.zip * - cd .. + working-directory: artifact - - uses: softprops/action-gh-release@v1 + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 with: - append_body: true files: | - ${{ github.event.repository.name }}-windows-x64.zip + ${{ github.event.repository.name }}-windows-x64.zip \ No newline at end of file diff --git a/manifest.json b/manifest.json index 8b2dc91..c7a6e74 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,13 @@ { - "name": "FuckNetherHeight", - "entry": "FuckNetherHeight.dll", + "name": "${modName}", + "entry": "${modFile}", "type": "native", - "version": "0.13.5" + "version": "${modVersion}", + "description": "Fuck Vanilla Bedrock Edition Nether Height Limit", + "author": "GroupMountain", + "dependencies": [ + { + "name": "GMLIB" + } + ] } \ No newline at end of file diff --git a/scripts/after_build.lua b/scripts/after_build.lua deleted file mode 100644 index 9475b5a..0000000 --- a/scripts/after_build.lua +++ /dev/null @@ -1,118 +0,0 @@ -function beautify_json(value, indent) - import("core.base.json") - local json_text = "" - local stack = {} - - local function escape_str(s) - return string.gsub(s, '[%c\\"]', function(c) - local replacements = {['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t', ['"'] = '\\"', ['\\'] = '\\\\'} - return replacements[c] or string.format('\\u%04x', c:byte()) - end) - end - - local function is_null(v) - return v == json.null - end - - local function is_empty_table(t) - if type(t) ~= 'table' then return false end - for _ in pairs(t) do - return false - end - return true - end - - local function is_array(t) - return type(t) == 'table' and json.is_marked_as_array(t) or #t > 0 - end - - local function serialize(val, level) - local spaces = string.rep(" ", level * indent) - - if type(val) == "table" and not stack[val] then - if is_empty_table(val) then - json_text = json_text .. (is_array(val) and "[]" or "{}") - return - end - - stack[val] = true - local isArray = is_array(val) - json_text = json_text .. (isArray and "[\n" or "{\n") - - local keys = isArray and {} or {} - for k in pairs(val) do - table.insert(keys, k) - end - if not isArray then - table.sort(keys) - end - - for _, k in ipairs(keys) do - local v = val[k] - json_text = json_text .. spaces .. (isArray and "" or '"' .. escape_str(tostring(k)) .. '": ') - serialize(v, level + 1) - json_text = json_text .. ",\n" - end - - json_text = string.sub(json_text, 1, -3) .. "\n" .. string.rep(" ", (level - 1) * indent) .. (isArray and "]" or "}") - stack[val] = nil - elseif type(val) == "string" then - json_text = json_text .. '"' .. escape_str(val) .. '"' - elseif type(val) == "number" then - if val % 1 == 0 then - json_text = json_text .. tostring(math.floor(val)) - else - json_text = json_text .. tostring(val) - end - elseif type(val) == "boolean" then - json_text = json_text .. tostring(val) - elseif is_null(val) then - json_text = json_text .. "null" - else - error("Invalid value type: " .. type(val)) - end - end - serialize(value, 1) - return json_text -end - -function string_formatter(str, variables) - return str:gsub("%${(.-)}", function(var) - return variables[var] or "${" .. var .. "}" - end) -end - -function pack_plugin(target,plugin_define) - import("lib.detect.find_file") - - local manifest_path = find_file("manifest.json", os.projectdir()) - if manifest_path then - local manifest = io.readfile(manifest_path) - local bindir = path.join(os.projectdir(), "bin") - local outputdir = path.join(bindir, plugin_define.pluginName) - local targetfile = path.join(outputdir, plugin_define.pluginFile) - local pdbfile = path.join(outputdir, path.basename(plugin_define.pluginFile) .. ".pdb") - local manifestfile = path.join(outputdir, "manifest.json") - local oritargetfile = target:targetfile() - local oripdbfile = path.join(path.directory(oritargetfile), path.basename(oritargetfile) .. ".pdb") - - os.mkdir(outputdir) - os.cp(oritargetfile, targetfile) - if os.isfile(oripdbfile) then - os.cp(oripdbfile, pdbfile) - end - - formattedmanifest = string_formatter(manifest, plugin_define) - io.writefile(manifestfile,formattedmanifest) - cprint("${bright green}[Plugin Packer]: ${reset}plugin already generated to " .. outputdir) - else - cprint("${bright yellow}warn: ${reset}not found manifest.json in root dir!") - end -end - - -return { - pack_plugin = pack_plugin, - beautify_json = beautify_json, - string_formatter = string_formatter -} diff --git a/src/Entry.cpp b/src/Entry.cpp new file mode 100644 index 0000000..f976a27 --- /dev/null +++ b/src/Entry.cpp @@ -0,0 +1,33 @@ +#include "Global.h" + +namespace FuckNetherHeight { + +std::unique_ptr& Entry::getInstance() { + static std::unique_ptr instance; + return instance; +} + +bool Entry::load() { + if (ll::getServerStatus() != ll::ServerStatus::Starting) { + logger.error("FuckNetherHeight must be loaded at startup!"); + return false; + } + enableMod(); + return true; +} + +bool Entry::enable() { + logger.info("FuckNetherHeight Loaded!"); + logger.info("Author: GroupMountain"); + logger.info("Repository: https://github.com/GroupMountain/FuckNetherHeight"); + return true; +} + +bool Entry::disable() { + disableMod(); + return true; +} + +} // namespace FuckNetherHeight + +LL_REGISTER_MOD(FuckNetherHeight::Entry, FuckNetherHeight::Entry::getInstance()); \ No newline at end of file diff --git a/src/Entry.h b/src/Entry.h new file mode 100644 index 0000000..6655c05 --- /dev/null +++ b/src/Entry.h @@ -0,0 +1,27 @@ +#include // temporarily fix the workflows build + +#include +#include + +namespace FuckNetherHeight { + +class Entry { + +public: + static std::unique_ptr& getInstance(); + + Entry(ll::mod::NativeMod& self) : mSelf(self) {} + + [[nodiscard]] ll::mod::NativeMod& getSelf() const { return mSelf; } + + bool load(); + + bool enable(); + + bool disable(); + +private: + ll::mod::NativeMod& mSelf; +}; + +} // namespace FuckNetherHeight \ No newline at end of file diff --git a/src/FuckNetherHeight.cpp b/src/FuckNetherHeight.cpp index ff760ae..516b656 100644 --- a/src/FuckNetherHeight.cpp +++ b/src/FuckNetherHeight.cpp @@ -1,241 +1,248 @@ #include "Global.h" -#include "TCHelper.h" namespace dimension_utils { -void sendEmptyChunk(const NetworkIdentifier& netId, int chunkX, int chunkZ, bool forceUpdate) { +void sendEmptyChunk(Player& player, int chunkX, int chunkZ, bool forceUpdate) { + GMLIB_BinaryStream stream; + stream.writePacketHeader(MinecraftPacketIds::LevelChunk); // header + stream.writeVarInt(chunkX); // chunkX + stream.writeVarInt(chunkZ); // chunkZ + stream.writeVarInt(VanillaDimensions::TheEnd.id); // dimensionId + stream.writeUnsignedVarInt(0); // subChunkCount + stream.writeBool(false); // cacheEnabled std::array biome{}; - LevelChunkPacket levelChunkPacket; - BinaryStream binaryStream{levelChunkPacket.mSerializedChunk, false}; - VarIntDataOutput varIntDataOutput(&binaryStream); - - varIntDataOutput.writeBytes(&biome, 4096); // write void biome + stream.write(&biome, 4096); // write void biome for (int i = 1; i < 8; i++) { - varIntDataOutput.writeByte((127 << 1) | 1); + stream.writeByte((127 << 1) | 1); } - varIntDataOutput.mStream->writeUnsignedChar(0); // write border blocks - - levelChunkPacket.mPos.x = chunkX; - levelChunkPacket.mPos.z = chunkZ; - levelChunkPacket.mDimensionType = 0; - levelChunkPacket.mCacheEnabled = false; - levelChunkPacket.mSubChunksCount = 0; - - ll::service::getLevel()->getPacketSender()->sendToClient(netId, levelChunkPacket, SubClientId::PrimaryClient); + stream.writeUnsignedChar(0); // write border blocks + stream.sendTo(player); if (forceUpdate) { - NetworkBlockPosition pos{chunkX << 4, 80, chunkZ << 4}; - UpdateBlockPacket blockPacket; - blockPacket.mPos = pos; - blockPacket.mLayer = UpdateBlockPacket::BlockLayer::Standard; - blockPacket.mUpdateFlags = BlockUpdateFlag::Neighbors; - ll::service::getLevel()->getPacketSender()->sendToClient(netId, blockPacket, SubClientId::PrimaryClient); + UpdateBlockPacket( + {chunkX << 4, 80, chunkZ << 4}, + (uint)UpdateBlockPacket::BlockLayer::Standard, + 0, + (uchar)BlockUpdateFlag::Neighbors + ) + .sendTo(player); } } -void sendEmptyChunks(const NetworkIdentifier& netId, const Vec3& position, int radius, bool forceUpdate) { - int chunkX = (int)(position.x) >> 4; - int chunkZ = (int)(position.z) >> 4; +void sendEmptyChunks(Player& player, int radius, bool forceUpdate) { + int chunkX = (int)(player.getPosition().x) >> 4; + int chunkZ = (int)(player.getPosition().z) >> 4; for (int x = -radius; x <= radius; x++) { for (int z = -radius; z <= radius; z++) { - sendEmptyChunk(netId, chunkX + x, chunkZ + z, forceUpdate); + sendEmptyChunk(player, chunkX + x, chunkZ + z, forceUpdate); } } } -void fakeChangeDimension( - const NetworkIdentifier& netId, - ActorRuntimeID runtimeId, - DimensionType fakeDimId, - const Vec3& pos -) { - PlayerFogPacket fogPacket{{}}; - ll::service::getLevel()->getPacketSender()->sendToClient(netId, fogPacket, SubClientId::PrimaryClient); - ChangeDimensionPacket changeDimensionPacket{fakeDimId, pos, true}; - ll::service::getLevel()->getPacketSender()->sendToClient(netId, changeDimensionPacket, SubClientId::PrimaryClient); - PlayerActionPacket playerActionPacket{PlayerActionType::ChangeDimensionAck, runtimeId}; - ll::service::getLevel()->getPacketSender()->sendToClient(netId, playerActionPacket, SubClientId::PrimaryClient); - sendEmptyChunks(netId, pos, 3, true); +void fakeChangeDimension(Player& player) { + PlayerFogPacket(std::vector{}).sendTo(player); + ChangeDimensionPacket(VanillaDimensions::TheEnd, player.getPosition(), true).sendTo(player); + PlayerActionPacket(PlayerActionType::ChangeDimensionAck, player.getRuntimeID()).sendTo(player); + sendEmptyChunks(player, 3, true); } } // namespace dimension_utils -void PatchNetherHeight() { - uintptr_t regionStart = - (uintptr_t)ll::memory::resolveSymbol("??0NetherDimension@@QEAA@AEAVILevel@@AEAVScheduler@@@Z"); - std::vector pattern = TCHelper::splitHex("41 B9 00 00 80 00"); - uintptr_t address = ModUtils::SigScan(pattern, regionStart, 0x300); - ModUtils::Replace(address, TCHelper::splitHex("41 B9 00 00 80 00"), TCHelper::splitHex8("41 B9 00 00 00 01")); +#define DIM_ID_MODIRY(name, ...) \ + if (name == VanillaDimensions::Nether) { \ + name = VanillaDimensions::TheEnd; \ + __VA_ARGS__ \ + } + +LL_TYPE_INSTANCE_HOOK( + ModifyNetherHeightHook, + HookPriority::Normal, + Dimension, + "??0Dimension@@QEAA@AEAVILevel@@V?$AutomaticID@VDimension@@H@@VDimensionHeightRange@@AEAVScheduler@@V?$basic_" + "string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z", + Dimension*, + ILevel& level, + DimensionType dimId, + DimensionHeightRange heightRange, + Scheduler& callbackContext, + std::string name +) { + if (dimId == VanillaDimensions::Nether) { + heightRange.max = 256; + } + return origin(level, dimId, heightRange, callbackContext, name); +} + +LL_TYPE_INSTANCE_HOOK( + ClientGenerationHook, + HookPriority::Normal, + PropertiesSettings, + &PropertiesSettings::isClientSideGenEnabled, + bool +) { + return false; } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( SubChunkRequestHandle, - ll::memory::HookPriority::Normal, + HookPriority::Normal, ServerNetworkHandler, "?handle@ServerNetworkHandler@@UEAAXAEBVNetworkIdentifier@@AEBVSubChunkRequestPacket@@@Z", void, NetworkIdentifier const& id, SubChunkRequestPacket& pkt ) { - auto pl = this->getServerPlayer(id, pkt.mClientSubId); - pkt.mDimensionType = pl->getDimensionId(); + pkt.mDimensionType = this->getServerPlayer(id, pkt.mClientSubId)->getDimensionId(); return origin(id, pkt); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( SubChunkPacketWrite, - ll::memory::HookPriority::Normal, + HookPriority::Normal, SubChunkPacket, "?write@SubChunkPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionType == 1) { - this->mDimensionType = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionType); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( ChangeDimensionPacketWrite, - ll::memory::HookPriority::Normal, + HookPriority::Normal, ChangeDimensionPacket, "?write@ChangeDimensionPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionId == 1) { - this->mDimensionId = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionId); + return origin(stream); } - -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( LevelRequestPlayerChangeDimensionHook, HookPriority::Normal, Level, "?requestPlayerChangeDimension@Level@@UEAAXAEAVPlayer@@$$QEAVChangeDimensionRequest@@@Z", void, - class Player& player, - class ChangeDimensionRequest&& changeRequest + Player& player, + ChangeDimensionRequest&& changeRequest ) { - auto inId = player.getDimensionId(); - if ((inId == 1 && changeRequest.mToDimensionId == 2) || (inId == 2 && changeRequest.mToDimensionId == 1)) { - dimension_utils::fakeChangeDimension( - player.getNetworkIdentifier(), - player.getRuntimeID(), - 0, - player.getPosition() - ); + auto fromId = player.getDimensionId(); + if ((fromId == 1 && changeRequest.mToDimensionId == 2) || (fromId == 2 && changeRequest.mToDimensionId == 1)) { + dimension_utils::fakeChangeDimension(player); } return origin(player, std::move(changeRequest)); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( StartGamePacketWrite, - ll::memory::HookPriority::Normal, + HookPriority::Normal, StartGamePacket, "?write@StartGamePacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { auto settings = this->mSettings.getSpawnSettings(); - if (settings.dimension == 1) { - settings.dimension = 2; - this->mSettings.setSpawnSettings(settings); - } - return origin(bs); + DIM_ID_MODIRY(settings.dimension, this->mSettings.setSpawnSettings(settings);); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( - ClientGenerationHook, - ll::memory::HookPriority::Normal, - PropertiesSettings, - &PropertiesSettings::isClientSideGenEnabled, - bool -) { - return false; -} - -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( AddVolumeEntityPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, AddVolumeEntityPacket, "?write@AddVolumeEntityPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionType == 1) { - this->mDimensionType = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionType); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( ClientboundMapItemDataPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, ClientboundMapItemDataPacket, "?write@ClientboundMapItemDataPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimension == 1) { - this->mDimension = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimension); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( RemoveVolumeEntityPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, RemoveVolumeEntityPacket, "?write@RemoveVolumeEntityPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionType == 1) { - this->mDimensionType = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionType); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( SetSpawnPositionPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, SetSpawnPositionPacket, "?write@SetSpawnPositionPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionType == 1) { - this->mDimensionType = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionType); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( SpawnParticleEffectPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, SpawnParticleEffectPacket, "?write@SpawnParticleEffectPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mVanillaDimensionId == 1) { - this->mVanillaDimensionId = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mVanillaDimensionId); + return origin(stream); } -LL_AUTO_TYPE_INSTANCE_HOOK( +LL_TYPE_INSTANCE_HOOK( LevelChunkPacketHook, - ll::memory::HookPriority::Normal, + HookPriority::Normal, LevelChunkPacket, "?write@LevelChunkPacket@@UEBAXAEAVBinaryStream@@@Z", void, - BinaryStream const& bs + BinaryStream const& stream ) { - if (this->mDimensionType == 1) { - this->mDimensionType = 2; - } - return origin(bs); + DIM_ID_MODIRY(this->mDimensionType); + return origin(stream); +} + +struct Impl { + ll::memory::HookRegistrar< + ModifyNetherHeightHook, + ClientGenerationHook, + SubChunkRequestHandle, + SubChunkPacketWrite, + ChangeDimensionPacketWrite, + LevelRequestPlayerChangeDimensionHook, + StartGamePacketWrite, + AddVolumeEntityPacketHook, + ClientboundMapItemDataPacketHook, + RemoveVolumeEntityPacketHook, + SetSpawnPositionPacketHook, + SpawnParticleEffectPacketHook, + LevelChunkPacketHook> + hook; +}; + +static std::unique_ptr impl; + +void enableMod() { + if (!impl) impl = std::make_unique(); +} + +void disableMod() { + if (impl) impl.reset(); } \ No newline at end of file diff --git a/src/Global.h b/src/Global.h index 4af078a..3a564fb 100644 --- a/src/Global.h +++ b/src/Global.h @@ -1,31 +1,12 @@ #pragma once -#include "Plugin.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -extern ll::Logger logger; -extern void PatchNetherHeight(); \ No newline at end of file +#include // temporarily fix the workflows build + +#include "Entry.h" +#include + +#define selfMod FuckNetherHeight::Entry::getInstance() +#define logger selfMod->getSelf().getLogger() + +extern void enableMod(); +extern void disableMod(); \ No newline at end of file diff --git a/src/MemoryOperators.cpp b/src/MemoryOperators.cpp index f7c918a..d10e189 100644 --- a/src/MemoryOperators.cpp +++ b/src/MemoryOperators.cpp @@ -1,7 +1,2 @@ -// This file will make your plugin use LeviLamina's memory operators by default. -// This improves the memory management of your plugin and is recommended to use. -// You should not modify anything in this file. - #define LL_MEMORY_OPERATORS - -#include +#include \ No newline at end of file diff --git a/src/ModUtils.h b/src/ModUtils.h deleted file mode 100644 index 968710d..0000000 --- a/src/ModUtils.h +++ /dev/null @@ -1,473 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#include "ini.h" -#define INRANGE(x, a, b) (x >= a && x <= b) -#define GET_BYTE(x) (GET_BITS(x[0]) << 4 | GET_BITS(x[1])) -#define GET_BITS(x) (INRANGE((x & (~0x20)), 'A', 'F') ? ((x & (~0x20)) - 'A' + 0xa) : (INRANGE(x, '0', '9') ? x - '0' : 0)) - -namespace ModUtils { -static std::string muModuleName = ""; -static HWND muWindow = NULL; -static FILE* muLogFile = nullptr; -static bool muLogOpened = false; -static constexpr int MASKED = 0xffff; -static constexpr unsigned char HK_NONE = 0x07; - -class Timer { -public: - Timer(unsigned int millis) { - m_interval = millis; - } - - bool Check() { - auto now = std::chrono::system_clock::now(); - if (m_resetOnNextCheck) { - m_lastExecutionTime = now; - m_resetOnNextCheck = false; - return false; - } - - auto diff = std::chrono::duration_cast(now - m_lastExecutionTime); - if (diff.count() >= m_interval) { - m_lastExecutionTime = now; - return true; - } - - return false; - } - - void Reset() { - m_resetOnNextCheck = true; - } - -private: - unsigned int m_interval = 0; - bool m_resetOnNextCheck = true; - std::chrono::system_clock::time_point m_lastExecutionTime; -}; - -// Gets the name of the .dll which the mod code is running in -inline std::string GetModuleName(bool thisModule = true) { - static char dummy = 'x'; - HMODULE module = NULL; - - if (thisModule) { - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, &dummy, &module); - } - - char lpFilename[MAX_PATH]; - GetModuleFileNameA(module, lpFilename, sizeof(lpFilename)); - std::string moduleName = strrchr(lpFilename, '\\'); - moduleName = moduleName.substr(1, moduleName.length()); - - if (thisModule) { - moduleName.erase(moduleName.find(".dll"), moduleName.length()); - } - - return moduleName; -} - -// Gets the path to the current mod relative to the game root folder -inline std::string GetModuleFolderPath() { - return std::string("mods\\" + GetModuleName(true)); -} - -// Logs both to std::out and to a log file simultaneously -inline void Log(std::string msg, ...) { - if (muModuleName == "") { - muModuleName = GetModuleName(true); - } - - if (muLogFile == nullptr && !muLogOpened) { - CreateDirectoryA(std::string("mods\\" + muModuleName).c_str(), NULL); - fopen_s(&muLogFile, std::string("mods\\" + muModuleName + "\\log.txt").c_str(), "w"); - muLogOpened = true; - } - - va_list args; - va_start(args, msg); - vprintf(std::string(muModuleName + " > " + msg + "\n").c_str(), args); - if (muLogFile != nullptr) { - vfprintf(muLogFile, std::string(muModuleName + " > " + msg + "\n").c_str(), args); - fflush(muLogFile); - } - va_end(args); -} - -// The log should preferably be closed when code execution is finished. -inline void CloseLog() { - if (muLogFile != nullptr) { - fclose(muLogFile); - muLogFile = nullptr; - } -} - -// Shows a popup with a warning and logs that same warning. -inline void RaiseError(std::string error) { - if (muModuleName == "") { - muModuleName = GetModuleName(true); - } - // Log("Raised error: %s", error.c_str()); - // MessageBox(NULL, error.c_str(), muModuleName.c_str(), MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); -} - -// Gets the base address of the game's memory. -inline DWORD_PTR GetProcessBaseAddress(DWORD processId) { - DWORD_PTR baseAddress = 0; - HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); - HMODULE* moduleArray = nullptr; - LPBYTE moduleArrayBytes = 0; - DWORD bytesRequired = 0; - - if (processHandle) { - if (EnumProcessModules(processHandle, NULL, 0, &bytesRequired)) { - if (bytesRequired) { - moduleArrayBytes = (LPBYTE)LocalAlloc(LPTR, bytesRequired); - - if (moduleArrayBytes) { - unsigned int moduleCount; - - moduleCount = bytesRequired / sizeof(HMODULE); - moduleArray = (HMODULE*)moduleArrayBytes; - - if (EnumProcessModules(processHandle, moduleArray, bytesRequired, &bytesRequired)) { - baseAddress = (DWORD_PTR)moduleArray[0]; - } - - LocalFree(moduleArrayBytes); - } - } - } - - CloseHandle(processHandle); - } - - return baseAddress; -} - - -// Disables or enables the memory protection in a given region. Remembers and restores the original memory protection type of the given addresses. -inline void ToggleMemoryProtection(bool protectionEnabled, uintptr_t address, size_t size) { - static std::map protectionHistory; - if (protectionEnabled && protectionHistory.find(address) != protectionHistory.end()) { - VirtualProtect((void*)address, size, protectionHistory[address], &protectionHistory[address]); - protectionHistory.erase(address); - } else if (!protectionEnabled && protectionHistory.find(address) == protectionHistory.end()) { - DWORD oldProtection = 0; - VirtualProtect((void*)address, size, PAGE_EXECUTE_READWRITE, &oldProtection); - protectionHistory[address] = oldProtection; - } -} - -// Copies memory after changing the permissions at both the source and destination so we don't get an access violation. -inline void MemCopy(uintptr_t destination, uintptr_t source, size_t numBytes) { - ToggleMemoryProtection(false, destination, numBytes); - ToggleMemoryProtection(false, source, numBytes); - memcpy((void*)destination, (void*)source, numBytes); - ToggleMemoryProtection(true, source, numBytes); - ToggleMemoryProtection(true, destination, numBytes); -} - -// Simple wrapper around memset -inline void MemSet(uintptr_t address, unsigned char byte, size_t numBytes) { - ToggleMemoryProtection(false, address, numBytes); - memset((void*)address, byte, numBytes); - ToggleMemoryProtection(true, address, numBytes); -} - -inline std::vector split(std::string str, std::string pattern) { - std::string::size_type pos; - std::vector result; - str += pattern; - size_t size = str.size(); - for (size_t i = 0; i < size; i++) { - pos = str.find(pattern, i); - if (pos < size) { - std::string s = str.substr(i, pos - i); - result.push_back(s); - i = pos + pattern.size() - 1; - } - } - return result; -} - -inline uintptr_t FindSig(const char* szSignature) { - const char* pattern = szSignature; - uintptr_t firstMatch = 0; - DWORD processId = GetCurrentProcessId(); - static const uintptr_t rangeStart = GetProcessBaseAddress(processId); - static MODULEINFO miModInfo; - static bool init = false; - if (!init) { - init = true; - GetModuleInformation(GetCurrentProcess(), (HMODULE)rangeStart, &miModInfo, sizeof(MODULEINFO)); - } - static const uintptr_t rangeEnd = rangeStart + miModInfo.SizeOfImage; - BYTE patByte = GET_BYTE(pattern); - const char* oldPat = pattern; - - for (uintptr_t pCur = rangeStart; pCur < rangeEnd; pCur++) { - if (!*pattern) - return firstMatch; - - while (*(PBYTE)pattern == ' ') - pattern++; - - if (!*pattern) - return firstMatch; - - if (oldPat != pattern) { - oldPat = pattern; - if (*(PBYTE)pattern != '\?') - patByte = GET_BYTE(pattern); - } - if (*(PBYTE)pattern == '\?' || *(BYTE*)pCur == patByte) { - if (!firstMatch) - firstMatch = pCur; - - if (!pattern[2] || !pattern[1]) - return firstMatch; - pattern += 2; - } else { - pattern = szSignature; - firstMatch = 0; - } - } - return 0; -} - -// Scans the whole memory of the main process module for the given signature. -inline uintptr_t SigScan(std::vector pattern) { - DWORD processId = GetCurrentProcessId(); - uintptr_t regionStart = GetProcessBaseAddress(processId); - // Log("Process name: %s", GetModuleName(false).c_str()); - // Log("Process ID: %i", processId); - // Log("Process base address: 0x%llX", regionStart); - - std::string patternString = ""; - for (auto bytes : pattern) { - std::stringstream stream; - std::string byte = ""; - if (bytes == MASKED) { - byte = "?"; - } else { - stream << "0x" << std::hex << bytes; - byte = stream.str(); - } - patternString.append(byte + " "); - } - // Log("Pattern: %s", patternString.c_str()); - - size_t numRegionsChecked = 0; - uintptr_t currentAddress = 0; - while (numRegionsChecked < 1000000000000000) { - MEMORY_BASIC_INFORMATION memoryInfo = {0}; - if (VirtualQuery((void*)regionStart, &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_PARAMETER) { - // Log("Reached end of scannable memory."); - } else { - // Log("VirtualQuery failed, error code: %i.", error); - } - break; - } - regionStart = (uintptr_t)memoryInfo.BaseAddress; - uintptr_t regionSize = (uintptr_t)memoryInfo.RegionSize; - uintptr_t regionEnd = regionStart + regionSize; - uintptr_t protection = (uintptr_t)memoryInfo.Protect; - uintptr_t state = (uintptr_t)memoryInfo.State; - - bool readableMemory = (protection == PAGE_EXECUTE_READWRITE || - protection == PAGE_READWRITE || - protection == PAGE_READONLY || - protection == PAGE_WRITECOPY || - protection == PAGE_EXECUTE_WRITECOPY || - protection == PAGE_EXECUTE_READ || - protection == PAGE_WRITECOMBINE) && - state == MEM_COMMIT; - - if (readableMemory) { - // Log("Checking region: %p", regionStart); - currentAddress = regionStart; - while (currentAddress < regionEnd - pattern.size()) { - for (size_t i = 0; i < pattern.size(); i++) { - if (pattern[i] == MASKED) { - currentAddress++; - continue; - } else if (*(unsigned char*)currentAddress != (unsigned char)pattern[i]) { - currentAddress++; - break; - } else if (i == pattern.size() - 1) { - uintptr_t signature = currentAddress - pattern.size() + 1; - // Log(std::to_string(protection)); - // Log("Found signature at %p", signature); - return signature; - } - currentAddress++; - } - } - } else { - // Log("Skipped region: %p", regionStart); - } - - numRegionsChecked++; - regionStart += memoryInfo.RegionSize; - } - - // Log("Stopped at: %p, num regions checked: %i", currentAddress, numRegionsChecked); - RaiseError("Could not find signature!"); - return 0; -} - -inline uintptr_t SigScan(std::vector pattern, uintptr_t regionStart, int32_t size) { - DWORD processId = GetCurrentProcessId(); - // Log("Process name: %s", GetModuleName(false).c_str()); - // Log("Process ID: %i", processId); - // Log("Process base address: 0x%llX", regionStart); - - std::string patternString = ""; - for (auto bytes : pattern) { - std::stringstream stream; - std::string byte = ""; - if (bytes == MASKED) { - byte = "?"; - } else { - stream << "0x" << std::hex << bytes; - byte = stream.str(); - } - patternString.append(byte + " "); - } - // Log("Pattern: %s", patternString.c_str()); - - size_t numRegionsChecked = 0; - uintptr_t currentAddress = 0; - while (numRegionsChecked < 1000000000000000) { - MEMORY_BASIC_INFORMATION memoryInfo = {0}; - if (VirtualQuery((void*)regionStart, &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_PARAMETER) { - // Log("Reached end of scannable memory."); - } else { - // Log("VirtualQuery failed, error code: %i.", error); - } - break; - } - uintptr_t regionSize = (uintptr_t)memoryInfo.RegionSize; - uintptr_t protection = (uintptr_t)memoryInfo.Protect; - uintptr_t state = (uintptr_t)memoryInfo.State; - - bool readableMemory = (protection == PAGE_EXECUTE_READWRITE || - protection == PAGE_READWRITE || - protection == PAGE_READONLY || - protection == PAGE_WRITECOPY || - protection == PAGE_EXECUTE_WRITECOPY || - protection == PAGE_EXECUTE_READ || - protection == PAGE_WRITECOMBINE) && - state == MEM_COMMIT; - uintptr_t regionEnd = regionStart + size; - if (readableMemory) { - // Log("Checking region: %p", regionStart); - currentAddress = regionStart; - while (currentAddress < regionEnd) { - for (size_t i = 0; i < pattern.size(); i++) { - if (pattern[i] == MASKED) { - currentAddress++; - continue; - } else if (*(unsigned char*)currentAddress != (unsigned char)pattern[i]) { - currentAddress++; - break; - } else if (i == pattern.size() - 1) { - uintptr_t signature = currentAddress - pattern.size() + 1; - // Log(std::to_string(protection)); - // Log("Found signature at %p", signature); - return signature; - } - currentAddress++; - } - } - } else { - // Log("Skipped region: %p", regionStart); - } - - numRegionsChecked++; - regionStart += memoryInfo.RegionSize; - } - - // Log("Stopped at: %p, num regions checked: %i", currentAddress, numRegionsChecked); - RaiseError("Could not find signature!"); - return 0; -} - -// Replaces the memory at a given address with newBytes. Performs memory comparison with originalBytes to stop unintended memory modification. -inline bool Replace(uintptr_t address, std::vector originalBytes, std::vector newBytes) { - std::vector truncatedOriginalBytes; - for (auto byte : originalBytes) { - truncatedOriginalBytes.push_back((uint8_t)byte); - } - - std::string bufferString = ""; - std::vector buffer(originalBytes.size()); - MemCopy((uintptr_t)&buffer[0], (uintptr_t)(void*)address, buffer.size()); - for (size_t i = 0; i < buffer.size(); i++) { - std::stringstream stream; - stream << "0x" << std::hex << (unsigned int)buffer[i]; - std::string byte = stream.str(); - bufferString.append(byte); - if (originalBytes[i] == MASKED) { - bufferString.append("?"); - buffer[i] = 0xff; - } - bufferString.append(" "); - } - // Log("Bytes at address: %s", bufferString.c_str()); - - std::string newBytesString = ""; - for (size_t i = 0; i < newBytes.size(); i++) { - std::stringstream stream; - stream << "0x" << std::hex << (unsigned int)newBytes[i]; - std::string byte = stream.str(); - newBytesString.append(byte + " "); - } - // Log("New bytes: %s", newBytesString.c_str()); - - if (memcmp(&buffer[0], &truncatedOriginalBytes[0], buffer.size()) == 0) { - // Log("Bytes match"); - MemCopy((uintptr_t)(void*)address, (uintptr_t)&newBytes[0], newBytes.size()); - // Log("Patch applied"); - return true; - } else { - RaiseError("Bytes do not match!"); - } - return false; -} - -// Takes a 4-byte relative address and converts it to an absolute 8-byte address. -inline uintptr_t RelativeToAbsoluteAddress(uintptr_t relativeAddressLocation) { - uintptr_t absoluteAddress = 0; - intptr_t relativeAddress = 0; - MemCopy((uintptr_t)&relativeAddress, relativeAddressLocation, 4); - absoluteAddress = relativeAddressLocation + 4 + relativeAddress; - return absoluteAddress; -} - -// Places a 14-byte absolutely addressed jump from A to B. Add extra clearance when the jump doesn't fit cleanly. -inline void Hook(uintptr_t address, uintptr_t destination, size_t extraClearance = 0) { - size_t clearance = 14 + extraClearance; - MemSet(address, 0x90, clearance); - *(uintptr_t*)address = 0x0000000025ff; - MemCopy((address + 6), (uintptr_t)&destination, 8); - // Log("Created jump from %p to %p with a clearance of %i", address, destination, clearance); -} -} // namespace ModUtils diff --git a/src/Plugin.cpp b/src/Plugin.cpp deleted file mode 100644 index 19d960e..0000000 --- a/src/Plugin.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "Global.h" - -ll::Logger logger("FuckNetherHeight"); - -namespace plugin { - -Plugin::Plugin(ll::mod::NativeMod& self) : mSelf(self) { - // Code for loading the plugin goes here. - PatchNetherHeight(); -} - -bool Plugin::enable() { - logger.info("FuckNetherHeight Loaded!"); - logger.info("Author: GroupMountain"); - logger.info("Repository: https://github.com/GroupMountain/FuckNetherHeight"); - return true; -} - -bool Plugin::disable() { - logger.info("FuckNetherHeight Disabled!"); - return true; -} - -} // namespace plugin \ No newline at end of file diff --git a/src/Plugin.h b/src/Plugin.h deleted file mode 100644 index cc70847..0000000 --- a/src/Plugin.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "Global.h" -#include "ll/api/mod/NativeMod.h" - -namespace plugin { - -class Plugin { -public: - explicit Plugin(ll::mod::NativeMod& self); - - Plugin(Plugin&&) = delete; - Plugin(const Plugin&) = delete; - Plugin& operator=(Plugin&&) = delete; - Plugin& operator=(const Plugin&) = delete; - - ~Plugin() = default; - - /// @return True if the plugin is enabled successfully. - bool enable(); - - /// @return True if the plugin is disabled successfully. - bool disable(); - -private: - ll::mod::NativeMod& mSelf; -}; - -} // namespace plugin diff --git a/src/TCHelper.h b/src/TCHelper.h deleted file mode 100644 index 88524b1..0000000 --- a/src/TCHelper.h +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include "ModUtils.h" -using namespace std; - -namespace TCHelper { -inline string uto_string(uintptr_t x) { - stringstream ss; - ss << hex << x; - return ss.str(); -} - -inline uintptr_t string_tohex(const string& str) { - stringstream ss; - ss << hex << str; - uintptr_t res; - ss >> res; - return res; -} - - -inline vector splitHex8(const string& str) { - vector res; - stringstream ss(str); - string item; - while (getline(ss, item, ' ')) { - if (item == "?") { - res.push_back(ModUtils::MASKED); - } else { - res.push_back(stoul(item, nullptr, 16)); - } - } - return res; -} - -inline string uto_string(uint8_t x) { - stringstream ss; - ss << hex << x; - return ss.str(); -} - - -inline vector splitHex(const string& str) { - vector res; - stringstream ss(str); - string item; - while (getline(ss, item, ' ')) { - if (item == "?") { - res.push_back(ModUtils::MASKED); - } else { - res.push_back(stoul(item, nullptr, 16)); - } - - } - return res; -} -} \ No newline at end of file diff --git a/src/dllmain.cpp b/src/dllmain.cpp deleted file mode 100644 index 6237b32..0000000 --- a/src/dllmain.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include "Global.h" -#include "Plugin.h" - -namespace plugin { - -// The global plugin instance. -std::unique_ptr plugin = nullptr; - -extern "C" { -_declspec(dllexport) bool ll_plugin_load(ll::mod::NativeMod& self) { - plugin = std::make_unique(self); - - return true; -} - -/// @warning Unloading the plugin may cause a crash if the plugin has not released all of its -/// resources. If you are unsure, keep this function commented out. -// _declspec(dllexport) bool ll_plugin_unload(ll::plugin::Plugin&) { -// plugin.reset(); -// -// return true; -// } - -_declspec(dllexport) bool ll_plugin_enable(ll::mod::NativeMod&) { return plugin->enable(); } - -_declspec(dllexport) bool ll_plugin_disable(ll::mod::NativeMod&) { return plugin->disable(); } -} - -} // namespace plugin diff --git a/tooth.json b/tooth.json index 16b15e4..f737a04 100644 --- a/tooth.json +++ b/tooth.json @@ -1,7 +1,7 @@ { "format_version": 2, "tooth": "github.com/GroupMountain/FuckNetherHeight", - "version": "0.13.5", + "version": "0.13.0", "info": { "name": "FuckNetherHeight", "description": "Fuck Vinalla Bedrock Edition Nether Height Limit", @@ -14,7 +14,8 @@ }, "asset_url": "https://github.com/GroupMountain/FuckNetherHeight/releases/download/v0.13.0/FuckNetherHeight-windows-x64.zip", "prerequisites": { - "github.com/LiteLDev/LeviLamina": ">=0.13.4" + "github.com/LiteLDev/LeviLamina": ">=0.13.4", + "github.com/GroupMountain/GMLIB": ">=0.13.2" }, "files": { "place": [ diff --git a/xmake.lua b/xmake.lua index ccdc85f..34e26c8 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,19 +1,33 @@ -add_rules("mode.debug", "mode.release", "mode.releasedbg") +add_rules("mode.debug", "mode.release") +add_repositories("groupmountain-repo https://github.com/GroupMountain/xmake-repo.git") add_repositories("liteldev-repo https://github.com/LiteLDev/xmake-repo.git") if not has_config("vs_runtime") then set_runtimes("MD") end --- Option 1: Use the latest version of LeviLamina released on GitHub. -add_requires("levilamina") +add_requires("levilaminalibrary", "gmlib") -target("FuckNetherHeight") -- Change this to your plugin name. +option("pdb") + set_default(true) + set_description("Generate PDB files") + +option("version") + set_default("") + set_description("Version of the mod") + +target("FuckNetherHeight") -- Change this to your mod name. add_cxflags( "/EHa", "/utf-8" ) + add_defines( + "NOMINMAX", + "UNICODE", + "_HAS_CXX17", + "_HAS_CXX20" + ) add_files( "src/**.cpp" ) @@ -21,22 +35,46 @@ target("FuckNetherHeight") -- Change this to your plugin name. "src" ) add_packages( - "levilamina" + "levilaminalibrary", + "gmlib" ) add_shflags( "/DELAYLOAD:bedrock_server.dll" ) set_exceptions("none") set_kind("shared") - set_languages("cxx23") + set_languages("c++23") + if get_config("pdb") then + set_symbols("debug") + end after_build(function (target) - local plugin_packer = import("scripts.after_build") + import("lib.detect.find_file") - local plugin_define = { - pluginName = target:name(), - pluginFile = path.filename(target:targetfile()), + local version = get_config("version") + if version == "" then + version = os.iorun("git describe --tags --abbrev=0 --always"):match("(%d+.%d+.%d+)") + if not version then + print("Failed to parse version tag, using 0.0.0") + version = "0.0.0" + end + end + local mod_define = { + modName = target:name(), + modFile = path.filename(target:targetfile()), + modVersion = version } - - plugin_packer.pack_plugin(target,plugin_define) - end) + + local output_dir = path.join(os.projectdir(), "bin", target:name()) + local manifest_path = path.join(output_dir, "manifest.json") + os.cp(path.join(os.projectdir(), "manifest.json"), manifest_path) + io.gsub(manifest_path, "%${(.-)}", function(var) + return mod_define[var] or "${" .. var .. "}" + end) + os.cp(target:targetfile(), path.join(output_dir, target:name() .. ".dll")) + if get_config("pdb") and os.isfile(target:symbolfile()) then + os.cp(target:symbolfile(), path.join(output_dir, target:name() .. ".pdb")) + else + os.rm(path.join(output_dir, target:name() .. ".pdb")) + end + end) \ No newline at end of file