Client API remoting stuff

This commit is contained in:
Sardelka9515
2023-02-12 22:06:57 +08:00
parent fb654f0e56
commit ac07edfe68
34 changed files with 764 additions and 212 deletions

View File

@ -0,0 +1,45 @@
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
public static class Config
{
public static System.String Username => GetConfig<System.String>("Username");
public static System.Boolean EnableAutoRespawn => GetConfig<System.Boolean>("EnableAutoRespawn");
public static GTA.BlipColor BlipColor => GetConfig<GTA.BlipColor>("BlipColor");
public static GTA.BlipSprite BlipSprite => GetConfig<GTA.BlipSprite>("BlipSprite");
public static System.Single BlipScale => GetConfig<System.Single>("BlipScale");
public static System.Boolean ShowPlayerNameTag => GetConfig<System.Boolean>("ShowPlayerNameTag");
}
#region PROPERTIES
public static System.Int32 LocalPlayerID => GetProperty<System.Int32>("LocalPlayerID");
public static System.Boolean IsOnServer => GetProperty<System.Boolean>("IsOnServer");
public static System.Net.IPEndPoint ServerEndPoint => GetProperty<System.Net.IPEndPoint>("ServerEndPoint");
public static System.Boolean IsMenuVisible => GetProperty<System.Boolean>("IsMenuVisible");
public static System.Boolean IsChatFocused => GetProperty<System.Boolean>("IsChatFocused");
public static System.Boolean IsPlayerListVisible => GetProperty<System.Boolean>("IsPlayerListVisible");
public static System.Version CurrentVersion => GetProperty<System.Version>("CurrentVersion");
public static System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo> Players => GetProperty<System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo>>("Players");
#endregion
#region FUNCTIONS
public static void Connect(System.String address) => InvokeCommand("Connect", address);
public static void Disconnect() => InvokeCommand("Disconnect");
public static System.Collections.Generic.List<RageCoop.Core.ServerInfo> ListServers() => InvokeCommand<System.Collections.Generic.List<RageCoop.Core.ServerInfo>>("ListServers");
public static void LocalChatMessage(System.String from, System.String message) => InvokeCommand("LocalChatMessage", from, message);
public static void SendChatMessage(System.String message) => InvokeCommand("SendChatMessage", message);
public static RageCoop.Client.Scripting.ClientResource GetResource(System.String name) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResource", name);
public static System.Object GetConfig(System.String name) => InvokeCommand<System.Object>("GetConfig", name);
public static void SetConfig(System.String name, System.String jsonVal) => InvokeCommand("SetConfig", name, jsonVal);
#endregion
}
}

View File

@ -0,0 +1,128 @@
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: DisableRuntimeMarshalling]
[assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
static readonly ThreadLocal<BufferWriter> _bufWriter = new(() => new(4096));
static readonly ThreadLocal<BufferReader> _bufReader = new(() => new());
/// <summary>
/// Copy content of string to a sequential block of memory
/// </summary>
/// <param name="strs"></param>
/// <returns>Pointer to the start of the block, can be used as argv</returns>
/// <remarks>Call <see cref="Marshal.FreeHGlobal(nint)"/> with the returned pointer when finished using</remarks>
internal static char** StringArrayToMemory(string[] strs)
{
var argc = strs.Length;
var cbSize = sizeof(IntPtr) * argc + strs.Sum(s => (s.Length + 1) * sizeof(char));
var result = (char**)Marshal.AllocHGlobal(cbSize);
var pCurStr = (char*)(result + argc);
for (int i = 0; i < argc; i++)
{
result[i] = pCurStr;
var len = strs[i].Length;
var cbStrSize = (len + 1) * sizeof(char); // null terminator
fixed (char* pStr = strs[i])
{
System.Buffer.MemoryCopy(pStr, pCurStr, cbStrSize, cbStrSize);
}
pCurStr += len + 1;
}
return result;
}
internal static void InvokeCommand(string name, params object[] args)
=> InvokeCommandAsJson(name, args);
internal static T InvokeCommand<T>(string name, params object[] args)
=> JsonDeserialize<T>(InvokeCommandAsJson(name, args));
/// <summary>
/// Invoke command and get the return value as json
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <returns>The json representation of returned object</returns>
/// <exception cref="Exception"></exception>
internal static string InvokeCommandAsJson(string name, params object[] args)
{
var argc = args.Length;
var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray());
try
{
var resultLen = InvokeCommandAsJsonUnsafe(name, argc, argv);
if (resultLen == 0)
throw new Exception(GetLastResult());
return GetLastResult();
}
finally
{
Marshal.FreeHGlobal((IntPtr)argv);
}
}
public static string GetLastResult()
{
var countCharsRequired = GetLastResultLenInChars() + 1;
if (countCharsRequired > _resultBuf.Value.Length)
{
_resultBuf.Value = new char[countCharsRequired];
}
var cbBufSize = _resultBuf.Value.Length * sizeof(char);
fixed (char* pBuf = _resultBuf.Value)
{
if (GetLastResult(pBuf, cbBufSize) > 0)
{
return new string(pBuf);
}
return null;
}
}
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
{
var writer = _bufWriter.Value;
writer.Reset();
CustomEvents.WriteObjects(writer, args);
SendCustomEvent(flags, hash, writer.Address, writer.Position);
}
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
internal static T GetProperty<T>(string name) => JsonDeserialize<T>(GetPropertyAsJson(name));
internal static T GetConfig<T>(string name) => JsonDeserialize<T>(GetConfigAsJson(name));
internal static void SetProperty(string name, object val) => InvokeCommand("SetProperty", name, val);
internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val);
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)]
public static partial CustomEventHash GetEventHash(string name);
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial void SetLastResult(string msg);
[LibraryImport("RageCoop.Client.dll")]
private static partial int GetLastResult(char* buf, int cbBufSize);
[LibraryImport("RageCoop.Client.dll", EntryPoint = "InvokeCommand", StringMarshalling = StringMarshalling.Utf16)]
private static partial int InvokeCommandAsJsonUnsafe(string name, int argc, char** argv);
[LibraryImport("RageCoop.Client.dll")]
private static partial void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData);
[LibraryImport("RageCoop.Client.dll")]
private static partial int GetLastResultLenInChars();
}
}

View File

@ -0,0 +1,9 @@
using GTA;
namespace RageCoop.Client.Scripting
{
[ScriptAttributes(NoDefaultInstance = true)]
public class ClientScript : Script
{
}
}

View File

@ -3,11 +3,11 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\RageCoop.Core.csproj" /> <ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,5 @@
using System; global using static RageCoop.Core.Shared;
global using static RageCoop.Client.Scripting.Shared;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -6,7 +7,7 @@ using System.Threading.Tasks;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
internal class ClientScript internal class Shared
{ {
} }
} }

60
Client/Scripting/Types.cs Normal file
View File

@ -0,0 +1,60 @@
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript" /> instance in this resource.
/// </summary>
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
/// <summary>
/// Get the <see cref="ResourceFile" /> where this script is loaded from.
/// </summary>
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
/// <summary>
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
/// </summary>
public Core.Logger Logger { get; internal set; }
}
public class PlayerInfo
{
public byte HolePunchStatus { get; internal set; }
public bool IsHost { get; internal set; }
public string Username { get; internal set; }
public int ID { get; internal set; }
public int EntityHandle { get; internal set; }
public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
public float Ping { get; internal set; }
public float PacketTravelTime { get; internal set; }
public bool DisplayNameTag { get; internal set; }
public bool HasDirectConnection { get; internal set; }
}
}

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Reflection;
using System.Threading; using System.Threading;
using GTA; using GTA;
using GTA.Math; using GTA.Math;
@ -237,6 +238,23 @@ namespace RageCoop.Client
return; return;
} }
if (e.KeyCode == Keys.U)
{
foreach (var prop in typeof(APIBridge).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
Console.PrintInfo($"{prop.Name}: {JsonSerialize(prop.GetValue(null))}");
}
foreach (var prop in typeof(APIBridge.Config).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
Console.PrintInfo($"{prop.Name}: {JsonSerialize(prop.GetValue(null))}");
}
}
if (e.KeyCode == Keys.I)
{
APIBridge.SendChatMessage("hello there");
}
#if CEF #if CEF
if (CefRunning) if (CefRunning)
{ {
@ -263,14 +281,14 @@ namespace RageCoop.Client
if (Game.IsControlPressed(Control.FrontendPause)) if (Game.IsControlPressed(Control.FrontendPause))
{ {
Call(ACTIVATE_FRONTEND_MENU, Call(ACTIVATE_FRONTEND_MENU,
Call<int>(GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0); SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return; return;
} }
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause) if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{ {
Call(ACTIVATE_FRONTEND_MENU, Call(ACTIVATE_FRONTEND_MENU,
Call<int>(GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0); SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return; return;
} }
} }

View File

@ -37,7 +37,7 @@ namespace RageCoop.Client
{ {
if (File.Exists(AnimationsDataPath)) if (File.Exists(AnimationsDataPath))
{ {
var anims = JsonConvert.DeserializeObject<AnimDic[]>(File.ReadAllText(AnimationsDataPath)); var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
foreach (var anim in anims) foreach (var anim in anims)
foreach (var a in anim.Animations) foreach (var a in anim.Animations)
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, Main.P, anim.DictionaryName, a, 3)) if (Call<bool>(IS_ENTITY_PLAYING_ANIM, Main.P, anim.DictionaryName, a, 3))

View File

@ -58,7 +58,7 @@ namespace RageCoop.Client.Menus
List<ServerInfo> serverList = null; List<ServerInfo> serverList = null;
var realUrl = Main.Settings.MasterServer; var realUrl = Main.Settings.MasterServer;
serverList = null; serverList = null;
try { serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl)); } try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
catch (Exception ex) { Main.Logger.Error(ex); } catch (Exception ex) { Main.Logger.Error(ex); }
// Need to be processed in main thread // Need to be processed in main thread

View File

@ -4,6 +4,7 @@ using System.Drawing;
using GTA; using GTA;
using GTA.UI; using GTA.UI;
using LemonUI.Menus; using LemonUI.Menus;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.Menus namespace RageCoop.Client.Menus
{ {
@ -66,8 +67,7 @@ namespace RageCoop.Client.Menus
}; };
_showNametag.Activated += (s, e) => _showNametag.Activated += (s, e) =>
{ {
Main.Settings.ShowPlayerNameTag = _showNametag.Checked; API.Config.ShowPlayerNameTag = _showNametag.Checked;
Util.SaveSettings();
}; };
Menu.Add(_disableTrafficItem); Menu.Add(_disableTrafficItem);

View File

@ -5,6 +5,7 @@ using GTA;
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using Newtonsoft.Json;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting; using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
@ -127,9 +128,21 @@ namespace RageCoop.Client
} }
} }
public class Player internal class Player
{ {
internal float _latencyToServer; internal float _latencyToServer;
internal bool ConnectWhenPunched { get; set; }
[JsonIgnore]
public Blip FakeBlip { get; internal set; }
[JsonIgnore]
public Vector3 Position { get; internal set; }
[JsonIgnore]
public SyncedPed Character { get; internal set; }
[JsonIgnore]
public NetConnection Connection { get; internal set; }
public byte HolePunchStatus { get; internal set; } = 1; public byte HolePunchStatus { get; internal set; } = 1;
public bool IsHost { get; internal set; } public bool IsHost { get; internal set; }
public string Username { get; internal set; } public string Username { get; internal set; }
@ -139,12 +152,10 @@ namespace RageCoop.Client
/// </summary> /// </summary>
public int ID { get; internal set; } public int ID { get; internal set; }
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
public IPEndPoint InternalEndPoint { get; internal set; } public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; } public IPEndPoint ExternalEndPoint { get; internal set; }
internal bool ConnectWhenPunched { get; set; }
public Blip FakeBlip { get; internal set; }
public Vector3 Position { get; internal set; }
public SyncedPed Character { get; internal set; }
/// <summary> /// <summary>
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection. /// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
@ -157,7 +168,6 @@ namespace RageCoop.Client
: Networking.Latency + _latencyToServer; : Networking.Latency + _latencyToServer;
public bool DisplayNameTag { get; set; } = true; public bool DisplayNameTag { get; set; } = true;
public NetConnection Connection { get; internal set; }
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected; public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
} }
} }

