Compare commits

..

No commits in common. "master" and "v2.0.6" have entirely different histories.

14 changed files with 61 additions and 178 deletions

View file

@ -14,17 +14,17 @@ jobs:
fail-fast: false
matrix:
os:
- { prettyName: Windows, platform: windows-latest, configurationName: Windows, extraArgs: "", buildPath: "Release/net8.0-windows/publish"}
- { prettyName: Linux, platform: ubuntu-latest, configurationName: Linux, extraArgs: "", buildPath: "Release/net8.0/publish"}
- { prettyName: Windows, platform: windows-latest, configurationName: Windows, extraArgs: "", buildPath: "Release/net6.0-windows/publish"}
- { prettyName: Linux, platform: ubuntu-latest, configurationName: Linux, extraArgs: "", buildPath: "Release/net6.0/publish"}
# - { prettyName: MacOS, platform: ubuntu-latest, configurationName: Release, platform: osx-x64 }
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install .NET 8.0
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: "8.0.x"
dotnet-version: "6.0.x"
- name: Compile for ${{ matrix.os.prettyName }}
run: dotnet publish -c ${{ matrix.os.configurationName }} ${{ matrix.os.extraArgs }}

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>UnionPatcher</AssemblyName>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>linux-x64</RuntimeIdentifiers>
<AssemblyName>LBPUnion.UnionPatcher.Gui.Linux</AssemblyName>
<RootNamespace>LBPUnion.UnionPatcher.Gui.Linux</RootNamespace>

View file

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.lbpunion.unionpatcher</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright LBP Union 2024©</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleIconFile</key>
<string>Icon.icns</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleName</key>
<string>UnionPatcher</string>
<key>CFBundleExecutable</key>
<string>LBPUnion.UnionPatcher.Gui.MacOS</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSRequiresAquaSystemAppearance</key>
<string>False</string>
</dict>
</plist>

View file

@ -2,13 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>osx-x64;osx-arm64</RuntimeIdentifiers>
<AssemblyName>LBPUnion.UnionPatcher.Gui.MacOS</AssemblyName>
<RootNamespace>LBPUnion.UnionPatcher.Gui.MacOS</RootNamespace>
<ApplicationIcon>Icon64.ico</ApplicationIcon>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
<ItemGroup>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<RootNamespace>LBPUnion.UnionPatcher.Gui.Windows</RootNamespace>
<AssemblyName>LBPUnion.UnionPatcher.Gui.Windows</AssemblyName>
<ApplicationIcon>Icon64.ico</ApplicationIcon>

View file

