From 40df56fa611e1539b11dcb774d263177c2c7150f Mon Sep 17 00:00:00 2001 From: theaddon Date: Mon, 30 Mar 2026 14:36:45 +0200 Subject: [PATCH] Implement basic map rendering --- src/dllmain.cpp | 219 ++++++++++++++++++++++++++++++++++++++++++++++-- src/dllmain.hpp | 1 + 2 files changed, 214 insertions(+), 6 deletions(-) diff --git a/src/dllmain.cpp b/src/dllmain.cpp index c928d13..6c3c51d 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -1,16 +1,51 @@ #include "dllmain.hpp" +#include "mc/src-client/common/client/renderer/TextureGroup.hpp" +#include +#include +#include +#include +#include ClientInstance* client; ShulkerRenderer shulkerRenderer; ItemStack shulkerInventory[SHULKER_CACHE_SIZE][27]; +Amethyst::InlineHook _MapItem_appendFormattedHovertext; Amethyst::InlineHook _Item_appendFormattedHovertext; Amethyst::InlineHook _ShulkerBoxBlockItem_appendFormattedHovertext; Amethyst::InlineHook _HoverRenderer__renderHoverBox; -static void Item_appendFormattedHovertext(Item* self, const ItemStackBase& itemStack, Level& level, std::string& text, uint8_t a5) { - _Item_appendFormattedHovertext(self, itemStack, level, text, a5); +std::string getDimensionStringId(DimensionType type) { + int id = type.runtimeID; + + switch (id) { + case 0: return "minecraft:overworld"; + case 1: return "minecraft:nether"; + case 2: return "minecraft:the_end"; + case 3: return "undefined"; + } + + return "unknown"; +} + +static void MapItem_appendFormattedHovertext(Item* self, const ItemStackBase& itemStack, Level& level, std::string& text, bool showCategory) { + CompoundTag* nbt = itemStack.mUserData; + + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); + + Item* item = itemStack.mItem; + std::string rawNameId = std::string(item->mRawNameId.c_str()); + text.append(std::format("\n{}8{}:{}{}r", "\xc2\xa7", item->mNamespace, rawNameId, "\xc2\xa7")); + text.append(std::format("{}8{:16x}", "\xc2\xa7", (uint64_t)nbt->getInt64("map_uuid"))); + + return; +} + +static void Item_appendFormattedHovertext(Item* self, const ItemStackBase& itemStack, Level& level, std::string& text, bool showCategory) { + Log::Info("{}", itemStack.mItem->getRawNameId().c_str()); + _Item_appendFormattedHovertext(self, itemStack, level, text, showCategory); Item* item = itemStack.mItem; uint64_t maxDamage = item->getMaxDamage(); @@ -32,6 +67,76 @@ static void Item_appendFormattedHovertext(Item* self, const ItemStackBase& itemS return; } + if (rawNameId == "clock") { + LocalPlayer* localPlayer = Amethyst::GetContext().mClientCtx->mClientInstance->getLocalPlayer(); + int tickTime = localPlayer->mLevel->getTime(); + int tickTimeOfDay = tickTime % 24000; // Minecraft day is 24000 ticks + int day = std::floor(tickTime / 24000); + + // Convert ticks to hours and minutes + // Minecraft day starts at 6:00 AM (tick 0) + // Each hour = 1000 ticks + int totalMinutes = (tickTimeOfDay / 1000.0) * 60; // Total minutes since midnight (tick 18000) + int minutesSinceSix = totalMinutes + (6 * 60); // Add 6 hours offset + + // Wrap around if past 24 hours + int totalMinutesInDay = minutesSinceSix % 1440; // 1440 minutes in a day + + int hours = totalMinutesInDay / 60; + int minutes = totalMinutesInDay % 60; + + // Convert to 12-hour format with AM/PM + bool isPM = hours >= 12; + int displayHours = hours % 12; + if (displayHours == 0) displayHours = 12; // 0 hours should display as 12 + + std::string ampm = isPM ? "PM" : "AM"; + + text.append(std::format("\n{}5{}:{:02d} {} Day {}", "\xc2\xa7", displayHours, minutes, ampm, day)); + text.append(std::format("\n{}5Tick {}/24000", "\xc2\xa7", tickTimeOfDay)); + } + + if (rawNameId == "compass") { + LocalPlayer* localPlayer = Amethyst::GetContext().mClientCtx->mClientInstance->getLocalPlayer(); + Player::PlayerSpawnPoint respawn = localPlayer->mPlayerRespawnPoint; + BlockPos defaultSpawn = localPlayer->mLevel->getDefaultSpawn(); + text.append(std::format("\n{}5XZ {} {}", "\xc2\xa7", defaultSpawn.x, defaultSpawn.z)); + } + + if (rawNameId == "lodestone_compass") { + uint32_t trackingHandle = itemStack.mUserData->getInt("trackingHandle"); + LocalPlayer* localPlayer = Amethyst::GetContext().mClientCtx->mClientInstance->getLocalPlayer(); + PositionTrackingDB::PositionTrackingDBClient* positionTrackingDBClient = localPlayer->mLevel->getPositionTrackerDBClient(); + + struct TrackingRecord { + std::byte unk[4]; + BlockPos mPosition; + DimensionType mDimensionType; + }*trackingRecord; + + using fnDecl = uint8_t(*)(void*, uint32_t const&, TrackingRecord**); + const auto fn = std::bit_cast(SigScan("40 55 56 57 48 83 EC 50 49 8B F0")); + + uint8_t result = fn(positionTrackingDBClient, trackingHandle, &trackingRecord); + + BlockPos pos = trackingRecord->mPosition; + text.append(std::format("\n{}5XYZ {} {} {} in {}({})", "\xc2\xa7", pos.x, pos.y, pos.z, getDimensionStringId(trackingRecord->mDimensionType), trackingRecord->mDimensionType.runtimeID)); + } + + if (rawNameId == "recovery_compass") { + LocalPlayer* localPlayer = Amethyst::GetContext().mClientCtx->mClientInstance->getLocalPlayer(); + std::optional lastDeathDimension = localPlayer->getLastDeathDimension(); + std::optional lastDeathPos = localPlayer->getLastDeathPos(); + if (lastDeathDimension.has_value() && lastDeathPos.has_value()) { + BlockPos pos = lastDeathPos.value(); + DimensionType dimension = lastDeathDimension.value(); + text.append(std::format("\n{}5XYZ {} {} {} in {}({})", "\xc2\xa7", pos.x, pos.y, pos.z, getDimensionStringId(dimension), dimension.runtimeID)); + } + else { + text.append(std::format("\n{}5No last death", "\xc2\xa7")); + } + } + text.append(std::format("\n{}8{}:{} ({}){}r", "\xc2\xa7", item->mNamespace, rawNameId, item->mId, "\xc2\xa7")); } @@ -78,15 +183,12 @@ static void ShulkerBoxBlockItem_appendFormattedHovertext(ShulkerBoxBlockItem* se byte slot = itemCompound->getByte("Slot"); shulkerInventory[thisIndex][slot]._loadItem(itemCompound); } + Log::Info("\n{}", text); } static void HoverRenderer__renderHoverBox(HoverRenderer* self, MinecraftUIRenderContext* ctx, IClientInstance* client, RectangleArea* aabb, float someFloat) { - // Freddie: // This is really bad code, it is relying on the fact that I have also hooked appendFormattedHovertext for items to append the item identifier // I have no idea where the currently hovered item is stored in the game! I can't find any references to it, so it might be set in some weird place? - // - // Lucy: - // No clue either but this should be more solid if (self->mFilteredContent.find("shulker_box") != std::string::npos) { // Find the position right after "shulker_box§r" @@ -127,6 +229,109 @@ static void HoverRenderer__renderHoverBox(HoverRenderer* self, MinecraftUIRender } } + if (self->mFilteredContent.find("filled_map") != std::string::npos) { + size_t mapPos = self->mFilteredContent.find("filled_map"); + if (mapPos != std::string::npos) { + size_t searchStart = mapPos + 10; // length of "filled_map" + + // Skip "§r" + if (searchStart + 2 < self->mFilteredContent.size() && + self->mFilteredContent[searchStart] == '\xc2' && + self->mFilteredContent[searchStart + 1] == '\xa7' && + self->mFilteredContent[searchStart + 2] == 'r') { + searchStart += 3; + } + + // Skip "§8" + if (searchStart + 2 < self->mFilteredContent.size() && + self->mFilteredContent[searchStart] == '\xc2' && + self->mFilteredContent[searchStart + 1] == '\xa7') { + searchStart += 3; + } + + // Extract exactly 16 hex characters + std::string uuidStr = self->mFilteredContent.substr(searchStart, 16); + + try { + uint64_t uuid = std::stoull(uuidStr, nullptr, 16); + + struct MapRenderer { + std::byte unk1[0x198]; + SubClientId mClientId; + mce::TexturePtr mDecorationTexture; + mce::TexturePtr mMapBackgroundTexture; + mce::TexturePtr mIconBackgroundTexture; + std::byte unk2[1352]; + mce::Mesh mBackgroundMesh; + mce::Mesh mForegroundMesh; + mce::Mesh mDecorationMeshes[25]; + }; + + auto mapRenderer = std::unique_ptr < MapRenderer, decltype([](MapRenderer* p) { + std::free(p); + }) > (static_cast(std::malloc(sizeof(MapRenderer)))); + + using mapRendererCtorDecl = MapRenderer * (MapRenderer::*)(SubClientId, std::shared_ptr); + const auto mapRendererCtor = std::bit_cast(SigScan("48 89 5C 24 10 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 B0 FA")); + + (mapRenderer.get()->*mapRendererCtor)(client->asInstance().mClientSubId, std::shared_ptr(*ctx->mTextures)); + + BaseActorRenderContext renderCtxPtr = BaseActorRenderContext(*ctx->mScreenContext, *ctx->mClient, *ctx->mClient->mMinecraftGame); + + using mapRenderer_generateMeshesDecl = void (MapRenderer::*)(BaseActorRenderContext&); + const auto mapRenderer_generateMeshes = std::bit_cast(SigScan("48 8B C4 48 89 58 18 48 89 70 20 55 57 41 56 48 8D A8 48 F9")); + + (mapRenderer.get()->*mapRenderer_generateMeshes)(renderCtxPtr); + + struct MapRenderer_MapInstance { + std::byte unk1[0x24]; + MapRenderer& mMapRenderer; + std::byte unk2[0x24]; + mce::TexturePtr mMapTexture; + std::byte unk3[0x64]; + }; + struct MapItemSavedData {}; + + auto mapInstance = std::unique_ptr < MapRenderer_MapInstance, decltype([](MapRenderer_MapInstance* p) { + std::free(p); + }) > (static_cast(std::malloc(sizeof(MapRenderer_MapInstance)))); + auto savedData = std::bit_cast(client->asInstance().getLocalPlayer()->getLevel()->getMapSavedData(uuid)); + + using MapRenderer_MapInstanceCtorDecl = MapRenderer_MapInstance * (MapRenderer_MapInstance::*)(SubClientId, const MapItemSavedData*, MapRenderer*); + const auto MapRenderer_MapInstanceCtor = std::bit_cast(SigScan("48 89 5C 24 10 48 89 74 24 18 55 57 41 54 41 56 41 57 48 8D AC 24 50 FF FF FF 48 81 EC B0 01 00 00 48 8B F1 48 89 8D")); + + (mapInstance.get()->*MapRenderer_MapInstanceCtor)(client->asInstance().mClientSubId, savedData, mapRenderer.get()); + + using MapRenderer_MapInstance_updateTextureDecl = void (MapRenderer_MapInstance::*)(); + const auto MapRenderer_MapInstance_updateTexture = std::bit_cast(SigScan("48 89 5C 24 10 48 89 74 24 18 48 89 7C 24 20 55 41 54 41 55 41 56 41 57 48 8D 6C 24 E0 48 81 EC 20 01 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 18 48 8B F1 48 89 4C")); + + (mapInstance.get()->*MapRenderer_MapInstance_updateTexture)(); + + Matrix& matrix = ctx->mScreenContext->camera->worldMatrixStack.stack.top(); + Matrix originalMatrix = matrix; + + ClientInstance& client = *Amethyst::GetContext().mClientCtx->mClientInstance; + Vec2 screenSize = client.mGuiData->clientUIScreenSize; + + mce::MaterialPtr* mapMaterial = reinterpret_cast(SlideAddress(0x59BD7E0)); + + matrix.translate(self->mCursorPosition.x + self->mOffset.x, self->mCursorPosition.y + self->mOffset.y, 0); + mapRenderer->mBackgroundMesh.renderMesh(*ctx->mScreenContext, *mapMaterial, mapRenderer->mMapBackgroundTexture); + mapRenderer->mBackgroundMesh.renderMesh(*ctx->mScreenContext, *mapMaterial, mapInstance->mMapTexture); + + Log::Info("{}", mapInstance->mMapTexture.mResourceLocation->mPath); + Log::Info("{}", mapRenderer->mMapBackgroundTexture.mResourceLocation->mPath); + + matrix = originalMatrix; + + return; + } + catch (...) { + // Fall through to default rendering + } + } + } + _HoverRenderer__renderHoverBox(self, ctx, client, aabb, someFloat); } @@ -138,4 +343,6 @@ ModFunction void Initialize(AmethystContext& ctx, const Amethyst::Mod& mod) { VHOOK(Item, appendFormattedHovertext, this); VHOOK(ShulkerBoxBlockItem, appendFormattedHovertext, this); HOOK(HoverRenderer, _renderHoverBox, this); + + hooks.CreateHookAbsolute(_MapItem_appendFormattedHovertext, SigScan("40 55 53 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 F8 48 81 EC 08 01 00 00 4D 8B F9 4D"), &MapItem_appendFormattedHovertext); } \ No newline at end of file diff --git a/src/dllmain.hpp b/src/dllmain.hpp index 2b895eb..4d2292d 100644 --- a/src/dllmain.hpp +++ b/src/dllmain.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include