Merge UnionRemotePatcher into UnionPatcher

Squashed commit of the following:

commit db54f752b1a7d876c8e8ac23f64fac0e133c6c42
Author: jvyden <jvyden@jvyden.xyz>
Date:   Tue Jun 14 21:50:24 2022 -0400

    Fix warnings in Program.cs, remove version

commit abab75e00a9fa5c762669365a130025b7fa28522
Author: jvyden <jvyden@jvyden.xyz>
Date:   Tue Jun 14 21:45:08 2022 -0400

    Enforce file-scoped namespaces

commit d004b8dd77546b0dedc572bc3e7849677a707697
Author: jvyden <jvyden@jvyden.xyz>
Date:   Tue Jun 14 21:43:53 2022 -0400

    Cleanup remote patching

commit d611df7e2e5ad86466acb3fe95b835e5df3efa38
Author: jvyden <jvyden@jvyden.xyz>
Date:   Tue Jun 14 21:25:57 2022 -0400

    Theoretically working state

commit 18b362e268d800bac3c6fa5c3ae0c0b896627648
Author: jvyden <jvyden@jvyden.xyz>
Date:   Tue Jun 14 21:10:58 2022 -0400

    Import code from UnionRemotePatcher

Co-authored-by: Logan Lowe <loganr.lowe@gmail.com>
This commit is contained in:
jvyden 2022-06-14 21:51:55 -04:00
parent ff38cd2bd6
commit 447ca7b4a5
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
11 changed files with 2287 additions and 65 deletions

403
.gitignore vendored
View file

@ -1,5 +1,398 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View file

@ -1,17 +1,16 @@
using System.Diagnostics;
using LBPUnion.UnionPatcher;
namespace LBPUnion.UnionPatcher.Cli;
namespace UnionPatcher.Cli;
public static class Program {
public const string Version = "1.0";
private static string fileName;
private static string? fileName;
public static string FileName {
get {
if(fileName != null) return fileName;
return fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName);
return fileName = (Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName) ?? "");
}
}
@ -46,7 +45,7 @@ public static class Program {
}
public static void PrintHelp() {
Console.WriteLine($"UnionPatcher {Version}");
Console.WriteLine("UnionPatcher");
Console.WriteLine($" Usage: {FileName} <Input EBOOT.elf> <Server URL> <Output filename>");
}
}

View file