@ -1,12 +1,8 @@
using System;
using System.IO;
using System.Reflection;
using System.Text;
using Eto;
using Eto.Drawing;
using Eto.Forms;
namespace LBPUnion.UnionPatcher.Gui.Forms;
namespace LBPUnion.UnionPatcher.Gui.Forms;
public class ModeSelectionForm : Form {
#region UI
@ -27,32 +23,10 @@ public class ModeSelectionForm : Form {
new TableCell(new Button(openFilePatcher) { Text = "File Patch (PS3/RPCS3)" })
),
},
};
};
}
private void openRemotePatcher(object sender, EventArgs e)
{
// If we're on macOS then set the CWD to the app bundle MacOS folder, so that SCETool can be found.
if (OSUtil.GetPlatform() == OSPlatform.OSX) Directory.SetCurrentDirectory(OSUtil.GetExecutablePath());
if (!Directory.Exists($"{OSUtil.GetExecutablePath()}/scetool"))
{
// This will always occur on macOS, so don't show this message for macOS users.
if (OSUtil.GetPlatform() != OSPlatform.OSX) Gui.CreateOkDialog("Workaround Triggered", ".NET could not locate the required files, triggering workaround.");
Gui.CreateOkDialog("Workaround", "UnionPatcher RemotePatcher requires a staging folder on macOS or in special circumstances on Windows, please set this to the directory of the UnionPatcher app or executable!");
SelectFolderDialog dialog = new SelectFolderDialog();
if (dialog.ShowDialog(this) != DialogResult.Ok)
{
Gui.CreateOkDialog("Workaround", "User did not specify a staging folder, aborting!");
return;
}
Directory.SetCurrentDirectory(dialog.Directory);
if (!Directory.Exists("scetool"))
{
Gui.CreateOkDialog("Workaround", "Invalid folder, remember to set the folder to the directory of the UnionPatcher app or executable!");
return;
}
}
private void openRemotePatcher(object sender, EventArgs e) {
RemotePatchForm rpForm = new RemotePatchForm();
rpForm.Show();
rpForm.Closed += OnSubFormClose;

View file

@ -1,4 +1,5 @@
using Eto.Forms;
using Eto.Drawing;
using Eto.Forms;
using LBPUnion.UnionPatcher.Gui.Forms;
namespace LBPUnion.UnionPatcher.Gui;
@ -9,7 +10,7 @@ public static class Gui {
}
public static void CreateOkDialog(string title, string errorMessage) {
MessageBox.Show(errorMessage, title, MessageBoxButtons.OK);
MessageBox.Show(errorMessage, title, MessageBoxButtons.OK, MessageBoxType.Information);
}
public static bool CreateConfirmationDialog(string title, string errorMessage) {
DialogResult result = MessageBox.Show(errorMessage, title, MessageBoxButtons.YesNo, MessageBoxType.Question);

View file

@ -7,11 +7,11 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' != 'Windows' ">
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Windows' ">
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
</PropertyGroup>
<ItemGroup>

View file

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
namespace LBPUnion.UnionPatcher
@ -32,17 +29,6 @@ namespace LBPUnion.UnionPatcher
return EnumeratePlatforms().FirstOrDefault(p
=> RuntimeInformation.IsOSPlatform(p.Value.RuntimePlatform))?.Platform ?? default;
}
public static string GetExecutablePath()
{
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (string.IsNullOrEmpty(path))
path = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName);
if (string.IsNullOrEmpty(path))
path = AppContext.BaseDirectory;
return path;
}
}
}

View file

@ -91,23 +91,16 @@ public class RemotePatch
{
Console.WriteLine("Restoring original EBOOT.BIN from EBOOT.BIN.BAK");
string workingDir = ".";
if (OSUtil.GetPlatform() == OSPlatform.OSX)
{
workingDir = $"{Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)}/UnionPatcher";
Directory.CreateDirectory(workingDir);
}
// Create a simple directory structure
Directory.CreateDirectory($@"{workingDir}/eboot");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}/original");
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", @$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN.BAK", user, pass);
FTP.UploadFile(@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN.BAK", $"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", 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
{
@ -118,13 +111,6 @@ public class RemotePatch
public void PSNEBOOTRemotePatch(string ps3ip, string gameID, string serverURL, string user, string pass)
{
Console.WriteLine("Detected Digital Copy - Running in Full Mode");
string workingDir = ".";
if (OSUtil.GetPlatform() == OSPlatform.OSX)
{
workingDir = $"{Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)}/UnionPatcher";
Directory.CreateDirectory(workingDir);
}
string idps = "";
string contentID = "";
@ -135,25 +121,25 @@ public class RemotePatch
this._ps3Mapi.PS3.Notify("UnionRemotePatcher Connected! Patching...");
// Create simple directory structure
Directory.CreateDirectory($@"{workingDir}/rifs");
Directory.CreateDirectory($@"{workingDir}/eboot");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}/original");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}/patched");
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",
@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN", user, pass);
@$"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(@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN",
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));
File.WriteAllBytes(@"data/idps", IDPSHelper.StringToByteArray(idps));
// Scan the users on the system
users = GetUsers(ps3ip, user, pass);
@ -167,12 +153,12 @@ public class RemotePatch
$"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata/", user, pass))
if (fileName.Contains(gameID))
{
FTP.DownloadFile($"ftp://{ps3ip}/dev_hdd0/home/{currentUser}/exdata/act.dat", $@"{workingDir}/data/act.dat",
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}",
@$"{workingDir}/rifs/{fileName}", user, pass);
@$"rifs/{fileName}", user, pass);
contentID = fileName.Substring(0, fileName.Length - 4);
@ -182,10 +168,10 @@ public class RemotePatch
}
// Finally, let's decrypt the EBOOT.BIN
LaunchSCETool($" -v -d \"{Path.GetFullPath(@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN")}\" \"{Path.GetFullPath(@$"{workingDir}/eboot/{gameID}/original/EBOOT.ELF")}\"");
LaunchSCETool($" -v -d \"{Path.GetFullPath(@$"eboot/{gameID}/original/EBOOT.BIN")}\" \"{Path.GetFullPath(@$"eboot/{gameID}/original/EBOOT.ELF")}\"");
// Now, patch the EBOOT;
Patcher.PatchFile($"{workingDir}/eboot/{gameID}/original/EBOOT.ELF", serverURL, $"{workingDir}/eboot/{gameID}/patched/EBOOT.ELF");
Patcher.PatchFile($"eboot/{gameID}/original/EBOOT.ELF", serverURL, $"eboot/{gameID}/patched/EBOOT.ELF");
// Encrypt the EBOOT (PSN)
LaunchSCETool($"--verbose " +
@ -205,10 +191,10 @@ public class RemotePatch
$" --np-app-type=SPRX" +
$" --np-content-id={contentID}" +
$" --np-real-fname=EBOOT.BIN" +
$" --encrypt {workingDir}/eboot/{gameID}/patched/EBOOT.ELF {workingDir}/eboot/{gameID}/patched/EBOOT.BIN");
$" --encrypt eboot/{gameID}/patched/EBOOT.ELF eboot/{gameID}/patched/EBOOT.BIN");
// And upload the encrypted, patched EBOOT to the system.
FTP.UploadFile(@$"{workingDir}/eboot/{gameID}/patched/EBOOT.BIN",
FTP.UploadFile(@$"eboot/{gameID}/patched/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", user, pass);
}
@ -217,45 +203,38 @@ public class RemotePatch
{
Console.WriteLine("Detected Disc Copy - Running in Simplified Mode");
string workingDir = ".";
if (OSUtil.GetPlatform() == OSPlatform.OSX)
{
workingDir = $"{Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)}/UnionPatcher";
Directory.CreateDirectory(workingDir);
}
// Create a simple directory structure
Directory.CreateDirectory($@"{workingDir}/eboot");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}/original");
Directory.CreateDirectory($@"{workingDir}/eboot/{gameID}/patched");
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",
@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN", user, pass);
@$"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(@$"{workingDir}/eboot/{gameID}/original/EBOOT.BIN",
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"))
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 {workingDir}/eboot/{gameID}/original/EBOOT.BIN {workingDir}/eboot/{gameID}/original/EBOOT.ELF");
LaunchSCETool($"-v -d eboot/{gameID}/original/EBOOT.BIN eboot/{gameID}/original/EBOOT.ELF");
// Now, patch the EBOOT;
Patcher.PatchFile($"{workingDir}/eboot/{gameID}/original/EBOOT.ELF", serverURL, $"{workingDir}/eboot/{gameID}/patched/EBOOT.ELF");
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(@$"{workingDir}/eboot/{gameID}/patched/EBOOT.ELF")}\" \"{Path.GetFullPath(@$"{workingDir}/eboot/{gameID}/patched/EBOOT.BIN")}\"");
$" -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(@$"{workingDir}/eboot/{gameID}/patched/EBOOT.BIN",
FTP.UploadFile(@$"eboot/{gameID}/patched/EBOOT.BIN",
$"ftp://{ps3ip}/dev_hdd0/game/{gameID}/USRDIR/EBOOT.BIN", user, pass);
}
}

View file

@ -3,7 +3,7 @@
<AssemblyName>LBPUnion.UnionPatcher</AssemblyName>
<RootNamespace>LBPUnion.UnionPatcher</RootNamespace>
<Configurations>Debug;Release;Windows</Configurations>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Platforms>AnyCPU</Platforms>
<ApplicationIcon>Icon64.ico</ApplicationIcon>
</PropertyGroup>