View File

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <NoAotCompile>false</NoAotCompile>
<TargetFramework>net7.0</TargetFramework>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch> <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir> <OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
@ -21,6 +22,9 @@
<EmbeddedResource Remove="GUI\**" /> <EmbeddedResource Remove="GUI\**" />
<None Remove="GUI\**" /> <None Remove="GUI\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="Scripting\ClientScript.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\AssemblyInfo.cs"> <Compile Update="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
@ -50,6 +54,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" /> <ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NAudio" Version="2.1.0" /> <PackageReference Include="NAudio" Version="2.1.0" />

View File

@ -1,87 +1,94 @@
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static RageCoop.Core.Scripting.CustomEvents; using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
public static unsafe partial class API internal static unsafe partial class API
{ {
[UnmanagedCallersOnly(EntryPoint = "Connect")] [ThreadStatic]
public static void Connect(char* address) => Connect(new string(address)); static string _lastResult;
[UnmanagedCallersOnly(EntryPoint = "GetLocalPlayerID")] [UnmanagedCallersOnly(EntryPoint = nameof(GetLastResult))]
public static int GetLocalPlayerID() => LocalPlayerID; public static int GetLastResult(char* buf, int cbBufSize)
/// <summary>
/// Get configuration value
/// </summary>
/// <param name="szName">The name of the config</param>
/// <param name="buf">Buffer to store retrived value</param>
/// <param name="bufSize">Buffer size</param>
/// <returns>The string length of returned value, not including the null terminator</returns>
[UnmanagedCallersOnly(EntryPoint = "GetConfigValue")]
public static int GetConfigValue(char* szName, char* buf, int bufSize)
{ {
var name = new string(szName); if (_lastResult == null)
var value = name switch
{
nameof(Config.EnableAutoRespawn) => Config.EnableAutoRespawn.ToString(),
nameof(Config.Username) => Config.Username.ToString(),
nameof(Config.BlipColor) => Config.BlipColor.ToString(),
nameof(Config.BlipScale) => Config.BlipScale.ToString(),
nameof(Config.BlipSprite) => Config.BlipSprite.ToString(),
_ => null
};
if (value == null)
return 0; return 0;
fixed (char* p = value) fixed (char* pErr = _lastResult)
{ {
var cbRequired = (value.Length + 1) * sizeof(char); var cbToCopy = sizeof(char) * (_lastResult.Length + 1);
Buffer.MemoryCopy(p, buf, bufSize, cbRequired); System.Buffer.MemoryCopy(pErr, buf, cbToCopy, Math.Min(cbToCopy, cbBufSize));
return value.Length; if (cbToCopy > cbBufSize && cbBufSize > 0)
{
buf[cbBufSize / sizeof(char) - 1] = '\0'; // Always add null terminator
}
return _lastResult.Length;
}
}
public static void SetLastResult(string msg) => _lastResult = msg;
[UnmanagedCallersOnly(EntryPoint = nameof(SetLastResult))]
public static void SetLastResult(char* msg)
{
try
{
SetLastResult(msg == null ? null : new string(msg));
}
catch (Exception ex)
{
SHVDN.PInvoke.MessageBoxA(default, ex.ToString(), "error", default);
} }
} }
[UnmanagedCallersOnly(EntryPoint = "SetConfigValue")] [UnmanagedCallersOnly(EntryPoint = nameof(GetEventHash))]
public static void SetConfigValue(char* szName, char* szValue)
{
var name = new string(szName);
var value = new string(szValue);
switch (name)
{
case nameof(Config.EnableAutoRespawn): Config.EnableAutoRespawn = bool.Parse(value); break;
case nameof(Config.Username): Config.Username = value; break;
case nameof(Config.BlipColor): Config.BlipColor = Enum.Parse<BlipColor>(value); break;
case nameof(Config.BlipScale): Config.BlipScale = float.Parse(value); break;
case nameof(Config.BlipSprite): Config.BlipSprite = Enum.Parse<BlipSprite>(value); break;
};
}
[UnmanagedCallersOnly(EntryPoint = "LocalChatMessage")]
public static void LocalChatMessage(char* from, char* msg) => LocalChatMessage(new string(from), new string(msg));
[UnmanagedCallersOnly(EntryPoint = "SendChatMessage")]
public static void SendChatMessage(char* msg) => SendChatMessage(new string(msg));
[UnmanagedCallersOnly(EntryPoint = "GetEventHash")]
public static CustomEventHash GetEventHash(char* name) => new string(name); public static CustomEventHash GetEventHash(char* name) => new string(name);
public static void SendCustomEvent(int hash, CustomEventFlags flags, byte* data, int cbData) [UnmanagedCallersOnly(EntryPoint = nameof(SendCustomEvent))]
public static void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData)
{ {
var payload = new byte[cbData];
Marshal.Copy((IntPtr)data, payload, 0, cbData);
Networking.Peer.SendTo(new Packets.CustomEvent()
{
Flags = flags,
Payload = payload,
Hash = hash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
} }
[UnmanagedCallersOnly(EntryPoint = nameof(InvokeCommand))]
public static int InvokeCommand(char* name, int argc, char** argv)
{
try
{
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = new(argv[i]);
}
_lastResult = _invokeCommand(new string(name), args);
return _lastResult.Length;
}
catch (Exception ex)
{
Main.Logger.Error(ex);
SetLastResult(ex.ToString());
return 0;
}
}
[UnmanagedCallersOnly(EntryPoint = nameof(GetLastResultLenInChars))]
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
/// <summary> /// <summary>
/// Convert Entity ID to handle /// Convert Entity ID to handle
/// </summary> /// </summary>
[UnmanagedCallersOnly(EntryPoint = "IdToHandle")] [UnmanagedCallersOnly(EntryPoint = nameof(IdToHandle))]
public static int IdToHandle(byte type, int id) public static int IdToHandle(byte type, int id)
{ {
return type switch return type switch

View File

@ -0,0 +1,55 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class RemotingAttribute : Attribute
{
public bool GenBridge = true;
}
/// <summary>
/// Some local remoting implementation with json-based serialization, somewhar slow, but convenient
/// </summary>
internal static unsafe partial class API
{
static readonly MethodInfo[] _apiEntries =
typeof(API).GetMethods(BindingFlags.Static | BindingFlags.Public);
static readonly Dictionary<string, MethodInfo> _commands =
new(typeof(API).GetMethods().
Where(md => md.CustomAttributes.
Any(attri => attri.AttributeType == typeof(RemotingAttribute))).
Select(x => new KeyValuePair<string, MethodInfo>(x.Name, x)));
static string _invokeCommand(string name, string[] argsJson)
{
if (_commands.TryGetValue(name, out var method))
{
var ps = method.GetParameters();
if (argsJson.Length != ps.Length)
throw new ArgumentException($"Parameter count mismatch, expecting {ps.Length} parameters, got {argsJson.Length}", nameof(argsJson));
object[] args = new object[ps.Length];
for (int i = 0; i < ps.Length; i++)
{
args[i] = JsonDeserialize(argsJson[i], ps[i].ParameterType);
}
var result = method.Invoke(null, args);
if (method.ReturnType == typeof(void))
{
return "void";
}
return JsonSerialize(result);
}
throw new KeyNotFoundException($"Command {name} was not found");
}
}
}

View File

@ -1,15 +1,17 @@
#undef DEBUG #undef DEBUG
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using GTA;
using Lidgren.Network; using Lidgren.Network;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
[assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
@ -31,7 +33,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Provides vital functionality to interact with RAGECOOP /// Provides vital functionality to interact with RAGECOOP
/// </summary> /// </summary>
public static unsafe partial class API internal static unsafe partial class API
{ {
#region INTERNAL #region INTERNAL
@ -78,6 +80,17 @@ namespace RageCoop.Client.Scripting
/// Get or set scale of player's blip /// Get or set scale of player's blip
/// </summary> /// </summary>
public static float BlipScale { get; set; } = 1; public static float BlipScale { get; set; } = 1;
public static bool ShowPlayerNameTag
{
get => Main.Settings.ShowPlayerNameTag;
set
{
if (value == ShowPlayerNameTag) return;
Main.Settings.ShowPlayerNameTag = value;
Util.SaveSettings();
}
}
} }
/// <summary> /// <summary>
@ -207,7 +220,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Get all players indexed by their ID /// Get all players indexed by their ID
/// </summary> /// </summary>
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players); public static Dictionary<int, Player> Players => new(PlayerList.Players);
#endregion #endregion
@ -255,57 +268,6 @@ namespace RageCoop.Client.Scripting
WorldThread.QueueAction(a); WorldThread.QueueAction(a);
} }
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "Disconnect")]
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
public static List<ServerInfo> ListServers()
{
return JsonConvert.DeserializeObject<List<ServerInfo>>(
HttpHelper.DownloadString(Main.Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
public static void LocalChatMessage(string from, string message)
{
Main.MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
public static void SendChatMessage(string message)
{
Networking.SendChatMessage(message);
}
/// <summary> /// <summary>
/// Send an event and data to the server. /// Send an event and data to the server.
/// </summary> /// </summary>
@ -315,13 +277,7 @@ namespace RageCoop.Client.Scripting
/// types /// types
/// </param> /// </param>
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args) public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
{ => SendCustomEvent(CustomEventFlags.None, eventHash, args);
Networking.Peer.SendTo(new Packets.CustomEvent
{
Args = args,
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
/// <summary> /// <summary>
/// Send an event and data to the server /// Send an event and data to the server
@ -346,8 +302,7 @@ namespace RageCoop.Client.Scripting
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread. /// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary> /// </summary>
/// <param name="hash"> /// <param name="hash">
/// An unique identifier of the event, you can hash your event name with /// An unique identifier of the event
/// <see cref="Core.Scripting.CustomEvents.Hash(string)" />
/// </param> /// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param> /// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler) public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
@ -384,6 +339,102 @@ namespace RageCoop.Client.Scripting
}); });
} }
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
[Remoting]
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
[Remoting]
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
[Remoting]
public static List<ServerInfo> ListServers()
{
return JsonDeserialize<List<ServerInfo>>(
HttpHelper.DownloadString(Main.Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
[Remoting]
public static void LocalChatMessage(string from, string message)
{
Main.MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
[Remoting]
public static void SendChatMessage(string message)
{
if (!IsOnServer)
throw new InvalidOperationException("Not on server");
Networking.SendChatMessage(message);
}
[Remoting]
public static ClientResource GetResource(string name)
{
if (Main.Resources.LoadedResources.TryGetValue(name, out var resource))
return resource;
return null;
}
[Remoting(GenBridge = false)]
public static object GetProperty(string name)
=> typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting(GenBridge = false)]
public static void SetProperty(string name, string jsonVal)
{
var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ;
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
[Remoting]
public static object GetConfig(string name)
=> typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting]
public static void SetConfig(string name, string jsonVal)
{
var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public);
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
#endregion #endregion
} }
} }

