Reworked on resource loading, Added RegisterCommands overload to register commands from an instance.

This commit is contained in:
Sardelka
2022-06-24 16:49:59 +08:00
parent 82ab9237f5
commit 6167417bf8
19 changed files with 301 additions and 131 deletions

View File

@ -31,6 +31,8 @@ namespace RageCoop.Client.Menus
public static NativeMenu LastMenu { get; set; } = Menu; public static NativeMenu LastMenu { get; set; } = Menu;
#region ITEMS #region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username }; private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress }; public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
private static readonly NativeItem _serverConnectItem = new NativeItem("Connect"); private static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" + private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
@ -51,6 +53,7 @@ namespace RageCoop.Client.Menus
Menu.Title.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated; _usernameItem.Activated += UsernameActivated;
_passwordItem.Activated+=_passwordActivated;
ServerIpItem.Activated += ServerIpActivated; ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); }; _serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
@ -58,6 +61,7 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(ServersMenu.Menu); Menu.AddSubMenu(ServersMenu.Menu);
Menu.Add(_usernameItem); Menu.Add(_usernameItem);
Menu.Add(_passwordItem);
Menu.Add(ServerIpItem); Menu.Add(ServerIpItem);
Menu.Add(_serverConnectItem); Menu.Add(_serverConnectItem);
@ -76,6 +80,8 @@ namespace RageCoop.Client.Menus
Menu.Add(_aboutItem); Menu.Add(_aboutItem);
} }
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground) public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
{ {
PopUp.Prompt=prompt; PopUp.Prompt=prompt;
@ -113,6 +119,16 @@ namespace RageCoop.Client.Menus
} }
} }
private static void _passwordActivated(object sender, System.EventArgs e)
{
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
if (!string.IsNullOrWhiteSpace(newPass))
{
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
}
public static void ServerIpActivated(object a, System.EventArgs b) public static void ServerIpActivated(object a, System.EventArgs b)
{ {
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60); string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);

View File

@ -39,7 +39,7 @@ namespace RageCoop.Client
}); });
} }
public static void ToggleConnection(string address,string password=null) public static void ToggleConnection(string address,string username=null,string password=null)
{ {
if (IsOnServer) if (IsOnServer)
{ {
@ -48,6 +48,7 @@ namespace RageCoop.Client
else else
{ {
password = password ?? Main.Settings.Password; password = password ?? Main.Settings.Password;
username=username ?? Main.Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP // 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0") NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{ {
@ -85,7 +86,7 @@ namespace RageCoop.Client
var handshake=new Packets.Handshake() var handshake=new Packets.Handshake()
{ {
PedID = Main.LocalPlayerID, PedID = Main.LocalPlayerID,
Username = Main.Settings.Username, Username =username,
ModVersion = Main.CurrentVersion, ModVersion = Main.CurrentVersion,
PassHashEncrypted=Security.Encrypt(password.GetHash()) PassHashEncrypted=Security.Encrypt(password.GetHash())
}; };

View File

@ -3,7 +3,6 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<OutputPath>D:\Games\Grand Theft Auto V\Scripts\RageCoop</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly> <ProduceReferenceAssembly>False</ProduceReferenceAssembly>
@ -13,13 +12,26 @@
<AssemblyVersion>0.5.0</AssemblyVersion> <AssemblyVersion>0.5.0</AssemblyVersion>
<FileVersion>0.5.0</FileVersion> <FileVersion>0.5.0</FileVersion>
<Version>0.5.0</Version> <Version>0.5.0</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>RAGECOOP</Authors>
<Description>An API reference for developing client-side resource for RAGECOOP</Description>
<PackageProjectUrl>https://ragecoop.online/</PackageProjectUrl>
<RepositoryUrl>https://github.com/RAGECOOP/RAGECOOP-V</RepositoryUrl>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize> <Optimize>True</Optimize>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DotNetZip" Version="1.16.0" /> <Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" /> <ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />

View File

@ -31,11 +31,7 @@ namespace RageCoop.Client.Scripting
List<InputArgument> arguments = new List<InputArgument>(); List<InputArgument> arguments = new List<InputArgument>();
int i; int i;
var ty = (byte)e.Args[0]; var ty = (byte)e.Args[0];
Main.Logger.Debug($"gettype");
Main.Logger.Flush();
TypeCode returnType=(TypeCode)ty; TypeCode returnType=(TypeCode)ty;
Main.Logger.Debug($"typeok");
Main.Logger.Flush();
i = returnType==TypeCode.Empty ? 1 : 2; i = returnType==TypeCode.Empty ? 1 : 2;
var hash = (Hash)e.Args[i++]; var hash = (Hash)e.Args[i++];
for(; i<e.Args.Count;i++) for(; i<e.Args.Count;i++)

View File

@ -3,21 +3,17 @@
/// <summary> /// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script. /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary> /// </summary>
public abstract class ClientScript:Core.Scripting.IScriptable public abstract class ClientScript:Core.Scripting.Scriptable
{ {
/// <summary> /// <summary>
/// This method would be called from main thread shortly after all scripts have been loaded. /// This method would be called from main thread shortly after all scripts have been loaded.
/// </summary> /// </summary>
public abstract void OnStart(); public override abstract void OnStart();
/// <summary> /// <summary>
/// This method would be called from main thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method. /// This method would be called from main thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method.
/// </summary> /// </summary>
public abstract void OnStop(); public override abstract void OnStop();
/// <summary>
/// Get the <see cref="Core.Scripting.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 Core.Scripting.Resource CurrentResource { get; internal set; }
} }
} }

View File

@ -1,6 +1,6 @@
using System.IO; using System.IO;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using Ionic.Zip; using ICSharpCode.SharpZipLib.Zip;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
@ -15,7 +15,6 @@ namespace RageCoop.Client.Scripting
{ {
foreach (var s in d.Scripts) foreach (var s in d.Scripts)
{ {
(s as ClientScript).CurrentResource=d;
Main.QueueAction(() => s.OnStart()); Main.QueueAction(() => s.OnStart());
} }
} }
@ -37,23 +36,23 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Load all resources from the server /// Load all resources from the server
/// </summary> /// </summary>
/// <param name="path">The path to the directory containing the resources.</param> /// <param name="path">The path to the directory containing all resources to load.</param>
public void Load(string path) public void Load(string path)
{ {
Unload(); Unload();
foreach (var d in Directory.GetDirectories(path)) foreach (var d in Directory.GetDirectories(path))
{ {
Directory.Delete(d, true); if(Path.GetFileName(d).ToLower() != "data")
} {
using (var zip = ZipFile.Read(Path.Combine(path, "Resources.zip"))) Directory.Delete(d, true);
{ }
zip.ExtractAll(path, ExtractExistingFileAction.OverwriteSilently);
} }
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path)) foreach (var resource in Directory.GetDirectories(path))
{ {
if (Path.GetFileName(resource).ToLower()!="data") { continue; }
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); Logger?.Info($"Loading resource: {Path.GetFileName(resource)}");
LoadResource(resource); LoadResource(resource,Path.Combine(path,"data"));
} }
StartAll(); StartAll();
} }

BIN
RageCoop.Client/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -22,6 +22,11 @@
<None Include="Lidgren.Network\Lidgren.Network.csproj" /> <None Include="Lidgren.Network\Lidgren.Network.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath> <HintPath>..\libs\Newtonsoft.Json.dll</HintPath>

View File

@ -1,15 +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 interface IScriptable
{
void OnStart();
void OnStop();
}
}

