From 8a46bd6b686b0660182d1dacbc2f71ead2fd2e7d Mon Sep 17 00:00:00 2001 From: Sardelka Date: Fri, 1 Jul 2022 12:22:31 +0800 Subject: [PATCH] Resource loading --- README.md | 1 + RageCoop.Client/Networking/DownloadManager.cs | 97 ++--- RageCoop.Client/Networking/Networking.cs | 9 +- RageCoop.Client/Networking/Receive.cs | 263 +++++++------ RageCoop.Client/Networking/Send.cs | 12 - RageCoop.Client/RageCoop.Client.csproj | 7 +- RageCoop.Client/Scripting/BaseScript.cs | 2 +- RageCoop.Client/Scripting/ClientScript.cs | 15 +- RageCoop.Client/Scripting/Resources.cs | 268 ++++++++++++- RageCoop.Client/Settings.cs | 5 + RageCoop.Client/Sync/EntityPool.cs | 22 +- RageCoop.Client/Sync/SyncEvents.cs | 18 +- RageCoop.Core/CoreUtils.cs | 12 + RageCoop.Core/Packets/CustomEvent.cs | 2 +- RageCoop.Core/Packets/FilePackets.cs | 62 ++- RageCoop.Core/Packets/Packets.cs | 19 +- RageCoop.Core/Packets/PedPackets.cs | 4 +- RageCoop.Core/Packets/PlayerPackets.cs | 12 +- RageCoop.Core/Packets/ProjectileSync.cs | 2 +- .../Packets/SyncEvents/BulletShot.cs | 2 +- .../Packets/SyncEvents/EnteredVehicle.cs | 2 +- .../Packets/SyncEvents/EnteringVehicle.cs | 2 +- .../Packets/SyncEvents/LeaveVehicle.cs | 2 +- .../Packets/SyncEvents/NozzleTransform.cs | 2 +- .../Packets/SyncEvents/OwnerChanged.cs | 2 +- RageCoop.Core/Packets/SyncEvents/PedKilled.cs | 2 +- RageCoop.Core/Packets/VehiclePackets.cs | 4 +- RageCoop.Core/Scripting/CustomEvents.cs | 2 +- RageCoop.Core/Scripting/Resource.cs | 27 -- RageCoop.Core/Scripting/ResourceFile.cs | 13 + RageCoop.Core/Scripting/ResourceLoader.cs | 256 ------------- RageCoop.Core/Scripting/Scriptable.cs | 25 -- RageCoop.Server/Client.cs | 8 - .../PublishProfiles/FolderProfile.pubxml | 17 - .../PublishProfiles/FolderProfile.pubxml.user | 9 - RageCoop.Server/RageCoop.Server.csproj | 1 + RageCoop.Server/Scripting/API.cs | 24 +- RageCoop.Server/Scripting/Resources.cs | 140 +++++-- RageCoop.Server/Scripting/ServerResource.cs | 150 ++++++++ RageCoop.Server/Scripting/ServerScript.cs | 12 +- RageCoop.Server/Server.cs | 362 +++++++++++------- libs/McMaster.NETCore.Plugins.dll | Bin 27648 -> 27136 bytes libs/ScriptHookVDotNet.dll | Bin 0 -> 147968 bytes 43 files changed, 1125 insertions(+), 771 deletions(-) delete mode 100644 RageCoop.Core/Scripting/Resource.cs create mode 100644 RageCoop.Core/Scripting/ResourceFile.cs delete mode 100644 RageCoop.Core/Scripting/ResourceLoader.cs delete mode 100644 RageCoop.Core/Scripting/Scriptable.cs delete mode 100644 RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml delete mode 100644 RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml.user create mode 100644 RageCoop.Server/Scripting/ServerResource.cs create mode 100644 libs/ScriptHookVDotNet.dll diff --git a/README.md b/README.md index f3455ba..4718fc8 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ _Old name: GTACOOP:R_ - [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1) - [ClearScript](https://github.com/microsoft/ClearScript) - [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) +- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins) # Features diff --git a/RageCoop.Client/Networking/DownloadManager.cs b/RageCoop.Client/Networking/DownloadManager.cs index 0893b9f..f048f55 100644 --- a/RageCoop.Client/Networking/DownloadManager.cs +++ b/RageCoop.Client/Networking/DownloadManager.cs @@ -1,15 +1,61 @@ using System.IO; using System.Linq; using System.Collections.Generic; +using RageCoop.Core; +using System; namespace RageCoop.Client { internal static class DownloadManager { + static DownloadManager() + { + + Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) => + { + var fr = new Packets.FileTransferRequest(); + fr.Unpack(data); + return new Packets.FileTransferResponse() + { + ID= fr.ID, + Response=AddFile(fr.ID,fr.Name,fr.FileLength) ? FileResponse.NeedToDownload : FileResponse.AlreadyExists + }; + }); + Networking.RequestHandlers.Add(PacketType.FileTransferComplete, (data) => + { + Packets.FileTransferComplete packet = new Packets.FileTransferComplete(); + packet.Unpack(data); + + Main.Logger.Debug($"Finalizing download:{packet.ID}"); + Complete(packet.ID); + + // Inform the server that the download is completed + return new Packets.FileTransferResponse() + { + ID= packet.ID, + Response=FileResponse.Completed + }; + }); + Networking.RequestHandlers.Add(PacketType.AllResourcesSent, (data) => + { + try + { + Main.Resources.Load(downloadFolder); + return new Packets.FileTransferResponse() { ID=0, Response=FileResponse.Loaded }; + } + catch(Exception ex) + { + + Main.Logger.Error("Error occurred when loading server resource:"); + Main.Logger.Error(ex); + return new Packets.FileTransferResponse() { ID=0, Response=FileResponse.LoadFailed }; + } + }); + } static string downloadFolder = $"RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}"; private static readonly Dictionary InProgressDownloads = new Dictionary(); - public static void AddFile(int id, string name, long length) + public static bool AddFile(int id, string name, long length) { Main.Logger.Debug($"Downloading file to {downloadFolder}\\{name} , id:{id}"); if (!Directory.Exists(downloadFolder)) @@ -20,22 +66,13 @@ namespace RageCoop.Client if (FileAlreadyExists(downloadFolder, name, length)) { Main.Logger.Debug($"File already exists! canceling download:{name}"); - Cancel(id); - if (name=="Resources.zip") - { - Main.Logger.Debug("Loading resources..."); - Main.Resources.Load(Path.Combine(downloadFolder)); - } - return; + return false; } if (!name.EndsWith(".zip")) { - Cancel(id); - - GTA.UI.Notification.Show($"The download of a file from the server was blocked! [{name}]", true); - Main.Logger.Error($"The download of a file from the server was blocked! [{name}]"); - return; + Main.Logger.Error($"File download blocked! [{name}]"); + return false; } lock (InProgressDownloads) { @@ -47,6 +84,7 @@ namespace RageCoop.Client Stream = new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite) }); } + return true; } /// @@ -87,47 +125,20 @@ namespace RageCoop.Client else { Main.Logger.Trace($"Received unhandled file chunk:{id}"); - return; } } } - public static void Cancel(int id) - { - Main.Logger.Debug($"Canceling download:{id}"); - - // Tell the server to stop sending chunks - Networking.SendDownloadFinish(id); - - DownloadFile file; - lock (InProgressDownloads) - { - if (InProgressDownloads.TryGetValue(id, out file)) - { - InProgressDownloads.Remove(id); - file.Dispose(); - } - } - } public static void Complete(int id) { DownloadFile f; if (InProgressDownloads.TryGetValue(id, out f)) { - lock (InProgressDownloads) - { - InProgressDownloads.Remove(id); - f.Dispose(); - Main.Logger.Info($"Download finished:{f.FileName}"); - if (f.FileName=="Resources.zip") - { - Main.Logger.Debug("Loading resources..."); - Main.Resources.Load(Path.Combine(downloadFolder)); - } - Networking.SendDownloadFinish(id); - } + InProgressDownloads.Remove(id); + f.Dispose(); + Main.Logger.Info($"Download finished:{f.FileName}"); } else { diff --git a/RageCoop.Client/Networking/Networking.cs b/RageCoop.Client/Networking/Networking.cs index 64a576f..1d21829 100644 --- a/RageCoop.Client/Networking/Networking.cs +++ b/RageCoop.Client/Networking/Networking.cs @@ -78,7 +78,8 @@ namespace RageCoop.Client { Client = new NetClient(config); Client.Start(); - + Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); }); + DownloadManager.Cleanup(); Security.Regen(); GetServerPublicKey(address); @@ -90,12 +91,12 @@ namespace RageCoop.Client Username =username, ModVersion = Main.CurrentVersion, PassHashEncrypted=Security.Encrypt(password.GetHash()) - }; + }; + Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted,out handshake.AesIVCrypted); handshake.Pack(outgoingMessage); Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage); - Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); }); - + }); } diff --git a/RageCoop.Client/Networking/Receive.cs b/RageCoop.Client/Networking/Receive.cs index 49bca86..643ea2a 100644 --- a/RageCoop.Client/Networking/Receive.cs +++ b/RageCoop.Client/Networking/Receive.cs @@ -16,6 +16,9 @@ namespace RageCoop.Client internal static partial class Networking { private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false); + + private static Dictionary> PendingResponses = new Dictionary>(); + internal static Dictionary> RequestHandlers = new Dictionary>(); public static void ProcessMessage(NetIncomingMessage message) { if(message == null) { return; } @@ -77,138 +80,47 @@ namespace RageCoop.Client { if (message.LengthBytes==0) { break; } - var packetType = PacketTypes.Unknown; + var packetType = PacketType.Unknown; try { - packetType = (PacketTypes)message.ReadByte(); - int len = message.ReadInt32(); - byte[] data = message.ReadBytes(len); + + // Get packet type + packetType = (PacketType)message.ReadByte(); switch (packetType) { - case PacketTypes.PlayerConnect: + case PacketType.Response: { - - Packets.PlayerConnect packet = new Packets.PlayerConnect(); - packet.Unpack(data); - - Main.QueueAction(() => PlayerConnect(packet)); - } - break; - case PacketTypes.PlayerDisconnect: - { - - Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect(); - packet.Unpack(data); - Main.QueueAction(() => PlayerDisconnect(packet)); - - } - break; - case PacketTypes.PlayerInfoUpdate: - { - var packet = new Packets.PlayerInfoUpdate(); - packet.Unpack(data); - PlayerList.UpdatePlayer(packet); + int id = message.ReadInt32(); + if (PendingResponses.TryGetValue(id, out var callback)) + { + callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32())); + PendingResponses.Remove(id); + } break; } - #region ENTITY SYNC - case PacketTypes.VehicleSync: + case PacketType.Request: { - - Packets.VehicleSync packet = new Packets.VehicleSync(); - packet.Unpack(data); - VehicleSync(packet); - - } - break; - case PacketTypes.PedSync: - { - - Packets.PedSync packet = new Packets.PedSync(); - packet.Unpack(data); - PedSync(packet); - - } - break; - case PacketTypes.VehicleStateSync: - { - - Packets.VehicleStateSync packet = new Packets.VehicleStateSync(); - packet.Unpack(data); - VehicleStateSync(packet); - - } - break; - case PacketTypes.PedStateSync: - { - - - Packets.PedStateSync packet = new Packets.PedStateSync(); - packet.Unpack(data); - PedStateSync(packet); - - } - break; - case PacketTypes.ProjectileSync: - { - Packets.ProjectileSync packet = new Packets.ProjectileSync(); - packet.Unpack(data); - ProjectileSync(packet); + int id = message.ReadInt32(); + var realType = (PacketType)message.ReadByte(); + int len = message.ReadInt32(); + Main.Logger.Debug($"{id},{realType},{len}"); + if (RequestHandlers.TryGetValue(realType, out var handler)) + { + var response = Client.CreateMessage(); + response.Write((byte)PacketType.Response); + response.Write(id); + handler(message.ReadBytes(len)).Pack(response); + Client.SendMessage(response, NetDeliveryMethod.ReliableOrdered); + } break; } - #endregion - case PacketTypes.ChatMessage: - { - - Packets.ChatMessage packet = new Packets.ChatMessage(); - packet.Unpack(data); - - Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; }); - - - } - break; - case PacketTypes.CustomEvent: - { - Packets.CustomEvent packet = new Packets.CustomEvent(); - packet.Unpack(data); - Scripting.API.Events.InvokeCustomEventReceived(packet); - } - break; - case PacketTypes.FileTransferChunk: - { - Packets.FileTransferChunk packet = new Packets.FileTransferChunk(); - packet.Unpack(data); - - DownloadManager.Write(packet.ID, packet.FileChunk); - - } - break; - case PacketTypes.FileTransferRequest: - { - Packets.FileTransferRequest packet = new Packets.FileTransferRequest(); - packet.Unpack(data); - - DownloadManager.AddFile(packet.ID, packet.Name, packet.FileLength); - - } - break; - case PacketTypes.FileTransferComplete: - { - Packets.FileTransferComplete packet = new Packets.FileTransferComplete(); - packet.Unpack(data); - - Main.Logger.Debug($"Finalizing download:{packet.ID}"); - DownloadManager.Complete(packet.ID); - - } - break; default: - if (packetType.IsSyncEvent()) { - // Dispatch to main thread - Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; }); + byte[] data = message.ReadBytes(message.ReadInt32()); + + HandlePacket(packetType, data); + break; } - break; } } catch (Exception ex) @@ -228,10 +140,10 @@ namespace RageCoop.Client break; case NetIncomingMessageType.UnconnectedData: { - var packetType = (PacketTypes)message.ReadByte(); + var packetType = (PacketType)message.ReadByte(); int len = message.ReadInt32(); byte[] data = message.ReadBytes(len); - if (packetType==PacketTypes.PublicKeyResponse) + if (packetType==PacketType.PublicKeyResponse) { var packet=new Packets.PublicKeyResponse(); packet.Unpack(data); @@ -252,7 +164,116 @@ namespace RageCoop.Client Client.Recycle(message); } + private static void HandlePacket(PacketType packetType, byte[] data) + { + switch (packetType) + { + case PacketType.PlayerConnect: + { + + Packets.PlayerConnect packet = new Packets.PlayerConnect(); + packet.Unpack(data); + + Main.QueueAction(() => PlayerConnect(packet)); + } + break; + case PacketType.PlayerDisconnect: + { + + Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect(); + packet.Unpack(data); + Main.QueueAction(() => PlayerDisconnect(packet)); + + } + break; + case PacketType.PlayerInfoUpdate: + { + var packet = new Packets.PlayerInfoUpdate(); + packet.Unpack(data); + PlayerList.UpdatePlayer(packet); + break; + } + #region ENTITY SYNC + case PacketType.VehicleSync: + { + + Packets.VehicleSync packet = new Packets.VehicleSync(); + packet.Unpack(data); + VehicleSync(packet); + + } + break; + case PacketType.PedSync: + { + + Packets.PedSync packet = new Packets.PedSync(); + packet.Unpack(data); + PedSync(packet); + + } + break; + case PacketType.VehicleStateSync: + { + + Packets.VehicleStateSync packet = new Packets.VehicleStateSync(); + packet.Unpack(data); + VehicleStateSync(packet); + + } + break; + case PacketType.PedStateSync: + { + + + Packets.PedStateSync packet = new Packets.PedStateSync(); + packet.Unpack(data); + PedStateSync(packet); + + } + break; + case PacketType.ProjectileSync: + { + Packets.ProjectileSync packet = new Packets.ProjectileSync(); + packet.Unpack(data); + ProjectileSync(packet); + break; + } + #endregion + case PacketType.ChatMessage: + { + + Packets.ChatMessage packet = new Packets.ChatMessage(); + packet.Unpack(data); + + Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; }); + + + } + break; + case PacketType.CustomEvent: + { + Packets.CustomEvent packet = new Packets.CustomEvent(); + packet.Unpack(data); + Scripting.API.Events.InvokeCustomEventReceived(packet); + } + break; + case PacketType.FileTransferChunk: + { + Packets.FileTransferChunk packet = new Packets.FileTransferChunk(); + packet.Unpack(data); + DownloadManager.Write(packet.ID, packet.FileChunk); + } + break; + default: + if (packetType.IsSyncEvent()) + { + // Dispatch to main thread + Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; }); + } + break; + } + } private static void PedSync(Packets.PedSync packet) { diff --git a/RageCoop.Client/Networking/Send.cs b/RageCoop.Client/Networking/Send.cs index ae4feab..24b18e2 100644 --- a/RageCoop.Client/Networking/Send.cs +++ b/RageCoop.Client/Networking/Send.cs @@ -158,18 +158,6 @@ namespace RageCoop.Client Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat); Client.FlushSendQueue(); -#if DEBUG -#endif - } - public static void SendDownloadFinish(int id) - { - NetOutgoingMessage outgoingMessage = Client.CreateMessage(); - - new Packets.FileTransferComplete() { ID = id }.Pack(outgoingMessage); - - Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.File); - Client.FlushSendQueue(); - #if DEBUG #endif } diff --git a/RageCoop.Client/RageCoop.Client.csproj b/RageCoop.Client/RageCoop.Client.csproj index 4287a85..f3fcbba 100644 --- a/RageCoop.Client/RageCoop.Client.csproj +++ b/RageCoop.Client/RageCoop.Client.csproj @@ -1,8 +1,7 @@  - net48 - true + True True false false @@ -22,6 +21,8 @@ MIT True SHVDN3 + net48 + true @@ -44,7 +45,7 @@ ..\libs\Newtonsoft.Json.dll - ..\..\RageCoop.SHVDN\bin\Release\ScriptHookVDotNet.dll + ..\libs\ScriptHookVDotNet.dll ..\libs\ScriptHookVDotNet3.dll diff --git a/RageCoop.Client/Scripting/BaseScript.cs b/RageCoop.Client/Scripting/BaseScript.cs index a35e1a6..ed4120b 100644 --- a/RageCoop.Client/Scripting/BaseScript.cs +++ b/RageCoop.Client/Scripting/BaseScript.cs @@ -16,7 +16,7 @@ namespace RageCoop.Client.Scripting { API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn); API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall); - API.RegisterCustomEventHandler(CustomEvents.CleanUpWorld, (s) => Main.QueueAction(() => Main.CleanUpWorld())); + } public override void OnStop() diff --git a/RageCoop.Client/Scripting/ClientScript.cs b/RageCoop.Client/Scripting/ClientScript.cs index 4d75929..4db5247 100644 --- a/RageCoop.Client/Scripting/ClientScript.cs +++ b/RageCoop.Client/Scripting/ClientScript.cs @@ -1,19 +1,26 @@ -namespace RageCoop.Client.Scripting +using RageCoop.Core.Scripting; + +namespace RageCoop.Client.Scripting { /// /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use . to initiate your script. /// - public abstract class ClientScript:Core.Scripting.Scriptable + public abstract class ClientScript { /// /// This method would be called from main thread shortly after all scripts have been loaded. /// - public override abstract void OnStart(); + public abstract void OnStart(); /// /// This method would be called from main thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method. /// - public override abstract void OnStop(); + public abstract void OnStop(); + + /// + /// Get the instance where this script is loaded from. + /// + public ResourceFile CurrentFile { get; internal set; } } } diff --git a/RageCoop.Client/Scripting/Resources.cs b/RageCoop.Client/Scripting/Resources.cs index a9e33b4..ab4bcd5 100644 --- a/RageCoop.Client/Scripting/Resources.cs +++ b/RageCoop.Client/Scripting/Resources.cs @@ -1,13 +1,34 @@ using System.IO; using RageCoop.Core.Scripting; +using RageCoop.Core; using ICSharpCode.SharpZipLib.Zip; using System; +using System.Reflection; +using System.Linq; +using System.Collections.Generic; namespace RageCoop.Client.Scripting { - internal class Resources:ResourceLoader + + public class ClientResource { - public Resources() : base("RageCoop.Client.Scripting.ClientScript", Main.Logger) { } + /// + /// Name of the resource + /// + public string Name { get; internal set; } + /// + /// A resource-specific folder that can be used to store your files. + /// + public string DataFolder { get; internal set; } + public List Scripts { get; internal set; } = new List(); + public Dictionary Files { get; internal set; } = new Dictionary(); + } + internal class Resources + { + public Resources(){ + BaseScriptType = "RageCoop.Client.Scripting.ClientScript"; + Logger = Main.Logger; + } private void StartAll() { lock (LoadedResources) @@ -65,11 +86,10 @@ namespace RageCoop.Client.Scripting } } Directory.CreateDirectory(path); - foreach (var resource in Directory.GetDirectories(path)) + foreach (var zipPath in Directory.GetFiles(path,"*.zip",SearchOption.TopDirectoryOnly)) { - if (Path.GetFileName(resource).ToLower()!="data") { continue; } - Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); - LoadResource(resource,Path.Combine(path,"data")); + Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zipPath)}"); + LoadResource(new ZipFile(zipPath),Path.Combine(path,"data")); } StartAll(); } @@ -78,6 +98,242 @@ namespace RageCoop.Client.Scripting StopAll(); LoadedResources.Clear(); } + private List ToIgnore = new List + { + "RageCoop.Client.dll", + "RageCoop.Core.dll", + "RageCoop.Server.dll", + "ScriptHookVDotNet3.dll" + }; + private List LoadedResources = new List(); + private string BaseScriptType; + public Logger Logger { get; set; } + + /// + /// Load a resource from a zip + /// + /// + private void LoadResource(ZipFile file, string dataFolderRoot) + { + var r = new ClientResource() + { + Scripts = new List(), + Name=Path.GetFileNameWithoutExtension(file.Name), + DataFolder=Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(file.Name)) + }; + Directory.CreateDirectory(r.DataFolder); + + foreach (ZipEntry entry in file) + { + ResourceFile rFile; + r.Files.Add(entry.Name, rFile=new ResourceFile() + { + Name=entry.Name, + IsDirectory=entry.IsDirectory, + }); + if (!entry.IsDirectory) + { + rFile.GetStream=() => { return file.GetInputStream(entry); }; + if (entry.Name.EndsWith(".dll")) + { + var tmp = Path.GetTempFileName(); + var f = File.OpenWrite(tmp); + rFile.GetStream().CopyTo(f); + f.Close(); + LoadScriptsFromAssembly(rFile, tmp, r, false); + } + } + } + LoadedResources.Add(r); + } + /// + /// Loads scripts from the specified assembly file. + /// + /// The path to the assembly file to load. + /// on success, otherwise + private bool LoadScriptsFromAssembly(ResourceFile file, string path, ClientResource resource, bool shadowCopy = true) + { + lock (LoadedResources) + { + if (!IsManagedAssembly(path)) { return false; } + if (ToIgnore.Contains(file.Name)) { try { File.Delete(path); } catch { }; return false; } + + Logger?.Debug($"Loading assembly {file.Name} ..."); + + Assembly assembly; + + try + { + if (shadowCopy) + { + var temp = Path.GetTempFileName(); + File.Copy(path, temp, true); + assembly = Assembly.LoadFrom(temp); + } + else + { + assembly = Assembly.LoadFrom(path); + } + } + catch (Exception ex) + { + Logger?.Error("Unable to load "+file.Name); + Logger?.Error(ex); + return false; + } + + return LoadScriptsFromAssembly(file, assembly, path, resource); + } + } + /// + /// Loads scripts from the specified assembly object. + /// + /// The path to the file associated with this assembly. + /// The assembly to load. + /// on success, otherwise + private bool LoadScriptsFromAssembly(ResourceFile rfile, Assembly assembly, string filename, ClientResource toload) + { + int count = 0; + + try + { + // Find all script types in the assembly + foreach (var type in assembly.GetTypes().Where(x => IsSubclassOf(x, BaseScriptType))) + { + ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes); + if (constructor != null && constructor.IsPublic) + { + try + { + // Invoke script constructor + var script = constructor.Invoke(null) as ClientScript; + // script.CurrentResource = toload; + script.CurrentFile=rfile; + toload.Scripts.Add(script); + count++; + } + catch (Exception ex) + { + Logger?.Error($"Error occurred when loading script: {type.FullName}."); + Logger?.Error(ex); + } + } + else + { + Logger?.Error($"Script {type.FullName} has an invalid contructor."); + } + } + } + catch (ReflectionTypeLoadException ex) + { + Logger?.Error($"Failed to load assembly {rfile.Name}: "); + Logger?.Error(ex); + foreach (var e in ex.LoaderExceptions) + { + Logger?.Error(e); + } + return false; + } + + Logger?.Info($"Loaded {count} script(s) in {rfile.Name}"); + return count != 0; + } + private bool IsManagedAssembly(string filename) + { + try + { + using (Stream file = new FileStream(filename, FileMode.Open, FileAccess.Read)) + { + if (file.Length < 64) + return false; + + using (BinaryReader bin = new BinaryReader(file)) + { + // PE header starts at offset 0x3C (60). Its a 4 byte header. + file.Position = 0x3C; + uint offset = bin.ReadUInt32(); + if (offset == 0) + offset = 0x80; + + // Ensure there is at least enough room for the following structures: + // 24 byte PE Signature & Header + // 28 byte Standard Fields (24 bytes for PE32+) + // 68 byte NT Fields (88 bytes for PE32+) + // >= 128 byte Data Dictionary Table + if (offset > file.Length - 256) + return false; + + // Check the PE signature. Should equal 'PE\0\0'. + file.Position = offset; + if (bin.ReadUInt32() != 0x00004550) + return false; + + // Read PE magic number from Standard Fields to determine format. + file.Position += 20; + var peFormat = bin.ReadUInt16(); + if (peFormat != 0x10b /* PE32 */ && peFormat != 0x20b /* PE32Plus */) + return false; + + // Read the 15th Data Dictionary RVA field which contains the CLI header RVA. + // When this is non-zero then the file contains CLI data otherwise not. + file.Position = offset + (peFormat == 0x10b ? 232 : 248); + return bin.ReadUInt32() != 0; + } + } + } + catch + { + // This is likely not a valid assembly if any IO exceptions occur during reading + return false; + } + } + private bool IsSubclassOf(Type type, string baseTypeName) + { + for (Type t = type.BaseType; t != null; t = t.BaseType) + if (t.FullName == baseTypeName) + return true; + return false; + } + /* + /// + /// Load a resource from a directory. + /// + /// Path of the directory. + private void LoadResource(string path, string dataFolderRoot) + { + var r = new ClientResource() + { + Scripts = new List(), + Name=Path.GetFileName(path), + DataFolder=Path.Combine(dataFolderRoot, Path.GetFileName(path)) + }; + Directory.CreateDirectory(r.DataFolder); + foreach (var dir in Directory.GetDirectories(path, "*", SearchOption.AllDirectories)) + { + r.Files.Add(dir, new ResourceFile() + { + IsDirectory=true, + Name=dir.Substring(path.Length+1) + }); + } + foreach (var file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) + { + var relativeName = file.Substring(path.Length+1); + var rfile = new ResourceFile() + { + GetStream=() => { return new FileStream(file, FileMode.Open, FileAccess.Read); }, + IsDirectory=false, + Name=relativeName + }; + if (file.EndsWith(".dll")) + { + LoadScriptsFromAssembly(rfile, file, r); + } + r.Files.Add(relativeName, rfile); + } + LoadedResources.Add(r); + } + */ } } diff --git a/RageCoop.Client/Settings.cs b/RageCoop.Client/Settings.cs index c743cb9..621ead4 100644 --- a/RageCoop.Client/Settings.cs +++ b/RageCoop.Client/Settings.cs @@ -56,5 +56,10 @@ namespace RageCoop.Client /// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended). /// public int WorldVehicleSoftLimit { get; set; } = 35; + + /// + /// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended). + /// + public int WorldPedSoftLimit { get; set; } = 50; } } diff --git a/RageCoop.Client/Sync/EntityPool.cs b/RageCoop.Client/Sync/EntityPool.cs index 9c9b5f7..1908271 100644 --- a/RageCoop.Client/Sync/EntityPool.cs +++ b/RageCoop.Client/Sync/EntityPool.cs @@ -14,6 +14,7 @@ namespace RageCoop.Client { internal class EntityPool { + private static bool _trafficSpawning=true; public static object PedsLock = new object(); private static Dictionary ID_Peds = new Dictionary(); public static int CharactersCount { get { return ID_Peds.Count; } } @@ -150,7 +151,7 @@ namespace RageCoop.Client { Handle_Peds.Remove(p.Handle); } - Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}"); + // Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}"); p.AttachedBlip?.Delete(); p.Kill(); p.MarkAsNoLongerNeeded(); @@ -208,7 +209,7 @@ namespace RageCoop.Client { Handle_Vehicles.Remove(veh.Handle); } - Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}"); + // Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}"); veh.AttachedBlip?.Delete(); veh.MarkAsNoLongerNeeded(); veh.Delete(); @@ -298,16 +299,21 @@ namespace RageCoop.Client vehStatesPerFrame=allVehicles.Length*5/(int)Game.FPS+1; pedStatesPerFrame=allPeds.Length*5/(int)Game.FPS+1; - if (Main.Settings.WorldVehicleSoftLimit>-1) + if (Main.Ticked%50==0) { - if (Main.Ticked%100==0) { if (allVehicles.Length>Main.Settings.WorldVehicleSoftLimit) { SetBudget(0); } else { SetBudget(1); } } + bool flag1 = allVehicles.Length>Main.Settings.WorldVehicleSoftLimit && Main.Settings.WorldVehicleSoftLimit>-1; + bool flag2 = allPeds.Length>Main.Settings.WorldPedSoftLimit && Main.Settings.WorldPedSoftLimit>-1; + if ((flag1||flag2) && _trafficSpawning) + { SetBudget(0); _trafficSpawning=false; } + else if(!_trafficSpawning) + { SetBudget(1); _trafficSpawning=true; } } #if BENCHMARK Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks; -#endif - +#endif + lock (ProjectilesLock) { @@ -369,7 +375,7 @@ namespace RageCoop.Client SyncedPed c = EntityPool.GetPedByHandle(p.Handle); if (c==null && (p!=Game.Player.Character)) { - Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}"); + // Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}"); c=new SyncedPed(p); EntityPool.Add(c); @@ -448,7 +454,7 @@ namespace RageCoop.Client { if (!Handle_Vehicles.ContainsKey(veh.Handle)) { - Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}"); + // Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}"); EntityPool.Add(new SyncedVehicle(veh)); diff --git a/RageCoop.Client/Sync/SyncEvents.cs b/RageCoop.Client/Sync/SyncEvents.cs index ea46032..e2d72b3 100644 --- a/RageCoop.Client/Sync/SyncEvents.cs +++ b/RageCoop.Client/Sync/SyncEvents.cs @@ -58,7 +58,7 @@ namespace RageCoop.Client { public static void TriggerBulletShot(uint hash,SyncedPed owner,Vector3 impactPosition) { - Main.Logger.Trace($"bullet shot:{(WeaponHash)hash}"); + // Main.Logger.Trace($"bullet shot:{(WeaponHash)hash}"); var start = owner.MainPed.GetMuzzlePosition(); @@ -211,18 +211,18 @@ namespace RageCoop.Client { } } } - public static void HandleEvent(PacketTypes type,byte[] data) + public static void HandleEvent(PacketType type,byte[] data) { switch (type) { - case PacketTypes.BulletShot: + case PacketType.BulletShot: { Packets.BulletShot p = new Packets.BulletShot(); p.Unpack(data); HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID); break; } - case PacketTypes.EnteringVehicle: + case PacketType.EnteringVehicle: { Packets.EnteringVehicle p = new Packets.EnteringVehicle(); p.Unpack(data); @@ -231,35 +231,35 @@ namespace RageCoop.Client { } break; - case PacketTypes.LeaveVehicle: + case PacketType.LeaveVehicle: { Packets.LeaveVehicle packet = new Packets.LeaveVehicle(); packet.Unpack(data); HandleLeaveVehicle(packet); } break; - case PacketTypes.OwnerChanged: + case PacketType.OwnerChanged: { Packets.OwnerChanged packet = new Packets.OwnerChanged(); packet.Unpack(data); HandleOwnerChanged(packet); } break; - case PacketTypes.PedKilled: + case PacketType.PedKilled: { var packet = new Packets.PedKilled(); packet.Unpack(data); HandlePedKilled(packet); } break; - case PacketTypes.EnteredVehicle: + case PacketType.EnteredVehicle: { var packet = new Packets.EnteredVehicle(); packet.Unpack(data); HandleEnteredVehicle(packet.PedID,packet.VehicleID,(VehicleSeat)packet.VehicleSeat); break; } - case PacketTypes.NozzleTransform: + case PacketType.NozzleTransform: { var packet = new Packets.NozzleTransform(); packet.Unpack(data); diff --git a/RageCoop.Core/CoreUtils.cs b/RageCoop.Core/CoreUtils.cs index bceafd3..551f6d4 100644 --- a/RageCoop.Core/CoreUtils.cs +++ b/RageCoop.Core/CoreUtils.cs @@ -7,6 +7,10 @@ using GTA.Math; using System.Security.Cryptography; using System.Net; using System.IO; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("RageCoop.Server")] +[assembly: InternalsVisibleTo("RageCoop.Client")] namespace RageCoop.Core { public class CoreUtils @@ -231,5 +235,13 @@ namespace RageCoop.Core } return output; } + + public static bool IsSubclassOf(this Type type, string baseTypeName) + { + for (Type t = type.BaseType; t != null; t = t.BaseType) + if (t.FullName == baseTypeName) + return true; + return false; + } } } diff --git a/RageCoop.Core/Packets/CustomEvent.cs b/RageCoop.Core/Packets/CustomEvent.cs index 8e09953..60fcab0 100644 --- a/RageCoop.Core/Packets/CustomEvent.cs +++ b/RageCoop.Core/Packets/CustomEvent.cs @@ -15,7 +15,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { Args= Args ?? new List(0); - message.Write((byte)PacketTypes.CustomEvent); + message.Write((byte)PacketType.CustomEvent); List result = new List(); result.AddInt(Hash); diff --git a/RageCoop.Core/Packets/FilePackets.cs b/RageCoop.Core/Packets/FilePackets.cs index 1035408..8266855 100644 --- a/RageCoop.Core/Packets/FilePackets.cs +++ b/RageCoop.Core/Packets/FilePackets.cs @@ -6,10 +6,13 @@ using Lidgren.Network; namespace RageCoop.Core { - public enum FileType:byte + public enum FileResponse:byte { - Resource=0, - Custom=1, + NeedToDownload=0, + AlreadyExists=1, + Completed=2, + Loaded=3, + LoadFailed=4, } public partial class Packets { @@ -24,7 +27,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.FileTransferRequest); + message.Write((byte)PacketType.FileTransferRequest); List byteArray = new List(); @@ -60,6 +63,36 @@ namespace RageCoop.Core } } + public class FileTransferResponse : Packet + { + public int ID { get; set; } + public FileResponse Response { get; set; } + public override void Pack(NetOutgoingMessage message) + { + message.Write((byte)PacketType.FileTransferResponse); + + List byteArray = new List(); + + // The ID from the download + byteArray.AddInt(ID); + + byteArray.Add((byte)Response); + + byte[] result = byteArray.ToArray(); + + message.Write(result.Length); + message.Write(result); + } + + public override void Unpack(byte[] array) + { + BitReader reader = new BitReader(array); + + ID = reader.ReadInt(); + Response = (FileResponse)reader.ReadByte(); + } + } + public class FileTransferChunk : Packet { public int ID { get; set; } @@ -69,7 +102,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.FileTransferChunk); + message.Write((byte)PacketType.FileTransferChunk); List byteArray = new List(); @@ -106,7 +139,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.FileTransferComplete); + message.Write((byte)PacketType.FileTransferComplete); List byteArray = new List(); @@ -129,5 +162,22 @@ namespace RageCoop.Core #endregion } } + public class AllResourcesSent : Packet + { + + public override void Pack(NetOutgoingMessage message) + { + #region PacketToNetOutGoingMessage + message.Write((byte)PacketType.AllResourcesSent); + message.Write(0); + #endregion + } + + public override void Unpack(byte[] array) + { + #region NetIncomingMessageToPacket + #endregion + } + } } } diff --git a/RageCoop.Core/Packets/Packets.cs b/RageCoop.Core/Packets/Packets.cs index cc56706..45455b5 100644 --- a/RageCoop.Core/Packets/Packets.cs +++ b/RageCoop.Core/Packets/Packets.cs @@ -7,7 +7,7 @@ using GTA.Math; namespace RageCoop.Core { - public enum PacketTypes:byte + public enum PacketType:byte { Handshake=0, PlayerConnect=1, @@ -15,6 +15,8 @@ namespace RageCoop.Core PlayerInfoUpdate=3, PublicKeyRequest=4, PublicKeyResponse=5, + Request=6, + Response=7, ChatMessage=10, // NativeCall=11, @@ -22,11 +24,13 @@ namespace RageCoop.Core // Mod=13, // CleanUpWorld=14, - FileTransferChunk=15, - FileTransferRequest=16, - FileTransferComplete=17, + FileTransferChunk=11, + FileTransferRequest=12, + FileTransferResponse = 13, + FileTransferComplete =14, + AllResourcesSent=15, - CustomEvent = 18, + CustomEvent = 16, #region Sync #region INTERVAL @@ -55,7 +59,7 @@ namespace RageCoop.Core } public static class PacketExtensions { - public static bool IsSyncEvent(this PacketTypes p) + public static bool IsSyncEvent(this PacketType p) { return (30<=(byte)p)&&((byte)p<=40); } @@ -69,6 +73,7 @@ namespace RageCoop.Core Mod = 7, File = 8, Event = 9, + RequestResponse=10, VehicleSync=20, PedSync=21, ProjectileSync = 22, @@ -153,7 +158,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.ChatMessage); + message.Write((byte)PacketType.ChatMessage); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/PedPackets.cs b/RageCoop.Core/Packets/PedPackets.cs index 4a5659a..17bfa9f 100644 --- a/RageCoop.Core/Packets/PedPackets.cs +++ b/RageCoop.Core/Packets/PedPackets.cs @@ -30,7 +30,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PedStateSync); + message.Write((byte)PacketType.PedStateSync); List byteArray = new List(); @@ -129,7 +129,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PedSync); + message.Write((byte)PacketType.PedSync); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/PlayerPackets.cs b/RageCoop.Core/Packets/PlayerPackets.cs index 0401345..aa812e1 100644 --- a/RageCoop.Core/Packets/PlayerPackets.cs +++ b/RageCoop.Core/Packets/PlayerPackets.cs @@ -34,7 +34,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.Handshake); + message.Write((byte)PacketType.Handshake); List byteArray = new List(); @@ -101,7 +101,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PlayerConnect); + message.Write((byte)PacketType.PlayerConnect); List byteArray = new List(); @@ -146,7 +146,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PlayerDisconnect); + message.Write((byte)PacketType.PlayerDisconnect); List byteArray = new List(); @@ -181,7 +181,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PlayerInfoUpdate); + message.Write((byte)PacketType.PlayerInfoUpdate); List byteArray = new List(); @@ -241,7 +241,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PublicKeyResponse); + message.Write((byte)PacketType.PublicKeyResponse); List byteArray = new List(); @@ -272,7 +272,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PublicKeyRequest); + message.Write((byte)PacketType.PublicKeyRequest); #endregion } public override void Unpack(byte[] array) diff --git a/RageCoop.Core/Packets/ProjectileSync.cs b/RageCoop.Core/Packets/ProjectileSync.cs index 4b2011b..e2d44b5 100644 --- a/RageCoop.Core/Packets/ProjectileSync.cs +++ b/RageCoop.Core/Packets/ProjectileSync.cs @@ -29,7 +29,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.ProjectileSync); + message.Write((byte)PacketType.ProjectileSync); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/BulletShot.cs b/RageCoop.Core/Packets/SyncEvents/BulletShot.cs index bc3f745..f70a490 100644 --- a/RageCoop.Core/Packets/SyncEvents/BulletShot.cs +++ b/RageCoop.Core/Packets/SyncEvents/BulletShot.cs @@ -21,7 +21,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.BulletShot); + message.Write((byte)PacketType.BulletShot); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/EnteredVehicle.cs b/RageCoop.Core/Packets/SyncEvents/EnteredVehicle.cs index c38ae00..36e7541 100644 --- a/RageCoop.Core/Packets/SyncEvents/EnteredVehicle.cs +++ b/RageCoop.Core/Packets/SyncEvents/EnteredVehicle.cs @@ -19,7 +19,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.EnteredVehicle); + message.Write((byte)PacketType.EnteredVehicle); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/EnteringVehicle.cs b/RageCoop.Core/Packets/SyncEvents/EnteringVehicle.cs index 5654c3d..2627507 100644 --- a/RageCoop.Core/Packets/SyncEvents/EnteringVehicle.cs +++ b/RageCoop.Core/Packets/SyncEvents/EnteringVehicle.cs @@ -19,7 +19,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.EnteringVehicle); + message.Write((byte)PacketType.EnteringVehicle); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/LeaveVehicle.cs b/RageCoop.Core/Packets/SyncEvents/LeaveVehicle.cs index 141e793..2add210 100644 --- a/RageCoop.Core/Packets/SyncEvents/LeaveVehicle.cs +++ b/RageCoop.Core/Packets/SyncEvents/LeaveVehicle.cs @@ -17,7 +17,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.LeaveVehicle); + message.Write((byte)PacketType.LeaveVehicle); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/NozzleTransform.cs b/RageCoop.Core/Packets/SyncEvents/NozzleTransform.cs index cb0be6a..dc1ac90 100644 --- a/RageCoop.Core/Packets/SyncEvents/NozzleTransform.cs +++ b/RageCoop.Core/Packets/SyncEvents/NozzleTransform.cs @@ -17,7 +17,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.NozzleTransform); + message.Write((byte)PacketType.NozzleTransform); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/OwnerChanged.cs b/RageCoop.Core/Packets/SyncEvents/OwnerChanged.cs index 0dd47d9..a92052c 100644 --- a/RageCoop.Core/Packets/SyncEvents/OwnerChanged.cs +++ b/RageCoop.Core/Packets/SyncEvents/OwnerChanged.cs @@ -18,7 +18,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.OwnerChanged); + message.Write((byte)PacketType.OwnerChanged); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/SyncEvents/PedKilled.cs b/RageCoop.Core/Packets/SyncEvents/PedKilled.cs index 75776a5..ed6d587 100644 --- a/RageCoop.Core/Packets/SyncEvents/PedKilled.cs +++ b/RageCoop.Core/Packets/SyncEvents/PedKilled.cs @@ -16,7 +16,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.PedKilled); + message.Write((byte)PacketType.PedKilled); List byteArray = new List(); diff --git a/RageCoop.Core/Packets/VehiclePackets.cs b/RageCoop.Core/Packets/VehiclePackets.cs index f296ab4..966b5c6 100644 --- a/RageCoop.Core/Packets/VehiclePackets.cs +++ b/RageCoop.Core/Packets/VehiclePackets.cs @@ -48,7 +48,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.VehicleStateSync); + message.Write((byte)PacketType.VehicleStateSync); List byteArray = new List(); @@ -256,7 +256,7 @@ namespace RageCoop.Core public override void Pack(NetOutgoingMessage message) { #region PacketToNetOutGoingMessage - message.Write((byte)PacketTypes.VehicleSync); + message.Write((byte)PacketType.VehicleSync); List byteArray = new List(); diff --git a/RageCoop.Core/Scripting/CustomEvents.cs b/RageCoop.Core/Scripting/CustomEvents.cs index 68948ef..3376828 100644 --- a/RageCoop.Core/Scripting/CustomEvents.cs +++ b/RageCoop.Core/Scripting/CustomEvents.cs @@ -17,7 +17,7 @@ namespace RageCoop.Core.Scripting public static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn"); public static readonly int NativeCall = Hash("RageCoop.NativeCall"); public static readonly int NativeResponse = Hash("RageCoop.NativeResponse"); - public static readonly int CleanUpWorld = Hash("RageCoop.CleanUpWorld"); + public static readonly int AllResourcesSent = Hash("RageCoop.AllResourcesSent"); /// /// Get a Int32 hash of a string. /// diff --git a/RageCoop.Core/Scripting/Resource.cs b/RageCoop.Core/Scripting/Resource.cs deleted file mode 100644 index 5a17d3b..0000000 --- a/RageCoop.Core/Scripting/Resource.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; - -namespace RageCoop.Core.Scripting -{ - public class Resource - { - /// - /// Name of the resource - /// - public string Name { get; internal set; } - /// - /// A resource-specific folder that can be used to store your files. - /// - public string DataFolder { get;internal set; } - public List Scripts { get; internal set; } = new List(); - public Dictionary Files { get; internal set; }=new Dictionary(); - } - public class ResourceFile - { - public string Name { get; internal set; } - public bool IsDirectory { get; internal set; } - public Func GetStream { get; internal set; } - } -} diff --git a/RageCoop.Core/Scripting/ResourceFile.cs b/RageCoop.Core/Scripting/ResourceFile.cs new file mode 100644 index 0000000..57a60d2 --- /dev/null +++ b/RageCoop.Core/Scripting/ResourceFile.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace RageCoop.Core.Scripting +{ + public class ResourceFile + { + public string Name { get; internal set; } + public bool IsDirectory { get; internal set; } + public Func GetStream { get; internal set; } + } +} diff --git a/RageCoop.Core/Scripting/ResourceLoader.cs b/RageCoop.Core/Scripting/ResourceLoader.cs deleted file mode 100644 index 9d36f20..0000000 --- a/RageCoop.Core/Scripting/ResourceLoader.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Reflection; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using ICSharpCode.SharpZipLib.Zip; - -[assembly: InternalsVisibleTo("RageCoop.Server")] -[assembly: InternalsVisibleTo("RageCoop.Client")] -namespace RageCoop.Core.Scripting -{ - - internal class ResourceLoader - { - protected List ToIgnore = new List - { - "RageCoop.Client.dll", - "RageCoop.Core.dll", - "RageCoop.Server.dll", - "ScriptHookVDotNet3.dll" - }; - protected List LoadedResources = new List(); - private string BaseScriptType; - public Logger Logger { get; set; } - public ResourceLoader(string baseType,Logger logger) - { - BaseScriptType = baseType; - Logger = logger; - } - /// - /// Load a resource from a directory. - /// - /// Path of the directory. - protected void LoadResource(string path,string dataFolderRoot) - { - var r = new Resource() - { - Scripts = new List(), - Name=Path.GetFileName(path), - DataFolder=Path.Combine(dataFolderRoot, Path.GetFileName(path)) - }; - Directory.CreateDirectory(r.DataFolder); - foreach (var dir in Directory.GetDirectories(path, "*", SearchOption.AllDirectories)) - { - r.Files.Add(dir, new ResourceFile() - { - IsDirectory=true, - Name=dir.Substring(path.Length+1) - }); - } - foreach (var file in Directory.GetFiles(path,"*",SearchOption.AllDirectories)) - { - var relativeName = file.Substring(path.Length+1); - var rfile = new ResourceFile() - { - GetStream=() => { return new FileStream(file, FileMode.Open, FileAccess.Read); }, - IsDirectory=false, - Name=relativeName - }; - if (file.EndsWith(".dll")) - { - LoadScriptsFromAssembly(rfile,file, r); - } - r.Files.Add(relativeName,rfile); - } - LoadedResources.Add(r); - } - /// - /// Load a resource from a zip - /// - /// - protected void LoadResource(ZipFile file,string dataFolderRoot) - { - var r = new Resource() - { - Scripts = new List(), - Name=Path.GetFileNameWithoutExtension(file.Name), - DataFolder=Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(file.Name)) - }; - Directory.CreateDirectory(r.DataFolder); - - foreach (ZipEntry entry in file) - { - ResourceFile rFile; - r.Files.Add(entry.Name, rFile=new ResourceFile() - { - Name=entry.Name, - IsDirectory=entry.IsDirectory, - }); - if (!entry.IsDirectory) - { - rFile.GetStream=() => { return file.GetInputStream(entry); }; - if (entry.Name.EndsWith(".dll")) - { - var tmp = Path.GetTempFileName(); - var f = File.OpenWrite(tmp); - rFile.GetStream().CopyTo(f); - f.Close(); - LoadScriptsFromAssembly(rFile, tmp, r, false); - } - } - } - LoadedResources.Add(r); - } - /// - /// Loads scripts from the specified assembly file. - /// - /// The path to the assembly file to load. - /// on success, otherwise - private bool LoadScriptsFromAssembly(ResourceFile file,string path, Resource resource,bool shadowCopy=true) - { - lock (LoadedResources) - { - if (!IsManagedAssembly(path)) { return false; } - if (ToIgnore.Contains(file.Name)) { try { File.Delete(path); } catch { }; return false; } - - Logger?.Debug($"Loading assembly {file.Name} ..."); - - Assembly assembly; - - try - { - if (shadowCopy) - { - var temp = Path.GetTempFileName(); - File.Copy(path, temp, true); - assembly = Assembly.LoadFrom(temp); - } - else - { - assembly = Assembly.LoadFrom(path); - } - } - catch (Exception ex) - { - Logger?.Error("Unable to load "+file.Name); - Logger?.Error(ex); - return false; - } - - return LoadScriptsFromAssembly(file,assembly, path, resource); - } - } - /// - /// Loads scripts from the specified assembly object. - /// - /// The path to the file associated with this assembly. - /// The assembly to load. - /// on success, otherwise - private bool LoadScriptsFromAssembly(ResourceFile rfile,Assembly assembly, string filename, Resource toload) - { - int count = 0; - - try - { - // Find all script types in the assembly - foreach (var type in assembly.GetTypes().Where(x => IsSubclassOf(x, BaseScriptType))) - { - ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes); - if (constructor != null && constructor.IsPublic) - { - try - { - // Invoke script constructor - var script = constructor.Invoke(null) as Scriptable; - script.CurrentResource = toload; - script.CurrentFile=rfile; - toload.Scripts.Add(script); - count++; - } - catch (Exception ex) - { - Logger?.Error($"Error occurred when loading script: {type.FullName}."); - Logger?.Error(ex); - } - } - else - { - Logger?.Error($"Script {type.FullName} has an invalid contructor."); - } - } - } - catch (ReflectionTypeLoadException ex) - { - Logger?.Error($"Failed to load assembly {rfile.Name}: "); - Logger?.Error(ex); - foreach (var e in ex.LoaderExceptions) - { - Logger?.Error(e); - } - return false; - } - - Logger?.Info($"Loaded {count} script(s) in {rfile.Name}"); - return count != 0; - } - private bool IsManagedAssembly(string filename) - { - try - { - using (Stream file = new FileStream(filename, FileMode.Open, FileAccess.Read)) - { - if (file.Length < 64) - return false; - - using (BinaryReader bin = new BinaryReader(file)) - { - // PE header starts at offset 0x3C (60). Its a 4 byte header. - file.Position = 0x3C; - uint offset = bin.ReadUInt32(); - if (offset == 0) - offset = 0x80; - - // Ensure there is at least enough room for the following structures: - // 24 byte PE Signature & Header - // 28 byte Standard Fields (24 bytes for PE32+) - // 68 byte NT Fields (88 bytes for PE32+) - // >= 128 byte Data Dictionary Table - if (offset > file.Length - 256) - return false; - - // Check the PE signature. Should equal 'PE\0\0'. - file.Position = offset; - if (bin.ReadUInt32() != 0x00004550) - return false; - - // Read PE magic number from Standard Fields to determine format. - file.Position += 20; - var peFormat = bin.ReadUInt16(); - if (peFormat != 0x10b /* PE32 */ && peFormat != 0x20b /* PE32Plus */) - return false; - - // Read the 15th Data Dictionary RVA field which contains the CLI header RVA. - // When this is non-zero then the file contains CLI data otherwise not. - file.Position = offset + (peFormat == 0x10b ? 232 : 248); - return bin.ReadUInt32() != 0; - } - } - } - catch - { - // This is likely not a valid assembly if any IO exceptions occur during reading - return false; - } - } - private bool IsSubclassOf(Type type, string baseTypeName) - { - for (Type t = type.BaseType; t != null; t = t.BaseType) - if (t.FullName == baseTypeName) - return true; - return false; - } - } -} diff --git a/RageCoop.Core/Scripting/Scriptable.cs b/RageCoop.Core/Scripting/Scriptable.cs deleted file mode 100644 index de7328e..0000000 --- a/RageCoop.Core/Scripting/Scriptable.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Linq; -namespace RageCoop.Core.Scripting -{ - public abstract class Scriptable - { - public abstract void OnStart(); - public abstract void OnStop(); - - /// - /// Get the instance where this script is loaded from. - /// - public ResourceFile CurrentFile { get; internal set; } - - /// - /// Get the object this script belongs to, this property will be initiated before (will be null if you access it in the constructor). - /// - public Resource CurrentResource { get; internal set; } - } -} diff --git a/RageCoop.Server/Client.cs b/RageCoop.Server/Client.cs index 24b5b2f..5b697aa 100644 --- a/RageCoop.Server/Client.cs +++ b/RageCoop.Server/Client.cs @@ -121,14 +121,6 @@ namespace RageCoop.Server Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } - /// - /// Send a CleanUpWorld message to this client. - /// - /// - public void SendCleanUpWorld(List clients = null) - { - SendCustomEvent(CustomEvents.CleanUpWorld, null); - } /// /// Send a native call to client and do a callback when the response received. diff --git a/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml b/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index f262f64..0000000 --- a/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Release - Any CPU - bin\Release\net6.0\publish\linux-x64\ - FileSystem - net6.0 - linux-x64 - true - True - True - - \ No newline at end of file diff --git a/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml.user b/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml.user deleted file mode 100644 index cae7f1b..0000000 --- a/RageCoop.Server/Properties/PublishProfiles/FolderProfile.pubxml.user +++ /dev/null @@ -1,9 +0,0 @@ - - - - - True|2022-06-27T05:30:18.9547908Z;False|2022-06-27T13:29:24.0778878+08:00;False|2022-06-27T13:28:37.4531587+08:00;False|2022-06-27T13:26:39.9475441+08:00;False|2022-06-27T13:25:40.8343571+08:00;True|2022-06-08T12:40:43.5537635+08:00;False|2022-06-08T12:39:50.7770086+08:00;False|2022-06-08T12:38:57.8444772+08:00;True|2022-06-06T20:32:39.8484415+08:00;True|2022-06-03T16:41:45.9403306+08:00;True|2022-06-03T16:41:27.9643943+08:00;True|2022-06-03T16:41:03.3149741+08:00;True|2022-06-03T16:40:25.3605097+08:00;True|2022-06-03T16:40:05.4510168+08:00;True|2022-06-02T13:21:10.3456459+08:00;True|2022-06-02T13:20:52.1088278+08:00;True|2022-06-02T13:20:25.6889167+08:00;True|2022-06-02T13:19:06.3089340+08:00;True|2022-06-01T18:47:39.6707493+08:00;True|2022-06-01T18:04:32.2932367+08:00;True|2022-06-01T18:03:17.8871227+08:00;True|2022-05-27T15:20:25.7264350+08:00;True|2022-05-27T15:20:04.2362276+08:00;True|2022-05-27T15:19:21.4852644+08:00;True|2022-05-27T15:18:36.0857345+08:00;True|2022-05-25T10:30:00.0927959+08:00;True|2022-05-25T10:26:50.6739643+08:00;True|2022-05-25T10:20:36.6658425+08:00;True|2022-05-25T10:19:47.8333108+08:00;True|2022-05-24T11:00:13.3617113+08:00;True|2022-05-22T16:56:31.0481188+08:00;True|2022-05-18T13:35:57.1402751+08:00;True|2022-05-18T13:10:28.4995253+08:00;True|2022-05-01T18:35:01.9624101+08:00;True|2022-05-01T12:32:20.8671319+08:00;False|2022-05-01T12:30:25.4596227+08:00; - - \ No newline at end of file diff --git a/RageCoop.Server/RageCoop.Server.csproj b/RageCoop.Server/RageCoop.Server.csproj index fe3fbf9..c18e086 100644 --- a/RageCoop.Server/RageCoop.Server.csproj +++ b/RageCoop.Server/RageCoop.Server.csproj @@ -18,6 +18,7 @@ An library for hosting a RAGECOOP server or API reference for developing a resource. icon.ico icon.png + True diff --git a/RageCoop.Server/Scripting/API.cs b/RageCoop.Server/Scripting/API.cs index 11015cc..e9da078 100644 --- a/RageCoop.Server/Scripting/API.cs +++ b/RageCoop.Server/Scripting/API.cs @@ -13,6 +13,11 @@ namespace RageCoop.Server.Scripting { public class APIEvents { + private readonly Server Server; + internal APIEvents(Server server) + { + Server = server; + } #region INTERNAL internal Dictionary>> CustomEventHandlers = new(); #endregion @@ -52,12 +57,12 @@ namespace RageCoop.Server.Scripting #region INVOKE internal void InvokePlayerHandshake(HandshakeEventArgs args) { OnPlayerHandshake?.Invoke(this, args); } - internal void InvokeOnCommandReceived(string cname, string[] cargs, Client sender) + internal void InvokeOnCommandReceived(string cmdName, string[] cmdArgs, Client sender) { var args = new OnCommandEventArgs() { - Name=cname, - Args=cargs, + Name=cmdName, + Args=cmdArgs, Sender=sender }; OnCommandReceived?.Invoke(this, args); @@ -65,7 +70,7 @@ namespace RageCoop.Server.Scripting { return; } - if (Commands.Any(x => x.Key.Name == cmdName)) + if (Server.Commands.Any(x => x.Key.Name == cmdName)) { string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray(); @@ -75,13 +80,13 @@ namespace RageCoop.Server.Scripting Args = argsWithoutCmd }; - KeyValuePair> command = Commands.First(x => x.Key.Name == cmdName); + KeyValuePair> command = Server.Commands.First(x => x.Key.Name == cmdName); command.Value.Invoke(ctx); } else { - SendChatMessage("Server", "Command not found!", sender.Connection); + Server.SendChatMessage("Server", "Command not found!", sender.Connection); } } @@ -121,8 +126,9 @@ namespace RageCoop.Server.Scripting internal API(Server server) { Server=server; + Events=new(server); } - public APIEvents Events { get; set; }=new APIEvents(); + public readonly APIEvents Events; #region FUNCTIONS /* /// @@ -220,10 +226,6 @@ namespace RageCoop.Server.Scripting /// /// Send CleanUpWorld to all players to delete all objects created by the server /// - public void SendCleanUpWorldToAll(List clients = null) - { - SendCustomEvent(CustomEvents.CleanUpWorld,null,clients); - } /// /// Register a new command chat command (Example: "/test") diff --git a/RageCoop.Server/Scripting/Resources.cs b/RageCoop.Server/Scripting/Resources.cs index 0ea6238..399c78e 100644 --- a/RageCoop.Server/Scripting/Resources.cs +++ b/RageCoop.Server/Scripting/Resources.cs @@ -4,17 +4,22 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using RageCoop.Core.Scripting; +using RageCoop.Core; using System.IO; using ICSharpCode.SharpZipLib.Zip; using System.Reflection; +using McMaster.NETCore.Plugins; namespace RageCoop.Server.Scripting { - internal class Resources : ResourceLoader + internal class Resources { + private Dictionary LoadedResources=new(); private readonly Server Server; - public Resources(Server server) : base("RageCoop.Server.Scripting.ServerScript", server.Logger) + private readonly Logger Logger; + public Resources(Server server) { Server = server; + Logger=server.Logger; } private List ClientResourceZips=new List(); public void LoadAll() @@ -22,7 +27,7 @@ namespace RageCoop.Server.Scripting // Client { var path = Path.Combine("Resources", "Client"); - var tmpDir = Path.Combine("Resources", "Temp"); + var tmpDir = Path.Combine("Resources", "Temp","Client"); Directory.CreateDirectory(path); if (Directory.Exists(tmpDir)) { @@ -76,20 +81,50 @@ namespace RageCoop.Server.Scripting Directory.CreateDirectory(path); foreach (var resource in Directory.GetDirectories(path)) { - if (Path.GetFileName(resource).ToLower()=="data") { continue; } - Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); - LoadResource(resource, dataFolder); + try + { + var name = Path.GetFileName(resource); + if (LoadedResources.ContainsKey(name)) + { + Logger?.Warning($"Resource \"{name}\" has already been loaded, ignoring..."); + continue; + } + if (name.ToLower()=="data") { continue; } + Logger?.Info($"Loading resource: {name}"); + var r = ServerResource.LoadFrom(resource, dataFolder, Logger); + LoadedResources.Add(r.Name, r); + } + catch(Exception ex) + { + Logger?.Error($"Failed to load resource: {Path.GetFileName(resource)}"); + Logger?.Error(ex); + } } foreach (var resource in Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly)) { - Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); - LoadResource(new ZipFile(resource), dataFolder); + try + { + var name = Path.GetFileNameWithoutExtension(resource); + if (LoadedResources.ContainsKey(name)) + { + Logger?.Warning($"Resource \"{name}\" has already been loaded, ignoring..."); + continue; + } + Logger?.Info($"Loading resource: name"); + var r = ServerResource.LoadFromZip(resource, Path.Combine("Resources", "Temp", "Server"), dataFolder, Logger); + LoadedResources.Add(r.Name, r); + } + catch(Exception ex) + { + Logger?.Error($"Failed to load resource: {Path.GetFileNameWithoutExtension(resource)}"); + Logger?.Error(ex); + } } // Start scripts lock (LoadedResources) { - foreach (var r in LoadedResources) + foreach (var r in LoadedResources.Values) { foreach (ServerScript s in r.Scripts) { @@ -106,11 +141,11 @@ namespace RageCoop.Server.Scripting } } - public void StopAll() + public void UnloadAll() { lock (LoadedResources) { - foreach (var d in LoadedResources) + foreach (var d in LoadedResources.Values) { foreach (var s in d.Scripts) { @@ -122,28 +157,87 @@ namespace RageCoop.Server.Scripting { Logger?.Error(ex); } + } + try + { + d.Dispose(); } + catch(Exception ex) + { + Logger.Error($"Resource \"{d.Name}\" cannot be unloaded."); + Logger.Error(ex); + } } + LoadedResources.Clear(); } } public void SendTo(Client client) { - - if (ClientResourceZips.Count!=0) + Task.Run(() => { - Task.Run(() => + + if (ClientResourceZips.Count!=0) { Logger?.Info($"Sending resources to client:{client.Username}"); - - - Logger?.Info($"Resources sent to:{client.Username}"); + foreach (var rs in ClientResourceZips) + { + using (var fs = File.OpenRead(rs)) + { + Server.SendFile(rs, Path.GetFileName(rs), client); + } + } - }); - } - else - { - client.IsReady=true; - } + Logger?.Info($"Resources sent to:{client.Username}"); + } + if (Server.GetResponse(client, new Packets.AllResourcesSent())?.Response==FileResponse.Loaded) + { + client.IsReady=true; + Server.API.Events.InvokePlayerReady(client); + } + else + { + Logger?.Warning($"Client {client.Username} failed to load resource."); + } + }); } + /// + /// Load a resource from a zip + /// + /// + /* + private void LoadResource(ZipFile file, string dataFolderRoot) + { + var r = new Resource() + { + Scripts = new List(), + Name=Path.GetFileNameWithoutExtension(file.Name), + DataFolder=Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(file.Name)) + }; + Directory.CreateDirectory(r.DataFolder); + + foreach (ZipEntry entry in file) + { + ResourceFile rFile; + r.Files.Add(entry.Name, rFile=new ResourceFile() + { + Name=entry.Name, + IsDirectory=entry.IsDirectory, + }); + if (!entry.IsDirectory) + { + rFile.GetStream=() => { return file.GetInputStream(entry); }; + if (entry.Name.EndsWith(".dll")) + { + var tmp = Path.GetTempFileName(); + var f = File.OpenWrite(tmp); + rFile.GetStream().CopyTo(f); + f.Close(); + LoadScriptsFromAssembly(rFile, tmp, r, false); + } + } + } + LoadedResources.Add(r.Name,r); + } + */ } } diff --git a/RageCoop.Server/Scripting/ServerResource.cs b/RageCoop.Server/Scripting/ServerResource.cs new file mode 100644 index 0000000..8f67a3c --- /dev/null +++ b/RageCoop.Server/Scripting/ServerResource.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RageCoop.Core; +using System.Reflection; +using McMaster.NETCore.Plugins; +using System.IO; +using RageCoop.Core.Scripting; +using ICSharpCode.SharpZipLib.Zip; + +namespace RageCoop.Server.Scripting +{ + + public class ServerResource : PluginLoader + { + private static readonly HashSet ToIgnore = new() + { + "RageCoop.Client.dll", + "RageCoop.Core.dll", + "RageCoop.Server.dll", + "ScriptHookVDotNet3.dll", + "ScriptHookVDotNet.dll" + }; + public Logger Logger; + internal ServerResource(PluginConfig config) : base(config) { } + internal static ServerResource LoadFrom(string resDir, string dataFolder, Logger logger = null, bool isTemp = false) + { + var conf = new PluginConfig(Path.GetFullPath(Path.Combine(resDir, Path.GetFileName(resDir)+".dll"))) + { + PreferSharedTypes = true, + EnableHotReload=!isTemp, + IsUnloadable=true, + LoadInMemory=true, + }; + ServerResource r = new(conf); + r.Logger= logger; + r.Name=Path.GetFileName(resDir); + if (!File.Exists(conf.MainAssemblyPath)) + { + r.Dispose(); + throw new FileNotFoundException($"Main assembly for resource \"{r.Name}\" cannot be found."); + } + r.Scripts = new List(); + r.DataFolder=Path.Combine(dataFolder, r.Name); + r.Reloaded+=(s, e) => { r.Logger?.Info($"Resource: {r.Name} has been reloaded"); }; + + Directory.CreateDirectory(r.DataFolder); + foreach (var dir in Directory.GetDirectories(resDir, "*", SearchOption.AllDirectories)) + { + r.Files.Add(dir, new ResourceFile() + { + IsDirectory=true, + Name=dir.Substring(resDir.Length+1) + }); + } + foreach (var file in Directory.GetFiles(resDir, "*", SearchOption.AllDirectories)) + { + if (ToIgnore.Contains(Path.GetFileName(file))) { try { File.Delete(file); } catch { } continue; } + var relativeName = file.Substring(resDir.Length+1); + var rfile = new ResourceFile() + { + GetStream=() => { return new FileStream(file, FileMode.Open, FileAccess.Read); }, + IsDirectory=false, + Name=relativeName + }; + if (file.EndsWith(".dll")) + { + r.LoadScriptsFromAssembly(rfile, r.LoadAssemblyFromPath(Path.GetFullPath(file))); + } + r.Files.Add(relativeName, rfile); + } + return r; + } + internal static ServerResource LoadFromZip(string zipPath, string tmpDir, string dataFolder, Logger logger = null) + { + tmpDir=Path.Combine(tmpDir, Path.GetFileNameWithoutExtension(zipPath)); + new FastZip().ExtractZip(zipPath, tmpDir, null); + return LoadFrom(tmpDir, dataFolder, logger, true); + } + /// + /// Name of the resource + /// + public string Name { get; internal set; } + /// + /// A resource-specific folder that can be used to store your files. + /// + public string DataFolder { get; internal set; } + public List Scripts { get; internal set; } = new List(); + public Dictionary Files { get; internal set; } = new Dictionary(); + + /// + /// Loads scripts from the specified assembly object. + /// + /// The path to the file associated with this assembly. + /// The assembly to load. + /// on success, otherwise + private bool LoadScriptsFromAssembly(ResourceFile rfile, Assembly assembly) + { + int count = 0; + + try + { + // Find all script types in the assembly + foreach (var type in assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(ServerScript)))) + { + ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes); + if (constructor != null && constructor.IsPublic) + { + try + { + // Invoke script constructor + var script = constructor.Invoke(null) as ServerScript; + script.CurrentResource = this; + script.CurrentFile=rfile; + Scripts.Add(script); + count++; + } + catch (Exception ex) + { + Logger?.Error($"Error occurred when loading script: {type.FullName}."); + Logger?.Error(ex); + } + } + else + { + Logger?.Error($"Script {type.FullName} has an invalid contructor."); + } + } + } + catch (ReflectionTypeLoadException ex) + { + Logger?.Error($"Failed to load assembly {rfile.Name}: "); + Logger?.Error(ex); + foreach (var e in ex.LoaderExceptions) + { + Logger?.Error(e); + } + return false; + } + + Logger?.Info($"Loaded {count} script(s) in {rfile.Name}"); + return count != 0; + } + public new void Dispose() + { + base.Dispose(); + } + + } +} diff --git a/RageCoop.Server/Scripting/ServerScript.cs b/RageCoop.Server/Scripting/ServerScript.cs index 6dfc849..f2c37c3 100644 --- a/RageCoop.Server/Scripting/ServerScript.cs +++ b/RageCoop.Server/Scripting/ServerScript.cs @@ -6,19 +6,25 @@ namespace RageCoop.Server.Scripting /// /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and will be null, you should use . to initiate your script. /// - public abstract class ServerScript : Scriptable + public abstract class ServerScript { /// /// This method would be called from main thread after all scripts have been loaded. /// - public override abstract void OnStart(); + public abstract void OnStart(); /// /// This method would be called from main thread when the server is shutting down, you MUST terminate all background jobs/threads in this method. /// - public override abstract void OnStop(); + public abstract void OnStop(); public API API { get; set; } + + /// + /// Get the object this script belongs to, this property will be initiated before (will be null if you access it in the constructor). + /// + public ServerResource CurrentResource { get; internal set; } + public ResourceFile CurrentFile { get; internal set; } } [AttributeUsage(AttributeTargets.Method, Inherited = false)] diff --git a/RageCoop.Server/Server.cs b/RageCoop.Server/Server.cs index 42496e9..34d6572 100644 --- a/RageCoop.Server/Server.cs +++ b/RageCoop.Server/Server.cs @@ -43,6 +43,8 @@ namespace RageCoop.Server private Thread _listenerThread; private Thread _announceThread; private Worker _worker; + private Dictionary> PendingResponses=new(); + private Dictionary> RequestHandlers=new(); private readonly string _compatibleVersion = "V0_5"; public Server(ServerSettings settings,Logger logger=null) { @@ -78,7 +80,7 @@ namespace RageCoop.Server MainNetServer = new NetServer(config); MainNetServer.Start(); - _worker=new Worker("ServerWorker"); + _worker=new Worker("ServerWorker",Logger); _sendInfoTimer.Elapsed+=(s, e) => { SendPlayerInfos(); }; _sendInfoTimer.AutoReset=true; _sendInfoTimer.Enabled=true; @@ -221,7 +223,7 @@ namespace RageCoop.Server Logger?.Info("Server is shutting down!"); MainNetServer.Shutdown("Server is shutting down!"); BaseScript.OnStop(); - Resources.StopAll(); + Resources.UnloadAll(); } private void ProcessMessage(NetIncomingMessage message) @@ -233,7 +235,7 @@ namespace RageCoop.Server case NetIncomingMessageType.ConnectionApproval: { Logger?.Info($"New incoming connection from: [{message.SenderConnection.RemoteEndPoint}]"); - if (message.ReadByte() != (byte)PacketTypes.Handshake) + if (message.ReadByte() != (byte)PacketType.Handshake) { Logger?.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: Wrong packet!"); message.SenderConnection.Deny("Wrong packet!"); @@ -275,151 +277,75 @@ namespace RageCoop.Server else if (status == NetConnectionStatus.Connected) { SendPlayerConnectPacket(sender); + _worker.QueueJob(() => API.Events.InvokePlayerConnected(sender)); Resources.SendTo(sender); - _worker.QueueJob(()=> API.Events.InvokePlayerConnected(sender)); - if (sender.IsReady) - { - _worker.QueueJob(()=>API.Events.InvokePlayerReady(sender)); - } } break; } case NetIncomingMessageType.Data: { - // Get packet type - byte btype = message.ReadByte(); - var type = (PacketTypes)btype; - int len = message.ReadInt32(); - byte[] data = message.ReadBytes(len); - - try + + // Get sender client + if (Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender)) { - // Get sender client - if (!Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender)) - { - throw new UnauthorizedAccessException("No client data found:"+message.SenderEndPoint); - } + // Get packet type + var type = (PacketType)message.ReadByte(); switch (type) { - - #region SyncData - - case PacketTypes.PedStateSync: + case PacketType.Response: { - Packets.PedStateSync packet = new(); - packet.Unpack(data); - - PedStateSync(packet, sender); - break; - } - case PacketTypes.VehicleStateSync: - { - Packets.VehicleStateSync packet = new(); - packet.Unpack(data); - - VehicleStateSync(packet, sender); - - break; - } - case PacketTypes.PedSync: - { - - Packets.PedSync packet = new(); - packet.Unpack(data); - - PedSync(packet, sender); - - } - break; - case PacketTypes.VehicleSync: - { - Packets.VehicleSync packet = new(); - packet.Unpack(data); - - VehicleSync(packet, sender); - - } - break; - case PacketTypes.ProjectileSync: - { - - Packets.ProjectileSync packet = new(); - packet.Unpack(data); - ProjectileSync(packet, sender); - - } - break; - - - #endregion - - case PacketTypes.ChatMessage: - { - - Packets.ChatMessage packet = new(); - packet.Unpack(data); - - _worker.QueueJob(()=>API.Events.InvokeOnChatMessage(packet, sender)); - SendChatMessage(packet,sender); - } - break; - case PacketTypes.CustomEvent: - { - Packets.CustomEvent packet = new Packets.CustomEvent(); - packet.Unpack(data); - _worker.QueueJob(() => API.Events.InvokeCustomEventReceived(packet, sender)); - } - break; - - case PacketTypes.FileTransferComplete: - { - Packets.FileTransferComplete packet = new Packets.FileTransferComplete(); - packet.Unpack(data); - FileTransfer toRemove; - - // Cancel the download if it's in progress - if (InProgressFileTransfers.TryGetValue(packet.ID,out toRemove)) + int id = message.ReadInt32(); + if (PendingResponses.TryGetValue(id, out var callback)) { - toRemove.Cancel=true; - if (toRemove.Name=="Resources.zip") - { - sender.IsReady=true; - _worker.QueueJob(() => API.Events.InvokePlayerReady(sender)); - } + callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32())); + PendingResponses.Remove(id); } + break; + } + case PacketType.Request: + { + int id = message.ReadInt32(); + if (RequestHandlers.TryGetValue((PacketType)message.ReadByte(), out var handler)) + { + var response=MainNetServer.CreateMessage(); + response.Write((byte)PacketType.Response); + response.Write(id); + handler(message.ReadBytes(message.ReadInt32())).Pack(response); + MainNetServer.SendMessage(response,message.SenderConnection,NetDeliveryMethod.ReliableOrdered); + } + break; } - break; default: - if (type.IsSyncEvent()) { - // Sync Events - try + byte[] data = message.ReadBytes(message.ReadInt32()); + if (type.IsSyncEvent()) { - var toSend = MainNetServer.Connections.Exclude(message.SenderConnection); - if (toSend.Count!=0) + // Sync Events + try { - var outgoingMessage = MainNetServer.CreateMessage(); - outgoingMessage.Write(btype); - outgoingMessage.Write(len); - outgoingMessage.Write(data); - MainNetServer.SendMessage(outgoingMessage, toSend, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.SyncEvents); + var toSend = MainNetServer.Connections.Exclude(message.SenderConnection); + if (toSend.Count!=0) + { + var outgoingMessage = MainNetServer.CreateMessage(); + outgoingMessage.Write((byte)type); + outgoingMessage.Write(data.Length); + outgoingMessage.Write(data); + MainNetServer.SendMessage(outgoingMessage, toSend, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.SyncEvents); + } + } + catch (Exception e) + { + DisconnectAndLog(message.SenderConnection, type, e); } } - catch (Exception e) + else { - DisconnectAndLog(message.SenderConnection, type, e); + HandlePacket(type, data, sender); } + break; } - else - { - Logger?.Error("Unhandled Data / Packet type"); - } - break; } - } - catch (Exception e) - { - DisconnectAndLog(message.SenderConnection, type, e); + } break; } @@ -448,7 +374,7 @@ namespace RageCoop.Server break; case NetIncomingMessageType.UnconnectedData: { - if (message.ReadByte()==(byte)PacketTypes.PublicKeyRequest) + if (message.ReadByte()==(byte)PacketType.PublicKeyRequest) { var msg = MainNetServer.CreateMessage(); var p=new Packets.PublicKeyResponse(); @@ -466,6 +392,98 @@ namespace RageCoop.Server MainNetServer.Recycle(message); } + internal void QueueJob(Action job) + { + _worker.QueueJob(job); + } + private void HandlePacket(PacketType type,byte[] data,Client sender) + { + + try + { + + switch (type) + { + + #region SyncData + + case PacketType.PedStateSync: + { + Packets.PedStateSync packet = new(); + packet.Unpack(data); + + PedStateSync(packet, sender); + break; + } + case PacketType.VehicleStateSync: + { + Packets.VehicleStateSync packet = new(); + packet.Unpack(data); + + VehicleStateSync(packet, sender); + + break; + } + case PacketType.PedSync: + { + + Packets.PedSync packet = new(); + packet.Unpack(data); + + PedSync(packet, sender); + + } + break; + case PacketType.VehicleSync: + { + Packets.VehicleSync packet = new(); + packet.Unpack(data); + + VehicleSync(packet, sender); + + } + break; + case PacketType.ProjectileSync: + { + + Packets.ProjectileSync packet = new(); + packet.Unpack(data); + ProjectileSync(packet, sender); + + } + break; + + + #endregion + + case PacketType.ChatMessage: + { + + Packets.ChatMessage packet = new(); + packet.Unpack(data); + + _worker.QueueJob(() => API.Events.InvokeOnChatMessage(packet, sender)); + SendChatMessage(packet, sender); + } + break; + case PacketType.CustomEvent: + { + Packets.CustomEvent packet = new Packets.CustomEvent(); + packet.Unpack(data); + _worker.QueueJob(() => API.Events.InvokeCustomEventReceived(packet, sender)); + } + break; + + default: + Logger?.Error("Unhandled Data / Packet type"); + break; + } + } + catch (Exception e) + { + DisconnectAndLog(sender.Connection, type, e); + } + } object _sendPlayersLock=new object(); internal void SendPlayerInfos() { @@ -492,7 +510,7 @@ namespace RageCoop.Server } } - private void DisconnectAndLog(NetConnection senderConnection,PacketTypes type, Exception e) + private void DisconnectAndLog(NetConnection senderConnection,PacketType type, Exception e) { Logger?.Error($"Error receiving a packet of type {type}"); Logger?.Error(e.Message); @@ -811,31 +829,64 @@ namespace RageCoop.Server RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength, (Action)Delegate.CreateDelegate(typeof(Action), method)); } } - + internal T GetResponse(Client client,Packet request, ConnectionChannel channel = ConnectionChannel.RequestResponse,int timeout=5000) where T:Packet, new() + { + if (Thread.CurrentThread==_listenerThread) + { + throw new InvalidOperationException("Cannot wait for response from the listener thread!"); + } + var received=new AutoResetEvent(false); + byte[] response=null; + var id = NewRequestID(); + PendingResponses.Add(id, (type,p) => + { + response=p; + received.Set(); + }); + var msg = MainNetServer.CreateMessage(); + msg.Write((byte)PacketType.Request); + msg.Write(id); + request.Pack(msg); + MainNetServer.SendMessage(msg,client.Connection,NetDeliveryMethod.ReliableOrdered,(int)channel); + if (received.WaitOne(timeout)) + { + var p = new T(); + p.Unpack(response); + return p; + } + else + { + return null; + } + } internal void SendFile(string path,string name,Client client,Action updateCallback=null) { - int id = RequestFileID(); + + int id = NewFileID(); var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); fs.Seek(0, SeekOrigin.Begin); var total = fs.Length; + if (GetResponse(client, new Packets.FileTransferRequest() + { + FileLength= total, + Name=name, + ID=id, + },ConnectionChannel.File)?.Response!=FileResponse.NeedToDownload) + { + Logger?.Info($"Skipping file transfer \"{name}\" to {client.Username}"); + fs.Close(); + fs.Dispose(); + return; + } Logger?.Debug($"Initiating file transfer:{name}, {total}"); FileTransfer transfer = new() { ID=id, Name = name, }; - InProgressFileTransfers.Add(id,transfer); - Send( - new Packets.FileTransferRequest() - { - FileLength= total, - Name=name, - ID=id, - }, - client, ConnectionChannel.File, NetDeliveryMethod.ReliableOrdered - ); + InProgressFileTransfers.Add(id, transfer); int read = 0; - int thisRead = 0; + int thisRead; do { // 4 KB chunk @@ -858,19 +909,19 @@ namespace RageCoop.Server if (updateCallback!=null) { updateCallback(transfer.Progress);} } while (thisRead>0); - Send( - new Packets.FileTransferComplete() - { - ID= id, - } - , client, ConnectionChannel.File, NetDeliveryMethod.ReliableOrdered - ); + if(GetResponse(client, new Packets.FileTransferComplete() + { + ID= id, + },ConnectionChannel.File)?.Response!=FileResponse.Completed) + { + Logger.Warning($"File trasfer to {client.Username} failed: "+name); + } fs.Close(); fs.Dispose(); Logger?.Debug($"All file chunks sent:{name}"); InProgressFileTransfers.Remove(id); } - private int RequestFileID() + private int NewFileID() { int ID = 0; while ((ID==0) @@ -885,6 +936,21 @@ namespace RageCoop.Server } return ID; } + private int NewRequestID() + { + int ID = 0; + while ((ID==0) + || PendingResponses.ContainsKey(ID)) + { + byte[] rngBytes = new byte[4]; + + RandomNumberGenerator.Create().GetBytes(rngBytes); + + // Convert the bytes into an integer + ID = BitConverter.ToInt32(rngBytes, 0); + } + return ID; + } /// /// Pack the packet then send to server. diff --git a/libs/McMaster.NETCore.Plugins.dll b/libs/McMaster.NETCore.Plugins.dll index 4c1c630d315a37c45a8fccb4606ec1c5c8931c9c..868e6a451d09d102233fc97ee11e3d730c7d29c8 100644 GIT binary patch delta 9665 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

oAY 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