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,493 @@
|
||||
#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
|
||||
Reference in new issue
Block a user