服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
123 lines
4.2 KiB
C++
123 lines
4.2 KiB
C++
#pragma once
|
|
|
|
namespace dnf90_first_login_recovery {
|
|
|
|
constexpr long kMaxFirstLoginRecoveryAttempts = 2;
|
|
|
|
inline bool IsFirstLoginServerProgressMessage(int messageType) {
|
|
return messageType == 1; // ENUM_CMDPACKET_LOGIN
|
|
}
|
|
|
|
inline bool IsFirstLoginClientProgressMessage(int messageType) {
|
|
switch (messageType) {
|
|
case 171: // ENUM_CMDPACKET_CLIENT_SPEC_STATISTIC
|
|
case 476: // ENUM_CMDPACKET_COMPATIBILITY_INDEX
|
|
case 1320: // ENUM_CMDPACKET_LOG_DOMAIN_CONNECT
|
|
case 1262: // ENUM_CMDPACKET_LOG_CONNECT_PROCESS
|
|
case 593: // ENUM_CMDPACKET_SECURITY_STATUS
|
|
case 8: // ENUM_CMDPACKET_GET_USERINFO
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The auto-connect timer can run inside the native startup message pump.
|
|
// Opening a socket while the packet processor is uninitialized loses LOGIN:
|
|
// native 02261E30 returns 80000001, but 03456304 still consumes the packet.
|
|
constexpr unsigned int kPacketProcessorPollMs = 50;
|
|
constexpr unsigned long long kPacketProcessorWaitLimitMs = 30000;
|
|
|
|
enum class PacketProcessorReadiness { NotReady, Ready, ReadFailed };
|
|
enum class AutoConnectAction { Ignore, Wait, Connect, TimedOut, ReadFailed };
|
|
|
|
struct AutoConnectReadiness {
|
|
long generation = 0;
|
|
unsigned long long startedAt = 0;
|
|
bool pending = false;
|
|
bool waited = false;
|
|
|
|
void Begin(long newGeneration, unsigned long long now) {
|
|
generation = newGeneration;
|
|
startedAt = now;
|
|
pending = true;
|
|
waited = false;
|
|
}
|
|
|
|
void Cancel() { pending = false; }
|
|
|
|
AutoConnectAction Poll(long currentGeneration, unsigned long long now,
|
|
PacketProcessorReadiness readiness) {
|
|
if (!pending || generation != currentGeneration) {
|
|
return AutoConnectAction::Ignore;
|
|
}
|
|
if (readiness == PacketProcessorReadiness::ReadFailed) {
|
|
pending = false;
|
|
return AutoConnectAction::ReadFailed;
|
|
}
|
|
if (readiness == PacketProcessorReadiness::Ready) {
|
|
pending = false; // Claim exactly one connect for this directory.
|
|
return AutoConnectAction::Connect;
|
|
}
|
|
waited = true;
|
|
if (now - startedAt >= kPacketProcessorWaitLimitMs) {
|
|
pending = false;
|
|
return AutoConnectAction::TimedOut;
|
|
}
|
|
return AutoConnectAction::Wait;
|
|
}
|
|
};
|
|
|
|
struct WatchdogState {
|
|
long watchdogGeneration;
|
|
long currentGeneration;
|
|
bool active;
|
|
bool serverDataSeen;
|
|
bool clientDataSeen;
|
|
long recoveryCount;
|
|
};
|
|
|
|
inline bool ShouldRecover(const WatchdogState& state) {
|
|
// Native ChannelConnect success is only a transport candidate. The first
|
|
// login is healthy once the client emits any proved post-login progress.
|
|
// serverDataSeen is diagnostic only: a stale native socket can prevent the
|
|
// receive hook from observing LOGIN even when the server already sent it.
|
|
return state.active &&
|
|
state.recoveryCount < kMaxFirstLoginRecoveryAttempts &&
|
|
state.watchdogGeneration > 0 &&
|
|
state.currentGeneration == state.watchdogGeneration &&
|
|
!state.clientDataSeen;
|
|
}
|
|
|
|
inline bool ShouldRecoverAfterRejectedRetry(
|
|
long initialSuccessGeneration,
|
|
long retryGeneration,
|
|
bool clientDataSeen,
|
|
long recoveryCount,
|
|
bool retryConnected) {
|
|
return initialSuccessGeneration > 0 &&
|
|
retryGeneration > initialSuccessGeneration &&
|
|
!clientDataSeen &&
|
|
recoveryCount < kMaxFirstLoginRecoveryAttempts &&
|
|
!retryConnected;
|
|
}
|
|
|
|
inline bool ShouldForceReleaseStaleSocket(
|
|
bool residentResetSucceeded,
|
|
bool socketPresent) {
|
|
return residentResetSucceeded && socketPresent;
|
|
}
|
|
|
|
// A proved login must not permanently disable recovery after disconnect.
|
|
// Only a rejected later directory connect with no live transport may reset;
|
|
// a successful connect or a still-owned socket always remains native-owned.
|
|
inline bool ShouldRecoverDisconnectedRetry(long initialGeneration,
|
|
long retryGeneration, bool clientProgress, bool connected,
|
|
bool transportReadable, bool socketAbsent, long attempts) {
|
|
return initialGeneration > 0 && retryGeneration > initialGeneration &&
|
|
clientProgress && !connected && transportReadable && socketAbsent &&
|
|
attempts < kMaxFirstLoginRecoveryAttempts;
|
|
}
|
|
|
|
} // namespace dnf90_first_login_recovery
|