Basically working resource system
This commit is contained in:
@ -36,10 +36,10 @@ namespace RageCoop.Client.Scripting
|
|||||||
public static void LocalChatMessage(System.String from, System.String message) => InvokeCommand("LocalChatMessage", from, message);
|
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 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 RageCoop.Client.Scripting.ClientResource GetResource(System.String name) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResource", name);
|
||||||
|
public static RageCoop.Client.Scripting.ClientResource GetResouceFromFilePath(System.String filePath) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResouceFromFilePath", filePath);
|
||||||
public static System.Object GetConfig(System.String name) => InvokeCommand<System.Object>("GetConfig", 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);
|
public static void SetConfig(System.String name, System.String jsonVal) => InvokeCommand("SetConfig", name, jsonVal);
|
||||||
|
public static void RegisterCustomEventHandler(RageCoop.Core.Scripting.CustomEventHash hash, RageCoop.Core.Scripting.CustomEventHandler handler) => InvokeCommand("RegisterCustomEventHandler", hash, handler);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ 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();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy content of string to a sequential block of memory
|
/// Copy content of string to a sequential block of memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -85,6 +85,8 @@ namespace RageCoop.Client.Scripting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SendCustomEvent(CustomEventHash hash, params object[] args)
|
||||||
|
=> SendCustomEvent(CustomEventFlags.None, hash, args);
|
||||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
|
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
|
||||||
{
|
{
|
||||||
var writer = GetWriter();
|
var writer = GetWriter();
|
||||||
@ -92,6 +94,9 @@ namespace RageCoop.Client.Scripting
|
|||||||
SendCustomEvent(flags, hash, writer.Address, writer.Position);
|
SendCustomEvent(flags, hash, writer.Address, writer.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||||
|
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
|
||||||
|
|
||||||
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
|
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
|
||||||
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
|
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
|
||||||
|
|
||||||
@ -120,9 +125,7 @@ namespace RageCoop.Client.Scripting
|
|||||||
[LibraryImport("RageCoop.Client.dll")]
|
[LibraryImport("RageCoop.Client.dll")]
|
||||||
private static partial int GetLastResultLenInChars();
|
private static partial int GetLastResultLenInChars();
|
||||||
|
|
||||||
[LibraryImport("RageCoop.Client.dll")]
|
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)]
|
||||||
[return: MarshalAs(UnmanagedType.I1)]
|
public static partial void LogEnqueue(LogLevel level, string msg);
|
||||||
public static partial bool RegisterCustomEventHandler(CustomEventHash hash, IntPtr ptrHandler);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using RageCoop.Core.Scripting;
|
using Newtonsoft.Json;
|
||||||
|
using RageCoop.Core.Scripting;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -9,5 +10,19 @@ namespace RageCoop.Client.Scripting
|
|||||||
{
|
{
|
||||||
public class ClientFile : ResourceFile
|
public class ClientFile : ResourceFile
|
||||||
{
|
{
|
||||||
|
public ClientFile() {
|
||||||
|
GetStream = GetStreamMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
|
public string FullPath { get; internal set; }
|
||||||
|
Stream GetStreamMethod()
|
||||||
|
{
|
||||||
|
if (IsDirectory)
|
||||||
|
{
|
||||||
|
return File.Open(FullPath, FileMode.Open);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Cannot open directory as file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,35 +17,38 @@ namespace RageCoop.Client.Scripting
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name of the resource
|
/// Name of the resource
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
public string Name { get; internal set; }
|
public string Name { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Directory where the scripts is loaded from
|
/// Directory where the scripts is loaded from
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
public string ScriptsDirectory { get; internal set; }
|
public string ScriptsDirectory { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A resource-specific folder that can be used to store your files.
|
/// A resource-specific folder that can be used to store your files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
public string DataFolder { get; internal set; }
|
public string DataFolder { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the <see cref="ResourceFile" /> where this script is loaded from.
|
/// Get the <see cref="ClientFile" /> where this script is loaded from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
|
[JsonProperty]
|
||||||
|
public Dictionary<string, ClientFile> Files { get; internal set; } = new Dictionary<string, ClientFile>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of the path of loaded modules, don't modify
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
|
public List<string> Modules = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
|
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
// TODO: call the api and use logging sinks
|
public ResourceLogger Logger => ResourceLogger.Default;
|
||||||
public Core.Logger Logger => throw new NotImplementedException();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all <see cref="ClientScript" /> instance in this resource.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
public List<ClientScript> Scripts { get; } = SHVDN.Core.ListScripts().OfType<ClientScript>().ToList();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,24 +1,72 @@
|
|||||||
using GTA;
|
using GTA;
|
||||||
|
using RageCoop.Core;
|
||||||
using RageCoop.Core.Scripting;
|
using RageCoop.Core.Scripting;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace RageCoop.Client.Scripting
|
namespace RageCoop.Client.Scripting
|
||||||
{
|
{
|
||||||
|
[JsonDontSerialize]
|
||||||
[ScriptAttributes(NoDefaultInstance = true)]
|
[ScriptAttributes(NoDefaultInstance = true)]
|
||||||
public abstract class ClientScript : Script
|
public abstract class ClientScript : Script
|
||||||
{
|
{
|
||||||
|
ConcurrentQueue<Func<bool>> _jobQueue = new();
|
||||||
|
Queue<Func<bool>> _reAdd = new();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the <see cref="ResourceFile" /> instance where this script is loaded from.
|
/// Fully qualified path to the module that the current script runs in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientFile CurrentFile { get; internal set; }
|
public static readonly string FullPath;
|
||||||
|
static unsafe ClientScript()
|
||||||
|
{
|
||||||
|
char* buf = stackalloc char[260];
|
||||||
|
SHVDN.PInvoke.GetModuleFileNameW(SHVDN.Core.CurrentModule, buf, 260);
|
||||||
|
if (Marshal.GetLastWin32Error() != 0)
|
||||||
|
throw new Win32Exception("Failed to get path for current module");
|
||||||
|
FullPath = new(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientScript()
|
||||||
|
{
|
||||||
|
CurrentResource = APIBridge.GetResouceFromFilePath(FullPath);
|
||||||
|
if (CurrentResource == null)
|
||||||
|
throw new Exception("No resource associated with this script is found");
|
||||||
|
|
||||||
|
CurrentFile = CurrentResource.Files.Values.FirstOrDefault(x => x?.FullPath?.ToLower() == FullPath.ToLower());
|
||||||
|
if (CurrentFile == null)
|
||||||
|
{
|
||||||
|
Logger.Warning("No file associated with curent script was found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tick += DoQueuedJobs;
|
||||||
|
}
|
||||||
|
protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action);
|
||||||
|
protected void QueueAction(Action action) => QueueAction(() => { action(); return true; });
|
||||||
|
|
||||||
|
private void DoQueuedJobs()
|
||||||
|
{
|
||||||
|
while (_reAdd.TryDequeue(out var toAdd))
|
||||||
|
_jobQueue.Enqueue(toAdd);
|
||||||
|
while (_jobQueue.TryDequeue(out var job))
|
||||||
|
{
|
||||||
|
if (!job())
|
||||||
|
_reAdd.Enqueue(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="ClientFile" /> instance where this script is loaded from.
|
||||||
|
/// </summary>
|
||||||
|
public ClientFile CurrentFile { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientResource CurrentResource { get; internal set; }
|
public ClientResource CurrentResource { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="Script.CurrentResource" />
|
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Core.Logger Logger => CurrentResource.Logger;
|
public ResourceLogger Logger => CurrentResource.Logger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
Client/Scripting/ResourceLogger.cs
Normal file
25
Client/Scripting/ResourceLogger.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RageCoop.Client.Scripting
|
||||||
|
{
|
||||||
|
public class ResourceLogger : Core.Logger
|
||||||
|
{
|
||||||
|
public static readonly ResourceLogger Default = new();
|
||||||
|
public ResourceLogger()
|
||||||
|
{
|
||||||
|
FlushImmediately= true;
|
||||||
|
OnFlush += FlushToMainModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushToMainModule(LogLine line, string fomatted)
|
||||||
|
{
|
||||||
|
APIBridge.LogEnqueue(line.LogLevel, line.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ namespace RageCoop.Client
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key to open menu
|
/// The key to open menu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Keys MenuKey { get; set; } = Keys.F9;
|
public Keys MenuKey { get; set; } = Keys.F7;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key to enter a vehicle as passenger.
|
/// The key to enter a vehicle as passenger.
|
||||||
|
@ -52,7 +52,7 @@ public static class EntryPoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "OnKeyboard")]
|
[UnmanagedCallersOnly(EntryPoint = "OnKeyboard")]
|
||||||
public unsafe static void OnKeyboard(int key, ushort repeats, bool scanCode, bool isExtended, bool isWithAlt, bool wasDownBefore, bool isUpNow)
|
public unsafe static void OnKeyboard(uint key, ushort repeats, bool scanCode, bool isExtended, bool isWithAlt, bool wasDownBefore, bool isUpNow)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -86,26 +86,6 @@ namespace RageCoop.Client.Scripting
|
|||||||
[UnmanagedCallersOnly(EntryPoint = nameof(GetLastResultLenInChars))]
|
[UnmanagedCallersOnly(EntryPoint = nameof(GetLastResultLenInChars))]
|
||||||
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
|
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = nameof(RegisterCustomEventHandler))]
|
|
||||||
public static bool RegisterCustomEventHandler(CustomEventHash hash, IntPtr ptrHandler)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lock (CustomEventHandlers)
|
|
||||||
{
|
|
||||||
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
|
||||||
CustomEventHandlers.Add(hash, handlers = new());
|
|
||||||
handlers.Add(new(ptrHandler));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(nameof(RegisterCustomEventHandler), ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert Entity ID to handle
|
/// Convert Entity ID to handle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -121,5 +101,16 @@ namespace RageCoop.Client.Scripting
|
|||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue a message to the main logger
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = nameof(LogEnqueue))]
|
||||||
|
public static void LogEnqueue(LogLevel level, char* msg)
|
||||||
|
{
|
||||||
|
Log.Enqueue((int)level, new(msg));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@ 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 SHVDN;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@ -309,14 +311,8 @@ namespace RageCoop.Client.Scripting
|
|||||||
/// </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)
|
||||||
{
|
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
|
||||||
lock (CustomEventHandlers)
|
|
||||||
{
|
|
||||||
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
|
||||||
CustomEventHandlers.Add(hash, handlers = new());
|
|
||||||
handlers.Add(handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -400,6 +396,11 @@ namespace RageCoop.Client.Scripting
|
|||||||
Networking.SendChatMessage(message);
|
Networking.SendChatMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="ClientResource"/> with this name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
[Remoting]
|
[Remoting]
|
||||||
public static ClientResource GetResource(string name)
|
public static ClientResource GetResource(string name)
|
||||||
{
|
{
|
||||||
@ -409,6 +410,21 @@ namespace RageCoop.Client.Scripting
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get <see cref="ClientResource"/> that contains the specified file
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[Remoting]
|
||||||
|
public static ClientResource GetResouceFromFilePath(string filePath)
|
||||||
|
{
|
||||||
|
foreach (var res in MainRes.LoadedResources)
|
||||||
|
{
|
||||||
|
if (res.Value.Files.Any(file => file.Value.FullPath.ToLower() == filePath.ToLower()))
|
||||||
|
return res.Value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Remoting(GenBridge = false)]
|
[Remoting(GenBridge = false)]
|
||||||
public static object GetProperty(string name)
|
public static object GetProperty(string name)
|
||||||
@ -436,6 +452,33 @@ namespace RageCoop.Client.Scripting
|
|||||||
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
|
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
|
||||||
|
/// 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
|
||||||
|
/// </param>
|
||||||
|
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||||
|
[Remoting]
|
||||||
|
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
|
||||||
|
{
|
||||||
|
if (handler.Module == default)
|
||||||
|
throw new ArgumentException("Module not specified");
|
||||||
|
|
||||||
|
if (handler.FunctionPtr == default)
|
||||||
|
throw new ArgumentException("Function pointer not specified");
|
||||||
|
|
||||||
|
lock (CustomEventHandlers)
|
||||||
|
{
|
||||||
|
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
||||||
|
CustomEventHandlers.Add(hash, handlers = new());
|
||||||
|
handlers.Add(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -33,7 +34,7 @@ namespace RageCoop.Client.Scripting
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Load(string path, string[] zips)
|
public unsafe void Load(string path, string[] zips)
|
||||||
{
|
{
|
||||||
LoadedResources.Clear();
|
LoadedResources.Clear();
|
||||||
foreach (var zip in zips)
|
foreach (var zip in zips)
|
||||||
@ -42,19 +43,45 @@ namespace RageCoop.Client.Scripting
|
|||||||
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
||||||
Unpack(zipPath, Path.Combine(path, "Data"));
|
Unpack(zipPath, Path.Combine(path, "Data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored())
|
|
||||||
.ForEach(File.Delete);
|
|
||||||
|
|
||||||
// TODO: Core.ScheduleLoad()...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unload()
|
public unsafe void Unload()
|
||||||
{
|
{
|
||||||
// TODO: Core.ScheduleUnload()...
|
HashSet<IntPtr> modules = new();
|
||||||
|
foreach (var res in LoadedResources.Values)
|
||||||
|
{
|
||||||
|
foreach (var module in res.Modules)
|
||||||
|
{
|
||||||
|
fixed (char* pModulePath = module)
|
||||||
|
{
|
||||||
|
Log.Debug($"Unloading module: {module}");
|
||||||
|
SHVDN.Core.ScheduleUnload(pModulePath);
|
||||||
|
var hModule = Util.GetModuleHandleW(module);
|
||||||
|
if (hModule == IntPtr.Zero)
|
||||||
|
Log.Warning("Failed to get module handler for " + Path.GetFileName(module));
|
||||||
|
else
|
||||||
|
modules.Add(hModule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister associated handler
|
||||||
|
foreach (var handlers in API.CustomEventHandlers.Values)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers.ToArray())
|
||||||
|
{
|
||||||
|
if (modules.Contains((IntPtr)handler.Module))
|
||||||
|
{
|
||||||
|
Log.Debug($"Unregister handler from module {handler.Module}");
|
||||||
|
handlers.Remove(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadedResources.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientResource Unpack(string zipPath, string dataFolderRoot)
|
private unsafe ClientResource Unpack(string zipPath, string dataFolderRoot)
|
||||||
{
|
{
|
||||||
var r = new ClientResource
|
var r = new ClientResource
|
||||||
{
|
{
|
||||||
@ -73,10 +100,11 @@ namespace RageCoop.Client.Scripting
|
|||||||
|
|
||||||
|
|
||||||
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
||||||
r.Files.Add(dir, new ResourceFile
|
r.Files.Add(dir, new ClientFile
|
||||||
{
|
{
|
||||||
IsDirectory = true,
|
IsDirectory = true,
|
||||||
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
|
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/'),
|
||||||
|
FullPath = dir
|
||||||
});
|
});
|
||||||
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
|
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
@ -99,9 +127,18 @@ namespace RageCoop.Client.Scripting
|
|||||||
var rfile = new ClientFile
|
var rfile = new ClientFile
|
||||||
{
|
{
|
||||||
IsDirectory = false,
|
IsDirectory = false,
|
||||||
Name = relativeName
|
Name = relativeName,
|
||||||
|
FullPath = file
|
||||||
};
|
};
|
||||||
r.Files.Add(relativeName, rfile);
|
r.Files.Add(relativeName, rfile);
|
||||||
|
if (file.EndsWith(".dll"))
|
||||||
|
{
|
||||||
|
fixed (char* pModulePath = file)
|
||||||
|
{
|
||||||
|
SHVDN.Core.ScheduleLoad(pModulePath);
|
||||||
|
r.Modules.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadedResources.TryAdd(r.Name, r);
|
LoadedResources.TryAdd(r.Name, r);
|
||||||
|
@ -17,7 +17,7 @@ using Font = GTA.UI.Font;
|
|||||||
|
|
||||||
namespace RageCoop.Client
|
namespace RageCoop.Client
|
||||||
{
|
{
|
||||||
internal static class Util
|
internal static partial class Util
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The location of the cursor on screen between 0 and 1.
|
/// The location of the cursor on screen between 0 and 1.
|
||||||
@ -206,9 +206,11 @@ namespace RageCoop.Client
|
|||||||
Call(DELETE_ENTITY, &handle);
|
Call(DELETE_ENTITY, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
[LibraryImport("kernel32.dll")]
|
||||||
public static extern ulong GetTickCount64();
|
public static partial ulong GetTickCount64();
|
||||||
|
|
||||||
|
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||||
|
public static partial IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
|
||||||
|
|
||||||
#region -- POINTER --
|
#region -- POINTER --
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using System.Net.Sockets;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Loader;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@ -30,9 +31,9 @@ namespace RageCoop.Core
|
|||||||
{
|
{
|
||||||
internal static class CoreUtils
|
internal static class CoreUtils
|
||||||
{
|
{
|
||||||
private static readonly Random random = new Random();
|
private static readonly Random random = new();
|
||||||
|
|
||||||
private static readonly HashSet<string> ToIgnore = new HashSet<string>
|
private static readonly HashSet<string> ToIgnore = new()
|
||||||
{
|
{
|
||||||
"RageCoop.Client",
|
"RageCoop.Client",
|
||||||
"RageCoop.Client.Loader",
|
"RageCoop.Client.Loader",
|
||||||
@ -41,7 +42,8 @@ namespace RageCoop.Core
|
|||||||
"RageCoop.Server",
|
"RageCoop.Server",
|
||||||
"ScriptHookVDotNet2",
|
"ScriptHookVDotNet2",
|
||||||
"ScriptHookVDotNet3",
|
"ScriptHookVDotNet3",
|
||||||
"ScriptHookVDotNet"
|
"ScriptHookVDotNet",
|
||||||
|
"ScriptHookVDotNetCore"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string FormatToSharpStyle(string input, int offset)
|
public static string FormatToSharpStyle(string input, int offset)
|
||||||
@ -106,7 +108,20 @@ namespace RageCoop.Core
|
|||||||
|
|
||||||
public static bool CanBeIgnored(this string name)
|
public static bool CanBeIgnored(this string name)
|
||||||
{
|
{
|
||||||
return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
|
name = Path.GetFileNameWithoutExtension(name);
|
||||||
|
return ToIgnore.Contains(name) || AssemblyLoadContext.Default.Assemblies.Any(x => x.GetName().Name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ForceLoadAllAssemblies()
|
||||||
|
{
|
||||||
|
foreach (var a in AssemblyLoadContext.Default.Assemblies)
|
||||||
|
LoadAllReferencedAssemblies(a.GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadAllReferencedAssemblies(this AssemblyName assembly)
|
||||||
|
{
|
||||||
|
foreach (var child in Assembly.Load(assembly).GetReferencedAssemblies())
|
||||||
|
LoadAllReferencedAssemblies(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToFullPath(this string path)
|
public static string ToFullPath(this string path)
|
||||||
|
@ -46,7 +46,7 @@ namespace RageCoop.Core
|
|||||||
public string Name = "Logger";
|
public string Name = "Logger";
|
||||||
|
|
||||||
private bool Stopping;
|
private bool Stopping;
|
||||||
public List<StreamWriter> Writers = new List<StreamWriter> { new StreamWriter(Console.OpenStandardOutput()) };
|
public List<StreamWriter> Writers = new() { new StreamWriter(Console.OpenStandardOutput()) };
|
||||||
|
|
||||||
internal Logger()
|
internal Logger()
|
||||||
{
|
{
|
||||||
@ -166,18 +166,29 @@ namespace RageCoop.Core
|
|||||||
var formatted = Format(line);
|
var formatted = Format(line);
|
||||||
Writers.ForEach(x =>
|
Writers.ForEach(x =>
|
||||||
{
|
{
|
||||||
x.WriteLine(formatted);
|
try
|
||||||
x.Flush();
|
{
|
||||||
|
x.WriteLine(formatted);
|
||||||
|
x.Flush();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleError(ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
OnFlush?.Invoke(line, formatted);
|
OnFlush?.Invoke(line, formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
HandleError(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void HandleError(Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Logger {this} flush error: {ex}");
|
||||||
|
}
|
||||||
public class LogLine
|
public class LogLine
|
||||||
{
|
{
|
||||||
public LogLevel LogLevel;
|
public LogLevel LogLevel;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Runtime.InteropServices;
|
using Newtonsoft.Json;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace RageCoop.Core.Scripting
|
namespace RageCoop.Core.Scripting
|
||||||
{
|
{
|
||||||
@ -22,20 +23,32 @@ namespace RageCoop.Core.Scripting
|
|||||||
}
|
}
|
||||||
public unsafe class CustomEventHandler
|
public unsafe class CustomEventHandler
|
||||||
{
|
{
|
||||||
|
// Make sure the handler doesn't get GC'd
|
||||||
|
static List<CustomEventHandler> _handlers = new();
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
static object _tag;
|
static object _tag;
|
||||||
public CustomEventHandler(IntPtr func)
|
public CustomEventHandler()
|
||||||
{
|
{
|
||||||
FunctionPtr = func;
|
lock (_handlers)
|
||||||
if (Path.GetFileName(Environment.ProcessPath).ToLower() == "gtav.exe")
|
|
||||||
{
|
{
|
||||||
Module = SHVDN.Core.CurrentModule;
|
_handlers.Add(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public CustomEventHandler(IntPtr func) : this()
|
||||||
|
{
|
||||||
|
FunctionPtr = (ulong)func;
|
||||||
|
Module = (ulong)SHVDN.Core.CurrentModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
private CustomEventHandlerDelegate _managedHandler; // Used to keep GC reference
|
private CustomEventHandlerDelegate _managedHandler; // Used to keep GC reference
|
||||||
public IntPtr FunctionPtr { get; }
|
|
||||||
public IntPtr Module { get; }
|
[JsonProperty]
|
||||||
|
public ulong FunctionPtr { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
|
public ulong Module { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
@ -11,11 +11,13 @@ namespace RageCoop.Core.Scripting
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Full name with relative path of this file
|
/// Full name with relative path of this file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
public string Name { get; internal set; }
|
public string Name { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this is a directory
|
/// Whether this is a directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
public bool IsDirectory { get; internal set; }
|
public bool IsDirectory { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
global using static RageCoop.Core.Shared;
|
global using static RageCoop.Core.Shared;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace RageCoop.Core
|
namespace RageCoop.Core
|
||||||
{
|
{
|
||||||
|
public class JsonDontSerialize : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
internal class Shared
|
internal class Shared
|
||||||
{
|
{
|
||||||
|
static Type JsonTypeCheck(Type type)
|
||||||
|
{
|
||||||
|
if (type.GetCustomAttribute<JsonDontSerialize>() != null)
|
||||||
|
throw new TypeAccessException($"The type {type} cannot be serialized");
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
static object JsonTypeCheck(object obj)
|
||||||
|
{
|
||||||
|
JsonTypeCheck(obj.GetType());
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
public static readonly JsonSerializerSettings JsonSettings = new();
|
public static readonly JsonSerializerSettings JsonSettings = new();
|
||||||
static Shared()
|
static Shared()
|
||||||
{
|
{
|
||||||
@ -16,12 +34,12 @@ namespace RageCoop.Core
|
|||||||
|
|
||||||
public static object JsonDeserialize(string text, Type type)
|
public static object JsonDeserialize(string text, Type type)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject(text, type, JsonSettings);
|
return JsonConvert.DeserializeObject(text, JsonTypeCheck(type), JsonSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T JsonDeserialize<T>(string text) => (T)JsonDeserialize(text, typeof(T));
|
public static T JsonDeserialize<T>(string text) => (T)JsonDeserialize(text, typeof(T));
|
||||||
|
|
||||||
public static string JsonSerialize(object obj) => JsonConvert.SerializeObject(obj, JsonSettings);
|
public static string JsonSerialize(object obj) => JsonConvert.SerializeObject(JsonTypeCheck(obj), JsonSettings);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shortcut to <see cref="BufferReader.ThreadLocal"/>
|
/// Shortcut to <see cref="BufferReader.ThreadLocal"/>
|
||||||
|
@ -15,7 +15,7 @@ using System.Resources;
|
|||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
[assembly: AssemblyVersion("1.6.0.33")]
|
[assembly: AssemblyVersion("1.6.0.45")]
|
||||||
[assembly: AssemblyFileVersion("1.6.0.33")]
|
[assembly: AssemblyFileVersion("1.6.0.45")]
|
||||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||||
|
|
||||||
|
@ -22,6 +22,11 @@ internal class Resources
|
|||||||
Logger = server.Logger;
|
Logger = server.Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Resources()
|
||||||
|
{
|
||||||
|
CoreUtils.ForceLoadAllAssemblies();
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsLoaded { get; private set; }
|
public bool IsLoaded { get; private set; }
|
||||||
|
|
||||||
public void LoadAll()
|
public void LoadAll()
|
||||||
@ -263,7 +268,7 @@ internal class Resources
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent(),
|
if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent(),
|
||||||
ConnectionChannel.RequestResponse, 30000)?.Response == FileResponse.Loaded)
|
ConnectionChannel.File, 30000)?.Response == FileResponse.Loaded)
|
||||||
{
|
{
|
||||||
client.IsReady = true;
|
client.IsReady = true;
|
||||||
Server.API.Events.InvokePlayerReady(client);
|
Server.API.Events.InvokePlayerReady(client);
|
||||||
|
@ -47,6 +47,10 @@ public class ServerResource : PluginLoader
|
|||||||
|
|
||||||
internal static ServerResource LoadFrom(string resDir, string dataFolder, Logger logger = null)
|
internal static ServerResource LoadFrom(string resDir, string dataFolder, Logger logger = null)
|
||||||
{
|
{
|
||||||
|
string mainAssemblyPath = Path.Combine(resDir, Path.GetFileName(resDir) + ".dll");
|
||||||
|
if (!File.Exists(mainAssemblyPath))
|
||||||
|
throw new FileNotFoundException($"Main assemby not found: {mainAssemblyPath}");
|
||||||
|
|
||||||
var runtimeLibs = Path.Combine(resDir, "RuntimeLibs", CoreUtils.GetInvariantRID());
|
var runtimeLibs = Path.Combine(resDir, "RuntimeLibs", CoreUtils.GetInvariantRID());
|
||||||
if (Directory.Exists(runtimeLibs))
|
if (Directory.Exists(runtimeLibs))
|
||||||
{
|
{
|
||||||
@ -61,16 +65,18 @@ public class ServerResource : PluginLoader
|
|||||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(runtimeLibs), new DirectoryInfo(resDir));
|
CoreUtils.CopyFilesRecursively(new DirectoryInfo(runtimeLibs), new DirectoryInfo(resDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
var conf = new PluginConfig(Path.GetFullPath(Path.Combine(resDir, Path.GetFileName(resDir) + ".dll")))
|
var conf = new PluginConfig(Path.GetFullPath(mainAssemblyPath))
|
||||||
{
|
{
|
||||||
PreferSharedTypes = true,
|
PreferSharedTypes = true,
|
||||||
EnableHotReload = false,
|
EnableHotReload = false,
|
||||||
IsUnloadable = false,
|
IsUnloadable = false,
|
||||||
LoadInMemory = true
|
LoadInMemory = true
|
||||||
};
|
};
|
||||||
ServerResource r = new(conf);
|
ServerResource r = new(conf)
|
||||||
r.Logger = logger;
|
{
|
||||||
r.Name = Path.GetFileName(resDir);
|
Logger = logger,
|
||||||
|
Name = Path.GetFileName(resDir)
|
||||||
|
};
|
||||||
if (!File.Exists(conf.MainAssemblyPath))
|
if (!File.Exists(conf.MainAssemblyPath))
|
||||||
{
|
{
|
||||||
r.Dispose();
|
r.Dispose();
|
||||||
|
Submodule libs/ScriptHookVDotNetCore updated: 3999bf64c7...fc239b823e
Reference in New Issue
Block a user