View File

@ -10,47 +10,11 @@ using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
/// <summary>
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript" /> instance in this resource.
/// </summary>
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
/// <summary>
/// Get the <see cref="ResourceFile" /> where this script is loaded from.
/// </summary>
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
/// <summary>
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
/// </summary>
public Logger Logger { get; internal set; }
}
internal class Resources internal class Resources
{ {
public static string TempPath; public static string TempPath;
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
new ConcurrentDictionary<string, ClientResource>();
static Resources() static Resources()
{ {
@ -86,7 +50,7 @@ namespace RageCoop.Client.Scripting
} }
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored()) Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored())
.ForEach(x => File.Delete(x)); .ForEach(File.Delete);
// TODO Core.ScheduleLoad()... // TODO Core.ScheduleLoad()...
} }

View File

@ -4,7 +4,7 @@ global using static GTA.Native.Function;
global using static GTA.Native.Hash; global using static GTA.Native.Hash;
global using static RageCoop.Client.Shared; global using static RageCoop.Client.Shared;
global using Console = GTA.Console; global using Console = GTA.Console;
global using static SHVDN.PInvoke; global using static RageCoop.Core.Shared;
using System.IO; using System.IO;
namespace RageCoop.Client namespace RageCoop.Client

View File

@ -1,7 +1,9 @@
<linker> <linker>
<assembly fullname="RageCoop.Client" preserve="all" /> <assembly fullname="RageCoop.Client" preserve="all" />
<assembly fullname="RageCoop.Client.Scripting" preserve="all" />
<assembly fullname="RageCoop.Core" preserve="all" /> <assembly fullname="RageCoop.Core" preserve="all" />
<assembly fullname="System.Collections"> <assembly fullname="System.Collections">
<type fullname="System.Collections.Generic.List`1" preserve="all" /> <type fullname="System.Collections.Generic.List`1" preserve="all" />
<type fullname="System.Collections.Generic.Dictionary`2" preserve="all" />
</assembly> </assembly>
</linker> </linker>

