Skip to content

Commit 5e656c6

Browse files
authored
Merge pull request #19 from LittleBigRefresh/ps3-patch
Initial support for patching PS3 consoles
2 parents 8d420b9 + f1d38bf commit 5e656c6

File tree

10 files changed

+357
-112
lines changed

10 files changed

+357
-112
lines changed

Refresher.sln.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@
2424
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reverify/@EntryIndexedValue">True</s:Boolean>
2525
<s:Boolean x:Key="/Default/UserDictionary/Words/=RFSH/@EntryIndexedValue">True</s:Boolean>
2626
<s:Boolean x:Key="/Default/UserDictionary/Words/=RPCS/@EntryIndexedValue">True</s:Boolean>
27-
<s:Boolean x:Key="/Default/UserDictionary/Words/=USRDIR/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
27+
<s:Boolean x:Key="/Default/UserDictionary/Words/=USRDIR/@EntryIndexedValue">True</s:Boolean>
28+
<s:Boolean x:Key="/Default/UserDictionary/Words/=webman/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Net;
2+
using FluentFTP;
3+
4+
namespace Refresher.Accessors;
5+
6+
public class ConsolePatchAccessor : PatchAccessor, IDisposable
7+
{
8+
private readonly FtpClient _client;
9+
private const string BasePath = "/dev_hdd0/";
10+
11+
public ConsolePatchAccessor(string remoteIp)
12+
{
13+
this._client = new FtpClient(remoteIp, "anonymous", "");
14+
this._client.Config.LogToConsole = true;
15+
this._client.AutoConnect();
16+
}
17+
18+
private static string GetPath(string path)
19+
{
20+
if (Path.IsPathRooted(path)) return path;
21+
return BasePath + path;
22+
}
23+
24+
public override bool DirectoryExists(string path) => this._client.DirectoryExists(GetPath(path));
25+
26+
public override bool FileExists(string path) => this._client.FileExists(GetPath(path));
27+
28+
public override IEnumerable<string> GetDirectoriesInDirectory(string path) =>
29+
this._client.GetListing(GetPath(path))
30+
.Where(l => l.Type == FtpObjectType.Directory)
31+
.Select(l => l.FullName);
32+
33+
public override IEnumerable<string> GetFilesInDirectory(string path) =>
34+
this._client.GetListing(GetPath(path))
35+
.Where(l => l.Type == FtpObjectType.File)
36+
.Select(l => l.FullName);
37+
38+
public override Stream OpenRead(string path)
39+
{
40+
MemoryStream ms = new();
41+
this._client.DownloadStream(ms, GetPath(path));
42+
ms.Seek(0, SeekOrigin.Begin);
43+
return ms;
44+
45+
// technically we can use a stream directly but this uses a lot more requests
46+
// and webman doesnt like that, it tends to just slow down to a crawl after a bunch of them :(
47+
// return this._client.OpenRead(GetPath(path));
48+
}
49+
50+
public override Stream OpenWrite(string path) => this._client.OpenWrite(GetPath(path));
51+
public override void RemoveFile(string path) => this._client.DeleteFile(GetPath(path));
52+
53+
public void Dispose()
54+
{
55+
this._client.Dispose();
56+
GC.SuppressFinalize(this);
57+
}
58+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace Refresher.Accessors;
2+
3+
public class EmulatorPatchAccessor : PatchAccessor
4+
{
5+
public EmulatorPatchAccessor(string basePath)
6+
{
7+
this.BasePath = basePath;
8+
}
9+
10+
private string BasePath { get; }
11+
12+
private string GetPath(string path)
13+
{
14+
if (Path.IsPathRooted(path)) return path;
15+
return Path.Join(this.BasePath, path);
16+
}
17+
18+
public override bool DirectoryExists(string path) => Directory.Exists(this.GetPath(path));
19+
public override bool FileExists(string path) => File.Exists(this.GetPath(path));
20+
21+
public override IEnumerable<string> GetDirectoriesInDirectory(string path) => Directory.GetDirectories(this.GetPath(path));
22+
public override IEnumerable<string> GetFilesInDirectory(string path) => Directory.GetFiles(this.GetPath(path));
23+
24+
public override Stream OpenRead(string path) => File.OpenRead(this.GetPath(path));
25+
public override Stream OpenWrite(string path) => File.OpenWrite(this.GetPath(path));
26+
public override void RemoveFile(string path) => File.Delete(this.GetPath(path));
27+
28+
public override void DuplicateFile(string inPath, string outPath)
29+
=> File.Copy(this.GetPath(inPath), this.GetPath(outPath));
30+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Refresher.Accessors;
2+
3+
public abstract class PatchAccessor
4+
{
5+
public abstract bool DirectoryExists(string path);
6+
public abstract bool FileExists(string path);
7+
public abstract IEnumerable<string> GetDirectoriesInDirectory(string path);
8+
public abstract IEnumerable<string> GetFilesInDirectory(string path);
9+
public abstract Stream OpenRead(string path);
10+
public abstract Stream OpenWrite(string path);
11+
public abstract void RemoveFile(string path);
12+
13+
public string DownloadFile(string path)
14+
{
15+
string outFile = Path.GetTempFileName();
16+
17+
using FileStream outStream = File.OpenWrite(outFile);
18+
using Stream inStream = this.OpenRead(path);
19+
inStream.CopyTo(outStream);
20+
21+
return outFile;
22+
}
23+
24+
public void UploadFile(string inPath, string outPath)
25+
{
26+
using FileStream inStream = File.OpenRead(inPath);
27+
using Stream outStream = this.OpenWrite(outPath);
28+
29+
inStream.CopyTo(outStream);
30+
}
31+
32+
public virtual void DuplicateFile(string inPath, string outPath)
33+
{
34+
using Stream inStream = this.OpenRead(inPath);
35+
using Stream outStream = this.OpenWrite(outPath);
36+
37+
inStream.CopyTo(outStream);
38+
}
39+
}

Refresher/Patching/Patcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public Patcher(Stream stream)
2121

2222
this.Stream.Position = 0;
2323

24-
this._targets = new(() => FindPatchableElements(stream));
24+
this._targets = new Lazy<List<PatchTargetInfo>>(() => FindPatchableElements(stream));
2525
}
2626

2727
public Stream Stream { get; }

Refresher/Refresher.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
<PackageReference Condition="'$(TargetFramework)' != 'net7.0-windows'" Include="Eto.Platform.Gtk" Version="2.7.5" />
2222
<PackageReference Condition="'$(TargetFramework)' == 'net7.0-windows'" Include="Eto.Platform.Wpf" Version="2.7.5" />
2323
<EmbeddedResource Include="Resources\refresher.ico" LogicalName="refresher.ico" />
24+
<PackageReference Include="FluentFTP" Version="47.1.0" />
2425
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
25-
<PackageReference Include="SCEToolSharp" Version="1.0.7" />
26+
<PackageReference Include="SCEToolSharp" Version="1.0.8" />
2627
</ItemGroup>
2728

29+
2830
</Project>

Refresher/UI/ConsolePatchForm.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Eto.Forms;
2+
using Refresher.Accessors;
3+
4+
namespace Refresher.UI;
5+
6+
public class ConsolePatchForm : IntegratedPatchForm
7+
{
8+
private TextBox _remoteAddress = null!;
9+
10+
public ConsolePatchForm() : base("PS3 Patch")
11+
{
12+
this._remoteAddress.LostFocus += this.PathChanged;
13+
}
14+
15+
protected override void PathChanged(object? sender, EventArgs ev)
16+
{
17+
this.Accessor = new ConsolePatchAccessor(this._remoteAddress.Text);
18+
base.PathChanged(sender, ev);
19+
}
20+
21+
protected override TableRow AddRemoteField()
22+
{
23+
return AddField("PS3's IP", out this._remoteAddress);
24+
}
25+
26+
protected override bool NeedsResign => true;
27+
protected override bool ShouldReplaceExecutable => true;
28+
}

Refresher/UI/EmulatorPatchForm.cs

Lines changed: 13 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,17 @@
1-
using System.Diagnostics;
21
using Eto;
3-
using Eto.Drawing;
42
using Eto.Forms;
5-
using Refresher.Patching;
6-
using Refresher.UI.Items;
7-
using Refresher.Verification;
8-
using SCEToolSharp;
3+
using Refresher.Accessors;
94

105
namespace Refresher.UI;
116

12-
public class EmulatorPatchForm : PatchForm<Patcher>
7+
public class EmulatorPatchForm : IntegratedPatchForm
138
{
14-
private readonly FilePicker _folderField;
15-
private readonly DropDown _gameDropdown;
16-
private readonly TextBox _outputField;
9+
private FilePicker _folderField = null!;
1710

18-
private string _tempFile;
19-
private string _usrDir;
20-
21-
protected override TableLayout FormPanel { get; }
22-
2311
public EmulatorPatchForm() : base("RPCS3 Patch")
2412
{
25-
this.FormPanel = new TableLayout(new List<TableRow>
26-
{
27-
AddField("RPCS3 dev_hdd0 folder", out this._folderField),
28-
AddField("Game to patch", out this._gameDropdown),
29-
AddField("Server URL", out this.UrlField),
30-
AddField("Identifier (EBOOT.<value>.elf)", out this._outputField),
31-
});
32-
3313
this._folderField.FileAction = FileAction.SelectFolder;
3414
this._folderField.FilePathChanged += this.PathChanged;
35-
36-
this._outputField.PlaceholderText = "refresh";
37-
38-
this._gameDropdown.SelectedValueChanged += this.GameChanged;
3915

4016
// RPCS3 builds for Windows are portable
4117
// TODO: Cache the last used location for easier entry
@@ -50,95 +26,24 @@ public EmulatorPatchForm() : base("RPCS3 Patch")
5026
this.LogMessage("RPCS3's path has been detected automatically! You do not need to change the path.");
5127
}
5228
}
53-
54-
this.InitializePatcher();
5529
}
5630