@ -4,49 +4,13 @@ using Eto;
using Eto.Drawing;
using Eto.Forms;
namespace LBPUnion.UnionPatcher.Gui;
namespace LBPUnion.UnionPatcher.Gui.Forms;
public class MainForm : Form {
public class FilePatchForm : Form {
#region UI
private readonly FilePicker filePicker;
private readonly TextBox serverUrl;
private readonly FilePicker outputFileName;
public Dialog CreateOkDialog(string title, string errorMessage) {
DynamicLayout layout = new();
Button button;
layout.Spacing = new Size(5, 5);
layout.MinimumSize = new Size(350, 100);
layout.BeginHorizontal();
layout.Add(new Label {
Text = errorMessage,
});
layout.BeginHorizontal();
layout.BeginVertical();
layout.Add(null);
layout.Add(button = new Button {
Text = "OK",
});
layout.EndVertical();
layout.EndHorizontal();
layout.EndHorizontal();
Dialog dialog = new() {
Content = layout,
Padding = new Padding(10, 10, 10, 10),
Title = title,
};
button.Click += delegate {
dialog.Close();
};
return dialog;
}
public Control CreatePatchButton(int tabIndex = 0) {
Button control = new() {
@ -54,7 +18,7 @@ public class MainForm : Form {
TabIndex = tabIndex,
};
control.Click += Patch;
control.Click += this.Patch;
return control;
}
@ -76,8 +40,8 @@ public class MainForm : Form {
return control;
}
public MainForm() {
this.Title = "Union Patcher";
public FilePatchForm() {
this.Title = "UnionPatcher - File Patch";
this.ClientSize = new Size(500, -1);
this.Content = new TableLayout {
Spacing = new Size(5,5),
@ -109,27 +73,27 @@ public class MainForm : Form {
private void Patch() {
if(string.IsNullOrWhiteSpace(this.filePicker.FilePath)) {
this.CreateOkDialog("Form Error", "No file specified!").ShowModal();
Gui.CreateOkDialog("Form Error", "No file specified!").ShowModal();
return;
}
if(string.IsNullOrWhiteSpace(this.serverUrl.Text)) {
this.CreateOkDialog("Form Error", "No server URL specified!").ShowModal();
Gui.CreateOkDialog("Form Error", "No server URL specified!").ShowModal();
return;
}
if(string.IsNullOrWhiteSpace(this.outputFileName.FilePath)) {
this.CreateOkDialog("Form Error", "No output file specified!").ShowModal();
Gui.CreateOkDialog("Form Error", "No output file specified!").ShowModal();
return;
}
if(this.filePicker.FilePath == this.outputFileName.FilePath) {
this.CreateOkDialog("Form Error", "Input and output filename are the same! Please save the patched file with a different name so you have a backup of your the original EBOOT.ELF.").ShowModal();
Gui.CreateOkDialog("Form Error", "Input and output filename are the same! Please save the patched file with a different name so you have a backup of your the original EBOOT.ELF.").ShowModal();
return;
}
if(!Uri.TryCreate(this.serverUrl.Text, UriKind.Absolute, out _)) {
this.CreateOkDialog("Form Error", "Server URL is invalid! Please enter a valid URL.").ShowModal();
Gui.CreateOkDialog("Form Error", "Server URL is invalid! Please enter a valid URL.").ShowModal();
return;
}
@ -138,17 +102,17 @@ public class MainForm : Form {
ElfFile eboot = new(this.filePicker.FilePath);
if(eboot.IsValid == false) {
this.CreateOkDialog("EBOOT Error", $"{eboot.Name} is not a valid ELF file (magic number mismatch)\n" + "The EBOOT must be decrypted before using this tool").ShowModal();
Gui.CreateOkDialog("EBOOT Error", $"{eboot.Name} is not a valid ELF file (magic number mismatch)\n" + "The EBOOT must be decrypted before using this tool").ShowModal();
return;
}
if(eboot.Is64Bit == null) {
this.CreateOkDialog("EBOOT Error", $"{eboot.Name} does not target a valid system").ShowModal();
Gui.CreateOkDialog("EBOOT Error", $"{eboot.Name} does not target a valid system").ShowModal();
return;
}
if(string.IsNullOrWhiteSpace(eboot.Architecture)) {
this.CreateOkDialog("EBOOT Error", $"{eboot.Name} does not target a valid architecture (PowerPC or ARM)").ShowModal();
Gui.CreateOkDialog("EBOOT Error", $"{eboot.Name} does not target a valid architecture (PowerPC or ARM)").ShowModal();
return;
}
@ -156,10 +120,10 @@ public class MainForm : Form {
Patcher.PatchFile(this.filePicker.FilePath, this.serverUrl.Text, this.outputFileName.FilePath);
}
catch(Exception e) {
this.CreateOkDialog("Error occurred while patching", "An error occured while patching:\n" + e).ShowModal();
Gui.CreateOkDialog("Error occurred while patching", "An error occured while patching:\n" + e).ShowModal();
return;
}
this.CreateOkDialog("Success!", "The Server URL has been patched to " + this.serverUrl.Text).ShowModal();
Gui.CreateOkDialog("Success!", "The Server URL has been patched to " + this.serverUrl.Text).ShowModal();
}
}

View file

@ -0,0 +1,42 @@
using System;
using Eto.Drawing;
using Eto.Forms;
namespace LBPUnion.UnionPatcher.Gui.Forms;
public class ModeSelectionForm : Form {
#region UI
public ModeSelectionForm() {
this.Title = "Welcome to UnionPatcher";
this.ClientSize = new Size(500, -1);
this.Content = new TableLayout {
Spacing = new Size(5, 5),
Padding = new Padding(10, 10, 10, 10),
Rows = {
new TableRow(
new TableCell(new Button(openRemotePatcher) { Text = "Remote Patcher (PS3)" })
),
new TableRow(
new TableCell(new Button(openLocalPatcher) { Text = "Local Patch (RPCS3)", Enabled = false })
),
new TableRow(
new TableCell(new Button(openFilePatcher) { Text = "File Patch (PS3/RPCS3)" })
),
},
};
}
private void openRemotePatcher(object sender, EventArgs e) {
new RemotePatchForm().Show();
this.Close();
}
private void openLocalPatcher(object sender, EventArgs e) {
throw new NotImplementedException();
}
private void openFilePatcher(object sender, EventArgs e) {
new FilePatchForm().Show();
this.Close();
}
#endregion
}

View file

@ -0,0 +1,182 @@
using System;
using System.Diagnostics;
using Eto.Drawing;
using Eto.Forms;
namespace LBPUnion.UnionPatcher.Gui.Forms;
public class RemotePatchForm : Form
{
public RemotePatchForm()
{
this.InitializeComponent();
Console.WriteLine("Welcome to UnionRemotePatcher");
}
public readonly RemotePatch RemotePatcher = new();
private TextBox ps3LocalIP;
private TextBox lbpGameID;
private TextBox serverUrl;
private TextBox ftpUser;
private TextBox ftpPass;
#region UI
public Control CreatePatchButton(int tabIndex = 0)
{
Button control = new()
{
Text = "Patch!",
TabIndex = tabIndex,
Width = 200,
};
control.Click += delegate {
if (string.IsNullOrEmpty(this.ps3LocalIP.Text))
{
Gui.CreateOkDialog("Error", "No PS3 IP address specified!").ShowModal();
return;
}
if (string.IsNullOrEmpty(this.lbpGameID.Text))
{
Gui.CreateOkDialog("Error", "No title ID specified!").ShowModal();
return;
}
if (string.IsNullOrEmpty(this.serverUrl.Text))
{
Gui.CreateOkDialog("Error", "No server URL specified!").ShowModal();
return;
}
if (!Uri.TryCreate(this.serverUrl.Text, UriKind.Absolute, out _))
{
Gui.CreateOkDialog("Error", "Server URL is invalid! Please enter a valid URL.").ShowModal();
return;
}
try
{
if (this.lbpGameID.Text.ToUpper().StartsWith('B'))
{
this.RemotePatcher.DiscEBOOTRemotePatch(this.ps3LocalIP.Text, this.lbpGameID.Text, this.serverUrl.Text, this.ftpUser.Text, this.ftpPass.Text);
}
else
{
this.RemotePatcher.PSNEBOOTRemotePatch(this.ps3LocalIP.Text, this.lbpGameID.Text, this.serverUrl.Text, this.ftpUser.Text, this.ftpPass.Text);
}
}
catch (Exception e)
{
Gui.CreateOkDialog("Error occurred while patching", "An error occured while patching:\n" + e).ShowModal();
return;
}
Gui.CreateOkDialog("Success!", $"The Server URL for {this.lbpGameID.Text} on the PS3 at {this.ps3LocalIP.Text} has been patched to {this.serverUrl.Text}").ShowModal();
};
return control;
}
public Control CreateRevertEBOOTButton(int tabIndex = 0)
{
Button control = new()
{
Text = "Revert EBOOT",
TabIndex = tabIndex,
Width = 200,
};
control.Click += delegate {
if (string.IsNullOrEmpty(this.ps3LocalIP.Text))
{
Gui.CreateOkDialog("Form Error", "No PS3 IP address specified!").ShowModal();
return;
}
if (string.IsNullOrEmpty(this.lbpGameID.Text))
{
Gui.CreateOkDialog("Form Error", "No game ID specified!").ShowModal();
return;
}
try
{
this.RemotePatcher.RevertEBOOT(this.ps3LocalIP.Text, this.lbpGameID.Text, this.serverUrl.Text, this.ftpUser.Text, this.ftpPass.Text);
}
catch (Exception e)
{
Gui.CreateOkDialog("Error occurred while reverting EBOOT", "An error occured while patching:\n" + e).ShowModal();
return;
}
Gui.CreateOkDialog("Success!", $"UnionRemotePatcher reverted your the EBOOT for {this.lbpGameID.Text} to stock. You're ready to patch your EBOOT again.").ShowModal();
};
return control;
}
public Control CreateHelpButton(int tabIndex = 0)
{
Button control = new()
{
Text = "Help",
TabIndex = tabIndex,
};
control.Click += delegate {
Process process = new();
process.StartInfo.UseShellExecute = true;
process.StartInfo.FileName = "https://www.lbpunion.com";
process.Start();
};
return control;
}
void InitializeComponent()
{
this.Title = "UnionPatcher - Remote Patch";
this.MinimumSize = new Size(450, 200);
this.Resizable = false;
this.Padding = 10;
this.Content = new TableLayout
{
Spacing = new Size(5, 5),
Padding = new Padding(10, 10, 10, 10),
Rows = {
new TableRow(
new TableCell(new Label { Text = "PS3 Local IP: ", VerticalAlignment = VerticalAlignment.Center }),
new TableCell(this.ps3LocalIP = new TextBox { TabIndex = 0 })
),
new TableRow(
new TableCell(new Label { Text = "Server URL: ", VerticalAlignment = VerticalAlignment.Center }),
new TableCell(this.serverUrl = new TextBox { TabIndex = 1 })
),
new TableRow(
new TableCell(new Label { Text = "Title ID (e.g. BCUS98245): ", VerticalAlignment = VerticalAlignment.Center }),
new TableCell(this.lbpGameID = new TextBox { TabIndex = 2 })
),
new TableRow(
new TableCell(new Label { Text = "FTP Username: ", VerticalAlignment = VerticalAlignment.Center }),
new TableCell(this.ftpUser = new TextBox { TabIndex = 3, Text = "anonymous" })
),
new TableRow(
new TableCell(new Label { Text = "FTP Password: ", VerticalAlignment = VerticalAlignment.Center }),
new TableCell(this.ftpPass = new TextBox { TabIndex = 4 })
),
new TableRow(
new TableCell(this.CreateHelpButton(7)),
new TableRow(
new TableCell(this.CreatePatchButton(5)),
new TableCell(this.CreateRevertEBOOTButton(6))
)
),
},
};
}
#endregion
}

View file

@ -1,9 +1,47 @@
using Eto.Forms;
using Eto.Drawing;
using Eto.Forms;
using LBPUnion.UnionPatcher.Gui.Forms;
namespace LBPUnion.UnionPatcher.Gui;
public static class Gui {
public static void Show() {
new Application().Run(new MainForm());
new Application().Run(new ModeSelectionForm());
}
public static Dialog CreateOkDialog(string title, string errorMessage) {
DynamicLayout layout = new();
Button button;
layout.Spacing = new Size(5, 5);
layout.MinimumSize = new Size(350, 100);
layout.BeginHorizontal();
layout.Add(new Label {
Text = errorMessage,
});
layout.BeginHorizontal();
layout.BeginVertical();
layout.Add(null);
layout.Add(button = new Button {
Text = "OK",
});
layout.EndVertical();
layout.EndHorizontal();
layout.EndHorizontal();
Dialog dialog = new() {
Content = layout,
Padding = new Padding(10, 10, 10, 10),
Title = title,
};
button.Click += delegate {
dialog.Close();
};
return dialog;
}
}

View file

@ -1,4 +1,10 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FTP/@EntryIndexedValue">FTP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IDPS/@EntryIndexedValue">IDPS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MAPI/@EntryIndexedValue">MAPI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PS/@EntryIndexedValue">PS</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=EBOOT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=LITTLEBIGPLANETPSP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Restitcher/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -0,0 +1,141 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace LBPUnion.UnionPatcher.Communication;
public static class FTP
{
public static bool FileExists(string url, string user, string pass)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Credentials = new NetworkCredential(user, pass);
request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
try
{
Console.Write($"FTP: Checking if file {url} exists... ");
request.GetResponse();
}
catch (WebException ex)
{
FtpWebResponse? response = (FtpWebResponse?)ex.Response;
if (response == null || response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
{
Console.WriteLine("No");
return false;
}
}
Console.WriteLine("Yes");
return true;
}
public static string[] ListDirectory(string url, string user, string pass)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Credentials = new NetworkCredential(user, pass);
request.Method = WebRequestMethods.Ftp.ListDirectory;
try
{
Console.WriteLine($"FTP: Listing directory {url}");
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
string names = new StreamReader(response.GetResponseStream()).ReadToEnd();
List<string> dirs = names
.Split("\r\n")
.Where(dir => !string.IsNullOrWhiteSpace(dir) && dir != "." && dir != "..")
.ToList();
foreach(string dir in dirs.ToArray())
{
Console.WriteLine($"/{dir}");
}
Console.WriteLine("");
return dirs.ToArray();
}
catch
{
return Array.Empty<string>();
}
}
public static void UploadFile(string source, string destination, string user, string pass)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(destination);
request.Credentials = new NetworkCredential(user, pass);
request.Method = WebRequestMethods.Ftp.UploadFile;
byte[] fileContents = File.ReadAllBytes(source);
request.ContentLength = fileContents.Length;
try
{
Console.WriteLine($"FTP: Uploading file {source} to {destination}");
using Stream requestStream = request.GetRequestStream();
requestStream.Write(fileContents, 0, fileContents.Length);
}
catch
{
throw new WebException("Could not upload file");
}
}
public static void DownloadFile(string source, string destination, string user, string pass)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(source);
request.Credentials = new NetworkCredential(user, pass);
request.Method = WebRequestMethods.Ftp.DownloadFile;
try
{
Console.WriteLine($"FTP: Downloading file {source} to {destination}");
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
using Stream s = File.Create(destination);
responseStream.CopyTo(s);
}
catch
{
throw new WebException("Could not download file");
}
}
public static string ReadFile(string url, string user, string pass)
{
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Credentials = new NetworkCredential(user, pass);
request.Method = WebRequestMethods.Ftp.DownloadFile;
try
{
Console.WriteLine($"FTP: Reading file {url}");
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
using StreamReader reader = new(responseStream);
return reader.ReadToEnd();
}
catch
{
return "<Error Downloading File>";
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
using System;
namespace LBPUnion.UnionPatcher;
public static class IDPSHelper
{
public static byte[] StringToByteArray(string hex)
{
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
public static int GetHexVal(char hex)
{
int val = (int)hex;
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
}
}

222
UnionPatcher/RemotePatch.cs Normal file
View file

@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using LBPUnion.UnionPatcher.Communication;
namespace LBPUnion.UnionPatcher;
public class RemotePatch
{
private readonly PS3MAPI _ps3Mapi = new();
private static Dictionary<string, string> GetUsers(string ps3Ip, string user, string pass)
{
Console.WriteLine("Getting users...");
Dictionary<string, string> users = new();
string[] userFolders = FTP.ListDirectory($"ftp://{ps3Ip}/dev_hdd0/home/", user, pass);
string username = "";
for (int i = 0; i < userFolders.Length; i++)
{
username = FTP.ReadFile($"ftp://{ps3Ip}/dev_hdd0/home/{userFolders[i]}/localusername", user,
pass);
users.Add(userFolders[i], username);
Console.WriteLine("User found: " + username + $" <{userFolders[i]}>");
}
return users;
}
public static void LaunchSCETool(string args)
{
string platformExecutable = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
platformExecutable = "scetool/win64/scetool.exe";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
platformExecutable = "scetool/linux64/scetool";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) platformExecutable = "";
if (platformExecutable != "")
{
ProcessStartInfo startInfo = new();
startInfo.UseShellExecute = false;
startInfo.FileName = Path.GetFullPath(platformExecutable);
startInfo.WorkingDirectory = Path.GetFullPath(".");
startInfo.Arguments = args;
startInfo.RedirectStandardOutput = true;
Console.WriteLine("\n\n===== START SCETOOL =====\n");
using (Process proc = Process.Start(startInfo))
{
while (!proc.StandardOutput.EndOfStream) Console.WriteLine(proc.StandardOutput.ReadLine());
proc.WaitForExit();
}
Console.WriteLine("\n===== END SCETOOL =====\n\n");
}
else
{
throw new Exception("Error starting SCETool. Your platform may not be supported yet.");
}
}
public void RevertEBOOT(string ps3ip, string gameID, string serverURL, string user, string pass)
{
Console.WriteLine("Restoring original EBOOT.BIN from EBOOT.BIN.BAK");
// Create a simple directory structure
Directory.CreateDirectory(@"eboot");
Directory.CreateDirectory($@"eboot/{gameID}");
Directory.CreateDirectory($@"eboot/{gameID}/original");
// Now we'll check and see if a backup exists on the server, if so download it and then upload it back as EBOOT.BIN
if (FTP.FileExists($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", user, pass))
{
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", @$"eboot/{gameID}/original/EBOOT.BIN.BAK", user, pass);
FTP.UploadFile(@$"eboot/{gameID}/original/EBOOT.BIN.BAK", $"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", user, pass);
}
else
{
throw new WebException("Could not find EBOOT.BIN.BAK on server.");
}
}
public void PSNEBOOTRemotePatch(string ps3ip, string gameID, string serverURL, string user, string pass)
{
Console.WriteLine("Detected Digital Copy - Running in Full Mode");
string idps = "";
string contentID = "";
Dictionary<string, string> users;
this._ps3Mapi.ConnectTarget(ps3ip);
this._ps3Mapi.PS3.RingBuzzer(PS3MAPI.PS3Cmd.BuzzerMode.Double);
this._ps3Mapi.PS3.Notify("UnionRemotePatcher Connected! Patching...");
// Create simple directory structure
Directory.CreateDirectory(@"rifs");
Directory.CreateDirectory(@"eboot");
Directory.CreateDirectory($@"eboot/{gameID}");
Directory.CreateDirectory($@"eboot/{gameID}/original");
Directory.CreateDirectory($@"eboot/{gameID}/patched");
// Let's grab and backup our EBOOT
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN",
@$"eboot/{gameID}/original/EBOOT.BIN", user, pass);
// Now we'll check and see if a backup exists on the server or not, if we don't have one on the server, then upload one
if (!FTP.FileExists($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", user, pass))
FTP.UploadFile(@$"eboot/{gameID}/original/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", user, pass);
// Start getting idps and act.dat - these will help us decrypt a PSN eboot
idps = PS3MAPI.PS3MAPIClientServer.PS3_GetIDPS();
File.WriteAllBytes(@"data/idps", IDPSHelper.StringToByteArray(idps));
// Scan the users on the system
users = GetUsers(ps3ip, user, pass);
// Scan the system for a license for the game
foreach (string currentUser in users.Keys.ToArray())
{
if (FTP.FileExists($"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata", user, pass))
{
foreach (string fileName in FTP.ListDirectory(
$"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata/", user, pass))
if (fileName.Contains(gameID))
{
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata/act.dat", @"data/act.dat",
user,
pass);
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata/{fileName}",
@$"rifs/{fileName}", user, pass);
contentID = fileName.Substring(0, fileName.Length - 4);
Console.WriteLine($"Got content ID {contentID}");
}
}
}
// Finally, let's decrypt the EBOOT.BIN
LaunchSCETool($" -v -d \"{Path.GetFullPath(@$"eboot/{gameID}/original/EBOOT.BIN")}\" \"{Path.GetFullPath(@$"eboot/{gameID}/original/EBOOT.ELF")}\"");
// Now, patch the EBOOT;
Patcher.PatchFile($"eboot/{gameID}/original/EBOOT.ELF", serverURL, $"eboot/{gameID}/patched/EBOOT.ELF");
// Encrypt the EBOOT (PSN)
LaunchSCETool($"--verbose " +
$"--sce-type=SELF" +
$" --skip-sections=FALSE" +
$" --self-add-shdrs=TRUE" +
$" --compress-data=TRUE" +
$" --key-revision=0A" +
$" --self-app-version=0001000000000000" +
$" --self-auth-id=1010000001000003" +
$" --self-vendor-id=01000002" +
$" --self-ctrl-flags=0000000000000000000000000000000000000000000000000000000000000000" +
$" --self-cap-flags=00000000000000000000000000000000000000000000003B0000000100040000" +
$" --self-type=NPDRM" +
$" --self-fw-version=0003005500000000" +
$" --np-license-type=FREE" +
$" --np-app-type=SPRX" +
$" --np-content-id={contentID}" +
$" --np-real-fname=EBOOT.BIN" +
$" --encrypt eboot/{gameID}/patched/EBOOT.ELF eboot/{gameID}/patched/EBOOT.BIN");
// And upload the encrypted, patched EBOOT to the system.
FTP.UploadFile(@$"eboot/{gameID}/patched/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", user, pass);
}
// Cut-down version that only patches disc copies
public void DiscEBOOTRemotePatch(string ps3ip, string gameID, string serverURL, string user, string pass)
{
Console.WriteLine("Detected Disc Copy - Running in Simplified Mode");
// Create a simple directory structure
Directory.CreateDirectory(@"eboot");
Directory.CreateDirectory($@"eboot/{gameID}");
Directory.CreateDirectory($@"eboot/{gameID}/original");
Directory.CreateDirectory($@"eboot/{gameID}/patched");
// Let's grab and backup our EBOOT
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN",
@$"eboot/{gameID}/original/EBOOT.BIN", user, pass);
// Now we'll check and see if a backup exists on the server or not, if we don't have one on the server, then upload one
if (!FTP.FileExists($"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", user, pass))
FTP.UploadFile(@$"eboot/{gameID}/original/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN.BAK", user, pass);
// Check for keys in the data directory
if (!File.Exists("data/keys"))
throw new FileNotFoundException(
"UnionRemotePatcher cannot find the keys, ldr_curves, or vsh_curves files required to continue. Please make sure you have copies of these files placed in the data directory where you found the executable to run UnionRemotePatcher. Without them, we can't patch your game.");
// Decrypt the EBOOT
LaunchSCETool($"-v -d eboot/{gameID}/original/EBOOT.BIN eboot/{gameID}/original/EBOOT.ELF");
// Now, patch the EBOOT;
Patcher.PatchFile($"eboot/{gameID}/original/EBOOT.ELF", serverURL, $"eboot/{gameID}/patched/EBOOT.ELF");
// Encrypt the EBOOT (Disc)
LaunchSCETool(
$" -v --sce-type=SELF --skip-sections=FALSE --key-revision=0A --self-app-version=0001000000000000 --self-auth-id=1010000001000003 --self-vendor-id=01000002 --self-ctrl-flags=0000000000000000000000000000000000000000000000000000000000000000 --self-cap-flags=00000000000000000000000000000000000000000000003B0000000100040000 --self-type=APP --self-fw-version=0003005500000000 --compress-data true --encrypt \"{Path.GetFullPath(@$"eboot/{gameID}/patched/EBOOT.ELF")}\" \"{Path.GetFullPath(@$"eboot/{gameID}/patched/EBOOT.BIN")}\"");
// And upload the encrypted, patched EBOOT to the system.
FTP.UploadFile(@$"eboot/{gameID}/patched/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", user, pass);
}
}