diff --git a/.idea/.idea.Refresher/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.Refresher/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/.idea.Refresher/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Refresher.sln.DotSettings b/Refresher.sln.DotSettings
index 495d1ea..7357d64 100644
--- a/Refresher.sln.DotSettings
+++ b/Refresher.sln.DotSettings
@@ -8,7 +8,9 @@
UseExplicitType
UseExplicitType
LBP
+ PSP
True
+ True
True
True
True
diff --git a/Refresher/CLI/CommandLine.cs b/Refresher/CLI/CommandLine.cs
index fbc5cf6..151c307 100644
--- a/Refresher/CLI/CommandLine.cs
+++ b/Refresher/CLI/CommandLine.cs
@@ -64,8 +64,8 @@ void DeleteTempFile(string? s)
}
//Create a new patcher with the temp file stream
- Patcher patcher = new(mappedFile.CreateViewStream());
- List messages = patcher.Verify(options.ServerUrl, options.Digest ?? false).ToList();
+ EbootPatcher ebootPatcher = new(mappedFile.CreateViewStream());
+ List messages = ebootPatcher.Verify(options.ServerUrl, options.Digest ?? false).ToList();
//Write the messages to the console
foreach (Message message in messages) Console.WriteLine($"{message.Level}: {message.Content}");
@@ -103,7 +103,7 @@ void DeleteTempFile(string? s)
try
{
//Patch the file
- patcher.Patch(options.ServerUrl, options.Digest ?? false);
+ ebootPatcher.Patch(options.ServerUrl, options.Digest ?? false);
//TODO: warn the user if they are overwriting the file
File.Move(tempFile, options.OutputFile, true);
diff --git a/Refresher/Patching/Patcher.cs b/Refresher/Patching/EbootPatcher.cs
similarity index 99%
rename from Refresher/Patching/Patcher.cs
rename to Refresher/Patching/EbootPatcher.cs
index b93d17c..dfa4a56 100644
--- a/Refresher/Patching/Patcher.cs
+++ b/Refresher/Patching/EbootPatcher.cs
@@ -8,11 +8,11 @@
namespace Refresher.Patching;
-public partial class Patcher
+public partial class EbootPatcher : IPatcher
{
private readonly Lazy> _targets;
- public Patcher(Stream stream)
+ public EbootPatcher(Stream stream)
{
if (!stream.CanRead || !stream.CanSeek || !stream.CanWrite)
throw new ArgumentException("Stream must be readable, seekable and writable", nameof(stream));
diff --git a/Refresher/Patching/IPatcher.cs b/Refresher/Patching/IPatcher.cs
new file mode 100644
index 0000000..6efd914
--- /dev/null
+++ b/Refresher/Patching/IPatcher.cs
@@ -0,0 +1,10 @@
+using Refresher.Verification;
+
+namespace Refresher.Patching;
+
+public interface IPatcher
+{
+ public List Verify(string url, bool patchDigest);
+
+ public void Patch(string url, bool patchDigest);
+}
\ No newline at end of file
diff --git a/Refresher/Patching/PSP/PSPPluginListEntry.cs b/Refresher/Patching/PSP/PSPPluginListEntry.cs
new file mode 100644
index 0000000..e2dcd7c
--- /dev/null
+++ b/Refresher/Patching/PSP/PSPPluginListEntry.cs
@@ -0,0 +1,13 @@
+namespace Refresher.Patching.PSP;
+
+public class PSPPluginListEntry
+{
+ public string Path;
+ public int? Type;
+
+ public PSPPluginListEntry(string path, int? type = null)
+ {
+ this.Path = path;
+ this.Type = type;
+ }
+}
\ No newline at end of file
diff --git a/Refresher/Patching/PSP/PSPPluginListParser.cs b/Refresher/Patching/PSP/PSPPluginListParser.cs
new file mode 100644
index 0000000..0f6f5ca
--- /dev/null
+++ b/Refresher/Patching/PSP/PSPPluginListParser.cs
@@ -0,0 +1,44 @@
+namespace Refresher.Patching.PSP;
+
+public static class PSPPluginListParser
+{
+ public static List Parse(TextReader reader)
+ {
+ List list = new();
+
+ while (reader.ReadLine() is { } line)
+ {
+ //Skip blank lines
+ if (string.IsNullOrWhiteSpace(line)) continue;
+
+ string[] parts = line.Split(" ");
+
+ PSPPluginListEntry entry = new(parts[0]);
+
+ if (parts.Length > 1)
+ {
+ entry.Type = int.Parse(parts[1]);
+ }
+
+ list.Add(entry);
+ }
+
+ return list;
+ }
+
+ public static void Write(List list, TextWriter writer)
+ {
+ foreach (PSPPluginListEntry entry in list)
+ {
+ writer.Write(entry.Path);
+ if (entry.Type.HasValue)
+ {
+ writer.Write(' ');
+ writer.Write(entry.Type.Value);
+ }
+ writer.Write('\n');
+ }
+
+ writer.Flush();
+ }
+}
\ No newline at end of file
diff --git a/Refresher/Patching/PSPPatcher.cs b/Refresher/Patching/PSPPatcher.cs
new file mode 100644
index 0000000..cf93df1
--- /dev/null
+++ b/Refresher/Patching/PSPPatcher.cs
@@ -0,0 +1,118 @@
+using System.Reflection;
+using Refresher.Patching.PSP;
+using Refresher.Verification;
+
+namespace Refresher.Patching;
+
+public class PSPPatcher : IPatcher
+{
+ public string? PSPDrivePath;
+
+ private readonly Stream _allefresher;
+
+ public PSPPatcher()
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ this._allefresher = assembly.GetManifestResourceStream("Refresher.Resources.Allefresher.prx")!;
+ }
+
+ public List Verify(string url, bool patchDigest)
+ {
+ List messages = new();
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out _))
+ messages.Add(new Message(MessageLevel.Error, "URL failed to parse!"));
+
+ if (string.IsNullOrEmpty(this.PSPDrivePath) || !Directory.Exists(this.PSPDrivePath))
+ messages.Add(new Message(MessageLevel.Error, "Invalid PSP Drive path!"));
+
+ return messages;
+ }
+
+ public void Patch(string url, bool patchDigest)
+ {
+ Uri uri = new(url);
+
+ string domain = uri.Host;
+ string format = $"{uri.Scheme}://%s:{uri.Port}{uri.AbsolutePath}%s";
+
+ string pluginsDir = Path.Combine(this.PSPDrivePath!, "SEPLUGINS");
+
+ //If the plugins directory does not exist
+ if (!Directory.Exists(pluginsDir))
+ {
+ //Create it
+ Directory.CreateDirectory(pluginsDir);
+ }
+
+ string domainPath = Path.Combine(pluginsDir, "Allefresher_domain.txt");
+ string formatPath = Path.Combine(pluginsDir, "Allefresher_format.txt");
+
+ //Delete the existing domain and format configuration files
+ File.Delete(domainPath);
+ File.Delete(formatPath);
+
+ //Write the new domain and format configuration
+ File.WriteAllText(domainPath, domain);
+ File.WriteAllText(formatPath, format);
+
+ //Match for all files called "game.txt" in the plugins directory
+ //NOTE: we do this because the PSP filesystem is case insensitive, and the .NET STL is case sensitive on linux
+ List possibleMatches = Directory.EnumerateFiles(pluginsDir, "game.txt", new EnumerationOptions
+ {
+ MatchCasing = MatchCasing.CaseInsensitive,
+ }).ToList();
+
+ FileStream gamePluginsFileStream;
+
+ const string allefresherPath = "ms0:/SEPLUGINS/Allefresher.prx";
+
+ List entries;
+ if (possibleMatches.Any())
+ {
+ //Open the first match
+ FileStream stream = File.OpenRead(possibleMatches[0]);
+
+ //Read out the matches
+ entries = PSPPluginListParser.Parse(new StreamReader(stream));
+
+ //If Allefresher is not in the list,
+ if (!entries.Any(entry => entry.Path.Contains("Allefresher.prx", StringComparison.InvariantCultureIgnoreCase)))
+ {
+ //Add Allefresher to the game plugin list
+ entries.Add(new PSPPluginListEntry(allefresherPath, 1));
+ }
+
+ //Dispose the read stream
+ stream.Dispose();
+
+ //Open a new write stream to the game.txt file
+ gamePluginsFileStream = File.Open(possibleMatches[0], FileMode.Truncate);
+ }
+ else
+ {
+ //Create a new list, with the only entry being Allefresher
+ entries = new List
+ {
+ new(allefresherPath, 1),
+ };
+
+ //Create a new game.txt file
+ gamePluginsFileStream = File.Open(Path.Combine(pluginsDir, "game.txt"), FileMode.CreateNew);
+ }
+
+ //Write the plugin list to the file
+ PSPPluginListParser.Write(entries, new StreamWriter(gamePluginsFileStream));
+
+ //Flush and dispose the stream
+ gamePluginsFileStream.Flush();
+ gamePluginsFileStream.Dispose();
+
+ using FileStream allefresherOutput = File.Open(Path.Combine(pluginsDir, "Allefresher.prx"), FileMode.Create);
+
+ this._allefresher.Seek(0, SeekOrigin.Begin);
+
+ //Copy the Allefresher embedded resource to the output file
+ this._allefresher.CopyTo(allefresherOutput);
+ }
+}
\ No newline at end of file
diff --git a/Refresher/Refresher.csproj b/Refresher/Refresher.csproj
index e6c6328..9859299 100644
--- a/Refresher/Refresher.csproj
+++ b/Refresher/Refresher.csproj
@@ -24,6 +24,7 @@
+
diff --git a/Refresher/Resources/Allefresher.prx b/Refresher/Resources/Allefresher.prx
new file mode 100644
index 0000000..e12e695
Binary files /dev/null and b/Refresher/Resources/Allefresher.prx differ
diff --git a/Refresher/UI/FilePatchForm.cs b/Refresher/UI/FilePatchForm.cs
index 910556a..b3c0d6a 100644
--- a/Refresher/UI/FilePatchForm.cs
+++ b/Refresher/UI/FilePatchForm.cs
@@ -5,7 +5,7 @@
namespace Refresher.UI;
-public class FilePatchForm : PatchForm
+public class FilePatchForm : PatchForm
{
private readonly FilePicker _inputFileField;
private readonly FilePicker _outputFileField;
@@ -92,7 +92,7 @@ private void FileUpdated(object? sender, EventArgs ev)
{
this._mappedFile?.Dispose();
this._mappedFile = MemoryMappedFile.CreateFromFile(this._tempFile, FileMode.Open, null, 0, MemoryMappedFileAccess.ReadWrite);
- this.Patcher = new Patcher(this._mappedFile.CreateViewStream());
+ this.Patcher = new EbootPatcher(this._mappedFile.CreateViewStream());
}
catch(Exception e)
{
diff --git a/Refresher/UI/IntegratedPatchForm.cs b/Refresher/UI/IntegratedPatchForm.cs
index 49011ae..5c0c6f3 100644
--- a/Refresher/UI/IntegratedPatchForm.cs
+++ b/Refresher/UI/IntegratedPatchForm.cs
@@ -10,7 +10,7 @@
namespace Refresher.UI;
-public abstract class IntegratedPatchForm : PatchForm
+public abstract class IntegratedPatchForm : PatchForm
{
private readonly DropDown _gameDropdown;
private readonly TextBox? _outputField;
@@ -29,8 +29,10 @@ protected IntegratedPatchForm(string subtitle) : base(subtitle)
{
this.AddRemoteField(),
AddField("Game to patch", out this._gameDropdown, forceHeight: 56),
- AddField("Server URL", out this.UrlField),
+ AddField("Server URL", out this.UrlField),
};
+
+ this._gameDropdown.SelectedValueChanged += this.GameChanged;
if (!this.ShouldReplaceExecutable)
{
@@ -40,8 +42,6 @@ protected IntegratedPatchForm(string subtitle) : base(subtitle)
this.FormPanel = new TableLayout(rows);
- this._gameDropdown.SelectedValueChanged += this.GameChanged;
-
this.InitializePatcher();
}
@@ -148,7 +148,7 @@ protected virtual void GameChanged(object? sender, EventArgs ev)
this.LogMessage($"The EBOOT has been successfully decrypted. It's stored at {this._tempFile}.");
- this.Patcher = new Patcher(File.Open(this._tempFile, FileMode.Open, FileAccess.ReadWrite));
+ this.Patcher = new EbootPatcher(File.Open(this._tempFile, FileMode.Open, FileAccess.ReadWrite));
this.Reverify(sender, ev);
}
@@ -191,7 +191,7 @@ public override void CompletePatch(object? sender, EventArgs e) {
Thread.Sleep(1000); // TODO: don't. block. the. main. thread.
this.Accessor.UploadFile(fileToUpload, destination);
- MessageBox.Show($"Successfully patched EBOOT! It was saved to '{destination}'.");
+ MessageBox.Show(this, $"Successfully patched EBOOT! It was saved to '{destination}'.", "Success!");
// Re-initialize patcher so we can patch with the same parameters again
// Probably slow but prevents crash
@@ -228,6 +228,12 @@ protected virtual void RevertToOriginalExecutable(object? sender, EventArgs e)
}
protected abstract TableRow AddRemoteField();
+ ///
+ /// Whether the target platform requires the executable to be resigned or not
+ ///
protected abstract bool NeedsResign { get; }
+ ///
+ /// Whether the target platform requires the executable to be named EBOOT.BIN
+ ///
protected abstract bool ShouldReplaceExecutable { get; }
}
\ No newline at end of file
diff --git a/Refresher/UI/MainForm.cs b/Refresher/UI/MainForm.cs
index fae01b0..b4c2a33 100644
--- a/Refresher/UI/MainForm.cs
+++ b/Refresher/UI/MainForm.cs
@@ -16,7 +16,8 @@ public class MainForm : RefresherForm
new Label { Text = "Welcome to Refresher! Please pick a patching method to continue." },
new Button((_, _) => this.ShowChild()) { Text = "File Patch (using a .ELF)" },
new Button((_, _) => this.ShowChild()) { Text = "RPCS3 Patch" },
- new Button((_, _) => this.ShowChild()) { Text = "PS3 Patch" }
+ new Button((_, _) => this.ShowChild()) { Text = "PS3 Patch" },
+ new Button((_, _) => this.ShowChild()) { Text = "PSP Setup" }
);
layout.Spacing = 5;
diff --git a/Refresher/UI/PSPSetupForm.cs b/Refresher/UI/PSPSetupForm.cs
new file mode 100644
index 0000000..941b141
--- /dev/null
+++ b/Refresher/UI/PSPSetupForm.cs
@@ -0,0 +1,74 @@
+using Eto.Forms;
+using Refresher.Patching;
+
+namespace Refresher.UI;
+
+public class PSPSetupForm : PatchForm
+{
+ private DropDown _pspDrive;
+
+ protected override TableLayout FormPanel { get; }
+
+ public PSPSetupForm() : base("PSP Setup")
+ {
+ this.Patcher = new PSPPatcher();
+
+ this.FormPanel = new TableLayout(new List
+ {
+ AddField("PSP Drive", out this._pspDrive),
+ AddField("Server URL", out this.UrlField),
+ });
+
+ this._pspDrive.SelectedKeyChanged += this.Reverify;
+ this._pspDrive.SelectedKeyChanged += this.SelectedDriveChange;
+
+ DriveInfo[] drives = DriveInfo.GetDrives();
+
+ foreach (DriveInfo drive in drives)
+ {
+ try
+ {
+ Console.WriteLine($"Checking drive {drive.Name}...");
+
+ //If theres no PSP folder,
+ if (!Directory.Exists(Path.Combine(drive.RootDirectory.FullName, "PSP")))
+ {
+ Console.WriteLine($"Drive {drive.Name} has no PSP folder, ignoring...");
+
+ //Skip this drive
+ continue;
+ }
+ }
+ catch(Exception ex)
+ {
+ Console.WriteLine($"Checking drive failed due to exception, see below");
+ Console.WriteLine(ex);
+
+ //If we fail to check dir info, its probably not mounted in a safe/accessible way
+ continue;
+ }
+
+ //If the drive has a PSP folder, add it to the list
+ this._pspDrive.Items.Add(drive.Name, drive.RootDirectory.FullName);
+ }
+
+ // If there are any items in the dropdown...
+ if (this._pspDrive.Items.Count > 0)
+ {
+ // ...then select the first item.
+ this._pspDrive.SelectedIndex = 0;
+ }
+
+ this.InitializePatcher();
+ }
+
+ private void SelectedDriveChange(object? sender, EventArgs e)
+ {
+ this.Patcher!.PSPDrivePath = this._pspDrive.SelectedKey;
+ }
+
+ public override void CompletePatch(object? sender, EventArgs e)
+ {
+ MessageBox.Show(this, "Setup complete! *Safely* eject your Memory Stick or PSP in your OS, then open the game!", "Success!");
+ }
+}
\ No newline at end of file
diff --git a/Refresher/UI/PatchForm.cs b/Refresher/UI/PatchForm.cs
index 65dd42d..c1b0555 100644
--- a/Refresher/UI/PatchForm.cs
+++ b/Refresher/UI/PatchForm.cs
@@ -11,7 +11,7 @@
namespace Refresher.UI;
-public abstract class PatchForm : RefresherForm where TPatcher : Patcher
+public abstract class PatchForm : RefresherForm where TPatcher : class, IPatcher
{
protected abstract TableLayout FormPanel { get; }