├── .github ├── FUNDING.yml └── workflows │ ├── build-and-publish.yml │ ├── deploy-mkdocs.yml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── .idea └── .idea.Reloaded.Memory.Buffers.dir │ └── .idea │ ├── .gitignore │ ├── discord.xml │ ├── encodings.xml │ ├── indexLayout.xml │ ├── projectSettingsUpdater.xml │ ├── vcs.xml │ └── workspace.xml ├── LICENSE ├── README.md ├── docs ├── index.md └── specification │ ├── allocation-algorithm.md │ ├── buffer-locator.md │ └── overview.md ├── mkdocs.yml ├── src-rust ├── .cargo │ └── config.toml ├── .gitignore ├── .idea │ ├── .gitignore │ ├── .idea.src-rust.dir │ │ └── .idea │ │ │ ├── .gitignore │ │ │ ├── discord.xml │ │ │ ├── encodings.xml │ │ │ ├── indexLayout.xml │ │ │ └── vcs.xml │ ├── modules.xml │ ├── src-rust.iml │ └── vcs.xml ├── .vscode │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── benches │ └── my_benchmark.rs ├── cbindgen_c.toml ├── cbindgen_cpp.toml └── src │ ├── buffers.rs │ ├── c │ ├── buffers_c_buffers.rs │ └── buffers_c_locatoritem.rs │ ├── internal │ ├── buffer_allocator.rs │ ├── buffer_allocator_linux.rs │ ├── buffer_allocator_mmap_rs.rs │ ├── buffer_allocator_osx.rs │ ├── buffer_allocator_windows.rs │ ├── locator_header_finder.rs │ ├── memory_mapped_file.rs │ ├── memory_mapped_file_unix.rs │ └── memory_mapped_file_windows.rs │ ├── lib.rs │ ├── structs │ ├── errors │ │ ├── buffer_allocation_error.rs │ │ ├── buffer_search_error.rs │ │ └── item_allocation_error.rs │ ├── internal │ │ ├── locator_header.rs │ │ └── locator_item.rs │ ├── params │ │ ├── buffer_allocator_settings.rs │ │ └── buffer_search_settings.rs │ ├── private_allocation.rs │ └── safe_locator_item.rs │ └── utilities │ ├── address_range.rs │ ├── cached.rs │ ├── disable_write_xor_execute.rs │ ├── icache_clear.rs │ ├── linux_map_parser.rs │ ├── map_parser_utilities.rs │ ├── mathematics.rs │ └── wrappers.rs └── src ├── .editorconfig ├── .idea └── .idea.Reloaded.Memory.Buffers │ └── .idea │ ├── .gitignore │ ├── .name │ ├── discord.xml │ ├── indexLayout.xml │ ├── projectSettingsUpdater.xml │ ├── vcs.xml │ └── workspace.xml ├── Reloaded.Memory.Buffers.Tests ├── Assets.cs ├── Assets │ ├── hello-world-win-arm64.exe │ ├── hello-world-win-x64.exe │ ├── hello-world-win-x86.exe │ └── hello.c ├── Attributes │ └── AutoLocatorHeader.cs ├── Reloaded.Memory.Buffers.Tests.csproj ├── Startup.cs ├── Tests │ ├── BufferAllocatorTests.Allocation.ExternalProcess.cs │ ├── BufferAllocatorTests.Allocation.cs │ ├── BufferAllocatorTests.Common.cs │ ├── BufferLocatorTests.cs │ ├── BufferTests.cs │ ├── Structs │ │ ├── LocatorHeaderTests.cs │ │ ├── LocatorItemTests.cs │ │ └── SafeLocatorItemTests.cs │ └── Utilities │ │ ├── AddressRangeTests.cs │ │ └── LinuxMapParserTests.cs ├── Utilities │ ├── PackingTestHelpers.cs │ └── Permutations.cs └── xunit.runner.json ├── Reloaded.Memory.Buffers.sln └── Reloaded.Memory.Buffers ├── Buffers.cs ├── Exceptions ├── MemoryBufferAllocationException.cs └── ThrowHelpers.cs ├── Internal ├── BufferAllocator.Linux.cs ├── BufferAllocator.OSX.cs ├── BufferAllocator.Windows.cs ├── BufferAllocator.cs ├── LocatorHeaderFinder.Posix.cs └── LocatorHeaderFinder.cs ├── Native ├── IMemoryMappedFile.cs ├── Linux │ └── MemoryMapEntry.cs ├── OSX │ └── Mach.cs ├── Posix.cs ├── UnixMemoryMappedFile.cs ├── Windows │ └── Kernel32.cs └── WindowsMemoryMappedFile.cs ├── PublicAPI ├── net48 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── net5.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── net6.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── net7.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── netcoreapp3.1 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── netstandard2.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt └── netstandard2.1 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── Reloaded.Memory.Buffers.csproj ├── Structs ├── Internal │ ├── LocatorHeader.cs │ └── LocatorItem.cs ├── Params │ ├── BufferAllocatorSettings.cs │ └── BufferSearchSettings.cs ├── PrivateAllocation.cs └── SafeLocatorItem.cs ├── Usings.cs └── Utilities ├── AddressRange.cs ├── Cached.cs ├── LinuxMapParser.cs ├── Mathematics.cs └── Polyfills.cs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Sewer56 4 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: C# Build, Test and Publish 2 | on: 3 | push: 4 | branches: [ master, refactor-reloaded3-compliance, support-android-and-bsd ] 5 | tags: 6 | - '*' 7 | pull_request: 8 | branches: [ master, refactor-reloaded3-compliance, support-android-and-bsd ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | os: 16 | - windows-latest 17 | - ubuntu-latest 18 | - macos-13 19 | targetFramework: 20 | - net7.0 21 | - net6.0 22 | - net5.0 23 | - netcoreapp3.1 24 | platform: 25 | - x64 26 | include: 27 | - os: windows-latest 28 | targetFramework: net48 29 | platform: x64 30 | - os: windows-latest 31 | targetFramework: net48 32 | platform: x86 33 | - os: windows-latest 34 | targetFramework: net7.0 35 | platform: x86 36 | - os: windows-latest 37 | targetFramework: net6.0 38 | platform: x86 39 | - os: windows-latest 40 | targetFramework: net5.0 41 | platform: x86 42 | - os: windows-latest 43 | targetFramework: netcoreapp3.1 44 | platform: x86 45 | 46 | runs-on: ${{ matrix.os }} 47 | steps: 48 | - name: Checkout Code 49 | uses: actions/checkout@v3 50 | with: 51 | fetch-depth: 0 52 | submodules: 'recursive' 53 | - name: Setup Reloaded Library SDKs & Components 54 | uses: Reloaded-Project/Reloaded.Project.Configurations/.github/actions/setup-sdks-components@main 55 | - name: Build Library 56 | run: dotnet build -f ${{ matrix.targetFramework }} ./src/Reloaded.Memory.Buffers.Tests/Reloaded.Memory.Buffers.Tests.csproj 57 | - name: Run Tests 58 | run: dotnet test -f ${{ matrix.targetFramework }} ./src/Reloaded.Memory.Buffers.Tests/Reloaded.Memory.Buffers.Tests.csproj --collect:"XPlat Code Coverage;" --results-directory "Coverage" 59 | - name: "Upload Coverage" 60 | uses: actions/upload-artifact@v3 61 | with: 62 | name: coverage-${{ matrix.os }}-${{ matrix.targetFramework }} 63 | path: Coverage/*/coverage.cobertura.xml 64 | upload: 65 | needs: build 66 | runs-on: ubuntu-latest 67 | steps: 68 | - name: "Checkout Code" 69 | uses: actions/checkout@v3 70 | with: 71 | fetch-depth: 0 72 | submodules: 'recursive' 73 | - name: "Setup Reloaded Library SDKs & Components" 74 | uses: Reloaded-Project/Reloaded.Project.Configurations/.github/actions/setup-sdks-components@main 75 | - name: Build Library 76 | run: dotnet build -c Release ./src 77 | - name: "Install ReportGenerator" 78 | run: dotnet tool install --global dotnet-reportgenerator-globaltool 79 | - name: "Download Coverage Artifacts" 80 | uses: actions/download-artifact@v3 81 | with: 82 | path: artifacts 83 | - name: "Merge Coverage Files" 84 | run: | 85 | dotnet tool install --global dotnet-coverage 86 | dotnet-coverage merge ./artifacts/*.cobertura.xml --recursive --output ./Cobertura.xml --output-format xml 87 | - name: "Upload Coverage & Packages" 88 | uses: Reloaded-Project/Reloaded.Project.Configurations/.github/actions/upload-coverage-packages@main 89 | with: 90 | code-coverage-path: './Cobertura.xml' 91 | changelog-path: './Changelog.md' 92 | nupkg-glob: '**.nupkg' 93 | snupkg-glob: '**.snupkg' 94 | nuget-key: ${{ secrets.NUGET_KEY }} 95 | changelog-template: 'keepachangelog' 96 | is-release: ${{ startsWith(github.ref, 'refs/tags/') }} 97 | release-tag: ${{ github.ref_name }} -------------------------------------------------------------------------------- /.github/workflows/deploy-mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push on the master branch 6 | push: 7 | branches: [ main, master, refactor-reloaded3-compliance ] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | build: 16 | # The type of runner that the job will run on 17 | runs-on: ubuntu-latest 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - name: Checkout Branch 24 | uses: actions/checkout@v2 25 | with: 26 | submodules: recursive 27 | 28 | # Deploy MkDocs 29 | - name: Deploy MkDocs 30 | # You may pin to the exact commit or the version. 31 | # uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb 32 | uses: mhausenblas/mkdocs-deploy-gh-pages@master 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | REQUIREMENTS: ./docs/requirements.txt -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust Build, Test & Publish 2 | 3 | on: 4 | push: 5 | branches: [ main, master, optimize-for-size ] 6 | tags: 7 | - '*' 8 | pull_request: 9 | branches: [ main, master, optimize-for-size ] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test-native: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | with: 22 | submodules: recursive 23 | 24 | - id: test-rust 25 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/test-and-coverage@v1 # upgrade if needed 26 | with: 27 | rust_project_path: src-rust 28 | upload_coverage: true 29 | rust_branch: nightly 30 | 31 | test-wine: 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | with: 37 | submodules: recursive 38 | 39 | - id: test-rust 40 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/test-in-wine@v1 # upgrade if needed 41 | with: 42 | rust_project_path: src-rust 43 | rust_branch: nightly 44 | 45 | build-c-libs-linux: 46 | runs-on: ${{ matrix.os }} 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | os: [ubuntu-latest] 51 | target: [x86_64-unknown-linux-gnu, i686-unknown-linux-gnu, aarch64-unknown-linux-gnu] 52 | features: ["c_exports", "c_exports,size_opt", "c_exports,size_opt,no_format", "c_exports,size_opt,no_format,all_private"] 53 | steps: 54 | - uses: actions/checkout@v3 55 | with: 56 | submodules: recursive 57 | - id: build-libs 58 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/build-c-library@v1 59 | with: 60 | rust_project_path: src-rust 61 | target: ${{ matrix.target }} 62 | features: ${{ matrix.features }} 63 | use_cross: true 64 | 65 | build-c-libs-macos: 66 | runs-on: ${{ matrix.os }} 67 | strategy: 68 | fail-fast: false 69 | matrix: 70 | os: [macos-latest] 71 | target: [x86_64-apple-darwin, aarch64-apple-darwin] 72 | features: ["c_exports", "c_exports,size_opt", "c_exports,size_opt,no_format", "c_exports,size_opt,no_format,all_private"] 73 | steps: 74 | - uses: actions/checkout@v3 75 | with: 76 | submodules: recursive 77 | - id: build-libs 78 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/build-c-library@v1 79 | with: 80 | rust_project_path: src-rust 81 | target: ${{ matrix.target }} 82 | features: ${{ matrix.features }} 83 | 84 | build-c-libs-windows: 85 | runs-on: ${{ matrix.os }} 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | os: [windows-latest] 90 | target: [x86_64-pc-windows-msvc, i686-pc-windows-msvc, aarch64-pc-windows-msvc] 91 | features: ["c_exports,external_processes", "c_exports,size_opt,external_processes", "c_exports,size_opt,no_format,external_processes", "c_exports,size_opt,no_format", "c_exports,size_opt,no_format,all_private"] 92 | steps: 93 | - uses: actions/checkout@v3 94 | with: 95 | submodules: recursive 96 | - id: build-libs 97 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/build-c-library@v1 98 | with: 99 | rust_project_path: src-rust 100 | target: ${{ matrix.target }} 101 | features: ${{ matrix.features }} 102 | 103 | build-c-headers: 104 | runs-on: ubuntu-latest 105 | steps: 106 | - uses: actions/checkout@v3 107 | with: 108 | submodules: recursive 109 | 110 | - name: Generate C++ bindings 111 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/generate-bindings@v1 112 | with: 113 | rust_project_path: src-rust 114 | config_file: cbindgen_cpp.toml 115 | header_file: bindings_cpp.hpp 116 | 117 | - name: Generate C bindings 118 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/generate-bindings@v1 119 | with: 120 | rust_project_path: src-rust 121 | config_file: cbindgen_c.toml 122 | header_file: bindings_c.h 123 | 124 | publish-artifacts: 125 | needs: ["build-c-headers", "build-c-libs-windows", "build-c-libs-linux", "build-c-libs-macos", "test-wine", "test-native"] 126 | # Publish only on tags 127 | if: startsWith(github.ref, 'refs/tags/') 128 | runs-on: ubuntu-latest 129 | steps: 130 | - uses: actions/checkout@v3 131 | with: 132 | submodules: recursive 133 | 134 | - name: ↙️ Download Artifacts 135 | uses: actions/download-artifact@v3 136 | with: 137 | path: artifacts 138 | 139 | - name: Compress Artifacts 140 | shell: bash 141 | run: | 142 | dir="artifacts" # Replace with your subdirectory 143 | for subdir in "$dir"/*; do 144 | if [ -d "$subdir" ]; then 145 | base=$(basename "$subdir") 146 | zip -r "$dir/$base.zip" "$subdir" 147 | rm -r "$subdir" 148 | fi 149 | done 150 | ls -A ./artifacts 151 | 152 | - name: GitHub Release Artifacts 153 | uses: softprops/action-gh-release@v1 154 | with: 155 | files: | 156 | artifacts/* 157 | 158 | - name: Publish to crates.io 159 | uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/publish-crate@v1 160 | with: 161 | rust_project_path: src-rust 162 | token: ${{ secrets.CRATES_IO_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Store app package directory 159 | AppPackages/ 160 | 161 | # Visual Studio cache files 162 | # files ending in .cache can be ignored 163 | *.[Cc]ache 164 | # but keep track of directories ending in .cache 165 | !*.[Cc]ache/ 166 | 167 | # Others 168 | ClientBin/ 169 | [Ss]tyle[Cc]op.* 170 | ~$* 171 | *~ 172 | *.dbmdl 173 | *.dbproj.schemaview 174 | *.pfx 175 | *.publishsettings 176 | node_modules/ 177 | orleans.codegen.cs 178 | 179 | # RIA/Silverlight projects 180 | Generated_Code/ 181 | 182 | # Backup & report files from converting an old project file 183 | # to a newer Visual Studio version. Backup files are not needed, 184 | # because we have git ;-) 185 | _UpgradeReport_Files/ 186 | Backup*/ 187 | UpgradeLog*.XML 188 | UpgradeLog*.htm 189 | 190 | # SQL Server files 191 | *.mdf 192 | *.ldf 193 | 194 | # Business Intelligence projects 195 | *.rdl.data 196 | *.bim.layout 197 | *.bim_*.settings 198 | 199 | # Microsoft Fakes 200 | FakesAssemblies/ 201 | 202 | # Node.js Tools for Visual Studio 203 | .ntvs_analysis.dat 204 | 205 | # Visual Studio 6 build log 206 | *.plg 207 | 208 | # Visual Studio 6 workspace options file 209 | *.opt 210 | 211 | # Visual Studio LightSwitch build output 212 | **/*.HTMLClient/GeneratedArtifacts 213 | **/*.DesktopClient/GeneratedArtifacts 214 | **/*.DesktopClient/ModelManifest.xml 215 | **/*.Server/GeneratedArtifacts 216 | **/*.Server/ModelManifest.xml 217 | _Pvt_Extensions 218 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/Reloaded.Project.Configurations"] 2 | path = src/Reloaded.Project.Configurations 3 | url = https://github.com/Reloaded-Project/Reloaded.Project.Configurations.git 4 | [submodule "docs/Reloaded"] 5 | path = docs/Reloaded 6 | url = https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2.git -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/.gitignore -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/projectSettingsUpdater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.Reloaded.Memory.Buffers.dir/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 21 | 22 | 24 | 25 | 27 | 28 | 29 | 32 | 50 | 51 | 52 | 53 | 54 | 1686204371521 55 | 60 | 61 | 62 | 63 | 65 | 66 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

The Reloaded Buffers Library

3 | 4 |

5 | Allocate Memory, & Knuckles 6 |

