Prepare for CoreCLR version

This commit is contained in:
Sardelka9515
2023-03-06 21:54:41 +08:00
parent 0e5271b322
commit 2451131e36
16 changed files with 155 additions and 125 deletions

View File

@ -1,9 +1,9 @@
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
[assembly: DisableRuntimeMarshalling]
[assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging [assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
@ -11,7 +11,28 @@ namespace RageCoop.Client.Scripting
public static unsafe partial class APIBridge public static unsafe partial class APIBridge
{ {
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]); static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
static List<CustomEventHandler> _handlers = new(); static readonly List<CustomEventHandler> _handlers = new();
static APIBridge()
{
if (SHVDN.Core.GetPtr == null)
throw new InvalidOperationException("Game not running");
foreach(var fd in typeof(APIBridge).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
var importAttri = fd.GetCustomAttribute<ApiImportAttribute>();
if (importAttri == null)
continue;
importAttri.EntryPoint ??= fd.Name;
var key = $"RageCoop.Client.Scripting.API.{importAttri.EntryPoint}";
var fptr = SHVDN.Core.GetPtr(key);
if (fptr == default)
throw new KeyNotFoundException($"Failed to find function pointer: {key}");
fd.SetValue(null,fptr);
}
}
/// <summary> /// <summary>
/// Copy content of string to a sequential block of memory /// Copy content of string to a sequential block of memory
/// </summary> /// </summary>
@ -56,10 +77,13 @@ namespace RageCoop.Client.Scripting
var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray()); var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray());
try try
{ {
var resultLen = InvokeCommandAsJsonUnsafe(name, argc, argv); fixed(char* pName = name)
if (resultLen == 0) {
throw new Exception(GetLastResult()); var resultLen = InvokeCommandAsJsonUnsafe(pName, argc, argv);
return GetLastResult(); if (resultLen == 0)
throw new Exception(GetLastResult());
return GetLastResult();
}
} }
finally finally
{ {
@ -77,7 +101,7 @@ namespace RageCoop.Client.Scripting
var cbBufSize = _resultBuf.Value.Length * sizeof(char); var cbBufSize = _resultBuf.Value.Length * sizeof(char);
fixed (char* pBuf = _resultBuf.Value) fixed (char* pBuf = _resultBuf.Value)
{ {
if (GetLastResult(pBuf, cbBufSize) > 0) if (GetLastResultUnsafe(pBuf, cbBufSize) > 0)
{ {
return new string(pBuf); return new string(pBuf);
} }
@ -91,7 +115,7 @@ namespace RageCoop.Client.Scripting
{ {
var writer = GetWriter(); var writer = GetWriter();
CustomEvents.WriteObjects(writer, args); CustomEvents.WriteObjects(writer, args);
SendCustomEvent(flags, hash, writer.Address, writer.Position); SendCustomEventUnsafe(flags, hash, writer.Address, writer.Position);
} }
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler) public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
@ -107,25 +131,31 @@ namespace RageCoop.Client.Scripting
internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val); internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val);
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)] [ApiImport]
public static partial CustomEventHash GetEventHash(string name); public static delegate* unmanaged<char*, CustomEventHash> GetEventHash;
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)] [ApiImport]
internal static partial void SetLastResult(string msg); private static delegate* unmanaged<char*,void> SetLastResult;
[LibraryImport("RageCoop.Client.dll")] [ApiImport(EntryPoint = "GetLastResult")]
private static partial int GetLastResult(char* buf, int cbBufSize); private static delegate* unmanaged<char*, int, int> GetLastResultUnsafe;
[LibraryImport("RageCoop.Client.dll", EntryPoint = "InvokeCommand", StringMarshalling = StringMarshalling.Utf16)] [ApiImport(EntryPoint = "InvokeCommand")]
private static partial int InvokeCommandAsJsonUnsafe(string name, int argc, char** argv); private static delegate* unmanaged<char*, int, char**, int> InvokeCommandAsJsonUnsafe;
[LibraryImport("RageCoop.Client.dll")] [ApiImport(EntryPoint = "SendCustomEvent")]
private static partial void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData); private static delegate* unmanaged<CustomEventFlags, int, byte*, int, void> SendCustomEventUnsafe;
[LibraryImport("RageCoop.Client.dll")] [ApiImport]
private static partial int GetLastResultLenInChars(); private static delegate* unmanaged<int> GetLastResultLenInChars;
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)] [ApiImport]
public static partial void LogEnqueue(LogLevel level, string msg); public static delegate* unmanaged<LogLevel, char*, void> LogEnqueue;
}
[AttributeUsage(AttributeTargets.Field)]
class ApiImportAttribute : Attribute
{
public string EntryPoint;
} }
} }

View File