57-
private void PathChanged(object? sender, EventArgs ev)
31+
public override void Guide(object? sender, EventArgs e)
5832
{
59-
string path = this._folderField.FilePath;
60-
this._gameDropdown.Items.Clear();
61-
62-
string gamesPath = Path.Join(path, "game");
63-
if (!Directory.Exists(gamesPath)) return;
64-
65-
string[] games = Directory.GetDirectories(Path.Join(path, "game"));
66-
67-
foreach (string gamePath in games)
68-
{
69-
string game = Path.GetFileName(gamePath);
70-
71-
// Example TitleID: BCUS98208, must be 9 chars
72-
if(game.Length != 9) continue; // Skip over profiles/save data/other garbage
73-
74-
GameItem item = new();
75-
76-
string iconPath = Path.Combine(gamePath, "ICON0.PNG");
77-
if (File.Exists(iconPath))
78-
{
79-
item.Image = new Bitmap(iconPath).WithSize(new Size(64, 64));
80-
}
81-
82-
string sfoPath = Path.Combine(gamePath, "PARAM.SFO");
83-
try
84-
{
85-
ParamSfo sfo = new(File.OpenRead(sfoPath));
86-
item.Text = $"{sfo.Table["TITLE"]} [{game}]";
87-
}
88-
catch
89-
{
90-
item.Text = game;
91-
}
92-
93-
item.TitleId = game;
94-
95-
this._gameDropdown.Items.Add(item);
96-
}
33+
this.OpenUrl("https://littlebigrefresh.github.io/Docs/patching/rpcs3");
9734
}
9835

