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 4c1c630..868e6a4 100644 Binary files a/libs/McMaster.NETCore.Plugins.dll and b/libs/McMaster.NETCore.Plugins.dll differ diff --git a/libs/ScriptHookVDotNet.dll b/libs/ScriptHookVDotNet.dll new file mode 100644 index 0000000..593df6e Binary files /dev/null and b/libs/ScriptHookVDotNet.dll differ