View File

@ -107,12 +107,12 @@ namespace RageCoop.Client
Settings settings; Settings settings;
try try
{ {
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path)); settings = JsonDeserialize<Settings>(File.ReadAllText(path));
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger?.Error(ex); Main.Logger?.Error(ex);
File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented)); File.WriteAllText(path, JsonSerialize(settings = new Settings()));
} }
return settings; return settings;
@ -126,7 +126,7 @@ namespace RageCoop.Client
settings = settings ?? Main.Settings; settings = settings ?? Main.Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented)); File.WriteAllText(path, JsonSerialize(settings));
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@ -25,14 +25,14 @@ namespace RageCoop.Client
static WeaponUtil() static WeaponUtil()
{ {
// Parse and load to memory // Parse and load to memory
foreach (var w in JsonConvert.DeserializeObject<VehicleWeaponInfo[]>( foreach (var w in JsonDeserialize<VehicleWeaponInfo[]>(
File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w); File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w);
Weapons = JsonConvert.DeserializeObject<Dictionary<uint, WeaponInfo>>( Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
File.ReadAllText(WeaponInfoDataPath)); File.ReadAllText(WeaponInfoDataPath));
if (File.Exists(WeaponFixDataPath)) if (File.Exists(WeaponFixDataPath))
WeaponFix = JsonConvert.DeserializeObject<WeaponFix>(File.ReadAllText(WeaponFixDataPath)); WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
else else
Main.Logger.Warning("Weapon fix data not found"); Main.Logger.Warning("Weapon fix data not found");
} }
@ -73,7 +73,7 @@ namespace RageCoop.Client
} }
} }
File.WriteAllText(path, JsonConvert.SerializeObject(fix, Formatting.Indented)); File.WriteAllText(path, JsonSerialize(fix));
P.IsInvincible = false; P.IsInvincible = false;
} }

