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

@@ -0,0 +1,189 @@
#pragma once
// Offline-tested 32-bit layouts from current 90CN EXE 8a7af614... .
// NOT a network packet builder, callable native adapter, or deployment switch.
// See docs/evidence/companion-v1-native-create-20260923.md.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <type_traits>
namespace dnf90_companion {
constexpr std::size_t kNativeRequestSize = 0x54;
constexpr std::size_t kNativeTailSize = 0x29;
constexpr std::uint32_t kAllocateNativeObjectKey = 0xffff;
constexpr std::uint32_t kProtocolVersion = 1;
// Keep target addresses 32-bit even when tests run in a 64-bit host process.
// Unresolved fields are named by offset, not assigned speculative semantics.
#pragma pack(push, 4)
struct NativeApcRequest32 {
std::uint32_t template_id; // 00
std::uint32_t source_value04; // 04: owner vtable+370 result
std::uint32_t level; // 08: narrowed to u8 by serializer
std::uint32_t x, y, z, direction; // 0c..18
std::uint32_t mode1c; // 1c: mode 2 rejected by sender
std::uint32_t extra_address32, extra_size; // 20..24
std::uint8_t flag28, padding29[3];
std::uint32_t object_key; // 2c: ffff asks native allocator
std::uint8_t flag30, flag31, padding32[2];
std::uint32_t tail_address32; // 34: optional 29-byte native tail
std::uint8_t flag38, padding39[3];
std::int32_t auxiliary3c;
std::uint8_t flag40, padding41[3];
std::uint32_t byte_list_address32; // 44: native container, NOT byte[]
std::uint8_t flag48, padding49[3];
std::uint32_t auxiliary4c;
std::uint8_t flag50, padding51[3];
};
#pragma pack(pop)
// Native initializer 037E6B30 has unaligned fields. Do not use a host ABI
// structure without packing, or memcpy these bytes into an upper game packet.
#pragma pack(push, 1)
struct NativeSummonTail32 {
std::uint8_t flag00, flag01, flag02;
std::uint32_t duration_value; // 03: item description's fourth int
std::uint16_t owner07, owner09;
std::uint8_t flag0b, reserved0c;
float rate0d;
std::uint8_t flag11;
std::uint16_t value12;
std::uint32_t value14, value18, value1c, value20;
std::uint8_t flag24;
std::int32_t value25;
};
#pragma pack(pop)
static_assert(sizeof(NativeApcRequest32) == kNativeRequestSize, "request ABI");
static_assert(alignof(NativeApcRequest32) == 4, "request alignment");
static_assert(offsetof(NativeApcRequest32, source_value04) == 0x04, "field04");
static_assert(offsetof(NativeApcRequest32, mode1c) == 0x1c, "field1c");
static_assert(offsetof(NativeApcRequest32, flag28) == 0x28, "field28");
static_assert(offsetof(NativeApcRequest32, object_key) == 0x2c, "object key");
static_assert(offsetof(NativeApcRequest32, flag31) == 0x31, "field31");
static_assert(offsetof(NativeApcRequest32, tail_address32) == 0x34, "tail pointer");
static_assert(offsetof(NativeApcRequest32, auxiliary3c) == 0x3c, "field3c");
static_assert(offsetof(NativeApcRequest32, byte_list_address32) == 0x44, "field44");
static_assert(offsetof(NativeApcRequest32, auxiliary4c) == 0x4c, "field4c");
static_assert(offsetof(NativeApcRequest32, flag50) == 0x50, "field50");
static_assert(sizeof(NativeSummonTail32) == kNativeTailSize, "tail ABI");
static_assert(offsetof(NativeSummonTail32, duration_value) == 3, "tail duration");
static_assert(offsetof(NativeSummonTail32, rate0d) == 0x0d, "tail rate");
static_assert(offsetof(NativeSummonTail32, value25) == 0x25, "tail ending");
static_assert(sizeof(float) == 4 && std::numeric_limits<float>::is_iec559,
"native float representation");
static_assert(std::is_trivially_copyable<NativeApcRequest32>::value, "request POD");
inline NativeApcRequest32 DefaultRequest() {
NativeApcRequest32 value;
// Native padding is not fully initialized; zero it to avoid leaked stack data.
std::memset(&value, 0, sizeof(value));
value.direction = 4;
value.object_key = kAllocateNativeObjectKey;
value.auxiliary3c = -1;
value.auxiliary4c = 5;
return value;
}
inline NativeSummonTail32 DefaultTail() {
NativeSummonTail32 value;
std::memset(&value, 0, sizeof(value));
value.owner07 = value.owner09 = 0xffff;
value.flag0b = 1;
value.rate0d = 1.0f; // actual constant at preferred VA 04B99848
value.value25 = -1;
return value;
}
struct RequestValues {
std::uint32_t template_id;
std::uint32_t source_value04;
std::uint32_t level;
std::int64_t x, y, z;
std::uint32_t direction;
};
enum class RequestError {
None, MissingOutput, TemplateRange, SourceValueRange, LevelRange,
CoordinateRange, DirectionRange
};
inline bool FitsCoordinate(std::int64_t value) { return value >= 0 && value <= 65535; }
inline RequestError BuildRequest(const RequestValues& values, NativeApcRequest32* out) {
if (!out) return RequestError::MissingOutput;
// Factory 02DE5CA0 supports positive ids <= 80000, but catalog existence
// and job/profile compatibility MUST still be proved separately.
if (values.template_id == 0 || values.template_id > 80000)
return RequestError::TemplateRange;
if (values.source_value04 > 65535) return RequestError::SourceValueRange;
if (values.level == 0 || values.level > 255) return RequestError::LevelRange;
if (!FitsCoordinate(values.x) || !FitsCoordinate(values.y) || !FitsCoordinate(values.z))
return RequestError::CoordinateRange;
// Native initializer uses 4; the item route uses the actor facing result.
// Only the scalar width is proved here, not a complete direction enum.
if (values.direction > 255) return RequestError::DirectionRange;
auto result = DefaultRequest();
result.template_id = values.template_id;
result.source_value04 = values.source_value04;
result.level = values.level;
result.x = static_cast<std::uint32_t>(values.x);
result.y = static_cast<std::uint32_t>(values.y);
result.z = static_cast<std::uint32_t>(values.z);
result.direction = values.direction;
*out = result; // errors leave output unchanged; used on the game thread
return RequestError::None;
}
// Pure receipt matcher for the future game-thread adapter. A sender returning
// does not prove creation. The actual receiver must independently observe
// registration, per-instance state and the engine's safe AI pause boundary.
struct SpawnIdentity {
std::uint64_t session_generation, run_token, request_sequence;
std::uint16_t owner_character;
};
inline bool SameSpawn(const SpawnIdentity& a, const SpawnIdentity& b) {
return a.session_generation == b.session_generation && a.run_token == b.run_token &&
a.request_sequence == b.request_sequence && a.owner_character == b.owner_character;
}
struct NativeReceipt {
SpawnIdentity identity;
std::uint32_t template_id;
std::uint32_t actor_address32;
std::uint32_t object_key;
std::uint32_t actor_generation;
bool registered;
bool inactive_confirmed;
};
enum class ReceiptError {
None, StaleSpawn, InvalidIdentity, WrongTemplate, InvalidActor,
InvalidKey, KeyCollision, NotRegistered, AINotPaused, ConflictingDuplicate
};
class PendingSpawn {
public:
PendingSpawn(SpawnIdentity identity, std::uint32_t template_id)
: identity_(identity), template_id_(template_id) {}
ReceiptError Observe(const NativeReceipt& receipt, bool key_collision) {
if (!SameSpawn(identity_, receipt.identity)) return ReceiptError::StaleSpawn;
if (!identity_.session_generation || !identity_.run_token ||
!identity_.request_sequence || !identity_.owner_character ||
!template_id_ || template_id_ > 80000) return ReceiptError::InvalidIdentity;
if (receipt.template_id != template_id_) return ReceiptError::WrongTemplate;
if (!receipt.actor_address32 || !receipt.actor_generation) return ReceiptError::InvalidActor;
if (!receipt.object_key || receipt.object_key >= 65535) return ReceiptError::InvalidKey;
if (key_collision) return ReceiptError::KeyCollision;
if (!receipt.registered) return ReceiptError::NotRegistered;
if (!receipt.inactive_confirmed) return ReceiptError::AINotPaused;
if (has_receipt_ && (receipt_.actor_address32 != receipt.actor_address32 ||
receipt_.object_key != receipt.object_key ||
receipt_.actor_generation != receipt.actor_generation))
return ReceiptError::ConflictingDuplicate;
receipt_ = receipt;
has_receipt_ = true;
return ReceiptError::None;
}
bool CanBindOwner() const { return has_receipt_; }
private:
SpawnIdentity identity_;
std::uint32_t template_id_;
bool has_receipt_ = false;
NativeReceipt receipt_{};
};
} // namespace dnf90_companion