View File

@ -0,0 +1,27 @@
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

@ -5,23 +5,13 @@ using System.Reflection;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ICSharpCode.SharpZipLib.Zip;
[assembly: InternalsVisibleTo("RageCoop.Server")] [assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")] [assembly: InternalsVisibleTo("RageCoop.Client")]
namespace RageCoop.Core.Scripting namespace RageCoop.Core.Scripting
{ {
public class Resource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get;internal set; }
/// <summary>
/// The directory of current resource
/// </summary>
public string Directory { get;internal set; }
public List<IScriptable> Scripts { get; internal set; }=new List<IScriptable>();
}
internal class ResourceLoader internal class ResourceLoader
{ {
protected List<string> ToIgnore = new List<string> protected List<string> ToIgnore = new List<string>
@ -40,20 +30,77 @@ namespace RageCoop.Core.Scripting
Logger = logger; Logger = logger;
} }
/// <summary> /// <summary>
/// Load all assemblies inside the resource directory. /// Load a resource from a directory.
/// </summary> /// </summary>
/// <param name="path">Path of the directory.</param> /// <param name="path">Path of the directory.</param>
protected void LoadResource(string path) protected void LoadResource(string path,string dataFolderRoot)
{ {
var r = new Resource() var r = new Resource()
{ {
Scripts = new List<IScriptable>(), Scripts = new List<Scriptable>(),
Name=Path.GetFileName(path), Name=Path.GetFileName(path),
Directory=path, DataFolder=Path.Combine(dataFolderRoot, Path.GetFileName(path))
}; };
foreach (var f in Directory.GetFiles(path, "*.dll")) Directory.CreateDirectory(r.DataFolder);
foreach (var dir in Directory.GetDirectories(path, "*", SearchOption.AllDirectories))
{ {
LoadScriptsFromAssembly(f, r); 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); LoadedResources.Add(r);
} }
@ -62,31 +109,38 @@ namespace RageCoop.Core.Scripting
/// </summary> /// </summary>
/// <param name="path">The path to the assembly file to load.</param> /// <param name="path">The path to the assembly file to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns> /// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
protected bool LoadScriptsFromAssembly(string path, Resource domain) private bool LoadScriptsFromAssembly(ResourceFile file,string path, Resource resource,bool shadowCopy=true)
{ {
lock (LoadedResources) lock (LoadedResources)
{ {
if (!IsManagedAssembly(path)) { return false; } if (!IsManagedAssembly(path)) { return false; }
if (ToIgnore.Contains(Path.GetFileName(path))) { try { File.Delete(path); } catch { }; return false; } if (ToIgnore.Contains(file.Name)) { try { File.Delete(path); } catch { }; return false; }
Logger?.Debug($"Loading assembly {Path.GetFileName(path)} ..."); Logger?.Debug($"Loading assembly {file.Name} ...");
Assembly assembly; Assembly assembly;
try try
{ {
var temp = Path.GetTempFileName(); if (shadowCopy)
File.Copy(path, temp, true); {
assembly = Assembly.LoadFrom(temp); var temp = Path.GetTempFileName();
File.Copy(path, temp, true);
assembly = Assembly.LoadFrom(temp);
}
else
{
assembly = Assembly.LoadFrom(path);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger?.Error("Unable to load "+Path.GetFileName(path)); Logger?.Error("Unable to load "+file.Name);
Logger?.Error(ex); Logger?.Error(ex);
return false; return false;
} }
return LoadScriptsFromAssembly(assembly, path, domain); return LoadScriptsFromAssembly(file,assembly, path, resource);
} }
} }
/// <summary> /// <summary>
@ -95,7 +149,7 @@ namespace RageCoop.Core.Scripting
/// <param name="filename">The path to the file associated with this assembly.</param> /// <param name="filename">The path to the file associated with this assembly.</param>
/// <param name="assembly">The assembly to load.</param> /// <param name="assembly">The assembly to load.</param>
/// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns> /// <returns><see langword="true" /> on success, <see langword="false" /> otherwise</returns>
private bool LoadScriptsFromAssembly(Assembly assembly, string filename, Resource toload) private bool LoadScriptsFromAssembly(ResourceFile rfile,Assembly assembly, string filename, Resource toload)
{ {
int count = 0; int count = 0;
@ -110,7 +164,10 @@ namespace RageCoop.Core.Scripting
try try
{ {
// Invoke script constructor // Invoke script constructor
toload.Scripts.Add(constructor.Invoke(null) as IScriptable); var script = constructor.Invoke(null) as Scriptable;
script.CurrentResource = toload;
script.CurrentFile=rfile;
toload.Scripts.Add(script);
count++; count++;
} }
catch (Exception ex) catch (Exception ex)
@ -127,7 +184,7 @@ namespace RageCoop.Core.Scripting
} }
catch (ReflectionTypeLoadException ex) catch (ReflectionTypeLoadException ex)
{ {
Logger?.Error($"Failed to load assembly {Path.GetFileName(filename)}: "); Logger?.Error($"Failed to load assembly {rfile.Name}: ");
Logger?.Error(ex); Logger?.Error(ex);
foreach (var e in ex.LoaderExceptions) foreach (var e in ex.LoaderExceptions)
{ {
@ -136,7 +193,7 @@ namespace RageCoop.Core.Scripting
return false; return false;
} }
Logger?.Info($"Loaded {count} script(s) in {Path.GetFileName(filename)}"); Logger?.Info($"Loaded {count} script(s) in {rfile.Name}");
return count != 0; return count != 0;
} }
private bool IsManagedAssembly(string filename) private bool IsManagedAssembly(string filename)

View File

@ -0,0 +1,25 @@
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

@ -9,11 +9,14 @@
<PackageProjectUrl>https://ragecoop.online/</PackageProjectUrl> <PackageProjectUrl>https://ragecoop.online/</PackageProjectUrl>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<Product>$(AssemblyName)-V</Product> <Product>$(AssemblyName)</Product>
<PackageId>RAGECOOP-V</PackageId> <PackageId>RageCoop.Server</PackageId>
<Authors>RAGECOOP</Authors> <Authors>RAGECOOP</Authors>
<Version>0.5</Version> <Version>0.5</Version>
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Description>An library for hosting a RAGECOOP server or API reference for developing a resource.</Description>
<ApplicationIcon>favicon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -23,7 +26,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DotNetZip" Version="1.16.0" /> <Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System.Reflection;
using System.Net; using System.Net;
namespace RageCoop.Server.Scripting namespace RageCoop.Server.Scripting
@ -221,14 +222,30 @@ namespace RageCoop.Server.Scripting
} }
/// <summary> /// <summary>
/// Register a class of commands /// Register all commands in a static class
/// </summary> /// </summary>
/// <typeparam name="T">The name of your class with functions</typeparam> /// <typeparam name="T">Your static class with commands</typeparam>
public void RegisterCommands<T>() public void RegisterCommands<T>()
{ {
Server.RegisterCommands<T>(); Server.RegisterCommands<T>();
} }
/// <summary>
/// Register all commands inside an class instance
/// </summary>
/// <param name="obj">The instance of type containing the commands</param>
public void RegisterCommands(object obj)
{
IEnumerable<MethodInfo> commands = obj.GetType().GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
foreach (MethodInfo method in commands)
{
Command attribute = method.GetCustomAttribute<Command>(true);
RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength,
(ctx) => { method.Invoke(obj, new object[] { ctx }); });
}
}
/// <summary> /// <summary>
/// Send an event and data to the specified clients. Use <see cref="Client.SendCustomEvent(int, List{object})"/> if you want to send event to individual client. /// Send an event and data to the specified clients. Use <see cref="Client.SendCustomEvent(int, List{object})"/> if you want to send event to individual client.
/// </summary> /// </summary>

