Resource loading

This commit is contained in:
Sardelka 2022-07-01 12:22:31 +08:00
parent 58362c2613
commit 8a46bd6b68
43 changed files with 1125 additions and 771 deletions

View File

@ -30,6 +30,7 @@ _Old name: GTACOOP:R_
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1) - [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1)
- [ClearScript](https://github.com/microsoft/ClearScript) - [ClearScript](https://github.com/microsoft/ClearScript)
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) - [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
# Features # Features

View File

@ -1,15 +1,61 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using RageCoop.Core;
using System;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class DownloadManager 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(":", ".")}"; static string downloadFolder = $"RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>(); private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
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}"); Main.Logger.Debug($"Downloading file to {downloadFolder}\\{name} , id:{id}");
if (!Directory.Exists(downloadFolder)) if (!Directory.Exists(downloadFolder))
@ -20,22 +66,13 @@ namespace RageCoop.Client
if (FileAlreadyExists(downloadFolder, name, length)) if (FileAlreadyExists(downloadFolder, name, length))
{ {
Main.Logger.Debug($"File already exists! canceling download:{name}"); Main.Logger.Debug($"File already exists! canceling download:{name}");
Cancel(id); return false;
if (name=="Resources.zip")
{
Main.Logger.Debug("Loading resources...");
Main.Resources.Load(Path.Combine(downloadFolder));
}
return;
} }
if (!name.EndsWith(".zip")) if (!name.EndsWith(".zip"))
{ {
Cancel(id); Main.Logger.Error($"File download blocked! [{name}]");
return false;
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;
} }
lock (InProgressDownloads) lock (InProgressDownloads)
{ {
@ -47,6 +84,7 @@ namespace RageCoop.Client
Stream = new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite) Stream = new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
}); });
} }
return true;
} }
/// <summary> /// <summary>
@ -87,47 +125,20 @@ namespace RageCoop.Client
else else
{ {
Main.Logger.Trace($"Received unhandled file chunk:{id}"); 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) public static void Complete(int id)
{ {
DownloadFile f; DownloadFile f;
if (InProgressDownloads.TryGetValue(id, out f)) if (InProgressDownloads.TryGetValue(id, out f))
{
lock (InProgressDownloads)
{ {
InProgressDownloads.Remove(id); InProgressDownloads.Remove(id);
f.Dispose(); f.Dispose();
Main.Logger.Info($"Download finished:{f.FileName}"); 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);
}
} }
else else
{ {

View File

@ -78,7 +78,8 @@ namespace RageCoop.Client
{ {
Client = new NetClient(config); Client = new NetClient(config);
Client.Start(); Client.Start();
Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); });
DownloadManager.Cleanup();
Security.Regen(); Security.Regen();
GetServerPublicKey(address); GetServerPublicKey(address);
@ -91,10 +92,10 @@ namespace RageCoop.Client
ModVersion = Main.CurrentVersion, ModVersion = Main.CurrentVersion,
PassHashEncrypted=Security.Encrypt(password.GetHash()) PassHashEncrypted=Security.Encrypt(password.GetHash())
}; };
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted,out handshake.AesIVCrypted); Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted,out handshake.AesIVCrypted);
handshake.Pack(outgoingMessage); handshake.Pack(outgoingMessage);
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage); Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); });
}); });

View File

