We have pushed out a set of updates to FiveM lately. The last one unfortunately did not come with a change log entry, so here is a coalesced version of the change log for both.
Summarized changelog
- Add additional pool entry names to the pool error message.
- Rename additional leftover project branding of FiveReborn/pΛ to say FiveM.
- Disconnect from the server when internal code calls
OnKillNetwork
events. - Expose
read
andreadbuffer
APIs tocitizen:scripting:v8
scripts, for use in updated versions of the C++ script hook porting library. - Resource system reworks to support compliance modules.
- Support for resource compliance. This requires a new version of the server, which is currently in closed testing.
- Fixes to make the client not hang when joining a game and resources are still being downloaded:
- Don’t switch
rage::fwClipSetManager
to network mode (this incurs a blocking load). - Disable
LOAD_ALL_OBJECTS_NOW
until the loading screens have been hidden, usually byspawnmanager
causing the player to spawn. This is a workaround for aLoadAllObjectsNow()
line inmapmanager
.
- Don’t switch
- Add a
fxd:/
mount to the VFS stack pointing to [AppData]\CitizenFX. - Client-side key/value pair storage for resources. This is detailed further below.
-
formats:fraglod
tool for the CitizenFX console (FiveM.com), fixing .#ft files that have incoherent LOD levels by cutting off the lower LODs. - An internal crash fix in
CfxCollection
that makes mistyped entries (filetype
instead offileType
) not crash. - SSC attribute for
CitizenFX.Core.UI.ScreenResolution
. - Fixes for anti-malware software badly hooking NtQueryInformationProcess, causing the ‘crash on loading screen’ seen with AVG/Avast to not occur anymore.
- Improved crash reporting:
- SysError calls (fatal errors) now cause the full crash reporting flow to be invoked, fixing the issue with ‘hearing an error sound, the game freezing, but no error popping up’ and improving telemetry.
- RAGE errors are cleaned up to also follow the above flow.
- Compatibility fix for a particular version of
PLD.asi
that doesn’t check return values offopen
before writing to a log file that apparently only serves to glorify the creator of the plugin. - Update Mono and change code around to hopefully fix a top crasher in
sgen_suspend_thread
. This might cause a bunch of weird regressions in CLR functionality. - Internal VFS interface improvements.
- Logging improvements:
- Hide ‘instrumented function 66/67’ pairs in CitizenFX.log.
- Don’t print the timestamp in CitizenFX.log in the middle of a line.
- Make the timestamp in CitizenFX.log relative to creation of the log file, not the OS’s global tick counter.
- Update V8 for use by compliance code.
- Improve detection for game-breaking plugins.
- Delete
caches.xml
automatically if a framework component file is broken/missing. - Potentially improve performance and clean up unneeded use of RTTI in the CitizenFX component model.
- Revert the entity ownership patch that caused some game crashes and broke
SET_*_AS_NO_LONGER_NEEDED
, instead just patch out ownership checks directly. - Rebrand the pause menu to no longer say ‘Grand Theft Auto Online’ and remove the Facebook link that claimed the user has been banned.
- Improve main menu performance and add new pages signifying a shift in the project.
- Fix CLR library updates somehow not having shipped.
- Revert Rockstar breaking the statue in the Venice Beach area.
Developer information
No wiki as usual, ha! Too busy implementing actual system improvements.
Key/value storage
The key/value storage allows storing arbitrary data on the client side. It can be accessed with standard natives exposed to the CitizenFX scripting component, however no example usage from Lua this time around, no updated natives.lua yet either so you’ll have to guess the definition for now.
The functions are (among others that aren’t exposed as they were ill-designed):
GET_RESOURCE_KVP_INT
Gets a KVP value for the current resource.
Arguments:
- key (string): the key to fetch
Return value:
An integer containing the integer value stored in the key.
Example:
int kvpValue = GET_RESOURCE_KVP_INT("bananabread");
GET_RESOURCE_KVP_FLOAT
Gets a float KVP value for the current resource.
Arguments:
- key (string): the key to fetch
Return value:
A float containing the floating-point value stored in the key.
Example:
float kvpValue = GET_RESOURCE_KVP_FLOAT("codfish");
GET_RESOURCE_KVP_STRING
Gets a string KVP value for the current resource.
Arguments:
- key (string): the key to fetch
Return value:
A string containing the value stored in the key.
Example:
const char* kvpValue = GET_RESOURCE_KVP_STRING("mollis");
SET_RESOURCE_KVP
Sets a string KVP value for the current resource.
Arguments:
- key (string): the key to set
- value (string): the value to write
Return value:
None.
Example:
SET_RESOURCE_KVP("mollis", "vesuvius citrate");
SET_RESOURCE_KVP_INT
Sets an integer KVP value for the current resource.
Arguments:
- key (string): the key to set
- value (int): the value to write
Return value:
None.
Example:
int lickMy = 42;
SET_RESOURCE_KVP_INT("bananabread", lickMy);
SET_RESOURCE_KVP_FLOAT
Sets a float KVP value for the current resource.
Arguments:
- key (string): the key to set
- value (float): the value to write
Return value:
None.
Example:
float curseBuster = 0xBC * 2.5f;
SET_RESOURCE_KVP_INT("codfish", curseBuster);
DELETE_RESOURCE_KVP
Removes a KVP value.
Arguments:
- key (string): the key to delete
Return value:
None.
Example:
DELETE_RESOURCE_KVP("liberty_city"); // it's over!
START_FIND_KVP
Opens a find handle for scanning KVP entries that begin with a specific prefix.
Arguments:
- prefix (string): a prefix match
Return value:
A KVP find handle to use with FIND_KVP
and close with END_FIND_KVP
once completed. -1
if unlucky.
Example:
SET_RESOURCE_KVP("mollis:2", "should be taken with alcohol");
SET_RESOURCE_KVP("mollis:1", "vesuvius citrate");
SET_RESOURCE_KVP("mollis:manufacturer", "Betta Pharmaceuticals");
int kvpHandle = START_FIND_KVP("mollis:");
if (kvpHandle != -1)
{
const char* key = nullptr;
do
{
key = FIND_KVP(kvpHandle);
if (key)
{
trace("%s: %s\n", key, GET_RESOURCE_KVP_STRING(key));
}
} while (key);
END_FIND_KVP(kvpHandle);
}
Potential output:
mollis:1: vesuvius citrate
mollis:2: should be taken with alcohol
mollis:manufacturer: Betta Pharmaceuticals
FIND_KVP
Returns the current key a KVP find handle is pointing to, then increments the cursor.
Arguments:
- handle (int): the KVP find handle returned from
START_FIND_KVP
.
Return value:
A string containing the current key, or null
if the end of the iteration has been reached.
Example:
See START_FIND_KVP
.
END_FIND_KVP
Closes a KVP find handle.
Arguments:
- handle (int): the KVP find handle returned from
START_FIND_KVP
.
Return value:
None.
Example:
See START_FIND_KVP
.
Compliance
Compliance is currently not exposed to end users, and will be exposed some time after the completion of the closed beta test.
A compliance snakeoil API exists to allow compliance resources to obfuscate objects differently.
SET_SNAKEOIL_FOR_ENTRY
Sets the snakeoil script file for a file entry in the current resource, passing additional metadata.
Arguments:
- name (string): the entry name to apply snakeoil to, for instance
stat_hilberty01.ydr
. - path (string): the path in the existing resource to the snakeoil script file.
- extradata (encoded JSON): extra data to pass to the script.
Return value:
A boolean indicating success.
Example:
function SetSnakeoilForEntry(name, path, data)
Citizen.InvokeNative(0xa7dd3209, name, path, data)
end
SetSnakeoilForEntry('stat_hilberty01.ydr', 'snakeoil.js', '{"liberty": "city"}')
SetSnakeoilForEntry('liberty_pedestal01.ytd', 'snakeoil.js', '{"liberty": "island"}')
Snakeoil script files
The snakeoil script file implements the snakeoil algorithm used for the resource. An example is as follows:
class oil {
initialize(data) {
print("initializing snakeoil on: " + data.liberty);
}
// to resynchronize a cipher with an offset in the file
seek(off) {
print("seeking: " + off);
}
// currently, argument is an ArrayBuffer, return value the same
// do note the argument's lifetime exists only for the duration of the function call!
decrypt(data) {
print("decrypting: " + data.byteLength + " bytes");
return data;
}
}
// the global 'snakeoil' object contains a factory returning a snakeoil instance.
snakeoil = () => new oil();
Future plans
None are known so far, recent changes have caused some plans for an internal reorganization of the project. We still set out to complete the goals we described earlier, but the exact time frame for this is sadly unknown.
We have also adopted (from Emoji One 2) as our official project mascot!