Browse Source

Updated to use ReSharper

pull/1/head
Anairkoen Schno 6 years ago
parent
commit
df498b03b5
64 changed files with 641 additions and 1029 deletions
  1. +0
    -24
      BSIPA.sln
  2. +12
    -0
      BSIPA.sln.DotSettings
  3. +7
    -8
      CollectDependencies/Program.cs
  4. +0
    -1
      CollectDependencies/Properties/AssemblyInfo.cs
  5. +14
    -18
      CollectDependencies/Virtualizer.cs
  6. +2
    -6
      IPA.Injector/Backups/BackupManager.cs
  7. +26
    -32
      IPA.Injector/Backups/BackupUnit.cs
  8. +8
    -8
      IPA.Injector/Bootstrapper.cs
  9. +23
    -25
      IPA.Injector/ConsoleWindow.cs
  10. +0
    -1
      IPA.Injector/IPA.Injector.csproj
  11. +34
    -31
      IPA.Injector/Injector.cs
  12. +22
    -27
      IPA.Injector/LibLoader.cs
  13. +3
    -5
      IPA.Injector/Properties/AssemblyInfo.cs
  14. +2
    -6
      IPA.Injector/Updates.cs
  15. +4
    -10
      IPA.Injector/Virtualizer.cs
  16. +0
    -21
      IPA.Injector/WtfThisDoesntNeedToExist.cs
  17. +16
    -18
      IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs
  18. +1
    -4
      IPA.Loader/Config/IConfigProvider.cs
  19. +12
    -14
      IPA.Loader/Config/IniFile.cs
  20. +28
    -35
      IPA.Loader/Config/ModPrefs.cs
  21. +4
    -6
      IPA.Loader/IPA.Loader.csproj
  22. +4
    -10
      IPA.Loader/Loader/Composite/CompositeBSPlugin.cs
  23. +7
    -16
      IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs
  24. +7
    -8
      IPA.Loader/Loader/PluginComponent.cs
  25. +29
    -31
      IPA.Loader/Loader/PluginManager.cs
  26. +0
    -4
      IPA.Loader/Logging/LogPrinter.cs
  27. +2
    -5
      IPA.Loader/Logging/Logger.cs
  28. +2
    -8
      IPA.Loader/Logging/Printers/ColoredConsolePrinter.cs
  29. +12
    -16
      IPA.Loader/Logging/Printers/GZFilePrinter.cs
  30. +2
    -7
      IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs
  31. +3
    -8
      IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs
  32. +3
    -8
      IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs
  33. +25
    -29
      IPA.Loader/Logging/StandardLogger.cs
  34. +4
    -16
      IPA.Loader/Logging/UnityLogInterceptor.cs
  35. +3
    -5
      IPA.Loader/PluginInterfaces/BeatSaber/IBeatSaberPlugin.cs
  36. +2
    -4
      IPA.Loader/PluginInterfaces/BeatSaber/IEnhancedBeatSaberPlugin.cs
  37. +10
    -12
      IPA.Loader/PluginInterfaces/BeatSaber/ModsaberModInfo.cs
  38. +2
    -7
      IPA.Loader/PluginInterfaces/IGenericEnhancedPlugin.cs
  39. +2
    -2
      IPA.Loader/PluginInterfaces/IPA/IEnhancedPlugin.cs
  40. +1
    -2
      IPA.Loader/PluginInterfaces/IPA/IPlugin.cs
  41. +0
    -147
      IPA.Loader/Updating/Backup/BackupUnit.cs
  42. +9
    -13
      IPA.Loader/Updating/Converters/ModsaberDependencyConverter.cs
  43. +2
    -4
      IPA.Loader/Updating/Converters/SemverRangeConverter.cs
  44. +2
    -7
      IPA.Loader/Updating/Converters/SemverVersionConverter.cs
  45. +10
    -15
      IPA.Loader/Updating/ModSaber/ApiEndpoint.cs
  46. +65
    -73
      IPA.Loader/Updating/ModSaber/Updater.cs
  47. +2
    -8
      IPA.Loader/Updating/SelfPlugin.cs
  48. +8
    -12
      IPA.Loader/Utilities/BeatSaber.cs
  49. +1
    -9
      IPA.Loader/Utilities/Extensions.cs
  50. +4
    -7
      IPA.Loader/Utilities/LoneFunctions.cs
  51. +6
    -10
      IPA.Loader/Utilities/Ref.cs
  52. +10
    -19
      IPA.Loader/Utilities/ReflectionUtil.cs
  53. +34
    -39
      IPA/Arguments.cs
  54. +4
    -7
      IPA/PatchContext.cs
  55. +7
    -12
      IPA/Patcher/BackupManager.cs
  56. +28
    -31
      IPA/Patcher/BackupUnit.cs
  57. +32
    -27
      IPA/Patcher/Patcher.cs
  58. +11
    -15
      IPA/Patcher/Virtualizer.cs
  59. +50
    -50
      IPA/Program.cs
  60. +2
    -3
      IPA/Properties/AssemblyInfo.cs
  61. +8
    -7
      IPA/Shortcut.cs
  62. +6
    -13
      MSBuildTasks/AssemblyRenameTask.cs
  63. +0
    -1
      MSBuildTasks/Properties/AssemblyInfo.cs
  64. +2
    -2
      appveyor.yml

+ 0
- 24
BSIPA.sln View File

@ -8,8 +8,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA", "IPA\IPA.csproj", "{1
{2A1AF16B-27F1-46E0-9A95-181516BC1CB7} = {2A1AF16B-27F1-46E0-9A95-181516BC1CB7} {2A1AF16B-27F1-46E0-9A95-181516BC1CB7} = {2A1AF16B-27F1-46E0-9A95-181516BC1CB7}
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA.Tests", "IPA.Tests\IPA.Tests.csproj", "{C66092B0-5C1E-44E9-B524-E0E8E1425379}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSBuildTasks", "MSBuildTasks\MSBuildTasks.csproj", "{F08C3C7A-3221-432E-BAB8-32BCE58408C8}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSBuildTasks", "MSBuildTasks\MSBuildTasks.csproj", "{F08C3C7A-3221-432E-BAB8-32BCE58408C8}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA.Loader", "IPA.Loader\IPA.Loader.csproj", "{5AD344F0-01A0-4CA8-92E5-9D095737744D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA.Loader", "IPA.Loader\IPA.Loader.csproj", "{5AD344F0-01A0-4CA8-92E5-9D095737744D}"
@ -79,28 +77,6 @@ Global
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x64.Build.0 = Verbose|Any CPU {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x64.Build.0 = Verbose|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.ActiveCfg = Release|Any CPU {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.ActiveCfg = Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.Build.0 = Release|Any CPU {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|x64.ActiveCfg = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|x64.Build.0 = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|x86.ActiveCfg = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Debug|x86.Build.0 = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Release|Any CPU.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Release|x64.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Release|x86.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Release|x86.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose_Release|Any CPU.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose_Release|Any CPU.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose_Release|x86.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|Any CPU.Build.0 = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|x64.ActiveCfg = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|x64.Build.0 = Debug|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|x86.ActiveCfg = Release|Any CPU
{C66092B0-5C1E-44E9-B524-E0E8E1425379}.Verbose|x86.Build.0 = Release|Any CPU
{F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|Any CPU.Build.0 = Debug|Any CPU {F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|x64.ActiveCfg = Debug|Any CPU {F08C3C7A-3221-432E-BAB8-32BCE58408C8}.Debug|x64.ActiveCfg = Debug|Any CPU


+ 12
- 0
BSIPA.sln.DotSettings View File

@ -0,0 +1,12 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BS/@EntryIndexedValue">BS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPA/@EntryIndexedValue">IPA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=beatsaber/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Coroutine/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Modsaber/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Prefs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unpatch/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Virtualize/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=waitfor/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 7
- 8
CollectDependencies/Program.cs View File

@ -3,12 +3,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CollectDependencies namespace CollectDependencies
{ {
class Program
static class Program
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
@ -34,7 +32,7 @@ namespace CollectDependencies
return v2; return v2;
} }
foreach (var line in depsfile.Split(new string[] { Environment.NewLine }, StringSplitOptions.None))
foreach (var line in depsfile.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
{ {
var parts = line.Split('"'); var parts = line.Split('"');
var path = parts.Last(); var path = parts.Last();
@ -48,7 +46,7 @@ namespace CollectDependencies
var arglist = string.Join(" ", parts); var arglist = string.Join(" ", parts);
if (command == "from") if (command == "from")
{ // an "import" type command { // an "import" type command
path = File.ReadAllText(Path.Combine(fdir, arglist));
path = File.ReadAllText(Path.Combine(fdir ?? throw new InvalidOperationException(), arglist));
} }
else if (command == "prompt") else if (command == "prompt")
{ {
@ -85,16 +83,17 @@ namespace CollectDependencies
if (fname == "") continue; if (fname == "") continue;
var outp = Path.Combine(fdir, Path.GetFileName(fname));
var outp = Path.Combine(fdir ?? throw new InvalidOperationException(), Path.GetFileName(fname) ?? throw new InvalidOperationException());
Console.WriteLine($"Copying \"{fname}\" to \"{outp}\""); Console.WriteLine($"Copying \"{fname}\" to \"{outp}\"");
if (File.Exists(outp)) File.Delete(outp); if (File.Exists(outp)) File.Delete(outp);
if (Path.GetExtension(fname).ToLower() == ".dll")
if (Path.GetExtension(fname)?.ToLower() == ".dll")
{ {
// ReSharper disable once StringLiteralTypo
if (fparts.Length > 1 && fparts[1] == "virt") if (fparts.Length > 1 && fparts[1] == "virt")
{ {
var module = VirtualizedModule.Load(fname); var module = VirtualizedModule.Load(fname);
module.Virtualize(fname = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName(), Path.GetFileName(fname)));
module.Virtualize(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName(), Path.GetFileName(fname) ?? throw new InvalidOperationException()));
} }
var modl = ModuleDefinition.ReadModule(fparts[0]); var modl = ModuleDefinition.ReadModule(fparts[0]);
foreach (var t in modl.Types) foreach (var t in modl.Types)


+ 0
- 1
CollectDependencies/Properties/AssemblyInfo.cs View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following


+ 14
- 18
CollectDependencies/Virtualizer.cs View File

@ -1,18 +1,13 @@
using Mono.Cecil; using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
namespace CollectDependencies namespace CollectDependencies
{ {
class VirtualizedModule class VirtualizedModule
{ {
private const string ENTRY_TYPE = "Display";
private FileInfo _File;
private ModuleDefinition _Module;
private readonly FileInfo _file;
private ModuleDefinition _module;
public static VirtualizedModule Load(string engineFile) public static VirtualizedModule Load(string engineFile)
{ {
@ -21,7 +16,7 @@ namespace CollectDependencies
private VirtualizedModule(string assemblyFile) private VirtualizedModule(string assemblyFile)
{ {
_File = new FileInfo(assemblyFile);
_file = new FileInfo(assemblyFile);
LoadModules(); LoadModules();
} }
@ -29,29 +24,29 @@ namespace CollectDependencies
private void LoadModules() private void LoadModules()
{ {
var resolver = new DefaultAssemblyResolver(); var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(_File.DirectoryName);
resolver.AddSearchDirectory(_file.DirectoryName);
var parameters = new ReaderParameters var parameters = new ReaderParameters
{ {
AssemblyResolver = resolver, AssemblyResolver = resolver,
}; };
_Module = ModuleDefinition.ReadModule(_File.FullName, parameters);
_module = ModuleDefinition.ReadModule(_file.FullName, parameters);
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="module"></param>
public void Virtualize(string targetfile)
/// <param name="targetFile"></param>
public void Virtualize(string targetFile)
{ {
foreach (var type in _Module.Types)
foreach (var type in _module.Types)
{ {
VirtualizeType(type); VirtualizeType(type);
} }
_Module.Write(targetfile);
_module.Write(targetFile);
} }
private void VirtualizeType(TypeDefinition type) private void VirtualizeType(TypeDefinition type)
@ -105,10 +100,11 @@ namespace CollectDependencies
{ {
get get
{ {
var awakeMethods = _Module.GetTypes().SelectMany(t => t.Methods.Where(m => m.Name == "Awake"));
if (awakeMethods.Count() == 0) return false;
var awakeMethods = _module.GetTypes().SelectMany(t => t.Methods.Where(m => m.Name == "Awake"));
var methodDefinitions = awakeMethods as MethodDefinition[] ?? awakeMethods.ToArray();
if (!methodDefinitions.Any()) return false;
return ((float)awakeMethods.Count(m => m.IsVirtual) / awakeMethods.Count()) > 0.5f;
return ((float)methodDefinitions.Count(m => m.IsVirtual) / methodDefinitions.Count()) > 0.5f;
} }
} }
} }


+ 2
- 6
IPA.Injector/Backups/BackupManager.cs View File

@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IPA.Injector.Backups namespace IPA.Injector.Backups
{ {
public class BackupManager
public static class BackupManager
{ {
public static BackupUnit FindLatestBackup(string dir) public static BackupUnit FindLatestBackup(string dir)
{ {


+ 26
- 32
IPA.Injector/Backups/BackupUnit.cs View File

@ -1,11 +1,7 @@
using IPA.Logging;
using IPA.Utilities;
using IPA.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
namespace IPA.Injector.Backups namespace IPA.Injector.Backups
{ {
@ -16,11 +12,11 @@ namespace IPA.Injector.Backups
{ {
public string Name { get; private set; } public string Name { get; private set; }
private DirectoryInfo _BackupPath;
private HashSet<string> _Files = new HashSet<string>();
private FileInfo _ManifestFile;
private static string _ManifestFileName = "$manifest$.txt";
private readonly DirectoryInfo _backupPath;
private readonly HashSet<string> _files = new HashSet<string>();
private readonly FileInfo _manifestFile;
private const string ManifestFileName = "$manifest$.txt";
public BackupUnit(string dir) : this(dir, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss")) public BackupUnit(string dir) : this(dir, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss"))
{ {
} }
@ -28,8 +24,8 @@ namespace IPA.Injector.Backups
private BackupUnit(string dir, string name) private BackupUnit(string dir, string name)
{ {
Name = name; Name = name;
_BackupPath = new DirectoryInfo(Path.Combine(dir, Name));
_ManifestFile = new FileInfo(Path.Combine(_BackupPath.FullName, _ManifestFileName));
_backupPath = new DirectoryInfo(Path.Combine(dir, Name));
_manifestFile = new FileInfo(Path.Combine(_backupPath.FullName, ManifestFileName));
} }
public static BackupUnit FromDirectory(DirectoryInfo directory, string dir) public static BackupUnit FromDirectory(DirectoryInfo directory, string dir)
@ -37,19 +33,19 @@ namespace IPA.Injector.Backups
var unit = new BackupUnit(dir, directory.Name); var unit = new BackupUnit(dir, directory.Name);
// Read Manifest // Read Manifest
if (unit._ManifestFile.Exists)
if (unit._manifestFile.Exists)
{ {
string manifest = File.ReadAllText(unit._ManifestFile.FullName);
foreach (var line in manifest.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
unit._Files.Add(line);
var manifest = File.ReadAllText(unit._manifestFile.FullName);
foreach (var line in manifest.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
unit._files.Add(line);
} }
else else
{ {
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories)) foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{ {
if (file.Name == _ManifestFileName) continue;
if (file.Name == ManifestFileName) continue;
var relativePath = file.FullName.Substring(directory.FullName.Length + 1); var relativePath = file.FullName.Substring(directory.FullName.Length + 1);
unit._Files.Add(relativePath);
unit._files.Add(relativePath);
} }
} }
@ -63,20 +59,20 @@ namespace IPA.Injector.Backups
internal void Delete() internal void Delete()
{ {
_BackupPath.Delete(true);
_backupPath.Delete(true);
} }
/// <summary> /// <summary>
/// Adds a file to the list of changed files and backups it. /// Adds a file to the list of changed files and backups it.
/// </summary> /// </summary>
/// <param name="path"></param>
/// <param name="file"></param>
public void Add(FileInfo file) public void Add(FileInfo file)
{ {
var relativePath = LoneFunctions.GetRelativePath(file.FullName, Environment.CurrentDirectory); var relativePath = LoneFunctions.GetRelativePath(file.FullName, Environment.CurrentDirectory);
var backupPath = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
var backupPath = new FileInfo(Path.Combine(_backupPath.FullName, relativePath));
// Copy over // Copy over
backupPath.Directory.Create();
backupPath.Directory?.Create();
if (file.Exists) if (file.Exists)
{ {
if (File.Exists(backupPath.FullName)) if (File.Exists(backupPath.FullName))
@ -89,17 +85,15 @@ namespace IPA.Injector.Backups
backupPath.Create().Close(); backupPath.Create().Close();
} }
if (!_Files.Contains(relativePath))
{
if (!File.Exists(_ManifestFile.FullName))
_ManifestFile.Create().Close();
var stream = _ManifestFile.AppendText();
stream.WriteLine(relativePath);
stream.Close();
if (_files.Contains(relativePath)) return;
if (!File.Exists(_manifestFile.FullName))
_manifestFile.Create().Close();
var stream = _manifestFile.AppendText();
stream.WriteLine(relativePath);
stream.Close();
// Add to list
_Files.Add(relativePath);
}
// Add to list
_files.Add(relativePath);
} }
} }


+ 8
- 8
IPA.Injector/Bootstrapper.cs View File

@ -1,24 +1,24 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine; using UnityEngine;
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedMember.Global
namespace IPA.Injector namespace IPA.Injector
{ {
class Bootstrapper : MonoBehaviour
internal class Bootstrapper : MonoBehaviour
{ {
public event Action Destroyed = delegate {}; public event Action Destroyed = delegate {};
void Awake()
public void Awake()
{ {
} }
void Start()
public void Start()
{ {
Destroy(gameObject); Destroy(gameObject);
} }
void OnDestroy()
public void OnDestroy()
{ {
Destroyed(); Destroyed();
} }


+ 23
- 25
IPA.Injector/ConsoleWindow.cs View File

@ -1,21 +1,19 @@
using System; using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.IO; using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
namespace IPA.Injector.Windows
namespace IPA.Injector
{ {
// https://stackoverflow.com/a/48864902/3117125 // https://stackoverflow.com/a/48864902/3117125
static class WinConsole
internal static class WinConsole
{ {
static public void Initialize(bool alwaysCreateNewConsole = true)
public static void Initialize(bool alwaysCreateNewConsole = true)
{ {
bool consoleAttached = true; bool consoleAttached = true;
if (alwaysCreateNewConsole if (alwaysCreateNewConsole
|| (AttachConsole(ATTACH_PARRENT) == 0
&& Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
|| (AttachConsole(AttachParent) == 0
&& Marshal.GetLastWin32Error() != ErrorAccessDenied))
{ {
consoleAttached = AllocConsole() != 0; consoleAttached = AllocConsole() != 0;
} }
@ -34,7 +32,7 @@ namespace IPA.Injector.Windows
private static void InitializeOutStream() private static void InitializeOutStream()
{ {
var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
var fs = CreateFileStream("CONOUT$", GenericWrite, FileShareWrite, FileAccess.Write);
if (fs != null) if (fs != null)
{ {
var writer = new StreamWriter(fs) { AutoFlush = true }; var writer = new StreamWriter(fs) { AutoFlush = true };
@ -45,7 +43,7 @@ namespace IPA.Injector.Windows
private static void InitializeInStream() private static void InitializeInStream()
{ {
var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
var fs = CreateFileStream("CONIN$", GenericRead, FileShareRead, FileAccess.Read);
if (fs != null) if (fs != null)
{ {
Console.SetIn(new StreamReader(fs)); Console.SetIn(new StreamReader(fs));
@ -55,7 +53,7 @@ namespace IPA.Injector.Windows
private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode, private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
FileAccess dotNetFileAccess) FileAccess dotNetFileAccess)
{ {
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true);
if (!file.IsInvalid) if (!file.IsInvalid)
{ {
var fs = new FileStream(file, dotNetFileAccess); var fs = new FileStream(file, dotNetFileAccess);
@ -77,7 +75,7 @@ namespace IPA.Injector.Windows
SetLastError = true, SetLastError = true,
CharSet = CharSet.Auto, CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)] CallingConvention = CallingConvention.StdCall)]
private static extern UInt32 AttachConsole(UInt32 dwProcessId);
private static extern uint AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", [DllImport("kernel32.dll",
EntryPoint = "CreateFileW", EntryPoint = "CreateFileW",
@ -86,23 +84,23 @@ namespace IPA.Injector.Windows
CallingConvention = CallingConvention.StdCall)] CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CreateFileW( private static extern IntPtr CreateFileW(
string lpFileName, string lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes, IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile IntPtr hTemplateFile
); );
private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 FILE_SHARE_READ = 0x00000001;
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
private const UInt32 OPEN_EXISTING = 0x00000003;
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
private const UInt32 ERROR_ACCESS_DENIED = 5;
private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;
private const uint GenericWrite = 0x40000000;
private const uint GenericRead = 0x80000000;
private const uint FileShareRead = 0x00000001;
private const uint FileShareWrite = 0x00000002;
private const uint OpenExisting = 0x00000003;
private const uint FileAttributeNormal = 0x80;
private const uint ErrorAccessDenied = 5;
private const uint AttachParent = 0xFFFFFFFF;
#endregion #endregion
} }

+ 0
- 1
IPA.Injector/IPA.Injector.csproj View File

@ -61,7 +61,6 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Updates.cs" /> <Compile Include="Updates.cs" />
<Compile Include="Virtualizer.cs" /> <Compile Include="Virtualizer.cs" />
<Compile Include="WtfThisDoesntNeedToExist.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\IPA.Loader\IPA.Loader.csproj"> <ProjectReference Include="..\IPA.Loader\IPA.Loader.csproj">


+ 34
- 31
IPA.Injector/Injector.cs View File

@ -1,23 +1,23 @@
using Harmony;
using IPA.Injector.Backups;
using IPA.Injector.Backups;
using IPA.Loader; using IPA.Loader;
using IPA.Logging; using IPA.Logging;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using UnityEngine; using UnityEngine;
using static IPA.Logging.Logger; using static IPA.Logging.Logger;
using MethodAttributes = Mono.Cecil.MethodAttributes; using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace IPA.Injector namespace IPA.Injector
{ {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class Injector public static class Injector
{ {
// ReSharper disable once UnusedParameter.Global
public static void Main(string[] args) public static void Main(string[] args)
{ // entry point for doorstop { // entry point for doorstop
// At this point, literally nothing but mscorlib is loaded, // At this point, literally nothing but mscorlib is loaded,
@ -28,7 +28,7 @@ namespace IPA.Injector
try try
{ {
if (!Environment.GetCommandLineArgs().Contains("--no-console")) if (!Environment.GetCommandLineArgs().Contains("--no-console"))
Windows.WinConsole.Initialize();
WinConsole.Initialize();
SetupLibraryLoading(); SetupLibraryLoading();
@ -96,30 +96,30 @@ namespace IPA.Injector
else else
{ {
var ilp = cctor.Body.GetILProcessor(); var ilp = cctor.Body.GetILProcessor();
for (int i = 0; i < Math.Min(2, cctor.Body.Instructions.Count); i++)
for (var i = 0; i < Math.Min(2, cctor.Body.Instructions.Count); i++)
{ {
var ins = cctor.Body.Instructions[i]; var ins = cctor.Body.Instructions[i];
if (i == 0)
switch (i)
{ {
if (ins.OpCode != OpCodes.Call)
{
case 0 when ins.OpCode != OpCodes.Call:
ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs));
modified = true; modified = true;
}
else
break;
case 0:
{ {
var mref = ins.Operand as MethodReference;
if (mref.FullName != cbs.FullName)
var methodRef = ins.Operand as MethodReference;
if (methodRef?.FullName != cbs.FullName)
{ {
ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs));
modified = true; modified = true;
} }
break;
} }
}
if (i == 1 && ins.OpCode != OpCodes.Ret)
{
ilp.Replace(ins, ilp.Create(OpCodes.Ret));
modified = true;
case 1 when ins.OpCode != OpCodes.Ret:
ilp.Replace(ins, ilp.Create(OpCodes.Ret));
modified = true;
break;
} }
} }
} }
@ -140,11 +140,11 @@ namespace IPA.Injector
#endregion #endregion
} }
private static bool bootstrapped = false;
private static bool _bootstrapped;
private static void CreateBootstrapper() private static void CreateBootstrapper()
{ {
if (bootstrapped) return;
bootstrapped = true;
if (_bootstrapped) return;
_bootstrapped = true;
Application.logMessageReceived += delegate (string condition, string stackTrace, LogType type) Application.logMessageReceived += delegate (string condition, string stackTrace, LogType type)
{ {
@ -154,30 +154,31 @@ namespace IPA.Injector
}; };
// need to reinit streams singe Unity seems to redirect stdout // need to reinit streams singe Unity seems to redirect stdout
Windows.WinConsole.InitializeStreams();
WinConsole.InitializeStreams();
var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent<Bootstrapper>(); var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent<Bootstrapper>();
bootstrapper.Destroyed += Bootstrapper_Destroyed; bootstrapper.Destroyed += Bootstrapper_Destroyed;
} }
private static bool injected = false;
private static bool _injected;
public static void Inject() public static void Inject()
{ {
if (!injected)
if (!_injected)
{ {
injected = true;
Windows.WinConsole.Initialize();
_injected = true;
WinConsole.Initialize();
SetupLibraryLoading(); SetupLibraryLoading();
var bootstrapper = new GameObject("Bootstrapper").AddComponent<Bootstrapper>(); var bootstrapper = new GameObject("Bootstrapper").AddComponent<Bootstrapper>();
bootstrapper.Destroyed += Bootstrapper_Destroyed; bootstrapper.Destroyed += Bootstrapper_Destroyed;
} }
} }
private static bool loadingDone = false;
public static void SetupLibraryLoading()
private static bool _loadingDone;
private static void SetupLibraryLoading()
{ {
if (loadingDone) return;
loadingDone = true;
if (_loadingDone) return;
_loadingDone = true;
#region Add Library load locations #region Add Library load locations
AppDomain.CurrentDomain.AssemblyResolve += LibLoader.AssemblyLibLoader; AppDomain.CurrentDomain.AssemblyResolve += LibLoader.AssemblyLibLoader;
/*try /*try
@ -191,9 +192,11 @@ namespace IPA.Injector
#endregion #endregion
} }
/*
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName); static extern bool SetDllDirectory(string lpPathName);
*/
private static void Bootstrapper_Destroyed() private static void Bootstrapper_Destroyed()
{ {


+ 22
- 27
IPA.Injector/LibLoader.cs View File

@ -1,20 +1,17 @@
using IPA.Logging;
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using IPA.Logging;
using static IPA.Logging.Logger; using static IPA.Logging.Logger;
namespace IPA.Injector namespace IPA.Injector
{ {
internal class LibLoader
internal static class LibLoader
{ {
public static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs");
public static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
private static Dictionary<string, string> filenameLocations = null;
private static string LibraryPath => Path.Combine(Environment.CurrentDirectory, "Libs");
private static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
private static Dictionary<string, string> filenameLocations;
public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e) public static Assembly AssemblyLibLoader(object source, ResolveEventArgs e)
{ {
@ -29,20 +26,18 @@ namespace IPA.Injector
filenameLocations.Add(fn.Name, fn.FullName); filenameLocations.Add(fn.Name, fn.FullName);
} }
var testFilen = $"{asmName.Name}.{asmName.Version}.dll";
Log(Level.Debug, $"Looking for file {testFilen}");
var testFile = $"{asmName.Name}.{asmName.Version}.dll";
Log(Level.Debug, $"Looking for file {testFile}");
if (filenameLocations.TryGetValue(testFilen, out string path))
if (filenameLocations.TryGetValue(testFile, out string path))
{ {
Log(Level.Debug, $"Found file {testFilen} as {path}");
Log(Level.Debug, $"Found file {testFile} as {path}");
if (File.Exists(path)) if (File.Exists(path))
{ {
return Assembly.LoadFrom(path); return Assembly.LoadFrom(path);
} }
else
{
Log(Level.Critical, $"but {path} no longer exists!");
}
Log(Level.Critical, $"but {path} no longer exists!");
} }
Log(Level.Critical, $"No library {asmName} found"); Log(Level.Critical, $"No library {asmName} found");
@ -67,13 +62,13 @@ namespace IPA.Injector
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-tree
private static IEnumerable<FileInfo> TraverseTree(string root, Func<string, bool> dirValidator = null) private static IEnumerable<FileInfo> TraverseTree(string root, Func<string, bool> dirValidator = null)
{ {
if (dirValidator == null) dirValidator = (s) => true;
if (dirValidator == null) dirValidator = s => true;
// Data structure to hold names of subfolders to be // Data structure to hold names of subfolders to be
// examined for files. // examined for files.
Stack<string> dirs = new Stack<string>(32); Stack<string> dirs = new Stack<string>(32);
if (!System.IO.Directory.Exists(root))
if (!Directory.Exists(root))
{ {
throw new ArgumentException(); throw new ArgumentException();
} }
@ -85,7 +80,7 @@ namespace IPA.Injector
string[] subDirs; string[] subDirs;
try try
{ {
subDirs = System.IO.Directory.GetDirectories(currentDir);
subDirs = Directory.GetDirectories(currentDir);
} }
// An UnauthorizedAccessException exception will be thrown if we do not have // An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable // discovery permission on a folder or file. It may or may not be acceptable
@ -101,16 +96,16 @@ namespace IPA.Injector
//Console.WriteLine(e.Message); //Console.WriteLine(e.Message);
continue; continue;
} }
catch (System.IO.DirectoryNotFoundException)
catch (DirectoryNotFoundException)
{ {
//Console.WriteLine(e.Message); //Console.WriteLine(e.Message);
continue; continue;
} }
string[] files = null;
string[] files;
try try
{ {
files = System.IO.Directory.GetFiles(currentDir);
files = Directory.GetFiles(currentDir);
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
@ -120,7 +115,7 @@ namespace IPA.Injector
continue; continue;
} }
catch (System.IO.DirectoryNotFoundException)
catch (DirectoryNotFoundException)
{ {
//Console.WriteLine(e.Message); //Console.WriteLine(e.Message);
continue; continue;
@ -135,14 +130,14 @@ namespace IPA.Injector
// Modify this block to perform your required task. // Modify this block to perform your required task.
foreach (string file in files) foreach (string file in files)
{ {
FileInfo nextValue = null;
FileInfo nextValue;
try try
{ {
// Perform whatever action is required in your scenario. // Perform whatever action is required in your scenario.
nextValue = new System.IO.FileInfo(file);
nextValue = new FileInfo(file);
//Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime); //Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
} }
catch (System.IO.FileNotFoundException)
catch (FileNotFoundException)
{ {
// If file was deleted by a separate application // If file was deleted by a separate application
// or thread since the call to TraverseTree() // or thread since the call to TraverseTree()


+ 3
- 5
IPA.Injector/Properties/AssemblyInfo.cs View File

@ -1,5 +1,4 @@
using IPA.Injector;
using System.Reflection;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -23,7 +22,6 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2a1af16b-27f1-46e0-9a95-181516bc1cb7")] [assembly: Guid("2a1af16b-27f1-46e0-9a95-181516bc1cb7")]
[assembly: InternalsVisibleTo("IPA.Loader")] [assembly: InternalsVisibleTo("IPA.Loader")]
[assembly: ForceAssemblyReference(typeof(SemVer.Version))]
// Version information for an assembly consists of the following four values: // Version information for an assembly consists of the following four values:
// //
@ -35,5 +33,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.11.1")] // will be patched by AppVeyor
[assembly: AssemblyFileVersion("3.11.1")] // will be patched by AppVeyor
[assembly: AssemblyVersion("3.11.2")]
[assembly: AssemblyFileVersion("3.11.2")]

+ 2
- 6
IPA.Injector/Updates.cs View File

@ -1,17 +1,13 @@
using IPA.Utilities; using IPA.Utilities;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static IPA.Logging.Logger; using static IPA.Logging.Logger;
namespace IPA.Injector namespace IPA.Injector
{ {
class Updates
internal static class Updates
{ {
public const string DeleteFileName = Updating.ModsaberML.Updater._SpecialDeletionsFile;
private const string DeleteFileName = Updating.ModSaber.Updater.SpecialDeletionsFile;
public static void InstallPendingUpdates() public static void InstallPendingUpdates()
{ {
var pendingDir = Path.Combine(BeatSaber.InstallPath, "IPA", "Pending"); var pendingDir = Path.Combine(BeatSaber.InstallPath, "IPA", "Pending");


+ 4
- 10
IPA.Injector/Virtualizer.cs View File

@ -1,19 +1,14 @@
using Mono.Cecil; using Mono.Cecil;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
namespace IPA.Injector namespace IPA.Injector
{ {
internal class VirtualizedModule internal class VirtualizedModule
{ {
private const string ENTRY_TYPE = "Display";
public FileInfo file;
public ModuleDefinition module;
private readonly FileInfo file;
private ModuleDefinition module;
public static VirtualizedModule Load(string engineFile) public static VirtualizedModule Load(string engineFile)
{ {
@ -35,11 +30,10 @@ namespace IPA.Injector
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="module"></param>
public void Virtualize(AssemblyName selfName, Action beforeChangeCallback = null) public void Virtualize(AssemblyName selfName, Action beforeChangeCallback = null)
{ {
bool changed = false;
bool virtualize = true;
var changed = false;
var virtualize = true;
foreach (var r in module.AssemblyReferences) foreach (var r in module.AssemblyReferences)
{ {
if (r.Name == selfName.Name) if (r.Name == selfName.Name)


+ 0
- 21
IPA.Injector/WtfThisDoesntNeedToExist.cs View File

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Injector
{
[AttributeUsage(AttributeTargets.Assembly)]
internal class ForceAssemblyReferenceAttribute : Attribute
{
public ForceAssemblyReferenceAttribute(Type forcedType)
{
//not sure if these two lines are required since
//the type is passed to constructor as parameter,
//thus effectively being used
Action<Type> noop = _ => { };
noop(forcedType);
}
}
}

+ 16
- 18
IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs View File

@ -1,12 +1,10 @@
using IPA.Logging;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using IPA.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Config.ConfigProviders namespace IPA.Config.ConfigProviders
{ {
@ -17,11 +15,11 @@ namespace IPA.Config.ConfigProviders
// TODO: create a wrapper that allows empty object creation // TODO: create a wrapper that allows empty object creation
public dynamic Dynamic => jsonObj; public dynamic Dynamic => jsonObj;
public bool HasChanged { get; private set; } = false;
public bool HasChanged { get; private set; }
public DateTime LastModified => File.GetLastWriteTime(Filename + ".json"); public DateTime LastModified => File.GetLastWriteTime(Filename + ".json");
private string _filename = null;
private string _filename;
public string Filename public string Filename
{ {
get => _filename; get => _filename;
@ -37,10 +35,10 @@ namespace IPA.Config.ConfigProviders
{ {
Logger.config.Debug($"Loading file {Filename}.json"); Logger.config.Debug($"Loading file {Filename}.json");
var finfo = new FileInfo(Filename + ".json");
if (finfo.Exists)
var fileInfo = new FileInfo(Filename + ".json");
if (fileInfo.Exists)
{ {
string json = finfo.OpenText().ReadToEnd();
var json = fileInfo.OpenText().ReadToEnd();
try try
{ {
jsonObj = JObject.Parse(json); jsonObj = JObject.Parse(json);
@ -50,7 +48,7 @@ namespace IPA.Config.ConfigProviders
Logger.config.Error($"Error parsing JSON in file {Filename}.json; resetting to empty JSON"); Logger.config.Error($"Error parsing JSON in file {Filename}.json; resetting to empty JSON");
Logger.config.Error(e); Logger.config.Error(e);
jsonObj = new JObject(); jsonObj = new JObject();
File.WriteAllText(finfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
File.WriteAllText(fileInfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
} }
} }
else else
@ -68,17 +66,17 @@ namespace IPA.Config.ConfigProviders
jsonObj.CollectionChanged += JsonObj_CollectionChanged; jsonObj.CollectionChanged += JsonObj_CollectionChanged;
} }
private void JsonObj_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
private void JsonObj_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
HasChanged = true; HasChanged = true;
} }
private void JsonObj_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
private void JsonObj_ListChanged(object sender, ListChangedEventArgs e)
{ {
HasChanged = true; HasChanged = true;
} }
private void JsonObj_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
private void JsonObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
HasChanged = true; HasChanged = true;
} }
@ -92,9 +90,9 @@ namespace IPA.Config.ConfigProviders
{ {
Logger.config.Debug($"Saving file {Filename}.json"); Logger.config.Debug($"Saving file {Filename}.json");
var finfo = new FileInfo(Filename + ".json");
var fileInfo = new FileInfo(Filename + ".json");
File.WriteAllText(finfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
File.WriteAllText(fileInfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
HasChanged = false; HasChanged = false;
} }


+ 1
- 4
IPA.Loader/Config/IConfigProvider.cs View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// ReSharper disable UnusedMember.Global
namespace IPA.Config namespace IPA.Config
{ {


+ 12
- 14
IPA.Loader/Config/IniFile.cs View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -72,28 +70,28 @@ namespace IPA.Config
/// <summary> /// <summary>
/// Write Data to the INI File /// Write Data to the INI File
/// </summary> /// </summary>
/// <PARAM name="Section"></PARAM>
/// <PARAM name="section"></PARAM>
/// Section name /// Section name
/// <PARAM name="Key"></PARAM>
/// <PARAM name="key"></PARAM>
/// Key Name /// Key Name
/// <PARAM name="Value"></PARAM>
/// <PARAM name="value"></PARAM>
/// Value Name /// Value Name
public void IniWriteValue(string Section, string Key, string Value)
public void IniWriteValue(string section, string key, string value)
{ {
WritePrivateProfileString(Section, Key, Value, IniFileInfo.FullName);
WritePrivateProfileString(section, key, value, IniFileInfo.FullName);
} }
/// <summary> /// <summary>
/// Read Data Value From the Ini File /// Read Data Value From the Ini File
/// </summary> /// </summary>
/// <PARAM name="Section"></PARAM>
/// <PARAM name="Key"></PARAM>
/// <PARAM name="section"></PARAM>
/// <PARAM name="key"></PARAM>
/// <returns></returns> /// <returns></returns>
public string IniReadValue(string Section, string Key)
public string IniReadValue(string section, string key)
{ {
const int MAX_CHARS = 1023;
StringBuilder result = new StringBuilder(MAX_CHARS);
GetPrivateProfileString(Section, Key, "", result, MAX_CHARS, IniFileInfo.FullName);
const int maxChars = 1023;
StringBuilder result = new StringBuilder(maxChars);
GetPrivateProfileString(section, key, "", result, maxChars, IniFileInfo.FullName);
return result.ToString(); return result.ToString();
} }
} }


+ 28
- 35
IPA.Loader/Config/ModPrefs.cs View File

@ -1,9 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text;
namespace IPA.Config namespace IPA.Config
{ {
@ -85,43 +84,37 @@ namespace IPA.Config
void SetBool(string section, string name, bool value); void SetBool(string section, string name, bool value);
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// Allows to get and set preferences for your mod. /// Allows to get and set preferences for your mod.
/// </summary> /// </summary>
public class ModPrefs : IModPrefs public class ModPrefs : IModPrefs
{ {
private static ModPrefs _staticInstance = null;
private static IModPrefs StaticInstace
{
get
{
if (_staticInstance == null)
_staticInstance = new ModPrefs();
return _staticInstance;
}
}
private static ModPrefs _staticInstance;
private static IModPrefs StaticInstance => _staticInstance ?? (_staticInstance = new ModPrefs());
internal static Dictionary<IBeatSaberPlugin, ModPrefs> ModPrefses { get; set; } = new Dictionary<IBeatSaberPlugin, ModPrefs>();
// ReSharper disable once IdentifierTypo
internal static Dictionary<IBeatSaberPlugin, ModPrefs> ModPrefss { get; set; } = new Dictionary<IBeatSaberPlugin, ModPrefs>();
private IniFile Instance;
private readonly IniFile _instance;
/// <summary> /// <summary>
/// Constructs a ModPrefs object for the provide plugin. /// Constructs a ModPrefs object for the provide plugin.
/// </summary> /// </summary>
/// <param name="plugin">the plugin to get the preferences file for</param> /// <param name="plugin">the plugin to get the preferences file for</param>
public ModPrefs(IBeatSaberPlugin plugin) { public ModPrefs(IBeatSaberPlugin plugin) {
Instance = new IniFile(Path.Combine(Environment.CurrentDirectory, "UserData", "ModPrefs", $"{plugin.Name}.ini"));
ModPrefses.Add(plugin, this);
_instance = new IniFile(Path.Combine(Environment.CurrentDirectory, "UserData", "ModPrefs", $"{plugin.Name}.ini"));
ModPrefss.Add(plugin, this);
} }
private ModPrefs() private ModPrefs()
{ {
Instance = new IniFile(Path.Combine(Environment.CurrentDirectory, "UserData", "modprefs.ini"));
_instance = new IniFile(Path.Combine(Environment.CurrentDirectory, "UserData", "modprefs.ini"));
} }
string IModPrefs.GetString(string section, string name, string defaultValue, bool autoSave) string IModPrefs.GetString(string section, string name, string defaultValue, bool autoSave)
{ {
var value = Instance.IniReadValue(section, name);
var value = _instance.IniReadValue(section, name);
if (value != "") if (value != "")
return value; return value;
else if (autoSave) else if (autoSave)
@ -138,11 +131,11 @@ namespace IPA.Config
/// <param name="autoSave">Whether or not the default value should be written if no value is found.</param> /// <param name="autoSave">Whether or not the default value should be written if no value is found.</param>
/// <returns></returns> /// <returns></returns>
public static string GetString(string section, string name, string defaultValue = "", bool autoSave = false) public static string GetString(string section, string name, string defaultValue = "", bool autoSave = false)
=> StaticInstace.GetString(section, name, defaultValue, autoSave);
=> StaticInstance.GetString(section, name, defaultValue, autoSave);
int IModPrefs.GetInt(string section, string name, int defaultValue, bool autoSave) int IModPrefs.GetInt(string section, string name, int defaultValue, bool autoSave)
{ {
if (int.TryParse(Instance.IniReadValue(section, name), out var value))
if (int.TryParse(_instance.IniReadValue(section, name), out var value))
return value; return value;
else if (autoSave) else if (autoSave)
(this as IModPrefs).SetInt(section, name, defaultValue); (this as IModPrefs).SetInt(section, name, defaultValue);
@ -158,11 +151,11 @@ namespace IPA.Config
/// <param name="autoSave">Whether or not the default value should be written if no value is found.</param> /// <param name="autoSave">Whether or not the default value should be written if no value is found.</param>
/// <returns></returns> /// <returns></returns>
public static int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) public static int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false)
=> StaticInstace.GetInt(section, name, defaultValue, autoSave);
=> StaticInstance.GetInt(section, name, defaultValue, autoSave);
float IModPrefs.GetFloat(string section, string name, float defaultValue, bool autoSave) float IModPrefs.GetFloat(string section, string name, float defaultValue, bool autoSave)
{ {
if (float.TryParse(Instance.IniReadValue(section, name), out var value))
if (float.TryParse(_instance.IniReadValue(section, name), out var value))
return value; return value;
else if (autoSave) else if (autoSave)
(this as IModPrefs).SetFloat(section, name, defaultValue); (this as IModPrefs).SetFloat(section, name, defaultValue);
@ -178,7 +171,7 @@ namespace IPA.Config
/// <param name="autoSave">Whether or not the default value should be written if no value is found.</param> /// <param name="autoSave">Whether or not the default value should be written if no value is found.</param>
/// <returns></returns> /// <returns></returns>
public static float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) public static float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false)
=> StaticInstace.GetFloat(section, name, defaultValue, autoSave);
=> StaticInstance.GetFloat(section, name, defaultValue, autoSave);
bool IModPrefs.GetBool(string section, string name, bool defaultValue, bool autoSave) bool IModPrefs.GetBool(string section, string name, bool defaultValue, bool autoSave)
{ {
@ -203,11 +196,11 @@ namespace IPA.Config
/// <param name="autoSave">Whether or not the default value should be written if no value is found.</param> /// <param name="autoSave">Whether or not the default value should be written if no value is found.</param>
/// <returns></returns> /// <returns></returns>
public static bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) public static bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false)
=> StaticInstace.GetBool(section, name, defaultValue, autoSave);
=> StaticInstance.GetBool(section, name, defaultValue, autoSave);
bool IModPrefs.HasKey(string section, string name) bool IModPrefs.HasKey(string section, string name)
{ {
return Instance.IniReadValue(section, name) != null;
return _instance.IniReadValue(section, name) != null;
} }
/// <summary> /// <summary>
/// Checks whether or not a key exists in the ini. /// Checks whether or not a key exists in the ini.
@ -215,11 +208,11 @@ namespace IPA.Config
/// <param name="section">Section of the key.</param> /// <param name="section">Section of the key.</param>
/// <param name="name">Name of the key.</param> /// <param name="name">Name of the key.</param>
/// <returns></returns> /// <returns></returns>
public static bool HasKey(string section, string name) => StaticInstace.HasKey(section, name);
public static bool HasKey(string section, string name) => StaticInstance.HasKey(section, name);
void IModPrefs.SetFloat(string section, string name, float value) void IModPrefs.SetFloat(string section, string name, float value)
{ {
Instance.IniWriteValue(section, name, value.ToString());
_instance.IniWriteValue(section, name, value.ToString(CultureInfo.InvariantCulture));
} }
/// <summary> /// <summary>
/// Sets a float in the ini. /// Sets a float in the ini.
@ -228,11 +221,11 @@ namespace IPA.Config
/// <param name="name">Name of the key.</param> /// <param name="name">Name of the key.</param>
/// <param name="value">Value that should be written.</param> /// <param name="value">Value that should be written.</param>
public static void SetFloat(string section, string name, float value) public static void SetFloat(string section, string name, float value)
=> StaticInstace.SetFloat(section, name, value);
=> StaticInstance.SetFloat(section, name, value);
void IModPrefs.SetInt(string section, string name, int value) void IModPrefs.SetInt(string section, string name, int value)
{ {
Instance.IniWriteValue(section, name, value.ToString());
_instance.IniWriteValue(section, name, value.ToString());
} }
/// <summary> /// <summary>
/// Sets an int in the ini. /// Sets an int in the ini.
@ -241,11 +234,11 @@ namespace IPA.Config
/// <param name="name">Name of the key.</param> /// <param name="name">Name of the key.</param>
/// <param name="value">Value that should be written.</param> /// <param name="value">Value that should be written.</param>
public static void SetInt(string section, string name, int value) public static void SetInt(string section, string name, int value)
=> StaticInstace.SetInt(section, name, value);
=> StaticInstance.SetInt(section, name, value);
void IModPrefs.SetString(string section, string name, string value) void IModPrefs.SetString(string section, string name, string value)
{ {
Instance.IniWriteValue(section, name, value);
_instance.IniWriteValue(section, name, value);
} }
/// <summary> /// <summary>
/// Sets a string in the ini. /// Sets a string in the ini.
@ -254,11 +247,11 @@ namespace IPA.Config
/// <param name="name">Name of the key.</param> /// <param name="name">Name of the key.</param>
/// <param name="value">Value that should be written.</param> /// <param name="value">Value that should be written.</param>
public static void SetString(string section, string name, string value) public static void SetString(string section, string name, string value)
=> StaticInstace.SetString(section, name, value);
=> StaticInstance.SetString(section, name, value);
void IModPrefs.SetBool(string section, string name, bool value) void IModPrefs.SetBool(string section, string name, bool value)
{ {
Instance.IniWriteValue(section, name, value ? "1" : "0");
_instance.IniWriteValue(section, name, value ? "1" : "0");
} }
/// <summary> /// <summary>
/// Sets a bool in the ini. /// Sets a bool in the ini.
@ -267,7 +260,7 @@ namespace IPA.Config
/// <param name="name">Name of the key.</param> /// <param name="name">Name of the key.</param>
/// <param name="value">Value that should be written.</param> /// <param name="value">Value that should be written.</param>
public static void SetBool(string section, string name, bool value) public static void SetBool(string section, string name, bool value)
=> StaticInstace.SetBool(section, name, value);
=> StaticInstance.SetBool(section, name, value);
} }
/// <summary> /// <summary>
@ -280,7 +273,7 @@ namespace IPA.Config
/// <param name="plugin">the plugin wanting the prefrences</param> /// <param name="plugin">the plugin wanting the prefrences</param>
/// <returns>the ModPrefs object</returns> /// <returns>the ModPrefs object</returns>
public static IModPrefs GetModPrefs(this IBeatSaberPlugin plugin) { public static IModPrefs GetModPrefs(this IBeatSaberPlugin plugin) {
return ModPrefs.ModPrefses.First(o => o.Key == plugin).Value;
return ModPrefs.ModPrefss.First(o => o.Key == plugin).Value;
} }
} }
} }

+ 4
- 6
IPA.Loader/IPA.Loader.csproj View File

@ -70,7 +70,7 @@
<Compile Include="Logging\Logger.cs" /> <Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\LogPrinter.cs" /> <Compile Include="Logging\LogPrinter.cs" />
<Compile Include="Config\ModPrefs.cs" /> <Compile Include="Config\ModPrefs.cs" />
<Compile Include="Updating\Converters\ModsaberDependencyConverter.cs" />
<Compile Include="Updating\Converters\ModSaberDependencyConverter.cs" />
<Compile Include="Updating\Converters\SemverRangeConverter.cs" /> <Compile Include="Updating\Converters\SemverRangeConverter.cs" />
<Compile Include="Updating\Converters\SemverVersionConverter.cs" /> <Compile Include="Updating\Converters\SemverVersionConverter.cs" />
<Compile Include="Utilities\BeatSaber.cs" /> <Compile Include="Utilities\BeatSaber.cs" />
@ -86,9 +86,8 @@
<Compile Include="Loader\PluginComponent.cs" /> <Compile Include="Loader\PluginComponent.cs" />
<Compile Include="Loader\PluginManager.cs" /> <Compile Include="Loader\PluginManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Updating\Backup\BackupUnit.cs" />
<Compile Include="Updating\ModsaberML\ApiEndpoint.cs" />
<Compile Include="Updating\ModsaberML\Updater.cs" />
<Compile Include="Updating\ModSaber\ApiEndpoint.cs" />
<Compile Include="Updating\ModSaber\Updater.cs" />
<Compile Include="Updating\SelfPlugin.cs" /> <Compile Include="Updating\SelfPlugin.cs" />
<Compile Include="Utilities\Extensions.cs" /> <Compile Include="Utilities\Extensions.cs" />
<Compile Include="Utilities\LoneFunctions.cs" /> <Compile Include="Utilities\LoneFunctions.cs" />
@ -107,7 +106,6 @@
<Version>1.2.0</Version> <Version>1.2.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

+ 4
- 10
IPA.Loader/Loader/Composite/CompositeBSPlugin.cs View File

@ -1,9 +1,5 @@
using IPA;
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using Logger = IPA.Logging.Logger; using Logger = IPA.Logging.Logger;
@ -11,7 +7,7 @@ namespace IPA.Loader.Composite
{ {
internal class CompositeBSPlugin : IBeatSaberPlugin internal class CompositeBSPlugin : IBeatSaberPlugin
{ {
IEnumerable<IBeatSaberPlugin> plugins;
private readonly IEnumerable<IBeatSaberPlugin> plugins;
private delegate void CompositeCall(IBeatSaberPlugin plugin); private delegate void CompositeCall(IBeatSaberPlugin plugin);
@ -83,14 +79,12 @@ namespace IPA.Loader.Composite
public string Version => throw new NotImplementedException(); public string Version => throw new NotImplementedException();
public Uri UpdateUri => throw new NotImplementedException();
public ModsaberModInfo ModInfo => throw new NotImplementedException(); public ModsaberModInfo ModInfo => throw new NotImplementedException();
public void OnLateUpdate() { public void OnLateUpdate() {
Invoke(plugin => { Invoke(plugin => {
if (plugin is IEnhancedBeatSaberPlugin)
((IEnhancedBeatSaberPlugin) plugin).OnLateUpdate();
if (plugin is IEnhancedBeatSaberPlugin saberPlugin)
saberPlugin.OnLateUpdate();
}); });
} }
} }

+ 7
- 16
IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs View File

@ -1,11 +1,6 @@
using System;
using IPA.Old;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using IPA;
using IPA.Old;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
using Logger = IPA.Logging.Logger; using Logger = IPA.Logging.Logger;
namespace IPA.Loader.Composite namespace IPA.Loader.Composite
@ -13,7 +8,7 @@ namespace IPA.Loader.Composite
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
internal class CompositeIPAPlugin : IPlugin internal class CompositeIPAPlugin : IPlugin
{ {
IEnumerable<IPlugin> plugins;
private readonly IEnumerable<IPlugin> plugins;
private delegate void CompositeCall(IPlugin plugin); private delegate void CompositeCall(IPlugin plugin);
@ -48,18 +43,14 @@ namespace IPA.Loader.Composite
Invoke(plugin => plugin.OnFixedUpdate()); Invoke(plugin => plugin.OnFixedUpdate());
} }
public string Name {
get { throw new NotImplementedException(); }
}
public string Name => throw new NotImplementedException();
public string Version {
get { throw new NotImplementedException(); }
}
public string Version => throw new NotImplementedException();
public void OnLateUpdate() { public void OnLateUpdate() {
Invoke(plugin => { Invoke(plugin => {
if (plugin is IEnhancedBeatSaberPlugin)
((IEnhancedBeatSaberPlugin) plugin).OnLateUpdate();
if (plugin is IEnhancedPlugin saberPlugin)
saberPlugin.OnLateUpdate();
}); });
} }


+ 7
- 8
IPA.Loader/Loader/PluginComponent.cs View File

@ -1,19 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using IPA.Loader.Composite;
using System;
using System.Diagnostics.CodeAnalysis;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using IPA.Loader;
using IPA.Loader.Composite;
using IPA.Logging;
// ReSharper disable UnusedMember.Local
namespace IPA.Loader namespace IPA.Loader
{ {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal class PluginComponent : MonoBehaviour internal class PluginComponent : MonoBehaviour
{ {
private CompositeBSPlugin bsPlugins; private CompositeBSPlugin bsPlugins;
private CompositeIPAPlugin ipaPlugins; private CompositeIPAPlugin ipaPlugins;
private bool quitting = false;
private bool quitting;
internal static PluginComponent Create() internal static PluginComponent Create()
{ {
@ -29,7 +28,7 @@ namespace IPA.Loader
ipaPlugins = new CompositeIPAPlugin(PluginManager.Plugins); ipaPlugins = new CompositeIPAPlugin(PluginManager.Plugins);
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
gameObject.AddComponent<Updating.ModsaberML.Updater>();
gameObject.AddComponent<Updating.ModSaber.Updater>();
bsPlugins.OnApplicationStart(); bsPlugins.OnApplicationStart();
ipaPlugins.OnApplicationStart(); ipaPlugins.OnApplicationStart();


+ 29
- 31
IPA.Loader/Loader/PluginManager.cs View File

@ -1,22 +1,20 @@
using IPA;
using IPA.Config;
using IPA.Config.ConfigProviders;
using IPA.Logging;
using IPA.Old;
using IPA.Updating;
using IPA.Utilities;
using Mono.Cecil;
using System;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
using IPA.Config;
using IPA.Config.ConfigProviders;
using IPA.Logging;
using IPA.Old;
using IPA.Updating;
using IPA.Utilities;
using Mono.Cecil;
using UnityEngine;
using Logger = IPA.Logging.Logger;
namespace IPA.Loader namespace IPA.Loader
{ {
@ -35,9 +33,9 @@ namespace IPA.Loader
internal IBeatSaberPlugin Plugin { get; set; } internal IBeatSaberPlugin Plugin { get; set; }
internal string Filename { get; set; } internal string Filename { get; set; }
/// <summary> /// <summary>
/// The Modsaber updating info for the mod, or null.
/// The ModSaber updating info for the mod, or null.
/// </summary> /// </summary>
public ModsaberModInfo ModsaberInfo { get; internal set; }
public ModsaberModInfo ModSaberInfo { get; internal set; }
} }
/// <summary> /// <summary>
@ -51,10 +49,10 @@ namespace IPA.Loader
{ {
LoadPlugins(); LoadPlugins();
} }
return _bsPlugins.Select(p => p.Plugin);
return (_bsPlugins ?? throw new InvalidOperationException()).Select(p => p.Plugin);
} }
} }
private static List<PluginInfo> _bsPlugins = null;
private static List<PluginInfo> _bsPlugins;
internal static IEnumerable<PluginInfo> BSMetas internal static IEnumerable<PluginInfo> BSMetas
{ {
get get
@ -74,17 +72,17 @@ namespace IPA.Loader
/// <returns>the plugin info for the requested plugin or null</returns> /// <returns>the plugin info for the requested plugin or null</returns>
public static PluginInfo GetPlugin(string name) public static PluginInfo GetPlugin(string name)
{ {
return BSMetas.Where(p => p.Plugin.Name == name).FirstOrDefault();
return BSMetas.FirstOrDefault(p => p.Plugin.Name == name);
} }
/// <summary> /// <summary>
/// Gets info about the plugin with the specified modsaber name.
/// Gets info about the plugin with the specified ModSaber name.
/// </summary> /// </summary>
/// <param name="name">the modsaber name of the plugin to get (must be an exact match)</param>
/// <param name="name">the ModSaber name of the plugin to get (must be an exact match)</param>
/// <returns>the plugin info for the requested plugin or null</returns> /// <returns>the plugin info for the requested plugin or null</returns>
public static PluginInfo GetPluginFromModsaberName(string name)
public static PluginInfo GetPluginFromModSaberName(string name)
{ {
return BSMetas.Where(p => p.ModsaberInfo.InternalName == name).FirstOrDefault();
return BSMetas.FirstOrDefault(p => p.ModSaberInfo.InternalName == name);
} }
/// <summary> /// <summary>
@ -102,11 +100,11 @@ namespace IPA.Loader
return _ipaPlugins; return _ipaPlugins;
} }
} }
private static List<IPlugin> _ipaPlugins = null;
private static List<IPlugin> _ipaPlugins;
internal static IConfigProvider SelfConfigProvider { get; set; } = null;
internal static IConfigProvider SelfConfigProvider { get; set; }
internal static List<KeyValuePair<IConfigProvider,Ref<DateTime>>> configProviders = new List<KeyValuePair<IConfigProvider, Ref<DateTime>>>();
internal static readonly List<KeyValuePair<IConfigProvider,Ref<DateTime>>> configProviders = new List<KeyValuePair<IConfigProvider, Ref<DateTime>>>();
private static void LoadPlugins() private static void LoadPlugins()
{ {
@ -175,7 +173,7 @@ namespace IPA.Loader
if (@ref.FullName == "IllusionInjector.Updating.Backup.BackupUnit") @ref.Namespace = "IPA.Updating.Backup"; //@ref.Name = ""; if (@ref.FullName == "IllusionInjector.Updating.Backup.BackupUnit") @ref.Namespace = "IPA.Updating.Backup"; //@ref.Name = "";
if (@ref.Namespace == "IllusionInjector.Utilities") @ref.Namespace = "IPA.Utilities"; //@ref.Name = ""; if (@ref.Namespace == "IllusionInjector.Utilities") @ref.Namespace = "IPA.Utilities"; //@ref.Name = "";
if (@ref.Namespace == "IllusionInjector.Logging.Printers") @ref.Namespace = "IPA.Logging.Printers"; //@ref.Name = ""; if (@ref.Namespace == "IllusionInjector.Logging.Printers") @ref.Namespace = "IPA.Logging.Printers"; //@ref.Name = "";
if (@ref.Namespace == "IllusionInjector.Updating.ModsaberML") @ref.Namespace = "IPA.Updating.ModsaberML"; //@ref.Name = "";
if (@ref.Namespace == "IllusionInjector.Updating.ModsaberML") @ref.Namespace = "IPA.Updating.ModSaber"; //@ref.Name = "";
} }
module.Write(pluginCopy); module.Write(pluginCopy);
@ -187,11 +185,11 @@ namespace IPA.Loader
Filename = Path.Combine(Environment.CurrentDirectory, "IPA.exe"), Filename = Path.Combine(Environment.CurrentDirectory, "IPA.exe"),
Plugin = SelfPlugin.Instance Plugin = SelfPlugin.Instance
}; };
selfPlugin.ModsaberInfo = selfPlugin.Plugin.ModInfo;
selfPlugin.ModSaberInfo = selfPlugin.Plugin.ModInfo;
_bsPlugins.Add(selfPlugin); _bsPlugins.Add(selfPlugin);
configProviders.Add(new KeyValuePair<IConfigProvider, Ref<DateTime>>(SelfConfigProvider = new JsonConfigProvider() { Filename = Path.Combine("UserData", SelfPlugin.IPA_Name) }, new Ref<DateTime>(SelfConfigProvider.LastModified)));
configProviders.Add(new KeyValuePair<IConfigProvider, Ref<DateTime>>(SelfConfigProvider = new JsonConfigProvider { Filename = Path.Combine("UserData", SelfPlugin.IPA_Name) }, new Ref<DateTime>(SelfConfigProvider.LastModified)));
SelfConfigProvider.Load(); SelfConfigProvider.Load();
//Load copied plugins //Load copied plugins
@ -204,7 +202,7 @@ namespace IPA.Loader
} }
Logger.log.Info(exeName); Logger.log.Info(exeName);
Logger.log.Info($"Running on Unity {UnityEngine.Application.unityVersion}");
Logger.log.Info($"Running on Unity {Application.unityVersion}");
Logger.log.Info($"Game version {BeatSaber.GameVersion}"); Logger.log.Info($"Game version {BeatSaber.GameVersion}");
Logger.log.Info("-----------------------------"); Logger.log.Info("-----------------------------");
Logger.log.Info($"Loading plugins from {LoneFunctions.GetRelativePath(pluginDirectory, Environment.CurrentDirectory)} and found {_bsPlugins.Count + _ipaPlugins.Count}"); Logger.log.Info($"Loading plugins from {LoneFunctions.GetRelativePath(pluginDirectory, Environment.CurrentDirectory)} and found {_bsPlugins.Count + _ipaPlugins.Count}");
@ -294,7 +292,7 @@ namespace IPA.Loader
{ {
if (cfgProvider == null) if (cfgProvider == null)
{ {
cfgProvider = new JsonConfigProvider() { Filename = Path.Combine("UserData", $"{bsPlugin.Name}") };
cfgProvider = new JsonConfigProvider { Filename = Path.Combine("UserData", $"{bsPlugin.Name}") };
configProviders.Add(new KeyValuePair<IConfigProvider, Ref<DateTime>>(cfgProvider, new Ref<DateTime>(cfgProvider.LastModified))); configProviders.Add(new KeyValuePair<IConfigProvider, Ref<DateTime>>(cfgProvider, new Ref<DateTime>(cfgProvider.LastModified)));
cfgProvider.Load(); cfgProvider.Load();
} }
@ -311,12 +309,12 @@ namespace IPA.Loader
{ {
Plugin = bsPlugin, Plugin = bsPlugin,
Filename = file.Replace("\\.cache", ""), // quick and dirty fix Filename = file.Replace("\\.cache", ""), // quick and dirty fix
ModsaberInfo = bsPlugin.ModInfo
ModSaberInfo = bsPlugin.ModInfo
}); });
} }
catch (AmbiguousMatchException) catch (AmbiguousMatchException)
{ {
Logger.loader.Error($"Only one Init allowed per plugin");
Logger.loader.Error("Only one Init allowed per plugin");
} }
} }
else else


+ 0
- 4
IPA.Loader/Logging/LogPrinter.cs View File

@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Logging namespace IPA.Logging
{ {


+ 2
- 5
IPA.Loader/Logging/Logger.cs View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// ReSharper disable InconsistentNaming
namespace IPA.Logging namespace IPA.Logging
{ {
@ -25,7 +22,7 @@ namespace IPA.Logging
internal static Logger libLoader => log.GetChildLogger("LibraryLoader"); internal static Logger libLoader => log.GetChildLogger("LibraryLoader");
internal static Logger loader => log.GetChildLogger("Loader"); internal static Logger loader => log.GetChildLogger("Loader");
internal static Logger config => log.GetChildLogger("Config"); internal static Logger config => log.GetChildLogger("Config");
internal static bool LogCreated => _log != null || UnityLogInterceptor._logger != null;
internal static bool LogCreated => _log != null || UnityLogInterceptor.Logger != null;
/// <summary> /// <summary>
/// The standard format for log messages. /// The standard format for log messages.


+ 2
- 8
IPA.Loader/Logging/Printers/ColoredConsolePrinter.cs View File

@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IPA.Logging;
namespace IPA.Logging.Printers namespace IPA.Logging.Printers
{ {
@ -35,8 +29,8 @@ namespace IPA.Logging.Printers
{ {
if (((byte)level & (byte)StandardLogger.PrintFilter) == 0) return; if (((byte)level & (byte)StandardLogger.PrintFilter) == 0) return;
Console.ForegroundColor = Color; Console.ForegroundColor = Color;
foreach (var line in message.Split(new string[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
Console.WriteLine(string.Format(Logger.LogFormat, line, logName, time, level.ToString().ToUpper()));
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
Console.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
Console.ResetColor(); Console.ResetColor();
} }
} }


+ 12
- 16
IPA.Loader/Logging/Printers/GZFilePrinter.cs View File

@ -1,12 +1,8 @@
using IPA.Logging;
using Ionic.Zlib;
using Ionic.Zlib;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace IPA.Logging.Printers namespace IPA.Logging.Printers
{ {
@ -26,7 +22,7 @@ namespace IPA.Logging.Printers
/// <summary> /// <summary>
/// The <see cref="StreamWriter"/> that writes to the GZip file. /// The <see cref="StreamWriter"/> that writes to the GZip file.
/// </summary> /// </summary>
protected StreamWriter fileWriter;
protected StreamWriter FileWriter;
private GZipStream zstream; private GZipStream zstream;
private FileStream fstream; private FileStream fstream;
@ -47,7 +43,7 @@ namespace IPA.Logging.Printers
fileInfo = new FileInfo(fileInfo.FullName + ".gz"); fileInfo = new FileInfo(fileInfo.FullName + ".gz");
fileInfo.Create().Close(); fileInfo.Create().Close();
var symlink = new FileInfo(Path.Combine(fileInfo.DirectoryName, $"latest{ext}.gz"));
var symlink = new FileInfo(Path.Combine(fileInfo.DirectoryName ?? throw new InvalidOperationException(), $"latest{ext}.gz"));
if (symlink.Exists) symlink.Delete(); if (symlink.Exists) symlink.Delete();
try try
@ -75,7 +71,7 @@ namespace IPA.Logging.Printers
/// <summary> /// <summary>
/// Called at the start of any print session. /// Called at the start of any print session.
/// </summary> /// </summary>
public override sealed void StartPrint()
public sealed override void StartPrint()
{ {
InitLog(); InitLog();
@ -84,21 +80,21 @@ namespace IPA.Logging.Printers
{ {
FlushMode = FlushType.Full FlushMode = FlushType.Full
}; };
fileWriter = new StreamWriter(zstream, new UTF8Encoding(false));
FileWriter = new StreamWriter(zstream, new UTF8Encoding(false));
} }
/// <summary> /// <summary>
/// Called at the end of any print session. /// Called at the end of any print session.
/// </summary> /// </summary>
public override sealed void EndPrint()
public sealed override void EndPrint()
{ {
fileWriter.Flush();
FileWriter.Flush();
zstream.Flush(); zstream.Flush();
fstream.Flush(); fstream.Flush();
fileWriter.Close();
FileWriter.Close();
zstream.Close(); zstream.Close();
fstream.Close(); fstream.Close();
fileWriter.Dispose();
FileWriter.Dispose();
zstream.Dispose(); zstream.Dispose();
fstream.Dispose(); fstream.Dispose();
} }
@ -120,13 +116,13 @@ namespace IPA.Logging.Printers
{ {
if (disposing) if (disposing)
{ {
fileWriter.Flush();
FileWriter.Flush();
zstream.Flush(); zstream.Flush();
fstream.Flush(); fstream.Flush();
fileWriter.Close();
FileWriter.Close();
zstream.Close(); zstream.Close();
fstream.Close(); fstream.Close();
fileWriter.Dispose();
FileWriter.Dispose();
zstream.Dispose(); zstream.Dispose();
fstream.Dispose(); fstream.Dispose();
} }


+ 2
- 7
IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs View File

@ -1,10 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IPA.Logging;
namespace IPA.Logging.Printers namespace IPA.Logging.Printers
{ {
@ -27,8 +22,8 @@ namespace IPA.Logging.Printers
/// <param name="message">the message to print</param> /// <param name="message">the message to print</param>
public override void Print(Logger.Level level, DateTime time, string logName, string message) public override void Print(Logger.Level level, DateTime time, string logName, string message)
{ {
foreach (var line in message.Split(new string[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
fileWriter.WriteLine(string.Format(Logger.LogFormat, line, logName, time, level.ToString().ToUpper()));
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
} }
/// <summary> /// <summary>


+ 3
- 8
IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs View File

@ -1,10 +1,5 @@
using IPA.Logging;
using System;
using System.Collections.Generic;
using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Logging.Printers namespace IPA.Logging.Printers
{ {
@ -50,8 +45,8 @@ namespace IPA.Logging.Printers
/// <param name="message">the message to print</param> /// <param name="message">the message to print</param>
public override void Print(Logger.Level level, DateTime time, string logName, string message) public override void Print(Logger.Level level, DateTime time, string logName, string message)
{ {
foreach (var line in message.Split(new string[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
fileWriter.WriteLine(string.Format(Logger.LogFormat, line, logName, time, level.ToString().ToUpper()));
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
} }
} }
} }

+ 3
- 8
IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs View File

@ -1,10 +1,5 @@
using IPA.Logging;
using System;
using System.Collections.Generic;
using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Logging.Printers namespace IPA.Logging.Printers
{ {
@ -53,8 +48,8 @@ namespace IPA.Logging.Printers
/// <param name="message">the message to print</param> /// <param name="message">the message to print</param>
public override void Print(Logger.Level level, DateTime time, string logName, string message) public override void Print(Logger.Level level, DateTime time, string logName, string message)
{ {
foreach (var line in message.Split(new string[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
fileWriter.WriteLine(string.Format("[{3} @ {2:HH:mm:ss}] {0}", line, logName, time, level.ToString().ToUpper()));
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine("[{2} @ {1:HH:mm:ss}] {0}", line, time, level.ToString().ToUpper());
} }
} }
} }

+ 25
- 29
IPA.Loader/Logging/StandardLogger.cs View File

@ -1,20 +1,15 @@
using IPA.Logging;
using IPA.Config;
using IPA.Logging.Printers;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using IPA;
using IPA.Logging.Printers;
using IPA.Config;
namespace IPA.Logging namespace IPA.Logging
{ {
/// <summary> /// <summary>
/// The default <see cref="Logger"/> implimentation.
/// The default <see cref="Logger"/> implementation.
/// </summary> /// </summary>
public class StandardLogger : Logger public class StandardLogger : Logger
{ {
@ -48,8 +43,8 @@ namespace IPA.Logging
new GlobalLogFilePrinter() new GlobalLogFilePrinter()
}; };
private string logName;
private static readonly bool showSourceClass = true;
private readonly string logName;
private static readonly bool showSourceClass;
/// <summary> /// <summary>
/// All levels defined by this filter will be sent to loggers. All others will be ignored. /// All levels defined by this filter will be sent to loggers. All others will be ignored.
/// </summary> /// </summary>
@ -122,32 +117,33 @@ namespace IPA.Logging
{ {
_logQueue.Add(new LogMessage _logQueue.Add(new LogMessage
{ {
level = level,
message = message,
logger = this,
time = DateTime.Now
Level = level,
Message = message,
Logger = this,
Time = DateTime.Now
}); });
} }
/// <inheritdoc />
/// <summary> /// <summary>
/// An override to <see cref="Logger.Debug(string)"/> which shows the method that called it.
/// An override to <see cref="M:IPA.Logging.Logger.Debug(System.String)" /> which shows the method that called it.
/// </summary> /// </summary>
/// <param name="message">the message to log</param> /// <param name="message">the message to log</param>
public override void Debug(string message) public override void Debug(string message)
{ // add source to message
var stfm = new StackTrace().GetFrame(1).GetMethod();
if (showSourceClass)
base.Debug($"{{{stfm.DeclaringType.FullName}::{stfm.Name}}} {message}");
else
base.Debug(message);
{
// add source to message
var stackFrame = new StackTrace().GetFrame(1).GetMethod();
base.Debug(showSourceClass
? $"{{{stackFrame.DeclaringType?.FullName}::{stackFrame.Name}}} {message}"
: message);
} }
internal struct LogMessage
private struct LogMessage
{ {
public Level level;
public StandardLogger logger;
public string message;
public DateTime time;
public Level Level;
public StandardLogger Logger;
public string Message;
public DateTime Time;
} }
private static BlockingCollection<LogMessage> _logQueue = new BlockingCollection<LogMessage>(); private static BlockingCollection<LogMessage> _logQueue = new BlockingCollection<LogMessage>();
@ -157,11 +153,11 @@ namespace IPA.Logging
{ {
HashSet<LogPrinter> started = new HashSet<LogPrinter>(); HashSet<LogPrinter> started = new HashSet<LogPrinter>();
while (_logQueue.TryTake(out LogMessage msg, Timeout.Infinite)) { while (_logQueue.TryTake(out LogMessage msg, Timeout.Infinite)) {
foreach (var printer in msg.logger.printers)
foreach (var printer in msg.Logger.printers)
{ {
try try
{ {
if (((byte)msg.level & (byte)printer.Filter) != 0)
if (((byte)msg.Level & (byte)printer.Filter) != 0)
{ {
if (!started.Contains(printer)) if (!started.Contains(printer))
{ {
@ -169,7 +165,7 @@ namespace IPA.Logging
started.Add(printer); started.Add(printer);
} }
printer.Print(msg.level, msg.time, msg.logger.logName, msg.message);
printer.Print(msg.Level, msg.Time, msg.Logger.logName, msg.Message);
} }
} }
catch (Exception e) catch (Exception e)


+ 4
- 16
IPA.Loader/Logging/UnityLogInterceptor.cs View File

@ -1,23 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine;
namespace IPA.Logging namespace IPA.Logging
{ {
internal class UnityLogInterceptor
internal static class UnityLogInterceptor
{ {
internal static Logger _logger;
public static Logger UnityLogger {
get
{
if (_logger == null)
_logger = new StandardLogger("UnityEngine");
return _logger;
}
}
internal static Logger Logger;
public static Logger UnityLogger => Logger ?? (Logger = new StandardLogger("UnityEngine"));
public static Logger.Level LogTypeToLevel(LogType type) public static Logger.Level LogTypeToLevel(LogType type)
{ {


+ 3
- 5
IPA.Loader/PluginInterfaces/BeatSaber/IBeatSaberPlugin.cs View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine.SceneManagement;
using UnityEngine.SceneManagement;
// ReSharper disable CheckNamespace
namespace IPA namespace IPA
{ {
@ -23,7 +21,7 @@ namespace IPA
string Version { get; } string Version { get; }
/// <summary> /// <summary>
/// Gets the info for the Modsaber release of this plugin. Return null if there is no Modsaber release.
/// Gets the info for the ModSaber release of this plugin. Return null if there is no ModSaber release.
/// </summary> /// </summary>
ModsaberModInfo ModInfo { get; } ModsaberModInfo ModInfo { get; }


+ 2
- 4
IPA.Loader/PluginInterfaces/BeatSaber/IEnhancedBeatSaberPlugin.cs View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
// ReSharper disable CheckNamespace
namespace IPA namespace IPA
{ {
/// <inheritdoc cref="IBeatSaberPlugin" />
/// <summary> /// <summary>
/// An enhanced version of a standard BeatSaber plugin. /// An enhanced version of a standard BeatSaber plugin.
/// </summary> /// </summary>


+ 10
- 12
IPA.Loader/PluginInterfaces/BeatSaber/ModsaberModInfo.cs View File

@ -1,14 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// ReSharper disable CheckNamespace
namespace IPA namespace IPA
{ {
/// <summary> /// <summary>
/// A class to provide information about a mod on ModSaber.ML /// A class to provide information about a mod on ModSaber.ML
/// </summary> /// </summary>
// ReSharper disable once IdentifierTypo
public class ModsaberModInfo public class ModsaberModInfo
{ {
/// <summary> /// <summary>
@ -16,12 +14,12 @@ namespace IPA
/// </summary> /// </summary>
public string InternalName public string InternalName
{ {
get => _InternalName;
get => _internalName;
set set
{ {
if (_InternalName == null)
if (_internalName == null)
{ {
_InternalName = value;
_internalName = value;
} }
else else
{ {
@ -29,19 +27,19 @@ namespace IPA
} }
} }
} }
private string _InternalName = null;
private string _internalName;
/// <summary> /// <summary>
/// The version of the currently installed mod. Used to compare to the version on ModSaber. Should be a valid SemVer version. /// The version of the currently installed mod. Used to compare to the version on ModSaber. Should be a valid SemVer version.
/// </summary> /// </summary>
public string CurrentVersion public string CurrentVersion
{ {
get => _CurrentVersion;
get => _currentVersion;
set set
{ {
if (_CurrentVersion == null)
if (_currentVersion == null)
{ {
_CurrentVersion = value;
_currentVersion = value;
} }
else else
{ {
@ -49,6 +47,6 @@ namespace IPA
} }
} }
} }
private string _CurrentVersion = null;
private string _currentVersion;
} }
} }

+ 2
- 7
IPA.Loader/PluginInterfaces/IGenericEnhancedPlugin.cs View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// ReSharper disable CheckNamespace
namespace IPA namespace IPA
{ {
/// <summary> /// <summary>
@ -12,7 +7,7 @@ namespace IPA
public interface IGenericEnhancedPlugin public interface IGenericEnhancedPlugin
{ {
/// <summary> /// <summary>
/// Gets a list of executables this plugin should be excuted on (without the file ending)
/// Gets a list of executables this plugin should be executed on (without the file ending)
/// </summary> /// </summary>
/// <example>{ "PlayClub", "PlayClubStudio" }</example> /// <example>{ "PlayClub", "PlayClubStudio" }</example>
string[] Filter { get; } string[] Filter { get; }


+ 2
- 2
IPA.Loader/PluginInterfaces/IPA/IEnhancedPlugin.cs View File

@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
// ReSharper disable CheckNamespace
namespace IPA.Old namespace IPA.Old
{ {
/// <inheritdoc cref="IPlugin" />
/// <summary> /// <summary>
/// An enhanced version of the standard IPA plugin. /// An enhanced version of the standard IPA plugin.
/// </summary> /// </summary>


+ 1
- 2
IPA.Loader/PluginInterfaces/IPA/IPlugin.cs View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
// ReSharper disable CheckNamespace
namespace IPA.Old namespace IPA.Old
{ {


+ 0
- 147
IPA.Loader/Updating/Backup/BackupUnit.cs View File

@ -1,147 +0,0 @@
using IPA.Logging;
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
namespace IPA.Updating.Backup
{
/// <summary>
/// A unit for backup. WIP.
/// </summary>
internal class BackupUnit
{
public string Name { get; private set; }
private DirectoryInfo _BackupPath;
private List<string> _Files = new List<string>();
private FileInfo _ManifestFile;
private static string _ManifestFileName = "$manifest$.txt";
public BackupUnit(string path) : this(path, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss"))
{
}
internal BackupUnit(string path, string name)
{
Name = name;
_BackupPath = new DirectoryInfo(Path.Combine(path, Name));
_ManifestFile = new FileInfo(Path.Combine(_BackupPath.FullName, _ManifestFileName));
}
public static BackupUnit FromDirectory(DirectoryInfo directory, string backupPath)
{
var unit = new BackupUnit(backupPath, directory.Name);
// Read Manifest
if (unit._ManifestFile.Exists)
{
string manifest = File.ReadAllText(unit._ManifestFile.FullName);
foreach (var line in manifest.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
unit._Files.Add(line);
}
else
{
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{
if (file.Name == _ManifestFileName) continue;
var relativePath = file.FullName.Substring(directory.FullName.Length + 1);
unit._Files.Add(relativePath);
}
}
return unit;
}
public void Add(string file)
{
Add(new FileInfo(file));
}
internal void Delete()
{
if (_BackupPath.Exists)
_BackupPath.Delete(true);
}
/// <summary>
/// Adds a file to the list of changed files and backups it.
/// </summary>
/// <param name="file"></param>
public void Add(FileInfo file)
{
var relativePath = LoneFunctions.GetRelativePath(file.FullName, Environment.CurrentDirectory);
var backupPath = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
Logger.updater.Debug($"rp={relativePath}, bp={backupPath}");
if (_Files.Contains(relativePath))
{
Logger.updater.Debug($"Skipping backup of {relativePath}");
return;
}
// Copy over
backupPath.Directory.Create();
if (file.Exists)
{
file.CopyTo(backupPath.FullName);
}
else
{
// Make empty file
backupPath.Create().Close();
}
if (!File.Exists(_ManifestFile.FullName))
_ManifestFile.Create().Close();
var stream = _ManifestFile.AppendText();
stream.WriteLine(relativePath);
stream.Close();
// Add to list
_Files.Add(relativePath);
}
/// <summary>
/// Reverts the changes made in this unit.
/// </summary>
public void Restore()
{
foreach (var relativePath in _Files)
{
Logger.updater.Debug($"Restoring {relativePath}");
// Original version
var backupFile = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
var target = new FileInfo(Path.Combine(Environment.CurrentDirectory, relativePath));
if (backupFile.Exists)
{
if (backupFile.Length > 0)
{
Logger.updater.Debug($" {backupFile.FullName} => {target.FullName}");
target.Directory.Create();
backupFile.CopyTo(target.FullName, true);
}
else
{
Logger.updater.Debug($" x {target.FullName}");
if (target.Exists)
{
target.Delete();
}
}
}
else
{
Logger.updater.Error("Backup not found!");
}
}
}
}
}

+ 9
- 13
IPA.Loader/Updating/Converters/ModsaberDependencyConverter.cs View File

@ -1,29 +1,25 @@
using IPA.Updating.ModsaberML;
using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static IPA.Updating.ModsaberML.ApiEndpoint.Mod;
using SemVer;
using static IPA.Updating.ModSaber.ApiEndpoint.Mod;
namespace IPA.Updating.Converters namespace IPA.Updating.Converters
{ {
internal class ModsaberDependencyConverter : JsonConverter<Dependency>
internal class ModSaberDependencyConverter : JsonConverter<Dependency>
{ {
public override Dependency ReadJson(JsonReader reader, Type objectType, Dependency existingValue, bool hasExistingValue, JsonSerializer serializer) public override Dependency ReadJson(JsonReader reader, Type objectType, Dependency existingValue, bool hasExistingValue, JsonSerializer serializer)
{ {
var parts = (reader.Value as string).Split('@');
return new Dependency()
var parts = (reader.Value as string)?.Split('@');
return new Dependency
{ {
Name = parts[0],
VersionRange = new SemVer.Range(parts[1])
Name = parts?[0],
VersionRange = new Range(parts?[1])
}; };
} }
public override void WriteJson(JsonWriter writer, Dependency value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, Dependency value, JsonSerializer serializer)
{ {
writer.WriteValue($"{value.Name}@{value.VersionRange.ToString()}");
writer.WriteValue($"{value.Name}@{value.VersionRange}");
} }
} }
} }

+ 2
- 4
IPA.Loader/Updating/Converters/SemverRangeConverter.cs View File

@ -1,13 +1,11 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using SemVer; using SemVer;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics.CodeAnalysis;
namespace IPA.Updating.Converters namespace IPA.Updating.Converters
{ {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal class SemverRangeConverter : JsonConverter<Range> internal class SemverRangeConverter : JsonConverter<Range>
{ {
public override Range ReadJson(JsonReader reader, Type objectType, Range existingValue, bool hasExistingValue, JsonSerializer serializer) => new Range(reader.Value as string); public override Range ReadJson(JsonReader reader, Type objectType, Range existingValue, bool hasExistingValue, JsonSerializer serializer) => new Range(reader.Value as string);


+ 2
- 7
IPA.Loader/Updating/Converters/SemverVersionConverter.cs View File

@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SemVer;
using Newtonsoft.Json;
using System;
using Version = SemVer.Version; using Version = SemVer.Version;
namespace IPA.Updating.Converters namespace IPA.Updating.Converters


IPA.Loader/Updating/ModsaberML/ApiEndpoint.cs → IPA.Loader/Updating/ModSaber/ApiEndpoint.cs View File

@ -1,18 +1,13 @@
using IPA.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using IPA.Updating.Converters; using IPA.Updating.Converters;
using IPA.Utilities; using IPA.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using SemVer; using SemVer;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Version = SemVer.Version; using Version = SemVer.Version;
namespace IPA.Updating.ModsaberML
namespace IPA.Updating.ModSaber
{ {
class ApiEndpoint class ApiEndpoint
{ {
@ -100,7 +95,7 @@ namespace IPA.Updating.ModsaberML
public Dictionary<string, byte[]> FileHashes = new Dictionary<string, byte[]>(); public Dictionary<string, byte[]> FileHashes = new Dictionary<string, byte[]>();
[JsonProperty("url")] [JsonProperty("url")]
public string DownloadPath = null;
public string DownloadPath;
public override string ToString() public override string ToString()
{ {
@ -112,14 +107,14 @@ namespace IPA.Updating.ModsaberML
public class FilesObject public class FilesObject
{ {
[JsonProperty("steam")] [JsonProperty("steam")]
public PlatformFile Steam = null;
public PlatformFile Steam;
[JsonProperty("oculus")] [JsonProperty("oculus")]
public PlatformFile Oculus = null;
public PlatformFile Oculus;
} }
[JsonProperty("files")] [JsonProperty("files")]
public FilesObject Files = null;
public FilesObject Files;
public class Dependency public class Dependency
{ {
@ -127,10 +122,10 @@ namespace IPA.Updating.ModsaberML
public Range VersionRange = null; public Range VersionRange = null;
} }
[JsonProperty("dependsOn", ItemConverterType = typeof(ModsaberDependencyConverter))]
[JsonProperty("dependsOn", ItemConverterType = typeof(ModSaberDependencyConverter))]
public Dependency[] Dependencies = new Dependency[0]; public Dependency[] Dependencies = new Dependency[0];
[JsonProperty("conflictsWith", ItemConverterType = typeof(ModsaberDependencyConverter))]
[JsonProperty("conflictsWith", ItemConverterType = typeof(ModSaberDependencyConverter))]
public Dependency[] Conflicts = new Dependency[0]; public Dependency[] Conflicts = new Dependency[0];
[JsonProperty("oldVersions", ItemConverterType = typeof(SemverVersionConverter))] [JsonProperty("oldVersions", ItemConverterType = typeof(SemverVersionConverter))]

IPA.Loader/Updating/ModsaberML/Updater.cs → IPA.Loader/Updating/ModSaber/Updater.cs View File

@ -1,43 +1,40 @@
using IPA.Utilities;
using IPA.Loader;
using Ionic.Zip;
using Newtonsoft.Json;
using System;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ionic.Zip;
using IPA.Utilities;
using Newtonsoft.Json;
using SemVer;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking; using UnityEngine.Networking;
using SemVer;
using static IPA.Loader.PluginManager;
using Logger = IPA.Logging.Logger; using Logger = IPA.Logging.Logger;
using Version = SemVer.Version; using Version = SemVer.Version;
using IPA.Updating.Backup;
using System.Runtime.Serialization;
using System.Reflection;
using static IPA.Loader.PluginManager;
namespace IPA.Updating.ModsaberML
namespace IPA.Updating.ModSaber
{ {
class Updater : MonoBehaviour
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal class Updater : MonoBehaviour
{ {
public static Updater instance;
public static Updater Instance;
public void Awake() public void Awake()
{ {
try try
{ {
if (instance != null)
if (Instance != null)
Destroy(this); Destroy(this);
else else
{ {
instance = this;
Instance = this;
CheckForUpdates(); CheckForUpdates();
} }
} }
@ -55,21 +52,21 @@ namespace IPA.Updating.ModsaberML
private class DependencyObject private class DependencyObject
{ {
public string Name { get; set; } public string Name { get; set; }
public Version Version { get; set; } = null;
public Version ResolvedVersion { get; set; } = null;
public Range Requirement { get; set; } = null;
public Range Conflicts { get; set; } = null;
public bool Resolved { get; set; } = false;
public bool Has { get; set; } = false;
public Version Version { get; set; }
public Version ResolvedVersion { get; set; }
public Range Requirement { get; set; }
public Range Conflicts { get; set; }
public bool Resolved { get; set; }
public bool Has { get; set; }
public HashSet<string> Consumers { get; set; } = new HashSet<string>(); public HashSet<string> Consumers { get; set; } = new HashSet<string>();
public bool MetaRequestFailed { get; set; } = false;
public bool MetaRequestFailed { get; set; }
public PluginInfo LocalPluginMeta { get; set; } = null;
public PluginInfo LocalPluginMeta { get; set; }
public override string ToString() public override string ToString()
{ {
return $"{Name}@{Version}{(Resolved ? $" -> {ResolvedVersion}" : "")} - ({Requirement} ! {Conflicts}) {(Has ? $" Already have" : "")}";
return $"{Name}@{Version}{(Resolved ? $" -> {ResolvedVersion}" : "")} - ({Requirement} ! {Conflicts}) {(Has ? " Already have" : "")}";
} }
} }
@ -79,7 +76,6 @@ namespace IPA.Updating.ModsaberML
if (requestCache.TryGetValue(url, out string value)) if (requestCache.TryGetValue(url, out string value))
{ {
result.Value = value; result.Value = value;
yield break;
} }
else else
{ {
@ -111,15 +107,14 @@ namespace IPA.Updating.ModsaberML
} }
} }
private Dictionary<string, ApiEndpoint.Mod> modCache = new Dictionary<string, ApiEndpoint.Mod>();
private IEnumerator GetModInfo(string name, string ver, Ref<ApiEndpoint.Mod> result)
private readonly Dictionary<string, ApiEndpoint.Mod> modCache = new Dictionary<string, ApiEndpoint.Mod>();
private IEnumerator GetModInfo(string modName, string ver, Ref<ApiEndpoint.Mod> result)
{ {
var uri = string.Format(ApiEndpoint.GetModInfoEndpoint, Uri.EscapeUriString(name), Uri.EscapeUriString(ver));
var uri = string.Format(ApiEndpoint.GetModInfoEndpoint, Uri.EscapeUriString(modName), Uri.EscapeUriString(ver));
if (modCache.TryGetValue(uri, out ApiEndpoint.Mod value)) if (modCache.TryGetValue(uri, out ApiEndpoint.Mod value))
{ {
result.Value = value; result.Value = value;
yield break;
} }
else else
{ {
@ -136,20 +131,18 @@ namespace IPA.Updating.ModsaberML
catch (Exception e) catch (Exception e)
{ {
result.Error = new Exception("Error decoding response", e); result.Error = new Exception("Error decoding response", e);
yield break;
} }
} }
} }
private Dictionary<string, List<ApiEndpoint.Mod>> modVersionsCache = new Dictionary<string, List<ApiEndpoint.Mod>>();
private IEnumerator GetModVersionsMatching(string name, string range, Ref<List<ApiEndpoint.Mod>> result)
private readonly Dictionary<string, List<ApiEndpoint.Mod>> modVersionsCache = new Dictionary<string, List<ApiEndpoint.Mod>>();
private IEnumerator GetModVersionsMatching(string modName, string range, Ref<List<ApiEndpoint.Mod>> result)
{ {
var uri = string.Format(ApiEndpoint.GetModsWithSemver, Uri.EscapeUriString(name), Uri.EscapeUriString(range));
var uri = string.Format(ApiEndpoint.GetModsWithSemver, Uri.EscapeUriString(modName), Uri.EscapeUriString(range));
if (modVersionsCache.TryGetValue(uri, out List<ApiEndpoint.Mod> value)) if (modVersionsCache.TryGetValue(uri, out List<ApiEndpoint.Mod> value))
{ {
result.Value = value; result.Value = value;
yield break;
} }
else else
{ {
@ -166,7 +159,6 @@ namespace IPA.Updating.ModsaberML
catch (Exception e) catch (Exception e)
{ {
result.Error = new Exception("Error decoding response", e); result.Error = new Exception("Error decoding response", e);
yield break;
} }
} }
} }
@ -177,9 +169,9 @@ namespace IPA.Updating.ModsaberML
foreach (var plugin in BSMetas) foreach (var plugin in BSMetas)
{ // initialize with data to resolve (1.1) { // initialize with data to resolve (1.1)
if (plugin.ModsaberInfo != null)
if (plugin.ModSaberInfo != null)
{ // updatable { // updatable
var msinfo = plugin.ModsaberInfo;
var msinfo = plugin.ModSaberInfo;
depList.Value.Add(new DependencyObject { depList.Value.Add(new DependencyObject {
Name = msinfo.InternalName, Name = msinfo.InternalName,
Version = new Version(msinfo.CurrentVersion), Version = new Version(msinfo.CurrentVersion),
@ -190,17 +182,17 @@ namespace IPA.Updating.ModsaberML
} }
foreach (var dep in depList.Value) foreach (var dep in depList.Value)
Logger.updater.Debug($"Phantom Dependency: {dep.ToString()}");
Logger.updater.Debug($"Phantom Dependency: {dep}");
yield return DependencyResolveFirstPass(depList); yield return DependencyResolveFirstPass(depList);
foreach (var dep in depList.Value) foreach (var dep in depList.Value)
Logger.updater.Debug($"Dependency: {dep.ToString()}");
Logger.updater.Debug($"Dependency: {dep}");
yield return DependencyResolveSecondPass(depList); yield return DependencyResolveSecondPass(depList);
foreach (var dep in depList.Value) foreach (var dep in depList.Value)
Logger.updater.Debug($"Dependency: {dep.ToString()}");
Logger.updater.Debug($"Dependency: {dep}");
DependendyResolveFinalPass(depList); DependendyResolveFinalPass(depList);
} }
@ -226,8 +218,8 @@ namespace IPA.Updating.ModsaberML
continue; continue;
} }
list.Value.AddRange(mod.Value.Dependencies.Select(d => new DependencyObject { Name = d.Name, Requirement = d.VersionRange, Consumers = new HashSet<string>() { dep.Name } }));
list.Value.AddRange(mod.Value.Conflicts.Select(d => new DependencyObject { Name = d.Name, Conflicts = d.VersionRange, Consumers = new HashSet<string>() { dep.Name } }));
list.Value.AddRange(mod.Value.Dependencies.Select(d => new DependencyObject { Name = d.Name, Requirement = d.VersionRange, Consumers = new HashSet<string> { dep.Name } }));
list.Value.AddRange(mod.Value.Conflicts.Select(d => new DependencyObject { Name = d.Name, Conflicts = d.VersionRange, Consumers = new HashSet<string> { dep.Name } }));
} }
var depNames = new HashSet<string>(); var depNames = new HashSet<string>();
@ -290,6 +282,7 @@ namespace IPA.Updating.ModsaberML
.Where(versionCheck => versionCheck.GameVersion == BeatSaber.GameVersion && versionCheck.Approved) .Where(versionCheck => versionCheck.GameVersion == BeatSaber.GameVersion && versionCheck.Approved)
.Where(conflictsCheck => dep.Conflicts == null || !dep.Conflicts.IsSatisfied(conflictsCheck.Version)) .Where(conflictsCheck => dep.Conflicts == null || !dep.Conflicts.IsSatisfied(conflictsCheck.Version))
.Select(mod => mod.Version).Max(); // (2.1) .Select(mod => mod.Version).Max(); // (2.1)
// ReSharper disable once AssignmentInConditionalExpression
if (dep.Resolved = ver != null) dep.ResolvedVersion = ver; // (2.2) if (dep.Resolved = ver != null) dep.ResolvedVersion = ver; // (2.2)
dep.Has = dep.Version == dep.ResolvedVersion && dep.Resolved; // dep.Version is only not null if its already installed dep.Has = dep.Version == dep.ResolvedVersion && dep.Resolved; // dep.Version is only not null if its already installed
} }
@ -303,10 +296,10 @@ namespace IPA.Updating.ModsaberML
{ // figure out which ones need to be downloaded (3.1) { // figure out which ones need to be downloaded (3.1)
if (dep.Resolved) if (dep.Resolved)
{ {
Logger.updater.Debug($"Resolved: {dep.ToString()}");
Logger.updater.Debug($"Resolved: {dep}");
if (!dep.Has) if (!dep.Has)
{ {
Logger.updater.Debug($"To Download: {dep.ToString()}");
Logger.updater.Debug($"To Download: {dep}");
toDl.Add(dep); toDl.Add(dep);
} }
} }
@ -324,10 +317,10 @@ namespace IPA.Updating.ModsaberML
Logger.updater.Debug($"Temp directory: {tempDirectory}"); Logger.updater.Debug($"Temp directory: {tempDirectory}");
foreach (var item in toDl) foreach (var item in toDl)
StartCoroutine(UpdateModCoroutine(item, tempDirectory));
StartCoroutine(UpdateModCoroutine(item));
} }
private IEnumerator UpdateModCoroutine(DependencyObject item, string tempDirectory)
private IEnumerator UpdateModCoroutine(DependencyObject item)
{ // (3.2) { // (3.2)
Logger.updater.Debug($"Release: {BeatSaber.ReleaseType}"); Logger.updater.Debug($"Release: {BeatSaber.ReleaseType}");
@ -351,12 +344,12 @@ namespace IPA.Updating.ModsaberML
Logger.updater.Debug($"URL = {url}"); Logger.updater.Debug($"URL = {url}");
const int MaxTries = 3;
int maxTries = MaxTries;
while (maxTries > 0)
const int maxTries = 3;
int tries = maxTries;
while (tries > 0)
{ {
if (maxTries-- != MaxTries)
Logger.updater.Debug($"Re-trying download...");
if (tries-- != maxTries)
Logger.updater.Debug("Re-trying download...");
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
using (var request = UnityWebRequest.Get(url)) using (var request = UnityWebRequest.Get(url))
@ -379,7 +372,7 @@ namespace IPA.Updating.ModsaberML
} }
if (request.isHttpError) if (request.isHttpError)
{ {
Logger.updater.Error($"Server returned an error code while trying to update mod");
Logger.updater.Error("Server returned an error code while trying to update mod");
Logger.updater.Error(request.error); Logger.updater.Error(request.error);
taskTokenSource.Cancel(); taskTokenSource.Cancel();
continue; continue;
@ -388,16 +381,17 @@ namespace IPA.Updating.ModsaberML
stream.Seek(0, SeekOrigin.Begin); // reset to beginning stream.Seek(0, SeekOrigin.Begin); // reset to beginning
var downloadTask = Task.Run(() => var downloadTask = Task.Run(() =>
{ // use slightly more multithreaded approach than coroutines
ExtractPluginAsync(stream, item, platformFile, tempDirectory);
{ // use slightly more multi threaded approach than co-routines
// ReSharper disable once AccessToDisposedClosure
ExtractPluginAsync(stream, item, platformFile);
}, taskTokenSource.Token); }, taskTokenSource.Token);
while (!(downloadTask.IsCompleted || downloadTask.IsCanceled || downloadTask.IsFaulted)) while (!(downloadTask.IsCompleted || downloadTask.IsCanceled || downloadTask.IsFaulted))
yield return null; // pause coroutine until task is done
yield return null; // pause co-routine until task is done
if (downloadTask.IsFaulted) if (downloadTask.IsFaulted)
{ {
if (downloadTask.Exception.InnerExceptions.Where(e => e is ModsaberInterceptException).Any())
if (downloadTask.Exception != null && downloadTask.Exception.InnerExceptions.Any(e => e is ModsaberInterceptException))
{ // any exception is an intercept exception { // any exception is an intercept exception
Logger.updater.Error($"Modsaber did not return expected data for {item.Name}"); Logger.updater.Error($"Modsaber did not return expected data for {item.Name}");
} }
@ -411,8 +405,8 @@ namespace IPA.Updating.ModsaberML
} }
} }
if (maxTries == 0)
Logger.updater.Warn($"Plugin download failed {MaxTries} times, not re-trying");
if (tries == 0)
Logger.updater.Warn($"Plugin download failed {maxTries} times, not re-trying");
else else
Logger.updater.Debug("Download complete"); Logger.updater.Debug("Download complete");
} }
@ -421,7 +415,7 @@ namespace IPA.Updating.ModsaberML
{ {
public MemoryStream Stream { get; set; } public MemoryStream Stream { get; set; }
public StreamDownloadHandler(MemoryStream stream) : base()
public StreamDownloadHandler(MemoryStream stream)
{ {
Stream = stream; Stream = stream;
} }
@ -437,15 +431,15 @@ namespace IPA.Updating.ModsaberML
Logger.updater.Debug("Download complete"); Logger.updater.Debug("Download complete");
} }
protected override bool ReceiveData(byte[] data, int dataLength)
protected override bool ReceiveData(byte[] rData, int dataLength)
{ {
if (data == null || data.Length < 1)
if (rData == null || rData.Length < 1)
{ {
Logger.updater.Debug("CustomWebRequest :: ReceiveData - received a null/empty buffer"); Logger.updater.Debug("CustomWebRequest :: ReceiveData - received a null/empty buffer");
return false; return false;
} }
Stream.Write(data, 0, dataLength);
Stream.Write(rData, 0, dataLength);
return true; return true;
} }
@ -458,11 +452,11 @@ namespace IPA.Updating.ModsaberML
public override string ToString() public override string ToString()
{ {
return $"{base.ToString()} ({Stream?.ToString()})";
return $"{base.ToString()} ({Stream})";
} }
} }
private void ExtractPluginAsync(MemoryStream stream, DependencyObject item, ApiEndpoint.Mod.PlatformFile fileInfo, string tempDirectory)
private void ExtractPluginAsync(MemoryStream stream, DependencyObject item, ApiEndpoint.Mod.PlatformFile fileInfo)
{ // (3.3) { // (3.3)
Logger.updater.Debug($"Extracting ZIP file for {item.Name}"); Logger.updater.Debug($"Extracting ZIP file for {item.Name}");
@ -472,8 +466,6 @@ namespace IPA.Updating.ModsaberML
if (!LoneFunctions.UnsafeCompare(hash, fileInfo.Hash)) if (!LoneFunctions.UnsafeCompare(hash, fileInfo.Hash))
throw new Exception("The hash for the file doesn't match what is defined"); throw new Exception("The hash for the file doesn't match what is defined");
var newFiles = new List<FileInfo>();
var targetDir = Path.Combine(BeatSaber.InstallPath, "IPA", Path.GetRandomFileName() + "_Pending"); var targetDir = Path.Combine(BeatSaber.InstallPath, "IPA", Path.GetRandomFileName() + "_Pending");
Directory.CreateDirectory(targetDir); Directory.CreateDirectory(targetDir);
@ -517,7 +509,7 @@ namespace IPA.Updating.ModsaberML
ostream.Seek(0, SeekOrigin.Begin); ostream.Seek(0, SeekOrigin.Begin);
FileInfo targetFile = new FileInfo(Path.Combine(targetDir, entry.FileName)); FileInfo targetFile = new FileInfo(Path.Combine(targetDir, entry.FileName));
Directory.CreateDirectory(targetFile.DirectoryName);
Directory.CreateDirectory(targetFile.DirectoryName ?? throw new InvalidOperationException());
if (LoneFunctions.GetRelativePath(targetFile.FullName, targetDir) == LoneFunctions.GetRelativePath(item.LocalPluginMeta?.Filename, BeatSaber.InstallPath)) if (LoneFunctions.GetRelativePath(targetFile.FullName, targetDir) == LoneFunctions.GetRelativePath(item.LocalPluginMeta?.Filename, BeatSaber.InstallPath))
shouldDeleteOldFile = false; // overwriting old file, no need to delete shouldDeleteOldFile = false; // overwriting old file, no need to delete
@ -538,7 +530,7 @@ namespace IPA.Updating.ModsaberML
} }
if (shouldDeleteOldFile && item.LocalPluginMeta != null) if (shouldDeleteOldFile && item.LocalPluginMeta != null)
File.AppendAllLines(Path.Combine(targetDir, _SpecialDeletionsFile), new string[] { LoneFunctions.GetRelativePath(item.LocalPluginMeta.Filename, BeatSaber.InstallPath) });
File.AppendAllLines(Path.Combine(targetDir, SpecialDeletionsFile), new[] { LoneFunctions.GetRelativePath(item.LocalPluginMeta.Filename, BeatSaber.InstallPath) });
} }
catch (Exception) catch (Exception)
{ // something failed; restore { // something failed; restore
@ -554,7 +546,7 @@ namespace IPA.Updating.ModsaberML
if (item.LocalPluginMeta?.Plugin is SelfPlugin) if (item.LocalPluginMeta?.Plugin is SelfPlugin)
{ // currently updating self, so copy to working dir and update { // currently updating self, so copy to working dir and update
LoneFunctions.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(BeatSaber.InstallPath)); LoneFunctions.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(BeatSaber.InstallPath));
if (File.Exists(Path.Combine(BeatSaber.InstallPath, _SpecialDeletionsFile))) File.Delete(Path.Combine(BeatSaber.InstallPath, _SpecialDeletionsFile));
if (File.Exists(Path.Combine(BeatSaber.InstallPath, SpecialDeletionsFile))) File.Delete(Path.Combine(BeatSaber.InstallPath, SpecialDeletionsFile));
Process.Start(new ProcessStartInfo Process.Start(new ProcessStartInfo
{ {
FileName = item.LocalPluginMeta.Filename, FileName = item.LocalPluginMeta.Filename,
@ -563,13 +555,13 @@ namespace IPA.Updating.ModsaberML
}); });
} }
else else
LoneFunctions.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(eventualOutput), _SpecialDeletionsFile);
LoneFunctions.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(eventualOutput), SpecialDeletionsFile);
Directory.Delete(targetDir, true); // delete extraction site Directory.Delete(targetDir, true); // delete extraction site
Logger.updater.Debug("Extractor exited"); Logger.updater.Debug("Extractor exited");
} }
internal const string _SpecialDeletionsFile = "$$delete";
internal const string SpecialDeletionsFile = "$$delete";
} }
[Serializable] [Serializable]

+ 2
- 8
IPA.Loader/Updating/SelfPlugin.cs View File

@ -1,17 +1,11 @@
using IPA;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine.SceneManagement;
using UnityEngine.SceneManagement;
namespace IPA.Updating namespace IPA.Updating
{ {
internal class SelfPlugin : IBeatSaberPlugin internal class SelfPlugin : IBeatSaberPlugin
{ {
internal const string IPA_Name = "Beat Saber IPA"; internal const string IPA_Name = "Beat Saber IPA";
internal const string IPA_Version = "3.11.1";
internal const string IPA_Version = "3.11.2";
public static SelfPlugin Instance { get; set; } = new SelfPlugin(); public static SelfPlugin Instance { get; set; } = new SelfPlugin();


+ 8
- 12
IPA.Loader/Utilities/BeatSaber.cs View File

@ -1,10 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SemVer;
using UnityEngine;
using Version = SemVer.Version; using Version = SemVer.Version;
namespace IPA.Utilities namespace IPA.Utilities
@ -14,11 +10,11 @@ namespace IPA.Utilities
/// </summary> /// </summary>
public static class BeatSaber public static class BeatSaber
{ {
private static Version _gameVersion = null;
private static Version _gameVersion;
/// <summary> /// <summary>
/// Provides the current game version /// Provides the current game version
/// </summary> /// </summary>
public static Version GameVersion => _gameVersion ?? (_gameVersion = new Version(UnityEngine.Application.version));
public static Version GameVersion => _gameVersion ?? (_gameVersion = new Version(Application.version));
/// <summary> /// <summary>
/// The different types of releases of the game. /// The different types of releases of the game.
@ -34,7 +30,7 @@ namespace IPA.Utilities
/// </summary> /// </summary>
Oculus Oculus
} }
private static Release? _releaseCache = null;
private static Release? _releaseCache;
/// <summary> /// <summary>
/// Gets the <see cref="Release"/> type of this installation of Beat Saber /// Gets the <see cref="Release"/> type of this installation of Beat Saber
/// </summary> /// </summary>
@ -56,11 +52,11 @@ namespace IPA.Utilities
private static bool FindSteamVRAsset() private static bool FindSteamVRAsset()
{ {
// these require assembly qualified names.... // these require assembly qualified names....
var SteamVRCamera = Type.GetType("SteamVR_Camera, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
var SteamVRExternalCamera = Type.GetType("SteamVR_ExternalCamera, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
var SteamVRFade = Type.GetType("SteamVR_Fade, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
var steamVRCamera = Type.GetType("SteamVR_Camera, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
var steamVRExternalCamera = Type.GetType("SteamVR_ExternalCamera, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
var steamVRFade = Type.GetType("SteamVR_Fade, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
return SteamVRCamera != null && SteamVRExternalCamera != null && SteamVRFade != null;
return steamVRCamera != null && steamVRExternalCamera != null && steamVRFade != null;
} }
} }
} }

+ 1
- 9
IPA.Loader/Utilities/Extensions.cs View File

@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Utilities namespace IPA.Utilities
{ {
@ -18,11 +14,7 @@ namespace IPA.Utilities
/// <returns>the default value of <paramref name="type"/></returns> /// <returns>the default value of <paramref name="type"/></returns>
public static object GetDefault(this Type type) public static object GetDefault(this Type type)
{ {
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
return type.IsValueType ? Activator.CreateInstance(type) : null;
} }
} }
} }

+ 4
- 7
IPA.Loader/Utilities/LoneFunctions.cs View File

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace IPA.Utilities namespace IPA.Utilities
{ {
@ -19,9 +16,9 @@ namespace IPA.Utilities
/// <returns>the corresponding byte array</returns> /// <returns>the corresponding byte array</returns>
public static byte[] StringToByteArray(string hex) public static byte[] StringToByteArray(string hex)
{ {
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes; return bytes;
} }
@ -62,7 +59,7 @@ namespace IPA.Utilities
if (*((long*)x1) != *((long*)x2)) return false; if (*((long*)x1) != *((long*)x2)) return false;
if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; } if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; } if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
if ((l & 1) != 0) if (*x1 != *x2) return false;
return true; return true;
} }
} }


+ 6
- 10
IPA.Loader/Utilities/Ref.cs View File

@ -1,10 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Utilities namespace IPA.Utilities
{ {
@ -28,7 +24,7 @@ namespace IPA.Utilities
set => _value = value; set => _value = value;
} }
private Exception _error = null;
private Exception _error;
/// <summary> /// <summary>
/// An exception that was generated while creating the value. /// An exception that was generated while creating the value.
/// </summary> /// </summary>
@ -64,14 +60,14 @@ namespace IPA.Utilities
internal static class ExceptionUtilities internal static class ExceptionUtilities
{ {
private static readonly FieldInfo STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly Type TRACE_FORMAT_TI = Type.GetType("System.Diagnostics.StackTrace").GetNestedType("TraceFormat", BindingFlags.NonPublic);
private static readonly MethodInfo TRACE_TO_STRING_MI = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { TRACE_FORMAT_TI }, null);
private static readonly FieldInfo StackTraceStringFi = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly Type TraceFormatTi = Type.GetType("System.Diagnostics.StackTrace")?.GetNestedType("TraceFormat", BindingFlags.NonPublic);
private static readonly MethodInfo TraceToStringMi = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { TraceFormatTi }, null);
public static Exception SetStackTrace(this Exception target, StackTrace stack) public static Exception SetStackTrace(this Exception target, StackTrace stack)
{ {
var getStackTraceString = TRACE_TO_STRING_MI.Invoke(stack, new object[] { Enum.GetValues(TRACE_FORMAT_TI).GetValue(0) });
STACK_TRACE_STRING_FI.SetValue(target, getStackTraceString);
var getStackTraceString = TraceToStringMi.Invoke(stack, new[] { Enum.GetValues(TraceFormatTi).GetValue(0) });
StackTraceStringFi.SetValue(target, getStackTraceString);
return target; return target;
} }
} }


+ 10
- 19
IPA.Loader/Utilities/ReflectionUtil.cs View File

@ -18,7 +18,7 @@ namespace IPA.Utilities
public static void SetPrivateField(this object obj, string fieldName, object value) public static void SetPrivateField(this object obj, string fieldName, object value)
{ {
var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
prop.SetValue(obj, value);
prop?.SetValue(obj, value);
} }
/// <summary> /// <summary>
@ -31,7 +31,7 @@ namespace IPA.Utilities
public static T GetPrivateField<T>(this object obj, string fieldName) public static T GetPrivateField<T>(this object obj, string fieldName)
{ {
var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
var value = prop.GetValue(obj);
var value = prop?.GetValue(obj);
return (T) value; return (T) value;
} }
@ -45,7 +45,7 @@ namespace IPA.Utilities
{ {
var prop = obj.GetType() var prop = obj.GetType()
.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); .GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
prop.SetValue(obj, value, null);
prop?.SetValue(obj, value, null);
} }
/// <summary> /// <summary>
@ -58,7 +58,7 @@ namespace IPA.Utilities
public static object InvokePrivateMethod(this object obj, string methodName, params object[] methodParams) public static object InvokePrivateMethod(this object obj, string methodName, params object[] methodParams)
{ {
MethodInfo dynMethod = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); MethodInfo dynMethod = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
return dynMethod.Invoke(obj, methodParams);
return dynMethod?.Invoke(obj, methodParams);
} }
/// <summary> /// <summary>
@ -91,7 +91,7 @@ namespace IPA.Utilities
while (type != typeof(MonoBehaviour)) while (type != typeof(MonoBehaviour))
{ {
CopyForType(type, original, copy); CopyForType(type, original, copy);
type = type.BaseType;
type = type?.BaseType;
} }
return copy; return copy;
@ -116,7 +116,7 @@ namespace IPA.Utilities
while (type != typeof(MonoBehaviour)) while (type != typeof(MonoBehaviour))
{ {
CopyForType(type, original, copy); CopyForType(type, original, copy);
type = type.BaseType;
type = type?.BaseType;
} }
return copy; return copy;
@ -161,7 +161,6 @@ namespace IPA.Utilities
if (type != null) if (type != null)
{ {
object instance = Activator.CreateInstance(type); object instance = Activator.CreateInstance(type);
if (instance != null)
{ {
Type instType = instance.GetType(); Type instType = instance.GetType();
MethodInfo methodInfo = instType.GetMethod(function, methodSig); MethodInfo methodInfo = instType.GetMethod(function, methodSig);
@ -169,20 +168,12 @@ namespace IPA.Utilities
{ {
return methodInfo.Invoke(instance, parameters); return methodInfo.Invoke(instance, parameters);
} }
else
{
throw new Exception("Method not found");
}
}
else
{
throw new Exception("Unable to instantiate object of type");
throw new Exception("Method not found");
} }
} }
else
{
throw new ArgumentNullException("type");
}
throw new ArgumentNullException(nameof(type));
} }
/// <summary> /// <summary>


+ 34
- 39
IPA/Arguments.cs View File

@ -1,23 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace IPA.ArgParsing
namespace IPA
{ {
public class Arguments public class Arguments
{ {
public static Arguments CmdLine = new Arguments(Environment.GetCommandLineArgs());
public static readonly Arguments CmdLine = new Arguments(Environment.GetCommandLineArgs());
private List<string> positional = new List<string>();
private Dictionary<string, string> longFlags = new Dictionary<string, string>();
private Dictionary<char, string> flags = new Dictionary<char, string>();
private List<ArgumentFlag> flagObjects = new List<ArgumentFlag>();
private readonly List<string> positional = new List<string>();
private readonly Dictionary<string, string> longFlags = new Dictionary<string, string>();
private readonly Dictionary<char, string> flags = new Dictionary<char, string>();
private readonly List<ArgumentFlag> flagObjects = new List<ArgumentFlag>();
private string[] toParse = null;
private string[] toParse;
private Arguments(string[] args) private Arguments(string[] args)
{ {
@ -58,10 +55,10 @@ namespace IPA.ArgParsing
{ // parse as flags { // parse as flags
var argument = arg.Substring(1); // cut off first char var argument = arg.Substring(1); // cut off first char
StringBuilder subBuildState = new StringBuilder();
bool parsingValue = false;
char mainChar = ' ';
foreach (char chr in argument)
var subBuildState = new StringBuilder();
var parsingValue = false;
var mainChar = ' ';
foreach (var chr in argument)
{ {
if (!parsingValue) if (!parsingValue)
{ {
@ -100,22 +97,20 @@ namespace IPA.ArgParsing
foreach (var flag in flagObjects) foreach (var flag in flagObjects)
{ {
foreach (var chflag in flag.shortFlags)
foreach (var charFlag in flag.ShortFlags)
{ {
if (flag.exists = HasFlag(chflag))
{
flag.value = GetFlagValue(chflag);
goto FoundValue; // continue to next flagObjects item
}
if (!(flag.exists_ = HasFlag(charFlag))) continue;
flag.value_ = GetFlagValue(charFlag);
goto FoundValue; // continue to next flagObjects item
} }
foreach (var lflag in flag.longFlags)
foreach (var longFlag in flag.LongFlags)
{ {
if (flag.exists = HasLongFlag(lflag))
{
flag.value = GetLongFlagValue(lflag);
goto FoundValue; // continue to next flagObjects item
}
if (!(flag.exists_ = HasLongFlag(longFlag))) continue;
flag.value_ = GetLongFlagValue(longFlag);
goto FoundValue; // continue to next flagObjects item
} }
FoundValue:; FoundValue:;
@ -145,8 +140,8 @@ namespace IPA.ArgParsing
public void PrintHelp() public void PrintHelp()
{ {
const string indent = " "; const string indent = " ";
string filename = Environment.GetCommandLineArgs()[0];
string format = @"usage:
var filename = Environment.GetCommandLineArgs()[0];
const string format = @"usage:
{2}{0} [FLAGS] [ARGUMENTS] {2}{0} [FLAGS] [ARGUMENTS]
flags: flags:
@ -155,12 +150,12 @@ flags:
foreach (var flag in flagObjects) foreach (var flag in flagObjects)
{ {
flagsBuilder.AppendFormat("{2}{0}{3}{1}", flagsBuilder.AppendFormat("{2}{0}{3}{1}",
string.Join(", ", flag.shortFlags.Select(s => $"-{s}").Concat( flag.longFlags.Select(s => $"--{s}")) ),
string.Join(", ", flag.ShortFlags.Select(s => $"-{s}").Concat( flag.LongFlags.Select(s => $"--{s}")) ),
Environment.NewLine, indent, flag.ValueString != null ? "=" + flag.ValueString : ""); Environment.NewLine, indent, flag.ValueString != null ? "=" + flag.ValueString : "");
flagsBuilder.AppendFormat("{2}{2}{0}{1}", flag.DocString, Environment.NewLine, indent); flagsBuilder.AppendFormat("{2}{2}{0}{1}", flag.DocString, Environment.NewLine, indent);
} }
Console.Write(string.Format(format, filename, flagsBuilder.ToString(), indent));
Console.Write(format, filename, flagsBuilder, indent);
} }
public IReadOnlyList<string> PositionalArgs => positional; public IReadOnlyList<string> PositionalArgs => positional;
@ -168,11 +163,11 @@ flags:
public class ArgumentFlag public class ArgumentFlag
{ {
internal List<char> shortFlags = new List<char>();
internal List<string> longFlags = new List<string>();
internal readonly List<char> ShortFlags = new List<char>();
internal readonly List<string> LongFlags = new List<string>();
internal string value = null;
internal bool exists = false;
internal string value_;
internal bool exists_;
public ArgumentFlag(params string[] flags) public ArgumentFlag(params string[] flags)
{ {
@ -183,18 +178,18 @@ flags:
private void AddPart(string flagPart) private void AddPart(string flagPart)
{ {
if (flagPart.StartsWith("--")) if (flagPart.StartsWith("--"))
longFlags.Add(flagPart.Substring(2));
LongFlags.Add(flagPart.Substring(2));
else if (flagPart.StartsWith("-")) else if (flagPart.StartsWith("-"))
shortFlags.Add(flagPart[1]);
ShortFlags.Add(flagPart[1]);
} }
public bool Exists => exists;
public string Value => value;
public bool Exists => exists_;
public string Value => value_;
public bool HasValue => Exists && Value != null; public bool HasValue => Exists && Value != null;
public string DocString { get; set; } = ""; public string DocString { get; set; } = "";
public string ValueString { get; set; } = null;
public string ValueString { get; set; }
public static implicit operator bool(ArgumentFlag f) public static implicit operator bool(ArgumentFlag f)
{ {


+ 4
- 7
IPA/PatchContext.cs View File

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
namespace IPA namespace IPA
{ {
@ -37,12 +34,12 @@ namespace IPA
{ {
Executable = exe Executable = exe
}; };
context.ProjectRoot = new FileInfo(context.Executable).Directory.FullName;
context.IPARoot = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "IPA");
context.IPA = Assembly.GetExecutingAssembly()?.Location ?? Path.Combine(context.ProjectRoot, "IPA.exe");
context.ProjectRoot = new FileInfo(context.Executable).Directory?.FullName;
context.IPARoot = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? throw new InvalidOperationException(), "IPA");
context.IPA = Assembly.GetExecutingAssembly().Location;
context.DataPathSrc = Path.Combine(context.IPARoot, "Data"); context.DataPathSrc = Path.Combine(context.IPARoot, "Data");
context.LibsPathSrc = Path.Combine(context.IPARoot, "Libs"); context.LibsPathSrc = Path.Combine(context.IPARoot, "Libs");
context.PluginsFolder = Path.Combine(context.ProjectRoot, "Plugins");
context.PluginsFolder = Path.Combine(context.ProjectRoot ?? throw new InvalidOperationException(), "Plugins");
context.ProjectName = Path.GetFileNameWithoutExtension(context.Executable); context.ProjectName = Path.GetFileNameWithoutExtension(context.Executable);
context.DataPathDst = Path.Combine(context.ProjectRoot, context.ProjectName + "_Data"); context.DataPathDst = Path.Combine(context.ProjectRoot, context.ProjectName + "_Data");
context.LibsPathDst = Path.Combine(context.ProjectRoot, "Libs"); context.LibsPathDst = Path.Combine(context.ProjectRoot, "Libs");


+ 7
- 12
IPA/Patcher/BackupManager.cs View File

@ -1,13 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IPA.Patcher namespace IPA.Patcher
{ {
public class BackupManager
public static class BackupManager
{ {
public static BackupUnit FindLatestBackup(PatchContext context) public static BackupUnit FindLatestBackup(PatchContext context)
{ {
@ -42,7 +39,7 @@ namespace IPA.Patcher
if (string.IsNullOrEmpty(dir)) if (string.IsNullOrEmpty(dir))
throw new ArgumentException( throw new ArgumentException(
"Starting directory is a null reference or an empty string", "Starting directory is a null reference or an empty string",
"dir");
nameof(dir));
try try
{ {
@ -53,15 +50,13 @@ namespace IPA.Patcher
var entries = Directory.EnumerateFileSystemEntries(dir); var entries = Directory.EnumerateFileSystemEntries(dir);
if (!entries.Any())
if (entries.Any()) return;
try
{ {
try
{
Directory.Delete(dir);
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
Directory.Delete(dir);
} }
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
} }
catch (UnauthorizedAccessException) { } catch (UnauthorizedAccessException) { }
} }


+ 28
- 31
IPA/Patcher/BackupUnit.cs View File

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
namespace IPA.Patcher namespace IPA.Patcher
{ {
@ -12,12 +9,12 @@ namespace IPA.Patcher
/// </summary> /// </summary>
public class BackupUnit public class BackupUnit
{ {
public string Name { get; private set; }
private string Name { get; }
private DirectoryInfo _BackupPath;
private PatchContext _Context;
private List<string> _Files = new List<string>();
private FileInfo _ManifestFile;
private readonly DirectoryInfo _backupPath;
private readonly PatchContext _context;
private readonly List<string> _files = new List<string>();
private readonly FileInfo _manifestFile;
private static string _ManifestFileName = "$manifest$.txt"; private static string _ManifestFileName = "$manifest$.txt";
public BackupUnit(PatchContext context) : this(context, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss")) public BackupUnit(PatchContext context) : this(context, DateTime.Now.ToString("yyyy-MM-dd_h-mm-ss"))
@ -27,9 +24,9 @@ namespace IPA.Patcher
private BackupUnit(PatchContext context, string name) private BackupUnit(PatchContext context, string name)
{ {
Name = name; Name = name;
_Context = context;
_BackupPath = new DirectoryInfo(Path.Combine(_Context.BackupPath, Name));
_ManifestFile = new FileInfo(Path.Combine(_BackupPath.FullName, _ManifestFileName));
_context = context;
_backupPath = new DirectoryInfo(Path.Combine(_context.BackupPath, Name));
_manifestFile = new FileInfo(Path.Combine(_backupPath.FullName, _ManifestFileName));
} }
public static BackupUnit FromDirectory(DirectoryInfo directory, PatchContext context) public static BackupUnit FromDirectory(DirectoryInfo directory, PatchContext context)
@ -37,11 +34,11 @@ namespace IPA.Patcher
var unit = new BackupUnit(context, directory.Name); var unit = new BackupUnit(context, directory.Name);
// Read Manifest // Read Manifest
if (unit._ManifestFile.Exists)
if (unit._manifestFile.Exists)
{ {
string manifest = File.ReadAllText(unit._ManifestFile.FullName);
foreach (var line in manifest.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
unit._Files.Add(line);
var manifest = File.ReadAllText(unit._manifestFile.FullName);
foreach (var line in manifest.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
unit._files.Add(line);
} }
else else
{ {
@ -49,7 +46,7 @@ namespace IPA.Patcher
{ {
if (file.Name == _ManifestFileName) continue; if (file.Name == _ManifestFileName) continue;
var relativePath = file.FullName.Substring(directory.FullName.Length + 1); var relativePath = file.FullName.Substring(directory.FullName.Length + 1);
unit._Files.Add(relativePath);
unit._files.Add(relativePath);
} }
} }
@ -63,32 +60,32 @@ namespace IPA.Patcher
internal void Delete() internal void Delete()
{ {
_BackupPath.Delete(true);
_backupPath.Delete(true);
} }
/// <summary> /// <summary>
/// Adds a file to the list of changed files and backups it. /// Adds a file to the list of changed files and backups it.
/// </summary> /// </summary>
/// <param name="path"></param>
/// <param name="file">the file to add</param>
public void Add(FileInfo file) public void Add(FileInfo file)
{ {
if(!file.FullName.StartsWith(_Context.ProjectRoot))
if(!file.FullName.StartsWith(_context.ProjectRoot))
{ {
Console.Error.WriteLine("Invalid file path for backup! {0}", file); Console.Error.WriteLine("Invalid file path for backup! {0}", file);
return; return;
} }
var relativePath = file.FullName.Substring(_Context.ProjectRoot.Length + 1);
var backupPath = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
var relativePath = file.FullName.Substring(_context.ProjectRoot.Length + 1);
var backupPath = new FileInfo(Path.Combine(_backupPath.FullName, relativePath));
if(_Files.Contains(relativePath))
if(_files.Contains(relativePath))
{ {
Console.WriteLine("Skipping backup of {0}", relativePath); Console.WriteLine("Skipping backup of {0}", relativePath);
return; return;
} }
// Copy over // Copy over
backupPath.Directory.Create();
backupPath.Directory?.Create();
if (file.Exists) if (file.Exists)
{ {
file.CopyTo(backupPath.FullName); file.CopyTo(backupPath.FullName);
@ -99,14 +96,14 @@ namespace IPA.Patcher
backupPath.Create().Close(); backupPath.Create().Close();
} }
if (!File.Exists(_ManifestFile.FullName))
_ManifestFile.Create().Close();
var stream = _ManifestFile.AppendText();
if (!File.Exists(_manifestFile.FullName))
_manifestFile.Create().Close();
var stream = _manifestFile.AppendText();
stream.WriteLine(relativePath); stream.WriteLine(relativePath);
stream.Close(); stream.Close();
// Add to list // Add to list
_Files.Add(relativePath);
_files.Add(relativePath);
} }
/// <summary> /// <summary>
@ -114,19 +111,19 @@ namespace IPA.Patcher
/// </summary> /// </summary>
public void Restore() public void Restore()
{ {
foreach(var relativePath in _Files)
foreach(var relativePath in _files)
{ {
Console.WriteLine("Restoring {0}", relativePath); Console.WriteLine("Restoring {0}", relativePath);
// Original version // Original version
var backupFile = new FileInfo(Path.Combine(_BackupPath.FullName, relativePath));
var target = new FileInfo(Path.Combine(_Context.ProjectRoot, relativePath));
var backupFile = new FileInfo(Path.Combine(_backupPath.FullName, relativePath));
var target = new FileInfo(Path.Combine(_context.ProjectRoot, relativePath));
if (backupFile.Exists) if (backupFile.Exists)
{ {
if (backupFile.Length > 0) if (backupFile.Length > 0)
{ {
Console.WriteLine(" {0} => {1}", backupFile.FullName, target.FullName); Console.WriteLine(" {0} => {1}", backupFile.FullName, target.FullName);
target.Directory.Create();
target.Directory?.Create();
backupFile.CopyTo(target.FullName, true); backupFile.CopyTo(target.FullName, true);
} }
else else


+ 32
- 27
IPA/Patcher/Patcher.cs View File

@ -4,16 +4,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
namespace IPA.Patcher namespace IPA.Patcher
{ {
class PatchedModule
internal class PatchedModule
{ {
private static readonly string[] ENTRY_TYPES = { "Input", "Display" };
private static readonly string[] EntryTypes = { "Input", "Display" };
private FileInfo _File;
private ModuleDefinition _Module;
private readonly FileInfo _file;
private ModuleDefinition _module;
internal struct PatchData { internal struct PatchData {
public bool IsPatched; public bool IsPatched;
@ -27,7 +26,7 @@ namespace IPA.Patcher
private PatchedModule(string engineFile) private PatchedModule(string engineFile)
{ {
_File = new FileInfo(engineFile);
_file = new FileInfo(engineFile);
LoadModules(); LoadModules();
} }
@ -35,27 +34,33 @@ namespace IPA.Patcher
private void LoadModules() private void LoadModules()
{ {
var resolver = new DefaultAssemblyResolver(); var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(_File.DirectoryName);
resolver.AddSearchDirectory(_file.DirectoryName);
var parameters = new ReaderParameters var parameters = new ReaderParameters
{ {
AssemblyResolver = resolver, AssemblyResolver = resolver,
}; };
_Module = ModuleDefinition.ReadModule(_File.FullName, parameters);
_module = ModuleDefinition.ReadModule(_file.FullName, parameters);
} }
public PatchData Data public PatchData Data
{ {
get get
{ {
var IIdata = new PatchData { IsPatched = false, Version = null };
foreach (var @ref in _Module.AssemblyReferences) {
if (@ref.Name == "IllusionInjector") IIdata = new PatchData { IsPatched = true, Version = new Version(0, 0, 0, 0) };
if (@ref.Name == "IllusionPlugin") IIdata = new PatchData { IsPatched = true, Version = new Version(0, 0, 0, 0) };
if (@ref.Name == "IPA.Injector") return new PatchData { IsPatched = true, Version = @ref.Version };
var data = new PatchData { IsPatched = false, Version = null };
foreach (var @ref in _module.AssemblyReferences) {
switch (@ref.Name)
{
case "IllusionInjector":
case "IllusionPlugin":
data = new PatchData { IsPatched = true, Version = new Version(0, 0, 0, 0) };
break;
case "IPA.Injector":
return new PatchData { IsPatched = true, Version = @ref.Version };
}
} }
return IIdata;
return data;
} }
} }
@ -63,26 +68,26 @@ namespace IPA.Patcher
{ {
// First, let's add the reference // First, let's add the reference
var nameReference = new AssemblyNameReference("IPA.Injector", v); var nameReference = new AssemblyNameReference("IPA.Injector", v);
var injectorPath = Path.Combine(_File.DirectoryName, "IPA.Injector.dll");
var injectorPath = Path.Combine(_file.DirectoryName ?? throw new InvalidOperationException(), "IPA.Injector.dll");
var injector = ModuleDefinition.ReadModule(injectorPath); var injector = ModuleDefinition.ReadModule(injectorPath);
bool hasIPAInjector = false; bool hasIPAInjector = false;
for (int i = 0; i < _Module.AssemblyReferences.Count; i++)
for (int i = 0; i < _module.AssemblyReferences.Count; i++)
{ {
if (_Module.AssemblyReferences[i].Name == "IllusionInjector")
_Module.AssemblyReferences.RemoveAt(i--);
if (_Module.AssemblyReferences[i].Name == "IllusionPlugin")
_Module.AssemblyReferences.RemoveAt(i--);
if (_Module.AssemblyReferences[i].Name == "IPA.Injector")
if (_module.AssemblyReferences[i].Name == "IllusionInjector")
_module.AssemblyReferences.RemoveAt(i--);
if (_module.AssemblyReferences[i].Name == "IllusionPlugin")
_module.AssemblyReferences.RemoveAt(i--);
if (_module.AssemblyReferences[i].Name == "IPA.Injector")
{ {
hasIPAInjector = true; hasIPAInjector = true;
_Module.AssemblyReferences[i].Version = v;
_module.AssemblyReferences[i].Version = v;
} }
} }
if (!hasIPAInjector) if (!hasIPAInjector)
{ {
_Module.AssemblyReferences.Add(nameReference);
_module.AssemblyReferences.Add(nameReference);
int patched = 0; int patched = 0;
foreach (var type in FindEntryTypes()) foreach (var type in FindEntryTypes())
@ -95,7 +100,7 @@ namespace IPA.Patcher
if (patched > 0) if (patched > 0)
{ {
_Module.Write(_File.FullName);
_module.Write(_file.FullName);
} }
else else
{ {
@ -104,7 +109,7 @@ namespace IPA.Patcher
} }
else else
{ {
_Module.Write(_File.FullName);
_module.Write(_file.FullName);
} }
} }
@ -113,7 +118,7 @@ namespace IPA.Patcher
var targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); var targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
if (targetMethod != null) if (targetMethod != null)
{ {
var methodReference = _Module.Import(injector.GetType("IPA.Injector.Injector").Methods.First(m => m.Name == "Inject"));
var methodReference = _module.Import(injector.GetType("IPA.Injector.Injector").Methods.First(m => m.Name == "Inject"));
targetMethod.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference)); targetMethod.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference));
return true; return true;
} }
@ -123,7 +128,7 @@ namespace IPA.Patcher
private IEnumerable<TypeDefinition> FindEntryTypes() private IEnumerable<TypeDefinition> FindEntryTypes()
{ {
return _Module.GetTypes().Where(m => ENTRY_TYPES.Contains(m.Name));
return _module.GetTypes().Where(m => EntryTypes.Contains(m.Name));
} }
} }
} }

+ 11
- 15
IPA/Patcher/Virtualizer.cs View File

@ -1,18 +1,14 @@
using Mono.Cecil; using Mono.Cecil;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
namespace IPA.Patcher namespace IPA.Patcher
{ {
class VirtualizedModule class VirtualizedModule
{ {
private const string ENTRY_TYPE = "Display";
private FileInfo _File;
private ModuleDefinition _Module;
private readonly FileInfo _file;
private ModuleDefinition _module;
public static VirtualizedModule Load(string engineFile) public static VirtualizedModule Load(string engineFile)
{ {
@ -21,7 +17,7 @@ namespace IPA.Patcher
private VirtualizedModule(string assemblyFile) private VirtualizedModule(string assemblyFile)
{ {
_File = new FileInfo(assemblyFile);
_file = new FileInfo(assemblyFile);
LoadModules(); LoadModules();
} }
@ -29,30 +25,29 @@ namespace IPA.Patcher
private void LoadModules() private void LoadModules()
{ {
var resolver = new DefaultAssemblyResolver(); var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(_File.DirectoryName);
resolver.AddSearchDirectory(_file.DirectoryName);
var parameters = new ReaderParameters var parameters = new ReaderParameters
{ {
AssemblyResolver = resolver, AssemblyResolver = resolver,
}; };
_Module = ModuleDefinition.ReadModule(_File.FullName, parameters);
_module = ModuleDefinition.ReadModule(_file.FullName, parameters);
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="module"></param>
public void Virtualize() public void Virtualize()
{ {
foreach (var type in _Module.Types)
foreach (var type in _module.Types)
{ {
VirtualizeType(type); VirtualizeType(type);
} }
Console.WriteLine(); Console.WriteLine();
_Module.Write(_File.FullName);
_module.Write(_file.FullName);
} }
private void VirtualizeType(TypeDefinition type) private void VirtualizeType(TypeDefinition type)
@ -110,10 +105,11 @@ namespace IPA.Patcher
{ {
get get
{ {
var awakeMethods = _Module.GetTypes().SelectMany(t => t.Methods.Where(m => m.Name == "Awake"));
if (awakeMethods.Count() == 0) return false;
var awakeMethods = _module.GetTypes().SelectMany(t => t.Methods.Where(m => m.Name == "Awake"));
var methodDefinitions = awakeMethods as MethodDefinition[] ?? awakeMethods.ToArray();
if (!methodDefinitions.Any()) return false;
return ((float)awakeMethods.Count(m => m.IsVirtual) / awakeMethods.Count()) > 0.5f;
return ((float)methodDefinitions.Count(m => m.IsVirtual) / methodDefinitions.Count()) > 0.5f;
} }
} }
} }


+ 50
- 50
IPA/Program.cs View File

@ -1,20 +1,21 @@
using IPA.Patcher;
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using IPA.ArgParsing;
using IPA.Patcher;
namespace IPA namespace IPA
{ {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public class Program public class Program
{ {
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum Architecture public enum Architecture
{ {
x86, x86,
@ -24,17 +25,17 @@ namespace IPA
public static Version Version => Assembly.GetEntryAssembly().GetName().Version; public static Version Version => Assembly.GetEntryAssembly().GetName().Version;
public static ArgumentFlag ArgHelp = new ArgumentFlag("--help", "-h") { DocString = "prints this message" };
public static ArgumentFlag ArgWaitFor = new ArgumentFlag("--waitfor", "-w") { DocString = "waits for the specified PID to exit", ValueString = "PID" };
public static ArgumentFlag ArgForce = new ArgumentFlag("--force", "-f") { DocString = "forces the operation to go through" };
public static ArgumentFlag ArgRevert = new ArgumentFlag("--revert", "-r") { DocString = "reverts the IPA installation" };
public static ArgumentFlag ArgNoWait = new ArgumentFlag("--nowait", "-n") { DocString = "doesn't wait for user input after the operation" };
public static ArgumentFlag ArgStart = new ArgumentFlag("--start", "-s") { DocString = "uses value as arguments to start the game after the patch/unpatch", ValueString = "ARGUMENTS" };
public static ArgumentFlag ArgLaunch = new ArgumentFlag("--launch", "-l") { DocString = "uses positional parameters as arguments to start the game after patch/unpatch" };
public static ArgumentFlag ArgDestructive = new ArgumentFlag("--destructive", "-d") { DocString = "patches the game using the now outdated destructive methods" };
public static readonly ArgumentFlag ArgHelp = new ArgumentFlag("--help", "-h") { DocString = "prints this message" };
public static readonly ArgumentFlag ArgWaitFor = new ArgumentFlag("--waitfor", "-w") { DocString = "waits for the specified PID to exit", ValueString = "PID" };
public static readonly ArgumentFlag ArgForce = new ArgumentFlag("--force", "-f") { DocString = "forces the operation to go through" };
public static readonly ArgumentFlag ArgRevert = new ArgumentFlag("--revert", "-r") { DocString = "reverts the IPA installation" };
public static readonly ArgumentFlag ArgNoWait = new ArgumentFlag("--nowait", "-n") { DocString = "doesn't wait for user input after the operation" };
public static readonly ArgumentFlag ArgStart = new ArgumentFlag("--start", "-s") { DocString = "uses value_ as arguments to start the game after the patch/unpatch", ValueString = "ARGUMENTS" };
public static readonly ArgumentFlag ArgLaunch = new ArgumentFlag("--launch", "-l") { DocString = "uses positional parameters as arguments to start the game after patch/unpatch" };
public static readonly ArgumentFlag ArgDestructive = new ArgumentFlag("--destructive", "-d") { DocString = "patches the game using the now outdated destructive methods" };
[STAThread] [STAThread]
static void Main(string[] args)
public static void Main(string[] args)
{ {
Arguments.CmdLine.Flags(ArgHelp, ArgWaitFor, ArgForce, ArgRevert, ArgNoWait, ArgStart, ArgLaunch, ArgDestructive).Process(); Arguments.CmdLine.Flags(ArgHelp, ArgWaitFor, ArgForce, ArgRevert, ArgNoWait, ArgStart, ArgLaunch, ArgDestructive).Process();
@ -48,7 +49,7 @@ namespace IPA
{ {
if (ArgWaitFor.HasValue) if (ArgWaitFor.HasValue)
{ // wait for process if necessary { // wait for process if necessary
int pid = int.Parse(ArgWaitFor.Value);
var pid = int.Parse(ArgWaitFor.Value);
try try
{ // wait for beat saber to exit (ensures we can modify the file) { // wait for beat saber to exit (ensures we can modify the file)
@ -58,20 +59,26 @@ namespace IPA
parent.WaitForExit(); parent.WaitForExit();
} }
catch (Exception) { }
catch (Exception)
{
// ignored
}
} }
PatchContext context = null; PatchContext context = null;
Assembly AssemblyLibLoader(object source, ResolveEventArgs e) Assembly AssemblyLibLoader(object source, ResolveEventArgs e)
{ {
// ReSharper disable AccessToModifiedClosure
if (context == null) return null;
var libsDir = context.LibsPathSrc; var libsDir = context.LibsPathSrc;
// ReSharper enable AccessToModifiedClosure
var asmName = new AssemblyName(e.Name); var asmName = new AssemblyName(e.Name);
var testFilen = Path.Combine(libsDir, $"{asmName.Name}.{asmName.Version}.dll");
var testFile = Path.Combine(libsDir, $"{asmName.Name}.{asmName.Version}.dll");
if (File.Exists(testFilen))
return Assembly.LoadFile(testFilen);
if (File.Exists(testFile))
return Assembly.LoadFile(testFile);
Console.WriteLine($"Could not load library {asmName}"); Console.WriteLine($"Could not load library {asmName}");
@ -369,11 +376,6 @@ namespace IPA
yield return new FileInfo(Path.Combine(nativePluginFolder.FullName, yield return new FileInfo(Path.Combine(nativePluginFolder.FullName,
relevantBit.Substring("x86_64".Length + 1))); relevantBit.Substring("x86_64".Length + 1)));
} }
else
{
// Throw away
yield break;
}
} }
else if (!isFlat && isFileFlat) else if (!isFlat && isFileFlat)
{ {
@ -416,34 +418,35 @@ namespace IPA
} }
// Copy each file into the new directory. // Copy each file into the new directory.
foreach (FileInfo fi in source.GetFiles())
foreach (var fi in source.GetFiles())
{ {
foreach (var targetFile in interceptor(fi, new FileInfo(Path.Combine(target.FullName, fi.Name)))) foreach (var targetFile in interceptor(fi, new FileInfo(Path.Combine(target.FullName, fi.Name))))
{ {
if (!targetFile.Exists || targetFile.LastWriteTimeUtc < fi.LastWriteTimeUtc || aggressive)
{
targetFile.Directory.Create();
if (targetFile.Exists && targetFile.LastWriteTimeUtc >= fi.LastWriteTimeUtc && !aggressive)
continue;
Console.CursorTop--;
ClearLine();
Console.WriteLine(@"Copying {0}", targetFile.FullName);
backup.Add(targetFile);
fi.CopyTo(targetFile.FullName, true);
}
Debug.Assert(targetFile.Directory != null, "targetFile.Directory != null");
targetFile.Directory.Create();
Console.CursorTop--;
ClearLine();
Console.WriteLine(@"Copying {0}", targetFile.FullName);
backup.Add(targetFile);
fi.CopyTo(targetFile.FullName, true);
} }
} }
// Copy each subdirectory using recursion. // Copy each subdirectory using recursion.
if (recurse)
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
{
DirectoryInfo nextTargetSubDir = new DirectoryInfo(Path.Combine(target.FullName, diSourceSubDir.Name));
CopyAll(diSourceSubDir, nextTargetSubDir, aggressive, backup, interceptor, recurse);
}
if (!recurse) return;
foreach (var diSourceSubDir in source.GetDirectories())
{
var nextTargetSubDir = new DirectoryInfo(Path.Combine(target.FullName, diSourceSubDir.Name));
CopyAll(diSourceSubDir, nextTargetSubDir, aggressive, backup, interceptor);
}
} }
static void Fail(string message)
private static void Fail(string message)
{ {
Console.Error.WriteLine("ERROR: " + message); Console.Error.WriteLine("ERROR: " + message);
@ -460,8 +463,8 @@ namespace IPA
/// <summary> /// <summary>
/// Encodes an argument for passing into a program /// Encodes an argument for passing into a program
/// </summary> /// </summary>
/// <param name="original">The value that should be received by the program</param>
/// <returns>The value which needs to be passed to the program for the original value
/// <param name="original">The value_ that should be received by the program</param>
/// <returns>The value_ which needs to be passed to the program for the original value_
/// to come through</returns> /// to come through</returns>
public static string EncodeParameterArgument(string original) public static string EncodeParameterArgument(string original)
{ {
@ -487,18 +490,15 @@ namespace IPA
if (machine == 0x8664) // IMAGE_FILE_MACHINE_AMD64 if (machine == 0x8664) // IMAGE_FILE_MACHINE_AMD64
return Architecture.x64; return Architecture.x64;
else if (machine == 0x014c) // IMAGE_FILE_MACHINE_I386
if (machine == 0x014c) // IMAGE_FILE_MACHINE_I386
return Architecture.x86; return Architecture.x86;
else if (machine == 0x0200) // IMAGE_FILE_MACHINE_IA64
if (machine == 0x0200) // IMAGE_FILE_MACHINE_IA64
return Architecture.x64; return Architecture.x64;
else
return Architecture.Unknown;
}
else
{
// Not a supported binary
return Architecture.Unknown; return Architecture.Unknown;
} }
// Not a supported binary
return Architecture.Unknown;
} }
} }


+ 2
- 3
IPA/Properties/AssemblyInfo.cs View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@ -32,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.11.1")] // will be patched by AppVeyor
[assembly: AssemblyFileVersion("3.11.1")] // will be patched by AppVeyor
[assembly: AssemblyVersion("3.11.2")]
[assembly: AssemblyFileVersion("3.11.2")]

+ 8
- 7
IPA/Shortcut.cs View File

@ -1,14 +1,15 @@
using System; using System;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace IPA namespace IPA
{ {
public class Shortcut
public static class Shortcut
{ {
private static Type m_type = Type.GetTypeFromProgID("WScript.Shell");
private static object m_shell = Activator.CreateInstance(m_type);
private static readonly Type MType = Type.GetTypeFromProgID("WScript.Shell");
private static readonly object MShell = Activator.CreateInstance(MType);
[ComImport, TypeLibType((short)0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
[ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
private interface IWshShortcut private interface IWshShortcut
{ {
[DispId(0)] [DispId(0)]
@ -38,8 +39,8 @@ namespace IPA
[DispId(0x3ef)] [DispId(0x3ef)]
string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] set; } string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] set; }
[TypeLibFunc((short)0x40), DispId(0x7d0)]
void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink);
[TypeLibFunc(0x40), DispId(0x7d0)]
void Load([In, MarshalAs(UnmanagedType.BStr)] string pathLink);
[DispId(0x7d1)] [DispId(0x7d1)]
void Save(); void Save();
@ -47,7 +48,7 @@ namespace IPA
public static void Create(string fileName, string targetPath, string arguments, string workingDirectory, string description, string hotkey, string iconPath) public static void Create(string fileName, string targetPath, string arguments, string workingDirectory, string description, string hotkey, string iconPath)
{ {
IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName });
IWshShortcut shortcut = (IWshShortcut)MType.InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, MShell, new object[] { fileName });
shortcut.Description = description; shortcut.Description = description;
shortcut.Hotkey = hotkey; shortcut.Hotkey = hotkey;
shortcut.TargetPath = targetPath; shortcut.TargetPath = targetPath;


+ 6
- 13
MSBuildTasks/AssemblyRenameTask.cs View File

@ -1,24 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities; using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Mono.Cecil;
using System;
using System.IO; using System.IO;
namespace MSBuildTasks namespace MSBuildTasks
{ {
public class AssemblyRename : Task public class AssemblyRename : Task
{ {
private ITaskItem[] assemblies;
[Required] [Required]
public ITaskItem[] Assemblies
{
get => assemblies;
set => assemblies = value;
}
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public ITaskItem[] Assemblies { get; set; }
public override bool Execute() public override bool Execute()
{ {
@ -47,7 +40,7 @@ namespace MSBuildTasks
var name = asmName.Name; var name = asmName.Name;
var version = asmName.Version; var version = asmName.Version;
var newFilen = $"{name}.{version}.dll"; var newFilen = $"{name}.{version}.dll";
var newFilePath = Path.Combine(Path.GetDirectoryName(assembly.ItemSpec), newFilen);
var newFilePath = Path.Combine(Path.GetDirectoryName(assembly.ItemSpec) ?? throw new InvalidOperationException(), newFilen);
Log.LogMessage(MessageImportance.Normal, $"Old file: {assembly.ItemSpec}, new file: {newFilePath}"); Log.LogMessage(MessageImportance.Normal, $"Old file: {assembly.ItemSpec}, new file: {newFilePath}");


+ 0
- 1
MSBuildTasks/Properties/AssemblyInfo.cs View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following


+ 2
- 2
appveyor.yml View File

@ -1,6 +1,6 @@
version: 'BSIPA-{branch}-{build}' version: 'BSIPA-{branch}-{build}'
environment: environment:
bsipa_version: '3.11.1'
bsipa_version: '3.11.2'
pull_requests: pull_requests:
do_not_increment_build_number: true do_not_increment_build_number: true
install: install:
@ -20,7 +20,7 @@ skip_tags: true
deploy: deploy:
- provider: GitHub - provider: GitHub
release: BSIPA $(bsipa_version)-draft release: BSIPA $(bsipa_version)-draft
tag: $(bsipa_version)
tag: $(bsipa_version)-d
description: | description: |
**Build:** [$(bsipa_version)-$(APPVEYOR_BUILD_NUMBER)](https://ci.appveyor.com/project/nike4613/beatsaber-ipa-reloaded/builds/$(APPVEYOR_BUILD_ID)) [$(configuration)|$(platform)] **Build:** [$(bsipa_version)-$(APPVEYOR_BUILD_NUMBER)](https://ci.appveyor.com/project/nike4613/beatsaber-ipa-reloaded/builds/$(APPVEYOR_BUILD_ID)) [$(configuration)|$(platform)]
**Latest Commit:** $(APPVEYOR_REPO_COMMIT) **Latest Commit:** $(APPVEYOR_REPO_COMMIT)


Loading…
Cancel
Save