@ -16,6 +16,9 @@ namespace RageCoop.Client
internal static partial class Networking internal static partial class Networking
{ {
private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false); private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false);
private static Dictionary<int, Action<PacketType, byte[]>> PendingResponses = new Dictionary<int, Action<PacketType, byte[]>>();
internal static Dictionary<PacketType, Func< byte[], Packet>> RequestHandlers = new Dictionary<PacketType, Func< byte[], Packet>>();
public static void ProcessMessage(NetIncomingMessage message) public static void ProcessMessage(NetIncomingMessage message)
{ {
if(message == null) { return; } if(message == null) { return; }
@ -77,140 +80,49 @@ namespace RageCoop.Client
{ {
if (message.LengthBytes==0) { break; } if (message.LengthBytes==0) { break; }
var packetType = PacketTypes.Unknown; var packetType = PacketType.Unknown;
try try
{ {
packetType = (PacketTypes)message.ReadByte();
int len = message.ReadInt32(); // Get packet type
byte[] data = message.ReadBytes(len); packetType = (PacketType)message.ReadByte();
switch (packetType) switch (packetType)
{ {
case PacketTypes.PlayerConnect: case PacketType.Response:
{ {
int id = message.ReadInt32();
Packets.PlayerConnect packet = new Packets.PlayerConnect(); if (PendingResponses.TryGetValue(id, out var callback))
packet.Unpack(data); {
callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32()));
Main.QueueAction(() => PlayerConnect(packet)); PendingResponses.Remove(id);
} }
break; break;
case PacketTypes.PlayerDisconnect: }
case PacketType.Request:
{ {
int id = message.ReadInt32();
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect(); var realType = (PacketType)message.ReadByte();
packet.Unpack(data); int len = message.ReadInt32();
Main.QueueAction(() => PlayerDisconnect(packet)); 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; break;
case PacketTypes.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.UpdatePlayer(packet);
break;
} }
#region ENTITY SYNC
case PacketTypes.VehicleSync:
{
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);
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: default:
if (packetType.IsSyncEvent())
{ {
// Dispatch to main thread byte[] data = message.ReadBytes(message.ReadInt32());
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
} HandlePacket(packetType, data);
break; break;
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
Main.QueueAction(() => { Main.QueueAction(() => {
@ -228,10 +140,10 @@ namespace RageCoop.Client
break; break;
case NetIncomingMessageType.UnconnectedData: case NetIncomingMessageType.UnconnectedData:
{ {
var packetType = (PacketTypes)message.ReadByte(); var packetType = (PacketType)message.ReadByte();
int len = message.ReadInt32(); int len = message.ReadInt32();
byte[] data = message.ReadBytes(len); byte[] data = message.ReadBytes(len);
if (packetType==PacketTypes.PublicKeyResponse) if (packetType==PacketType.PublicKeyResponse)
{ {
var packet=new Packets.PublicKeyResponse(); var packet=new Packets.PublicKeyResponse();
packet.Unpack(data); packet.Unpack(data);
@ -252,7 +164,116 @@ namespace RageCoop.Client
Client.Recycle(message); 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) private static void PedSync(Packets.PedSync packet)
{ {

View File

@ -158,18 +158,6 @@ namespace RageCoop.Client
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat); Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat);
Client.FlushSendQueue(); 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 #if DEBUG
#endif #endif
} }

View File

@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <UseWindowsForms>True</UseWindowsForms>
<UseWindowsForms>true</UseWindowsForms>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
@ -22,6 +21,8 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<DefineConstants>SHVDN3</DefineConstants> <DefineConstants>SHVDN3</DefineConstants>
<TargetFrameworks>net48</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -44,7 +45,7 @@
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath> <HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="ScriptHookVDotNet"> <Reference Include="ScriptHookVDotNet">
<HintPath>..\..\RageCoop.SHVDN\bin\Release\ScriptHookVDotNet.dll</HintPath> <HintPath>..\libs\ScriptHookVDotNet.dll</HintPath>
</Reference> </Reference>
<Reference Include="ScriptHookVDotNet3"> <Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath> <HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>

View File

@ -16,7 +16,7 @@ namespace RageCoop.Client.Scripting
{ {
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn); API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall); API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall);
API.RegisterCustomEventHandler(CustomEvents.CleanUpWorld, (s) => Main.QueueAction(() => Main.CleanUpWorld()));
} }
public override void OnStop() public override void OnStop()

View File

@ -1,19 +1,26 @@
namespace RageCoop.Client.Scripting using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{ {
/// <summary> /// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script. /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary> /// </summary>
public abstract class ClientScript:Core.Scripting.Scriptable public abstract class ClientScript
{ {
/// <summary> /// <summary>
/// This method would be called from main thread shortly after all scripts have been loaded. /// This method would be called from main thread shortly after all scripts have been loaded.
/// </summary> /// </summary>
public override abstract void OnStart(); public abstract void OnStart();
/// <summary> /// <summary>
/// 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. /// 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.
/// </summary> /// </summary>
public override abstract void OnStop(); public abstract void OnStop();
/// <summary>
/// Get the <see cref="ResourceFile"/> instance where this script is loaded from.
/// </summary>
public ResourceFile CurrentFile { get; internal set; }
} }
} }

View File

@ -1,13 +1,34 @@
using System.IO; using System.IO;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using RageCoop.Core;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using System; using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
internal class Resources:ResourceLoader
public class ClientResource
{ {
public Resources() : base("RageCoop.Client.Scripting.ClientScript", Main.Logger) { } /// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
}
internal class Resources
{
public Resources(){
BaseScriptType = "RageCoop.Client.Scripting.ClientScript";
Logger = Main.Logger;
}
private void StartAll() private void StartAll()
{ {
lock (LoadedResources) lock (LoadedResources)
@ -65,11 +86,10 @@ namespace RageCoop.Client.Scripting
} }
} }
Directory.CreateDirectory(path); 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.GetFileNameWithoutExtension(zipPath)}");
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); LoadResource(new ZipFile(zipPath),Path.Combine(path,"data"));
LoadResource(resource,Path.Combine(path,"data"));
} }
StartAll(); StartAll();
} }
@ -78,6 +98,242 @@ namespace RageCoop.Client.Scripting
StopAll(); StopAll();
LoadedResources.Clear(); LoadedResources.Clear();
} }
private List<string> ToIgnore = new List<string>
{
"RageCoop.Client.dll",
"RageCoop.Core.dll",
"RageCoop.Server.dll",
"ScriptHookVDotNet3.dll"
};
private List<ClientResource> LoadedResources = new List<ClientResource>();
private string BaseScriptType;
public Logger Logger { get; set; }
/// <summary>
/// Load a resource from a zip
/// </summary>
/// <param name="file"></param>
private void LoadResource(ZipFile file, string dataFolderRoot)
{
var r = new ClientResource()
{
Scripts = new List<ClientScript>(),
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);
}
/// <summary>
/// Loads scripts from the specified assembly file.
/// </summary>
/// <param name="path">The path to the assembly file to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
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);
}
}
/// <summary>
/// Loads scripts from the specified assembly object.
/// </summary>
/// <param name="filename">The path to the file associated with this assembly.</param>
/// <param name="assembly">The assembly to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
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;
}
/*
/// <summary>
/// Load a resource from a directory.
/// </summary>
/// <param name="path">Path of the directory.</param>
private void LoadResource(string path, string dataFolderRoot)
{
var r = new ClientResource()
{
Scripts = new List<ClientScript>(),
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);
}
*/
} }
} }

