168 lines
4.8 KiB
C#
168 lines
4.8 KiB
C#
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
|
|
{
|
|
try
|
|
{
|
|
a = Assembly.LoadFile(dllFile);
|
|
}
|
|
catch
|
|
{
|
|
LogFile.Log.Debug($"Error loading {dllFile}, deleting file");
|
|
File.Delete(dllFile);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
} |