7 | 8 | 9 | Coverage 10 | 11 | 12 | 13 | NuGet 14 | 15 | 16 | 17 | Build Status 18 | 19 |
20 | 21 | 22 | NuGet 23 | 24 | 25 | 26 | Build Status 27 | 28 |
29 | 30 | ## About 31 | 32 | `Reloaded.Memory.Buffers` is a library for allocating memory between a given minimum and maximum memory address, for C# and Rust. 33 | 34 | With the following properties: 35 | 36 | - ***Memory Efficient***: No wasted memory. 37 | - ***Shared***: Can be found and read/written to by multiple users. 38 | - ***Static***: Allocated data never moves, or is overwritten. 39 | - ***Permanent***: Allocated data lasts the lifetime of the process. 40 | - ***Concurrent***: Multiple users can access at the same time. 41 | - ***Large Address Aware:*** On Windows, the library can correctly leverage all 4GB in 32-bit processes. 42 | - ***Cross Platform***: Supports Windows, OSX and Linux. 43 | 44 | Note: Rust/C port also work with FreeBSD (untested), and has partial [(limited) Android support](https://github.com/Reloaded-Project/Reloaded.Memory.Buffers/issues/3). 45 | 46 | ## Wiki & Documentation 47 | 48 | [For full documentation, please see the Wiki](https://reloaded-project.github.io/Reloaded.Memory.Buffers/). 49 | 50 | ## Example Use Cases 51 | 52 | These are just examples: 53 | 54 | - ***Hooks***: Hooking libraries like [Reloaded.Hooks](https://github.com/Reloaded-Project/Reloaded.Hooks) can reduce amount of bytes stolen from functions. 55 | - ***Libraries***: Libraries like [Reloaded.Assembler](https://github.com/Reloaded-Project/Reloaded.Assembler) require memory be allocated in first 2GB for x64 FASM. 56 | 57 | ## Usage 58 | 59 | !!! info "The library provides a simple high level API to use." 60 | 61 | !!! info "See [Wiki](https://reloaded-project.github.io/Reloaded.Memory.Buffers/) for Rust usage" 62 | 63 | ### Get A Buffer 64 | 65 | Gets a buffer where you can allocate 4096 bytes in first 2GiB of address space. 66 | 67 | ```csharp 68 | var settings = new BufferSearchSettings() 69 | { 70 | MinAddress = 0, 71 | MaxAddress = int.MaxValue, 72 | Size = 4096 73 | }; 74 | 75 | // Make sure to dispose, so lock gets released. 76 | using var item = Buffers.GetBuffer(settings); 77 | 78 | // Write some data, get pointer back. 79 | var ptr = item->Append(data); 80 | ``` 81 | 82 | ### Get A Buffer (With Proximity) 83 | 84 | Gets a buffer where 4096 bytes written will be within 2GiB of 0x140000000. 85 | 86 | ```csharp 87 | var settings = BufferSearchSettings.FromProximity(int.MaxValue, (nuint)0x140000000, 4096); 88 | 89 | // Make sure to dispose, so lock gets released. 90 | using var item = Buffers.GetBuffer(settings); 91 | 92 | // Write some data, get pointer back. 93 | var ptr = item->Append(data); 94 | ``` 95 | 96 | ### Allocate Memory 97 | 98 | Allows you to temporarily allocate memory within a specific address range and size constraints. 99 | 100 | ```csharp 101 | // Arrange 102 | var settings = new BufferAllocatorSettings() 103 | { 104 | MinAddress = 0, 105 | MaxAddress = int.MaxValue, 106 | Size = 4096 107 | }; 108 | 109 | using var item = Buffers.AllocatePrivateMemory(settings); 110 | 111 | // You have allocated memory in first 2GiB of address space. 112 | // Disposing this memory (via `using` statement) will free it. 113 | item.BaseAddress.Should().NotBeNull(); 114 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 115 | ``` 116 | 117 | You can specify another process with `TargetProcess = someProcess` in `BufferAllocatorSettings`, but this is only supported on Windows. 118 | 119 | ## Crate Features (Rust) 120 | 121 | - `std`: [Enabled by Default] Enables use of standard library. 122 | - `external_processes`: Support external processes (windows only). 123 | - `no_format`: Disables formatting code in errors, saving ~8kB of space. 124 | - `size_opt`: Makes cold paths optimized for size instead of optimized for speed. [Requires 'nightly' Rust] 125 | - `c_exports` Provides C exports for the library. 126 | 127 | ## Community Feedback 128 | 129 | If you have questions/bug reports/etc. feel free to [Open an Issue](https://github.com/Reloaded-Project/Reloaded.Memory.Buffers/issues/new). 130 | 131 | Contributions are welcome and encouraged. Feel free to implement new features, make bug fixes or suggestions so long as 132 | they meet the quality standards set by the existing code in the repository. 133 | 134 | For an idea as to how things are set up, [see Reloaded Project Configurations.](https://github.com/Reloaded-Project/Reloaded.Project.Configurations) 135 | 136 | Happy Hacking 💜 -------------------------------------------------------------------------------- /docs/specification/overview.md: -------------------------------------------------------------------------------- 1 | # Buffers Specification 2 | 3 | !!! note "Version 1.0.0 (Library Version 3.0.0+)" 4 | 5 | !!! info "`Reloaded.Memory.Buffers` is a library for allocating memory between a given minimum and maximum memory address" 6 | 7 | With the following properties: 8 | 9 | - ***Memory Efficient***: No wasted memory. 10 | - ***Shared***: Can be found and read/written to by multiple users. 11 | - ***Static***: Allocated data never moves, or is overwritten. 12 | - ***Permanent***: Allocated data lasts the lifetime of the process. 13 | - ***Concurrent***: Multiple users can access at the same time. 14 | - ***Large Address Aware:*** On Windows, the library can correctly leverage all 4GB in 32-bit processes. 15 | 16 | ## Use Cases 17 | 18 | !!! tip "These are just examples." 19 | 20 | - ***Hooks***: Hooking libraries like [Reloaded.Hooks](https://github.com/Reloaded-Project/Reloaded.Hooks) can reduce amount of bytes stolen from functions. 21 | - ***Libraries***: Libraries like [Reloaded.Assembler](https://github.com/Reloaded-Project/Reloaded.Assembler) require memory be allocated in first 2GB. 22 | 23 | And some other useful functionality. 24 | 25 | ## Field Sizes 26 | 27 | Field sizes used used in this spec are based on Rust notation; with some custom types e.g. 28 | 29 | - `u8`: Unsigned 8 bits. 30 | - `i8`: Signed 8 bits. 31 | - `u4`: 4 bits. 32 | - `u32/u64`: 4 Bytes or 8 Bytes (depending on variant). 33 | 34 | Assume any bit packed values are sequential, i.e. if `u4` then `u4` is specified, first `u4` is the upper 4 bits. 35 | 36 | All packed fields are `little-endian`; and written out when total number of bits aligns with a power of 2. 37 | 38 | - `u6` + `u12` is 2 bytes `little-endian` 39 | - `u15` + `u17` is 4 bytes `little-endian` 40 | - `u26` + `u22` + `u16` is 8 bytes `little-endian` 41 | - `u6` + `u11` + `u17` ***is 4 bytes*** `little-endian`, ***not 2+2*** 42 | 43 | ## General Access Pattern 44 | 45 | !!! note "Names below are not final API, only for illustration purposes." 46 | 47 | ```mermaid 48 | flowchart TB 49 | User["User"] --> GetOrAllocateBuffer(["Buffers.GetBuffer"]) 50 | GetOrAllocateBuffer --> BufferLocatorFind(["LocatorHeaderFinder.Find"]) 51 | BufferLocatorFind -- "(Via Memory Mapped Files)" --> GetAvailableItem(["LocatorHeader.GetFirstAvailableItem(locator)"]) 52 | GetAvailableItem --> BufferMatch{Buffer Match Found?} 53 | BufferMatch -- Yes --> ReturnBufferToUser["Lock & Return Buffer to User"] 54 | BufferMatch -- "No (Make New Buffer)" --> CanRegisterBuffer{Locator Has Space for New Entry?} 55 | CanRegisterBuffer -- "No (Alloc New Locator, Link via Pointer & Try Again)" --> GetAvailableItem 56 | CanRegisterBuffer -- "Yes (Allocate Memory and Register)" --> BufferAllocator(["BufferAllocator.Allocate"]) 57 | BufferAllocator -- "Lock & Register Returned Buffer" --> BufferLocatorRegister(["LocatorHeader.Register"]) 58 | BufferLocatorRegister --> ReturnNewBufferToUser["Return New Buffer to User"] 59 | ``` 60 | 61 | In the flowchart above, the user calls `GetBuffer`, which in turn calls `LocatorHeaderFinder.Find` to get address of the 62 | [locator structure](buffer-locator.md#structure). 63 | 64 | If a match is found (Yes path), the buffer is locked and then returned to the user. 65 | 66 | If no match is found (No path), a new buffer is allocated, and locked. If this buffer can fit into the current 67 | locator, it is appended. Otherwise a new locator is allocated (linked via pointer), and the buffer is registered into 68 | the new locator. Buffer is then returned to the user. -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Reloaded Memory Buffers 2 | site_url: https://github.com/Reloaded-Project/Reloaded.Memory.Buffers 3 | 4 | repo_name: Reloaded-Project/Reloaded.Memory.Buffers 5 | repo_url: https://github.com/Reloaded-Project/Reloaded.Memory.Buffers 6 | 7 | extra: 8 | social: 9 | - icon: fontawesome/brands/github 10 | link: https://github.com/Reloaded-Project 11 | - icon: fontawesome/brands/twitter 12 | link: https://twitter.com/thesewer56?lang=en-GB 13 | 14 | extra_css: 15 | - Reloaded/Stylesheets/extra.css 16 | 17 | markdown_extensions: 18 | - admonition 19 | - tables 20 | - pymdownx.details 21 | - pymdownx.highlight 22 | - pymdownx.superfences: 23 | custom_fences: 24 | - name: mermaid 25 | class: mermaid 26 | format: !!python/name:pymdownx.superfences.fence_code_format 27 | - pymdownx.tasklist 28 | - def_list 29 | - meta 30 | - md_in_html 31 | - attr_list 32 | - footnotes 33 | - pymdownx.tabbed: 34 | alternate_style: true 35 | - pymdownx.emoji: 36 | emoji_index: !!python/name:materialx.emoji.twemoji 37 | emoji_generator: !!python/name:materialx.emoji.to_svg 38 | 39 | theme: 40 | name: material 41 | palette: 42 | scheme: reloaded3-slate 43 | features: 44 | - navigation.instant 45 | 46 | plugins: 47 | - search 48 | 49 | nav: 50 | - Home: index.md 51 | - Specification: 52 | - Overview: specification/overview.md 53 | - Buffer Locator: specification/buffer-locator.md 54 | - Allocation Algorithm: specification/allocation-algorithm.md 55 | - Contributing: Reloaded/Pages/contributing.md -------------------------------------------------------------------------------- /src-rust/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Note: This breaks IntelliJ Rust. Remove this line temporarily if working from that IDE. 3 | # target = ['x86_64-unknown-linux-gnu','x86_64-apple-darwin','x86_64-pc-windows-gnu'] 4 | 5 | # This is needed for Memory Mapped File based Tests. 6 | [env] 7 | RUST_TEST_THREADS = "1" -------------------------------------------------------------------------------- /src-rust/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb -------------------------------------------------------------------------------- /src-rust/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /src-rust/.idea/.idea.src-rust.dir/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /contentModel.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.src-rust.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /src-rust/.idea/.idea.src-rust.dir/.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src-rust/.idea/.idea.src-rust.dir/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src-rust/.idea/.idea.src-rust.dir/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src-rust/.idea/.idea.src-rust.dir/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src-rust/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src-rust/.idea/src-rust.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src-rust/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src-rust/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'reloaded-memory-buffers'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=reloaded-memory-buffers" 17 | ], 18 | "filter": { 19 | "name": "reloaded-memory-buffers", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug benchmark 'my_benchmark'", 30 | "cargo": { 31 | "args": [ 32 | "test", 33 | "--no-run", 34 | "--bench=my_benchmark", 35 | "--package=reloaded-memory-buffers" 36 | ], 37 | "filter": { 38 | "name": "my_benchmark", 39 | "kind": "bench" 40 | } 41 | }, 42 | "args": [], 43 | "cwd": "${workspaceFolder}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /src-rust/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.command": "clippy", 3 | "[rust]": { 4 | "editor.formatOnSave": true 5 | }, 6 | "coverage-gutters.coverageFileNames": [ 7 | "cobertura.xml", 8 | "lcov.info", 9 | "cov.xml", 10 | "coverage.xml", 11 | "jacoco.xml", 12 | "coverage.cobertura.xml" 13 | ], 14 | "rust-analyzer.cargo.features": "all", 15 | } 16 | -------------------------------------------------------------------------------- /src-rust/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Reloaded: Cargo Watch Tarpaulin (Auto Coverage on Save)", 6 | "type": "shell", 7 | "command": "cargo watch -x \"tarpaulin --skip-clean --out Xml --out Html --engine llvm --target-dir target/coverage-build\" -w src/", 8 | "group": "test", 9 | "presentation": { 10 | "reveal": "always" 11 | }, 12 | "problemMatcher": [] 13 | }, 14 | { 15 | "label": "Reloaded: Generate Code Coverage", 16 | "type": "shell", 17 | "command": "cargo tarpaulin --out Xml --out Html --engine llvm --target-dir target/coverage-build", 18 | "group": "test", 19 | "presentation": { 20 | "reveal": "always" 21 | }, 22 | "problemMatcher": [] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reloaded-memory-buffers" 3 | version = "4.1.0" 4 | edition = "2021" 5 | authors = [ "sewer56" ] 6 | description = "Shared, Concurrent, Permanent Memory Allocator tied to Process Lifetime" 7 | documentation = "https://reloaded-project.github.io/Reloaded.Memory.Buffers/" 8 | readme = "../README.md" 9 | repository = "https://github.com/Reloaded-Project/Reloaded.Memory.Buffers" 10 | license = "GPL-3.0" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | [features] 17 | default = ["std"] 18 | std = [] # Better thread yield 19 | external_processes = [] # Support for external processes in Windows 20 | c_exports = [] 21 | no_format = [] # Removes string formatting (less detailed errors) for binary size. 22 | all_private = [] # No memory mapped files, memory is not shared. 23 | size_opt = ["nightly"] 24 | nightly = [] # Optimizations for nightly builds. 25 | 26 | [dependencies] 27 | concat-string = "1.0.1" 28 | memoffset = "0.9.0" 29 | errno = "0.3.3" 30 | spin = "0.9.8" 31 | itoa = "1.0.10" 32 | nanokit = { version = "0.1.0", features = ["no-inline-concat"] } 33 | 34 | [dev-dependencies] 35 | rstest = "0.18.1" 36 | criterion = "0.5.1" 37 | 38 | [target.'cfg(unix)'.dependencies] 39 | libc = "0.2.146" 40 | 41 | [target.'cfg(all(unix, not(target_os = "android")))'.dev-dependencies] 42 | pprof = { version = "0.13", features = ["flamegraph", "criterion"] } 43 | ahash = "0.8.9" 44 | 45 | [target.'cfg(target_os = "macos")'.dependencies] 46 | mach = "0.3.2" 47 | 48 | [target.'cfg(not(target_os = "windows"))'.dependencies] 49 | mmap-rs-with-map-from-existing = "0.6.0" 50 | clf = "0.1.7" 51 | 52 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys] 53 | version = "0.52.0" 54 | features = [ 55 | "Win32_System_Memory", 56 | "Win32_Foundation", 57 | "Win32_System_Diagnostics_Debug", 58 | "Win32_System_SystemInformation", 59 | "Win32_System_Threading", 60 | "Win32_Security" 61 | ] 62 | 63 | # Benchmarks 64 | [[bench]] 65 | name = "my_benchmark" 66 | harness = false 67 | 68 | # Profile Build 69 | [profile.profile] 70 | inherits = "release" 71 | debug = true 72 | codegen-units = 1 73 | lto = true 74 | strip = false # No stripping!! 75 | 76 | # Optimized Release Build 77 | [profile.release] 78 | codegen-units = 1 79 | lto = "fat" 80 | strip = true # Automatically strip symbols from the binary. 81 | panic = "abort" -------------------------------------------------------------------------------- /src-rust/benches/my_benchmark.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(target_os = "android"))] 2 | 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | 5 | #[cfg(not(target_os = "windows"))] 6 | use pprof::criterion::{Output, PProfProfiler}; 7 | use reloaded_memory_buffers::{buffers::Buffers, structs::params::BufferSearchSettings}; 8 | 9 | fn get_buffer() { 10 | let settings = BufferSearchSettings { 11 | min_address: 0_usize, 12 | max_address: i32::MAX as usize, 13 | size: 4096, 14 | }; 15 | 16 | // Automatically dropped. 17 | let _item = Buffers::get_buffer(&settings).unwrap(); 18 | } 19 | 20 | fn criterion_benchmark(c: &mut Criterion) { 21 | c.bench_function("get_buffer", |b| b.iter(get_buffer)); 22 | } 23 | 24 | #[cfg(not(target_os = "windows"))] 25 | criterion_group! { 26 | name = benches; 27 | config = Criterion::default().with_profiler(PProfProfiler::new(1000, Output::Flamegraph(None))); 28 | targets = criterion_benchmark 29 | } 30 | 31 | #[cfg(target_os = "windows")] 32 | criterion_group! { 33 | name = benches; 34 | config = Criterion::default(); 35 | targets = criterion_benchmark 36 | } 37 | 38 | criterion_main!(benches); 39 | -------------------------------------------------------------------------------- /src-rust/src/c/buffers_c_locatoritem.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::internal::LocatorItem; 2 | use core::slice; 3 | 4 | /// Returns the amount of bytes left in the buffer. 5 | #[no_mangle] 6 | pub extern "C" fn locatoritem_bytes_left(item: *const LocatorItem) -> u32 { 7 | unsafe { (*item).bytes_left() } 8 | } 9 | 10 | /// Returns the minimum address of this locator item. 11 | #[no_mangle] 12 | pub extern "C" fn locatoritem_min_address(item: *const LocatorItem) -> usize { 13 | unsafe { (*item).min_address() } 14 | } 15 | 16 | /// Returns the minimum address of this locator item. 17 | #[no_mangle] 18 | pub extern "C" fn locatoritem_max_address(item: *const LocatorItem) -> usize { 19 | unsafe { (*item).max_address() } 20 | } 21 | 22 | /// Returns true if the buffer is allocated, false otherwise. 23 | #[no_mangle] 24 | pub extern "C" fn locatoritem_is_allocated(item: *const LocatorItem) -> bool { 25 | unsafe { (*item).is_allocated() } 26 | } 27 | 28 | /// Returns true if the current item is locked, else false. 29 | #[no_mangle] 30 | pub extern "C" fn locatoritem_is_taken(item: *const LocatorItem) -> bool { 31 | unsafe { (*item).is_taken() } 32 | } 33 | 34 | /// Tries to acquire the lock. 35 | /// 36 | /// Returns true if the lock was successfully acquired, false otherwise. 37 | #[no_mangle] 38 | pub extern "C" fn locatoritem_try_lock(item: *mut LocatorItem) -> bool { 39 | unsafe { (*item).try_lock() } 40 | } 41 | 42 | /// Acquires the lock, blocking until it can do so. 43 | #[no_mangle] 44 | pub extern "C" fn locatoritem_lock(item: *mut LocatorItem) { 45 | unsafe { (*item).lock() } 46 | } 47 | 48 | /// Unlocks the object in a thread-safe manner. 49 | #[no_mangle] 50 | pub extern "C" fn locatoritem_unlock(item: *mut LocatorItem) { 51 | unsafe { (*item).unlock() } 52 | } 53 | 54 | /// Determines if this locator item can be used given the constraints. 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `size` - Available bytes between `min_address` and `max_address`. 59 | /// * `min_address` - Minimum address accepted. 60 | /// * `max_address` - Maximum address accepted. 61 | /// 62 | /// # Returns 63 | /// 64 | /// Returns `true` if this buffer can be used given the parameters, and `false` otherwise. 65 | #[no_mangle] 66 | pub extern "C" fn locatoritem_can_use( 67 | item: *const LocatorItem, 68 | size: u32, 69 | min_address: usize, 70 | max_address: usize, 71 | ) -> bool { 72 | unsafe { (*item).can_use(size, min_address, max_address) } 73 | } 74 | 75 | /// Appends the data to this buffer. 76 | /// 77 | /// # Arguments 78 | /// 79 | /// * `data` - The data to append to the item. 80 | /// 81 | /// # Returns 82 | /// 83 | /// The address of the written data. 84 | /// 85 | /// # Remarks 86 | /// 87 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer. 88 | /// When returning buffers from the library, the library will ensure there's at least the requested amount of space; 89 | /// so if the total size of your data falls under that space, you are good. 90 | /// 91 | /// # Safety 92 | /// 93 | /// This function is safe provided that the caller ensures that the buffer is large enough to hold the data. 94 | /// There is no error thrown if size is insufficient. 95 | #[no_mangle] 96 | pub unsafe extern "C" fn locatoritem_append_bytes( 97 | item: *mut LocatorItem, 98 | data: *const u8, 99 | data_len: usize, 100 | ) -> usize { 101 | (*item).append_bytes(slice::from_raw_parts(data, data_len)) 102 | } 103 | -------------------------------------------------------------------------------- /src-rust/src/internal/buffer_allocator_linux.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::errors::BufferAllocationError; 2 | use crate::structs::internal::LocatorItem; 3 | use crate::structs::params::BufferAllocatorSettings; 4 | use crate::utilities::cached::get_sys_info; 5 | use crate::utilities::linux_map_parser::get_free_regions_from_process_id; 6 | use crate::{ 7 | internal::buffer_allocator::get_possible_buffer_addresses, 8 | utilities::map_parser_utilities::MemoryMapEntry, 9 | }; 10 | use libc::{ 11 | mmap, munmap, MAP_ANONYMOUS, MAP_FIXED_NOREPLACE, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE, 12 | }; 13 | 14 | // Implementation // 15 | pub fn allocate_linux( 16 | settings: &BufferAllocatorSettings, 17 | ) -> Result { 18 | for _ in 0..settings.retry_count { 19 | let regions = get_free_regions_from_process_id(settings.target_process_id as i32); 20 | for region in regions { 21 | if region.start_address > settings.max_address { 22 | break; 23 | } 24 | 25 | unsafe { 26 | match try_allocate_buffer(®ion, settings) { 27 | Ok(item) => return Ok(item), 28 | Err(_) => continue, 29 | } 30 | } 31 | } 32 | } 33 | 34 | Err(BufferAllocationError::new( 35 | *settings, 36 | "Failed to allocate buffer on Linux", 37 | )) 38 | } 39 | 40 | unsafe fn try_allocate_buffer( 41 | entry: &MemoryMapEntry, 42 | settings: &BufferAllocatorSettings, 43 | ) -> Result { 44 | let buffer: &mut [usize; 4] = &mut [0; 4]; 45 | 46 | for addr in get_possible_buffer_addresses( 47 | settings.min_address, 48 | settings.max_address, 49 | entry.start_address, 50 | entry.end_address, 51 | settings.size as usize, 52 | get_sys_info().allocation_granularity as usize, 53 | buffer, 54 | ) { 55 | let allocated = mmap( 56 | *addr as *mut _, 57 | settings.size as usize, 58 | PROT_READ | PROT_WRITE | PROT_EXEC, 59 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, 60 | -1, 61 | 0, 62 | ); 63 | 64 | if allocated.is_null() { 65 | continue; 66 | } 67 | 68 | if allocated as usize != *addr { 69 | munmap(allocated, settings.size as usize); 70 | continue; 71 | } 72 | 73 | return Ok(LocatorItem::new(allocated as usize, settings.size)); 74 | } 75 | 76 | Err("Failed to allocate buffer") 77 | } 78 | -------------------------------------------------------------------------------- /src-rust/src/internal/buffer_allocator_mmap_rs.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::errors::BufferAllocationError; 2 | use crate::structs::internal::LocatorItem; 3 | use crate::structs::params::BufferAllocatorSettings; 4 | use crate::utilities::cached::get_sys_info; 5 | use crate::utilities::map_parser_utilities::get_free_regions; 6 | use crate::{ 7 | internal::buffer_allocator::get_possible_buffer_addresses, 8 | utilities::map_parser_utilities::MemoryMapEntry, 9 | }; 10 | use core::cmp::min; 11 | use core::mem; 12 | use mmap_rs_with_map_from_existing::{MemoryAreas, MmapOptions, UnsafeMmapFlags}; 13 | 14 | // Implementation // 15 | pub fn allocate_mmap_rs( 16 | settings: &BufferAllocatorSettings, 17 | ) -> Result { 18 | for _ in 0..settings.retry_count { 19 | let maps = MemoryAreas::open(None).map_err(|_x| BufferAllocationError { 20 | settings: *settings, 21 | text: "Failed to Query Memory Pages via mmap-rs. Probably unsupported or lacking permissions.", 22 | })?; 23 | 24 | let mapped_regions: Vec = maps 25 | .filter(|x| x.is_ok()) 26 | .map( 27 | |x: Result< 28 | mmap_rs_with_map_from_existing::MemoryArea, 29 | mmap_rs_with_map_from_existing::Error, 30 | >| unsafe { 31 | let area = x.unwrap_unchecked(); 32 | MemoryMapEntry::new(area.start(), area.end()) 33 | }, 34 | ) 35 | .collect(); 36 | 37 | let free_regions = get_free_regions(&mapped_regions); 38 | 39 | for region in free_regions { 40 | if region.start_address > settings.max_address { 41 | break; 42 | } 43 | 44 | unsafe { 45 | match try_allocate_buffer(®ion, settings) { 46 | Ok(item) => return Ok(item), 47 | Err(_) => continue, 48 | } 49 | } 50 | } 51 | } 52 | 53 | Err(BufferAllocationError::new( 54 | *settings, 55 | "Failed to allocate buffer (mmap_rs)", 56 | )) 57 | } 58 | 59 | unsafe fn try_allocate_buffer( 60 | entry: &MemoryMapEntry, 61 | settings: &BufferAllocatorSettings, 62 | ) -> Result { 63 | let buffer: &mut [usize; 4] = &mut [0; 4]; 64 | 65 | for addr in get_possible_buffer_addresses( 66 | settings.min_address, 67 | settings.max_address, 68 | entry.start_address, 69 | entry.end_address, 70 | settings.size as usize, 71 | get_sys_info().get_allocation_granularity() as usize, 72 | buffer, 73 | ) { 74 | let mmapoptions = MmapOptions::new(settings.size as usize) 75 | .map_err(|_x| "Failed to create mmap options")? 76 | .with_address(*addr) 77 | .with_unsafe_flags(UnsafeMmapFlags::MAP_FIXED) 78 | .with_unsafe_flags(UnsafeMmapFlags::JIT); 79 | 80 | let map: Result< 81 | mmap_rs_with_map_from_existing::MmapMut, 82 | mmap_rs_with_map_from_existing::Error, 83 | > = unsafe { mmapoptions.map_exec_mut() }; 84 | if map.is_err() { 85 | continue; 86 | } 87 | 88 | let mapped = map.unwrap(); 89 | let mapped_addr = mapped.start(); 90 | 91 | if mapped.start() != *addr { 92 | continue; // dropped 93 | } 94 | 95 | mem::forget(mapped); 96 | return Ok(LocatorItem::new(mapped_addr, settings.size)); 97 | } 98 | 99 | Err("Failed to allocate buffer") 100 | } 101 | -------------------------------------------------------------------------------- /src-rust/src/internal/buffer_allocator_osx.rs: -------------------------------------------------------------------------------- 1 | use crate::internal::buffer_allocator::get_possible_buffer_addresses; 2 | use crate::structs::errors::BufferAllocationError; 3 | use crate::structs::internal::LocatorItem; 4 | use crate::structs::params::BufferAllocatorSettings; 5 | use crate::utilities::cached::get_sys_info; 6 | use crate::utilities::wrappers::Unaligned; 7 | use core::cmp::min; 8 | use core::mem; 9 | use libc::{mach_msg_type_number_t, mach_port_t, mach_task_self, mach_vm_size_t}; 10 | use mach::vm::{mach_vm_allocate, mach_vm_deallocate, mach_vm_protect, mach_vm_region}; 11 | use mach::vm_prot::*; 12 | use mach::vm_region; 13 | use mach::vm_region::vm_region_basic_info_data_64_t; 14 | use mach::vm_types::mach_vm_address_t; 15 | 16 | // Implementation // 17 | pub fn allocate_osx( 18 | settings: &BufferAllocatorSettings, 19 | ) -> Result { 20 | let max_address = min(get_sys_info().max_address, settings.max_address); 21 | let min_address = settings.min_address; 22 | 23 | unsafe { 24 | let self_task = mach_task_self(); 25 | for _ in 0..settings.retry_count { 26 | let mut count = 27 | mem::size_of::() as mach_msg_type_number_t; 28 | let mut object_name: mach_port_t = 0; 29 | 30 | let max_address = max_address as u64; 31 | let mut current_address = min_address as u64; 32 | 33 | while current_address <= max_address { 34 | let mut actual_address = current_address; 35 | let mut available_size: u64 = 0; 36 | let region_info = vm_region_basic_info_data_64_t::default(); 37 | let kr = mach_vm_region( 38 | self_task, 39 | &mut actual_address, 40 | &mut available_size, 41 | vm_region::VM_REGION_BASIC_INFO_64, 42 | ®ion_info as *const mach::vm_region::vm_region_basic_info_64 as *mut i32, 43 | &mut count, 44 | &mut object_name, 45 | ); 46 | 47 | if kr == 1 { 48 | let padding = max_address as usize - current_address as usize; 49 | if padding > 0 { 50 | let mut result_addr: usize = 0; 51 | if try_allocate_buffer( 52 | current_address as usize, 53 | padding, 54 | settings, 55 | self_task, 56 | &mut result_addr, 57 | ) { 58 | return Ok(LocatorItem { 59 | base_address: Unaligned::new(result_addr), 60 | size: settings.size, 61 | position: 0, 62 | is_taken: Default::default(), 63 | }); 64 | } 65 | } 66 | break; 67 | } 68 | 69 | if kr != 0 { 70 | break; 71 | } 72 | 73 | if actual_address > current_address { 74 | let free_bytes = actual_address - current_address; 75 | let mut result_addr: usize = 0; 76 | if try_allocate_buffer( 77 | current_address as usize, 78 | free_bytes as usize, 79 | settings, 80 | self_task, 81 | &mut result_addr, 82 | ) { 83 | return Ok(LocatorItem { 84 | base_address: Unaligned::new(result_addr), 85 | size: settings.size, 86 | position: 0, 87 | is_taken: Default::default(), 88 | }); 89 | } 90 | } 91 | 92 | current_address = actual_address + available_size; 93 | } 94 | } 95 | 96 | Err(BufferAllocationError::new( 97 | *settings, 98 | "Failed to allocate buffer on OSX", 99 | )) 100 | } 101 | } 102 | 103 | fn try_allocate_buffer( 104 | page_address: usize, 105 | page_size: usize, 106 | settings: &BufferAllocatorSettings, 107 | self_task: mach_port_t, 108 | result_addr: &mut usize, 109 | ) -> bool { 110 | let mut results: [usize; 4] = [0; 4]; 111 | let buffer_pointers = get_buffer_pointers_in_page_range( 112 | page_address, 113 | page_size, 114 | settings.size as usize, 115 | settings.min_address, 116 | settings.max_address, 117 | &mut results, 118 | ); 119 | 120 | for addr in buffer_pointers { 121 | let mut allocated: mach_vm_address_t = *addr as mach_vm_address_t; 122 | let mut kr = unsafe { 123 | mach_vm_allocate( 124 | self_task, 125 | &mut allocated, 126 | settings.size as mach_vm_size_t, 127 | 0, 128 | ) 129 | }; 130 | 131 | if kr != 0 { 132 | continue; 133 | } 134 | 135 | // TODO: M1 W^X 136 | // M1 macOS has strict W^X enforcement where pages are not allowed to be writeable 137 | // and executable at the same time. Therefore, we have to work around this by allocating as RW 138 | // and temporarily changing it on every write. 139 | 140 | // This is not safe, but later we'll get a better workaround going. 141 | #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] 142 | const PROT: vm_prot_t = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE; 143 | 144 | #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 145 | const PROT: vm_prot_t = VM_PROT_READ | VM_PROT_WRITE; 146 | 147 | kr = unsafe { 148 | mach_vm_protect( 149 | self_task, 150 | allocated, 151 | settings.size as mach_vm_size_t, 152 | 0, 153 | PROT, 154 | ) 155 | }; 156 | 157 | if kr != 0 { 158 | unsafe { 159 | mach_vm_deallocate(self_task, allocated, settings.size as mach_vm_size_t); 160 | } 161 | 162 | continue; 163 | } 164 | 165 | *result_addr = allocated as usize; 166 | return true; 167 | } 168 | 169 | false 170 | } 171 | 172 | fn get_buffer_pointers_in_page_range( 173 | page_address: usize, 174 | page_size: usize, 175 | buffer_size: usize, 176 | minimum_ptr: usize, 177 | maximum_ptr: usize, 178 | results: &mut [usize; 4], 179 | ) -> &[usize] { 180 | let page_start = page_address; 181 | let page_end = page_address + page_size; 182 | let allocation_granularity = get_sys_info().allocation_granularity; 183 | unsafe { 184 | get_possible_buffer_addresses( 185 | minimum_ptr, 186 | maximum_ptr, 187 | page_start, 188 | page_end, 189 | buffer_size, 190 | allocation_granularity as usize, 191 | results, 192 | ) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src-rust/src/internal/memory_mapped_file.rs: -------------------------------------------------------------------------------- 1 | pub trait MemoryMappedFile { 2 | fn already_existed(&self) -> bool; 3 | unsafe fn data(&self) -> *mut u8; 4 | fn length(&self) -> usize; 5 | } 6 | -------------------------------------------------------------------------------- /src-rust/src/internal/memory_mapped_file_windows.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use core::ffi::c_void; 4 | 5 | use crate::internal::memory_mapped_file::MemoryMappedFile; 6 | use alloc::ffi::CString; 7 | use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; 8 | use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; 9 | use windows_sys::Win32::System::Memory::{ 10 | CreateFileMappingA, MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, 11 | MEMORY_MAPPED_VIEW_ADDRESS, PAGE_EXECUTE_READWRITE, 12 | }; 13 | 14 | pub struct WindowsMemoryMappedFile { 15 | already_existed: bool, 16 | data: *mut u8, 17 | length: usize, 18 | map_handle: HANDLE, 19 | } 20 | 21 | impl WindowsMemoryMappedFile { 22 | pub fn new(name: &str, length: usize) -> WindowsMemoryMappedFile { 23 | let file_name = CString::new(name).unwrap(); 24 | let mut already_existed = true; 25 | 26 | unsafe { 27 | let mut map_handle = 28 | OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, file_name.as_ptr() as *const u8); 29 | 30 | // No file existed, as open failed. Try create a new one. 31 | if map_handle == 0 { 32 | map_handle = CreateFileMappingA( 33 | INVALID_HANDLE_VALUE, 34 | core::ptr::null::(), 35 | PAGE_EXECUTE_READWRITE, 36 | 0, 37 | length as u32, 38 | file_name.as_ptr() as *const u8, 39 | ); 40 | 41 | already_existed = false; 42 | } 43 | 44 | let data = 45 | MapViewOfFile(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, length).Value as *mut u8; 46 | 47 | WindowsMemoryMappedFile { 48 | already_existed, 49 | data, 50 | length, 51 | map_handle, 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl Drop for WindowsMemoryMappedFile { 58 | fn drop(&mut self) { 59 | unsafe { 60 | UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { 61 | Value: self.data as *mut c_void, 62 | }); 63 | CloseHandle(self.map_handle); 64 | } 65 | } 66 | } 67 | 68 | impl MemoryMappedFile for WindowsMemoryMappedFile { 69 | fn already_existed(&self) -> bool { 70 | self.already_existed 71 | } 72 | unsafe fn data(&self) -> *mut u8 { 73 | self.data 74 | } 75 | fn length(&self) -> usize { 76 | self.length 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | use crate::utilities::cached::get_sys_info; 84 | 85 | #[test] 86 | fn test_windows_memory_mapped_file_creation() { 87 | // Let's create a memory mapped file with a specific size. 88 | let file_name = format!( 89 | "/Reloaded.Memory.Buffers.MemoryBuffer.Test, PID {}", 90 | get_sys_info().this_process_id 91 | ); 92 | let file_length = get_sys_info().allocation_granularity as usize; 93 | let mmf = WindowsMemoryMappedFile::new(&file_name, file_length); 94 | 95 | assert_eq!(mmf.already_existed, false); 96 | assert_eq!(mmf.length, file_length); 97 | 98 | // Assert the file can be opened again (i.e., it exists) 99 | let mmf_existing = WindowsMemoryMappedFile::new(&file_name, file_length); 100 | assert_eq!(mmf_existing.already_existed, true); 101 | } 102 | 103 | #[test] 104 | fn test_windows_memory_mapped_file_data() { 105 | let file_name = format!( 106 | "/Reloaded.Memory.Buffers.MemoryBuffer.Test, PID {}", 107 | get_sys_info().this_process_id 108 | ); 109 | 110 | let file_length = get_sys_info().allocation_granularity as usize; 111 | let mmf = WindowsMemoryMappedFile::new(&file_name, file_length); 112 | 113 | // Let's test we can read and write to the data. 114 | unsafe { 115 | let data_ptr = mmf.data; 116 | assert_ne!(data_ptr, std::ptr::null_mut()); 117 | 118 | // Write a value 119 | *data_ptr = 123; 120 | 121 | // Read it back 122 | assert_eq!(*data_ptr, 123); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # The Reloaded Buffers Library 3 | 4 | **Allocate Memory, & Knuckles** 5 | 6 | - ![Coverage](https://codecov.io/gh/Reloaded-Project/Reloaded.Memory.Buffers/branch/master/graph/badge.svg) 7 | - ![Crate](https://img.shields.io/crates/dv/reloaded_memory_buffers) 8 | - ![Build Status](https://img.shields.io/github/actions/workflow/status/Reloaded-Project/Reloaded.Memory.Buffers/rust-cargo-test.yml) 9 | 10 | ## About 11 | 12 | `Reloaded.Memory.Buffers` is a library for allocating memory between a given minimum and maximum memory address, for C# and Rust. 13 | 14 | With the following properties: 15 | 16 | - ***Memory Efficient***: No wasted memory. 17 | - ***Shared***: Can be found and read/written to by multiple users. 18 | - ***Static***: Allocated data never moves, or is overwritten. 19 | - ***Permanent***: Allocated data lasts the lifetime of the process. 20 | - ***Concurrent***: Multiple users can access at the same time. 21 | - ***Large Address Aware:*** On Windows, the library can correctly leverage all 4GB in 32-bit processes. 22 | - ***Cross Platform***: Supports Windows, OSX and Linux. 23 | 24 | ## Wiki & Documentation 25 | 26 | This is a cross-platform library, with shared documentation. 27 | 28 | [For basic usage, see wiki](https://reloaded-project.github.io/Reloaded.Memory.Buffers/) 29 | 30 | ## Example Use Cases 31 | 32 | These are just examples: 33 | 34 | - ***Hooks***: Hooking libraries like [Reloaded.Hooks](https://github.com/Reloaded-Project/Reloaded.Hooks) can reduce amount of bytes stolen from functions. 35 | - ***Libraries***: Libraries like [Reloaded.Assembler](https://github.com/Reloaded-Project/Reloaded.Assembler) require memory be allocated in first 2GB for x64 FASM. 36 | 37 | ## Usage 38 | 39 | The library provides a simple high level API to use. 40 | 41 | See [Wiki](https://reloaded-project.github.io/Reloaded.Memory.Buffers/) for Rust usage 42 | 43 | ### Community Feedback 44 | 45 | If you have questions/bug reports/etc. feel free to [Open an Issue](https://github.com/Reloaded-Project/Reloaded.Memory.Buffers/issues/new). 46 | 47 | Contributions are welcome and encouraged. Feel free to implement new features, make bug fixes or suggestions so long as 48 | they meet the quality standards set by the existing code in the repository. 49 | 50 | For an idea as to how things are set up, [see Reloaded Project Configurations.](https://github.com/Reloaded-Project/Reloaded.Project.Configurations) 51 | 52 | Happy Hacking 💜 53 | */ 54 | 55 | #![cfg_attr(feature = "nightly", feature(optimize_attribute))] 56 | #![cfg_attr(not(feature = "std"), no_std)] 57 | 58 | #[cfg(not(feature = "std"))] 59 | #[macro_use] 60 | extern crate alloc; 61 | 62 | pub mod structs { 63 | 64 | pub mod params { 65 | pub mod buffer_allocator_settings; 66 | pub use buffer_allocator_settings::BufferAllocatorSettings; 67 | 68 | pub mod buffer_search_settings; 69 | pub use buffer_search_settings::BufferSearchSettings; 70 | } 71 | 72 | pub mod errors { 73 | pub mod buffer_allocation_error; 74 | pub use buffer_allocation_error::BufferAllocationError; 75 | 76 | pub mod buffer_search_error; 77 | pub use buffer_search_error::BufferSearchError; 78 | 79 | pub mod item_allocation_error; 80 | pub use item_allocation_error::ItemAllocationError; 81 | } 82 | 83 | pub mod safe_locator_item; 84 | pub use safe_locator_item::SafeLocatorItem; 85 | 86 | pub mod private_allocation; 87 | pub use private_allocation::PrivateAllocation; 88 | 89 | /// Provides access to underlying internal structures. 90 | /// i.e. The Raw structures used in the backend. 91 | /// 92 | /// Unsafe and for advanced users only, not recommended for general use. 93 | pub mod internal { 94 | pub mod locator_item; 95 | pub use locator_item::LocatorItem; 96 | 97 | pub mod locator_header; 98 | pub use locator_header::LocatorHeader; 99 | } 100 | } 101 | 102 | pub(crate) mod internal { 103 | pub mod buffer_allocator; 104 | pub mod locator_header_finder; 105 | 106 | #[cfg(target_os = "linux")] 107 | pub mod buffer_allocator_linux; 108 | 109 | #[cfg(target_os = "macos")] 110 | pub mod buffer_allocator_osx; 111 | 112 | #[cfg(target_os = "windows")] 113 | pub mod buffer_allocator_windows; 114 | 115 | #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] 116 | pub mod buffer_allocator_mmap_rs; 117 | 118 | #[cfg(not(feature = "all_private"))] 119 | pub mod memory_mapped_file; 120 | 121 | #[cfg(unix)] 122 | #[cfg(not(feature = "all_private"))] 123 | pub mod memory_mapped_file_unix; 124 | 125 | #[cfg(target_os = "windows")] 126 | #[cfg(not(feature = "all_private"))] 127 | pub mod memory_mapped_file_windows; 128 | } 129 | 130 | pub(crate) mod utilities { 131 | 132 | pub mod address_range; 133 | pub mod cached; 134 | pub mod icache_clear; 135 | pub mod map_parser_utilities; 136 | pub mod mathematics; 137 | pub mod wrappers; 138 | 139 | #[cfg(target_os = "linux")] 140 | pub mod linux_map_parser; 141 | 142 | // Internal, disables W^X for internal buffers. 143 | pub(crate) mod disable_write_xor_execute; 144 | } 145 | 146 | /// Provides a C interface to the library. 147 | pub mod c { 148 | #[cfg(feature = "c_exports")] 149 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 150 | pub mod buffers_c_buffers; 151 | #[cfg(feature = "c_exports")] 152 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 153 | pub mod buffers_c_locatoritem; 154 | } 155 | 156 | pub mod buffers; 157 | -------------------------------------------------------------------------------- /src-rust/src/structs/errors/buffer_allocation_error.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::params::BufferAllocatorSettings; 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::string::String; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct BufferAllocationError { 8 | pub settings: BufferAllocatorSettings, 9 | pub text: &'static str, 10 | } 11 | 12 | #[allow(clippy::inherent_to_string_shadow_display)] 13 | impl BufferAllocationError { 14 | pub fn to_string(&self) -> String { 15 | // We save some space here for C binding use. 16 | #[cfg(feature = "no_format")] 17 | { 18 | use nanokit::string_concat::concat_2; 19 | const BASE_MSG: &str = "Buffer Allocation Error: "; 20 | concat_2(BASE_MSG, self.text) 21 | } 22 | 23 | #[cfg(not(feature = "no_format"))] 24 | { 25 | format!( 26 | "Buffer Allocation Error: {}. Settings: {:?}", 27 | self.text, self.settings 28 | ) 29 | } 30 | } 31 | } 32 | 33 | impl core::fmt::Display for BufferAllocationError { 34 | #[cfg_attr(feature = "size_opt", optimize(size))] 35 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 36 | #[cfg(feature = "no_format")] 37 | { 38 | f.write_str("Buffer Allocation Error: ")?; 39 | f.write_str(self.text) 40 | } 41 | 42 | #[cfg(not(feature = "no_format"))] 43 | { 44 | write!( 45 | f, 46 | "Buffer Allocation Error: {}. Settings: {:?}", 47 | self.text, self.settings 48 | ) 49 | } 50 | } 51 | } 52 | 53 | impl BufferAllocationError { 54 | pub fn new(settings: BufferAllocatorSettings, text: &'static str) -> Self { 55 | Self { settings, text } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src-rust/src/structs/errors/buffer_search_error.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::params::BufferSearchSettings; 2 | use core::fmt::{Display, Formatter}; 3 | 4 | #[cfg(not(feature = "std"))] 5 | use alloc::string::String; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct BufferSearchError { 9 | pub settings: BufferSearchSettings, 10 | pub text: &'static str, 11 | } 12 | 13 | #[allow(clippy::inherent_to_string_shadow_display)] 14 | impl BufferSearchError { 15 | pub fn to_string(&self) -> String { 16 | #[cfg(feature = "no_format")] 17 | { 18 | use nanokit::string_concat::concat_2; 19 | const BASE_MSG: &str = "Buffer Search Error: "; 20 | concat_2(BASE_MSG, self.text) 21 | } 22 | 23 | #[cfg(not(feature = "no_format"))] 24 | { 25 | format!( 26 | "Buffer Search Error: {}. Settings: {:?}", 27 | self.text, self.settings 28 | ) 29 | } 30 | } 31 | } 32 | 33 | impl Display for BufferSearchError { 34 | #[cfg_attr(feature = "size_opt", optimize(size))] 35 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 36 | #[cfg(feature = "no_format")] 37 | { 38 | const BASE_MSG: &str = "Buffer Search Error: "; 39 | f.write_str(BASE_MSG)?; 40 | f.write_str(self.text) 41 | } 42 | 43 | #[cfg(not(feature = "no_format"))] 44 | { 45 | write!( 46 | f, 47 | "Buffer Search Error: {}. Settings: {:?}", 48 | self.text, self.settings 49 | ) 50 | } 51 | } 52 | } 53 | 54 | impl BufferSearchError { 55 | pub fn new(settings: BufferSearchSettings, text: &'static str) -> Self { 56 | Self { settings, text } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src-rust/src/structs/errors/item_allocation_error.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq)] 2 | pub enum ItemAllocationError { 3 | NoSpaceInHeader, 4 | CannotAllocateMemory, 5 | } 6 | 7 | impl ItemAllocationError { 8 | pub fn as_string(&self) -> &'static str { 9 | match self { 10 | ItemAllocationError::NoSpaceInHeader => "No more space in locator header", 11 | ItemAllocationError::CannotAllocateMemory => "Could not allocate memory", 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src-rust/src/structs/params/buffer_allocator_settings.rs: -------------------------------------------------------------------------------- 1 | use crate::utilities::{cached::get_sys_info, mathematics}; 2 | use core::cmp::max; 3 | 4 | /// Settings to pass to the buffer allocator. 5 | #[derive(Debug, Clone, Copy)] 6 | #[repr(C)] 7 | pub struct BufferAllocatorSettings { 8 | /// Minimum address of the allocation. 9 | pub min_address: usize, 10 | 11 | /// Maximum address of the allocation. 12 | pub max_address: usize, 13 | 14 | /// Required size of the data. 15 | pub size: u32, 16 | 17 | /// Process to allocate memory in. 18 | /// Stored as process id. 19 | pub target_process_id: u32, 20 | 21 | /// Amount of times library should retry after failing to allocate a region. 22 | /// 23 | /// # Remarks 24 | /// This is useful when there's high memory pressure, meaning pages become unavailable between the time 25 | /// they are found and the time we try to allocate them. 26 | pub retry_count: i32, 27 | 28 | /// Whether to use brute force to find a suitable address. 29 | /// 30 | /// # Remarks 31 | /// 32 | /// In the original library, this was for some reason only ever needed in FFXIV under Wine; and was contributed 33 | /// (prior to rewrite) by the Dalamud folks. In Wine and on FFXIV *only*; this was ever the case. 34 | /// Inclusion of a brute force approach is a last ditch workaround for that. 35 | /// 36 | /// This setting is only used on Windows targets today. 37 | pub brute_force: bool, 38 | } 39 | 40 | impl BufferAllocatorSettings { 41 | /// Initializes the buffer allocator with default settings. 42 | pub fn new() -> Self { 43 | let sys_info = get_sys_info(); 44 | Self { 45 | min_address: 0, 46 | max_address: sys_info.max_address, 47 | size: 4096, 48 | target_process_id: sys_info.this_process_id, 49 | retry_count: 8, 50 | brute_force: true, 51 | } 52 | } 53 | 54 | /// Creates settings such that the returned buffer will always be within `proximity` bytes of `target`. 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `proximity` - Max proximity (number of bytes) to target. 59 | /// * `target` - Target address. 60 | /// * `size` - Size required in the settings. 61 | /// 62 | /// # Returns 63 | /// 64 | /// * `BufferAllocatorSettings` - Settings that would satisfy this search. 65 | pub fn from_proximity(proximity: usize, target: usize, size: usize) -> Self { 66 | Self { 67 | max_address: mathematics::add_with_overflow_cap(target, proximity), 68 | min_address: mathematics::subtract_with_underflow_cap(target, proximity), 69 | size: size as u32, 70 | ..Self::new() 71 | } 72 | } 73 | 74 | /// Sanitizes the input values. 75 | pub fn sanitize(&mut self) { 76 | // On Windows, VirtualAlloc treats 0 as 'any address', we might aswell avoid this out the gate. 77 | let sys_info = get_sys_info(); 78 | if cfg!(windows) && (self.min_address < sys_info.allocation_granularity as usize) { 79 | self.min_address = sys_info.allocation_granularity as usize; 80 | } 81 | 82 | self.size = max(self.size, 1); 83 | self.size = 84 | mathematics::round_up(self.size as usize, sys_info.allocation_granularity as usize) 85 | as u32; 86 | } 87 | } 88 | 89 | impl Default for BufferAllocatorSettings { 90 | fn default() -> Self { 91 | Self::new() 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | 98 | use super::*; 99 | use crate::utilities::cached::get_sys_info; 100 | 101 | #[test] 102 | fn test_default_settings() { 103 | let settings = BufferAllocatorSettings::new(); 104 | assert_eq!(settings.min_address, 0); 105 | assert_eq!(settings.max_address, get_sys_info().max_address); 106 | assert_eq!(settings.size, 4096); 107 | assert_eq!(settings.target_process_id, get_sys_info().this_process_id); 108 | assert_eq!(settings.retry_count, 8); 109 | assert!(settings.brute_force); 110 | } 111 | 112 | #[test] 113 | fn test_from_proximity() { 114 | let proximity: usize = 1000; 115 | let target: usize = 2000; 116 | let size: usize = 3000; 117 | let settings = BufferAllocatorSettings::from_proximity(proximity, target, size); 118 | 119 | assert_eq!( 120 | settings.max_address, 121 | mathematics::add_with_overflow_cap(target, proximity) 122 | ); 123 | assert_eq!( 124 | settings.min_address, 125 | mathematics::subtract_with_underflow_cap(target, proximity) 126 | ); 127 | assert_eq!(settings.size, size as u32); 128 | assert_eq!(settings.target_process_id, get_sys_info().this_process_id); 129 | assert_eq!(settings.retry_count, 8); 130 | assert!(settings.brute_force); 131 | } 132 | 133 | #[test] 134 | fn test_sanitize() { 135 | let mut settings = BufferAllocatorSettings::new(); 136 | settings.min_address = 0; 137 | settings.size = 1; 138 | 139 | settings.sanitize(); 140 | 141 | if cfg!(windows) { 142 | assert_eq!( 143 | settings.min_address, 144 | get_sys_info().allocation_granularity as usize 145 | ); 146 | } else { 147 | assert_eq!(settings.min_address, 0); 148 | } 149 | 150 | assert_eq!( 151 | settings.size, 152 | mathematics::round_up( 153 | max(1, 1) as usize, 154 | get_sys_info().allocation_granularity as usize 155 | ) as u32 156 | ); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src-rust/src/structs/params/buffer_search_settings.rs: -------------------------------------------------------------------------------- 1 | use crate::utilities::{cached::get_sys_info, mathematics}; 2 | 3 | /// Settings to pass to buffer search mechanisms. 4 | #[derive(Debug, Clone, Copy)] 5 | #[repr(C)] 6 | pub struct BufferSearchSettings { 7 | /// Minimum address of the allocation. 8 | pub min_address: usize, 9 | 10 | /// Maximum address of the allocation. 11 | pub max_address: usize, 12 | 13 | /// Required size of the data. 14 | pub size: u32, 15 | } 16 | 17 | impl BufferSearchSettings { 18 | /// Initializes the buffer allocator with default settings. 19 | pub fn new() -> Self { 20 | Self { 21 | min_address: 0, 22 | max_address: get_sys_info().max_address, 23 | size: 4096, 24 | } 25 | } 26 | 27 | /// Creates settings such that the returned buffer will always be within `proximity` bytes of `target`. 28 | /// 29 | /// # Arguments 30 | /// 31 | /// * `proximity` - Max proximity (number of bytes) to target. 32 | /// * `target` - Target address. 33 | /// * `size` - Size required in the settings. 34 | /// 35 | /// # Returns 36 | /// 37 | /// * `BufferSearchSettings` - Settings that would satisfy this search. 38 | pub fn from_proximity(proximity: usize, target: usize, size: usize) -> Self { 39 | Self { 40 | max_address: mathematics::add_with_overflow_cap(target, proximity), 41 | min_address: mathematics::subtract_with_underflow_cap(target, proximity), 42 | size: size as u32, 43 | } 44 | } 45 | } 46 | 47 | impl Default for BufferSearchSettings { 48 | fn default() -> Self { 49 | Self::new() 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[test] 58 | fn test_default_settings() { 59 | let settings = BufferSearchSettings::new(); 60 | assert_eq!(settings.min_address, 0); 61 | assert_eq!(settings.max_address, get_sys_info().max_address); 62 | assert_eq!(settings.size, 4096); 63 | } 64 | 65 | #[test] 66 | fn test_from_proximity() { 67 | let proximity: usize = 1000; 68 | let target: usize = 2000; 69 | let size: usize = 3000; 70 | let settings = BufferSearchSettings::from_proximity(proximity, target, size); 71 | 72 | assert_eq!( 73 | settings.max_address, 74 | mathematics::add_with_overflow_cap(target, proximity) 75 | ); 76 | assert_eq!( 77 | settings.min_address, 78 | mathematics::subtract_with_underflow_cap(target, proximity) 79 | ); 80 | assert_eq!(settings.size, size as u32); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src-rust/src/structs/safe_locator_item.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::internal::LocatorItem; 2 | use core::cell::Cell; 3 | 4 | /// An individual item in the buffer locator that can be dropped (disposed). 5 | /// 6 | /// The item behind this struct. 7 | /// 8 | /// Use at your own risk. 9 | /// Unsafe. 10 | #[repr(C)] 11 | pub struct SafeLocatorItem { 12 | pub item: Cell<*mut LocatorItem>, 13 | } 14 | 15 | impl SafeLocatorItem { 16 | /// Appends the code to this buffer. 17 | /// This is same as [`append_bytes`] but automatically clears the instruction cache on given CPU. 18 | /// 19 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer. 20 | /// When returning buffers from the library, the library will ensure there's at least 21 | /// the requested amount of space; so if the total size of your data falls under that 22 | /// space, you are good. 23 | /// 24 | /// # Arguments 25 | /// 26 | /// * `data` - The data to append to the item. 27 | /// 28 | /// # Returns 29 | /// 30 | /// The address of the written data. 31 | /// 32 | /// # Safety 33 | /// 34 | /// This function is safe provided that the caller ensures that the buffer is large enough to hold the data. 35 | /// There is no error thrown if size is insufficient. 36 | pub unsafe fn append_code(&self, data: &[u8]) -> usize { 37 | (*self.item.get()).append_code(data) 38 | } 39 | 40 | /// Appends the data to this buffer. 41 | /// 42 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer. 43 | /// When returning buffers from the library, the library will ensure there's at least 44 | /// the requested amount of space; so if the total size of your data falls under that 45 | /// space, you are good. 46 | /// 47 | /// # Safety 48 | /// 49 | /// This function is unsafe because it writes to raw (untracked by Rust) memory. 50 | pub unsafe fn append_bytes(&self, data: &[u8]) -> usize { 51 | (*self.item.get()).append_bytes(data) 52 | } 53 | 54 | /// Appends the blittable variable to this buffer. 55 | /// 56 | /// Type of the item to write. 57 | /// The item to append to the buffer. 58 | /// Returns: Address of the written data. 59 | /// 60 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer. 61 | /// When returning buffers from the library, the library will ensure there's at least 62 | /// the requested amount of space; so if the total size of your data falls under that 63 | /// space, you are good. 64 | /// 65 | /// # Safety 66 | /// 67 | /// This function is unsafe because it writes to raw (untracked by Rust) memory. 68 | pub unsafe fn append_copy(&self, data: &T) -> usize 69 | where 70 | T: Copy, 71 | { 72 | (*self.item.get()).append_copy(data) 73 | } 74 | } 75 | 76 | /// Safely dispose. 77 | impl Drop for SafeLocatorItem { 78 | fn drop(&mut self) { 79 | unsafe { 80 | // Need to amend C API if we ever need to do anything more here, since it forgets item. 81 | (*self.item.get()).unlock(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src-rust/src/utilities/address_range.rs: -------------------------------------------------------------------------------- 1 | /// Defines a physical address range with a minimum and maximum address. 2 | pub struct AddressRange { 3 | pub start_pointer: usize, 4 | pub end_pointer: usize, 5 | } 6 | 7 | impl AddressRange { 8 | /// Constructs a new AddressRange. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `start_pointer` - The starting address of the range. 13 | /// * `end_pointer` - The ending address of the range. 14 | pub fn new(start_pointer: usize, end_pointer: usize) -> Self { 15 | Self { 16 | start_pointer, 17 | end_pointer, 18 | } 19 | } 20 | 21 | /// Calculates the size of the address range. 22 | /// 23 | /// # Returns 24 | /// 25 | /// * The size of the range, calculated as the difference between the end pointer and the start pointer. 26 | pub fn size(&self) -> usize { 27 | self.end_pointer - self.start_pointer 28 | } 29 | 30 | /// Checks if the other address range is completely inside this address range. 31 | /// 32 | /// # Arguments 33 | /// 34 | /// * `other` - The other address range to check against. 35 | /// 36 | /// # Returns 37 | /// 38 | /// * `true` if the other address range is contained entirely inside this one, `false` otherwise. 39 | pub fn contains(&self, other: &AddressRange) -> bool { 40 | other.start_pointer >= self.start_pointer && other.end_pointer <= self.end_pointer 41 | } 42 | 43 | /// Checks if the other address range intersects this address range, i.e. start or end of this range falls inside other range. 44 | /// 45 | /// # Arguments 46 | /// 47 | /// * `other` - The other address range to check for overlap. 48 | /// 49 | /// # Returns 50 | /// 51 | /// * `true` if there are any overlaps in the address ranges, `false` otherwise. 52 | pub fn overlaps(&self, other: &AddressRange) -> bool { 53 | self.point_in_range(other.start_pointer) 54 | || self.point_in_range(other.end_pointer) 55 | || other.point_in_range(self.start_pointer) 56 | || other.point_in_range(self.end_pointer) 57 | } 58 | 59 | /// Checks if a number "point", is between min and max of this address range. 60 | /// 61 | /// # Arguments 62 | /// 63 | /// * `point` - The point to test. 64 | /// 65 | /// # Returns 66 | /// 67 | /// * `true` if the point is within this address range, `false` otherwise. 68 | fn point_in_range(&self, point: usize) -> bool { 69 | point >= self.start_pointer && point <= self.end_pointer 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn contains_should_be_true_when_other_range_is_inside() { 79 | let range = AddressRange::new(100, 200); 80 | let other_range = AddressRange::new(120, 180); 81 | 82 | assert!(range.contains(&other_range)); 83 | } 84 | 85 | #[test] 86 | fn contains_should_be_false_when_other_range_is_not_inside() { 87 | let range = AddressRange::new(100, 200); 88 | let other_range = AddressRange::new(80, 220); 89 | 90 | assert!(!range.contains(&other_range)); 91 | } 92 | 93 | #[test] 94 | fn overlaps_should_be_true_when_other_range_overlaps() { 95 | let range = AddressRange::new(100, 200); 96 | let other_range = AddressRange::new(150, 220); 97 | 98 | assert!(range.overlaps(&other_range)); 99 | } 100 | 101 | #[test] 102 | fn overlaps_should_be_false_when_other_range_does_not_overlap() { 103 | let range = AddressRange::new(100, 200); 104 | let other_range = AddressRange::new(300, 400); 105 | 106 | assert!(!range.overlaps(&other_range)); 107 | } 108 | 109 | #[test] 110 | fn overlaps_should_be_true_when_ranges_are_same() { 111 | let range = AddressRange::new(100, 200); 112 | let other_range = AddressRange::new(100, 200); 113 | 114 | assert!(range.overlaps(&other_range)); 115 | } 116 | 117 | #[test] 118 | fn overlaps_should_be_true_when_one_range_is_fully_inside_other() { 119 | let range = AddressRange::new(100, 200); 120 | let other_range = AddressRange::new(120, 180); 121 | 122 | assert!(range.overlaps(&other_range)); 123 | } 124 | 125 | #[test] 126 | fn overlaps_should_be_true_when_ranges_are_adjacent() { 127 | let range = AddressRange::new(100, 200); 128 | let other_range = AddressRange::new(200, 300); 129 | 130 | assert!(range.overlaps(&other_range)); 131 | } 132 | 133 | #[test] 134 | fn overlaps_should_be_true_when_other_range_starts_inside_range() { 135 | let range = AddressRange::new(100, 200); 136 | let other_range = AddressRange::new(150, 250); 137 | 138 | assert!(range.overlaps(&other_range)); 139 | } 140 | 141 | #[test] 142 | fn overlaps_should_be_true_when_other_range_ends_inside_range() { 143 | let range = AddressRange::new(100, 200); 144 | let other_range = AddressRange::new(50, 150); 145 | 146 | assert!(range.overlaps(&other_range)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src-rust/src/utilities/cached.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | use windows_sys::Win32::System::{ 3 | SystemInformation::{GetSystemInfo, SYSTEM_INFO}, 4 | Threading::GetCurrentProcessId, 5 | }; 6 | 7 | static mut CACHED: Option = None; 8 | 9 | pub fn get_sys_info() -> &'static Cached { 10 | // No thread safety needed here (we're running code with no side effects), so we omit lazy_static to save on library space. 11 | unsafe { 12 | if CACHED.is_some() { 13 | return CACHED.as_ref().unwrap_unchecked(); 14 | } 15 | 16 | make_sys_info(); 17 | return CACHED.as_ref().unwrap_unchecked(); 18 | } 19 | } 20 | 21 | #[inline(never)] // bloats the binary 22 | fn make_sys_info() { 23 | unsafe { CACHED = Some(Cached::new()) }; 24 | } 25 | 26 | pub struct Cached { 27 | pub max_address: usize, 28 | pub allocation_granularity: i32, 29 | pub page_size: u32, 30 | pub this_process_id: u32, 31 | } 32 | 33 | #[allow(dead_code)] 34 | impl Cached { 35 | pub fn new() -> Cached { 36 | let mut allocation_granularity: i32 = Default::default(); 37 | let mut page_size: i32 = Default::default(); 38 | let mut max_address: usize = Default::default(); 39 | 40 | #[cfg(target_os = "windows")] 41 | Self::get_address_and_allocation_granularity_windows( 42 | &mut allocation_granularity, 43 | &mut max_address, 44 | &mut page_size, 45 | ); 46 | 47 | #[cfg(not(target_os = "windows"))] 48 | Self::get_address_and_allocation_granularity_mmap_rs( 49 | &mut allocation_granularity, 50 | &mut max_address, 51 | &mut page_size, 52 | ); 53 | 54 | Cached { 55 | max_address, 56 | allocation_granularity, 57 | this_process_id: Self::get_process_id(), 58 | page_size: 4096, 59 | } 60 | } 61 | 62 | #[cfg(unix)] 63 | fn get_process_id() -> u32 { 64 | unsafe { libc::getpid() as u32 } 65 | } 66 | 67 | #[cfg(target_os = "windows")] 68 | fn get_process_id() -> u32 { 69 | unsafe { GetCurrentProcessId() } 70 | } 71 | 72 | #[cfg(target_os = "windows")] 73 | fn get_address_and_allocation_granularity_windows( 74 | allocation_granularity: &mut i32, 75 | max_address: &mut usize, 76 | page_size: &mut i32, 77 | ) { 78 | use core::mem::zeroed; 79 | 80 | unsafe { 81 | let mut info: SYSTEM_INFO = zeroed(); 82 | GetSystemInfo(&mut info); 83 | 84 | *max_address = info.lpMaximumApplicationAddress as usize; 85 | *allocation_granularity = info.dwAllocationGranularity as i32; 86 | *page_size = info.dwPageSize as i32; 87 | } 88 | } 89 | 90 | #[allow(overflowing_literals)] 91 | #[cfg(not(target_os = "windows"))] 92 | fn get_address_and_allocation_granularity_mmap_rs( 93 | allocation_granularity: &mut i32, 94 | max_address: &mut usize, 95 | page_size: &mut i32, 96 | ) { 97 | // Note: This is a fallback mechanism dependent on mmap-rs. 98 | 99 | use core::cmp::max; 100 | 101 | use mmap_rs_with_map_from_existing::MmapOptions; 102 | if cfg!(target_pointer_width = "32") { 103 | *max_address = 0xFFFF_FFFF; 104 | } else if cfg!(target_pointer_width = "64") { 105 | *max_address = 0x7FFFFFFFFFFF; // no max-address API, so restricted to Linux level 106 | } 107 | 108 | #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] 109 | { 110 | *page_size = MmapOptions::page_size() as i32; 111 | } 112 | 113 | #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 114 | { 115 | // Apple lies about page size in libc on M1 says it's 4096 instead of 16384 116 | *page_size = MmapOptions::page_size() as i32; 117 | } 118 | 119 | *allocation_granularity = max(MmapOptions::allocation_granularity() as i32, *page_size); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src-rust/src/utilities/disable_write_xor_execute.rs: -------------------------------------------------------------------------------- 1 | // An utility to disable write xor execute protection on a memory region. 2 | // This method contains the code to disable W^X on platforms where it's enforced. 3 | 4 | #[cfg(target_os = "macos")] 5 | use { 6 | libc::mach_task_self, mach::vm::mach_vm_protect, mach::vm_prot::VM_PROT_EXECUTE, 7 | mach::vm_prot::VM_PROT_READ, mach::vm_prot::VM_PROT_WRITE, mach::vm_types::mach_vm_size_t, 8 | }; 9 | 10 | /// Temporarily disables write XOR execute protection with an OS specialized 11 | /// API call (if available). 12 | /// 13 | /// # Parameters 14 | /// 15 | /// - `address`: The address of the memory to disable write XOR execute protection for. 16 | /// - `size`: The size of the memory to disable write XOR execute protection for. 17 | /// 18 | /// # Returns 19 | /// 20 | /// - `usize`: The old memory protection (if needed for call to [`self::restore_write_xor_execute`]). 21 | /// 22 | /// # Remarks 23 | /// 24 | /// This is not currently used on any platform, but is intended for environments 25 | /// which enforce write XOR execute, such as M1 macs. 26 | /// 27 | /// The idea is that you use memory which is read_write_execute (MAP_JIT if mmap), 28 | /// then disable W^X for the current thread. Then we write the code, and re-enable W^X. 29 | #[allow(unused_variables)] 30 | #[inline(always)] 31 | pub(crate) fn disable_write_xor_execute(address: *const u8, size: usize) { 32 | #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 33 | unsafe { 34 | mach_vm_protect( 35 | mach_task_self(), 36 | address as u64, 37 | size as mach_vm_size_t, 38 | 0, 39 | VM_PROT_READ | VM_PROT_WRITE, 40 | ); 41 | } 42 | } 43 | 44 | /// Restores write XOR execute protection. 45 | /// 46 | /// # Parameters 47 | /// 48 | /// - `address`: The address of the memory to disable write XOR execute protection for. 49 | /// - `size`: The size of the memory to disable write XOR execute protection for. 50 | /// - `protection`: The protection returned in the result of the call to [`self::disable_write_xor_execute`]. 51 | /// 52 | /// # Returns 53 | /// 54 | /// Success or error. 55 | #[allow(unused_variables)] 56 | #[inline(always)] 57 | pub(crate) fn restore_write_xor_execute(address: *const u8, size: usize) { 58 | #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 59 | unsafe { 60 | mach_vm_protect( 61 | mach_task_self(), 62 | address as u64, 63 | size as mach_vm_size_t, 64 | 0, 65 | VM_PROT_READ | VM_PROT_EXECUTE, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src-rust/src/utilities/icache_clear.rs: -------------------------------------------------------------------------------- 1 | /// Clears the instruction cache for the specified range. 2 | /// 3 | /// # Arguments 4 | /// 5 | /// * `start` - The start address of the range to clear. 6 | /// * `end` - The end address of the range to clear. 7 | /// 8 | /// # Remarks 9 | /// 10 | /// This function is provided by LLVM. It might not work in non-LLVM backends. 11 | #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] 12 | #[cfg(not(target_os = "windows"))] 13 | #[inline(always)] 14 | pub fn clear_instruction_cache(start: *const u8, end: *const u8) { 15 | clf::cache_line_flush_with_ptr(start, end); 16 | } 17 | 18 | /// Clears the instruction cache for the specified range. 19 | /// 20 | /// # Arguments 21 | /// 22 | /// * `start` - The start address of the range to clear. 23 | /// * `end` - The end address of the range to clear. 24 | #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] 25 | #[cfg(target_os = "windows")] // MSVC fix 26 | #[inline(always)] 27 | pub fn clear_instruction_cache(start: *const u8, end: *const u8) { 28 | use windows_sys::Win32::System::{ 29 | Diagnostics::Debug::FlushInstructionCache, Threading::GetCurrentProcess, 30 | }; 31 | 32 | unsafe { 33 | FlushInstructionCache( 34 | GetCurrentProcess(), 35 | start as *const core::ffi::c_void, 36 | end as usize - start as usize, 37 | ); 38 | } 39 | } 40 | 41 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 42 | #[inline(always)] 43 | pub fn clear_instruction_cache(_start: *const u8, _end: *const u8) { 44 | // x86 & x86_64 have unified data and instruction cache, thus flushing is not needed. 45 | // Therefore it is a no-op 46 | } 47 | -------------------------------------------------------------------------------- /src-rust/src/utilities/map_parser_utilities.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use super::cached::get_sys_info; 4 | 5 | #[cfg(not(feature = "std"))] 6 | use alloc::vec::Vec; 7 | 8 | // Generic structure to use for custom parsers. 9 | #[derive(Debug)] 10 | pub struct MemoryMapEntry { 11 | pub start_address: usize, 12 | pub end_address: usize, 13 | } 14 | 15 | /// This struct represents an entry in the memory map, 16 | /// which is a region in the process's virtual memory space. 17 | impl MemoryMapEntry { 18 | pub fn new(start_address: usize, end_address: usize) -> MemoryMapEntry { 19 | MemoryMapEntry { 20 | start_address, 21 | end_address, 22 | } 23 | } 24 | } 25 | 26 | // Trait to use for external types. 27 | pub trait MemoryMapEntryTrait { 28 | fn start_address(&self) -> usize; 29 | fn end_address(&self) -> usize; 30 | } 31 | 32 | impl MemoryMapEntryTrait for MemoryMapEntry { 33 | fn start_address(&self) -> usize { 34 | self.start_address 35 | } 36 | 37 | fn end_address(&self) -> usize { 38 | self.end_address 39 | } 40 | } 41 | 42 | /// Returns all free regions based on the found regions. 43 | /// 44 | /// # Arguments 45 | /// 46 | /// * `regions` - A slice of MemoryMapEntry that contains the regions. 47 | 48 | #[cfg_attr(feature = "size_opt", optimize(size))] 49 | pub fn get_free_regions(regions: &[T]) -> Vec { 50 | let mut last_end_address: usize = 0; 51 | let mut free_regions = Vec::with_capacity(regions.len() + 2); // +2 for start and finish 52 | 53 | for entry in regions.iter() { 54 | if entry.start_address() > last_end_address { 55 | free_regions.push(MemoryMapEntry { 56 | start_address: last_end_address, 57 | end_address: entry.start_address() - 1, 58 | }); 59 | } 60 | 61 | last_end_address = entry.end_address(); 62 | } 63 | 64 | // After the last region, up to the end of memory 65 | if last_end_address < get_sys_info().max_address { 66 | free_regions.push(MemoryMapEntry { 67 | start_address: last_end_address, 68 | end_address: get_sys_info().max_address, 69 | }); 70 | } 71 | 72 | free_regions 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use crate::utilities::map_parser_utilities::{get_free_regions, MemoryMapEntry}; 78 | 79 | #[test] 80 | fn get_free_regions_with_no_gap() { 81 | let regions = vec![ 82 | MemoryMapEntry::new(0, 10), 83 | MemoryMapEntry::new(10, 20), 84 | MemoryMapEntry::new(20, usize::MAX), 85 | ]; 86 | let free_regions = get_free_regions(®ions); 87 | assert_eq!(free_regions.len(), 0); 88 | } 89 | 90 | #[test] 91 | fn get_free_regions_single_gap() { 92 | let regions = vec![ 93 | MemoryMapEntry::new(0, 10), 94 | MemoryMapEntry::new(10, 20), 95 | MemoryMapEntry::new(30, usize::MAX), 96 | ]; 97 | let free_regions = get_free_regions(®ions); 98 | assert_eq!(free_regions.len(), 1); 99 | assert_eq!(free_regions[0].start_address, 20); 100 | assert_eq!(free_regions[0].end_address, 29); 101 | } 102 | 103 | #[test] 104 | fn get_free_regions_multiple_gaps() { 105 | let regions = vec![ 106 | MemoryMapEntry::new(0, 10), 107 | MemoryMapEntry::new(20, 30), 108 | MemoryMapEntry::new(40, usize::MAX), 109 | ]; 110 | let free_regions = get_free_regions(®ions); 111 | assert_eq!(free_regions.len(), 2); 112 | assert_eq!(free_regions[0].start_address, 10); 113 | assert_eq!(free_regions[0].end_address, 19); 114 | assert_eq!(free_regions[1].start_address, 30); 115 | assert_eq!(free_regions[1].end_address, 39); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src-rust/src/utilities/mathematics.rs: -------------------------------------------------------------------------------- 1 | /// Rounds up a specified number to the next multiple of X. 2 | /// 3 | /// # Arguments 4 | /// 5 | /// * `number` - The number to round up. 6 | /// * `multiple` - The multiple the number should be rounded to. 7 | pub fn round_up>(number: usize, multiple: T) -> usize { 8 | let multiple = multiple.into(); 9 | if multiple == 0 { 10 | return number; 11 | } 12 | 13 | let remainder = number % multiple; 14 | if remainder == 0 { 15 | return number; 16 | } 17 | 18 | number + multiple - remainder 19 | } 20 | 21 | /// Rounds down a specified number to the previous multiple of X. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `number` - The number to round down. 26 | /// * `multiple` - The multiple the number should be rounded to. 27 | pub fn round_down>(number: usize, multiple: T) -> usize { 28 | let multiple = multiple.into(); 29 | if multiple == 0 { 30 | return number; 31 | } 32 | 33 | let remainder = number % multiple; 34 | if remainder == 0 { 35 | return number; 36 | } 37 | 38 | number - remainder 39 | } 40 | 41 | /// Returns smaller of the two values. 42 | /// 43 | /// # Arguments 44 | /// 45 | /// * `a` - First value. 46 | /// * `b` - Second value. 47 | #[allow(dead_code)] 48 | pub fn min(a: usize, b: usize) -> usize { 49 | a.min(b) 50 | } 51 | 52 | /// Adds the two values, but caps the result at MaxValue if it overflows. 53 | /// 54 | /// # Arguments 55 | /// 56 | /// * `a` - First value. 57 | /// * `b` - Second value. 58 | pub fn add_with_overflow_cap(a: usize, b: usize) -> usize { 59 | a.saturating_add(b) 60 | } 61 | 62 | /// Subtracts the two values, but caps the result at MinValue if it overflows. 63 | /// 64 | /// # Arguments 65 | /// 66 | /// * `a` - First value. 67 | /// * `b` - Second value. 68 | pub fn subtract_with_underflow_cap(a: usize, b: usize) -> usize { 69 | a.saturating_sub(b) 70 | } 71 | -------------------------------------------------------------------------------- /src-rust/src/utilities/wrappers.rs: -------------------------------------------------------------------------------- 1 | #[repr(C, packed)] 2 | #[derive(Copy, Clone)] 3 | pub struct Unaligned { 4 | pub value: T, 5 | } 6 | 7 | impl Unaligned { 8 | pub fn new(value: T) -> Self { 9 | Self { value } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | Reloaded.Project.Configurations/.editorconfig -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /projectSettingsUpdater.xml 6 | /modules.xml 7 | /.idea.Reloaded.Memory.Buffers.iml 8 | /contentModel.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/.name: -------------------------------------------------------------------------------- 1 | Reloaded.Memory.Buffers -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ../docs 6 | Reloaded.Project.Configurations 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/projectSettingsUpdater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/.idea/.idea.Reloaded.Memory.Buffers/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Assets.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Reloaded.Memory.Buffers.Exceptions; 5 | using Reloaded.Memory.Buffers.Utilities; 6 | 7 | namespace Reloaded.Memory.Buffers.Tests; 8 | 9 | public class Assets 10 | { 11 | /// 12 | /// Gets the path of the 'Hello World' executable. 13 | /// 14 | public static string GetHelloWorldExePath() 15 | { 16 | // TODO: Tests on ARM64 (GitHub Runner does not support ARM64) 17 | Architecture architecture = RuntimeInformation.ProcessArchitecture; 18 | if (Polyfills.IsWindows()) 19 | { 20 | return architecture switch 21 | { 22 | Architecture.X86 => GetItemWithRelativePath("hello-world-win-x86.exe"), 23 | Architecture.X64 => GetItemWithRelativePath("hello-world-win-x64.exe"), 24 | Architecture.Arm64 => GetItemWithRelativePath("hello-world-win-arm64.exe"), 25 | _ => throw new PlatformNotSupportedException($"Windows with platform {architecture} not supported.") 26 | }; 27 | } 28 | 29 | ThrowHelpers.ThrowPlatformNotSupportedException(); 30 | return ""; 31 | } 32 | 33 | /// 34 | /// Gets an item with the specified relative path to the 'Assets' folder. 35 | /// 36 | public static string GetItemWithRelativePath(string path) 37 | => Path.Combine(AppContext.BaseDirectory, "Assets", path); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-arm64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-arm64.exe -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-x64.exe -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-x86.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers.Tests/Assets/hello-world-win-x86.exe -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Assets/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | printf("hello world\n"); 6 | 7 | while (1) 8 | { 9 | getchar(); 10 | } 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Attributes/AutoLocatorHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoFixture; 3 | using AutoFixture.Dsl; 4 | using AutoFixture.Kernel; 5 | using AutoFixture.Xunit2; 6 | using JetBrains.Annotations; 7 | using Reloaded.Memory.Buffers.Structs.Internal; 8 | 9 | namespace Reloaded.Memory.Buffers.Tests.Attributes; 10 | 11 | /// 12 | /// Custom with support for 13 | /// creating a dummies for locator headers. 14 | /// 15 | [PublicAPI] 16 | public class AutoLocatorHeader : AutoDataAttribute 17 | { 18 | public AutoLocatorHeader(bool randomizeHeader) : base(() => 19 | { 20 | var ret = new Fixture(); 21 | ret.Customize(composer => 22 | { 23 | IPostprocessComposer? result = composer.FromFactory(() => new LocatorHeader()); 24 | if (!randomizeHeader) 25 | result = result.OmitAutoProperties(); 26 | 27 | return result; 28 | }); 29 | 30 | ret.Customizations.Add(new NullLocatorHeaderPointerBuilder()); 31 | return ret; 32 | }) { } 33 | 34 | private class NullLocatorHeaderPointerBuilder : ISpecimenBuilder 35 | { 36 | public object Create(object request, ISpecimenContext context) 37 | { 38 | if (request is not Type type || type != typeof(LocatorHeader*)) 39 | return new NoSpecimen(); 40 | 41 | return null!; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Reloaded.Memory.Buffers.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | runtime; build; native; contentfiles; analyzers; buildtransitive 10 | all 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Reloaded.Memory.Buffers.Tests; 4 | 5 | public class Startup 6 | { 7 | public void ConfigureServices(IServiceCollection services) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/BufferAllocatorTests.Allocation.ExternalProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using FluentAssertions; 7 | using Reloaded.Memory.Buffers.Internal; 8 | using Reloaded.Memory.Buffers.Structs.Params; 9 | using Reloaded.Memory.Buffers.Utilities; 10 | using Xunit; 11 | 12 | namespace Reloaded.Memory.Buffers.Tests.Tests; 13 | 14 | [SuppressMessage("ReSharper", "RedundantCast")] 15 | [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyleForMemberAccess")] 16 | public class BufferAllocatorTestsAllocationExternalProcess 17 | { 18 | [Fact] 19 | public void CanAllocateIn2GiB() 20 | { 21 | // Windows Only. 22 | if (!Polyfills.IsWindows() || IntPtr.Size < 4) 23 | return; 24 | 25 | // Arrange 26 | using var target = new TemporaryProcess(); 27 | var settings = new BufferAllocatorSettings() 28 | { 29 | MinAddress = 0, 30 | MaxAddress = int.MaxValue, 31 | Size = 4096, 32 | TargetProcess = target.Process 33 | }; 34 | 35 | var item = BufferAllocator.Allocate(settings); 36 | item.BaseAddress.Should().NotBeNull(); 37 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 38 | } 39 | 40 | [Fact] 41 | public void CanAllocate_UpToMaxAddress() 42 | { 43 | // Windows Only. 44 | if (!Polyfills.IsWindows()) 45 | return; 46 | 47 | // Arrange 48 | using var target = new TemporaryProcess(); 49 | var settings = new BufferAllocatorSettings() 50 | { 51 | MinAddress = Cached.GetMaxAddress() / 2, 52 | MaxAddress = Cached.GetMaxAddress(), 53 | Size = 4096, 54 | TargetProcess = target.Process 55 | }; 56 | 57 | var item = BufferAllocator.Allocate(settings); 58 | item.BaseAddress.Should().NotBeNull(); 59 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 60 | } 61 | 62 | private class TemporaryProcess : IDisposable 63 | { 64 | // Create dummy HelloWorld.exe 65 | public Process Process { get; } 66 | 67 | public TemporaryProcess() 68 | { 69 | var filePath = Path.GetFullPath(Assets.GetHelloWorldExePath()); 70 | 71 | try 72 | { 73 | Process = Process.Start(new ProcessStartInfo 74 | { 75 | FileName = filePath, CreateNoWindow = true, UseShellExecute = false 76 | })!; 77 | } 78 | catch (Win32Exception) 79 | { 80 | throw new Exception($"Failed to start process with path: {filePath}"); 81 | } 82 | } 83 | 84 | // Dispose of HelloWorld.exe 85 | public void Dispose() 86 | { 87 | Process.Kill(); 88 | Process.Dispose(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/BufferAllocatorTests.Allocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | using FluentAssertions; 5 | using Reloaded.Memory.Buffers.Internal; 6 | using Reloaded.Memory.Buffers.Structs.Internal; 7 | using Reloaded.Memory.Buffers.Structs.Params; 8 | using Reloaded.Memory.Buffers.Utilities; 9 | using Reloaded.Memory.Native.Unix; 10 | using Reloaded.Memory.Native.Windows; 11 | using Xunit; 12 | 13 | namespace Reloaded.Memory.Buffers.Tests.Tests; 14 | 15 | [SuppressMessage("ReSharper", "RedundantCast")] 16 | public class BufferAllocatorTestsAllocation 17 | { 18 | [Fact] 19 | public void CanAllocateIn2GiB() 20 | { 21 | // Not supported on OSX. 22 | if (Polyfills.IsMacOS()) 23 | return; 24 | 25 | // Arrange 26 | var settings = new BufferAllocatorSettings() 27 | { 28 | MinAddress = 0, 29 | MaxAddress = int.MaxValue, 30 | Size = 4096, 31 | TargetProcess = Process.GetCurrentProcess() 32 | }; 33 | 34 | var item = BufferAllocator.Allocate(settings); 35 | item.BaseAddress.Should().NotBeNull(); 36 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 37 | Free(item, settings); 38 | } 39 | 40 | [Fact] 41 | public void CanAllocate_UpToMaxAddress() 42 | { 43 | // Arrange 44 | var settings = new BufferAllocatorSettings() 45 | { 46 | MinAddress = Cached.GetMaxAddress() / 2, 47 | MaxAddress = Cached.GetMaxAddress(), 48 | Size = 4096, 49 | TargetProcess = Process.GetCurrentProcess() 50 | }; 51 | 52 | var item = BufferAllocator.Allocate(settings); 53 | item.BaseAddress.Should().NotBeNull(); 54 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 55 | Free(item, settings); 56 | } 57 | 58 | /// 59 | /// For testing use only. 60 | /// 61 | /// 62 | /// 63 | internal static void Free(LocatorItem item, BufferAllocatorSettings settings) 64 | { 65 | #pragma warning disable CA1416 // Validate platform compatibility 66 | if (Polyfills.IsWindows()) 67 | { 68 | if (settings.TargetProcess.Id == Polyfills.GetProcessId()) 69 | Assert.True(Kernel32.VirtualFree(item.BaseAddress, (UIntPtr)0, Kernel32.MEM_ALLOCATION_TYPE.MEM_RELEASE)); 70 | else 71 | Assert.True(Kernel32.VirtualFreeEx((nint)settings.TargetProcess.Id, (UIntPtr)0, (nuint)item.Size, Kernel32.MEM_ALLOCATION_TYPE.MEM_RELEASE)); 72 | } 73 | else if (Polyfills.IsLinux() || Polyfills.IsMacOS()) 74 | { 75 | Posix.munmap(item.BaseAddress, (nuint)item.Size); 76 | } 77 | #pragma warning restore CA1416 // Validate platform compatibility 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/BufferAllocatorTests.Common.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Reloaded.Memory.Buffers.Utilities; 3 | using Xunit; 4 | using static Reloaded.Memory.Buffers.Internal.BufferAllocator; 5 | 6 | namespace Reloaded.Memory.Buffers.Tests.Tests; 7 | 8 | public class BufferAllocatorTestsCommon 9 | { 10 | private readonly int _allocationGranularity = 65536; // Assuming 64KB Allocation Granularity 11 | 12 | [Fact] 13 | public void PageDoesNotOverlapWithMinMax() 14 | { 15 | var minPtr = (nuint)100000; 16 | var maxPtr = (nuint)200000; 17 | var pageSize = (nuint)50000; 18 | var bufSize = (nuint)30000; 19 | 20 | // No overlap between min-max range and page 21 | var pageStart = maxPtr + 1; 22 | var pageEnd = pageStart + pageSize; 23 | 24 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4]).Length; 25 | Assert.Equal(0, result); 26 | } 27 | 28 | [Fact] 29 | public void BufferSizeGreaterThanPage() 30 | { 31 | var minPtr = (nuint)100000; 32 | var maxPtr = (nuint)200000; 33 | var pageSize = (nuint)30000; 34 | var bufSize = (nuint)50000; // Greater than pageSize 35 | 36 | // Page is within min-max range 37 | var pageStart = minPtr; 38 | var pageEnd = pageStart + pageSize; 39 | 40 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4]).Length; 41 | Assert.Equal(0, result); 42 | } 43 | 44 | [Fact] 45 | public void RoundUpFromPtrMin() 46 | { 47 | var minPtr = (nuint)100000; 48 | var maxPtr = (nuint)200000; 49 | var pageSize = (nuint)200000; 50 | var bufSize = (nuint)30000; 51 | 52 | // Page is bigger than min-max range 53 | var pageStart = minPtr - 50000; 54 | var pageEnd = pageStart + pageSize; 55 | 56 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4])[0]; 57 | Assert.True(result > 0); 58 | } 59 | 60 | [Fact] 61 | public void RoundUpFromPageMin() 62 | { 63 | var minPtr = (nuint)1; 64 | var maxPtr = (nuint)200000; 65 | var pageSize = (nuint)100000; 66 | var bufSize = (nuint)30000; 67 | 68 | // Page start is not aligned with allocation granularity 69 | var pageStart = minPtr + 5000; // Not multiple of 65536 70 | var pageEnd = pageStart + pageSize; 71 | 72 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4])[0]; 73 | result.Should().Be(Mathematics.RoundUp(pageStart, _allocationGranularity)); 74 | } 75 | 76 | [Fact] 77 | public void RoundDownFromPtrMax() 78 | { 79 | var minPtr = (nuint)10000; 80 | var maxPtr = (nuint)200000; 81 | var pageSize = (nuint)1000000; 82 | var bufSize = (nuint)30000; 83 | 84 | // Max pointer is not aligned with allocation granularity 85 | maxPtr -= 5000; // Not multiple of 65536 86 | 87 | // Page start is aligned with allocation granularity 88 | var pageStart = (nuint)80000; 89 | var pageEnd = pageStart + pageSize; 90 | 91 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4])[0]; 92 | result.Should().Be(Mathematics.RoundDown(maxPtr - bufSize, _allocationGranularity)); 93 | } 94 | 95 | [Fact] 96 | public void RoundDownFromPageMax() 97 | { 98 | var minPtr = (nuint)1; 99 | var maxPtr = (nuint)200000; 100 | var pageSize = (nuint)120000; 101 | var bufSize = (nuint)30000; 102 | 103 | // Page end is not aligned with allocation granularity 104 | var pageStart = minPtr; 105 | var pageEnd = pageStart + pageSize - 5000; // Not multiple of 65536 106 | 107 | var result = GetPossibleBufferAddresses(minPtr, maxPtr, pageStart, pageEnd, bufSize, _allocationGranularity, new nuint[4])[0]; 108 | result.Should().Be(Mathematics.RoundDown(pageEnd - bufSize, _allocationGranularity)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/BufferLocatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Reloaded.Memory.Buffers.Internal; 4 | using Reloaded.Memory.Buffers.Structs.Internal; 5 | using Reloaded.Memory.Buffers.Utilities; 6 | using Xunit; 7 | 8 | namespace Reloaded.Memory.Buffers.Tests.Tests; 9 | 10 | public unsafe class BufferLocatorTests 11 | { 12 | // Reset the state between tests. 13 | public BufferLocatorTests() => LocatorHeaderFinder.Reset(); 14 | 15 | [Fact] 16 | public void Find_ShouldReturnAddress_WhenPreviouslyExists() 17 | { 18 | // Arrange 19 | using var map = LocatorHeaderFinder.OpenOrCreateMemoryMappedFile(); 20 | ((LocatorHeader*)map.Data)->Initialize(map.Length); 21 | 22 | // Act 23 | LocatorHeader* address = LocatorHeaderFinder.Find(out LocatorHeaderFinder.FindReason reason); 24 | 25 | // Assert 26 | ((nuint)address).Should().NotBeNull(); 27 | reason.Should().Be(LocatorHeaderFinder.FindReason.PreviouslyExisted); 28 | LocatorHeaderFinder.Reset(); 29 | } 30 | 31 | [Fact] 32 | public void Find_ShouldReturnAddress_WhenCreated() 33 | { 34 | // Act 35 | LocatorHeader* address = LocatorHeaderFinder.Find(out LocatorHeaderFinder.FindReason reason); 36 | 37 | // Assert 38 | ((nuint)address).Should().NotBeNull(); 39 | reason.Should().Be(LocatorHeaderFinder.FindReason.Created); 40 | } 41 | 42 | [Fact] 43 | public void Find_ShouldInitializeCorrectly_WhenCreated() 44 | { 45 | // Act 46 | LocatorHeader* address = LocatorHeaderFinder.Find(out LocatorHeaderFinder.FindReason _); 47 | 48 | // Assert 49 | var expected = Math.Round((Cached.GetAllocationGranularity() - sizeof(LocatorHeader)) / (float) LocatorHeader.LengthOfPreallocatedChunks); 50 | address->NumItems.Should().Be((byte)expected); 51 | 52 | for (int x = 0; x < address->NumItems; x++) 53 | { 54 | var item = address->GetItem(x); 55 | item->Position.Should().Be(0); 56 | item->BaseAddress.Should().NotBeNull(); 57 | item->Size.Should().BeGreaterThan(0); 58 | } 59 | } 60 | 61 | [Fact] 62 | public void Find_ShouldReturnCachedAddress_WhenCalledTwice() 63 | { 64 | // Act 65 | LocatorHeader* firstAddress = LocatorHeaderFinder.Find(out LocatorHeaderFinder.FindReason firstReason); 66 | LocatorHeader* secondAddress = LocatorHeaderFinder.Find(out LocatorHeaderFinder.FindReason secondReason); 67 | 68 | // Assert 69 | ((nuint)firstAddress).Should().NotBeNull(); 70 | firstReason.Should().Be(LocatorHeaderFinder.FindReason.Created); 71 | 72 | ((nuint)secondAddress).Should().NotBeNull(); 73 | secondReason.Should().Be(LocatorHeaderFinder.FindReason.Cached); 74 | 75 | ((nuint)firstAddress).Should().Be((nuint)secondAddress); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/BufferTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FluentAssertions; 4 | using Reloaded.Memory.Buffers.Internal; 5 | using Reloaded.Memory.Buffers.Structs.Params; 6 | using Reloaded.Memory.Buffers.Utilities; 7 | using Xunit; 8 | 9 | namespace Reloaded.Memory.Buffers.Tests.Tests; 10 | 11 | public class BufferTests 12 | { 13 | [Fact] 14 | public void AllocatePrivateMemory_In2GiB() 15 | { 16 | // Not supported on OSX. 17 | if (Polyfills.IsMacOS()) 18 | return; 19 | 20 | // Arrange 21 | var settings = new BufferAllocatorSettings() 22 | { 23 | MinAddress = 0, 24 | MaxAddress = int.MaxValue, 25 | Size = 4096, 26 | TargetProcess = Process.GetCurrentProcess() 27 | }; 28 | 29 | using var item = Buffers.AllocatePrivateMemory(settings); 30 | item.BaseAddress.Should().NotBeNull(); 31 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 32 | } 33 | 34 | [Fact] 35 | public void AllocatePrivateMemory_UpToMaxAddress() 36 | { 37 | // Arrange 38 | var settings = new BufferAllocatorSettings() 39 | { 40 | MinAddress = Cached.GetMaxAddress() / 2, 41 | MaxAddress = Cached.GetMaxAddress(), 42 | Size = 4096, 43 | TargetProcess = Process.GetCurrentProcess() 44 | }; 45 | 46 | using var item = Buffers.AllocatePrivateMemory(settings); 47 | item.BaseAddress.Should().NotBeNull(); 48 | item.Size.Should().BeGreaterOrEqualTo(settings.Size); 49 | } 50 | 51 | [Fact] 52 | public unsafe void GetBuffer_Baseline() 53 | { 54 | // Arrange 55 | var settings = new BufferSearchSettings() 56 | { 57 | MinAddress = Cached.GetMaxAddress() / 2, 58 | MaxAddress = Cached.GetMaxAddress(), 59 | Size = 4096 60 | }; 61 | 62 | // In case left over uninitialized by previous test. 63 | LocatorHeaderFinder.Reset(); 64 | using var item = Buffers.GetBuffer(settings); 65 | ((nuint)item.Item).Should().NotBeNull(); 66 | item.Item->Size.Should().BeGreaterOrEqualTo(settings.Size); 67 | } 68 | 69 | [Fact] 70 | public unsafe void GetBuffer_WithProximity() 71 | { 72 | const int size = 4096; 73 | ulong baseAddress = Cached.GetMaxAddress() - int.MaxValue; 74 | 75 | // In case left over uninitialized by previous test. 76 | LocatorHeaderFinder.Reset(); 77 | using var item = Buffers.GetBuffer(BufferSearchSettings.FromProximity(int.MaxValue, (nuint)baseAddress, size)); 78 | ((nuint)item.Item).Should().NotBeNull(); 79 | item.Item->Size.Should().BeGreaterOrEqualTo(size); 80 | 81 | var offset = Math.Abs((long)(item.Item->BaseAddress - baseAddress)); 82 | offset.Should().BeLessThan(int.MaxValue); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/Structs/LocatorItemTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using FluentAssertions; 4 | using Reloaded.Memory.Buffers.Structs.Internal; 5 | using Xunit; 6 | 7 | namespace Reloaded.Memory.Buffers.Tests.Tests.Structs; 8 | 9 | [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyleForMemberAccess")] 10 | public class LocatorItemTests 11 | { 12 | [Fact] 13 | public unsafe void IsCorrectSize() 14 | { 15 | var expected = IntPtr.Size == 4 ? 16 : 20; 16 | sizeof(LocatorItem).Should().Be(expected); 17 | } 18 | 19 | [Fact] 20 | public void TryLock_ShouldLockItem_WhenLockIsAvailable() 21 | { 22 | // Arrange 23 | var item = new LocatorItem(); 24 | 25 | // Act 26 | var result = item.TryLock(); 27 | 28 | // Assert 29 | result.Should().BeTrue(); 30 | item.IsTaken.Should().BeTrue(); 31 | } 32 | 33 | [Fact] 34 | public void TryLock_ShouldNotLockItem_WhenLockIsAlreadyAcquired() 35 | { 36 | // Arrange 37 | var item = new LocatorItem(); 38 | item.TryLock(); 39 | 40 | // Act 41 | var result = item.TryLock(); 42 | 43 | // Assert 44 | result.Should().BeFalse(); 45 | item.IsTaken.Should().BeTrue(); 46 | } 47 | 48 | [Fact] 49 | public void Lock_ShouldAcquireLock_WhenLockIsAvailable() 50 | { 51 | // Arrange 52 | var item = new LocatorItem(); 53 | 54 | // Act 55 | item.Lock(); 56 | 57 | // Assert 58 | item.IsTaken.Should().BeTrue(); 59 | } 60 | 61 | [Fact] 62 | public void Unlock_ShouldReleaseLock_WhenItemIsLocked() 63 | { 64 | // Arrange 65 | var item = new LocatorItem(); 66 | item.Lock(); 67 | 68 | // Act 69 | item.Unlock(); 70 | 71 | // Assert 72 | item.IsTaken.Should().BeFalse(); 73 | } 74 | 75 | #if DEBUG 76 | // Debug only feature. 77 | [Fact] 78 | public void Unlock_ShouldThrowException_WhenItemIsNotLocked() 79 | { 80 | // Arrange 81 | var item = new LocatorItem(); 82 | 83 | // Act 84 | var action = new Action(() => item.Unlock()); 85 | 86 | // Assert 87 | action.Should().Throw() 88 | .WithMessage("Attempted to unlock a LocatorItem that wasn't locked"); 89 | } 90 | #endif 91 | 92 | [Theory] 93 | [InlineData(0, 0, 0)] // Case when BaseAddress is 0 94 | [InlineData(10, 20, 10)] // Case when BaseAddress is not 0 95 | public void MinAddress_ShouldReturnBaseAddress_WhenCalled(int baseAddress, uint size, int expected) 96 | { 97 | // Arrange 98 | var item = new LocatorItem { BaseAddress = (nuint)baseAddress, Size = size }; 99 | 100 | // Act 101 | var result = item.MinAddress; 102 | 103 | // Assert 104 | result.Should().Be((nuint)expected); 105 | } 106 | 107 | [Theory] 108 | [InlineData(0, 0, 0)] // Case when BaseAddress is 0 109 | [InlineData(10, 20, 30)] // Case when BaseAddress is not 0 110 | public void MaxAddress_ShouldReturnSumOfBaseAddressAndSize_WhenCalled(int baseAddress, uint size, int expected) 111 | { 112 | // Arrange 113 | var item = new LocatorItem { BaseAddress = (nuint)baseAddress, Size = size }; 114 | 115 | // Act 116 | var result = item.MaxAddress; 117 | 118 | // Assert 119 | result.Should().Be((nuint)expected); 120 | } 121 | 122 | [Theory] 123 | [InlineData(50, 100, 200, 50, true)] // size needed is available 124 | [InlineData(50, 100, 200, 80, false)] // size needed is not available 125 | [InlineData(50, 100, 200, 300, false)] // size needed is beyond maxAddress 126 | [InlineData(0, 100, 150, 0, true)] // no size needed, and at start 127 | public void CanUse_ShouldReturnExpectedResult(int position, uint baseAddress, uint maxAddress, uint size, bool expected) 128 | { 129 | // Arrange 130 | var locatorItem = new LocatorItem 131 | { 132 | BaseAddress = baseAddress, 133 | Position = (uint)position, 134 | Size = maxAddress - baseAddress, 135 | }; 136 | 137 | // Act 138 | bool result = locatorItem.CanUse(size, baseAddress, maxAddress); 139 | 140 | // Assert 141 | result.Should().Be(expected); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/Structs/SafeLocatorItemTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Reloaded.Memory.Buffers.Structs; 4 | using Reloaded.Memory.Buffers.Structs.Internal; 5 | using Xunit; 6 | 7 | namespace Reloaded.Memory.Buffers.Tests.Tests.Structs; 8 | 9 | public unsafe class SafeLocatorItemTests 10 | { 11 | [Fact] 12 | public void AppendData_BufferShouldContainData() 13 | { 14 | const int length = 10; 15 | 16 | // Arrange 17 | var buffer = stackalloc byte[length]; 18 | var locatorItem = new LocatorItem((nuint)buffer, length); 19 | var safeLocatorItem = new SafeLocatorItem(&locatorItem); 20 | var testData = new Span(new byte[]{1, 2, 3, 4, 5}); 21 | 22 | // Act 23 | safeLocatorItem.Append(testData); 24 | 25 | // Assert 26 | var result = new Span(buffer, length).Slice(0, testData.Length); 27 | Assert.True(result.SequenceEqual(testData)); 28 | } 29 | 30 | [Fact] 31 | public void AppendBlittableData_ShouldBeAtCorrectAddress() 32 | { 33 | const int length = 4; 34 | 35 | // Arrange 36 | var buffer = stackalloc byte[length]; 37 | var locatorItem = new LocatorItem((nuint)buffer, length); 38 | var safeLocatorItem = new SafeLocatorItem(&locatorItem); 39 | int testData = 123456; 40 | 41 | // Act 42 | nuint address = safeLocatorItem.Append(testData); 43 | 44 | // Assert 45 | int* ptr = (int*)address; 46 | (*ptr).Should().Be(testData); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/Utilities/AddressRangeTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Reloaded.Memory.Buffers.Utilities; 3 | using Xunit; 4 | 5 | namespace Reloaded.Memory.Buffers.Tests.Tests.Utilities; 6 | 7 | public class AddressRangeTests 8 | { 9 | [Fact] 10 | public void Contains_ShouldBeTrue_WhenOtherRangeIsInside() 11 | { 12 | var range = new AddressRange(100, 200); 13 | var otherRange = new AddressRange(120, 180); 14 | 15 | range.Contains(otherRange).Should().BeTrue(); 16 | } 17 | 18 | [Fact] 19 | public void Contains_ShouldBeFalse_WhenOtherRangeIsNotInside() 20 | { 21 | var range = new AddressRange(100, 200); 22 | var otherRange = new AddressRange(80, 220); 23 | 24 | range.Contains(otherRange).Should().BeFalse(); 25 | } 26 | 27 | [Fact] 28 | public void Overlaps_ShouldBeTrue_WhenOtherRangeOverlaps() 29 | { 30 | var range = new AddressRange(100, 200); 31 | var otherRange = new AddressRange(150, 220); 32 | 33 | range.Overlaps(otherRange).Should().BeTrue(); 34 | } 35 | 36 | [Fact] 37 | public void Overlaps_ShouldBeFalse_WhenOtherRangeDoesNotOverlap() 38 | { 39 | var range = new AddressRange(100, 200); 40 | var otherRange = new AddressRange(300, 400); 41 | 42 | range.Overlaps(otherRange).Should().BeFalse(); 43 | } 44 | 45 | [Fact] 46 | public void Overlaps_ShouldBeTrue_WhenRangesAreSame() 47 | { 48 | var range = new AddressRange(100, 200); 49 | var otherRange = new AddressRange(100, 200); 50 | 51 | range.Overlaps(otherRange).Should().BeTrue(); 52 | } 53 | 54 | [Fact] 55 | public void Overlaps_ShouldBeTrue_WhenOneRangeIsFullyInsideOther() 56 | { 57 | var range = new AddressRange(100, 200); 58 | var otherRange = new AddressRange(120, 180); 59 | 60 | range.Overlaps(otherRange).Should().BeTrue(); 61 | } 62 | 63 | [Fact] 64 | public void Overlaps_ShouldBeTrue_WhenRangesAreAdjacent() 65 | { 66 | var range = new AddressRange(100, 200); 67 | var otherRange = new AddressRange(200, 300); 68 | 69 | range.Overlaps(otherRange).Should().BeTrue(); 70 | } 71 | 72 | [Fact] 73 | public void Overlaps_ShouldBeTrue_WhenOtherRangeStartsInsideRange() 74 | { 75 | var range = new AddressRange(100, 200); 76 | var otherRange = new AddressRange(150, 250); 77 | 78 | range.Overlaps(otherRange).Should().BeTrue(); 79 | } 80 | 81 | [Fact] 82 | public void Overlaps_ShouldBeTrue_WhenOtherRangeEndsInsideRange() 83 | { 84 | var range = new AddressRange(100, 200); 85 | var otherRange = new AddressRange(50, 150); 86 | 87 | range.Overlaps(otherRange).Should().BeTrue(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Tests/Utilities/LinuxMapParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Reloaded.Memory.Buffers.Native.Linux; 4 | using Reloaded.Memory.Buffers.Utilities; 5 | using Xunit; 6 | // ReSharper disable RedundantCast 7 | 8 | namespace Reloaded.Memory.Buffers.Tests.Tests.Utilities; 9 | 10 | #pragma warning disable CS8778 // Constant may overflow at runtime 11 | public unsafe class LinuxMapParserTests 12 | { 13 | [Fact] 14 | public void GetFreeRegions_WithNoGap() 15 | { 16 | var regions = new MemoryMapEntry[] 17 | { 18 | new() { StartAddress = 0, EndAddress = 10 }, 19 | new() { StartAddress = 10, EndAddress = 20 }, 20 | new() { StartAddress = 20, EndAddress = Cached.GetMaxAddress() } 21 | }; 22 | 23 | var freeRegions = LinuxMapParser.GetFreeRegions(regions); 24 | freeRegions.Should().BeEmpty(); 25 | } 26 | 27 | [Fact] 28 | public void GetFreeRegions_SingleGap() 29 | { 30 | var regions = new MemoryMapEntry[] 31 | { 32 | new() { StartAddress = 0, EndAddress = 10 }, 33 | new() { StartAddress = 10, EndAddress = 20 }, 34 | new() { StartAddress = 30, EndAddress = Cached.GetMaxAddress() } 35 | }; 36 | 37 | var freeRegions = LinuxMapParser.GetFreeRegions(regions); 38 | freeRegions.Should().HaveCount(1); 39 | freeRegions[0].StartAddress.Should().Be((nuint)20); 40 | freeRegions[0].EndAddress.Should().Be((nuint)29); 41 | } 42 | 43 | [Fact] 44 | public void GetFreeRegions_Multiple_Gaps() 45 | { 46 | var regions = new MemoryMapEntry[] 47 | { 48 | new() { StartAddress = 0, EndAddress = 10 }, 49 | new() { StartAddress = 20, EndAddress = 30 }, 50 | new() { StartAddress = 40, EndAddress = Cached.GetMaxAddress() } 51 | }; 52 | 53 | var freeRegions = LinuxMapParser.GetFreeRegions(regions); 54 | 55 | freeRegions.Should().HaveCount(2); 56 | freeRegions[0].StartAddress.Should().Be((nuint)10); 57 | freeRegions[0].EndAddress.Should().Be((nuint)19); 58 | freeRegions[1].StartAddress.Should().Be((nuint)30); 59 | freeRegions[1].EndAddress.Should().Be((nuint)39); 60 | } 61 | 62 | [Fact] 63 | public void ParseMemoryMapEntry_ValidLine_ReturnsCorrectEntries() 64 | { 65 | if (sizeof(nuint) < 8) return; 66 | 67 | var line = "7f9c89991000-7f9c89993000 r--p 00000000 08:01 3932177 /path/to/file"; 68 | var result = LinuxMapParser.ParseMemoryMapEntry(line); 69 | 70 | // Replace the numbers with whatever you expect the result to be 71 | result.StartAddress.Should().Be((nuint)0x7f9c89991000); 72 | result.EndAddress.Should().Be((nuint)0x7f9c89993000); 73 | } 74 | 75 | [Fact] 76 | public void ParseMemoryMapEntry_ValidLine_ReturnsCorrectEntry() 77 | { 78 | if (sizeof(nuint) < 8) return; 79 | 80 | var line = "7f9c89991000-7f9c89993000 r--p 00000000 08:01 3932177 /path/to/file"; 81 | var result = LinuxMapParser.ParseMemoryMapEntry(line); 82 | 83 | // Replace the numbers with whatever you expect the result to be 84 | result.StartAddress.Should().Be((nuint)0x7f9c89991000); 85 | result.EndAddress.Should().Be((nuint)0x7f9c89993000); 86 | } 87 | 88 | [Fact] 89 | public void ParseMemoryMapEntry_InvalidLine_ThrowsException() 90 | { 91 | if (sizeof(nuint) < 8) return; 92 | 93 | var line = "Invalid line"; 94 | Action act = () => LinuxMapParser.ParseMemoryMapEntry(line); 95 | act.Should().Throw().WithMessage("Invalid Memory Map Entry"); 96 | } 97 | 98 | [Fact] 99 | public void ParseMemoryMap_ValidLines_ReturnsCorrectEntries() 100 | { 101 | if (sizeof(nuint) < 8) return; 102 | 103 | var lines = new[] { 104 | "7f9c89991000-7f9c89993000 r--p 00000000 08:01 3932177 /path/to/file", 105 | "7f9c89994000-7f9c89995000 r--p 00000000 08:01 3932178 /path/to/file" 106 | }; 107 | var result = LinuxMapParser.ParseMemoryMap(lines); 108 | 109 | result.Length.Should().Be(2); 110 | result.Span[0].StartAddress.Should().Be((nuint)0x7f9c89991000); 111 | result.Span[0].EndAddress.Should().Be((nuint)0x7f9c89993000); 112 | result.Span[1].StartAddress.Should().Be((nuint)0x7f9c89994000); 113 | result.Span[1].EndAddress.Should().Be((nuint)0x7f9c89995000); 114 | } 115 | 116 | [Fact] 117 | public void ParseMemoryMapEntry_ValidLine_ReturnsCorrectEntry_32Bit() 118 | { 119 | var line = "7f899100-7f899300 r--p 00000000 08:01 3932177 /path/to/file"; 120 | var result = LinuxMapParser.ParseMemoryMapEntry(line); 121 | 122 | // Replace the numbers with whatever you expect the result to be 123 | result.StartAddress.Should().Be((nuint)0x7f899100); 124 | result.EndAddress.Should().Be((nuint)0x7f899300); 125 | } 126 | 127 | [Fact] 128 | public void ParseMemoryMapEntry_InvalidLine_ThrowsException_32Bit() 129 | { 130 | var line = "Invalid line"; 131 | Action act = () => LinuxMapParser.ParseMemoryMapEntry(line); 132 | act.Should().Throw().WithMessage("Invalid Memory Map Entry"); 133 | } 134 | 135 | [Fact] 136 | public void ParseMemoryMap_ValidLines_ReturnsCorrectEntries_32Bit() 137 | { 138 | var lines = new[] { 139 | "7f899100-7f899300 r--p 00000000 08:01 3932177 /path/to/file", 140 | "7f899400-7f899500 r--p 00000000 08:01 3932178 /path/to/file" 141 | }; 142 | var result = LinuxMapParser.ParseMemoryMap(lines); 143 | 144 | result.Length.Should().Be(2); 145 | result.Span[0].StartAddress.Should().Be((nuint)0x7f899100); 146 | result.Span[0].EndAddress.Should().Be((nuint)0x7f899300); 147 | result.Span[1].StartAddress.Should().Be((nuint)0x7f899400); 148 | result.Span[1].EndAddress.Should().Be((nuint)0x7f899500); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Utilities/PackingTestHelpers.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | namespace Reloaded.Memory.Buffers.Tests.Utilities; 4 | 5 | /// 6 | /// Helpers for packing tests. 7 | /// 8 | public class PackingTestHelpers 9 | { 10 | public static void TestPackedProperties( 11 | ref TStruct instance, 12 | SetPropertyByRefDelegate setProperty1, 13 | GetPropertyByRefDelegate getProperty1, 14 | SetPropertyByRefDelegate setProperty2, 15 | GetPropertyByRefDelegate getProperty2, 16 | long value1, 17 | long value2) where TStruct : struct 18 | { 19 | // Assert Basic Packing 20 | setProperty1(ref instance, value1); 21 | getProperty1(ref instance).Should().Be(value1); 22 | 23 | setProperty2(ref instance, value2); 24 | getProperty2(ref instance).Should().Be(value2); 25 | 26 | // Verify Property1 and Property2 can be packed 27 | 28 | // Now verify Property2 didn't override Property1 29 | getProperty1(ref instance).Should().Be(value1); 30 | 31 | // Now write Property1 and ensure Property2 is unmodified 32 | setProperty1(ref instance, value1); 33 | getProperty2(ref instance).Should().Be(value2); 34 | } 35 | 36 | public static void AssertSizeBits( 37 | ref TStruct instance, 38 | SetPropertyByRefDelegate setProperty, 39 | GetPropertyByRefDelegate getProperty, 40 | int numBits) where TStruct : struct 41 | { 42 | // Test claimed numbits. 43 | foreach (var testValue in Permutations.GetBitPackingOverlapTestValues(numBits)) 44 | { 45 | setProperty(ref instance, testValue); 46 | getProperty(ref instance).Should().Be(testValue); 47 | } 48 | 49 | // Numbits + 1 should overflow. 50 | var overflowValue = Permutations.GetBitPackingOverlapTestValue(numBits + 1); 51 | setProperty(ref instance, overflowValue); 52 | getProperty(ref instance).Should().NotBe(overflowValue); 53 | } 54 | 55 | public delegate void SetPropertyByRefDelegate(ref TStruct instance, TValue value) 56 | where TStruct : struct; 57 | 58 | public delegate TValue GetPropertyByRefDelegate(ref TStruct instance) where TStruct : struct; 59 | } 60 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/Utilities/Permutations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Reloaded.Memory.Buffers.Tests.Utilities; 5 | 6 | public static class Permutations 7 | { 8 | /// 9 | /// Returns values for bit packing tests.
10 | /// This function returns values from numBits 0 to such that:

