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/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..aefbd60 --- /dev/null +++ b/Refresher.Core/Native/Elf/Elf64Ehdr.cs @@ -0,0 +1,26 @@ +// 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 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..aca1feb --- /dev/null +++ b/Refresher.Core/Native/Elf/Elf64Phdr.cs @@ -0,0 +1,20 @@ +// 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 Elf64Phdr +{ + 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..167941c --- /dev/null +++ b/Refresher.Core/Native/Sce/AppInfo.cs @@ -0,0 +1,17 @@ +// 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; + +[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..b51b458 --- /dev/null +++ b/Refresher.Core/Native/Sce/ControlInfo.cs @@ -0,0 +1,23 @@ +// 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; + +[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..6568514 --- /dev/null +++ b/Refresher.Core/Native/Sce/SceHeader.cs @@ -0,0 +1,19 @@ +// 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; + +[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..d49e056 --- /dev/null +++ b/Refresher.Core/Native/Sce/SceVersionData.cs @@ -0,0 +1,22 @@ +// 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; + +[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..324a572 --- /dev/null +++ b/Refresher.Core/Native/Sce/SceVersionInfo.cs @@ -0,0 +1,16 @@ +// 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; + +[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..6d11668 --- /dev/null +++ b/Refresher.Core/Native/Sce/SegmentInfo.cs @@ -0,0 +1,18 @@ +// 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; + +[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..d768553 --- /dev/null +++ b/Refresher.Core/Native/Sce/SelfHeader.cs @@ -0,0 +1,22 @@ +// 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; + +[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 new file mode 100644 index 0000000..b517a0f --- /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 => true; + + 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 + typeof(FakeEncryptGameEbootStep), + typeof(PrintInfoForEncryptedGameEbootStep), + typeof(BackupGameEbootBeforeReplaceStep), + typeof(UploadPatchworkSprxStep), + typeof(UploadPatchworkConfigurationStep), + typeof(UploadGameEbootStep), + ]; +} \ 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.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs new file mode 100644 index 0000000..eb8536c --- /dev/null +++ b/Refresher.Core/Pipelines/Steps/FakeEncryptGameEbootStep.cs @@ -0,0 +1,152 @@ +// 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; + +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); + } + + 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(); + 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; + 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/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 4bba9c5..d37d7cc 100644 --- a/Refresher.Core/Refresher.Core.csproj +++ b/Refresher.Core/Refresher.Core.csproj @@ -5,6 +5,7 @@ enable enable embedded + true @@ -23,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 0000000..b0a9c05 Binary files /dev/null and b/Refresher.Core/Resources/patchwork-rpcs3.sprx differ diff --git a/Refresher.Core/Resources/patchwork.sprx b/Refresher.Core/Resources/patchwork.sprx index 7a3e773..95e2991 100644 Binary files a/Refresher.Core/Resources/patchwork.sprx and b/Refresher.Core/Resources/patchwork.sprx differ diff --git a/Refresher/UI/MainForm.cs b/Refresher/UI/MainForm.cs index d5404ef..e5d76bf 100644 --- a/Refresher/UI/MainForm.cs +++ b/Refresher/UI/MainForm.cs @@ -20,10 +20,14 @@ public class MainForm : RefresherForm new Label { Text = "LittleBigPlanet:" }, this.PipelineButton("Patch LBP1/2/3 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"), this.PipelineButton("Patch any PS3 game"), + #if DEBUG new Label { Text = "Debugging options:" }, this.PipelineButton("Example Pipeline"), 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" });