embed plugin loader directly into the launcher
This commit is contained in:
159
PluginLoader/Data/GitHubPlugin.cs
Normal file
159
PluginLoader/Data/GitHubPlugin.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using PluginLoader.Compiler;
|
||||
using PluginLoader.Network;
|
||||
using ProtoBuf;
|
||||
using Sandbox.Graphics.GUI;
|
||||
|
||||
namespace PluginLoader.Data;
|
||||
|
||||
[ProtoContract]
|
||||
public class GitHubPlugin : PluginData
|
||||
{
|
||||
private const string pluginFile = "plugin.dll";
|
||||
private const string commitHashFile = "commit.sha1";
|
||||
private string cacheDir, assemblyName;
|
||||
|
||||
public GitHubPlugin()
|
||||
{
|
||||
Status = PluginStatus.None;
|
||||
}
|
||||
|
||||
public override string Source => "GitHub";
|
||||
|
||||
[ProtoMember(1)] public string Commit { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[XmlArray]
|
||||
[XmlArrayItem("Directory")]
|
||||
public string[] SourceDirectories { get; set; }
|
||||
|
||||
public void Init(string mainDirectory)
|
||||
{
|
||||
var nameArgs = Id.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (nameArgs.Length != 2)
|
||||
throw new("Invalid GitHub name: " + Id);
|
||||
|
||||
if (SourceDirectories != null)
|
||||
for (var i = SourceDirectories.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var path = SourceDirectories[i].Replace('\\', '/').TrimStart('/');
|
||||
|
||||
if (path.Length == 0)
|
||||
{
|
||||
SourceDirectories.RemoveAtFast(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path[path.Length - 1] != '/')
|
||||
path += '/';
|
||||
|
||||
SourceDirectories[i] = path;
|
||||
}
|
||||
|
||||
assemblyName = MakeSafeString(nameArgs[1]);
|
||||
cacheDir = Path.Combine(mainDirectory, "GitHub", nameArgs[0], nameArgs[1]);
|
||||
}
|
||||
|
||||
private string MakeSafeString(string s)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ch in s)
|
||||
if (char.IsLetterOrDigit(ch))
|
||||
sb.Append(ch);
|
||||
else
|
||||
sb.Append('_');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override Assembly GetAssembly()
|
||||
{
|
||||
if (!Directory.Exists(cacheDir))
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
|
||||
Assembly a;
|
||||
|
||||
var dllFile = Path.Combine(cacheDir, pluginFile);
|
||||
var commitFile = Path.Combine(cacheDir, commitHashFile);
|
||||
if (!File.Exists(dllFile) || !File.Exists(commitFile) || File.ReadAllText(commitFile) != Commit)
|
||||
{
|
||||
var lbl = Main.Instance.Splash;
|
||||
lbl.SetText($"Downloading '{FriendlyName}'");
|
||||
var data = CompileFromSource(x => lbl.SetBarValue(x));
|
||||
File.WriteAllBytes(dllFile, data);
|
||||
File.WriteAllText(commitFile, Commit);
|
||||
Status = PluginStatus.Updated;
|
||||
lbl.SetText($"Compiled '{FriendlyName}'");
|
||||
a = Assembly.Load(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = Assembly.LoadFile(dllFile);
|
||||
}
|
||||
|
||||
Version = a.GetName().Version;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
public byte[] CompileFromSource(Action<float> callback = null)
|
||||
{
|
||||
var compiler = new RoslynCompiler();
|
||||
using (var s = GitHub.DownloadRepo(Id, Commit, out var fileName))
|
||||
using (var zip = new ZipArchive(s))
|
||||
{
|
||||
callback?.Invoke(0);
|
||||
for (var i = 0; i < zip.Entries.Count; i++)
|
||||
{
|
||||
var entry = zip.Entries[i];
|
||||
CompileFromSource(compiler, entry);
|
||||
callback?.Invoke(i / (float)zip.Entries.Count);
|
||||
}
|
||||
|
||||
callback?.Invoke(1);
|
||||
}
|
||||
|
||||
return compiler.Compile(assemblyName + '_' + Path.GetRandomFileName(), out _);
|
||||
}
|
||||
|
||||
private void CompileFromSource(RoslynCompiler compiler, ZipArchiveEntry entry)
|
||||
{
|
||||
if (AllowedZipPath(entry.FullName))
|
||||
using (var entryStream = entry.Open())
|
||||
{
|
||||
compiler.Load(entryStream, entry.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllowedZipPath(string path)
|
||||
{
|
||||
if (!path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
if (SourceDirectories == null || SourceDirectories.Length == 0)
|
||||
return true;
|
||||
|
||||
path = RemoveRoot(path); // Make the base of the path the root of the repository
|
||||
|
||||
foreach (var dir in SourceDirectories)
|
||||
if (path.StartsWith(dir, StringComparison.Ordinal))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string RemoveRoot(string path)
|
||||
{
|
||||
path = path.Replace('\\', '/').TrimStart('/');
|
||||
var index = path.IndexOf('/');
|
||||
if (index >= 0 && index + 1 < path.Length)
|
||||
return path.Substring(index + 1);
|
||||
return path;
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
MyGuiSandbox.OpenUrl("https://github.com/" + Id, UrlOpenMode.SteamOrExternalWithConfirm);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user