diff --git a/Scope.Installer/Installer.cs b/Scope.Installer/Installer.cs
index 6d73e8e..f94ba10 100644
--- a/Scope.Installer/Installer.cs
+++ b/Scope.Installer/Installer.cs
@@ -24,6 +24,7 @@ namespace Scope.Installer
///
public class Installer
{
+ // TODO
///
/// The URL to download the Scope files from.
///
@@ -53,9 +54,7 @@ public static async Task Main(string[] args)
string gameFolder = await GetSLFolder();
if (!Directory.Exists(gameFolder) || Directory.GetFiles(gameFolder).Contains("SCPSL.exe"))
{
- Console.WriteLine("Could not find the game folder, aborting.");
- Console.Read();
- Environment.Exit(0);
+ Error("Could not find the game folder, aborting.");
}
try
@@ -68,18 +67,14 @@ public static async Task Main(string[] args)
if (hash != ZipHash)
{
- Console.WriteLine("The archive hash does not match!");
- Console.Read();
- Environment.Exit(0);
+ Error("The archive hash does not match!");
}
Console.WriteLine("Extracting files...");
var archive = new ZipArchive(download);
if (archive.Entries.All(x => !x.FullName.StartsWith("ScopeStuff")))
{
- Console.WriteLine("Unable to find archive contents, is the installer outdated?");
- Console.Read();
- Environment.Exit(0);
+ Error("Unable to find archive contents, is the installer outdated?");
}
Console.WriteLine("Moving files...");
@@ -137,9 +132,7 @@ public static async Task Main(string[] args)
}
catch (Exception ex)
{
- Console.WriteLine(ex);
- Console.Read();
- Environment.Exit(0);
+ Error(ex.ToString());
}
}
@@ -170,9 +163,7 @@ private static async Task Download(string url)
throw;
}
- Console.WriteLine("Check your internet connection and Scope server status and try again");
- Console.Read();
- Environment.Exit(0);
+ Error("Check your internet connection and Scope server status and try again");
}
return stream;
@@ -274,5 +265,14 @@ private static async Task GetSLFolder()
return SLPath;
}
+
+ public static void Error(string message)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(message);
+ Console.ResetColor();
+ Console.Read();
+ Environment.Exit(0);
+ }
}
}
\ No newline at end of file
diff --git a/Scope.Installer/Scope.Installer.csproj b/Scope.Installer/Scope.Installer.csproj
index 6f88fc0..33fbec6 100644
--- a/Scope.Installer/Scope.Installer.csproj
+++ b/Scope.Installer/Scope.Installer.csproj
@@ -21,6 +21,10 @@
+
+
+
+
bin\Debug\Scope.Installer.xml
$(NoWarn),1573,1591,1712
diff --git a/Scope.Patcher/Deserializer.cs b/Scope.Patcher/Deserializer.cs
new file mode 100644
index 0000000..4b22ee0
--- /dev/null
+++ b/Scope.Patcher/Deserializer.cs
@@ -0,0 +1,66 @@
+namespace Scope.Patcher
+{
+ using System.Collections.Generic;
+ using System.IO;
+ using Models;
+
+ public static class Deserializer
+ {
+ public static PatchCollection Read(string patchFile)
+ {
+ if (!File.Exists(patchFile))
+ {
+ throw new FileNotFoundException(nameof(patchFile));
+ }
+
+ var patches = Deserialize1337(File.ReadAllLines(patchFile), out var name);
+ return new PatchCollection(name, patches);
+ }
+
+ private static IEnumerable Deserialize1337(string[] lines, out string assemblyname)
+ {
+ string file = null;
+ List result = new List();
+
+ foreach (var line in lines)
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ if (line.StartsWith(">"))
+ {
+ if (file is null)
+ {
+ file = line.Substring(1);
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (file is null)
+ {
+ throw new InvalidDataException("Patch file is not valid!");
+ }
+
+ var splitData = line.Replace("->", ":").Split(':');
+ if (splitData.Length != 3)
+ {
+ throw new InvalidDataException("Unable to read patch data!");
+ }
+
+ result.Add(new Patch(
+ uint.Parse(splitData[0], System.Globalization.NumberStyles.HexNumber),
+ byte.Parse(splitData[1], System.Globalization.NumberStyles.HexNumber),
+ byte.Parse(splitData[1], System.Globalization.NumberStyles.HexNumber)));
+ }
+
+ assemblyname = file;
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scope.Patcher/Models/Patch.cs b/Scope.Patcher/Models/Patch.cs
new file mode 100644
index 0000000..f3e6afe
--- /dev/null
+++ b/Scope.Patcher/Models/Patch.cs
@@ -0,0 +1,16 @@
+namespace Scope.Patcher.Models
+{
+ public struct Patch
+ {
+ public uint Address { get; private set; }
+ public byte OldValue { get; private set; }
+ public byte NewValue { get; private set; }
+
+ public Patch(uint address, byte oldValue, byte newValue)
+ {
+ Address = address;
+ OldValue = oldValue;
+ NewValue = newValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scope.Patcher/Models/PatchCollection.cs b/Scope.Patcher/Models/PatchCollection.cs
new file mode 100644
index 0000000..eed8826
--- /dev/null
+++ b/Scope.Patcher/Models/PatchCollection.cs
@@ -0,0 +1,28 @@
+namespace Scope.Patcher.Models
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using Newtonsoft.Json;
+
+ public class PatchCollection : List
+ {
+ public string AssemblyName { get; internal set; }
+ public bool CanPatch => this.AssemblyName is not null && this.Count > 0;
+
+ public PatchCollection()
+ {
+ }
+
+ public PatchCollection(string assemblyName, IEnumerable patches)
+ {
+ if (patches == null)
+ {
+ throw new ArgumentNullException(nameof(patches));
+ }
+
+ this.AddRange(patches);
+ this.AssemblyName = assemblyName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scope.Patcher/Patcher.cs b/Scope.Patcher/Patcher.cs
new file mode 100644
index 0000000..8c20504
--- /dev/null
+++ b/Scope.Patcher/Patcher.cs
@@ -0,0 +1,60 @@
+namespace Scope.Patcher
+{
+ using System;
+ using System.IO;
+ using Models;
+
+ public static class Patcher
+ {
+ public static void Patch(this PatchCollection patches, string path, out bool expected, bool checkOld = false, bool backup = false)
+ {
+ if (!File.Exists(path))
+ {
+ throw new FileNotFoundException(nameof(path));
+ }
+
+ if (patches.Count < 1)
+ {
+ throw new IndexOutOfRangeException(nameof(patches));
+ }
+
+ expected = true;
+ var data = File.ReadAllBytes(path);
+ foreach (var patch in patches)
+ {
+ if(patch.Address > data.Length)
+ {
+ continue;
+ }
+
+ if(data[patch.Address] != patch.OldValue)
+ {
+ if (checkOld)
+ {
+ throw new Exception(
+ $"Data does not match (expected: {patch.OldValue}, actual: {data[patch.Address]})");
+ }
+ else
+ {
+ expected = false;
+ }
+ }
+
+ data[patch.Address] = patch.NewValue;
+ }
+
+ if(backup)
+ {
+ Console.WriteLine("Backup already exists, continue? (y/n)");
+ var input = Console.ReadLine()?.ToLower();
+ if(input != "y")
+ {
+ return;
+ }
+ File.Copy(path, $"{path}.old", true);
+ }
+
+ File.WriteAllBytes(path, data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scope.Patcher/Program.cs b/Scope.Patcher/Program.cs
new file mode 100644
index 0000000..2fc74a1
--- /dev/null
+++ b/Scope.Patcher/Program.cs
@@ -0,0 +1,109 @@
+namespace Scope.Patcher
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using Models;
+ using Newtonsoft.Json;
+
+ internal class Program
+ {
+ public static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine("Usage: Scope.Patcher.exe ");
+ Console.WriteLine("Usage: Scope.Patcher.exe ");
+ }
+
+ if (args.Length == 1)
+ {
+ if (!File.Exists(args.ElementAt(0)))
+ {
+ Error("Unable to find file: " + args.ElementAt(0));
+ }
+
+ try
+ {
+ var collection = Deserializer.Read(args.ElementAt(0));
+ if (!collection.CanPatch)
+ {
+ Error("The file is incomplete!, aborting...");
+ }
+ var json = JsonSerializer.Create();
+ using (TextWriter tw = new StreamWriter("patches.json"))
+ {
+ using var jw = new JsonTextWriter(tw);
+ jw.WriteComment(collection.AssemblyName);
+ json.Serialize(jw, collection);
+ }
+ }
+ catch (InvalidDataException e)
+ {
+ Error("The patch file is invalid: " + e.Message);
+ }
+ catch (Exception e)
+ {
+ Error("An error occurred while reading the patch file: " + e.Message);
+ }
+ }
+
+ if (args.Length == 2)
+ {
+ if (!File.Exists(args.ElementAt(0)) || !File.Exists(args.ElementAt(1)))
+ {
+ Error("Unable to find files!");
+ }
+
+ PatchCollection collection;
+ string lines;
+ using (TextReader tr = new StreamReader(args.ElementAt(0)))
+ {
+ lines = tr.ReadToEnd();
+ collection = JsonConvert.DeserializeObject(lines);
+ }
+
+ if (collection is null)
+ {
+ Error("Unable to read the file!");
+ }
+
+ if (Regex.Match(lines, @"\/\*[^\\]+\*\/", RegexOptions.Multiline | RegexOptions.IgnoreCase).Success)
+ {
+ collection!.AssemblyName = lines.Substring(lines.IndexOf(@"/*"));
+ }
+
+ if (!collection!.CanPatch)
+ {
+ Error("The patch file is incomplete!, aborting...");
+ }
+
+ collection.Patch(args.ElementAt(1), out var expected);
+ if (!expected)
+ {
+ Warn("Old values didnt match!");
+ }
+ }
+ }
+
+ internal static void Error(string message, bool exit = false)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(message);
+ Console.ResetColor();
+ if (exit)
+ {
+ Console.Read();
+ Environment.Exit(0);
+ }
+ }
+
+ internal static void Warn(string message)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine(message);
+ Console.ResetColor();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scope.Patcher/Properties/AssemblyInfo.cs b/Scope.Patcher/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..49ae9cd
--- /dev/null
+++ b/Scope.Patcher/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Scope.Patcher")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Scope.Patcher")]
+[assembly: AssemblyCopyright("Copyright © 2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("DB836450-BD75-433D-BAF6-82695ED8E0C6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/Scope.Patcher/Scope.Patcher.csproj b/Scope.Patcher/Scope.Patcher.csproj
new file mode 100644
index 0000000..8a8188e
--- /dev/null
+++ b/Scope.Patcher/Scope.Patcher.csproj
@@ -0,0 +1,65 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {DB836450-BD75-433D-BAF6-82695ED8E0C6}
+ Exe
+ Properties
+ Scope.Patcher
+ Scope.Patcher
+ v4.7.2
+ 512
+ true
+ 9
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Newtonsoft.Json.13.0.2-beta1\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Scope.Patcher/packages.config b/Scope.Patcher/packages.config
new file mode 100644
index 0000000..a98330f
--- /dev/null
+++ b/Scope.Patcher/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Scope.sln b/Scope.sln
index e538a39..0c11703 100644
--- a/Scope.sln
+++ b/Scope.sln
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scope.Launcher", "Scope.Launcher\Scope.Launcher.csproj", "{C162996D-0CA0-482A-A76D-EC53B3F11BFE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scope.Patcher", "Scope.Patcher\Scope.Patcher.csproj", "{DB836450-BD75-433D-BAF6-82695ED8E0C6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,6 +33,10 @@ Global
{C162996D-0CA0-482A-A76D-EC53B3F11BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C162996D-0CA0-482A-A76D-EC53B3F11BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C162996D-0CA0-482A-A76D-EC53B3F11BFE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB836450-BD75-433D-BAF6-82695ED8E0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB836450-BD75-433D-BAF6-82695ED8E0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB836450-BD75-433D-BAF6-82695ED8E0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB836450-BD75-433D-BAF6-82695ED8E0C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..9e5e1fd
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "6.0.0",
+ "rollForward": "latestMajor",
+ "allowPrerelease": true
+ }
+}
\ No newline at end of file