├── .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 |
5 |
6 |
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 |
5 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
50 |
51 |
52 |
53 |
54 | 1686204371521
55 |
56 |
57 | 1686204371521
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
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 |
5 |
6 |
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 | - 
7 | - 
8 | - 
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 |
5 |
6 |
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 |
5 |
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 |
--------------------------------------------------------------------------------