11 | /// Value 0: 0b1
12 | /// Value 1: 0b11
13 | /// Value 2: 0b111
14 | /// Value 3: 0b1111
15 | /// etc.
16 | /// These values are used for testing individual bit packed values do not overlap. 17 | ///
18 | public static IEnumerable GetBitPackingOverlapTestValues(int maxBits) 19 | { 20 | long value = 1; 21 | for (var x = 0; x < maxBits; x++) 22 | { 23 | yield return value; 24 | value <<= 1; 25 | value |= 1; 26 | } 27 | } 28 | 29 | /// 30 | /// Gets the value that would appear at index of 31 | /// . 32 | /// 33 | public static long GetBitPackingOverlapTestValue(int numBits) 34 | { 35 | long value = 1; 36 | for (var x = 0; x < numBits - 1; x++) 37 | { 38 | value <<= 1; 39 | value |= 1; 40 | } 41 | 42 | return value; 43 | } 44 | 45 | /// 46 | /// Retrieves all permutations of a given collection. 47 | /// 48 | public static IEnumerable GetPermutations(this IEnumerable elements) 49 | { 50 | List elementList = elements.ToList(); 51 | var indexList = Enumerable.Range(0, elementList.Count).ToArray(); 52 | 53 | yield return elementList.ToArray(); 54 | while (true) 55 | { 56 | var i = elementList.Count - 1; 57 | while (i > 0 && indexList[i - 1] >= indexList[i]) 58 | i--; 59 | 60 | if (i <= 0) 61 | break; 62 | 63 | var j = elementList.Count - 1; 64 | while (indexList[j] <= indexList[i - 1]) 65 | j--; 66 | 67 | Swap(indexList, i - 1, j); 68 | j = elementList.Count - 1; 69 | while (i < j) 70 | { 71 | Swap(indexList, i, j); 72 | i++; 73 | j--; 74 | } 75 | 76 | yield return indexList.Select(x => elementList[x]).ToArray(); 77 | } 78 | } 79 | 80 | private static void Swap(T[] array, int i, int j) => (array[i], array[j]) = (array[j], array[i]); 81 | } 82 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "parallelizeAssembly": false, 3 | "parallelizeTestCollections": false, 4 | "maxParallelThreads": 1 5 | } 6 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32519.111 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Memory.Buffers.Tests", "Reloaded.Memory.Buffers.Tests\Reloaded.Memory.Buffers.Tests.csproj", "{7F503240-976D-44EF-9DCB-5C491EDB8A04}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Memory.Buffers", "Reloaded.Memory.Buffers\Reloaded.Memory.Buffers.csproj", "{63205E76-CB23-4EA8-B58B-71A911EF3788}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solutionItems", ".solutionItems", "{163B7E2D-3AC3-4074-9A7C-9E050D67AA33}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\.github\workflows\build-and-publish.yml = ..\.github\workflows\build-and-publish.yml 13 | ..\mkdocs.yml = ..\mkdocs.yml 14 | ..\README.md = ..\README.md 15 | ..\LICENSE = ..\LICENSE 16 | Reloaded.Project.Configurations\FixUndeclaredAPIs.ps1 = Reloaded.Project.Configurations\FixUndeclaredAPIs.ps1 17 | EndProjectSection 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.ActiveCfg = Debug|x64 26 | {7F503240-976D-44EF-9DCB-5C491EDB8A04}.Debug|Any CPU.Build.0 = Debug|x64 27 | {7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.ActiveCfg = Release|x64 28 | {7F503240-976D-44EF-9DCB-5C491EDB8A04}.Release|Any CPU.Build.0 = Release|x64 29 | {63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {63205E76-CB23-4EA8-B58B-71A911EF3788}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {63205E76-CB23-4EA8-B58B-71A911EF3788}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {AC37BD2F-63C3-4D66-A000-DB108F4709F4} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Buffers.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Exceptions; 2 | using Reloaded.Memory.Buffers.Internal; 3 | using Reloaded.Memory.Buffers.Structs; 4 | using Reloaded.Memory.Buffers.Structs.Internal; 5 | using Reloaded.Memory.Buffers.Structs.Params; 6 | 7 | namespace Reloaded.Memory.Buffers; 8 | 9 | /// 10 | /// Provides the high level API for Reloaded.Memory.Buffers. 11 | /// 12 | public static class Buffers 13 | { 14 | /// 15 | /// Allocates some memory with user specified settings.
16 | /// The allocated memory is for your use only. 17 | ///
18 | /// Settings with which to allocate the memory. 19 | /// Information about the recently made allocation. 20 | /// 21 | /// Allocating inside another process is only supported on Windows. 22 | /// 23 | public static PrivateAllocation AllocatePrivateMemory(BufferAllocatorSettings settings) 24 | { 25 | LocatorItem alloc = BufferAllocator.Allocate(settings); 26 | return new PrivateAllocation(alloc.BaseAddress, alloc.Size, settings.TargetProcess); 27 | } 28 | 29 | /// 30 | /// Gets a buffer with user specified requirements. 31 | /// 32 | /// Settings with which to allocate the memory. 33 | /// 34 | /// Item allowing you to write to the buffer.
35 | /// Make sure you dispose it, by using `using` statement. 36 | ///
37 | /// Allocating inside another process is only supported on Windows. 38 | /// 39 | /// Memory cannot be allocated within the needed constraints when there 40 | /// is no existing suitable buffer. 41 | /// 42 | public static unsafe SafeLocatorItem GetBuffer(BufferSearchSettings settings) => GetBufferRecursive(settings, LocatorHeaderFinder.Find()); 43 | 44 | private static unsafe SafeLocatorItem GetBufferRecursive(BufferSearchSettings settings, LocatorHeader* locator) 45 | { 46 | SafeLocatorItem? item = 47 | locator->GetFirstAvailableItemLocked(settings.Size, settings.MinAddress, settings.MaxAddress); 48 | if (item != null) 49 | return item.Value; 50 | 51 | if (locator->TryAllocateItem(settings.Size, settings.MinAddress, settings.MaxAddress, out item)) 52 | return item.Value; 53 | 54 | return GetBufferRecursive(settings, locator->GetNextLocator()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Exceptions/MemoryBufferAllocationException.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InterpolatedStringExpressionIsNotIFormattable 2 | 3 | using JetBrains.Annotations; 4 | 5 | namespace Reloaded.Memory.Buffers.Exceptions; 6 | 7 | /// 8 | /// This is an exception thrown when memory allocation of a specified number of bytes in a specified address range fails. 9 | /// 10 | [PublicAPI] 11 | public class MemoryBufferAllocationException : Exception 12 | { 13 | /// 14 | /// Creates a new instance of . 15 | /// 16 | /// The end of the address range used in allocation. 17 | /// Size of the data to allocate in. 18 | /// The start of the address range used in allocation. 19 | public MemoryBufferAllocationException(nuint start, nuint end, int size) 20 | : base($"Failed to allocate {size} bytes in address range {start:X8} - {end:X8}.") { } 21 | 22 | /// 23 | public MemoryBufferAllocationException() { } 24 | 25 | /// 26 | public MemoryBufferAllocationException(string message) : base(message) { } 27 | 28 | /// 29 | public MemoryBufferAllocationException(string message, Exception innerException) : base(message, innerException) { } 30 | } 31 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Exceptions/ThrowHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Reloaded.Memory.Buffers.Exceptions; 4 | 5 | /// 6 | /// Helper class for throwing exceptions. 7 | /// 8 | internal abstract class ThrowHelpers 9 | { 10 | [MethodImpl(MethodImplOptions.NoInlining)] 11 | public static void ThrowPlatformNotSupportedException() 12 | => throw new PlatformNotSupportedException("Operating System in use is not supported."); 13 | 14 | [MethodImpl(MethodImplOptions.NoInlining)] 15 | public static void ThrowExternalAllocationNotSupportedException() 16 | => throw new PlatformNotSupportedException("Allocating memory in external process is not supported on this platform."); 17 | 18 | [MethodImpl(MethodImplOptions.NoInlining)] 19 | public static void ThrowLinuxBadMemoryMapEntry() 20 | => throw new FormatException("Invalid Memory Map Entry"); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Internal/BufferAllocator.Linux.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Exceptions; 2 | using Reloaded.Memory.Buffers.Native.Linux; 3 | using Reloaded.Memory.Buffers.Structs.Internal; 4 | using Reloaded.Memory.Buffers.Structs.Params; 5 | using Reloaded.Memory.Buffers.Utilities; 6 | using Reloaded.Memory.Enums; 7 | using Reloaded.Memory.Native.Unix; 8 | 9 | namespace Reloaded.Memory.Buffers.Internal; 10 | 11 | #pragma warning disable CA1416 // Validate platform compatibility 12 | 13 | /// 14 | /// Windows specific buffer allocator. 15 | /// 16 | internal static partial class BufferAllocator 17 | { 18 | // Devirtualized based on target. 19 | private static LocatorItem AllocateLinux(BufferAllocatorSettings settings) 20 | { 21 | for (int x = 0; x < settings.RetryCount; x++) 22 | { 23 | // Until we get all of the pages. 24 | var regions = LinuxMapParser.GetFreeRegions(settings.TargetProcess); 25 | foreach (MemoryMapEntry region in regions) 26 | { 27 | // Exit if we are done iterating. 28 | if (region.StartAddress > settings.MaxAddress) 29 | break; 30 | 31 | // Add the page and increment address iterator to go to next page. 32 | if (TryAllocateBuffer(region, settings, out var item)) 33 | return item; 34 | } 35 | } 36 | 37 | throw new MemoryBufferAllocationException(settings.MinAddress, settings.MaxAddress, (int)settings.Size); 38 | } 39 | 40 | private static bool TryAllocateBuffer(MemoryMapEntry entry, BufferAllocatorSettings settings, out LocatorItem result) 41 | { 42 | result = default; 43 | 44 | Span results = stackalloc nuint[4]; 45 | foreach (var addr in GetPossibleBufferAddresses(settings.MinAddress, settings.MaxAddress, entry.StartAddress, entry.EndAddress, settings.Size, Cached.GetAllocationGranularity(), results)) 46 | { 47 | // ReSharper disable once RedundantCast 48 | // MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE = 0x100022 49 | nint allocated = Posix.mmap(addr, (nuint)settings.Size, (int)MemoryProtection.ReadWriteExecute, 0x100022, -1, 0); 50 | 51 | if (allocated == -1) 52 | continue; 53 | 54 | // Just in case, for older kernels; 55 | if ((nuint)allocated != addr) 56 | { 57 | Posix.munmap((nuint)allocated, settings.Size); 58 | continue; 59 | } 60 | 61 | result = new LocatorItem((nuint)allocated, settings.Size); 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Internal/BufferAllocator.OSX.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Exceptions; 2 | using Reloaded.Memory.Buffers.Structs.Internal; 3 | using Reloaded.Memory.Buffers.Structs.Params; 4 | using Reloaded.Memory.Buffers.Utilities; 5 | using static Reloaded.Memory.Buffers.Native.OSX.Mach; 6 | 7 | namespace Reloaded.Memory.Buffers.Internal; 8 | 9 | #pragma warning disable CA1416 // Validate platform compatibility 10 | 11 | /// 12 | /// Windows specific buffer allocator. 13 | /// 14 | internal static partial class BufferAllocator 15 | { 16 | // Devirtualized based on target. 17 | private static LocatorItem AllocateOSX(BufferAllocatorSettings settings) 18 | { 19 | var maxAddress = Math.Min(Cached.GetMaxAddress(), settings.MaxAddress); 20 | nuint currentAddress = settings.MinAddress; 21 | 22 | var selfTask = mach_task_self(); 23 | for (int x = 0; x < settings.RetryCount; x++) 24 | { 25 | // Until we get all of the pages. 26 | // ReSharper disable once RedundantCast 27 | foreach (var page in GetFreePages(currentAddress, (nuint)maxAddress, selfTask)) 28 | { 29 | if (TryAllocateBuffer(page.addr, page.size, settings, selfTask, out var item)) 30 | return item; 31 | } 32 | } 33 | 34 | throw new MemoryBufferAllocationException(settings.MinAddress, settings.MaxAddress, (int)settings.Size); 35 | } 36 | 37 | private static List<(nuint addr, nuint size)> GetFreePages(nuint minAddress, nuint maxAddress, nuint selfTask) 38 | { 39 | var result = new List<(nuint addr, nuint size)>(); 40 | uint infoCount = VM_REGION_BASIC_INFO_COUNT; 41 | var currentAddress = minAddress; 42 | 43 | // Until we get all of the pages. 44 | while (currentAddress <= maxAddress) 45 | { 46 | var actualAddress = currentAddress; 47 | nuint availableSize = 0; 48 | int kr = mach_vm_region(selfTask, ref actualAddress, ref availableSize, VM_REGION_BASIC_INFO_64, out vm_region_basic_info_64 _, ref infoCount, out _); 49 | 50 | // KERN_INVALID_ADDRESS, i.e. no more regions. 51 | if (kr == 1) 52 | { 53 | var padding = maxAddress - currentAddress; 54 | if (padding > 0) 55 | result.Add((currentAddress, padding)); 56 | 57 | break; 58 | } 59 | 60 | // Any other error. 61 | if (kr != 0) 62 | break; 63 | 64 | if (actualAddress > currentAddress) 65 | result.Add((currentAddress, actualAddress - currentAddress)); 66 | 67 | currentAddress = actualAddress + availableSize; 68 | } 69 | 70 | return result; 71 | } 72 | 73 | private static unsafe bool TryAllocateBuffer(nuint pageAddress, nuint pageSize, 74 | BufferAllocatorSettings settings, nuint selfTask, out LocatorItem result) 75 | { 76 | result = default; 77 | Span results = stackalloc nuint[4]; 78 | foreach (var addr in GetBufferPointersInPageRange(pageAddress, pageSize, (int)settings.Size, settings.MinAddress, settings.MaxAddress, results)) 79 | { 80 | int kr = mach_vm_allocate(selfTask, (nuint)(&addr), settings.Size, 0); 81 | 82 | if (kr != 0) 83 | continue; 84 | 85 | result = new LocatorItem(addr, settings.Size); 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | private static Span GetBufferPointersInPageRange(nuint pageAddress, nuint pageSize, int bufferSize, nuint minimumPtr, nuint maximumPtr, Span results) 93 | { 94 | nuint pageStart = pageAddress; 95 | nuint pageEnd = pageAddress + pageSize; 96 | int allocationGranularity = Cached.GetAllocationGranularity(); 97 | return GetPossibleBufferAddresses(minimumPtr, maximumPtr, pageStart, pageEnd, (nuint) bufferSize, allocationGranularity, results); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Internal/BufferAllocator.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Exceptions; 2 | using Reloaded.Memory.Buffers.Structs.Internal; 3 | using Reloaded.Memory.Buffers.Structs.Params; 4 | using Reloaded.Memory.Buffers.Utilities; 5 | using Reloaded.Memory.Extensions; 6 | using static Reloaded.Memory.Buffers.Utilities.Mathematics; 7 | 8 | namespace Reloaded.Memory.Buffers.Internal; 9 | 10 | /// 11 | /// Class for allocating buffers between given memory ranges inside the current process. 12 | /// 13 | internal static partial class BufferAllocator 14 | { 15 | /// 16 | /// Allocates a region of memory that satisfies the given parameters. 17 | /// 18 | /// Settings for the memory allocator. 19 | /// Native address of the allocated region. 20 | /// Memory cannot be allocated within the needed constraints. 21 | /// This operation is not supported on the current platform. 22 | internal static LocatorItem Allocate(BufferAllocatorSettings settings) 23 | { 24 | settings.Sanitize(); 25 | if (Polyfills.IsWindows()) 26 | return AllocateWindows(settings); 27 | 28 | if (settings.TargetProcess.Id != Polyfills.GetProcessId()) 29 | ThrowHelpers.ThrowExternalAllocationNotSupportedException(); 30 | 31 | if (Polyfills.IsLinux()) 32 | return AllocateLinux(settings); 33 | 34 | if (Polyfills.IsMacOS()) 35 | return AllocateOSX(settings); 36 | 37 | ThrowHelpers.ThrowPlatformNotSupportedException(); 38 | return default; 39 | } 40 | 41 | internal static Span GetPossibleBufferAddresses(nuint minimumPtr, nuint maximumPtr, nuint pageStart, nuint pageEnd, 42 | nuint bufSize, int allocationGranularity, Span results) 43 | { 44 | // Get range for page and min-max region. 45 | var minMaxRange = new AddressRange(minimumPtr, maximumPtr); 46 | var pageRange = new AddressRange(pageStart, pageEnd); 47 | 48 | // Check if there is any overlap at all. 49 | if (!pageRange.Overlaps(minMaxRange)) 50 | return default; 51 | 52 | // Three possible cases here: 53 | // 1. Page fits entirely inside min-max range and is smaller. 54 | if (bufSize > pageRange.Size) 55 | return default; // does not fit. 56 | 57 | int numItems = 0; 58 | 59 | // Note: We have to test aligned to both page boundaries and min-max range boundaries; 60 | // because, they may not perfectly overlap, e.g. min-max may be way greater than 61 | // page size, so testing from start/end of that will not even overlap with available pages. 62 | // Or the opposite can happen... min-max range may be smaller than page size. 63 | 64 | // 2. Min-max range is inside page, test aligned to page boundaries. 65 | 66 | // Round up from page min. 67 | nuint pageMinAligned = RoundUp(pageRange.StartPointer, allocationGranularity); 68 | var pageMinRange = new AddressRange(pageMinAligned, AddWithOverflowCap(pageMinAligned, bufSize)); 69 | 70 | if (pageRange.Contains(pageMinRange) && minMaxRange.Contains(pageMinRange)) 71 | results.DangerousGetReferenceAt(numItems++) = pageMinRange.StartPointer; 72 | 73 | // Round down from page max. 74 | nuint pageMaxAligned = RoundDown(SubtractWithUnderflowCap(pageRange.EndPointer, bufSize), allocationGranularity); 75 | var pageMaxRange = new AddressRange(pageMaxAligned, pageMaxAligned + bufSize); 76 | 77 | if (pageRange.Contains(pageMaxRange) && minMaxRange.Contains(pageMaxRange)) 78 | results.DangerousGetReferenceAt(numItems++) = pageMaxRange.StartPointer; 79 | 80 | // 3. Min-max range is inside page, test aligned to Min-max range. 81 | 82 | // Round up from ptr min. 83 | nuint ptrMinAligned = RoundUp(minimumPtr, allocationGranularity); 84 | var ptrMinRange = new AddressRange(ptrMinAligned, AddWithOverflowCap(ptrMinAligned, bufSize)); 85 | 86 | if (pageRange.Contains(ptrMinRange) && minMaxRange.Contains(ptrMinRange)) 87 | results.DangerousGetReferenceAt(numItems++) = ptrMinRange.StartPointer; 88 | 89 | // Round down from ptr max. 90 | nuint ptrMaxAligned = RoundDown(SubtractWithUnderflowCap(maximumPtr, bufSize), allocationGranularity); 91 | var ptrMaxRange = new AddressRange(ptrMaxAligned, ptrMaxAligned + bufSize); 92 | 93 | if (pageRange.Contains(ptrMaxRange) && minMaxRange.Contains(ptrMaxRange)) 94 | results.DangerousGetReferenceAt(numItems++) = ptrMaxRange.StartPointer; 95 | 96 | return results.SliceFast(0, numItems); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Internal/LocatorHeaderFinder.Posix.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Reloaded.Memory.Buffers.Native; 3 | using Reloaded.Memory.Buffers.Utilities; 4 | using Posix = Reloaded.Memory.Buffers.Native.Posix; 5 | 6 | namespace Reloaded.Memory.Buffers.Internal; 7 | 8 | /// 9 | /// Class that locates the buffer locator. 10 | /// 11 | internal static partial class LocatorHeaderFinder 12 | { 13 | private static void Cleanup() 14 | { 15 | // Keep the view around forever for other mods/programs/etc. to use. 16 | 17 | // Note: At runtime this is only ever executed once per library instance, so this should be okay. 18 | // On Linux and OSX we need to execute a runtime check to ensure that after a crash, no MMF was left over. 19 | // because the OS does not auto dispose them. 20 | #pragma warning disable RCS1075 21 | #pragma warning disable CA1416 22 | if (Polyfills.IsMacOS() || Polyfills.IsLinux()) 23 | { 24 | CleanupPosix(UnixMemoryMappedFile.BaseDir, (path) => 25 | { 26 | try { File.Delete(path); } 27 | catch (Exception) { /* Ignored */ } 28 | }); 29 | } 30 | #pragma warning restore RCS1075 31 | #pragma warning restore CA1416 32 | } 33 | 34 | private static void CleanupPosix(string mmfDirectory, Action deleteFile) 35 | { 36 | const string memoryMappedFilePrefix = "Reloaded.Memory.Buffers.MemoryBuffer, PID "; 37 | var files = Directory.EnumerateFiles(mmfDirectory); 38 | 39 | foreach (var file in files) 40 | { 41 | var fileName = Path.GetFileName(file); 42 | if (!fileName.StartsWith(memoryMappedFilePrefix)) 43 | continue; 44 | 45 | // Extract PID from the file name 46 | var pidStr = fileName.Substring(memoryMappedFilePrefix.Length); 47 | if (!int.TryParse(pidStr, out var pid)) 48 | continue; 49 | 50 | // Check if the process is still running 51 | if (!IsProcessRunning(pid)) 52 | deleteFile(fileName); 53 | } 54 | } 55 | 56 | private static bool IsProcessRunning(int pid) 57 | { 58 | try 59 | { 60 | Process.GetProcessById(pid); 61 | return true; 62 | } 63 | catch (ArgumentException) 64 | { 65 | // Process is not running 66 | return false; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Internal/LocatorHeaderFinder.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Exceptions; 2 | using Reloaded.Memory.Buffers.Native; 3 | using Reloaded.Memory.Buffers.Structs.Internal; 4 | using Reloaded.Memory.Buffers.Utilities; 5 | 6 | namespace Reloaded.Memory.Buffers.Internal; 7 | 8 | /// 9 | /// Class that locates the buffer locator. 10 | /// 11 | internal static unsafe partial class LocatorHeaderFinder 12 | { 13 | private static LocatorHeader* s_locatorHeaderAddress; 14 | private static IMemoryMappedFile? s_mmf; 15 | private static object _lock = new(); 16 | 17 | /// 18 | /// Retrieves the address of the first locator. 19 | /// 20 | /// Address of the first locator. 21 | internal static LocatorHeader* Find() => Find(out _); 22 | 23 | /// 24 | /// Retrieves the address of the first locator. 25 | /// 26 | /// The reason the element was found. 27 | /// Address of the first locator. 28 | /// This operation is not supported on the current platform. 29 | internal static LocatorHeader* Find(out FindReason reason) 30 | { 31 | if (s_locatorHeaderAddress != (LocatorHeader*)0) 32 | { 33 | reason = FindReason.Cached; 34 | return s_locatorHeaderAddress; 35 | } 36 | 37 | // Create or open the memory-mapped file 38 | lock (_lock) 39 | { 40 | IMemoryMappedFile mmf = OpenOrCreateMemoryMappedFile(); 41 | 42 | // If the MMF previously existed, we need to read the real address from the header, then close 43 | // our mapping. 44 | if (mmf.AlreadyExisted) 45 | { 46 | s_locatorHeaderAddress = ((LocatorHeader*)mmf.Data)->ThisAddress; 47 | reason = FindReason.PreviouslyExisted; 48 | mmf.Dispose(); 49 | return s_locatorHeaderAddress; 50 | } 51 | 52 | Cleanup(); 53 | s_mmf = mmf; 54 | s_locatorHeaderAddress = (LocatorHeader*)mmf.Data; 55 | s_locatorHeaderAddress->Initialize(mmf.Length); 56 | reason = FindReason.Created; 57 | return s_locatorHeaderAddress; 58 | } 59 | } 60 | 61 | internal static IMemoryMappedFile OpenOrCreateMemoryMappedFile() 62 | { 63 | var name = $"/Reloaded.Memory.Buffers.MemoryBuffer, PID {Polyfills.GetProcessId().ToString()}"; 64 | 65 | #pragma warning disable CA1416 66 | if (Polyfills.IsWindows()) 67 | return new WindowsMemoryMappedFile(name, Cached.GetAllocationGranularity()); 68 | 69 | if (Polyfills.IsMacOS() || Polyfills.IsLinux()) 70 | return new UnixMemoryMappedFile(name, Cached.GetAllocationGranularity()); 71 | 72 | ThrowHelpers.ThrowPlatformNotSupportedException(); 73 | return null!; 74 | #pragma warning restore CA1416 75 | } 76 | 77 | /// 78 | /// For test purposes only. 79 | /// Discards any present views. 80 | /// 81 | internal static void Reset() 82 | { 83 | s_locatorHeaderAddress = (LocatorHeader*)0; 84 | s_mmf?.Dispose(); 85 | s_mmf = null; 86 | } 87 | 88 | internal enum FindReason 89 | { 90 | /// 91 | /// Previously found and cached. 92 | /// 93 | Cached, 94 | 95 | /// 96 | /// Memory mapped file was already opened by someone else. 97 | /// 98 | PreviouslyExisted, 99 | 100 | /// 101 | /// Memory mapped file was newly created (nobody made it before). 102 | /// 103 | Created 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/IMemoryMappedFile.cs: -------------------------------------------------------------------------------- 1 | namespace Reloaded.Memory.Buffers.Native; 2 | 3 | internal interface IMemoryMappedFile : IDisposable 4 | { 5 | bool AlreadyExisted { get; } 6 | unsafe byte* Data { get; } 7 | public int Length { get; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/Linux/MemoryMapEntry.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InterpolatedStringExpressionIsNotIFormattable 2 | namespace Reloaded.Memory.Buffers.Native.Linux; 3 | 4 | /// 5 | /// Individual entry in Linux memory map. 6 | /// 7 | internal struct MemoryMapEntry 8 | { 9 | /// 10 | /// Gets or sets the start address of the memory mapping. 11 | /// 12 | public nuint StartAddress { get; set; } 13 | 14 | /// 15 | /// Gets or sets the end address of the memory mapping. 16 | /// 17 | public nuint EndAddress { get; set; } 18 | 19 | /// 20 | /// Returns a string representation of the memory mapping entry. 21 | /// 22 | /// A string representation of the memory mapping entry. 23 | public override string ToString() 24 | => $"Start: 0x{StartAddress:X}, End: 0x{EndAddress:X}"; 25 | } 26 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/OSX/Mach.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Reloaded.Memory.Buffers.Native.OSX; 4 | 5 | // ReSharper disable once PartialTypeWithSinglePart 6 | internal partial class Mach 7 | { 8 | #if NET7_0_OR_GREATER 9 | [LibraryImport("/usr/lib/system/libsystem_kernel.dylib")] 10 | public static partial int mach_vm_region(nuint task, ref nuint address, ref nuint size, int flavor, out vm_region_basic_info_64 basicInfo64, ref uint infoCount, out int objectName); 11 | #else 12 | [DllImport("/usr/lib/system/libsystem_kernel.dylib")] 13 | public static extern int mach_vm_region(nuint task, ref nuint address, ref nuint size, int flavor, out vm_region_basic_info_64 basicInfo64, ref uint infoCount, out int objectName); 14 | #endif 15 | 16 | #if NET7_0_OR_GREATER 17 | [LibraryImport("/usr/lib/system/libsystem_kernel.dylib")] 18 | public static partial int mach_vm_allocate(nuint task, nuint address, nuint size, int anywhere); 19 | #else 20 | [DllImport("/usr/lib/system/libsystem_kernel.dylib")] 21 | public static extern int mach_vm_allocate(nuint task, nuint address, nuint size, int anywhere); 22 | #endif 23 | 24 | #if NET7_0_OR_GREATER 25 | [LibraryImport("/usr/lib/system/libsystem_kernel.dylib")] 26 | public static partial nuint mach_task_self(); 27 | #else 28 | [DllImport("/usr/lib/system/libsystem_kernel.dylib")] 29 | public static extern nuint mach_task_self(); 30 | #endif 31 | 32 | [StructLayout(LayoutKind.Sequential)] 33 | public struct vm_region_basic_info_64 34 | { 35 | public int protection; 36 | public int max_protection; 37 | public uint inheritance; 38 | public int shared; 39 | public int reserved; 40 | public ulong offset; 41 | public int behavior; 42 | public ushort user_wired_count; 43 | } 44 | 45 | public const int VM_REGION_BASIC_INFO_64 = 9; 46 | public const int VM_REGION_BASIC_INFO_COUNT = 9; // sizeof(vm_region_basic_info_64) / sizeof(int) 47 | } 48 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/Posix.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | // ReSharper disable PartialTypeWithSinglePart 3 | 4 | namespace Reloaded.Memory.Buffers.Native; 5 | 6 | internal static partial class Posix 7 | { 8 | #if NET7_0_OR_GREATER 9 | [LibraryImport("libc", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] 10 | public static partial int shm_open(string name, int oflag, int mode); 11 | #else 12 | [DllImport("libc", SetLastError = true)] 13 | public static extern int shm_open(string name, int oflag, int mode); 14 | #endif 15 | #if NET7_0_OR_GREATER 16 | [LibraryImport("libc", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] 17 | public static partial int shm_unlink(string name); 18 | #else 19 | [DllImport("libc", SetLastError = true)] 20 | public static extern int shm_unlink(string name); 21 | #endif 22 | 23 | #if NET7_0_OR_GREATER 24 | [LibraryImport("libc", SetLastError = true)] 25 | public static partial int ftruncate(int fd, long length); 26 | #else 27 | [DllImport("libc", SetLastError = true)] 28 | public static extern int ftruncate(int fd, long length); 29 | #endif 30 | } 31 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/UnixMemoryMappedFile.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.IO.MemoryMappedFiles; 3 | #if NET5_0_OR_GREATER 4 | using System.Runtime.Versioning; 5 | #endif 6 | 7 | namespace Reloaded.Memory.Buffers.Native; 8 | 9 | #if NET5_0_OR_GREATER 10 | [SupportedOSPlatform("macos")] 11 | #endif 12 | [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyleForMemberAccess")] 13 | [SuppressMessage("ReSharper", "PartialTypeWithSinglePart")] 14 | internal class UnixMemoryMappedFile : IMemoryMappedFile 15 | { 16 | public static readonly string BaseDir; 17 | 18 | static UnixMemoryMappedFile() => BaseDir = "/tmp/.reloaded/memory.buffers"; 19 | 20 | public bool AlreadyExisted { get; } 21 | public unsafe byte* Data { get; } 22 | public int Length { get; } 23 | public string FileName { get; } 24 | 25 | private readonly MemoryMappedFile _memoryMappedFile; 26 | private readonly MemoryMappedViewAccessor _view; 27 | private readonly FileStream _stream; 28 | 29 | public unsafe UnixMemoryMappedFile(string name, int length) 30 | { 31 | name = name.TrimStart('/'); 32 | AlreadyExisted = true; 33 | FileName = name; 34 | Length = length; 35 | 36 | // Override or create existing file. 37 | var filePath = Path.Combine(BaseDir, name); 38 | Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); 39 | AlreadyExisted = File.Exists(filePath); 40 | 41 | _stream = new FileStream( 42 | filePath, 43 | FileMode.OpenOrCreate, 44 | FileAccess.ReadWrite, 45 | FileShare.ReadWrite | FileShare.Delete, 46 | length); 47 | 48 | _stream.SetLength(length); 49 | 50 | _memoryMappedFile = MemoryMappedFile.CreateFromFile( 51 | _stream, 52 | null, 53 | length, 54 | MemoryMappedFileAccess.ReadWriteExecute, 55 | HandleInheritability.Inheritable, 56 | true); 57 | 58 | _view = _memoryMappedFile.CreateViewAccessor(0, Length, MemoryMappedFileAccess.ReadWriteExecute); 59 | Data = (byte*)_view.SafeMemoryMappedViewHandle.DangerousGetHandle(); 60 | } 61 | 62 | ~UnixMemoryMappedFile() => Dispose(); 63 | 64 | /// 65 | public void Dispose() 66 | { 67 | _memoryMappedFile.Dispose(); 68 | _view.Dispose(); 69 | _stream.Dispose(); 70 | 71 | if (!AlreadyExisted) 72 | File.Delete(_stream.Name); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Native/WindowsMemoryMappedFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO.MemoryMappedFiles; 2 | #if NET5_0_OR_GREATER 3 | using System.Runtime.Versioning; 4 | #endif 5 | 6 | namespace Reloaded.Memory.Buffers.Native; 7 | 8 | #if NET5_0_OR_GREATER 9 | [SupportedOSPlatform("windows")] 10 | #endif 11 | internal class WindowsMemoryMappedFile : IMemoryMappedFile 12 | { 13 | public bool AlreadyExisted { get; } 14 | public unsafe byte* Data { get; } 15 | public int Length { get; } 16 | public string FileName { get; } 17 | 18 | private readonly MemoryMappedFile _memoryMappedFile; 19 | private readonly MemoryMappedViewAccessor _view; 20 | 21 | public unsafe WindowsMemoryMappedFile(string name, int length) 22 | { 23 | AlreadyExisted = true; 24 | FileName = name; 25 | Length = length; 26 | 27 | try 28 | { 29 | _memoryMappedFile = MemoryMappedFile.OpenExisting(name, MemoryMappedFileRights.ReadWriteExecute); 30 | } 31 | catch (FileNotFoundException) 32 | { 33 | _memoryMappedFile = MemoryMappedFile.CreateNew(name, Length, MemoryMappedFileAccess.ReadWriteExecute); 34 | AlreadyExisted = false; 35 | } 36 | 37 | _view = _memoryMappedFile.CreateViewAccessor(0, Length, MemoryMappedFileAccess.ReadWriteExecute); 38 | Data = (byte*)_view.SafeMemoryMappedViewHandle.DangerousGetHandle(); 39 | } 40 | 41 | /// 42 | public void Dispose() 43 | { 44 | _view.Dispose(); 45 | _memoryMappedFile.Dispose(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/net48/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/net48/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/net5.0/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/net5.0/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/net6.0/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/net6.0/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/net7.0/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/net7.0/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Reloaded-Project/Reloaded.Memory.Buffers/5b15fbad50e26b97a1ec7a4673f1efe407ca53e4/src/Reloaded.Memory.Buffers/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Reloaded.Memory.Buffers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Reloaded.Memory.Buffers 7 | https://github.com/Reloaded-Project/Reloaded.Memory.Buffers 8 | An implementation of efficient, shared, concurrent and permanent storage of objects in unmanaged memory in static, non-changing locations that last the lifetime of a given process. Allows allocating memory between a given minimum and maximum address. Useful stuff for DLL Injection and Hooking. 9 | 3.0.6 10 | Sewer56 11 | Reloaded Memory Buffers Library 12 | Reloaded.Memory.Buffers 13 | 14 | true 15 | Project Reloaded 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Structs/Internal/LocatorItem.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Reloaded.Memory.Buffers.Utilities; 3 | 4 | namespace Reloaded.Memory.Buffers.Structs.Internal; 5 | 6 | /// 7 | /// Individual item in the locator. 8 | /// 9 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 10 | internal struct LocatorItem 11 | { 12 | /// 13 | /// Address of the buffer in memory. 14 | /// 15 | public nuint BaseAddress; 16 | 17 | /// 18 | /// True if the buffer is allocated. 19 | /// 20 | public bool IsAllocated => BaseAddress != 0; 21 | 22 | /// 23 | /// True if this item is locked, else false. 24 | /// 25 | private int _isTaken; 26 | 27 | /// 28 | /// Returns true if the current item is locked, else false. 29 | /// 30 | public bool IsTaken => _isTaken == 1; 31 | 32 | /// 33 | /// Size of the buffer. 34 | /// 35 | public uint Size; 36 | 37 | /// 38 | /// Current position of the buffer. 39 | /// 40 | public uint Position; 41 | 42 | /// 43 | /// Available number of bytes in item. 44 | /// 45 | public uint BytesLeft => Size - Position; 46 | 47 | /// 48 | /// Minimum address of the allocation. 49 | /// 50 | public nuint MinAddress => BaseAddress; 51 | 52 | /// 53 | /// Maximum address of the allocation. 54 | /// 55 | public nuint MaxAddress => BaseAddress + Size; 56 | 57 | /// 58 | /// Creates a new instance of given base address, size and position. 59 | /// 60 | /// The base address. 61 | /// The size. 62 | public LocatorItem(nuint baseAddress, uint size) 63 | { 64 | BaseAddress = baseAddress; 65 | Size = size; 66 | Position = 0; 67 | } 68 | 69 | /// 70 | /// Tries to acquire the lock. 71 | /// 72 | /// True if the lock was successfully acquired, false otherwise. 73 | public bool TryLock() => Interlocked.CompareExchange(ref _isTaken, 1, 0) == 0; 74 | 75 | /// 76 | /// Acquires the lock, blocking until it can do so. 77 | /// 78 | public void Lock() 79 | { 80 | while (!TryLock()) 81 | { 82 | // We're using `SpinWait` to implement a backoff strategy, which can provide better performance 83 | // than a simple busy loop when contention for the lock is high. 84 | Thread.SpinWait(100); 85 | } 86 | } 87 | 88 | /// 89 | /// Unlocks the object in a thread-safe manner. 90 | /// 91 | /// 92 | /// If the buffer is already unlocked, this error is thrown. 93 | /// It is only thrown in debug mode. 94 | /// 95 | public void Unlock() 96 | { 97 | // Set _isTaken to 0 and return the original value. 98 | // ReSharper disable once UnusedVariable 99 | var original = Interlocked.Exchange(ref _isTaken, 0); 100 | 101 | // If the original value was already 0, something went wrong. 102 | #if DEBUG 103 | if (original == 0) 104 | throw new InvalidOperationException("Attempted to unlock a LocatorItem that wasn't locked"); 105 | #endif 106 | } 107 | 108 | /// 109 | /// Determines if this locator item can be used given the constraints. 110 | /// 111 | /// Available bytes between and . 112 | /// Minimum address accepted. 113 | /// Maximum address accepted. 114 | /// If this buffer can be used given the parameters. 115 | public bool CanUse(uint size, nuint minAddress, nuint maxAddress) 116 | { 117 | if (!IsAllocated || BytesLeft < size) 118 | return false; 119 | 120 | // Calculate the start and end positions within the buffer 121 | var startAvailableAddress = BaseAddress + Position; 122 | var endAvailableAddress = Mathematics.AddWithOverflowCap(BaseAddress, Size); 123 | 124 | // Check if the requested memory lies within the remaining buffer and within the specified address range 125 | // If any of the checks fail, the buffer can't be used 126 | // [startAvailableAddress >= minAddress] checks if in range. 127 | // [endAvailableAddress <= maxAddress] checks if in range. 128 | return startAvailableAddress >= minAddress && endAvailableAddress <= maxAddress; 129 | } 130 | 131 | /// 132 | /// Appends the data to this buffer. 133 | /// 134 | /// The data to append to the item. 135 | /// Address of the written data. 136 | /// 137 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer.
138 | /// When returning buffers from the library, the library will ensure there's at least the requested amount of space; 139 | /// so if the total size of your data falls under that space, you are good. 140 | ///
141 | public unsafe nuint Append(Span data) 142 | { 143 | nuint address = BaseAddress + Position; 144 | data.CopyTo(new Span((void*)address, data.Length)); 145 | Position += (uint)data.Length; 146 | return address; 147 | } 148 | 149 | /// 150 | /// Appends the blittable variable to this buffer. 151 | /// 152 | /// Type of the item to write. 153 | /// The item to append to the buffer. 154 | /// Address of the written data. 155 | /// 156 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer.
157 | /// When returning buffers from the library, the library will ensure there's at least the requested amount of space; 158 | /// so if the total size of your data falls under that space, you are good. 159 | ///
160 | public unsafe nuint Append(in T data) where T : unmanaged 161 | { 162 | var address = (T*)(BaseAddress + Position); 163 | *address = data; 164 | return (nuint)address; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Structs/Params/BufferAllocatorSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using JetBrains.Annotations; 3 | using Reloaded.Memory.Buffers.Utilities; 4 | 5 | namespace Reloaded.Memory.Buffers.Structs.Params; 6 | 7 | /// 8 | /// Settings to pass to the buffer allocator. 9 | /// 10 | [PublicAPI] 11 | public struct BufferAllocatorSettings 12 | { 13 | /// 14 | /// Minimum address of the allocation. 15 | /// 16 | public required nuint MinAddress { get; set; } = 0; 17 | 18 | /// 19 | /// Maximum address of the allocation. 20 | /// 21 | public required nuint MaxAddress { get; set; } = Cached.GetMaxAddress(); 22 | 23 | /// 24 | /// Required size of the data. 25 | /// 26 | public required uint Size { get; set; } = 4096; 27 | 28 | /// 29 | /// Process to allocate memory in. 30 | /// 31 | public Process TargetProcess { get; init; } = Cached.GetThisProcess(); 32 | 33 | /// 34 | /// Amount of times library should retry after failing to allocate a region. 35 | /// 36 | /// 37 | /// This is useful when there's high memory pressure, meaning pages become unavailable between the time 38 | /// they are found and the time we try to allocate them. 39 | /// 40 | public int RetryCount { get; set; } = 8; 41 | 42 | /// 43 | /// Whether to use brute force to find a suitable address. 44 | /// 45 | /// 46 | /// This for some reason only ever was needed in FFXIV under Wine; and was contributed in the original library 47 | /// (prior to rewrite) by the Dalamud folks. In Wine and on FFXIV *only*; the regular procedure of trying to allocate 48 | /// returned pages doesn't always work. This is a last ditch workaround for that.
49 | /// This setting is only used on Windows targets today. 50 | ///
51 | public bool BruteForce { get; set; } = true; 52 | 53 | /// 54 | /// Initializes the buffer allocator with default settings. 55 | /// 56 | public BufferAllocatorSettings() { } 57 | 58 | /// 59 | /// Creates settings such that the returned buffer will always be within bytes of . 60 | /// 61 | /// Max proximity (number of bytes) to target. 62 | /// Target address. 63 | /// Size required in the settings. 64 | /// Settings that would satisfy this search. 65 | public static BufferAllocatorSettings FromProximity(nuint proximity, nuint target, nuint size) 66 | { 67 | return new BufferAllocatorSettings() 68 | { 69 | MaxAddress = Mathematics.AddWithOverflowCap(target, proximity), 70 | MinAddress = Mathematics.SubtractWithUnderflowCap(target, proximity), 71 | Size = (uint)size 72 | }; 73 | } 74 | 75 | /// 76 | /// Sanitizes the input values. 77 | /// 78 | public void Sanitize() 79 | { 80 | // On Windows, VirtualAlloc treats 0 as 'any address', we might aswell avoid this out the gate. 81 | if (Polyfills.IsWindows() && MinAddress < (ulong)Cached.GetAllocationGranularity()) 82 | MinAddress = (nuint)Cached.GetAllocationGranularity(); 83 | 84 | Size = (uint)Mathematics.RoundUp(Math.Max(Size, 1), Cached.GetAllocationGranularity()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Structs/Params/BufferSearchSettings.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Reloaded.Memory.Buffers.Utilities; 3 | 4 | namespace Reloaded.Memory.Buffers.Structs.Params; 5 | 6 | /// 7 | /// Settings to pass to buffer search mechanisms. 8 | /// 9 | [PublicAPI] 10 | public struct BufferSearchSettings 11 | { 12 | /// 13 | /// Minimum address of the allocation. 14 | /// 15 | public required nuint MinAddress { get; init; } = 0; 16 | 17 | /// 18 | /// Maximum address of the allocation. 19 | /// 20 | public required nuint MaxAddress { get; init; } = Cached.GetMaxAddress(); 21 | 22 | /// 23 | /// Required size of the data. 24 | /// 25 | public required uint Size { get; init; } = 4096; 26 | 27 | /// 28 | /// Initializes the buffer allocator with default settings. 29 | /// 30 | public BufferSearchSettings() { } 31 | 32 | /// 33 | /// Creates settings such that the returned buffer will always be within bytes of . 34 | /// 35 | /// Max proximity (number of bytes) to target. 36 | /// Target address. 37 | /// Size required in the settings. 38 | /// Settings that would satisfy this search. 39 | public static BufferSearchSettings FromProximity(nuint proximity, nuint target, nuint size) => new() 40 | { 41 | MaxAddress = Mathematics.AddWithOverflowCap(target, proximity), 42 | MinAddress = Mathematics.SubtractWithUnderflowCap(target, proximity), 43 | Size = (uint)size 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Structs/PrivateAllocation.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Reloaded.Memory.Buffers.Utilities; 3 | using Reloaded.Memory.Structs; 4 | 5 | namespace Reloaded.Memory.Buffers.Structs; 6 | 7 | /// 8 | /// Provides information about a recently made allocation. 9 | /// 10 | /// 11 | /// This memory is automatically disposed by Garbage Collector if this class is no longer in use, 12 | /// if you wish to keep it around forever, make sure to store it in a field or use ``. 13 | /// 14 | public class PrivateAllocation : IDisposable 15 | { 16 | /// 17 | /// Address of the buffer in memory. 18 | /// 19 | public nuint BaseAddress { get; } 20 | 21 | /// 22 | /// Exact size of allocated data. 23 | /// 24 | public uint Size { get; } 25 | 26 | private bool _isDisposed; 27 | private readonly Action _free; 28 | 29 | /// 30 | /// Creates a private allocation returned to user upon allocating a region of memory. 31 | /// 32 | /// 33 | /// 34 | /// 35 | public PrivateAllocation(nuint baseAddress, nuint size, Process process) 36 | { 37 | BaseAddress = baseAddress; 38 | Size = (uint)size; 39 | _free = GetDisposeMethod(process); 40 | _isDisposed = false; 41 | } 42 | 43 | /// 44 | ~PrivateAllocation() => ReleaseUnmanagedResources(); 45 | 46 | /// 47 | public void Dispose() 48 | { 49 | ReleaseUnmanagedResources(); 50 | GC.SuppressFinalize(this); 51 | } 52 | 53 | private void ReleaseUnmanagedResources() 54 | { 55 | if (_isDisposed) 56 | return; 57 | 58 | _free.Invoke(); 59 | _isDisposed = true; 60 | } 61 | 62 | private Action GetDisposeMethod(Process process) 63 | { 64 | #pragma warning disable CA1416 // Validate platform compatibility 65 | if (process.Id == Polyfills.GetProcessId()) 66 | return () => Memory.Instance.Free(new MemoryAllocation { Address =BaseAddress, Length = Size }); 67 | 68 | // Note: Action holds reference to Process so it doesn't get disposed. 69 | return () => new ExternalMemory(process).Free(new MemoryAllocation { Address =BaseAddress, Length = Size }); 70 | #pragma warning restore CA1416 // Validate platform compatibility 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Structs/SafeLocatorItem.cs: -------------------------------------------------------------------------------- 1 | using Reloaded.Memory.Buffers.Structs.Internal; 2 | 3 | namespace Reloaded.Memory.Buffers.Structs; 4 | 5 | /// 6 | /// An individual item in the buffer locator that you can dispose. 7 | /// 8 | public unsafe struct SafeLocatorItem : IDisposable 9 | { 10 | /// 11 | /// The item behind this struct. 12 | /// 13 | /// 14 | /// Use at your own risk. 15 | /// Unsafe. 16 | /// 17 | internal LocatorItem* Item; 18 | 19 | /// 20 | /// Creates a disposable locator item. 21 | /// 22 | /// The item to dispose. 23 | /// 24 | /// Item used with this constructor must be locked. 25 | /// 26 | internal SafeLocatorItem(LocatorItem* item) => Item = item; 27 | 28 | /// 29 | /// Appends the data to this buffer. 30 | /// 31 | /// The data to append to the item. 32 | /// 33 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer.
34 | /// When returning buffers from the library, the library will ensure there's at least the requested amount of space; 35 | /// so if the total size of your data falls under that space, you are good. 36 | ///
37 | public nuint Append(Span data) => Item->Append(data); 38 | 39 | /// 40 | /// Appends the blittable variable to this buffer. 41 | /// 42 | /// Type of the item to write. 43 | /// The item to append to the buffer. 44 | /// Address of the written data. 45 | /// 46 | /// It is the caller's responsibility to ensure there is sufficient space in the buffer.
47 | /// When returning buffers from the library, the library will ensure there's at least the requested amount of space; 48 | /// so if the total size of your data falls under that space, you are good. 49 | ///
50 | public nuint Append(in T data) where T : unmanaged => Item->Append(data); 51 | 52 | /// 53 | public void Dispose() => Item->Unlock(); 54 | } 55 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Usings.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable RedundantUsingDirective.Global 2 | 3 | global using System; 4 | global using System.Collections.Generic; 5 | global using System.IO; 6 | global using System.Linq; 7 | global using System.Threading; 8 | global using System.Threading.Tasks; 9 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Utilities/AddressRange.cs: -------------------------------------------------------------------------------- 1 | namespace Reloaded.Memory.Buffers.Utilities; 2 | 3 | /// 4 | /// Defines a physical address range with a minimum and maximum address. 5 | /// 6 | internal readonly struct AddressRange 7 | { 8 | public readonly nuint StartPointer; 9 | public readonly nuint EndPointer; 10 | public nuint Size => EndPointer - StartPointer; 11 | 12 | public AddressRange(nuint startPointer, nuint endPointer) 13 | { 14 | StartPointer = startPointer; 15 | EndPointer = endPointer; 16 | } 17 | 18 | /// 19 | /// Returns true if the other address range is completely inside 20 | /// the current address range. 21 | /// 22 | /// True if this address range is contained entirely inside the other. 23 | public bool Contains(in AddressRange otherRange) 24 | => otherRange.StartPointer >= StartPointer && otherRange.EndPointer <= EndPointer; 25 | 26 | /// 27 | /// Returns true if the other address range intersects this address range, i.e. 28 | /// start or end of this range falls inside other range. 29 | /// 30 | /// Returns true if there are any overlaps in the address ranges. 31 | public bool Overlaps(in AddressRange otherRange) 32 | { 33 | if (PointInRange(otherRange, StartPointer)) return true; 34 | if (PointInRange(otherRange, EndPointer)) return true; 35 | if (PointInRange(this, otherRange.StartPointer)) return true; 36 | if (PointInRange(this, otherRange.EndPointer)) return true; 37 | 38 | return false; 39 | } 40 | 41 | /// 42 | /// Returns true if a number "point", is between min and max of address range. 43 | /// 44 | /// Range inside which to test the point. 45 | /// The point to test. 46 | private bool PointInRange(in AddressRange range, nuint point) 47 | => point >= range.StartPointer && point <= range.EndPointer; 48 | } 49 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Utilities/Cached.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Reloaded.Memory.Buffers.Exceptions; 3 | using Reloaded.Memory.Native.Unix; 4 | using static Reloaded.Memory.Buffers.Native.Windows.Kernel32; 5 | // ReSharper disable RedundantOverflowCheckingContext 6 | 7 | namespace Reloaded.Memory.Buffers.Utilities; 8 | 9 | /// 10 | /// Retrieves cached values that can be reused. 11 | /// 12 | internal static class Cached 13 | { 14 | private static readonly nuint s_maxAddress; 15 | private static readonly int s_allocationGranularity; 16 | private static readonly Process s_thisProcess; 17 | private const int ScPagesizeLinux = 30; 18 | private const int ScPagesizeOsx = 29; 19 | 20 | static unsafe Cached() 21 | { 22 | #pragma warning disable CA1416 // Validate platform compatibility 23 | if (Polyfills.IsWindows()) 24 | { 25 | GetSystemInfo(out SYSTEM_INFO info); 26 | s_maxAddress = info.lpMaximumApplicationAddress; 27 | s_allocationGranularity = (int)info.dwAllocationGranularity; 28 | } 29 | // Note: On POSIX, applications are aware of full address space by default. 30 | // Technically a chunk of address space is reserved for kernel, however for our use case that's not a concern. 31 | // Note 2: There is no API on Linux (or OSX) to get max address; so we'll restrict to signed 48-bits on x64 for now. 32 | else if (Polyfills.IsLinux()) 33 | { 34 | s_maxAddress = sizeof(nuint) == 4 ? unchecked((nuint)(-1)) : unchecked((nuint)0x7FFFFFFFFFFFL); 35 | s_allocationGranularity = (int)Posix.sysconf(ScPagesizeLinux); 36 | } 37 | else if (Polyfills.IsMacOS()) 38 | { 39 | s_maxAddress = sizeof(nuint) == 4 ? unchecked((nuint)(-1)) : unchecked((nuint)0x7FFFFFFFFFFFL); 40 | s_allocationGranularity = (int)Posix.sysconf(ScPagesizeOsx); 41 | } 42 | else 43 | { 44 | ThrowHelpers.ThrowPlatformNotSupportedException(); 45 | } 46 | 47 | s_thisProcess = Process.GetCurrentProcess(); 48 | #pragma warning restore CA1416 // Validate platform compatibility 49 | } 50 | 51 | public static nuint GetMaxAddress() => s_maxAddress; 52 | 53 | public static int GetAllocationGranularity() => s_allocationGranularity; 54 | 55 | public static Process GetThisProcess() => s_thisProcess; 56 | } 57 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Utilities/LinuxMapParser.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Reloaded.Memory.Buffers.Exceptions; 3 | using Reloaded.Memory.Buffers.Native.Linux; 4 | using Reloaded.Memory.Extensions; 5 | using Reloaded.Memory.Utilities; 6 | using static Reloaded.Memory.Buffers.Utilities.Polyfills; 7 | 8 | namespace Reloaded.Memory.Buffers.Utilities; 9 | 10 | /// 11 | /// Parses Linux' /proc/{id}/maps file. 12 | /// 13 | internal static class LinuxMapParser 14 | { 15 | /// 16 | /// Returns all free regions based on the found regions. 17 | /// 18 | /// The process to get free regions from. 19 | public static List GetFreeRegions(Process targetProcess) 20 | { 21 | using var regs = ParseMemoryMap(targetProcess.Id); 22 | return GetFreeRegions(regs.Span); 23 | } 24 | 25 | /// 26 | /// Returns all free regions based on the found regions. 27 | /// 28 | /// The found regions. 29 | public static List GetFreeRegions(Span regions) 30 | { 31 | nuint lastEndAddress = 0; 32 | var freeRegions = new List(regions.Length + 2); // +2 for start and finish 33 | 34 | for (int x = 0; x < regions.Length; x++) 35 | { 36 | MemoryMapEntry entry = regions.DangerousGetReferenceAt(x); 37 | if (entry.StartAddress > lastEndAddress) 38 | { 39 | freeRegions.Add(new MemoryMapEntry 40 | { 41 | StartAddress = lastEndAddress, 42 | EndAddress = entry.StartAddress - 1 43 | }); 44 | } 45 | 46 | lastEndAddress = entry.EndAddress; 47 | } 48 | 49 | // After the last region, up to the end of memory 50 | if (lastEndAddress < Cached.GetMaxAddress()) 51 | { 52 | freeRegions.Add(new MemoryMapEntry 53 | { 54 | StartAddress = lastEndAddress, 55 | EndAddress = Cached.GetMaxAddress() 56 | }); 57 | } 58 | 59 | return freeRegions; 60 | } 61 | 62 | /// 63 | /// Parses the contents of the /proc/{id}/maps file and returns an array of memory mapping entries. 64 | /// 65 | /// The process id to get mapping ranges for. 66 | /// An array of memory mapping entries. 67 | /// One of the lines in the memory map could not be correctly parsed. 68 | public static ArrayRentalSlice ParseMemoryMap(int processId) 69 | { 70 | var mapsPath = $"/proc/{processId}/maps"; 71 | return ParseMemoryMap(File.ReadAllLines(mapsPath)); 72 | } 73 | 74 | /// 75 | /// Parses the contents of the /proc/self/maps file and returns an array of memory mapping entries. 76 | /// 77 | /// Contents of file in /proc/self/maps or equivalent. 78 | /// An array of memory mapping entries. 79 | /// One of the lines in the memory map could not be correctly parsed. 80 | public static ArrayRentalSlice ParseMemoryMap(string[] lines) 81 | { 82 | var items = new ArrayRental(lines.Length); 83 | for (int x = 0; x < lines.Length; x++) 84 | items.Array.DangerousGetReferenceAt(x) = ParseMemoryMapEntry(lines[x]); 85 | 86 | return new ArrayRentalSlice(items, lines.Length); 87 | } 88 | 89 | internal static MemoryMapEntry ParseMemoryMapEntry(string line) 90 | { 91 | // Example line: "7f9c89991000-7f9c89993000 r--p 00000000 08:01 3932177 /path/to/file" 92 | ReadOnlySpan lineSpan = line.AsSpan(); 93 | var dashIndex = lineSpan.IndexOf('-'); 94 | if (dashIndex == -1) 95 | ThrowHelpers.ThrowLinuxBadMemoryMapEntry(); 96 | 97 | var spaceIndex = lineSpan.Slice(dashIndex).IndexOf(' '); 98 | if (spaceIndex == -1) 99 | ThrowHelpers.ThrowLinuxBadMemoryMapEntry(); 100 | 101 | ReadOnlySpan startAddressSpan = lineSpan.SliceFast(..dashIndex); 102 | ReadOnlySpan endAddressSpan = lineSpan.SliceFast(dashIndex + 1, spaceIndex - 1); 103 | 104 | return new MemoryMapEntry 105 | { 106 | StartAddress = ParseHexAddress(startAddressSpan), 107 | EndAddress = ParseHexAddress(endAddressSpan) 108 | }; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Utilities/Mathematics.cs: -------------------------------------------------------------------------------- 1 | namespace Reloaded.Memory.Buffers.Utilities; 2 | 3 | internal static class Mathematics 4 | { 5 | /// 6 | /// Rounds up a specified number to the next multiple of X. 7 | /// 8 | /// The number to round up. 9 | /// The multiple the number should be rounded to. 10 | /// 11 | internal static nuint RoundUp(nuint number, nuint multiple) 12 | { 13 | if (multiple == 0) 14 | return number; 15 | 16 | nuint remainder = number % multiple; 17 | if (remainder == 0) 18 | return number; 19 | 20 | return number + multiple - remainder; 21 | } 22 | 23 | /// 24 | /// Rounds up a specified number to the previous multiple of X. 25 | /// 26 | /// The number to round down. 27 | /// The multiple the number should be rounded to. 28 | internal static nuint RoundDown(nuint number, nuint multiple) 29 | { 30 | if (multiple == 0) 31 | return number; 32 | 33 | nuint remainder = number % multiple; 34 | if (remainder == 0) 35 | return number; 36 | 37 | return number - remainder; 38 | } 39 | 40 | /// 41 | /// Rounds up a specified number to the previous multiple of X. 42 | /// 43 | /// The number to round down. 44 | /// The multiple the number should be rounded to. 45 | /// 46 | internal static nuint RoundDown(nuint number, int multiple) => RoundDown(number, (nuint)multiple); 47 | 48 | /// 49 | /// Rounds up a specified number to the next multiple of X. 50 | /// 51 | /// The number to round up. 52 | /// The multiple the number should be rounded to. 53 | internal static nuint RoundUp(nuint number, int multiple) => RoundUp(number, (nuint)multiple); 54 | 55 | /// 56 | /// Returns smaller of the two values. 57 | /// 58 | /// First value. 59 | /// Second value. 60 | internal static nuint Min(nuint a, nuint b) => a < b ? a : b; 61 | 62 | /// 63 | /// Adds the two values, but caps the result at MaxValue if it overflows. 64 | /// 65 | /// First value. 66 | /// Second value. 67 | internal static nuint AddWithOverflowCap(nuint a, nuint b) 68 | { 69 | var max = unchecked((nuint)(-1)); 70 | if (max - a >= b) 71 | return a + b; 72 | 73 | return max; 74 | } 75 | 76 | /// 77 | /// Subtracts the two values, but caps the result at MinValue if it overflows. 78 | /// 79 | /// First value. 80 | /// Second value. 81 | public static nuint SubtractWithUnderflowCap(nuint a, nuint b) => b <= a ? a - b : 0; 82 | } 83 | -------------------------------------------------------------------------------- /src/Reloaded.Memory.Buffers/Utilities/Polyfills.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | #if !NET5_0_OR_GREATER 3 | using System.Runtime.InteropServices; 4 | #endif 5 | 6 | namespace Reloaded.Memory.Buffers.Utilities; 7 | 8 | internal static class Polyfills 9 | { 10 | #if !NET5_0_OR_GREATER 11 | private static int? s_processId; 12 | #endif 13 | public static int GetProcessId() 14 | { 15 | #if NET5_0_OR_GREATER 16 | return Environment.ProcessId; 17 | #else 18 | if (s_processId != null) 19 | return s_processId.Value; 20 | 21 | s_processId = System.Diagnostics.Process.GetCurrentProcess().Id; 22 | return s_processId.Value; 23 | #endif 24 | } 25 | 26 | // The OS identifier platform code below is JIT friendly; compiled out at runtime for .NET 5 and above. 27 | 28 | /// 29 | /// Returns true if the current operating system is Windows. 30 | /// 31 | public static bool IsWindows() 32 | { 33 | #if NET5_0_OR_GREATER 34 | return OperatingSystem.IsWindows(); 35 | #else 36 | return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 37 | #endif 38 | } 39 | 40 | /// 41 | /// Returns true if the current operating system is Linux. 42 | /// 43 | public static bool IsLinux() 44 | { 45 | #if NET5_0_OR_GREATER 46 | return OperatingSystem.IsLinux(); 47 | #else 48 | return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 49 | #endif 50 | } 51 | 52 | /// 53 | /// Returns true if the current operating system is MacOS. 54 | /// 55 | public static bool IsMacOS() 56 | { 57 | #if NET5_0_OR_GREATER 58 | return OperatingSystem.IsMacOS(); 59 | #else 60 | return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 61 | #endif 62 | } 63 | 64 | /// 65 | /// Parses a hex address from the string provided.. 66 | /// 67 | /// The text to parse the address from. 68 | /// Parsed address. 69 | /// Limited to 64-bit on older frameworks. 70 | public static nuint ParseHexAddress(ReadOnlySpan text) 71 | { 72 | #if NET6_0_OR_GREATER 73 | return nuint.Parse(text, NumberStyles.HexNumber); 74 | #else 75 | return (nuint)ulong.Parse(text.ToString(), NumberStyles.HexNumber); 76 | #endif 77 | } 78 | } 79 | --------------------------------------------------------------------------------