From f804fe0c37bdb3fdeb361c68bc02e4b2ba9b7c68 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 9 Jun 2025 16:16:28 -0400 Subject: [PATCH 1/6] WIP LBP RPCS3 patch --- .../Pipelines/Lbp/LbpRPCS3PatchPipeline.cs | 40 +++++++++++++++++++ .../Lbp/PatchworkRPCS3ConfigPipeline.cs | 9 +++++ Refresher/UI/MainForm.cs | 5 ++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs create mode 100644 Refresher.Core/Pipelines/Lbp/PatchworkRPCS3ConfigPipeline.cs diff --git a/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs new file mode 100644 index 0000000..f3737f7 --- /dev/null +++ b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs @@ -0,0 +1,40 @@ +using Refresher.Core.Accessors; +using Refresher.Core.Pipelines.Steps; + +namespace Refresher.Core.Pipelines.Lbp; + +public class LbpRPCS3PatchPipeline : Pipeline +{ + public override string Id => "lbp-rpcs3-patch"; + public override string Name => "LBP RPCS3 Patch"; + + protected override Type SetupAccessorStepType => typeof(SetupEmulatorAccessorStep); + public override bool ReplacesEboot => false; + + public override string GuideLink => "https://docs.littlebigrefresh.com/rpcs3"; + + public override IEnumerable GameNameFilters => ["littlebigplanet", "lbp"]; + + protected override List StepTypes => + [ + // Info gathering stage + typeof(ValidateGameStep), + typeof(DownloadParamSfoStep), + typeof(DownloadGameEbootStep), + typeof(ReadEbootContentIdStep), + typeof(DownloadGameLicenseStep), + + // Decryption and patch stage + typeof(PrepareSceToolStep), + typeof(DecryptGameEbootStep), + typeof(ApplySprxPatchToEbootStep), + + // Encryption and upload stage + // FIXME: encrypting seems to break the elf in rpcs3? + // typeof(EncryptGameEbootStep), + // typeof(BackupGameEbootBeforeReplaceStep), + typeof(UploadPatchworkSprxStep), + typeof(UploadPatchworkConfigurationStep), + typeof(UploadGameEbootElfStep), // upload EBOOT.elf for now + ]; +} \ No newline at end of file diff --git a/Refresher.Core/Pipelines/Lbp/PatchworkRPCS3ConfigPipeline.cs b/Refresher.Core/Pipelines/Lbp/PatchworkRPCS3ConfigPipeline.cs new file mode 100644 index 0000000..c422344 --- /dev/null +++ b/Refresher.Core/Pipelines/Lbp/PatchworkRPCS3ConfigPipeline.cs @@ -0,0 +1,9 @@ +using Refresher.Core.Pipelines.Steps; + +namespace Refresher.Core.Pipelines.Lbp; + +public class PatchworkRPCS3ConfigPipeline : PatchworkConfigPipeline +{ + protected override string ConsoleName => "RPCS3"; + protected override Type? SetupAccessorStepType => typeof(SetupEmulatorAccessorStep); +} \ No newline at end of file diff --git a/Refresher/UI/MainForm.cs b/Refresher/UI/MainForm.cs index 5498f46..965d6a0 100644 --- a/Refresher/UI/MainForm.cs +++ b/Refresher/UI/MainForm.cs @@ -19,8 +19,9 @@ public class MainForm : RefresherForm new Label { Text = "Welcome to Refresher! Please pick a patching method to continue." }, new Label { Text = "LittleBigPlanet:" }, this.PipelineButton("Patch LBP1/2/3 for PS3"), - - this.PipelineButton("Reconfigure Patch for PS3"), + this.PipelineButton("Reconfigure Patch for PS3"), + this.PipelineButton("Patch LBP1/2/3 for RPCS3"), + this.PipelineButton("Reconfigure Patch for RPCS3"), new Label { Text = "General (for non-LBP games):" }, new Button((_, _) => this.ShowChild()) { Text = "File Patch (using a .ELF)" }, this.PipelineButton("Patch any RPCS3 game"), From 1a34c7df2d3d1b4a98515cb326057f3d726e753a Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 18 Jun 2025 17:20:13 -0400 Subject: [PATCH 2/6] Add fake signer for RPCS3 --- Refresher.Core/LogType.cs | 4 +- Refresher.Core/Native/Elf/Elf64Ehdr.cs | 22 +++ Refresher.Core/Native/Elf/Elf64Phdr.cs | 16 ++ Refresher.Core/Native/Sce/AppInfo.cs | 13 ++ Refresher.Core/Native/Sce/ControlInfo.cs | 19 +++ Refresher.Core/Native/Sce/SceHeader.cs | 15 ++ Refresher.Core/Native/Sce/SceVersionData.cs | 18 +++ Refresher.Core/Native/Sce/SceVersionInfo.cs | 12 ++ Refresher.Core/Native/Sce/SegmentInfo.cs | 14 ++ Refresher.Core/Native/Sce/SelfHeader.cs | 18 +++ .../Pipelines/Lbp/LbpRPCS3PatchPipeline.cs | 8 +- .../Steps/FakeEncryptGameEbootStep.cs | 148 ++++++++++++++++++ .../PrintInfoForEncryptedGameEbootStep.cs | 16 ++ Refresher.Core/Refresher.Core.csproj | 1 + 14 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 Refresher.Core/Native/Elf/Elf64Ehdr.cs create mode 100644 Refresher.Core/Native/Elf/Elf64Phdr.cs create mode 100644 Refresher.Core/Native/Sce/AppInfo.cs create mode 100644 Refresher.Core/Native/Sce/ControlInfo.cs create mode 100644 Refresher.Core/Native/Sce/SceHeader.cs create mode 100644 Refresher.Core/Native/Sce/SceVersionData.cs create mode 100644 Refresher.Core/Native/Sce/SceVersionInfo.cs create mode 100644 Refresher.Core/Native/Sce/SegmentInfo.cs create mode 100644 Refresher.Core/Native/Sce/SelfHeader.cs create mode 100644 Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs create mode 100644 Refresher.Core/Pipelines/Steps/PrintInfoForEncryptedGameEbootStep.cs diff --git a/Refresher.Core/LogType.cs b/Refresher.Core/LogType.cs index 2c6373a..b985682 100644 --- a/Refresher.Core/LogType.cs +++ b/Refresher.Core/LogType.cs @@ -18,5 +18,7 @@ public enum LogType : byte Vita, RPCS3, Pipeline, - Patchwork + Patchwork, + Encrypt, + Decrypt, } \ No newline at end of file diff --git a/Refresher.Core/Native/Elf/Elf64Ehdr.cs b/Refresher.Core/Native/Elf/Elf64Ehdr.cs new file mode 100644 index 0000000..f065835 --- /dev/null +++ b/Refresher.Core/Native/Elf/Elf64Ehdr.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Elf; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct Elf64Ehdr +{ + public unsafe fixed byte e_ident[16]; + public ushort e_type; + public ushort e_machine; + public uint e_version; + public ulong e_entry; + public ulong e_phoff; + public ulong e_shoff; + public uint e_flags; + public ushort e_ehsize; + public ushort e_phentsize; + public ushort e_phnum; + public ushort e_shentsize; + public ushort e_shnum; + public ushort e_shstrndx; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Elf/Elf64Phdr.cs b/Refresher.Core/Native/Elf/Elf64Phdr.cs new file mode 100644 index 0000000..696be88 --- /dev/null +++ b/Refresher.Core/Native/Elf/Elf64Phdr.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Elf; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct Elf64_Phdr +{ + public uint p_type; + public uint p_flags; + public ulong p_offset; + public ulong p_vaddr; + public ulong p_paddr; + public ulong p_filesz; + public ulong p_memsz; + public ulong p_align; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/AppInfo.cs b/Refresher.Core/Native/Sce/AppInfo.cs new file mode 100644 index 0000000..e85af3d --- /dev/null +++ b/Refresher.Core/Native/Sce/AppInfo.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct AppInfo +{ + public ulong auth_id; // 0x1010000001000003 + public uint vendor_id; // 0x1000002 + public uint self_type; // 0x4 (application) + public ulong version; // 0x0001000000000000 + public ulong padding; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/ControlInfo.cs b/Refresher.Core/Native/Sce/ControlInfo.cs new file mode 100644 index 0000000..39c9645 --- /dev/null +++ b/Refresher.Core/Native/Sce/ControlInfo.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct ControlInfo +{ + public uint type; // 0x2 + public uint size; // 0x40 + public ulong next; // 0x0 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] digest1; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] digest2; + + public ulong padding; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/SceHeader.cs b/Refresher.Core/Native/Sce/SceHeader.cs new file mode 100644 index 0000000..c8645cd --- /dev/null +++ b/Refresher.Core/Native/Sce/SceHeader.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SceHeader +{ + public uint magic; // 0x53434500 + public uint version; // 0x2 + public ushort keyrev; // 0x8000 (devkit) + public ushort type; // 0x1 (self) + public uint meta_off; // generated from ELF + public ulong head_len; // generated from ELF + public ulong data_len; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/SceVersionData.cs b/Refresher.Core/Native/Sce/SceVersionData.cs new file mode 100644 index 0000000..3eed0c9 --- /dev/null +++ b/Refresher.Core/Native/Sce/SceVersionData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SceVersionData +{ + public ushort unknown1; + public ushort unknown2; // 0x1 + public uint unknown3; // 0x30 + public uint unknown4; // 0x0 + public uint unknown5; // 0x1 + public ulong offset; // 0x0 + public ulong size; // 0x0 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] control_flags; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/SceVersionInfo.cs b/Refresher.Core/Native/Sce/SceVersionInfo.cs new file mode 100644 index 0000000..417ffc2 --- /dev/null +++ b/Refresher.Core/Native/Sce/SceVersionInfo.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SceVersionInfo +{ + public uint subheader_type; // 0x1 + public uint present; // 0x0 + public uint size; // 0x10 + public uint unknown4; // 0x0 +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/SegmentInfo.cs b/Refresher.Core/Native/Sce/SegmentInfo.cs new file mode 100644 index 0000000..e654475 --- /dev/null +++ b/Refresher.Core/Native/Sce/SegmentInfo.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SegmentInfo +{ + public ulong offset; + public ulong size; + public uint compressed; + public uint unknown1; + public uint unknown2; + public uint encrypted; +} \ No newline at end of file diff --git a/Refresher.Core/Native/Sce/SelfHeader.cs b/Refresher.Core/Native/Sce/SelfHeader.cs new file mode 100644 index 0000000..7ef51d3 --- /dev/null +++ b/Refresher.Core/Native/Sce/SelfHeader.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Refresher.Core.Native.Sce; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct SelfHeader +{ + public ulong header_type; // 0x3 (self) + public ulong appinfo_offset; // 0x70 + public ulong elf_offset; // 0x90 + public ulong phdr_offset; // generated from ELF + public ulong shdr_offset; // generated from ELF + public ulong section_info_offset; // generated from ELF + public ulong sceversion_offset; // generated from ELF + public ulong controlinfo_offset; // generated from ELF + public ulong controlinfo_length; // generated from ELF + public ulong padding; +} \ No newline at end of file diff --git a/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs index f3737f7..cfc0018 100644 --- a/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs +++ b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs @@ -9,7 +9,7 @@ public class LbpRPCS3PatchPipeline : Pipeline public override string Name => "LBP RPCS3 Patch"; protected override Type SetupAccessorStepType => typeof(SetupEmulatorAccessorStep); - public override bool ReplacesEboot => false; + public override bool ReplacesEboot => true; public override string GuideLink => "https://docs.littlebigrefresh.com/rpcs3"; @@ -30,9 +30,9 @@ public class LbpRPCS3PatchPipeline : Pipeline typeof(ApplySprxPatchToEbootStep), // Encryption and upload stage - // FIXME: encrypting seems to break the elf in rpcs3? - // typeof(EncryptGameEbootStep), - // typeof(BackupGameEbootBeforeReplaceStep), + typeof(FakeEncryptGameEbootStep), + typeof(PrintInfoForEncryptedGameEbootStep), + typeof(BackupGameEbootBeforeReplaceStep), typeof(UploadPatchworkSprxStep), typeof(UploadPatchworkConfigurationStep), typeof(UploadGameEbootElfStep), // upload EBOOT.elf for now diff --git a/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs new file mode 100644 index 0000000..5323ceb --- /dev/null +++ b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs @@ -0,0 +1,148 @@ +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Refresher.Core.Native.Elf; +using Refresher.Core.Native.Sce; + +namespace Refresher.Core.Pipelines.Steps; + +public class FakeEncryptGameEbootStep : Step +{ + public FakeEncryptGameEbootStep(Pipeline pipeline) : base(pipeline) + {} + + private static ushort Swap16(ushort val) => (ushort)(((val & 0xFF00) >> 8) | ((val & 0x00FF) << 8)); + private static uint Swap32(uint val) => + ((val & 0xFF000000) >> 24) | + ((val & 0x00FF0000) >> 8) | + ((val & 0x0000FF00) << 8) | + ((val & 0x000000FF) << 24); + + private static ulong Swap64(ulong val) => + ((val & 0xFF00000000000000UL) >> 56) | + ((val & 0x00FF000000000000UL) >> 40) | + ((val & 0x0000FF0000000000UL) >> 24) | + ((val & 0x000000FF00000000UL) >> 8) | + ((val & 0x00000000FF000000UL) << 8) | + ((val & 0x0000000000FF0000UL) << 24) | + ((val & 0x000000000000FF00UL) << 40) | + ((val & 0x00000000000000FFUL) << 56); + + private static void WriteStruct(Stream stream, T @struct) where T : struct + { + int size = Marshal.SizeOf(); + byte[] buffer = new byte[size]; + IntPtr ptr = Marshal.AllocHGlobal(size); + + Marshal.StructureToPtr(@struct, ptr, false); + Marshal.Copy(ptr, buffer, 0, size); + Marshal.FreeHGlobal(ptr); + + stream.Write(buffer, 0, size); + } + + public override float Progress { get; protected set; } + public override Task ExecuteAsync(CancellationToken cancellationToken = default) + { + State.Logger.LogDebug(Encrypt, "Loading ELF to memory..."); + byte[] elfData = File.ReadAllBytes(this.Game.DecryptedEbootPath); + + Elf64Ehdr elfHeader = MemoryMarshal.Read(elfData); + + State.Logger.LogDebug(Encrypt, "Calculating offsets..."); + + SceHeader sceHeader = new(); + SelfHeader selfHeader = new(); + AppInfo appInfo = new(); + SceVersionInfo sceVersionInfo = new(); + SceVersionData sceVersionData = new(); + ControlInfo controlInfo = new(); + + int fselfHeaderSize = Marshal.SizeOf() + Marshal.SizeOf() + Marshal.SizeOf(); + + ulong phdrOffset = Swap64(elfHeader.e_phoff) + (ulong)fselfHeaderSize; + ushort phdrCount = Swap16(elfHeader.e_phnum); + + ulong sectionInfoOffset = (ulong)fselfHeaderSize + Swap16(elfHeader.e_ehsize) + ((ulong)Swap16(elfHeader.e_phentsize) * phdrCount); + ulong sceVersionOffset = sectionInfoOffset + (ulong)(phdrCount * Marshal.SizeOf()); + ulong controlInfoOffset = sceVersionOffset + (ulong)Marshal.SizeOf(); + + sceHeader.magic = Swap32(0x53434500); + sceHeader.version = Swap32(0x2); + sceHeader.keyrev = Swap16(0x8000); + sceHeader.type = Swap16(0x1); + sceHeader.meta_off = Swap32((uint)(controlInfoOffset + (ulong)Marshal.SizeOf() + (ulong)(Marshal.SizeOf() / 2))); + sceHeader.head_len = Swap64(controlInfoOffset + (ulong)Marshal.SizeOf() + (ulong)Marshal.SizeOf()); + sceHeader.data_len = Swap64((ulong)elfData.Length); + + ulong shdrOffset = Swap64(elfHeader.e_shoff) + Swap64(sceHeader.head_len); + + State.Logger.LogDebug(Encrypt, "Calculating segments ..."); + SegmentInfo[] segments = new SegmentInfo[phdrCount]; + + for (int i = 0; i < phdrCount; i++) + { + long offset = (long)Swap64(elfHeader.e_phoff) + i * Marshal.SizeOf(); + Elf64_Phdr elfPhdr = MemoryMarshal.Read(elfData.AsSpan((int)offset)); + + segments[i].offset = Swap64(Swap64(elfPhdr.p_offset) + Swap64(sceHeader.head_len)); + segments[i].size = elfPhdr.p_filesz; + segments[i].compressed = Swap32(0x1); + segments[i].encrypted = Swap32(0x2); + } + + selfHeader.header_type = Swap64(0x3); + selfHeader.appinfo_offset = Swap64(0x70); + selfHeader.elf_offset = Swap64(0x90); + selfHeader.phdr_offset = Swap64(phdrOffset); + selfHeader.shdr_offset = Swap64(shdrOffset); + selfHeader.section_info_offset = Swap64(sectionInfoOffset); + selfHeader.sceversion_offset = Swap64(sceVersionOffset); + selfHeader.controlinfo_offset = Swap64(controlInfoOffset); + selfHeader.controlinfo_length = Swap64((ulong)(Marshal.SizeOf() + Marshal.SizeOf())); + + appInfo.auth_id = Swap64(0x1010000001000003); + appInfo.vendor_id = Swap32(0x1000002); + appInfo.self_type = Swap32(0x4); + appInfo.version = Swap64(0x0001000000000000); + + sceVersionInfo.subheader_type = Swap32(0x1); + sceVersionInfo.size = Swap32((uint)Marshal.SizeOf()); + + sceVersionData.unknown2 = Swap16(0x1); + sceVersionData.unknown3 = Swap32((uint)Marshal.SizeOf()); + sceVersionData.unknown5 = Swap32(0x1); + sceVersionData.control_flags = new byte[16]; + + controlInfo.type = Swap32(0x2); + controlInfo.size = Swap32((uint)Marshal.SizeOf()); + controlInfo.digest1 = new byte[20]; + controlInfo.digest2 = new byte[20]; + + State.Logger.LogDebug(Encrypt, "Calculating hashes..."); + byte[] hardcodedSha = [0x62, 0x7c, 0xb1, 0x80, 0x8a, 0xb9, 0x38, 0xe3, 0x2c, 0x8c, 0x09, 0x17, 0x08, 0x72, 0x6a, 0x57, 0x9e, 0x25, 0x86, 0xe4]; + Buffer.BlockCopy(hardcodedSha, 0, controlInfo.digest1, 0, 20); + controlInfo.digest2 = SHA1.HashData(elfData); + + State.Logger.LogDebug(Encrypt, "FSELF built, writing..."); + string outputPath = this.Game.EncryptedEbootPath = Path.GetTempFileName(); + using FileStream output = File.Open(outputPath, FileMode.Create); + WriteStruct(output, sceHeader); + WriteStruct(output, selfHeader); + WriteStruct(output, appInfo); + output.Write(elfData, 0, Swap16(elfHeader.e_ehsize) + (Swap16(elfHeader.e_phentsize) * phdrCount)); + + foreach (SegmentInfo seg in segments) + WriteStruct(output, seg); + + WriteStruct(output, sceVersionInfo); + WriteStruct(output, sceVersionData); + WriteStruct(output, controlInfo); + + output.Write(elfData); + output.Flush(); + + return Task.CompletedTask; + } + + +} \ No newline at end of file diff --git a/Refresher.Core/Pipelines/Steps/PrintInfoForEncryptedGameEbootStep.cs b/Refresher.Core/Pipelines/Steps/PrintInfoForEncryptedGameEbootStep.cs new file mode 100644 index 0000000..ce8479b --- /dev/null +++ b/Refresher.Core/Pipelines/Steps/PrintInfoForEncryptedGameEbootStep.cs @@ -0,0 +1,16 @@ +using SCEToolSharp; + +namespace Refresher.Core.Pipelines.Steps; + +public class PrintInfoForEncryptedGameEbootStep : Step +{ + public PrintInfoForEncryptedGameEbootStep(Pipeline pipeline) : base(pipeline) + {} + + public override float Progress { get; protected set; } + public override Task ExecuteAsync(CancellationToken cancellationToken = default) + { + LibSceToolSharp.PrintInfos(this.Game.EncryptedEbootPath); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Refresher.Core/Refresher.Core.csproj b/Refresher.Core/Refresher.Core.csproj index 4bba9c5..1ca8fda 100644 --- a/Refresher.Core/Refresher.Core.csproj +++ b/Refresher.Core/Refresher.Core.csproj @@ -5,6 +5,7 @@ enable enable embedded + true From d5ca8f5e7ed8e2bdd77ef5a6ff8f363172b52748 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 18 Jun 2025 17:27:22 -0400 Subject: [PATCH 3/6] Upload RPCS3-only patchwork sprx --- .../Steps/UploadPatchworkSprxStep.cs | 16 +++++++++++----- Refresher.Core/Refresher.Core.csproj | 3 +++ Refresher.Core/Resources/patchwork-rpcs3.sprx | Bin 0 -> 30512 bytes Refresher.Core/Resources/patchwork.sprx | Bin 15488 -> 16528 bytes 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 Refresher.Core/Resources/patchwork-rpcs3.sprx diff --git a/Refresher.Core/Pipelines/Steps/UploadPatchworkSprxStep.cs b/Refresher.Core/Pipelines/Steps/UploadPatchworkSprxStep.cs index 7fa3cf6..e095f78 100644 --- a/Refresher.Core/Pipelines/Steps/UploadPatchworkSprxStep.cs +++ b/Refresher.Core/Pipelines/Steps/UploadPatchworkSprxStep.cs @@ -15,6 +15,7 @@ await PatchAccessor.TryAsync(async () => { const string pluginsFolder = "plugins/"; const string sprxName = "patchwork.sprx"; + const string sprxNameEmulator = "patchwork-rpcs3.sprx"; const string sprxPath = pluginsFolder + sprxName; this.Pipeline.Accessor!.CreateDirectoryIfNotExists(pluginsFolder); @@ -23,19 +24,24 @@ await PatchAccessor.TryAsync(async () => this.Pipeline.Accessor.RemoveFile(sprxPath); this.Progress = 0.5f; + + string localSprxName = this.Pipeline.Accessor is EmulatorPatchAccessor + ? sprxNameEmulator + : sprxName; - if (File.Exists(sprxName)) + if (File.Exists(localSprxName)) { - State.Logger.LogInfo(Patchwork, "Found custom patchwork.sprx next to exe, uploading that instead"); - this.Pipeline.Accessor.UploadFile(sprxName, sprxPath); + State.Logger.LogInfo(Patchwork, "Found custom patchwork.sprx next to exe file, uploading that instead"); + this.Pipeline.Accessor.UploadFile(localSprxName, sprxPath); } else { await using Stream writeStream = this.Pipeline.Accessor.OpenWrite(sprxPath); - await using Stream? readStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(sprxName); + await using Stream? readStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(localSprxName); if (readStream == null) - throw new InvalidOperationException("The sprx file is missing from this build! Tell a developer."); + throw new InvalidOperationException($"The sprx file for {this.Pipeline.Accessor.GetType().Name} is missing from this build!" + + $"Please tell a developer on Discord/GitHub!"); await readStream.CopyToAsync(writeStream, cancellationToken); await writeStream.FlushAsync(cancellationToken); diff --git a/Refresher.Core/Refresher.Core.csproj b/Refresher.Core/Refresher.Core.csproj index 1ca8fda..d37d7cc 100644 --- a/Refresher.Core/Refresher.Core.csproj +++ b/Refresher.Core/Refresher.Core.csproj @@ -24,6 +24,9 @@ patchwork.sprx + + patchwork-rpcs3.sprx + diff --git a/Refresher.Core/Resources/patchwork-rpcs3.sprx b/Refresher.Core/Resources/patchwork-rpcs3.sprx new file mode 100644 index 0000000000000000000000000000000000000000..b0a9c05120216d02589c6a17c5ef35a5e4483ae5 GIT binary patch literal 30512 zcmd^n4R}=5we~u5W|BYx1Q;=tKu-QJftZAVIzfa4A_fQ;`MIKClaLG~5|SaA;G|B- zL?gyZZSdN!(E>NNKv8*mvBef|{Rv1Fg|@WXURrH$xY8Pow>P%-HV|~~yY|^<=FB96 z_}qTq^F7bq;heqqyVqW8?X}k4XP=*8)uLMo$>JkYkdm4S@ys6Cen!s&XP+$XmU+C3XkVa} zW)uX==8|L^grpt@5Dhk*gg)l6vW<{(3q(fbLq1Y_kKQiigtv2@k6b~M73goG+wyOE ze`S6MZKy1rWcZnNEHK~=-sgQ^ebMoL*3MDa#x&e}=ic-k=eL}^^@;IoKHRhQp5DYC zO|D+|v8V2j2K~#~L^I*FuWuvzSq@R#T?mXOqW6{)9Y`knIFo49y+osD6Ll>h>PMsv zpwBhHTJ9vOL!U9@iDD)|*_%LDgnTTKy^d&uM)Zv=q9;**AGq5~q&bNGTud}$Dba;3 zM45<`b7P3MFDCjqWKE1A+6An7E79HgL~pNwj3lBT&muaHwtkp+A@FqY$*U&XjedIz ziEJ4}7t4t5+(6{T7`zLKx-rOYkdXrVH*O;;h8~$1*V6Swr(w0x*+f4mAZkVXpYxD= zh<=UoTaa^OG0_f0{-cwL9=iwp(uoRhCHh+$(eu}WC$NgI5Pb{sFV_>j3mfjMAv%Tf zPg0@p4QK<;3dlHx+56Zi^uxHmk8xMPc6GpOHWM8~6#N|ed;??r08#k^*z>RJi0bbK z?JYzPZYP?BvHc~5=;%K{el0NAaSq1$kFfdM82f)(iDs|Dcoq?rz(=WeqCwd2UHIqK z8L%VxpDRay@cIt=`XD18_WL^ey<9@{&Cx`0ZlbZEdwd1aU6>7XQQyS2YamJjpHmBo zN+&`d`YX`=0mxW^@g72X0rY$UzMpeD(QB}Cuajs!jz7X)7cssr$k~K3RKf=5S3=+W ziPnHtjYI4}c6TOXE|KWd8_^GTSg?$!CmnGK8E3#FX%f-1$wU)SZbZ2eF>r&8Xe#XY z*e2Kv{bL|M(@8WL{#jU!u|VI&plbrY9CmsWGwUVLeYt|@zYxn8HUfu_r-0`B80*w% zqJP0y|Az7ZCwOPVmX9Fz{)+JpRl#1cpB1+3fqu!LcVjFM!G`C6T?2dm3i3Y2nC8Gn zkHgM4;&>4L+LMR4sVDj#gno`)br7?|H=NZpe&+ zjEjqjny-Zo5L3C3+nqwRvKjMZE>Zk+*bi|x6TDw51s~}C*?mOUYRFe14q)@M828P0 zAr3ae&#mwU^zBD{eF?fh)Pk7*U$)*4Ci^SAF_r^E=csE?p~Kz{=jb4sl-(mN&1edWtDwIl_gQuXmK{ z;kK4ci`_BG?*~s8c*diz%c!H>h5DIRUvWXf9xCFxA#XKB&0kG1^SrUDr!B=tKGq@D zV(0d5lvk5=e!iv@d8yc;>*YVF^CpF9cpP1z1J5%&9^^jO@dD;SUo6FYW7Grv{r3uImw%hZFsbV9Z zO!o4@ULCMkr<9iqd0CK`D`UgQv4MI=b0%r*E6B@rj3Snu%XFxBA~vXSxBoQD5A)Yn z8P6Sv=Wet7!B>UPdm#TL`1B0-m8CG$SJR&1afkZ~^MUnsdq)YsoHYBT4>H)+#Wd|} z+*i;A>^yBUW2|$8SQ{^Nf11w1c7212LahH_tD+Ut)RuDiN9xkWw(ClwC;Ajz+Z6vV z@{@V~&^QmBvv_FwdDyBGvb%VU$U6-k=lQ6)y#3Q|IZk^qjXB4B+h9}CAM~bqorbX{ zLYBg^5Kjv9xPj^6vx5=%zgFH}shRk~k*MK3F?qIyttG^BThT;km%`74s#2ng7#5bDM92FOKJ(-Zu0f(hobO z{huCzf4t-$KLY>7%lwzmfq$S4{OyuI+J?R+`FBL}FZw1({s|-Sp8)=COk=cxf1>1% zwxR1K|903(%z5@PV#XUu9@==_-YM5Am)SOKw`Kl|FEUTY4ZA-0%y9b_(}Y6y9n;?b z3~8QN5uYBmKd+Z9RqH(L=VF@tBGVxzT&&|@5_QY`paW%&Bjg8-emu5f`cgCZxv+o^ z_JPJb%3?>q!=O7X=RrUY{2U|Y+`)aoTdZvuW4jz-x$=a~AW zq0i|mJWgJ_^D{|7zb@%hm(Uyfw^Xk4%rmV!Ez_5wZ?w=~(8HI=1HJ*RyS)8Gf}HR1 zpdH0>Kx6o34*RApQ(#?&9M(yfGt|?Pj%#lZO(>?QpUS?5ua2N^yo}8dpB2-%{Y*P- zJQ(l&&%p;6iwb+|3mCwaP0EVb}S z?r@)s8QK3M`{ZnS`>UmFXWb{(F|20X-<;5WyZs*CJ7WIx_1X&`d$DdSfolflgT5w@4$OaE zFF+^mMR5Ihh-(Md#by3aeg_#?L+ne2?MI*U_l51J+vI-QGpJA}??-VB7jp|d=RwZ` zUC&s*qc@)LxJZGmi;8LDPo=(!ai4L8eeW?T!vPtk<~=*#V|m}2&G%WZT?@#A`v%{x zGU~v%#9GE5I6Tz#Ynk5xqc$o=@=Qy)k9efMEckr&WQ zm#^yu`2k&`O*+P~UKH~M^cwMA-MF`scH(<*aX$(>^>Ms(AqH^GcUQwVeBGCOmbK97 zjJ#H#^^6v=(ko-8kL||S?Utfd9*$ct#<0x)*u0|1V;u;|uua3+p3eW)|B|#OePhF@yPA%=c)2hj_W?bK<3hWr>)9 zy#L!VGjl&hdn&P?ZpZqtd5WiS54p0^@r>~S(lYkbxPpf$&2y;lc*`T|fxh#&C)n>F zbhJ~#TPkPR^NajGFRje-JRDt~a~#iYWeqm11~&gRzuxaVBXOAEktg%P9V0tUgxQp_F-Wf9B&j z;ioUk!%KNttWRN^-#s&(q6?m+M9+b~w_vzL&I}C;a~1 zr-aQ0*_PPD$9O-;EoGZ_oDe?o3!9$`*!&~dHX*+V&+(-Fvc3y@?C_s7e8u*I-IT%| z%1+qL!gkB)hW+-^q2`US-*nJFrLLa2U!j6MpAPbv**+L^437b}i|;!jY^T9?8IYgL zF#-Fhz%-G;~F2kpgv**+9k;VhR5YGjkd!Eyf zMaMPRX`JT`9?!}w@2E;o^JslMp3}N4PtS1;c8lHhD5-}7^`=+1LX^=Jk7p#)}PSE!{T8`r=p4aS@_Bw0WE5tAD z@C)LN#{pj2^N)ge)Pv`nqgfV@12+3=yX7$Zg>)Gh!)TTRe}MmGAMA8(!Jf+=@a==I z`{46F#(2HRZ}$s3vAvoLD5ZH6rEq+Lhuw3UQotu3ddI*fv@4e4-~;P2zyFBN^U@jc zAjb)X=R+Uc8@`SY^2uX9*5)084{;yC3w#g*3E&kEzINCmfq8mWeykSlL?sdiBoo{l9sxy#1$>^L<3vrt?_l;2KH0z;{rkj9ryv-L(kz zR$=S+DZcYHvUh$5wtA2fpMQ|1<4E(W(MMRX!Q{^h(Kr6mImI)StaPxQyx+vTi-%G@ z`2~0vmQ1-9&nb>eic)YXSt-6$sFYr+P}W{*Q_3$rtyEt+rnrU_#Wz%_bPiQ0T|;e3 z_t4YI!J%Wy;h}TN(UuusvbWs(mc1FzMw&O1mcNv&{LB$CL9o?%$j*fih~>5{e#`E7 z9ll;lTHC#N*7YG-*Hl-}UsL0mzh?9IUunr)=vm!+a(+wZ-5#{3t?qMi*^V;>ia* zKI6KV=6bxe)ZRIo3LxXq13yChKjS{(hci77T6`tm=TZJXjvwGSg5&T^5A-N#|2idZ zKlB_wt2%Hq#{Tesk@ewUP|U*(6xZHJ@edy%`@@}-;Qj4!yZ2w-vajBJaQ^D@GxHHE znrA<0t7{zdy>z#y_2C3h>$3@t)>D@}YdX4sE%mHbYEz1$;?B(68YWs7W%Ed^+3PJyJ?E2^}``g%cGTIe68*8 zds^T9i|6LM$?A>6^MF?D$G?pIxVp&W{q$y!_r2MkMVfzg%MBKLKYa8d{I%;Q?4u?T zeF%R;S9?Ev-Vfh(0&BxJ10TYlEtxCr{qSQy{J0C4Zvt@m-U~hY;n#lnxf56$-p783 zc)&R9{qXfJ)O~hfh!1bH#omc>8@}TB5V6uSeW`sH%D!=^BbL|}ohV;GY&nvZ9*)JF z2jM5YYs5RXIW}JGW7~s?fg#7f3jPp>D)iZhq+XVeaK$_ zOHY3p`@H9HEuKahH&>8r%@Qm7)!zEhQu`pVnY(Gh{l6s*?;d-(jbkV05v;F>8IGOS z>UTZZJNTM4tEOF`g8X~LyQ>4Oo8R>`*WmreXwYsZ1#>~+dwRs^M>!7@bx)%-^mB-J zl;FjF_w-`F$a7=HCCsCR=6R&>9=uD=BbMFjpKE^}x?{hSaV2`h#QB^3RB-cde@|=W zyPnpfw>`LqT;y?KOmRGZtYH~ByTyCQ;-|M$TFcDdQXV7g=a@>lJfB-&6PDYO`T6=e z%JJR7UgrDR`Y+_?J@9jD(9i5+_AmRJecTWKvY&Jz@+q|DCTRRvsI_qkvxqa(3ur_Vi${>8IR)kBiY~2IlJ(eB$kiJ~DF$-PDqd zciH6k-9IGWSGsUtaS|~T1742&3OdOCvVV;O>dcbiS<`l|0kDFF8n-zF>2f%^wEbAV~B0*0AIGRu&2qs zBh8_pP8VN>9AQi7qstn>mWZi1%xy(J%NfM`%Xo)EF3j~lPZHuB^SSl?ArIzxFXsM$ z_a&^&ds8vy!C&xw;{E%uM)-*C{|RYKhgi(u>sTN7AP#z=W1pDw=!>~Nh;@Z{t|PXe zxxZbmFW|#{G0#t8Kikaxm^b&u8Zi*yC;Kx$<^g-p6l;@0@z}0dU@P%0fNT?E!rByQ z|5x~|8t(&Sd++D8pZ}cp zzRzht^EvJBd`|n>k=hY5At@0xAL1H+rj&{mI~7*YQx><6BCAnH+gcn?VCj1=h=tIa zxTRKsb{}ZZM4+_<@l|09*#P9Fr)bB&ev*qTbjBe1YgsQO zjagnc`0raE{p;XI@BR6?ZR*?arak>(^gj>y_ARccsdZLq?s~1kSy82JL{0F_sd8>B ztFEfb&2evW<+xbm`=D`IZT*G~&1J5N#zs`DvfUnN$|(|kQ$wvFiqu|Jv&q@$Ht})Q zyP90tuEyC5gqc`qdS33V>|C5n>MN?8RVZw%Z{X^}(!BKC!V*>qRYNHht1B9D-r#iB zA!~HHr`J^0*WKcn-dI<43qBlVegFP)8Y=3l>bKN<#aT6690djgrDtgvj?e<%{27+z z!9>7koA98&s+?|TrCZxnvBjCCHP<&aX!UiqHFZv{rctY_cWaFm8=c8vez|W;S%Y(H z6D%&p2jySyuI30ZaOA%5KWh&ef5Y0sX;H_W*%kkiY4~4g50!#{7P`7WGl%gM{ENpo z&{y+k^!J%8@xQdV@T)mpTW>1b^+M0k&o8|2?*9CLIKJ+#kH>%f_<{%aJ(u|9U!9G9 zwrj$J>y)%v552M`uI06cSMG89e*gSu-+C|VyT`uuvHSeDYL5NqE4S?!cWlq=X>m`V zzWzd1{Ze=Pv+EB(^WL_L9l!0Gf5*y$vtHkFSiSD#t9P}&@p^CUUw-x7UAINORe6U! zGjr2*KN|f|_LOAD@~kbFS2@N^bdGLZz3tx7KW;nzX7=L^Sr2{t)xM_>ere;f-zqi{pdEc=~({8Gbxvu!Wk~xQ8dLchC?guN*KD%_? z?ze8Q&aVH9`|A(<$+N$*zWt=fzVi)BOy?{9OZ!S|`(L^Eoxi=l)$?cF?I~8Q!)Z8* z@MUT{j(s>z;5d)3q~F44LAb{7nmtz2^Nf$M<{riuu6%tti5zQuD%Ro5)kJ9tI0|s! z`jC!tI+lTSY`@dXadd(PdByAk`d?7{Yb&AvT8%A&7Gs5WQ_)@aN=nZ^n z7=4V+C}l+$y@7W~LGl{Udj(D@@TEuwF6$`=!|=6wJ>{J+yh^X*!HS*?K5o65awrUc zLgMhZAeHone^bte(eIP=*t3MtOR`jH54{m_C-qLnxI^eAU20J%f8KX+N-frNgAeaT zIHi{8c>wRy>#3!oxV~oreL<*PU#_HwT|@NAkhqj%@K4orT%5Us=mb9*qplof#r>!C=cVR(TKbAutduQBM2>k##FqbZsDUxP`v&(vlr&M43;N~r{m z`*Wi|uNN6Ocnq9mdkR=`N-gIzl0mQ6LhF1Z8Q!1gJ5p+uo(I-pSq`h+bsuhpyOv(+K_LI+og@^PzT`8<|<3 ztydU-kL++(=6Jd)lF^^X@52#he`DW~7AL5$JpRW<=&$MhjNj3U>!)NTGDALB_C?k| zOWJ`vvU9jTtpBSx!u99t9a376o>PI$jm#|n?a1=4kCz>CWj8K8U0aIuD^o(KG~oB%*n22cpjoBmhV6@T>gOGoY|}A zcw!Nn%<^xEEI-@mRW1v77ATU@pXJ{MBusv$B+Ha~P^kor%;?WuORgZlT<@Ut>Ny#9 zF#5Cne?XgI59xotSLQUMKx8z`{{}spm327Dev&$nh*yCS%3U`HGx2ii>Z3eR;@JwSuP>YMeNjn{Z{GSMsDXfV~yWOsdAuPMAnn3IWK z`5qcfbu-zzy?RZ#U`9jfm46AXnL0LBB zr^vdw|Bb+mda$b~)%Qq%{lM~#2Qmgd%P$6Q@~7+}vQ1@rO_^^-QI0?#N`*yfL^|Lj zt~bE(Z+HS|=o``q+}IC>&nmAeJ=(#v= z$JxLSoA3vNxV6`WcLech^L+i93ID`I|9B7|W46OLP54n0{q6|%+#jTmH}iQ0${X{@ zn14K;mxJ^ZPMY{1N7-06jP;D|^BWVryl>!M3c?{kZ;Ti83eRb2w+UwYg(iAC%AA;f zE%K4*+khMT82U4PR|NXwU^2qGs{G!>2St&X&t;TGkZZ{^@iFWGQp>85?Qi*ZkPrW_ zLq@V3G~u}h3O=U&;Kbb~{0BjLd%FpL(S*Ne!e0sE31&S{nDBBF{aYjJubTXoz&4Rd z&5b~x5BtdLjz#*#V!`UOFarGwG)g;9?$w!Fc7mSw9uiMHB!wbVtAhAc&4kyOaFZR> zx*(p!u_Tkq_XJW;YoZRaIJG6He+d3S1pW6!z+Z@f|11K29>R@yw(9zD{9AdCBkhoE ziXUr%iQd4O-ek8B`Ytdr;$M!JrC5KAlYyJ!(0ba0JD5^W)_;SNQXd0TH1O$zF->tM z+U5Yih{X>6YkStV*DE>T*3nXwh1pc(f>1W>Br0Zd5LC!#+JGKDP$x z196VHj#_7;?>5mlz+c97MZ!G4qP7S5jBS?tIp zIMe?Qa!0t2i24M$G4G&jrd9n zZ(YjpBCU*Hf0f}}HFCp;}l zeDUXO$lg-Xuo-X6_ucE;zi`ToFavJeTV5Kt$&aZSe zXRqSOm?eTFm!o8sIN@hNh}hXU<>l$;8~#pW@ussDo@B9laZyEOwX>wY-gSGE8(*n< zD&5U4Ct{NGGJg4r`O#Qik$1z59H?c&5Td%Mz5(w~OY%6Z^YU>L0e&L~c;Sjg#ka4_ zrOoaQwfu!FhjbawiZYk0sZ9T%F}t!dtf`^C(%Fd64a3>h?wT#oDXf9P28o&&msR3> z$*T>v;P<&=<^qa%)``Rxekht89yS`7ho*H@Yht+=S13N&l3WLcb8EY@XbU`Lp6|e29x^ zq--(Y1SGyImX4zAkecp2{t7^sfQ%!GqKdYn@JfSrLrOBJt&Nkc8QkRW>y=z-}UtC|jRmwP91)eZ2max#Ykg9}7Qam#&(XY_OBQ~zm}N;#<5~j$FS1;lj+*i%%QVyqEvXqX z8=5wfB?YxNEvdQUA@Q52%;l#_#nGbv8x+$;eSx@HIt6j~-(v9`Sjokau1r=`Yzmcl z@yg?NE?PzLPA-lT#ku3~O^?Fr-7(J9ND5R_a>vz>!dlHG9+<*6Gn9@UnzE5?G*_9; z*=*fzlY5mrunDAbN*)uSgzY*Tp0PVA1T;ViniIjf4nA8 zUCffdjy`j*;o&RT3R@Pay9T_ID)3Wy#&Oh7+7PUCO~Q}RjFulx!dQ&TJCmHj$_JCc zO|~euNt=R|)JfIBO5r5;EK(lV1u13Y+<8V#3~4SN)mv`oR%vxSf>!^Pjj>&MMcRbN z$Xj8hLAMH?fU#AS$GQ5d!Vkpo0?U%g)t`te#$6#6-_(j=qP&}#b>TI1y)d!9oCn1DjlYi%5QFsPA=v5X<&otq;`Z3rgM15m(dY^Q9nSg`(+%z z8nAu0AEjT|Cwxn(zN`D?4`_v7)FG}ORZyin&}(ZtPU}!%I=SAc4uP=O@A0AESavW&In>$KsQa zCCyVVhG_uX>n%bHX?NRbE}$d-pW@=xh^>?d{sdM5WMIo<=UZFbVjl3T3JlDFXxnJ# z9e}7p6Wdd|Ik*+(u>I_sO8mT@8Pjht54TThaJor+kqgWs|&X!=oh+;|66BT7* zZ0y|Fo6!_A2E<%kGSLAyk3w~9usT`+DR+!brX>?|Kp!_LK!8_b*=W}3uZz>rI@V@t z9XBVy8gIR(g8?4magPOYMxP}U$14+*0tlWE>}iR@%Pli!K?BeZ8mTMOTodGP0f$MZ zDq61{Q4$&wt~CvRa&Y)~!Zj5wvnH;IS{r*81~bJ*v!HTftg;5(rp^(yX`He2q*xHM zX7D{EOQUg1vd)IvbVez$a1|4#PE~^J)0jQer<=su@IHu%f}O83F~=y8l=I>edz)1m z8aoD8QsXOXB|AGu|5sC2S=&_Q%*nwPj6&{G&^>t5f|Z&1@wIpIYIXL6)MP!W+ccw? ztTt)tm~0^GXidFO(+N_N($u#oJI33nHZN6UwV0dJu2a9F<#20``jw@EDoq`=SG_M= z9mW0BQS*#)0MT(RF-_g}Yjq4r0Awd;s2&YRcJg&#4zM#y@QQz9GEx1$tp_0udWUoj} z!(Vn@E(p_irX(e$BqgUzOR>bjtLzzy^CRI$qMq6~0mKPZ-%#NiPYzk{#R9OiXl##K z@?`WkM_Ee$N!e@JqwcnT%l1@MSIqwz{f*cs;{I{W<70P?>x}>Uc)gRRXM90b`bx(3 zD(iK6z3=$i?P!-Y;(~km$~5A6;?bh9KFjJo>OU#yzk06)@41kg>Kbb{)!`?bmDLpu zY zUY(@iDuoO?5K_1H;1U!_XjhI7@A47<|9V6pq4gvwYeYsC@ZJ>YNy=`(-QsIDWc*c& zvR-8PzX>Vu{WIv5Lx77&sTUbp&gx&#NlwaPzy+k#iHs~gq}(l=kSVwivYa4gvk5v1 zXpYdC-wtC;;)SIoP+mgoJn@YjGO`@Pt%|8{40AkgG=Se2}H5p>AN>LBGV zk&zXFf^96=i0+Ka?kE}w+K@Fv*N6$%h6E^D!Xe>b7>C&*R;?%X;yWmQBJ~AJwwYN9 z2L(FuxX-7#F6#&4B~Phd*q!IDC51hawbVf|EFOHzK1b zt3Z)a6fBF0hK!SMWGwynrEUR6;=^a*NcF&tw$Bz8D$0BI_gGuhyp4^ z9TjL4>=SiHM=@&V4|f_Viu*umvmrhL$H9usQpezJlJ~B z3S)w;(Q1+a9rz;_p3}(qL5#Z9hrN}NM7t(z7OkOOA2c$G#z-+dWE9;MIuPaw<8td9 z9B>aNG&0c#6GHUaEzl4-=)E`n3ne}y3>Od1u^}r5 z&Nk))$*`NH3KbL1_dc8~)i{~Pr8EebEcb=sk4t<hjNHuUeTip>(PMGs zq~_sd=EL?vQj2ji;|6`$yqIdxX&gc>7&Yhn)qhSY@b{~&Re2TyYYNO zQoo6lnNO2|N&OCTGar}4zZXW|A#r{%YNqd$_?a+zrbAK(aWdJ#h%4S>nsIR-4akZk zL{FYqNO+_diVq2xtaw6crnhdD_)O$xJr7HKVHkc^;$imZXRn;B>v1yi;h}J{K7o_T zp4RgMChOB-_}?V{EOL___+cw2E8E$O&l9k?h=<@htY6qiqW?7rO#0YHOFsV=hG$4T z%r61^hxx@==WX20%zv@Y2hUhT@r@D>^NZ~%iC-T^|1*g%3&rsiSR~s$$j0N~F(dKj zNF-jLOm+xd@9e;NZItwamQT!YB>FVqBhj;+%=Epeb1KI%5`7hL6TPvnQhNk?o=+xv QV_wj~2=s?S>9x}T2II|eIsgCw literal 0 HcmV?d00001 diff --git a/Refresher.Core/Resources/patchwork.sprx b/Refresher.Core/Resources/patchwork.sprx index 7a3e7738f0dd80eb04ddd55d330f451820629432..95e2991af6bbd554186572fbd37f821151f41870 100644 GIT binary patch literal 16528 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 literal 15488 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 From 282bcb7ea78e6d57ed469d4421bda00ff8b8d606 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 18 Jun 2025 17:55:15 -0400 Subject: [PATCH 4/6] Fix RPCS3 pipeline uploading elf instead of bin --- Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs | 2 +- Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs index cfc0018..b517a0f 100644 --- a/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs +++ b/Refresher.Core/Pipelines/Lbp/LbpRPCS3PatchPipeline.cs @@ -35,6 +35,6 @@ public class LbpRPCS3PatchPipeline : Pipeline typeof(BackupGameEbootBeforeReplaceStep), typeof(UploadPatchworkSprxStep), typeof(UploadPatchworkConfigurationStep), - typeof(UploadGameEbootElfStep), // upload EBOOT.elf for now + typeof(UploadGameEbootStep), ]; } \ No newline at end of file diff --git a/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs index 5323ceb..fb37f4e 100644 --- a/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs +++ b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs @@ -37,7 +37,7 @@ private static void WriteStruct(Stream stream, T @struct) where T : struct Marshal.Copy(ptr, buffer, 0, size); Marshal.FreeHGlobal(ptr); - stream.Write(buffer, 0, size); + stream.Write(buffer); } public override float Progress { get; protected set; } From 576e55eb320f51f9476ff0a74fd9739022eff79c Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 18 Jun 2025 17:58:33 -0400 Subject: [PATCH 5/6] Fix form not registering RPCS3 folder when value is previously filled --- Refresher/UI/PipelineForm.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Refresher/UI/PipelineForm.cs b/Refresher/UI/PipelineForm.cs index 296ad18..2aff2b7 100644 --- a/Refresher/UI/PipelineForm.cs +++ b/Refresher/UI/PipelineForm.cs @@ -83,6 +83,10 @@ namespace Refresher.UI; } State.Log += this.OnLog; + this.UpdateFormState(); + + if(this._shouldTriggerOnConnect) + this.OnConnect(this, EventArgs.Empty); } private void UpdateFormState() @@ -112,6 +116,8 @@ private void UpdateFormState() this._button.Text = this._controller.MainButtonText; } + private bool _shouldTriggerOnConnect = false; + private void InitializePipeline() { this._pipeline = new TPipeline(); @@ -140,7 +146,10 @@ private void InitializePipeline() case StepInputType.Directory: row = AddField(input, value); if (input.ShouldCauseGameDownloadWhenChanged) + { (row.Cells[1].Control as FilePicker)!.FilePathChanged += this.OnConnect; + this._shouldTriggerOnConnect = value != null; + } break; case StepInputType.ConsoleIp: row = AddField(input, value, this._connectButton = new Button(this.OnConnect) { Text = "Connect" }); From 13bdbeaf3a4aebd92fe0e7e9db296d336aa57cc1 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 18 Jun 2025 18:03:11 -0400 Subject: [PATCH 6/6] Credit jjolano's make_fself --- README.md | 9 ++++++++- Refresher.Core/Native/Elf/Elf64Ehdr.cs | 6 +++++- Refresher.Core/Native/Elf/Elf64Phdr.cs | 8 ++++++-- Refresher.Core/Native/Sce/AppInfo.cs | 6 +++++- Refresher.Core/Native/Sce/ControlInfo.cs | 6 +++++- Refresher.Core/Native/Sce/SceHeader.cs | 6 +++++- Refresher.Core/Native/Sce/SceVersionData.cs | 6 +++++- Refresher.Core/Native/Sce/SceVersionInfo.cs | 6 +++++- Refresher.Core/Native/Sce/SegmentInfo.cs | 6 +++++- Refresher.Core/Native/Sce/SelfHeader.cs | 6 +++++- .../Pipelines/Steps/FakeEncryptGameEbootStep.cs | 10 +++++++--- 11 files changed, 61 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fdc67b9..a94d324 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # Refresher A utility for patching LittleBigPlanet games to custom servers. +## Downloading + +You can download Refresher for your platform by visiting our Releases. + +https://github.com/LittleBigRefresh/Refresher/releases/latest + ## Credits/Libraries we use ### UI - Eto.Forms @@ -10,9 +16,10 @@ A utility for patching LittleBigPlanet games to custom servers. - ELFSharp - SPRXPatcher - SCEToolSharp +- Our C# port of jjolano's make_fself ### Connections -- WebMAN (technically?) +- WebMAN (technically) - FluentFTP ### Infrastructure diff --git a/Refresher.Core/Native/Elf/Elf64Ehdr.cs b/Refresher.Core/Native/Elf/Elf64Ehdr.cs index f065835..aefbd60 100644 --- a/Refresher.Core/Native/Elf/Elf64Ehdr.cs +++ b/Refresher.Core/Native/Elf/Elf64Ehdr.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Elf; diff --git a/Refresher.Core/Native/Elf/Elf64Phdr.cs b/Refresher.Core/Native/Elf/Elf64Phdr.cs index 696be88..aca1feb 100644 --- a/Refresher.Core/Native/Elf/Elf64Phdr.cs +++ b/Refresher.Core/Native/Elf/Elf64Phdr.cs @@ -1,9 +1,13 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Elf; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct Elf64_Phdr +public struct Elf64Phdr { public uint p_type; public uint p_flags; diff --git a/Refresher.Core/Native/Sce/AppInfo.cs b/Refresher.Core/Native/Sce/AppInfo.cs index e85af3d..167941c 100644 --- a/Refresher.Core/Native/Sce/AppInfo.cs +++ b/Refresher.Core/Native/Sce/AppInfo.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/ControlInfo.cs b/Refresher.Core/Native/Sce/ControlInfo.cs index 39c9645..b51b458 100644 --- a/Refresher.Core/Native/Sce/ControlInfo.cs +++ b/Refresher.Core/Native/Sce/ControlInfo.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/SceHeader.cs b/Refresher.Core/Native/Sce/SceHeader.cs index c8645cd..6568514 100644 --- a/Refresher.Core/Native/Sce/SceHeader.cs +++ b/Refresher.Core/Native/Sce/SceHeader.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/SceVersionData.cs b/Refresher.Core/Native/Sce/SceVersionData.cs index 3eed0c9..d49e056 100644 --- a/Refresher.Core/Native/Sce/SceVersionData.cs +++ b/Refresher.Core/Native/Sce/SceVersionData.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/SceVersionInfo.cs b/Refresher.Core/Native/Sce/SceVersionInfo.cs index 417ffc2..324a572 100644 --- a/Refresher.Core/Native/Sce/SceVersionInfo.cs +++ b/Refresher.Core/Native/Sce/SceVersionInfo.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/SegmentInfo.cs b/Refresher.Core/Native/Sce/SegmentInfo.cs index e654475..6d11668 100644 --- a/Refresher.Core/Native/Sce/SegmentInfo.cs +++ b/Refresher.Core/Native/Sce/SegmentInfo.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Native/Sce/SelfHeader.cs b/Refresher.Core/Native/Sce/SelfHeader.cs index 7ef51d3..d768553 100644 --- a/Refresher.Core/Native/Sce/SelfHeader.cs +++ b/Refresher.Core/Native/Sce/SelfHeader.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; namespace Refresher.Core.Native.Sce; diff --git a/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs index fb37f4e..eb8536c 100644 --- a/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs +++ b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs @@ -1,4 +1,8 @@ -using System.Runtime.InteropServices; +// This file contains modified & ported code from jjolano's make_fself C project. +// make_fself is licensed under GPL-3.0. +// Find it here: https://github.com/jjolano/make_fself + +using System.Runtime.InteropServices; using System.Security.Cryptography; using Refresher.Core.Native.Elf; using Refresher.Core.Native.Sce; @@ -81,8 +85,8 @@ public override Task ExecuteAsync(CancellationToken cancellationToken = default) for (int i = 0; i < phdrCount; i++) { - long offset = (long)Swap64(elfHeader.e_phoff) + i * Marshal.SizeOf(); - Elf64_Phdr elfPhdr = MemoryMarshal.Read(elfData.AsSpan((int)offset)); + long offset = (long)Swap64(elfHeader.e_phoff) + i * Marshal.SizeOf(); + Elf64Phdr elfPhdr = MemoryMarshal.Read(elfData.AsSpan((int)offset)); segments[i].offset = Swap64(Swap64(elfPhdr.p_offset) + Swap64(sceHeader.head_len)); segments[i].size = elfPhdr.p_filesz;