#pragma once #include #include 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(packedGrow & 0x0Fu); const std::uint8_t awakening = static_cast((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( packedGrow & 0x0Fu); } static std::uint8_t AwakeningStage( std::uint8_t packedGrow) { return static_cast( (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(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(packet[1]) | static_cast(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