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

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

494 lines
15 KiB
C++

#pragma once
#include <cstdint>
#include <cstddef>
namespace dnf90_companion_p0 {
struct FixedApcDescriptor {
std::int32_t apcCode;
std::int32_t minimumLevel;
std::int32_t maximumLevel;
std::int32_t lifetimeValue;
};
constexpr std::int32_t kCompanionLifetimeValue = 0x7FFFFFFF;
constexpr FixedApcDescriptor FixedApc5405Descriptor() {
return {5405, 25, 25, kCompanionLifetimeValue};
}
constexpr FixedApcDescriptor FixedApc56901Descriptor() {
return {56901, 70, 70, kCompanionLifetimeValue};
}
struct ProjectionBaseline {
FixedApcDescriptor descriptor{};
std::uint8_t sourceJob = 0;
std::uint8_t sourceGrow = 0;
std::uint8_t aicJob = 0;
std::uint8_t aicGrow = 0;
bool bridged = false;
};
struct ProjectionBaselineEntry {
std::uint8_t sourceJob;
std::uint8_t sourceGrow;
std::int32_t apcCode;
std::int32_t baselineLevel;
std::uint8_t aicJob;
std::uint8_t aicGrow;
};
struct ProjectionBaselineContract {
static const ProjectionBaselineEntry* Entries(std::size_t* count) {
static const ProjectionBaselineEntry entries[] = {
{0,0,900,70,0,0},{0,1,1201,86,0,1},{0,2,1202,86,0,2},{0,3,1203,86,0,3},{0,4,1204,86,0,4},{0,5,900,70,0,0},
{1,0,5405,25,1,0},{1,1,6200,86,1,1},{1,2,6201,86,1,2},{1,3,6202,86,1,3},{1,4,6203,86,1,4},
{2,0,10903,70,2,0},{2,1,11201,86,2,1},{2,2,11202,86,2,2},{2,3,11203,86,2,3},{2,4,11204,86,2,4},{2,5,10903,70,2,0},
{3,0,15902,70,3,0},{3,1,15121,86,3,1},{3,2,15122,86,3,2},{3,3,15123,86,3,3},{3,4,15124,86,3,4},{3,5,15902,70,3,0},
{4,0,20902,70,4,0},{4,1,21201,86,4,1},{4,2,21202,86,4,2},{4,3,21203,86,4,3},{4,4,21204,86,4,4},{4,5,20902,70,4,0},
{5,0,25900,70,5,0},{5,1,26201,86,5,1},{5,2,26202,86,5,2},{5,3,26203,86,5,3},{5,4,26204,86,5,4},{5,5,25900,70,5,0},
{6,0,30901,70,6,0},{6,1,31200,86,6,1},{6,2,31201,86,6,2},{6,3,31202,86,6,3},{6,4,31203,86,6,4},{6,5,30901,70,6,0},
{7,0,35903,70,7,0},{7,1,36200,86,7,1},{7,2,36201,86,7,2},{7,3,36202,86,7,3},{7,4,36203,86,7,4},
{8,0,40800,75,8,0},{8,1,41201,86,8,1},{8,2,41202,86,8,2},{8,3,41210,86,8,3},{8,4,41213,86,8,4},{8,5,41216,86,8,5},
{9,0,902,70,0,0},
{10,0,15903,70,3,0},
{11,0,56901,70,11,0},{11,1,56200,86,11,1},{11,2,56201,86,11,2},{11,3,56202,86,11,3},{11,4,56203,86,11,4},{11,5,56901,70,11,0},
{12,0,60901,70,12,0},{12,1,61200,86,12,1},{12,2,61209,86,12,2},{12,3,61203,86,12,3},{12,4,61206,86,12,4},
{13,0,65001,70,13,0},{13,1,65121,86,13,1},{13,2,65122,86,13,2},{13,3,65001,70,13,0},{13,4,65001,70,13,0},
{14,0,70003,70,14,0},{14,1,71200,86,14,1},{14,2,71203,86,14,2},{14,3,71206,86,14,3},{14,4,71209,86,14,4},
{15,0,75001,70,15,0},{15,1,75001,70,15,0},{15,2,75001,70,15,0},{15,3,75001,70,15,0},{15,4,75001,70,15,0},
};
if (count) {
*count = sizeof(entries) / sizeof(entries[0]);
}
return entries;
}
static std::size_t EntryCount() {
std::size_t count = 0;
Entries(&count);
return count;
}
static bool EntryAt(
std::size_t index,
ProjectionBaselineEntry* output)
{
if (!output) return false;
std::size_t count = 0;
const ProjectionBaselineEntry* entries =
Entries(&count);
if (index >= count) return false;
*output = entries[index];
return true;
}
static bool TrySelect(
std::uint8_t job,
std::uint8_t packedGrow,
ProjectionBaseline* output)
{
if (!output) return false;
const std::uint8_t firstGrow =
static_cast<std::uint8_t>(packedGrow & 0x0Fu);
const std::uint8_t awakening =
static_cast<std::uint8_t>((packedGrow >> 4) & 0x0Fu);
if (awakening > 2) return false;
std::size_t count = 0;
const ProjectionBaselineEntry* entries =
Entries(&count);
const ProjectionBaselineEntry* exact = nullptr;
const ProjectionBaselineEntry* stable = nullptr;
for (std::size_t index = 0;
index < count;
++index) {
const ProjectionBaselineEntry& entry =
entries[index];
if (entry.sourceJob != job) {
continue;
}
if (!exact && entry.sourceGrow == firstGrow) {
exact = &entry;
}
if (!stable &&
entry.sourceGrow == 0 &&
entry.aicJob == job &&
entry.aicGrow == 0) {
stable = &entry;
}
}
if (!exact) {
return false;
}
// PVP/branch-specific APCs often carry their own movement/FSM policy.
// Prefer one same-job grow=0 PVE shell and project the selected
// character's grow/awakening/skills onto the per-instance AIC copy.
// Jobs without a same-job stable shell keep their exact legacy entry.
const ProjectionBaselineEntry& selected =
stable ? *stable : *exact;
output->descriptor = {
selected.apcCode,
selected.baselineLevel,
selected.baselineLevel,
kCompanionLifetimeValue
};
output->sourceJob = job;
output->sourceGrow = firstGrow;
output->aicJob = selected.aicJob;
output->aicGrow = selected.aicGrow;
output->bridged =
selected.aicJob != job ||
selected.aicGrow != firstGrow;
return true;
}
};
struct ProjectionQuickSkillContract {
static bool PairValid(
std::uint32_t id,
std::uint32_t level)
{
return (id == 0) == (level == 0);
}
};
struct ProjectionGrowBridgeContract {
static std::uint8_t FirstGrow(
std::uint8_t packedGrow)
{
return static_cast<std::uint8_t>(
packedGrow & 0x0Fu);
}
static std::uint8_t AwakeningStage(
std::uint8_t packedGrow)
{
return static_cast<std::uint8_t>(
(packedGrow >> 4) & 0x0Fu);
}
static bool SameJob(
const ProjectionBaseline& baseline)
{
return baseline.sourceJob == baseline.aicJob;
}
static bool ShouldApplyFirstGrow(
const ProjectionBaseline& baseline)
{
return SameJob(baseline) &&
baseline.sourceGrow != baseline.aicGrow;
}
static bool ShouldApplyAwakening(
const ProjectionBaseline& baseline,
std::uint8_t packedGrow)
{
return SameJob(baseline) &&
AwakeningStage(packedGrow) != 0;
}
};
struct ProjectionRuntimeKeyContract {
static constexpr std::uint32_t kQuickSlotCount = 12;
static constexpr std::uint32_t kDirectPoolBase = 12;
static constexpr std::uint32_t kBasicAttackProgramIndex = 24;
static constexpr std::uint32_t kRequiredProgramCount = 25;
// Native AIC loaders are not uniform: some job shells expose one
// additional trailing runtime program. The companion-owned programs
// remain indices 0..24, so accept the proven 25/26 shapes and validate
// every mutable direct-pool slot before writing anything.
static constexpr std::uint32_t kMaxCompatibleProgramCount = 26;
static constexpr std::uint32_t kPlaceholderSkillId = 511;
static constexpr std::uint32_t kSkillCodeBias = 0x100;
static bool EncodeSkill(
std::uint32_t skillId,
std::uint32_t* encoded)
{
if (!encoded || skillId == 0 ||
skillId > 0xFFFFu) {
return false;
}
*encoded = kSkillCodeBias + skillId;
return true;
}
static bool ProgramCountValid(
std::size_t count)
{
return count >= kRequiredProgramCount &&
count <= kMaxCompatibleProgramCount;
}
};
static_assert(sizeof(FixedApcDescriptor) == 16,
"native helper reads exactly four dwords");
class FixedApcRetryPolicy {
public:
void Arm() {
pending_ = true;
submitted_ = false;
}
void Cancel() {
pending_ = false;
submitted_ = false;
}
bool ShouldSubmit(
bool routeReady,
bool nativeReady,
bool sceneReady,
bool projectionReady) {
if (!pending_ || submitted_ ||
!routeReady || !nativeReady || !sceneReady ||
!projectionReady) {
return false;
}
submitted_ = true;
pending_ = false;
return true;
}
bool Pending() const { return pending_; }
bool Submitted() const { return submitted_; }
private:
bool pending_ = false;
bool submitted_ = false;
};
struct DynamicAicApplyContract {
static bool Matches(
bool projectionPending,
bool insideP0Receive,
bool alreadyApplied,
std::uint32_t currentThread,
std::uint32_t armedThread,
const void* destination,
const void* source)
{
return projectionPending &&
insideP0Receive &&
!alreadyApplied &&
armedThread != 0 &&
currentThread == armedThread &&
destination != nullptr &&
source != nullptr &&
destination != source;
}
static bool VectorFits(
std::uintptr_t begin,
std::uintptr_t end,
std::uintptr_t capacityEnd,
std::size_t count,
std::size_t elementSize)
{
if (!begin || elementSize == 0 ||
end < begin || capacityEnd < end) return false;
if (((end - begin) % elementSize) != 0 ||
((capacityEnd - begin) % elementSize) != 0) return false;
return count <= (capacityEnd - begin) / elementSize;
}
static bool EquipmentFits(
std::uintptr_t begin,
std::uintptr_t end,
std::uintptr_t capacityEnd,
std::size_t count)
{
return VectorFits(begin, end, capacityEnd, count, 12u);
}
static bool SkillFits(
std::uintptr_t begin,
std::uintptr_t end,
std::uintptr_t capacityEnd,
std::size_t count)
{
return VectorFits(begin, end, capacityEnd, count, 8u);
}
};
struct ProjectionLookupContract {
static bool Matches(
bool applyActive,
std::uint16_t requestedKey,
std::uint16_t apcKey,
const void* apcActor)
{
return applyActive &&
requestedKey != 0 &&
requestedKey != 0xFFFF &&
requestedKey == apcKey &&
apcActor != nullptr;
}
};
struct SceneModeEntryContract {
static bool IsRelativeJump(std::uint8_t opcode) {
return opcode == 0xE9;
}
static bool JumpTargetInsideModule(
std::uintptr_t entry,
std::int32_t relative,
std::uintptr_t moduleBase,
std::size_t moduleSize)
{
if (!entry || !moduleBase || moduleSize == 0) return false;
const std::uintptr_t target =
entry + 5u + static_cast<std::intptr_t>(relative);
return target >= moduleBase &&
target < moduleBase + moduleSize;
}
};
struct ProjectionRegistrationContract {
static bool Matches(
bool p0Armed,
bool insideP0Receive,
std::uint32_t currentThread,
std::uint32_t armedThread,
std::uint32_t objectKey,
const void* actor,
unsigned char nativeResult)
{
return p0Armed &&
insideP0Receive &&
armedThread != 0 &&
currentThread == armedThread &&
objectKey != 0 &&
objectKey < 0xFFFFu &&
actor != nullptr &&
nativeResult != 0;
}
};
struct DirectOp40SendContract {
static bool Matches(
std::uint32_t routeKind,
std::int32_t targetSlot,
const void* actor,
const void* armedActor,
std::uint32_t mode)
{
return routeKind == 1 &&
targetSlot == -1 &&
actor != nullptr &&
actor == armedActor &&
mode == 3;
}
};
struct DirectOp40PacketContract {
static constexpr std::size_t kHeaderSize = 7;
static constexpr std::uint8_t kClassification = 2;
static constexpr std::uint16_t kMessageID = 40;
static bool Matches(const std::uint8_t* packet, std::size_t length) {
if (!packet || length < kHeaderSize) return false;
const std::uint16_t messageID =
static_cast<std::uint16_t>(packet[1]) |
static_cast<std::uint16_t>(packet[2] << 8);
return packet[0] == kClassification &&
messageID == kMessageID;
}
};
struct DirectOp40PeerDeliveryContract {
static bool Matches(
bool armed,
bool handled,
bool inReceive,
std::uint32_t currentThread,
std::uint32_t armedThread,
const std::uint8_t* packet,
std::size_t length,
std::uint32_t routeKind,
std::uint32_t mode)
{
return armed &&
handled &&
!inReceive &&
armedThread != 0 &&
currentThread == armedThread &&
routeKind == 1 &&
mode == 3 &&
DirectOp40PacketContract::Matches(packet, length);
}
};
bool DirectOp40Enabled();
bool ConfigureNewNativeBindings();
void SetNewNativeHooksReady(bool ready);
void NewNativeSafePoint(int opcode);
void SetOriginalPayloadEncode(void* original);
void __cdecl ProxyPayloadEncode(
std::uint8_t* payload,
std::int32_t length,
void* keyState);
void SetOriginalPeerDelivery(void* original);
void SetOriginalNetworkRouter(void* original);
int __fastcall ProxyNetworkRouter(
void* self,
void* unusedEdx,
std::int32_t sourceContext,
std::uint8_t* packet,
std::int32_t totalLength);
void SetOriginalAicCopy(void* original);
void* __fastcall ProxyAicCopy(
void* destination,
void* unusedEdx,
const void* source);
void SetOriginalApcRegistration(void* original);
void SetOriginalAICondition(void* original);
unsigned char __fastcall ProxyAICondition(void* condition, void* unusedEdx);
unsigned char __fastcall ProxyApcRegistration(
void* self,
void* unusedEdx,
void* ownerActor,
std::uint32_t flags,
std::uint32_t objectKey,
void* actor,
std::uint32_t value5,
std::uint32_t value6,
std::uint32_t value7,
std::uint32_t value8);
void SetProjectionClass0Dispatch(void* original);
bool CaptureProjectionClass0Packet(int dispatcherArg, int packet);
void LogProjectionDecodeRejected(
const unsigned char* bytes,
std::size_t packetLength,
const char* stage);
bool ApplyPendingProjection();
void* ProjectionActorOverrideForObjectKey(std::uint16_t objectKey);
bool ProjectionMode0OwnerContextOverride();
void __fastcall ProxyPeerDelivery(
void* self,
void* unusedEdx,
std::uint8_t* packet,
std::int32_t length,
std::int32_t slot,
std::uint32_t routeKind,
std::uint32_t mode);
// Test-only P0 lifecycle. Arm on confirmed dungeon entry, poll from subsequent
// native dispatch callbacks, cancel on accepted return-to-town.
void ArmFixedApc5405();
bool PollFixedApc5405();
void CancelFixedApc5405();
} // namespace dnf90_companion_p0