View File

@ -34,7 +34,7 @@ namespace RageCoop.Server.Scripting
public string Username { get; set; } public string Username { get; set; }
/// <summary> /// <summary>
/// The hashed value of client password, sent with RSA asymmetric encryption. /// The client password hashed with SHA256 algorithm.
/// </summary> /// </summary>
public string PasswordHash { get; set; } public string PasswordHash { get; set; }
public IPEndPoint EndPoint { get; set; } public IPEndPoint EndPoint { get; set; }

View File

@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using System.IO; using System.IO;
using Ionic.Zip; using ICSharpCode.SharpZipLib.Zip;
using System.Reflection; using System.Reflection;
namespace RageCoop.Server.Scripting namespace RageCoop.Server.Scripting
{ {
@ -16,64 +16,95 @@ namespace RageCoop.Server.Scripting
{ {
Server = server; Server = server;
} }
private List<string> ClientResourceZips=new List<string>();
public bool HasClientResources = false; public bool HasClientResources { get; private set; }
public void LoadAll() public void LoadAll()
{ {
#region CLIENT #region CLIENT
var path = Path.Combine("Resources", "Client"); var path = Path.Combine("Resources", "Client");
var tmp = Path.Combine("Resources", "ClientTemp");
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
var clientResources = Directory.GetDirectories(path); if (Directory.Exists(tmp))
if (clientResources.Length!=0) {
foreach(var dir in Directory.GetDirectories(tmp))
{
Directory.Delete(dir, true);
}
}
else
{
Directory.CreateDirectory(tmp);
}
var resourceFolders = Directory.GetDirectories(path,"*",SearchOption.TopDirectoryOnly);
if (resourceFolders.Length!=0)
{ {
// Pack client side resources as a zip file HasClientResources=true;
Logger?.Info("Packing client-side resources"); foreach (var resourceFolder in resourceFolders)
{
try // Pack client side resource as a zip file
{ Logger?.Info("Packing client-side resource:"+resourceFolder);
var zippath = Path.Combine(path, "Resources.zip"); var zipPath = Path.Combine(tmp, Path.GetFileName(resourceFolder));
if (File.Exists(zippath)) try
{ {
File.Delete(zippath); using (ZipFile zip = ZipFile.Create(zipPath))
{
foreach (var dir in Directory.GetDirectories(resourceFolder, "*", SearchOption.AllDirectories))
{
zip.AddDirectory(dir.Substring(resourceFolder.Length+1));
}
foreach (var file in Directory.GetFiles(resourceFolder, "*", SearchOption.AllDirectories))
{
zip.Add(file,file.Substring(resourceFolder.Length+1));
}
zip.Close();
ClientResourceZips.Add(zipPath);
}
} }
using (ZipFile zip = new ZipFile()) catch (Exception ex)
{ {
zip.AddDirectory(path); Logger?.Error($"Failed to pack client resource:{resourceFolder}");
zip.Save(zippath); Logger?.Error(ex);
} }
HasClientResources=true;
}
catch (Exception ex)
{
Logger?.Error("Failed to pack client resources");
Logger?.Error(ex);
} }
} }
#endregion var packed = Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly);
if (packed.Length>0)
{
HasClientResources =true;
ClientResourceZips.AddRange(packed);
}
#endregion
#region SERVER #region SERVER
path = Path.Combine("Resources", "Server"); path = Path.Combine("Resources", "Server");
var dataFolder = Path.Combine(path, "data");
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path)) foreach (var resource in Directory.GetDirectories(path))
{ {
if (Path.GetFileName(resource).ToLower()=="data") { continue; }
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}"); Logger?.Info($"Loading resource: {Path.GetFileName(resource)}");
LoadResource(resource); LoadResource(resource,dataFolder);
} }
foreach(var resource in Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly))
{
Logger?.Info($"Loading resource: {Path.GetFileName(resource)}");
LoadResource(new ZipFile(resource), dataFolder);
}
// Start scripts // Start scripts
lock (LoadedResources) lock (LoadedResources)
{ {
foreach (var d in LoadedResources) foreach (var r in LoadedResources)
{ {
foreach (ServerScript s in d.Scripts) foreach (ServerScript s in r.Scripts)
{ {
s.CurrentResource = d;
s.API=Server.API; s.API=Server.API;
try try
{ {
Logger?.Debug("Starting script:"+s.CurrentFile.Name);
s.OnStart(); s.OnStart();
} }
catch(Exception ex) {Logger?.Error($"Failed to start resource: {d.Name}"); Logger?.Error(ex); } catch(Exception ex) {Logger?.Error($"Failed to start resource: {r.Name}"); Logger?.Error(ex); }
} }
} }
} }

