Skip to content

Commit

Permalink
feat(natives): implement async fetching for mounted file hash
Browse files Browse the repository at this point in the history
  • Loading branch information
PichotM committed Sep 21, 2024
1 parent c942ca3 commit ad2cd80
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 27 deletions.
110 changes: 88 additions & 22 deletions code/components/extra-natives-five/src/FileNatives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <botan/sha2_32.h>
#include <boost/algorithm/string.hpp>
#include <concurrent_unordered_map.h>
#include <atomic>

static const std::array<std::string_view, 7> kWhitelistedPaths = {
"platform:/",
Expand Down Expand Up @@ -40,39 +42,103 @@ static std::string ToHexString(const uint8_t* data, size_t length)
return result;
};

static HookFunction hookFunction([]()
struct HashResult
{
fx::ScriptEngine::RegisterNativeHandler("GET_MOUNTED_FILE_HASH", [](fx::ScriptContext& context)
std::string hash;
bool isComplete;
};

static concurrency::concurrent_unordered_map<std::string, HashResult> g_fileHashes;
static std::atomic<bool> g_isFetching(false);

static DWORD WINAPI CalculateFileHash(LPVOID lpParameter)
{
std::unique_ptr<std::string> pathStr(static_cast<std::string*>(lpParameter));
std::string result = "missing";

const char* path = pathStr->c_str();
rage::fiDevice* device = rage::fiDevice::GetDevice(path, true);

if (device)
{
const char* path = context.CheckArgument<const char*>(0);
static std::string result = "missing";
auto handle = device->Open(path, true);

if (IsPathWhitelisted(path))
if (handle != -1)
{
rage::fiDevice* device = rage::fiDevice::GetDevice(path, true);
auto len = device->GetFileLength(handle);
std::vector<char> data(len);

if (device)
{
auto handle = device->Open(path, true);
device->Read(handle, data.data(), len);
device->Close(handle);

Botan::SHA_256 hashFunction;
hashFunction.update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
std::vector<uint8_t> hash(hashFunction.output_length());
hashFunction.final(hash.data());

if (handle != -1)
{
auto len = device->GetFileLength(handle);
std::vector<char> data(len);
result = ToHexString(hash.data(), hash.size());
}
}

g_fileHashes[*pathStr] = { result, true };
g_isFetching.store(false, std::memory_order_release);

return 0;
}

static HookFunction hookFunction([]()
{
fx::ScriptEngine::RegisterNativeHandler("FETCH_MOUNTED_FILE_HASH", [](fx::ScriptContext& context)
{
bool expected = false;
if (!g_isFetching.compare_exchange_strong(expected, true, std::memory_order_acq_rel))
{
context.SetResult(false);
return;
}

device->Read(handle, data.data(), len);
device->Close(handle);
const std::string path = context.CheckArgument<const char*>(0);

Botan::SHA_256 hashFunction;
hashFunction.update(reinterpret_cast<const uint8_t*>(data.data()), data.size());
std::vector<uint8_t> hash(hashFunction.output_length());
hashFunction.final(hash.data());
auto it = g_fileHashes.find(path);
if (it == g_fileHashes.end())
{
if (IsPathWhitelisted(path))
{
g_fileHashes[path] = { "", false };

result = ToHexString(hash.data(), hash.size());
}
auto pathCopy = new std::string(path);
QueueUserWorkItem(CalculateFileHash, pathCopy, 0);
}
else
{
// Mark non-whitelisted paths as complete with "missing" hash
g_fileHashes[path] = { "missing", true };
}
}

context.SetResult(result.c_str());
context.SetResult(false);
});

fx::ScriptEngine::RegisterNativeHandler("GET_MOUNTED_FILE_HASH", [](fx::ScriptContext& context)
{
const std::string path = context.CheckArgument<const char*>(0);

auto it = g_fileHashes.find(path);
if (it != g_fileHashes.end() && it->second.isComplete)
{
context.SetResult(it->second.hash.c_str());
}
else
{
context.SetResult<const char*>("missing");
}
});

fx::ScriptEngine::RegisterNativeHandler("IS_MOUNTED_FILE_HASH_READY", [](fx::ScriptContext& context)
{
const std::string path = context.CheckArgument<const char*>(0);

auto it = g_fileHashes.find(path);
context.SetResult(it != g_fileHashes.end() && it->second.isComplete);
});
});
34 changes: 34 additions & 0 deletions ext/native-decls/FetchMountedFileHash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
ns: CFX
apiset: client
---
## FETCH_MOUNTED_FILE_HASH

```c
void FETCH_MOUNTED_FILE_HASH(char* path);
```
This native will fetch the file and calculate its hash for use with [`GET_MOUNTED_FILE_HASH`](#_0xC1657E48).
Use [`IS_MOUNTED_FILE_HASH_READY`](#_0xEEBC88EC) to check if the hash is ready.
## Examples
```lua
local hashes = {
"platform:/levels/gta5/props/residential/v_garden/prop_airport_sale.ytd",
"common:/data/materials/materials.dat"
}
for _,v in pairs(hashes) do
FetchMountedFileHash(v)
while not IsMountedFileHashReady(v) do
Wait(0)
end
print(GetMountedFileHash(v))
end
```

## Parameters
* **path**: The path to the file.
16 changes: 11 additions & 5 deletions ext/native-decls/GetMountedFileHash.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ apiset: client
char* GET_MOUNTED_FILE_HASH(char* path);
```
This native returns the hash of the file that is currently mounted. The hash can be useful to verify if the file has been altered. If the path is not whitelisted or the file is not mounted, the native will return "missing".
This native returns the hash of the file that is currently mounted. The hash can be useful to verify if the file has been altered. If the path is not whitelisted, the file has not been fetched or the file is not mounted, the native will return "missing".
## Examples
```lua
local hashes = {
GetMountedFileHash("platform:/levels/gta5/props/residential/v_garden/prop_airport_sale.ytd"),
GetMountedFileHash("common:/data/materials/materials.dat")
}
"platform:/levels/gta5/props/residential/v_garden/prop_airport_sale.ytd",
"common:/data/materials/materials.dat"
}
for _,v in pairs(hashes) do
print(v)
FetchMountedFileHash(v)
while not IsMountedFileHashReady(v) do
Wait(0)
end
print(GetMountedFileHash(v))
end
```

Expand Down
37 changes: 37 additions & 0 deletions ext/native-decls/IsMountedFileHashReady.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
ns: CFX
apiset: client
---
## IS_MOUNTED_FILE_HASH_READY

```c
BOOL IS_MOUNTED_FILE_HASH_READY(char* path);
```
This native is used to check if the hash of a file is ready for use with [`GET_MOUNTED_FILE_HASH`](#_0xC1657E48_).
## Examples
```lua
local hashes = {
"platform:/levels/gta5/props/residential/v_garden/prop_airport_sale.ytd",
"common:/data/materials/materials.dat"
}
for _,v in pairs(hashes) do
FetchMountedFileHash(v)
while not IsMountedFileHashReady(v) do
Wait(0)
end
print(GetMountedFileHash(v))
end
```

## Parameters
* **path**: The path to the file.

## Return value

Returns `true` if the hash is ready, `false` otherwise.

0 comments on commit ad2cd80

Please sign in to comment.