diff --git a/Refresher/Accessors/ConsolePatchAccessor.cs b/Refresher.Core/Accessors/ConsolePatchAccessor.cs similarity index 83% rename from Refresher/Accessors/ConsolePatchAccessor.cs rename to Refresher.Core/Accessors/ConsolePatchAccessor.cs index 4ebe249..fdd7f7b 100644 --- a/Refresher/Accessors/ConsolePatchAccessor.cs +++ b/Refresher.Core/Accessors/ConsolePatchAccessor.cs @@ -1,7 +1,7 @@ using FluentFTP; -using Refresher.Exceptions; +using Refresher.Core.Exceptions; -namespace Refresher.Accessors; +namespace Refresher.Core.Accessors; public class ConsolePatchAccessor : PatchAccessor, IDisposable { @@ -27,7 +27,7 @@ public ConsolePatchAccessor(string remoteIp) private byte[]? GetIdps() { - Program.Log("Getting IDPS...", "IDPS"); + State.Logger.LogInfo(IDPS, "Getting IDPS from the console..."); UriBuilder idpsPs3 = new("http", this._remoteIp, 80, "idps.ps3"); UriBuilder idpsHex = new("http", this._remoteIp, 80, "dev_hdd0/idps.hex"); UriBuilder idpsHexUsb = new("http", this._remoteIp, 80, "dev_usb000/idps.hex"); @@ -48,7 +48,7 @@ public ConsolePatchAccessor(string remoteIp) { HttpResponseMessage response; - Program.Log($" {stepName} ({uri.AbsolutePath})", "IDPS"); + State.Logger.LogDebug(IDPS, $" {stepName} ({uri.AbsolutePath})"); try { response = client.GetAsync(uri).Result; @@ -62,17 +62,17 @@ public ConsolePatchAccessor(string remoteIp) { if (!HandleIdpsRequestError(e)) { - Program.Log($"Couldn't fetch the IDPS from the PS3 because of an unknown error: {e}", "IDPS", BreadcrumbLevel.Error); + State.Logger.LogError(IDPS, $"Couldn't fetch the IDPS from the PS3 because of an unknown error: {e}"); SentrySdk.CaptureException(e); } return null; } - Program.Log($" {(int)response.StatusCode} {response.StatusCode} (success: {response.IsSuccessStatusCode})", "IDPS"); + State.Logger.LogDebug(IDPS, $" {(int)response.StatusCode} {response.StatusCode} (success: {response.IsSuccessStatusCode})"); if (!response.IsSuccessStatusCode) { - Program.Log("Couldn't fetch the IDPS from the PS3 because of a bad status code.", "IDPS", BreadcrumbLevel.Error); - Program.Log(response.Content.ReadAsStringAsync().Result, "IDPS", BreadcrumbLevel.Debug); + State.Logger.LogError(IDPS, "Couldn't fetch the IDPS from the PS3 because of a bad status code."); + State.Logger.LogDebug(IDPS, response.Content.ReadAsStringAsync().Result); return null; } @@ -83,7 +83,7 @@ private static bool HandleIdpsRequestError(Exception inner) { if (inner is HttpRequestException httpException) { - Program.Log($"Couldn't fetch the IDPS from the PS3 because we couldn't make the request: {httpException.Message}", "IDPS", BreadcrumbLevel.Error); + State.Logger.LogError(IDPS, $"Couldn't fetch the IDPS from the PS3 because we couldn't make the request: {httpException.Message}"); return true; } diff --git a/Refresher/Accessors/EmulatorPatchAccessor.cs b/Refresher.Core/Accessors/EmulatorPatchAccessor.cs similarity index 97% rename from Refresher/Accessors/EmulatorPatchAccessor.cs rename to Refresher.Core/Accessors/EmulatorPatchAccessor.cs index 7197ace..856227f 100644 --- a/Refresher/Accessors/EmulatorPatchAccessor.cs +++ b/Refresher.Core/Accessors/EmulatorPatchAccessor.cs @@ -1,4 +1,4 @@ -namespace Refresher.Accessors; +namespace Refresher.Core.Accessors; public class EmulatorPatchAccessor : PatchAccessor { diff --git a/Refresher/Accessors/GameCacheAccessor.cs b/Refresher.Core/Accessors/GameCacheAccessor.cs similarity index 77% rename from Refresher/Accessors/GameCacheAccessor.cs rename to Refresher.Core/Accessors/GameCacheAccessor.cs index 8275b9f..a271059 100644 --- a/Refresher/Accessors/GameCacheAccessor.cs +++ b/Refresher.Core/Accessors/GameCacheAccessor.cs @@ -1,6 +1,4 @@ -using Eto.Forms; - -namespace Refresher.Accessors; +namespace Refresher.Core.Accessors; public static class GameCacheAccessor { @@ -19,8 +17,9 @@ static GameCacheAccessor() } catch (IOException e) { - MessageBox.Show($"Couldn't create the directory for the games cache: {e.Message}\n" + - $"This error is rare and I don't know how to cleanly handle this scenario so Refresher is just gonna exit."); + // FIXME: no native MessageBox in Refresher.Core + // MessageBox.Show($"Couldn't create the directory for the games cache: {e.Message}\n" + + // $"This error is rare and I don't know how to cleanly handle this scenario so Refresher is just gonna exit."); Environment.Exit(1); // shrug } } diff --git a/Refresher/Accessors/PatchAccessor.cs b/Refresher.Core/Accessors/PatchAccessor.cs similarity index 97% rename from Refresher/Accessors/PatchAccessor.cs rename to Refresher.Core/Accessors/PatchAccessor.cs index 0776627..905d4c4 100644 --- a/Refresher/Accessors/PatchAccessor.cs +++ b/Refresher.Core/Accessors/PatchAccessor.cs @@ -1,4 +1,4 @@ -namespace Refresher.Accessors; +namespace Refresher.Core.Accessors; public abstract class PatchAccessor { diff --git a/Refresher/Exceptions/FTPConnectionFailureException.cs b/Refresher.Core/Exceptions/FTPConnectionFailureException.cs similarity index 57% rename from Refresher/Exceptions/FTPConnectionFailureException.cs rename to Refresher.Core/Exceptions/FTPConnectionFailureException.cs index 9e70237..952f64a 100644 --- a/Refresher/Exceptions/FTPConnectionFailureException.cs +++ b/Refresher.Core/Exceptions/FTPConnectionFailureException.cs @@ -1,5 +1,3 @@ -using System.Runtime.CompilerServices; - -namespace Refresher.Exceptions; +namespace Refresher.Core.Exceptions; public class FTPConnectionFailureException() : Exception("Could not connect to the FTP server."); \ No newline at end of file diff --git a/Refresher.Core/GlobalUsings.cs b/Refresher.Core/GlobalUsings.cs new file mode 100644 index 0000000..3af29f2 --- /dev/null +++ b/Refresher.Core/GlobalUsings.cs @@ -0,0 +1 @@ +global using static Refresher.Core.LogType; \ No newline at end of file diff --git a/Refresher.Core/LogType.cs b/Refresher.Core/LogType.cs new file mode 100644 index 0000000..45b3120 --- /dev/null +++ b/Refresher.Core/LogType.cs @@ -0,0 +1,18 @@ +namespace Refresher.Core; + +public enum LogType : byte +{ + Patcher, + PPU, + Verify, + CLI, + PatchAccessor, + PatchForm, + IntegratedPatchForm, + InfoRetrieval, + Crypto, + IDPS, + OSIntegration, + AutoDiscover, + PSP, +} \ No newline at end of file diff --git a/Refresher.Core/Logging/SentryBreadcrumbSink.cs b/Refresher.Core/Logging/SentryBreadcrumbSink.cs new file mode 100644 index 0000000..fe6e627 --- /dev/null +++ b/Refresher.Core/Logging/SentryBreadcrumbSink.cs @@ -0,0 +1,31 @@ +using NotEnoughLogs; +using NotEnoughLogs.Sinks; + +namespace Refresher.Core.Logging; + +public class SentryBreadcrumbSink : ILoggerSink +{ + private static BreadcrumbLevel GetLevel(LogLevel level) + { + return level switch + { + LogLevel.Critical => BreadcrumbLevel.Critical, + LogLevel.Error => BreadcrumbLevel.Error, + LogLevel.Warning => BreadcrumbLevel.Warning, + LogLevel.Info => BreadcrumbLevel.Info, + LogLevel.Debug => BreadcrumbLevel.Debug, + LogLevel.Trace => BreadcrumbLevel.Debug, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null), + }; + } + + public void Log(LogLevel level, ReadOnlySpan category, ReadOnlySpan content) + { + SentrySdk.AddBreadcrumb(content.ToString(), category.ToString(), level: GetLevel(level)); + } + + public void Log(LogLevel level, ReadOnlySpan category, ReadOnlySpan format, params object[] args) + { + SentrySdk.AddBreadcrumb(string.Format(format.ToString(), args), category.ToString(), level: GetLevel(level)); + } +} \ No newline at end of file diff --git a/Refresher/Patching/EbootPatcher.cs b/Refresher.Core/Patching/EbootPatcher.cs similarity index 97% rename from Refresher/Patching/EbootPatcher.cs rename to Refresher.Core/Patching/EbootPatcher.cs index 5105ff4..6d10205 100644 --- a/Refresher/Patching/EbootPatcher.cs +++ b/Refresher.Core/Patching/EbootPatcher.cs @@ -7,9 +7,9 @@ using System.Text.RegularExpressions; using ELFSharp.ELF; using ELFSharp.ELF.Segments; -using Refresher.Verification; +using Refresher.Core.Verification; -namespace Refresher.Patching; +namespace Refresher.Core.Patching; public partial class EbootPatcher : IPatcher { @@ -141,7 +141,7 @@ private static List FindPatchableElements(Stream file) FindLbpkDomains(reader, lbpkPositions, foundItems); long end = Stopwatch.GetTimestamp(); - Program.Log($"Detecting patchables took {(double)(end - start) / (double)Stopwatch.Frequency} seconds!"); + State.Logger.LogDebug(Patcher, $"Detecting patchables took {(end - start) / (double)Stopwatch.Frequency} seconds!"); return foundItems; } @@ -245,7 +245,7 @@ private static void FilterValidUrls(BinaryReader reader, List foundPossibl if (UrlMatch().Matches(str).Count != 0) { - Program.Log($"Found URL at offset {foundPosition}: '{str}'"); + State.Logger.LogTrace(Patcher, $"Found URL at offset {foundPosition}: '{str}'"); foundItems.Add(new PatchTargetInfo { Length = len, @@ -344,7 +344,7 @@ public static string GeneratePpuHash(Stream stream) string ppuHash = BitConverter.ToString(hash.Hash!).Replace("-", "").ToLower(); - Program.Log($"PPU hash: PPU-{ppuHash}", "PPU", BreadcrumbLevel.Debug); + State.Logger.LogDebug(PPU, $"PPU hash: PPU-{ppuHash}"); return ppuHash; } @@ -359,7 +359,7 @@ public List Verify(string url, bool patchDigest) this.Stream.Position = 0; - Program.Log($"URL: {url}", "Verify"); + State.Logger.LogInfo(LogType.Verify, $"Verifying EBOOT against URL: {url}"); // Check url if (url.EndsWith('/')) messages.Add(new Message(MessageLevel.Error, @@ -373,7 +373,7 @@ public List Verify(string url, bool patchDigest) { Class output = ELFReader.CheckELFType(this.Stream); - Program.Log($"ELF class: {output}", "Verify"); + State.Logger.LogDebug(LogType.Verify, $"ELF class: {output}"); if (output == Class.NotELF) { messages.Add(new Message(MessageLevel.Error, "EBOOT is not a valid ELF file.")); diff --git a/Refresher/Patching/IPatcher.cs b/Refresher.Core/Patching/IPatcher.cs similarity index 67% rename from Refresher/Patching/IPatcher.cs rename to Refresher.Core/Patching/IPatcher.cs index 6efd914..39418ed 100644 --- a/Refresher/Patching/IPatcher.cs +++ b/Refresher.Core/Patching/IPatcher.cs @@ -1,6 +1,6 @@ -using Refresher.Verification; +using Refresher.Core.Verification; -namespace Refresher.Patching; +namespace Refresher.Core.Patching; public interface IPatcher { diff --git a/Refresher/Patching/PSP/PSPPluginListEntry.cs b/Refresher.Core/Patching/PSP/PSPPluginListEntry.cs similarity index 84% rename from Refresher/Patching/PSP/PSPPluginListEntry.cs rename to Refresher.Core/Patching/PSP/PSPPluginListEntry.cs index e2dcd7c..a2935da 100644 --- a/Refresher/Patching/PSP/PSPPluginListEntry.cs +++ b/Refresher.Core/Patching/PSP/PSPPluginListEntry.cs @@ -1,4 +1,4 @@ -namespace Refresher.Patching.PSP; +namespace Refresher.Core.Patching.PSP; public class PSPPluginListEntry { diff --git a/Refresher/Patching/PSP/PSPPluginListParser.cs b/Refresher.Core/Patching/PSP/PSPPluginListParser.cs similarity index 96% rename from Refresher/Patching/PSP/PSPPluginListParser.cs rename to Refresher.Core/Patching/PSP/PSPPluginListParser.cs index 0f6f5ca..4926132 100644 --- a/Refresher/Patching/PSP/PSPPluginListParser.cs +++ b/Refresher.Core/Patching/PSP/PSPPluginListParser.cs @@ -1,4 +1,4 @@ -namespace Refresher.Patching.PSP; +namespace Refresher.Core.Patching.PSP; public static class PSPPluginListParser { diff --git a/Refresher/Patching/PSPPatcher.cs b/Refresher.Core/Patching/PSPPatcher.cs similarity index 97% rename from Refresher/Patching/PSPPatcher.cs rename to Refresher.Core/Patching/PSPPatcher.cs index cf93df1..39ce6da 100644 --- a/Refresher/Patching/PSPPatcher.cs +++ b/Refresher.Core/Patching/PSPPatcher.cs @@ -1,8 +1,8 @@ using System.Reflection; -using Refresher.Patching.PSP; -using Refresher.Verification; +using Refresher.Core.Patching.PSP; +using Refresher.Core.Verification; -namespace Refresher.Patching; +namespace Refresher.Core.Patching; public class PSPPatcher : IPatcher { diff --git a/Refresher/Patching/PatchTargetInfo.cs b/Refresher.Core/Patching/PatchTargetInfo.cs similarity index 79% rename from Refresher/Patching/PatchTargetInfo.cs rename to Refresher.Core/Patching/PatchTargetInfo.cs index f9dc1d0..dec1ae6 100644 --- a/Refresher/Patching/PatchTargetInfo.cs +++ b/Refresher.Core/Patching/PatchTargetInfo.cs @@ -1,4 +1,4 @@ -namespace Refresher.Patching; +namespace Refresher.Core.Patching; public struct PatchTargetInfo { diff --git a/Refresher/Patching/PatchTargetType.cs b/Refresher.Core/Patching/PatchTargetType.cs similarity index 88% rename from Refresher/Patching/PatchTargetType.cs rename to Refresher.Core/Patching/PatchTargetType.cs index 1fec206..a9e53dd 100644 --- a/Refresher/Patching/PatchTargetType.cs +++ b/Refresher.Core/Patching/PatchTargetType.cs @@ -1,4 +1,4 @@ -namespace Refresher.Patching; +namespace Refresher.Core.Patching; public enum PatchTargetType { diff --git a/Refresher.Core/Refresher.Core.csproj b/Refresher.Core/Refresher.Core.csproj new file mode 100644 index 0000000..7b67f98 --- /dev/null +++ b/Refresher.Core/Refresher.Core.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Refresher.Core/State.cs b/Refresher.Core/State.cs new file mode 100644 index 0000000..d5f4a2f --- /dev/null +++ b/Refresher.Core/State.cs @@ -0,0 +1,23 @@ +using NotEnoughLogs; +using NotEnoughLogs.Behaviour; +using NotEnoughLogs.Sinks; +using Refresher.Core.Logging; + +namespace Refresher.Core; + +public static class State +{ + public static readonly Logger Logger = InitializeLogger([new ConsoleSink(), new SentryBreadcrumbSink()]); + + private static Logger InitializeLogger(IEnumerable sinks) + { + // if(Logger != null) + // Logger.Dispose(); + + return new Logger(sinks, new LoggerConfiguration + { + Behaviour = new DirectLoggingBehaviour(), + MaxLevel = LogLevel.Trace, + }); + } +} \ No newline at end of file diff --git a/Refresher/Verification/Autodiscover/AutodiscoverResponse.cs b/Refresher.Core/Verification/Autodiscover/AutodiscoverResponse.cs similarity index 59% rename from Refresher/Verification/Autodiscover/AutodiscoverResponse.cs rename to Refresher.Core/Verification/Autodiscover/AutodiscoverResponse.cs index 45e3469..68a19a5 100644 --- a/Refresher/Verification/Autodiscover/AutodiscoverResponse.cs +++ b/Refresher.Core/Verification/Autodiscover/AutodiscoverResponse.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace Refresher.Verification.Autodiscover; +namespace Refresher.Core.Verification.Autodiscover; #nullable disable @@ -8,15 +8,15 @@ public class AutodiscoverResponse { private const int SupportedVersion = 2; - [JsonProperty("version")] + [JsonPropertyName("version")] public int Version { get; set; } - [JsonProperty("serverBrand")] + [JsonPropertyName("serverBrand")] public string ServerBrand { get; set; } - [JsonProperty("url")] + [JsonPropertyName("url")] public string Url { get; set; } - [JsonProperty("usesCustomDigestKey")] + [JsonPropertyName("usesCustomDigestKey")] public bool? UsesCustomDigestKey { get; set; } = false; // We mark as nullable, as this was added in version 2 } \ No newline at end of file diff --git a/Refresher/Verification/Message.cs b/Refresher.Core/Verification/Message.cs similarity index 88% rename from Refresher/Verification/Message.cs rename to Refresher.Core/Verification/Message.cs index fdd2922..3e2b0a9 100644 --- a/Refresher/Verification/Message.cs +++ b/Refresher.Core/Verification/Message.cs @@ -1,4 +1,4 @@ -namespace Refresher.Verification; +namespace Refresher.Core.Verification; public readonly struct Message { diff --git a/Refresher/Verification/MessageLevel.cs b/Refresher.Core/Verification/MessageLevel.cs similarity index 61% rename from Refresher/Verification/MessageLevel.cs rename to Refresher.Core/Verification/MessageLevel.cs index 9f66784..316dbcd 100644 --- a/Refresher/Verification/MessageLevel.cs +++ b/Refresher.Core/Verification/MessageLevel.cs @@ -1,4 +1,4 @@ -namespace Refresher.Verification; +namespace Refresher.Core.Verification; public enum MessageLevel { diff --git a/Refresher/Verification/ParamSfo.cs b/Refresher.Core/Verification/ParamSfo.cs similarity index 98% rename from Refresher/Verification/ParamSfo.cs rename to Refresher.Core/Verification/ParamSfo.cs index 368cc76..f84f4ba 100644 --- a/Refresher/Verification/ParamSfo.cs +++ b/Refresher.Core/Verification/ParamSfo.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Refresher.Verification; +namespace Refresher.Core.Verification; // TODO: Move to separate repository with its own nuget package // Maybe combine with NPTicket? diff --git a/Refresher.sln b/Refresher.sln index 63116da..38a8cae 100644 --- a/Refresher.sln +++ b/Refresher.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refresher", "Refresher\Refresher.csproj", "{0A67E8A2-0E7C-4BB7-90E5-8D8EA59631D4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refresher.Core", "Refresher.Core\Refresher.Core.csproj", "{49CE141A-8BD7-4CD5-BE86-257BDA5C5796}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {0A67E8A2-0E7C-4BB7-90E5-8D8EA59631D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A67E8A2-0E7C-4BB7-90E5-8D8EA59631D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A67E8A2-0E7C-4BB7-90E5-8D8EA59631D4}.Release|Any CPU.Build.0 = Release|Any CPU + {49CE141A-8BD7-4CD5-BE86-257BDA5C5796}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49CE141A-8BD7-4CD5-BE86-257BDA5C5796}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49CE141A-8BD7-4CD5-BE86-257BDA5C5796}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49CE141A-8BD7-4CD5-BE86-257BDA5C5796}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Refresher/CLI/CommandLine.cs b/Refresher/CLI/CommandLine.cs index 81aa361..8abee35 100644 --- a/Refresher/CLI/CommandLine.cs +++ b/Refresher/CLI/CommandLine.cs @@ -1,6 +1,8 @@ using System.IO.MemoryMappedFiles; -using Refresher.Patching; -using Refresher.Verification; +using Refresher.Core; +using Refresher.Core.Patching; +using Refresher.Core.Verification; +using Refresher.Core.Patching; namespace Refresher.CLI; @@ -25,7 +27,7 @@ void DeleteTempFile(string? s) //If the input file does not exist, exit if (!File.Exists(options.InputFile)) { - Program.Log("Input file does not exist."); + State.Logger.LogCritical(LogType.CLI, "Input file does not exist."); Environment.Exit(1); return; } @@ -41,7 +43,7 @@ void DeleteTempFile(string? s) } catch (Exception e) { - Program.Log("Could not create and copy to temporary file.\n" + e); + State.Logger.LogCritical(LogType.CLI, "Could not create and copy to temporary file.\n" + e); DeleteTempFile(tempFile); Environment.Exit(1); @@ -56,7 +58,7 @@ void DeleteTempFile(string? s) } catch (Exception e) { - Program.Log("Could not read data from the input file.\n" + e); + State.Logger.LogCritical(LogType.CLI, "Could not read data from the input file.\n" + e); DeleteTempFile(tempFile); Environment.Exit(1); @@ -68,12 +70,12 @@ void DeleteTempFile(string? s) List messages = ebootPatcher.Verify(options.ServerUrl, options.Digest ?? false).ToList(); //Write the messages to the console - foreach (Message message in messages) Program.Log($"{message.Level}: {message.Content}"); + foreach (Message message in messages) State.Logger.LogInfo(Verify, $"{message.Level}: {message.Content}"); //If there are any errors, exit if (messages.Any(m => m.Level == MessageLevel.Error)) { - Program.Log("\nThe patching operation cannot continue due to errors while verifying. Stopping."); + State.Logger.LogCritical(Verify, "\nThe patching operation cannot continue due to errors while verifying. Stopping."); mappedFile.Dispose(); DeleteTempFile(tempFile); @@ -89,7 +91,7 @@ void DeleteTempFile(string? s) if (key.KeyChar == 'n') { - Program.Log("Patching cancelled due to warnings."); + State.Logger.LogCritical(Verify, "Patching cancelled due to warnings."); mappedFile.Dispose(); DeleteTempFile(tempFile); @@ -98,7 +100,7 @@ void DeleteTempFile(string? s) } } - Program.Log("Patching..."); + State.Logger.LogInfo(LogType.CLI, "Patching..."); try { @@ -110,7 +112,7 @@ void DeleteTempFile(string? s) } catch (Exception e) { - Program.Log("Could not complete patch, stopping.\n" + e); + State.Logger.LogCritical(LogType.CLI, "Could not complete patch, stopping.\n" + e); mappedFile.Dispose(); DeleteTempFile(tempFile); @@ -119,6 +121,6 @@ void DeleteTempFile(string? s) } DeleteTempFile(tempFile); - Program.Log("Successfully patched EBOOT!"); + State.Logger.LogInfo(LogType.CLI, "Successfully patched EBOOT!"); } } \ No newline at end of file diff --git a/Refresher/GlobalUsings.cs b/Refresher/GlobalUsings.cs new file mode 100644 index 0000000..3af29f2 --- /dev/null +++ b/Refresher/GlobalUsings.cs @@ -0,0 +1 @@ +global using static Refresher.Core.LogType; \ No newline at end of file diff --git a/Refresher/Program.cs b/Refresher/Program.cs index b9918fe..31524c6 100644 --- a/Refresher/Program.cs +++ b/Refresher/Program.cs @@ -3,6 +3,7 @@ using CommandLine; using Eto.Forms; using Refresher.CLI; +using Refresher.Core; using Refresher.UI; namespace Refresher; @@ -57,7 +58,7 @@ public static void Main(string[] args) if (args.Length > 0) { - Log("Launching in CLI mode"); + State.Logger.LogInfo(OSIntegration, "Launching in CLI mode"); AppDomain.CurrentDomain.UnhandledException += (_, eventArgs) => { @@ -70,7 +71,7 @@ public static void Main(string[] args) } else { - Log("Launching in GUI mode"); + State.Logger.LogInfo(OSIntegration, "Launching in GUI mode"); try { App = new Application(); diff --git a/Refresher/Refresher.csproj b/Refresher/Refresher.csproj index c71084b..dc1fa93 100644 --- a/Refresher/Refresher.csproj +++ b/Refresher/Refresher.csproj @@ -42,18 +42,18 @@ - - - - + + + + diff --git a/Refresher/UI/ConsolePatchForm.cs b/Refresher/UI/ConsolePatchForm.cs index 7fe71ab..b1787ac 100644 --- a/Refresher/UI/ConsolePatchForm.cs +++ b/Refresher/UI/ConsolePatchForm.cs @@ -1,8 +1,9 @@ using System.Net.Sockets; using System.Reflection; using Eto.Forms; -using Refresher.Accessors; -using Refresher.Exceptions; +using Refresher.Core; +using Refresher.Core.Accessors; +using Refresher.Core.Exceptions; namespace Refresher.UI; @@ -54,7 +55,7 @@ protected override void RevertToOriginalExecutable(object? sender, EventArgs e) private bool InitializePatchAccessor() { this.DisposePatchAccessor(); - Program.Log("Making a new patch accessor", "Accessor"); + State.Logger.LogTrace(LogType.PatchAccessor, "Making a new patch accessor"); try { this.Accessor = new ConsolePatchAccessor(this._remoteAddress.Text.Trim()); @@ -85,14 +86,14 @@ private bool InitializePatchAccessor() private void DisposePatchAccessor() { - Program.Log("Disposing patch accessor", "Accessor"); + State.Logger.LogTrace(LogType.PatchAccessor, "Disposing patch accessor"); if (this.Accessor is IDisposable disposable) disposable.Dispose(); } protected override IEnumerable AddFields() { - return new[] { AddField("PS3's IP", out this._remoteAddress, new Button(this.PathChanged) { Text = "Connect" }) }; + return [AddField("PS3's IP", out this._remoteAddress, new Button(this.PathChanged) { Text = "Connect" })]; } public override void Guide(object? sender, EventArgs e) diff --git a/Refresher/UI/EmulatorFilePatchForm.cs b/Refresher/UI/EmulatorFilePatchForm.cs index 1f04914..62052ee 100644 --- a/Refresher/UI/EmulatorFilePatchForm.cs +++ b/Refresher/UI/EmulatorFilePatchForm.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using Eto; using Eto.Forms; -using Refresher.Accessors; +using Refresher.Core.Accessors; using Refresher.UI.Items; namespace Refresher.UI; diff --git a/Refresher/UI/EmulatorPatchForm.cs b/Refresher/UI/EmulatorPatchForm.cs index e70a2e0..fae5f30 100644 --- a/Refresher/UI/EmulatorPatchForm.cs +++ b/Refresher/UI/EmulatorPatchForm.cs @@ -1,7 +1,8 @@ using System.Diagnostics; using Eto; using Eto.Forms; -using Refresher.Accessors; +using Refresher.Core; +using Refresher.Core.Accessors; using Refresher.UI.Items; namespace Refresher.UI; @@ -38,7 +39,7 @@ protected override void BeforePatch(object? sender, EventArgs e) { if (this.GameDropdown.SelectedValue is not GameItem game) { - Program.Log("Game was null before patch, bailing", nameof(EmulatorPatchForm)); + State.Logger.LogError(PatchForm, "Game was null before patch, bailing"); return; } @@ -57,7 +58,7 @@ protected override void BeforePatch(object? sender, EventArgs e) } catch (Exception ex) { - Program.Log($"Exception while trying to create RPCS3 patches folder: {ex}", nameof(ConsolePatchAccessor), BreadcrumbLevel.Warning); + State.Logger.LogError(PatchForm, $"Exception while trying to create RPCS3 patches folder: {ex}"); } } } @@ -93,7 +94,7 @@ protected override void GameChanged(object? sender, EventArgs ev) if (this.Patcher == null) { - Program.Log("Patcher was null, bailing", nameof(EmulatorPatchForm)); + State.Logger.LogError(PatchForm, "Patcher was null, bailing"); return; } diff --git a/Refresher/UI/FilePatchForm.cs b/Refresher/UI/FilePatchForm.cs index b3c0d6a..055ea08 100644 --- a/Refresher/UI/FilePatchForm.cs +++ b/Refresher/UI/FilePatchForm.cs @@ -1,7 +1,8 @@ using System.IO.MemoryMappedFiles; using Eto; using Eto.Forms; -using Refresher.Patching; +using Refresher.Core.Patching; +using Refresher.Core.Patching; namespace Refresher.UI; diff --git a/Refresher/UI/IntegratedPatchForm.cs b/Refresher/UI/IntegratedPatchForm.cs index 8044f98..9500206 100644 --- a/Refresher/UI/IntegratedPatchForm.cs +++ b/Refresher/UI/IntegratedPatchForm.cs @@ -3,10 +3,12 @@ using Eto.Drawing; using Eto.Forms; using FluentFTP.Exceptions; -using Refresher.Accessors; -using Refresher.Patching; +using Refresher.Core; +using Refresher.Core.Accessors; +using Refresher.Core.Patching; +using Refresher.Core.Verification; +using Refresher.Core.Patching; using Refresher.UI.Items; -using Refresher.Verification; using SCEToolSharp; using Sentry; @@ -54,11 +56,11 @@ protected virtual void PathChanged(object? sender, EventArgs ev) { if (!(this.Accessor?.Available ?? false)) { - Program.Log("Suppressing path change because accessor is unavailable"); + State.Logger.LogWarning(LogType.IntegratedPatchForm, "Suppressing path change because accessor is unavailable"); return; } - Program.Log($"Path changed, using accessor {this.Accessor.GetType().Name}"); + State.Logger.LogDebug(LogType.IntegratedPatchForm, $"Path changed, using accessor {this.Accessor.GetType().Name}"); this.GameDropdown.Items.Clear(); if (!this.Accessor.DirectoryExists("game")) return; @@ -116,7 +118,7 @@ protected virtual void PathChanged(object? sender, EventArgs ev) } catch(Exception e) { - Program.Log($"Failed to set image for {game}: {e}", level: BreadcrumbLevel.Warning); + State.Logger.LogWarning(InfoRetrieval, $"Failed to set image for {game}: {e}"); SentrySdk.CaptureException(e); } @@ -138,19 +140,16 @@ protected virtual void PathChanged(object? sender, EventArgs ev) } else { - Program.Log($"Could not find APP_VER for {game}. Defaulting to 01.00.", "SFO", - BreadcrumbLevel.Warning); + State.Logger.LogWarning(InfoRetrieval, $"Could not find APP_VER for {game}. Defaulting to 01.00."); } item.Text = $"{sfo.Table["TITLE"]} [{game} {item.Version}]"; - Program.Log($"Processed {game}'s PARAM.SFO file. text:\"{item.Text}\" version:\"{item.Version}\"", - "SFO"); + State.Logger.LogDebug(InfoRetrieval, $"Processed {game}'s PARAM.SFO file. text:\"{item.Text}\" version:\"{item.Version}\""); } else { - Program.Log($"No PARAM.SFO exists for {game} (path should be '{sfoPath}')", "SFO", - BreadcrumbLevel.Warning); + State.Logger.LogWarning(InfoRetrieval, $"No PARAM.SFO exists for {game} (path should be '{sfoPath}')"); } } catch (Exception e) when (e is IOException or FtpException or TimeoutException) @@ -160,24 +159,24 @@ protected virtual void PathChanged(object? sender, EventArgs ev) } catch (EndOfStreamException) { - Program.Log($"Couldn't load {game}'s PARAM.SFO because the file was incomplete.", "SFO", BreadcrumbLevel.Error); + State.Logger.LogError(InfoRetrieval, $"Couldn't load {game}'s PARAM.SFO because the file was incomplete."); } catch(Exception e) { item.Text = $"Unknown PARAM.SFO [{game}]"; - Program.Log($"Couldn't load {game}'s PARAM.SFO: {e}", "SFO", BreadcrumbLevel.Error); + State.Logger.LogError(InfoRetrieval, $"Couldn't load {game}'s PARAM.SFO: {e}"); if (sfo != null) { - Program.Log($"PARAM.SFO version:{sfo.Version} dump:", "SFO", BreadcrumbLevel.Debug); + State.Logger.LogDebug(InfoRetrieval, $"PARAM.SFO version:{sfo.Version} dump:"); foreach ((string? key, object? value) in sfo.Table) { - Program.Log($" '{key}' = '{value}'", "SFO", BreadcrumbLevel.Debug); + State.Logger.LogDebug(InfoRetrieval, $" '{key}' = '{value}'"); } } else { - Program.Log("PARAM.SFO was not read, can't dump", "SFO", BreadcrumbLevel.Warning); + State.Logger.LogWarning(InfoRetrieval, "PARAM.SFO was not read, can't dump"); } SentrySdk.CaptureException(e); @@ -197,17 +196,17 @@ protected virtual void GameChanged(object? sender, EventArgs ev) if (this.GameDropdown.SelectedValue is not GameItem game) { - Program.Log("Game was null, bailing", nameof(IntegratedPatchForm)); + State.Logger.LogWarning(LogType.IntegratedPatchForm, "Game was null, bailing"); return; } if (this.Accessor == null) { - Program.Log("Accessor was null, bailing", nameof(IntegratedPatchForm)); + State.Logger.LogWarning(LogType.IntegratedPatchForm, "Accessor was null, bailing"); return; } - Program.Log($"Game changed to TitleID '{game.TitleId}'"); + State.Logger.LogInfo(LogType.IntegratedPatchForm, $"Game changed to TitleID '{game.TitleId}'"); this._usrDir = Path.Combine("game", game.TitleId, "USRDIR"); string ebootPath = Path.Combine(this._usrDir, "EBOOT.BIN.ORIG"); // Prefer original backup over active copy @@ -259,7 +258,7 @@ protected virtual void GameChanged(object? sender, EventArgs ev) // if this is a NP game then download the RIF for the right content ID, disc copies don't need anything else if (game.TitleId.StartsWith('N')) { - Program.Log("Digital game detected, trying to download license file"); + State.Logger.LogInfo(LogType.IntegratedPatchForm, "Digital game detected, trying to download license file"); this.DownloadLicenseFile(downloadedFile, game); } } @@ -271,17 +270,17 @@ protected virtual void GameChanged(object? sender, EventArgs ev) this._tempFile = Path.GetTempFileName(); - Program.Log("Decrypting..."); + State.Logger.LogInfo(Crypto, "Decrypting..."); LibSceToolSharp.Decrypt(downloadedFile, this._tempFile); // HACK: scetool doesn't give us result codes, check if the file has been written to instead if (new FileInfo(this._tempFile).Length == 0) { - Program.Log("Decryption failed on TitleID " + game.TitleId); + State.Logger.LogWarning(LogType.IntegratedPatchForm, "Decryption failed on TitleID " + game.TitleId); // before we completely fail, let's check if we're a disc game // some weird betas like LBP HUB require a license despite having a disc titleid if (game.TitleId.StartsWith('B')) { - Program.Log("Disc game detected - trying to gather a license as a workaround for LBP Hub"); + State.Logger.LogInfo(Crypto, "Disc game detected - trying to gather a license as a workaround for LBP Hub"); try { @@ -298,7 +297,7 @@ protected virtual void GameChanged(object? sender, EventArgs ev) if (new FileInfo(this._tempFile).Length == 0) { - Program.Log("Still couldn't decrypt."); + State.Logger.LogError(Crypto, "Still couldn't decrypt."); this.FailVerify("The EBOOT failed to decrypt. Check the log for more information."); return; } @@ -312,11 +311,18 @@ protected virtual void GameChanged(object? sender, EventArgs ev) private void DownloadLicenseFile(string ebootPath, GameItem game) { - Program.Log($"Downloading license file for TitleID {game.TitleId} (from eboot @ {ebootPath})"); - string contentId = LibSceToolSharp.GetContentId(ebootPath).TrimEnd('\0'); + State.Logger.LogInfo(Crypto, $"Downloading license file for TitleID {game.TitleId} (from eboot @ {ebootPath})"); + string? contentId = LibSceToolSharp.GetContentId(ebootPath)?.TrimEnd('\0'); + + if (contentId == null) + { + State.Logger.LogWarning(Crypto, "Couldn't get the content ID from the EBOOT."); + return; + } + this._cachedContentIds[game.TitleId] = contentId; - Program.Log($"ContentID for {game.TitleId} is {contentId}"); + State.Logger.LogDebug(Crypto, $"ContentID for {game.TitleId} is {contentId}"); string licenseDir = Path.Join(Path.GetTempPath(), "refresher-" + Random.Shared.Next()); Directory.CreateDirectory(licenseDir); @@ -330,12 +336,12 @@ private void DownloadLicenseFile(string ebootPath, GameItem game) { bool found = false; - Program.Log($"Checking all license files in {user}"); + State.Logger.LogDebug(Crypto, $"Checking all license files in {user}"); string exdataFolder = Path.Combine(user, "exdata"); if (!this.Accessor.DirectoryExists(exdataFolder)) { - Program.Log($"Exdata folder doesn't exist for user {user}, skipping..."); + State.Logger.LogDebug(Crypto, $"Exdata folder doesn't exist for user {user}, skipping..."); continue; } @@ -345,7 +351,7 @@ private void DownloadLicenseFile(string ebootPath, GameItem game) if (!licenseFile.Contains(contentId) && !licenseFile.Contains(game.TitleId)) continue; - Program.Log($"Found compatible rap: {licenseFile}"); + State.Logger.LogDebug(Crypto, $"Found compatible rap: {licenseFile}"); string actDatPath = Path.Combine(user, "exdata", "act.dat"); @@ -360,7 +366,7 @@ private void DownloadLicenseFile(string ebootPath, GameItem game) string downloadedLicenseFile = this.Accessor.DownloadFile(licenseFile); File.Move(downloadedLicenseFile, Path.Join(licenseDir, Path.GetFileName(licenseFile))); - Program.Log($"Downloaded license file {licenseFile}."); + State.Logger.LogInfo(Crypto, $"Downloaded compatible license file {licenseFile}."); found = true; } @@ -375,12 +381,12 @@ private void DownloadLicenseFile(string ebootPath, GameItem game) byte[]? idps = consolePatchAccessor.IdpsFile.Value; if (idps == null) { - Program.Log("Can't set IDPS.", "IDPS", BreadcrumbLevel.Error); + State.Logger.LogError(IDPS, "Can't set IDPS."); this.FailVerify("Couldn't retrieve the IDPS used for encryption from the PS3. The log may have more details."); } else { - Program.Log("Got the IDPS.", "IDPS", BreadcrumbLevel.Error); + State.Logger.LogInfo(IDPS, "Got the IDPS."); LibSceToolSharp.SetIdpsKey(idps); } } @@ -392,7 +398,7 @@ private void DownloadLicenseFile(string ebootPath, GameItem game) public override void CompletePatch(object? sender, EventArgs e) { if (!(this.Accessor?.Available ?? false)) { - Program.Log("Suppressing patch completion because accessor is unavailable"); + State.Logger.LogWarning(LogType.IntegratedPatchForm, "Suppressing patch completion because accessor is unavailable"); MessageBox.Show("Couldn't complete the patch because we couldn't reach the console. Try restarting Refresher.", MessageBoxType.Warning); return; } diff --git a/Refresher/UI/PSPSetupForm.cs b/Refresher/UI/PSPSetupForm.cs index b48f143..b604cfd 100644 --- a/Refresher/UI/PSPSetupForm.cs +++ b/Refresher/UI/PSPSetupForm.cs @@ -1,5 +1,7 @@ using Eto.Forms; -using Refresher.Patching; +using Refresher.Core; +using Refresher.Core.Patching; +using Refresher.Core.Patching; namespace Refresher.UI; @@ -31,7 +33,7 @@ public PSPSetupForm() : base("PSP Setup") try { - Program.Log($"Checking drive {drive.Name}..."); + State.Logger.LogInfo(PSP, $"Checking drive {drive.Name}..."); // Match for all directories called PSP and PSPEMU // NOTE: we do this because the PSP filesystem is case insensitive, and the .NET STL is case sensitive on linux @@ -50,7 +52,7 @@ public PSPSetupForm() : base("PSP Setup") // If theres no PSP folder or PSPEMU folder, if (!possiblePspMatches.Any() && !possiblePsVitaMatches.Any()) { - Program.Log($"Drive {drive.Name} has no PSP/PSPEMU folder, ignoring..."); + State.Logger.LogInfo(PSP, $"Drive {drive.Name} has no PSP/PSPEMU folder, ignoring..."); //Skip this drive continue; @@ -61,10 +63,8 @@ public PSPSetupForm() : base("PSP Setup") } catch(Exception ex) { - Program.Log("Checking drive failed due to exception, see below"); - Program.Log(ex.ToString()); - - //If we fail to check dir info, its probably not mounted in a safe/accessible way + State.Logger.LogError(PSP, $"Couldn't check the drive due to an exception: {ex}"); + // If we fail to check dir info, it's probably not mounted in a safe/accessible way continue; } diff --git a/Refresher/UI/PatchForm.cs b/Refresher/UI/PatchForm.cs index f06aad7..ace0468 100644 --- a/Refresher/UI/PatchForm.cs +++ b/Refresher/UI/PatchForm.cs @@ -1,13 +1,16 @@ using System.ComponentModel; using System.Diagnostics; +using System.Net.Http.Json; using System.Net.Sockets; using System.Runtime.InteropServices; +using System.Text.Json; using Eto.Drawing; using Eto.Forms; -using Newtonsoft.Json; -using Refresher.Patching; -using Refresher.Verification; -using Refresher.Verification.Autodiscover; +using Refresher.Core; +using Refresher.Core.Patching; +using Refresher.Core.Verification; +using Refresher.Core.Verification.Autodiscover; +using Refresher.Core.Patching; using Sentry; using Task = System.Threading.Tasks.Task; @@ -57,7 +60,7 @@ protected void InitializePatcher() { this._messages, new Button(this.Guide) { Text = "View guide" }, - new Button(this.AutoDiscover) { Text = "AutoDiscover" }, + new Button(this.InvokeAutoDiscover) { Text = "AutoDiscover" }, this._patchButton, }) { @@ -130,7 +133,7 @@ protected void OpenUrl(string url) } catch (Exception e) { - Program.Log(e.ToString(), "OpenUrl", BreadcrumbLevel.Error); + State.Logger.LogError(OSIntegration, e.ToString()); MessageBox.Show("We couldn't open your browser due to an error.\n" + $"You can use this link instead: {url}\n\n" + $"Exception details: {e.GetType().Name} {e.Message}", @@ -139,7 +142,7 @@ protected void OpenUrl(string url) // based off of https://stackoverflow.com/a/43232486 } - private void AutoDiscover(object? sender, EventArgs arg) + private void InvokeAutoDiscover(object? sender, EventArgs arg) { string url = this.UrlField.Text; if(!url.StartsWith("http")) @@ -147,14 +150,14 @@ private void AutoDiscover(object? sender, EventArgs arg) if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? autodiscoverUri)) { - Program.Log($"Invalid URL for autodiscover: {url}"); + State.Logger.LogWarning(AutoDiscover, $"Invalid URL for autodiscover: {url}"); MessageBox.Show("Server URL could not be parsed correctly. AutoDiscover cannot continue.", "Error", MessageBoxType.Error); return; } Debug.Assert(autodiscoverUri != null); - Program.Log($"Invoking autodiscover on URL '{url}'"); + State.Logger.LogInfo(AutoDiscover, $"Invoking autodiscover on URL '{url}'"); try { using HttpClient client = new(); @@ -163,14 +166,8 @@ private void AutoDiscover(object? sender, EventArgs arg) HttpResponseMessage response = client.GetAsync("/autodiscover").Result; response.EnsureSuccessStatusCode(); - - Stream stream = response.Content.ReadAsStream(); - - JsonSerializer serializer = new(); - using StreamReader streamReader = new(stream); - using JsonTextReader jsonReader = new(streamReader); - - AutodiscoverResponse? autodiscover = serializer.Deserialize(jsonReader); + + AutodiscoverResponse? autodiscover = response.Content.ReadFromJsonAsync().Result; if (autodiscover == null) throw new InvalidOperationException("autoresponse was null"); string text = $"Successfully found a '{autodiscover.ServerBrand}' server at the given URL!\n\n" + @@ -219,7 +216,7 @@ private static bool HandleAutoDiscoverError(Exception inner) return true; } - if (inner is JsonReaderException) + if (inner is JsonException) { MessageBox.Show("AutoDiscover failed, because the server sent invalid data. There might be an outage; please try again in a few moments."); return true; @@ -272,7 +269,7 @@ private void Patch(object? sender, EventArgs e) switch (result) { case DialogResult.Yes: - this.AutoDiscover(this, EventArgs.Empty); + this.InvokeAutoDiscover(this, EventArgs.Empty); break; case DialogResult.No: MessageBox.Show("Okay, AutoDiscover won't be used. If you have issues with this patch, try using it next time.\n" + @@ -303,7 +300,7 @@ private void FailVerify(string reason, bool clear = true) if(clear) this._messages.Items.Clear(); this._messages.Items.Add(reason); - Program.Log(reason, "Verification Failure", level: BreadcrumbLevel.Error); + State.Logger.LogError(Verify, reason); MessageBox.Show(reason, "Verification Failed", MessageBoxType.Error); this.Reset(); @@ -313,13 +310,13 @@ private void FailVerify(string reason, bool clear = true) protected void LogMessage(string message) { - Program.Log(message, "Log"); + State.Logger.LogInfo(PatchForm, message); this._messages.Items.Add(message); } protected void Reverify(object? sender, EventArgs args) { - Program.Log($"Reverify triggered for patcher {this.Patcher?.GetType().Name}"); + State.Logger.LogDebug(PatchForm, $"Reverify triggered for patcher {this.Patcher?.GetType().Name}"); if (this.Patcher == null) return; // Cancel the current task, and wait for it to complete diff --git a/Refresher/UI/RefresherForm.cs b/Refresher/UI/RefresherForm.cs index 78f1103..830147d 100644 --- a/Refresher/UI/RefresherForm.cs +++ b/Refresher/UI/RefresherForm.cs @@ -2,6 +2,7 @@ using Eto.Forms; using Sentry; using System.Runtime.InteropServices; +using Refresher.Core; namespace Refresher.UI; @@ -38,7 +39,7 @@ protected RefresherForm(string subtitle, Size size, bool padBottom = true) TForm form = new(); form.Show(); - Program.Log($"Showing child form {form.GetType().Name} '{form.Title}'"); + State.Logger.LogDebug(OSIntegration, $"Showing child form {form.GetType().Name} '{form.Title}'"); if (close) this.Visible = false; }