View File

@ -1,25 +1,22 @@
using System; using System;
using RageCoop.Core.Scripting;
namespace RageCoop.Server.Scripting namespace RageCoop.Server.Scripting
{ {
/// <summary> /// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and <see cref="API"/> will be null, you should use <see cref="OnStart"/>. to initiate your script. /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded and <see cref="API"/> will be null, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary> /// </summary>
public abstract class ServerScript :Core.Scripting.IScriptable public abstract class ServerScript : Scriptable
{ {
/// <summary> /// <summary>
/// This method would be called from main thread after all scripts have been loaded. /// This method would be called from main thread after all scripts have been loaded.
/// </summary> /// </summary>
public abstract void OnStart(); public override abstract void OnStart();
/// <summary> /// <summary>
/// This method would be called from main thread when the server is shutting down, you MUST terminate all background jobs/threads in this method. /// This method would be called from main thread when the server is shutting down, you MUST terminate all background jobs/threads in this method.
/// </summary> /// </summary>
public abstract void OnStop(); public override abstract void OnStop();
/// <summary>
/// Get the <see cref="Core.Scripting.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 Core.Scripting.Resource CurrentResource { get;internal set; }
public API API { get; set; } public API API { get; set; }
} }

View File

@ -24,21 +24,21 @@ namespace RageCoop.Server
public string Address { get; set; } public string Address { get; set; }
} }
internal class Server public class Server
{ {
private readonly string _compatibleVersion = "V0_5"; private readonly string _compatibleVersion = "V0_5";
public BaseScript BaseScript { get; set; }=new BaseScript(); internal BaseScript BaseScript { get; set; }=new BaseScript();
public readonly Settings MainSettings = Util.Read<Settings>("Settings.xml"); internal readonly Settings MainSettings = Util.Read<Settings>("Settings.xml");
public NetServer MainNetServer; internal NetServer MainNetServer;
public readonly Dictionary<Command, Action<CommandContext>> Commands = new(); internal readonly Dictionary<Command, Action<CommandContext>> Commands = new();
public readonly Dictionary<long,Client> Clients = new(); internal readonly Dictionary<long,Client> Clients = new();
private System.Timers.Timer SendPlayerTimer = new System.Timers.Timer(5000); private System.Timers.Timer SendPlayerTimer = new System.Timers.Timer(5000);
private Dictionary<int,FileTransfer> InProgressFileTransfers=new(); private Dictionary<int,FileTransfer> InProgressFileTransfers=new();
private Resources Resources; private Resources Resources;
public API API; public API API { get; private set; }
public Logger Logger; internal Logger Logger;
private Security Security; private Security Security;
public Server(Logger logger=null) public Server(Logger logger=null)
{ {
@ -58,8 +58,7 @@ namespace RageCoop.Server
Port = MainSettings.Port, Port = MainSettings.Port,
MaximumConnections = MainSettings.MaxPlayers, MaximumConnections = MainSettings.MaxPlayers,
EnableUPnP = false, EnableUPnP = false,
AutoFlushSendQueue = true, AutoFlushSendQueue = true
MaximumTransmissionUnit=2000, // PublicKeyResponse
}; };
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
@ -436,7 +435,7 @@ namespace RageCoop.Server
MainNetServer.Recycle(message); MainNetServer.Recycle(message);
} }
object _sendPlayersLock=new object(); object _sendPlayersLock=new object();
public void SendPlayerInfos() internal void SendPlayerInfos()
{ {
lock (_sendPlayersLock) lock (_sendPlayersLock)
{ {
@ -499,7 +498,7 @@ namespace RageCoop.Server
try try
{ {
Security.AddConnection(connection.RemoteEndPoint, packet.AesKeyCrypted,packet.AesIVCrypted); Security.AddConnection(connection.RemoteEndPoint, packet.AesKeyCrypted,packet.AesIVCrypted);
passhash=Security.Decrypt(packet.PassHashEncrypted,connection.RemoteEndPoint).GetString(); passhash=BitConverter.ToString(Security.Decrypt(packet.PassHashEncrypted, connection.RemoteEndPoint)).Replace("-", String.Empty);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -759,7 +758,7 @@ namespace RageCoop.Server
Logger?.Info(packet.Username + ": " + packet.Message); Logger?.Info(packet.Username + ": " + packet.Message);
} }
public void SendChatMessage(string username, string message, List<NetConnection> targets = null) internal void SendChatMessage(string username, string message, List<NetConnection> targets = null)
{ {
if (MainNetServer.Connections.Count==0) { return; } if (MainNetServer.Connections.Count==0) { return; }
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
@ -768,14 +767,14 @@ namespace RageCoop.Server
MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat); MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat);
} }
public void SendChatMessage(string username, string message, NetConnection target) internal void SendChatMessage(string username, string message, NetConnection target)
{ {
SendChatMessage(username, message, new List<NetConnection>() { target }); SendChatMessage(username, message, new List<NetConnection>() { target });
} }
#endregion #endregion
public void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback) internal void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{ {
Command command = new(name) { Usage = usage, ArgsLength = argsLength }; Command command = new(name) { Usage = usage, ArgsLength = argsLength };
@ -786,7 +785,7 @@ namespace RageCoop.Server
Commands.Add(command, callback); Commands.Add(command, callback);
} }
public void RegisterCommand(string name, Action<CommandContext> callback) internal void RegisterCommand(string name, Action<CommandContext> callback)
{ {
Command command = new(name); Command command = new(name);
@ -798,7 +797,7 @@ namespace RageCoop.Server
Commands.Add(command, callback); Commands.Add(command, callback);
} }
public void RegisterCommands<T>() internal void RegisterCommands<T>()
{ {
IEnumerable<MethodInfo> commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any()); IEnumerable<MethodInfo> commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
@ -810,7 +809,7 @@ namespace RageCoop.Server
} }
} }
public void SendFile(string path,string name,Client client,Action<float> updateCallback=null) internal void SendFile(string path,string name,Client client,Action<float> updateCallback=null)
{ {
int id = RequestFileID(); int id = RequestFileID();
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
@ -890,7 +889,7 @@ namespace RageCoop.Server
/// <param name="p"></param> /// <param name="p"></param>
/// <param name="channel"></param> /// <param name="channel"></param>
/// <param name="method"></param> /// <param name="method"></param>
public void Send(Packet p,Client client, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced) internal void Send(Packet p,Client client, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{ {
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage); p.Pack(outgoingMessage);

BIN
RageCoop.Server/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB