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)
- [ClearScript](https://github.com/microsoft/ClearScript)
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
# Features

View File

@ -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<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}");
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;
}
/// <summary>
@ -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
{

View File

@ -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..."); });
});
}

View File

@ -16,6 +16,9 @@ namespace RageCoop.Client
internal static partial class Networking
{
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)
{
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)
{

View File

@ -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
}

View File

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

View File

@ -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()

View File

@ -1,19 +1,26 @@
namespace RageCoop.Client.Scripting
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary>
public abstract class ClientScript:Core.Scripting.Scriptable
public abstract class ClientScript
{
/// <summary>
/// This method would be called from main thread shortly after all scripts have been loaded.
/// </summary>
public override abstract void OnStart();
public abstract void OnStart();
/// <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.
/// </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 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) { }
/// <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()
{
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<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).
/// </summary>
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
{
private static bool _trafficSpawning=true;
public static object PedsLock = new object();
private static Dictionary<int, SyncedPed> ID_Peds = new Dictionary<int, SyncedPed>();
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));

View File

@ -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);

View File

@ -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;
}
}
}

View File

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

View File

@ -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<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 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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
@ -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
}
}
}
}

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
@ -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<byte> byteArray = new List<byte>();
@ -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)

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();

View File

@ -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<byte> byteArray = new List<byte>();
@ -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<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 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");
/// <summary>
/// Get a Int32 hash of a string.
/// </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} <<");
}
}
/// <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>
/// 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>
<ApplicationIcon>icon.ico</ApplicationIcon>
<PackageIcon>icon.png</PackageIcon>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@ -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<int, List<Action<CustomEventReceivedArgs>>> 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, 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);
}
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
/*
/// <summary>
@ -220,10 +226,6 @@ namespace RageCoop.Server.Scripting
/// <summary>
/// Send CleanUpWorld to all players to delete all objects created by the server
/// </summary>
public void SendCleanUpWorldToAll(List<Client> clients = null)
{
SendCustomEvent(CustomEvents.CleanUpWorld,null,clients);
}
/// <summary>
/// Register a new command chat command (Example: "/test")

View File

@ -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<string,ServerResource> 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<string> ClientResourceZips=new List<string>();
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<Packets.FileTransferResponse>(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.");
}
});
}
/// <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>
/// 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>
public abstract class ServerScript : Scriptable
public abstract class ServerScript
{
/// <summary>
/// This method would be called from main thread after all scripts have been loaded.
/// </summary>
public override abstract void OnStart();
public abstract void OnStart();
/// <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.
/// </summary>
public override abstract void OnStop();
public abstract void OnStop();
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)]

View File

@ -43,6 +43,8 @@ namespace RageCoop.Server
private Thread _listenerThread;
private Thread _announceThread;
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";
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<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)
{
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<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}");
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<Packets.FileTransferResponse>(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;
}
/// <summary>
/// Pack the packet then send to server.

Binary file not shown.

BIN
libs/ScriptHookVDotNet.dll Normal file

Binary file not shown.