99-
private void GameChanged(object? sender, EventArgs ev)
36+
protected override void PathChanged(object? sender, EventArgs ev)
10037
{
101-
GameItem? game = this._gameDropdown.SelectedValue as GameItem;
102-
Debug.Assert(game != null);
103-
104-
this._usrDir = Path.Combine(this._folderField.FilePath, "game", game.TitleId, "USRDIR");
105-
string ebootPath = Path.Combine(this._usrDir, "EBOOT.BIN");
106-
string rapDir = Path.Combine(this._folderField.FilePath, "home", "00000001", "exdata");
107-
108-
this.LogMessage("EBOOT Path: " + ebootPath);
109-
if (!File.Exists(ebootPath))
110-
{
111-
this.FailVerify("Could not find the EBOOT. Patching cannot continue.", clear: false);
112-
return;
113-
}
114-
115-
this._tempFile = Path.GetTempFileName();
116-
117-
LibSceToolSharp.SetRapDirectory(rapDir);
118-
LibSceToolSharp.Decrypt(ebootPath, this._tempFile);
119-
120-
this.LogMessage($"The EBOOT has been successfully decrypted. It's stored at {this._tempFile}.");
121-
122-
this.Patcher = new Patcher(File.Open(this._tempFile, FileMode.Open, FileAccess.ReadWrite));
123-
124-
this.Reverify(sender, ev);
125-
}
126-
127-
public override void CompletePatch(object? sender, EventArgs e) {
128-
string identifier = string.IsNullOrWhiteSpace(this._outputField.Text) ? this._outputField.PlaceholderText : this._outputField.Text;
129-
130-
string destination = Path.Combine(this._usrDir, $"EBOOT.{identifier}.elf");
131-
132-
File.Move(this._tempFile, destination, true);
133-
MessageBox.Show($"Successfully patched EBOOT! It was saved to '{destination}'.");
134-
135-
// Re-initialize patcher so we can patch with the same parameters again
136-
// Probably slow but prevents crash
137-
this.GameChanged(this, EventArgs.Empty);
38+
this.Accessor = new EmulatorPatchAccessor(this._folderField.FilePath);
39+
base.PathChanged(sender, ev);
13840
}
13941

140-
public override void Guide(object? sender, EventArgs e)
42+
protected override TableRow AddRemoteField()
14143
{
142-
this.OpenUrl("https://littlebigrefresh.github.io/Docs/patching/rpcs3");
44+
return AddField("RPCS3 dev_hdd0 folder", out this._folderField);
14345
}
46+
47+
protected override bool NeedsResign => false;
48+
protected override bool ShouldReplaceExecutable => false;
14449
}

0 commit comments

Comments
 (0)