Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(HOGWARTSMP_CLIENT_FILES
src/core/hooks/localplayer.cpp
src/core/hooks/pak_mount.cpp
src/core/hooks/player_controller.cpp
src/core/hooks/population.cpp
src/core/hooks/render_device.cpp
src/core/hooks/world.cpp
src/core/modules/human.cpp
Expand Down
10 changes: 10 additions & 0 deletions code/client/src/core/game_layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ namespace HogwartsMP::Game {
Aob ulocalplayerCtor; // ULocalPlayer::ULocalPlayer (call site)
Aob apcCtor; // APlayerController::APlayerController (call site)

// --- Ambient population control ---
Aob populationManagerTick; // FUN_14316dd90 — no-op to disable all ambient NPC fleshing

Offsets offsets;
};

Expand Down Expand Up @@ -116,6 +119,10 @@ namespace HogwartsMP::Game {
{"LocalPlayer/ULocalPlayer::ULocalPlayer", "E9 ? ? ? ? C3 66 66 66 2E 0F 1F 84 00 00 00 00 00 48 8D 64 24 D8 41 54 F7 1C 24", true},
{"LocalPlayer/APlayerController::APlayerController", "E9 ? ? ? ? C3 85 C0 3C 88", true},

// Ambient population — PopulationManagerTick (FUN_14316dd90). No-op disables ambient NPCs.
// `80 B9 ? ? 00 00 00` = cmp byte[rcx+0x964],0 (the pause gate). RVA 0x316DD90 is the fallback.
{"Population/PopulationManagerTick", "48 89 5C 24 10 57 48 83 EC 40 80 B9 64 09 00 00 00 48 8B FA 48 8B D9", false},

// Offsets
{/*PersistentLevel*/ 0x30, /*OwningGameInstance*/ 0x330, /*LocalPlayers*/ 0x38},
};
Expand Down Expand Up @@ -155,6 +162,9 @@ namespace HogwartsMP::Game {
{"LocalPlayer/ULocalPlayer::ULocalPlayer", "E9 ? ? ? ? C3 66 66 66 2E 0F 1F 84 00 00 00 00 00 48 8D 64 24 D8 41 54 F7 1C 24", true},
{"LocalPlayer/APlayerController::APlayerController", "E9 ? ? ? ? C3 85 C0 3C 88", true},

// Ambient population — not independently verified for this older build; marked optional.
{"Population/PopulationManagerTick", "48 89 5C 24 10 57 48 83 EC 40 80 B9 64 09 00 00 00 48 8B FA 48 8B D9", true},

{/*PersistentLevel*/ 0x30, /*OwningGameInstance*/ 0x320, /*LocalPlayers*/ 0x38},
};

Expand Down
66 changes: 66 additions & 0 deletions code/client/src/core/hooks/population.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <utils/safe_win32.h>

#include <MinHook.h>
#include <utils/hooking/hook_function.h>
#include <utils/hooking/hooking.h>

#include <logging/logger.h>

#include <atomic>
#include <cstdint>

#include "../aob_scan.h"
#include "population.h"

// The whole tiered ambient crowd (BP_Tier3_Character_C / Tier4_Actor / students) is driven by
// UPopulationManager's per-frame tick, FUN_14316dd90, which ticks its 4 async population jobs.
// void __fastcall(UPopulationManager* this, FTickCtx*). Address via the game_layout AOB, RVA fallback.

namespace HogwartsMP::Core::Hooks {
namespace {
constexpr uintptr_t kPopTick_RVA = 0x316DD90; // FUN_14316dd90 fallback (Steam build 20773316)

using PopTick_t = void(__fastcall *)(void *, void *);
PopTick_t s_orig = nullptr;

// Default OFF (no-op the tick from boot) => areas load EMPTY.
// Server owners can flip to ON for vanilla via SetAmbientPopulation.
std::atomic<bool> s_enabled{false}; // true = vanilla population; false = no ambient NPCs

auto Log() {
return Framework::Logging::GetLogger("NpcLab");
}

void __fastcall PopTick_Hook(void *mgr, void *tickCtx) {
if (!s_enabled.load(std::memory_order_relaxed)) {
return; // skip the population tick entirely -> no flesh
}
s_orig(mgr, tickCtx);
}
} // namespace

void SetAmbientPopulation(bool enabled) {
s_enabled.store(enabled, std::memory_order_relaxed);
Log()->info("AmbientPopulation: {}", enabled ? "ON (vanilla NPCs)" : "OFF (no ambient NPCs)");
}

void ToggleAmbientPopulation() {
SetAmbientPopulation(!s_enabled.load(std::memory_order_relaxed));
}

bool IsAmbientPopulationEnabled() {
return s_enabled.load(std::memory_order_relaxed);
}
} // namespace HogwartsMP::Core::Hooks

static InitFunction init([]() {
using namespace HogwartsMP::Core::Hooks;
auto *target = HogwartsMP::Core::AobFirst(HogwartsMP::Game::gLayout.populationManagerTick);
if (!target) {
// AOB miss/ambiguous — fall back to the pinned RVA for the known build.
target = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(GetModuleHandle(nullptr)) + kPopTick_RVA);
}
const MH_STATUS st = MH_CreateHook(target, reinterpret_cast<LPVOID>(&PopTick_Hook), reinterpret_cast<void **>(&s_orig));
Framework::Logging::GetLogger("NpcLab")->info("AmbientPopulation: hooked PopulationManagerTick @ 0x{:x} status {} (default {})",
reinterpret_cast<uintptr_t>(target), static_cast<int>(st), IsAmbientPopulationEnabled() ? "ON" : "OFF");
}, "AmbientPopulation");
13 changes: 13 additions & 0 deletions code/client/src/core/hooks/population.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

// Ambient population control. Toggles the game's whole ambient-NPC ("Hobo"/Tier crowd) system on/off by
// no-oping UPopulationManager's per-frame tick. With it OFF, the manager never requests flesh, so
// NPCs are never spawned.

namespace HogwartsMP::Core::Hooks {
// enabled=true => vanilla ambient population; false => no ambient NPCs. Default ON.
// Exposed so a console command / scripting builtin can let server owners flip it.
void SetAmbientPopulation(bool enabled);
void ToggleAmbientPopulation();
bool IsAmbientPopulationEnabled();
} // namespace HogwartsMP::Core::Hooks
1 change: 1 addition & 0 deletions code/client/src/core/states/session_connected.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <utils/states/machine.h>

#include "core/application.h"
#include "core/hooks/population.h"

namespace HogwartsMP::Core::States {
SessionConnectedState::SessionConnectedState() {}
Expand Down
1 change: 1 addition & 0 deletions code/client/src/core/states/session_offline_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <utils/states/machine.h>

#include "../application.h"
#include "../hooks/population.h"

namespace HogwartsMP::Core::States {
SessionOfflineDebugState::SessionOfflineDebugState() {}
Expand Down