Skip to content

Commit

Permalink
Implement FixedAllocator and use it for allocating memory in VmtCopy
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkrupinski committed Jul 24, 2023
1 parent 43eb73a commit 4f281c8
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Source/GlobalContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "InventoryChanger/InventoryChanger.h"
#include "Hacks/Features.h"
#include "Hooks.h"
#include "MemoryAllocation/FixedAllocator.h"

namespace csgo
{
Expand Down Expand Up @@ -62,6 +63,7 @@ class GlobalContext {
void swapWindowHook(SDL_Window* window);
#endif

FixedAllocator<10'000> fixedAllocator;
std::optional<Hooks> hooks;
std::optional<EventListener> gameEventListener;
std::optional<EngineInterfacesPODs> engineInterfacesPODs;
Expand Down
31 changes: 31 additions & 0 deletions Source/MemoryAllocation/FixedAllocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <cstddef>

#include "FreeMemoryRegionList.h"

template <std::size_t Capacity>
class FixedAllocator {
public:
FixedAllocator() = default;
FixedAllocator(const FixedAllocator&) = delete;
FixedAllocator(FixedAllocator&&) = delete;
FixedAllocator& operator=(const FixedAllocator&) = delete;
FixedAllocator& operator=(FixedAllocator&&) = delete;

static_assert(Capacity >= FreeMemoryRegionList::minimumAllocationSize());

[[nodiscard]] std::byte* allocate(std::size_t size) noexcept
{
return freeRegionList.allocate(size);
}

void deallocate(std::byte* pointer, std::size_t size) noexcept
{
freeRegionList.deallocate(pointer, size);
}

private:
alignas(FreeMemoryRegionList::minimumAlignment()) std::byte storage[Capacity];
FreeMemoryRegionList freeRegionList{ storage };
};
39 changes: 39 additions & 0 deletions Source/MemoryAllocation/FreeMemoryRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>

struct FreeMemoryRegion {
std::size_t size;
FreeMemoryRegion* nextFreeRegion;

[[nodiscard]] bool isLargeEnoughForAllocation(std::size_t allocationSize) const noexcept
{
if (size < allocationSize)
return false;

const auto sizeAfterAllocation = size - allocationSize;
return sizeAfterAllocation == 0 || sizeAfterAllocation >= sizeof(FreeMemoryRegion);
}

void tryMergeWithNextRegion() noexcept
{
assert(nextFreeRegion == nullptr || reinterpret_cast<std::uintptr_t>(nextFreeRegion) > reinterpret_cast<std::uintptr_t>(this));
if (nextFreeRegion && reinterpret_cast<std::uintptr_t>(nextFreeRegion) - reinterpret_cast<std::uintptr_t>(this) == size) {
size += nextFreeRegion->size;
nextFreeRegion = nextFreeRegion->nextFreeRegion;
}
}

void appendRegion(std::byte* newRegionPointer, std::size_t newRegionSize) noexcept
{
assert(newRegionPointer != nullptr);
assert(reinterpret_cast<std::uintptr_t>(newRegionPointer) > reinterpret_cast<std::uintptr_t>(this));
assert(newRegionSize >= sizeof(FreeMemoryRegion));

nextFreeRegion = new (newRegionPointer) FreeMemoryRegion{ newRegionSize, nextFreeRegion };
nextFreeRegion->tryMergeWithNextRegion();
tryMergeWithNextRegion();
}
};
33 changes: 33 additions & 0 deletions Source/MemoryAllocation/FreeMemoryRegionFinder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <cstddef>
#include <cstdint>

#include "FreeMemoryRegion.h"

struct FreeMemoryRegionFinder {
explicit FreeMemoryRegionFinder(FreeMemoryRegion** firstFreeRegion) noexcept
: firstFreeRegion{ firstFreeRegion }
{
}

[[nodiscard]] FreeMemoryRegion** findFreeRegion(std::size_t size) const noexcept
{
for (FreeMemoryRegion** region = firstFreeRegion; *region != nullptr; region = &(*region)->nextFreeRegion) {
if ((*region)->isLargeEnoughForAllocation(size))
return region;
}
return nullptr;
}

[[nodiscard]] FreeMemoryRegion** findNearestFreeRegion(std::uintptr_t address) const noexcept
{
FreeMemoryRegion** region = firstFreeRegion;
while (*region != nullptr && (*region)->nextFreeRegion != nullptr && reinterpret_cast<std::uintptr_t>((*region)->nextFreeRegion) < address)
region = &(*region)->nextFreeRegion;
return region;
}

private:
FreeMemoryRegion** firstFreeRegion;
};
92 changes: 92 additions & 0 deletions Source/MemoryAllocation/FreeMemoryRegionList.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <span>

#include "FreeMemoryRegion.h"
#include "FreeMemoryRegionFinder.h"

#ifndef NDEBUG
#include <Utils/MemorySection.h>
#endif

class FreeMemoryRegionList {
public:
explicit FreeMemoryRegionList(std::span<std::byte> memory) noexcept
: firstFreeRegion{ createInitialFreeRegion(memory) }
#ifndef NDEBUG
, memory{ memory }
#endif
{
}

FreeMemoryRegionList(const FreeMemoryRegionList&) = delete;
FreeMemoryRegionList(FreeMemoryRegionList&&) = delete;
FreeMemoryRegionList& operator=(const FreeMemoryRegionList&) = delete;
FreeMemoryRegionList& operator=(FreeMemoryRegionList&&) = delete;

[[nodiscard]] static constexpr std::size_t minimumAlignment() noexcept
{
return alignof(FreeMemoryRegion);
}

[[nodiscard]] static constexpr std::size_t minimumAllocationSize() noexcept
{
return sizeof(FreeMemoryRegion);
}

#ifndef NDEBUG
~FreeMemoryRegionList() noexcept
{
assert(firstFreeRegion != nullptr && firstFreeRegion->size == memory.size() && firstFreeRegion->nextFreeRegion == nullptr && "Memory leak detected");
}
#endif

[[nodiscard]] std::byte* allocate(std::size_t size) noexcept
{
assert(size >= minimumAllocationSize());
assert(size % minimumAlignment() == 0);

const auto freeRegion = FreeMemoryRegionFinder{ &firstFreeRegion }.findFreeRegion(size);
if (freeRegion == nullptr) [[unlikely]]
return nullptr;

const auto result = reinterpret_cast<std::byte*>(*freeRegion);
if ((*freeRegion)->size != size)
*freeRegion = new (result + size) FreeMemoryRegion{ (*freeRegion)->size - size, (*freeRegion)->nextFreeRegion };
else
*freeRegion = (*freeRegion)->nextFreeRegion;
return result;
}

void deallocate(std::byte* pointer, std::size_t size) noexcept
{
assert(MemorySection{ memory }.contains(reinterpret_cast<std::uintptr_t>(pointer), size) && "Invalid pointer or size provided for deallocation");

const auto nearestRegion = FreeMemoryRegionFinder{ &firstFreeRegion }.findNearestFreeRegion(reinterpret_cast<std::uintptr_t>(pointer));
const auto noFreeRegionsBeforeDeallocation = (*nearestRegion == nullptr);
if (noFreeRegionsBeforeDeallocation || reinterpret_cast<std::uintptr_t>(*nearestRegion) > reinterpret_cast<std::uintptr_t>(pointer)) {
*nearestRegion = new (pointer) FreeMemoryRegion{ size, *nearestRegion };
} else {
(*nearestRegion)->appendRegion(pointer, size);
}

if (!noFreeRegionsBeforeDeallocation)
(*nearestRegion)->tryMergeWithNextRegion();
}

private:
[[nodiscard]] static FreeMemoryRegion* createInitialFreeRegion(std::span<std::byte> memory) noexcept
{
assert(memory.size() >= minimumAllocationSize());
assert(std::uintptr_t(memory.data()) % minimumAlignment() == 0);
return new (memory.data()) FreeMemoryRegion{ memory.size(), nullptr };
}

FreeMemoryRegion* firstFreeRegion;
#ifndef NDEBUG
std::span<std::byte> memory;
#endif
};
22 changes: 22 additions & 0 deletions Source/MemoryAllocation/MemoryAllocator.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
#pragma once

#include <cstddef>
#include <type_traits>

#include <Utils/Align.h>

#include "FreeMemoryRegionList.h"

struct MemoryAllocator {
[[nodiscard]] static std::byte* allocate(std::size_t size) noexcept;
static void deallocate(std::byte* memory, std::size_t size) noexcept;

template <typename T>
[[nodiscard]] static auto memoryFor() noexcept
{
static_assert(!std::is_array_v<T>);
static_assert(alignof(T) <= FreeMemoryRegionList::minimumAlignment(), "Unsupported alignment");
return utils::align<sizeof(T), FreeMemoryRegionList::minimumAlignment()>();
}

template <typename T>
[[nodiscard]] static auto memoryFor(std::size_t numberOfElements) noexcept
{
static_assert(std::is_array_v<T>);
static_assert(alignof(T) <= FreeMemoryRegionList::minimumAlignment(), "Unsupported alignment");
assert(numberOfElements <= (std::numeric_limits<std::size_t>::max)() / sizeof(std::remove_extent_t<T>));
return utils::align(numberOfElements * sizeof(std::remove_extent_t<T>), FreeMemoryRegionList::minimumAlignment());
}
};
6 changes: 3 additions & 3 deletions Source/Osiris.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ void initializeGlobalContext(Args&&... args)

std::byte* MemoryAllocator::allocate(std::size_t size) noexcept
{
return static_cast<std::byte*>(std::malloc(size));
return globalContext->fixedAllocator.allocate(size);
}

void MemoryAllocator::deallocate(std::byte* memory, std::size_t) noexcept
void MemoryAllocator::deallocate(std::byte* memory, std::size_t size) noexcept
{
std::free(memory);
globalContext->fixedAllocator.deallocate(memory, size);
}

#if IS_WIN32() || IS_WIN64()
Expand Down
4 changes: 4 additions & 0 deletions Source/Osiris.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@
<ClInclude Include="InventoryChanger\ItemId.h" />
<ClInclude Include="InventoryChanger\WeaponNames.h" />
<ClInclude Include="JsonForward.h" />
<ClInclude Include="MemoryAllocation\FixedAllocator.h" />
<ClInclude Include="MemoryAllocation\FreeMemoryRegion.h" />
<ClInclude Include="MemoryAllocation\FreeMemoryRegionFinder.h" />
<ClInclude Include="MemoryAllocation\FreeMemoryRegionList.h" />
<ClInclude Include="MemoryAllocation\MemoryAllocator.h" />
<ClInclude Include="MemorySearch\BytePattern.h" />
<ClInclude Include="MemorySearch\BytePatternConverter.h" />
Expand Down
12 changes: 12 additions & 0 deletions Source/Osiris.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,18 @@
<ClInclude Include="MemoryAllocation\MemoryAllocator.h">
<Filter>MemoryAllocation</Filter>
</ClInclude>
<ClInclude Include="MemoryAllocation\FixedAllocator.h">
<Filter>MemoryAllocation</Filter>
</ClInclude>
<ClInclude Include="MemoryAllocation\FreeMemoryRegion.h">
<Filter>MemoryAllocation</Filter>
</ClInclude>
<ClInclude Include="MemoryAllocation\FreeMemoryRegionFinder.h">
<Filter>MemoryAllocation</Filter>
</ClInclude>
<ClInclude Include="MemoryAllocation\FreeMemoryRegionList.h">
<Filter>MemoryAllocation</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<FxCompile Include="Resources\Shaders\default_vs.hlsl">
Expand Down
29 changes: 29 additions & 0 deletions Source/Utils/Align.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <cassert>
#include <cstddef>
#include <limits>

namespace utils
{

template <std::size_t Size, std::size_t Alignment>
consteval std::size_t align() noexcept
{
if constexpr (constexpr auto offset = Alignment - Size % Alignment; offset != Alignment) {
static_assert(Size <= (std::numeric_limits<std::size_t>::max)() - offset, "Size is too large to align");
return Size + offset;
}
return Size;
}

[[nodiscard]] inline std::size_t align(std::size_t size, std::size_t alignment) noexcept
{
if (const auto offset = alignment - size % alignment; offset != alignment) {
assert(size <= (std::numeric_limits<std::size_t>::max)() - offset && "Size is too large to align");
return size + offset;
}
return size;
}

}
4 changes: 2 additions & 2 deletions Source/Vmt/VmtCopy.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class VmtCopy {
~VmtCopy() noexcept
{
if (replacementVmtWithTypeInfo)
MemoryAllocator::deallocate(reinterpret_cast<std::byte*>(replacementVmtWithTypeInfo), sizeof(std::uintptr_t) * lengthWithTypeInfo());
MemoryAllocator::deallocate(reinterpret_cast<std::byte*>(replacementVmtWithTypeInfo), MemoryAllocator::memoryFor<std::uintptr_t[]>(lengthWithTypeInfo()));
}

private:
[[nodiscard]] std::uintptr_t* allocateReplacementVmtWithTypeInfo() const noexcept
{
return new (MemoryAllocator::allocate(sizeof(std::uintptr_t) * lengthWithTypeInfo())) std::uintptr_t[lengthWithTypeInfo()];
return new (MemoryAllocator::allocate(MemoryAllocator::memoryFor<std::uintptr_t[]>(lengthWithTypeInfo()))) std::uintptr_t[lengthWithTypeInfo()];
}

void copyOriginalVmt() const noexcept
Expand Down

0 comments on commit 4f281c8

Please sign in to comment.