Resource loading
This commit is contained in:
parent
58362c2613
commit
8a46bd6b68
@ -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
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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..."); });
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
13
RageCoop.Core/Scripting/ResourceFile.cs
Normal file
13
RageCoop.Core/Scripting/ResourceFile.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
150
RageCoop.Server/Scripting/ServerResource.cs
Normal file
150
RageCoop.Server/Scripting/ServerResource.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)]
|
||||
|
@ -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
BIN
libs/ScriptHookVDotNet.dll
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user