View file

@ -1,4 +1,4 @@
mkdir -p builds
mkdir -p builds;
#dotnet clean;
dotnet publish -c Windows -r win-x64 --self-contained
@ -13,19 +13,19 @@ dotnet publish -c MacOS -r osx-arm64 --self-contained
# $1: Name.zip
# $2: Path to zip
function createBuild() {
currentDirectory=$(pwd)
cd $2 || return 1
zip -r "$1" *
cd $currentDirectory || return 1
mv "$2/$1" builds/
currentDirectory=$(pwd)
cd $2 || return 1;
zip -r "$1" *;
cd $currentDirectory || return 1;
mv "$2/$1" builds/
}
createBuild "UnionPatcher-Windows-x64.zip" "UnionPatcher.Gui.Windows/bin/Release/net8.0-windows/win-x64/publish/"
createBuild "UnionPatcher-Windows-x64.zip" "UnionPatcher.Gui.Windows/bin/Release/net6.0-windows/win-x64/publish/"
createBuild "UnionPatcher-Linux-x64.zip" "UnionPatcher.Gui.Linux/bin/Release/net8.0/linux-x64/publish/"
createBuild "UnionPatcher-Linux-arm.zip" "UnionPatcher.Gui.Linux/bin/Release/net8.0/linux-arm/publish/"
createBuild "UnionPatcher-Linux-arm64.zip" "UnionPatcher.Gui.Linux/bin/Release/net8.0/linux-arm64/publish/"
createBuild "UnionPatcher-Linux-x64.zip" "UnionPatcher.Gui.Linux/bin/Release/net6.0/linux-x64/publish/"
createBuild "UnionPatcher-Linux-arm.zip" "UnionPatcher.Gui.Linux/bin/Release/net6.0/linux-arm/publish/"
createBuild "UnionPatcher-Linux-arm64.zip" "UnionPatcher.Gui.Linux/bin/Release/net6.0/linux-arm64/publish/"
# CODESIGN_IDENTITY is the certificate that you want to use for codesigning for mac, if not present then will not be signed
./build-mac.sh $CODESIGN_IDENTITY
createBuild "UnionPatcher-macOS-x64.zip" "UnionPatcher.Gui.MacOS/bin/Release/net6.0/osx-x64/publish/"
createBuild "UnionPatcher-macOS-arm64.zip" "UnionPatcher.Gui.MacOS/bin/Release/net6.0/osx-arm64/publish/"

View file

@ -1,21 +0,0 @@
# Script to build UnionPatcher for mac, builds a universal binary, and zips it up. also codesigns if $1 is specified
dotnet clean
dotnet publish UnionPatcher.Gui.MacOS --configuration Release /p:Platform="Any CPU" --self-contained -o macbuild
dotnet publish UnionPatcher.Gui.MacOS --configuration Release /p:Platform="Any CPU" --arch x64 --self-contained -o macbuildx86
rm -rf macbuilduniversal
mkdir macbuilduniversal
cp -r macbuild/UnionPatcher.Gui.MacOS.app macbuilduniversal/UnionPatcher.app
cp UnionPatcher.Gui.MacOS/Info.plist macbuilduniversal/UnionPatcher.app/Contents/Info.plist
rm -rf macbuilduniversal/UnionPatcher.app/Contents/MacOS/scetool/linux*
rm -rf macbuilduniversal/UnionPatcher.app/Contents/MacOS/scetool/win*
lipo -create -output macbuilduniversal/UnionPatcher.app/Contents/MacOS/LBPUnion.UnionPatcher.Gui.MacOS macbuildx86/LBPUnion.UnionPatcher.Gui.MacOS macbuild/LBPUnion.UnionPatcher.Gui.MacOS
touch macbuilduniversal/UnionPatcher.app
if [ -z ${1+x} ]; then
codesign -f --deep -s "$1" macbuilduniversal/UnionPatcher.app
fi
cd macbuilduniversal
zip -r UnionPatcher-macOS-universal.zip UnionPatcher.app