View File

@ -115,7 +115,7 @@ namespace RageCoop.Client
} }
#endregion #endregion
/*
public static Image CaptureWindow(IntPtr handle) public static Image CaptureWindow(IntPtr handle)
{ {
var windowDC = User32.GetWindowDC(handle); var windowDC = User32.GetWindowDC(handle);
@ -134,7 +134,7 @@ namespace RageCoop.Client
User32.ReleaseDC(handle, windowDC); User32.ReleaseDC(handle, windowDC);
return image; return image;
} }
*/
public static void ClearLastError() public static void ClearLastError()
{ {
SetLastErrorEx(0, 0); SetLastErrorEx(0, 0);

View File

@ -20,6 +20,7 @@ using RageCoop.Core.Scripting;
[assembly: InternalsVisibleTo("RageCoop.Server")] [assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")] [assembly: InternalsVisibleTo("RageCoop.Client")]
[assembly: InternalsVisibleTo("RageCoop.Client.Scripting")]
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")] [assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
[assembly: InternalsVisibleTo("DataDumper")] [assembly: InternalsVisibleTo("DataDumper")]
[assembly: InternalsVisibleTo("UnitTest")] [assembly: InternalsVisibleTo("UnitTest")]
@ -289,7 +290,7 @@ namespace RageCoop.Core
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]"); throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<IpInfo>(content); return JsonDeserialize<IpInfo>(content);
} }
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)

59
Core/JsonCoverters.cs Normal file
View File

@ -0,0 +1,59 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
return IPAddress.Parse((string)reader.Value);
}
}
class IPEndPointConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPEndPoint));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IPEndPoint ep = (IPEndPoint)value;
JObject jo = new()
{
{ "Address", JToken.FromObject(ep.Address, serializer) },
{ "Port", ep.Port }
};
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
int port = (int)jo["Port"];
return new IPEndPoint(address, port);
}
}
}

View File

@ -8,7 +8,6 @@ namespace RageCoop.Core
[Serializable] [Serializable]
public class ServerInfo public class ServerInfo
{ {
#pragma warning disable 1591
public string address { get; set; } public string address { get; set; }
public int port { get; set; } public int port { get; set; }
public string name { get; set; } public string name { get; set; }

View File

@ -96,6 +96,7 @@ namespace RageCoop.Core.Scripting
} }
/// <summary> /// <summary>
/// Common processing for custome client\server events
/// </summary> /// </summary>
public static partial class CustomEvents public static partial class CustomEvents
{ {

26
Core/Shared.cs Normal file
View File

@ -0,0 +1,26 @@
global using static RageCoop.Core.Shared;
using Newtonsoft.Json;
using System;
namespace RageCoop.Core
{
internal class Shared
{
public static readonly JsonSerializerSettings JsonSettings = new();
static Shared()
{
JsonSettings.Converters.Add(new IPAddressConverter());
JsonSettings.Converters.Add(new IPEndPointConverter());
JsonSettings.Formatting = Formatting.Indented;
}
public static object JsonDeserialize(string text, Type type)
{
return JsonConvert.DeserializeObject(text, type, JsonSettings);
}
public static T JsonDeserialize<T>(string text) => (T)JsonDeserialize(text, typeof(T));
public static string JsonSerialize(object obj) => JsonConvert.SerializeObject(obj, JsonSettings);
}
}

View File

@ -24,9 +24,9 @@ namespace RageCoop.Core
} }
Console.WriteLine("Deserializing"); Console.WriteLine("Deserializing");
var anims = JsonConvert.DeserializeObject<AnimDic[]>(File.ReadAllText(input)); var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(input));
Console.WriteLine("Serializing"); Console.WriteLine("Serializing");
File.WriteAllText(output, JsonConvert.SerializeObject(anims, Formatting.Indented)); File.WriteAllText(output, JsonSerialize(anims));
return anims; return anims;
} }
} }
@ -113,12 +113,11 @@ namespace RageCoop.Core
} }
Console.WriteLine("Deserializing"); Console.WriteLine("Deserializing");
var infos = JsonConvert.DeserializeObject<VehicleInfo[]>(File.ReadAllText(input)); var infos = JsonDeserialize<VehicleInfo[]>(File.ReadAllText(input));
Console.WriteLine("Serializing"); Console.WriteLine("Serializing");
File.WriteAllText(output, File.WriteAllText(output,
JsonConvert.SerializeObject( JsonSerialize(
infos.Select(FromVehicle).Where(x => x != null), infos.Select(FromVehicle).Where(x => x != null)));
Formatting.Indented));
} }
public static VehicleWeaponInfo FromVehicle(VehicleInfo info) public static VehicleWeaponInfo FromVehicle(VehicleInfo info)

View File

@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{70A1F09D
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataDumper", "Tools\DataDumper\DataDumper.csproj", "{6387D897-09AF-4464-B440-80438E3BB8D0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataDumper", "Tools\DataDumper\DataDumper.csproj", "{6387D897-09AF-4464-B440-80438E3BB8D0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Scripting", "RageCoop.Client.Scripting\RageCoop.Client.Scripting.csproj", "{FE47AFBA-0613-4378-B318-892DEB7B3D88}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Scripting", "Client\Scripting\RageCoop.Client.Scripting.csproj", "{FE47AFBA-0613-4378-B318-892DEB7B3D88}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tools\UnitTest\UnitTest.csproj", "{4FE96671-3DC5-4394-B2E3-584399E57310}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tools\UnitTest\UnitTest.csproj", "{4FE96671-3DC5-4394-B2E3-584399E57310}"
EndProject EndProject
@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lidgren.Network", "libs\Lid
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptHookVDotNetCore.Generator", "libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore.Generator\ScriptHookVDotNetCore.Generator.csproj", "{2C08A5AF-C189-4350-B54F-3D23379E9E1D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptHookVDotNetCore.Generator", "libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore.Generator\ScriptHookVDotNetCore.Generator.csproj", "{2C08A5AF-C189-4350-B54F-3D23379E9E1D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGen", "Tools\CodeGen\CodeGen.csproj", "{C4CF8A98-7393-42BD-97A1-2E850D12890A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
API|Any CPU = API|Any CPU API|Any CPU = API|Any CPU
@ -164,6 +166,18 @@ Global
{2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|Any CPU.Build.0 = Release|Any CPU {2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|Any CPU.Build.0 = Release|Any CPU
{2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|x64.ActiveCfg = Release|Any CPU {2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|x64.ActiveCfg = Release|Any CPU
{2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|x64.Build.0 = Release|Any CPU {2C08A5AF-C189-4350-B54F-3D23379E9E1D}.Release|x64.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -177,6 +191,7 @@ Global
{B15EDABB-30AF-475A-823D-ACB9F75CFE13} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A} {B15EDABB-30AF-475A-823D-ACB9F75CFE13} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{02616B5A-2A68-42AA-A91E-311EF95FCF44} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A} {02616B5A-2A68-42AA-A91E-311EF95FCF44} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{2C08A5AF-C189-4350-B54F-3D23379E9E1D} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A} {2C08A5AF-C189-4350-B54F-3D23379E9E1D} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{C4CF8A98-7393-42BD-97A1-2E850D12890A} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7} SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}

View File

@ -1,7 +0,0 @@
namespace RageCoop.Client.Scripting
{
public static class API
{
}
}

View File

@ -79,8 +79,7 @@ public partial class Server
if (!CanAnnounce) if (!CanAnnounce)
{ {
var existing = JsonConvert var existing = JsonDeserialize<List<ServerInfo>>(
.DeserializeObject<List<ServerInfo>>(
HttpHelper.DownloadString(Util.GetFinalRedirect(Settings.MasterServer))) HttpHelper.DownloadString(Util.GetFinalRedirect(Settings.MasterServer)))
.Where(x => x.address == IpInfo.Address).FirstOrDefault(); .Where(x => x.address == IpInfo.Address).FirstOrDefault();
if (existing != null) if (existing != null)
@ -117,7 +116,7 @@ public partial class Server
publicKeyModulus = Convert.ToBase64String(pModulus), publicKeyModulus = Convert.ToBase64String(pModulus),
publicKeyExponent = Convert.ToBase64String(pExpoenet) publicKeyExponent = Convert.ToBase64String(pExpoenet)
}; };
var msg = JsonConvert.SerializeObject(serverInfo); var msg = JsonSerialize(serverInfo);
var realUrl = Util.GetFinalRedirect(Settings.MasterServer); var realUrl = Util.GetFinalRedirect(Settings.MasterServer);
response = httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json")) response = httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json"))

View File

@ -15,7 +15,7 @@ using System.Resources;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
// Version information // Version information
[assembly: AssemblyVersion("1.6.0.28")] [assembly: AssemblyVersion("1.6.0.30")]
[assembly: AssemblyFileVersion("1.6.0.28")] [assembly: AssemblyFileVersion("1.6.0.30")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )] [assembly: NeutralResourcesLanguageAttribute( "en-US" )]

9
Server/Shared.cs Normal file
View File

@ -0,0 +1,9 @@
global using static RageCoop.Core.Shared;
global using static RageCoop.Server.Shared;
global using RageCoop.Core;
namespace RageCoop.Server
{
internal class Shared
{
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<OutDir>$(SolutionDir)bin\Tools\CodeGen</OutDir>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Client\Scripts\RageCoop.Client.csproj" />
</ItemGroup>
</Project>

82
Tools/CodeGen/Program.cs Normal file
View File

@ -0,0 +1,82 @@
using RageCoop.Client.Scripting;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CodeGen
{
internal static class Program
{
static void Main(string[] args)
{
Directory.SetCurrentDirectory(@"..\..\..\");
var props = new StringBuilder();
var config = new StringBuilder();
var funcs = new StringBuilder();
foreach (var prop in typeof(API.Config).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
config.AppendLine($"public static {prop.PropertyType.ToTypeName()} {prop.Name} => GetConfig<{prop.PropertyType.ToTypeName()}>(\"{prop.Name}\");");
}
foreach (var prop in typeof(API).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
props.AppendLine($"public static {prop.PropertyType.ToTypeName()} {prop.Name} => GetProperty<{prop.PropertyType.ToTypeName()}>(\"{prop.Name}\");");
}
foreach (var f in
typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static).
Where(x =>
{
var attri = x.GetCustomAttribute<RemotingAttribute>();
if (attri ==null) return false;
return attri.GenBridge;
}))
{
var ret = f.ReturnType.ToTypeName();
var gReturn = $"<{ret}>";
if (ret == "System.Void") { ret = "void"; gReturn = string.Empty; }
var ps = f.GetParameters();
var paras = string.Join(',', ps.Select(x => $"{x.ParameterType.ToTypeName()} {x.Name}"));
var parasNoType = string.Join(',', ps.Select(x => x.Name));
if (ps.Length > 0)
{
parasNoType = "," + parasNoType;
}
funcs.AppendLine($"public static {ret} {f.Name}({paras}) => InvokeCommand{gReturn}(\"{f.Name}\"{parasNoType});");
}
var code = $@"namespace RageCoop.Client.Scripting
{{
public static unsafe partial class APIBridge
{{
public static class Config
{{
{config}
}}
#region PROPERTIES
{props}
#endregion
#region FUNCTIONS
{funcs}
#endregion
}}
}}
";
File.WriteAllText(@"Client\Scripting\APIBridge.Generated.cs", code);
}
static string ToTypeName(this Type type)
{
var name = type.ToString();
if (type.GenericTypeArguments.Length > 0)
{
name = $"{name.Substring(0, name.IndexOf('`'))}<{string.Join(',', type.GenericTypeArguments.Select(ToTypeName))}>";
}
name = name.Replace("RageCoop.Client.Player", "RageCoop.Client.Scripting.PlayerInfo");
return name;
}
}
}