@ -20,7 +20,8 @@ namespace RageCoop.Client.Scripting
static unsafe ClientScript() static unsafe ClientScript()
{ {
char* buf = stackalloc char[260]; char* buf = stackalloc char[260];
SHVDN.PInvoke.GetModuleFileNameW(SHVDN.Core.CurrentModule, buf, 260); // TODO: needs some fix up here
// SHVDN.PInvoke.GetModuleFileNameW(SHVDN.Core.CurrentModule, buf, 260);
if (Marshal.GetLastWin32Error() != 0) if (Marshal.GetLastWin32Error() != 0)
throw new Win32Exception("Failed to get path for current module"); throw new Win32Exception("Failed to get path for current module");
FullPath = new(buf); FullPath = new(buf);
@ -37,12 +38,14 @@ namespace RageCoop.Client.Scripting
{ {
Logger.Warning("No file associated with curent script was found"); Logger.Warning("No file associated with curent script was found");
} }
Tick += DoQueuedJobs;
} }
protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action); protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action);
protected void QueueAction(Action action) => QueueAction(() => { action(); return true; }); protected void QueueAction(Action action) => QueueAction(() => { action(); return true; });
protected override void OnTick()
{
base.OnTick();
DoQueuedJobs();
}
private void DoQueuedJobs() private void DoQueuedJobs()
{ {
while (_reAdd.TryDequeue(out var toAdd)) while (_reAdd.TryDequeue(out var toAdd))

View File

@ -12,13 +12,16 @@ namespace RageCoop.Client.Scripting
public static readonly ResourceLogger Default = new(); public static readonly ResourceLogger Default = new();
public ResourceLogger() public ResourceLogger()
{ {
FlushImmediately= true; FlushImmediately = true;
OnFlush += FlushToMainModule; OnFlush += FlushToMainModule;
} }
private void FlushToMainModule(LogLine line, string fomatted) private unsafe void FlushToMainModule(LogLine line, string fomatted)
{ {
APIBridge.LogEnqueue(line.LogLevel, line.Message); fixed (char* pMsg = line.Message)
{
APIBridge.LogEnqueue(line.LogLevel, pMsg);
}
} }
} }

View File

@ -1,48 +0,0 @@
using System;
using System.Collections.Generic;
using GTA.UI;
namespace RageCoop.Client
{
internal enum TimeStamp
{
AddPeds,
PedTotal,
AddVehicles,
VehicleTotal,
SendPed,
SendPedState,
SendVehicle,
SendVehicleState,
UpdatePed,
UpdateVehicle,
CheckProjectiles,
GetAllEntities,
Receive,
ProjectilesTotal
}
internal static class Debug
{
public static Dictionary<TimeStamp, long> TimeStamps = new Dictionary<TimeStamp, long>();
private static int _lastNfHandle;
static Debug()
{
foreach (TimeStamp t in Enum.GetValues<TimeStamp>()) TimeStamps.Add(t, 0);
}
public static string Dump(this Dictionary<TimeStamp, long> d)
{
var s = "";
foreach (var kvp in d) s += kvp.Key + ":" + kvp.Value + "\n";
return s;
}
public static void ShowTimeStamps()
{
Notification.Hide(_lastNfHandle);
_lastNfHandle = Notification.Show(TimeStamps.Dump());
}
}
}

View File

