From 447ca7b4a5ae53a22b011229232b0b21282a63b9 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 14 Jun 2022 21:51:55 -0400 Subject: [PATCH] Merge UnionRemotePatcher into UnionPatcher Squashed commit of the following: commit db54f752b1a7d876c8e8ac23f64fac0e133c6c42 Author: jvyden Date: Tue Jun 14 21:50:24 2022 -0400 Fix warnings in Program.cs, remove version commit abab75e00a9fa5c762669365a130025b7fa28522 Author: jvyden Date: Tue Jun 14 21:45:08 2022 -0400 Enforce file-scoped namespaces commit d004b8dd77546b0dedc572bc3e7849677a707697 Author: jvyden Date: Tue Jun 14 21:43:53 2022 -0400 Cleanup remote patching commit d611df7e2e5ad86466acb3fe95b835e5df3efa38 Author: jvyden Date: Tue Jun 14 21:25:57 2022 -0400 Theoretically working state commit 18b362e268d800bac3c6fa5c3ae0c0b896627648 Author: jvyden Date: Tue Jun 14 21:10:58 2022 -0400 Import code from UnionRemotePatcher Co-authored-by: Logan Lowe --- .gitignore | 403 +++++- UnionPatcher.Cli/Program.cs | 13 +- .../{MainForm.cs => Forms/FilePatchForm.cs} | 66 +- UnionPatcher.Gui/Forms/ModeSelectionForm.cs | 42 + UnionPatcher.Gui/Forms/RemotePatchForm.cs | 182 +++ UnionPatcher.Gui/Gui.cs | 42 +- UnionPatcher.sln.DotSettings | 6 + UnionPatcher/Communication/FTP.cs | 141 ++ UnionPatcher/Communication/PS3MAPI.cs | 1208 +++++++++++++++++ UnionPatcher/IDPSHelper.cs | 27 + UnionPatcher/RemotePatch.cs | 222 +++ 11 files changed, 2287 insertions(+), 65 deletions(-) rename UnionPatcher.Gui/{MainForm.cs => Forms/FilePatchForm.cs} (59%) create mode 100644 UnionPatcher.Gui/Forms/ModeSelectionForm.cs create mode 100644 UnionPatcher.Gui/Forms/RemotePatchForm.cs create mode 100644 UnionPatcher/Communication/FTP.cs create mode 100644 UnionPatcher/Communication/PS3MAPI.cs create mode 100644 UnionPatcher/IDPSHelper.cs create mode 100644 UnionPatcher/RemotePatch.cs diff --git a/.gitignore b/.gitignore index add57be..426d76d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,398 @@ -bin/ -obj/ -/packages/ -riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file +## 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 diff --git a/UnionPatcher.Cli/Program.cs b/UnionPatcher.Cli/Program.cs index 5d4ee07..4069dbe 100644 --- a/UnionPatcher.Cli/Program.cs +++ b/UnionPatcher.Cli/Program.cs @@ -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 void Main(string[] args) { } public static void PrintHelp() { - Console.WriteLine($"UnionPatcher {Version}"); + Console.WriteLine("UnionPatcher"); Console.WriteLine($" Usage: {FileName} "); } } \ No newline at end of file diff --git a/UnionPatcher.Gui/MainForm.cs b/UnionPatcher.Gui/Forms/FilePatchForm.cs similarity index 59% rename from UnionPatcher.Gui/MainForm.cs rename to UnionPatcher.Gui/Forms/FilePatchForm.cs index 22ffb0d..27e9fa8 100644 --- a/UnionPatcher.Gui/MainForm.cs +++ b/UnionPatcher.Gui/Forms/FilePatchForm.cs @@ -4,49 +4,13 @@ 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 Control CreatePatchButton(int tabIndex = 0) { TabIndex = tabIndex, }; - control.Click += Patch; + control.Click += this.Patch; return control; } @@ -76,8 +40,8 @@ public Control CreateHelpButton(int tabIndex = 0) { 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 @@ private void Patch(object sender, EventArgs e) { 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 @@ private void Patch() { 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 @@ private void Patch() { 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(); } } \ No newline at end of file diff --git a/UnionPatcher.Gui/Forms/ModeSelectionForm.cs b/UnionPatcher.Gui/Forms/ModeSelectionForm.cs new file mode 100644 index 0000000..e121e05 --- /dev/null +++ b/UnionPatcher.Gui/Forms/ModeSelectionForm.cs @@ -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 +} \ No newline at end of file diff --git a/UnionPatcher.Gui/Forms/RemotePatchForm.cs b/UnionPatcher.Gui/Forms/RemotePatchForm.cs new file mode 100644 index 0000000..1e84400 --- /dev/null +++ b/UnionPatcher.Gui/Forms/RemotePatchForm.cs @@ -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 +} \ No newline at end of file diff --git a/UnionPatcher.Gui/Gui.cs b/UnionPatcher.Gui/Gui.cs index e09968d..9a62e51 100644 --- a/UnionPatcher.Gui/Gui.cs +++ b/UnionPatcher.Gui/Gui.cs @@ -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; } } \ No newline at end of file diff --git a/UnionPatcher.sln.DotSettings b/UnionPatcher.sln.DotSettings index b104aa0..598dc8c 100644 --- a/UnionPatcher.sln.DotSettings +++ b/UnionPatcher.sln.DotSettings @@ -1,4 +1,10 @@  + FTP + ID + IDPS + IP + MAPI + PS True True True \ No newline at end of file diff --git a/UnionPatcher/Communication/FTP.cs b/UnionPatcher/Communication/FTP.cs new file mode 100644 index 0000000..8ae313a --- /dev/null +++ b/UnionPatcher/Communication/FTP.cs @@ -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 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(); + } + } + + 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 ""; + } + } +} \ No newline at end of file diff --git a/UnionPatcher/Communication/PS3MAPI.cs b/UnionPatcher/Communication/PS3MAPI.cs new file mode 100644 index 0000000..f99a96a --- /dev/null +++ b/UnionPatcher/Communication/PS3MAPI.cs @@ -0,0 +1,1208 @@ +/*PS3 MANAGER API + * Copyright (c) 2014-2015 _NzV_. + * + * This code is write by _NzV_ . + * It may be used for any purpose as long as this notice remains intact on all + * source code distributions. + */ + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; + +namespace LBPUnion.UnionPatcher.Communication; + +public class PS3MAPI +{ + public readonly PS3Cmd PS3 = new(); + //public VSH_PLUGINS_CMD VSH_Plugin = new VSH_PLUGINS_CMD(); + + public PS3MAPI() + { + this.PS3 = new PS3Cmd(); + } + + #region PS3MAPI_Client + + /// + /// Indicates if PS3MAPI is connected + /// + public bool IsConnected => PS3MAPIClientServer.IsConnected; + + /// Connect the target with "ConnectDialog". + /// Ip + /// Port + public void ConnectTarget(string ip, int port = 7887) + { + try + { + PS3MAPIClientServer.Connect(ip, port); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + } + + public class PS3Cmd + { + /// Get PS3 Firmware Version. + public uint GetFirmwareVersion() + { + try + { + return PS3MAPIClientServer.PS3_GetFwVersion(); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + } + /// Get PS3 Firmware Version. + public string GetFirmwareVersion_Str() + { + string ver = PS3MAPIClientServer.PS3_GetFwVersion().ToString("X4"); + string char1 = ver.Substring(1, 1) + "."; + string char2 = ver.Substring(2, 1); + string char3 = ver.Substring(3, 1); + return char1 + char2 + char3; + } + + /// Get PS3 Firmware Type. + public string GetFirmwareType() + { + try + { + return PS3MAPIClientServer.PS3_GetFirmwareType(); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + } + + /// PS3 VSH Notify. + /// Your message + public void Notify(string msg) + { + try + { + PS3MAPIClientServer.PS3_Notify(msg); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + } + public enum BuzzerMode + { + Single = 1, + Double = 2, + Triple = 3, + } + + /// Ring PS3 Buzzer. + /// Simple, Double, Continuous + public void RingBuzzer(BuzzerMode mode) + { + try + { + PS3MAPIClientServer.PS3_Buzzer((int)mode); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + } + + } + + #endregion PS3MAPI_Client + + #region PS3MAPI_Client_Server + internal class PS3MAPIClientServer + { + #region Private Members + + static private int ps3m_api_server_minversion = 0x0120; + static private PS3MAPI_ResponseCode eResponseCode; + static private string sResponse; + static private string sMessages = ""; + static private string sServerIP = ""; + static private int iPort = 7887; + static private string sBucket = ""; + static private int iTimeout = 5000; // 5 Second + static private uint iPid = 0; + static private uint[] iprocesses_pid = new uint[16]; + static private int[] imodules_prx_id = new int[64]; + static private string sLog = ""; + #endregion Private Members + + #region Internal Members + + static internal Socket main_sock; + static internal Socket listening_sock; + static internal Socket data_sock; + static internal IPEndPoint main_ipEndPoint; + static internal IPEndPoint data_ipEndPoint; + internal enum PS3MAPI_ResponseCode + { + DataConnectionAlreadyOpen = 125, + MemoryStatusOK = 150, + CommandOK = 200, + RequestSuccessful = 226, + EnteringPassiveMode = 227, + PS3MAPIConnected = 220, + PS3MAPIConnectedOK = 230, + MemoryActionCompleted = 250, + MemoryActionPended = 350 + } + + #endregion Internal Members + + #region Public Properties + + /// + /// Return all process_pid + /// + static public string Log => sLog; + + /// + /// Return all process_pid + /// + static public uint[] Processes_Pid => iprocesses_pid; + + /// + /// Attached process_pid + /// + static public uint Process_Pid + { + get => iPid; + set => iPid = value; + } + + /// + /// Return all modules_prx_id + /// + static public int[] Modules_Prx_Id => imodules_prx_id; + + /// + /// User Specified Timeout: Defaults to 5000 (5 seconds) + /// + static public int Timeout + { + get => iTimeout; + set => iTimeout = value; + } + + /// + /// Indicates if PS3MAPI is connected + /// + static public bool IsConnected => ((main_sock != null) ? main_sock.Connected : false); + + /// + /// Indicates if PS3MAPI is attached + /// + static public bool IsAttached => ((iPid != 0) ? true : false); + + #endregion Public Properties + + //SERVER--------------------------------------------------------------------------------- + internal static void Connect() + { + Connect(sServerIP, iPort); + } + internal static void Connect(string sServer, int Port) + { + sServerIP = sServer; + iPort = Port; + if (Port.ToString().Length == 0) + { + throw new Exception("Unable to Connect - No Port Specified."); + } + if (sServerIP.Length == 0) + { + throw new Exception("Unable to Connect - No Server Specified."); + } + if (main_sock != null) + { + if (main_sock.Connected) + { + return; + } + } + main_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + main_ipEndPoint = new IPEndPoint(Dns.GetHostByName(sServerIP).AddressList[0], Port); + try + { + main_sock.Connect(main_ipEndPoint); + } + catch (Exception ex) + { + throw new Exception(ex.Message, ex); + } + ReadResponse(); + if (eResponseCode != PS3MAPI_ResponseCode.PS3MAPIConnected) + { + Fail(); + } + ReadResponse(); + if (eResponseCode != PS3MAPI_ResponseCode.PS3MAPIConnectedOK) + { + Fail(); + } + if (Server_GetMinVersion() < ps3m_api_server_minversion) + { + Disconnect(); + throw new Exception("PS3M_API SERVER (webMAN-MOD) OUTDATED! PLEASE UPDATE."); + } + else if (Server_GetMinVersion() > ps3m_api_server_minversion) + { + Disconnect(); + throw new Exception("PS3M_API PC_LIB (PS3ManagerAPI.dll) OUTDATED! PLEASE UPDATE."); + } + return; + } + internal static void Disconnect() + { + CloseDataSocket(); + if (main_sock != null) + { + if (main_sock.Connected) + { + SendCommand("DISCONNECT"); + iPid = 0; + main_sock.Close(); + } + main_sock = null; + } + main_ipEndPoint = null; + } + internal static uint Server_Get_Version() + { + if (IsConnected) + { + SendCommand("SERVER GETVERSION"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToUInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static uint Server_GetMinVersion() + { + if (IsConnected) + { + SendCommand("SERVER GETMINVERSION"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToUInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //CORE----------------------------------------------------------------------------------- + internal static uint Core_Get_Version() + { + if (IsConnected) + { + SendCommand("CORE GETVERSION"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToUInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static uint Core_GetMinVersion() + { + if (IsConnected) + { + SendCommand("CORE GETMINVERSION"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToUInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //PS3------------------------------------------------------------------------------------ + internal static uint PS3_GetFwVersion() + { + if (IsConnected) + { + SendCommand("PS3 GETFWVERSION"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToUInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static string PS3_GetFirmwareType() + { + if (IsConnected) + { + SendCommand("PS3 GETFWTYPE"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return sResponse; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_Shutdown() + { + if (IsConnected) + { + SendCommand("PS3 SHUTDOWN"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + Disconnect(); + break; + default: + Fail(); + break; + } + + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_Reboot() + { + if (IsConnected) + { + SendCommand("PS3 REBOOT"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + Disconnect(); + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_Notify(string msg) + { + if (IsConnected) + { + SendCommand("PS3 NOTIFY " + msg); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_Buzzer(int mode) + { + if (IsConnected) + { + SendCommand("PS3 BUZZER" + mode); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_Led(int color, int mode) + { + if (IsConnected) + { + SendCommand("PS3 LED " + color + " " + mode); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_GetTemp(out uint cpu, out uint rsx) + { + cpu = 0; rsx = 0; + if (IsConnected) + { + SendCommand("PS3 GETTEMP"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + string[] tmp = sResponse.Split(new char[] { '|' }); + cpu = System.Convert.ToUInt32(tmp[0], 10); + rsx = System.Convert.ToUInt32(tmp[1], 10); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_DisableSyscall(int num) + { + if (IsConnected) + { + SendCommand("PS3 DISABLESYSCALL " + num); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_ClearHistory(bool include_directory) + { + if (IsConnected) + { + if (include_directory) SendCommand("PS3 DELHISTORY+D"); + else SendCommand("PS3 DELHISTORY"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static bool PS3_CheckSyscall(int num) + { + if (IsConnected) + { + SendCommand("PS3 CHECKSYSCALL " + num); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + if (Convert.ToInt32(sResponse) == 0) return true; + else return false; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_PartialDisableSyscall8(int mode) + { + if (IsConnected) + { + SendCommand("PS3 PDISABLESYSCALL8 " + mode); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static int PS3_PartialCheckSyscall8() + { + if (IsConnected) + { + SendCommand("PS3 PCHECKSYSCALL8"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return Convert.ToInt32(sResponse); + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void PS3_RemoveHook() + { + if (IsConnected) + { + SendCommand("PS3 REMOVEHOOK"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static string PS3_GetIDPS() + { + if (IsConnected) + { + SendCommand("PS3 GETIDPS"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return sResponse; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //PROCESS-------------------------------------------------------------------------------- + internal static string Process_GetName(uint pid) + { + if (IsConnected) + { + SendCommand("PROCESS GETNAME " + string.Format("{0}", pid)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return sResponse; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static uint[] Process_GetPidList() + { + if (IsConnected) + { + SendCommand("PROCESS GETALLPID"); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + int i = 0; + iprocesses_pid = new uint[16]; + foreach (string s in sResponse.Split(new char[] { '|' })) + { + if (s.Length != 0 && s != null && s != "" && s != " " && s != "0") { iprocesses_pid[i] = Convert.ToUInt32(s, 10); i++; } + } + return iprocesses_pid; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //MEMORY-------------------------------------------------------------------------------- + internal static void Memory_Get(uint Pid, ulong Address, byte[] Bytes) + { + if (IsConnected) + { + SetBinaryMode(true); + int BytesLength = Bytes.Length; + long TotalBytes = 0; + long lBytesReceived = 0; + bool bComplete = false; + OpenDataSocket(); + SendCommand("MEMORY GET " + string.Format("{0}", Pid) + " " + string.Format("{0:X16}", Address) + " " + string.Format("{0}", Bytes.Length)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.DataConnectionAlreadyOpen: + case PS3MAPI_ResponseCode.MemoryStatusOK: + break; + default: + throw new Exception(sResponse); + } + ConnectDataSocket(); + byte[] buffer = new byte[Bytes.Length]; + while (bComplete != true) + { + try + { + lBytesReceived = data_sock.Receive(buffer, BytesLength, 0); + if (lBytesReceived > 0) + { + Buffer.BlockCopy(buffer, 0, Bytes, (int)TotalBytes, (int)lBytesReceived); + TotalBytes += lBytesReceived; + if ((int)(((TotalBytes) * 100) / BytesLength) >= 100) bComplete = true; + } + else + { + bComplete = true; + } + if (bComplete) + { + CloseDataSocket(); + ReadResponse(); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.MemoryActionCompleted: + break; + default: + throw new Exception(sResponse); + } + SetBinaryMode(false); + } + } + catch (Exception e) + { + CloseDataSocket(); + ReadResponse(); + SetBinaryMode(false); + throw e; + } + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void Memory_Set(uint Pid, ulong Address, byte[] Bytes) + { + if (IsConnected) + { + SetBinaryMode(true); + int BytesLength = Bytes.Length; + long TotalBytes = 0; + long lBytesSended = 0; + bool bComplete = false; + OpenDataSocket(); + SendCommand("MEMORY SET " + string.Format("{0}", Pid) + " " + string.Format("{0:X16}", Address)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.DataConnectionAlreadyOpen: + case PS3MAPI_ResponseCode.MemoryStatusOK: + break; + default: + throw new Exception(sResponse); + } + ConnectDataSocket(); + while (bComplete != true) + { + try + { + byte[] buffer = new byte[BytesLength - (int)TotalBytes]; + Buffer.BlockCopy(Bytes, (int)lBytesSended, buffer, 0, (BytesLength - (int)TotalBytes)); + lBytesSended = data_sock.Send(buffer, (Bytes.Length - (int)TotalBytes), 0); + bComplete = false; + if (lBytesSended > 0) + { + TotalBytes += lBytesSended; + if ((int)(((TotalBytes) * 100) / BytesLength) >= 100) bComplete = true; + } + else + { + bComplete = true; + } + if (bComplete) + { + CloseDataSocket(); + ReadResponse(); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.MemoryActionCompleted: + break; + default: + throw new Exception(sResponse); + } + SetBinaryMode(false); + } + } + catch (Exception e) + { + CloseDataSocket(); + ReadResponse(); + SetBinaryMode(false); + throw e; + } + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //MODULES-------------------------------------------------------------------------------- + internal static int[] Module_GetPrxIdList(uint pid) + { + if (IsConnected) + { + SendCommand("MODULE GETALLPRXID " + string.Format("{0}", pid)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + int i = 0; + imodules_prx_id = new int[128]; + foreach (string s in sResponse.Split(new char[] { '|' })) + { + if (s.Length != 0 && s != null && s != "" && s != " " && s != "0") { imodules_prx_id[i] = Convert.ToInt32(s, 10); i++; } + } + return imodules_prx_id; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static string Module_GetName(uint pid, int prxid) + { + if (IsConnected) + { + SendCommand("MODULE GETNAME " + string.Format("{0}", pid) + " " + prxid); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return sResponse; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static string Module_GetFilename(uint pid, int prxid) + { + if (IsConnected) + { + SendCommand("MODULE GETFILENAME " + string.Format("{0}", pid) + " " + prxid); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + return sResponse; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void Module_Load(uint pid, string path) + { + if (IsConnected) + { + SendCommand("MODULE LOAD " + string.Format("{0}", pid) + " " + path); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void Module_Unload(uint pid, int prx_id) + { + if (IsConnected) + { + SendCommand("MODULE UNLOAD " + string.Format("{0}", pid) + " " + prx_id); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //VSH PLUGINS (MODULES)------------------------------------------------------------------- + internal static void VSHPlugins_GetInfoBySlot(uint slot, out string name, out string path) + { + name = ""; path = ""; + if (IsConnected) + { + SendCommand("MODULE GETVSHPLUGINFO " + string.Format("{0}", slot)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + string[] tmp = sResponse.Split(new char[] { '|' }); + name = tmp[0]; + path = tmp[1]; + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void VSHPlugins_Load(uint slot, string path) + { + if (IsConnected) + { + SendCommand("MODULE LOADVSHPLUG " + string.Format("{0}", slot) + " " + path); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + internal static void VSHPlugins_Unload(uint slot) + { + if (IsConnected) + { + SendCommand("MODULE UNLOADVSHPLUGS " + string.Format("{0}", slot)); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + else + { + throw new Exception("PS3MAPI not connected!"); + } + } + //---------------------------------------------------------------------------------------- + internal static void Fail() + { + Fail(new Exception("[" + eResponseCode + "] " + sResponse)); + } + internal static void Fail(Exception e) + { + Disconnect(); + throw e; + } + internal static void SetBinaryMode(bool bMode) + { + SendCommand("TYPE" + ((bMode) ? " I" : " A")); + switch (eResponseCode) + { + case PS3MAPI_ResponseCode.RequestSuccessful: + case PS3MAPI_ResponseCode.CommandOK: + break; + default: + Fail(); + break; + } + } + internal static void OpenDataSocket() + { + string[] pasv; + string sServer; + int iPort; + Connect(); + SendCommand("PASV"); + if (eResponseCode != PS3MAPI_ResponseCode.EnteringPassiveMode) + { + Fail(); + } + try + { + int i1, i2; + i1 = sResponse.IndexOf('(') + 1; + i2 = sResponse.IndexOf(')') - i1; + pasv = sResponse.Substring(i1, i2).Split(','); + } + catch (Exception) + { + Fail(new Exception("Malformed PASV response: " + sResponse)); + throw new Exception("Malformed PASV response: " + sResponse); + } + + if (pasv.Length < 6) + { + Fail(new Exception("Malformed PASV response: " + sResponse)); + } + + sServer = String.Format("{0}.{1}.{2}.{3}", pasv[0], pasv[1], pasv[2], pasv[3]); + iPort = (int.Parse(pasv[4]) << 8) + int.Parse(pasv[5]); + try + { + CloseDataSocket(); + data_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + data_ipEndPoint = new IPEndPoint(Dns.GetHostByName(sServerIP).AddressList[0], iPort); + data_sock.Connect(data_ipEndPoint); + } + catch (Exception e) + { + throw new Exception("Failed to connect for data transfer: " + e.Message); + } + } + internal static void ConnectDataSocket() + { + if (data_sock != null) // already connected (always so if passive mode) + return; + try + { + data_sock = listening_sock.Accept(); // Accept is blocking + listening_sock.Close(); + listening_sock = null; + if (data_sock == null) + { + throw new Exception("Winsock error: " + + Convert.ToString(System.Runtime.InteropServices.Marshal.GetLastWin32Error())); + } + } + catch (Exception ex) + { + throw new Exception("Failed to connect for data transfer: " + ex.Message); + } + } + internal static void CloseDataSocket() + { + if (data_sock != null) + { + if (data_sock.Connected) + { + data_sock.Close(); + } + data_sock = null; + } + data_ipEndPoint = null; + } + internal static void ReadResponse() + { + string sBuffer; + sMessages = ""; + while (true) + { + sBuffer = GetLineFromBucket(); + if (Regex.Match(sBuffer, "^[0-9]+ ").Success) + { + sResponse = sBuffer.Substring(4).Replace("\r", "").Replace("\n", ""); + eResponseCode = (PS3MAPI_ResponseCode)int.Parse(sBuffer.Substring(0, 3)); + sLog = sLog + "RESPONSE CODE: " + eResponseCode + Environment.NewLine; + sLog = sLog + "RESPONSE MSG: " + sResponse + Environment.NewLine + Environment.NewLine; + break; + } + else + { + sMessages += Regex.Replace(sBuffer, "^[0-9]+-", "") + "\n"; + } + } + } + internal static void SendCommand(string sCommand) + { + sLog = sLog + "COMMAND: " + sCommand + Environment.NewLine; + Connect(); + Byte[] byCommand = Encoding.ASCII.GetBytes((sCommand + "\r\n").ToCharArray()); + main_sock.Send(byCommand, byCommand.Length, 0); + ReadResponse(); + } + internal static void FillBucket() + { + Byte[] bytes = new Byte[512]; + long lBytesRecieved; + int iMilliSecondsPassed = 0; + while (main_sock.Available < 1) + { + System.Threading.Thread.Sleep(50); + iMilliSecondsPassed += 50; + + if (iMilliSecondsPassed > Timeout) // Prevents infinite loop + { + Fail(new Exception("Timed out waiting on server to respond.")); + } + } + while (main_sock.Available > 0) + { + // gives any further data not yet received, a small chance to arrive + lBytesRecieved = main_sock.Receive(bytes, 512, 0); + sBucket += Encoding.ASCII.GetString(bytes, 0, (int)lBytesRecieved); + System.Threading.Thread.Sleep(50); + } + } + internal static string GetLineFromBucket() + { + string sBuffer = ""; + int i = sBucket.IndexOf('\n'); + + while (i < 0) + { + FillBucket(); + i = sBucket.IndexOf('\n'); + } + + sBuffer = sBucket.Substring(0, i); + sBucket = sBucket.Substring(i + 1); + + return sBuffer; + } + } + #endregion PS3MAPI_Client_Server + +} \ No newline at end of file diff --git a/UnionPatcher/IDPSHelper.cs b/UnionPatcher/IDPSHelper.cs new file mode 100644 index 0000000..2498ed6 --- /dev/null +++ b/UnionPatcher/IDPSHelper.cs @@ -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)); + } +} \ No newline at end of file diff --git a/UnionPatcher/RemotePatch.cs b/UnionPatcher/RemotePatch.cs new file mode 100644 index 0000000..d2d90c1 --- /dev/null +++ b/UnionPatcher/RemotePatch.cs @@ -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 GetUsers(string ps3Ip, string user, string pass) + { + Console.WriteLine("Getting users..."); + + Dictionary 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 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); + } +} \ No newline at end of file