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 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 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 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
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace RageCoop.Client.Scripting
|
||||
public static unsafe partial class APIBridge
|
||||
{
|
||||
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
|
||||
|
||||
static List<CustomEventHandler> _handlers = new();
|
||||
/// <summary>
|
||||
/// Copy content of string to a sequential block of memory
|
||||
/// </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)
|
||||
{
|
||||
var writer = GetWriter();
|
||||
@ -92,6 +94,9 @@ namespace RageCoop.Client.Scripting
|
||||
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 GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
|
||||
|
||||
@ -120,9 +125,7 @@ namespace RageCoop.Client.Scripting
|
||||
[LibraryImport("RageCoop.Client.dll")]
|
||||
private static partial int GetLastResultLenInChars();
|
||||
|
||||
[LibraryImport("RageCoop.Client.dll")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static partial bool RegisterCustomEventHandler(CustomEventHash hash, IntPtr ptrHandler);
|
||||
|
||||
[LibraryImport("RageCoop.Client.dll", StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial void LogEnqueue(LogLevel level, string msg);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using RageCoop.Core.Scripting;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -9,5 +10,19 @@ namespace RageCoop.Client.Scripting
|
||||
{
|
||||
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>
|
||||
/// Name of the resource
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory where the scripts is loaded from
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string ScriptsDirectory { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A resource-specific folder that can be used to store your files.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DataFolder { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ResourceFile" /> where this script is loaded from.
|
||||
/// Get the <see cref="ClientFile" /> where this script is loaded from.
|
||||
/// </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>
|
||||
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
// TODO: call the api and use logging sinks
|
||||
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();
|
||||
public ResourceLogger Logger => ResourceLogger.Default;
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,24 +1,72 @@
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
[JsonDontSerialize]
|
||||
[ScriptAttributes(NoDefaultInstance = true)]
|
||||
public abstract class ClientScript : Script
|
||||
{
|
||||
ConcurrentQueue<Func<bool>> _jobQueue = new();
|
||||
Queue<Func<bool>> _reAdd = new();
|
||||
/// <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>
|
||||
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>
|
||||
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
||||
/// </summary>
|
||||
public ClientResource CurrentResource { get; internal set; }
|
||||
public ClientResource CurrentResource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="Script.CurrentResource" />
|
||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
|
||||
/// </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>
|
||||
/// The key to open menu
|
||||
/// </summary>
|
||||
public Keys MenuKey { get; set; } = Keys.F9;
|
||||
public Keys MenuKey { get; set; } = Keys.F7;
|
||||
|
||||
/// <summary>
|
||||
/// The key to enter a vehicle as passenger.
|
||||
|
@ -52,7 +52,7 @@ public static class EntryPoint
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
|
@ -86,26 +86,6 @@ namespace RageCoop.Client.Scripting
|
||||
[UnmanagedCallersOnly(EntryPoint = nameof(GetLastResultLenInChars))]
|
||||
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>
|
||||
/// Convert Entity ID to handle
|
||||
/// </summary>
|
||||
@ -121,5 +101,16 @@ namespace RageCoop.Client.Scripting
|
||||
_ => 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.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
@ -309,14 +311,8 @@ namespace RageCoop.Client.Scripting
|
||||
/// </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)
|
||||
{
|
||||
lock (CustomEventHandlers)
|
||||
{
|
||||
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
||||
CustomEventHandlers.Add(hash, handlers = new());
|
||||
handlers.Add(handler);
|
||||
}
|
||||
}
|
||||
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
@ -400,6 +396,11 @@ namespace RageCoop.Client.Scripting
|
||||
Networking.SendChatMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientResource"/> with this name
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
[Remoting]
|
||||
public static ClientResource GetResource(string name)
|
||||
{
|
||||
@ -409,6 +410,21 @@ namespace RageCoop.Client.Scripting
|
||||
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)]
|
||||
public static object GetProperty(string name)
|
||||
@ -436,6 +452,33 @@ namespace RageCoop.Client.Scripting
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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();
|
||||
foreach (var zip in zips)
|
||||
@ -42,19 +43,45 @@ namespace RageCoop.Client.Scripting
|
||||
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
||||
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
|
||||
{
|
||||
@ -73,10 +100,11 @@ namespace RageCoop.Client.Scripting
|
||||
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
r.Files.Add(dir, new ResourceFile
|
||||
r.Files.Add(dir, new ClientFile
|
||||
{
|
||||
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))
|
||||
{
|
||||
@ -99,9 +127,18 @@ namespace RageCoop.Client.Scripting
|
||||
var rfile = new ClientFile
|
||||
{
|
||||
IsDirectory = false,
|
||||
Name = relativeName
|
||||
Name = relativeName,
|
||||
FullPath = file
|
||||
};
|
||||
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);
|
||||
|
@ -17,7 +17,7 @@ using Font = GTA.UI.Font;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Util
|
||||
internal static partial class Util
|
||||
{
|
||||
/// <summary>
|
||||
/// The location of the cursor on screen between 0 and 1.
|
||||
@ -206,10 +206,12 @@ namespace RageCoop.Client
|
||||
Call(DELETE_ENTITY, &handle);
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern ulong GetTickCount64();
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
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 --
|
||||
|
||||
private static int _steeringAngleOffset { get; set; }
|
||||
|
@ -9,6 +9,7 @@ using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
@ -30,9 +31,9 @@ namespace RageCoop.Core
|
||||
{
|
||||
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.Loader",
|
||||
@ -41,7 +42,8 @@ namespace RageCoop.Core
|
||||
"RageCoop.Server",
|
||||
"ScriptHookVDotNet2",
|
||||
"ScriptHookVDotNet3",
|
||||
"ScriptHookVDotNet"
|
||||
"ScriptHookVDotNet",
|
||||
"ScriptHookVDotNetCore"
|
||||
};
|
||||
|
||||
public static string FormatToSharpStyle(string input, int offset)
|
||||
@ -106,7 +108,20 @@ namespace RageCoop.Core
|
||||
|
||||
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)
|
||||
|
@ -46,7 +46,7 @@ namespace RageCoop.Core
|
||||
public string Name = "Logger";
|
||||
|
||||
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()
|
||||
{
|
||||
@ -166,18 +166,29 @@ namespace RageCoop.Core
|
||||
var formatted = Format(line);
|
||||
Writers.ForEach(x =>
|
||||
{
|
||||
x.WriteLine(formatted);
|
||||
x.Flush();
|
||||
try
|
||||
{
|
||||
x.WriteLine(formatted);
|
||||
x.Flush();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleError(ex);
|
||||
}
|
||||
});
|
||||
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 LogLevel LogLevel;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RageCoop.Core.Scripting
|
||||
{
|
||||
@ -22,20 +23,32 @@ namespace RageCoop.Core.Scripting
|
||||
}
|
||||
public unsafe class CustomEventHandler
|
||||
{
|
||||
// Make sure the handler doesn't get GC'd
|
||||
static List<CustomEventHandler> _handlers = new();
|
||||
|
||||
[ThreadStatic]
|
||||
static object _tag;
|
||||
public CustomEventHandler(IntPtr func)
|
||||
public CustomEventHandler()
|
||||
{
|
||||
FunctionPtr = func;
|
||||
if (Path.GetFileName(Environment.ProcessPath).ToLower() == "gtav.exe")
|
||||
lock (_handlers)
|
||||
{
|
||||
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
|
||||
public IntPtr FunctionPtr { get; }
|
||||
public IntPtr Module { get; }
|
||||
|
||||
[JsonProperty]
|
||||
public ulong FunctionPtr { get; private set; }
|
||||
|
||||
[JsonProperty]
|
||||
public ulong Module { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
@ -11,11 +11,13 @@ namespace RageCoop.Core.Scripting
|
||||
/// <summary>
|
||||
/// Full name with relative path of this file
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is a directory
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsDirectory { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,11 +1,29 @@
|
||||
global using static RageCoop.Core.Shared;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RageCoop.Core
|
||||
{
|
||||
public class JsonDontSerialize : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
static Shared()
|
||||
{
|
||||
@ -16,12 +34,12 @@ namespace RageCoop.Core
|
||||
|
||||
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 string JsonSerialize(object obj) => JsonConvert.SerializeObject(obj, JsonSettings);
|
||||
public static string JsonSerialize(object obj) => JsonConvert.SerializeObject(JsonTypeCheck(obj), JsonSettings);
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to <see cref="BufferReader.ThreadLocal"/>
|
||||
|
@ -15,7 +15,7 @@ using System.Resources;
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.6.0.33")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.33")]
|
||||
[assembly: AssemblyVersion("1.6.0.45")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.45")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
|
||||
|
@ -22,6 +22,11 @@ internal class Resources
|
||||
Logger = server.Logger;
|
||||
}
|
||||
|
||||
static Resources()
|
||||
{
|
||||
CoreUtils.ForceLoadAllAssemblies();
|
||||
}
|
||||
|
||||
public bool IsLoaded { get; private set; }
|
||||
|
||||
public void LoadAll()
|
||||
@ -263,7 +268,7 @@ internal class Resources
|
||||
}
|
||||
|
||||
if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent(),
|
||||
ConnectionChannel.RequestResponse, 30000)?.Response == FileResponse.Loaded)
|
||||
ConnectionChannel.File, 30000)?.Response == FileResponse.Loaded)
|
||||
{
|
||||
client.IsReady = true;
|
||||
Server.API.Events.InvokePlayerReady(client);
|
||||
|
@ -47,6 +47,10 @@ public class ServerResource : PluginLoader
|
||||
|
||||
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());
|
||||
if (Directory.Exists(runtimeLibs))
|
||||
{
|
||||
@ -61,16 +65,18 @@ public class ServerResource : PluginLoader
|
||||
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,
|
||||
EnableHotReload = false,
|
||||
IsUnloadable = false,
|
||||
LoadInMemory = true
|
||||
};
|
||||
ServerResource r = new(conf);
|
||||
r.Logger = logger;
|
||||
r.Name = Path.GetFileName(resDir);
|
||||
ServerResource r = new(conf)
|
||||
{
|
||||
Logger = logger,
|
||||
Name = Path.GetFileName(resDir)
|
||||
};
|
||||
if (!File.Exists(conf.MainAssemblyPath))
|
||||
{
|
||||
r.Dispose();
|
||||
|
Submodule libs/ScriptHookVDotNetCore updated: 3999bf64c7...fc239b823e
Reference in New Issue
Block a user