diff --git a/source/shared/idlib/blockalloc.h b/source/shared/idlib/blockalloc.h index 9522f5e..c96afe4 100644 --- a/source/shared/idlib/blockalloc.h +++ b/source/shared/idlib/blockalloc.h @@ -1,7 +1,308 @@ #pragma once -// The complete allocator implementation survived in Doom 3 BFG's Heap.h. -// Tungsten split the same fixed-block and dynamic-block templates into this -// path; retain that source-level include path while using the vetted PC code. -#include "idlib/precompiled.h" +#include "blockalloc_base.h" +#include "containers/btree.h" +#include +#include + +// Variable-size allocator reconstructed from the recovered PDB layout and the +// Hex-Rays AllocInternal/ResizeInternal/FreeInternal/linking bodies. +template +class idDynamicBlockAlloc { +public: + using block_t = idDynamicBlock; + using tree_t = idBTree; + + block_t* firstBlock; + block_t* lastBlock; + tree_t freeTree; + bool allowAllocs; + bool clearAllocs; + int numBaseBlocks; + int baseBlockMemory; + int numUsedBlocks; + int usedBlockMemory; + int numFreeBlocks; + int freeBlockMemory; + int numAllocs; + int numResizes; + int numFrees; + + idDynamicBlockAlloc() { + Init(); + } + + ~idDynamicBlockAlloc() { + Shutdown(); + } + + idDynamicBlockAlloc(const idDynamicBlockAlloc&) = delete; + idDynamicBlockAlloc& operator=(const idDynamicBlockAlloc&) = delete; + + void Init() { + firstBlock = nullptr; + lastBlock = nullptr; + freeTree.Init(); + allowAllocs = true; + clearAllocs = false; + numBaseBlocks = 0; + baseBlockMemory = 0; + numUsedBlocks = 0; + usedBlockMemory = 0; + numFreeBlocks = 0; + freeBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; + } + + void Shutdown() { + for (block_t* block = firstBlock; block != nullptr; block = block->next) { + if (block->node == nullptr) { + FreeInternal(block); + } + } + while (firstBlock != nullptr) { + block_t* const block = firstBlock; + firstBlock = block->next; + _aligned_free(block); + } + freeTree.Shutdown(); + lastBlock = nullptr; + allowAllocs = true; + numBaseBlocks = 0; + baseBlockMemory = 0; + numUsedBlocks = 0; + usedBlockMemory = 0; + numFreeBlocks = 0; + freeBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; + } + + void SetFixedBlocks(const int numBlocks) { + const int count = numBlocks > 0 ? numBlocks : 0; + while (numBaseBlocks < count) { + block_t* const block = AllocBaseBlock(baseBlockSize); + if (block == nullptr) { + break; + } + LinkFreeInternal(block); + } + allowAllocs = false; + } + + void SetAllocAllowed(const bool allowed) { allowAllocs = allowed; } + void SetClear(const bool clear) { clearAllocs = clear; } + + void FreeEmptyBaseBlocks() { + block_t* block = firstBlock; + while (block != nullptr) { + block_t* const next = block->next; + if (block->IsBaseBlock() && block->node != nullptr + && (next == nullptr || next->IsBaseBlock())) { + UnlinkFreeInternal(block); + if (block->prev != nullptr) block->prev->next = next; + else firstBlock = next; + if (next != nullptr) next->prev = block->prev; + else lastBlock = block->prev; + --numBaseBlocks; + baseBlockMemory -= block->GetSize() + static_cast(sizeof(block_t)); + _aligned_free(block); + } + block = next; + } + } + + T* Alloc(const int num) { + ++numAllocs; + if (num <= 0) return nullptr; + block_t* block = AllocInternal(num); + if (block == nullptr) return nullptr; + block = ResizeInternal(block, num); + if (block == nullptr) return nullptr; + ++numUsedBlocks; + usedBlockMemory += block->GetSize(); + if (clearAllocs) std::memset(block->GetMemory(), 0, num); + return block->GetMemory(); + } + + T* Resize(T* ptr, const int num) { + ++numResizes; + if (ptr == nullptr) return Alloc(num); + if (num <= 0) { + Free(ptr); + return nullptr; + } + block_t* const oldBlock = reinterpret_cast(ptr) - 1; + const int oldSize = oldBlock->GetSize(); + block_t* const newBlock = ResizeInternal(oldBlock, num); + if (newBlock == nullptr) return nullptr; + usedBlockMemory += newBlock->GetSize() - oldSize; + return newBlock->GetMemory(); + } + + void Free(T* ptr) { + ++numFrees; + if (ptr == nullptr) return; + block_t* const block = reinterpret_cast(ptr) - 1; + --numUsedBlocks; + usedBlockMemory -= block->GetSize(); + FreeInternal(block); + } + + const char* CheckMemory(const T* ptr) const { + if (ptr == nullptr) return "null pointer"; + const block_t* const candidate = reinterpret_cast(ptr) - 1; + for (const block_t* block = firstBlock; block != nullptr; block = block->next) { + if (block == candidate) return block->node == nullptr ? nullptr : "memory is free"; + } + return "memory was not allocated by this allocator"; + } + + int GetNumBaseBlocks() const { return numBaseBlocks; } + int GetBaseBlockMemory() const { return baseBlockMemory; } + int GetNumUsedBlocks() const { return numUsedBlocks; } + int GetUsedBlockMemory() const { return usedBlockMemory; } + int GetNumFreeBlocks() const { return numFreeBlocks; } + int GetFreeBlockMemory() const { return freeBlockMemory; } + int GetNumEmptyBaseBlocks() const { + int count = 0; + for (block_t* block = firstBlock; block != nullptr; block = block->next) { + if (block->IsBaseBlock() && block->node != nullptr + && (block->next == nullptr || block->next->IsBaseBlock())) { + ++count; + } + } + return count; + } + +private: + static int AlignSize(const int num) { + return ((num + minBlockSize - 1) / minBlockSize) * minBlockSize; + } + + block_t* AllocBaseBlock(const int requestedPayload) { + int bytes = std::max(requestedPayload + static_cast(sizeof(block_t)), + baseBlockSize); + bytes = (bytes + 0xFFFF) & ~0xFFFF; + block_t* const block = static_cast(_aligned_malloc(bytes, 16)); + if (block == nullptr) return nullptr; + block->size = static_cast(sizeof(block_t)) - bytes; + block->prev = lastBlock; + block->next = nullptr; + block->node = nullptr; + if (lastBlock != nullptr) lastBlock->next = block; + else firstBlock = block; + lastBlock = block; + ++numBaseBlocks; + baseBlockMemory += bytes; + return block; + } + + block_t* AllocInternal(const int num) { + const int aligned = AlignSize(num); + block_t* const reusable = freeTree.FindSmallestLargerEqual(aligned); + if (reusable != nullptr) { + UnlinkFreeInternal(reusable); + return reusable; + } + if (!allowAllocs) return nullptr; + return AllocBaseBlock(aligned); + } + + block_t* ResizeInternal(block_t* block, const int num) { + const int aligned = AlignSize(num); + block_t* result = block; + const int oldSize = block->GetSize(); + if (aligned > oldSize) { + block_t* const next = block->next; + if (next != nullptr && !next->IsBaseBlock() && next->node != nullptr + && oldSize + next->GetSize() + static_cast(sizeof(block_t)) >= aligned) { + UnlinkFreeInternal(next); + const int combined = oldSize + next->GetSize() + + static_cast(sizeof(block_t)); + block->size = block->IsBaseBlock() ? -combined : combined; + block->next = next->next; + if (block->next != nullptr) block->next->prev = block; + else lastBlock = block; + } else { + result = AllocInternal(num); + if (result == nullptr) return nullptr; + result = ResizeInternal(result, num); + if (result == nullptr) return nullptr; + std::memcpy(result->GetMemory(), block->GetMemory(), oldSize); + FreeInternal(block); + return result; + } + } + + const int remainder = result->GetSize() + - static_cast(sizeof(block_t)) - aligned; + if (remainder >= minBlockSize) { + unsigned char* const splitAddress = + reinterpret_cast(result->GetMemory()) + aligned; + block_t* const split = reinterpret_cast(splitAddress); + split->size = remainder; + split->prev = result; + split->next = result->next; + split->node = nullptr; + if (split->next != nullptr) split->next->prev = split; + else lastBlock = split; + const bool base = result->IsBaseBlock(); + result->next = split; + result->size = base ? -aligned : aligned; + FreeInternal(split); + } + return result; + } + + void FreeInternal(block_t* block) { + block_t* const next = block->next; + if (next != nullptr && !next->IsBaseBlock() && next->node != nullptr) { + UnlinkFreeInternal(next); + const int combined = block->GetSize() + next->GetSize() + + static_cast(sizeof(block_t)); + block->size = block->IsBaseBlock() ? -combined : combined; + block->next = next->next; + if (block->next != nullptr) block->next->prev = block; + else lastBlock = block; + } + + block_t* const prev = block->prev; + if (prev != nullptr && !block->IsBaseBlock() && prev->node != nullptr) { + UnlinkFreeInternal(prev); + const int combined = prev->GetSize() + block->GetSize() + + static_cast(sizeof(block_t)); + prev->size = prev->IsBaseBlock() ? -combined : combined; + prev->next = block->next; + if (prev->next != nullptr) prev->next->prev = prev; + else lastBlock = prev; + LinkFreeInternal(prev); + } else { + LinkFreeInternal(block); + } + } + + void LinkFreeInternal(block_t* block) { + block->node = freeTree.Add(block, block->GetSize()); + ++numFreeBlocks; + freeBlockMemory += block->GetSize(); + } + + void UnlinkFreeInternal(block_t* block) { + freeTree.Remove(block->node); + block->node = nullptr; + --numFreeBlocks; + freeBlockMemory -= block->GetSize(); + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idBlockAlloc) == 20, + "Recovered idBlockAlloc ABI changed"); +static_assert(sizeof(idDynamicBlockAlloc) == 72, + "Recovered idDynamicBlockAlloc ABI changed"); +#endif diff --git a/source/shared/idlib/blockalloc_base.h b/source/shared/idlib/blockalloc_base.h new file mode 100644 index 0000000..5ccef4e --- /dev/null +++ b/source/shared/idlib/blockalloc_base.h @@ -0,0 +1,230 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Fixed block allocator reconstructed from the repeated PDB specializations +// and the AllocNewBlock/Alloc/Free/Shutdown bodies in the Hex-Rays dump. +template +class idBlockAlloc { +public: + static_assert(blockSize > 0, "idBlockAlloc requires a positive block size"); + + static constexpr std::size_t ELEMENT_SIZE = + sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*); + + union element_t { + T* data; + element_t* next; + alignas(T) unsigned char buffer[ELEMENT_SIZE]; + + element_t() {} + ~element_t() {} + }; + + class idBlock { + public: + element_t elements[blockSize]; + idBlock* next; + element_t* free; + int freeCount; + + idBlock() : next(nullptr), free(nullptr), freeCount(0) {} + }; + + idBlock* blocks; + element_t* free; + int total; + int active; + bool allowAllocs; + bool clearAllocs; + + explicit idBlockAlloc(const bool clear = false) + : blocks(nullptr), free(nullptr), total(0), active(0), + allowAllocs(true), clearAllocs(clear) { + } + + ~idBlockAlloc() { + Shutdown(); + } + + idBlockAlloc(const idBlockAlloc&) = delete; + idBlockAlloc& operator=(const idBlockAlloc&) = delete; + + std::size_t Allocated() const { + return static_cast(total) * sizeof(T); + } + + std::size_t Size() const { + return sizeof(*this) + Allocated(); + } + + void Shutdown() { + while (blocks != nullptr) { + idBlock* const block = blocks; + blocks = block->next; + block->~idBlock(); + _aligned_free(block); + } + blocks = nullptr; + free = nullptr; + active = 0; + total = 0; + } + + void SetFixedBlocks(const int numBlocks) { + const int count = numBlocks > 0 ? numBlocks : 0; + while (total < count * blockSize && AllocNewBlock()) { + } + allowAllocs = false; + } + + void SetAllocAllowed(const bool allowed) { + allowAllocs = allowed; + } + + void SetClear(const bool clear) { + clearAllocs = clear; + } + + void FreeEmptyBlocks() { + for (idBlock* block = blocks; block != nullptr; block = block->next) { + block->free = nullptr; + block->freeCount = 0; + } + + element_t* element = free; + while (element != nullptr) { + element_t* const next = element->next; + const std::uintptr_t address = reinterpret_cast(element); + for (idBlock* block = blocks; block != nullptr; block = block->next) { + const std::uintptr_t begin = + reinterpret_cast(&block->elements[0]); + const std::uintptr_t end = + reinterpret_cast(&block->elements[blockSize]); + if (address >= begin && address < end) { + element->next = block->free; + block->free = element; + ++block->freeCount; + break; + } + } + element = next; + } + + free = nullptr; + idBlock** link = &blocks; + while (*link != nullptr) { + idBlock* const block = *link; + if (block->freeCount == blockSize) { + *link = block->next; + total -= blockSize; + block->~idBlock(); + _aligned_free(block); + continue; + } + element_t* blockFree = block->free; + while (blockFree != nullptr) { + element_t* const next = blockFree->next; + blockFree->next = free; + free = blockFree; + blockFree = next; + } + link = &block->next; + } + } + + T* Alloc() { + if (free == nullptr && (!allowAllocs || !AllocNewBlock())) { + return nullptr; + } + + element_t* const element = free; + free = element->next; + ++active; + if (clearAllocs) { + std::memset(element->buffer, 0, sizeof(element->buffer)); + } + return new (element->buffer) T(); + } + + void Free(T* value) { + if (value == nullptr) { + return; + } + value->~T(); + element_t* const element = reinterpret_cast(value); + element->next = free; + free = element; + --active; + } + + int GetTotalCount() const { return total; } + int GetAllocCount() const { return active; } + int GetFreeCount() const { return total - active; } + +private: + bool AllocNewBlock() { + constexpr std::size_t alignment = alignof(T) > 16 ? alignof(T) : 16; + void* const memory = _aligned_malloc(sizeof(idBlock), alignment); + if (memory == nullptr) { + return false; + } + + idBlock* const block = new (memory) idBlock(); + block->next = blocks; + blocks = block; + for (int index = 0; index < blockSize; ++index) { + block->elements[index].next = free; + free = &block->elements[index]; + } + total += blockSize; + return true; + } +}; + +template +class idBTreeNode; + +template +class idBTree; + +// The recovered idDynamicBlock specializations all have this four-field, +// sixteen-byte Win32 layout. A negative size marks the first block in a base +// allocation; membership in the free tree marks whether a block is free. +template +class idDynamicBlock { +public: + int size; + idDynamicBlock* prev; + idDynamicBlock* next; + idBTreeNode, int>* node; + + T* GetMemory() { + return reinterpret_cast(this + 1); + } + + const T* GetMemory() const { + return reinterpret_cast(this + 1); + } + + int GetSize() const { + return size < 0 ? -size : size; + } + + void SetSize(const int newSize, const bool isBaseBlock) { + size = isBaseBlock ? -newSize : newSize; + } + + bool IsBaseBlock() const { + return size < 0; + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idDynamicBlock) == 16, + "Recovered idDynamicBlock ABI changed"); +#endif diff --git a/source/shared/idlib/bv/bounds.h b/source/shared/idlib/bv/bounds.h index 50902fc..1844f42 100644 --- a/source/shared/idlib/bv/bounds.h +++ b/source/shared/idlib/bv/bounds.h @@ -2,8 +2,7 @@ #include "../math/vector.h" -// Minimal tungsten ABI facade used while the BFG idBounds implementation is -// still the baseline for the larger geometry subsystem. +// Recovered two-vector bounds layout used by the geometry reconstruction. class idBounds { public: idVec3 b[2]; diff --git a/source/shared/idlib/bv/box.h b/source/shared/idlib/bv/box.h new file mode 100644 index 0000000..9abc2cd --- /dev/null +++ b/source/shared/idlib/bv/box.h @@ -0,0 +1,69 @@ +#pragma once + +#include "bounds.h" + +#include + +class idBox { +public: + idVec3 center; + idVec3 extents; + idMat3 axis; + + idBox() : center(0.0f, 0.0f, 0.0f), extents(0.0f, 0.0f, 0.0f), axis(1.0f) {} + + explicit idBox(const idBounds& bounds) : axis(1.0f) { + SetFromBounds(bounds); + } + + idBox(const idBounds& bounds, const idVec3& origin, const idMat3& newAxis) + : axis(newAxis) { + SetFromBounds(bounds); + const idVec3 localCenter = center; + center.Set( + origin.x + newAxis[0].x * localCenter.x + + newAxis[1].x * localCenter.y + newAxis[2].x * localCenter.z, + origin.y + newAxis[0].y * localCenter.x + + newAxis[1].y * localCenter.y + newAxis[2].y * localCenter.z, + origin.z + newAxis[0].z * localCenter.x + + newAxis[1].z * localCenter.y + newAxis[2].z * localCenter.z); + } + + bool ContainsPoint(const idVec3& point) const { + const idVec3 relative = point - center; + return std::fabs(axis[0].Dot(relative)) <= extents.x + && std::fabs(axis[1].Dot(relative)) <= extents.y + && std::fabs(axis[2].Dot(relative)) <= extents.z; + } + + void AxisProjection(const idMat3& projectionAxis, idBounds& bounds) const { + for (int index = 0; index < 3; ++index) { + const float projectedCenter = projectionAxis[index].Dot(center); + const float projectedRadius = + std::fabs(projectionAxis[index].Dot(axis[0])) * extents.x + + std::fabs(projectionAxis[index].Dot(axis[1])) * extents.y + + std::fabs(projectionAxis[index].Dot(axis[2])) * extents.z; + bounds[0][index] = projectedCenter - projectedRadius; + bounds[1][index] = projectedCenter + projectedRadius; + } + } + +private: + void SetFromBounds(const idBounds& bounds) { + center = (bounds[0] + bounds[1]) * 0.5f; + extents = bounds[1] - center; + } +}; + +struct box { + int c0min; + int c0max; + int c1min; + int c1max; + int c2min; + int c2max; + int volume; + int colorcount; +}; + +static_assert(sizeof(idBox) == 60, "Recovered idBox ABI changed"); diff --git a/source/shared/idlib/bv/cylinder.h b/source/shared/idlib/bv/cylinder.h new file mode 100644 index 0000000..8baa9b5 --- /dev/null +++ b/source/shared/idlib/bv/cylinder.h @@ -0,0 +1,14 @@ +#pragma once + +#include "idlib/math/vector.h" + +// Exact member order recovered as IDA local type 13212. +class idCylinder { +public: + idVec3 origin; + float halfHeight; + float radius; +}; + +static_assert(sizeof(idCylinder) == 20, "Recovered idCylinder ABI changed"); + diff --git a/source/shared/idlib/bv/frustum.h b/source/shared/idlib/bv/frustum.h index 19e2c8a..c415916 100644 --- a/source/shared/idlib/bv/frustum.h +++ b/source/shared/idlib/bv/frustum.h @@ -1,11 +1,11 @@ #pragma once -#include "idlib/precompiled.h" +#include "../math/vector.h" class idFrustum { public: idFrustum() - : origin(vec3_origin), axis(mat3_identity), dNear(0.0f), dFar(0.0f), + : origin(0.0f, 0.0f, 0.0f), axis(1.0f), dNear(0.0f), dFar(0.0f), dLeft(0.0f), dUp(0.0f), invFar(0.0f) { } @@ -19,7 +19,7 @@ public: void ToPoints(idVec3 points[8]) const; -private: + // Public in the recovered PDB declaration (ordinal 13098). idVec3 origin; idMat3 axis; float dNear; diff --git a/source/shared/idlib/bv/sphere.cpp b/source/shared/idlib/bv/sphere.cpp new file mode 100644 index 0000000..80d7a50 --- /dev/null +++ b/source/shared/idlib/bv/sphere.cpp @@ -0,0 +1,18 @@ +#include "sphere.h" + +bool idSphere::LineIntersection(const idVec3& start, const idVec3& end) const { + const idVec3 fromCenter = start - origin; + const idVec3 direction = end - start; + const float projection = -fromCenter.Dot(direction); + if (projection <= 0.0f) return fromCenter.LengthSqr() < radius * radius; + + const float directionLengthSqr = direction.LengthSqr(); + if (projection >= directionLengthSqr) { + const idVec3 endFromCenter = end - origin; + return endFromCenter.LengthSqr() < radius * radius; + } + + const idVec3 closest = fromCenter + + direction * (projection / directionLengthSqr); + return closest.LengthSqr() < radius * radius; +} diff --git a/source/shared/idlib/bv/sphere.h b/source/shared/idlib/bv/sphere.h new file mode 100644 index 0000000..e96b851 --- /dev/null +++ b/source/shared/idlib/bv/sphere.h @@ -0,0 +1,31 @@ +#pragma once + +#include "idlib/typesafenumber.h" +#include "idlib/math/vector.h" + +enum SphereUnique_t : int; +typedef idTypesafeNumber sphere_t; + +class idSphere { +public: + idVec3 origin; + float radius; + + idSphere() = default; + idSphere(const idVec3& newOrigin, const float newRadius) + : origin(newOrigin), radius(newRadius) {} + void Clear() { origin.Zero(); radius = -1.0f; } + void Zero() { origin.Zero(); radius = 0.0f; } + bool IsCleared() const { return radius < 0.0f; } + void SetOrigin(const idVec3& newOrigin) { origin = newOrigin; } + void SetRadius(const float newRadius) { radius = newRadius; } + const idVec3& GetOrigin() const { return origin; } + float GetRadius() const { return radius; } + bool ContainsPoint(const idVec3& point) const { + return (point - origin).LengthSqr() < radius * radius; + } + bool LineIntersection(const idVec3& start, const idVec3& end) const; +}; + +static_assert(sizeof(idSphere) == 16, "Recovered idSphere ABI changed"); +static_assert(sizeof(sphere_t) == 4, "Recovered sphere_t ABI changed"); diff --git a/source/shared/idlib/callback.h b/source/shared/idlib/callback.h new file mode 100644 index 0000000..31dd8a1 --- /dev/null +++ b/source/shared/idlib/callback.h @@ -0,0 +1,161 @@ +#pragma once + +#include "math/vector.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +enum gpuCallback_t : int { + GPUTAG_START_FRAME = 0, + GPUTAG_END_FRAME = 1 +}; + +struct _XOKERBINFO; +struct _XOTSKERBINFO; + +// This Xbox service interface is retained as recovered ABI evidence only. It +// has no PC implementation and is not used by the reconstructed idLib target. +struct IXoCallback { + virtual int XoKerbBuildApReq(unsigned int, LARGE_INTEGER*, + unsigned char*, unsigned int, unsigned char*, unsigned int*) = 0; + virtual int XoKerbCrackApRep(unsigned int, LARGE_INTEGER*, unsigned int, + unsigned char*, unsigned int, unsigned char*, unsigned int) = 0; + virtual _XOKERBINFO* XoKerbGetInfo(unsigned int) = 0; + virtual unsigned int XoGetServiceIpa(unsigned int) = 0; + virtual int XoKerbCrackApReq(unsigned char*, unsigned int, unsigned char*, + LARGE_INTEGER*, unsigned int*, _XOTSKERBINFO*) = 0; + virtual int XoKerbBuildApRep(unsigned char*, LARGE_INTEGER, unsigned int, + _XOTSKERBINFO*, unsigned char*, unsigned int*) = 0; +}; + +class idCallback { +public: + virtual ~idCallback() = default; + virtual void Call() {} + virtual idCallback* Clone() const { return new idCallback(*this); } +}; + +class idCallbackStatic : public idCallback { +public: + using function_t = void (*)(); + + explicit idCallbackStatic(function_t function = nullptr) : f(function) {} + ~idCallbackStatic() override = default; + void Call() override { if (f != nullptr) f(); } + idCallback* Clone() const override { return new idCallbackStatic(*this); } + + function_t f; +}; + +template +class idCallbackBindMem : public idCallback { +public: + using function_t = void (type_t::*)(); + + idCallbackBindMem(type_t* object = nullptr, function_t function = nullptr) + : t(object), f(function) {} + ~idCallbackBindMem() override = default; + void Call() override { if (t != nullptr && f != nullptr) (t->*f)(); } + idCallback* Clone() const override { return new idCallbackBindMem(*this); } + + type_t* t; + function_t f; +}; + +template +class idCallbackBindMemArg1 : public idCallback { +public: + using function_t = void (type_t::*)(argument_t); + + idCallbackBindMemArg1(type_t* object = nullptr, + function_t function = nullptr, argument_t argument = argument_t()) + : t(object), f(function), a1(argument) {} + ~idCallbackBindMemArg1() override = default; + void Call() override { if (t != nullptr && f != nullptr) (t->*f)(a1); } + idCallback* Clone() const override { + return new idCallbackBindMemArg1(*this); + } + + type_t* t; + function_t f; + argument_t a1; +}; + +template +idCallbackBindMem MakeCallback(type_t* object, + void (type_t::*function)()) { + return idCallbackBindMem(object, function); +} + +template +idCallbackBindMemArg1 MakeCallback(type_t* object, + void (type_t::*function)(argument_t), argument_t argument) { + return idCallbackBindMemArg1(object, function, argument); +} + +class idAAS2; +class idFiniteStateMachine; +class idTypeInfo; + +class idAAS2Callback { +public: + virtual ~idAAS2Callback() = default; + virtual bool PathValid(const idAAS2*, const idVec3*, const idVec3*) { + return true; + } + virtual int AdditionalTravelTimeForPath( + const idAAS2*, const idVec3*, const idVec3*) { + return 0; + } + virtual bool AreaIsGoal(const idAAS2*, int, const idVec3*) { + return false; + } +}; + +class idFSMCallback { +public: + virtual ~idFSMCallback() = default; + virtual void OnTransition(const idFiniteStateMachine*, const idTypeInfo*, + const idTypeInfo*, const idTypeInfo*, int) {} + virtual void OnRestart(const idFiniteStateMachine*, const idTypeInfo*) {} + virtual void OnError(const idFiniteStateMachine*, const idTypeInfo*, + const idTypeInfo*, int) {} +}; + +struct idFSMLog { + void* list; + int num; + int size; + short granularity; + std::uint8_t memTag; + std::uint8_t listStatic; + int first; + int maxSize; +}; + +class idAIFSMCallback : public idFSMCallback { +public: + ~idAIFSMCallback() override = default; + void OnTransition(const idFiniteStateMachine*, const idTypeInfo*, + const idTypeInfo*, const idTypeInfo*, int) override {} + void OnRestart(const idFiniteStateMachine*, const idTypeInfo*) override {} + void OnError(const idFiniteStateMachine*, const idTypeInfo*, + const idTypeInfo*, int) override {} + + idFSMLog log; +}; + +using D3DCALLBACK = void (*)(unsigned int); +using guiCallBack_t = void (*)(bool); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCallback) == 4, "Recovered idCallback ABI changed"); +static_assert(sizeof(idCallbackStatic) == 8, + "Recovered idCallbackStatic ABI changed"); +static_assert(sizeof(idFSMLog) == 24, "Recovered idFSMLog ABI changed"); +static_assert(sizeof(idAIFSMCallback) == 28, + "Recovered idAIFSMCallback ABI changed"); +#endif diff --git a/source/shared/idlib/containers/array.h b/source/shared/idlib/containers/array.h new file mode 100644 index 0000000..aaf2625 --- /dev/null +++ b/source/shared/idlib/containers/array.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// All 82 recovered instantiations are exactly a fixed C array named ptr. +template +class idArray { +public: + static_assert(count > 0, "idArray requires a positive element count"); + type ptr[count]; + + constexpr int Num() const { return count; } + type* Ptr() { return ptr; } + const type* Ptr() const { return ptr; } + type& operator[](const int index) { + assert(index >= 0 && index < count); return ptr[index]; + } + const type& operator[](const int index) const { + assert(index >= 0 && index < count); return ptr[index]; + } + type* begin() { return ptr; } + const type* begin() const { return ptr; } + type* end() { return ptr + count; } + const type* end() const { return ptr + count; } +}; + +static_assert(sizeof(idArray) == sizeof(int) * 4, + "Recovered idArray ABI changed"); + diff --git a/source/shared/idlib/containers/arraywrapper.h b/source/shared/idlib/containers/arraywrapper.h new file mode 100644 index 0000000..de2b2d4 --- /dev/null +++ b/source/shared/idlib/containers/arraywrapper.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +template +class idArrayWrapper { +public: + type* ptr; + int numElements; + + idArrayWrapper() + : ptr(nullptr) + , numElements(0) { + } + + idArrayWrapper(type* elements, const int count) + : ptr(elements) + , numElements(count) { + } + + int Num() const { + return numElements; + } + + type* Ptr() { return ptr; } + const type* Ptr() const { return ptr; } + + type& operator[](const int index) { + assert(index >= 0 && index < numElements); + return ptr[index]; + } + + const type& operator[](const int index) const { + assert(index >= 0 && index < numElements); + return ptr[index]; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idArrayWrapper) == 8, + "Recovered idArrayWrapper ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/bitflag.h b/source/shared/idlib/containers/bitflag.h new file mode 100644 index 0000000..8ff311d --- /dev/null +++ b/source/shared/idlib/containers/bitflag.h @@ -0,0 +1,24 @@ +#pragma once + +class idBitFlag32 { +public: + int flags; + + idBitFlag32() + : flags(0) { + } + + idBitFlag32(const int initialFlags) + : flags(initialFlags) { + } + + void Clear() { flags = 0; } + void Set(const int mask) { flags |= mask; } + void Clear(const int mask) { flags &= ~mask; } + bool IsSet(const int mask) const { return (flags & mask) != 0; } + + operator int() const { return flags; } +}; + +static_assert(sizeof(idBitFlag32) == 4, "Recovered idBitFlag32 ABI changed"); + diff --git a/source/shared/idlib/containers/btree.cpp b/source/shared/idlib/containers/btree.cpp index 66d29cf..2a60127 100644 --- a/source/shared/idlib/containers/btree.cpp +++ b/source/shared/idlib/containers/btree.cpp @@ -1,3 +1,3 @@ // Tungsten's containers/BTree.cpp contains only the testBinaryTree console -// command. The reusable idBTree implementation is header-only and is supplied -// by the compiled Doom 3 BFG idlib/containers/BTree.h baseline. +// command. The reusable recovered idBTree implementation is header-only in +// btree.h; the engine console registration is intentionally deferred. diff --git a/source/shared/idlib/containers/btree.h b/source/shared/idlib/containers/btree.h new file mode 100644 index 0000000..6b375de --- /dev/null +++ b/source/shared/idlib/containers/btree.h @@ -0,0 +1,292 @@ +#pragma once + +#include "../blockalloc_base.h" + +// Header-only B-tree reconstructed from the recovered idBTree specializations +// and the Add/SplitNode/MergeNodes/Remove/FindSmallestLargerEqual bodies. +template +class idBTreeNode { +public: + Key key; + Object* object; + idBTreeNode* parent; + idBTreeNode* next; + idBTreeNode* prev; + int numChildren; + idBTreeNode* firstChild; + idBTreeNode* lastChild; +}; + +template +class idBTree { +public: + using node_t = idBTreeNode; + + node_t* root; + idBlockAlloc nodeAllocator; + + idBTree() : root(nullptr), nodeAllocator(false) { + static_assert(maxChildren >= 4, "idBTree requires at least four children"); + } + + ~idBTree() { + Shutdown(); + } + + idBTree(const idBTree&) = delete; + idBTree& operator=(const idBTree&) = delete; + + void Init() { + Shutdown(); + root = AllocNode(); + } + + void Shutdown() { + nodeAllocator.Shutdown(); + root = nullptr; + } + + node_t* Add(Object* object, const Key key) { + if (root == nullptr) root = AllocNode(); + if (root == nullptr) return nullptr; + + if (root->numChildren >= maxChildren) { + node_t* const newRoot = AllocNode(); + if (newRoot == nullptr) return nullptr; + newRoot->key = root->key; + newRoot->firstChild = root; + newRoot->lastChild = root; + newRoot->numChildren = 1; + root->parent = newRoot; + SplitNode(root); + root = newRoot; + } + + node_t* const inserted = AllocNode(); + if (inserted == nullptr) return nullptr; + inserted->key = key; + inserted->object = object; + + node_t* branch = root; + while (branch->firstChild != nullptr) { + if (key > branch->key) branch->key = key; + node_t* child = branch->firstChild; + while (child->next != nullptr && key > child->key) child = child->next; + if (child->object != nullptr) { + InsertLeaf(branch, child, inserted); + return inserted; + } + if (child->numChildren >= maxChildren) { + SplitNode(child); + if (key <= child->prev->key) child = child->prev; + } + branch = child; + } + + inserted->parent = root; + root->key = key; + root->firstChild = inserted; + root->lastChild = inserted; + ++root->numChildren; + return inserted; + } + + void Remove(node_t* node) { + if (node == nullptr || node->parent == nullptr) return; + node_t* branch = node->parent; + if (node->prev != nullptr) node->prev->next = node->next; + else branch->firstChild = node->next; + if (node->next != nullptr) node->next->prev = node->prev; + else branch->lastChild = node->prev; + --branch->numChildren; + + for (; branch != root; branch = branch->parent) { + if (branch->numChildren > 1) break; + node_t* left = nullptr; + node_t* right = nullptr; + if (branch->next != nullptr) { + left = branch; + right = branch->next; + } else if (branch->prev != nullptr) { + left = branch->prev; + right = branch; + } else { + continue; + } + branch = MergeNodes(left, right); + if (branch->lastChild != nullptr && branch->key > branch->lastChild->key) { + branch->key = branch->lastChild->key; + } + if (branch->numChildren > maxChildren) { + SplitNode(branch); + break; + } + } + + for (node_t* current = branch; current != nullptr; current = current->parent) { + if (current->lastChild == nullptr) break; + if (current->key > current->lastChild->key) { + current->key = current->lastChild->key; + } + } + nodeAllocator.Free(node); + + if (root != nullptr && root->numChildren == 1 + && root->firstChild != nullptr && root->firstChild->object == nullptr) { + node_t* const oldRoot = root; + root = oldRoot->firstChild; + root->parent = nullptr; + nodeAllocator.Free(oldRoot); + } + } + + node_t* NodeFind(const Key key) const { + node_t* const node = NodeFindSmallestLargerEqual(key); + return node != nullptr && node->key == key ? node : nullptr; + } + + node_t* NodeFindSmallestLargerEqual(const Key key) const { + if (root == nullptr || root->firstChild == nullptr) return nullptr; + node_t* child = root->firstChild; + for (;;) { + while (child->next != nullptr && child->key < key) child = child->next; + if (child->object != nullptr) return child->key >= key ? child : nullptr; + child = child->firstChild; + if (child == nullptr) return nullptr; + } + } + + node_t* NodeFindLargestSmallerEqual(const Key key) const { + node_t* candidate = nullptr; + for (node_t* leaf = FirstLeaf(); leaf != nullptr; leaf = GetNextLeaf(leaf)) { + if (leaf->key > key) break; + candidate = leaf; + } + return candidate; + } + + Object* Find(const Key key) const { + node_t* const node = NodeFind(key); + return node != nullptr ? node->object : nullptr; + } + + Object* FindSmallestLargerEqual(const Key key) const { + node_t* const node = NodeFindSmallestLargerEqual(key); + return node != nullptr ? node->object : nullptr; + } + + Object* FindLargestSmallerEqual(const Key key) const { + node_t* const node = NodeFindLargestSmallerEqual(key); + return node != nullptr ? node->object : nullptr; + } + + node_t* GetRoot() const { return root; } + int GetNodeCount() const { return nodeAllocator.GetAllocCount(); } + + node_t* GetNext(node_t* node) const { + if (node == nullptr) return root; + if (node->firstChild != nullptr) return node->firstChild; + while (node != nullptr && node->next == nullptr) node = node->parent; + return node != nullptr ? node->next : nullptr; + } + + node_t* GetNextLeaf(node_t* node) const { + if (node == nullptr) return FirstLeaf(); + while (node != nullptr && node->next == nullptr) node = node->parent; + if (node == nullptr) return nullptr; + node = node->next; + while (node->firstChild != nullptr) node = node->firstChild; + return node; + } + +private: + node_t* AllocNode() { + node_t* const node = nodeAllocator.Alloc(); + if (node == nullptr) return nullptr; + node->key = Key(); + node->object = nullptr; + node->parent = nullptr; + node->next = nullptr; + node->prev = nullptr; + node->numChildren = 0; + node->firstChild = nullptr; + node->lastChild = nullptr; + return node; + } + + node_t* FirstLeaf() const { + node_t* node = root; + if (node == nullptr || node->firstChild == nullptr) return nullptr; + node = node->firstChild; + while (node->firstChild != nullptr) node = node->firstChild; + return node; + } + + static void InsertLeaf(node_t* parent, node_t* position, node_t* inserted) { + if (inserted->key > position->key) { + inserted->prev = position; + inserted->next = position->next; + position->next = inserted; + if (inserted->next != nullptr) inserted->next->prev = inserted; + else parent->lastChild = inserted; + } else { + inserted->prev = position->prev; + inserted->next = position; + position->prev = inserted; + if (inserted->prev != nullptr) inserted->prev->next = inserted; + else parent->firstChild = inserted; + } + inserted->parent = parent; + ++parent->numChildren; + } + + void SplitNode(node_t* node) { + node_t* const split = AllocNode(); + if (split == nullptr) return; + split->parent = node->parent; + const int movedCount = node->numChildren / 2; + node_t* splitLast = node->firstChild; + for (int index = 1; index < movedCount; ++index) splitLast = splitLast->next; + for (node_t* child = node->firstChild;; child = child->next) { + child->parent = split; + if (child == splitLast) break; + } + split->key = splitLast->key; + split->numChildren = movedCount; + split->firstChild = node->firstChild; + split->lastChild = splitLast; + node->numChildren -= movedCount; + node->firstChild = splitLast->next; + node->firstChild->prev = nullptr; + splitLast->next = nullptr; + split->prev = node->prev; + split->next = node; + if (node->prev != nullptr) node->prev->next = split; + else node->parent->firstChild = split; + node->prev = split; + ++node->parent->numChildren; + } + + node_t* MergeNodes(node_t* left, node_t* right) { + for (node_t* child = left->firstChild; child != nullptr; child = child->next) { + child->parent = right; + } + left->lastChild->next = right->firstChild; + right->firstChild->prev = left->lastChild; + right->firstChild = left->firstChild; + right->numChildren += left->numChildren; + if (left->prev != nullptr) left->prev->next = right; + else left->parent->firstChild = right; + right->prev = left->prev; + --right->parent->numChildren; + nodeAllocator.Free(left); + return right; + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idBTreeNode) == 32, + "Recovered idBTreeNode ABI changed"); +static_assert(sizeof(idBTree) == 24, + "Recovered idBTree ABI changed"); +#endif diff --git a/source/shared/idlib/containers/grid.h b/source/shared/idlib/containers/grid.h new file mode 100644 index 0000000..99b1f6a --- /dev/null +++ b/source/shared/idlib/containers/grid.h @@ -0,0 +1,12 @@ +#pragma once + +// Recovered Windows/Xbox large-grid region identifier. The Windows SDK +// already owns the public LGRPID spelling on PC; its unsigned-long storage is +// the same four-byte ABI as Tungsten's recovered unsigned-int typedef. +#if defined(_WIN32) +#include +#else +typedef unsigned int LGRPID; +#endif + +static_assert(sizeof(LGRPID) == 4, "Recovered LGRPID ABI changed"); diff --git a/source/shared/idlib/containers/hashindex.cpp b/source/shared/idlib/containers/hashindex.cpp new file mode 100644 index 0000000..7a61331 --- /dev/null +++ b/source/shared/idlib/containers/hashindex.cpp @@ -0,0 +1,92 @@ +#include "hashindex.h" + +#include + +void idHashIndex::InternalInit(const int initialHashSize, + const int initialIndexSize) { + hash = nullptr; + indexChain = nullptr; + hashSize = (std::max)(initialHashSize, 1); + indexSize = (std::max)(initialIndexSize, 1); + granularity = 1024; + hashMask = hashSize - 1; + lookupMask = 0; +} + +void idHashIndex::Free() { + delete[] hash; + delete[] indexChain; + hash = nullptr; + indexChain = nullptr; + lookupMask = 0; +} + +void idHashIndex::Allocate(int newHashSize, int newIndexSize) { + delete[] hash; + delete[] indexChain; + hash = nullptr; + indexChain = nullptr; + newHashSize = NextPowerOfTwo((std::max)(newHashSize, 1)); + newIndexSize = (std::max)(newIndexSize, 1); + hashSize = newHashSize; + indexSize = newIndexSize; + hashMask = hashSize - 1; + lookupMask = 0; + hash = new (std::nothrow) int[hashSize]; + indexChain = new (std::nothrow) int[indexSize]; + if (hash == nullptr || indexChain == nullptr) { + delete[] hash; + delete[] indexChain; + hash = nullptr; + indexChain = nullptr; + return; + } + std::fill(hash, hash + hashSize, -1); + std::fill(indexChain, indexChain + indexSize, -1); + lookupMask = -1; +} + +void idHashIndex::ResizeIndex(const int requestedIndexSize) { + if (requestedIndexSize <= indexSize) return; + const int step = granularity > 0 ? granularity : 1024; + const int newIndexSize = ((requestedIndexSize + step - 1) / step) * step; + if (indexChain == nullptr) { + indexSize = newIndexSize; + return; + } + int* const oldChain = indexChain; + indexChain = new (std::nothrow) int[newIndexSize]; + if (indexChain == nullptr) { + indexChain = oldChain; + return; + } + std::copy(oldChain, oldChain + indexSize, indexChain); + std::fill(indexChain + indexSize, indexChain + newIndexSize, -1); + delete[] oldChain; + indexSize = newIndexSize; +} + +int idHashIndex::GetSpread() const { + if (hash == nullptr || hashSize <= 0) return 100; + int* counts = new (std::nothrow) int[hashSize]; + if (counts == nullptr) return 100; + int total = 0; + for (int bucket = 0; bucket < hashSize; ++bucket) { + counts[bucket] = 0; + for (int index = hash[bucket]; index >= 0; + index = indexChain[index]) ++counts[bucket]; + total += counts[bucket]; + } + if (total <= 1) { + delete[] counts; + return 100; + } + const int average = total / hashSize; + int error = 0; + for (int bucket = 0; bucket < hashSize; ++bucket) { + const int difference = std::abs(counts[bucket] - average); + if (difference > 1) error += difference - 1; + } + delete[] counts; + return 100 - 100 * error / total; +} diff --git a/source/shared/idlib/containers/hashindex.h b/source/shared/idlib/containers/hashindex.h new file mode 100644 index 0000000..75582e9 --- /dev/null +++ b/source/shared/idlib/containers/hashindex.h @@ -0,0 +1,150 @@ +#pragma once + +#include "idlib/sys/sys_alloc.h" +#include "idlib/text/str.h" + +#include +#include +#include +#include + +class idHashIndex { +public: + int* hash; + int* indexChain; + int hashSize; + int indexSize; + int granularity; + int hashMask; + int lookupMask; + memTag_t memTag; + + explicit idHashIndex(const int initialHashSize = 1024, + const int initialIndexSize = 1024, + const memTag_t tag = TAG_IDLIST) + : hash(nullptr), indexChain(nullptr), hashSize(0), indexSize(0), + granularity(1024), hashMask(0), lookupMask(0), memTag(tag) { + InternalInit(initialHashSize, initialIndexSize); + } + + idHashIndex(const idHashIndex& other) : idHashIndex(0, 0, other.memTag) { + *this = other; + } + ~idHashIndex() { Free(); } + + idHashIndex& operator=(const idHashIndex& other) { + if (this == &other) return *this; + Free(); + InternalInit(other.hashSize, other.indexSize); + granularity = other.granularity; + memTag = other.memTag; + if (other.hash != nullptr) Allocate(other.hashSize, other.indexSize); + if (hash != nullptr) + std::copy(other.hash, other.hash + hashSize, hash); + if (indexChain != nullptr && other.indexChain != nullptr) + std::copy(other.indexChain, other.indexChain + indexSize, indexChain); + lookupMask = other.lookupMask; + return *this; + } + + void InternalInit(int initialHashSize, int initialIndexSize); + void Allocate(int newHashSize, int newIndexSize); + void Free(); + + void Clear() { + if (hash != nullptr) std::fill(hash, hash + hashSize, -1); + if (indexChain != nullptr) std::fill(indexChain, indexChain + indexSize, -1); + } + + void Add(const int key, const int index) { + if (hash == nullptr) Allocate(hashSize, indexSize); + EnsureIndex(index); + if (hash == nullptr || index < 0 || index >= indexSize) return; + const int bucket = key & hashMask; + indexChain[index] = hash[bucket]; + hash[bucket] = index; + } + + void Remove(const int key, const int index) { + if (hash == nullptr || index < 0 || index >= indexSize) return; + const int bucket = key & hashMask; + if (hash[bucket] == index) hash[bucket] = indexChain[index]; + else { + int current = hash[bucket]; + while (current >= 0 && indexChain[current] != index) + current = indexChain[current]; + if (current >= 0) indexChain[current] = indexChain[index]; + } + indexChain[index] = -1; + } + + int First(const int key) const { + return hash == nullptr ? -1 : hash[key & hashMask & lookupMask]; + } + int Next(const int index) const { + return indexChain == nullptr || index < 0 || index >= indexSize + ? -1 : indexChain[index & lookupMask]; + } + + int GenerateKeyForString(const char* text, const bool caseSensitive = true) const { + std::uint32_t key = 5381u; + if (text != nullptr) { + while (*text != '\0') { + unsigned char value = static_cast(*text++); + if (!caseSensitive) value = static_cast(std::tolower(value)); + key = ((key << 5) + key) ^ value; + } + } + return static_cast(key & static_cast(hashMask)); + } + + int GetSpread() const; + + std::size_t Allocated() const { + return static_cast(hashSize + indexSize) * sizeof(int); + } + + void ResizeIndex(int newIndexSize); + +private: + static int NextPowerOfTwo(int value) { + int result = 1; + while (result < value) result <<= 1; + return result; + } + void EnsureIndex(const int index) { + if (hash == nullptr) Allocate(hashSize, indexSize); + if (index < indexSize) return; + const int step = granularity > 0 ? granularity : 1024; + const int newSize = ((index + 1 + step - 1) / step) * step; + int* replacement = new (std::nothrow) int[newSize]; + if (replacement == nullptr) return; + std::fill(replacement, replacement + newSize, -1); + if (indexChain != nullptr) { + std::copy(indexChain, indexChain + indexSize, replacement); + delete[] indexChain; + } + indexChain = replacement; + indexSize = newSize; + } +}; + +template +class idHashNodeT { +public: + keyType key; + valueType value; + idHashNodeT* next; +}; + +template +class idHashNodeT { +public: + idStr key; + valueType value; + idHashNodeT* next; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idHashIndex) == 32, "Recovered idHashIndex ABI changed"); +#endif diff --git a/source/shared/idlib/containers/hashtable.h b/source/shared/idlib/containers/hashtable.h new file mode 100644 index 0000000..eb9e4d5 --- /dev/null +++ b/source/shared/idlib/containers/hashtable.h @@ -0,0 +1,133 @@ +#pragma once + +#include "idlib/containers/hashindex.h" + +#include +#include +#include +#include +#include + +template +struct idHashTableKeyOps { + static std::uint32_t Hash(const keyType& key) { + return static_cast(std::hash()(key)); + } + static bool Equal(const keyType& stored, const keyType& key) { + return stored == key; + } +}; + +template<> +struct idHashTableKeyOps { + static std::uint32_t Hash(const char* key) { + std::uint32_t hash = 2166136261u; + if (key != nullptr) while (*key != '\0') { + hash = (hash ^ static_cast(*key++)) * 16777619u; + } + return hash; + } + static bool Equal(const idStr& stored, const char* key) { + return idStr::Cmp(stored.c_str(), key) == 0; + } +}; + +template +class idHashTableT { +public: + using nodeType = idHashNodeT; + + nodeType** heads; + int tableSize; + int numEntries; + int tableSizeMask; + + explicit idHashTableT(const int requestedSize = 256) + : heads(nullptr), tableSize(NextPowerOfTwo(requestedSize)), + numEntries(0), tableSizeMask(tableSize - 1) { + heads = new (std::nothrow) nodeType*[tableSize]; + if (heads != nullptr) std::fill(heads, heads + tableSize, nullptr); + } + ~idHashTableT() { Clear(); delete[] heads; } + + idHashTableT(const idHashTableT&) = delete; + idHashTableT& operator=(const idHashTableT&) = delete; + + int Num() const { return numEntries; } + void Clear() { + if (heads == nullptr) return; + for (int bucket = 0; bucket < tableSize; ++bucket) { + nodeType* node = heads[bucket]; + while (node != nullptr) { + nodeType* next = node->next; + delete node; + node = next; + } + heads[bucket] = nullptr; + } + numEntries = 0; + } + + bool Get(const keyType& key, valueType** value) { + nodeType* node = FindNode(key); + if (value != nullptr) *value = node == nullptr ? nullptr : &node->value; + return node != nullptr; + } + bool Get(const keyType& key, const valueType** value) const { + const nodeType* node = FindNode(key); + if (value != nullptr) *value = node == nullptr ? nullptr : &node->value; + return node != nullptr; + } + + valueType& Set(const keyType& key, const valueType& value) { + nodeType* node = FindNode(key); + if (node == nullptr) { + const int bucket = Bucket(key); + node = new nodeType(); + node->key = key; + node->next = heads[bucket]; + heads[bucket] = node; + ++numEntries; + } + node->value = value; + return node->value; + } + + valueType* GetIndex(int index) { + if (index < 0 || index >= numEntries) return nullptr; + for (int bucket = 0; bucket < tableSize; ++bucket) + for (nodeType* node = heads[bucket]; node != nullptr; node = node->next) + if (index-- == 0) return &node->value; + return nullptr; + } + +private: + static int NextPowerOfTwo(int value) { + int result = 1; + while (result < (value > 0 ? value : 1)) result <<= 1; + return result; + } + int Bucket(const keyType& key) const { + return static_cast(idHashTableKeyOps::Hash(key)) & tableSizeMask; + } + nodeType* FindNode(const keyType& key) { + if (heads == nullptr) return nullptr; + for (nodeType* node = heads[Bucket(key)]; node != nullptr; node = node->next) + if (idHashTableKeyOps::Equal(node->key, key)) return node; + return nullptr; + } + const nodeType* FindNode(const keyType& key) const { + return const_cast(this)->FindNode(key); + } +}; + +template +class idHashTable : public idHashTableT { +public: + using idHashTableT::idHashTableT; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idHashTableT) == 16, + "Recovered idHashTableT ABI changed"); +#endif diff --git a/source/shared/idlib/containers/hierarchy.h b/source/shared/idlib/containers/hierarchy.h new file mode 100644 index 0000000..5bcb528 --- /dev/null +++ b/source/shared/idlib/containers/hierarchy.h @@ -0,0 +1,51 @@ +#pragma once + +template +class idHierarchy { +public: + idHierarchy* parent; + idHierarchy* sibling; + idHierarchy* child; + type* owner; + + explicit idHierarchy(type* ownerObject = nullptr) + : parent(nullptr), sibling(nullptr), child(nullptr), owner(ownerObject) {} + ~idHierarchy() { RemoveFromHierarchy(); } + + void SetOwner(type* ownerObject) { owner = ownerObject; } + type* Owner() const { return owner; } + idHierarchy* Parent() const { return parent; } + idHierarchy* Child() const { return child; } + idHierarchy* Sibling() const { return sibling; } + + idHierarchy* GetPriorSiblingNode() const { + if (parent == nullptr || parent->child == this) return nullptr; + idHierarchy* node = parent->child; + while (node != nullptr && node->sibling != this) node = node->sibling; + return node; + } + + void ParentTo(idHierarchy& newParent) { + RemoveFromHierarchy(); + parent = &newParent; + sibling = newParent.child; + newParent.child = this; + } + + void RemoveFromHierarchy() { + if (parent != nullptr) { + if (parent->child == this) parent->child = sibling; + else { + idHierarchy* prior = GetPriorSiblingNode(); + if (prior != nullptr) prior->sibling = sibling; + } + } + parent = nullptr; + sibling = nullptr; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idHierarchy) == 16, "Recovered idHierarchy ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/linklist.h b/source/shared/idlib/containers/linklist.h new file mode 100644 index 0000000..74c3678 --- /dev/null +++ b/source/shared/idlib/containers/linklist.h @@ -0,0 +1,59 @@ +#pragma once + +template +class idLinkList { +public: + idLinkList* head; + idLinkList* next; + idLinkList* prev; + type* owner; + + explicit idLinkList(type* ownerObject = nullptr) + : head(this), next(this), prev(this), owner(ownerObject) {} + ~idLinkList() { Remove(); } + + void SetOwner(type* ownerObject) { owner = ownerObject; } + type* Owner() const { return owner; } + bool InList() const { return head != this; } + bool IsListEmpty() const { return head->next == head; } + + void AddToFront(idLinkList& node) { + node.Remove(); + node.head = head; + node.next = head->next; + node.prev = head; + head->next->prev = &node; + head->next = &node; + } + + void AddToEnd(idLinkList& node) { + node.Remove(); + node.head = head; + node.next = head; + node.prev = head->prev; + head->prev->next = &node; + head->prev = &node; + } + + void Remove() { + if (head != this) { + prev->next = next; + next->prev = prev; + } + head = this; + next = this; + prev = this; + } + + void Clear() { + while (next != this) next->Remove(); + } + + type* Next() const { return next == head ? nullptr : next->owner; } + type* Prev() const { return prev == head ? nullptr : prev->owner; } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idLinkList) == 16, "Recovered idLinkList ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/list.cpp b/source/shared/idlib/containers/list.cpp index 794eecd..b222626 100644 --- a/source/shared/idlib/containers/list.cpp +++ b/source/shared/idlib/containers/list.cpp @@ -1,4 +1,4 @@ // Tungsten's containers/List.cpp contains diagnostic console commands for the -// header-only idList and idArray implementations. The PC runtime templates are -// supplied by the compiled Doom 3 BFG idlib/containers/List.h baseline; the -// recovery suite exercises container behavior without registering engine CVars. +// header-only idList and idArray implementations. The recovered templates live +// in this tree; the standalone recovery suite exercises their behavior without +// registering engine CVars. diff --git a/source/shared/idlib/containers/list.h b/source/shared/idlib/containers/list.h new file mode 100644 index 0000000..0d2e1e3 --- /dev/null +++ b/source/shared/idlib/containers/list.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include + +// Reconstructed from the common layout shared by all 2,438 recovered PDB +// instantiations and the template bodies in the Hex-Rays list.h dump. +template +class idList { +public: + type* list; + int num; + int size; + std::int16_t granularity; + std::uint8_t memTag; + std::uint8_t listStatic; + + explicit idList(const int initialGranularity = 16) + : list(nullptr), num(0), size(0), + granularity(static_cast(initialGranularity)), + memTag(static_cast(memoryTag)), listStatic(0) {} + + idList(const idList& other) : idList(other.granularity) { *this = other; } + + ~idList() { + if (listStatic == 0) delete[] list; + } + + idList& operator=(const idList& other) { + if (this != &other && SetNum(other.num)) { + for (int index = 0; index < num; ++index) list[index] = other.list[index]; + } + return *this; + } + + int Num() const { return num; } + int NumAllocated() const { return size; } + int MemoryUsed() const { return size * static_cast(sizeof(type)); } + bool IsEmpty() const { return num == 0; } + type* Ptr() { return list; } + const type* Ptr() const { return list; } + + type& operator[](const int index) { return list[index]; } + const type& operator[](const int index) const { return list[index]; } + + void SetGranularity(const int newGranularity) { + granularity = static_cast(newGranularity > 0 ? newGranularity : 16); + } + + bool Resize(const int newSize) { + if (newSize < 0 || (listStatic != 0 && newSize > size)) return false; + if (newSize == size) return true; + type* replacement = new (std::nothrow) type[newSize]; + if (replacement == nullptr && newSize != 0) return false; + const int copyCount = (std::min)(num, newSize); + for (int index = 0; index < copyCount; ++index) + replacement[index] = std::move(list[index]); + if (listStatic == 0) delete[] list; + list = replacement; + size = newSize; + num = copyCount; + listStatic = 0; + return true; + } + + bool PreAllocate(const int requestedSize) { + if (requestedSize <= size) return true; + return Resize(RoundedCapacity(requestedSize)); + } + + bool SetNum(const int newNum) { + if (newNum < 0) return false; + if (newNum > size && !PreAllocate(newNum)) return false; + num = newNum; + return true; + } + + type* Alloc() { + if (!SetNum(num + 1)) return nullptr; + return &list[num - 1]; + } + + int Append(const type& object) { + if (!SetNum(num + 1)) return -1; + list[num - 1] = object; + return num - 1; + } + + int Append(type&& object) { + if (!SetNum(num + 1)) return -1; + list[num - 1] = std::move(object); + return num - 1; + } + + int AddUnique(const type& object) { + const int found = FindIndex(object); + return found >= 0 ? found : Append(object); + } + + int Insert(const type& object, int index = 0) { + if (index < 0) index = 0; + if (index > num) index = num; + if (!SetNum(num + 1)) return -1; + for (int move = num - 1; move > index; --move) + list[move] = std::move(list[move - 1]); + list[index] = object; + return index; + } + + int FindIndex(const type& object) const { + for (int index = 0; index < num; ++index) + if (list[index] == object) return index; + return -1; + } + + type* Find(const type& object) { + const int index = FindIndex(object); + return index >= 0 ? &list[index] : nullptr; + } + + const type* Find(const type& object) const { + const int index = FindIndex(object); + return index >= 0 ? &list[index] : nullptr; + } + + bool RemoveIndex(const int index) { + if (index < 0 || index >= num) return false; + for (int move = index; move + 1 < num; ++move) + list[move] = std::move(list[move + 1]); + --num; + return true; + } + + bool RemoveIndexFast(const int index) { + if (index < 0 || index >= num) return false; + if (index != num - 1) list[index] = std::move(list[num - 1]); + --num; + return true; + } + + bool Remove(const type& object) { return RemoveIndex(FindIndex(object)); } + + void Clear() { num = 0; } + + void ClearFree() { + num = 0; + if (listStatic == 0) { + delete[] list; + list = nullptr; + size = 0; + } + } + + void Swap(idList& other) { + using std::swap; + swap(list, other.list); swap(num, other.num); swap(size, other.size); + swap(granularity, other.granularity); swap(memTag, other.memTag); + swap(listStatic, other.listStatic); + } + +protected: + void SetStaticBuffer(type* buffer, const int capacity) { + if (listStatic == 0) delete[] list; + list = buffer; + num = 0; + size = capacity; + granularity = 0; + listStatic = 1; + } + +private: + int RoundedCapacity(const int requestedSize) const { + const int step = granularity > 0 ? granularity : 16; + return ((requestedSize + step - 1) / step) * step; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idList) == 16, "Recovered idList ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/pair.h b/source/shared/idlib/containers/pair.h new file mode 100644 index 0000000..83d8178 --- /dev/null +++ b/source/shared/idlib/containers/pair.h @@ -0,0 +1,18 @@ +#pragma once + +// Generic form inferred from the ten concrete layouts in the recovered PDB. +// Keeping the fields public also matches all recovered direct member access. +template +class idPair { +public: + firstType first; + secondType second; + + idPair() = default; + + idPair(const firstType& firstValue, const secondType& secondValue) + : first(firstValue) + , second(secondValue) { + } +}; + diff --git a/source/shared/idlib/containers/planeset.h b/source/shared/idlib/containers/planeset.h new file mode 100644 index 0000000..04b7e88 --- /dev/null +++ b/source/shared/idlib/containers/planeset.h @@ -0,0 +1,13 @@ +#pragma once + +// Recovered IDA enum ordinal 943. The type belongs to this header in the +// Tungsten PDB even though later idTech trees group it with plane math. +enum planeSide_t : int { + PLANESIDE_FRONT = 0, + PLANESIDE_BACK = 1, + PLANESIDE_ON = 2, + PLANESIDE_CROSS = 3 +}; + +static_assert(sizeof(planeSide_t) == 4, "Recovered planeSide_t ABI changed"); + diff --git a/source/shared/idlib/containers/queue.h b/source/shared/idlib/containers/queue.h new file mode 100644 index 0000000..e9158ca --- /dev/null +++ b/source/shared/idlib/containers/queue.h @@ -0,0 +1,42 @@ +#pragma once + +template +class idQueueNode { +public: + type* next; + idQueueNode() : next(nullptr) {} +}; + +template +class idQueue { +public: + type* first; + type* last; + + idQueue() : first(nullptr), last(nullptr) {} + bool IsEmpty() const { return first == nullptr; } + type* First() const { return first; } + void Add(type* node) { + node->next = nullptr; + if (last != nullptr) last->next = node; + else first = node; + last = node; + } + type* RemoveFirst() { + type* node = first; + if (node != nullptr) { + first = node->next; + node->next = nullptr; + if (first == nullptr) last = nullptr; + } + return node; + } + void Clear() { first = nullptr; last = nullptr; } +}; + +#if defined(_WIN32) && !defined(_WIN64) +struct idRecoveredQueueNode : idQueueNode {}; +static_assert(sizeof(idQueue) == 8, + "Recovered idQueue ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/recoveredlist.h b/source/shared/idlib/containers/recoveredlist.h index 33fca1f..5d841b3 100644 --- a/source/shared/idlib/containers/recoveredlist.h +++ b/source/shared/idlib/containers/recoveredlist.h @@ -1,23 +1,24 @@ #pragma once -#include #include #include -#include #include +#include +// Small ABI-compatible list used by recovered subsystems whose PDB type was +// emitted as an anonymous idList specialization. Unlike the earlier POD-only +// facade, this version preserves constructors and destructors for idStr-backed +// XML values as required by the recovered call sites. template class idRecoveredList { public: - explicit idRecoveredList(const int initialGranularity = 16) + explicit idRecoveredList(const int initialGranularity = 16, + const std::uint8_t tag = 0) : list(nullptr), num(0), size(0), granularity(static_cast(initialGranularity)), - memTag(0), listStatic(0) { - } + memTag(tag), listStatic(0) {} - ~idRecoveredList() { - Clear(true); - } + ~idRecoveredList() { Clear(true); } idRecoveredList(const idRecoveredList&) = delete; idRecoveredList& operator=(const idRecoveredList&) = delete; @@ -29,9 +30,9 @@ public: T* const replacement = static_cast( std::malloc(sizeof(T) * static_cast(newSize))); if (replacement == nullptr) return false; - if (list != nullptr && num > 0) { - std::memcpy(replacement, list, - sizeof(T) * static_cast(num)); + for (int index = 0; index < num; ++index) { + new (&replacement[index]) T(std::move(list[index])); + list[index].~T(); } std::free(list); list = replacement; @@ -41,19 +42,20 @@ public: T* Alloc() { if (!Reserve(num + 1)) return nullptr; - T* const result = list + num++; - std::memset(result, 0, sizeof(T)); + T* const result = &list[num++]; + new (result) T(); return result; } - bool Append(const T& value) { - T* const destination = Alloc(); - if (destination == nullptr) return false; - *destination = value; - return true; + T* Append(const T& value) { + if (!Reserve(num + 1)) return nullptr; + T* const result = &list[num++]; + new (result) T(value); + return result; } void Clear(const bool freeMemory = false) { + for (int index = 0; index < num; ++index) list[index].~T(); num = 0; if (freeMemory) { std::free(list); @@ -82,4 +84,3 @@ private: static_assert(sizeof(idRecoveredList) == 16, "Recovered list ABI changed"); #endif - diff --git a/source/shared/idlib/containers/sort.h b/source/shared/idlib/containers/sort.h new file mode 100644 index 0000000..b4ddce6 --- /dev/null +++ b/source/shared/idlib/containers/sort.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +template +inline void SwapValues(type& left, type& right) { + using std::swap; + swap(left, right); +} + +template +class idSort { +public: + virtual ~idSort() = default; + virtual void Sort(type* base, unsigned int num) = 0; +}; + +template +class idSort_QuickDefault { +public: + int Compare(const type& left, const type& right) const { + return left < right ? -1 : (right < left ? 1 : 0); + } +}; + +template> +class idSort_Quick : public idSort, public comparer { +public: + void Sort(type* base, const unsigned int num) override { + if (base == nullptr || num < 2) return; + QuickSort(base, 0, static_cast(num) - 1); + } + +private: + void QuickSort(type* base, int left, int right) { + int i = left; + int j = right; + const type pivot = base[left + (right - left) / 2]; + while (i <= j) { + while (this->Compare(base[i], pivot) < 0) ++i; + while (this->Compare(base[j], pivot) > 0) --j; + if (i <= j) { + if (i != j) SwapValues(base[i], base[j]); + ++i; + --j; + } + } + if (left < j) QuickSort(base, left, j); + if (i < right) QuickSort(base, i, right); + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idSort_Quick) == 4, "Recovered idSort ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/stack.h b/source/shared/idlib/containers/stack.h new file mode 100644 index 0000000..5ecd88d --- /dev/null +++ b/source/shared/idlib/containers/stack.h @@ -0,0 +1,41 @@ +#pragma once + +template +class idStackNode { +public: + type* next; + idStackNode() : next(nullptr) {} +}; + +template +class idStack { +public: + type* first; + type* last; + + idStack() : first(nullptr), last(nullptr) {} + bool IsEmpty() const { return first == nullptr; } + type* First() const { return first; } + void Push(type* node) { + node->next = first; + first = node; + if (last == nullptr) last = node; + } + type* Pop() { + type* node = first; + if (node != nullptr) { + first = node->next; + node->next = nullptr; + if (first == nullptr) last = nullptr; + } + return node; + } + void Clear() { first = nullptr; last = nullptr; } +}; + +#if defined(_WIN32) && !defined(_WIN64) +struct idRecoveredStackNode : idStackNode {}; +static_assert(sizeof(idStack) == 8, + "Recovered idStack ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/staticlist.h b/source/shared/idlib/containers/staticlist.h new file mode 100644 index 0000000..3549810 --- /dev/null +++ b/source/shared/idlib/containers/staticlist.h @@ -0,0 +1,25 @@ +#pragma once + +#include "idlib/containers/list.h" + +template +class idStaticList : public idList { +public: + type staticList[capacity]; + + idStaticList() { this->SetStaticBuffer(staticList, capacity); } + idStaticList(const idStaticList& other) : idStaticList() { *this = other; } + idStaticList& operator=(const idStaticList& other) { + this->SetNum(other.Num()); + for (int index = 0; index < this->num; ++index) + staticList[index] = other.staticList[index]; + return *this; + } + int Max() const { return capacity; } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idStaticList) == 32, + "Recovered idStaticList ABI changed"); +#endif + diff --git a/source/shared/idlib/containers/strlist.h b/source/shared/idlib/containers/strlist.h new file mode 100644 index 0000000..6e756f2 --- /dev/null +++ b/source/shared/idlib/containers/strlist.h @@ -0,0 +1,7 @@ +#pragma once + +#include "idlib/containers/list.h" +#include "idlib/text/str.h" + +typedef idList idStrList; + diff --git a/source/shared/idlib/containers/vectorset.h b/source/shared/idlib/containers/vectorset.h new file mode 100644 index 0000000..21124eb --- /dev/null +++ b/source/shared/idlib/containers/vectorset.h @@ -0,0 +1,216 @@ +#pragma once + +#include "hashindex.h" +#include "list.h" +#include "../math/vector.h" + +#include +#include +#include + +template +class idVectorSubset { +public: + idHashIndex hash; + T mins; + T maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; + + idVectorSubset() : hash(0, 0, TAG_HASHINDEX), boxHashSize(16) { + hash.Allocate(IntegerPower(boxHashSize, dimension), 128); + for (int index = 0; index < dimension; ++index) { + boxInvSize[index] = 0.0f; + boxHalfSize[index] = 0.0f; + } + } + + idVectorSubset(const T& boundsMins, const T& boundsMaxs, + const int hashSize, const int initialSize) + : hash(0, 0, TAG_HASHINDEX), boxHashSize(16) { + Init(boundsMins, boundsMaxs, hashSize, initialSize); + } + + std::size_t Allocated() const { return hash.Allocated(); } + std::size_t Size() const { return sizeof(*this) + Allocated(); } + + void Init(const T& boundsMins, const T& boundsMaxs, + const int newBoxHashSize, const int initialSize) { + boxHashSize = newBoxHashSize > 0 ? newBoxHashSize : 1; + hash.Allocate(IntegerPower(boxHashSize, dimension), + initialSize > 0 ? initialSize : 1); + mins = boundsMins; + maxs = boundsMaxs; + for (int index = 0; index < dimension; ++index) { + const float boxSize = (maxs[index] - mins[index]) + / static_cast(boxHashSize); + boxInvSize[index] = boxSize != 0.0f ? 1.0f / boxSize : 0.0f; + boxHalfSize[index] = boxSize * 0.5f; + } + } + + void Clear() { hash.Clear(); } + + int FindVector(const T* vectorList, const int vectorNum, + const float epsilon) { + const T& vector = vectorList[vectorNum]; + int partialHashKey[dimension]; + for (int component = 0; component < dimension; ++component) { + assert(epsilon <= boxHalfSize[component]); + partialHashKey[component] = static_cast( + (vector[component] - mins[component] - boxHalfSize[component]) + * boxInvSize[component]); + } + + for (int corner = 0; corner < (1 << dimension); ++corner) { + int hashKey = 0; + for (int component = 0; component < dimension; ++component) { + hashKey *= boxHashSize; + hashKey += partialHashKey[component] + ((corner >> component) & 1); + } + for (int candidate = hash.First(hashKey); candidate >= 0; + candidate = hash.Next(candidate)) { + int component = 0; + for (; component < dimension; ++component) { + if (std::fabs(vectorList[candidate][component] + - vector[component]) > epsilon) break; + } + if (component == dimension) return candidate; + } + } + + hash.Add(HashKey(vector), vectorNum); + return vectorNum; + } + +private: + static int IntegerPower(const int base, const int exponent) { + int result = 1; + for (int index = 0; index < exponent; ++index) result *= base; + return result; + } + + int HashKey(const T& vector) const { + int key = 0; + for (int component = 0; component < dimension; ++component) { + key *= boxHashSize; + key += static_cast((vector[component] - mins[component]) + * boxInvSize[component]); + } + return key; + } +}; + +template +class idVectorSet : public idList { +public: + idHashIndex hash; + T mins; + T maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; + + idVectorSet() : idList(), hash(0, 0, TAG_HASHINDEX), boxHashSize(16) { + hash.Allocate(IntegerPower(boxHashSize, dimension), 128); + for (int index = 0; index < dimension; ++index) { + boxInvSize[index] = 0.0f; + boxHalfSize[index] = 0.0f; + } + } + + idVectorSet(const T& boundsMins, const T& boundsMaxs, + const int hashSize, const int initialSize) + : idVectorSet() { + Init(boundsMins, boundsMaxs, hashSize, initialSize); + } + + std::size_t Allocated() const { + return static_cast(this->MemoryUsed()) + hash.Allocated(); + } + std::size_t Size() const { return sizeof(*this) + Allocated(); } + + void Init(const T& boundsMins, const T& boundsMaxs, + const int newBoxHashSize, const int initialSize) { + this->ClearFree(); + this->PreAllocate(initialSize); + boxHashSize = newBoxHashSize > 0 ? newBoxHashSize : 1; + hash.Allocate(IntegerPower(boxHashSize, dimension), + initialSize > 0 ? initialSize : 1); + mins = boundsMins; + maxs = boundsMaxs; + for (int index = 0; index < dimension; ++index) { + const float boxSize = (maxs[index] - mins[index]) + / static_cast(boxHashSize); + boxInvSize[index] = boxSize != 0.0f ? 1.0f / boxSize : 0.0f; + boxHalfSize[index] = boxSize * 0.5f; + } + } + + void ResizeIndex(const int newSize) { + this->Resize(newSize); + hash.ResizeIndex(newSize); + } + + void Clear() { + idList::Clear(); + hash.Clear(); + } + + int FindVector(const T& vector, const float epsilon) { + int partialHashKey[dimension]; + for (int component = 0; component < dimension; ++component) { + assert(epsilon <= boxHalfSize[component]); + partialHashKey[component] = static_cast( + (vector[component] - mins[component] - boxHalfSize[component]) + * boxInvSize[component]); + } + for (int corner = 0; corner < (1 << dimension); ++corner) { + int hashKey = 0; + for (int component = 0; component < dimension; ++component) { + hashKey *= boxHashSize; + hashKey += partialHashKey[component] + ((corner >> component) & 1); + } + for (int candidate = hash.First(hashKey); candidate >= 0; + candidate = hash.Next(candidate)) { + int component = 0; + for (; component < dimension; ++component) { + if (std::fabs((*this)[candidate][component] + - vector[component]) > epsilon) break; + } + if (component == dimension) return candidate; + } + } + const int index = this->Num(); + hash.Add(HashKey(vector), index); + this->Append(vector); + return index; + } + +private: + static int IntegerPower(const int base, const int exponent) { + int result = 1; + for (int index = 0; index < exponent; ++index) result *= base; + return result; + } + + int HashKey(const T& vector) const { + int key = 0; + for (int component = 0; component < dimension; ++component) { + key *= boxHashSize; + key += static_cast((vector[component] - mins[component]) + * boxInvSize[component]); + } + return key; + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idVectorSubset) == 84, + "Recovered idVectorSubset ABI changed"); +static_assert(sizeof(idVectorSubset) == 68, + "Recovered idVectorSubset ABI changed"); +static_assert(sizeof(idVectorSet) == 100, + "Recovered idVectorSet ABI changed"); +#endif diff --git a/source/shared/idlib/csystems/cmdsystem.h b/source/shared/idlib/csystems/cmdsystem.h new file mode 100644 index 0000000..aa22ddb --- /dev/null +++ b/source/shared/idlib/csystems/cmdsystem.h @@ -0,0 +1,97 @@ +#pragma once + +#include "autocomplete.h" +#include "../containers/list.h" +#include "../text/strstatic.h" + +using cmdFunction_t = void (*)(const idCmdArgs& args); +using argCompletion_t = void (*)(idAutoComplete& completion); + +class idCmdSystem { +public: + virtual ~idCmdSystem(); + virtual void Init() = 0; + virtual void AddCommand(const char* name, cmdFunction_t function, + const char* description, argCompletion_t argCompletion = nullptr) = 0; + virtual const char* GetCommandDescription(const char* name) = 0; + virtual bool CommandExists(const char* name, + bool searchForCommandString) = 0; + virtual void FindCommands(const char* prefix, idList& commands) = 0; + virtual void CommandCompletion(idAutoComplete& completion) = 0; + virtual void ExecuteCommandText(const char* text) = 0; + virtual void AppendCommandText(const char* text) = 0; + virtual void ExecuteCommandBuffer() = 0; + virtual void ArgCompletion_FolderExtension(idAutoComplete& completion, + const char* folder, const char* extension, bool stripFolder) = 0; + + static void ArgCompletion_Boolean(idAutoComplete& completion); + static void ArgCompletion_ConfigName(idAutoComplete& completion); + static void ArgCompletion_DemoFile(idAutoComplete& completion); + static void ArgCompletion_EventName(idAutoComplete& completion); + static void ArgCompletion_FileName(idAutoComplete& completion); + static void ArgCompletion_ImageName(idAutoComplete& completion); + static void ArgCompletion_MapName(idAutoComplete& completion); + static void ArgCompletion_ModelName(idAutoComplete& completion); + static void ArgCompletion_PlayTestFile(idAutoComplete& completion); + static void ArgCompletion_RegressionTestName(idAutoComplete& completion); + static void ArgCompletion_SaveGame(idAutoComplete& completion); + static void ArgCompletion_TimeTrial(idAutoComplete& completion); + + template + static void ArgCompletion_Integer(idAutoComplete& completion); + + template + static void ArgCompletion_String(idAutoComplete& completion); +}; + +struct commandDef_s { + commandDef_s* next; + const char* name; + cmdFunction_t function; + argCompletion_t argCompletion; + const char* description; +}; + +class idCmdSystemLocal : public idCmdSystem { +public: + idCmdSystemLocal(); + ~idCmdSystemLocal() override; + + void Init() override; + void AddCommand(const char* name, cmdFunction_t function, + const char* description, argCompletion_t argCompletion) override; + const char* GetCommandDescription(const char* name) override; + bool CommandExists(const char* name, + bool searchForCommandString) override; + void FindCommands(const char* prefix, idList& commands) override; + void CommandCompletion(idAutoComplete& completion) override; + void ExecuteCommandText(const char* text) override; + void AppendCommandText(const char* text) override; + void ExecuteCommandBuffer() override; + void ArgCompletion_FolderExtension(idAutoComplete& completion, + const char* folder, const char* extension, bool stripFolder) override; + + void ExecuteTokenizedString(const idCmdArgs& args); + void InsertCommandText(const char* text); + + commandDef_s* commands; + int wait; + idStrStatic<32768> textBuffer; + +private: + static void Echo_f(const idCmdArgs& args); + static void Exec_f(const idCmdArgs& args); + static void List_f(const idCmdArgs& args); + static void Parse_f(const idCmdArgs& args); + static void Vstr_f(const idCmdArgs& args); + static void Wait_f(const idCmdArgs& args); +}; + +extern idCmdSystem* cmdSystem; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCmdSystem) == 4, "Recovered idCmdSystem ABI changed"); +static_assert(sizeof(commandDef_s) == 20, "Recovered commandDef_s ABI changed"); +static_assert(sizeof(idCmdSystemLocal) == 32812, + "Recovered idCmdSystemLocal ABI changed"); +#endif diff --git a/source/shared/idlib/csystems/cvarsystem.h b/source/shared/idlib/csystems/cvarsystem.h new file mode 100644 index 0000000..23af077 --- /dev/null +++ b/source/shared/idlib/csystems/cvarsystem.h @@ -0,0 +1,165 @@ +#pragma once + +#include "autocomplete.h" +#include "../callback.h" +#include "../containers/hashindex.h" +#include "../containers/list.h" +#include "../filesystem/file.h" +#include "../text/str.h" + +enum cvarFlags_t : int { + CVAR_BOOL = 0x00000001, + CVAR_INTEGER = 0x00000002, + CVAR_FLOAT = 0x00000004, + CVAR_CHEAT = 0x00000008, + CVAR_NOCHEAT = 0x00000010, + CVAR_INIT = 0x00004000, + CVAR_ROM = 0x00008000, + CVAR_ARCHIVE = 0x00010000, + CVAR_MODIFIED = 0x00020000, + CVAR_MODIFIED2 = 0x00040000 +}; + +class idCVar { +public: + using valueCompletion_t = void (*)(idAutoComplete& completion); + + struct cvarCallback_t { + idCallback* callback; + cvarCallback_t* next; + }; + + idCVar(const char* name, const char* value, int flags, + const char* description, valueCompletion_t completion = nullptr); + idCVar(const char* name, const char* value, int flags, + const char* description, float valueMin, float valueMax, + valueCompletion_t completion = nullptr); + idCVar(const char* name, const char* value, int flags, + const char* description, const char** valueStrings, + valueCompletion_t completion = nullptr); + + const char* GetName() const { return name; } + const char* GetString() const { return valueString.c_str(); } + bool GetBool() const { return valueInteger != 0; } + int GetInteger() const { return valueInteger; } + float GetFloat() const { return valueFloat; } + const char* GetDescription() const { return description; } + int GetFlags() const { return flags; } + float GetMinValue() const { return valueMin; } + float GetMaxValue() const { return valueMax; } + const char** GetValueStrings() const { return valueStrings; } + valueCompletion_t GetValueCompletion() const { return valueCompletion; } + + void Reset(); + bool Set(const char* newValue, bool force = false); + void SetString(const char* newValue, bool force = false); + void SetBool(bool newValue, bool force = false); + void SetInteger(int newValue, bool force = false); + void SetFloat(float newValue, bool force = false); + static void RegisterStaticVars(); + + idStr valueString; + int valueInteger; + float valueFloat; + const char* name; + const char* resetString; + const char* description; + int flags; + float valueMin; + float valueMax; + const char** valueStrings; + valueCompletion_t valueCompletion; + cvarCallback_t* onChange; + idCVar* next; + +protected: + void Init(const char* name, const char* value, int flags, + const char* description, float valueMin, float valueMax, + const char** valueStrings, valueCompletion_t completion); + void UpdateValue(); +}; + +class idCVarSystem { +public: + virtual ~idCVarSystem(); + virtual int NumCVars() const = 0; + virtual const idCVar* FindByIndex(int index) const = 0; + virtual idCVar* Find(const char* name) const = 0; + virtual bool CvarExists(const char* name, + bool searchForCvarString) const = 0; + virtual void FindCvarsByPrefix(const char* prefix, + idList& cvars) const = 0; + virtual void SetCVarString(const char* name, const char* value, + int flags = 0) = 0; + virtual void SetCVarBool(const char* name, bool value, int flags = 0) = 0; + virtual void SetCVarInteger(const char* name, int value, + int flags = 0) = 0; + virtual void SetCVarFloat(const char* name, float value, + int flags = 0) = 0; + virtual const char* GetCVarString(const char* name, + const char* defaultValue = "") const = 0; + virtual bool GetCVarBool(const char* name, bool defaultValue = false) const = 0; + virtual int GetCVarInteger(const char* name, int defaultValue = 0) const = 0; + virtual float GetCVarFloat(const char* name, + float defaultValue = 0.0f) const = 0; + virtual bool Command(const idCmdArgs& args) = 0; + virtual void CommandCompletion(idAutoComplete& completion) = 0; + virtual void SetModifiedFlags(int flags) = 0; + virtual int GetModifiedFlags() const = 0; + virtual void ClearModifiedFlags(int flags) = 0; + virtual void ResetFlaggedVariables(int flags) = 0; + virtual void WriteFlaggedVariables(int flags, idFile* file) const = 0; + virtual void ReportModifiedCVars() = 0; + virtual void ClearModifiedCVars() = 0; +}; + +class idCVarSystemLocal : public idCVarSystem { +public: + idCVarSystemLocal(); + ~idCVarSystemLocal() override; + + int NumCVars() const override { return cvars.Num(); } + const idCVar* FindByIndex(int index) const override { return cvars[index]; } + idCVar* Find(const char* name) const override; + bool CvarExists(const char* name, + bool searchForCvarString) const override; + void FindCvarsByPrefix(const char* prefix, + idList& found) const override; + void SetCVarString(const char* name, const char* value, + int flags) override; + void SetCVarBool(const char* name, bool value, int flags) override; + void SetCVarInteger(const char* name, int value, int flags) override; + void SetCVarFloat(const char* name, float value, int flags) override; + const char* GetCVarString(const char* name, + const char* defaultValue) const override; + bool GetCVarBool(const char* name, bool defaultValue) const override; + int GetCVarInteger(const char* name, int defaultValue) const override; + float GetCVarFloat(const char* name, float defaultValue) const override; + bool Command(const idCmdArgs& args) override; + void CommandCompletion(idAutoComplete& completion) override; + void SetModifiedFlags(int flags) override; + int GetModifiedFlags() const override { return modifiedFlags; } + void ClearModifiedFlags(int flags) override; + void ResetFlaggedVariables(int flags) override; + void WriteFlaggedVariables(int flags, idFile* file) const override; + void ReportModifiedCVars() override; + void ClearModifiedCVars() override; + + static void ListCvars(const idCmdArgs& args); + + idList cvars; + idHashIndex cvarHash; + int modifiedFlags; +}; + +extern idCVarSystem* cvarSystem; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCVar::cvarCallback_t) == 8, + "Recovered idCVar callback ABI changed"); +static_assert(sizeof(idCVar) == 80, "Recovered idCVar ABI changed"); +static_assert(sizeof(idCVarSystem) == 4, + "Recovered idCVarSystem ABI changed"); +static_assert(sizeof(idCVarSystemLocal) == 56, + "Recovered idCVarSystemLocal ABI changed"); +#endif diff --git a/source/shared/idlib/dict.h b/source/shared/idlib/dict.h new file mode 100644 index 0000000..d353164 --- /dev/null +++ b/source/shared/idlib/dict.h @@ -0,0 +1,53 @@ +#pragma once + +#include "containers/hashindex.h" +#include "containers/list.h" +#include "text/str.h" + +class idFile; +class idLexer; + +class idKeyValue { +public: + const idStr& GetKey() const { return key; } + const idStr& GetValue() const { return value; } + + idStr key; + idStr value; +}; + +class idDict { +public: + idDict(); + idDict(const idDict& other); + ~idDict(); + + idDict& operator=(const idDict& other); + int GetNumKeyVals() const { return args.Num(); } + const idKeyValue* GetKeyVal(int index) const { return &args[index]; } + const idKeyValue* FindKey(const char* key) const; + int FindKeyIndex(const char* key) const; + const char* GetString(const char* key, + const char* defaultString = "") const { + const idKeyValue* const kv = FindKey(key); + return kv == nullptr ? defaultString : kv->value.c_str(); + } + bool GetInt(const char* key, int defaultValue, int& out) const; + bool GetFloat(const char* key, float defaultValue, float& out) const; + bool Set(const char* key, const char* value); + void Delete(const char* key); + void Clear(); + unsigned int Checksum() const; + void Print() const; + void WriteToIniFile(idFile* file) const; + bool ReadFromIniFile(idFile* file); + bool Parse(idLexer& parser, const char* start, const char* end); + + idList args; + idHashIndex argHash; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idKeyValue) == 64, "Recovered idKeyValue ABI changed"); +static_assert(sizeof(idDict) == 48, "Recovered idDict ABI changed"); +#endif diff --git a/source/shared/idlib/filesystem/compressor.h b/source/shared/idlib/filesystem/compressor.h new file mode 100644 index 0000000..d0e2b4e --- /dev/null +++ b/source/shared/idlib/filesystem/compressor.h @@ -0,0 +1,370 @@ +#pragma once + +#include "file.h" +#include "../containers/hashindex.h" + +#include + +class idCompressor : public idFile { +public: + ~idCompressor() override; + + virtual void Init(idFile* file, bool compress, int wordLength) = 0; + virtual void FinishCompress() = 0; + virtual float GetCompressionRatio() const = 0; + virtual int GetCompressedSize() const = 0; + virtual int GetUncompressedSize() const = 0; + + static idCompressor* AllocArithmetic(); + static idCompressor* AllocLZSS_ByteAligned(); + static idCompressor* AllocLZW(); + static idCompressor* AllocRunLength_ZeroBased(); +}; + +class alignas(4) idCompressor_None : public idCompressor { +public: + idCompressor_None(); + ~idCompressor_None() override; + + const char* GetName() const override; + const char* GetFullPath() const override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + std::int64_t Length() const override; + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; + unsigned int Timestamp() const override; + void Flush() override; + void ForceFlush() override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + float GetCompressionRatio() const override; + int GetCompressedSize() const override; + int GetUncompressedSize() const override; + + idFile* file; + bool compress; +}; + +class idCompressor_BitStream : public idCompressor_None { +public: + idCompressor_BitStream(); + ~idCompressor_BitStream() override; + + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + float GetCompressionRatio() const override; + int GetCompressedSize() const override; + int GetUncompressedSize() const override; + + std::uint8_t buffer[512]; + int wordLength; + int readTotalBytes; + int readTotalBits; + int readLength; + int readByte; + int readBit; + const std::uint8_t* readData; + int writeTotalBytes; + int writeTotalBits; + int writeLength; + int writeByte; + int writeBit; + std::uint8_t* writeData; + +protected: + void InitDecompress(void* data, int length); + int ReadBits(int numBits); + std::uint8_t ReadAlignedByte(); + std::uint16_t ReadAlignedWord(); + void WriteBits(int value, int numBits); + void WriteAlignedByte(std::uint8_t value); + void WriteAlignedWord(std::uint16_t value); +}; + +class idCompressor_RunLength : public idCompressor_BitStream { +public: + ~idCompressor_RunLength() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + int runLengthCode; +}; + +class idCompressor_ZRLE_ByteAligned : public idCompressor_BitStream { +public: + ~idCompressor_ZRLE_ByteAligned() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + int zeroCount; +}; + +class idCompressor_RunLength_ZeroBased : public idCompressor_BitStream { +public: + idCompressor_RunLength_ZeroBased(); + ~idCompressor_RunLength_ZeroBased() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + std::uint8_t buffer[32]; + int bp; + int count; + int runBits; + int maxRun; + +private: + bool BitsToRead(); + bool BitsToWrite(); + int CompressBlock(const std::uint8_t* input, int length); + int DecompressBlock(std::uint8_t* output, int length); + void WriteRun(); +}; + +struct idHuffmanNode { + idHuffmanNode* left; + idHuffmanNode* right; + idHuffmanNode* parent; + idHuffmanNode* next; + idHuffmanNode* prev; + idHuffmanNode** head; + int weight; + int symbol; +}; + +class idCompressor_Huffman : public idCompressor_None { +public: + idCompressor_Huffman(); + ~idCompressor_Huffman() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + float GetCompressionRatio() const override; + int GetCompressedSize() const override; + int GetUncompressedSize() const override; + + std::uint8_t seq[65536]; + int bloc; + int blocMax; + int blocIn; + int blocNode; + int blocPtrs; + int compressedSize; + int unCompressedSize; + idHuffmanNode* tree; + idHuffmanNode* lhead; + idHuffmanNode* ltail; + idHuffmanNode* loc[257]; + idHuffmanNode** freelist; + idHuffmanNode nodeList[768]; + idHuffmanNode* nodePtrs[768]; +}; + +class idCompressor_Arithmetic : public idCompressor_BitStream { +public: + struct idAcProbs { + unsigned int low; + unsigned int high; + }; + struct idAcSymbol { + unsigned int low; + unsigned int high; + int position; + }; + + idCompressor_Arithmetic(); + ~idCompressor_Arithmetic() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + idAcProbs probabilities[256]; + int symbolBuffer; + int symbolBit; + std::uint16_t low; + std::uint16_t high; + std::uint16_t code; + unsigned int underflowBits; + unsigned int scale; + +private: + void EncodeSymbol(idAcSymbol* symbol); + int GetByte(); + void InitCode(); + int ProbabilityForCount(unsigned int count); + void RemoveSymbolFromStream(idAcSymbol* symbol); + void UpdateProbabilities(idAcSymbol* symbol); + void WriteOverflowBits(); +}; + +class idCompressor_LZSS : public idCompressor_BitStream { +public: + idCompressor_LZSS(); + ~idCompressor_LZSS() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + virtual void CompressBlock(); + virtual void DecompressBlock(); + + int offsetBits; + int lengthBits; + int minMatchWords; + std::uint8_t block[32768]; + int blockSize; + int blockIndex; + int hashTable[1024]; + int hashNext[32768]; +}; + +class idCompressor_LZSS_WordAligned : public idCompressor_LZSS { +public: + ~idCompressor_LZSS_WordAligned() override; + void CompressBlock() override; + void DecompressBlock() override; +}; + +class idCompressor_LZSS_ByteAligned : public idCompressor_BitStream { +public: + idCompressor_LZSS_ByteAligned(); + ~idCompressor_LZSS_ByteAligned() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + int offsetBits; + int lengthBits; + int minMatchWords; + std::uint8_t block[131072]; + int blockSize; + int blockIndex; + int hashTable[65536]; + int hashNext[131072]; + +private: + void CompressBlock(); + void DecompressBlock(); + bool FindByteMatch(int start, int end, int& offset, int& length); + int GetHashKey(int index) const; +}; + +class idCompressor_LZW : public idCompressor_BitStream { +public: + struct dictionary_t { + int k; + int w; + }; + + idCompressor_LZW(); + ~idCompressor_LZW() override; + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + void Init(idFile* backingFile, bool compressing, int wordBits) override; + void FinishCompress() override; + + dictionary_t dictionary[4096]; + idHashIndex index; + int nextCode; + int codeBits; + std::uint8_t block[32768]; + int blockSize; + int blockIndex; + int codeWord; + int oldCode; + +protected: + bool BumpBits(); + void DecompressBlock(); + int Lookup(int w, int k); + int WriteChain(int code); +}; + +struct lzwCompressionData_t { + std::uint8_t dictionaryK[4096]; + std::uint16_t dictionaryW[4096]; + int nextCode; + int codeBits; + int codeWord; + std::uint64_t tempValue; + int tempBits; + int bytesWritten; +}; + +class alignas(8) idLZWCompressor { +public: + void Start(std::uint8_t* streamData, int maximumSize, bool append); + int End(); + int Read(void* output, int length, bool ignoreOverflow = false); + int Write(const void* input, int length); + int ReadByte(bool ignoreOverflow = false); + void WriteByte(std::uint8_t value); + int ReadBits(int bits); + void WriteBits(unsigned int value, int bits); + void Save(); + void Restore(); + bool BumpBits(); + void DecompressBlock(); + int Lookup(int w, int k); + int WriteChain(int code); + + template + unsigned int ReadAgnostic(type_t& value, bool ignoreOverflow = false) { + return static_cast( + Read(&value, static_cast(sizeof(value)), ignoreOverflow)); + } + + lzwCompressionData_t* lzwData; + std::uint16_t hash[1024]; + std::uint16_t nextHash[4096]; + int oldCode; + std::uint8_t* data; + int maxSize; + bool overflowed; + int bytesRead; + std::uint8_t block[32768]; + int blockSize; + int blockIndex; + int savedBytesWritten; + int savedCodeWord; + int saveCodeBits; + std::uint64_t savedTempValue; + int savedTempBits; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCompressor) == 8, + "Recovered idCompressor ABI changed"); +static_assert(sizeof(idCompressor_None) == 16, + "Recovered idCompressor_None ABI changed"); +static_assert(sizeof(idCompressor_BitStream) == 580, + "Recovered idCompressor_BitStream ABI changed"); +static_assert(sizeof(idCompressor_RunLength_ZeroBased) == 628, + "Recovered zero-run compressor ABI changed"); +static_assert(sizeof(idHuffmanNode) == 32, + "Recovered idHuffmanNode ABI changed"); +static_assert(sizeof(idCompressor_Arithmetic) == 2652, + "Recovered arithmetic compressor ABI changed"); +static_assert(sizeof(idCompressor_LZSS) == 168536, + "Recovered LZSS compressor ABI changed"); +static_assert(sizeof(idCompressor_LZSS_ByteAligned) == 918104, + "Recovered byte-aligned LZSS ABI changed"); +static_assert(sizeof(idCompressor_LZW) == 66172, + "Recovered LZW compressor ABI changed"); +static_assert(sizeof(lzwCompressionData_t) == 12320, + "Recovered LZW state ABI changed"); +static_assert(sizeof(idLZWCompressor) == 43072, + "Recovered lightweight LZW ABI changed"); +#endif diff --git a/source/shared/idlib/filesystem/file.cpp b/source/shared/idlib/filesystem/file.cpp new file mode 100644 index 0000000..8c769a4 --- /dev/null +++ b/source/shared/idlib/filesystem/file.cpp @@ -0,0 +1,229 @@ +#include "file.h" + +#include +#include +#include +#include + +namespace { +std::atomic nextFileId(0); +} + +idFile::idFile() + : uniqID(++nextFileId) { +} + +unsigned int idFile::ReadOfs(const std::int64_t offset, void* data, + const unsigned int length) { + const std::int64_t position = Tell(); + if (Seek(offset, FS_SEEK_SET) != 0) return 0; + const unsigned int result = Read(data, length); + Seek(position, FS_SEEK_SET); + return result; +} + +unsigned int idFile::WriteOfs(const std::int64_t offset, const void* data, + const unsigned int length) { + const std::int64_t position = Tell(); + if (Seek(offset, FS_SEEK_SET) != 0) return 0; + const unsigned int result = Write(data, length); + Seek(position, FS_SEEK_SET); + return result; +} + +unsigned int idFile::Printf(const char* format, ...) { + va_list arguments; + va_start(arguments, format); + const unsigned int result = VPrintf(format, arguments); + va_end(arguments); + return result; +} + +unsigned int idFile::VPrintf(const char* format, char* arguments) { + char text[4096]; + const int length = _vsnprintf_s(text, sizeof(text), _TRUNCATE, + format, arguments); + return length > 0 ? Write(text, static_cast(length)) : 0; +} + +unsigned int idFile::WriteFloatString(const char* format, ...) { + va_list arguments; + va_start(arguments, format); + const unsigned int result = VPrintf(format, arguments); + va_end(arguments); + return result; +} + +unsigned int idFile::ReadString(idStr& string) { + std::uint32_t length = 0; + unsigned int bytes = ReadLittle(length); + if (length == 0) { + string.Clear(); + return bytes; + } + char* text = static_cast(std::malloc(length + 1)); + if (text == nullptr) return bytes; + const unsigned int read = Read(text, length); + text[read] = '\0'; + string = text; + std::free(text); + return bytes + read; +} + +unsigned int idFile::WriteString(const idStr& string) { + return WriteString(string.c_str()); +} + +unsigned int idFile::WriteString(const char* string) { + const char* const safeString = string != nullptr ? string : ""; + const std::uint32_t length = static_cast( + std::strlen(safeString)); + return WriteLittle(length) + Write(safeString, length); +} + +idFile_Memory::idFile_Memory() + : idFile(), name(), mode(FS_WRITE), maxSize(0), fileSize(0), allocated(0), + timestamp(0), filePtr(nullptr), curPtr(nullptr), ownsData(true) { +} + +idFile_Memory::idFile_Memory(const char* fileName) + : idFile_Memory() { + name = fileName; +} + +idFile_Memory::~idFile_Memory() { + if (ownsData) std::free(filePtr); +} + +void idFile_Memory::SetReadOnlyData(const char* data, + const unsigned int length) { + if (ownsData) std::free(filePtr); + maxSize = fileSize = allocated = length; + mode = FS_READ; + filePtr = const_cast(data); + curPtr = filePtr; + ownsData = false; +} + +void idFile_Memory::SetWritableData(char* data, const unsigned int length) { + if (ownsData) std::free(filePtr); + maxSize = allocated = length; + fileSize = 0; + mode = FS_WRITE; + filePtr = curPtr = data; + ownsData = false; +} + +unsigned int idFile_Memory::Read(void* data, const unsigned int length) { + if (data == nullptr || filePtr == nullptr || curPtr == nullptr) return 0; + const unsigned int position = static_cast(curPtr - filePtr); + const unsigned int available = position < fileSize ? fileSize - position : 0; + const unsigned int amount = (std::min)(length, available); + std::memcpy(data, curPtr, amount); + curPtr += amount; + return amount; +} + +unsigned int idFile_Memory::Write(const void* data, + const unsigned int length) { + if (mode == FS_READ || data == nullptr) return 0; + unsigned int position = filePtr != nullptr && curPtr != nullptr + ? static_cast(curPtr - filePtr) + : 0; + if (maxSize != 0 && position + length > maxSize) + return 0; + if (position + length > allocated) { + const unsigned int target = (std::max)(position + length, + allocated == 0 ? 256u : allocated * 2u); + const unsigned int oldFileSize = fileSize; + SetLength(target); + fileSize = oldFileSize; + position = curPtr != nullptr ? static_cast(curPtr - filePtr) : 0; + } + std::memcpy(curPtr, data, length); + curPtr += length; + fileSize = (std::max)(fileSize, position + length); + return length; +} + +unsigned int idFile_Memory::ReadOfs(const std::int64_t offset, void* data, + const unsigned int length) { + if (offset < 0 || static_cast(offset) > fileSize) return 0; + const unsigned int position = static_cast(offset); + const unsigned int amount = (std::min)(length, fileSize - position); + std::memcpy(data, filePtr + position, amount); + return amount; +} + +unsigned int idFile_Memory::WriteOfs(const std::int64_t offset, + const void* data, const unsigned int length) { + if (offset < 0) return 0; + const std::int64_t oldPosition = Tell(); + if (Seek(offset, FS_SEEK_SET) != 0) return 0; + const unsigned int result = Write(data, length); + Seek(oldPosition, FS_SEEK_SET); + return result; +} + +void idFile_Memory::SetLength(const unsigned int length) { + if (length > allocated) { + if (maxSize != 0 && length > maxSize) return; + const std::size_t position = filePtr != nullptr && curPtr != nullptr + ? static_cast(curPtr - filePtr) + : 0; + char* replacement = static_cast(std::malloc(length)); + if (replacement == nullptr && length != 0) return; + if (filePtr != nullptr) + std::memcpy(replacement, filePtr, (std::min)(fileSize, length)); + if (ownsData) std::free(filePtr); + filePtr = replacement; + curPtr = filePtr + (std::min)(position, static_cast(length)); + allocated = length; + ownsData = true; + } + fileSize = length; + if (curPtr != nullptr && curPtr > filePtr + fileSize) + curPtr = filePtr + fileSize; +} + +std::int64_t idFile_Memory::Tell() const { + return filePtr != nullptr && curPtr != nullptr ? curPtr - filePtr : 0; +} + +int idFile_Memory::Seek(const std::int64_t offset, const fsOrigin_t origin) { + std::int64_t target = offset; + if (origin == FS_SEEK_CUR) target += Tell(); + else if (origin == FS_SEEK_END) target += fileSize; + if (target < 0 || static_cast(target) > fileSize) return -1; + curPtr = filePtr + static_cast(target); + return 0; +} + +void idFile_Memory::Clear(const bool freeMemory) { + fileSize = 0; + if (freeMemory && ownsData) { + std::free(filePtr); + filePtr = curPtr = nullptr; + allocated = 0; + } else { + curPtr = filePtr; + } +} + +void idFile_Memory::SetMaxLength(const unsigned int length) { + const unsigned int oldSize = fileSize; + SetLength(length); + maxSize = length; + fileSize = oldSize; +} + +void idFile_Memory::MakeReadOnly() { + mode = FS_READ; + Seek(0, FS_SEEK_SET); +} + +void idFile_Memory::MakeWritable() { + mode = FS_WRITE; + Seek(0, FS_SEEK_SET); +} + diff --git a/source/shared/idlib/filesystem/file.h b/source/shared/idlib/filesystem/file.h new file mode 100644 index 0000000..2176a8e --- /dev/null +++ b/source/shared/idlib/filesystem/file.h @@ -0,0 +1,179 @@ +#pragma once + +#include "../bv/bounds.h" +#include "../text/strstatic.h" + +#include +#include +#include + +enum fsPath_t : int { + FSPATH_BASE = 0, + FSPATH_CACHE = 1, + FSPATH_SAVE = 2, + FSPATH_INSTALL = 3 +}; + +enum fsDevice_t : int { + FS_DEVICE_HARD_DISK_DRIVE = 0, + FS_DEVICE_OPTICAL_DISK_DRIVE = 1, + FS_DEVICE_SOLID_STATE_DRIVE = 2, + FS_DEVICE_NETWORK = 3, + FS_DEVICE_MEMORY = 4 +}; + +enum fsOrigin_t : int { + FS_SEEK_CUR = 0, + FS_SEEK_END = 1, + FS_SEEK_SET = 2 +}; + +enum fsMode_t : int { + FS_READ = 0, + FS_WRITE = 1, + FS_READ_WRITE = 2, + FS_READ_NO_BUFFERING = 3, + FS_APPEND = 4 +}; + +enum fsLock_t : int { + FS_LOCK_SHARED = 0, + FS_LOCK_EXCLUSIVE = 1 +}; + +class idFile { +public: + idFile(); + virtual ~idFile() = default; + + virtual const char* GetName() const { return ""; } + virtual const char* GetFullPath() const { return GetName(); } + virtual unsigned int Read(void*, unsigned int) { return 0; } + virtual unsigned int Write(const void*, unsigned int) { return 0; } + virtual unsigned int ReadOfs(std::int64_t offset, void* data, + unsigned int length); + virtual unsigned int WriteOfs(std::int64_t offset, const void* data, + unsigned int length); + virtual bool Lock(std::int64_t, unsigned int, fsLock_t) { return false; } + virtual bool Unlock(std::int64_t, unsigned int) { return false; } + virtual std::int64_t Length() const { return 0; } + virtual void SetLength(unsigned int) {} + virtual std::int64_t Tell() const { return 0; } + virtual int Seek(std::int64_t, fsOrigin_t) { return -1; } + virtual unsigned int Printf(const char* format, ...); + virtual unsigned int VPrintf(const char* format, char* arguments); + virtual unsigned int WriteFloatString(const char* format, ...); + virtual unsigned int Timestamp() const { return 0; } + virtual void Flush() {} + virtual void ForceFlush() {} + virtual int GetSectorSize() const { return 1; } + virtual fsDevice_t GetDevice() const { return FS_DEVICE_MEMORY; } + virtual bool IsOSNative() const { return false; } + + template + unsigned int ReadLittle(type_t& value) { + return Read(&value, static_cast(sizeof(value))); + } + + template + unsigned int WriteLittle(const type_t& value) { + return Write(&value, static_cast(sizeof(value))); + } + + unsigned int ReadString(idStr& string); + unsigned int WriteString(const idStr& string); + unsigned int WriteString(const char* string); + + unsigned int uniqID; +}; + +class idFile_Memory : public idFile { +public: + idFile_Memory(); + explicit idFile_Memory(const char* fileName); + ~idFile_Memory() override; + + const char* GetName() const override { return name.c_str(); } + const char* GetFullPath() const override { return name.c_str(); } + unsigned int Read(void* data, unsigned int length) override; + unsigned int Write(const void* data, unsigned int length) override; + unsigned int ReadOfs(std::int64_t offset, void* data, + unsigned int length) override; + unsigned int WriteOfs(std::int64_t offset, const void* data, + unsigned int length) override; + std::int64_t Length() const override { return fileSize; } + void SetLength(unsigned int length) override; + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; + unsigned int Timestamp() const override { return timestamp; } + fsDevice_t GetDevice() const override { return FS_DEVICE_MEMORY; } + virtual void Clear(bool freeMemory = true); + + void SetReadOnlyData(const char* data, unsigned int length); + void SetWritableData(char* data, unsigned int length); + void SetMaxLength(unsigned int length); + void MakeReadOnly(); + void MakeWritable(); + const char* GetDataPtr() const { return filePtr; } + char* GetDataPtr() { return filePtr; } + + idStrStatic<260> name; + int mode; + unsigned int maxSize; + unsigned int fileSize; + unsigned int allocated; + unsigned int timestamp; + char* filePtr; + char* curPtr; + bool ownsData; +}; + +class idFile_Stat : public idFile { +public: + idFile_Stat(const char* name = "", std::int64_t size = 0, + unsigned int time = 0) + : fileName(name), fileSize(size), fileTimestamp(time) {} + const char* GetName() const override { return fileName.c_str(); } + const char* GetFullPath() const override { return fileName.c_str(); } + unsigned int Read(void* data, unsigned int length) override; + unsigned int Write(const void* data, unsigned int length) override; + unsigned int ReadOfs(std::int64_t offset, void* data, + unsigned int length) override; + unsigned int WriteOfs(std::int64_t offset, const void* data, + unsigned int length) override; + std::int64_t Length() const override { return fileSize; } + void SetLength(unsigned int length) override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; + unsigned int Timestamp() const override { return fileTimestamp; } + + idStr fileName; + std::int64_t fileSize; + unsigned int fileTimestamp; +}; + +class idFile_String : public idFile_Memory { +public: + using idFile_Memory::idFile_Memory; +}; + +class idFileLocal { +public: + explicit idFileLocal(idFile* filePointer = nullptr) : file(filePointer) {} + ~idFileLocal() { delete file; } + idFile* operator->() { return file; } + const idFile* operator->() const { return file; } + idFile* Release() { idFile* result = file; file = nullptr; return result; } + + idFile* file; +}; + +using HFILE = int; +using _HFILE = void*; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idFile) == 8, "Recovered idFile ABI changed"); +static_assert(sizeof(idFile_Memory) == 332, + "Recovered idFile_Memory ABI changed"); +static_assert(sizeof(idFile_Stat) == 56, "Recovered idFile_Stat ABI changed"); +static_assert(sizeof(idFileLocal) == 4, "Recovered idFileLocal ABI changed"); +#endif diff --git a/source/shared/idlib/filesystem/file_inzip.h b/source/shared/idlib/filesystem/file_inzip.h new file mode 100644 index 0000000..ecd7c7e --- /dev/null +++ b/source/shared/idlib/filesystem/file_inzip.h @@ -0,0 +1,32 @@ +#pragma once + +#include "file.h" + +class idFile_InZip : public idFile { +public: + idFile_InZip(); + ~idFile_InZip() override; + + const char* GetName() const override { return name.c_str(); } + const char* GetFullPath() const override { return fullPath.c_str(); } + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + std::int64_t Length() const override; + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; + unsigned int Timestamp() const override { return timeStamp; } + void Flush() override; + void ForceFlush() override; + + idStr name; + idStr fullPath; + int zipFilePos; + int fileSize; + void* z; + unsigned int timeStamp; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idFile_InZip) == 88, + "Recovered idFile_InZip ABI changed"); +#endif diff --git a/source/shared/idlib/filesystem/file_metrics.cpp b/source/shared/idlib/filesystem/file_metrics.cpp index 9e935b1..70bdc10 100644 --- a/source/shared/idlib/filesystem/file_metrics.cpp +++ b/source/shared/idlib/filesystem/file_metrics.cpp @@ -44,7 +44,7 @@ int StringBytes(const char* text) { } // namespace idFile_Metrics::idFile_Metrics(const char* streamName) - : uniqID(0), name(streamName == nullptr ? "" : streamName), bytesSent(0) { + : name(streamName == nullptr ? "" : streamName), bytesSent(0) { } idFile_Metrics::~idFile_Metrics() { @@ -67,17 +67,18 @@ const char* idFile_Metrics::GetFullPath() const { return fullpath.c_str(); } -int idFile_Metrics::Read(void*, int) { +unsigned int idFile_Metrics::Read(void*, unsigned int) { return 0; } -int idFile_Metrics::Write(const void* buffer, const int len) { - const int written = WriteInternal(name.c_str(), buffer, len); +unsigned int idFile_Metrics::Write(const void* buffer, const unsigned int len) { + const int written = WriteInternal( + name.c_str(), buffer, static_cast(len)); bytesSent += written; return written; } -int idFile_Metrics::Seek(long, fsOrigin_t) { +int idFile_Metrics::Seek(std::int64_t, fsOrigin_t) { return -1; } @@ -126,8 +127,8 @@ void idFile_Metrics::ConfigureServer(const char* host, const unsigned short port std::strncpy(metricsServer, safeHost, sizeof(metricsServer) - 1); metricsServer[sizeof(metricsServer) - 1] = '\0'; metricsPort = port; - initialRetryTime = std::max(1, initialRetryMilliseconds); - maximumRetryTime = std::max(initialRetryTime, maximumRetryMilliseconds); + initialRetryTime = (std::max)(1, initialRetryMilliseconds); + maximumRetryTime = (std::max)(initialRetryTime, maximumRetryMilliseconds); retryTime = 0; timeoutWait = 0; sendIndex = pendingIndex = 0; @@ -156,7 +157,7 @@ bool idFile_Metrics::EnsureConnection() { } retryTime = retryTime == 0 ? initialRetryTime - : std::min(maximumRetryTime, retryTime * 2); + : (std::min)(maximumRetryTime, retryTime * 2); timeoutWait = Milliseconds() + static_cast(retryTime); return false; } @@ -166,7 +167,7 @@ void idFile_Metrics::WriteToQueue(const void* buffer, const int len) { const int retained = pendingIndex - sendIndex; const int required = retained + len; if (required > sendQueueSize) { - int newSize = std::max(1024, sendQueueSize); + int newSize = (std::max)(1024, sendQueueSize); while (newSize < required) newSize *= 2; unsigned char* const replacement = static_cast( std::malloc(static_cast(newSize))); @@ -200,7 +201,7 @@ void idFile_Metrics::BufferedWriteInternal(bool& queueTraffic, return; } const int written = metricsTCP.Write(buffer, len); - const int validWritten = std::max(0, written); + const int validWritten = (std::max)(0, written); if (written != len) { queueTraffic = true; WriteToQueue(static_cast(buffer) + validWritten, diff --git a/source/shared/idlib/filesystem/file_metrics.h b/source/shared/idlib/filesystem/file_metrics.h index 198c33d..0431cc4 100644 --- a/source/shared/idlib/filesystem/file_metrics.h +++ b/source/shared/idlib/filesystem/file_metrics.h @@ -1,16 +1,6 @@ #pragma once -// The recovery target still uses Doom 3 BFG's portable idFile implementation -// as its base file layer. idFile_Metrics retains the tungsten data layout and -// supplies the additional network-backed stream behavior recovered from the -// Xbox 360 executable. -#include "idlib/precompiled.h" - -// BFG emulates the C++11 keyword for its original compiler. Recovery sources -// use the real C++14 keyword and the standard library. -#ifdef nullptr -#undef nullptr -#endif +#include "file.h" class idFile_Metrics : public idFile { public: @@ -19,11 +9,11 @@ public: const char* GetName() const override { return name.c_str(); } const char* GetFullPath() const override; - int Read(void* buffer, int len) override; - int Write(const void* buffer, int len) override; - int Length() const override { return bytesSent; } - int Tell() const override { return bytesSent; } - int Seek(long offset, fsOrigin_t origin) override; + unsigned int Read(void* buffer, unsigned int len) override; + unsigned int Write(const void* buffer, unsigned int len) override; + std::int64_t Length() const override { return bytesSent; } + std::int64_t Tell() const override { return bytesSent; } + int Seek(std::int64_t offset, fsOrigin_t origin) override; void Flush() override; void ForceFlush() override; @@ -46,9 +36,6 @@ private: static void WriteFrame(bool& queueTraffic, const char* streamName, const void* buffer, int len); - // idTech 5 added uniqID to idFile; BFG's portable base predates it. Keep - // the field in the same derived-object position to preserve tungsten ABI. - unsigned int uniqID; idStr name; mutable idStr fullpath; int bytesSent; diff --git a/source/shared/idlib/filesystem/file_mtp.cpp b/source/shared/idlib/filesystem/file_mtp.cpp index 2ed8947..cebbed8 100644 --- a/source/shared/idlib/filesystem/file_mtp.cpp +++ b/source/shared/idlib/filesystem/file_mtp.cpp @@ -48,7 +48,7 @@ std::uint64_t GetUInt64BE(const unsigned char* input) { } // namespace idFile_MTP::idFile_MTP() - : uniqID(0), position(0), mode(MTP_FS_READ), length(0), + : position(0), mode(FS_READ), length(0), timestamp(static_cast(-1)) { } @@ -63,8 +63,8 @@ void idFile_MTP::ConfigureServer(const char* host, const unsigned short port, std::strncpy(mtpServer, safeHost, sizeof(mtpServer) - 1); mtpServer[sizeof(mtpServer) - 1] = '\0'; mtpPort = port; - mtpWriteSize = std::max(1, writeSize); - mtpTimeout = std::max(1, timeoutMilliseconds); + mtpWriteSize = (std::max)(1, writeSize); + mtpTimeout = (std::max)(1, timeoutMilliseconds); } void idFile_MTP::ShutdownTransport() { @@ -82,7 +82,7 @@ bool idFile_MTP::SendRequest(const std::uint64_t offset, const char* filename) { if (!EnsureConnection()) return false; const char* const safeName = filename == nullptr ? "" : filename; - const std::size_t filenameLength = std::min( + const std::size_t filenameLength = (std::min)( std::strlen(safeName), 0xFFFFu); unsigned char request[16] = {}; PutUInt64BE(request, offset); @@ -103,15 +103,22 @@ bool idFile_MTP::SendRequest(const std::uint64_t offset, return true; } -bool idFile_MTP::Open(const char* filename, const mtpFileMode_t openMode) { +bool idFile_MTP::Open(const char* filename, const fsMode_t openMode) { const char* const safeName = filename == nullptr ? "" : filename; name = safeName; fullPath.Format("MTP:%s", safeName); mode = openMode; position = 0; + unsigned int access = 1; + if (mode == FS_READ || mode == FS_READ_NO_BUFFERING) { + access = 0; + } else if (mode == FS_APPEND) { + access = 2; + } + std::lock_guard lock(mtpMutex); - if (!SendRequest(0, static_cast(openMode), OP_OPEN, + if (!SendRequest(0, access, OP_OPEN, name.c_str())) return false; unsigned char stats[16] = {}; if (mtpTCP.ReadBlocking(stats, sizeof(stats), mtpTimeout) @@ -122,18 +129,19 @@ bool idFile_MTP::Open(const char* filename, const mtpFileMode_t openMode) { length = GetUInt64BE(stats); timestamp = GetUInt32BE(stats + 8); if (timestamp == static_cast(-1)) return false; - if (mode == MTP_FS_APPEND) position = length; + if (mode == FS_APPEND) position = length; return true; } -int idFile_MTP::Read(void* buffer, const int len) { +unsigned int idFile_MTP::Read(void* buffer, const unsigned int len) { return ReadOfs(static_cast(position), buffer, len); } -int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) { +unsigned int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, + const unsigned int len) { if (buffer == nullptr || len <= 0 || offset < 0 - || (mode != MTP_FS_READ && mode != MTP_FS_READ_WRITE - && mode != MTP_FS_READ_NO_BUFFERING)) return 0; + || (mode != FS_READ && mode != FS_READ_WRITE + && mode != FS_READ_NO_BUFFERING)) return 0; std::lock_guard lock(mtpMutex); for (int attempt = 0; attempt < 5; ++attempt) { @@ -142,7 +150,7 @@ int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) mtpTCP.Close(); continue; } - int total = 0; + unsigned int total = 0; bool failed = false; while (total < len) { unsigned char sizeBytes[4] = {}; @@ -153,14 +161,14 @@ int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) } const unsigned int chunk = GetUInt32BE(sizeBytes); if (chunk == 0) break; - if (chunk > static_cast(len - total) + if (chunk > len - total || mtpTCP.ReadBlocking(static_cast(buffer) + total, static_cast(chunk), mtpTimeout) != static_cast(chunk)) { failed = true; break; } - total += static_cast(chunk); + total += chunk; } if (!failed) { position = static_cast(offset) + total; @@ -171,23 +179,25 @@ int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) return 0; } -int idFile_MTP::Write(const void* buffer, const int len) { +unsigned int idFile_MTP::Write(const void* buffer, const unsigned int len) { return WriteOfs(static_cast(position), buffer, len); } -int idFile_MTP::WriteOfs(const std::int64_t offset, const void* buffer, - const int len) { +unsigned int idFile_MTP::WriteOfs(const std::int64_t offset, + const void* buffer, const unsigned int len) { if (buffer == nullptr || len <= 0 || offset < 0 - || (mode != MTP_FS_WRITE && mode != MTP_FS_READ_WRITE - && mode != MTP_FS_APPEND)) return 0; + || (mode != FS_WRITE && mode != FS_READ_WRITE + && mode != FS_APPEND)) return 0; std::lock_guard lock(mtpMutex); - int total = 0; + unsigned int total = 0; while (total < len) { - const int chunk = std::min(mtpWriteSize, len - total); + const unsigned int chunk = (std::min)( + static_cast(mtpWriteSize), len - total); if (!SendRequest(static_cast(offset) + total, - static_cast(chunk), OP_WRITE, name.c_str()) + chunk, OP_WRITE, name.c_str()) || mtpTCP.WriteBlocking(static_cast(buffer) - + total, chunk, mtpTimeout) != chunk) { + + total, static_cast(chunk), mtpTimeout) + != static_cast(chunk)) { mtpTCP.Close(); break; } @@ -197,8 +207,8 @@ int idFile_MTP::WriteOfs(const std::int64_t offset, const void* buffer, mtpTCP.Close(); break; } - const int accepted = static_cast(GetUInt32BE(countBytes)); - if (accepted <= 0 || accepted > chunk) { + const unsigned int accepted = GetUInt32BE(countBytes); + if (accepted == 0 || accepted > chunk) { mtpTCP.Close(); break; } @@ -206,7 +216,7 @@ int idFile_MTP::WriteOfs(const std::int64_t offset, const void* buffer, if (accepted != chunk) break; } position = static_cast(offset) + total; - length = std::max(length, position); + length = (std::max)(length, position); return total; } @@ -218,15 +228,15 @@ void idFile_MTP::SetLength(const unsigned int len) { } } -int idFile_MTP::Length() const { - return length > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast(length); +std::int64_t idFile_MTP::Length() const { + return static_cast(length); } -int idFile_MTP::Tell() const { - return position > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast(position); +std::int64_t idFile_MTP::Tell() const { + return static_cast(position); } -int idFile_MTP::Seek(const long offset, const fsOrigin_t origin) { +int idFile_MTP::Seek(const std::int64_t offset, const fsOrigin_t origin) { std::int64_t target = 0; if (origin == FS_SEEK_CUR) { target = static_cast(position) + offset; diff --git a/source/shared/idlib/filesystem/file_mtp.h b/source/shared/idlib/filesystem/file_mtp.h index bffa4ae..db3d976 100644 --- a/source/shared/idlib/filesystem/file_mtp.h +++ b/source/shared/idlib/filesystem/file_mtp.h @@ -1,39 +1,30 @@ #pragma once -#include "idlib/precompiled.h" - -#ifdef nullptr -#undef nullptr -#endif +#include "file.h" +#include "../containers/list.h" #include -enum mtpFileMode_t { - MTP_FS_READ = 0, - MTP_FS_WRITE = 1, - MTP_FS_READ_WRITE = 2, - MTP_FS_READ_NO_BUFFERING = 3, - MTP_FS_APPEND = 4 -}; - -class idFile_MTP : public idFile { +class alignas(8) idFile_MTP : public idFile { public: idFile_MTP(); ~idFile_MTP() override; const char* GetName() const override { return name.c_str(); } const char* GetFullPath() const override { return fullPath.c_str(); } - int Read(void* buffer, int len) override; - int Write(const void* buffer, int len) override; - int Length() const override; - ID_TIME_T Timestamp() const override { return timestamp; } - int Tell() const override; - int Seek(long offset, fsOrigin_t origin) override; + unsigned int Read(void* buffer, unsigned int len) override; + unsigned int Write(const void* buffer, unsigned int len) override; + unsigned int ReadOfs(std::int64_t offset, void* buffer, + unsigned int len) override; + unsigned int WriteOfs(std::int64_t offset, const void* buffer, + unsigned int len) override; + std::int64_t Length() const override; + unsigned int Timestamp() const override { return timestamp; } + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; - bool Open(const char* filename, mtpFileMode_t openMode); - int ReadOfs(std::int64_t offset, void* buffer, int len); - int WriteOfs(std::int64_t offset, const void* buffer, int len); - void SetLength(unsigned int len); + bool Open(const char* filename, fsMode_t openMode); + void SetLength(unsigned int len) override; std::uint64_t Length64() const { return length; } std::uint64_t Tell64() const { return position; } bool List(const char* directory, const char* extension, @@ -57,12 +48,10 @@ private: static bool SendRequest(std::uint64_t offset, unsigned int requestLength, operation_t operation, const char* filename); - // BFG's idFile predates the recovered idTech 5 uniqID field. - unsigned int uniqID; idStr name; idStr fullPath; std::uint64_t position; - mtpFileMode_t mode; + fsMode_t mode; std::uint64_t length; unsigned int timestamp; }; diff --git a/source/shared/idlib/filesystem/file_nfs.cpp b/source/shared/idlib/filesystem/file_nfs.cpp index 38873b7..73be69f 100644 --- a/source/shared/idlib/filesystem/file_nfs.cpp +++ b/source/shared/idlib/filesystem/file_nfs.cpp @@ -162,13 +162,13 @@ bool HasExtension(const std::wstring& name, const char* extension) { } // namespace idFile_Nfs::idFile_Nfs() - : uniqID(0), openRemote(false), openPadding{}, fh{}, - fullPath("invalid"), mode(NFS_FS_READ), position(0), size(0), - timeStamp(0), demandSeek(false), seekPadding{}, nfsClient(nullptr), + : openRemote(false), fh{}, + fullPath("invalid"), mode(FS_READ), position(0), size(0), + timeStamp(0), demandSeek(false), nfsClient(nullptr), ro(false) {} idFile_Nfs::~idFile_Nfs() { - delete static_cast(nfsClient); + delete reinterpret_cast(nfsClient); nfsClient = nullptr; openRemote = false; } @@ -205,30 +205,30 @@ void idFile_Nfs::UnmountAll() { mounts.clear(); } -bool idFile_Nfs::Open(const char* path, const nfsFileMode_t openMode, +bool idFile_Nfs::Open(const char* path, const fsMode_t openMode, bool create, const bool createPath) { - delete static_cast(nfsClient); + delete reinterpret_cast(nfsClient); nfsClient = nullptr; openRemote = false; std::wstring native; bool mountReadOnly = false; if (!Resolve(path, native, &mountReadOnly)) return false; - const bool wantsWrite = openMode == NFS_FS_WRITE - || openMode == NFS_FS_READ_WRITE || openMode == NFS_FS_APPEND; + const bool wantsWrite = openMode == FS_WRITE + || openMode == FS_READ_WRITE || openMode == FS_APPEND; if (mountReadOnly && wantsWrite) return false; - if (openMode == NFS_FS_WRITE || openMode == NFS_FS_APPEND) create = true; + if (openMode == FS_WRITE || openMode == FS_APPEND) create = true; if (createPath && create && !CreateDirectories(native, false)) return false; DWORD access = GENERIC_READ; DWORD disposition = OPEN_EXISTING; - if (openMode == NFS_FS_WRITE) { + if (openMode == FS_WRITE) { access = GENERIC_WRITE | GENERIC_READ; disposition = CREATE_ALWAYS; - } else if (openMode == NFS_FS_READ_WRITE) { + } else if (openMode == FS_READ_WRITE) { access = GENERIC_WRITE | GENERIC_READ; disposition = create ? OPEN_ALWAYS : OPEN_EXISTING; - } else if (openMode == NFS_FS_APPEND) { + } else if (openMode == FS_APPEND) { access = GENERIC_WRITE | GENERIC_READ; disposition = OPEN_ALWAYS; } @@ -242,7 +242,7 @@ bool idFile_Nfs::Open(const char* path, const nfsFileMode_t openMode, delete state; return false; } - nfsClient = state; + nfsClient = reinterpret_cast(state); openRemote = true; ro = mountReadOnly; mode = openMode; @@ -253,13 +253,13 @@ bool idFile_Nfs::Open(const char* path, const nfsFileMode_t openMode, openRemote = false; return false; } - position = openMode == NFS_FS_APPEND ? size : 0; + position = openMode == FS_APPEND ? size : 0; return true; } -int idFile_Nfs::ReadOfs(const std::int64_t offset, void* buffer, - const int len) { - FileState* const state = static_cast(nfsClient); +unsigned int idFile_Nfs::ReadOfs(const std::int64_t offset, void* buffer, + const unsigned int len) { + FileState* const state = reinterpret_cast(nfsClient); if (state == nullptr || buffer == nullptr || len <= 0 || offset < 0) return 0; std::lock_guard lock(state->mutex); LARGE_INTEGER saved{}, target{}; @@ -273,9 +273,9 @@ int idFile_Nfs::ReadOfs(const std::int64_t offset, void* buffer, return success ? static_cast(amount) : 0; } -int idFile_Nfs::WriteOfs(const std::int64_t offset, const void* buffer, - const int len) { - FileState* const state = static_cast(nfsClient); +unsigned int idFile_Nfs::WriteOfs(const std::int64_t offset, + const void* buffer, const unsigned int len) { + FileState* const state = reinterpret_cast(nfsClient); if (state == nullptr || ro || buffer == nullptr || len <= 0 || offset < 0) return 0; std::lock_guard lock(state->mutex); LARGE_INTEGER saved{}, target{}; @@ -287,36 +287,36 @@ int idFile_Nfs::WriteOfs(const std::int64_t offset, const void* buffer, static_cast(len), &amount, nullptr); SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN); if (success) { - size = std::max(size, static_cast(offset) + amount); + size = (std::max)(size, static_cast(offset) + amount); RefreshMetadata(*state, size, timeStamp); } return success ? static_cast(amount) : 0; } -int idFile_Nfs::Read(void* buffer, const int len) { - const int amount = ReadOfs(static_cast(position), buffer, len); +unsigned int idFile_Nfs::Read(void* buffer, const unsigned int len) { + const unsigned int amount = ReadOfs( + static_cast(position), buffer, len); position += static_cast(amount); return amount; } -int idFile_Nfs::Write(const void* buffer, const int len) { - if (mode == NFS_FS_APPEND) position = size; - const int amount = WriteOfs(static_cast(position), buffer, len); +unsigned int idFile_Nfs::Write(const void* buffer, const unsigned int len) { + if (mode == FS_APPEND) position = size; + const unsigned int amount = WriteOfs( + static_cast(position), buffer, len); position += static_cast(amount); return amount; } -int idFile_Nfs::Length() const { - return size > static_cast(INT_MAX) ? INT_MAX - : static_cast(size); +std::int64_t idFile_Nfs::Length() const { + return static_cast(size); } -int idFile_Nfs::Tell() const { - return position > static_cast(INT_MAX) ? INT_MAX - : static_cast(position); +std::int64_t idFile_Nfs::Tell() const { + return static_cast(position); } -int idFile_Nfs::Seek(const long offset, const fsOrigin_t origin) { +int idFile_Nfs::Seek(const std::int64_t offset, const fsOrigin_t origin) { return Seek64(offset, origin); } @@ -334,7 +334,7 @@ int idFile_Nfs::Seek64(const std::int64_t offset, const fsOrigin_t origin) { void idFile_Nfs::SetLength(const unsigned int len) { SetLength64(len); } bool idFile_Nfs::SetLength64(const std::uint64_t len) { - FileState* const state = static_cast(nfsClient); + FileState* const state = reinterpret_cast(nfsClient); if (state == nullptr || ro || len > static_cast(INT64_MAX)) return false; std::lock_guard lock(state->mutex); LARGE_INTEGER saved{}, target{}; @@ -350,7 +350,7 @@ bool idFile_Nfs::SetLength64(const std::uint64_t len) { } void idFile_Nfs::Flush() { - FileState* const state = static_cast(nfsClient); + FileState* const state = reinterpret_cast(nfsClient); if (state != nullptr && !ro) FlushFileBuffers(state->handle); } diff --git a/source/shared/idlib/filesystem/file_nfs.h b/source/shared/idlib/filesystem/file_nfs.h index 6434e4b..98f35c4 100644 --- a/source/shared/idlib/filesystem/file_nfs.h +++ b/source/shared/idlib/filesystem/file_nfs.h @@ -1,24 +1,13 @@ #pragma once -#include "idlib/precompiled.h" - -#ifdef nullptr -#undef nullptr -#endif -#ifdef strcmp -#undef strcmp -#endif +#include "file.h" +#include "../containers/list.h" #include -enum nfsFileMode_t { - NFS_FS_READ = 0, - NFS_FS_WRITE = 1, - NFS_FS_READ_WRITE = 2, - NFS_FS_READ_NO_BUFFERING = 3, - NFS_FS_APPEND = 4 -}; +class idNfsClient; +#pragma pack(push, 4) class idFile_Nfs : public idFile { public: idFile_Nfs(); @@ -26,20 +15,22 @@ public: const char* GetName() const override { return fullPath.c_str(); } const char* GetFullPath() const override { return fullPath.c_str(); } - int Read(void* buffer, int len) override; - int Write(const void* buffer, int len) override; - int Length() const override; - ID_TIME_T Timestamp() const override { return timeStamp; } - int Tell() const override; - int Seek(long offset, fsOrigin_t origin) override; + unsigned int Read(void* buffer, unsigned int len) override; + unsigned int Write(const void* buffer, unsigned int len) override; + unsigned int ReadOfs(std::int64_t offset, void* buffer, + unsigned int len) override; + unsigned int WriteOfs(std::int64_t offset, const void* buffer, + unsigned int len) override; + std::int64_t Length() const override; + unsigned int Timestamp() const override { return timeStamp; } + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; void Flush() override; void ForceFlush() override; - bool Open(const char* path, nfsFileMode_t openMode, bool create = false, + bool Open(const char* path, fsMode_t openMode, bool create = false, bool createPath = false); - int ReadOfs(std::int64_t offset, void* buffer, int len); - int WriteOfs(std::int64_t offset, const void* buffer, int len); - void SetLength(unsigned int len); + void SetLength(unsigned int len) override; bool SetLength64(std::uint64_t len); std::uint64_t Length64() const { return size; } std::uint64_t Tell64() const { return position; } @@ -55,23 +46,25 @@ public: static bool RemoveFile(const char* path); static bool RenameFile(const char* oldPath, const char* newPath); -private: - struct NfsInternalFh { std::uint32_t pad[38]; }; + class NfsInternalFh { + public: + std::uint32_t pad[38]; + }; + +private: - unsigned int uniqID; bool openRemote; - unsigned char openPadding[3]; NfsInternalFh fh; idStr fullPath; - nfsFileMode_t mode; + fsMode_t mode; std::uint64_t position; std::uint64_t size; unsigned int timeStamp; bool demandSeek; - unsigned char seekPadding[3]; - void* nfsClient; + idNfsClient* nfsClient; bool ro; }; +#pragma pack(pop) #if INTPTR_MAX == INT32_MAX static_assert(sizeof(idFile_Nfs) == 232, diff --git a/source/shared/idlib/filesystem/file_permanent.cpp b/source/shared/idlib/filesystem/file_permanent.cpp new file mode 100644 index 0000000..6783712 --- /dev/null +++ b/source/shared/idlib/filesystem/file_permanent.cpp @@ -0,0 +1,162 @@ +#include "file_permanent.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace { +HANDLE FileHandle(const void* handle) { + return static_cast(const_cast(handle)); +} +} + +idFile_Permanent::idFile_Permanent(const char* relativePath, + const char* osPath, const fsMode_t fileMode, const bool create) + : idFile(), name(relativePath), fullPath(osPath), mode(fileMode), + fileSize(0), sectorSize(1), device(FS_DEVICE_HARD_DISK_DRIVE), + handle(nullptr) { + DWORD access = GENERIC_READ; + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE; + DWORD disposition = OPEN_EXISTING; + if (mode == FS_WRITE) { + access = GENERIC_WRITE; + share = 0; + disposition = CREATE_ALWAYS; + } else if (mode == FS_READ_WRITE) { + access = GENERIC_READ | GENERIC_WRITE; + disposition = create ? OPEN_ALWAYS : OPEN_EXISTING; + } else if (mode == FS_APPEND) { + access = FILE_APPEND_DATA; + share |= FILE_SHARE_DELETE; + disposition = OPEN_ALWAYS; + } + HANDLE file = CreateFileA(fullPath.c_str(), access, + share, nullptr, disposition, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return; + handle = file; + LARGE_INTEGER size; + if (GetFileSizeEx(file, &size)) fileSize = size.QuadPart; + if (mode == FS_APPEND) Seek(0, FS_SEEK_END); +} + +idFile_Permanent::~idFile_Permanent() { + if (handle != nullptr) CloseHandle(FileHandle(handle)); + handle = nullptr; +} + +unsigned int idFile_Permanent::Read(void* data, const unsigned int length) { + if (handle == nullptr || data == nullptr) return 0; + DWORD amount = 0; + return ReadFile(FileHandle(handle), data, length, &amount, nullptr) + ? amount : 0; +} + +unsigned int idFile_Permanent::Write(const void* data, + const unsigned int length) { + if (handle == nullptr || data == nullptr || mode == FS_READ) return 0; + DWORD amount = 0; + if (!WriteFile(FileHandle(handle), data, length, &amount, nullptr)) return 0; + const std::int64_t position = Tell(); + if (position > fileSize) fileSize = position; + return amount; +} + +unsigned int idFile_Permanent::ReadOfs(const std::int64_t offset, void* data, + const unsigned int length) { + if (handle == nullptr || offset < 0) return 0; + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast( + static_cast(offset) >> 32); + DWORD amount = 0; + return ReadFile(FileHandle(handle), data, length, &amount, &overlapped) + ? amount : 0; +} + +unsigned int idFile_Permanent::WriteOfs(const std::int64_t offset, + const void* data, const unsigned int length) { + if (handle == nullptr || offset < 0 || mode == FS_READ) return 0; + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast( + static_cast(offset) >> 32); + DWORD amount = 0; + if (!WriteFile(FileHandle(handle), data, length, &amount, &overlapped)) return 0; + const std::int64_t end = offset + amount; + if (end > fileSize) fileSize = end; + return amount; +} + +bool idFile_Permanent::Lock(const std::int64_t offset, + const unsigned int length, const fsLock_t lock) { + if (handle == nullptr || offset < 0) return false; + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast( + static_cast(offset) >> 32); + return LockFileEx(FileHandle(handle), + lock == FS_LOCK_EXCLUSIVE ? LOCKFILE_EXCLUSIVE_LOCK : 0, 0, + length, 0, &overlapped) != FALSE; +} + +bool idFile_Permanent::Unlock(const std::int64_t offset, + const unsigned int length) { + if (handle == nullptr || offset < 0) return false; + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast( + static_cast(offset) >> 32); + return UnlockFileEx(FileHandle(handle), 0, length, 0, &overlapped) != FALSE; +} + +std::int64_t idFile_Permanent::Length() const { + return fileSize; +} + +void idFile_Permanent::SetLength(const unsigned int length) { + if (handle == nullptr || mode == FS_READ) return; + const std::int64_t oldPosition = Tell(); + if (Seek(length, FS_SEEK_SET) == 0 && SetEndOfFile(FileHandle(handle))) + fileSize = length; + Seek(oldPosition < fileSize ? oldPosition : fileSize, FS_SEEK_SET); +} + +std::int64_t idFile_Permanent::Tell() const { + if (handle == nullptr) return 0; + LARGE_INTEGER distance = {}, position = {}; + return SetFilePointerEx(FileHandle(handle), distance, &position, FILE_CURRENT) + ? position.QuadPart : 0; +} + +int idFile_Permanent::Seek(const std::int64_t offset, const fsOrigin_t origin) { + if (handle == nullptr) return -1; + DWORD method = FILE_BEGIN; + if (origin == FS_SEEK_CUR) method = FILE_CURRENT; + else if (origin == FS_SEEK_END) method = FILE_END; + LARGE_INTEGER distance; + distance.QuadPart = offset; + return SetFilePointerEx(FileHandle(handle), distance, nullptr, method) + ? 0 : -1; +} + +unsigned int idFile_Permanent::Timestamp() const { + if (handle == nullptr) return 0; + FILETIME writeTime; + if (!GetFileTime(FileHandle(handle), nullptr, nullptr, &writeTime)) return 0; + ULARGE_INTEGER value; + value.LowPart = writeTime.dwLowDateTime; + value.HighPart = writeTime.dwHighDateTime; + const std::uint64_t unixTicks = value.QuadPart > 116444736000000000ULL + ? value.QuadPart - 116444736000000000ULL : 0; + return static_cast(unixTicks / 10000000ULL); +} + +void idFile_Permanent::Flush() { + if (handle != nullptr) FlushFileBuffers(FileHandle(handle)); +} + +void idFile_Permanent::ForceFlush() { + Flush(); +} diff --git a/source/shared/idlib/filesystem/file_permanent.h b/source/shared/idlib/filesystem/file_permanent.h index 7f247c9..ddcf408 100644 --- a/source/shared/idlib/filesystem/file_permanent.h +++ b/source/shared/idlib/filesystem/file_permanent.h @@ -1,13 +1,45 @@ #pragma once -// The portable permanent-file implementation currently comes from the BFG -// framework baseline. This compatibility include restores the original -// shared/idlib path used by tungsten sources while its wider idTech 5 layout -// is introduced incrementally at the filesystem boundary. -#include "idlib/precompiled.h" +#include "file.h" -#ifdef nullptr -#undef nullptr +class alignas(8) idFile_Permanent : public idFile { +public: + idFile_Permanent(const char* relativePath, const char* osPath, + fsMode_t fileMode, bool create); + ~idFile_Permanent() override; + + const char* GetName() const override { return name.c_str(); } + const char* GetFullPath() const override { return fullPath.c_str(); } + unsigned int Read(void* data, unsigned int length) override; + unsigned int Write(const void* data, unsigned int length) override; + unsigned int ReadOfs(std::int64_t offset, void* data, + unsigned int length) override; + unsigned int WriteOfs(std::int64_t offset, const void* data, + unsigned int length) override; + bool Lock(std::int64_t offset, unsigned int length, fsLock_t lock) override; + bool Unlock(std::int64_t offset, unsigned int length) override; + std::int64_t Length() const override; + void SetLength(unsigned int length) override; + std::int64_t Tell() const override; + int Seek(std::int64_t offset, fsOrigin_t origin) override; + unsigned int Timestamp() const override; + void Flush() override; + void ForceFlush() override; + int GetSectorSize() const override { return sectorSize; } + fsDevice_t GetDevice() const override { return device; } + bool IsOSNative() const override { return true; } + bool IsOpen() const { return handle != nullptr; } + + idStr name; + idStr fullPath; + fsMode_t mode; + std::int64_t fileSize; + int sectorSize; + fsDevice_t device; + void* handle; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idFile_Permanent) == 104, + "Recovered idFile_Permanent ABI changed"); #endif - -inline int idFilePermanentSectorSize(const idFile_Permanent&) { return 1; } diff --git a/source/shared/idlib/filesystem/file_savegame.h b/source/shared/idlib/filesystem/file_savegame.h new file mode 100644 index 0000000..236473e --- /dev/null +++ b/source/shared/idlib/filesystem/file_savegame.h @@ -0,0 +1,163 @@ +#pragma once + +#include "file.h" +#include "../sys/sys_threading.h" +#include "../text/strstatic.h" + +#include + +struct internal_state; + +struct z_stream_s { + std::uint8_t* next_in; + unsigned int avail_in; + unsigned int total_in; + std::uint8_t* next_out; + unsigned int avail_out; + unsigned int total_out; + char* msg; + internal_state* state; + void* (*zalloc)(void* opaque, unsigned int items, unsigned int size); + void (*zfree)(void* opaque, void* address); + void* opaque; + int data_type; + unsigned int adler; + unsigned int reserved; +}; + +struct blockForIO_t { + std::uint8_t* data; + unsigned int bytes; +}; + +class idFile_SaveGame : public idFile_Memory { +public: + idFile_SaveGame(const char* name, int type); + ~idFile_SaveGame() override = default; + + int type; + bool error; +}; + +class idFile_SaveGamePipelined; + +class idSGFreadThread : public idSysThread { +public: + explicit idSGFreadThread(idFile_SaveGamePipelined* file = nullptr) + : sgf(file) {} + ~idSGFreadThread() override; + int Run() override; + idFile_SaveGamePipelined* sgf; +}; + +class idSGFwriteThread : public idSysThread { +public: + explicit idSGFwriteThread(idFile_SaveGamePipelined* file = nullptr) + : sgf(file) {} + ~idSGFwriteThread() override; + int Run() override; + idFile_SaveGamePipelined* sgf; +}; + +class idSGFdecompressThread : public idSysThread { +public: + explicit idSGFdecompressThread(idFile_SaveGamePipelined* file = nullptr) + : sgf(file) {} + ~idSGFdecompressThread() override; + int Run() override; + idFile_SaveGamePipelined* sgf; +}; + +class idSGFcompressThread : public idSysThread { +public: + explicit idSGFcompressThread(idFile_SaveGamePipelined* file = nullptr) + : sgf(file) {} + ~idSGFcompressThread() override; + int Run() override; + idFile_SaveGamePipelined* sgf; +}; + +class alignas(8) idFile_SaveGamePipelined : public idFile { +public: + enum mode_t : int { + WRITE = 0x1, + READ = 0x2 + }; + + idFile_SaveGamePipelined(); + ~idFile_SaveGamePipelined() override; + + const char* GetName() const override { return name.c_str(); } + const char* GetFullPath() const override { return osPath.c_str(); } + unsigned int Read(void* buffer, unsigned int length) override; + unsigned int Write(const void* buffer, unsigned int length) override; + std::int64_t Length() const override { return compressedLength; } + void SetLength(unsigned int length) override { compressedLength = length; } + std::int64_t Tell() const override; + unsigned int Timestamp() const override { return 0; } + void Flush() override {} + void ForceFlush() override {} + + bool OpenForReading(const char* const fileName, bool threaded); + bool OpenForWriting(const char* const fileName, bool threaded); + bool NextReadBlock(blockForIO_t* block, unsigned int checksum); + bool NextWriteBlock(blockForIO_t* block); + void Finish(); + void Abort(); + + static bool cancelToTerminate; + + std::int64_t ioPos; + idStr name; + idStr osPath; + mode_t mode; + unsigned int compressedLength; + std::uint8_t uncompressed[524288]; + unsigned int uncompressedProducedBytes; + unsigned int uncompressedConsumedBytes; + std::uint8_t compressed[262144]; + unsigned int compressedProducedBytes; + unsigned int compressedConsumedBytes; + std::uint8_t* dataZlib; + unsigned int bytesZlib; + std::uint8_t* dataIO; + unsigned int bytesIO; + z_stream_s zStream; + int zLibFlushType; + bool zStreamEndHit; + int numChecksums; + idFile* nativeFile; + bool nativeFileEndHit; + bool finished; + idSGFreadThread* readThread; + idSGFwriteThread* writeThread; + idSGFdecompressThread* decompressThread; + idSGFcompressThread* compressThread; + idSysSignal blockRequested; + idSysSignal blockAvailable; + idSysSignal blockFinished; + idStrStatic<32> buildVersion; + std::int16_t pointerSize; + std::int16_t saveFormatVersion; + +private: + void CompressBlock(); + void DecompressBlock(); + void FlushCompressedBlock(); + void FlushUncompressedBlock(); + void PumpCompressedBlock(); + void ReadBlock(); + void WriteBlock(); +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(z_stream_s) == 56, "Recovered z_stream_s ABI changed"); +static_assert(sizeof(blockForIO_t) == 8, + "Recovered blockForIO_t ABI changed"); +static_assert(sizeof(idFile_SaveGame) == 340, + "Recovered idFile_SaveGame ABI changed"); +static_assert(sizeof(idSGFreadThread) == 60, + "Recovered savegame worker ABI changed"); +static_assert(sizeof(idFile_SaveGamePipelined) == 786728, + "Recovered pipelined savegame ABI changed"); +#endif diff --git a/source/shared/idlib/filesystem/filesystem.cpp b/source/shared/idlib/filesystem/filesystem.cpp new file mode 100644 index 0000000..d0c7be9 --- /dev/null +++ b/source/shared/idlib/filesystem/filesystem.cpp @@ -0,0 +1,898 @@ +#include "filesystem.h" + +#include "file_inzip.h" +#include "file_mtp.h" +#include "file_nfs.h" +#include "file_permanent.h" +#include "../lib_print.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +bool PrefixNoCase(const char* text, const char* prefix) { + if (text == nullptr || prefix == nullptr) return false; + return _strnicmp(text, prefix, std::strlen(prefix)) == 0; +} + +bool EqualNoCase(const char* left, const char* right) { + return _stricmp(left == nullptr ? "" : left, + right == nullptr ? "" : right) == 0; +} + +bool IsOSPathRecovered(const char* path) { + if (path == nullptr || path[0] == '\0') return false; + static const char* const prefixes[] = { + "mtp:", "nfs:", "devkit:", "game:", "cache:", "dlc:", "saves:" + }; + for (const char* prefix : prefixes) { + if (PrefixNoCase(path, prefix)) return true; + } + return ((std::isalpha(static_cast(path[0])) != 0) + && path[1] == ':') + || path[0] == '\\' || path[0] == '/'; +} + +bool IsNativeOSPath(const char* path) { + return IsOSPathRecovered(path) + && !PrefixNoCase(path, "mtp:") && !PrefixNoCase(path, "nfs:"); +} + +std::string SlashesToWindows(std::string path) { + std::replace(path.begin(), path.end(), '/', '\\'); + return path; +} + +std::string SlashesToGame(std::string path) { + std::replace(path.begin(), path.end(), '\\', '/'); + return path; +} + +std::string JoinPath(const char* base, const char* relativePath) { + const char* relative = relativePath == nullptr ? "" : relativePath; + if (IsOSPathRecovered(relative)) return SlashesToWindows(relative); + + std::string result = base == nullptr ? "" : base; + if (!result.empty() && result.back() != '\\' && result.back() != '/') + result.push_back('\\'); + while (*relative == '\\' || *relative == '/') ++relative; + result += relative; + result = SlashesToWindows(result); + + if (IsNativeOSPath(result.c_str())) { + char fullPath[32768] = {}; + const DWORD amount = GetFullPathNameA(result.c_str(), + static_cast(sizeof(fullPath)), fullPath, nullptr); + if (amount > 0 && amount < sizeof(fullPath)) result.assign(fullPath); + } + return result; +} + +std::string ParentPath(const std::string& path) { + const std::size_t separator = path.find_last_of("\\/"); + if (separator == std::string::npos) return std::string(); + if (separator == 2 && path.size() >= 3 && path[1] == ':') + return path.substr(0, 3); + return path.substr(0, separator); +} + +void CopyString(char* destination, const int destinationSize, + const char* source) { + if (destination == nullptr || destinationSize <= 0) return; + _snprintf_s(destination, static_cast(destinationSize), + _TRUNCATE, "%s", source == nullptr ? "" : source); +} + +std::string ExecutableDirectory() { + char path[32768] = {}; + const DWORD amount = GetModuleFileNameA(nullptr, path, + static_cast(sizeof(path))); + if (amount == 0 || amount >= sizeof(path)) return "."; + return ParentPath(path); +} + +std::string EnvironmentPath(const char* name) { + char value[32768] = {}; + const DWORD amount = GetEnvironmentVariableA(name, value, + static_cast(sizeof(value))); + return amount > 0 && amount < sizeof(value) ? value : ""; +} + +unsigned int NativeTimestamp(const char* path) { + WIN32_FILE_ATTRIBUTE_DATA attributes = {}; + if (!GetFileAttributesExA(path, GetFileExInfoStandard, &attributes)) + return static_cast(-1); + ULARGE_INTEGER value = {}; + value.LowPart = attributes.ftLastWriteTime.dwLowDateTime; + value.HighPart = attributes.ftLastWriteTime.dwHighDateTime; + const std::uint64_t ticks = value.QuadPart > 116444736000000000ULL + ? value.QuadPart - 116444736000000000ULL : 0; + return static_cast(ticks / 10000000ULL); +} + +bool IsDirectoryNative(const char* path) { + const DWORD attributes = GetFileAttributesA(path); + return attributes != INVALID_FILE_ATTRIBUTES + && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool ExtensionMatches(const char* name, const char* extension) { + if (extension == nullptr || extension[0] == '\0') return true; + const char* wanted = extension; + while (*wanted == '*' || *wanted == '.') ++wanted; + const char* actual = std::strrchr(name, '.'); + if (actual == nullptr) return false; + return EqualNoCase(actual + 1, wanted); +} + +std::string WithExtension(std::string path, const char* extension) { + const std::size_t separator = path.find_last_of("\\/"); + const std::size_t dot = path.find_last_of('.'); + if (dot != std::string::npos + && (separator == std::string::npos || dot > separator)) { + path.erase(dot); + } + if (extension != nullptr && extension[0] != '\0') { + if (extension[0] != '.') path.push_back('.'); + path += extension; + } + return path; +} + +const char* StripRecoveredBasePrefix(const char* path) { + if (path == nullptr) return ""; + if (PrefixNoCase(path, "base/") || PrefixNoCase(path, "base\\")) + return path + 5; + if ((path[0] == '/' || path[0] == '\\') + && (PrefixNoCase(path + 1, "base/") + || PrefixNoCase(path + 1, "base\\"))) { + return path + 6; + } + while ((path[0] == '/' && path[1] != '/') + || (path[0] == '\\' && path[1] != '\\')) ++path; + return path; +} + +void ListNativeFiles(const char* directory, const char* extension, + idList& list) { + const std::string search = JoinPath(directory, "*"); + WIN32_FIND_DATAA findData = {}; + HANDLE handle = FindFirstFileA(search.c_str(), &findData); + if (handle == INVALID_HANDLE_VALUE) return; + const bool wantDirectories = extension != nullptr + && extension[0] == '/' && extension[1] == '\0'; + do { + const bool isDirectory = + (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (wantDirectories != isDirectory) continue; + if (!wantDirectories && !ExtensionMatches(findData.cFileName, extension)) + continue; + list.Append(idStr(findData.cFileName)); + } while (FindNextFileA(handle, &findData)); + FindClose(handle); +} + +} // namespace + +idFileSystemLocal fileSystemLocal; +idFileSystem* fileSystem = &fileSystemLocal; + +idFileSystem::~idFileSystem() = default; + +idFileList::idFileList() : basePath(), list(16) {} + +idCachedZipFile::idCachedZipFile() + : zipFileName(), relativeZipFileName(), handle(nullptr), numFiles(0), + fileList(nullptr), next(nullptr) { + std::memset(hashTable, 0, sizeof(hashTable)); +} + +idCachedZipFile::~idCachedZipFile() { + delete[] fileList; + fileList = nullptr; + handle = nullptr; +} + +unsigned int idFile_Stat::Read(void*, unsigned int) { + idLibPrint::Error("Read is not allowed with idFile_Stat"); +} + +unsigned int idFile_Stat::Write(const void*, unsigned int) { + idLibPrint::Error("Write is not allowed with idFile_Stat"); +} + +unsigned int idFile_Stat::ReadOfs(std::int64_t, void*, unsigned int) { + idLibPrint::Error("ReadOfs is not allowed with idFile_Stat"); +} + +unsigned int idFile_Stat::WriteOfs(std::int64_t, const void*, unsigned int) { + idLibPrint::Error("WriteOfs is not allowed with idFile_Stat"); +} + +void idFile_Stat::SetLength(unsigned int) { + idLibPrint::Error("SetLength is not allowed with idFile_Stat"); +} + +int idFile_Stat::Seek(std::int64_t, fsOrigin_t) { + idLibPrint::Error("Seeking is not allowed with idFile_Stat"); +} + +idFileSystemLocal::idFileSystemLocal() + : searchPaths(16), cachedZipFiles(nullptr), loadCount(0), loadStack(0), + pathBase(), pathCache(), pathInstall(), pathSave(), preCallback(nullptr), + postCallback(nullptr), cacheStatCallback(nullptr), cacheCallback(nullptr), + patchCallback(nullptr) {} + +idFileSystemLocal::~idFileSystemLocal() { + ClearZipCache(); +} + +bool idFileSystemLocal::CopyGameFile(idFile* source, idFile* destination) { + if (source == nullptr || destination == nullptr) return false; + unsigned char buffer[4096]; + std::int64_t total = 0; + for (;;) { + const unsigned int amount = source->Read(buffer, sizeof(buffer)); + if (amount == 0) break; + if (destination->Write(buffer, amount) != amount) { + idLibPrint::Warning("Write didn't match requested size"); + return false; + } + total += amount; + } + source->Seek(0, FS_SEEK_SET); + if (total != source->Length()) + idLibPrint::Warning("Total bytes written doesn't match filesize"); + return total == source->Length(); +} + +int idFileSystemLocal::ReadFile(const char* relativePath, void** buffer, + unsigned int* timestamp) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (relativePath == nullptr || relativePath[0] == '\0') { + idLibPrint::Warning("idFileSystemLocal::ReadFile with empty name"); + return -1; + } + if (timestamp != nullptr) *timestamp = static_cast(-1); + if (buffer != nullptr) *buffer = nullptr; + idFileLocal file(OpenFileRead(relativePath, buffer != nullptr, false)); + if (file.file == nullptr) return -1; + const std::int64_t fileLength = file->Length(); + if (fileLength < 0 || fileLength > INT_MAX) return -1; + const int length = static_cast(fileLength); + if (timestamp != nullptr) *timestamp = file->Timestamp(); + if (buffer != nullptr) { + ++loadCount; + ++loadStack; + unsigned char* data = static_cast( + std::malloc(static_cast(length) + 1)); + if (data == nullptr) { + --loadStack; + return -1; + } + const unsigned int amount = file->Read(data, + static_cast(length)); + if (amount != static_cast(length)) { + std::free(data); + --loadStack; + return -1; + } + data[length] = 0; + *buffer = data; + } + return length; +} + +void idFileSystemLocal::FreeFile(void* buffer) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (buffer == nullptr) + idLibPrint::FatalError("idFileSystemLocal::FreeFile( NULL )"); + --loadStack; + std::free(buffer); +} + +const char* idFileSystemLocal::GetBasePathStr(const fsPath_t basePath) const { + switch (basePath) { + case FSPATH_BASE: return pathBase.c_str(); + case FSPATH_CACHE: + if (pathCache.Length() > 0) return pathCache.c_str(); + // Recovered code deliberately falls through to the save path. + case FSPATH_SAVE: + return pathSave.Length() > 0 ? pathSave.c_str() : pathBase.c_str(); + case FSPATH_INSTALL: return pathInstall.c_str(); + default: return ""; + } +} + +void idFileSystemLocal::GetNumberedFilename(int& lastNumber, + const char* base, const char* extension, idStr& outputFilename, + const bool overrideProduction) { + if (++lastNumber > 99999) lastNumber = 99999; + while (lastNumber < 99999) { + outputFilename.Format("%s%05i.%s", base == nullptr ? "" : base, + lastNumber, extension == nullptr ? "" : extension); + if (!FileExists(outputFilename.c_str(), overrideProduction)) break; + ++lastNumber; + } +} + +bool idFileSystemLocal::IsInitialized() const { + return searchPaths.Num() > 0; +} + +idFile* idFileSystemLocal::OpenFromMTP(const char* path, fsMode_t mode) { + idFile_MTP* file = new idFile_MTP(); + const char* remotePath = PrefixNoCase(path, "mtp:") ? path + 4 : path; + if (file->Open(remotePath, mode)) return file; + delete file; + return nullptr; +} + +idFile* idFileSystemLocal::OpenFromNFS(const char* path, fsMode_t mode, + bool create) { + idFile_Nfs* file = new idFile_Nfs(); + if (file->Open(path, mode, create, create)) return file; + delete file; + return nullptr; +} + +bool idFileSystemLocal::IsRelativePath(const char* path) const { + return !IsOSPathRecovered(path); +} + +void idFileSystemLocal::CreateOSPath(const char* osPath) { + if (osPath == nullptr || osPath[0] == '\0') return; + if (PrefixNoCase(osPath, "nfs:")) { + idFile_Nfs::CreateOsPath(osPath); + return; + } + if (!IsNativeOSPath(osPath)) return; + std::string path = SlashesToWindows(osPath); + for (std::size_t index = 1; index < path.size(); ++index) { + if (path[index] != '\\') continue; + if (index == 2 && path[1] == ':') continue; + if (index == 1 && path[0] == '\\') continue; + const char saved = path[index]; + path[index] = '\0'; + if (path[0] != '\0') CreateDirectoryA(path.c_str(), nullptr); + path[index] = saved; + } +} + +idStr idFileSystemLocal::BuildOSPath(const char* base, + const char* relativePath) const { + return idStr(JoinPath(base, relativePath).c_str()); +} + +idStr idFileSystemLocal::BuildOSPath(const fsPath_t basePath, + const char* relativePath) const { + const std::string gameRoot = JoinPath(GetBasePathStr(basePath), "base"); + return idStr(JoinPath(gameRoot.c_str(), relativePath).c_str()); +} + +idStr idFileSystemLocal::GeneratedPath(const char* appendedPath) const { + const std::string generated = JoinPath("generated", appendedPath); + return idStr(SlashesToGame(generated).c_str()); +} + +bool idFileSystemLocal::FixLongFilename(const char* basePath, + const char* extension, const char* inputPath, char* fixedPath, + const int fixedPathSize) const { + if (fixedPath == nullptr || fixedPathSize <= 0) return false; + const char* relative = inputPath == nullptr ? "" : inputPath; + if (PrefixNoCase(relative, "../../")) relative += 6; + + std::string input = WithExtension(SlashesToGame(relative), extension); + const std::size_t slash = input.find_last_of('/'); + const std::string directory = slash == std::string::npos + ? "" : input.substr(0, slash + 1); + std::string filename = slash == std::string::npos + ? input : input.substr(slash + 1); + const std::size_t dot = filename.find_last_of('.'); + const std::string suffix = dot == std::string::npos + ? "" : filename.substr(dot); + std::string stem = dot == std::string::npos + ? filename : filename.substr(0, dot); + + std::string recoveredPath = directory; + if (filename.size() >= 38) { + while (stem.size() > 30) { + recoveredPath += stem.substr(0, 30); + recoveredPath.push_back('/'); + stem.erase(0, 30); + } + } + recoveredPath += stem + suffix; + const std::string result = JoinPath(basePath, recoveredPath.c_str()); + CopyString(fixedPath, fixedPathSize, result.c_str()); + return true; +} + +bool idFileSystemLocal::OSPathToRelativePath(const char* osPath, + char* relativePath, const int relativePathSize) { + if (relativePath == nullptr || relativePathSize <= 0) return false; + relativePath[0] = '\0'; + if (osPath == nullptr) return false; + for (int index = 0; index < searchPaths.Num(); ++index) { + const std::string root = SlashesToWindows(searchPaths[index].c_str()); + const std::string candidate = SlashesToWindows(osPath); + if (_strnicmp(root.c_str(), candidate.c_str(), root.size()) != 0) + continue; + const char* suffix = candidate.c_str() + root.size(); + while (*suffix == '\\' || *suffix == '/') ++suffix; + const std::string gamePath = SlashesToGame(suffix); + CopyString(relativePath, relativePathSize, gamePath.c_str()); + return true; + } + idLibPrint::Warning("idFileSystem::OSPathToRelativePath failed on %s", + osPath); + return false; +} + +void idFileSystemLocal::RelativePathToOSPath(const char* relativePath, + char* osPath, const int osPathSize, const fsPath_t basePath) const { + const idStr result = BuildOSPath(basePath, relativePath); + CopyString(osPath, osPathSize, result.c_str()); +} + +idStr idFileSystemLocal::RelativePathToOSPath(const char* relativePath, + const fsPath_t basePath) const { + return BuildOSPath(basePath, relativePath); +} + +bool idFileSystemLocal::RemoveDir(const char* relativePath) { + bool result = true; + if (pathSave.Length() > 0) { + const idStr savePath = BuildOSPath(FSPATH_SAVE, relativePath); + result = PrefixNoCase(savePath.c_str(), "nfs:") + ? idFile_Nfs::RemoveFile(savePath.c_str()) + : RemoveDirectoryA(savePath.c_str()) != FALSE; + } + const idStr basePath = BuildOSPath(FSPATH_BASE, relativePath); + const bool baseResult = PrefixNoCase(basePath.c_str(), "nfs:") + ? idFile_Nfs::RemoveFile(basePath.c_str()) + : RemoveDirectoryA(basePath.c_str()) != FALSE; + return result && baseResult; +} + +bool idFileSystemLocal::RenameFile(const char* relativePath, + const char* newName, const fsPath_t basePath) { + const idStr oldPath = BuildOSPath(basePath, relativePath); + const idStr newPath = BuildOSPath(basePath, newName); + if (PrefixNoCase(relativePath, "nfs:")) + return idFile_Nfs::RenameFile(oldPath.c_str(), newPath.c_str()); + if (MoveFileExA(oldPath.c_str(), newPath.c_str(), + MOVEFILE_REPLACE_EXISTING) != FALSE) return true; + idLibPrint::Warning("RenameFile( %s, %s ) error %lu", newPath.c_str(), + oldPath.c_str(), GetLastError()); + return false; +} + +bool idFileSystemLocal::IsWritable(const char* relativePath) const { + const idStr path = BuildOSPath(FSPATH_BASE, relativePath); + const DWORD attributes = GetFileAttributesA(path.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES) + return (attributes & FILE_ATTRIBUTE_READONLY) == 0; + const std::string parent = ParentPath(path.c_str()); + const DWORD parentAttributes = GetFileAttributesA(parent.c_str()); + return parentAttributes != INVALID_FILE_ATTRIBUTES + && (parentAttributes & FILE_ATTRIBUTE_READONLY) == 0; +} + +sysFolder_t idFileSystemLocal::IsFolder(const char* relativePath, + const fsPath_t basePath) const { + const idStr path = BuildOSPath(basePath, relativePath); + const DWORD attributes = GetFileAttributesA(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES) return FOLDER_ERROR; + return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 + ? FOLDER_YES : FOLDER_NO; +} + +void idFileSystemLocal::ListOSFiles(const char* directory, + const char* extension, idList& list) { + const char* const filter = extension == nullptr ? "" : extension; + if (PrefixNoCase(directory, "nfs:")) { + idFile_Nfs::ListFiles(directory, filter, list); + } else if (PrefixNoCase(directory, "mtp:")) { + idFile_MTP file; + file.List(directory, filter, list); + } else { + ListNativeFiles(directory, filter, list); + } +} + +idCachedZipFile* idFileSystemLocal::LoadZipFile(const char*, const char*) { + // The recovered implementation is activated with recovered unzip.cpp. + return nullptr; +} + +std::int64_t idFileSystemLocal::GetFileLength(const char* relativePath) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization"); + if (relativePath == nullptr || relativePath[0] == '\0') { + idLibPrint::Warning("idFileSystemLocal::GetFileLength with empty name"); + return -1; + } + idFileLocal file(OpenFileRead(relativePath, false, false)); + return file.file == nullptr ? -1 : file->Length(); +} + +idFile* idFileSystemLocal::OpenFileAppend(const char* relativePath, + const fsPath_t basePath) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + const idStr osPath = BuildOSPath(basePath, relativePath); + if (PrefixNoCase(osPath.c_str(), "nfs:")) + return OpenFromNFS(osPath.c_str(), FS_APPEND, true); + if (PrefixNoCase(osPath.c_str(), "mtp:")) + return OpenFromMTP(osPath.c_str(), FS_APPEND); + CreateOSPath(osPath.c_str()); + idFile_Permanent* file = new idFile_Permanent(relativePath, + osPath.c_str(), FS_APPEND, false); + if (file->IsOpen()) return file; + delete file; + return nullptr; +} + +void idFileSystemLocal::SetFilePreCallback(filePreCallback_t callback) { + preCallback = callback; +} + +void idFileSystemLocal::SetFilePostCallback(filePostCallback_t callback) { + postCallback = callback; +} + +void idFileSystemLocal::SetFileCacheCallback(fileCacheCallback_t statCallback, + fileCacheCallback_t callback) { + cacheStatCallback = statCallback; + cacheCallback = callback; +} + +void idFileSystemLocal::SetFilePatchCallback(filePatchCallback_t callback) { + patchCallback = callback; +} + +bool idFileSystemLocal::CopyGameFile(const char* from, const char* to, + const fsPath_t basePath) { + idFileLocal source(OpenFileRead(from, false, false)); + idFileLocal destination(OpenFileWritePermanent(to, basePath)); + return source.file != nullptr && destination.file != nullptr + && CopyGameFile(source.file, destination.file); +} + +idFile_InZip* idFileSystemLocal::ReadFileFromZip(idCachedZipFile*, + const char*, bool) { + // The recovered implementation is activated with recovered file_inzip.cpp. + return nullptr; +} + +idFile* idFileSystemLocal::OpenFileRead(const char* relativePath, + const bool allowCopyFiles, const bool uncompressedOnly) { + if (patchCallback != nullptr) { + idFile* patched = patchCallback(relativePath); + if (patched != nullptr) return patched; + } + if (preCallback != nullptr && allowCopyFiles) { + idFile* preOpened = preCallback(relativePath); + const std::size_t length = relativePath == nullptr + ? 0 : std::strlen(relativePath); + const bool loadout = length >= 8 + && EqualNoCase(relativePath + length - 8, ".loadout"); + if (preOpened != nullptr || !loadout) return preOpened; + } + if (cacheCallback != nullptr && allowCopyFiles) { + idFile* cached = cacheCallback(relativePath); + if (cached != nullptr) return cached; + } else if (cacheStatCallback != nullptr && !allowCopyFiles) { + idFile* stat = cacheStatCallback(relativePath); + if (stat != nullptr) return stat; + } + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (relativePath == nullptr) + idLibPrint::FatalError( + "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n"); + + const char* path = StripRecoveredBasePrefix(relativePath); + if (path[0] == '\0') return nullptr; + idFile* result = nullptr; + idStr foundPath; + for (int index = 0; index < searchPaths.Num() && result == nullptr; + ++index) { + const idStr osPath = BuildOSPath(searchPaths[index].c_str(), path); + if (PrefixNoCase(osPath.c_str(), "mtp:")) { + result = OpenFromMTP(osPath.c_str(), + uncompressedOnly ? FS_READ_NO_BUFFERING : FS_READ); + } else if (PrefixNoCase(osPath.c_str(), "nfs:")) { + result = OpenFromNFS(osPath.c_str(), FS_READ, false); + } else { + idFile_Permanent* permanent = new idFile_Permanent(path, + osPath.c_str(), + uncompressedOnly ? FS_READ_NO_BUFFERING : FS_READ, false); + if (permanent->IsOpen()) result = permanent; + else delete permanent; + } + if (result != nullptr) foundPath = osPath; + } + + // Recovered arbitrary-zip lookup remains tied to the pending unzip port. + if (result != nullptr && postCallback != nullptr) + postCallback(foundPath.c_str(), result); + return result; +} + +int idFileSystemLocal::AddUnique(const char* name, idList& list, + idHashIndex& hash) const { + const int key = hash.GenerateKeyForString(name, false); + for (int index = hash.First(key); index >= 0; index = hash.Next(index)) { + if (EqualNoCase(list[index].c_str(), name)) return index; + } + const int index = list.Append(idStr(name)); + if (index >= 0) hash.Add(key, index); + return index; +} + +void idFileSystemLocal::GetExtensionList(const char* extensions, + idList& extensionList) const { + const char* cursor = extensions == nullptr ? "" : extensions; + for (;;) { + const char* separator = std::strchr(cursor, '|'); + const std::size_t amount = separator == nullptr + ? std::strlen(cursor) : static_cast(separator - cursor); + std::string extension(cursor, amount); + extensionList.Append(idStr(extension.c_str())); + if (separator == nullptr) break; + cursor = separator + 1; + } +} + +int idFileSystemLocal::GetFileList(const char* relativePath, + const idList& extensions, idList& list, + idHashIndex& hash, const bool fullRelativePath) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (relativePath == nullptr || extensions.Num() == 0) return 0; + for (int pathIndex = 0; pathIndex < searchPaths.Num(); ++pathIndex) { + const idStr directory = BuildOSPath(searchPaths[pathIndex].c_str(), + relativePath); + for (int extensionIndex = 0; extensionIndex < extensions.Num(); + ++extensionIndex) { + idList found; + ListOSFiles(directory.c_str(), extensions[extensionIndex].c_str(), + found); + for (int fileIndex = 0; fileIndex < found.Num(); ++fileIndex) { + if (EqualNoCase(found[fileIndex].c_str(), ".") + || EqualNoCase(found[fileIndex].c_str(), "..")) continue; + if (fullRelativePath && relativePath[0] != '\0') { + std::string full = SlashesToGame(relativePath); + if (!full.empty() && full.back() != '/') full.push_back('/'); + full += found[fileIndex].c_str(); + AddUnique(full.c_str(), list, hash); + } else { + AddUnique(found[fileIndex].c_str(), list, hash); + } + } + } + } + return list.Num(); +} + +idFileList* idFileSystemLocal::ListFiles(const char* relativePath, + const char* extension, const bool sort, const bool fullRelativePath) { + idFileList* result = new idFileList(); + result->basePath = relativePath; + idList extensions; + GetExtensionList(extension, extensions); + idHashIndex hash(4096, 4096, TAG_HASHINDEX); + GetFileList(relativePath, extensions, result->list, hash, + fullRelativePath); + if (sort && result->list.Num() > 1) + std::sort(result->list.Ptr(), result->list.Ptr() + result->list.Num()); + return result; +} + +int idFileSystemLocal::GetFileListTree(const char* relativePath, + const idList& extensions, idList& list, + idHashIndex& hash) { + idList directoryExtension; + directoryExtension.Append(idStr("/")); + idList directories; + idHashIndex directoryHash(1024, 128, TAG_HASHINDEX); + GetFileList(relativePath, directoryExtension, directories, directoryHash, + true); + for (int index = 0; index < directories.Num(); ++index) { + const char* directory = directories[index].c_str(); + const char* leaf = std::strrchr(directory, '/'); + leaf = leaf == nullptr ? directory : leaf + 1; + if (leaf[0] != '.' && !EqualNoCase(directory, relativePath)) + GetFileListTree(directory, extensions, list, hash); + } + return GetFileList(relativePath, extensions, list, hash, true); +} + +idFileList* idFileSystemLocal::ListFilesTree(const char* relativePath, + const char* extension, const bool sort) { + idFileList* result = new idFileList(); + result->basePath = relativePath; + idList extensions; + GetExtensionList(extension, extensions); + idHashIndex hash(4096, 4096, TAG_HASHINDEX); + GetFileListTree(relativePath, extensions, result->list, hash); + if (sort && result->list.Num() > 1) + std::sort(result->list.Ptr(), result->list.Ptr() + result->list.Num()); + return result; +} + +void idFileSystemLocal::AddGameDirectory(const char* rootPath, + const char* gameFolder) { + std::string path = SlashesToGame(JoinPath(rootPath, gameFolder)); + if (!path.empty() && path.back() != '/') path.push_back('/'); + const idStr value(path.c_str()); + if (searchPaths.FindIndex(value) < 0) searchPaths.Insert(value, 0); +} + +void idFileSystemLocal::ReInit(const char* basePath) { + pathBase = basePath; + searchPaths.ClearFree(); + AddGameDirectory(pathBase.c_str(), "base"); +} + +void idFileSystemLocal::ClearZipCache() { + while (cachedZipFiles != nullptr) { + idCachedZipFile* next = cachedZipFiles->next; + delete cachedZipFiles; + cachedZipFiles = next; + } +} + +void idFileSystemLocal::FreeFileList(idFileList* fileList) { + delete fileList; +} + +void idFileSystemLocal::Init() { + pathBase = EnvironmentPath("TECH5_BASE_PATH").c_str(); + pathCache = EnvironmentPath("TECH5_CACHE_PATH").c_str(); + pathSave = EnvironmentPath("TECH5_SAVE_PATH").c_str(); + pathInstall = EnvironmentPath("TECH5_INSTALL_PATH").c_str(); + if (pathBase.Length() == 0) pathBase = ExecutableDirectory().c_str(); + if (pathInstall.Length() == 0) pathInstall = pathBase; + searchPaths.ClearFree(); + AddGameDirectory(pathBase.c_str(), "base"); + if (pathSave.Length() > 0) AddGameDirectory(pathSave.c_str(), "base"); + if (pathInstall.Length() > 0) + AddGameDirectory(pathInstall.c_str(), "base"); +} + +bool idFileSystemLocal::RemoveFile(const char* relativePath, + const fsPath_t basePath) { + const idStr path = BuildOSPath(basePath, relativePath); + if (PrefixNoCase(path.c_str(), "nfs:")) + return idFile_Nfs::RemoveFile(path.c_str()); + DWORD attributes = GetFileAttributesA(path.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES + && (attributes & FILE_ATTRIBUTE_READONLY) != 0) { + SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); + } + return DeleteFileA(path.c_str()) != FALSE; +} + +bool idFileSystemLocal::FileExists(const char* relativePath, + const bool allowCopyFiles) { + return GetTimestamp(relativePath, allowCopyFiles) + != static_cast(-1); +} + +unsigned int idFileSystemLocal::GetTimestamp(const char* relativePath, + const bool allowCopyFiles) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (relativePath == nullptr || relativePath[0] == '\0') + idLibPrint::FatalError( + "idFileSystemLocal::GetTimestamp with empty name\n"); + idFileLocal file(OpenFileRead(relativePath, allowCopyFiles, false)); + return file.file == nullptr ? static_cast(-1) + : file->Timestamp(); +} + +bool idFileSystemLocal::PreOpenFileWrite(const char* relativePath, + fsPath_t& basePath, idStr& osPath) const { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + if (relativePath == nullptr || relativePath[0] == '\0') return false; + if (PrefixNoCase(relativePath, "generated")) basePath = FSPATH_SAVE; + osPath = BuildOSPath(basePath, relativePath); + return true; +} + +idFile* idFileSystemLocal::OpenFileWritePermanent(const char* relativePath, + fsPath_t basePath) { + idStr osPath; + if (!PreOpenFileWrite(relativePath, basePath, osPath)) return nullptr; + if (PrefixNoCase(osPath.c_str(), "mtp:")) + return OpenFromMTP(osPath.c_str(), FS_WRITE); + if (PrefixNoCase(osPath.c_str(), "nfs:")) + return OpenFromNFS(osPath.c_str(), FS_WRITE, true); + CreateOSPath(osPath.c_str()); + idFile_Permanent* file = new idFile_Permanent(relativePath, + osPath.c_str(), FS_WRITE, false); + if (file->IsOpen()) return file; + delete file; + return nullptr; +} + +idFile* idFileSystemLocal::OpenFileReadWrite(const char* relativePath, + const bool create, const fsPath_t basePath) { + if (!IsInitialized()) + idLibPrint::FatalError("Filesystem call made without initialization\n"); + const idStr osPath = BuildOSPath(basePath, relativePath); + if (PrefixNoCase(osPath.c_str(), "mtp:")) + return OpenFromMTP(osPath.c_str(), FS_READ_WRITE); + if (PrefixNoCase(osPath.c_str(), "nfs:")) + return OpenFromNFS(osPath.c_str(), FS_READ_WRITE, create); + CreateOSPath(osPath.c_str()); + idFile_Permanent* file = new idFile_Permanent(relativePath, + osPath.c_str(), FS_READ_WRITE, create); + if (file->IsOpen()) return file; + delete file; + return nullptr; +} + +idFile* idFileSystemLocal::OpenFileWrite(const char* relativePath, + const fsPath_t basePath) { + // Atomic buffering is restored with the remaining recovered file.cpp body. + return OpenFileWritePermanent(relativePath, basePath); +} + +unsigned int idFileSystemLocal::WriteFile(const char* relativePath, + const void* buffer, const unsigned int length, + const fsPath_t basePath) { + idFileLocal file(OpenFileWrite(relativePath, basePath)); + if (file.file == nullptr) + idLibPrint::Error("Failed to open %s\n", relativePath); + return file->Write(buffer, length); +} + +void idFileSystemLocal::Copy_f(const idCmdArgs& args) { + if (args.Argc() == 3) + fileSystemLocal.CopyGameFile(args.Argv(1), args.Argv(2), FSPATH_BASE); +} + +void idFileSystemLocal::Dir_f(const idCmdArgs& args) { + const char* path = args.Argc() > 1 ? args.Argv(1) : ""; + const char* extension = args.Argc() > 2 ? args.Argv(2) : ""; + idFileList* files = fileSystemLocal.ListFiles(path, extension, true, false); + for (int index = 0; index < files->GetNumFiles(); ++index) + idLibPrint::Printf("%s\n", files->GetFile(index)); + fileSystemLocal.FreeFileList(files); +} + +void idFileSystemLocal::DirTree_f(const idCmdArgs& args) { + const char* path = args.Argc() > 1 ? args.Argv(1) : ""; + const char* extension = args.Argc() > 2 ? args.Argv(2) : ""; + idFileList* files = fileSystemLocal.ListFilesTree(path, extension, true); + for (int index = 0; index < files->GetNumFiles(); ++index) + idLibPrint::Printf("%s\n", files->GetFile(index)); + fileSystemLocal.FreeFileList(files); +} + +void idFileSystemLocal::Path_f(const idCmdArgs&) { + idLibPrint::Printf("Current search path:\n"); + for (int index = 0; index < fileSystemLocal.searchPaths.Num(); ++index) + idLibPrint::Printf("%s\n", fileSystemLocal.searchPaths[index].c_str()); +} diff --git a/source/shared/idlib/filesystem/filesystem.h b/source/shared/idlib/filesystem/filesystem.h new file mode 100644 index 0000000..47bf7b7 --- /dev/null +++ b/source/shared/idlib/filesystem/filesystem.h @@ -0,0 +1,239 @@ +#pragma once + +#include "file.h" +#include "../containers/hashindex.h" +#include "../containers/list.h" +#include "../text/cmdargs.h" + +enum sysFolder_t : int { + FOLDER_ERROR = -1, + FOLDER_NO = 0, + FOLDER_YES = 1 +}; + +class idFileList { +public: + idFileList(); + + const char* GetBasePath() const { return basePath.c_str(); } + int GetNumFiles() const { return list.Num(); } + const char* GetFile(int index) const { return list[index].c_str(); } + + idStr basePath; + idList list; +}; + +class idZippedFile { +public: + idStr name; + unsigned int pos; + idZippedFile* next; +}; + +class idCachedZipFile { +public: + idCachedZipFile(); + ~idCachedZipFile(); + + idStr zipFileName; + idStr relativeZipFileName; + void* handle; + int numFiles; + idZippedFile* hashTable[128]; + idZippedFile* fileList; + idCachedZipFile* next; +}; + +using filePreCallback_t = idFile* (*)(const char* relativePath); +using filePostCallback_t = void (*)(const char* relativePath, idFile* file); +using fileCacheCallback_t = idFile* (*)(const char* relativePath); +using filePatchCallback_t = idFile* (*)(const char* relativePath); + +class idFileSystem { +public: + virtual ~idFileSystem(); + virtual void Init() = 0; + virtual void ReInit(const char* gameFolder) = 0; + virtual bool IsInitialized() const = 0; + virtual void SetFilePreCallback(filePreCallback_t callback) = 0; + virtual void SetFilePostCallback(filePostCallback_t callback) = 0; + virtual void SetFileCacheCallback(fileCacheCallback_t statCallback, + fileCacheCallback_t cacheCallback) = 0; + virtual void SetFilePatchCallback(filePatchCallback_t callback) = 0; + virtual idFileList* ListFiles(const char* relativePath, + const char* extension, bool sort = false, + bool fullRelativePath = false) = 0; + virtual idFileList* ListFilesTree(const char* relativePath, + const char* extension, bool sort = false) = 0; + virtual void FreeFileList(idFileList* fileList) = 0; + virtual bool IsRelativePath(const char* path) const = 0; + virtual idStr RelativePathToOSPath(const char* relativePath, + fsPath_t basePath) const = 0; + virtual void RelativePathToOSPath(const char* relativePath, char* osPath, + int osPathSize, fsPath_t basePath) const = 0; + virtual bool OSPathToRelativePath(const char* osPath, + char* relativePath, int relativePathSize) = 0; + virtual bool FixLongFilename(const char* basePath, + const char* extension, const char* inputPath, char* fixedPath, + int fixedPathSize) const = 0; + virtual void CreateOSPath(const char* osPath) = 0; + virtual bool FileExists(const char* relativePath, + bool allowCopyFiles = true) = 0; + virtual bool IsWritable(const char* relativePath) const = 0; + virtual sysFolder_t IsFolder(const char* relativePath, + fsPath_t basePath) const = 0; + virtual unsigned int GetTimestamp(const char* relativePath, + bool allowCopyFiles = true) = 0; + virtual void GetNumberedFilename(int& index, const char* prefix, + const char* extension, idStr& result, bool zeroPad = false) = 0; + virtual int ReadFile(const char* relativePath, void** buffer, + unsigned int* timestamp = nullptr) = 0; + virtual void FreeFile(void* buffer) = 0; + virtual unsigned int WriteFile(const char* relativePath, + const void* buffer, unsigned int length, fsPath_t basePath) = 0; + virtual bool RemoveFile(const char* relativePath, fsPath_t basePath) = 0; + virtual bool RemoveDir(const char* relativePath) = 0; + virtual bool RenameFile(const char* from, const char* to, + fsPath_t basePath) = 0; + virtual idFile* OpenFileRead(const char* relativePath, + bool allowCopyFiles = true, bool uncompressedOnly = false) = 0; + virtual idFile* OpenFileWrite(const char* relativePath, + fsPath_t basePath) = 0; + virtual idFile* OpenFileWritePermanent(const char* relativePath, + fsPath_t basePath) = 0; + virtual idFile* OpenFileReadWrite(const char* relativePath, bool create, + fsPath_t basePath) = 0; + virtual idFile* OpenFileAppend(const char* relativePath, + fsPath_t basePath) = 0; + virtual const char* GetBasePathStr(fsPath_t basePath) const = 0; + virtual idStr GeneratedPath(const char* relativePath) const = 0; + virtual std::int64_t GetFileLength(const char* relativePath) = 0; + virtual void ClearZipCache() = 0; + virtual bool CopyGameFile(const char* from, const char* to, + fsPath_t basePath) = 0; +}; + +class idFile_InZip; + +class idFileSystemLocal : public idFileSystem { +public: + idFileSystemLocal(); + ~idFileSystemLocal() override; + + void Init() override; + void ReInit(const char* gameFolder) override; + bool IsInitialized() const override; + void SetFilePreCallback(filePreCallback_t callback) override; + void SetFilePostCallback(filePostCallback_t callback) override; + void SetFileCacheCallback(fileCacheCallback_t statCallback, + fileCacheCallback_t cacheCallback) override; + void SetFilePatchCallback(filePatchCallback_t callback) override; + idFileList* ListFiles(const char* relativePath, const char* extension, + bool sort, bool fullRelativePath) override; + idFileList* ListFilesTree(const char* relativePath, + const char* extension, bool sort) override; + void FreeFileList(idFileList* fileList) override; + bool IsRelativePath(const char* path) const override; + idStr RelativePathToOSPath(const char* relativePath, + fsPath_t basePath) const override; + void RelativePathToOSPath(const char* relativePath, char* osPath, + int osPathSize, fsPath_t basePath) const override; + bool OSPathToRelativePath(const char* osPath, char* relativePath, + int relativePathSize) override; + bool FixLongFilename(const char* basePath, const char* extension, + const char* inputPath, char* fixedPath, + int fixedPathSize) const override; + void CreateOSPath(const char* osPath) override; + bool FileExists(const char* relativePath, bool allowCopyFiles) override; + bool IsWritable(const char* relativePath) const override; + sysFolder_t IsFolder(const char* relativePath, + fsPath_t basePath) const override; + unsigned int GetTimestamp(const char* relativePath, + bool allowCopyFiles) override; + void GetNumberedFilename(int& index, const char* prefix, + const char* extension, idStr& result, bool zeroPad) override; + int ReadFile(const char* relativePath, void** buffer, + unsigned int* timestamp) override; + void FreeFile(void* buffer) override; + unsigned int WriteFile(const char* relativePath, const void* buffer, + unsigned int length, fsPath_t basePath) override; + bool RemoveFile(const char* relativePath, fsPath_t basePath) override; + bool RemoveDir(const char* relativePath) override; + bool RenameFile(const char* from, const char* to, + fsPath_t basePath) override; + idFile* OpenFileRead(const char* relativePath, bool allowCopyFiles, + bool uncompressedOnly) override; + idFile* OpenFileWrite(const char* relativePath, + fsPath_t basePath) override; + idFile* OpenFileWritePermanent(const char* relativePath, + fsPath_t basePath) override; + idFile* OpenFileReadWrite(const char* relativePath, bool create, + fsPath_t basePath) override; + idFile* OpenFileAppend(const char* relativePath, + fsPath_t basePath) override; + const char* GetBasePathStr(fsPath_t basePath) const override; + idStr GeneratedPath(const char* relativePath) const override; + std::int64_t GetFileLength(const char* relativePath) override; + void ClearZipCache() override; + bool CopyGameFile(const char* from, const char* to, + fsPath_t basePath) override; + bool CopyGameFile(idFile* source, idFile* destination); + + static void Copy_f(const idCmdArgs& args); + static void Dir_f(const idCmdArgs& args); + static void DirTree_f(const idCmdArgs& args); + static void Path_f(const idCmdArgs& args); + + idList searchPaths; + idCachedZipFile* cachedZipFiles; + int loadCount; + int loadStack; + idStr pathBase; + idStr pathCache; + idStr pathInstall; + idStr pathSave; + filePreCallback_t preCallback; + filePostCallback_t postCallback; + fileCacheCallback_t cacheStatCallback; + fileCacheCallback_t cacheCallback; + filePatchCallback_t patchCallback; + +private: + void AddGameDirectory(const char* rootPath, const char* gameFolder); + int AddUnique(const char* name, idList& list, + idHashIndex& hash) const; + idStr BuildOSPath(const char* root, const char* relativePath) const; + idStr BuildOSPath(fsPath_t basePath, const char* relativePath) const; + void GetExtensionList(const char* extensions, + idList& extensionList) const; + int GetFileList(const char* relativePath, + const idList& extensions, idList& list, + idHashIndex& hash, bool fullRelativePath); + int GetFileListTree(const char* relativePath, + const idList& extensions, idList& list, + idHashIndex& hash); + void ListOSFiles(const char* directory, const char* extension, + idList& list); + idCachedZipFile* LoadZipFile(const char* zipFile, + const char* relativeZipFile); + idFile* OpenFromMTP(const char* path, fsMode_t mode); + idFile* OpenFromNFS(const char* path, fsMode_t mode, bool create); + bool PreOpenFileWrite(const char* relativePath, fsPath_t& basePath, + idStr& osPath) const; + idFile_InZip* ReadFileFromZip(idCachedZipFile* zip, + const char* relativePath, bool uncompressedOnly); +}; + +extern idFileSystem* fileSystem; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idFileList) == 48, "Recovered idFileList ABI changed"); +static_assert(sizeof(idZippedFile) == 40, + "Recovered idZippedFile ABI changed"); +static_assert(sizeof(idCachedZipFile) == 592, + "Recovered idCachedZipFile ABI changed"); +static_assert(sizeof(idFileSystem) == 4, + "Recovered idFileSystem ABI changed"); +static_assert(sizeof(idFileSystemLocal) == 180, + "Recovered idFileSystemLocal ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/drawvert.h b/source/shared/idlib/geometry/drawvert.h new file mode 100644 index 0000000..7bfc055 --- /dev/null +++ b/source/shared/idlib/geometry/drawvert.h @@ -0,0 +1,97 @@ +#pragma once + +#include "../math/vector.h" + +#include +#include +#include + +// Recovered Tungsten vertex layout. Normals and tangents use the observed +// unsigned-byte [-1, 1] encoding; tangent[3] stores the bitangent handedness. +class idDrawVert { +public: + idVec3 xyz; + idVec2 st; + std::uint8_t normal[4]; + std::uint8_t tangent[4]; + std::uint8_t color[4]; + + idDrawVert& operator=(const idDrawVert& rhs) = default; + + idVec3 GetNormal() const { return DecodeDirection(normal); } + idVec3 GetTangent() const { return DecodeDirection(tangent); } + + idVec3 GetBiTangent() const { + const idVec3 bitangent = GetNormal().Cross(GetTangent()); + return tangent[3] >= 128 ? bitangent : -bitangent; + } + + void SetNormal(const idVec3& value) { EncodeDirection(value, normal); } + void SetTangent(const idVec3& value) { EncodeDirection(value, tangent); } + + void SetBiTangent(const idVec3& value) { + tangent[3] = value.Dot(GetNormal().Cross(GetTangent())) >= 0.0f + ? std::uint8_t(255) + : std::uint8_t(0); + } + + void LerpAll(const idDrawVert& a, const idDrawVert& b, const float fraction) { + xyz.Set( + Lerp(a.xyz.x, b.xyz.x, fraction), + Lerp(a.xyz.y, b.xyz.y, fraction), + Lerp(a.xyz.z, b.xyz.z, fraction)); + st.Set( + Lerp(a.st.x, b.st.x, fraction), + Lerp(a.st.y, b.st.y, fraction)); + + const idVec3 interpolatedNormal = Normalize( + a.GetNormal() + (b.GetNormal() - a.GetNormal()) * fraction); + const idVec3 interpolatedTangent = Normalize( + a.GetTangent() + (b.GetTangent() - a.GetTangent()) * fraction); + const idVec3 interpolatedBiTangent = Normalize( + a.GetBiTangent() + (b.GetBiTangent() - a.GetBiTangent()) * fraction); + SetNormal(interpolatedNormal); + SetTangent(interpolatedTangent); + SetBiTangent(interpolatedBiTangent); + + for (int component = 0; component < 4; ++component) { + const int value = static_cast( + static_cast(a.color[component]) + + (static_cast(b.color[component]) + - static_cast(a.color[component])) * fraction); + color[component] = static_cast( + (std::max)(0, (std::min)(255, value))); + } + } + +private: + static float Lerp(const float a, const float b, const float fraction) { + return a + (b - a) * fraction; + } + + static idVec3 Normalize(const idVec3& value) { + const float lengthSqr = value.LengthSqr(); + if (lengthSqr <= 1.0e-30f) { + return idVec3(0.0f, 0.0f, 0.0f); + } + return value * (1.0f / std::sqrt(lengthSqr)); + } + + static idVec3 DecodeDirection(const std::uint8_t value[4]) { + return Normalize(idVec3( + static_cast(value[0]) * (2.0f / 255.0f) - 1.0f, + static_cast(value[1]) * (2.0f / 255.0f) - 1.0f, + static_cast(value[2]) * (2.0f / 255.0f) - 1.0f)); + } + + static void EncodeDirection(const idVec3& input, std::uint8_t value[4]) { + const idVec3 direction = Normalize(input); + for (int component = 0; component < 3; ++component) { + const float scaled = (direction[component] + 1.0f) * 127.5f + 0.5f; + value[component] = static_cast( + (std::max)(0.0f, (std::min)(255.0f, scaled))); + } + } +}; + +static_assert(sizeof(idDrawVert) == 32, "Recovered idDrawVert layout changed"); diff --git a/source/shared/idlib/geometry/jointtransform.h b/source/shared/idlib/geometry/jointtransform.h new file mode 100644 index 0000000..7ae95a3 --- /dev/null +++ b/source/shared/idlib/geometry/jointtransform.h @@ -0,0 +1,87 @@ +#pragma once + +#include "../math/matrix.h" + +#include + +enum jointModTransform_t : int { + JOINTMOD_NONE = 0, + JOINTMOD_LOCAL = 1, + JOINTMOD_LOCAL_OVERRIDE = 2, + JOINTMOD_MODEL = 3, + JOINTMOD_MODEL_OVERRIDE = 4, + JOINTMOD_PARENT_OVERRIDE = 5 +}; + +class idJointQuat { +public: + float jointQuat[8]; + + void FromMat4(const idMat4& matrix) { + const float trace = matrix[0].x + matrix[1].y + matrix[2].z; + if (trace > 0.0f) { + const float scale = std::sqrt(trace + 1.0f); + jointQuat[3] = 0.5f * scale; + const float inverseScale = 0.5f / scale; + jointQuat[0] = (matrix[2].y - matrix[1].z) * inverseScale; + jointQuat[1] = (matrix[0].z - matrix[2].x) * inverseScale; + jointQuat[2] = (matrix[1].x - matrix[0].y) * inverseScale; + } else { + const int next[3] = { 1, 2, 0 }; + int i = matrix[1].y > matrix[0].x ? 1 : 0; + if (matrix[2].z > matrix[i][i]) i = 2; + const int j = next[i]; + const int k = next[j]; + float scale = std::sqrt( + matrix[i][i] - matrix[j][j] - matrix[k][k] + 1.0f); + jointQuat[i] = 0.5f * scale; + scale = scale > 0.0f ? 0.5f / scale : 0.0f; + jointQuat[3] = (matrix[k][j] - matrix[j][k]) * scale; + jointQuat[j] = (matrix[j][i] + matrix[i][j]) * scale; + jointQuat[k] = (matrix[k][i] + matrix[i][k]) * scale; + } + jointQuat[4] = matrix[0].w; + jointQuat[5] = matrix[1].w; + jointQuat[6] = matrix[2].w; + jointQuat[7] = 0.0f; + } + + idQuat& Rotation() { return *reinterpret_cast(jointQuat); } + const idQuat& Rotation() const { + return *reinterpret_cast(jointQuat); + } + idVec3& Translation() { return *reinterpret_cast(jointQuat + 4); } + const idVec3& Translation() const { + return *reinterpret_cast(jointQuat + 4); + } +}; + +class idJointMat { +public: + float mat[12]; + + float* operator[](const int row) { return mat + row * 4; } + const float* operator[](const int row) const { return mat + row * 4; } +}; + +struct jointTransforms_t { + idJointMat left; + idJointMat right; + idJointMat origin; +}; + +struct jointTransform_t { + idQuat msQuat; + idVec3 msOrigin; +}; + +using getJointTransform_t = bool (*)( + void*, const idJointMat*, const char*, idVec3*, idMat3*); + +static_assert(sizeof(idJointQuat) == 32, "Recovered idJointQuat layout changed"); +static_assert(sizeof(idJointMat) == 48, "Recovered idJointMat layout changed"); +static_assert(sizeof(jointTransforms_t) == 144, + "Recovered jointTransforms_t layout changed"); +static_assert(sizeof(jointTransform_t) == 28, + "Recovered jointTransform_t layout changed"); + diff --git a/source/shared/idlib/geometry/rendermatrix.h b/source/shared/idlib/geometry/rendermatrix.h new file mode 100644 index 0000000..4fc0152 --- /dev/null +++ b/source/shared/idlib/geometry/rendermatrix.h @@ -0,0 +1,371 @@ +#pragma once + +#include "../bv/bounds.h" +#include "../math/matrix.h" +#include "../math/plane.h" + +#include +#include +#include +#include + +// Row-major 4x4 matrix recovered from Tungsten. The methods below follow the +// operand order and projection conventions visible in the Hex-Rays bodies. +class idRenderMatrix { +public: + float m[16]; + + float* operator[](const int row) { return m + row * 4; } + const float* operator[](const int row) const { return m + row * 4; } + + void Identity() { + std::memset(m, 0, sizeof(m)); + m[0] = m[5] = m[10] = m[15] = 1.0f; + } + + void TransformPoint(const idVec3& input, idVec4& output) const { + output.Set( + input.x * m[0] + input.y * m[1] + input.z * m[2] + m[3], + input.x * m[4] + input.y * m[5] + input.z * m[6] + m[7], + input.x * m[8] + input.y * m[9] + input.z * m[10] + m[11], + input.x * m[12] + input.y * m[13] + input.z * m[14] + m[15]); + } + + void TransformPoint(const idVec4& input, idVec4& output) const { + idVec4 result; + result.Set( + input.x * m[0] + input.y * m[1] + input.z * m[2] + input.w * m[3], + input.x * m[4] + input.y * m[5] + input.z * m[6] + input.w * m[7], + input.x * m[8] + input.y * m[9] + input.z * m[10] + input.w * m[11], + input.x * m[12] + input.y * m[13] + input.z * m[14] + input.w * m[15]); + output = result; + } + + void TransformDir(const idVec3& input, idVec3& output, + const bool normalize) const { + idVec3 result( + input.x * m[0] + input.y * m[1] + input.z * m[2], + input.x * m[4] + input.y * m[5] + input.z * m[6], + input.x * m[8] + input.y * m[9] + input.z * m[10]); + if (normalize) Normalize(result); + output = result; + } + + void InverseTransformPlane(const idPlane& input, idPlane& output, + const bool normalize) const { + idPlane result( + input.a * m[0] + input.b * m[4] + input.c * m[8] + input.d * m[12], + input.a * m[1] + input.b * m[5] + input.c * m[9] + input.d * m[13], + input.a * m[2] + input.b * m[6] + input.c * m[10] + input.d * m[14], + input.a * m[3] + input.b * m[7] + input.c * m[11] + input.d * m[15]); + if (normalize) { + const float lengthSqr = result.a * result.a + result.b * result.b + + result.c * result.c; + if (lengthSqr > 1.0e-30f) { + const float inverseLength = 1.0f / std::sqrt(lengthSqr); + result.a *= inverseLength; + result.b *= inverseLength; + result.c *= inverseLength; + result.d *= inverseLength; + } + } + output = result; + } + + static void FromOriginAxisScale(const idVec3& origin, const idMat3& axis, + const idVec3& scale, idRenderMatrix& output) { + output.m[0] = axis[0].x * scale.x; + output.m[1] = axis[1].x * scale.y; + output.m[2] = axis[2].x * scale.z; + output.m[3] = origin.x; + output.m[4] = axis[0].y * scale.x; + output.m[5] = axis[1].y * scale.y; + output.m[6] = axis[2].y * scale.z; + output.m[7] = origin.y; + output.m[8] = axis[0].z * scale.x; + output.m[9] = axis[1].z * scale.y; + output.m[10] = axis[2].z * scale.z; + output.m[11] = origin.z; + output.m[12] = output.m[13] = output.m[14] = 0.0f; + output.m[15] = 1.0f; + } + + static void ModelViewFromOriginAxis(const idVec3& origin, + const idMat3& axis, idRenderMatrix& output) { + output.m[0] = -axis[1].x; + output.m[1] = -axis[1].y; + output.m[2] = -axis[1].z; + output.m[3] = -(origin.x * output.m[0] + origin.y * output.m[1] + + origin.z * output.m[2]); + output.m[4] = axis[2].x; + output.m[5] = axis[2].y; + output.m[6] = axis[2].z; + output.m[7] = -(origin.x * output.m[4] + origin.y * output.m[5] + + origin.z * output.m[6]); + output.m[8] = -axis[0].x; + output.m[9] = -axis[0].y; + output.m[10] = -axis[0].z; + output.m[11] = -(origin.x * output.m[8] + origin.y * output.m[9] + + origin.z * output.m[10]); + output.m[12] = output.m[13] = output.m[14] = 0.0f; + output.m[15] = 1.0f; + } + + static void Multiply(const idRenderMatrix& a, const idRenderMatrix& b, + idRenderMatrix& output) { + idRenderMatrix result; + for (int row = 0; row < 4; ++row) { + for (int column = 0; column < 4; ++column) { + result[row][column] = a[row][0] * b[0][column] + + a[row][1] * b[1][column] + + a[row][2] * b[2][column] + + a[row][3] * b[3][column]; + } + } + output = result; + } + + static bool InverseByDoubles(const idRenderMatrix& source, + idRenderMatrix& output) { + double augmented[4][8] = {}; + for (int row = 0; row < 4; ++row) { + for (int column = 0; column < 4; ++column) + augmented[row][column] = source[row][column]; + augmented[row][row + 4] = 1.0; + } + for (int column = 0; column < 4; ++column) { + int pivotRow = column; + for (int row = column + 1; row < 4; ++row) { + if (std::fabs(augmented[row][column]) + > std::fabs(augmented[pivotRow][column])) { + pivotRow = row; + } + } + if (std::fabs(augmented[pivotRow][column]) < 1.0e-20) return false; + if (pivotRow != column) { + for (int entry = 0; entry < 8; ++entry) + std::swap(augmented[pivotRow][entry], augmented[column][entry]); + } + const double inversePivot = 1.0 / augmented[column][column]; + for (int entry = 0; entry < 8; ++entry) + augmented[column][entry] *= inversePivot; + for (int row = 0; row < 4; ++row) { + if (row == column) continue; + const double scale = augmented[row][column]; + for (int entry = 0; entry < 8; ++entry) + augmented[row][entry] -= scale * augmented[column][entry]; + } + } + for (int row = 0; row < 4; ++row) + for (int column = 0; column < 4; ++column) + output[row][column] = static_cast(augmented[row][column + 4]); + return true; + } + + static bool Inverse(const idRenderMatrix& source, idRenderMatrix& output) { + return InverseByDoubles(source, output); + } + + static void InverseByTranspose(const idRenderMatrix& source, + idRenderMatrix& output) { + output.m[0] = source.m[0]; output.m[1] = source.m[4]; + output.m[2] = source.m[8]; output.m[12] = 0.0f; + output.m[4] = source.m[1]; output.m[5] = source.m[5]; + output.m[6] = source.m[9]; output.m[13] = 0.0f; + output.m[8] = source.m[2]; output.m[9] = source.m[6]; + output.m[10] = source.m[10]; output.m[14] = 0.0f; + output.m[3] = -(source.m[3] * source.m[0] + + source.m[7] * source.m[4] + source.m[11] * source.m[8]); + output.m[7] = -(source.m[3] * source.m[1] + + source.m[7] * source.m[5] + source.m[11] * source.m[9]); + output.m[11] = -(source.m[3] * source.m[2] + + source.m[7] * source.m[6] + source.m[11] * source.m[10]); + output.m[15] = 1.0f; + } + + static void BuildProjection(const float xMin, const float xMax, + const float yMin, const float yMax, const float zNear, + const float zFar, idRenderMatrix& output) { + std::memset(output.m, 0, sizeof(output.m)); + output.m[0] = 2.0f * zNear / (xMax - xMin); + output.m[2] = (xMin + xMax) / (xMax - xMin); + output.m[5] = 2.0f * zNear / (yMax - yMin); + output.m[6] = (yMin + yMax) / (yMax - yMin); + output.m[14] = -1.0f; + if (zFar > zNear) { + output.m[10] = -zFar / (zFar - zNear); + output.m[11] = -(zNear * zFar) / (zFar - zNear); + } else { + output.m[10] = -1.0f; + output.m[11] = -zNear; + } + } + + static void BuildProjectionFov(const float xFovDegrees, + const float yFovDegrees, const float zNear, const float zFar, + const float xOffset, const float yOffset, idRenderMatrix& output) { + const float degreeToRadian = 0.01745329251994329577f; + const float xSize = std::tan(0.5f * xFovDegrees * degreeToRadian) * zNear; + const float ySize = std::tan(0.5f * yFovDegrees * degreeToRadian) * zNear; + BuildProjection(-xSize + xOffset, xSize + xOffset, + -ySize + yOffset, ySize + yOffset, zNear, zFar, output); + } + + static void OffsetScaleForBounds(const idRenderMatrix& source, + const idBounds& bounds, idRenderMatrix& output) { + const idVec3 center = (bounds[0] + bounds[1]) * 0.5f; + const idVec3 extent = (bounds[1] - bounds[0]) * 0.5f; + for (int row = 0; row < 4; ++row) { + output[row][0] = source[row][0] * extent.x; + output[row][1] = source[row][1] * extent.y; + output[row][2] = source[row][2] * extent.z; + output[row][3] = source[row][3] + source[row][0] * center.x + + source[row][1] * center.y + source[row][2] * center.z; + } + } + + static void InverseOffsetScaleForBounds(const idRenderMatrix& source, + const idBounds& bounds, idRenderMatrix& output) { + const idVec3 center = (bounds[0] + bounds[1]) * 0.5f; + const idVec3 extent = (bounds[1] - bounds[0]) * 0.5f; + for (int row = 0; row < 4; ++row) { + const float inverseExtent = row < 3 && extent[row] != 0.0f + ? 1.0f / extent[row] + : 1.0f; + for (int column = 0; column < 4; ++column) + output[row][column] = source[row][column] * inverseExtent; + } + for (int column = 0; column < 4; ++column) { + output[0][column] -= center.x * output[3][column]; + output[1][column] -= center.y * output[3][column]; + output[2][column] -= center.z * output[3][column]; + } + } + + static void CopyMatrix(const idRenderMatrix& matrix, idVec4& row0, + idVec4& row1, idVec4& row2, idVec4& row3) { + row0.Set(matrix.m[0], matrix.m[1], matrix.m[2], matrix.m[3]); + row1.Set(matrix.m[4], matrix.m[5], matrix.m[6], matrix.m[7]); + row2.Set(matrix.m[8], matrix.m[9], matrix.m[10], matrix.m[11]); + row3.Set(matrix.m[12], matrix.m[13], matrix.m[14], matrix.m[15]); + } + + static void SetMVP(const idRenderMatrix& matrix, idVec4& row0, + idVec4& row1, idVec4& row2, idVec4& row3, + bool& negativeDeterminant) { + CopyMatrix(matrix, row0, row1, row2, row3); + negativeDeterminant = Determinant3x3(matrix) < 0.0f; + } + + static void SetMVPForBounds(const idRenderMatrix& matrix, + const idBounds& bounds, idVec4& row0, idVec4& row1, + idVec4& row2, idVec4& row3, bool& negativeDeterminant) { + idRenderMatrix adjusted; + OffsetScaleForBounds(matrix, bounds, adjusted); + SetMVP(adjusted, row0, row1, row2, row3, negativeDeterminant); + } + + static void SetMVPForInverseProject(const idRenderMatrix& matrix, + const idRenderMatrix& inverseProject, idVec4& row0, + idVec4& row1, idVec4& row2, idVec4& row3, + bool& negativeDeterminant) { + idRenderMatrix adjusted; + Multiply(matrix, inverseProject, adjusted); + SetMVP(adjusted, row0, row1, row2, row3, negativeDeterminant); + } + + static bool CullPointToMVPbits(const idRenderMatrix& matrix, + const idVec3& point, std::uint8_t* outBits, const bool zeroToOne) { + idVec4 clip; + matrix.TransformPoint(point, clip); + std::uint8_t bits = 0; + if (clip.x < -clip.w) bits |= 1; + if (clip.x > clip.w) bits |= 2; + if (clip.y < -clip.w) bits |= 4; + if (clip.y > clip.w) bits |= 8; + if (clip.z < (zeroToOne ? 0.0f : -clip.w)) bits |= 16; + if (clip.z > clip.w) bits |= 32; + if (outBits != nullptr) *outBits = bits; + return bits != 0; + } + + static bool CullBoundsToMVPbits(const idRenderMatrix& matrix, + const idBounds& bounds, std::uint8_t* outBits, + const bool zeroToOne) { + std::uint8_t allBits = 0x3F; + std::uint8_t anyBits = 0; + for (int corner = 0; corner < 8; ++corner) { + const idVec3 point( + bounds[(corner & 1) != 0 ? 1 : 0].x, + bounds[(corner & 2) != 0 ? 1 : 0].y, + bounds[(corner & 4) != 0 ? 1 : 0].z); + std::uint8_t bits; + CullPointToMVPbits(matrix, point, &bits, zeroToOne); + anyBits |= bits; + allBits &= bits; + } + if (outBits != nullptr) *outBits = anyBits; + return allBits != 0; + } + + static void ProjectedBounds(idBounds& projected, + const idRenderMatrix& matrix, const idBounds& bounds, + const bool windowSpace) { + projected[0].Set(1.0e30f, 1.0e30f, 1.0e30f); + projected[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); + for (int corner = 0; corner < 8; ++corner) { + idVec4 clip; + matrix.TransformPoint(idVec3( + bounds[(corner & 1) != 0 ? 1 : 0].x, + bounds[(corner & 2) != 0 ? 1 : 0].y, + bounds[(corner & 4) != 0 ? 1 : 0].z), clip); + if (std::fabs(clip.w) < 1.0e-20f) continue; + const float inverseW = 1.0f / clip.w; + idVec3 point(clip.x * inverseW, clip.y * inverseW, clip.z * inverseW); + if (windowSpace) { + point.x = point.x * 0.5f + 0.5f; + point.y = point.y * 0.5f + 0.5f; + } + for (int axis = 0; axis < 3; ++axis) { + projected[0][axis] = (std::min)(projected[0][axis], point[axis]); + projected[1][axis] = (std::max)(projected[1][axis], point[axis]); + } + } + } + + static void DepthBoundsForBounds(float& minDepth, float& maxDepth, + const idRenderMatrix& matrix, const idBounds& bounds, + const bool windowSpace) { + idBounds projected; + ProjectedBounds(projected, matrix, bounds, windowSpace); + minDepth = projected[0].z; + maxDepth = projected[1].z; + } + + static void TransformModelToClip(const idVec3& source, + const idRenderMatrix& modelView, const idRenderMatrix& projection, + idVec4& eye, idVec4& clip) { + modelView.TransformPoint(source, eye); + projection.TransformPoint(eye, clip); + } + + static void TransformClipToDevice(const idVec4& clip, idVec3& device) { + const float inverseW = std::fabs(clip.w) > 1.0e-20f ? 1.0f / clip.w : 0.0f; + device.Set(clip.x * inverseW, clip.y * inverseW, clip.z * inverseW); + } + +private: + static void Normalize(idVec3& value) { + const float lengthSqr = value.LengthSqr(); + if (lengthSqr > 1.0e-30f) value = value * (1.0f / std::sqrt(lengthSqr)); + } + + static float Determinant3x3(const idRenderMatrix& matrix) { + return matrix.m[0] * (matrix.m[5] * matrix.m[10] - matrix.m[6] * matrix.m[9]) + - matrix.m[1] * (matrix.m[4] * matrix.m[10] - matrix.m[6] * matrix.m[8]) + + matrix.m[2] * (matrix.m[4] * matrix.m[9] - matrix.m[5] * matrix.m[8]); + } +}; + +static_assert(sizeof(idRenderMatrix) == 64, + "Recovered idRenderMatrix layout changed"); diff --git a/source/shared/idlib/geometry/surface.h b/source/shared/idlib/geometry/surface.h new file mode 100644 index 0000000..47bbe59 --- /dev/null +++ b/source/shared/idlib/geometry/surface.h @@ -0,0 +1,95 @@ +#pragma once + +#include "drawvert.h" +#include "../bv/bounds.h" +#include "../containers/list.h" +#include "../math/matrix.h" + +enum SurfaceSwap : int { + SWAP_LOWRED = 0, + SWAP_LOWBLUE = 1 +}; + +enum surfaceType_t : int { + SURFACE_NONE = 0, + SURFACE_FLOOR = 1, + SURFACE_FLOOR_CRAWL = 2, + SURFACE_WALL = 3, + SURFACE_WALL_CRAWL = 4, + SURFACE_CEILING = 5, + SURFACE_HORIZONTAL_POLE = 6, + SURFACE_PERCH = 7, + SURFACE_AIR = 8, + SURFACE_CUSTOM = 9 +}; + +struct surfaceEdge_t { + int verts[2]; + int tris[2]; +}; + +class idSurface { +public: + idSurface(); + idSurface(const idSurface&) = default; + ~idSurface(); + + void TranslateSelf(const idVec3& translation); + void RotateSelf(const idMat3& rotation); + void GetBounds(idBounds& bounds) const; + + idList verts; + idList indexes; + idList edges; + idList edgeIndexes; + +protected: + void GenerateEdgeIndexes(); +}; + +struct matchVert_t { + int next; + int v; + int tv; + int morph; + unsigned int color; + idVec3 normal; + idVec3 tangents[2]; +}; + +class idMaterial; + +class idRawSurface { +public: + const idMaterial* material; + int materialNum; + const idList* pvList; + const idList* ptvList; + const idList* pMorphList; + idList indexes; + idList verts; + idList vertHash; + bool generateNormals; + float normalEpsilon; +}; + +// These Xbox graphics objects appeared in the PDB attribution for surface.h, +// but are platform types rather than idSurface storage. Keep their names +// available without importing XDK definitions into the PC idLib boundary. +struct D3DSurface; +struct D3DSURFACES { + D3DSurface* pDepthStencilSurface; + D3DSurface* pRenderTarget[4]; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(surfaceEdge_t) == 16, + "Recovered surfaceEdge_t ABI changed"); +static_assert(sizeof(idSurface) == 64, "Recovered idSurface ABI changed"); +static_assert(sizeof(matchVert_t) == 56, + "Recovered matchVert_t ABI changed"); +static_assert(sizeof(idRawSurface) == 76, + "Recovered idRawSurface ABI changed"); +static_assert(sizeof(D3DSURFACES) == 20, + "Recovered D3DSURFACES ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/surface_patch.h b/source/shared/idlib/geometry/surface_patch.h new file mode 100644 index 0000000..a296a95 --- /dev/null +++ b/source/shared/idlib/geometry/surface_patch.h @@ -0,0 +1,44 @@ +#pragma once + +#include "surface.h" + +class alignas(4) idSurface_Patch : public idSurface { +public: + idSurface_Patch(); + idSurface_Patch(const idSurface_Patch& other); + ~idSurface_Patch(); + + void SetSize(int patchWidth, int patchHeight); + void Subdivide(float horizontalError, float verticalError, + float maxLength, bool generateNormals); + void SubdivideExplicit(int horizontalSubdivisions, + int verticalSubdivisions, bool generateNormals, + bool removeLinearColumnsRows); + + int width; + int height; + int maxWidth; + int maxHeight; + bool expanded; + +private: + void Collapse(); + void Expand(); + void GenerateIndexes(); + void GenerateNormals(); + void ProjectPointOntoVector(const idVec3& point, + const idVec3& start, const idVec3& end, idVec3& projected); + void PutOnCurve(); + void RemoveLinearColumnsRows(); + void ResizeExpanded(int newHeight, int newWidth); + void SampleSinglePatch(const idDrawVert (*controlPoints)[3], + int baseCol, int baseRow, int width, int height, int horzSub, + idDrawVert* out) const; + void SampleSinglePatchPoint(const idDrawVert (*controlPoints)[3], + float u, float v, idDrawVert* out) const; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idSurface_Patch) == 84, + "Recovered idSurface_Patch ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/surface_polytope.h b/source/shared/idlib/geometry/surface_polytope.h new file mode 100644 index 0000000..0d442e4 --- /dev/null +++ b/source/shared/idlib/geometry/surface_polytope.h @@ -0,0 +1,14 @@ +#pragma once + +#include "surface.h" + +class idSurface_Polytope : public idSurface { +public: + idSurface_Polytope() = default; + ~idSurface_Polytope() = default; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idSurface_Polytope) == 64, + "Recovered idSurface_Polytope ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/surface_sweptspline.h b/source/shared/idlib/geometry/surface_sweptspline.h new file mode 100644 index 0000000..556cde2 --- /dev/null +++ b/source/shared/idlib/geometry/surface_sweptspline.h @@ -0,0 +1,20 @@ +#pragma once + +#include "surface.h" + +template +class idCurve_Spline; + +class idSurface_SweptSpline : public idSurface { +public: + idSurface_SweptSpline() : spline(nullptr), sweptSpline(nullptr) {} + ~idSurface_SweptSpline() = default; + + idCurve_Spline* spline; + idCurve_Spline* sweptSpline; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idSurface_SweptSpline) == 72, + "Recovered idSurface_SweptSpline ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/tracemodel.h b/source/shared/idlib/geometry/tracemodel.h new file mode 100644 index 0000000..53a1006 --- /dev/null +++ b/source/shared/idlib/geometry/tracemodel.h @@ -0,0 +1,104 @@ +#pragma once + +#include "../bv/bounds.h" +#include "../math/matrix.h" + +#include +#include + +enum traceModel_t : int { + TRM_INVALID = 0, + TRM_BOX = 1, + TRM_OCTAHEDRON = 2, + TRM_DODECAHEDRON = 3, + TRM_CYLINDER = 4, + TRM_CONE = 5, + TRM_BONE = 6, + TRM_POLYGON = 7, + TRM_POLYGONVOLUME = 8, + TRM_CUSTOM = 9 +}; + +struct traceModelEdge_t { + std::uint16_t v[2]; +}; + +struct polygonIntegrals_t; +struct projectionIntegrals_t; +struct volumeIntegrals_t; + +class idTraceModel { +public: + static void* operator new(std::size_t size); + static void operator delete(void* memory); + + void SetupBox(const idBounds& bounds); + void SetupBox(float size); + void SetupOctahedron(const idBounds& bounds); + void SetupDodecahedron(const idBounds& bounds); + void SetupCylinder(const idBounds& bounds, int numSides); + void SetupCylinder(float height, float width, int numSides); + void SetupCone(const idBounds& bounds, int numSides); + void SetupBone(float length, float width); + void SetupPolygon(const idVec3* vertices, int count); + void SetupPolygonVolume(const idVec3* vertices, int count, + const idVec3& depth); + void ClearUnused(); + void Translate(const idVec3& translation); + void Rotate(const idMat3& rotation); + void Scale(const idVec3& scale); + void Shrink(float amount); + void CalculateInsetSphereRadius(); + int GenerateEdgeNormals(); + void TestConvexity(); + bool Compare(const idTraceModel& other) const; + bool ContainsPoint(const idVec3& point) const; + bool HasFlaps() const; + bool IsClosedSurface() const; + void GetMassProperties(float density, float& mass, + idVec3& centerOfMass, idMat3& inertiaTensor) const; + + float vertsX[32]; + float vertsY[32]; + float vertsZ[32]; + float edgeNormalX[32]; + float edgeNormalY[32]; + float edgeNormalZ[32]; + float polyPlaneX[16]; + float polyPlaneY[16]; + float polyPlaneZ[16]; + float polyPlaneW[16]; + std::uint8_t polyEdges[16][16]; + unsigned int numPolyEdges[16]; + traceModelEdge_t edges[32]; + traceModel_t type; + unsigned int numVerts; + unsigned int numEdges; + unsigned int numPolys; + unsigned int maxPolyEdges; + idVec3 offset; + idBounds bounds; + float radius; + bool isConvex; + std::uint8_t pad[3]; + +private: + void CalculatePolygonPlanes(); + void ExtendPolygonToVolume(const idVec3& depth); + void InitBone(); + void InitBox(); + void InitDodecahedron(); + void InitOctahedron(); + void PolygonIntegrals(int poly, int a, int b, int c, + polygonIntegrals_t& integrals) const; + void ProjectionIntegrals(int poly, int a, int b, + projectionIntegrals_t& integrals) const; + void VolumeIntegrals(volumeIntegrals_t& integrals) const; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(traceModelEdge_t) == 4, + "Recovered traceModelEdge_t ABI changed"); +static_assert(sizeof(idTraceModel) == 1536, + "Recovered idTraceModel ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/winding.h b/source/shared/idlib/geometry/winding.h new file mode 100644 index 0000000..fcb91ca --- /dev/null +++ b/source/shared/idlib/geometry/winding.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../bv/bounds.h" +#include "../containers/list.h" +#include "../math/plane.h" + +class idWinding { +public: + idWinding(); + idWinding(const idWinding& other); + virtual ~idWinding(); + + idWinding& operator=(const idWinding& other); + virtual void Clear(); + virtual bool ReAllocate(int numPoints, bool keep = false); + void AddPoint(const idVec3& point); + void AddPoint(const idVec5& point); + void AddToConvexHull(const idVec3& point, const idVec3& normal, + float epsilon = 0.1f); + void BaseForPlane(const idVec3& normal, float distance, + float size = 65536.0f); + bool ClipInPlace(const idPlane& plane, float epsilon = 0.1f, + bool keepOn = false); + void GetBounds(idBounds& bounds) const; + idVec3 GetCenter() const; + void GetPlane(idPlane& plane) const; + bool IsHuge(float radius) const; + bool PointInside(const idVec3& normal, const idVec3& point, + float epsilon) const; + void ReverseSelf(); + + int GetNumPoints() const { return numPoints; } + const idVec5& operator[](int index) const { return p[index]; } + idVec5& operator[](int index) { return p[index]; } + + int numPoints; + idVec5* p; + int allocedSize; +}; + +class idFixedWinding : public idWinding { +public: + idFixedWinding(); + ~idFixedWinding() override; + void Clear() override; + bool ReAllocate(int numPoints, bool keep = false) override; + int SplitInPlace(const idPlane& plane, float epsilon, + idFixedWinding* back); + + idVec5 data[64]; +}; + +class idCarveWinding { +public: + int otherPlaneNum; + idList edges; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idWinding) == 16, "Recovered idWinding ABI changed"); +static_assert(sizeof(idFixedWinding) == 1296, + "Recovered idFixedWinding ABI changed"); +static_assert(sizeof(idCarveWinding) == 20, + "Recovered idCarveWinding ABI changed"); +#endif diff --git a/source/shared/idlib/geometry/winding2d.h b/source/shared/idlib/geometry/winding2d.h new file mode 100644 index 0000000..5cfcdef --- /dev/null +++ b/source/shared/idlib/geometry/winding2d.h @@ -0,0 +1,33 @@ +#pragma once + +#include "idlib/math/vector.h" + +#include + +class idWinding2D { +public: + static constexpr int MAX_POINTS = 32; + int numPoints; + idVec2 p[MAX_POINTS]; + idVec2 st[MAX_POINTS]; + + idWinding2D() : numPoints(0) {} + void Clear() { numPoints = 0; } + int GetNumPoints() const { return numPoints; } + bool AddPoint(const idVec2& point, const idVec2& texCoord = idVec2()) { + if (numPoints >= MAX_POINTS) return false; + p[numPoints] = point; + st[numPoints] = texCoord; + ++numPoints; + return true; + } + idVec2& operator[](const int index) { + assert(index >= 0 && index < numPoints); return p[index]; + } + const idVec2& operator[](const int index) const { + assert(index >= 0 && index < numPoints); return p[index]; + } +}; + +static_assert(sizeof(idWinding2D) == 516, "Recovered idWinding2D ABI changed"); + diff --git a/source/shared/idlib/handle.h b/source/shared/idlib/handle.h new file mode 100644 index 0000000..bbbaef8 --- /dev/null +++ b/source/shared/idlib/handle.h @@ -0,0 +1,67 @@ +#pragma once + +// Recovered from tungsten.exe.h. The tag type prevents unrelated handles +// with the same storage type from being mixed, while InvalidValue is part of +// the type and supplies the default/invalid representation. +template +class idHandle { +public: + valueType value; + + idHandle() + : value(InvalidValue) { + } + + idHandle(const valueType initialValue) + : value(initialValue) { + } + + bool IsValid() const { + return value != InvalidValue; + } + + void Invalidate() { + value = InvalidValue; + } + + valueType Get() const { + return value; + } + + operator valueType() const { + return value; + } + + idHandle& operator=(const valueType newValue) { + value = newValue; + return *this; + } + + bool operator==(const idHandle& other) const { return value == other.value; } + bool operator!=(const idHandle& other) const { return value != other.value; } + bool operator<(const idHandle& other) const { return value < other.value; } + + bool operator==(const valueType other) const { return value == other; } + bool operator!=(const valueType other) const { return value != other; } +}; + +template +inline bool operator==(const valueType lhs, + const idHandle& rhs) { + return rhs == lhs; +} + +template +inline bool operator!=(const valueType lhs, + const idHandle& rhs) { + return rhs != lhs; +} + +// Exact two-word layout recovered as IDA local type 14180. +struct pvsHandle_t { + int i; + unsigned int h; +}; + +static_assert(sizeof(pvsHandle_t) == 8, "Recovered pvsHandle_t ABI changed"); + diff --git a/source/shared/idlib/hashing/crc32.cpp b/source/shared/idlib/hashing/crc32.cpp new file mode 100644 index 0000000..301b73a --- /dev/null +++ b/source/shared/idlib/hashing/crc32.cpp @@ -0,0 +1,42 @@ +#include "crc32.h" + +#include +#include + +namespace { + +const std::uint32_t* CRC32Table() { + static const std::array table = [] { + std::array values = {}; + for (std::uint32_t index = 0; index < 256; ++index) { + std::uint32_t value = index; + for (int bit = 0; bit < 8; ++bit) + value = (value >> 1) ^ ((value & 1) != 0 ? 0xEDB88320u : 0u); + values[index] = value; + } + return values; + }(); + return table.data(); +} + +} // namespace + +void CRC32_UpdateChecksum(unsigned int& checksum, const void* data, + const int length) { + if (data == nullptr || length <= 0) return; + const std::uint32_t* const table = CRC32Table(); + const unsigned char* bytes = static_cast(data); + for (int index = 0; index < length; ++index) + checksum = table[(checksum ^ bytes[index]) & 0xFFu] ^ (checksum >> 8); +} + +void CRC32_FinishChecksum(unsigned int& checksum) { + checksum = ~checksum; +} + +unsigned int CRC32_BlockChecksum(const void* data, const int length) { + unsigned int checksum = 0xFFFFFFFFu; + CRC32_UpdateChecksum(checksum, data, length); + CRC32_FinishChecksum(checksum); + return checksum; +} diff --git a/source/shared/idlib/hashing/crc32.h b/source/shared/idlib/hashing/crc32.h new file mode 100644 index 0000000..7c214e3 --- /dev/null +++ b/source/shared/idlib/hashing/crc32.h @@ -0,0 +1,5 @@ +#pragma once + +void CRC32_UpdateChecksum(unsigned int& checksum, const void* data, int length); +void CRC32_FinishChecksum(unsigned int& checksum); +unsigned int CRC32_BlockChecksum(const void* data, int length); diff --git a/source/shared/idlib/hashing/crc8.h b/source/shared/idlib/hashing/crc8.h new file mode 100644 index 0000000..6dd7896 --- /dev/null +++ b/source/shared/idlib/hashing/crc8.h @@ -0,0 +1,13 @@ +#pragma once + +#include "idlib/handle.h" + +enum invalidCrc_t : int { + INVALID_CRC = 0xFFFF +}; + +typedef idHandle(INVALID_CRC)> crc_t; + +static_assert(sizeof(crc_t) == 2, "Recovered crc_t ABI changed"); + diff --git a/source/shared/idlib/hashing/md5.cpp b/source/shared/idlib/hashing/md5.cpp new file mode 100644 index 0000000..1f58061 --- /dev/null +++ b/source/shared/idlib/hashing/md5.cpp @@ -0,0 +1,149 @@ +#include "md5.h" + +#include +#include + +namespace { + +std::uint32_t RotateLeft(const std::uint32_t value, const int amount) { + return (value << amount) | (value >> (32 - amount)); +} + +std::uint32_t LoadLittle32(const unsigned char* bytes) { + return static_cast(bytes[0]) + | (static_cast(bytes[1]) << 8) + | (static_cast(bytes[2]) << 16) + | (static_cast(bytes[3]) << 24); +} + +void StoreLittle32(unsigned char* bytes, const std::uint32_t value) { + bytes[0] = static_cast(value); + bytes[1] = static_cast(value >> 8); + bytes[2] = static_cast(value >> 16); + bytes[3] = static_cast(value >> 24); +} + +} // namespace + +void MD5_Transform(unsigned int state[4], const unsigned char block[64]) { + static const int shifts[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const std::uint32_t constants[64] = { + 0xd76aa478u, 0xe8c7b756u, 0x242070dbu, 0xc1bdceeeu, + 0xf57c0fafu, 0x4787c62au, 0xa8304613u, 0xfd469501u, + 0x698098d8u, 0x8b44f7afu, 0xffff5bb1u, 0x895cd7beu, + 0x6b901122u, 0xfd987193u, 0xa679438eu, 0x49b40821u, + 0xf61e2562u, 0xc040b340u, 0x265e5a51u, 0xe9b6c7aau, + 0xd62f105du, 0x02441453u, 0xd8a1e681u, 0xe7d3fbc8u, + 0x21e1cde6u, 0xc33707d6u, 0xf4d50d87u, 0x455a14edu, + 0xa9e3e905u, 0xfcefa3f8u, 0x676f02d9u, 0x8d2a4c8au, + 0xfffa3942u, 0x8771f681u, 0x6d9d6122u, 0xfde5380cu, + 0xa4beea44u, 0x4bdecfa9u, 0xf6bb4b60u, 0xbebfbc70u, + 0x289b7ec6u, 0xeaa127fau, 0xd4ef3085u, 0x04881d05u, + 0xd9d4d039u, 0xe6db99e5u, 0x1fa27cf8u, 0xc4ac5665u, + 0xf4292244u, 0x432aff97u, 0xab9423a7u, 0xfc93a039u, + 0x655b59c3u, 0x8f0ccc92u, 0xffeff47du, 0x85845dd1u, + 0x6fa87e4fu, 0xfe2ce6e0u, 0xa3014314u, 0x4e0811a1u, + 0xf7537e82u, 0xbd3af235u, 0x2ad7d2bbu, 0xeb86d391u + }; + + std::uint32_t words[16]; + for (int index = 0; index < 16; ++index) + words[index] = LoadLittle32(block + index * 4); + + std::uint32_t a = state[0]; + std::uint32_t b = state[1]; + std::uint32_t c = state[2]; + std::uint32_t d = state[3]; + for (int index = 0; index < 64; ++index) { + std::uint32_t function; + int wordIndex; + if (index < 16) { + function = (b & c) | (~b & d); + wordIndex = index; + } else if (index < 32) { + function = (d & b) | (~d & c); + wordIndex = (5 * index + 1) & 15; + } else if (index < 48) { + function = b ^ c ^ d; + wordIndex = (3 * index + 5) & 15; + } else { + function = c ^ (b | ~d); + wordIndex = (7 * index) & 15; + } + const std::uint32_t oldD = d; + d = c; + c = b; + b += RotateLeft(a + function + constants[index] + words[wordIndex], + shifts[index]); + a = oldD; + } + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +void MD5_Init(MD5_CTX* context) { + context->state[0] = 0x67452301u; + context->state[1] = 0xefcdab89u; + context->state[2] = 0x98badcfeu; + context->state[3] = 0x10325476u; + context->bits[0] = 0; + context->bits[1] = 0; + std::memset(context->in, 0, sizeof(context->in)); +} + +void MD5_Update(MD5_CTX* context, const unsigned char* input, + const unsigned int inputLength) { + if (inputLength == 0) return; + const unsigned int oldLowBits = context->bits[0]; + const unsigned int bufferIndex = (oldLowBits >> 3) & 63u; + context->bits[0] += inputLength << 3; + if (context->bits[0] < oldLowBits) ++context->bits[1]; + context->bits[1] += inputLength >> 29; + + unsigned int consumed = 0; + const unsigned int firstBlock = 64u - bufferIndex; + if (inputLength >= firstBlock) { + std::memcpy(context->in + bufferIndex, input, firstBlock); + MD5_Transform(context->state, context->in); + consumed = firstBlock; + while (consumed + 63u < inputLength) { + MD5_Transform(context->state, input + consumed); + consumed += 64; + } + std::memcpy(context->in, input + consumed, inputLength - consumed); + } else { + std::memcpy(context->in + bufferIndex, input, inputLength); + } +} + +void MD5_Final(MD5_CTX* context, unsigned char digest[16]) { + static const unsigned char padding[64] = { 0x80 }; + unsigned char bitCount[8]; + StoreLittle32(bitCount, context->bits[0]); + StoreLittle32(bitCount + 4, context->bits[1]); + const unsigned int bufferIndex = (context->bits[0] >> 3) & 63u; + const unsigned int paddingLength = bufferIndex < 56 + ? 56u - bufferIndex : 120u - bufferIndex; + MD5_Update(context, padding, paddingLength); + MD5_Update(context, bitCount, sizeof(bitCount)); + for (int index = 0; index < 4; ++index) + StoreLittle32(digest + index * 4, context->state[index]); + std::memset(context, 0, sizeof(*context)); +} + +unsigned int MD5_BlockChecksum(const void* data, const unsigned int length) { + MD5_CTX context; + unsigned char digest[16]; + MD5_Init(&context); + MD5_Update(&context, static_cast(data), length); + MD5_Final(&context, digest); + return LoadLittle32(digest) ^ LoadLittle32(digest + 4) + ^ LoadLittle32(digest + 8) ^ LoadLittle32(digest + 12); +} diff --git a/source/shared/idlib/hashing/md5.h b/source/shared/idlib/hashing/md5.h new file mode 100644 index 0000000..bf6539a --- /dev/null +++ b/source/shared/idlib/hashing/md5.h @@ -0,0 +1,16 @@ +#pragma once + +struct MD5_CTX { + unsigned int state[4]; + unsigned int bits[2]; + unsigned char in[64]; +}; + +void MD5_Transform(unsigned int state[4], const unsigned char block[64]); +void MD5_Init(MD5_CTX* context); +void MD5_Update(MD5_CTX* context, const unsigned char* input, + unsigned int inputLength); +void MD5_Final(MD5_CTX* context, unsigned char digest[16]); +unsigned int MD5_BlockChecksum(const void* data, unsigned int length); + +static_assert(sizeof(MD5_CTX) == 88, "Recovered MD5_CTX layout changed"); diff --git a/source/shared/idlib/langdict.h b/source/shared/idlib/langdict.h new file mode 100644 index 0000000..5aa386f --- /dev/null +++ b/source/shared/idlib/langdict.h @@ -0,0 +1,71 @@ +#pragma once + +#include "blockalloc.h" +#include "containers/hashindex.h" +#include "containers/list.h" + +class idLangKeyValue { +public: + char* key; + char* value; +}; + +class idLangDict { +public: + idLangDict(); + ~idLangDict(); + + static bool IsStringId(const char* string); + bool Load(const unsigned char* buffer, int bufferLength, const char* name); + bool Save(const char* fileName); + void Clear(); + bool SetString(const char* key, const char* value); + bool DeleteString(int index); + void AddKeyVal(const char* key, const char* value); + const idLangKeyValue* GetKeyVal(int index) const; + int GetNumKeyVals() const { return keyVals.Num(); } + + static const char* KEY_PREFIX; + static int KEY_PREFIX_LEN; + + idDynamicBlockAlloc blockAlloc; + idList keyVals; + idHashIndex keyIndex; + +private: + int FindStringIndex(const char* string) const; + const char* FindString_r(const char* string, int& depth) const; + + friend class idStrId; + friend class idLocalization; +}; + +class idStrId { +public: + idStrId() : index(-1) {} + explicit idStrId(const char* key) : index(-1) { Set(key); } + + void Set(const char* key); + const char* GetKey() const; + const char* GetLocalizedString() const; + int GetIndex() const { return index; } + bool IsValid() const { return index >= 0; } + + int index; +}; + +class idLocalization { +public: + static bool LoadDictionary(const unsigned char* data, int dataLength, + const char* fileName); + static const char* GetString(const char* string); + static const char* FindString(const char* string); + + static idLangDict languageDict; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idLangKeyValue) == 8, + "Recovered idLangKeyValue ABI changed"); +static_assert(sizeof(idStrId) == 4, "Recovered idStrId ABI changed"); +#endif diff --git a/source/shared/idlib/math/angles.h b/source/shared/idlib/math/angles.h new file mode 100644 index 0000000..f762902 --- /dev/null +++ b/source/shared/idlib/math/angles.h @@ -0,0 +1,4 @@ +#pragma once + +#include "idlib/math/vector.h" + diff --git a/source/shared/idlib/math/complex.h b/source/shared/idlib/math/complex.h new file mode 100644 index 0000000..248d676 --- /dev/null +++ b/source/shared/idlib/math/complex.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +class idComplex { +public: + float r; + float i; + + idComplex() = default; + idComplex(const float real, const float imaginary) : r(real), i(imaginary) {} + + void Set(const float real, const float imaginary) { r = real; i = imaginary; } + void Zero() { r = 0.0f; i = 0.0f; } + + float operator[](const int index) const { + assert(index >= 0 && index < 2); + return (&r)[index]; + } + float& operator[](const int index) { + assert(index >= 0 && index < 2); + return (&r)[index]; + } + + idComplex operator-() const { return idComplex(-r, -i); } + idComplex operator+(const idComplex& other) const { + return idComplex(r + other.r, i + other.i); + } + idComplex operator-(const idComplex& other) const { + return idComplex(r - other.r, i - other.i); + } + idComplex operator*(const idComplex& other) const { + return idComplex(r * other.r - i * other.i, + i * other.r + r * other.i); + } + idComplex operator/(const idComplex& other) const { + const float denominator = other.r * other.r + other.i * other.i; + assert(denominator != 0.0f); + return idComplex((r * other.r + i * other.i) / denominator, + (i * other.r - r * other.i) / denominator); + } + idComplex operator*(const float scale) const { return idComplex(r * scale, i * scale); } + idComplex operator/(const float scale) const { + assert(scale != 0.0f); + return idComplex(r / scale, i / scale); + } + idComplex operator+(const float value) const { return idComplex(r + value, i); } + idComplex operator-(const float value) const { return idComplex(r - value, i); } + + idComplex& operator+=(const idComplex& other) { r += other.r; i += other.i; return *this; } + idComplex& operator-=(const idComplex& other) { r -= other.r; i -= other.i; return *this; } + idComplex& operator*=(const idComplex& other) { return *this = *this * other; } + idComplex& operator/=(const idComplex& other) { return *this = *this / other; } + idComplex& operator+=(const float value) { r += value; return *this; } + idComplex& operator-=(const float value) { r -= value; return *this; } + idComplex& operator*=(const float scale) { r *= scale; i *= scale; return *this; } + idComplex& operator/=(const float scale) { + assert(scale != 0.0f); r /= scale; i /= scale; return *this; + } + + bool Compare(const idComplex& other) const { return r == other.r && i == other.i; } + bool Compare(const idComplex& other, const float epsilon) const { + return std::fabs(r - other.r) <= epsilon + && std::fabs(i - other.i) <= epsilon; + } + bool operator==(const idComplex& other) const { return Compare(other); } + bool operator!=(const idComplex& other) const { return !Compare(other); } + + idComplex Reciprocal() const { + const float denominator = r * r + i * i; + assert(denominator != 0.0f); + return idComplex(r / denominator, -i / denominator); + } + idComplex Sqrt() const { + const float magnitude = Abs(); + const float real = std::sqrt((magnitude + r) * 0.5f); + const float imaginary = std::copysign( + std::sqrt((magnitude - r) * 0.5f), i); + return idComplex(real, imaginary); + } + float Abs() const { return std::sqrt(r * r + i * i); } + int GetDimension() const { return 2; } + const float* ToFloatPtr() const { return &r; } + float* ToFloatPtr() { return &r; } +}; + +inline idComplex operator*(const float lhs, const idComplex& rhs) { return rhs * lhs; } +inline idComplex operator/(const float lhs, const idComplex& rhs) { + return idComplex(lhs, 0.0f) / rhs; +} +inline idComplex operator+(const float lhs, const idComplex& rhs) { return rhs + lhs; } +inline idComplex operator-(const float lhs, const idComplex& rhs) { + return idComplex(lhs - rhs.r, -rhs.i); +} + +static_assert(sizeof(idComplex) == 8, "Recovered idComplex ABI changed"); + diff --git a/source/shared/idlib/math/curve.h b/source/shared/idlib/math/curve.h new file mode 100644 index 0000000..b0d6a13 --- /dev/null +++ b/source/shared/idlib/math/curve.h @@ -0,0 +1,141 @@ +#pragma once + +#include "angles.h" +#include "vector.h" +#include "../containers/list.h" + +template +class alignas(4) idCurve { +public: + idCurve() : times(), values(), currentIndex(-1), changed(true) {} + virtual ~idCurve() = default; + + virtual int AddValue(float time, const type_t& value) { + int index = 0; + while (index < times.Num() && times[index] < time) ++index; + times.Insert(time, index); + values.Insert(value, index); + changed = true; + currentIndex = -1; + return index; + } + + virtual void RemoveIndex(int index) { + if (index < 0 || index >= times.Num()) return; + times.RemoveIndex(index); + values.RemoveIndex(index); + changed = true; + currentIndex = -1; + } + + virtual void Clear() { + times.Clear(); + values.Clear(); + currentIndex = -1; + changed = true; + } + + virtual void SetNumValues(int count) { + times.SetNum(count); + values.SetNum(count); + currentIndex = -1; + changed = true; + } + + virtual type_t GetCurrentValue(float time) const { + if (values.Num() == 0) return type_t(); + return values[IndexForTime(time)]; + } + virtual type_t GetCurrentFirstDerivative(float) const { return type_t(); } + virtual type_t GetCurrentSecondDerivative(float) const { return type_t(); } + virtual bool IsDone(float time) const { + return times.Num() == 0 || time >= times[times.Num() - 1]; + } + virtual float GetLengthForTime(float time) const { + return EstimateLengthForTime(time); + } + virtual float EstimateLengthForTime(float) const { return 0.0f; } + + int GetNumValues() const { return values.Num(); } + float GetTime(int index) const { return times[index]; } + const type_t& GetValue(int index) const { return values[index]; } + type_t& GetValue(int index) { changed = true; return values[index]; } + void SetTime(int index, float time) { times[index] = time; changed = true; } + void SetValue(int index, const type_t& value) { + values[index] = value; + changed = true; + } + void ShiftTime(float delta) { + for (int index = 0; index < times.Num(); ++index) times[index] += delta; + changed = true; + } + void MakeUniform(float totalTime) { + const int count = times.Num(); + if (count <= 1) return; + const float step = totalTime / static_cast(count - 1); + for (int index = 0; index < count; ++index) times[index] = step * index; + changed = true; + } + float MakeUniformMoveSpeed(float totalTime) { + MakeUniform(totalTime); + return totalTime; + } + void SetConstantSpeed(float totalTime) { MakeUniform(totalTime); } + float GetLengthBetweenKnots(int, int) const { return 0.0f; } + float GetTimeForLength(float length, float epsilon = 0.1f) const { + return EstimateTimeForLength(length, epsilon); + } + float EstimateTimeForLength(float length, float) const { return length; } + void GetBatchValues(const float* timesIn, type_t* valuesOut, + type_t* derivativesOut, int count) const { + for (int index = 0; index < count; ++index) { + valuesOut[index] = GetCurrentValue(timesIn[index]); + if (derivativesOut != nullptr) { + derivativesOut[index] = GetCurrentFirstDerivative(timesIn[index]); + } + } + } + + idList times; + idList values; + mutable int currentIndex; + mutable bool changed; + +protected: + virtual idCurve* CreateNewCurve() const { + return new idCurve(); + } + + int IndexForTime(float time) const { + if (times.Num() <= 1) return 0; + int low = 0; + int high = times.Num(); + while (low < high) { + const int middle = (low + high) / 2; + if (times[middle] <= time) low = middle + 1; + else high = middle; + } + currentIndex = low > 0 ? low - 1 : 0; + return currentIndex; + } + + float RombergIntegral(float, float, int) const { return 0.0f; } +}; + +template +class idCurve_Spline : public idCurve { +public: + idCurve_Spline() = default; + ~idCurve_Spline() override = default; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCurve) == 44, + "Recovered idCurve ABI changed"); +static_assert(sizeof(idCurve) == 44, + "Recovered idCurve ABI changed"); +static_assert(sizeof(idCurve) == 44, + "Recovered idCurve ABI changed"); +static_assert(sizeof(idCurve) == 44, + "Recovered idCurve ABI changed"); +#endif diff --git a/source/shared/idlib/math/degrees.h b/source/shared/idlib/math/degrees.h new file mode 100644 index 0000000..91c3f15 --- /dev/null +++ b/source/shared/idlib/math/degrees.h @@ -0,0 +1,9 @@ +#pragma once + +#include "idlib/typesafenumber.h" + +enum DegreesUnique_t : int; +typedef idTypesafeNumber degrees_t; + +static_assert(sizeof(degrees_t) == 4, "Recovered degrees_t ABI changed"); + diff --git a/source/shared/idlib/math/extrapolate.h b/source/shared/idlib/math/extrapolate.h new file mode 100644 index 0000000..0d2eff9 --- /dev/null +++ b/source/shared/idlib/math/extrapolate.h @@ -0,0 +1,155 @@ +#pragma once + +#include "vector.h" + +#include + +enum extrapolation_t : int { + EXTRAPOLATION_NONE = 0x01, + EXTRAPOLATION_LINEAR = 0x02, + EXTRAPOLATION_ACCELLINEAR = 0x04, + EXTRAPOLATION_DECELLINEAR = 0x08, + EXTRAPOLATION_ACCELSINE = 0x10, + EXTRAPOLATION_DECELSINE = 0x20, + EXTRAPOLATION_NOSTOP = 0x40 +}; + +template +class idExtrapolate { +public: + extrapolation_t extrapolationType; + float startTime; + float duration; + T startValue; + T baseSpeed; + T speed; + mutable float currentTime; + mutable T currentValue; + + idExtrapolate() + : extrapolationType(EXTRAPOLATION_NONE), startTime(0.0f), duration(0.0f), + startValue(T()), baseSpeed(T()), speed(T()), currentTime(-1.0f), + currentValue(startValue) { + } + + void Init(const float newStartTime, const float newDuration, + const T& newStartValue, const T& newBaseSpeed, const T& newSpeed, + const extrapolation_t newType) { + extrapolationType = newType; + startTime = newStartTime; + duration = newDuration; + startValue = newStartValue; + baseSpeed = newBaseSpeed; + speed = newSpeed; + currentTime = -1.0f; + currentValue = startValue; + } + + T GetCurrentValue(float time) const { + if (time == currentTime) return currentValue; + currentTime = time; + if (time < startTime) { + currentValue = startValue; + return currentValue; + } + + const int type = static_cast(extrapolationType) & ~EXTRAPOLATION_NOSTOP; + if (duration == 0.0f + && type != EXTRAPOLATION_NONE && type != EXTRAPOLATION_LINEAR) { + currentValue = startValue; + return currentValue; + } + if ((static_cast(extrapolationType) & EXTRAPOLATION_NOSTOP) == 0 + && time > startTime + duration) { + time = startTime + duration; + } + + const float elapsed = time - startTime; + const float elapsedSeconds = elapsed * 0.001f; + const float fraction = duration != 0.0f ? elapsed / duration : 0.0f; + switch (type) { + case EXTRAPOLATION_NONE: + currentValue = startValue + baseSpeed * elapsedSeconds; + break; + case EXTRAPOLATION_LINEAR: + currentValue = startValue + (baseSpeed + speed) * elapsedSeconds; + break; + case EXTRAPOLATION_ACCELLINEAR: + currentValue = startValue + baseSpeed * elapsedSeconds + + speed * (0.5f * fraction * fraction * duration * 0.001f); + break; + case EXTRAPOLATION_DECELLINEAR: + currentValue = startValue + baseSpeed * elapsedSeconds + + speed * ((-0.5f * fraction * fraction + fraction) + * duration * 0.001f); + break; + case EXTRAPOLATION_ACCELSINE: + currentValue = startValue + baseSpeed * elapsedSeconds + + speed * ((1.0f - std::cos(fraction * HALF_PI)) + * duration * SQRT_HALF * 0.001f); + break; + case EXTRAPOLATION_DECELSINE: + currentValue = startValue + baseSpeed * elapsedSeconds + + speed * (std::sin(fraction * HALF_PI) + * duration * SQRT_HALF * 0.001f); + break; + default: + currentValue = startValue; + break; + } + return currentValue; + } + + T GetCurrentSpeed(const float time) const { + if (time < startTime) return T(); + const int type = static_cast(extrapolationType) & ~EXTRAPOLATION_NOSTOP; + if (duration == 0.0f + && type != EXTRAPOLATION_NONE && type != EXTRAPOLATION_LINEAR) { + return T(); + } + if ((static_cast(extrapolationType) & EXTRAPOLATION_NOSTOP) == 0 + && time > startTime + duration) { + return T(); + } + const float fraction = duration != 0.0f + ? (time - startTime) / duration : 0.0f; + switch (type) { + case EXTRAPOLATION_LINEAR: return baseSpeed + speed; + case EXTRAPOLATION_ACCELLINEAR: return baseSpeed + speed * fraction; + case EXTRAPOLATION_DECELLINEAR: return baseSpeed + speed * (1.0f - fraction); + case EXTRAPOLATION_ACCELSINE: + return baseSpeed + speed * std::sin(fraction * HALF_PI); + case EXTRAPOLATION_DECELSINE: + return baseSpeed + speed * std::cos(fraction * HALF_PI); + case EXTRAPOLATION_NONE: + default: return baseSpeed; + } + } + + bool IsDone(const float time) const { + return (static_cast(extrapolationType) & EXTRAPOLATION_NOSTOP) == 0 + && time >= startTime + duration; + } + + void SetStartTime(const float value) { startTime = value; currentTime = -1.0f; } + void SetStartValue(const T& value) { startValue = value; currentTime = -1.0f; } + float GetStartTime() const { return startTime; } + float GetEndTime() const { return startTime + duration; } + float GetDuration() const { return duration; } + const T& GetStartValue() const { return startValue; } + const T& GetBaseSpeed() const { return baseSpeed; } + const T& GetSpeed() const { return speed; } + +private: + static constexpr float HALF_PI = 1.57079632679489661923f; + static constexpr float SQRT_HALF = 0.70710678118654752440f; +}; + +static_assert(sizeof(idExtrapolate) == 32, + "Recovered idExtrapolate ABI changed"); +static_assert(sizeof(idExtrapolate) == 64, + "Recovered idExtrapolate ABI changed"); +static_assert(sizeof(idExtrapolate) == 64, + "Recovered idExtrapolate ABI changed"); +static_assert(sizeof(idExtrapolate) == 80, + "Recovered idExtrapolate ABI changed"); diff --git a/source/shared/idlib/math/interpolate.h b/source/shared/idlib/math/interpolate.h new file mode 100644 index 0000000..7e04031 --- /dev/null +++ b/source/shared/idlib/math/interpolate.h @@ -0,0 +1,327 @@ +#pragma once + +#include "extrapolate.h" + +#include + +enum XUI_INTERPOLATE : int { + XUI_INTERPOLATE_LINEAR = 0, + XUI_INTERPOLATE_NONE = 1, + XUI_INTERPOLATE_EASE = 2 +}; + +class idInterpolateParms { +public: + int accelTimeMs; + int decelTimeMs; + int durationMs; +}; + +template +class idInterpolate { +public: + float startTime; + float duration; + T startValue; + T endValue; + mutable float currentTime; + mutable T currentValue; + + idInterpolate() + : startTime(0.0f), duration(0.0f), startValue(T()), endValue(T()), + currentTime(-1.0f), currentValue(startValue) { + } + + void Init(const float newStartTime, const float newDuration, + const T& newStartValue, const T& newEndValue) { + startTime = newStartTime; + duration = newDuration; + startValue = newStartValue; + endValue = newEndValue; + currentTime = -1.0f; + currentValue = startValue; + } + + T GetCurrentValue(const float time) const { + if (time == currentTime) return currentValue; + currentTime = time; + const float delta = time - startTime; + if ((duration >= 0.0f && delta <= 0.0f) + || (duration < 0.0f && delta >= 0.0f)) { + currentValue = startValue; + } else if ((duration >= 0.0f && delta >= duration) + || (duration < 0.0f && delta <= duration)) { + currentValue = endValue; + } else { + currentValue = startValue + (endValue - startValue) * (delta / duration); + } + return currentValue; + } + + T GetCurrentValueEaseOut(const float time) const { + const float delta = time - startTime; + if (duration <= 0.0f || delta <= 0.0f) return startValue; + if (delta >= duration) return endValue; + const float fraction = std::sin((delta / duration) * 1.57079632679489661923f); + currentTime = time; + currentValue = startValue + (endValue - startValue) * fraction; + return currentValue; + } + + bool IsDone(const float time) const { + return duration >= 0.0f ? time >= startTime + duration + : time <= startTime + duration; + } + void SetStartTime(const float value) { startTime = value; currentTime = -1.0f; } + void SetDuration(const float value) { duration = value; currentTime = -1.0f; } + void SetStartValue(const T& value) { startValue = value; currentTime = -1.0f; } + void SetEndValue(const T& value) { endValue = value; currentTime = -1.0f; } + float GetStartTime() const { return startTime; } + float GetEndTime() const { return startTime + duration; } + float GetDuration() const { return duration; } + const T& GetStartValue() const { return startValue; } + const T& GetEndValue() const { return endValue; } +}; + +template +class idInterpolateAccelDecelLinear { +public: + float startTime; + float accelTime; + float linearTime; + float decelTime; + T startValue; + T endValue; + mutable idExtrapolate extrapolate; + + idInterpolateAccelDecelLinear() + : startTime(0.0f), accelTime(0.0f), linearTime(0.0f), decelTime(0.0f), + startValue(T()), endValue(T()), extrapolate() { + } + + void Init(const float newStartTime, float newAccelTime, float newDecelTime, + const float duration, const T& newStartValue, const T& newEndValue) { + startTime = newStartTime; + accelTime = newAccelTime; + decelTime = newDecelTime; + startValue = newStartValue; + endValue = newEndValue; + if (duration <= 0.0f) { + linearTime = 0.0f; + extrapolate.Init(startTime, 0.0f, startValue, T(), T(), EXTRAPOLATION_NONE); + return; + } + if (accelTime + decelTime > duration) { + const float sum = accelTime + decelTime; + accelTime = sum > 0.0f ? accelTime * duration / sum : 0.0f; + decelTime = duration - accelTime; + } + linearTime = duration - accelTime - decelTime; + const float effectiveTime = 0.5f * (accelTime + decelTime) + linearTime; + const T phaseSpeed = (endValue - startValue) * (1000.0f / effectiveTime); + extrapolation_t phase = EXTRAPOLATION_ACCELLINEAR; + float phaseDuration = accelTime; + if (accelTime == 0.0f) { + phase = linearTime == 0.0f + ? EXTRAPOLATION_DECELLINEAR : EXTRAPOLATION_LINEAR; + phaseDuration = linearTime == 0.0f ? decelTime : linearTime; + } + extrapolate.Init(startTime, phaseDuration, startValue, T(), phaseSpeed, phase); + } + + T GetCurrentValue(const float time) const { + SetPhase(time); + return extrapolate.GetCurrentValue(time); + } + T GetCurrentSpeed(const float time) const { + SetPhase(time); + return extrapolate.GetCurrentSpeed(time); + } + bool IsDone(const float time) const { + return time >= startTime + accelTime + linearTime + decelTime; + } + float GetStartTime() const { return startTime; } + float GetEndTime() const { return startTime + accelTime + linearTime + decelTime; } + float GetDuration() const { return accelTime + linearTime + decelTime; } + void SetStartTime(const float value) { startTime = value; extrapolate.currentTime = -1.0f; } + void SetStartValue(const T& value) { startValue = value; extrapolate.currentTime = -1.0f; } + void SetEndValue(const T& value) { endValue = value; extrapolate.currentTime = -1.0f; } + +private: + void SetPhase(const float time) const { + const float elapsed = time - startTime; + const T zero = T(); + const T phaseSpeed = extrapolate.speed; + if (elapsed < accelTime) { + if ((static_cast(extrapolate.extrapolationType) & ~EXTRAPOLATION_NOSTOP) + != EXTRAPOLATION_ACCELLINEAR) { + extrapolate.Init(startTime, accelTime, startValue, zero, + phaseSpeed, EXTRAPOLATION_ACCELLINEAR); + } + } else if (elapsed < accelTime + linearTime) { + if ((static_cast(extrapolate.extrapolationType) & ~EXTRAPOLATION_NOSTOP) + != EXTRAPOLATION_LINEAR) { + const T phaseStart = startValue + + phaseSpeed * (accelTime * 0.0005f); + extrapolate.Init(startTime + accelTime, linearTime, phaseStart, + zero, phaseSpeed, EXTRAPOLATION_LINEAR); + } + } else if ((static_cast(extrapolate.extrapolationType) + & ~EXTRAPOLATION_NOSTOP) != EXTRAPOLATION_DECELLINEAR) { + const T phaseStart = endValue + - phaseSpeed * (decelTime * 0.0005f); + extrapolate.Init(startTime + accelTime + linearTime, decelTime, + phaseStart, zero, phaseSpeed, EXTRAPOLATION_DECELLINEAR); + } + } +}; + +template +class idInterpolateAccelDecelSine { +public: + float startTime; + float accelTime; + float linearTime; + float decelTime; + T startValue; + T endValue; + mutable idExtrapolate extrapolate; + + idInterpolateAccelDecelSine() + : startTime(0.0f), accelTime(0.0f), linearTime(0.0f), decelTime(0.0f), + startValue(T()), endValue(T()), extrapolate() { + } + + void Init(const float newStartTime, float newAccelTime, float newDecelTime, + const float duration, const T& newStartValue, const T& newEndValue) { + startTime = newStartTime; + accelTime = newAccelTime; + decelTime = newDecelTime; + startValue = newStartValue; + endValue = newEndValue; + if (duration <= 0.0f) { + linearTime = 0.0f; + extrapolate.Init(startTime, 0.0f, startValue, T(), T(), EXTRAPOLATION_NONE); + return; + } + if (accelTime + decelTime > duration) { + const float sum = accelTime + decelTime; + accelTime = sum > 0.0f ? accelTime * duration / sum : 0.0f; + decelTime = duration - accelTime; + } + linearTime = duration - accelTime - decelTime; + const float effectiveTime = 0.70710678118654752440f + * (accelTime + decelTime) + linearTime; + const T phaseSpeed = (endValue - startValue) * (1000.0f / effectiveTime); + extrapolation_t phase = EXTRAPOLATION_ACCELSINE; + float phaseDuration = accelTime; + if (accelTime == 0.0f) { + phase = linearTime == 0.0f + ? EXTRAPOLATION_DECELSINE : EXTRAPOLATION_LINEAR; + phaseDuration = linearTime == 0.0f ? decelTime : linearTime; + } + extrapolate.Init(startTime, phaseDuration, startValue, T(), phaseSpeed, phase); + } + + T GetCurrentValue(const float time) const { SetPhase(time); return extrapolate.GetCurrentValue(time); } + T GetCurrentSpeed(const float time) const { SetPhase(time); return extrapolate.GetCurrentSpeed(time); } + bool IsDone(const float time) const { return time >= startTime + accelTime + linearTime + decelTime; } + +private: + void SetPhase(const float time) const { + constexpr float SQRT_HALF = 0.70710678118654752440f; + const float elapsed = time - startTime; + const T zero = T(); + const T phaseSpeed = extrapolate.speed; + if (elapsed < accelTime) { + if ((static_cast(extrapolate.extrapolationType) & ~EXTRAPOLATION_NOSTOP) + != EXTRAPOLATION_ACCELSINE) { + extrapolate.Init(startTime, accelTime, startValue, zero, + phaseSpeed, EXTRAPOLATION_ACCELSINE); + } + } else if (elapsed < accelTime + linearTime) { + if ((static_cast(extrapolate.extrapolationType) & ~EXTRAPOLATION_NOSTOP) + != EXTRAPOLATION_LINEAR) { + const T phaseStart = startValue + + phaseSpeed * (accelTime * SQRT_HALF * 0.001f); + extrapolate.Init(startTime + accelTime, linearTime, phaseStart, + zero, phaseSpeed, EXTRAPOLATION_LINEAR); + } + } else if ((static_cast(extrapolate.extrapolationType) + & ~EXTRAPOLATION_NOSTOP) != EXTRAPOLATION_DECELSINE) { + const T phaseStart = endValue + - phaseSpeed * (decelTime * SQRT_HALF * 0.001f); + extrapolate.Init(startTime + accelTime + linearTime, decelTime, + phaseStart, zero, phaseSpeed, EXTRAPOLATION_DECELSINE); + } + } +}; + +template +class idInterpolateAccelLinearEx { +public: + float startTime; + float duration; + float startSpeed; + float endSpeed; + T startValue; + T endValue; + idExtrapolate extrapolate; + + idInterpolateAccelLinearEx() + : startTime(0.0f), duration(0.0f), startSpeed(0.0f), endSpeed(0.0f), + startValue(T()), endValue(T()), extrapolate() { + } + + void InitDuration(const float newStartTime, const float newStartSpeed, + const float newDuration, const T& newStartValue, const T& newEndValue) { + startTime = newStartTime; + startSpeed = newStartSpeed; + duration = newDuration; + startValue = newStartValue; + endValue = newEndValue; + endSpeed = duration > 0.0f + ? -2.0f * ((duration * 0.001f * startSpeed + startValue - endValue) + / (duration * 0.001f)) + startSpeed + : startSpeed; + extrapolate.Init(startTime, duration, startValue, T() + startSpeed, + T() + (endSpeed - startSpeed), EXTRAPOLATION_ACCELLINEAR); + } + + float InitEndSpeed(const float newStartTime, const float newStartSpeed, + const float newEndSpeed, const T& newStartValue, const T& newEndValue) { + startTime = newStartTime; + startSpeed = newStartSpeed; + endSpeed = newEndSpeed; + startValue = newStartValue; + endValue = newEndValue; + const float denominator = 2.0f * startSpeed + (endSpeed - startSpeed); + duration = denominator != 0.0f + ? static_cast((endValue - startValue) / denominator) * 2000.0f + : 0.0f; + extrapolate.Init(startTime, duration, startValue, T() + startSpeed, + T() + (endSpeed - startSpeed), EXTRAPOLATION_ACCELLINEAR); + return duration; + } + + T GetCurrentValue(const float time) const { + if (time < startTime + duration) return extrapolate.GetCurrentValue(time); + if (startSpeed == endSpeed) return endValue; + return endValue + (T() + endSpeed) * ((time - startTime - duration) * 0.001f); + } +}; + +static_assert(sizeof(idInterpolate) == 24, + "Recovered idInterpolate ABI changed"); +static_assert(sizeof(idInterpolate) == 48, + "Recovered idInterpolate ABI changed"); +static_assert(sizeof(idInterpolate) == 60, + "Recovered idInterpolate ABI changed"); +static_assert(sizeof(idInterpolateAccelDecelLinear) == 56, + "Recovered idInterpolateAccelDecelLinear ABI changed"); +static_assert(sizeof(idInterpolateAccelDecelLinear) == 104, + "Recovered idInterpolateAccelDecelLinear ABI changed"); +static_assert(sizeof(idInterpolateAccelDecelLinear) == 128, + "Recovered idInterpolateAccelDecelLinear ABI changed"); +static_assert(sizeof(idInterpolateAccelLinearEx) == 56, + "Recovered idInterpolateAccelLinearEx ABI changed"); diff --git a/source/shared/idlib/math/lcp.h b/source/shared/idlib/math/lcp.h new file mode 100644 index 0000000..11593dd --- /dev/null +++ b/source/shared/idlib/math/lcp.h @@ -0,0 +1,26 @@ +#pragma once + +class idMatX; +class idVecX; +class idCmdArgs; + +class idLCP { +public: + virtual ~idLCP() = default; + virtual bool Solve(const idMatX* matrix, idVecX* result, + const idVecX* constants, const idVecX* lower, + const idVecX* upper, const int* boxIndex, + const float* ignored = nullptr) = 0; + virtual void SetMaxIterations(const int maximum) { maxIterations = maximum; } + virtual int GetMaxIterations() { return maxIterations; } + + static idLCP* AllocSymmetric(); + static void Test_f(const idCmdArgs& args); + + int maxIterations; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idLCP) == 8, "Recovered idLCP ABI changed"); +#endif + diff --git a/source/shared/idlib/math/mat3x4.h b/source/shared/idlib/math/mat3x4.h index 7f0dda2..c0be01d 100644 --- a/source/shared/idlib/math/mat3x4.h +++ b/source/shared/idlib/math/mat3x4.h @@ -1,6 +1,6 @@ #pragma once -#include "idlib/precompiled.h" +#include "vector.h" class idMat3x4 { public: @@ -75,7 +75,7 @@ public: float* ToFloatPtr() { return mat; } const float* ToFloatPtr() const { return mat; } -private: +public: float mat[12]; }; diff --git a/source/shared/idlib/math/matrix.h b/source/shared/idlib/math/matrix.h new file mode 100644 index 0000000..9e0244f --- /dev/null +++ b/source/shared/idlib/math/matrix.h @@ -0,0 +1,105 @@ +#pragma once + +#include "../containers/array.h" +#include "vector.h" +#include "mat3x4.h" + +struct alignas(16) idNativeVector4 { + float value[4]; +}; + +struct _XMMATRIX { + union { + idNativeVector4 r[4]; + struct { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + } __s1; + float m[4][4]; + } ___u0; +}; + +struct _D3DMATRIX { + union { + struct { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + } __s0; + float m[4][4]; + } ___u0; +}; + +struct _ATIMATRIX { + float _11, _21, _31, _41; + float _12, _22, _32, _42; + float _13, _23, _33, _43; + float _14, _24, _34, _44; + unsigned int dwFlags; +}; + +class idMat2 { +public: + idVec2 mat[2]; + idMat2() = default; + explicit idMat2(float diagonal) { + mat[0].Set(diagonal, 0.0f); + mat[1].Set(0.0f, diagonal); + } + idVec2& operator[](int index) { return mat[index]; } + const idVec2& operator[](int index) const { return mat[index]; } +}; + +class idMat4 { +public: + idVec4 mat[4]; + idMat4() = default; + explicit idMat4(float diagonal) { + for (int row = 0; row < 4; ++row) + for (int column = 0; column < 4; ++column) + mat[row][column] = row == column ? diagonal : 0.0f; + } + idVec4& operator[](int index) { return mat[index]; } + const idVec4& operator[](int index) const { return mat[index]; } +}; + +class idMat5 { +public: + idVec5 mat[5]; + idVec5& operator[](int index) { return mat[index]; } + const idVec5& operator[](int index) const { return mat[index]; } +}; + +class idMat6 { +public: + idVec6 mat[6]; + idVec6& operator[](int index) { return mat[index]; } + const idVec6& operator[](int index) const { return mat[index]; } +}; + +struct swfMatrix_t { + float xx; + float yy; + float xy; + float yx; + float tx; + float ty; +}; + +using XMMATRIX = _XMMATRIX; +using ATIMATRIX = _ATIMATRIX; +using D3DMATRIX = _D3DMATRIX; +using matrix_t = idArray; +using FXLMATRIX = _XMMATRIX; + +static_assert(sizeof(_XMMATRIX) == 64, "Recovered XMMATRIX ABI changed"); +static_assert(sizeof(_D3DMATRIX) == 64, "Recovered D3DMATRIX ABI changed"); +static_assert(sizeof(_ATIMATRIX) == 68, "Recovered ATIMATRIX ABI changed"); +static_assert(sizeof(idMat2) == 16, "Recovered idMat2 ABI changed"); +static_assert(sizeof(idMat4) == 64, "Recovered idMat4 ABI changed"); +static_assert(sizeof(idMat5) == 100, "Recovered idMat5 ABI changed"); +static_assert(sizeof(idMat6) == 144, "Recovered idMat6 ABI changed"); +static_assert(sizeof(swfMatrix_t) == 24, "Recovered swfMatrix_t ABI changed"); diff --git a/source/shared/idlib/math/matx.h b/source/shared/idlib/math/matx.h new file mode 100644 index 0000000..f762902 --- /dev/null +++ b/source/shared/idlib/math/matx.h @@ -0,0 +1,4 @@ +#pragma once + +#include "idlib/math/vector.h" + diff --git a/source/shared/idlib/math/ode.h b/source/shared/idlib/math/ode.h new file mode 100644 index 0000000..e89d34f --- /dev/null +++ b/source/shared/idlib/math/ode.h @@ -0,0 +1,23 @@ +#pragma once + +typedef void (*idODEDeriveFunction)(float time, const void* userData, + const float* state, float* derivatives); + +class idODE { +public: + idODE(const int stateDimension, idODEDeriveFunction deriveFunction, + const void* data) + : dimension(stateDimension), derive(deriveFunction), userData(data) {} + virtual ~idODE() = default; + virtual float Evaluate(const float* state, float* newState, + float time, float timeStep) = 0; + + int dimension; + idODEDeriveFunction derive; + const void* userData; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idODE) == 16, "Recovered idODE ABI changed"); +#endif + diff --git a/source/shared/idlib/math/plane.h b/source/shared/idlib/math/plane.h new file mode 100644 index 0000000..95bb20f --- /dev/null +++ b/source/shared/idlib/math/plane.h @@ -0,0 +1,48 @@ +#pragma once + +#include "idlib/math/vector.h" + +#include +#include + +class idPlane { +public: + float a; + float b; + float c; + float d; + + idPlane() = default; + idPlane(const float newA, const float newB, const float newC, const float newD) + : a(newA), b(newB), c(newC), d(newD) {} + idPlane(const idVec3& normal, const float distance) + : a(normal.x), b(normal.y), c(normal.z), d(-distance) {} + + float operator[](const int index) const { + assert(index >= 0 && index < 4); return (&a)[index]; + } + float& operator[](const int index) { + assert(index >= 0 && index < 4); return (&a)[index]; + } + idPlane operator-() const { return idPlane(-a, -b, -c, -d); } + idVec3& Normal() { return *reinterpret_cast(&a); } + const idVec3& Normal() const { return *reinterpret_cast(&a); } + float Dist() const { return -d; } + void SetDist(const float distance) { d = -distance; } + float Distance(const idVec3& point) const { + return a * point.x + b * point.y + c * point.z + d; + } + bool Compare(const idPlane& other, const float normalEpsilon, + const float distanceEpsilon) const { + return std::fabs(a - other.a) <= normalEpsilon + && std::fabs(b - other.b) <= normalEpsilon + && std::fabs(c - other.c) <= normalEpsilon + && std::fabs(d - other.d) <= distanceEpsilon; + } + int GetDimension() const { return 4; } + const float* ToFloatPtr() const { return &a; } + float* ToFloatPtr() { return &a; } +}; + +static_assert(sizeof(idPlane) == 16, "Recovered idPlane ABI changed"); + diff --git a/source/shared/idlib/math/pluecker.h b/source/shared/idlib/math/pluecker.h new file mode 100644 index 0000000..7df40c5 --- /dev/null +++ b/source/shared/idlib/math/pluecker.h @@ -0,0 +1,128 @@ +#pragma once + +#include "idlib/math/vector.h" + +#include +#include +#include + +class idPluecker { +public: + float p[6]; + + idPluecker() = default; + explicit idPluecker(const float* values) { + std::memcpy(p, values, sizeof(p)); + } + idPluecker(const float p0, const float p1, const float p2, + const float p3, const float p4, const float p5) { + Set(p0, p1, p2, p3, p4, p5); + } + idPluecker(const idVec3& start, const idVec3& end) { FromLine(start, end); } + + float operator[](const int index) const { assert(index >= 0 && index < 6); return p[index]; } + float& operator[](const int index) { assert(index >= 0 && index < 6); return p[index]; } + + idPluecker operator-() const { + return idPluecker(-p[0], -p[1], -p[2], -p[3], -p[4], -p[5]); + } + idPluecker operator*(const float scale) const { + return idPluecker(p[0] * scale, p[1] * scale, p[2] * scale, + p[3] * scale, p[4] * scale, p[5] * scale); + } + idPluecker operator/(const float scale) const { + assert(scale != 0.0f); return *this * (1.0f / scale); + } + float operator*(const idPluecker& other) const { + return PermutedInnerProduct(other); + } + idPluecker operator+(const idPluecker& other) const { + return idPluecker(p[0] + other.p[0], p[1] + other.p[1], + p[2] + other.p[2], p[3] + other.p[3], + p[4] + other.p[4], p[5] + other.p[5]); + } + idPluecker operator-(const idPluecker& other) const { return *this + -other; } + idPluecker& operator*=(const float scale) { + for (float& value : p) value *= scale; return *this; + } + idPluecker& operator/=(const float scale) { + assert(scale != 0.0f); return *this *= 1.0f / scale; + } + idPluecker& operator+=(const idPluecker& other) { + for (int index = 0; index < 6; ++index) p[index] += other.p[index]; + return *this; + } + idPluecker& operator-=(const idPluecker& other) { + for (int index = 0; index < 6; ++index) p[index] -= other.p[index]; + return *this; + } + + bool Compare(const idPluecker& other) const { + for (int index = 0; index < 6; ++index) if (p[index] != other.p[index]) return false; + return true; + } + bool Compare(const idPluecker& other, const float epsilon) const { + for (int index = 0; index < 6; ++index) + if (std::fabs(p[index] - other.p[index]) > epsilon) return false; + return true; + } + bool operator==(const idPluecker& other) const { return Compare(other); } + bool operator!=(const idPluecker& other) const { return !Compare(other); } + + void Set(const float p0, const float p1, const float p2, + const float p3, const float p4, const float p5) { + p[0] = p0; p[1] = p1; p[2] = p2; + p[3] = p3; p[4] = p4; p[5] = p5; + } + void Zero() { for (float& value : p) value = 0.0f; } + void FromLine(const idVec3& start, const idVec3& end) { + p[0] = start.x * end.y - end.x * start.y; + p[1] = start.x * end.z - end.x * start.z; + p[2] = start.x - end.x; + p[3] = start.y * end.z - end.y * start.z; + p[4] = start.z - end.z; + p[5] = end.y - start.y; + } + void FromRay(const idVec3& start, const idVec3& direction) { + p[0] = start.x * direction.y - direction.x * start.y; + p[1] = start.x * direction.z - direction.x * start.z; + p[2] = -direction.x; + p[3] = start.y * direction.z - direction.y * start.z; + p[4] = -direction.z; + p[5] = direction.y; + } + bool ToRay(idVec3& start, idVec3& direction) const { + const idVec3 moment(p[3], -p[1], p[0]); + direction.Set(-p[2], p[5], -p[4]); + const float lengthSqr = direction.LengthSqr(); + if (lengthSqr == 0.0f) return false; + start = direction.Cross(moment) * (1.0f / lengthSqr); + return true; + } + bool ToLine(idVec3& start, idVec3& end) const { + idVec3 direction; + if (!ToRay(start, direction)) return false; + end = start + direction; + return true; + } + void ToDir(idVec3& direction) const { direction.Set(-p[2], p[5], -p[4]); } + float PermutedInnerProduct(const idPluecker& other) const { + return p[0] * other.p[4] + p[1] * other.p[5] + + p[2] * other.p[3] + p[4] * other.p[0] + + p[5] * other.p[1] + p[3] * other.p[2]; + } + float LengthSqr() const { return p[2] * p[2] + p[4] * p[4] + p[5] * p[5]; } + float Length() const { return std::sqrt(LengthSqr()); } + float NormalizeSelf() { + const float length = Length(); + if (length != 0.0f) *this /= length; + return length; + } + idPluecker Normalize() const { idPluecker result(*this); result.NormalizeSelf(); return result; } + int GetDimension() const { return 6; } + const float* ToFloatPtr() const { return p; } + float* ToFloatPtr() { return p; } +}; + +static_assert(sizeof(idPluecker) == 24, "Recovered idPluecker ABI changed"); + diff --git a/source/shared/idlib/math/polar.h b/source/shared/idlib/math/polar.h new file mode 100644 index 0000000..385a305 --- /dev/null +++ b/source/shared/idlib/math/polar.h @@ -0,0 +1,44 @@ +#pragma once + +#include "idlib/math/vector.h" + +#include +#include + +class idPolar3 { +public: + float radius; + float theta; + float phi; + + idPolar3() = default; + idPolar3(const float newRadius, const float newTheta, const float newPhi) { + Set(newRadius, newTheta, newPhi); + } + + void Set(const float newRadius, const float newTheta, const float newPhi) { + assert(newRadius >= 0.0f); + radius = newRadius; + theta = newTheta; + phi = newPhi; + } + + float operator[](const int index) const { + assert(index >= 0 && index < 3); + return (&radius)[index]; + } + float& operator[](const int index) { + assert(index >= 0 && index < 3); + return (&radius)[index]; + } + idPolar3 operator-() const { return idPolar3(radius, -theta, -phi); } + + idVec3 ToVec3() const { + const float cosPhi = std::cos(phi); + return idVec3(cosPhi * radius * std::cos(theta), + cosPhi * radius * std::sin(theta), radius * std::sin(phi)); + } +}; + +static_assert(sizeof(idPolar3) == 12, "Recovered idPolar3 ABI changed"); + diff --git a/source/shared/idlib/math/polynomial.h b/source/shared/idlib/math/polynomial.h new file mode 100644 index 0000000..a487e47 --- /dev/null +++ b/source/shared/idlib/math/polynomial.h @@ -0,0 +1,105 @@ +#pragma once + +#include "idlib/math/complex.h" + +#include +#include +#include +#include + +class idPolynomial { +public: + int degree; + int allocated; + float* coefficient; + + idPolynomial() : degree(-1), allocated(0), coefficient(nullptr) {} + explicit idPolynomial(const int newDegree) : idPolynomial() { Zero(newDegree); } + idPolynomial(const idPolynomial& other) : idPolynomial() { *this = other; } + ~idPolynomial() { delete[] coefficient; } + + idPolynomial& operator=(const idPolynomial& other) { + if (this != &other) { + Resize(other.degree, false); + std::copy(other.coefficient, other.coefficient + other.degree + 1, + coefficient); + } + return *this; + } + + float operator[](const int index) const { + assert(index >= 0 && index <= degree); return coefficient[index]; + } + float& operator[](const int index) { + assert(index >= 0 && index <= degree); return coefficient[index]; + } + void Zero() { if (coefficient != nullptr) std::fill(coefficient, coefficient + degree + 1, 0.0f); } + void Zero(const int newDegree) { Resize(newDegree, false); Zero(); } + int GetDimension() const { return degree + 1; } + int GetDegree() const { return degree; } + float GetValue(const float x) const { + float result = 0.0f; + for (int index = degree; index >= 0; --index) result = result * x + coefficient[index]; + return result; + } + idComplex GetValue(const idComplex& x) const { + idComplex result(0.0f, 0.0f); + for (int index = degree; index >= 0; --index) + result = result * x + coefficient[index]; + return result; + } + idPolynomial GetDerivative() const { + idPolynomial result((std::max)(degree - 1, 0)); + if (degree <= 0) { result[0] = 0.0f; return result; } + for (int index = 1; index <= degree; ++index) + result[index - 1] = coefficient[index] * static_cast(index); + return result; + } + idPolynomial GetAntiDerivative() const { + idPolynomial result(degree + 1); + result[0] = 0.0f; + for (int index = 0; index <= degree; ++index) + result[index + 1] = coefficient[index] / static_cast(index + 1); + return result; + } + bool Compare(const idPolynomial& other) const { + if (degree != other.degree) return false; + for (int index = 0; index <= degree; ++index) + if (coefficient[index] != other.coefficient[index]) return false; + return true; + } + bool Compare(const idPolynomial& other, const float epsilon) const { + if (degree != other.degree) return false; + for (int index = 0; index <= degree; ++index) + if (std::fabs(coefficient[index] - other.coefficient[index]) > epsilon) return false; + return true; + } + bool operator==(const idPolynomial& other) const { return Compare(other); } + bool operator!=(const idPolynomial& other) const { return !Compare(other); } + +private: + void Resize(const int newDegree, const bool keep) { + assert(newDegree >= 0); + const int required = newDegree + 1; + if (required > allocated) { + const int newAllocated = (required + 3) & ~3; + float* replacement = new float[newAllocated]; + std::fill(replacement, replacement + newAllocated, 0.0f); + if (keep && coefficient != nullptr) { + std::copy(coefficient, + coefficient + (std::min)(degree + 1, required), replacement); + } + delete[] coefficient; + coefficient = replacement; + allocated = newAllocated; + } else if (!keep && coefficient != nullptr) { + std::fill(coefficient, coefficient + allocated, 0.0f); + } + degree = newDegree; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPolynomial) == 12, "Recovered idPolynomial ABI changed"); +#endif + diff --git a/source/shared/idlib/math/quat.h b/source/shared/idlib/math/quat.h new file mode 100644 index 0000000..3839e42 --- /dev/null +++ b/source/shared/idlib/math/quat.h @@ -0,0 +1,28 @@ +#pragma once + +#include "idlib/math/vector.h" + +#include + +class idCQuat { +public: + float x; + float y; + float z; + + idCQuat() = default; + idCQuat(const float newX, const float newY, const float newZ) + : x(newX), y(newY), z(newZ) {} + float operator[](const int index) const { + assert(index >= 0 && index < 3); return (&x)[index]; + } + float& operator[](const int index) { + assert(index >= 0 && index < 3); return (&x)[index]; + } +}; + +// PDB type 23290 is an intentionally empty marker. +struct quat_t {}; + +static_assert(sizeof(idCQuat) == 12, "Recovered idCQuat ABI changed"); + diff --git a/source/shared/idlib/math/radians.h b/source/shared/idlib/math/radians.h new file mode 100644 index 0000000..ec161bd --- /dev/null +++ b/source/shared/idlib/math/radians.h @@ -0,0 +1,9 @@ +#pragma once + +#include "idlib/typesafenumber.h" + +enum RadiansUnique_t : int; +typedef idTypesafeNumber radians_t; + +static_assert(sizeof(radians_t) == 4, "Recovered radians_t ABI changed"); + diff --git a/source/shared/idlib/math/random.h b/source/shared/idlib/math/random.h new file mode 100644 index 0000000..d69e60f --- /dev/null +++ b/source/shared/idlib/math/random.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +class idRandom { +public: + static const int MAX_RAND = 0x7FFF; + + int seed; + + explicit idRandom(const int initialSeed = 0) : seed(initialSeed) {} + void SetSeed(const int value) { seed = value; } + int GetSeed() const { return seed; } + + int RandomInt() { + seed = static_cast(1103515245u * static_cast(seed) + 12345u); + return (static_cast(seed) >> 16) & MAX_RAND; + } + + int RandomInt(const int max) { + return max == 0 ? 0 : RandomInt() % max; + } + + float RandomFloat() { return RandomInt() * (1.0f / 32768.0f); } + float CRandomFloat() { return 2.0f * RandomFloat() - 1.0f; } +}; + +class idRandom2 { +public: + static const int MAX_RAND = 0x7FFF; + + unsigned int seed; + + explicit idRandom2(const unsigned int initialSeed = 0) : seed(initialSeed) {} + void SetSeed(const unsigned int value) { seed = value; } + unsigned int GetSeed() const { return seed; } + + int RandomInt() { + seed = 1664525u * seed + 1013904223u; + return static_cast((seed >> 10) & MAX_RAND); + } + + int RandomInt(const int max) { + return max == 0 ? 0 : RandomInt() % max; + } + + int RandomInt(const int min, const int max) { + return min >= max ? min : min + RandomInt(max - min + 1); + } + + float RandomFloat() { return RandomInt() * (1.0f / 32768.0f); } + float CRandomFloat() { return 2.0f * RandomFloat() - 1.0f; } + + float BellCurve(const int degree) { + if (degree <= 0) return 0.0f; + float sum = 0.0f; + for (int index = 0; index < degree; ++index) sum += CRandomFloat(); + return sum / static_cast(degree); + } +}; + +class idRandomMersenneCyclic { +public: + unsigned int MT[624]; +}; + +class idRandomWELL1024 { +public: + unsigned int seedArray[32]; + unsigned int state_i; + unsigned int STATE[32]; +}; + +class idRandomMersenne { +public: + unsigned int MT[624]; + unsigned int index; + + explicit idRandomMersenne(const unsigned int seed = 5489u) { + SetSeed(seed); + } + + void SetSeed(const unsigned int seed) { + MT[0] = seed; + for (unsigned int i = 1; i < 624; ++i) { + MT[i] = 1812433253u * (MT[i - 1] ^ (MT[i - 1] >> 30)) + i; + } + index = 624; + } + + void GenerateNumbers() { + static const unsigned int mag01[2] = { 0u, 0x9908B0DFu }; + for (unsigned int i = 0; i < 227; ++i) { + const unsigned int value = (MT[i] & 0x80000000u) + | (MT[i + 1] & 0x7FFFFFFEu); + MT[i] = MT[i + 397] ^ (value >> 1) ^ mag01[value & 1u]; + } + for (unsigned int i = 227; i < 623; ++i) { + const unsigned int value = (MT[i] & 0x80000000u) + | (MT[i + 1] & 0x7FFFFFFEu); + MT[i] = MT[i - 227] ^ (value >> 1) ^ mag01[value & 1u]; + } + const unsigned int value = (MT[623] & 0x80000000u) + | (MT[0] & 0x7FFFFFFEu); + MT[623] = MT[396] ^ (value >> 1) ^ mag01[value & 1u]; + } + + unsigned int RandomInt() { + if (index >= 624) { + index = 0; + GenerateNumbers(); + } + unsigned int value = MT[index++]; + value ^= value >> 11; + value ^= (value << 7) & 0x9D2C5680u; + value ^= (value << 15) & 0xEFC60000u; + value ^= value >> 18; + return value; + } +}; + +using idRandomType = idRandom2; + +static_assert(sizeof(idRandom) == 4, "Recovered idRandom ABI changed"); +static_assert(sizeof(idRandom2) == 4, "Recovered idRandom2 ABI changed"); +static_assert(sizeof(idRandomMersenneCyclic) == 2496, + "Recovered idRandomMersenneCyclic ABI changed"); +static_assert(sizeof(idRandomWELL1024) == 260, + "Recovered idRandomWELL1024 ABI changed"); +static_assert(sizeof(idRandomMersenne) == 2500, + "Recovered idRandomMersenne ABI changed"); diff --git a/source/shared/idlib/math/rotation.h b/source/shared/idlib/math/rotation.h new file mode 100644 index 0000000..b883715 --- /dev/null +++ b/source/shared/idlib/math/rotation.h @@ -0,0 +1,89 @@ +#pragma once + +#include "vector.h" + +#include + +class idRotation { +public: + idVec3 origin; + idVec3 vec; + float angle; + mutable idMat3 axis; + mutable bool axisValid; + + idRotation() + : origin(0.0f, 0.0f, 0.0f), vec(0.0f, 0.0f, 1.0f), angle(0.0f), + axis(1.0f), axisValid(false) { + } + + idRotation(const idVec3& rotationOrigin, const idVec3& rotationVector, + const float rotationAngle) + : origin(rotationOrigin), vec(rotationVector), angle(rotationAngle), + axis(1.0f), axisValid(false) { + } + + const idMat3& ToMat3() const { + if (axisValid) return axis; + const float halfAngle = angle * 0.00872664625997164788f; + const float sine = std::sin(halfAngle); + const float cosine = std::cos(halfAngle); + const float x = vec.x * sine; + const float y = vec.y * sine; + const float z = vec.z * sine; + const float x2 = x + x; + const float y2 = y + y; + const float z2 = z + z; + const float xx = x * x2; + const float xy = x * y2; + const float xz = x * z2; + const float yy = y * y2; + const float yz = y * z2; + const float zz = z * z2; + const float wx = cosine * x2; + const float wy = cosine * y2; + const float wz = cosine * z2; + axis = idMat3( + 1.0f - (yy + zz), xy - wz, xz + wy, + xy + wz, 1.0f - (xx + zz), yz - wx, + xz - wy, yz + wx, 1.0f - (xx + yy)); + axisValid = true; + return axis; + } + + idVec3 operator*(const idVec3& point) const { + return origin + ToMat3() * (point - origin); + } + + idRotation operator-() const { + return idRotation(origin, vec, -angle); + } + + void RotatePoint(idVec3& point) const { point = *this * point; } + void RotateAxis(idMat3& value) const { value *= ToMat3(); } + + void Normalize180() { + angle -= std::floor(angle / 360.0f) * 360.0f; + if (angle > 180.0f) angle -= 360.0f; + if (angle < -180.0f) angle += 360.0f; + axisValid = false; + } + + idVec3 ToAngularVelocity() const { + return vec * (angle * 0.01745329251994329577f); + } + + void SetOrigin(const idVec3& value) { origin = value; } + void SetVec(const idVec3& value) { vec = value; axisValid = false; } + void SetAngle(float value) { angle = value; axisValid = false; } + const idVec3& GetOrigin() const { return origin; } + const idVec3& GetVec() const { return vec; } + float GetAngle() const { return angle; } +}; + +inline idVec3& operator*=(idVec3& vector, const idRotation& rotation) { + vector = rotation * vector; + return vector; +} + +static_assert(sizeof(idRotation) == 68, "Recovered idRotation ABI changed"); diff --git a/source/shared/idlib/math/spatialmat.h b/source/shared/idlib/math/spatialmat.h index 7065017..8d0b5f4 100644 --- a/source/shared/idlib/math/spatialmat.h +++ b/source/shared/idlib/math/spatialmat.h @@ -1,7 +1,6 @@ #pragma once -#include "idlib/precompiled.h" - +#include "vector.h" #include "spatialvec.h" // Tungsten stores every spatial matrix in a six-row, eight-float-stride slab. @@ -65,6 +64,8 @@ private: bool Inverse6x6(idSpatialMat& dst) const; void ClearPadding(); +public: + // Public in the recovered PDB declaration (ordinal 12835). int numRows; int numColumns; int allocatedRows; @@ -74,4 +75,3 @@ private: #if INTPTR_MAX == INT32_MAX static_assert(sizeof(idSpatialMat) == 16, "Recovered idSpatialMat ABI changed"); #endif - diff --git a/source/shared/idlib/math/vector.h b/source/shared/idlib/math/vector.h index 49ff996..e6edbaa 100644 --- a/source/shared/idlib/math/vector.h +++ b/source/shared/idlib/math/vector.h @@ -12,6 +12,7 @@ public: idVec1() = default; explicit idVec1(const float newX) : x(newX) {} void Zero() { x = 0.0f; } + int GetDimension() const { return 1; } float operator[](const int) const { return x; } float& operator[](const int) { return x; } }; @@ -42,6 +43,8 @@ public: y = 0.0f; } + int GetDimension() const { return 2; } + float operator[](const int index) const { assert(index >= 0 && index < 2); return (&x)[index]; @@ -84,6 +87,9 @@ public: z = 0.0f; } + + int GetDimension() const { return 3; } + float operator[](const int index) const { assert(index >= 0 && index < 3); return (&x)[index]; @@ -144,8 +150,73 @@ public: mat[2].Set(0.0f, 0.0f, diagonal); } + idMat3(float xx, float xy, float xz, + float yx, float yy, float yz, + float zx, float zy, float zz) { + mat[0].Set(xx, xy, xz); + mat[1].Set(yx, yy, yz); + mat[2].Set(zx, zy, zz); + } + idVec3& operator[](const int index) { return mat[index]; } const idVec3& operator[](const int index) const { return mat[index]; } + + idVec3 operator*(const idVec3& vector) const { + return idVec3( + mat[0].x * vector.x + mat[0].y * vector.y + mat[0].z * vector.z, + mat[1].x * vector.x + mat[1].y * vector.y + mat[1].z * vector.z, + mat[2].x * vector.x + mat[2].y * vector.y + mat[2].z * vector.z); + } + + idMat3 operator*(const idMat3& other) const { + idMat3 result; + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + result[row][column] = mat[row][0] * other[0][column] + + mat[row][1] * other[1][column] + + mat[row][2] * other[2][column]; + } + } + return result; + } + + idMat3& operator*=(const idMat3& other) { + *this = *this * other; + return *this; + } + + idMat3 Transpose() const { + return idMat3( + mat[0].x, mat[1].x, mat[2].x, + mat[0].y, mat[1].y, mat[2].y, + mat[0].z, mat[1].z, mat[2].z); + } + + float Determinant() const { + return mat[0].x * (mat[1].y * mat[2].z - mat[1].z * mat[2].y) + - mat[0].y * (mat[1].x * mat[2].z - mat[1].z * mat[2].x) + + mat[0].z * (mat[1].x * mat[2].y - mat[1].y * mat[2].x); + } + + bool InverseSelf() { + const float determinant = Determinant(); + if (std::fabs(determinant) < 1.0e-14f) return false; + const float inverseDeterminant = 1.0f / determinant; + const idMat3 source = *this; + mat[0].Set( + (source[1].y * source[2].z - source[1].z * source[2].y) * inverseDeterminant, + (source[0].z * source[2].y - source[0].y * source[2].z) * inverseDeterminant, + (source[0].y * source[1].z - source[0].z * source[1].y) * inverseDeterminant); + mat[1].Set( + (source[1].z * source[2].x - source[1].x * source[2].z) * inverseDeterminant, + (source[0].x * source[2].z - source[0].z * source[2].x) * inverseDeterminant, + (source[0].z * source[1].x - source[0].x * source[1].z) * inverseDeterminant); + mat[2].Set( + (source[1].x * source[2].y - source[1].y * source[2].x) * inverseDeterminant, + (source[0].y * source[2].x - source[0].x * source[2].y) * inverseDeterminant, + (source[0].x * source[1].y - source[0].y * source[1].x) * inverseDeterminant); + return true; + } }; static_assert(sizeof(idMat3) == 36, "Recovered idMat3 layout changed"); @@ -183,6 +254,8 @@ public: w = newW; } + int GetDimension() const { return 4; } + float operator[](const int index) const { assert(index >= 0 && index < 4); return (&x)[index]; @@ -196,6 +269,35 @@ public: static_assert(sizeof(idVec4) == 16, "Recovered idVec4 layout changed"); +class idVec5 { +public: + float x; + float y; + float z; + float s; + float t; + + idVec5() = default; + idVec5(float newX, float newY, float newZ, float newS, float newT) + : x(newX), y(newY), z(newZ), s(newS), t(newT) {} + int GetDimension() const { return 5; } + float& operator[](int index) { return (&x)[index]; } + float operator[](int index) const { return (&x)[index]; } +}; + +class idVec6 { +public: + float p[6]; + int GetDimension() const { return 6; } + + idVec6() = default; + float& operator[](int index) { return p[index]; } + float operator[](int index) const { return p[index]; } +}; + +static_assert(sizeof(idVec5) == 20, "Recovered idVec5 layout changed"); +static_assert(sizeof(idVec6) == 24, "Recovered idVec6 layout changed"); + class idAngles { public: float pitch; @@ -209,6 +311,66 @@ public: float operator[](const int index) const { return (&pitch)[index]; } float& operator[](const int index) { return (&pitch)[index]; } + + idAngles operator+(const idAngles& other) const { + return idAngles(pitch + other.pitch, yaw + other.yaw, roll + other.roll); + } + idAngles operator-(const idAngles& other) const { + return idAngles(pitch - other.pitch, yaw - other.yaw, roll - other.roll); + } + idAngles operator*(const float scale) const { + return idAngles(pitch * scale, yaw * scale, roll * scale); + } + + idAngles& Normalize360() { + float* angle = &pitch; + for (int index = 0; index < 3; ++index) { + angle[index] -= std::floor(angle[index] / 360.0f) * 360.0f; + if (angle[index] >= 360.0f) angle[index] -= 360.0f; + if (angle[index] < 0.0f) angle[index] += 360.0f; + } + return *this; + } + + idAngles& Normalize180() { + Normalize360(); + if (pitch > 180.0f) pitch -= 360.0f; + if (yaw > 180.0f) yaw -= 360.0f; + if (roll > 180.0f) roll -= 360.0f; + return *this; + } + + void ToVectors(idVec3* forward, idVec3* right = nullptr, + idVec3* up = nullptr) const { + constexpr float DEG2RAD = 0.01745329251994329577f; + const float sy = std::sin(yaw * DEG2RAD); + const float cy = std::cos(yaw * DEG2RAD); + const float sp = std::sin(pitch * DEG2RAD); + const float cp = std::cos(pitch * DEG2RAD); + const float sr = std::sin(roll * DEG2RAD); + const float cr = std::cos(roll * DEG2RAD); + if (forward != nullptr) forward->Set(cp * cy, cp * sy, -sp); + if (right != nullptr) right->Set( + cr * sy - sr * sp * cy, + -(sr * sp * sy + cr * cy), + -sr * cp); + if (up != nullptr) up->Set( + cr * sp * cy + sr * sy, + cr * sp * sy - sr * cy, + cr * cp); + } + + idVec3 ToForward() const { + idVec3 result; + ToVectors(&result); + return result; + } + + idMat3 ToMat3() const { + idMat3 result; + ToVectors(&result[0], &result[1], &result[2]); + return result; + } }; static_assert(sizeof(idAngles) == 12, "Recovered idAngles layout changed"); @@ -227,14 +389,23 @@ public: float operator[](const int index) const { return (&x)[index]; } float& operator[](const int index) { return (&x)[index]; } + + idQuat operator+(const idQuat& other) const { + return idQuat(x + other.x, y + other.y, z + other.z, w + other.w); + } + idQuat operator-(const idQuat& other) const { + return idQuat(x - other.x, y - other.y, z - other.z, w - other.w); + } + idQuat operator*(const float scale) const { + return idQuat(x * scale, y * scale, z * scale, w * scale); + } }; static_assert(sizeof(idQuat) == 16, "Recovered idQuat layout changed"); // The Xbox 360 type-information stream serializes the dynamic math types by -// their three/four-field facades. Keep these definitions allocation-simple on -// the standalone recovery targets; the complete idLib target uses BFG's -// layout-compatible implementations. +// their three/four-field facades. Keep these definitions allocation-simple on +// the standalone recovery targets while preserving the recovered public ABI. class idVecX { public: idVecX() : size(0), alloced(0), p(nullptr) {} @@ -268,7 +439,7 @@ public: float& operator[](const int index) { return p[index]; } float operator[](const int index) const { return p[index]; } -private: +public: int size; int alloced; float* p; @@ -319,7 +490,7 @@ public: return mat + row * numColumns; } -private: +public: int numRows; int numColumns; int alloced; diff --git a/source/shared/idlib/math/vectori.h b/source/shared/idlib/math/vectori.h new file mode 100644 index 0000000..b3f4d8d --- /dev/null +++ b/source/shared/idlib/math/vectori.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +class idVec2i { +public: + int x; + int y; + + idVec2i() = default; + idVec2i(const int newX, const int newY) : x(newX), y(newY) {} + void Set(const int newX, const int newY) { x = newX; y = newY; } + void Zero() { x = 0; y = 0; } + int operator[](const int index) const { + assert(index >= 0 && index < 2); return (&x)[index]; + } + int& operator[](const int index) { + assert(index >= 0 && index < 2); return (&x)[index]; + } + bool operator==(const idVec2i& other) const { return x == other.x && y == other.y; } + bool operator!=(const idVec2i& other) const { return !(*this == other); } +}; + +class idVec3i { +public: + int x; + int y; + int z; + + idVec3i() = default; + idVec3i(const int newX, const int newY, const int newZ) + : x(newX), y(newY), z(newZ) {} + void Set(const int newX, const int newY, const int newZ) { + x = newX; y = newY; z = newZ; + } + void Zero() { x = 0; y = 0; z = 0; } + int operator[](const int index) const { + assert(index >= 0 && index < 3); return (&x)[index]; + } + int& operator[](const int index) { + assert(index >= 0 && index < 3); return (&x)[index]; + } + bool operator==(const idVec3i& other) const { + return x == other.x && y == other.y && z == other.z; + } + bool operator!=(const idVec3i& other) const { return !(*this == other); } +}; + +static_assert(sizeof(idVec2i) == 8, "Recovered idVec2i ABI changed"); +static_assert(sizeof(idVec3i) == 12, "Recovered idVec3i ABI changed"); + diff --git a/source/shared/idlib/math/vecx.h b/source/shared/idlib/math/vecx.h new file mode 100644 index 0000000..f762902 --- /dev/null +++ b/source/shared/idlib/math/vecx.h @@ -0,0 +1,4 @@ +#pragma once + +#include "idlib/math/vector.h" + diff --git a/source/shared/idlib/metrics/timer.h b/source/shared/idlib/metrics/timer.h new file mode 100644 index 0000000..e045047 --- /dev/null +++ b/source/shared/idlib/metrics/timer.h @@ -0,0 +1,54 @@ +#pragma once + +#include "idlib/text/str.h" + +#include +#include + +class idTimer { +public: + enum timerState_t : int { TS_STARTED = 0, TS_STOPPED = 1 }; + + idTimer() : state(TS_STOPPED), start(0), clockTicks(0) {} + virtual ~idTimer() = default; + + void Start() { state = TS_STARTED; start = ClockNow(); } + void Stop() { + if (state == TS_STARTED) { + clockTicks += ClockNow() - start; + state = TS_STOPPED; + } + } + void Clear() { state = TS_STOPPED; start = 0; clockTicks = 0; } + std::int64_t ClockTicks() const { return clockTicks; } + double Milliseconds() const { return static_cast(clockTicks) / 1000000.0; } + + alignas(8) timerState_t state; + std::int64_t start; + std::int64_t clockTicks; + +private: + static std::int64_t ClockNow() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } +}; + +class idJobTimer { +public: + idStr name; + int start; + int ends; +}; + +class idSPUTimer { +public: + unsigned int startDecr; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idTimer) == 32, "Recovered idTimer ABI changed"); +static_assert(sizeof(idJobTimer) == 40, "Recovered idJobTimer ABI changed"); +static_assert(sizeof(idSPUTimer) == 4, "Recovered idSPUTimer ABI changed"); +#endif + diff --git a/source/shared/idlib/networking/bitmsg.cpp b/source/shared/idlib/networking/bitmsg.cpp new file mode 100644 index 0000000..f60ab65 --- /dev/null +++ b/source/shared/idlib/networking/bitmsg.cpp @@ -0,0 +1,233 @@ +#include "bitmsg.h" + +#include + +idBitMsg::idBitMsg() + : writeData(nullptr), readData(nullptr), maxSize(0), curSize(0), + writeBit(0), readCount(0), readBit(0), allowOverflow(false), + overflowed(false), tempValue(0) {} + +idBitMsg::idBitMsg(unsigned char* data, const int length) : idBitMsg() { + Init(data, length); +} + +idBitMsg::idBitMsg(const unsigned char* data, const int length) : idBitMsg() { + Init(data, length); +} + +void idBitMsg::Init(unsigned char* data, const int length) { + writeData = data; + readData = data; + maxSize = length; + BeginWriting(); +} + +void idBitMsg::Init(const unsigned char* data, const int length) { + writeData = nullptr; + readData = data; + maxSize = curSize = length; + writeBit = 0; + BeginReading(); + allowOverflow = overflowed = false; + tempValue = 0; +} + +void idBitMsg::BeginWriting() { + curSize = writeBit = readCount = readBit = 0; + overflowed = false; + tempValue = 0; +} + +void idBitMsg::BeginReading() const { + readCount = readBit = 0; +} + +bool idBitMsg::CheckOverflow(const int numBits) { + if (curSize * 8 + writeBit + numBits <= maxSize * 8) return false; + if (!allowOverflow || numBits > maxSize * 8) { + overflowed = true; + return true; + } + curSize = writeBit = 0; + tempValue = 0; + overflowed = true; + return false; +} + +unsigned char* idBitMsg::GetByteSpace(const int length) { + if (writeData == nullptr || length < 0) return nullptr; + if (writeBit != 0) { + ++curSize; + writeBit = 0; + tempValue = 0; + } + if (CheckOverflow(length * 8)) return nullptr; + unsigned char* const result = writeData + curSize; + curSize += length; + return result; +} + +void idBitMsg::WriteBits(const int value, int numBits) { + if (writeData == nullptr || numBits == 0 || numBits < -31 || numBits > 32) + return; + if (numBits < 0) numBits = -numBits; + if (CheckOverflow(numBits)) return; + std::uint32_t bits = static_cast(value); + if (numBits < 32) bits &= (1u << numBits) - 1u; + for (int bit = 0; bit < numBits; ++bit) { + if (writeBit == 0) writeData[curSize] = 0; + if ((bits & (1u << bit)) != 0) + writeData[curSize] |= static_cast(1u << writeBit); + if (++writeBit == 8) { + writeBit = 0; + ++curSize; + } + } + tempValue = writeBit != 0 ? writeData[curSize] : 0; +} + +int idBitMsg::ReadBits(int numBits) const { + if (readData == nullptr || numBits == 0 || numBits < -31 || numBits > 32) + return -1; + const bool signedValue = numBits < 0; + if (signedValue) numBits = -numBits; + const int availableBits = (curSize - readCount) * 8 + + (readBit != 0 ? 8 - readBit : 0); + if (numBits > availableBits) return -1; + std::uint32_t result = 0; + for (int bit = 0; bit < numBits; ++bit) { + if (readBit == 0) ++readCount; + if ((readData[readCount - 1] & (1u << readBit)) != 0) + result |= 1u << bit; + readBit = (readBit + 1) & 7; + } + if (signedValue && numBits < 32 && (result & (1u << (numBits - 1))) != 0) + result |= ~((1u << numBits) - 1u); + return static_cast(result); +} + +void idBitMsg::WriteData(const void* data, const int length) { + unsigned char* const destination = GetByteSpace(length); + if (destination != nullptr && data != nullptr && length > 0) + std::memcpy(destination, data, static_cast(length)); +} + +int idBitMsg::ReadData(void* data, const int length) const { + if (length <= 0 || readData == nullptr) return 0; + readBit = 0; + const int amount = (std::min)(length, curSize - readCount); + if (data != nullptr && amount > 0) + std::memcpy(data, readData + readCount, static_cast(amount)); + readCount += amount; + return amount; +} + +void idBitMsg::WriteDelta(const int oldValue, const int newValue, + const int numBits) { + WriteBits(oldValue != newValue, 1); + if (oldValue != newValue) WriteBits(newValue, numBits); +} + +int idBitMsg::ReadDelta(const int oldValue, const int numBits) const { + return ReadBits(1) != 0 ? ReadBits(numBits) : oldValue; +} + +void idBitMsg::WriteString(const char* string, const int maxLength, + const bool make7Bit) { + const char* const source = string != nullptr ? string : ""; + int length = static_cast(std::strlen(source)); + if (maxLength >= 0 && length >= maxLength) length = (std::max)(0, maxLength - 1); + unsigned char* const destination = GetByteSpace(length + 1); + if (destination == nullptr) return; + for (int index = 0; index < length; ++index) { + const unsigned char value = static_cast(source[index]); + destination[index] = make7Bit && value > 127 ? '.' : value; + } + destination[length] = 0; +} + +int idBitMsg::ReadString(char* buffer, const int bufferSize) const { + if (buffer == nullptr || bufferSize <= 0) return 0; + readBit = 0; + int length = 0; + while (readCount < curSize) { + unsigned char value = readData[readCount++]; + if (value == 0 || value == 255) break; + if (value == '%') value = '.'; + if (length < bufferSize - 1) buffer[length++] = static_cast(value); + } + buffer[length] = 0; + return length; +} + +int idBitMsg::ReadString(idStr& string) const { + char buffer[4096]; + const int length = ReadString(buffer, sizeof(buffer)); + string = buffer; + return length; +} + +void idBitMsg::WriteNetadr(const netadr_t& address) { + WriteData(address.ip, 4); + WriteBits(address.port, 16); + WriteBits(address.type, 8); +} + +void idBitMsg::ReadNetadr(netadr_t* address) const { + if (address == nullptr) return; + ReadData(address->ip, 4); + address->port = static_cast(ReadBits(16)); + address->type = static_cast(ReadBits(8)); +} + +void idBitMsg::WriteDeltaShortCounter(const int oldValue, const int newValue) { + const int delta = newValue - oldValue; + unsigned int magnitude = delta < 0 ? -delta : delta; + int bits = 1; + while (magnitude >>= 1) ++bits; + WriteBits(bits - 1, 4); + WriteBits(delta, -(bits + 1)); +} + +void idBitMsg::WriteDeltaLongCounter(const int oldValue, const int newValue) { + const int delta = newValue - oldValue; + unsigned int magnitude = delta < 0 ? -delta : delta; + int bits = 1; + while (magnitude >>= 1) ++bits; + WriteBits(bits - 1, 5); + WriteBits(delta, -(bits + 1)); +} + +int idBitMsg::ReadDeltaShortCounter(const int oldValue) const { + const int bits = ReadBits(4) + 1; + return oldValue + ReadBits(-(bits + 1)); +} + +int idBitMsg::ReadDeltaLongCounter(const int oldValue) const { + const int bits = ReadBits(5) + 1; + return oldValue + ReadBits(-(bits + 1)); +} + +idFile_BitMsg::idFile_BitMsg(idBitMsg& message) + : idFile(), name("*bitmsg*"), mode(FS_READ_WRITE), msg(&message) {} + +unsigned int idFile_BitMsg::Read(void* data, const unsigned int length) { + return static_cast(msg != nullptr + ? msg->ReadData(data, static_cast(length)) : 0); +} + +unsigned int idFile_BitMsg::Write(const void* data, const unsigned int length) { + if (msg == nullptr || mode == FS_READ) return 0; + msg->WriteData(data, static_cast(length)); + return length; +} + +std::int64_t idFile_BitMsg::Length() const { + return msg != nullptr ? msg->GetSize() : 0; +} + +std::int64_t idFile_BitMsg::Tell() const { + if (msg == nullptr) return 0; + return mode == FS_READ ? msg->GetReadCount() : msg->GetSize(); +} + diff --git a/source/shared/idlib/networking/bitmsg.h b/source/shared/idlib/networking/bitmsg.h new file mode 100644 index 0000000..ae15c45 --- /dev/null +++ b/source/shared/idlib/networking/bitmsg.h @@ -0,0 +1,124 @@ +#pragma once + +#include "../filesystem/file.h" +#include "../math/vector.h" +#include "../sys/sys_networking.h" + +#include +#include + +class idBitMsg { +public: + idBitMsg(); + idBitMsg(unsigned char* data, int length); + idBitMsg(const unsigned char* data, int length); + + void Init(unsigned char* data, int length); + void Init(const unsigned char* data, int length); + void BeginWriting(); + void BeginReading() const; + + void SetAllowOverflow(bool allow) { allowOverflow = allow; } + bool IsOverflowed() const { return overflowed; } + int GetSize() const { return curSize + (writeBit != 0 ? 1 : 0); } + int GetMaxSize() const { return maxSize; } + int GetReadCount() const { return readCount; } + int GetRemainingData() const { return curSize - readCount; } + unsigned char* GetWriteData() { return writeData; } + const unsigned char* GetReadData() const { return readData; } + + void WriteBits(int value, int numBits); + int ReadBits(int numBits) const; + void WriteData(const void* data, int length); + int ReadData(void* data, int length) const; + void WriteString(const char* string, int maxLength = -1, + bool make7Bit = true); + int ReadString(char* buffer, int bufferSize) const; + int ReadString(idStr& string) const; + void WriteNetadr(const netadr_t& address); + void ReadNetadr(netadr_t* address) const; + void WriteDeltaShortCounter(int oldValue, int newValue); + void WriteDeltaLongCounter(int oldValue, int newValue); + int ReadDeltaShortCounter(int oldValue) const; + int ReadDeltaLongCounter(int oldValue) const; + + template + void WriteQuantizedVector(const vector_t& value) { + const int quantizedMax = (1 << (numBits - 1)) - 1; + const float scale = static_cast(quantizedMax) + / static_cast(maxValue); + for (int component = 0; component < value.GetDimension(); ++component) { + int quantized = static_cast(value[component] * scale); + quantized = (std::max)(-quantizedMax, + (std::min)(quantizedMax, quantized)); + WriteBits(quantized, -numBits); + } + } + + template + void ReadQuantizedVector(vector_t& value) const { + const int quantizedMax = (1 << (numBits - 1)) - 1; + const float scale = static_cast(maxValue) + / static_cast(quantizedMax); + for (int component = 0; component < value.GetDimension(); ++component) + value[component] = static_cast(ReadBits(-numBits)) * scale; + } + + template + void WriteQuantizedUFloat(const float value) { + const unsigned int storeMax = (1u << numBits) - 1u; + const unsigned int scale = storeMax / static_cast(maxValue); + int quantized = static_cast(value * static_cast(scale)); + quantized = (std::max)(0, + (std::min)(static_cast(storeMax), quantized)); + WriteBits(quantized, numBits); + } + + template + float ReadQuantizedUFloat() const { + const unsigned int storeMax = (1u << numBits) - 1u; + const unsigned int scale = storeMax / static_cast(maxValue); + return scale != 0 ? static_cast(ReadBits(numBits)) / scale : 0.0f; + } + + unsigned char* writeData; + const unsigned char* readData; + int maxSize; + int curSize; + int writeBit; + mutable int readCount; + mutable int readBit; + bool allowOverflow; + bool overflowed; + std::uint64_t tempValue; + +private: + bool CheckOverflow(int numBits); + unsigned char* GetByteSpace(int length); + void WriteDelta(int oldValue, int newValue, int numBits); + int ReadDelta(int oldValue, int numBits) const; +}; + +class idFile_BitMsg : public idFile { +public: + explicit idFile_BitMsg(idBitMsg& message); + ~idFile_BitMsg() override = default; + const char* GetName() const override { return name.c_str(); } + const char* GetFullPath() const override { return name.c_str(); } + unsigned int Read(void* data, unsigned int length) override; + unsigned int Write(const void* data, unsigned int length) override; + std::int64_t Length() const override; + std::int64_t Tell() const override; + fsDevice_t GetDevice() const override { return FS_DEVICE_MEMORY; } + + idStr name; + int mode; + idBitMsg* msg; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idBitMsg) == 40, "Recovered idBitMsg ABI changed"); +static_assert(sizeof(idFile_BitMsg) == 48, + "Recovered idFile_BitMsg ABI changed"); +#endif + diff --git a/source/shared/idlib/networking/dataqueue.h b/source/shared/idlib/networking/dataqueue.h new file mode 100644 index 0000000..1d4f536 --- /dev/null +++ b/source/shared/idlib/networking/dataqueue.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../containers/staticlist.h" + +#include +#include + +template +class idDataQueue { +public: + struct msgItem_t { + int sequence; + int length; + int dataOffset; + }; + + idStaticList items; + int dataLength; + std::uint8_t data[maxData]; + + idDataQueue() : dataLength(0) {} + + bool Append(const int sequence, const std::uint8_t* first, + const int firstLength, const std::uint8_t* second, + const int secondLength) { + if (firstLength < 0 || secondLength < 0 + || items.Num() == items.Max() + || dataLength + firstLength + secondLength >= maxData) { + return false; + } + msgItem_t* const item = items.Alloc(); + if (item == nullptr) return false; + item->sequence = sequence; + item->length = firstLength + secondLength; + item->dataOffset = dataLength; + if (firstLength > 0) { + std::memcpy(&data[dataLength], first, firstLength); + dataLength += firstLength; + } + if (secondLength > 0) { + std::memcpy(&data[dataLength], second, secondLength); + dataLength += secondLength; + } + return true; + } + + void RemoveOlderThan(const int sequence) { + int removeBytes = 0; + while (items.Num() > 0 && items[0].sequence < sequence) { + removeBytes += items[0].length; + items.RemoveIndex(0); + } + if (removeBytes < dataLength) { + if (removeBytes > 0) { + std::memmove(data, data + removeBytes, dataLength - removeBytes); + dataLength -= removeBytes; + } + } else { + dataLength = 0; + } + int offset = 0; + for (int index = 0; index < items.Num(); ++index) { + items[index].dataOffset = offset; + offset += items[index].length; + } + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idDataQueue<63, 8000>) == 8776, + "Recovered idDataQueue<63,8000> ABI changed"); +static_assert(sizeof(idDataQueue<64, 65536>) == 66324, + "Recovered idDataQueue<64,65536> ABI changed"); +#endif diff --git a/source/shared/idlib/parallelism/paralleljoblist.h b/source/shared/idlib/parallelism/paralleljoblist.h new file mode 100644 index 0000000..6f433bd --- /dev/null +++ b/source/shared/idlib/parallelism/paralleljoblist.h @@ -0,0 +1,19 @@ +#pragma once + +class idParallelJobList_Threads; +class idColor; + +class idParallelJobList { +public: + struct idParallelJobList_SPURS; + + idParallelJobList_SPURS* jobListSPURS; + idParallelJobList_Threads* jobListThreads; + const idColor* color; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idParallelJobList) == 12, + "Recovered idParallelJobList ABI changed"); +#endif + diff --git a/source/shared/idlib/parallelism/softwarecache.h b/source/shared/idlib/parallelism/softwarecache.h new file mode 100644 index 0000000..dba05f8 --- /dev/null +++ b/source/shared/idlib/parallelism/softwarecache.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +// All four recovered template instantiations occupy exactly 128 bytes. The +// Xbox cache machinery is opaque until its call sites are ported, but the +// generic declaration and storage contract are now available to PC headers. +template +class idSoftwareCache { +public: + std::uint8_t storage[128]; +}; + +static_assert(sizeof(idSoftwareCache) == 128, + "Recovered idSoftwareCache ABI changed"); + diff --git a/source/shared/idlib/podtest.h b/source/shared/idlib/podtest.h new file mode 100644 index 0000000..9d96554 --- /dev/null +++ b/source/shared/idlib/podtest.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +// Tungsten's debug types show an empty checker class with an internal union +// containing T. Preserve the empty runtime layout and expose the result as a +// compile-time constant for recovered container checks. +template +class idPodTest { +public: + template + struct withUnion_t { + union { + unionType t; + } funion; + }; + + enum { + value = std::is_trivially_copyable::value + }; +}; + diff --git a/source/shared/idlib/stlalloc.h b/source/shared/idlib/stlalloc.h new file mode 100644 index 0000000..d740827 --- /dev/null +++ b/source/shared/idlib/stlalloc.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include + +// Layout recovered from tungsten.exe.h. The allocator itself is stateless; +// the PC port uses the process allocator until idLib's heap routing is active. +struct testAlloc_t { + void* ptr; + int size; + bool mapHeap; +}; + +template +class idSTLAllocator { +public: + using value_type = type; + using pointer = type*; + using const_pointer = const type*; + using reference = type&; + using const_reference = const type&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + template + struct rebind { + using other = idSTLAllocator; + }; + + idSTLAllocator() noexcept = default; + + template + idSTLAllocator(const idSTLAllocator&) noexcept { + } + + pointer allocate(const size_type count, const void* = nullptr) { + if (count > max_size()) { + throw std::bad_alloc(); + } + return static_cast(::operator new(count * sizeof(type))); + } + + void deallocate(pointer address, size_type) noexcept { + ::operator delete(address); + } + + template + void construct(objectType* address, argsType&&... args) { + ::new (static_cast(address)) objectType( + std::forward(args)...); + } + + template + void destroy(objectType* address) { + address->~objectType(); + } + + size_type max_size() const noexcept { + return (std::numeric_limits::max)() / sizeof(type); + } +}; + +template +inline bool operator==(const idSTLAllocator&, + const idSTLAllocator&) noexcept { + return true; +} + +template +inline bool operator!=(const idSTLAllocator&, + const idSTLAllocator&) noexcept { + return false; +} + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(testAlloc_t) == 12, "Recovered testAlloc_t ABI changed"); +#endif + diff --git a/source/shared/idlib/swap.h b/source/shared/idlib/swap.h new file mode 100644 index 0000000..1f45d78 --- /dev/null +++ b/source/shared/idlib/swap.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +class idSwap { +public: + template + static void Little(type&) { + static_assert(!std::is_pointer::value, + "serialized pointers cannot be endian swapped"); + } + + template + static void Big(type& value) { + static_assert(!std::is_pointer::value, + "serialized pointers cannot be endian swapped"); + unsigned char* bytes = reinterpret_cast(&value); + std::reverse(bytes, bytes + sizeof(type)); + } + + template + static void LittleArray(type*, int) { + } + + template + static void BigArray(type* values, const int count) { + for (int index = 0; index < count; ++index) Big(values[index]); + } + + static void SixtetsForInt(unsigned char* output, const int source) { + const unsigned int value = static_cast(source); + output[0] = static_cast((value >> 18) & 0x3F); + output[1] = static_cast((value >> 12) & 0x3F); + output[2] = static_cast((value >> 6) & 0x3F); + output[3] = static_cast(value & 0x3F); + } + + static int IntForSixtets(const unsigned char* input) { + return static_cast((static_cast(input[0]) << 18) + | (static_cast(input[1]) << 12) + | (static_cast(input[2]) << 6) + | static_cast(input[3])); + } +}; + +static_assert(sizeof(idSwap) == 1, "Recovered idSwap must remain empty"); + diff --git a/source/shared/idlib/sys/sys_fibers.h b/source/shared/idlib/sys/sys_fibers.h new file mode 100644 index 0000000..70e5433 --- /dev/null +++ b/source/shared/idlib/sys/sys_fibers.h @@ -0,0 +1,6 @@ +#pragma once + +// The Win32 implementation was reconstructed from the recovered fiber API; +// retain the platform-neutral Tungsten include path for callers. +#include "idlib/sys/win32/win_fibers.h" + diff --git a/source/shared/idlib/sys/sys_threading.cpp b/source/shared/idlib/sys/sys_threading.cpp new file mode 100644 index 0000000..e33ed6b --- /dev/null +++ b/source/shared/idlib/sys/sys_threading.cpp @@ -0,0 +1,176 @@ +#include "sys_threading.h" + +#include + +idSysMutex::idSysMutex() + : handle(CreateMutexA(nullptr, FALSE, nullptr)) { +} + +idSysMutex::~idSysMutex() { + if (handle != nullptr) CloseHandle(static_cast(handle)); + handle = nullptr; +} + +bool idSysMutex::Lock(const bool blocking) { + if (handle == nullptr) return false; + const DWORD result = WaitForSingleObject(static_cast(handle), + blocking ? INFINITE : 0); + return result == WAIT_OBJECT_0 || result == WAIT_ABANDONED; +} + +void idSysMutex::Unlock() { + if (handle != nullptr) ReleaseMutex(static_cast(handle)); +} + +idSysSignal::idSysSignal(const bool manualReset) + : handle(CreateEventA(nullptr, manualReset ? TRUE : FALSE, FALSE, nullptr)) { +} + +idSysSignal::~idSysSignal() { + if (handle != nullptr) CloseHandle(static_cast(handle)); + handle = nullptr; +} + +void idSysSignal::Raise() { + if (handle != nullptr) SetEvent(static_cast(handle)); +} + +void idSysSignal::Clear() { + if (handle != nullptr) ResetEvent(static_cast(handle)); +} + +bool idSysSignal::Wait(const int timeoutMilliseconds) const { + if (handle == nullptr) return false; + const DWORD timeout = timeoutMilliseconds < 0 + ? INFINITE + : static_cast(timeoutMilliseconds); + return WaitForSingleObject(static_cast(handle), timeout) + == WAIT_OBJECT_0; +} + +idSysThread::idSysThread() + : name(), threadHandle(0), isWorker(false), isRunning(false), + isTerminating(false), moreWorkToDo(false), signalWorkerDone(true), + signalMoreWorkToDo(false), signalMutex() { +} + +idSysThread::~idSysThread() { + StopThread(true); + if (threadHandle != 0) { + CloseHandle(reinterpret_cast( + static_cast(threadHandle))); + threadHandle = 0; + } +} + +int idSysThread::Run() { + return 0; +} + +DWORD WINAPI idSysThread::ThreadProc(void* parameter) { + idSysThread* const thread = static_cast(parameter); + int result = 0; + if (thread->isWorker) { + for (;;) { + thread->signalMutex.Lock(); + if (!thread->moreWorkToDo) { + thread->signalWorkerDone.Raise(); + thread->signalMutex.Unlock(); + thread->signalMoreWorkToDo.Wait(); + continue; + } + thread->moreWorkToDo = false; + thread->signalMoreWorkToDo.Clear(); + const bool terminate = thread->isTerminating; + thread->signalMutex.Unlock(); + if (terminate) break; + result = thread->Run(); + } + thread->signalWorkerDone.Raise(); + } else { + result = thread->Run(); + } + thread->isRunning = false; + return static_cast(result); +} + +bool idSysThread::StartThread(const char* threadName, const core_t core, + const xthreadPriority priority, const int stackSize) { + if (isRunning) return false; + name = threadName; + isTerminating = false; + if (threadHandle != 0) { + HANDLE oldHandle = reinterpret_cast( + static_cast(threadHandle)); + WaitForSingleObject(oldHandle, INFINITE); + CloseHandle(oldHandle); + threadHandle = 0; + } + HANDLE handle = CreateThread(nullptr, static_cast(stackSize), + ThreadProc, this, 0, nullptr); + if (handle == nullptr) return false; + threadHandle = static_cast( + reinterpret_cast(handle)); + static const int priorities[] = { + THREAD_PRIORITY_LOWEST, + THREAD_PRIORITY_BELOW_NORMAL, + THREAD_PRIORITY_NORMAL, + THREAD_PRIORITY_ABOVE_NORMAL, + THREAD_PRIORITY_HIGHEST + }; + const int priorityIndex = static_cast(priority); + if (priorityIndex >= 0 && priorityIndex < 5) + SetThreadPriority(handle, priorities[priorityIndex]); + if (core != CORE_ANY && static_cast(core) < 32) + SetThreadAffinityMask(handle, std::uintptr_t(1) << static_cast(core)); + isRunning = true; + return true; +} + +bool idSysThread::StartWorkerThread(const char* threadName, const core_t core, + const xthreadPriority priority, const int stackSize) { + if (isRunning) return false; + isWorker = true; + signalWorkerDone.Clear(); + if (!StartThread(threadName, core, priority, stackSize)) return false; + signalWorkerDone.Wait(); + return true; +} + +void idSysThread::StopThread(const bool wait) { + if (!isRunning) return; + if (isWorker) { + signalMutex.Lock(); + moreWorkToDo = true; + signalWorkerDone.Clear(); + isTerminating = true; + signalMoreWorkToDo.Raise(); + signalMutex.Unlock(); + } else { + isTerminating = true; + } + if (wait) WaitForThread(); +} + +void idSysThread::WaitForThread() { + if (isWorker) { + signalWorkerDone.Wait(); + return; + } + if (threadHandle == 0) return; + HANDLE handle = reinterpret_cast( + static_cast(threadHandle)); + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + threadHandle = 0; +} + +void idSysThread::SignalWork() { + if (!isWorker) return; + signalMutex.Lock(); + moreWorkToDo = true; + signalWorkerDone.Clear(); + signalMoreWorkToDo.Raise(); + signalMutex.Unlock(); +} + diff --git a/source/shared/idlib/sys/sys_threading.h b/source/shared/idlib/sys/sys_threading.h new file mode 100644 index 0000000..e1697b0 --- /dev/null +++ b/source/shared/idlib/sys/sys_threading.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../text/str.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include + +enum xthreadPriority : int { + THREAD_LOWEST = 0, + THREAD_BELOW_NORMAL = 1, + THREAD_NORMAL = 2, + THREAD_ABOVE_NORMAL = 3, + THREAD_HIGHEST = 4 +}; + +enum core_t : int { + CORE_ANY = -1, + CORE_0A = 0, + CORE_0B = 1, + CORE_1A = 2, + CORE_1B = 3, + CORE_2A = 4, + CORE_2B = 5 +}; + +class idSysMutex { +public: + void* handle; + + idSysMutex(); + ~idSysMutex(); + bool Lock(bool blocking = true); + void Unlock(); + + idSysMutex(const idSysMutex&) = delete; + idSysMutex& operator=(const idSysMutex&) = delete; +}; + +class idSysSignal { +public: + void* handle; + + explicit idSysSignal(bool manualReset = false); + ~idSysSignal(); + void Raise(); + void Clear(); + bool Wait(int timeoutMilliseconds = -1) const; + + idSysSignal(const idSysSignal&) = delete; + idSysSignal& operator=(const idSysSignal&) = delete; +}; + +class idSysThread { +public: + idSysThread(); + virtual ~idSysThread(); + virtual int Run(); + + bool StartThread(const char* threadName, core_t core = CORE_ANY, + xthreadPriority priority = THREAD_NORMAL, int stackSize = 0x20000); + bool StartWorkerThread(const char* threadName, core_t core = CORE_ANY, + xthreadPriority priority = THREAD_NORMAL, int stackSize = 0x20000); + void StopThread(bool wait = true); + void WaitForThread(); + void SignalWork(); + + bool IsRunning() const { return isRunning; } + bool IsTerminating() const { return isTerminating; } + const char* GetName() const { return name.c_str(); } + + idStr name; + unsigned int threadHandle; + bool isWorker; + bool isRunning; + volatile bool isTerminating; + volatile bool moreWorkToDo; + idSysSignal signalWorkerDone; + idSysSignal signalMoreWorkToDo; + idSysMutex signalMutex; + +private: + static DWORD WINAPI ThreadProc(void* parameter); +}; + +struct ssThreadInfo_t { + int spawnId; + int objectId; + const char* name; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idSysMutex) == 4, "Recovered idSysMutex ABI changed"); +static_assert(sizeof(idSysSignal) == 4, "Recovered idSysSignal ABI changed"); +static_assert(sizeof(idSysThread) == 56, "Recovered idSysThread ABI changed"); +static_assert(sizeof(ssThreadInfo_t) == 12, "Recovered ssThreadInfo_t ABI changed"); +#endif diff --git a/source/shared/idlib/text/base64.cpp b/source/shared/idlib/text/base64.cpp new file mode 100644 index 0000000..dcf185e --- /dev/null +++ b/source/shared/idlib/text/base64.cpp @@ -0,0 +1,79 @@ +#include "base64.h" + +namespace { + +int DecodeCharacter(const unsigned char character) { + if (character >= 'A' && character <= 'Z') return character - 'A'; + if (character >= 'a' && character <= 'z') return character - 'a' + 26; + if (character >= '0' && character <= '9') return character - '0' + 52; + if (character == '+') return 62; + if (character == '/') return 63; + return -1; +} + +} // namespace + +void idBase64::Encode(const unsigned char* source, const int size) { + static const unsigned char alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (source == nullptr || size <= 0) { + if (EnsureAlloced(1)) data[0] = 0; + len = 0; + return; + } + const int outputLength = ((size + 2) / 3) * 4; + if (!EnsureAlloced(outputLength + 1)) return; + len = 0; + int input = 0; + while (input + 2 < size) { + const unsigned int value = (static_cast(source[input]) << 16) + | (static_cast(source[input + 1]) << 8) + | source[input + 2]; + data[len++] = alphabet[(value >> 18) & 63]; + data[len++] = alphabet[(value >> 12) & 63]; + data[len++] = alphabet[(value >> 6) & 63]; + data[len++] = alphabet[value & 63]; + input += 3; + } + if (size - input == 1) { + const unsigned int value = static_cast(source[input]) << 16; + data[len++] = alphabet[(value >> 18) & 63]; + data[len++] = alphabet[(value >> 12) & 63]; + data[len++] = '='; + data[len++] = '='; + } else if (size - input == 2) { + const unsigned int value = (static_cast(source[input]) << 16) + | (static_cast(source[input + 1]) << 8); + data[len++] = alphabet[(value >> 18) & 63]; + data[len++] = alphabet[(value >> 12) & 63]; + data[len++] = alphabet[(value >> 6) & 63]; + data[len++] = '='; + } + data[len] = 0; +} + +int idBase64::Decode(unsigned char* destination) const { + if (destination == nullptr || data == nullptr || len == 0 + || (len & 3) != 0) return 0; + int output = 0; + for (int input = 0; input < len; input += 4) { + const bool lastGroup = input + 4 == len; + const bool pad2 = data[input + 2] == '='; + const bool pad3 = data[input + 3] == '='; + if ((!lastGroup && (pad2 || pad3)) || (pad2 && !pad3)) return 0; + const int a = DecodeCharacter(data[input]); + const int b = DecodeCharacter(data[input + 1]); + const int c = pad2 ? 0 : DecodeCharacter(data[input + 2]); + const int d = pad3 ? 0 : DecodeCharacter(data[input + 3]); + if (a < 0 || b < 0 || c < 0 || d < 0) return 0; + const unsigned int value = (static_cast(a) << 18) + | (static_cast(b) << 12) + | (static_cast(c) << 6) + | static_cast(d); + destination[output++] = static_cast(value >> 16); + if (!pad2) destination[output++] = static_cast(value >> 8); + if (!pad3) destination[output++] = static_cast(value); + } + destination[output] = 0; + return output; +} diff --git a/source/shared/idlib/text/base64.h b/source/shared/idlib/text/base64.h new file mode 100644 index 0000000..9976db5 --- /dev/null +++ b/source/shared/idlib/text/base64.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +class idBase64 { +public: + unsigned char* data; + int len; + int alloced; + + idBase64() : data(nullptr), len(0), alloced(0) {} + idBase64(const idBase64& other) : idBase64() { + if (EnsureAlloced(other.len + 1)) { + std::memcpy(data, other.data, static_cast(other.len + 1)); + len = other.len; + } + } + ~idBase64() { std::free(data); } + + idBase64& operator=(const idBase64& other) { + if (this != &other && EnsureAlloced(other.len + 1)) { + std::memcpy(data, other.data, static_cast(other.len + 1)); + len = other.len; + } + return *this; + } + + void Encode(const unsigned char* source, int size); + int Decode(unsigned char* destination) const; + + const char* c_str() const { + return data == nullptr ? "" : reinterpret_cast(data); + } + int Length() const { return len; } + +private: + bool EnsureAlloced(const int amount) { + if (amount <= alloced) return true; + void* replacement = std::realloc(data, static_cast(amount)); + if (replacement == nullptr) return false; + data = static_cast(replacement); + alloced = amount; + return true; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idBase64) == 12, "Recovered idBase64 ABI changed"); +#endif diff --git a/source/shared/idlib/text/lexer.h b/source/shared/idlib/text/lexer.h new file mode 100644 index 0000000..1397218 --- /dev/null +++ b/source/shared/idlib/text/lexer.h @@ -0,0 +1,108 @@ +#pragma once + +#include "token.h" + +#include + +struct punctuation_t { + char* p; + int n; +}; + +class alignas(4) idLexer { +public: + explicit idLexer(int flags = 0); + ~idLexer(); + + bool LoadFile(const char* fileName, bool OSPath = false); + bool LoadFilePartial(const char* fileName, bool OSPath, int length); + bool LoadMemory(const char* pointer, unsigned int length, + const char* name); + void FreeSource(); + void Reset(); + bool SetScriptP(const char* pointer); + void SetPunctuations(const punctuation_t* punctuation); + + bool ReadToken(idToken& token); + bool ReadTokenOnLine(idToken& token); + void UnreadToken(); + bool ExpectTokenString(const char* string); + bool ExpectTokenType(int type, int subtype, idToken& token); + bool ExpectAnyToken(idToken& token); + bool CheckTokenString(const char* string); + bool CheckTokenType(int type, int subtype, idToken& token); + bool PeekTokenString(const char* string); + bool PeekTokenType(int type, int subtype, idToken& token); + + bool SkipUntilString(const char* string); + bool SkipRestOfLine(); + bool SkipBracedSection(bool parseFirstBrace = true); + bool SkipWhiteSpace(bool currentLine); + bool ParseBracedSectionExact(idStr& out, bool parseFirstBrace, + char openBrace = '{', char closeBrace = '}'); + const char* ParseRestOfLine(idStr& out); + const char* ParseCompleteLine(idStr& out); + int ParseInt(); + unsigned int ParseUnsignedInt(); + bool ParseBool(); + float ParseFloat(bool* errorFlag = nullptr); + bool Parse1DMatrix(int x, float* matrix, bool expectCommas = false); + bool Parse2DMatrix(int y, int x, float* matrix); + + int GetLastWhiteSpace(idStr& whiteSpace) const; + int GetNextWhiteSpace(idStr& whiteSpace, bool currentLine); + bool GetWhiteSpaceBeforeToken(const idToken& token, + idStr& whiteSpace) const; + const char* GetPunctuationFromId(int id) const; + int GetPunctuationId(const char* punctuation) const; + bool EndOfFile() const; + bool HadError() const; + bool HadWarning() const; + const char* GetFileName() const { return filename.c_str(); } + int GetFileOffset() const { + return script_p == nullptr || buffer == nullptr + ? 0 : static_cast(script_p - buffer); + } + int GetLineNum() const { return line; } + + void Error(const char* format, ...); + void Warning(const char* format, ...); + + bool loaded; + idStr filename; + int allocated; + const char* buffer; + const char* script_p; + const char* end_p; + const char* lastScript_p; + const char* whiteSpaceStart_p; + const char* whiteSpaceEnd_p; + unsigned int fileTime; + unsigned int length; + int line; + int lastline; + int flags; + const punctuation_t* punctuations; + int* punctuationtable; + int* nextpunctuation; + idLexer* next; + idStr errorMsg; + bool hadError; + bool hadWarning; + +private: + bool CheckString(const char* string) const; + void CreatePunctuationTable(const punctuation_t* punctuation); + bool ReadEscapeCharacter(char* character); + bool ReadName(idToken& token); + bool ReadNumber(idToken& token); + bool ReadPunctuation(idToken& token); + bool ReadRawStringBlock(idToken& token); + bool ReadString(idToken& token, int quote); +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(punctuation_t) == 8, + "Recovered punctuation_t ABI changed"); +static_assert(sizeof(idLexer) == 136, "Recovered idLexer ABI changed"); +#endif diff --git a/source/shared/idlib/text/parser.h b/source/shared/idlib/text/parser.h new file mode 100644 index 0000000..2756f37 --- /dev/null +++ b/source/shared/idlib/text/parser.h @@ -0,0 +1,159 @@ +#pragma once + +#include "lexer.h" +#include "../containers/list.h" + +class idParser { +public: + struct define_t { + idStr name; + int scope; + int builtin; + int numparms; + idToken* parms; + idToken* tokens; + define_t* next; + define_t* hashnext; + }; + + struct indent_t { + int type; + int skip; + int skipElse; + idLexer* script; + }; + + class idDependency { + public: + int includeLevel; + idStr fileName; + }; + + using pragmaCallback_t = void (*)(void* data, const char* text); + + explicit idParser(int flags = 0); + ~idParser(); + + bool LoadFile(const char* fileName, bool OSPath = false); + bool LoadMemory(const char* pointer, int length, const char* name); + void FreeSource(); + void AddInclude(const char* fileName); + int AddDefine(const char* defineString); + void PushDefineScope(); + void PopDefineScope(); + void SetFlags(int parserFlags); + int GetFlags() const { return flags; } + void SetIncludePath(const char* path) { includepath = path; } + void SetPragmaCallback(pragmaCallback_t callback, void* data) { + pragmaCallback = callback; + pragmaData = data; + } + + bool ReadToken(idToken& token); + int ReadTokenOnLine(idToken& token); + void UnreadToken(const idToken& token); + bool ExpectTokenString(const char* string); + int ExpectTokenType(int type, int subtype, idToken& token); + int ExpectAnyToken(idToken& token); + int CheckTokenString(const char* string); + int CheckTokenType(int type, int subtype, idToken& token); + int PeekTokenString(const char* string); + int PeekTokenType(int type, int subtype, idToken& token); + bool SkipUntilString(const char* string); + int SkipRestOfLine(); + int SkipBracedSection(bool parseFirstBrace = true); + bool ParseBracedSectionExact(idStr& out, bool parseFirstBrace = true); + const char* ParseRestOfLine(idStr& out); + int ParseInt(); + bool ParseBool(); + float ParseFloat(bool* errorFlag = nullptr); + bool Parse1DMatrix(int x, float* matrix); + int GetLastWhiteSpace(idStr& whiteSpace) const; + int GetNextWhiteSpace(idStr& whiteSpace, bool currentLine); + int GetPunctuationId(const char* punctuation); + bool HadError() const; + bool HadWarning() const; + const idList& GetDependencies() const { + return dependencies; + } + void Error(const char* format, ...) const; + void Warning(const char* format, ...) const; + + static void SetupGlobalDefines(); + + bool loaded; + idStr filename; + idStr includepath; + bool OSPath; + const punctuation_t* punctuations; + int flags; + idLexer* scriptstack; + idToken* tokens; + int defineScope; + define_t* definehash[128]; + idList indentstack; + int skip; + pragmaCallback_t pragmaCallback; + void* pragmaData; + bool hadError; + bool hadWarning; + int includeLevel; + idList dependencies; + +private: + void AddDependency(int level, const char* fileName); + void AddGlobalDefinesToSource(); + define_t* CopyDefine(define_t* define); + static define_t* DefineFromString(const char* string); + static void FreeDefine(define_t* define); + define_t* FindHashedDefine(const char* name); + int FindDefineParm(define_t* define, const char* name); + bool PushScript(idLexer* script); + bool UnreadSourceToken(const idToken& token); + int ReadSourceToken(idToken& token); + int ReadDefineParms(define_t* define, idToken** parms, int maxParms); + int ReadDirective(); + int ReadDollarDirective(); + int ReadLine(idToken& token, bool multiline); + void PushIndent(int type, int skip, int skipElse); + void UnreadSignToken(); + int StringizeTokens(idToken* tokens, idToken* token); + int MergeTokens(idToken* first, idToken* second); + int ExpandBuiltinDefine(idToken* token, define_t* define, + idToken** firstToken, idToken** lastToken); + int ExpandDefine(idToken* token, define_t* define, + idToken** firstToken, idToken** lastToken); + int ExpandDefineIntoSource(idToken* token, define_t* define); + int EvaluateTokens(idToken* tokens, int* intValue, double* floatValue, + int integer); + int Evaluate(int* intValue, double* floatValue, int integer); + int EvaluateFunction(int* intValue, double* floatValue, int integer); + define_t* Directive_define(bool builtin); + int Directive_elif(); + int Directive_else(); + int Directive_endif(); + int Directive_error(); + int Directive_evalfloat(); + int Directive_evalint(); + int Directive_if(); + int Directive_if_def(int type); + int Directive_include(); + int Directive_line(); + int Directive_pragma(); + int Directive_undef(); + int Directive_warning(); + int DollarDirective_elif(); + int DollarDirective_else(); + int DollarDirective_endif(); + int DollarDirective_if_def(int type); +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idParser::define_t) == 60, + "Recovered idParser::define_t ABI changed"); +static_assert(sizeof(idParser::indent_t) == 16, + "Recovered idParser::indent_t ABI changed"); +static_assert(sizeof(idParser::idDependency) == 36, + "Recovered idParser::idDependency ABI changed"); +static_assert(sizeof(idParser) == 656, "Recovered idParser ABI changed"); +#endif diff --git a/source/shared/idlib/text/str.h b/source/shared/idlib/text/str.h index 1f3e08b..128b0e7 100644 --- a/source/shared/idlib/text/str.h +++ b/source/shared/idlib/text/str.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include // Exact tungsten idStr storage layout (tungsten.exe.h type 12142). This is a // deliberately small ABI facade; more recovered text methods will be added as @@ -103,6 +105,17 @@ public: Append(text.c_str()); } + void Format(const char* format, ...) { + char buffer[4096]; + va_list arguments; + va_start(arguments, format); + const int count = _vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, + format != nullptr ? format : "", arguments); + va_end(arguments); + if (count >= 0) Assign(buffer); + else Clear(); + } + void ReplaceRecovered(const char* oldText, const char* newText) { if (oldText == nullptr || oldText[0] == '\0') { return; @@ -181,7 +194,7 @@ private: return false; } - const int newAmount = std::max(amount, amount + amount / 2); + const int newAmount = (std::max)(amount, amount + amount / 2); char* const replacement = static_cast( std::malloc(static_cast(newAmount)) ); diff --git a/source/shared/idlib/text/strstatic.h b/source/shared/idlib/text/strstatic.h new file mode 100644 index 0000000..0dfa102 --- /dev/null +++ b/source/shared/idlib/text/strstatic.h @@ -0,0 +1,31 @@ +#pragma once + +#include "idlib/text/str.h" + +template +class idStrStatic : public idStr { +public: + char buffer[bufferSize]; + + idStrStatic() { UseStaticBufferRecovered(buffer, bufferSize); } + idStrStatic(const char* text) : idStrStatic() { idStr::operator=(text); } + idStrStatic(const idStr& text) : idStrStatic() { idStr::operator=(text); } + idStrStatic(const idStrStatic& text) : idStrStatic() { + idStr::operator=(static_cast(text)); + } + idStrStatic& operator=(const idStrStatic& text) { + idStr::operator=(static_cast(text)); return *this; + } + idStrStatic& operator=(const idStr& text) { + idStr::operator=(text); return *this; + } + idStrStatic& operator=(const char* text) { + idStr::operator=(text); return *this; + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idStrStatic<8>) == 40, "Recovered idStrStatic<8> ABI changed"); +static_assert(sizeof(idStrStatic<260>) == 292, "Recovered idStrStatic<260> ABI changed"); +#endif + diff --git a/source/shared/idlib/text/token.h b/source/shared/idlib/text/token.h new file mode 100644 index 0000000..e6f9f4f --- /dev/null +++ b/source/shared/idlib/text/token.h @@ -0,0 +1,21 @@ +#pragma once + +#include "idlib/text/tokenstatic.h" + +enum Tokens : int { + TOK_basedLp = 0, + TOK_cdecl, + TOK_pascal, + TOK_stdcall, + TOK_thiscall, + TOK_fastcall, + TOK_cocall, + TOK_eabi, + TOK_ptr64, + TOK_restrict, + TOK_unaligned, + TOK__last +}; + +typedef idTokenStatic<256> token_t; + diff --git a/source/shared/idlib/typeinfo/typeinfoobject.h b/source/shared/idlib/typeinfo/typeinfoobject.h index b871a31..ef555be 100644 --- a/source/shared/idlib/typeinfo/typeinfoobject.h +++ b/source/shared/idlib/typeinfo/typeinfoobject.h @@ -1,6 +1,7 @@ #pragma once #include "idlib/text/str.h" +#include "idlib/typeinfo/typeinfovariable.h" #include #include @@ -75,6 +76,13 @@ struct typeInfo_t { class idTypeInfoTools { public: + struct readWrite_t { + void (*Write)(const idTypeInfoTools*, idTypeInfoFile*, const char*, + const char*, const char*, const char*, int, const char*, void*); + void (*Read)(const idTypeInfoTools*, idTypeInfoFile*, const char*, + const char*, const char*, const char*, int, const char*, void*); + }; + explicit idTypeInfoTools(const typeInfo_t* info = nullptr) : typeInfo(info), enumHash{}, classHash{}, enumObject{}, enumPointer{}, classObject{}, classPointer{}, editDepth(0), designDepth(0), @@ -234,48 +242,6 @@ struct idPathTypeInfo { std::uint32_t metaData[4]; }; -struct idTypeInfoVariable { - idTypeInfoVariable(const char* typeName = "", const char* typeOps = "", - const char* variablePath = "") - : type(typeName), ops(typeOps), path(variablePath) {} - const char* type; - const char* ops; - const char* path; -}; - -struct idTypeInfoVariable_bool : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; -struct idTypeInfoVariable_int : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; -struct idTypeInfoVariable_float : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; -struct idTypeInfoVariable_StrPtr : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; - -struct idTypeInfoVariableTemplate : idTypeInfoVariable { - idTypeInfoVariableTemplate(const char* typeName = "", - const char* typeOps = "", const char* variablePath = "", - const char* argumentType = "", const char* argumentOps = "") - : idTypeInfoVariable(typeName, typeOps, variablePath), - argType(argumentType), argOps(argumentOps) {} - const char* argType; - const char* argOps; -}; - -struct idTypeInfoVariable_idList : idTypeInfoVariableTemplate { - using idTypeInfoVariableTemplate::idTypeInfoVariableTemplate; -}; -struct idTypeInfoVariable_enum : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; -struct idTypeInfoVariable_idStr : idTypeInfoVariable { - using idTypeInfoVariable::idTypeInfoVariable; -}; - class idTypeInfoObject { public: idTypeInfoObject(void* objectPointer, const char* objectTypeName, diff --git a/source/shared/idlib/typeinfo/typeinfotools.h b/source/shared/idlib/typeinfo/typeinfotools.h new file mode 100644 index 0000000..8b83237 --- /dev/null +++ b/source/shared/idlib/typeinfo/typeinfotools.h @@ -0,0 +1,6 @@ +#pragma once + +// The recovered implementation groups these declarations in +// typeinfoobject.h; this restores Tungsten's original public include path. +#include "idlib/typeinfo/typeinfoobject.h" + diff --git a/source/shared/idlib/typeinfo/typeinfovariable.h b/source/shared/idlib/typeinfo/typeinfovariable.h new file mode 100644 index 0000000..8f9b03e --- /dev/null +++ b/source/shared/idlib/typeinfo/typeinfovariable.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +class idTypeInfoVariable { +public: + idTypeInfoVariable(const char* typeName = "", const char* typeOps = "", + const char* variablePath = "") + : type(typeName), ops(typeOps), path(variablePath) {} + + const char* type; + const char* ops; + const char* path; +}; + +class idTypeInfoVariableTemplate : public idTypeInfoVariable { +public: + idTypeInfoVariableTemplate(const char* typeName = "", + const char* typeOps = "", const char* variablePath = "", + const char* argumentType = "", const char* argumentOps = "") + : idTypeInfoVariable(typeName, typeOps, variablePath), + argType(argumentType), argOps(argumentOps) {} + + const char* argType; + const char* argOps; +}; + +#define IDTECH5_TYPEINFO_VARIABLE(name) \ + class idTypeInfoVariable_##name : public idTypeInfoVariable { \ + public: \ + using idTypeInfoVariable::idTypeInfoVariable; \ + } + +IDTECH5_TYPEINFO_VARIABLE(bool); +IDTECH5_TYPEINFO_VARIABLE(int); +IDTECH5_TYPEINFO_VARIABLE(float); +IDTECH5_TYPEINFO_VARIABLE(StrPtr); +IDTECH5_TYPEINFO_VARIABLE(enum); +IDTECH5_TYPEINFO_VARIABLE(idStr); +IDTECH5_TYPEINFO_VARIABLE(idMat2); +IDTECH5_TYPEINFO_VARIABLE(idMat3); +IDTECH5_TYPEINFO_VARIABLE(idAngles); +IDTECH5_TYPEINFO_VARIABLE(idVec4); +IDTECH5_TYPEINFO_VARIABLE(unsigned_char); +IDTECH5_TYPEINFO_VARIABLE(wchar_t); +IDTECH5_TYPEINFO_VARIABLE(idBounds); +IDTECH5_TYPEINFO_VARIABLE(short); +IDTECH5_TYPEINFO_VARIABLE(idVec3); +IDTECH5_TYPEINFO_VARIABLE(char); +IDTECH5_TYPEINFO_VARIABLE(idVecX); +IDTECH5_TYPEINFO_VARIABLE(idMat4); +IDTECH5_TYPEINFO_VARIABLE(idEntityPtr); +IDTECH5_TYPEINFO_VARIABLE(unsigned_int); +IDTECH5_TYPEINFO_VARIABLE(idColor); +IDTECH5_TYPEINFO_VARIABLE(double); +IDTECH5_TYPEINFO_VARIABLE(long); +IDTECH5_TYPEINFO_VARIABLE(unsigned_long); +IDTECH5_TYPEINFO_VARIABLE(idVec2); +IDTECH5_TYPEINFO_VARIABLE(idMatX); + +#undef IDTECH5_TYPEINFO_VARIABLE + +class idTypeInfoVariable_idList : public idTypeInfoVariableTemplate { +public: + using idTypeInfoVariableTemplate::idTypeInfoVariableTemplate; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idTypeInfoVariable) == 12, + "Recovered idTypeInfoVariable ABI changed"); +static_assert(sizeof(idTypeInfoVariableTemplate) == 20, + "Recovered template variable ABI changed"); +#endif diff --git a/source/shared/idlib/typesafenumber.h b/source/shared/idlib/typesafenumber.h new file mode 100644 index 0000000..b0940fc --- /dev/null +++ b/source/shared/idlib/typesafenumber.h @@ -0,0 +1,62 @@ +#pragma once + +// Recovered one-field strongly-typed number used for angles and time units. +// The empty uniqueType parameter carries the unit without adding storage. +template +class idTypesafeNumber { +public: + valueType value; + + idTypesafeNumber() = default; + + idTypesafeNumber(const valueType initialValue) + : value(initialValue) { + } + + valueType Get() const { + return value; + } + + operator valueType() const { + return value; + } + + idTypesafeNumber& operator=(const valueType newValue) { + value = newValue; + return *this; + } + + idTypesafeNumber& operator+=(const idTypesafeNumber& other) { + value += other.value; + return *this; + } + + idTypesafeNumber& operator-=(const idTypesafeNumber& other) { + value -= other.value; + return *this; + } + + bool operator==(const idTypesafeNumber& other) const { return value == other.value; } + bool operator!=(const idTypesafeNumber& other) const { return value != other.value; } + bool operator<(const idTypesafeNumber& other) const { return value < other.value; } + bool operator<=(const idTypesafeNumber& other) const { return value <= other.value; } + bool operator>(const idTypesafeNumber& other) const { return value > other.value; } + bool operator>=(const idTypesafeNumber& other) const { return value >= other.value; } +}; + +template +inline idTypesafeNumber operator+( + idTypesafeNumber lhs, + const idTypesafeNumber& rhs) { + lhs += rhs; + return lhs; +} + +template +inline idTypesafeNumber operator-( + idTypesafeNumber lhs, + const idTypesafeNumber& rhs) { + lhs -= rhs; + return lhs; +} + diff --git a/source/shared/idlib/uniquewarning.h b/source/shared/idlib/uniquewarning.h new file mode 100644 index 0000000..c09ca32 --- /dev/null +++ b/source/shared/idlib/uniquewarning.h @@ -0,0 +1,20 @@ +#pragma once + +#include "idlib/text/str.h" + +// Exact member order recovered as IDA local type 13110. +class idUniqueWarning { +public: + idStr message; + unsigned int count; + + idUniqueWarning() + : count(0) { + } +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idUniqueWarning) == 36, + "Recovered idUniqueWarning ABI changed"); +#endif + diff --git a/source/shared/idlib/xml/xmlelement.h b/source/shared/idlib/xml/xmlelement.h index f0fcfad..24aa13e 100644 --- a/source/shared/idlib/xml/xmlelement.h +++ b/source/shared/idlib/xml/xmlelement.h @@ -1,88 +1,7 @@ #pragma once #include "xmlattribute.h" - -#include -#include -#include - -template -class idRecoveredList { -public: - explicit idRecoveredList(const std::uint8_t tag = 44, - const std::int16_t initialGranularity = 16) - : list(nullptr), num(0), size(0), granularity(initialGranularity), - memTag(tag), listStatic(0) { - } - - ~idRecoveredList() { - Clear(); - } - - idRecoveredList(const idRecoveredList&) = delete; - idRecoveredList& operator=(const idRecoveredList&) = delete; - - int Num() const { return num; } - type& operator[](const int index) { return list[index]; } - const type& operator[](const int index) const { return list[index]; } - - type* Append(const type& value) { - if (num == size && !Grow()) { - return nullptr; - } - list[num] = value; - return &list[num++]; - } - - void Clear() { - if (list != nullptr) { - for (int index = 0; index < size; ++index) { - list[index].~type(); - } - std::free(list); - } - list = nullptr; - num = 0; - size = 0; - } - -private: - type* list; - int num; - int size; - std::int16_t granularity; - std::uint8_t memTag; - std::uint8_t listStatic; - - bool Grow() { - const int amount = granularity > 0 ? granularity : 16; - const int newSize = size + amount; - type* const replacement = static_cast( - std::malloc(sizeof(type) * static_cast(newSize)) - ); - if (replacement == nullptr) { - return false; - } - for (int index = 0; index < newSize; ++index) { - new (&replacement[index]) type(); - } - for (int index = 0; index < num; ++index) { - replacement[index] = list[index]; - } - if (list != nullptr) { - for (int index = 0; index < size; ++index) { - list[index].~type(); - } - std::free(list); - } - list = replacement; - size = newSize; - return true; - } -}; - -static_assert(sizeof(idRecoveredList) == 16, - "Recovered idList ABI changed"); +#include "../containers/recoveredlist.h" class idXMLElement { public: