241 changes: 56 additions & 185 deletions code/client/clrcore/InternalManager.cs
Expand Up @@ -61,6 +61,8 @@ internal static void AddScript(BaseScript script)
{
if (!ms_definedScripts.Contains(script))
{
script.InitializeOnAdd();

ms_definedScripts.Add(script);
}
}
Expand Down Expand Up @@ -104,169 +106,7 @@ private static Assembly CreateAssemblyInternal(string assemblyFile, byte[] assem

Debug.WriteLine("Instantiated instance of script {0}.", type.FullName);

var allMethods = derivedScript.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);

IEnumerable<MethodInfo> GetMethods(Type t)
{
return allMethods.Where(m => m.GetCustomAttributes(t, false).Length > 0);
}

// register all Tick decorators
try
{
foreach (var method in GetMethods(typeof(TickAttribute)))
{
Debug.WriteLine("Registering Tick for attributed method {0}", method.Name);

if (method.IsStatic)
derivedScript.RegisterTick((Func<Task>)Delegate.CreateDelegate(typeof(Func<Task>), method));
else
derivedScript.RegisterTick((Func<Task>)Delegate.CreateDelegate(typeof(Func<Task>), derivedScript, method.Name));
}
}
catch (Exception e)
{
Debug.WriteLine("Registering Tick failed: {0}", e.ToString());
}

// register all EventHandler decorators
try
{
foreach (var method in GetMethods(typeof(EventHandlerAttribute)))
{
var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray();
var actionType = Expression.GetDelegateType(parameters.Concat(new[] { typeof(void) }).ToArray());
var attribute = method.GetCustomAttribute<EventHandlerAttribute>();

Debug.WriteLine("Registering EventHandler {2} for attributed method {0}, with parameters {1}", method.Name, String.Join(", ", parameters.Select(p => p.GetType().ToString())), attribute.Name);

if (method.IsStatic)
derivedScript.RegisterEventHandler(attribute.Name, Delegate.CreateDelegate(actionType, method));
else
derivedScript.RegisterEventHandler(attribute.Name, Delegate.CreateDelegate(actionType, derivedScript, method.Name));
}
}
catch (Exception e)
{
Debug.WriteLine("Registering EventHandler failed: {0}", e.ToString());
}

// register all commands
try
{
foreach (var method in GetMethods(typeof(CommandAttribute)))
{
var attribute = method.GetCustomAttribute<CommandAttribute>();
var parameters = method.GetParameters();

Debug.WriteLine("Registering command {0}", attribute.Command);

// no params, trigger only
if (parameters.Length == 0)
{
if (method.IsStatic)
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(null, null);
}), attribute.Restricted);
}
else
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(derivedScript, null);
}), attribute.Restricted);
}
}
// Player
else if (parameters.Any(p => p.ParameterType == typeof(Player)) && parameters.Length == 1)
{
#if IS_FXSERVER
if (method.IsStatic)
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(null, new object[] { new Player(source.ToString()) });
}), attribute.Restricted);
}
else
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(derivedScript, new object[] { new Player(source.ToString()) });
}), attribute.Restricted);
}
#else
Debug.WriteLine("Client commands with parameter type Player not supported");
#endif
}
// string[]
else if (parameters.Length == 1)
{
if (method.IsStatic)
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(null, new object[] { args.Select(a => (string)a).ToArray() });
}), attribute.Restricted);
}
else
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(derivedScript, new object[] { args.Select(a => (string)a).ToArray() });
}), attribute.Restricted);
}
}
// Player, string[]
else if (parameters.Any(p => p.ParameterType == typeof(Player)) && parameters.Length == 2)
{
#if IS_FXSERVER
if (method.IsStatic)
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(null, new object[] { new Player(source.ToString()), args.Select(a => (string)a).ToArray() });
}), attribute.Restricted);
}
else
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(derivedScript, new object[] { new Player(source.ToString()), args.Select(a => (string)a).ToArray() });
}), attribute.Restricted);
}
#else
Debug.WriteLine("Client commands with parameter type Player not supported");
#endif
}
// legacy --> int, List<object>, string
else
{
if (method.IsStatic)
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(null, new object[] { source, args, rawCommand });
}), attribute.Restricted);
}
else
{
Native.API.RegisterCommand(attribute.Command, new Action<int, List<object>, string>((source, args, rawCommand) =>
{
method.Invoke(derivedScript, new object[] { source, args, rawCommand });
}), attribute.Restricted);
}
}
}
}
catch (Exception e)
{
Debug.WriteLine("Registering command failed: {0}", e.ToString());
}

ms_definedScripts.Add(derivedScript);
AddScript(derivedScript);
}
catch (Exception e)
{
Expand All @@ -290,55 +130,86 @@ static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEv
Debug.WriteLine("Unhandled exception: {0}", e.ExceptionObject.ToString());
}

private static HashSet<string> ms_assemblySearchPaths = new HashSet<string>();

public void LoadAssembly(string name)
{
LoadAssemblyInternal(name.Replace(".dll", ""));
}

static Assembly LoadAssemblyInternal(string name)
static Assembly LoadAssemblyInternal(string baseName, bool useSearchPaths = false)
{
try
{
var assemblyStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".dll")));
var assemblyBytes = assemblyStream.ReadBytes((int)assemblyStream.BaseStream.Length);
var attemptPaths = new List<string>();
attemptPaths.Add(baseName);

byte[] symbolBytes = null;
var exceptions = new StringBuilder();

try
if (useSearchPaths)
{
foreach (var path in ms_assemblySearchPaths)
{
var symbolStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".dll.mdb")));
symbolBytes = symbolStream.ReadBytes((int)symbolStream.BaseStream.Length);
attemptPaths.Add($"{path.Replace('\\', '/')}/{baseName}");
}
catch
}

foreach (var name in attemptPaths)
{
try
{
var assemblyStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".dll")));
var assemblyBytes = assemblyStream.ReadBytes((int)assemblyStream.BaseStream.Length);

byte[] symbolBytes = null;

try
{
var symbolStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".pdb")));
var symbolStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".dll.mdb")));
symbolBytes = symbolStream.ReadBytes((int)symbolStream.BaseStream.Length);
}
catch
{
// nothing
try
{
var symbolStream = new BinaryReader(new FxStreamWrapper(ScriptHost.OpenHostFile(name + ".pdb")));
symbolBytes = symbolStream.ReadBytes((int)symbolStream.BaseStream.Length);
}
catch
{
// nothing
}
}

if (assemblyBytes != null)
{
var dirName = Path.GetDirectoryName(name);

if (!string.IsNullOrWhiteSpace(dirName))
{
ms_assemblySearchPaths.Add(dirName);
}
}

return CreateAssemblyInternal(name + ".dll", assemblyBytes, symbolBytes);
}
catch (Exception e)
{
//Switching the FileNotFound to a NotImplemented tells mono to disable I18N support.
//See: https://github.com/mono/mono/blob/8fee89e530eb3636325306c66603ba826319e8c5/mcs/class/corlib/System.Text/EncodingHelper.cs#L131
if (e is FileNotFoundException && string.Equals(name, "I18N", StringComparison.OrdinalIgnoreCase))
throw new NotImplementedException("I18N not found", e);

return CreateAssemblyInternal(name + ".dll", assemblyBytes, symbolBytes);
}
catch (Exception e)
{
//Switching the FileNotFound to a NotImplemented tells mono to disable I18N support.
//See: https://github.com/mono/mono/blob/8fee89e530eb3636325306c66603ba826319e8c5/mcs/class/corlib/System.Text/EncodingHelper.cs#L131
if (e is FileNotFoundException && string.Equals(name, "I18N", StringComparison.OrdinalIgnoreCase))
throw new NotImplementedException("I18N not found", e);
Debug.WriteLine($"Exception loading assembly {name}: {e}");
exceptions.AppendLine($"Exception loading assembly {name}: {e}");
}
}

Debug.WriteLine($"Could not load assembly {baseName} - loading exceptions: {exceptions}");

return null;
}

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return LoadAssemblyInternal(args.Name.Split(',')[0]);
return LoadAssemblyInternal(args.Name.Split(',')[0], useSearchPaths: true);
}

public static void AddDelay(int delay, AsyncCallback callback)
Expand Down
4 changes: 4 additions & 0 deletions code/client/clrcore/MsgPackSerializer.cs
Expand Up @@ -73,6 +73,10 @@ private static void Serialize(object obj, Packer packer)
{
packer.Pack(obj);
}
else if (obj is byte[] bytes)
{
packer.Pack(bytes);
}
else if (obj is IDictionary)
{
var dict = (IDictionary)obj;
Expand Down
25 changes: 13 additions & 12 deletions code/client/clrcore/Server/ServerWrappers.cs
@@ -1,10 +1,11 @@
using CitizenFX.Core.Native;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using static CitizenFX.Core.Native.Function;
using static CitizenFX.Core.Native.Hash;
using static CitizenFX.Core.Native.API;

namespace CitizenFX.Core
{
Expand All @@ -24,19 +25,19 @@ internal Player(string sourceString)
m_handle = sourceString;
}

public string Name => Call<string>(GET_PLAYER_NAME, m_handle);
public string Name => GetPlayerName(m_handle);

public int Ping => Call<int>(GET_PLAYER_PING, m_handle);
public int Ping => GetPlayerPing(m_handle);

public int LastMsg => Call<int>(GET_PLAYER_LAST_MSG, m_handle);
public int LastMsg => GetPlayerLastMsg(m_handle);

public IdentifierCollection Identifiers => new IdentifierCollection(this);

public string EndPoint => Call<string>(GET_PLAYER_ENDPOINT, m_handle);
public string EndPoint => GetPlayerEndpoint(m_handle);

public Ped Character => Ped.FromPlayerHandle(m_handle);

public void Drop(string reason) => Call(DROP_PLAYER, m_handle, reason);
public void Drop(string reason) => DropPlayer(m_handle, reason);

public void TriggerEvent(string eventName, params object[] args)
{
Expand All @@ -46,7 +47,7 @@ public void TriggerEvent(string eventName, params object[] args)
{
fixed (byte* serialized = &argsSerialized[0])
{
Call(TRIGGER_CLIENT_EVENT_INTERNAL, eventName, m_handle, serialized, argsSerialized.Length);
Function.Call(Hash.TRIGGER_CLIENT_EVENT_INTERNAL, eventName, m_handle, serialized, argsSerialized.Length);
}
}
}
Expand Down Expand Up @@ -93,11 +94,11 @@ internal IdentifierCollection(Player player)

public IEnumerator<string> GetEnumerator()
{
int numIndices = Call<int>(GET_NUM_PLAYER_IDENTIFIERS, m_player.Handle);
int numIndices = GetNumPlayerIdentifiers(m_player.Handle);

for (var i = 0; i < numIndices; i++)
{
yield return Call<string>(GET_PLAYER_IDENTIFIER, m_player.Handle, i);
yield return GetPlayerIdentifier(m_player.Handle, i);
}
}

Expand All @@ -121,11 +122,11 @@ public class PlayerList : IEnumerable<Player>
{
public IEnumerator<Player> GetEnumerator()
{
int numIndices = Call<int>(GET_NUM_PLAYER_INDICES);
int numIndices = GetNumPlayerIndices();

for (var i = 0; i < numIndices; i++)
{
yield return new Player(Call<string>(GET_PLAYER_FROM_INDEX, i));
yield return new Player(GetPlayerFromIndex(i));
}
}

Expand Down
Expand Up @@ -321,6 +321,11 @@ void ResourceMetaDataComponent::GlobEntries(const std::string& key, const std::f

for (auto& file : mf)
{
if (file.length() < (relRoot.length() + 1))
{
continue;
}

entryCallback(file.substr(relRoot.length() + 1));
}
}
Expand Down
3 changes: 2 additions & 1 deletion code/components/citizen-server-impl/component.json
Expand Up @@ -19,7 +19,8 @@
"vendor:lz4",
"vendor:glm",
"vendor:eastl",
"vendor:slikenet"
"vendor:slikenet",
"vendor:utfcpp"
],
"provides": []
}
44 changes: 44 additions & 0 deletions code/components/citizen-server-impl/include/MakeClientFunction.h
@@ -0,0 +1,44 @@
#pragma once

#include <ScriptEngine.h>

#include <ClientRegistry.h>
#include <ResourceManager.h>
#include <ServerInstanceBaseRef.h>

template<typename TFn>
inline auto MakeClientFunction(TFn fn, uintptr_t defaultValue = 0)
{
return [=](fx::ScriptContext & context)
{
// get the current resource manager
auto resourceManager = fx::ResourceManager::GetCurrent();

// get the owning server instance
auto instance = resourceManager->GetComponent<fx::ServerInstanceBaseRef>()->Get();

// get the server's client registry
auto clientRegistry = instance->GetComponent<fx::ClientRegistry>();

// parse the client ID
const char* id = context.CheckArgument<const char*>(0);

if (!id)
{
context.SetResult(defaultValue);
return;
}

uint32_t netId = atoi(id);

auto client = clientRegistry->GetClientByNetID(netId);

if (!client)
{
context.SetResult(defaultValue);
return;
}

context.SetResult(fn(context, client));
};
};
@@ -0,0 +1,22 @@
#pragma once

class ServerLicensingComponent : public fwRefCountable
{
public:
ServerLicensingComponent(const std::string& key)
{
m_key = key;
}

inline std::string GetLicenseKey()
{
return m_key;
}

private:
std::string m_key;
};

DECLARE_INSTANCE_TYPE(ServerLicensingComponent);

#define LICENSING_EP "https://keymaster.fivem.net/"
26 changes: 17 additions & 9 deletions code/components/citizen-server-impl/src/InitConnectMethod.cpp
Expand Up @@ -301,21 +301,29 @@ static InitFunction initFunction([]()
return;
}

if (!VerifyTicket(guid, ticketIt->second))
try
{
sendError("FiveM ticket authorization failed.");
return;
}
if (!VerifyTicket(guid, ticketIt->second))
{
sendError("FiveM ticket authorization failed.");
return;
}

auto optionalTicket = VerifyTicketEx(ticketIt->second);
auto optionalTicket = VerifyTicketEx(ticketIt->second);

if (!optionalTicket)
if (!optionalTicket)
{
sendError("FiveM ticket authorization failed. (2)");
return;
}

ticketData = *optionalTicket;
}
catch (const std::exception& e)
{
sendError("FiveM ticket authorization failed. (2)");
sendError(fmt::sprintf("Parsing error while verifying FiveM ticket. %s", e.what()));
return;
}

ticketData = *optionalTicket;
}

std::string token = boost::uuids::to_string(boost::uuids::basic_random_generator<boost::random_device>()());
Expand Down
58 changes: 12 additions & 46 deletions code/components/citizen-server-impl/src/PlayerScriptFunctions.cpp
Expand Up @@ -11,60 +11,26 @@

#include <se/Security.h>

#include <MakeClientFunction.h>

static InitFunction initFunction([]()
{
auto makeClientFunction = [](auto fn, uintptr_t defaultValue = 0)
{
return [=](fx::ScriptContext& context)
{
// get the current resource manager
auto resourceManager = fx::ResourceManager::GetCurrent();

// get the owning server instance
auto instance = resourceManager->GetComponent<fx::ServerInstanceBaseRef>()->Get();

// get the server's client registry
auto clientRegistry = instance->GetComponent<fx::ClientRegistry>();

// parse the client ID
const char* id = context.CheckArgument<const char*>(0);

if (!id)
{
context.SetResult(defaultValue);
return;
}

uint32_t netId = atoi(id);

auto client = clientRegistry->GetClientByNetID(netId);

if (!client)
{
context.SetResult(defaultValue);
return;
}

context.SetResult(fn(context, client));
};
};

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_NAME", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_NAME", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
return client->GetName().c_str();
}));

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_GUID", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_GUID", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
return client->GetGuid().c_str();
}));

fx::ScriptEngine::RegisterNativeHandler("GET_NUM_PLAYER_IDENTIFIERS", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_NUM_PLAYER_IDENTIFIERS", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
return client->GetIdentifiers().size();
}));

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_IDENTIFIER", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_IDENTIFIER", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
int idx = context.GetArgument<int>(1);

Expand All @@ -76,15 +42,15 @@ static InitFunction initFunction([]()
return client->GetIdentifiers()[idx].c_str();
}));

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_ENDPOINT", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_ENDPOINT", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
static thread_local std::string str;
str = client->GetTcpEndPoint();

return str.c_str();
}));

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_PING", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_PING", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
auto peer = gscomms_get_peer(client->GetPeer());

Expand All @@ -96,12 +62,12 @@ static InitFunction initFunction([]()
return int(peer->GetPing());
}));

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_LAST_MSG", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_LAST_MSG", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
return (msec() - client->GetLastSeen()).count();
}, 0x7fffffff));

fx::ScriptEngine::RegisterNativeHandler("DROP_PLAYER", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("DROP_PLAYER", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
// get the current resource manager
auto resourceManager = fx::ResourceManager::GetCurrent();
Expand All @@ -117,7 +83,7 @@ static InitFunction initFunction([]()
return true;
}));

fx::ScriptEngine::RegisterNativeHandler("IS_PLAYER_ACE_ALLOWED", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
fx::ScriptEngine::RegisterNativeHandler("IS_PLAYER_ACE_ALLOWED", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client)
{
const char* object = context.CheckArgument<const char*>(1);

Expand Down Expand Up @@ -210,7 +176,7 @@ static InitFunction initFunction([]()
}
});

fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_PED", makeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
fx::ScriptEngine::RegisterNativeHandler("GET_PLAYER_PED", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
{
try
{
Expand Down
208 changes: 208 additions & 0 deletions code/components/citizen-server-impl/src/ServerCommerce.cpp
@@ -0,0 +1,208 @@
#include <StdInc.h>

#include <MakeClientFunction.h>

#include <ServerInstanceBase.h>
#include <ServerInstanceBaseRef.h>

#include <ResourceManager.h>
#include <ScriptEngine.h>

#include <HttpClient.h>

#include <ServerLicensingComponent.h>

#include <json.hpp>

#include <optional>

inline std::string GetLicenseKey()
{
auto resourceManager = fx::ResourceManager::GetCurrent();
auto instance = resourceManager->GetComponent<fx::ServerInstanceBaseRef>()->Get();
auto licensing = instance->GetComponent<ServerLicensingComponent>();

return licensing->GetLicenseKey();
}

class CommerceComponent : public fwRefCountable
{
public:
CommerceComponent(fx::Client* client)
: m_commerceDataLoaded(false), m_client(client)
{

}

inline bool HasCommerceDataLoaded()
{
return m_commerceDataLoaded;
}

void LoadCommerceData();

std::optional<int> GetUserId();

void SetSkus(std::set<int>&& list);

bool OwnsSku(int sku);

void RequestSkuPurchase(int sku);

private:
fx::Client* m_client;

bool m_commerceDataLoaded;

std::set<int> m_ownedSkus;
};

static HttpClient* httpClient;

void CommerceComponent::LoadCommerceData()
{
auto userId = GetUserId();

if (m_commerceDataLoaded || !userId)
{
return;
}

fwRefContainer<CommerceComponent> thisRef(this);

httpClient->DoGetRequest(fmt::sprintf(LICENSING_EP "api/entitlements/%d/%s", *userId, GetLicenseKey()), [thisRef](bool success, const char* data, size_t length)
{
if (success)
{
try
{
auto json = nlohmann::json::parse(std::string(data, length));
std::set<int> skuIds;

for (auto& entry : json["entitlements"])
{
skuIds.insert(entry.value<int>("sku_id", 0));
}

thisRef->SetSkus(std::move(skuIds));
}
catch (const std::exception& e)
{

}
}
});
}

void CommerceComponent::SetSkus(std::set<int>&& list)
{
m_ownedSkus = std::move(list);
m_commerceDataLoaded = true;
}

bool CommerceComponent::OwnsSku(int sku)
{
return m_ownedSkus.find(sku) != m_ownedSkus.end();
}

void CommerceComponent::RequestSkuPurchase(int sku)
{
auto userId = GetUserId();

if (!userId)
{
return;
}

fwRefContainer<CommerceComponent> thisRef(this);
auto clientRef = m_client->shared_from_this();

httpClient->DoGetRequest(fmt::sprintf(LICENSING_EP "api/paymentRequest/%d/%d/%s", *userId, sku, GetLicenseKey()), [thisRef, clientRef](bool success, const char* data, size_t length)
{
if (success)
{
// build the target event
net::Buffer outBuffer;
outBuffer.Write(HashRageString("msgPaymentRequest"));

// payload
outBuffer.Write(data, length);

// send along
clientRef->SendPacket(0, outBuffer, NetPacketType_Reliable);
}
});
}

std::optional<int> CommerceComponent::GetUserId()
{
const auto& identifiers = m_client->GetIdentifiers();

for (const auto& identifier : identifiers)
{
if (identifier.find("fivem:") == 0)
{
int userId = atoi(identifier.substr(6).c_str());

if (userId != 0)
{
return userId;
}
}
}

return {};
}

DECLARE_INSTANCE_TYPE(CommerceComponent);

static InitFunction initFunction([]()
{
httpClient = new HttpClient(L"FXServer/Licensing");

fx::ScriptEngine::RegisterNativeHandler("CAN_PLAYER_START_COMMERCE_SESSION", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
{
return client->GetComponent<CommerceComponent>()->GetUserId() ? true : false;
}));

fx::ServerInstanceBase::OnServerCreate.Connect([](fx::ServerInstanceBase* instance)
{
auto clientRegistry = instance->GetComponent<fx::ClientRegistry>();

clientRegistry->OnClientCreated.Connect([=](fx::Client* client)
{
client->SetComponent(new CommerceComponent(client));
});
});

fx::ScriptEngine::RegisterNativeHandler("LOAD_PLAYER_COMMERCE_DATA", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
{
auto commerceData = client->GetComponent<CommerceComponent>();

commerceData->LoadCommerceData();

return commerceData->HasCommerceDataLoaded();
}));

fx::ScriptEngine::RegisterNativeHandler("IS_PLAYER_COMMERCE_INFO_LOADED", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
{
auto commerceData = client->GetComponent<CommerceComponent>();

return commerceData->HasCommerceDataLoaded();
}));

fx::ScriptEngine::RegisterNativeHandler("DOES_PLAYER_OWN_SKU", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> uint32_t
{
auto commerceData = client->GetComponent<CommerceComponent>();

return commerceData->OwnsSku(context.GetArgument<int>(1));
}));

fx::ScriptEngine::RegisterNativeHandler("REQUEST_PLAYER_COMMERCE_SESSION", MakeClientFunction([](fx::ScriptContext& context, const std::shared_ptr<fx::Client>& client) -> bool
{
auto commerceData = client->GetComponent<CommerceComponent>();
commerceData->RequestSkuPurchase(context.GetArgument<int>(1));

return true;
}));
});
157 changes: 147 additions & 10 deletions code/components/citizen-server-impl/src/TerminalInput.cpp
@@ -1,5 +1,5 @@
#include <StdInc.h>
#include <linenoise.h>
#include <replxx.hxx>

#ifdef _WIN32
#include <io.h>
Expand All @@ -10,11 +10,73 @@
#include <CoreConsole.h>
#include <ServerInstanceBase.h>

#include <boost/algorithm/string/trim.hpp>

#include <utf8.h>
#include <regex>

using namespace std::chrono_literals;

static int utf8str_codepoint_len(char const* s, int utf8len)
{
int codepointLen = 0;
unsigned char m4 = 128 + 64 + 32 + 16;
unsigned char m3 = 128 + 64 + 32;
unsigned char m2 = 128 + 64;
for (int i = 0; i < utf8len; ++i, ++codepointLen)
{
char c = s[i];
if ((c & m4) == m4) {
i += 3;
}
else if ((c & m3) == m3) {
i += 2;
}
else if ((c & m2) == m2) {
i += 1;
}
}
return (codepointLen);
};

void hook_color(std::string const& context, replxx::Replxx::colors_t& colors, std::vector<std::pair<std::string, replxx::Replxx::Color>> const& regex_color)
{
// highlight matching regex sequences
for (auto const& e : regex_color)
{
size_t pos{ 0 };
std::string str = context;
std::smatch match;

while (std::regex_search(str, match, std::regex(e.first)))
{
std::string c{ match[0] };
std::string prefix(match.prefix().str());
pos += utf8str_codepoint_len(prefix.c_str(), static_cast<int>(prefix.length()));
int len(utf8str_codepoint_len(c.c_str(), static_cast<int>(c.length())));

for (int i = 0; i < len; ++i) {
colors.at(pos + i) = e.second;
}

pos += len;
str = match.suffix();
}
}
}

static InitFunction initFunction([]()
{
linenoiseInstallWindowChangeHandler();
static replxx::Replxx rxx;
rxx.install_window_change_handler();

// replxx printing does *not* like incremental writes that don't involve \n as it will instantly overwrite them with its prompt
#if 0
console::CoreSetPrintFunction([](const char* str)
{
rxx.print("%s", str);
});
#endif

fx::ServerInstanceBase::OnServerCreate.Connect([](fx::ServerInstanceBase* instance)
{
Expand All @@ -24,26 +86,102 @@ static InitFunction initFunction([]()

static auto disableTTYVariable = instance->AddVariable<bool>("con_disableNonTTYReads", ConVar_None, false);

linenoiseSetCompletionCallback([](const char* prefix, linenoiseCompletions* lc)
auto getCmdsForContext = [](const std::string& input, int& contextLen)
{
static std::set<std::string> cmds;

// make a substring of the input of [contextLen] codepoints
auto it = input.end();

for (int i = 0; i < contextLen; i++)
{
utf8::prior(it, input.begin());
}

auto inputCopy = std::string(it, input.end());
boost::algorithm::trim_left(inputCopy);

// count new (trimmed) context length
int len = 0;

for (auto it = inputCopy.begin(); it != inputCopy.end(); utf8::next(it, inputCopy.end()))
{
len++;
}

contextLen = len;

// get commands
cmds.clear();

con->GetCommandManager()->ForAllCommands([&](const std::string& cmd)
con->GetCommandManager()->ForAllCommands([&inputCopy](const std::string& cmd)
{
if (strncmp(cmd.c_str(), prefix, strlen(prefix)) == 0)
if (cmd == "_crash")
{
return;
}

if (strncmp(cmd.c_str(), inputCopy.c_str(), inputCopy.length()) == 0)
{
cmds.insert(cmd);
}
});

for (auto& cmd : cmds)
return std::vector<std::string>{cmds.begin(), cmds.end()};
};

using cl = replxx::Replxx::Color;
static std::vector<std::pair<std::string, cl>> regex_color
{
// commands
{ "(^|;\\s*)[^\\s;]+", cl::BRIGHTMAGENTA },

// special characters
{"\\\"", cl::BRIGHTBLUE},
{";", cl::BRIGHTMAGENTA},

// numbers
{"[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // integers
{"[\\-|+]{0,1}[0-9]*\\.[0-9]+", cl::YELLOW}, // decimals
{"[\\-|+]{0,1}[0-9]+e[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // scientific notation

// booleans
{"true", cl::BRIGHTBLUE},
{"false", cl::BRIGHTBLUE},

// strings
{"\".*?(\"|$)", cl::BRIGHTGREEN}, // double quotes

};

rxx.set_completion_callback([=](const std::string& input, int& contextLen)
{
return getCmdsForContext(input, contextLen);
});

rxx.set_hint_callback([=](const std::string& input, int& contextLen, replxx::Replxx::Color& color) -> replxx::Replxx::hints_t
{
if (input.length() > 0)
{
linenoiseAddCompletion(lc, cmd.c_str());
auto cmds = getCmdsForContext(input, contextLen);

if (!cmds.empty())
{
return { cmds[0] };
}
}

return {};
});

rxx.set_highlighter_callback(std::bind(&hook_color, std::placeholders::_1, std::placeholders::_2, std::cref(regex_color)));

rxx.set_word_break_characters(";");

rxx.set_double_tab_completion(false);
rxx.set_complete_on_empty(true);
rxx.set_beep_on_ambiguous_completion(true);

std::thread([=]()
{
while (true)
Expand All @@ -54,7 +192,7 @@ static InitFunction initFunction([]()
continue;
}

char* result = linenoise("cfx> ");
const char* result = rxx.input("cfx> ");

// null result?
if (result == nullptr)
Expand All @@ -65,13 +203,12 @@ static InitFunction initFunction([]()

// make a string and free
std::string resultStr = result;
free(result);

// handle input
con->AddToBuffer(resultStr);
con->AddToBuffer("\n");

linenoiseHistoryAdd(resultStr.c_str());
rxx.history_add(resultStr);

// wait until console buffer was processed
while (!con->IsBufferEmpty())
Expand Down
Expand Up @@ -275,7 +275,7 @@ struct SchedulerInit
ServerGameState::ServerGameState()
: m_frameIndex(0), m_entitiesById(1 << 13)
{
static SchedulerInit si;
static SchedulerInit* si = new SchedulerInit;

m_tg = std::make_unique<tbb::task_group>();
}
Expand Down Expand Up @@ -810,7 +810,7 @@ void ServerGameState::Tick(fx::ServerInstanceBase* instance)
}
}

static SchedulerInit si;
static SchedulerInit* si = new SchedulerInit();

m_tg->run([this, scl]()
{
Expand Down
5 changes: 5 additions & 0 deletions code/components/conhost-v2/src/ConsoleHostImpl.cpp
Expand Up @@ -195,6 +195,11 @@ static InitFunction initFunction([] ()
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';

io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigDockingWithShift = true;
io.ConfigWindowsResizeFromEdges = true;

io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;

static std::string imguiIni = ToNarrow(MakeRelativeCitPath(L"citizen/imgui.ini"));
io.IniFilename = const_cast<char*>(imguiIni.c_str());
Expand Down
918 changes: 918 additions & 0 deletions code/components/debug-net/include/imguivariouscontrols.h

Large diffs are not rendered by default.

204 changes: 150 additions & 54 deletions code/components/debug-net/src/NetDebug.cpp
Expand Up @@ -16,7 +16,9 @@
#include <mmsystem.h>

#include <ConsoleHost.h>

#include <imgui.h>
#include <imguivariouscontrols.h>

const int g_netOverlayOffsetX = -30;
const int g_netOverlayOffsetY = -60;
Expand All @@ -26,6 +28,64 @@ const int g_netOverlayHeight = 300;
const int g_netOverlaySampleSize = 200; // milliseconds per sample frame
const int g_netOverlaySampleCount = 150;

class RageHashList
{
public:
template<int Size>
RageHashList(const char* (&list)[Size])
{
for (int i = 0; i < Size; i++)
{
m_lookupList.insert({ HashRageString(list[i]), list[i] });
}
}

inline std::string LookupHash(uint32_t hash)
{
auto it = m_lookupList.find(hash);

if (it != m_lookupList.end())
{
return std::string(it->second);
}

return fmt::sprintf("0x%08x", hash);
}

private:
std::map<uint32_t, std::string_view> m_lookupList;
};

static const char* g_knownPackets[]
{
"msgConVars",
"msgEnd",
"msgEntityCreate",
"msgNetGameEvent",
"msgObjectIds",
"msgPackedAcks",
"msgPackedClones",
"msgPaymentRequest",
"msgRequestObjectIds",
"msgResStart",
"msgResStop",
"msgRoute",
"msgRpcEntityCreation",
"msgRpcNative",
"msgServerCommand",
"msgServerEvent",
"msgTimeSync",
"msgTimeSyncReq",
"msgWorldGrid",
"msgFrame",
"msgIHost",
"gameStateAck",
"msgNetEvent",
"msgServerEvent",
};

static RageHashList g_hashes{ g_knownPackets };

class NetOverlayMetricSink : public INetMetricSink
{
public:
Expand All @@ -43,9 +103,9 @@ class NetOverlayMetricSink : public INetMetricSink

virtual void OnRouteDelayResult(int msec) override;

virtual void OnIncomingCommand(uint32_t type, size_t size) override;
virtual void OnIncomingCommand(uint32_t type, size_t size, bool reliable) override;

virtual void OnOutgoingCommand(uint32_t type, size_t size) override;
virtual void OnOutgoingCommand(uint32_t type, size_t size, bool reliable) override;

virtual void OverrideBandwidthStats(uint32_t in, uint32_t out) override;

Expand Down Expand Up @@ -91,6 +151,10 @@ class NetOverlayMetricSink : public INetMetricSink

std::mutex m_metricMutex;

std::map<uint32_t, bool> m_incomingReliable;

std::map<uint32_t, bool> m_outgoingReliable;

std::map<uint32_t, size_t> m_incomingMetrics;

std::map<uint32_t, size_t> m_outgoingMetrics;
Expand Down Expand Up @@ -124,7 +188,7 @@ class NetOverlayMetricSink : public INetMetricSink
}
}

CRGBA GetColorIndex(int i);
ImColor GetColorIndex(int i);

void UpdateMetrics();

Expand All @@ -146,20 +210,42 @@ NetOverlayMetricSink::NetOverlayMetricSink()

static ConVar<bool> conVar("netgraph", ConVar_Archive, false, &m_enabled);

OnPostFrontendRender.Connect([=] ()
ConHost::OnShouldDrawGui.Connect([this](bool* should)
{
// update metrics
UpdateMetrics();
*should = *should || m_enabled;
});

// if enabled, render
if (m_enabled)
ConHost::OnDrawGui.Connect([this]()
{
if (!m_enabled)
{
// draw the base metrics
DrawBaseMetrics();
return;
}

auto& io = ImGui::GetIO();

ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x + g_netOverlayOffsetX, io.DisplaySize.y + g_netOverlayOffsetY), ImGuiCond_Once, ImVec2(1.0f, 1.0f));
ImGui::SetNextWindowSize(ImVec2(g_netOverlayWidth, g_netOverlayHeight));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

if (ImGui::Begin("NetGraph", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize))
{
// draw the graph
DrawGraph();

// draw the base metrics
DrawBaseMetrics();
}

ImGui::PopStyleVar();
ImGui::End();
});

OnPostFrontendRender.Connect([=] ()
{
// update metrics
UpdateMetrics();
}, 50);

static ConVar<bool> commandVar("net_showCommands", ConVar_Archive, false, &m_enabledCommands);
Expand All @@ -180,13 +266,16 @@ NetOverlayMetricSink::NetOverlayMetricSink()
static bool showIncoming = true;
static bool showOutgoing = true;

auto showList = [](const decltype(m_lastIncomingMetrics)& list)
auto showList = [](const decltype(m_lastIncomingMetrics)& list, const decltype(m_incomingReliable)& reliable)
{
ImGui::Columns(2);
ImGui::Columns(3);

for (auto& entry : list)
{
ImGui::Text("0x%08x", entry.first);
ImGui::Text("%s", (reliable.find(entry.first)->second ? "R" : "U"));
ImGui::NextColumn();

ImGui::Text("%s", g_hashes.LookupHash(entry.first));
ImGui::NextColumn();

ImGui::Text("%d B", entry.second);
Expand All @@ -198,12 +287,12 @@ NetOverlayMetricSink::NetOverlayMetricSink()

if (ImGui::CollapsingHeader("Incoming", &showIncoming))
{
showList(m_lastIncomingMetrics);
showList(m_lastIncomingMetrics, m_incomingReliable);
}

if (ImGui::CollapsingHeader("Outgoing", &showOutgoing))
{
showList(m_lastOutgoingMetrics);
showList(m_lastOutgoingMetrics, m_outgoingReliable);
}
}

Expand Down Expand Up @@ -269,16 +358,18 @@ void NetOverlayMetricSink::OnRouteDelayResult(int msec)
m_inRouteDelayMax = *std::max_element(m_inRouteDelaySamplesArchive, m_inRouteDelaySamplesArchive + _countof(m_inRouteDelaySamplesArchive));
}

void NetOverlayMetricSink::OnIncomingCommand(uint32_t type, size_t size)
void NetOverlayMetricSink::OnIncomingCommand(uint32_t type, size_t size, bool reliable)
{
std::unique_lock<std::mutex> lock(m_metricMutex);
m_incomingMetrics[type] += size;
m_incomingReliable[type] = reliable;
}

void NetOverlayMetricSink::OnOutgoingCommand(uint32_t type, size_t size)
void NetOverlayMetricSink::OnOutgoingCommand(uint32_t type, size_t size, bool reliable)
{
std::unique_lock<std::mutex> lock(m_metricMutex);
m_outgoingMetrics[type] += size;
m_outgoingReliable[type] = reliable;
}

// log data if enabled
Expand Down Expand Up @@ -379,50 +470,50 @@ void NetOverlayMetricSink::UpdateMetrics()

void NetOverlayMetricSink::DrawGraph()
{
// calculate maximum height for this data subset
float maxHeight = 0;

for (int i = 0; i < _countof(m_metrics); i++)
static const char* names[NET_PACKET_SUB_MAX] =
{
auto metric = m_metrics[i];
auto totalSize = metric.GetTotalSize();

if (totalSize > maxHeight)
{
maxHeight = totalSize;
}
}

// calculate per-sample size
int perSampleSize = (g_netOverlayWidth / g_netOverlaySampleCount);
"Routed Messages",
"Reliables",
"Misc",
"Overhead"
};

for (int i = 0; i < _countof(m_metrics) - 1; i++) // the last entry is transient, so ignore that
static const ImColor colors[NET_PACKET_SUB_MAX] =
{
auto metric = m_metrics[i];
GetColorIndex(0),
GetColorIndex(1),
GetColorIndex(2),
GetColorIndex(3)
};

// base X/Y for this metric
int x = GetOverlayLeft() + (perSampleSize * i);
int y = GetOverlayTop() + (g_netOverlayHeight - 100);
struct DataContext
{
NetPacketMetrics* metrics;
NetPacketSubComponent index;
};

for (int j = 0; j < NET_PACKET_SUB_MAX; j++)
{
// get Y for this submetric
float y1 = ceilf(y - ((metric.GetElementSize((NetPacketSubComponent)j) / maxHeight) * (g_netOverlayHeight - 100)));
float y2 = y;
auto data0 = DataContext{ m_metrics, NET_PACKET_SUB_ROUTED_MESSAGES };
auto data1 = DataContext{ m_metrics, NET_PACKET_SUB_RELIABLES };
auto data2 = DataContext{ m_metrics, NET_PACKET_SUB_MISC };
auto data3 = DataContext{ m_metrics, NET_PACKET_SUB_OVERHEAD };

// set a rectangle
CRect rect(x, y1, x + perSampleSize, y2);
CRGBA color = GetColorIndex(j);
const void* datas[NET_PACKET_SUB_MAX] =
{
&data0,
&data1,
&data2,
&data3
};

TheFonts->DrawRectangle(rect, color);
ImGui::PlotMultiLines("Net Bw", NET_PACKET_SUB_MAX, names, colors, [](const void* cxt, int idx) -> float
{
auto dataContext = (DataContext*)cxt;

// the next one starts where this one left off
y = y1;
}
}
return dataContext->metrics[idx].GetElementSize(dataContext->index);
}, datas, _countof(m_metrics) - 1, FLT_MAX, FLT_MAX, ImVec2(g_netOverlayWidth, g_netOverlayHeight - 100));
}

CRGBA NetOverlayMetricSink::GetColorIndex(int index)
ImColor NetOverlayMetricSink::GetColorIndex(int index)
{
static CRGBA colorTable[] = {
CRGBA(0x00, 0x00, 0xAA),
Expand All @@ -439,7 +530,9 @@ CRGBA NetOverlayMetricSink::GetColorIndex(int index)
CRGBA(0xFF, 0xFF, 0x55)
};

return colorTable[index % _countof(colorTable)];
auto thisColor = colorTable[index % _countof(colorTable)];

return ImColor{ thisColor.red, thisColor.green, thisColor.blue, thisColor.alpha };
}

void NetOverlayMetricSink::DrawBaseMetrics()
Expand All @@ -459,7 +552,9 @@ void NetOverlayMetricSink::DrawBaseMetrics()
int outRoutePackets = m_lastOutRoutePackets;

// drawing
TheFonts->DrawText(va(L"ping: %dms\nin: %d/s\nout: %d/s\nrt: %d/%d/s", ping, inPackets, outPackets, inRoutePackets, outRoutePackets), rect, color, 22.0f, 1.0f, "Lucida Console");
ImGui::Columns(2);
ImGui::Text("%s", va("ping: %dms\nin: %d/s\nout: %d/s\nrt: %d/%d/s", ping, inPackets, outPackets, inRoutePackets, outRoutePackets));
ImGui::NextColumn();

//
// second column
Expand All @@ -475,7 +570,8 @@ void NetOverlayMetricSink::DrawBaseMetrics()
int inRouteDelayMax = m_inRouteDelayMax;

// drawing
TheFonts->DrawText(va(L"\nin: %d b/s\nout: %d b/s\nrd: %d~%dms", inBytes, outBytes, inRouteDelay, inRouteDelayMax), rect, color, 22.0f, 1.0f, "Lucida Console");
ImGui::Text("%s", va("\nin: %d b/s\nout: %d b/s\nrd: %d~%dms", inBytes, outBytes, inRouteDelay, inRouteDelayMax));
ImGui::Columns(1);
}

static InitFunction initFunction([] ()
Expand Down
4,331 changes: 4,331 additions & 0 deletions code/components/debug-net/src/imguivariouscontrols.cpp

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions code/components/devtools-five/src/DrawFPS.cpp
@@ -0,0 +1,73 @@
#include "StdInc.h"
#include <ConsoleHost.h>

#include <imgui.h>

#include <CoreConsole.h>

#include <mmsystem.h>

static InitFunction initFunction([]()
{
static bool drawFpsEnabled;
static bool streamingListEnabled;
static bool streamingMemoryEnabled;

static ConVar<bool> drawFps("cl_drawFPS", ConVar_Archive, false, &drawFpsEnabled);

ConHost::OnShouldDrawGui.Connect([](bool* should)
{
*should = *should || drawFpsEnabled;
});

ConHost::OnDrawGui.Connect([]()
{
if (!drawFpsEnabled)
{
return;
}

auto& io = ImGui::GetIO();

static std::chrono::high_resolution_clock::duration previous;
static uint32_t index;
static std::chrono::microseconds previousTimes[6];

auto t = std::chrono::high_resolution_clock::now().time_since_epoch();
auto frameTime = std::chrono::duration_cast<std::chrono::microseconds>(t - previous);
previous = t;

previousTimes[index % std::size(previousTimes)] = frameTime;
index++;

ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::SetNextWindowPos(ImVec2(10, 10), 0, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

if (ImGui::Begin("DrawFps", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize))
{
if (index > 6)
{
std::chrono::microseconds total{ 0 };

for (int i = 0; i < std::size(previousTimes); i++)
{
total += previousTimes[i];
}

if (total.count() == 0)
{
total = { 1 };
}

uint64_t fps = ((uint64_t)1000000 * 1000) * std::size(previousTimes) / total.count();
fps = (fps + 500) / 1000;

ImGui::Text("%llufps", fps);
}
}

ImGui::PopStyleVar();
ImGui::End();
});
});
10 changes: 7 additions & 3 deletions code/components/devtools-five/src/StreamingDebug.cpp
Expand Up @@ -232,6 +232,7 @@ static InitFunction initFunction([]()
if (ImGui::Begin("Streaming Memory", &streamingMemoryOpen))
{
static std::vector<StreamingMemoryInfo> entryList(streaming->numEntries);
entryList.resize(streaming->numEntries);

int entryIdx = 0;
size_t lockedMem = 0;
Expand All @@ -250,7 +251,12 @@ static InitFunction initFunction([]()
info.virtualMemory = entry.ComputeVirtualSize(i, nullptr, false);
info.physicalMemory = entry.ComputePhysicalSize(i);

entryList[entryIdx] = info;
if (entryIdx < entryList.size())
{
entryList[entryIdx] = info;

entryIdx++;
}

if (!streaming->IsObjectReadyToDelete(i, 0xF1 | 8))
{
Expand All @@ -274,8 +280,6 @@ static InitFunction initFunction([]()

usedMem += info.virtualMemory;
usedPhys += info.physicalMemory;

entryIdx++;
}
}

Expand Down
Expand Up @@ -269,7 +269,7 @@ static HookFunction initFunction([]()

fx::ScriptEngine::RegisterNativeHandler("GET_VEHICLE_DASHBOARD_SPEED", readVehicleMemory<float, DashSpeedOffset>);

fx::ScriptEngine::RegisterNativeHandler("GET_VEHICLE_ACCELERATION", readVehicleMemory<float, AccelerationOffset>);
fx::ScriptEngine::RegisterNativeHandler("GET_VEHICLE_CURRENT_ACCELERATION", readVehicleMemory<float, AccelerationOffset>);

fx::ScriptEngine::RegisterNativeHandler("SET_VEHICLE_GRAVITY", readVehicleMemory<float, AccelerationOffset>);

Expand Down
95 changes: 95 additions & 0 deletions code/components/glue/src/ConnectToNative.cpp
Expand Up @@ -20,6 +20,8 @@
#include <sstream>
#include "KnownFolders.h"
#include <ShlObj.h>
#include <Shellapi.h>
#include <HttpClient.h>

#include <json.hpp>

Expand Down Expand Up @@ -168,8 +170,15 @@ static void HandleAuthPayload(const std::string& payloadStr)
}
}

#include <LegitimacyAPI.h>

static std::string g_discourseClientId;
static std::string g_discourseUserToken;

static InitFunction initFunction([] ()
{
static std::function<void()> g_onYesCallback;

NetLibrary::OnNetLibraryCreate.Connect([] (NetLibrary* lib)
{
netLibrary = lib;
Expand Down Expand Up @@ -249,6 +258,66 @@ static InitFunction initFunction([] ()
disconnected = true;
}
}, 5000);

lib->AddReliableHandler("msgPaymentRequest", [](const char* buf, size_t len)
{
try
{
auto json = nlohmann::json::parse(std::string(buf, len));

se::ScopedPrincipal scope(se::Principal{ "system.console" });
console::GetDefaultContext()->GetVariableManager()->FindEntryRaw("warningMessageResult")->SetValue("0");
console::GetDefaultContext()->ExecuteSingleCommandDirect(ProgramArguments{ "warningmessage", "PURCHASE REQUEST", fmt::sprintf("The server is requesting a purchase of %s for %s.", json.value("sku_name", ""), json.value("sku_price", "")), "Do you want to purchase this item?", "20" });

g_onYesCallback = [json]()
{
std::map<std::string, std::string> postMap;
postMap["data"] = json.value<std::string>("data", "");
postMap["sig"] = json.value<std::string>("sig", "");
postMap["clientId"] = g_discourseClientId;
postMap["userToken"] = g_discourseUserToken;

Instance<HttpClient>::Get()->DoPostRequest("https://keymaster.fivem.net/api/paymentAssign", postMap, [](bool success, const char* data, size_t length)
{
if (success)
{
auto res = nlohmann::json::parse(std::string(data, length));
auto url = res.value("url", "");

if (!url.empty())
{
if (url.find("http://") == 0 || url.find("https://") == 0)
{
ShellExecute(nullptr, L"open", ToWide(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
}
}
});
};
}
catch (const std::exception& e)
{

}
}, true);
});

OnMainGameFrame.Connect([]()
{
if (g_onYesCallback)
{
int result = atoi(console::GetDefaultContext()->GetVariableManager()->FindEntryRaw("warningMessageResult")->GetValue().c_str());

if (result != 0)
{
if (result == 4)
{
g_onYesCallback();
}

g_onYesCallback = {};
}
}
});

OnKillNetwork.Connect([](const char*)
Expand Down Expand Up @@ -375,6 +444,32 @@ static InitFunction initFunction([] ()
TerminateProcess(GetCurrentProcess(), 0);
});
}
else if (!_wcsicmp(type, L"setDiscourseIdentity"))
{
try
{
auto json = nlohmann::json::parse(ToNarrow(arg));

g_discourseUserToken = json.value<std::string>("token", "");
g_discourseClientId = json.value<std::string>("clientId", "");

Instance<::HttpClient>::Get()->DoPostRequest(
"https://lambda.fivem.net/api/validate/discourse",
{
{ "entitlementId", ros::GetEntitlementSource() },
{ "authToken", g_discourseUserToken },
{ "clientId", g_discourseClientId },
},
[](bool, const char*, size_t)
{

});
}
catch (const std::exception& e)
{
trace("failed to set discourse identity: %s\n", e.what());
}
}
});

OnGameFrame.Connect([]()
Expand Down
318 changes: 318 additions & 0 deletions code/components/gta-core-five/src/FontFormatFixes.cpp

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions code/components/gta-core-five/src/GameInit.cpp
Expand Up @@ -16,6 +16,7 @@
#include <Hooking.h>

#include <CoreConsole.h>
#include <scrEngine.h>

FiveGameInit g_gameInit;

Expand Down Expand Up @@ -154,5 +155,39 @@ static InitFunction initFunction([] ()
*(volatile int*)0 = 0;
});

static int warningMessageActive = false;
static ConVar<int> warningMessageResult("warningMessageResult", ConVar_None, 0);
static int wmButtons;

static ConsoleCommand warningMessageCmd("warningMessage", [](const std::string& heading, const std::string& label, const std::string& label2, int buttons)
{
AddCustomText("CUST_WARN_HEADING", heading.c_str());
AddCustomText("CUST_WARN_LABEL", label.c_str());
AddCustomText("CUST_WARN_LABEL2", label2.c_str());

wmButtons = buttons;
warningMessageActive = true;
});

OnGameFrame.Connect([]()
{
if (warningMessageActive)
{
uint32_t headingHash = HashString("CUST_WARN_HEADING");
uint32_t labelHash = HashString("CUST_WARN_LABEL");
uint32_t label2Hash = HashString("CUST_WARN_LABEL2");

NativeInvoke::Invoke<0xDC38CC1E35B6A5D7, uint32_t>("CUST_WARN_HEADING", "CUST_WARN_LABEL", wmButtons, "CUST_WARN_LABEL2", 0, -1, 0, 0, 1);

int result = getWarningResult(true, 0);

if (result != 0)
{
warningMessageResult.GetHelper()->SetRawValue(result);
warningMessageActive = false;
}
}
});

Instance<ICoreGameInit>::Set(&g_gameInit);
});
4 changes: 2 additions & 2 deletions code/components/gta-net-five/src/CloneDebug.cpp
Expand Up @@ -32,7 +32,7 @@ static bool Splitter(bool split_vertically, float thickness, float* size1, float
ImRect bb;
bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f);
return SplitterBehavior(id, bb, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
}

namespace rage
Expand Down Expand Up @@ -743,7 +743,7 @@ static InitFunction initFunction([]()

ImGui::SetNextWindowSizeConstraints(ImVec2(1020.0f, 400.0f), ImVec2(1020.0f, 2000.0f));

if (ImGui::Begin("Network Object Viewer", &novOpen))
if (ImGui::Begin("Network Object Viewer", &novOpen) && rage::netObjectMgr::GetInstance())
{
static float treeSize = 400.f;
static float detailSize = 600.f;
Expand Down
2 changes: 2 additions & 0 deletions code/components/gta-streaming-five/src/PatchPackfileLimit.cpp
@@ -1,6 +1,7 @@
#include <StdInc.h>
#include <Hooking.h>

#if 0
#include <fiCollectionWrapper.h>

constexpr int NUM_STREAMING_ARCHIVES = 4096;
Expand Down Expand Up @@ -192,3 +193,4 @@ static HookFunction hookFunction([]()
hook::call(location, SetStreamingInterface);
}
});
#endif
4 changes: 3 additions & 1 deletion code/components/gta-streaming-five/src/ScaleformHacks.cpp
Expand Up @@ -177,7 +177,7 @@ class GFxMemoryHeap
virtual void Free(void* memory) = 0;
};

static GFxMemoryHeap** g_gfxMemoryHeap;// = (GFxMemoryHeap**)0x142CBB3E8;
GFxMemoryHeap** g_gfxMemoryHeap;// = (GFxMemoryHeap**)0x142CBB3E8;

class GFxRefCountBase
{
Expand Down Expand Up @@ -293,6 +293,8 @@ static uint32_t* g_gfxId;
static void SetupTerritories()
{
g_origSetupTerritories();

overlayRootClip = {};

g_foregroundOverlay3D->CreateEmptyMovieClip(&overlayRootClip, "asTestClip3D");

Expand Down
2 changes: 1 addition & 1 deletion code/components/gta-streaming-five/src/UnkStuff.cpp
Expand Up @@ -633,7 +633,7 @@ static HookFunction hookFunction([]()
// additional netgame checks for scenarios
hook::nop(hook::get_pattern("B2 04 75 65 80 7B 39", 2), 2);
hook::put<uint8_t>(hook::get_pattern("74 24 84 D2 74 20 8B 83", 4), 0xEB);
hook::nop(hook::get_pattern("84 D2 75 41 8B 83", 2), 2);
hook::put<uint8_t>(hook::get_pattern("84 D2 75 41 8B 83", 0x5F), 0xEB);
//hook::put<uint8_t>(hook::get_pattern("40 B6 01 74 52 F3 0F 10 01", 3), 0xEB); // this skips a world grid check, might be bad!

// another scenario cluster network game check
Expand Down
580 changes: 580 additions & 0 deletions code/components/gta-streaming-five/src/sfFontStuff.cpp

Large diffs are not rendered by default.

36 changes: 30 additions & 6 deletions code/components/net-http-server/include/HttpServer.h
Expand Up @@ -10,6 +10,7 @@
#include "TcpServer.h"

#include <forward_list>
#include <shared_mutex>

namespace net
{
Expand Down Expand Up @@ -37,33 +38,53 @@ class HttpRequest : public fwRefCountable

HeaderMap m_headerList;

std::function<void(const std::vector<uint8_t>&)> m_dataHandler;
std::shared_ptr<std::function<void(const std::vector<uint8_t>&)>> m_dataHandler;

std::function<void()> m_cancelHandler;
std::shared_mutex m_dataHandlerMutex;

std::shared_ptr<std::function<void()>> m_cancelHandler;

std::shared_mutex m_cancelHandlerMutex;

public:
HttpRequest(int httpVersionMajor, int httpVersionMinor, const std::string& requestMethod, const std::string& path, const HeaderMap& headerList, const std::string& remoteAddress);

virtual ~HttpRequest() override;

inline const std::function<void(const std::vector<uint8_t>& data)>& GetDataHandler() const
inline std::shared_ptr<std::function<void(const std::vector<uint8_t>& data)>> GetDataHandler()
{
std::shared_lock<std::shared_mutex> lock(m_dataHandlerMutex);
return m_dataHandler;
}

inline void SetDataHandler()
{
std::unique_lock<std::shared_mutex> lock(m_dataHandlerMutex);
m_dataHandler = {};
}

inline void SetDataHandler(const std::function<void(const std::vector<uint8_t>& data)>& handler)
{
m_dataHandler = handler;
std::unique_lock<std::shared_mutex> lock(m_dataHandlerMutex);
m_dataHandler = std::make_shared<std::remove_const_t<std::remove_reference_t<decltype(handler)>>>(handler);
}

inline const std::function<void()>& GetCancelHandler() const
inline std::shared_ptr<std::function<void()>> GetCancelHandler()
{
std::shared_lock<std::shared_mutex> lock(m_cancelHandlerMutex);
return m_cancelHandler;
}

inline void SetCancelHandler()
{
std::unique_lock<std::shared_mutex> lock(m_cancelHandlerMutex);
m_cancelHandler = {};
}

inline void SetCancelHandler(const std::function<void()>& handler)
{
m_cancelHandler = handler;
std::unique_lock<std::shared_mutex> lock(m_cancelHandlerMutex);
m_cancelHandler = std::make_shared<std::remove_const_t<std::remove_reference_t<decltype(handler)>>>(handler);
}

inline std::pair<int, int> GetHttpVersion() const
Expand Down Expand Up @@ -106,6 +127,9 @@ struct HttpState

// a function to call when we want to unblock the request
std::function<void()> ping;

// a lock for ping being set
std::mutex pingLock;
};

class
Expand Down
51 changes: 33 additions & 18 deletions code/components/net-http-server/src/Http1Server.cpp
Expand Up @@ -50,7 +50,7 @@ class Http1Response : public HttpResponse

outData << "HTTP/1.1 " << std::to_string(statusCode) << " " << (statusMessage.empty() ? GetStatusMessage(statusCode) : statusMessage) << "\r\n";

auto& usedHeaders = (headers.size() == 0) ? m_headerList : headers;
auto usedHeaders = (headers.size() == 0) ? m_headerList : headers;

if (usedHeaders.find("date") == usedHeaders.end())
{
Expand Down Expand Up @@ -144,7 +144,7 @@ class Http1Response : public HttpResponse

virtual void End() override
{
if (m_chunked)
if (m_chunked && m_clientStream.GetRef())
{
// assume chunked
m_clientStream->Write("0\r\n\r\n");
Expand All @@ -154,13 +154,20 @@ class Http1Response : public HttpResponse
{
m_requestState->blocked = false;

if (m_requestState->ping)
decltype(m_requestState->ping) ping;

{
m_requestState->ping();
std::unique_lock<std::mutex> lock(m_requestState->pingLock);
ping = m_requestState->ping;
}

if (ping)
{
ping();
}
}

if (m_closeConnection)
if (m_closeConnection && m_clientStream.GetRef())
{
m_clientStream->Close();
}
Expand Down Expand Up @@ -389,13 +396,13 @@ void HttpServerImpl::OnConnection(fwRefContainer<TcpServerStream> stream)
readQueue.erase(readQueue.begin(), readQueue.begin() + contentLength);

// call the data handler
auto& dataHandler = localConnectionData->request->GetDataHandler();
auto dataHandler = localConnectionData->request->GetDataHandler();

if (dataHandler)
{
dataHandler(requestData);
localConnectionData->request->SetDataHandler();

localConnectionData->request->SetDataHandler(std::function<void(const std::vector<uint8_t>&)>());
(*dataHandler)(requestData);
}

// clean up the req/res
Expand Down Expand Up @@ -453,15 +460,15 @@ void HttpServerImpl::OnConnection(fwRefContainer<TcpServerStream> stream)
readQueue.erase(readQueue.begin(), readQueue.begin() + readQueue.size() - result);

// call the data handler
auto& dataHandler = localConnectionData->request->GetDataHandler();
auto dataHandler = localConnectionData->request->GetDataHandler();

if (dataHandler)
{
requestData.resize(localConnectionData->lastLength);
localConnectionData->request->SetDataHandler();

dataHandler(requestData);
requestData.resize(localConnectionData->lastLength);

localConnectionData->request->SetDataHandler(std::function<void(const std::vector<uint8_t>&)>());
(*dataHandler)(requestData);
}

// clean up the req/res
Expand All @@ -479,29 +486,37 @@ void HttpServerImpl::OnConnection(fwRefContainer<TcpServerStream> stream)
}
};

reqState->ping = [=]()
{
readCallback({});
};
std::unique_lock<std::mutex> lock(reqState->pingLock);

reqState->ping = [readCallback]()
{
if (readCallback)
{
readCallback({});
}
};
}

stream->SetReadCallback(readCallback);

stream->SetCloseCallback([=]()
{
if (connectionData->request.GetRef())
{
auto& cancelHandler = connectionData->request->GetCancelHandler();
auto cancelHandler = connectionData->request->GetCancelHandler();

if (cancelHandler)
{
cancelHandler();
(*cancelHandler)();

connectionData->request->SetCancelHandler(std::function<void()>());
connectionData->request->SetCancelHandler();
}

connectionData->request = nullptr;
}

std::unique_lock<std::mutex> lock(reqState->pingLock);
reqState->ping = {};
});
}
Expand Down
10 changes: 5 additions & 5 deletions code/components/net-http-server/src/Http2Server.cpp
Expand Up @@ -126,13 +126,13 @@ class Http2Response : public HttpResponse
{
if (m_request.GetRef() && !m_ended)
{
auto& cancelHandler = m_request->GetCancelHandler();
auto cancelHandler = m_request->GetCancelHandler();

if (cancelHandler)
{
cancelHandler();
(*cancelHandler)();

m_request->SetCancelHandler(std::function<void()>());
m_request->SetCancelHandler();
}
}

Expand Down Expand Up @@ -318,11 +318,11 @@ void Http2ServerImpl::OnConnection(fwRefContainer<TcpServerStream> stream)
if (req->httpReq.GetRef())
{
auto handler = req->httpReq->GetDataHandler();
req->httpReq->SetDataHandler({});
req->httpReq->SetDataHandler();

if (handler)
{
handler(req->body);
(*handler)(req->body);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions code/components/net-tcp-server/include/UvTcpServer.h
Expand Up @@ -10,6 +10,7 @@
#include <uv.h>

#include <memory>
#include <shared_mutex>

#include "TcpServer.h"

Expand All @@ -28,6 +29,8 @@ class UvTcpServerStream : public TcpServerStream

std::unique_ptr<uv_async_t> m_writeCallback;

std::shared_mutex m_writeCallbackMutex;

tbb::concurrent_queue<std::function<void()>> m_pendingRequests;

std::vector<char> m_readBuffer;
Expand Down
96 changes: 60 additions & 36 deletions code/components/net-tcp-server/src/UvTcpServer.cpp
Expand Up @@ -116,10 +116,17 @@ void UvTcpServerStream::CloseClient()
{
if (m_client.get())
{
decltype(m_writeCallback) writeCallback;

{
std::unique_lock<std::shared_mutex> lock(m_writeCallbackMutex);
writeCallback = std::move(m_writeCallback);
}

uv_read_stop(reinterpret_cast<uv_stream_t*>(m_client.get()));

UvClose(std::move(m_client));
UvClose(std::move(m_writeCallback));
UvClose(std::move(writeCallback));
}
}

Expand All @@ -130,10 +137,14 @@ bool UvTcpServerStream::Accept(std::unique_ptr<uv_tcp_t>&& client)
uv_tcp_nodelay(m_client.get(), true);

// initialize a write callback handle
m_writeCallback = std::make_unique<uv_async_t>();
m_writeCallback->data = this;
{
std::unique_lock<std::shared_mutex> lock(m_writeCallbackMutex);
m_writeCallback = std::make_unique<uv_async_t>();

m_writeCallback->data = this;

uv_async_init(m_server->GetManager()->GetLoop(), m_writeCallback.get(), UvCallback<uv_async_t, UvTcpServerStream, &UvTcpServerStream::HandlePendingWrites>);
uv_async_init(m_server->GetManager()->GetLoop(), m_writeCallback.get(), UvCallback<uv_async_t, UvTcpServerStream, &UvTcpServerStream::HandlePendingWrites>);
}

// accept
int result = uv_accept(reinterpret_cast<uv_stream_t*>(m_server->GetServer()),
Expand Down Expand Up @@ -204,43 +215,52 @@ void UvTcpServerStream::Write(const std::vector<uint8_t>& data)
fwRefContainer<UvTcpServerStream> stream;
};

// prepare a write request
UvWriteReq* writeReq = new UvWriteReq;
writeReq->sendData = data;
writeReq->buffer.base = reinterpret_cast<char*>(&writeReq->sendData[0]);
writeReq->buffer.len = writeReq->sendData.size();
writeReq->stream = this;

writeReq->write.data = writeReq;

// submit the write request
m_pendingRequests.push([=]()
{
if (!m_client)
{
return;
}
std::shared_lock<std::shared_mutex> lock(m_writeCallbackMutex);

// send the write request
uv_write(&writeReq->write, reinterpret_cast<uv_stream_t*>(m_client.get()), &writeReq->buffer, 1, [](uv_write_t* write, int status)
if (m_writeCallback)
{
UvWriteReq* req = reinterpret_cast<UvWriteReq*>(write->data);

if (status < 0)
{
//trace("write to %s failed - %s\n", req->stream->GetPeerAddress().ToString().c_str(), uv_strerror(status));
}
// prepare a write request
UvWriteReq* writeReq = new UvWriteReq;
writeReq->sendData = data;
writeReq->buffer.base = reinterpret_cast<char*>(&writeReq->sendData[0]);
writeReq->buffer.len = writeReq->sendData.size();
writeReq->stream = this;

delete req;
});
});
writeReq->write.data = writeReq;

// wake the callback
uv_async_send(m_writeCallback.get());
// submit the write request
m_pendingRequests.push([this, writeReq]()
{
if (!m_client)
{
return;
}

// send the write request
uv_write(&writeReq->write, reinterpret_cast<uv_stream_t*>(m_client.get()), &writeReq->buffer, 1, [](uv_write_t* write, int status)
{
UvWriteReq* req = reinterpret_cast<UvWriteReq*>(write->data);

if (status < 0)
{
//trace("write to %s failed - %s\n", req->stream->GetPeerAddress().ToString().c_str(), uv_strerror(status));
}

delete req;
});
});

// wake the callback
uv_async_send(m_writeCallback.get());
}
}
}

void UvTcpServerStream::ScheduleCallback(const TScheduledCallback& callback)
{
std::shared_lock<std::shared_mutex> lock(m_writeCallbackMutex);

if (m_writeCallback)
{
m_pendingRequests.push(callback);
Expand Down Expand Up @@ -299,11 +319,15 @@ void UvTcpServerStream::Close()
});

// wake the callback
auto wc = m_writeCallback.get();

if (wc)
{
uv_async_send(wc);
std::shared_lock<std::shared_mutex> lock(m_writeCallbackMutex);

auto wc = m_writeCallback.get();

if (wc)
{
uv_async_send(wc);
}
}
}
}
5 changes: 3 additions & 2 deletions code/components/net/include/INetMetricSink.h
Expand Up @@ -28,9 +28,9 @@ class INetMetricSink : public fwRefCountable

virtual void OnRouteDelayResult(int msec) = 0;

virtual void OnIncomingCommand(uint32_t type, size_t size) = 0;
virtual void OnIncomingCommand(uint32_t type, size_t size, bool reliable = false) = 0;

virtual void OnOutgoingCommand(uint32_t type, size_t size) = 0;
virtual void OnOutgoingCommand(uint32_t type, size_t size, bool reliable = false) = 0;

virtual void OverrideBandwidthStats(uint32_t in, uint32_t out) = 0;
};
Expand All @@ -40,6 +40,7 @@ enum NetPacketSubComponent
NET_PACKET_SUB_ROUTED_MESSAGES,
NET_PACKET_SUB_RELIABLES,
NET_PACKET_SUB_MISC,
NET_PACKET_SUB_OVERHEAD,
NET_PACKET_SUB_MAX
};

Expand Down
52 changes: 35 additions & 17 deletions code/components/net/src/NetLibraryImplV2.cpp
Expand Up @@ -37,7 +37,7 @@ class NetLibraryImplV2 : public NetLibraryImplBase
virtual bool IsDisconnected() override;

private:
void ProcessPacket(const uint8_t* data, size_t size);
void ProcessPacket(const uint8_t* data, size_t size, NetPacketMetrics& metrics, ENetPacketFlag flags);

private:
std::string m_connectData;
Expand Down Expand Up @@ -122,7 +122,7 @@ void NetLibraryImplV2::SendReliableCommand(uint32_t type, const char* buffer, si
enet_peer_send(m_serverPeer, 0, packet);
}

m_base->GetMetricSink()->OnOutgoingCommand(type, length);
m_base->GetMetricSink()->OnOutgoingCommand(type, length, true);
}

void NetLibraryImplV2::SendData(const NetAddress& netAddress, const char* data, size_t length)
Expand Down Expand Up @@ -164,6 +164,7 @@ void NetLibraryImplV2::Flush()
void NetLibraryImplV2::RunFrame()
{
uint32_t inDataSize = 0;
NetPacketMetrics inMetrics;

ENetEvent event;

Expand All @@ -183,7 +184,7 @@ void NetLibraryImplV2::RunFrame()
}
case ENET_EVENT_TYPE_RECEIVE:
{
ProcessPacket(event.packet->data, event.packet->dataLength);
ProcessPacket(event.packet->data, event.packet->dataLength, inMetrics, (ENetPacketFlag)event.packet->flags);
inDataSize += event.packet->dataLength;

enet_packet_destroy(event.packet);
Expand Down Expand Up @@ -224,7 +225,7 @@ void NetLibraryImplV2::RunFrame()

enet_peer_send(m_serverPeer, 1, enet_packet_create(msg.GetBuffer(), msg.GetCurLength(), ENET_PACKET_FLAG_UNSEQUENCED));

m_base->GetMetricSink()->OnOutgoingCommand(0xE938445B, packet.payload.size() + 4);
m_base->GetMetricSink()->OnOutgoingCommand(0xE938445B, packet.payload.size() + 4, false);
m_base->GetMetricSink()->OnOutgoingRoutePackets(1);
}

Expand All @@ -244,18 +245,18 @@ void NetLibraryImplV2::RunFrame()
// update received metrics
if (m_host->totalReceivedData != 0)
{
for (uint32_t i = 0; i < m_host->totalReceivedPackets; ++i)
{
NetPacketMetrics m;
m.AddElementSize(NET_PACKET_SUB_MISC, inDataSize);
// actually: overhead
inMetrics.AddElementSize(NET_PACKET_SUB_OVERHEAD, m_host->totalReceivedData - inDataSize);

// actually: overhead
m.AddElementSize(NET_PACKET_SUB_RELIABLES, m_host->totalReceivedData - inDataSize);
m_base->GetMetricSink()->OnIncomingPacket(inMetrics);

m_base->GetMetricSink()->OnIncomingPacket(m);
m_host->totalReceivedData = 0;
inDataSize = 0;

m_host->totalReceivedData = 0;
inDataSize = 0;
for (uint32_t i = 1; i < m_host->totalReceivedPackets; ++i)
{
NetPacketMetrics m;
m_base->GetMetricSink()->OnIncomingPacket(m);
}

m_base->AddReceiveTick();
Expand Down Expand Up @@ -297,7 +298,7 @@ void NetLibraryImplV2::SendConnect(const std::string& connectData)
#endif
}

void NetLibraryImplV2::ProcessPacket(const uint8_t* data, size_t size)
void NetLibraryImplV2::ProcessPacket(const uint8_t* data, size_t size, NetPacketMetrics& metrics, ENetPacketFlag flags)
{
NetBuffer msg((char*)data, size);
uint32_t msgType = msg.Read<uint32_t>();
Expand Down Expand Up @@ -358,9 +359,9 @@ void NetLibraryImplV2::ProcessPacket(const uint8_t* data, size_t size)
return;
}

m_base->GetMetricSink()->OnIncomingCommand(msgType, size);
m_base->GetMetricSink()->OnIncomingCommand(msgType, size, (flags & ENET_PACKET_FLAG_RELIABLE) != 0);

if (msgType == 0xE938445B) // 'msgRoute'
if (msgType == HashRageString("msgRoute")) // 'msgRoute'
{
uint16_t netID = msg.Read<uint16_t>();
uint16_t rlength = msg.Read<uint16_t>();
Expand All @@ -377,9 +378,26 @@ void NetLibraryImplV2::ProcessPacket(const uint8_t* data, size_t size)

// add to metrics
m_base->GetMetricSink()->OnIncomingRoutePackets(1);

// add as routed message
metrics.AddElementSize(NET_PACKET_SUB_ROUTED_MESSAGES, size);
}
else if (msgType != 0xCA569E63) // reliable command
else if (msgType != HashRageString("msgEnd")) // reliable command
{
auto subType = NET_PACKET_SUB_MISC;

// cloning data is considered routing
if (msgType == HashRageString("msgPackedAcks") || msgType == HashRageString("msgPackedClones"))
{
subType = NET_PACKET_SUB_ROUTED_MESSAGES;
}
else if (flags & ENET_PACKET_FLAG_RELIABLE)
{
subType = NET_PACKET_SUB_RELIABLES;
}

metrics.AddElementSize(subType, size);

size_t reliableSize = size - 4;

std::vector<char> reliableBuf(reliableSize);
Expand Down
6 changes: 4 additions & 2 deletions code/components/nui-core/src/NUIClient.cpp
Expand Up @@ -39,8 +39,10 @@ NUIClient::NUIClient(NUIWindow* window)

m_renderHandler = new NUIRenderHandler(this);

CefRefPtr<NUIClient> thisRef(this);

auto httpClient = Instance<HttpClient>::Get();
httpClient->DoGetRequest("https://runtime.fivem.net/nui-blacklist.json", [=](bool success, const char* data, size_t length)
httpClient->DoGetRequest("https://runtime.fivem.net/nui-blacklist.json", [thisRef](bool success, const char* data, size_t length)
{
if (success)
{
Expand All @@ -55,7 +57,7 @@ NUIClient::NUIClient(NUIWindow* window)
{
if (it->IsString())
{
m_requestBlacklist.emplace_back(it->GetString(), std::regex_constants::ECMAScript | std::regex_constants::icase);
thisRef->m_requestBlacklist.emplace_back(it->GetString(), std::regex_constants::ECMAScript | std::regex_constants::icase);
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions code/components/steam/src/ClientEngineMapper.cpp
Expand Up @@ -101,7 +101,9 @@ bool ClientEngineMapper::IsMethodAnInterface(void* methodPtr, bool* isUser, bool
ud_set_mode(&ud, 64);
#endif

bool hadUnwantedCall = false;
bool hadExternCall = false;
bool hadMov = false;

// set the program counter
ud_set_pc(&ud, reinterpret_cast<uint64_t>(methodPtr));
Expand Down Expand Up @@ -224,6 +226,8 @@ bool ClientEngineMapper::IsMethodAnInterface(void* methodPtr, bool* isUser, bool
// get the first operand
auto operand = ud_insn_opr(&ud, 0);

bool isWantedCall = false;

// if the operand is immediate
if (operand->type == UD_OP_JIMM)
{
Expand Down Expand Up @@ -251,6 +255,7 @@ bool ClientEngineMapper::IsMethodAnInterface(void* methodPtr, bool* isUser, bool
if (*(char**)operandPtr == (char*)GetProcAddress(GetModuleHandleW(L"tier0_s64.dll"), "?Lock@CThreadMutex@@QEAAXXZ"))
{
hadExternCall = true;
isWantedCall = true;
}
}
else
Expand All @@ -268,6 +273,32 @@ bool ClientEngineMapper::IsMethodAnInterface(void* methodPtr, bool* isUser, bool
}
}
}

if (!isWantedCall)
{
hadUnwantedCall = true;
}
}
// and another 2019-05 update breaks yet another thing
else if (!child && ud_insn_mnemonic(&ud) == UD_Icmp)
{
auto operand1 = ud_insn_opr(&ud, 0);
auto operand = ud_insn_opr(&ud, 1);

if (operand1->type == UD_OP_REG && operand->type == UD_OP_IMM && operand->lval.udword == 0xFF && hadExternCall && hadMov && !hadUnwantedCall)
{
*isUser = true;
return true;
}
}
else if (!child && ud_insn_mnemonic(&ud) == UD_Imov)
{
auto operand = ud_insn_opr(&ud, 1);

if (operand->type == UD_OP_REG && operand->base == UD_R_R8D)
{
hadMov = true;
}
}
#else
#error Current machine type not supported in InterfaceMapper
Expand Down
3 changes: 2 additions & 1 deletion code/vendor/imgui.lua
Expand Up @@ -12,7 +12,8 @@ return {
files_project "../vendor/imgui/" {
"imgui.cpp",
"imgui_draw.cpp",
"imgui_demo.cpp"
"imgui_demo.cpp",
"imgui_widgets.cpp",
}
end
}
5 changes: 3 additions & 2 deletions code/vendor/linenoise.lua
@@ -1,14 +1,15 @@
return {
include = function()
includedirs "../vendor/linenoise-ng/include/"
includedirs "../vendor/replxx/include/"
end,

run = function()
language "C++"
kind "StaticLib"

files {
"../vendor/linenoise-ng/src/*.cpp",
"../vendor/replxx/src/*.cpp",
"../vendor/replxx/src/*.cxx",
}
end
}
4 changes: 2 additions & 2 deletions data/client/citizen/common/data/gameconfig.xml
Expand Up @@ -25,11 +25,11 @@
</Item>
<Item>
<PoolName>CNetObjPed</PoolName>
<PoolSize value="174"/> <!-- 110 + player count -->
<PoolSize value="238"/> <!-- 110 + player count -->
</Item>
<Item>
<PoolName>CNetBlenderPed</PoolName>
<PoolSize value="174"/> <!-- 110 + player count -->
<PoolSize value="238"/> <!-- 110 + player count -->
</Item>
<Item>
<PoolName>CNetObjPed::CNetTennisMotionData</PoolName>
Expand Down
Binary file added data/client/citizen/font_lib_cfx.gfx
Binary file not shown.
8 changes: 8 additions & 0 deletions ext/cfx-ui/src/app/discourse.service.ts
Expand Up @@ -231,6 +231,14 @@ export class DiscourseService {
return `${DiscourseService.BASE_URL}/user-api-key/new?${this.serializeParams(params)}`;
}

public getToken() {
return this.authToken;
}

public getExtClientId() {
return this.clientId;
}

private async generateNonce() {
this.nonce = randomBytes(16);
window.localStorage.setItem('lastAuthNonce', this.nonce);
Expand Down
12 changes: 12 additions & 0 deletions ext/cfx-ui/src/app/game.service.ts
Expand Up @@ -208,6 +208,10 @@ export abstract class GameService {
public setConvar(name: string, value: string) {

}

public setDiscourseIdentity(token: string, clientId: string) {

}
}

export class ServerHistoryEntry {
Expand Down Expand Up @@ -258,6 +262,10 @@ export class CfxGameService extends GameService {
}
});

this.discourseService.signinChange.subscribe(identity => {
this.setDiscourseIdentity(this.discourseService.getToken(), this.discourseService.getExtClientId());
});

this.discourseService.messageEvent.subscribe((msg) => {
this.invokeInformational(msg);
});
Expand Down Expand Up @@ -575,6 +583,10 @@ export class CfxGameService extends GameService {
openUrl(url: string): void {
(<any>window).invokeNative('openUrl', url);
}

setDiscourseIdentity(token: string, clientId: string) {
(<any>window).invokeNative('setDiscourseIdentity', JSON.stringify({ token, clientId }));
}
}

@Injectable()
Expand Down
2 changes: 1 addition & 1 deletion vendor/curl
Submodule curl updated 1 files
+1 −1 lib/file.c
2 changes: 1 addition & 1 deletion vendor/imgui
Submodule imgui updated 217 files
1 change: 1 addition & 0 deletions vendor/replxx
Submodule replxx added at d798b7