View File

@ -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). /// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary> /// </summary>
public int WorldVehicleSoftLimit { get; set; } = 35; public int WorldVehicleSoftLimit { get; set; } = 35;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldPedSoftLimit { get; set; } = 50;
} }
} }

View File

@ -14,6 +14,7 @@ namespace RageCoop.Client
{ {
internal class EntityPool internal class EntityPool
{ {
private static bool _trafficSpawning=true;
public static object PedsLock = new object(); public static object PedsLock = new object();
private static Dictionary<int, SyncedPed> ID_Peds = new Dictionary<int, SyncedPed>(); private static Dictionary<int, SyncedPed> ID_Peds = new Dictionary<int, SyncedPed>();
public static int CharactersCount { get { return ID_Peds.Count; } } public static int CharactersCount { get { return ID_Peds.Count; } }
@ -150,7 +151,7 @@ namespace RageCoop.Client
{ {
Handle_Peds.Remove(p.Handle); 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.AttachedBlip?.Delete();
p.Kill(); p.Kill();
p.MarkAsNoLongerNeeded(); p.MarkAsNoLongerNeeded();
@ -208,7 +209,7 @@ namespace RageCoop.Client
{ {
Handle_Vehicles.Remove(veh.Handle); 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.AttachedBlip?.Delete();
veh.MarkAsNoLongerNeeded(); veh.MarkAsNoLongerNeeded();
veh.Delete(); veh.Delete();
@ -298,9 +299,14 @@ namespace RageCoop.Client
vehStatesPerFrame=allVehicles.Length*5/(int)Game.FPS+1; vehStatesPerFrame=allVehicles.Length*5/(int)Game.FPS+1;
pedStatesPerFrame=allPeds.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 #if BENCHMARK
@ -369,7 +375,7 @@ namespace RageCoop.Client
SyncedPed c = EntityPool.GetPedByHandle(p.Handle); SyncedPed c = EntityPool.GetPedByHandle(p.Handle);
if (c==null && (p!=Game.Player.Character)) 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); c=new SyncedPed(p);
EntityPool.Add(c); EntityPool.Add(c);
@ -448,7 +454,7 @@ namespace RageCoop.Client
{ {
if (!Handle_Vehicles.ContainsKey(veh.Handle)) 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)); EntityPool.Add(new SyncedVehicle(veh));

View File

@ -58,7 +58,7 @@ namespace RageCoop.Client {
public static void TriggerBulletShot(uint hash,SyncedPed owner,Vector3 impactPosition) 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(); 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) switch (type)
{ {
case PacketTypes.BulletShot: case PacketType.BulletShot:
{ {
Packets.BulletShot p = new Packets.BulletShot(); Packets.BulletShot p = new Packets.BulletShot();
p.Unpack(data); p.Unpack(data);
HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID); HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID);
break; break;
} }
case PacketTypes.EnteringVehicle: case PacketType.EnteringVehicle:
{ {
Packets.EnteringVehicle p = new Packets.EnteringVehicle(); Packets.EnteringVehicle p = new Packets.EnteringVehicle();
p.Unpack(data); p.Unpack(data);
@ -231,35 +231,35 @@ namespace RageCoop.Client {
} }
break; break;
case PacketTypes.LeaveVehicle: case PacketType.LeaveVehicle:
{ {
Packets.LeaveVehicle packet = new Packets.LeaveVehicle(); Packets.LeaveVehicle packet = new Packets.LeaveVehicle();
packet.Unpack(data); packet.Unpack(data);
HandleLeaveVehicle(packet); HandleLeaveVehicle(packet);
} }
break; break;
case PacketTypes.OwnerChanged: case PacketType.OwnerChanged:
{ {
Packets.OwnerChanged packet = new Packets.OwnerChanged(); Packets.OwnerChanged packet = new Packets.OwnerChanged();
packet.Unpack(data); packet.Unpack(data);
HandleOwnerChanged(packet); HandleOwnerChanged(packet);
} }
break; break;
case PacketTypes.PedKilled: case PacketType.PedKilled:
{ {
var packet = new Packets.PedKilled(); var packet = new Packets.PedKilled();
packet.Unpack(data); packet.Unpack(data);
HandlePedKilled(packet); HandlePedKilled(packet);
} }
break; break;
case PacketTypes.EnteredVehicle: case PacketType.EnteredVehicle:
{ {
var packet = new Packets.EnteredVehicle(); var packet = new Packets.EnteredVehicle();
packet.Unpack(data); packet.Unpack(data);
HandleEnteredVehicle(packet.PedID,packet.VehicleID,(VehicleSeat)packet.VehicleSeat); HandleEnteredVehicle(packet.PedID,packet.VehicleID,(VehicleSeat)packet.VehicleSeat);
break; break;
} }
case PacketTypes.NozzleTransform: case PacketType.NozzleTransform:
{ {
var packet = new Packets.NozzleTransform(); var packet = new Packets.NozzleTransform();
packet.Unpack(data); packet.Unpack(data);

View File

@ -7,6 +7,10 @@ using GTA.Math;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Net; using System.Net;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
namespace RageCoop.Core namespace RageCoop.Core
{ {
public class CoreUtils public class CoreUtils
@ -231,5 +235,13 @@ namespace RageCoop.Core
} }
return output; 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;
}
} }
} }

View File

@ -15,7 +15,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
Args= Args ?? new List<object>(0); Args= Args ?? new List<object>(0);
message.Write((byte)PacketTypes.CustomEvent); message.Write((byte)PacketType.CustomEvent);
List<byte> result = new List<byte>(); List<byte> result = new List<byte>();
result.AddInt(Hash); result.AddInt(Hash);

View File

@ -6,10 +6,13 @@ using Lidgren.Network;
namespace RageCoop.Core namespace RageCoop.Core
{ {
public enum FileType:byte public enum FileResponse:byte
{ {
Resource=0, NeedToDownload=0,
Custom=1, AlreadyExists=1,
Completed=2,
Loaded=3,
LoadFailed=4,
} }
public partial class Packets public partial class Packets
{ {
@ -24,7 +27,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferRequest); message.Write((byte)PacketType.FileTransferRequest);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
// 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 class FileTransferChunk : Packet
{ {
public int ID { get; set; } public int ID { get; set; }
@ -69,7 +102,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferChunk); message.Write((byte)PacketType.FileTransferChunk);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -106,7 +139,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferComplete); message.Write((byte)PacketType.FileTransferComplete);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -129,5 +162,22 @@ namespace RageCoop.Core
#endregion #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
}
}
} }
} }

View File

@ -7,7 +7,7 @@ using GTA.Math;
namespace RageCoop.Core namespace RageCoop.Core
{ {
public enum PacketTypes:byte public enum PacketType:byte
{ {
Handshake=0, Handshake=0,
PlayerConnect=1, PlayerConnect=1,
@ -15,6 +15,8 @@ namespace RageCoop.Core
PlayerInfoUpdate=3, PlayerInfoUpdate=3,
PublicKeyRequest=4, PublicKeyRequest=4,
PublicKeyResponse=5, PublicKeyResponse=5,
Request=6,
Response=7,
ChatMessage=10, ChatMessage=10,
// NativeCall=11, // NativeCall=11,
@ -22,11 +24,13 @@ namespace RageCoop.Core
// Mod=13, // Mod=13,
// CleanUpWorld=14, // CleanUpWorld=14,
FileTransferChunk=15, FileTransferChunk=11,
FileTransferRequest=16, FileTransferRequest=12,
FileTransferComplete=17, FileTransferResponse = 13,
FileTransferComplete =14,
AllResourcesSent=15,
CustomEvent = 18, CustomEvent = 16,
#region Sync #region Sync
#region INTERVAL #region INTERVAL
@ -55,7 +59,7 @@ namespace RageCoop.Core
} }
public static class PacketExtensions 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); return (30<=(byte)p)&&((byte)p<=40);
} }
@ -69,6 +73,7 @@ namespace RageCoop.Core
Mod = 7, Mod = 7,
File = 8, File = 8,
Event = 9, Event = 9,
RequestResponse=10,
VehicleSync=20, VehicleSync=20,
PedSync=21, PedSync=21,
ProjectileSync = 22, ProjectileSync = 22,
@ -153,7 +158,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.ChatMessage); message.Write((byte)PacketType.ChatMessage);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -30,7 +30,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedStateSync); message.Write((byte)PacketType.PedStateSync);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -129,7 +129,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedSync); message.Write((byte)PacketType.PedSync);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -34,7 +34,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.Handshake); message.Write((byte)PacketType.Handshake);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -101,7 +101,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerConnect); message.Write((byte)PacketType.PlayerConnect);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -146,7 +146,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerDisconnect); message.Write((byte)PacketType.PlayerDisconnect);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -181,7 +181,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerInfoUpdate); message.Write((byte)PacketType.PlayerInfoUpdate);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -241,7 +241,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PublicKeyResponse); message.Write((byte)PacketType.PublicKeyResponse);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -272,7 +272,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PublicKeyRequest); message.Write((byte)PacketType.PublicKeyRequest);
#endregion #endregion
} }
public override void Unpack(byte[] array) public override void Unpack(byte[] array)

