## Adds Auto Archiving Backups are archived once an hour, day, and month. Archives older than 60 days are pruned automatically. _Note: Automatic pruning is off by default_ ### Archive compression format The following formats are supported: * Zstd (The fastest with best compression ratio) * GZip * Zip * None (Tar) _Note: By default archives use [zstandard](http://facebook.github.io/zstd/) format. The archive format can be changed in modernuo.json `autoArchive.compressionFormat`_ ### Restoring world from archive Move the archive to the Saves folder (tar.zst file). On startup the server will extract the file and restore the latest save. See pictures below. ### Manually extracting an archives #### Windows * Use the latest version of [7-zip w/ ZStandard](https://github.com/mcmilk/7-Zip-zstd/releases/latest) 1. Extract the `.tar.zst` file. 2. Extract the `.tar` file. (Yes you have to do it in two steps) * On Windows 10 you can use the command line. `zstd.exe` is in the Assemblies folder after building ModernUO. 1. `tar --use-compress-program "Distribution\Assemblies\zstd.exe -d" -xvf "Archives\Hourly\archivefile.tar.zst" -C "path to where you want to extract it"` #### Mac * Install [Keka](https://www.keka.io) 1. Drop the .tar.zst onto the keka interface. #### Linux 1. Install zstd from a package manager 2. Run `tar -I zstd -xvf "Archives\Hourly\archivefile.tar.zst" -C "path to where you want to extract it"` ### Other changes * Changes Autosave to occur at the same time no matter when the server is booted. * Adds `[SaveFrequency <delay> [warning]`command to set save frequency and warning frequency in-game. * Adds support for time zones that are configurable. The system timezone can also be manually configured. Check `TimeZoneHandler.cs` for details. <img width="257" alt="Screen Shot 2021-09-22 at 11 23 18 PM" src="https://user-images.githubusercontent.com/3953314/134464929-a5bf3cd8-2ef0-4476-a9ad-71d0816a19ac.png"> <img width="241" alt="Screen Shot 2021-09-22 at 11 23 39 PM" src="https://user-images.githubusercontent.com/3953314/134464945-5f6f96dc-3d1e-434d-8256-5c6b3705e786.png"> <img width="836" alt="Screen Shot 2021-09-22 at 11 34 39 PM" src="https://user-images.githubusercontent.com/3953314/134464957-625e57f2-ef47-4ca1-a0a7-cd40c9b6539c.png"> <img width="631" alt="Screen Shot 2021-09-22 at 11 34 50 PM" src="https://user-images.githubusercontent.com/3953314/134464969-b2d65c0d-f8f5-497a-a832-5d805e8e0b22.png">
354 lines
12 KiB
C#
354 lines
12 KiB
C#
/*************************************************************************
|
|
* ModernUO *
|
|
* Copyright 2019-2020 - ModernUO Development Team *
|
|
* Email: hi@modernuo.com *
|
|
* File: AssemblyHandler.cs *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
*************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.Loader;
|
|
|
|
namespace Server
|
|
{
|
|
public static class AssemblyHandler
|
|
{
|
|
private static readonly Dictionary<Assembly, TypeCache> m_TypeCaches = new();
|
|
private static TypeCache m_NullCache;
|
|
public static Assembly[] Assemblies { get; set; }
|
|
|
|
internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) =>
|
|
LoadAssemblyByAssemblyName(args.Name);
|
|
|
|
private static void EnsureAssemblyDirectories()
|
|
{
|
|
if (ServerConfiguration.AssemblyDirectories.Count == 0)
|
|
{
|
|
ServerConfiguration.AssemblyDirectories.Add(Path.Combine(Core.BaseDirectory, "Assemblies"));
|
|
ServerConfiguration.Save();
|
|
}
|
|
}
|
|
|
|
public static Assembly LoadAssemblyByAssemblyName(string fullAssemblyName)
|
|
{
|
|
var assemblyName = new AssemblyName(fullAssemblyName);
|
|
var assemblyFile = $"{assemblyName.Name}.dll";
|
|
|
|
EnsureAssemblyDirectories();
|
|
var assemblyDirectories = ServerConfiguration.AssemblyDirectories;
|
|
|
|
foreach (var assemblyDir in assemblyDirectories)
|
|
{
|
|
var assemblyPath = Path.Combine(assemblyDir, assemblyFile);
|
|
if (File.Exists(assemblyPath))
|
|
{
|
|
var assemblyNameCheck = AssemblyName.GetAssemblyName(assemblyPath);
|
|
if (assemblyNameCheck.FullName == assemblyName.FullName)
|
|
{
|
|
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static Assembly LoadAssemblyByFileName(string assemblyFile)
|
|
{
|
|
EnsureAssemblyDirectories();
|
|
var assemblyDirectories = ServerConfiguration.AssemblyDirectories;
|
|
|
|
foreach (var assemblyDir in assemblyDirectories)
|
|
{
|
|
var assemblyPath = Path.Combine(assemblyDir, assemblyFile);
|
|
if (File.Exists(assemblyPath))
|
|
{
|
|
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static void LoadAssemblies(string[] files)
|
|
{
|
|
var assemblies = new Assembly[files.Length];
|
|
|
|
for (var i = 0; i < files.Length; i++)
|
|
{
|
|
var assembly = LoadAssemblyByFileName(files[i]);
|
|
if (assembly == null)
|
|
{
|
|
throw new FileNotFoundException($"Could not load {files[i]}");
|
|
}
|
|
assemblies[i] = LoadAssemblyByFileName(files[i]);
|
|
}
|
|
|
|
Assemblies = assemblies;
|
|
}
|
|
|
|
public static void Invoke(string method)
|
|
{
|
|
var invoke = new List<MethodInfo>();
|
|
|
|
Core.Assembly.AddMethods(method, invoke);
|
|
|
|
for (var i = 0; i < Assemblies.Length; i++)
|
|
{
|
|
Assemblies[i].AddMethods(method, invoke);
|
|
}
|
|
|
|
invoke.Sort(new CallPriorityComparer());
|
|
|
|
for (var i = 0; i < invoke.Count; ++i)
|
|
{
|
|
invoke[i].Invoke(null, null);
|
|
}
|
|
}
|
|
|
|
private static void AddMethods(this Assembly assembly, string method, List<MethodInfo> list)
|
|
{
|
|
var types = assembly.GetTypes();
|
|
|
|
for (int i = 0; i < types.Length; i++)
|
|
{
|
|
var m = types[i].GetMethod(method, BindingFlags.Static | BindingFlags.Public);
|
|
if (m?.GetParameters().Length == 0)
|
|
{
|
|
list.Add(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public static TypeCache GetTypeCache(Assembly asm)
|
|
{
|
|
if (asm == null)
|
|
{
|
|
return m_NullCache ??= new TypeCache(null);
|
|
}
|
|
|
|
if (m_TypeCaches.TryGetValue(asm, out var c))
|
|
{
|
|
return c;
|
|
}
|
|
|
|
return m_TypeCaches[asm] = new TypeCache(asm);
|
|
}
|
|
|
|
public static Type FindTypeByFullName(string name, bool ignoreCase = true) =>
|
|
FindTypeByName(name, true, ignoreCase);
|
|
|
|
public static Type FindTypeByName(string name, bool fullName = false, bool ignoreCase = true)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (var i = 0; i < Assemblies.Length; i++)
|
|
{
|
|
foreach (var type in GetTypeCache(Assemblies[i]).GetTypesByName(name, fullName, ignoreCase))
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
|
|
foreach(var type in GetTypeCache(Core.Assembly).GetTypesByName(name, fullName, ignoreCase))
|
|
{
|
|
return type;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static string EnsureDirectory(string dir)
|
|
{
|
|
var path = Path.Combine(Core.BaseDirectory, dir);
|
|
Directory.CreateDirectory(path);
|
|
|
|
return path;
|
|
}
|
|
|
|
public static void EnsureDirectory(FileInfo fi)
|
|
{
|
|
var dir = fi.DirectoryName;
|
|
if (dir != null)
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
}
|
|
|
|
public static void EnsureDirectory(DirectoryInfo di)
|
|
{
|
|
Directory.CreateDirectory(di.FullName);
|
|
}
|
|
}
|
|
|
|
public class TypeCache
|
|
{
|
|
private readonly Dictionary<string, int[]> _nameMap = new();
|
|
private readonly Dictionary<string, int[]> _nameMapInsensitive = new();
|
|
private readonly Dictionary<string, int[]> _fullNameMap = new();
|
|
private readonly Dictionary<string, int[]> _fullNameMapInsensitive = new();
|
|
|
|
public TypeCache(Assembly asm)
|
|
{
|
|
Types = asm?.GetTypes() ?? Type.EmptyTypes;
|
|
|
|
var nameMap = new Dictionary<string, HashSet<int>>();
|
|
var nameMapInsensitive = new Dictionary<string, HashSet<int>>();
|
|
var fullNameMap = new Dictionary<string, HashSet<int>>();
|
|
var fullNameMapInsensitive = new Dictionary<string, HashSet<int>>();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
void addTypeToRefs(int index, string fullTypeName)
|
|
{
|
|
var typeName = fullTypeName[(fullTypeName.LastIndexOf('.') + 1)..];
|
|
AddToRefs(index, typeName, nameMap);
|
|
AddToRefs(index, typeName.ToLower(), nameMapInsensitive);
|
|
AddToRefs(index, fullTypeName, fullNameMap);
|
|
AddToRefs(index, fullTypeName.ToLower(), fullNameMapInsensitive);
|
|
}
|
|
|
|
var aliasType = typeof(TypeAliasAttribute);
|
|
for (var i = 0; i < Types.Length; i++)
|
|
{
|
|
var current = Types[i];
|
|
addTypeToRefs(i, current.FullName);
|
|
if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias)
|
|
{
|
|
for (var j = 0; j < alias.Aliases.Length; j++)
|
|
{
|
|
addTypeToRefs(i, alias.Aliases[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var (key, value) in nameMap)
|
|
{
|
|
_nameMap[key] = value.ToArray();
|
|
}
|
|
|
|
foreach (var (key, value) in nameMapInsensitive)
|
|
{
|
|
_nameMapInsensitive[key] = value.ToArray();
|
|
}
|
|
|
|
foreach (var (key, value) in fullNameMap)
|
|
{
|
|
_fullNameMap[key] = value.ToArray();
|
|
}
|
|
|
|
foreach (var (key, value) in fullNameMapInsensitive)
|
|
{
|
|
_fullNameMapInsensitive[key] = value.ToArray();
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void AddToRefs(int index, string key, Dictionary<string, HashSet<int>> map)
|
|
{
|
|
if (key == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (map.TryGetValue(key, out var refs))
|
|
{
|
|
refs.Add(index);
|
|
}
|
|
else
|
|
{
|
|
refs = new HashSet<int> { index };
|
|
map.Add(key, refs);
|
|
}
|
|
}
|
|
|
|
public Type[] Types { get; }
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TypeEnumerable GetTypesByName(string name, bool full, bool ignoreCase) => new(name, this, full, ignoreCase);
|
|
|
|
public ref struct TypeEnumerable
|
|
{
|
|
private readonly TypeCache _cache;
|
|
private readonly string _name;
|
|
private readonly bool _ignoreCase;
|
|
private readonly bool _full;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TypeEnumerable(string name, TypeCache cache, bool full, bool ignoreCase)
|
|
{
|
|
_name = name;
|
|
_cache = cache;
|
|
_ignoreCase = ignoreCase;
|
|
_full = full;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TypeEnumerator GetEnumerator() => new(_name, _cache, _full, _ignoreCase);
|
|
}
|
|
|
|
public ref struct TypeEnumerator
|
|
{
|
|
private readonly TypeCache _cache;
|
|
private readonly int[] _values;
|
|
private int _index;
|
|
private Type _current;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal TypeEnumerator(string name, TypeCache cache, bool full, bool ignoreCase)
|
|
{
|
|
_cache = cache;
|
|
|
|
if (ignoreCase)
|
|
{
|
|
var map = full ? _cache._fullNameMapInsensitive : _cache._nameMapInsensitive;
|
|
_values = map.TryGetValue(name.ToLower(), out var values) ? values : Array.Empty<int>();
|
|
}
|
|
else
|
|
{
|
|
var map = full ? _cache._fullNameMap : _cache._nameMap;
|
|
_values = map.TryGetValue(name, out var values) ? values : Array.Empty<int>();
|
|
}
|
|
|
|
_index = 0;
|
|
_current = default;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool MoveNext()
|
|
{
|
|
int[] localList = _values;
|
|
|
|
if ((uint)_index < (uint)localList.Length)
|
|
{
|
|
_current = _cache.Types[_values[_index++]];
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public Type Current
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => _current;
|
|
}
|
|
}
|
|
}
|
|
}
|