This commit is contained in:
sardelka9515
2022-10-10 16:45:58 +08:00
parent fe53e01a4a
commit 8701ac703e
20 changed files with 153 additions and 81 deletions

View File

@ -59,7 +59,7 @@ namespace RageCoop.Client
if (Settings.DataDirectory.StartsWith("Scripts"))
{
var defaultDir = new Settings().DataDirectory;
Console.Warning.WriteLine("Data directory must be outside scripts folder, migrating to default direcoty: "+defaultDir);
Console.Warning.WriteLine("Data directory must be outside scripts folder, migrating to default direcoty: " + defaultDir);
if (Directory.Exists(Settings.DataDirectory))
{
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Settings.DataDirectory), new DirectoryInfo(defaultDir));
@ -83,9 +83,10 @@ namespace RageCoop.Client
#if DEBUG
LogLevel = 0,
#else
LogLevel=Settings.LogLevel,
LogLevel = Settings.LogLevel,
#endif
};
Logger.OnFlush += (s, d) => Console.WriteLine(d);
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
ScriptDomain.CurrentDomain.Tick += DomainTick;
Resources = new Resources();
@ -114,7 +115,6 @@ namespace RageCoop.Client
Aborted += OnAborted;
Tick += OnTick;
KeyDown += OnKeyDown;
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
Util.NativeMemory();
Counter.Restart();
@ -125,11 +125,11 @@ namespace RageCoop.Client
{
try
{
WorldThread.DoQueuedActions();
WorldThread.Instance?.Abort();
DevTool.Instance?.Abort();
ResourceDomain.UnloadAll();
ScriptDomain.CurrentDomain.Tick -= DomainTick;
Disconnected("Abort");
WorldThread.DoQueuedActions();
}
catch (Exception ex)
{
@ -383,11 +383,11 @@ namespace RageCoop.Client
CoopMenu.DisconnectedMenuSetting();
GTA.UI.Notification.Show("~r~Disconnected: " + reason);
LocalPlayerID = default;
Resources.Unload();
});
Memory.RestorePatches();
DownloadManager.Cleanup();
Voice.ClearAll();
Resources.Unload();
}

View File

@ -2,6 +2,7 @@
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System;
using System.Threading;
@ -260,19 +261,20 @@ namespace RageCoop.Client
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Deserialize(msg);
if (packet.Flags.HasEventFlag(Core.Scripting.CustomEventFlags.Queued))
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
{
recycle = false;
API.QueueAction(() =>
{
API.Events.InvokeCustomEventReceived(packet);
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg);
});
}
else
{
API.Events.InvokeCustomEventReceived(packet);
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
}
break;

View File

@ -16,7 +16,7 @@ using System.Resources;
// Version informationr(
[assembly: AssemblyVersion("1.5.4.186")]
[assembly: AssemblyFileVersion("1.5.4.186")]
[assembly: AssemblyVersion("1.5.4.244")]
[assembly: AssemblyFileVersion("1.5.4.244")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -3,9 +3,11 @@ using GTA;
using Newtonsoft.Json;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace RageCoop.Client.Scripting
@ -29,7 +31,7 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// Client configuration, this will conflict with server-side config.
/// </summary>
public class ClientConfig
public class ClientConfig : MarshalByRefObject
{
/// <summary>
/// Get or set local player's username, set won't be effective if already connected to a server.
@ -72,7 +74,7 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// Base events for RageCoop
/// </summary>
public class ClientEvents
public class ClientEvents : MarshalByRefObject
{
internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
@ -130,6 +132,11 @@ namespace RageCoop.Client.Scripting
{
handlers.ForEach((x) => { x.Invoke(args); });
}
if (Util.IsPrimaryDomain)
{
ResourceDomain.DoCallBack("CustomEvent",p);
}
}
#endregion
}
@ -139,6 +146,16 @@ namespace RageCoop.Client.Scripting
/// </summary>
public class API : MarshalByRefObject
{
static API()
{
if (!Util.IsPrimaryDomain)
{
ResourceDomain.RegisterCallBackForCurrentDomain("CustomEvent",
(data) => {
Events.InvokeCustomEventReceived(data as Packets.CustomEvent);
});
}
}
static API Instance;
private API() { }
@ -161,7 +178,7 @@ namespace RageCoop.Client.Scripting
return Instance;
}
public ClientEvents Events = new ClientEvents();
public static ClientEvents Events = new ClientEvents();
public ClientConfig Config = new ClientConfig();
#region PROPERTIES
@ -217,7 +234,8 @@ namespace RageCoop.Client.Scripting
#region FUNCTIONS
public ClientResource GetResource(string name)
{
if(Main.Resources.LoadedResources.TryGetValue(name.ToLower(), out var res)){
if (Main.Resources.LoadedResources.TryGetValue(name.ToLower(), out var res))
{
return res;
}
return null;
@ -282,12 +300,28 @@ namespace RageCoop.Client.Scripting
{
WorldThread.QueueAction(a);
}
public void QueueActionAndWait(Action a, int timeout = 15000)
{
var done = new AutoResetEvent(false);
Exception e = null;
QueueAction(() =>
{
try
{
a();
done.Set();
}
catch (Exception ex) { e = ex; }
});
if (e != null) { throw e; }
else if (!done.WaitOne(timeout)) { throw new TimeoutException(); }
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
public void QueueAction(Func<bool> a)
public static void QueueAction(Func<bool> a)
{
WorldThread.QueueAction(a);
}
@ -321,22 +355,6 @@ namespace RageCoop.Client.Scripting
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <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, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
{
lock (Events.CustomEventHandlers)
{
if (!Events.CustomEventHandlers.TryGetValue(hash, out List<Action<CustomEventReceivedArgs>> handlers))
{
Events.CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
}
handlers.Add(handler);
}
}
/// <summary>
///
@ -365,6 +383,17 @@ namespace RageCoop.Client.Scripting
}
});
}
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
{
lock (Events.CustomEventHandlers)
{
if (!Events.CustomEventHandlers.TryGetValue(hash, out List<Action<CustomEventReceivedArgs>> handlers))
{
Events.CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
}
handlers.Add(handler);
}
}
#endregion
}
}