View File

@ -29,7 +29,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.ProjectileSync); message.Write((byte)PacketType.ProjectileSync);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -21,7 +21,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.BulletShot); message.Write((byte)PacketType.BulletShot);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -19,7 +19,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.EnteredVehicle); message.Write((byte)PacketType.EnteredVehicle);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -19,7 +19,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.EnteringVehicle); message.Write((byte)PacketType.EnteringVehicle);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -17,7 +17,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.LeaveVehicle); message.Write((byte)PacketType.LeaveVehicle);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -17,7 +17,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.NozzleTransform); message.Write((byte)PacketType.NozzleTransform);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -18,7 +18,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.OwnerChanged); message.Write((byte)PacketType.OwnerChanged);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -16,7 +16,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedKilled); message.Write((byte)PacketType.PedKilled);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -48,7 +48,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.VehicleStateSync); message.Write((byte)PacketType.VehicleStateSync);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();
@ -256,7 +256,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message) public override void Pack(NetOutgoingMessage message)
{ {
#region PacketToNetOutGoingMessage #region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.VehicleSync); message.Write((byte)PacketType.VehicleSync);
List<byte> byteArray = new List<byte>(); List<byte> byteArray = new List<byte>();

View File

@ -17,7 +17,7 @@ namespace RageCoop.Core.Scripting
public static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn"); public static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn");
public static readonly int NativeCall = Hash("RageCoop.NativeCall"); public static readonly int NativeCall = Hash("RageCoop.NativeCall");
public static readonly int NativeResponse = Hash("RageCoop.NativeResponse"); public static readonly int NativeResponse = Hash("RageCoop.NativeResponse");
public static readonly int CleanUpWorld = Hash("RageCoop.CleanUpWorld"); public static readonly int AllResourcesSent = Hash("RageCoop.AllResourcesSent");
/// <summary> /// <summary>
/// Get a Int32 hash of a string. /// Get a Int32 hash of a string.
/// </summary> /// </summary>

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace RageCoop.Core.Scripting
{
public class Resource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get;internal set; }
public List<Scriptable> Scripts { get; internal set; } = new List<Scriptable>();
public Dictionary<string,ResourceFile> Files { get; internal set; }=new Dictionary<string,ResourceFile>();
}
public class ResourceFile
{
public string Name { get; internal set; }
public bool IsDirectory { get; internal set; }
public Func<Stream> GetStream { get; internal set; }
}
}

View File

@ -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<Stream> GetStream { get; internal set; }
}
}

View File

@ -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<string> ToIgnore = new List<string>
{
"RageCoop.Client.dll",
"RageCoop.Core.dll",
"RageCoop.Server.dll",
"ScriptHookVDotNet3.dll"
};
protected List<Resource> LoadedResources = new List<Resource>();
private string BaseScriptType;
public Logger Logger { get; set; }
public ResourceLoader(string baseType,Logger logger)
{
BaseScriptType = baseType;
Logger = logger;
}
/// <summary>
/// Load a resource from a directory.
/// </summary>
/// <param name="path">Path of the directory.</param>
protected void LoadResource(string path,string dataFolderRoot)
{
var r = new Resource()
{
Scripts = new List<Scriptable>(),
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);
}
/// <summary>
/// Load a resource from a zip
/// </summary>
/// <param name="file"></param>
protected void LoadResource(ZipFile file,string dataFolderRoot)
{
var r = new Resource()
{
Scripts = new List<Scriptable>(),
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);
}
/// <summary>
/// Loads scripts from the specified assembly file.
/// </summary>
/// <param name="path">The path to the assembly file to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
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);
}
}
/// <summary>
/// Loads scripts from the specified assembly object.
/// </summary>
/// <param name="filename">The path to the file associated with this assembly.</param>
/// <param name="assembly">The assembly to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
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;
}
}
}

View File

@ -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();
/// <summary>
/// Get the <see cref="ResourceFile"/> instance where this script is loaded from.
/// </summary>
public ResourceFile CurrentFile { get; internal set; }
/// <summary>
/// Get the <see cref="Resource"/> object this script belongs to, this property will be initiated before <see cref="OnStart"/> (will be null if you access it in the constructor).
/// </summary>
public Resource CurrentResource { get; internal set; }
}
}

View File

@ -121,14 +121,6 @@ namespace RageCoop.Server
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
} }
} }
/// <summary>
/// Send a CleanUpWorld message to this client.
/// </summary>
/// <param name="clients"></param>
public void SendCleanUpWorld(List<Client> clients = null)
{
SendCustomEvent(CustomEvents.CleanUpWorld, null);
}
/// <summary> /// <summary>
/// Send a native call to client and do a callback when the response received. /// Send a native call to client and do a callback when the response received.

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net6.0\publish\linux-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>True</PublishSingleFile>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<History>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;</History>
</PropertyGroup>
</Project>

View File

@ -18,6 +18,7 @@
<Description>An library for hosting a RAGECOOP server or API reference for developing a resource.</Description> <Description>An library for hosting a RAGECOOP server or API reference for developing a resource.</Description>
<ApplicationIcon>icon.ico</ApplicationIcon> <ApplicationIcon>icon.ico</ApplicationIcon>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -13,6 +13,11 @@ namespace RageCoop.Server.Scripting
{ {
public class APIEvents public class APIEvents
{ {
private readonly Server Server;
internal APIEvents(Server server)
{
Server = server;
}
#region INTERNAL #region INTERNAL
internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new(); internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new();
#endregion #endregion
@ -52,12 +57,12 @@ namespace RageCoop.Server.Scripting
#region INVOKE #region INVOKE
internal void InvokePlayerHandshake(HandshakeEventArgs args) internal void InvokePlayerHandshake(HandshakeEventArgs args)
{ OnPlayerHandshake?.Invoke(this, 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() var args = new OnCommandEventArgs()
{ {
Name=cname, Name=cmdName,
Args=cargs, Args=cmdArgs,
Sender=sender Sender=sender
}; };
OnCommandReceived?.Invoke(this, args); OnCommandReceived?.Invoke(this, args);
@ -65,7 +70,7 @@ namespace RageCoop.Server.Scripting
{ {
return; return;
} }
if (Commands.Any(x => x.Key.Name == cmdName)) if (Server.Commands.Any(x => x.Key.Name == cmdName))
{ {
string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray(); string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray();
@ -75,13 +80,13 @@ namespace RageCoop.Server.Scripting
Args = argsWithoutCmd Args = argsWithoutCmd
}; };
KeyValuePair<Command, Action<CommandContext>> command = Commands.First(x => x.Key.Name == cmdName); KeyValuePair<Command, Action<CommandContext>> command = Server.Commands.First(x => x.Key.Name == cmdName);
command.Value.Invoke(ctx); command.Value.Invoke(ctx);
} }
else 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) internal API(Server server)
{ {
Server=server; Server=server;
Events=new(server);
} }
public APIEvents Events { get; set; }=new APIEvents(); public readonly APIEvents Events;
#region FUNCTIONS #region FUNCTIONS
/* /*
/// <summary> /// <summary>
@ -220,10 +226,6 @@ namespace RageCoop.Server.Scripting
/// <summary> /// <summary>
/// Send CleanUpWorld to all players to delete all objects created by the server /// Send CleanUpWorld to all players to delete all objects created by the server
/// </summary> /// </summary>
public void SendCleanUpWorldToAll(List<Client> clients = null)
{
SendCustomEvent(CustomEvents.CleanUpWorld,null,clients);
}
/// <summary> /// <summary>
/// Register a new command chat command (Example: "/test") /// Register a new command chat command (Example: "/test")

View File

@ -4,17 +4,22 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using RageCoop.Core;
using System.IO; using System.IO;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using System.Reflection; using System.Reflection;
using McMaster.NETCore.Plugins;
namespace RageCoop.Server.Scripting namespace RageCoop.Server.Scripting
{ {
internal class Resources : ResourceLoader internal class Resources
{ {
private Dictionary<string,ServerResource> LoadedResources=new();
private readonly Server Server; 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; Server = server;
Logger=server.Logger;
} }
private List<string> ClientResourceZips=new List<string>(); private List<string> ClientResourceZips=new List<string>();
public void LoadAll() public void LoadAll()
@ -22,7 +27,7 @@ namespace RageCoop.Server.Scripting
// Client // Client
{ {
var path = Path.Combine("Resources", "Client"); var path = Path.Combine("Resources", "Client");
var tmpDir = Path.Combine("Resources", "Temp"); var tmpDir = Path.Combine("Resources", "Temp","Client");
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
if (Directory.Exists(tmpDir)) if (Directory.Exists(tmpDir))
{ {
@ -76,20 +81,50 @@ namespace RageCoop.Server.Scripting
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path)) foreach (var resource in Directory.GetDirectories(path))
{ {
if (Path.GetFileName(resource).ToLower()=="data") { continue; } try
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); {
LoadResource(resource, dataFolder); 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)) foreach (var resource in Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly))
{ {
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); try
LoadResource(new ZipFile(resource), dataFolder); {
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 // Start scripts
lock (LoadedResources) lock (LoadedResources)
{ {
foreach (var r in LoadedResources) foreach (var r in LoadedResources.Values)
{ {
foreach (ServerScript s in r.Scripts) foreach (ServerScript s in r.Scripts)
{ {
@ -106,11 +141,11 @@ namespace RageCoop.Server.Scripting
} }
} }
public void StopAll() public void UnloadAll()
{ {
lock (LoadedResources) lock (LoadedResources)
{ {
foreach (var d in LoadedResources) foreach (var d in LoadedResources.Values)
{ {
foreach (var s in d.Scripts) foreach (var s in d.Scripts)
{ {
@ -123,27 +158,86 @@ namespace RageCoop.Server.Scripting
Logger?.Error(ex); 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) public void SendTo(Client client)
{
Task.Run(() =>
{ {
if (ClientResourceZips.Count!=0) if (ClientResourceZips.Count!=0)
{
Task.Run(() =>
{ {
Logger?.Info($"Sending resources to client:{client.Username}"); Logger?.Info($"Sending resources to client:{client.Username}");
foreach (var rs in ClientResourceZips)
{
using (var fs = File.OpenRead(rs))
{
Server.SendFile(rs, Path.GetFileName(rs), client);
}
}
Logger?.Info($"Resources sent to:{client.Username}"); Logger?.Info($"Resources sent to:{client.Username}");
}
}); if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent())?.Response==FileResponse.Loaded)
{
client.IsReady=true;
Server.API.Events.InvokePlayerReady(client);
} }
else else
{ {
client.IsReady=true; Logger?.Warning($"Client {client.Username} failed to load resource.");
}
});
}
/// <summary>
/// Load a resource from a zip
/// </summary>
/// <param name="file"></param>
/*
private void LoadResource(ZipFile file, string dataFolderRoot)
{
var r = new Resource()
{
Scripts = new List<Scriptable>(),
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);
}
*/
}
} }

View File

@ -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<string> 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<ServerScript>();
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);
}
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
public List<ServerScript> Scripts { get; internal set; } = new List<ServerScript>();
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
/// <summary>
/// Loads scripts from the specified assembly object.
/// </summary>
/// <param name="filename">The path to the file associated with this assembly.</param>
/// <param name="assembly">The assembly to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
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();
}
}
}

