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,308 @@
#pragma once
// Client-side execution coordinator. Not installed into 90CN.dll.
// NativeDriver is deliberately an interface: no current native implementation
// has passed creation/pause/retirement verification. Offline tests inject a fake.
#include "90CN_companion_native_contract.h"
#include <thread>
namespace dnf90_companion {
struct ActorHandle {
std::uint32_t address32 = 0, object_key = 0, generation = 0;
};
inline bool SameActor(const ActorHandle& a, const ActorHandle& b) {
return a.address32 == b.address32 && a.object_key == b.object_key &&
a.generation == b.generation;
}
inline bool ValidActor(const ActorHandle& h) {
return h.address32 && h.object_key && h.object_key < 65535 && h.generation;
}
struct ClientPlan {
SpawnIdentity identity{};
std::uint16_t source_character = 0;
RequestValues values{};
};
enum class ActorPresence { Unavailable, Present, Gone, IdentityMismatch };
struct ActorObservation {
ActorPresence presence = ActorPresence::Unavailable;
bool registered = false, paused = false, alive = false;
std::uint64_t room_generation = 0;
};
enum class Submission { NotSubmitted, Pending };
// Every method executes on the verified game thread and MUST NOT wait for IO.
// Implementations resolve handles afresh before touching native memory, and
// validate provenance against the authenticated server/session/run and profile.
// BeginPaused must guarantee no AI attack before the creation observer; a later
// best-effort pause is insufficient. Pending also covers uncertain submission.
// Gone means proven engine retirement, not lookup error or missing room manager.
class NativeDriver {
public:
virtual ~NativeDriver() = default;
virtual bool VerifiedFor(const ClientPlan&) const noexcept = 0;
virtual bool RoomReady(const SpawnIdentity&, std::uint64_t) const noexcept = 0;
virtual Submission BeginPaused(const ClientPlan&, std::uint64_t) noexcept = 0;
virtual bool BelongsToRequest(const ClientPlan&, const ActorHandle&) const noexcept = 0;
virtual ActorObservation Inspect(const SpawnIdentity&, const ActorHandle&) const noexcept = 0;
virtual bool SetPaused(const SpawnIdentity&, const ActorHandle&, bool) noexcept = 0;
virtual bool RequestRetirement(const SpawnIdentity&, const ActorHandle&) noexcept = 0;
// true means the pending operation AND its callback queue are drained,
// with no undisclosed live object. false does not authorize another spawn.
virtual bool CancelAndDrain(const SpawnIdentity&) noexcept = 0;
virtual bool CreationDrained(const SpawnIdentity&) const noexcept = 0;
};
enum class ClientPhase {
Idle, Prepared, Creating, AwaitOwner, Bound, Active, Transitioning, Dead,
Retiring, CleanupBlocked, Closed
};
enum class ClientResult {
Ok, WrongThread, InvalidPlan, StaleRun, Busy, Unsupported, RoomNotReady,
WrongPhase, WrongActor, ObservationUnavailable, AINotPaused, NativeFailure,
Timeout, CleanupPending, ClockRegression
};
struct ClientStatus {
ClientPhase phase = ClientPhase::Idle;
ClientResult reason = ClientResult::Ok;
SpawnIdentity identity{};
ActorHandle actor{};
std::uint64_t room_generation = 0;
bool owner_bound = false, creation_pending = false;
};
struct ClientTimeouts {
std::uint64_t preparation_ms = 10000, creation_ms = 5000,
binding_ms = 5000, transition_ms = 10000, retirement_ms = 5000;
};
// All mutating methods, including observations, are game-thread only. The
// future channel must queue immutable messages there, never call this object
// from its reader thread. The coordinator creates no thread, hook or timer.
class ClientRuntime {
public:
ClientRuntime(NativeDriver& driver, std::uint64_t session_generation,
std::uint16_t owner_character, std::uint16_t player_object_key,
ClientTimeouts timeouts = {})
: driver_(driver), thread_(std::this_thread::get_id()),
session_(session_generation), owner_(owner_character),
player_key_(player_object_key), timeouts_(timeouts) {}
ClientRuntime(const ClientRuntime&) = delete;
ClientRuntime& operator=(const ClientRuntime&) = delete;
bool TryStatus(ClientStatus& out) const {
if (std::this_thread::get_id() != thread_) return false;
out = state_; return true;
}
ClientResult Prepare(const ClientPlan& plan, std::uint64_t now) {
auto guard = TimeGuard(now); if (guard != ClientResult::Ok) return guard;
if (state_.phase != ClientPhase::Idle && state_.phase != ClientPhase::Closed)
return ClientResult::Busy;
const auto& id = plan.identity;
NativeApcRequest32 request{};
if (!session_ || !owner_ || owner_ == 65535 || !player_key_ || player_key_ == 65535 ||
id.session_generation != session_ || id.owner_character != owner_ ||
!id.run_token || !id.request_sequence || !plan.source_character ||
plan.source_character == owner_ || plan.source_character == 65535 ||
BuildRequest(plan.values, &request) != RequestError::None)
return ClientResult::InvalidPlan;
if (id.request_sequence <= last_request_) return ClientResult::StaleRun;
if (!driver_.VerifiedFor(plan)) return ClientResult::Unsupported;
plan_ = plan; state_ = {}; state_.identity = id;
state_.phase = ClientPhase::Prepared; since_ = now;
last_request_ = id.request_sequence;
retire_sent_ = cancel_sent_ = false;
return ClientResult::Ok;
}
ClientResult RoomReady(const SpawnIdentity& id, std::uint64_t room, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (!room) return ClientResult::RoomNotReady;
if (room == state_.room_generation &&
(state_.phase == ClientPhase::Creating || state_.phase == ClientPhase::AwaitOwner ||
state_.phase == ClientPhase::Bound || state_.phase == ClientPhase::Active))
return ClientResult::Ok; // repeated load callback never resubmits
if (room <= state_.room_generation || !driver_.RoomReady(id, room))
return ClientResult::RoomNotReady;
if (state_.phase == ClientPhase::Transitioning) {
const auto o = driver_.Inspect(id, state_.actor);
if (o.presence != ActorPresence::Present) return ClientResult::ObservationUnavailable;
if (!o.alive) { state_.phase = ClientPhase::Dead; return ClientResult::Ok; }
if (!o.registered || !o.paused || o.room_generation != room || !state_.owner_bound)
return ClientResult::RoomNotReady;
state_.room_generation = room;
return Resume(now);
}
if (state_.phase != ClientPhase::Prepared) return ClientResult::WrongPhase;
if (!driver_.VerifiedFor(plan_)) return Close(ClientResult::Unsupported);
state_.room_generation = room; state_.phase = ClientPhase::Creating;
state_.creation_pending = true; since_ = now;
// State is installed before submitting. The driver must enqueue its
// observer callbacks until this call has returned (no re-entrancy).
if (driver_.BeginPaused(plan_, room) == Submission::NotSubmitted) {
state_.creation_pending = false;
return Close(ClientResult::NativeFailure);
}
return ClientResult::Ok;
}
ClientResult NativeCreated(const SpawnIdentity& id, ActorHandle h, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (!ValidActor(h) || h.object_key == player_key_ || !driver_.BelongsToRequest(plan_, h))
return ClientResult::WrongActor;
if (ValidActor(state_.actor)) {
if (!SameActor(state_.actor, h)) return Block(ClientResult::WrongActor);
// A duplicate cannot reset a terminal/dead/active lifecycle.
return ClientResult::Ok;
}
if (!state_.creation_pending ||
(state_.phase != ClientPhase::Creating && state_.phase != ClientPhase::Retiring &&
state_.phase != ClientPhase::CleanupBlocked)) return ClientResult::WrongPhase;
const auto o = driver_.Inspect(id, h);
if (o.presence != ActorPresence::Present || !o.registered)
return ClientResult::ObservationUnavailable;
state_.actor = h; state_.creation_pending = false;
if (state_.phase == ClientPhase::Retiring || state_.phase == ClientPhase::CleanupBlocked) {
state_.phase = ClientPhase::Retiring; since_ = now;
return SendRetirement(); // late result after cancel: never bind or activate
}
if (!o.paused || !o.alive || o.room_generation != state_.room_generation) {
return StartRetirement(now, ClientResult::AINotPaused);
}
state_.phase = ClientPhase::AwaitOwner; since_ = now;
return ClientResult::Ok;
}
ClientResult OwnerBound(const SpawnIdentity& id, ActorHandle h, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (!SameActor(h, state_.actor) || !ValidActor(h)) return ClientResult::WrongActor;
if ((state_.phase == ClientPhase::Bound || state_.phase == ClientPhase::Active) &&
state_.owner_bound) return ClientResult::Ok;
if (state_.phase != ClientPhase::AwaitOwner) return ClientResult::WrongPhase;
const auto o = driver_.Inspect(id, h);
if (o.presence != ActorPresence::Present || !o.registered)
return ClientResult::ObservationUnavailable;
if (!o.paused || !o.alive || o.room_generation != state_.room_generation)
return StartRetirement(now, ClientResult::AINotPaused);
state_.owner_bound = true; state_.phase = ClientPhase::Bound;
return ClientResult::Ok;
}
ClientResult Activate(const SpawnIdentity& id, ActorHandle h, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (!SameActor(h, state_.actor) || !ValidActor(h)) return ClientResult::WrongActor;
if (state_.phase == ClientPhase::Active) return ClientResult::Ok;
if (state_.phase != ClientPhase::Bound || !state_.owner_bound) return ClientResult::WrongPhase;
return Resume(now);
}
ClientResult RoomLeaving(const SpawnIdentity& id, std::uint64_t room, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (room != state_.room_generation) return ClientResult::RoomNotReady;
if (state_.phase == ClientPhase::Transitioning || state_.phase == ClientPhase::Dead)
return ClientResult::Ok;
if (state_.phase != ClientPhase::Active) return ClientResult::WrongPhase;
auto o = driver_.Inspect(id, state_.actor);
if (o.presence != ActorPresence::Present) return Block(ClientResult::ObservationUnavailable);
if (!o.alive) { state_.phase = ClientPhase::Dead; return ClientResult::Ok; }
if (!driver_.SetPaused(id, state_.actor, true)) return StartRetirement(now, ClientResult::NativeFailure);
o = driver_.Inspect(id, state_.actor);
if (o.presence != ActorPresence::Present || !o.paused)
return StartRetirement(now, ClientResult::AINotPaused);
state_.phase = ClientPhase::Transitioning; since_ = now;
return ClientResult::Ok;
}
ClientResult Retire(const SpawnIdentity& id, std::uint64_t now) {
auto guard = Guard(id, now); if (guard != ClientResult::Ok) return guard;
if (state_.phase == ClientPhase::Closed) return ClientResult::Ok;
if (state_.phase == ClientPhase::Retiring || state_.phase == ClientPhase::CleanupBlocked)
return ClientResult::CleanupPending;
return StartRetirement(now, ClientResult::Ok);
}
ClientResult Poll(std::uint64_t now) {
auto guard = TimeGuard(now); if (guard != ClientResult::Ok) return guard;
const auto phase = state_.phase;
if (phase == ClientPhase::Idle || phase == ClientPhase::Closed) return ClientResult::Ok;
if (phase == ClientPhase::Retiring || phase == ClientPhase::CleanupBlocked) {
if (state_.creation_pending && driver_.CreationDrained(state_.identity))
state_.creation_pending = false;
if (!ValidActor(state_.actor)) {
if (!state_.creation_pending) return Close(state_.reason);
} else {
const auto o = driver_.Inspect(state_.identity, state_.actor);
if (o.presence == ActorPresence::Gone && !state_.creation_pending)
return Close(state_.reason);
if (o.presence == ActorPresence::IdentityMismatch) return Block(ClientResult::WrongActor);
}
if (Elapsed(now, timeouts_.retirement_ms)) return Block(ClientResult::Timeout);
return ClientResult::CleanupPending;
}
if (phase == ClientPhase::Active || phase == ClientPhase::Transitioning || phase == ClientPhase::Dead) {
const auto o = driver_.Inspect(state_.identity, state_.actor);
if (o.presence == ActorPresence::IdentityMismatch) return Block(ClientResult::WrongActor);
if (o.presence == ActorPresence::Gone) return Close(ClientResult::NativeFailure);
if (o.presence == ActorPresence::Unavailable) return ClientResult::ObservationUnavailable;
if (!o.alive) { state_.phase = ClientPhase::Dead; return ClientResult::Ok; }
}
std::uint64_t timeout = 0;
if (phase == ClientPhase::Prepared) timeout = timeouts_.preparation_ms;
if (phase == ClientPhase::Creating) timeout = timeouts_.creation_ms;
if (phase == ClientPhase::AwaitOwner || phase == ClientPhase::Bound) timeout = timeouts_.binding_ms;
if (phase == ClientPhase::Transitioning) timeout = timeouts_.transition_ms;
if (timeout && Elapsed(now, timeout)) return StartRetirement(now, ClientResult::Timeout);
return ClientResult::Ok;
}
private:
ClientResult TimeGuard(std::uint64_t now) {
if (std::this_thread::get_id() != thread_) return ClientResult::WrongThread;
if (now < last_now_) return ClientResult::ClockRegression;
last_now_ = now; return ClientResult::Ok;
}
ClientResult Guard(const SpawnIdentity& id, std::uint64_t now) {
auto result = TimeGuard(now); if (result != ClientResult::Ok) return result;
return SameSpawn(id, state_.identity) ? ClientResult::Ok : ClientResult::StaleRun;
}
bool Elapsed(std::uint64_t now, std::uint64_t limit) const { return now - since_ >= limit; }
ClientResult Block(ClientResult reason) {
state_.phase = ClientPhase::CleanupBlocked; state_.reason = reason; return reason;
}
ClientResult Close(ClientResult reason) {
state_.phase = ClientPhase::Closed; state_.reason = reason;
state_.owner_bound = false; state_.creation_pending = false;
return reason;
}
ClientResult SendRetirement() {
if (retire_sent_) return ClientResult::CleanupPending;
const auto o = driver_.Inspect(state_.identity, state_.actor);
if (o.presence == ActorPresence::Gone) return Close(state_.reason);
if (o.presence != ActorPresence::Present) return Block(ClientResult::ObservationUnavailable);
retire_sent_ = true; // no uncertain native operation is blindly retried
if (!driver_.RequestRetirement(state_.identity, state_.actor)) return Block(ClientResult::NativeFailure);
return ClientResult::CleanupPending;
}
ClientResult StartRetirement(std::uint64_t now, ClientResult reason) {
state_.phase = ClientPhase::Retiring; state_.reason = reason; since_ = now;
if (ValidActor(state_.actor)) return SendRetirement();
if (state_.creation_pending && !cancel_sent_) {
cancel_sent_ = true;
if (driver_.CancelAndDrain(state_.identity)) state_.creation_pending = false;
}
if (!state_.creation_pending) return Close(reason);
return ClientResult::CleanupPending;
}
ClientResult Resume(std::uint64_t now) {
const auto o = driver_.Inspect(state_.identity, state_.actor);
if (o.presence != ActorPresence::Present || !o.registered || !o.paused || !o.alive ||
o.room_generation != state_.room_generation || !state_.owner_bound)
return StartRetirement(now, ClientResult::AINotPaused);
if (!driver_.SetPaused(state_.identity, state_.actor, false))
return StartRetirement(now, ClientResult::NativeFailure);
const auto after = driver_.Inspect(state_.identity, state_.actor);
if (after.presence != ActorPresence::Present || !after.registered || after.paused ||
!after.alive || after.room_generation != state_.room_generation)
return StartRetirement(now, ClientResult::NativeFailure);
state_.phase = ClientPhase::Active; since_ = now;
return ClientResult::Ok;
}
NativeDriver& driver_;
const std::thread::id thread_;
const std::uint64_t session_;
const std::uint16_t owner_, player_key_;
const ClientTimeouts timeouts_;
ClientPlan plan_{};
ClientStatus state_{};
std::uint64_t since_ = 0, last_now_ = 0, last_request_ = 0;
bool retire_sent_ = false, cancel_sent_ = false;
};
} // namespace dnf90_companion