View File

@ -185,7 +185,7 @@ namespace RageCoop.Client.Scripting
prop.Model = (Model)e.Args[1];
prop.Position = (Vector3)e.Args[2];
prop.Rotation = (Vector3)e.Args[3];
Main.Logger.Debug("Prop: "+ prop.Model.Request(1000));
prop.Model.Request(1000);
prop.Update();
}
private void NativeCall(CustomEventReceivedArgs e)

View File

@ -1,11 +1,11 @@
using RageCoop.Core.Scripting;
using System;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary>
[GTA.ScriptAttributes(Author = "RageCoop", NoDefaultInstance = true, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
public abstract class ClientScript : GTA.Script
{
/// <summary>

View File

@ -19,8 +19,12 @@ namespace RageCoop.Client.Scripting
public static ScriptDomain PrimaryDomain;
public string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
private ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
private static ConcurrentDictionary<string, Action<object>> _callBacks = new ConcurrentDictionary<string, Action<object>>();
API API => API.GetInstance();
private ResourceDomain(ScriptDomain primary, string[] apiPaths)
{
AppDomain.CurrentDomain.SetData("Primary", primary);
foreach (var apiPath in apiPaths)
{
try
@ -33,14 +37,11 @@ namespace RageCoop.Client.Scripting
}
}
PrimaryDomain = primary;
// Bridge to current ScriptDomain
primary.Tick += Tick;
primary.KeyEvent += KeyEvent;
primary.KeyEvent += KeyEvent;
CurrentDomain.Start();
SetupScripts();
AppDomain.CurrentDomain.SetData("Primary", false);
Console.WriteLine("Loaded scondary domain: " + AppDomain.CurrentDomain.Id + " " + Util.IsPrimaryDomain);
Console.WriteLine($"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
}
public static bool IsLoaded(string dir)
{
@ -53,26 +54,50 @@ namespace RageCoop.Client.Scripting
try
{
API.Logger.Debug("Starting script: " + s.GetType().FullName);
var script = (ClientScript)s;
var res = Main.API.GetResource(Path.GetFileName(Directory.GetParent(script.Filename).FullName));
if (res == null) { Main.API.Logger.Warning("Failed to locate resource for script: " + script.Filename); continue; }
var res = API.GetResource(Path.GetFileName(Directory.GetParent(script.Filename).FullName));
if (res == null) { API.Logger.Warning("Failed to locate resource for script: " + script.Filename); continue; }
script.CurrentResource = res;
script.CurrentFile = res.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.BaseDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
script.CurrentFile = res.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
res.Scripts.Add(script);
s.GetType().Assembly.GetReferencedAssemblies().ForEach(x => Assembly.Load(x.FullName));
script.OnStart();
}
catch (Exception ex)
{
Main.API.Logger.Error($"Failed to start {s.GetType().FullName}", ex);
API.Logger.Error($"Failed to start {s.GetType().FullName}", ex);
}
}
}
public object[] GetClientScripts()
{
Console.WriteLine("Running scripts: " + ScriptDomain.CurrentDomain.RunningScripts.Select(x => x.ScriptInstance.GetType().FullName).Dump());
return ScriptDomain.CurrentDomain.RunningScripts.Where(x =>
x.ScriptInstance.GetType().IsAssignableFrom(typeof(ClientScript)) &&
x.ScriptInstance.GetType().IsSubclassOf(typeof(ClientScript)) &&
!x.ScriptInstance.GetType().IsAbstract).Select(x => x.ScriptInstance).ToArray();
}
public static void RegisterCallBackForCurrentDomain(string name, Action<object> callback)
{
if (!_callBacks.TryAdd(name, callback))
{
throw new Exception("Failed to add callback");
}
}
public void DoCallback(string name,object data)
{
if(_callBacks.TryGetValue(name, out var callBack))
{
callBack(data);
}
}
public static void DoCallBack(string name,object data)
{
foreach(var d in _loadedDomains)
{
d.Value.DoCallback(name, data);
}
}
public static ResourceDomain Load(string dir = @"RageCoop\Scripts\Debug")
{
lock (_loadedDomains)
@ -132,7 +157,7 @@ namespace RageCoop.Client.Scripting
{
lock (_loadedDomains)
{
Exception ex=null;
Exception ex = null;
Main.QueueToMainThread(() =>
{
try
@ -141,10 +166,10 @@ namespace RageCoop.Client.Scripting
ScriptDomain.Unload(domain.CurrentDomain);
_loadedDomains.TryRemove(domain.BaseDirectory, out _);
}
catch(Exception e) { ex = e; }
catch (Exception e) { ex = e; }
});
GTA.Script.Yield();
if(ex != null) { throw ex; }
if (ex != null) { throw ex; }
}
}
public static void Unload(string dir)
@ -174,19 +199,20 @@ namespace RageCoop.Client.Scripting
public void Dispose()
{
foreach(var s in GetClientScripts())
PrimaryDomain.Tick -= Tick;
PrimaryDomain.KeyEvent -= KeyEvent;
foreach (var s in GetClientScripts())
{
try
{
API.Logger.Debug("Stopping script: " + s.GetType().FullName);
((ClientScript)s).OnStop();
}
catch(Exception ex)
catch (Exception ex)
{
Main.API.Logger.Error($"Failed to stop {s.GetType().FullName}",ex);
API.Logger.Error($"Failed to stop {s.GetType().FullName}", ex);
}
}
PrimaryDomain.Tick -= Tick;
PrimaryDomain.KeyEvent -= KeyEvent;
}
}
}

View File

@ -7,23 +7,27 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace RageCoop.Client.Scripting
{
/// <summary>
///
/// </summary>
public class ClientResource
public class ClientResource : MarshalByRefObject
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
public string BaseDirectory { 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 => Path.Combine(BaseDirectory, "Data");
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript"/> instance in this resource.
/// </summary>
@ -40,9 +44,7 @@ namespace RageCoop.Client.Scripting
}
internal class Resources
{
static readonly API API = Main.API;
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new ConcurrentDictionary<string, ClientResource>();
private const string BaseScriptType = "RageCoop.Client.Scripting.ClientScript";
private Logger Logger { get; set; }
public Resources()
{
@ -57,11 +59,12 @@ namespace RageCoop.Client.Scripting
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
Unpack(zipPath, Path.Combine(path, "Data"));
}
ResourceDomain.Load(path);
Main.API.QueueActionAndWait(() => ResourceDomain.Load(path));
}
public void Unload()
{
ResourceDomain.UnloadAll();
LoadedResources.Clear();
}
private void Unpack(string zipPath, string dataFolderRoot)
@ -71,30 +74,31 @@ namespace RageCoop.Client.Scripting
Logger = Main.API.Logger,
Scripts = new List<ClientScript>(),
Name = Path.GetFileNameWithoutExtension(zipPath),
BaseDirectory = Path.Combine(Directory.GetParent(zipPath).FullName, Path.GetFileNameWithoutExtension(zipPath))
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
ScriptsDirectory = Path.Combine(Directory.GetParent(zipPath).FullName, Path.GetFileNameWithoutExtension(zipPath))
};
Directory.CreateDirectory(r.DataFolder);
var resDir = r.BaseDirectory;
if (Directory.Exists(resDir)) { Directory.Delete(resDir, true); }
else if (File.Exists(resDir)) { File.Delete(resDir); }
Directory.CreateDirectory(resDir);
var scriptsDir = r.ScriptsDirectory;
if (Directory.Exists(scriptsDir)) { Directory.Delete(scriptsDir, true); }
else if (File.Exists(scriptsDir)) { File.Delete(scriptsDir); }
Directory.CreateDirectory(scriptsDir);
new FastZip().ExtractZip(zipPath, resDir, null);
new FastZip().ExtractZip(zipPath, scriptsDir, null);
foreach (var dir in Directory.GetDirectories(resDir, "*", SearchOption.AllDirectories))
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
{
r.Files.Add(dir, new ResourceFile()
{
IsDirectory = true,
Name = dir.Substring(resDir.Length + 1).Replace('\\', '/')
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
});
}
var assemblies = new Dictionary<ResourceFile, Assembly>();
foreach (var file in Directory.GetFiles(resDir, "*", SearchOption.AllDirectories))
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
{
if (Path.GetFileName(file).CanBeIgnored()) { try { File.Delete(file); } catch { } continue; }
var relativeName = file.Substring(resDir.Length + 1).Replace('\\', '/');
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
var rfile = new ResourceFile()
{
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },

View File

@ -36,7 +36,7 @@ namespace RageCoop.Client
/// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 00;
public int LogLevel = 1;
/// <summary>
/// The key to open menu

View File

@ -258,7 +258,7 @@ namespace RageCoop.Client
if (!getBulletImpact())
{
API.QueueAction(getBulletImpact);
Scripting.API.QueueAction(getBulletImpact);
}
}
else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default)

View File

@ -13,13 +13,19 @@ using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SHVDN;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client
{
internal static class Util
{
public static bool IsPrimaryDomain => (AppDomain.CurrentDomain?.GetData("Primary") as bool?) != false;
public static bool IsPrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") == null;
public static ScriptDomain GetPrimaryDomain()
{
if (IsPrimaryDomain) { return ScriptDomain.CurrentDomain; }
else { return AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain; }
}
public static SizeF ResolutionMaintainRatio
{
get
@ -141,7 +147,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, JsonConvert.SerializeObject(settings, Formatting.Indented));
return true;
}
catch (Exception ex)

View File

@ -9,7 +9,7 @@ namespace RageCoop.Core
/// <summary>
///
/// </summary>
public class Logger : IDisposable
public class Logger : MarshalByRefObject,IDisposable
{
/// <summary>
@ -34,6 +34,7 @@ namespace RageCoop.Core
private readonly Thread LoggerThread;
private bool Stopping = false;
private readonly bool FlushImmediately;
public event EventHandler<string> OnFlush;
internal Logger(bool flushImmediately = false, bool overwrite = true)
{
@ -195,7 +196,7 @@ namespace RageCoop.Core
private string Date()
{
return DateTime.Now.ToString();
return DateTime.Now.ToString("HH:mm:ss");
}
/// <summary>
///
@ -218,6 +219,7 @@ namespace RageCoop.Core
logWriter = new StreamWriter(LogPath, true, Encoding.UTF8);
logWriter.Write(Buffer);
logWriter.Close();
OnFlush?.Invoke(this,Buffer);
Buffer = "";
}
catch { }

View File

@ -1,8 +1,11 @@
namespace RageCoop.Core
using System;
namespace RageCoop.Core
{
/// <summary>
/// A json object representing a server's information as annouced to master server.
/// </summary>
[Serializable]
public class ServerInfo
{
#pragma warning disable 1591

View File

@ -144,7 +144,7 @@ namespace RageCoop.Core
void Deserialize(NetIncomingMessage m);
}
internal abstract class Packet : IPacket
internal abstract class Packet : MarshalByRefObject, IPacket
{
public abstract PacketType Type { get; }
public void Pack(NetOutgoingMessage m)

View File

@ -27,7 +27,7 @@ namespace RageCoop.Core.Scripting
/// <summary>
/// Struct to identify different event using hash
/// </summary>
public struct CustomEventHash
public class CustomEventHash : MarshalByRefObject
{
private static readonly MD5 Hasher = MD5.Create();
private static readonly Dictionary<int, string> Hashed = new Dictionary<int, string>();

View File

@ -6,7 +6,7 @@ namespace RageCoop.Core.Scripting
/// <summary>
///
/// </summary>
public class ResourceFile
public class ResourceFile : MarshalByRefObject
{
/// <summary>
/// Full name with relative path of this file

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.