View File

@ -6,19 +6,25 @@ namespace RageCoop.Server.Scripting
/// <summary> /// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and <see cref="API"/> will be null, you should use <see cref="OnStart"/>. to initiate your script. /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and <see cref="API"/> will be null, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary> /// </summary>
public abstract class ServerScript : Scriptable public abstract class ServerScript
{ {
/// <summary> /// <summary>
/// This method would be called from main thread after all scripts have been loaded. /// This method would be called from main thread after all scripts have been loaded.
/// </summary> /// </summary>
public override abstract void OnStart(); public abstract void OnStart();
/// <summary> /// <summary>
/// This method would be called from main thread when the server is shutting down, you MUST terminate all background jobs/threads in this method. /// This method would be called from main thread when the server is shutting down, you MUST terminate all background jobs/threads in this method.
/// </summary> /// </summary>
public override abstract void OnStop(); public abstract void OnStop();
public API API { get; set; } public API API { get; set; }
/// <summary>
/// Get the <see cref="ClientResource"/> object this script belongs to, this property will be initiated before <see cref="OnStart"/> (will be null if you access it in the constructor).
/// </summary>
public ServerResource CurrentResource { get; internal set; }
public ResourceFile CurrentFile { get; internal set; }
} }
[AttributeUsage(AttributeTargets.Method, Inherited = false)] [AttributeUsage(AttributeTargets.Method, Inherited = false)]

View File

