Client API remoting stuff
This commit is contained in:
45
Client/Scripting/APIBridge.Generated.cs
Normal file
45
Client/Scripting/APIBridge.Generated.cs
Normal 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
|
||||
}
|
||||
}
|
128
Client/Scripting/APIBridge.cs
Normal file
128
Client/Scripting/APIBridge.cs
Normal 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();
|
||||
}
|
||||
}
|
9
Client/Scripting/ClientScript.cs
Normal file
9
Client/Scripting/ClientScript.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GTA;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
[ScriptAttributes(NoDefaultInstance = true)]
|
||||
public class ClientScript : Script
|
||||
{
|
||||
}
|
||||
}
|
@ -3,11 +3,11 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\RageCoop.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -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.Linq;
|
||||
using System.Text;
|
||||
@ -6,7 +7,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class ClientScript
|
||||
internal class Shared
|
||||
{
|
||||
}
|
||||
}
|
60
Client/Scripting/Types.cs
Normal file
60
Client/Scripting/Types.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
@ -122,7 +123,7 @@ namespace RageCoop.Client
|
||||
|
||||
Resources = new Resources();
|
||||
|
||||
|
||||
|
||||
|
||||
Logger.Info(
|
||||
$"Main script initialized");
|
||||
@ -135,7 +136,7 @@ namespace RageCoop.Client
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
|
||||
|
||||
}
|
||||
protected override void OnTick()
|
||||
{
|
||||
@ -237,6 +238,23 @@ namespace RageCoop.Client
|
||||
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 (CefRunning)
|
||||
{
|
||||
@ -263,14 +281,14 @@ namespace RageCoop.Client
|
||||
if (Game.IsControlPressed(Control.FrontendPause))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
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 a in anim.Animations)
|
||||
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, Main.P, anim.DictionaryName, a, 3))
|
||||
|
@ -58,7 +58,7 @@ namespace RageCoop.Client.Menus
|
||||
List<ServerInfo> serverList = null;
|
||||
var realUrl = Main.Settings.MasterServer;
|
||||
serverList = null;
|
||||
try { serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl)); }
|
||||
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
|
||||
catch (Exception ex) { Main.Logger.Error(ex); }
|
||||
|
||||
// Need to be processed in main thread
|
||||
|
@ -4,6 +4,7 @@ using System.Drawing;
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
@ -66,8 +67,7 @@ namespace RageCoop.Client.Menus
|
||||
};
|
||||
_showNametag.Activated += (s, e) =>
|
||||
{
|
||||
Main.Settings.ShowPlayerNameTag = _showNametag.Checked;
|
||||
Util.SaveSettings();
|
||||
API.Config.ShowPlayerNameTag = _showNametag.Checked;
|
||||
};
|
||||
|
||||
Menu.Add(_disableTrafficItem);
|
||||
|
@ -5,6 +5,7 @@ using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
@ -127,9 +128,21 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
|
||||
public class Player
|
||||
internal class Player
|
||||
{
|
||||
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 bool IsHost { get; internal set; }
|
||||
public string Username { get; internal set; }
|
||||
@ -139,12 +152,10 @@ namespace RageCoop.Client
|
||||
/// </summary>
|
||||
public int ID { get; internal set; }
|
||||
|
||||
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
|
||||
|
||||
public IPEndPoint InternalEndPoint { 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>
|
||||
/// 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;
|
||||
|
||||
public bool DisplayNameTag { get; set; } = true;
|
||||
public NetConnection Connection { get; internal set; }
|
||||
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<NoAotCompile>false</NoAotCompile>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
|
||||
@ -21,6 +22,9 @@
|
||||
<EmbeddedResource Remove="GUI\**" />
|
||||
<None Remove="GUI\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Scripting\ClientScript.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\AssemblyInfo.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
@ -50,6 +54,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
|
@ -1,87 +1,94 @@
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using static RageCoop.Core.Scripting.CustomEvents;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public static unsafe partial class API
|
||||
internal static unsafe partial class API
|
||||
{
|
||||
[UnmanagedCallersOnly(EntryPoint = "Connect")]
|
||||
public static void Connect(char* address) => Connect(new string(address));
|
||||
[ThreadStatic]
|
||||
static string _lastResult;
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "GetLocalPlayerID")]
|
||||
public static int GetLocalPlayerID() => LocalPlayerID;
|
||||
|
||||
/// <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)
|
||||
[UnmanagedCallersOnly(EntryPoint = nameof(GetLastResult))]
|
||||
public static int GetLastResult(char* buf, int cbBufSize)
|
||||
{
|
||||
var name = new string(szName);
|
||||
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)
|
||||
if (_lastResult == null)
|
||||
return 0;
|
||||
|
||||
fixed (char* p = value)
|
||||
fixed (char* pErr = _lastResult)
|
||||
{
|
||||
var cbRequired = (value.Length + 1) * sizeof(char);
|
||||
Buffer.MemoryCopy(p, buf, bufSize, cbRequired);
|
||||
return value.Length;
|
||||
var cbToCopy = sizeof(char) * (_lastResult.Length + 1);
|
||||
System.Buffer.MemoryCopy(pErr, buf, cbToCopy, Math.Min(cbToCopy, cbBufSize));
|
||||
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")]
|
||||
public static void SetConfigValue(char* szName, char* szValue)
|
||||
[UnmanagedCallersOnly(EntryPoint = nameof(GetEventHash))]
|
||||
public static CustomEventHash GetEventHash(char* name) => new string(name);
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = nameof(SendCustomEvent))]
|
||||
public static void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData)
|
||||
{
|
||||
var name = new string(szName);
|
||||
var value = new string(szValue);
|
||||
switch (name)
|
||||
var payload = new byte[cbData];
|
||||
Marshal.Copy((IntPtr)data, payload, 0, cbData);
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent()
|
||||
{
|
||||
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;
|
||||
};
|
||||
Flags = flags,
|
||||
Payload = payload,
|
||||
Hash = hash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
[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 void SendCustomEvent(int hash, CustomEventFlags flags, byte* data, int cbData)
|
||||
[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>
|
||||
/// Convert Entity ID to handle
|
||||
/// </summary>
|
||||
[UnmanagedCallersOnly(EntryPoint = "IdToHandle")]
|
||||
[UnmanagedCallersOnly(EntryPoint = nameof(IdToHandle))]
|
||||
public static int IdToHandle(byte type, int id)
|
||||
{
|
||||
return type switch
|
||||
|
55
Client/Scripts/Scripting/API.Remoting.cs
Normal file
55
Client/Scripts/Scripting/API.Remoting.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
#undef DEBUG
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using Lidgren.Network;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
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
|
||||
{
|
||||
@ -31,7 +33,7 @@ namespace RageCoop.Client.Scripting
|
||||
/// <summary>
|
||||
/// Provides vital functionality to interact with RAGECOOP
|
||||
/// </summary>
|
||||
public static unsafe partial class API
|
||||
internal static unsafe partial class API
|
||||
{
|
||||
#region INTERNAL
|
||||
|
||||
@ -78,6 +80,17 @@ namespace RageCoop.Client.Scripting
|
||||
/// Get or set scale of player's blip
|
||||
/// </summary>
|
||||
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>
|
||||
@ -207,7 +220,7 @@ namespace RageCoop.Client.Scripting
|
||||
/// <summary>
|
||||
/// Get all players indexed by their ID
|
||||
/// </summary>
|
||||
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
|
||||
public static Dictionary<int, Player> Players => new(PlayerList.Players);
|
||||
|
||||
#endregion
|
||||
|
||||
@ -255,57 +268,6 @@ namespace RageCoop.Client.Scripting
|
||||
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>
|
||||
/// Send an event and data to the server.
|
||||
/// </summary>
|
||||
@ -315,13 +277,7 @@ namespace RageCoop.Client.Scripting
|
||||
/// types
|
||||
/// </param>
|
||||
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
=> SendCustomEvent(CustomEventFlags.None, eventHash, args);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="hash">
|
||||
/// An unique identifier of the event, you can hash your event name with
|
||||
/// <see cref="Core.Scripting.CustomEvents.Hash(string)" />
|
||||
/// An unique identifier of the event
|
||||
/// </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)
|
||||
@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -10,47 +10,11 @@ using RageCoop.Core.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
|
||||
{
|
||||
public static string TempPath;
|
||||
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources =
|
||||
new ConcurrentDictionary<string, ClientResource>();
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
|
||||
|
||||
static Resources()
|
||||
{
|
||||
@ -86,7 +50,7 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
|
||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored())
|
||||
.ForEach(x => File.Delete(x));
|
||||
.ForEach(File.Delete);
|
||||
|
||||
// TODO Core.ScheduleLoad()...
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ global using static GTA.Native.Function;
|
||||
global using static GTA.Native.Hash;
|
||||
global using static RageCoop.Client.Shared;
|
||||
global using Console = GTA.Console;
|
||||
global using static SHVDN.PInvoke;
|
||||
global using static RageCoop.Core.Shared;
|
||||
using System.IO;
|
||||
|
||||
namespace RageCoop.Client
|
@ -1,7 +1,9 @@
|
||||
<linker>
|
||||
<assembly fullname="RageCoop.Client" preserve="all" />
|
||||
<assembly fullname="RageCoop.Client.Scripting" preserve="all" />
|
||||
<assembly fullname="RageCoop.Core" preserve="all" />
|
||||
<assembly fullname="System.Collections">
|
||||
<type fullname="System.Collections.Generic.List`1" preserve="all" />
|
||||
<type fullname="System.Collections.Generic.Dictionary`2" preserve="all" />
|
||||
</assembly>
|
||||
</linker>
|
@ -107,12 +107,12 @@ namespace RageCoop.Client
|
||||
Settings settings;
|
||||
try
|
||||
{
|
||||
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
|
||||
settings = JsonDeserialize<Settings>(File.ReadAllText(path));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger?.Error(ex);
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
|
||||
File.WriteAllText(path, JsonSerialize(settings = new Settings()));
|
||||
}
|
||||
|
||||
return settings;
|
||||
@ -126,7 +126,7 @@ namespace RageCoop.Client
|
||||
settings = settings ?? Main.Settings;
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
|
||||
File.WriteAllText(path, JsonSerialize(settings));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -25,14 +25,14 @@ namespace RageCoop.Client
|
||||
static WeaponUtil()
|
||||
{
|
||||
// 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);
|
||||
|
||||
Weapons = JsonConvert.DeserializeObject<Dictionary<uint, WeaponInfo>>(
|
||||
Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
|
||||
File.ReadAllText(WeaponInfoDataPath));
|
||||
|
||||
if (File.Exists(WeaponFixDataPath))
|
||||
WeaponFix = JsonConvert.DeserializeObject<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
|
||||
WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/*
|
||||
public static Image CaptureWindow(IntPtr handle)
|
||||
{
|
||||
var windowDC = User32.GetWindowDC(handle);
|
||||
@ -134,7 +134,7 @@ namespace RageCoop.Client
|
||||
User32.ReleaseDC(handle, windowDC);
|
||||
return image;
|
||||
}
|
||||
|
||||
*/
|
||||
public static void ClearLastError()
|
||||
{
|
||||
SetLastErrorEx(0, 0);
|
||||
|
@ -20,6 +20,7 @@ using RageCoop.Core.Scripting;
|
||||
|
||||
[assembly: InternalsVisibleTo("RageCoop.Server")]
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client")]
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Scripting")]
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
|
||||
[assembly: InternalsVisibleTo("DataDumper")]
|
||||
[assembly: InternalsVisibleTo("UnitTest")]
|
||||
@ -289,7 +290,7 @@ namespace RageCoop.Core
|
||||
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
|
||||
|
||||
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
return JsonConvert.DeserializeObject<IpInfo>(content);
|
||||
return JsonDeserialize<IpInfo>(content);
|
||||
}
|
||||
|
||||
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
|
||||
|
59
Core/JsonCoverters.cs
Normal file
59
Core/JsonCoverters.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,7 +8,6 @@ namespace RageCoop.Core
|
||||
[Serializable]
|
||||
public class ServerInfo
|
||||
{
|
||||
#pragma warning disable 1591
|
||||
public string address { get; set; }
|
||||
public int port { get; set; }
|
||||
public string name { get; set; }
|
||||
|
@ -96,6 +96,7 @@ namespace RageCoop.Core.Scripting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common processing for custome client\server events
|
||||
/// </summary>
|
||||
public static partial class CustomEvents
|
||||
{
|
||||
|
26
Core/Shared.cs
Normal file
26
Core/Shared.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -24,9 +24,9 @@ namespace RageCoop.Core
|
||||
}
|
||||
|
||||
Console.WriteLine("Deserializing");
|
||||
var anims = JsonConvert.DeserializeObject<AnimDic[]>(File.ReadAllText(input));
|
||||
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(input));
|
||||
Console.WriteLine("Serializing");
|
||||
File.WriteAllText(output, JsonConvert.SerializeObject(anims, Formatting.Indented));
|
||||
File.WriteAllText(output, JsonSerialize(anims));
|
||||
return anims;
|
||||
}
|
||||
}
|
||||
@ -113,12 +113,11 @@ namespace RageCoop.Core
|
||||
}
|
||||
|
||||
Console.WriteLine("Deserializing");
|
||||
var infos = JsonConvert.DeserializeObject<VehicleInfo[]>(File.ReadAllText(input));
|
||||
var infos = JsonDeserialize<VehicleInfo[]>(File.ReadAllText(input));
|
||||
Console.WriteLine("Serializing");
|
||||
File.WriteAllText(output,
|
||||
JsonConvert.SerializeObject(
|
||||
infos.Select(FromVehicle).Where(x => x != null),
|
||||
Formatting.Indented));
|
||||
JsonSerialize(
|
||||
infos.Select(FromVehicle).Where(x => x != null)));
|
||||
}
|
||||
|
||||
public static VehicleWeaponInfo FromVehicle(VehicleInfo info)
|
||||
|
@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{70A1F09D
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataDumper", "Tools\DataDumper\DataDumper.csproj", "{6387D897-09AF-4464-B440-80438E3BB8D0}"
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tools\UnitTest\UnitTest.csproj", "{4FE96671-3DC5-4394-B2E3-584399E57310}"
|
||||
EndProject
|
||||
@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lidgren.Network", "libs\Lid
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptHookVDotNetCore.Generator", "libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore.Generator\ScriptHookVDotNetCore.Generator.csproj", "{2C08A5AF-C189-4350-B54F-3D23379E9E1D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGen", "Tools\CodeGen\CodeGen.csproj", "{C4CF8A98-7393-42BD-97A1-2E850D12890A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x64.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -177,6 +191,7 @@ Global
|
||||
{B15EDABB-30AF-475A-823D-ACB9F75CFE13} = {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}
|
||||
{C4CF8A98-7393-42BD-97A1-2E850D12890A} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
|
||||
|
@ -1,7 +0,0 @@
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public static class API
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -79,8 +79,7 @@ public partial class Server
|
||||
|
||||
if (!CanAnnounce)
|
||||
{
|
||||
var existing = JsonConvert
|
||||
.DeserializeObject<List<ServerInfo>>(
|
||||
var existing = JsonDeserialize<List<ServerInfo>>(
|
||||
HttpHelper.DownloadString(Util.GetFinalRedirect(Settings.MasterServer)))
|
||||
.Where(x => x.address == IpInfo.Address).FirstOrDefault();
|
||||
if (existing != null)
|
||||
@ -117,7 +116,7 @@ public partial class Server
|
||||
publicKeyModulus = Convert.ToBase64String(pModulus),
|
||||
publicKeyExponent = Convert.ToBase64String(pExpoenet)
|
||||
};
|
||||
var msg = JsonConvert.SerializeObject(serverInfo);
|
||||
var msg = JsonSerialize(serverInfo);
|
||||
|
||||
var realUrl = Util.GetFinalRedirect(Settings.MasterServer);
|
||||
response = httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json"))
|
||||
|
@ -15,7 +15,7 @@ using System.Resources;
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.6.0.28")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.28")]
|
||||
[assembly: AssemblyVersion("1.6.0.30")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.30")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
|
||||
|
9
Server/Shared.cs
Normal file
9
Server/Shared.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
14
Tools/CodeGen/CodeGen.csproj
Normal file
14
Tools/CodeGen/CodeGen.csproj
Normal 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
82
Tools/CodeGen/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user