DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,335 @@
|
||||
#pragma once
|
||||
|
||||
#include "90CN_companion_p0.h"
|
||||
#include "90CN_companion_projection.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// DB4 client only. The installer owns exact-image/signature checks. These
|
||||
// adapters preserve the shared companion parser, queue and lifecycle owner.
|
||||
namespace dnf90_companion_new_native {
|
||||
|
||||
using NewNativeClass0DispatchFn = int (__stdcall*)(int, int);
|
||||
using NewNativeCaptureLogFn = void (*)(const char*, ...);
|
||||
|
||||
static std::uintptr_t g_captureBase = 0;
|
||||
static std::uintptr_t g_captureResume = 0;
|
||||
static std::uintptr_t g_captureConsumed = 0;
|
||||
static NewNativeClass0DispatchFn g_originalClass0Dispatch = nullptr;
|
||||
static NewNativeCaptureLogFn g_captureLog = nullptr;
|
||||
static DWORD g_dispatchThread = 0;
|
||||
static HWND g_safePointWindow = nullptr;
|
||||
constexpr UINT_PTR kSafePointTimer = 0x90C07F41u;
|
||||
|
||||
constexpr std::size_t kCapturePacketCapacity =
|
||||
dnf90_companion_projection::kPacketHeaderSize +
|
||||
dnf90_companion_projection::kBodyHeaderSize +
|
||||
dnf90_companion_projection::kMaxDynamicAicBytes +
|
||||
2 * dnf90_companion_projection::kMaxProjectionBody;
|
||||
constexpr std::uintptr_t kReaderStateRva = 0x053F9AD0;
|
||||
|
||||
struct NewNativeReaderState {
|
||||
const unsigned char* cursor;
|
||||
int remaining;
|
||||
const unsigned char* packet;
|
||||
int packetLength;
|
||||
};
|
||||
static_assert(sizeof(NewNativeReaderState) == 16, "DB4 x86 native reader ABI");
|
||||
|
||||
// SetTimer's callback is dispatched by the window's own message thread. Only
|
||||
// the native packet thread is allowed to own this timer or call shared APC code.
|
||||
static void CALLBACK NewNativeSafePointTimer(HWND window, UINT, UINT_PTR id, DWORD) {
|
||||
if (id != kSafePointTimer || window != g_safePointWindow ||
|
||||
GetCurrentThreadId() != g_dispatchThread) return;
|
||||
dnf90_companion_p0::NewNativeSafePoint(-2);
|
||||
}
|
||||
|
||||
static BOOL CALLBACK FindNewNativeThreadWindow(HWND window, LPARAM parameter) {
|
||||
DWORD process = 0;
|
||||
const DWORD thread = GetWindowThreadProcessId(window, &process);
|
||||
if (process != GetCurrentProcessId() || thread != g_dispatchThread ||
|
||||
!IsWindowVisible(window) || GetWindow(window, GW_OWNER)) return TRUE;
|
||||
*reinterpret_cast<HWND*>(parameter) = window;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void EnsureNewNativeSafePointTimer() {
|
||||
if (!g_dispatchThread) g_dispatchThread = GetCurrentThreadId();
|
||||
if (GetCurrentThreadId() != g_dispatchThread) return;
|
||||
if (g_safePointWindow && IsWindow(g_safePointWindow)) return;
|
||||
HWND window = nullptr;
|
||||
EnumThreadWindows(g_dispatchThread, FindNewNativeThreadWindow,
|
||||
reinterpret_cast<LPARAM>(&window));
|
||||
if (!window) return;
|
||||
if (SetTimer(window, kSafePointTimer, 100, NewNativeSafePointTimer)) {
|
||||
g_safePointWindow = window;
|
||||
if (g_captureLog) g_captureLog(
|
||||
"new-native companion UI safe-point timer installed window=%p thread=%lu interval_ms=100",
|
||||
window, g_dispatchThread);
|
||||
}
|
||||
}
|
||||
|
||||
struct NewNativeTownSnapshot {
|
||||
const unsigned char* start = nullptr;
|
||||
std::size_t bodyLength = 0;
|
||||
std::size_t consumedMinimum = 0;
|
||||
std::uint16_t localKey = 0;
|
||||
};
|
||||
|
||||
struct NewNativeDungeonInfoSnapshot {
|
||||
const unsigned char* start = nullptr;
|
||||
std::size_t bodyLength = 0;
|
||||
std::uint32_t dungeon = 0;
|
||||
};
|
||||
|
||||
// Current class0 op28 is registered to 01FF5050 at 02001F73. This is a
|
||||
// per-dungeon notification, while op29 is also used for ordinary room changes.
|
||||
// Only the former opens a new run; no duplicate companion lifecycle is stored.
|
||||
static NewNativeDungeonInfoSnapshot SnapshotNewNativeDungeonInfo(
|
||||
int opcode, const unsigned char* header) {
|
||||
NewNativeDungeonInfoSnapshot result{};
|
||||
if (opcode != 28 || !g_captureBase || !header) return result;
|
||||
__try {
|
||||
const std::uint32_t total = dnf90_companion_projection::Read32(header + 3);
|
||||
if (total < 52 || total > kCapturePacketCapacity) return result;
|
||||
const NewNativeReaderState reader = *reinterpret_cast<const NewNativeReaderState*>(
|
||||
g_captureBase + kReaderStateRva);
|
||||
const std::size_t bodyLength = total - 16;
|
||||
if (!reader.cursor || reader.remaining < 0 ||
|
||||
static_cast<std::size_t>(reader.remaining) < bodyLength) return result;
|
||||
const std::uint32_t dungeon = dnf90_companion_projection::Read32(reader.cursor);
|
||||
if (!dungeon) return result;
|
||||
result.start = reader.cursor;
|
||||
result.bodyLength = bodyLength;
|
||||
result.dungeon = dungeon;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
result = {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool NewNativeBodyWasConsumed(
|
||||
const unsigned char* startPointer, std::size_t bodyLength, std::size_t minimum) {
|
||||
if (!startPointer || !g_captureBase) return false;
|
||||
__try {
|
||||
const NewNativeReaderState reader = *reinterpret_cast<const NewNativeReaderState*>(
|
||||
g_captureBase + kReaderStateRva);
|
||||
const std::uintptr_t start = reinterpret_cast<std::uintptr_t>(startPointer);
|
||||
const std::uintptr_t cursor = reinterpret_cast<std::uintptr_t>(reader.cursor);
|
||||
return reader.remaining >= 0 && cursor >= start + minimum &&
|
||||
cursor <= start + bodyLength;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// DB4 class0 initializer 02524320 calls 020460E0, which registers op24 to
|
||||
// 02042900 at 020461A3. Its row reads at 020432C2..02043312 are 3 WORDs and
|
||||
// 3 BYTEs (nine bytes, versus eight in the old client). Early-return gates at
|
||||
// 02042948/02042956 run before any reader consumption.
|
||||
static NewNativeTownSnapshot SnapshotNewNativeTown(int opcode, const unsigned char* header) {
|
||||
NewNativeTownSnapshot result{};
|
||||
if (opcode != 24 || !g_captureBase || !header) return result;
|
||||
__try {
|
||||
const std::uint32_t total = dnf90_companion_projection::Read32(header + 3);
|
||||
if (total < 20 || total > kCapturePacketCapacity) return result;
|
||||
const NewNativeReaderState reader = *reinterpret_cast<const NewNativeReaderState*>(
|
||||
g_captureBase + kReaderStateRva);
|
||||
const std::size_t bodyLength = total - 16;
|
||||
if (!reader.cursor || reader.remaining < 0 ||
|
||||
static_cast<std::size_t>(reader.remaining) < bodyLength) return result;
|
||||
const std::size_t rows = dnf90_companion_projection::Read16(reader.cursor + 2);
|
||||
if (!rows || rows > (bodyLength - 4) / 9) return result;
|
||||
const std::uint16_t key = dnf90_companion_projection::Read16(
|
||||
reader.cursor + 4 + (rows - 1) * 9);
|
||||
if (!key) return result;
|
||||
result.start = reader.cursor;
|
||||
result.bodyLength = bodyLength;
|
||||
result.consumedMinimum = 4 + rows * 9;
|
||||
result.localKey = key;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
result = {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool NewNativeTownWasAccepted(const NewNativeTownSnapshot& snapshot) {
|
||||
if (!snapshot.start || !snapshot.localKey || !g_captureBase) return false;
|
||||
__try {
|
||||
if (!NewNativeBodyWasConsumed(snapshot.start, snapshot.bodyLength,
|
||||
snapshot.consumedMinimum)) return false;
|
||||
using CurrentActor = void* (__cdecl*)();
|
||||
using ActorByKey = void* (__cdecl*)(unsigned short);
|
||||
void* current = reinterpret_cast<CurrentActor>(g_captureBase + 0x025610E0)();
|
||||
void* selected = reinterpret_cast<ActorByKey>(g_captureBase + 0x0256D730)(snapshot.localKey);
|
||||
return current && current == selected;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConfigureNewNativeCaptureResume(
|
||||
std::uintptr_t base,
|
||||
NewNativeCaptureLogFn logger = nullptr)
|
||||
{
|
||||
g_captureBase = base;
|
||||
g_captureResume = base ? base + 0x02123C94 : 0;
|
||||
g_captureConsumed = base ? base + 0x02123F50 : 0;
|
||||
g_captureLog = logger;
|
||||
}
|
||||
|
||||
static void SetOriginalNewNativeClass0Dispatch(void* original) {
|
||||
g_originalClass0Dispatch =
|
||||
reinterpret_cast<NewNativeClass0DispatchFn>(original);
|
||||
}
|
||||
|
||||
// Called only at VA 02523C8A, after native decoding/decompression and reader
|
||||
// binding, before admission is enforced. EDI may still be the protected outer
|
||||
// packet, so its header is copied but plaintext always comes from reader.cursor.
|
||||
// The native decompressor at 02523820 updates header+3 to decoded body size+16;
|
||||
// the enclosing router then binds that body size as reader.remaining. Using the
|
||||
// original socket receive length here would truncate compressed private frames.
|
||||
// Return true for every recognized private frame, including malformed frames:
|
||||
// the native router must never index its opcode tables with private opcode 7F41.
|
||||
static bool __cdecl CaptureNewNativeDecodedProjection(
|
||||
int sourceContext,
|
||||
const unsigned char* header)
|
||||
{
|
||||
bool privatePacket = false;
|
||||
bool captured = false;
|
||||
unsigned char* ownedFrame = nullptr;
|
||||
std::uint32_t packetLength = 0;
|
||||
int remaining = -1;
|
||||
unsigned long exceptionCode = 0;
|
||||
__try {
|
||||
do {
|
||||
if (!g_captureBase || !header || header[0] != 0 ||
|
||||
dnf90_companion_projection::Read16(header + 1) !=
|
||||
dnf90_companion_projection::kPrivateOpcode) {
|
||||
break;
|
||||
}
|
||||
privatePacket = true;
|
||||
packetLength = dnf90_companion_projection::Read32(header + 3);
|
||||
if (packetLength <
|
||||
dnf90_companion_projection::kPacketHeaderSize +
|
||||
dnf90_companion_projection::kBodyHeaderSize ||
|
||||
packetLength > kCapturePacketCapacity) {
|
||||
break;
|
||||
}
|
||||
const NewNativeReaderState reader =
|
||||
*reinterpret_cast<const NewNativeReaderState*>(
|
||||
g_captureBase + kReaderStateRva);
|
||||
remaining = reader.remaining;
|
||||
const std::size_t bodyLength = packetLength -
|
||||
dnf90_companion_projection::kPacketHeaderSize;
|
||||
if (!reader.cursor || reader.remaining < 0 ||
|
||||
static_cast<std::size_t>(reader.remaining) < bodyLength) {
|
||||
break;
|
||||
}
|
||||
ownedFrame = static_cast<unsigned char*>(
|
||||
HeapAlloc(GetProcessHeap(), 0, packetLength));
|
||||
if (!ownedFrame) break;
|
||||
std::memcpy(ownedFrame, header,
|
||||
dnf90_companion_projection::kPacketHeaderSize);
|
||||
std::memcpy(ownedFrame +
|
||||
dnf90_companion_projection::kPacketHeaderSize,
|
||||
reader.cursor, bodyLength);
|
||||
|
||||
// Lifecycle work can reenter native readers. The complete private
|
||||
// frame is owned before calling it, so no borrowed body survives.
|
||||
dnf90_companion_p0::NewNativeSafePoint(-1);
|
||||
captured = dnf90_companion_p0::CaptureProjectionClass0Packet(
|
||||
sourceContext, reinterpret_cast<int>(ownedFrame));
|
||||
if (captured) {
|
||||
dnf90_companion_p0::NewNativeSafePoint(
|
||||
dnf90_companion_projection::kPrivateOpcode);
|
||||
} else {
|
||||
dnf90_companion_p0::LogProjectionDecodeRejected(
|
||||
ownedFrame, packetLength, "new-native-decoded-boundary");
|
||||
}
|
||||
} while (false);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
exceptionCode = GetExceptionCode();
|
||||
}
|
||||
if (ownedFrame) HeapFree(GetProcessHeap(), 0, ownedFrame);
|
||||
if (privatePacket && g_captureLog) {
|
||||
g_captureLog("new-native companion decoded capture context=%d length=%u remaining=%d captured=%u exception=0x%08lX",
|
||||
sourceContext, static_cast<unsigned int>(packetLength), remaining,
|
||||
captured ? 1u : 0u, exceptionCode);
|
||||
}
|
||||
return privatePacket;
|
||||
}
|
||||
|
||||
// The displaced 10 bytes contain no relative instructions:
|
||||
// 8A 45 A3 mov al,[ebp-5D]
|
||||
// C7 45 AC 01 00 00 00 mov dword ptr [ebp-54],1
|
||||
// Resuming at 02523C94 preserves admission for all non-private packets. The
|
||||
// private branch enters the router's normal consumed epilogue at 02523F50.
|
||||
// That epilogue writes EAX=1 itself and never reads [EBP-54], so the private
|
||||
// branch deliberately does not initialize the displaced dispatch-result local.
|
||||
__declspec(naked) static void ProxyNewNativeProjectionCapture() {
|
||||
__asm {
|
||||
pushfd
|
||||
pushad
|
||||
push edi
|
||||
push dword ptr [ebp - 70h]
|
||||
call CaptureNewNativeDecodedProjection
|
||||
add esp, 8
|
||||
test al, al
|
||||
jnz private_consumed
|
||||
popad
|
||||
popfd
|
||||
mov al, byte ptr [ebp - 5Dh]
|
||||
mov dword ptr [ebp - 54h], 1
|
||||
jmp dword ptr [g_captureResume]
|
||||
private_consumed:
|
||||
popad
|
||||
popfd
|
||||
jmp dword ptr [g_captureConsumed]
|
||||
}
|
||||
}
|
||||
|
||||
// Current VA 0251E340 has the same two-argument stdcall ABI as the shared
|
||||
// legacy owner. Its callbacks consume global reader state before this point.
|
||||
static int __stdcall ProxyNewNativeClass0Dispatch(int sourceContext, int packet) {
|
||||
NewNativeClass0DispatchFn original = g_originalClass0Dispatch;
|
||||
if (!original) return 1;
|
||||
int opcode = -1;
|
||||
__try {
|
||||
const unsigned char* bytes =
|
||||
reinterpret_cast<const unsigned char*>(packet);
|
||||
if (bytes && bytes[0] == 0) {
|
||||
opcode = dnf90_companion_projection::Read16(bytes + 1);
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
opcode = -1;
|
||||
}
|
||||
const NewNativeTownSnapshot town = SnapshotNewNativeTown(opcode,
|
||||
reinterpret_cast<const unsigned char*>(packet));
|
||||
const NewNativeDungeonInfoSnapshot dungeon = SnapshotNewNativeDungeonInfo(opcode,
|
||||
reinterpret_cast<const unsigned char*>(packet));
|
||||
const int result = original(sourceContext, packet);
|
||||
EnsureNewNativeSafePointTimer();
|
||||
if (NewNativeTownWasAccepted(town)) {
|
||||
dnf90_companion_p0::CancelFixedApc5405();
|
||||
if (g_captureLog) g_captureLog(
|
||||
"new-native companion lifecycle cancelled reason=accepted-town opcode=24 key=%u consumed_min=%zu",
|
||||
static_cast<unsigned int>(town.localKey), town.consumedMinimum);
|
||||
return result;
|
||||
}
|
||||
if (dungeon.dungeon && NewNativeBodyWasConsumed(dungeon.start, dungeon.bodyLength, 36)) {
|
||||
dnf90_companion_p0::CancelFixedApc5405();
|
||||
if (g_captureLog) g_captureLog(
|
||||
"new-native companion lifecycle reset reason=accepted-dungeon-info opcode=28 dungeon=%u",
|
||||
static_cast<unsigned int>(dungeon.dungeon));
|
||||
}
|
||||
if (opcode >= 0) {
|
||||
dnf90_companion_p0::NewNativeSafePoint(opcode);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace dnf90_companion_new_native
|
||||
Reference in new issue
Block a user