@ -43,6 +43,8 @@ namespace RageCoop.Server
private Thread _listenerThread; private Thread _listenerThread;
private Thread _announceThread; private Thread _announceThread;
private Worker _worker; private Worker _worker;
private Dictionary<int,Action<PacketType,byte[]>> PendingResponses=new();
private Dictionary<PacketType, Func<byte[],Packet>> RequestHandlers=new();
private readonly string _compatibleVersion = "V0_5"; private readonly string _compatibleVersion = "V0_5";
public Server(ServerSettings settings,Logger logger=null) public Server(ServerSettings settings,Logger logger=null)
{ {
@ -78,7 +80,7 @@ namespace RageCoop.Server
MainNetServer = new NetServer(config); MainNetServer = new NetServer(config);
MainNetServer.Start(); MainNetServer.Start();
_worker=new Worker("ServerWorker"); _worker=new Worker("ServerWorker",Logger);
_sendInfoTimer.Elapsed+=(s, e) => { SendPlayerInfos(); }; _sendInfoTimer.Elapsed+=(s, e) => { SendPlayerInfos(); };
_sendInfoTimer.AutoReset=true; _sendInfoTimer.AutoReset=true;
_sendInfoTimer.Enabled=true; _sendInfoTimer.Enabled=true;
@ -221,7 +223,7 @@ namespace RageCoop.Server
Logger?.Info("Server is shutting down!"); Logger?.Info("Server is shutting down!");
MainNetServer.Shutdown("Server is shutting down!"); MainNetServer.Shutdown("Server is shutting down!");
BaseScript.OnStop(); BaseScript.OnStop();
Resources.StopAll(); Resources.UnloadAll();
} }
private void ProcessMessage(NetIncomingMessage message) private void ProcessMessage(NetIncomingMessage message)
@ -233,7 +235,7 @@ namespace RageCoop.Server
case NetIncomingMessageType.ConnectionApproval: case NetIncomingMessageType.ConnectionApproval:
{ {
Logger?.Info($"New incoming connection from: [{message.SenderConnection.RemoteEndPoint}]"); 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!"); Logger?.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: Wrong packet!");
message.SenderConnection.Deny("Wrong packet!"); message.SenderConnection.Deny("Wrong packet!");
@ -275,121 +277,47 @@ namespace RageCoop.Server
else if (status == NetConnectionStatus.Connected) else if (status == NetConnectionStatus.Connected)
{ {
SendPlayerConnectPacket(sender); SendPlayerConnectPacket(sender);
_worker.QueueJob(() => API.Events.InvokePlayerConnected(sender));
Resources.SendTo(sender); Resources.SendTo(sender);
_worker.QueueJob(()=> API.Events.InvokePlayerConnected(sender));
if (sender.IsReady)
{
_worker.QueueJob(()=>API.Events.InvokePlayerReady(sender));
}
} }
break; break;
} }
case NetIncomingMessageType.Data: 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 // Get sender client
if (!Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender)) 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) switch (type)
{ {
case PacketType.Response:
#region SyncData
case PacketTypes.PedStateSync:
{ {
Packets.PedStateSync packet = new(); int id = message.ReadInt32();
packet.Unpack(data); if (PendingResponses.TryGetValue(id, out var callback))
PedStateSync(packet, sender);
break;
}
case PacketTypes.VehicleStateSync:
{ {
Packets.VehicleStateSync packet = new(); callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32()));
packet.Unpack(data); PendingResponses.Remove(id);
VehicleStateSync(packet, sender);
break;
}
case PacketTypes.PedSync:
{
Packets.PedSync packet = new();
packet.Unpack(data);
PedSync(packet, sender);
} }
break; break;
case PacketTypes.VehicleSync: }
case PacketType.Request:
{ {
Packets.VehicleSync packet = new(); int id = message.ReadInt32();
packet.Unpack(data); if (RequestHandlers.TryGetValue((PacketType)message.ReadByte(), out var handler))
{
VehicleSync(packet, sender); 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;
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))
{
toRemove.Cancel=true;
if (toRemove.Name=="Resources.zip")
{
sender.IsReady=true;
_worker.QueueJob(() => API.Events.InvokePlayerReady(sender));
}
}
}
break;
default: default:
{
byte[] data = message.ReadBytes(message.ReadInt32());
if (type.IsSyncEvent()) if (type.IsSyncEvent())
{ {
// Sync Events // Sync Events
@ -399,8 +327,8 @@ namespace RageCoop.Server
if (toSend.Count!=0) if (toSend.Count!=0)
{ {
var outgoingMessage = MainNetServer.CreateMessage(); var outgoingMessage = MainNetServer.CreateMessage();
outgoingMessage.Write(btype); outgoingMessage.Write((byte)type);
outgoingMessage.Write(len); outgoingMessage.Write(data.Length);
outgoingMessage.Write(data); outgoingMessage.Write(data);
MainNetServer.SendMessage(outgoingMessage, toSend, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.SyncEvents); MainNetServer.SendMessage(outgoingMessage, toSend, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.SyncEvents);
} }
@ -412,14 +340,12 @@ namespace RageCoop.Server
} }
else else
{ {
Logger?.Error("Unhandled Data / Packet type"); HandlePacket(type, data, sender);
} }
break; break;
} }
} }
catch (Exception e)
{
DisconnectAndLog(message.SenderConnection, type, e);
} }
break; break;
} }
@ -448,7 +374,7 @@ namespace RageCoop.Server
break; break;
case NetIncomingMessageType.UnconnectedData: case NetIncomingMessageType.UnconnectedData:
{ {
if (message.ReadByte()==(byte)PacketTypes.PublicKeyRequest) if (message.ReadByte()==(byte)PacketType.PublicKeyRequest)
{ {
var msg = MainNetServer.CreateMessage(); var msg = MainNetServer.CreateMessage();
var p=new Packets.PublicKeyResponse(); var p=new Packets.PublicKeyResponse();
@ -466,6 +392,98 @@ namespace RageCoop.Server
MainNetServer.Recycle(message); 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(); object _sendPlayersLock=new object();
internal void SendPlayerInfos() 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($"Error receiving a packet of type {type}");
Logger?.Error(e.Message); Logger?.Error(e.Message);
@ -811,31 +829,64 @@ namespace RageCoop.Server
RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength, (Action<CommandContext>)Delegate.CreateDelegate(typeof(Action<CommandContext>), method)); RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength, (Action<CommandContext>)Delegate.CreateDelegate(typeof(Action<CommandContext>), method));
} }
} }
internal T GetResponse<T>(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<float> updateCallback=null) internal void SendFile(string path,string name,Client client,Action<float> updateCallback=null)
{ {
int id = RequestFileID();
int id = NewFileID();
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
fs.Seek(0, SeekOrigin.Begin); fs.Seek(0, SeekOrigin.Begin);
var total = fs.Length; var total = fs.Length;
if (GetResponse<Packets.FileTransferResponse>(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}"); Logger?.Debug($"Initiating file transfer:{name}, {total}");
FileTransfer transfer = new() FileTransfer transfer = new()
{ {
ID=id, ID=id,
Name = name, Name = name,
}; };
InProgressFileTransfers.Add(id,transfer); InProgressFileTransfers.Add(id, transfer);
Send(
new Packets.FileTransferRequest()
{
FileLength= total,
Name=name,
ID=id,
},
client, ConnectionChannel.File, NetDeliveryMethod.ReliableOrdered
);
int read = 0; int read = 0;
int thisRead = 0; int thisRead;
do do
{ {
// 4 KB chunk // 4 KB chunk
@ -858,19 +909,19 @@ namespace RageCoop.Server
if (updateCallback!=null) { updateCallback(transfer.Progress);} if (updateCallback!=null) { updateCallback(transfer.Progress);}
} while (thisRead>0); } while (thisRead>0);
Send( if(GetResponse<Packets.FileTransferResponse>(client, new Packets.FileTransferComplete()
new Packets.FileTransferComplete()
{ {
ID= id, ID= id,
},ConnectionChannel.File)?.Response!=FileResponse.Completed)
{
Logger.Warning($"File trasfer to {client.Username} failed: "+name);
} }
, client, ConnectionChannel.File, NetDeliveryMethod.ReliableOrdered
);
fs.Close(); fs.Close();
fs.Dispose(); fs.Dispose();
Logger?.Debug($"All file chunks sent:{name}"); Logger?.Debug($"All file chunks sent:{name}");
InProgressFileTransfers.Remove(id); InProgressFileTransfers.Remove(id);
} }
private int RequestFileID() private int NewFileID()
{ {
int ID = 0; int ID = 0;
while ((ID==0) while ((ID==0)
@ -885,6 +936,21 @@ namespace RageCoop.Server
} }
return ID; 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;
}
/// <summary> /// <summary>
/// Pack the packet then send to server. /// Pack the packet then send to server.

Binary file not shown.

BIN
libs/ScriptHookVDotNet.dll Normal file

Binary file not shown.