DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

+413
View File
@@ -0,0 +1,413 @@
#include "90CN_seria_minimap.h"
#include "90CN_seria_room_policy.h"
#include <string.h>
namespace dnf90_seria {
static Logger LogLine;
static HMODULE g_image;
static volatile LONG g_ready;
static SRWLOCK g_lock = SRWLOCK_INIT;
static Lifecycle g_owner = {};
static void* g_original[8];
typedef uintptr_t (__thiscall* TownResourceLookup)(void*, int, int);
static TownResourceLookup g_titleTownLookup;
enum Kind { Setter, Bind, TownGet, Region, Draw, Marker, Rect };
static uintptr_t Va(uintptr_t address) { return reinterpret_cast<uintptr_t>(g_image) + address - 0x400000; }
// PUSHAD layout. Native argument words, register/flag/FPU/SSE state, return
// values and RET n cleanup are forwarded verbatim; no guessed C++ native ABI.
struct Registers { DWORD edi, esi, ebp, esp, ebx, edx, ecx, eax, flags; };
struct Display { Owner owner; uintptr_t ui, entry, marker; int region; };
struct Frame {
uintptr_t nativeStack, caller, self, esi;
Kind kind;
int town, area;
bool active;
Display display;
ULONGLONG entered;
};
struct PacketFrame { unsigned ticket; unsigned opcode; Notice notice; };
struct ThreadState {
Frame hooks[128]; unsigned depth;
PacketFrame packets[8]; unsigned packetDepth, nextTicket;
};
static __declspec(thread) ThreadState g_thread = {};
static bool ReadScene(Scene* scene) {
*scene = Scene();
__try {
uintptr_t root = *reinterpret_cast<uintptr_t*>(Va(0x051b1290));
if (!root) return false;
scene->world = *reinterpret_cast<uintptr_t*>(root + 0xb4);
if (!scene->world) return false;
scene->town = *reinterpret_cast<int*>(scene->world + 0x1e4);
uintptr_t area = *reinterpret_cast<uintptr_t*>(scene->world + 0x1e8);
scene->area = area ? *reinterpret_cast<int*>(area) : -1;
return true;
} __except (EXCEPTION_EXECUTE_HANDLER) { *scene = Scene(); return false; }
}
static uintptr_t ReadUi() {
__try {
uintptr_t manager = *reinterpret_cast<uintptr_t*>(Va(0x051b2764));
return manager ? *reinterpret_cast<uintptr_t*>(manager + 0xb7f0) : 0;
} __except (EXCEPTION_EXECUTE_HANDLER) { return 0; }
}
static bool ReadDisplay(Display* value, bool binding) {
*value = Display();
Scene scene;
if (!ReadScene(&scene) || !Shared(scene)) return false;
AcquireSRWLockShared(&g_lock);
value->owner = g_owner.context;
ReleaseSRWLockShared(&g_lock);
if (value->owner.world != scene.world || !FindTown(value->owner.town)) return false;
value->ui = ReadUi();
if (!value->ui) return false;
__try {
value->entry = *reinterpret_cast<uintptr_t*>(value->ui + 0x2c);
if (!value->entry) return binding;
value->region = *reinterpret_cast<int*>(value->entry + 0x68);
value->marker = *reinterpret_cast<uintptr_t*>(value->entry + 0x64);
bool supported = value->marker && *reinterpret_cast<uintptr_t*>(value->marker) == Va(0x045a6dd8) &&
*reinterpret_cast<int*>(value->marker + 4) == 2 &&
*reinterpret_cast<WORD*>(value->marker + 0x5c) != 0xffff;
// Binding precedes native marker construction. A previous region's
// marker must not veto the FIRST resource selection on cold login.
return binding || supported;
} __except (EXCEPTION_EXECUTE_HANDLER) { return false; }
}
static bool SameDisplay(const Display& a, const Display& b) {
return a.owner.world == b.owner.world && a.owner.serial == b.owner.serial &&
a.owner.town == b.owner.town && a.entry == b.entry && a.marker == b.marker &&
b.region == b.owner.region;
}
static Frame* Current(Kind kind) {
for (unsigned n = g_thread.depth; n; --n) {
Frame* frame = g_thread.hooks + n - 1;
if (frame->kind != kind) continue;
// The innermost frame owns this call, even when it is ineligible.
return frame->active && GetTickCount64() - frame->entered < 1500 ? frame : 0;
}
return 0;
}
static bool ValidFrame(Frame* frame, Display* display) {
return frame && ReadDisplay(display, false) && SameDisplay(frame->display, *display);
}
static void BindPending() {
if (!g_thread.packetDepth) return;
PacketFrame* packet = g_thread.packets + g_thread.packetDepth - 1;
if (packet->opcode != 24) return;
Scene scene;
if (!ReadScene(&scene)) return;
AcquireSRWLockExclusive(&g_lock);
bool changed = g_owner.Bind(packet->notice, scene);
Owner owner = g_owner.context;
ReleaseSRWLockExclusive(&g_lock);
if (changed) LogLine("Seria minimap owner bound town=%d region=%d serial=%u (UI only)", owner.town, owner.region, owner.serial);
}
unsigned BeginPacket(int packet) {
if (!g_ready || !packet || g_thread.packetDepth >= 8) return 0;
unsigned opcode;
__try { opcode = *reinterpret_cast<unsigned short*>(packet + 1); }
__except (EXCEPTION_EXECUTE_HANDLER) { return 0; }
PacketFrame value = {};
value.opcode = opcode;
if (opcode == 24) ReadScene(&value.notice.before);
value.ticket = ++g_thread.nextTicket;
if (!value.ticket) value.ticket = ++g_thread.nextTicket;
g_thread.packets[g_thread.packetDepth++] = value;
return value.ticket;
}
void EndPacket(unsigned ticket) {
if (!ticket || !g_thread.packetDepth) return;
PacketFrame* value = g_thread.packets + g_thread.packetDepth - 1;
if (value->ticket != ticket) return;
if (value->opcode == 24) {
Scene after;
if (ReadScene(&after)) {
AcquireSRWLockExclusive(&g_lock);
g_owner.Finish(value->notice, after);
ReleaseSRWLockExclusive(&g_lock);
}
}
--g_thread.packetDepth;
}
static void ReturnThunk();
static void __cdecl Before(unsigned id, Registers* registers) {
if (!g_ready || id > Rect) return;
uintptr_t* stack = reinterpret_cast<uintptr_t*>(registers + 1);
// A native SEH unwind can bypass a return thunk. Discard only frames whose
// original stack was actually unwound; never reuse a stale marker scope.
while (g_thread.depth && *stack != reinterpret_cast<uintptr_t>(&ReturnThunk) &&
reinterpret_cast<uintptr_t>(stack) >= g_thread.hooks[g_thread.depth-1].nativeStack)
--g_thread.depth;
if (g_thread.depth == 128) return; // no return-address rewrite on overflow
Frame value = {};
value.kind = static_cast<Kind>(id);
value.nativeStack = reinterpret_cast<uintptr_t>(stack);
value.caller = *stack;
value.self = registers->ecx;
value.esi = registers->esi;
value.entered = GetTickCount64();
__try {
if (id == Setter || id == Region || id == Rect) value.town = static_cast<int>(stack[1]);
if (id == Rect) value.area = static_cast<int>(stack[2]);
} __except (EXCEPTION_EXECUTE_HANDLER) { return; }
if (id == Setter && value.caller == Va(0x01d82152) && g_thread.packetDepth &&
g_thread.packets[g_thread.packetDepth-1].opcode == 1375) {
Scene scene;
if (ReadScene(&scene) && scene.world == value.self) {
AcquireSRWLockExclusive(&g_lock);
g_owner.Seed(scene.world, value.town);
ReleaseSRWLockExclusive(&g_lock);
LogLine("Seria minimap explicit initial owner town=%d (native cache untouched)", value.town);
}
} else if (id == Bind) {
value.active = value.self && value.self == ReadUi();
if (value.active) BindPending(); // before the binder's FIRST town getter
} else if (id == Draw) {
value.active = ReadDisplay(&value.display, false) && value.self == value.display.entry &&
value.display.region == value.display.owner.region;
} else if (id == Marker) {
Frame* draw = Current(Draw);
value.active = ValidFrame(draw, &value.display) && value.self == value.display.marker;
}
g_thread.hooks[g_thread.depth++] = value;
*stack = reinterpret_cast<uintptr_t>(&ReturnThunk);
}
// Synthetic descriptors are DLL-owned UI data, not a native scene/PVF object.
struct Descriptor { int town, area; int* geometry; int rect[4]; };
static Descriptor g_rects[sizeof(kGlyphs)/sizeof(kGlyphs[0])];
static uintptr_t DescriptorFor(const GlyphRow* glyph) {
return glyph ? reinterpret_cast<uintptr_t>(g_rects + (glyph - kGlyphs)) : 0;
}
static uintptr_t ProjectRect(const Frame& frame, uintptr_t original) {
Display state;
if (frame.caller != Va(0x0224e083) || frame.town != 38 || frame.area != 1 ||
!ValidFrame(Current(Marker), &state) || frame.self != state.entry) return original;
const GlyphRow* glyph = 0;
__try {
const int* geometry = original ? *reinterpret_cast<int**>(original + 8) : 0;
glyph = BorrowGlyph(state.owner.town, original == 0, geometry);
} __except (EXCEPTION_EXECUTE_HANDLER) { return original; }
return glyph ? DescriptorFor(glyph) : original;
}
static void __cdecl After(Registers* registers) {
// Return target resides in the reserved word ABOVE saved flags. Popping
// this word with RET preserves every native output register and ESP.
uintptr_t* target = reinterpret_cast<uintptr_t*>(registers + 1);
if (!g_thread.depth) {
// A missing target is an invariant violation, not a recoverable UI
// state. It cannot be produced by disabled/overflow hooks (no rewrite).
RaiseFailFastException(0, 0, 0);
return;
}
Frame frame = g_thread.hooks[--g_thread.depth];
*target = frame.caller;
if (!g_ready) return;
Display state;
if (frame.kind == TownGet && registers->eax == 38 &&
ReadDisplay(&state, frame.caller == Va(0x01c987f6) || frame.caller == Va(0x01c9bc5b)) &&
frame.self == state.owner.world) {
Frame* bind = Current(Bind);
bool allowed = (frame.caller == Va(0x01c987f6) && bind && bind->self == state.ui) ||
frame.caller == Va(0x01c9bc5b) ||
(frame.caller == Va(0x022478bd) && frame.esi == state.marker && ValidFrame(Current(Draw), &state));
if (allowed) registers->eax = state.owner.town;
} else if (frame.kind == Region && frame.town == 38 && registers->eax == 18 &&
frame.caller == Va(0x0223c4fd) && ValidFrame(Current(Marker), &state)) {
registers->eax = state.owner.region;
} else if (frame.kind == Rect) {
registers->eax = static_cast<DWORD>(ProjectRect(frame, registers->eax));
}
}
// sub_3411790 binds the minimap, then independently copies the REAL town
// resource's name at 3412282. Its room38 name bypasses scene_town_get entirely.
// Redirect only that CALL's string source; native copy/destruction still owns
// the value, and the scene, town resource and global string copier stay intact.
static void __cdecl BeforeTitleCopy(Registers* registers) {
if (!g_ready || !g_titleTownLookup || !g_thread.packetDepth ||
g_thread.packets[g_thread.packetDepth-1].opcode != 24) return;
uintptr_t* stack = reinterpret_cast<uintptr_t*>(registers + 1);
if (*stack != Va(0x03412287)) return;
Display state;
if (!ReadDisplay(&state, true) || state.region != state.owner.region ||
registers->edi != state.owner.world || state.owner.town == 38) return;
__try {
// The guarded caller takes this exact source from its scene resource.
uintptr_t realTown = *reinterpret_cast<uintptr_t*>(registers->ebp - 0x24);
if (!realTown || stack[1] != realTown + 4) return;
uintptr_t loader = *reinterpret_cast<uintptr_t*>(Va(0x051b26d8));
// This singleton is constructed with town resource type 12 at
// 22938d1..22938df; the generic lookup must not parse another type.
if (!loader || *reinterpret_cast<int*>(loader + 0x22c) != 12) return;
// Same native cache-enabled resource lookup used by scene entry.
uintptr_t town = g_titleTownLookup(reinterpret_cast<void*>(loader), state.owner.town, 1);
if (!town) return;
uintptr_t name = town + 4;
unsigned length = *reinterpret_cast<unsigned*>(name + 0x14);
unsigned capacity = *reinterpret_cast<unsigned*>(name + 0x18);
if (!length || length > capacity) return;
Display current;
if (!ReadDisplay(&current, true) || !SameDisplay(state, current)) return;
stack[1] = name;
LogLine("Seria minimap title projected town=%d region=%d serial=%u (native resource name)",
state.owner.town, state.owner.region, state.owner.serial);
} __except (EXCEPTION_EXECUTE_HANDLER) { return; }
}
// Preserve flags and the complete x87/MMX/SSE state around our observer, not
// merely callee-saved integer registers. FX area is 16-byte aligned even when
// the game's incoming stack is not. The native function sees its exact stack.
#define SAVE_OBSERVER __asm { pushfd } __asm { pushad } __asm { sub esp, 528 } \
__asm { lea eax, [esp+15] } __asm { and eax, -16 } __asm { fxsave [eax] }
#define RESTORE_OBSERVER __asm { lea eax, [esp+15] } __asm { and eax, -16 } \
__asm { fxrstor [eax] } __asm { add esp, 528 } __asm { popad } __asm { popfd }
#define ENTRY(name, id, offset) static void __declspec(naked) name() { \
SAVE_OBSERVER __asm { lea eax, [esp+528] } __asm { push eax } __asm { push id } \
__asm { call Before } __asm { add esp, 8 } RESTORE_OBSERVER \
__asm { jmp dword ptr [g_original+offset] } }
ENTRY(SetterEntry, 0, 0)
ENTRY(BindEntry, 1, 4)
ENTRY(TownEntry, 2, 8)
ENTRY(RegionEntry, 3, 12)
ENTRY(DrawEntry, 4, 16)
ENTRY(MarkerEntry, 5, 20)
ENTRY(RectEntry, 6, 24)
static void __declspec(naked) TitleCopyEntry() {
SAVE_OBSERVER
__asm { lea eax, [esp+528] }
__asm { push eax }
__asm { call BeforeTitleCopy }
__asm { add esp, 4 }
RESTORE_OBSERVER
__asm { jmp dword ptr [g_original+28] }
}
static void __declspec(naked) ReturnThunk() {
__asm { push 0 }
SAVE_OBSERVER
__asm { lea eax, [esp+528] }
__asm { push eax }
__asm { call After }
__asm { add esp, 4 }
RESTORE_OBSERVER
__asm { ret }
}
#undef ENTRY
#undef SAVE_OBSERVER
#undef RESTORE_OBSERVER
struct Hook { const char* name; uintptr_t address; const char* bytes; unsigned size; void* entry; uintptr_t callTarget; BYTE saved[16]; };
static Hook g_hooks[] = {
{"world_town_set",0x03406800,"558bec568b7508",7,SetterEntry},
{"minimap_bind",0x01c987c0,"568bf1ff156c261a04",9,BindEntry},
{"scene_town_get",0x03406320,"8b81e4010000",6,TownEntry},
{"town_region_resolve",0x0392cc90,"558bec83ec18",6,RegionEntry},
{"minimap_draw_layers",0x022476c0,"558bec6aff",5,DrawEntry},
{"marker_update",0x0223c460,"558bec83ec0c",6,MarkerEntry},
{"map_rect_lookup",0x0224d960,"558bec83ec18",6,RectEntry},
{"minimap_title_copy",0x03412282,"e849a732fd",5,TitleCopyEntry,0x0073c9d0}
};
static const unsigned kHookCount = sizeof(g_hooks)/sizeof(g_hooks[0]);
struct Guard { uintptr_t address; const char* bytes; };
static const Guard g_guards[] = {
{0x03406818,"85c0740980b8f0000000017443"}, {0x01d8214d,"e8ae4668018be55dc3"},
{0x01c987f1,"e82adb760150e89444c901"}, {0x01c9bc56,"e8c5a6760150e82f10c901"},
{0x022478b8,"e863ea1b0150e8cd536e0183c4043b43687568"},
{0x02247884,"8b34b985f6744d837e0402753a"}, {0x0223c4f8,"e893076f018bf883c404"},
{0x0224e07e,"e8ddf8ffff8b4d0c"}, {0x03680720,"8b412cc3"}, {0x034e4b90,"8b4164c3"},
{0x01c43fa3,"e8e80b8a0183c008"}, {0x01c44239,"e8420b0500"},
{0x0224821e,"8b55e4897a64"}, {0x02240c56,"895e08895e0c"},
{0x022479a2,"c23800"},
{0x03411950,"6a01899fe40100008b0dd8261b0553e84c8704008945dc"},
{0x03412273,"8b45dc83ec2083c0048bcc8965d050e849a732fdc745fc03000000c745fcffffffff8bcee8142183fe"},
{0x0345a0b0,"558bec5356578bf1e873f9ffff85c074118b5d08538bc8e8c4aa19008bf885ff75095f5e33c05b5dc208008b450c508bce899e34020000e804ad37ff578bcee8acfc13005fc78634020000ffffffff5e5b5dc20800"},
{0x0073c9d0,"558bec6aff6868f6f90364a1000000005083ec1456a13c670e05"}
};
static int Hex(char c) { return c >= '0' && c <= '9' ? c-'0' : c >= 'a' && c <= 'f' ? c-'a'+10 : -1; }
static bool Match(uintptr_t address, const char* bytes) {
__try {
size_t length = strlen(bytes);
if (length % 2) return false;
for (size_t i = 0; i < length/2; ++i)
if (*reinterpret_cast<BYTE*>(Va(address)+i) != (Hex(bytes[2*i])*16+Hex(bytes[2*i+1]))) return false;
return true;
} __except (EXCEPTION_EXECUTE_HANDLER) { return false; }
}
static bool WriteCode(void* destination, const void* source, unsigned size) {
DWORD protection = 0;
if (!VirtualProtect(destination, size, PAGE_EXECUTE_READWRITE, &protection)) return false;
memcpy(destination, source, size);
FlushInstructionCache(GetCurrentProcess(), destination, size);
DWORD ignored;
return VirtualProtect(destination, size, protection, &ignored) != FALSE;
}
static void Jump(BYTE* destination, uintptr_t source, uintptr_t target) {
destination[0] = 0xe9;
*reinterpret_cast<DWORD*>(destination+1) = static_cast<DWORD>(target - source - 5);
}
bool Install(HMODULE image, Logger logger) {
if (g_ready) return true;
g_image = image;
LogLine = logger;
if (!image || !logger) return false;
// Verify ALL entry/caller/layout guards before allocation or any hook.
for (unsigned n = 0; n < sizeof(g_hooks)/sizeof(g_hooks[0]); ++n)
if (!Match(g_hooks[n].address, g_hooks[n].bytes)) {
LogLine("Seria minimap disabled: native entry mismatch %s", g_hooks[n].name); return false;
}
for (unsigned n = 0; n < sizeof(g_guards)/sizeof(g_guards[0]); ++n)
if (!Match(g_guards[n].address, g_guards[n].bytes)) {
LogLine("Seria minimap disabled: native guard mismatch %08X", static_cast<unsigned>(g_guards[n].address)); return false;
}
g_titleTownLookup = reinterpret_cast<TownResourceLookup>(Va(0x0345a0b0));
for (unsigned n = 0; n < sizeof(g_rects)/sizeof(g_rects[0]); ++n) {
g_rects[n].town = 38; g_rects[n].area = 1; g_rects[n].geometry = g_rects[n].rect;
memcpy(g_rects[n].rect, kGlyphs[n].rect, sizeof(g_rects[n].rect));
}
unsigned allocated = 0, written = 0;
bool prepared = true, patched = true;
for (; allocated != kHookCount; ++allocated) {
Hook& hook = g_hooks[allocated];
memcpy(hook.saved, reinterpret_cast<void*>(Va(hook.address)), hook.size);
if (hook.callTarget) {
g_original[allocated] = reinterpret_cast<void*>(Va(hook.callTarget));
continue;
}
BYTE* trampoline = static_cast<BYTE*>(VirtualAlloc(0, hook.size+5, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE));
if (!trampoline) { prepared = false; break; }
g_original[allocated] = trampoline;
memcpy(trampoline, hook.saved, hook.size);
Jump(trampoline+hook.size, reinterpret_cast<uintptr_t>(trampoline)+hook.size, Va(hook.address)+hook.size);
DWORD previous;
if (!VirtualProtect(trampoline, hook.size+5, PAGE_EXECUTE_READ, &previous)) { prepared = false; ++allocated; break; }
FlushInstructionCache(GetCurrentProcess(), trampoline, hook.size+5);
}
if (prepared && allocated == kHookCount) {
for (; written != kHookCount; ++written) {
Hook& hook = g_hooks[written]; BYTE replacement[16]; memset(replacement, 0x90, hook.size);
Jump(replacement, Va(hook.address), reinterpret_cast<uintptr_t>(hook.entry));
if (hook.callTarget) replacement[0] = 0xe8; // keep the native CALL/RET 4 ABI
if (!WriteCode(reinterpret_cast<void*>(Va(hook.address)), replacement, hook.size)) { patched = false; ++written; break; }
}
if (patched && written == kHookCount) {
InterlockedExchange(&g_ready, 1);
LogLine("Seria minimap ready: owning-town title hooks=8 guards=19 generated-rooms=16, recording independent");
return true;
}
}
// Roll back entry bytes BEFORE freeing executable trampolines.
bool restored = true;
for (unsigned n = 0; n != written; ++n)
restored = WriteCode(reinterpret_cast<void*>(Va(g_hooks[n].address)), g_hooks[n].saved, g_hooks[n].size) && restored;
if (restored) for (unsigned n = 0; n != allocated; ++n) {
if (g_original[n] && !g_hooks[n].callTarget) VirtualFree(g_original[n], 0, MEM_RELEASE);
g_original[n] = 0;
}
LogLine("Seria minimap disabled: install failed; entry rollback=%d", restored ? 1 : 0);
return false;
}
}