Browse Source

Added then discovered that my updating system is obsolete

refactor
Anairkoen Schno 6 years ago
parent
commit
5363cb450e
10 changed files with 381 additions and 43 deletions
  1. +2
    -0
      IllusionInjector/BeatSaber/CompositeBSPlugin.cs
  2. +32
    -31
      IllusionInjector/ConsoleWindow.cs
  3. +10
    -2
      IllusionInjector/IllusionInjector.csproj
  4. +6
    -1
      IllusionInjector/PluginComponent.cs
  5. +21
    -7
      IllusionInjector/PluginManager.cs
  6. +205
    -0
      IllusionInjector/Updating/ModUpdater.cs
  7. +98
    -0
      IllusionInjector/Updating/UpdateScript.cs
  8. +1
    -1
      IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache
  9. +1
    -1
      IllusionInjector/packages.config
  10. +5
    -0
      IllusionPlugin/BeatSaber/IBeatSaberPlugin.cs

+ 2
- 0
IllusionInjector/BeatSaber/CompositeBSPlugin.cs View File

@ -86,6 +86,8 @@ namespace IllusionInjector {
get { throw new NotImplementedException(); } get { throw new NotImplementedException(); }
} }
// public Uri UpdateUri => throw new NotImplementedException();
public void OnLateUpdate() { public void OnLateUpdate() {
Invoke(plugin => { Invoke(plugin => {
if (plugin is IEnhancedBeatSaberPlugin) if (plugin is IEnhancedBeatSaberPlugin)


+ 32
- 31
IllusionInjector/ConsoleWindow.cs View File

@ -3,6 +3,7 @@ using System.Collections;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.Win32.SafeHandles;
namespace Windows namespace Windows
{ {
@ -11,39 +12,39 @@ namespace Windows
{ {
public static void CreateConsole() public static void CreateConsole()
{ {
if (hasConsole)
return;
if (oldOut == IntPtr.Zero)
oldOut = GetStdHandle( -11 );
if (! AllocConsole())
throw new Exception("AllocConsole() failed");
conOut = CreateFile( "CONOUT$", 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero );
if (! SetStdHandle(-11, conOut))
throw new Exception("SetStdHandle() failed");
StreamToConsole();
hasConsole = true;
if (hasConsole)
return;
if (oldOut == IntPtr.Zero)
oldOut = GetStdHandle( -11 );
if (! AllocConsole())
throw new Exception("AllocConsole() failed");
conOut = CreateFile( "CONOUT$", 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero );
if (! SetStdHandle(-11, conOut))
throw new Exception("SetStdHandle() failed");
StreamToConsole();
hasConsole = true;
} }
public static void ReleaseConsole() public static void ReleaseConsole()
{ {
if (! hasConsole)
return;
if (! CloseHandle(conOut))
throw new Exception("CloseHandle() failed");
conOut = IntPtr.Zero;
if (! FreeConsole())
throw new Exception("FreeConsole() failed");
if (! SetStdHandle(-11, oldOut))
throw new Exception("SetStdHandle() failed");
StreamToConsole();
hasConsole = false;
if (! hasConsole)
return;
if (! CloseHandle(conOut))
throw new Exception("CloseHandle() failed");
conOut = IntPtr.Zero;
if (! FreeConsole())
throw new Exception("FreeConsole() failed");
if (! SetStdHandle(-11, oldOut))
throw new Exception("SetStdHandle() failed");
StreamToConsole();
hasConsole = false;
} }
private static void StreamToConsole() private static void StreamToConsole()
{ {
Stream cstm = Console.OpenStandardOutput();
StreamWriter cstw = new StreamWriter( cstm, Encoding.Default );
cstw.AutoFlush = true;
Console.SetOut( cstw );
Console.SetError( cstw );
Stream cstm = Console.OpenStandardOutput();
StreamWriter cstw = new StreamWriter( cstm, Encoding.Default );
cstw.AutoFlush = true;
Console.SetOut( cstw );
Console.SetError( cstw );
} }
private static bool hasConsole = false; private static bool hasConsole = false;
private static IntPtr conOut; private static IntPtr conOut;
@ -59,11 +60,11 @@ namespace Windows
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern IntPtr CreateFile( private static extern IntPtr CreateFile(
string fileName, string fileName,
int desiredAccess,
int shareMode,
int desiredAccess,
int shareMode,
IntPtr securityAttributes, IntPtr securityAttributes,
int creationDisposition,
int flagsAndAttributes,
int creationDisposition,
int flagsAndAttributes,
IntPtr templateFile ); IntPtr templateFile );
[DllImport("kernel32.dll", ExactSpelling=true, SetLastError=true)] [DllImport("kernel32.dll", ExactSpelling=true, SetLastError=true)]
private static extern bool CloseHandle(IntPtr handle); private static extern bool CloseHandle(IntPtr handle);


+ 10
- 2
IllusionInjector/IllusionInjector.csproj View File

@ -34,8 +34,8 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Ionic.Zlib, Version=1.9.1.5, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c, processorArchitecture=MSIL">
<HintPath>..\packages\Ionic.Zlib.1.9.1.5\lib\Ionic.Zlib.dll</HintPath>
<Reference Include="Ionic.Zip, Version=1.9.1.8, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c, processorArchitecture=MSIL">
<HintPath>..\packages\Ionic.Zip.1.9.1.8\lib\Ionic.Zip.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
@ -48,6 +48,10 @@
<HintPath>..\Libs\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\Libs\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.UnityWebRequestModule">
<HintPath>..\..\..\..\..\..\Game Library\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.UnityWebRequestModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Bootstrapper.cs" /> <Compile Include="Bootstrapper.cs" />
@ -64,6 +68,9 @@
<Compile Include="PluginManager.cs" /> <Compile Include="PluginManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PluginComponent.cs" /> <Compile Include="PluginComponent.cs" />
<Compile Include="Updating\ModUpdater.cs" />
<Compile Include="Updating\UpdateScript.cs" />
<Compile Include="Utilities\SimpleJson.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\IllusionPlugin\IllusionPlugin.csproj"> <ProjectReference Include="..\IllusionPlugin\IllusionPlugin.csproj">
@ -74,6 +81,7 @@
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.


+ 6
- 1
IllusionInjector/PluginComponent.cs View File

@ -1,4 +1,5 @@
using System;
using IllusionInjector.Updating;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
@ -23,6 +24,10 @@ namespace IllusionInjector
bsPlugins = new CompositeBSPlugin(PluginManager.BSPlugins); bsPlugins = new CompositeBSPlugin(PluginManager.BSPlugins);
ipaPlugins = new CompositeIPAPlugin(PluginManager.IPAPlugins); ipaPlugins = new CompositeIPAPlugin(PluginManager.IPAPlugins);
// this has no relevance since there is a new mod updater system
//gameObject.AddComponent<ModUpdater>(); // AFTER plugins are loaded, but before most things
bsPlugins.OnApplicationStart(); bsPlugins.OnApplicationStart();
ipaPlugins.OnApplicationStart(); ipaPlugins.OnApplicationStart();


+ 21
- 7
IllusionInjector/PluginManager.cs View File

@ -16,9 +16,12 @@ namespace IllusionInjector
{ {
#pragma warning disable CS0618 // Type or member is obsolete (IPlugin) #pragma warning disable CS0618 // Type or member is obsolete (IPlugin)
/// <summary>
/// Gets the list of loaded plugins and loads them if necessary.
/// </summary>
public class BSPluginMeta
{
public IBeatSaberPlugin Plugin { get; internal set; }
public string Filename { get; internal set; }
}
public static IEnumerable<IBeatSaberPlugin> BSPlugins public static IEnumerable<IBeatSaberPlugin> BSPlugins
{ {
get get
@ -27,10 +30,21 @@ namespace IllusionInjector
{ {
LoadPlugins(); LoadPlugins();
} }
return _bsPlugins.Select(p => p.Plugin);
}
}
private static List<BSPluginMeta> _bsPlugins = null;
internal static IEnumerable<BSPluginMeta> BSMetas
{
get
{
if (_bsPlugins == null)
{
LoadPlugins();
}
return _bsPlugins; return _bsPlugins;
} }
} }
private static List<IBeatSaberPlugin> _bsPlugins = null;
public static IEnumerable<IPlugin> IPAPlugins public static IEnumerable<IPlugin> IPAPlugins
{ {
@ -54,7 +68,7 @@ namespace IllusionInjector
// so we need to resort to P/Invoke // so we need to resort to P/Invoke
string exeName = Path.GetFileNameWithoutExtension(AppInfo.StartupPath); string exeName = Path.GetFileNameWithoutExtension(AppInfo.StartupPath);
Logger.log.Info(exeName); Logger.log.Info(exeName);
_bsPlugins = new List<IBeatSaberPlugin>();
_bsPlugins = new List<BSPluginMeta>();
_ipaPlugins = new List<IPlugin>(); _ipaPlugins = new List<IPlugin>();
if (!Directory.Exists(pluginDirectory)) return; if (!Directory.Exists(pluginDirectory)) return;
@ -86,7 +100,7 @@ namespace IllusionInjector
foreach (string s in copiedPlugins) foreach (string s in copiedPlugins)
{ {
var result = LoadPluginsFromFile(s, exeName); var result = LoadPluginsFromFile(s, exeName);
_bsPlugins.AddRange(result.Item1);
_bsPlugins.AddRange(result.Item1.Select(p => new BSPluginMeta { Plugin = p, Filename = s }));
_ipaPlugins.AddRange(result.Item2); _ipaPlugins.AddRange(result.Item2);
} }
@ -98,7 +112,7 @@ namespace IllusionInjector
Logger.log.Info("-----------------------------"); Logger.log.Info("-----------------------------");
foreach (var plugin in _bsPlugins) foreach (var plugin in _bsPlugins)
{ {
Logger.log.Info($"{plugin.Name}: {plugin.Version}");
Logger.log.Info($"{plugin.Plugin.Name}: {plugin.Plugin.Version}");
} }
Logger.log.Info("-----------------------------"); Logger.log.Info("-----------------------------");
foreach (var plugin in _ipaPlugins) foreach (var plugin in _ipaPlugins)


+ 205
- 0
IllusionInjector/Updating/ModUpdater.cs View File

@ -0,0 +1,205 @@
using IllusionInjector.Logging;
using SimpleJSON;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using UnityEngine.Networking;
using UnityEngine;
using IllusionPlugin;
using System.Text.RegularExpressions;
using Logger = IllusionInjector.Logging.Logger;
namespace IllusionInjector.Updating
{
class ModUpdater : MonoBehaviour
{
public ModUpdater instance;
public void Awake()
{
instance = this;
CheckForUpdates();
}
public void CheckForUpdates()
{
StartCoroutine(CheckForUpdatesCoroutine());
}
struct UpdateCheckQueueItem
{
public PluginManager.BSPluginMeta Plugin;
public Uri UpdateUri;
public string Name;
}
struct UpdateQueueItem
{
public PluginManager.BSPluginMeta Plugin;
public Uri DownloadUri;
public string Name;
public Version NewVersion;
}
private Regex commentRegex = new Regex(@"(?: \/\/.+)?$", RegexOptions.Compiled | RegexOptions.Multiline);
private Dictionary<Uri, UpdateScript> cachedRequests = new Dictionary<Uri, UpdateScript>();
IEnumerator CheckForUpdatesCoroutine()
{
Logger.log.Info("Checking for mod updates...");
var toUpdate = new List<UpdateQueueItem>();
var plugins = new Queue<UpdateCheckQueueItem>(PluginManager.BSMetas.Select(p => new UpdateCheckQueueItem { Plugin = p, UpdateUri = p.Plugin.UpdateUri, Name = p.Plugin.Name }));
for (; plugins.Count > 0;)
{
var plugin = plugins.Dequeue();
Logger.log.Debug($"Checking for updates for {plugin.Name}");
if (plugin.UpdateUri != null)
{
if (!cachedRequests.ContainsKey(plugin.UpdateUri))
using (var request = UnityWebRequest.Get(plugin.UpdateUri))
{
yield return request.SendWebRequest();
if (request.isNetworkError)
{
Logger.log.Error("Network error while trying to update mods");
Logger.log.Error(request.error);
break;
}
if (request.isHttpError)
{
Logger.log.Error($"Server returned an error code while trying to update mod {plugin.Name}");
Logger.log.Error(request.error);
}
var json = request.downloadHandler.text;
json = commentRegex.Replace(json, "");
JSONObject obj = null;
try
{
obj = JSON.Parse(json).AsObject;
}
catch (InvalidCastException)
{
Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
Logger.log.Error($"Response doesn't seem to be a JSON object");
continue;
}
catch (Exception e)
{
Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
Logger.log.Error(e);
continue;
}
UpdateScript ss;
try
{
ss = UpdateScript.Parse(obj);
}
catch (Exception e)
{
Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
Logger.log.Error($"Script at {plugin.UpdateUri} doesn't seem to be a valid update script");
Logger.log.Debug(e);
continue;
}
cachedRequests.Add(plugin.UpdateUri, ss);
}
var script = cachedRequests[plugin.UpdateUri];
if (script.Info.TryGetValue(plugin.Name, out UpdateScript.PluginVersionInfo info))
{
Logger.log.Debug($"Checking version info for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
if (info.NewName != null || info.NewScript != null)
plugins.Enqueue(new UpdateCheckQueueItem
{
Plugin = plugin.Plugin,
Name = info.NewName ?? plugin.Name,
UpdateUri = info.NewScript ?? plugin.UpdateUri
});
else
{
Logger.log.Debug($"New version: {info.Version}, Current version: {plugin.Plugin.Plugin.Version}");
if (info.Version > plugin.Plugin.Plugin.Version)
{ // we should update plugin
Logger.log.Debug($"Queueing update for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
toUpdate.Add(new UpdateQueueItem
{
Plugin = plugin.Plugin,
DownloadUri = info.Download,
Name = plugin.Name,
NewVersion = info.Version
});
}
}
}
else
{
Logger.log.Error($"Script defined for plugin {plugin.Name} doesn't define information for {plugin.Name}");
continue;
}
}
}
Logger.log.Info($"{toUpdate.Count} mods need updating");
if (toUpdate.Count == 0) yield break;
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetRandomFileName());
Directory.CreateDirectory(tempDirectory);
Logger.log.Debug($"Created temp download dirtectory {tempDirectory}");
foreach (var item in toUpdate)
{
StartCoroutine(DownloadPluginCoroutine(tempDirectory, item));
}
}
IEnumerator DownloadPluginCoroutine(string tempdir, UpdateQueueItem item)
{
var file = Path.Combine(tempdir, item.Name + ".dll");
using (var req = UnityWebRequest.Get(item.DownloadUri))
{
req.downloadHandler = new DownloadHandlerFile(file);
yield return req.SendWebRequest();
if (req.isNetworkError)
{
Logger.log.Error($"Network error while trying to download update for {item.Plugin.Plugin.Name}");
Logger.log.Error(req.error);
yield break;
}
if (req.isHttpError)
{
Logger.log.Error($"Server returned an error code while trying to download update for {item.Plugin.Plugin.Name}");
Logger.log.Error(req.error);
yield break;
}
}
var pluginDir = Path.GetDirectoryName(item.Plugin.Filename);
var newFile = Path.Combine(pluginDir, item.Name + ".dll");
File.Delete(item.Plugin.Filename);
if (File.Exists(newFile))
File.Delete(newFile);
File.Move(file, newFile);
Logger.log.Info($"{item.Plugin.Plugin.Name} updated to {item.NewVersion}");
}
}
}

+ 98
- 0
IllusionInjector/Updating/UpdateScript.cs View File

@ -0,0 +1,98 @@
using SimpleJSON;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace IllusionInjector.Updating
{
/** // JSON format
* {
* "_updateScript": "0.1", // version
* "<pluginName>": { // an entry for your plugin, using its annotated name
* "version": "<version>", // required, should be in .NET Version class format
* // note: only required if neither newName nor newScript is specified
* "newName": "<newName>", // optional, defines a new name for the plugin (gets saved under this name)
* // (updater will also check this file for this name to get latest)
* "newScript": "<newScript>", // optional, defines a new location for the update script
* // updater will look here for latest version too
* // note: if both newName and newScript are defined, the updater will only look in newScript
* // for newName, and not any other combination
* "download": "<url>", // required, defines URL to use for downloading new version
* // note: only required if neither newName nor newScript is specified
* },
* ...
* }
*/
class UpdateScript
{
static readonly Version ScriptVersion = new Version(0, 1);
public Version Version { get; private set; }
private Dictionary<string, PluginVersionInfo> info = new Dictionary<string, PluginVersionInfo>();
public IReadOnlyDictionary<string, PluginVersionInfo> Info { get => info; }
public class PluginVersionInfo
{
public Version Version { get; protected internal set; }
public string NewName { get; protected internal set; }
public Uri NewScript { get; protected internal set; }
public Uri Download { get; protected internal set; }
}
public static UpdateScript Parse(JSONObject jscript)
{
var script = new UpdateScript
{
Version = Version.Parse(jscript["_updateScript"].Value)
};
if (script.Version != ScriptVersion)
throw new UpdateScriptParseException("Script version mismatch");
jscript.Remove("_updateScript");
foreach (var kvp in jscript)
{
var obj = kvp.Value.AsObject;
var pvi = new PluginVersionInfo
{
Version = obj.Linq.Any(p => p.Key == "version") ? Version.Parse(obj["version"].Value) : null,
Download = obj.Linq.Any(p => p.Key == "download") ? new Uri(obj["download"].Value) : null,
NewName = obj.Linq.Any(p => p.Key == "newName") ? obj["newName"] : null,
NewScript = obj.Linq.Any(p => p.Key == "newScript") ? new Uri(obj["newScript"]) : null
};
if (pvi.NewName == null && pvi.NewScript == null && (pvi.Version == null || pvi.Download == null))
throw new UpdateScriptParseException($"Required fields missing from object {kvp.Key}");
script.info.Add(kvp.Key, pvi);
}
return script;
}
[Serializable]
private class UpdateScriptParseException : Exception
{
public UpdateScriptParseException()
{
}
public UpdateScriptParseException(string message) : base(message)
{
}
public UpdateScriptParseException(string message, Exception innerException) : base(message, innerException)
{
}
protected UpdateScriptParseException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
}

+ 1
- 1
IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache View File

@ -1 +1 @@
2fe547896965157e6254a6138df5fa8e17aceac2
c45de925bb8a7971d5428fabdc9f32b04b599913

+ 1
- 1
IllusionInjector/packages.config View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Ionic.Zlib" version="1.9.1.5" targetFramework="net46" />
<package id="Ionic.Zip" version="1.9.1.8" targetFramework="net46" />
</packages> </packages>

+ 5
- 0
IllusionPlugin/BeatSaber/IBeatSaberPlugin.cs View File

@ -22,6 +22,11 @@ namespace IllusionPlugin
/// </summary> /// </summary>
Version Version { get; } Version Version { get; }
/// <summary>
/// The URI to the update script for the plugin. May be <see langword="null"/>.
/// </summary>
//Uri UpdateUri { get; }
/// <summary> /// <summary>
/// Gets invoked when the application is started. /// Gets invoked when the application is started.
/// </summary> /// </summary>


Loading…
Cancel
Save