@ -5,6 +5,7 @@ using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using GTA; using GTA;
using GTA.Math; using GTA.Math;
@ -34,7 +35,7 @@ namespace RageCoop.Client
internal static Logger Log = null; internal static Logger Log = null;
internal static ulong Ticked = 0; internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition; internal static Vector3 PlayerPosition;
internal static Resources MainRes = null; internal static Scripting.Resources MainRes = null;
public static Ped P; public static Ped P;
public static float FPS; public static float FPS;
@ -61,7 +62,8 @@ namespace RageCoop.Client
Log = new Logger() Log = new Logger()
{ {
Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) }, FlushImmediately = true,
Writers = null,
#if DEBUG #if DEBUG
LogLevel = 0, LogLevel = 0,
#else #else
@ -70,26 +72,11 @@ namespace RageCoop.Client
}; };
Log.OnFlush += (line, formatted) => Log.OnFlush += (line, formatted) =>
{ {
switch (line.LogLevel) SHVDN.Logger.Write(line.Message, (uint)line.LogLevel);
{
#if DEBUG
// case LogLevel.Trace:
case LogLevel.Debug:
Console.PrintInfo(line.Message);
break;
#endif
case LogLevel.Info:
Console.PrintInfo(line.Message);
break;
case LogLevel.Warning:
Console.PrintWarning(line.Message);
break;
case LogLevel.Error:
Console.PrintError(line.Message);
break;
}
}; };
// Run static constructor to register all function pointers and remoting entries
RuntimeHelpers.RunClassConstructor(typeof(API).TypeHandle);
} }
protected override void OnAborted(AbortedEventArgs e) protected override void OnAborted(AbortedEventArgs e)
@ -120,7 +107,7 @@ namespace RageCoop.Client
throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!"); throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!");
} }
MainRes = new Resources(); MainRes = new();
@ -220,6 +207,22 @@ namespace RageCoop.Client
protected override void OnKeyUp(GTA.KeyEventArgs e) protected override void OnKeyUp(GTA.KeyEventArgs e)
{ {
base.OnKeyUp(e); base.OnKeyUp(e);
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("test");
}
#if CEF #if CEF
if (CefRunning) if (CefRunning)
{ {
@ -379,23 +382,23 @@ namespace RageCoop.Client
if (reason != "Abort") if (reason != "Abort")
{ {
Log.Info($">> Disconnected << reason: {reason}"); Log.Info($">> Disconnected << reason: {reason}");
Notification.Show("~r~Disconnected: " + reason); Notification.Show("~r~Disconnected: " + reason);
} }
if (MainChat.Focused) if (MainChat?.Focused == true)
{ {
MainChat.Focused = false; MainChat.Focused = false;
} }
PlayerList.Cleanup(); PlayerList.Cleanup();
MainChat.Clear(); MainChat?.Clear();
EntityPool.Cleanup(); EntityPool.Cleanup();
WorldThread.Traffic(true); WorldThread.Traffic(true);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false); Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting(); CoopMenu.DisconnectedMenuSetting();
LocalPlayerID = default; LocalPlayerID = default;
MainRes.Unload(); MainRes?.Unload();
Memory.RestorePatches(); Memory.RestorePatches();
#if CEF #if CEF
if (CefRunning) if (CefRunning)

View File

@ -38,9 +38,9 @@ namespace RageCoop.Client
{ {
DiagnosticMenu.Clear(); DiagnosticMenu.Clear();
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug())); DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
foreach (var pair in Debug.TimeStamps) // foreach (var pair in Debug.TimeStamps)
DiagnosticMenu.Add( // DiagnosticMenu.Add(
new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString())); // new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
}; };
ShowNetworkInfoItem.CheckboxChanged += (s, e) => ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{ {

View File

@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<EnableDynamicLoading>true</EnableDynamicLoading>
<NoAotCompile>false</NoAotCompile> <NoAotCompile>false</NoAotCompile>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch> <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
@ -63,9 +64,6 @@
<PackageReference Include="SharpZipLib" Version="1.4.0" /> <PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" /> <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(SolutionDir)' != '*Undefined*' And '$(NoAotCompile)' != 'true'">
<Exec Command="dotnet publish &quot;$(ProjectPath)&quot; -o &quot;$(OutDir)\native&quot; -p:PublishAOT=true -r win-x64 " />
</Target>
<PropertyGroup Condition="'$(SolutionDir)' != '*Undefined*' AND '$(PublishAot)' != 'true'"> <PropertyGroup Condition="'$(SolutionDir)' != '*Undefined*' AND '$(PublishAot)' != 'true'">
<PostBuildEvent Condition=" '$(DevEnvDir)' != '*Undefined*'"> <PostBuildEvent Condition=" '$(DevEnvDir)' != '*Undefined*'">
"$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt" "$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"

View File

@ -3,6 +3,8 @@ 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.Diagnostics;
using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static RageCoop.Core.Scripting.CustomEvents; using static RageCoop.Core.Scripting.CustomEvents;
@ -11,6 +13,23 @@ namespace RageCoop.Client.Scripting
{ {
internal static unsafe partial class API internal static unsafe partial class API
{ {
static API()
{
RegisterFunctionPointers();
}
static void RegisterFunctionPointers()
{
foreach (var method in typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static))
{
var attri = method.GetCustomAttribute<UnmanagedCallersOnlyAttribute>();
if (attri == null) continue;
Debug.Assert(attri.EntryPoint == method.Name);
SHVDN.Core.SetPtr($"{typeof(API).FullName}.{method.Name}", method.MethodHandle.GetFunctionPointer());
Log.Debug($"Registered function pointer for {method.DeclaringType}.{method.Name}");
}
}
[ThreadStatic] [ThreadStatic]
static string _lastResult; static string _lastResult;

View File

@ -465,8 +465,8 @@ namespace RageCoop.Client.Scripting
[Remoting] [Remoting]
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler) public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
{ {
if (handler.Module == default) if (handler.Directory == default)
throw new ArgumentException("Module not specified"); throw new ArgumentException("Script directory not specified");
if (handler.FunctionPtr == default) if (handler.FunctionPtr == default)
throw new ArgumentException("Function pointer not specified"); throw new ArgumentException("Function pointer not specified");

View File

@ -65,6 +65,8 @@ namespace RageCoop.Client.Scripting
} }
} }
// TODO
/*
// Unregister associated handler // Unregister associated handler
foreach (var handlers in API.CustomEventHandlers.Values) foreach (var handlers in API.CustomEventHandlers.Values)
{ {
@ -77,7 +79,7 @@ namespace RageCoop.Client.Scripting
} }
} }
} }
*/
LoadedResources.Clear(); LoadedResources.Clear();
} }

View File

@ -14,9 +14,13 @@ namespace RageCoop.Client
/// </summary> /// </summary>
internal static class ThreadManager internal static class ThreadManager
{ {
private static List<Thread> _threads = new(); private static readonly List<Thread> _threads = new();
private static Thread _watcher = new(() => _removeStopped()); private static readonly Thread _watcher = new(RemoveStopped);
private static void _removeStopped() static ThreadManager()
{
_watcher.Start();
}
private static void RemoveStopped()
{ {
while (!IsUnloading) while (!IsUnloading)
{ {
@ -46,8 +50,10 @@ namespace RageCoop.Client
{ {
Log.Debug($"Thread stopped: " + Environment.CurrentManagedThreadId); Log.Debug($"Thread stopped: " + Environment.CurrentManagedThreadId);
} }
}); })
created.Name = name; {
Name = name
};
Log.Debug($"Thread created: {name}, id: {created.ManagedThreadId}"); Log.Debug($"Thread created: {name}, id: {created.ManagedThreadId}");
_threads.Add(created); _threads.Add(created);
if (startNow) created.Start(); if (startNow) created.Start();

View File

@ -50,7 +50,7 @@ namespace RageCoop.Core
internal Logger() internal Logger()
{ {
Name = Process.GetCurrentProcess().Id.ToString(); Name = Environment.ProcessId.ToString();
if (!FlushImmediately) if (!FlushImmediately)
{ {
LoggerThread = new Thread(() => LoggerThread = new Thread(() =>
@ -164,7 +164,7 @@ namespace RageCoop.Core
while (_queuedLines.TryDequeue(out var line)) while (_queuedLines.TryDequeue(out var line))
{ {
var formatted = Format(line); var formatted = Format(line);
Writers.ForEach(x => Writers?.ForEach(x =>
{ {
try try
{ {

View File

@ -38,7 +38,7 @@ namespace RageCoop.Core.Scripting
public CustomEventHandler(IntPtr func) : this() public CustomEventHandler(IntPtr func) : this()
{ {
FunctionPtr = (ulong)func; FunctionPtr = (ulong)func;
Module = (ulong)SHVDN.Core.CurrentModule; Directory = SHVDN.Core.CurrentDirectory;
} }
[JsonIgnore] [JsonIgnore]
@ -48,7 +48,7 @@ namespace RageCoop.Core.Scripting
public ulong FunctionPtr { get; private set; } public ulong FunctionPtr { get; private set; }
[JsonProperty] [JsonProperty]
public ulong Module { get; private set; } public string Directory { get; private set; }
/// <summary> /// <summary>
/// ///

View File

@ -2,6 +2,7 @@
using GTA.Math; using GTA.Math;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -290,7 +291,20 @@ namespace RageCoop.Core.Scripting
return Args; return Args;
} }
[LibraryImport("RageCoop.Client.dll")] static unsafe delegate* unmanaged<byte, int, int> _idToHandlePtr;
public static partial int IdToHandle(byte type, int id); public static unsafe int IdToHandle(byte type, int id)
{
if (_idToHandlePtr == default)
{
if (SHVDN.Core.GetPtr == default)
throw new InvalidOperationException("Not client");
_idToHandlePtr = (delegate* unmanaged<byte, int, int>)SHVDN.Core.GetPtr("RageCoop.Client.Scripting.API.IdToHandle");
if (_idToHandlePtr == default)
throw new KeyNotFoundException("IdToHandle function not found");
}
return _idToHandlePtr(type, id);
}
} }
} }

View File

@ -15,13 +15,13 @@ namespace RageCoop.Core
{ {
static Type JsonTypeCheck(Type type) static Type JsonTypeCheck(Type type)
{ {
if (type.GetCustomAttribute<JsonDontSerialize>() != null) if (type?.GetCustomAttribute<JsonDontSerialize>() != null)
throw new TypeAccessException($"The type {type} cannot be serialized"); throw new TypeAccessException($"The type {type} cannot be serialized");
return type; return type;
} }
static object JsonTypeCheck(object obj) static object JsonTypeCheck(object obj)
{ {
JsonTypeCheck(obj.GetType()); JsonTypeCheck(obj?.GetType());
return obj; return obj;
} }
public static readonly JsonSerializerSettings JsonSettings = new(); public static readonly JsonSerializerSettings JsonSettings = new();