mirror of
https://github.com/xeghosted/OrbisForge.git
synced 2026-10-06 09:00:41 +02:00
Add optional FTP deploy-to-console (CLI flags + GUI checkbox, off by default)
This commit is contained in:
1 parent
66e8bedf23
commit
96225e370e
8 files changed
+195
-12
No files matched your search
@@ -27,8 +27,13 @@ A Windows GUI wizard and a cross-platform CLI both scaffold the same project lay
|
||||
target helpers), a `build.bat` that shells into WSL, and a `build.sh` for native Linux/WSL.
|
||||
- **Automatic sanity check** — every packaged target runs `readoelf` over its own output as a
|
||||
post-build step, catching malformed builds before you ever copy them to hardware.
|
||||
- **GUI wizard (Windows)** — pick a name, type, output folder, and optional `.pkg` packaging;
|
||||
detects your WSL distro and the OpenOrbis toolchain automatically.
|
||||
- **Optional FTP deploy** — `deploy.bat`/`deploy.sh` (curl-based, no extra dependency) upload the
|
||||
built artifact straight to your PS4's homebrew FTP server. Off by default; opt in via `--deploy-host`
|
||||
or the GUI checkbox. No credentials baked in by design — most PS4 FTP servers run anonymous.
|
||||
- **GUI wizard (Windows)** — pick a name, type, output folder, optional `.pkg` packaging, and
|
||||
optional FTP deploy target; detects your WSL distro and the OpenOrbis toolchain automatically.
|
||||
- **VS Code integration** — generated projects include a build task (Ctrl+Shift+B) and IntelliSense
|
||||
configuration for the toolchain's unusual target triple.
|
||||
- **CLI scaffolder (Windows + Linux + WSL)** — `orbisforge new --name foo --type sprx`.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -16,12 +16,17 @@ var help = """
|
||||
when running on Windows (where that env var normally
|
||||
only exists inside WSL). Point this at the toolchain's
|
||||
\\wsl.localhost\... path if using --pkg from Windows.
|
||||
--deploy-host <host> Console IP/hostname to FTP the built artifact to. Presence
|
||||
of this flag is what enables deploy.bat/deploy.sh generation.
|
||||
--deploy-port <port> FTP port (default: 21)
|
||||
--deploy-path <path> Remote directory to upload into (default: /)
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
orbisforge new --name hello_cmake --type exe
|
||||
orbisforge new --name my_plugin --type sprx --output E:\Projects\PS4
|
||||
orbisforge new --name my_game --type exe --pkg
|
||||
orbisforge new --name my_game --type exe --deploy-host 192.168.1.50 --deploy-path /data/homebrew/my_game/
|
||||
""";
|
||||
|
||||
if (args.Length == 0 || args[0] is "-h" or "--help" or "help")
|
||||
@@ -42,6 +47,9 @@ string outputDir = Directory.GetCurrentDirectory();
|
||||
bool includePackaging = false;
|
||||
string? ptype = null;
|
||||
string? toolchainPath = null;
|
||||
string? deployHost = null;
|
||||
int deployPort = 21;
|
||||
string deployPath = "/";
|
||||
|
||||
for (int i = 1; i < args.Length; i++)
|
||||
{
|
||||
@@ -65,6 +73,15 @@ for (int i = 1; i < args.Length; i++)
|
||||
case "--toolchain-path":
|
||||
toolchainPath = RequireValue(args, ref i, "--toolchain-path");
|
||||
break;
|
||||
case "--deploy-host":
|
||||
deployHost = RequireValue(args, ref i, "--deploy-host");
|
||||
break;
|
||||
case "--deploy-port":
|
||||
deployPort = int.Parse(RequireValue(args, ref i, "--deploy-port"));
|
||||
break;
|
||||
case "--deploy-path":
|
||||
deployPath = RequireValue(args, ref i, "--deploy-path");
|
||||
break;
|
||||
case "-h":
|
||||
case "--help":
|
||||
Console.WriteLine(help);
|
||||
@@ -101,6 +118,9 @@ var options = new ProjectOptions
|
||||
IncludePackaging = includePackaging,
|
||||
PType = ptype,
|
||||
ToolchainPathOverride = toolchainPath,
|
||||
DeployHost = deployHost,
|
||||
DeployPort = deployPort,
|
||||
DeployRemotePath = deployPath,
|
||||
};
|
||||
|
||||
var result = ProjectGenerator.Generate(options);
|
||||
|
||||
@@ -29,6 +29,16 @@ public static class OrbisTargetTypeExtensions
|
||||
public static bool IsLibrary(this OrbisTargetType type) =>
|
||||
type is OrbisTargetType.Prx or OrbisTargetType.Sprx;
|
||||
|
||||
/// <summary>Path to the packaged artifact create-fself produces, relative to the
|
||||
/// project root (i.e. under the CMake build directory).</summary>
|
||||
public static string ArtifactRelativePath(this OrbisTargetType type, string name) => type switch
|
||||
{
|
||||
OrbisTargetType.Exe => "build/eboot.bin",
|
||||
OrbisTargetType.Prx => $"build/{name}.prx",
|
||||
OrbisTargetType.Sprx => $"build/{name}.sprx",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
public static bool TryParse(string text, out OrbisTargetType type)
|
||||
{
|
||||
switch (text.Trim().ToUpperInvariant())
|
||||
|
||||
@@ -48,6 +48,12 @@ public static class ProjectGenerator
|
||||
CopyPackagingAssets(root, options.ToolchainPathOverride, files, warnings);
|
||||
}
|
||||
|
||||
if (options.IncludeDeploy)
|
||||
{
|
||||
WriteFile(Path.Combine(root, "deploy.sh"), Unix(DeployScriptsTemplate.BuildSh(options)), files);
|
||||
WriteFile(Path.Combine(root, "deploy.bat"), Windows(DeployScriptsTemplate.BuildBat(options)), files);
|
||||
}
|
||||
|
||||
return new GeneratedProject
|
||||
{
|
||||
RootDirectory = root,
|
||||
|
||||
@@ -27,6 +27,21 @@ public sealed class ProjectOptions
|
||||
/// \\wsl.localhost\... UNC equivalent here instead.</summary>
|
||||
public string? ToolchainPathOverride { get; set; }
|
||||
|
||||
/// <summary>Console IP/hostname to deploy the built artifact to via FTP. Null/empty
|
||||
/// disables deploy script generation entirely -- this is the opt-in flag, there's no
|
||||
/// separate boolean, since a deploy target without a host is meaningless. Deliberately
|
||||
/// no username/password fields: most PS4 homebrew FTP servers run anonymous, and baking
|
||||
/// credentials into a script that might get committed to git is bad hygiene -- edit the
|
||||
/// generated script by hand if your setup needs auth.</summary>
|
||||
public string? DeployHost { get; set; }
|
||||
|
||||
public int DeployPort { get; set; } = 21;
|
||||
|
||||
/// <summary>Remote directory the artifact is uploaded into, e.g. "/data/homebrew/my_game/".</summary>
|
||||
public string DeployRemotePath { get; set; } = "/";
|
||||
|
||||
public bool IncludeDeploy => !string.IsNullOrWhiteSpace(DeployHost);
|
||||
|
||||
public string SanitizedName => Sanitize(Name);
|
||||
|
||||
private static string Sanitize(string name)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace OrbisForge.Core.Templates;
|
||||
|
||||
internal static class DeployScriptsTemplate
|
||||
{
|
||||
// Uses curl (built into Windows 10 1803+/System32, and present on essentially every
|
||||
// Linux/WSL install) for a single dependency-free tool on both platforms, rather than
|
||||
// hand-rolling FTP with FtpWebRequest/lftp/ncftp. --ftp-create-dirs makes the first
|
||||
// deploy work even if the remote directory doesn't exist yet. No credentials here on
|
||||
// purpose -- see ProjectOptions.DeployHost's doc comment.
|
||||
public static string BuildBat(ProjectOptions options)
|
||||
{
|
||||
var artifact = options.Type.ArtifactRelativePath(options.SanitizedName).Replace('/', '\\');
|
||||
return $"""
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "SCRIPTDIR=%~dp0"
|
||||
if "%SCRIPTDIR:~-1%"=="\" set "SCRIPTDIR=%SCRIPTDIR:~0,-1%"
|
||||
|
||||
set "ARTIFACT=%SCRIPTDIR%\{artifact}"
|
||||
set "DEPLOY_URL=ftp://{options.DeployHost}:{options.DeployPort}{options.DeployRemotePath}"
|
||||
|
||||
if not exist "%ARTIFACT%" (
|
||||
echo Build artifact not found: %ARTIFACT%
|
||||
echo Build the project first ^(build.bat^).
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
curl -T "%ARTIFACT%" "%DEPLOY_URL%" --ftp-create-dirs
|
||||
""";
|
||||
}
|
||||
|
||||
public static string BuildSh(ProjectOptions options)
|
||||
{
|
||||
var artifact = options.Type.ArtifactRelativePath(options.SanitizedName);
|
||||
return $$"""
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
ARTIFACT="{{artifact}}"
|
||||
DEPLOY_URL="ftp://{{options.DeployHost}}:{{options.DeployPort}}{{options.DeployRemotePath}}"
|
||||
|
||||
if [ ! -f "$ARTIFACT" ]; then
|
||||
echo "Build artifact not found: $ARTIFACT" >&2
|
||||
echo "Build the project first (bash build.sh)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -T "$ARTIFACT" "$DEPLOY_URL" --ftp-create-dirs
|
||||
""";
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,26 @@ internal static class ReadmeTemplate
|
||||
public static string Render(ProjectOptions options)
|
||||
{
|
||||
var name = options.SanitizedName;
|
||||
var outputs = options.Type switch
|
||||
{
|
||||
OrbisTargetType.Exe => "build/eboot.bin",
|
||||
OrbisTargetType.Prx => $"build/{name}.prx",
|
||||
OrbisTargetType.Sprx => $"build/{name}.sprx",
|
||||
_ => "build/",
|
||||
};
|
||||
var outputs = options.Type.ArtifactRelativePath(name);
|
||||
|
||||
var pkgNote = options.IncludePackaging && options.Type == OrbisTargetType.Exe
|
||||
? "\nTo build the full retail-style installer: `cmake --build build --target {0}_pkg` (Windows: `wsl -- bash -c \"cd '<wsl-path>' && cmake --build build --target {0}_pkg\"`). Produces `build/pkg_root/*.pkg`.\n"
|
||||
: "";
|
||||
pkgNote = string.Format(pkgNote, name);
|
||||
|
||||
var deployNote = options.IncludeDeploy
|
||||
? $"""
|
||||
|
||||
## Deploying
|
||||
|
||||
`deploy.bat` / `bash deploy.sh` FTP the built `{outputs}` to `{options.DeployHost}:{options.DeployPort}{options.DeployRemotePath}`.
|
||||
Build first -- deploy just uploads whatever's already in `build/`. No credentials are
|
||||
baked in (most PS4 homebrew FTP servers run anonymous); edit the script by hand if yours
|
||||
needs auth.
|
||||
|
||||
"""
|
||||
: "";
|
||||
|
||||
return $$"""
|
||||
# {{options.Name}}
|
||||
|
||||
@@ -35,7 +42,7 @@ internal static class ReadmeTemplate
|
||||
**Windows:** double-click `build.bat`, or `build.bat clean` to wipe the build directory.
|
||||
|
||||
**Linux / WSL (native):** `bash build.sh`, or `bash build.sh clean` (use `bash` rather than `./build.sh` since files written from Windows don't carry the executable bit).
|
||||
{{pkgNote}}
|
||||
{{pkgNote}}{{deployNote}}
|
||||
## VS Code
|
||||
|
||||
Open this folder in VS Code and press **Ctrl+Shift+B** to build (`.vscode/tasks.json` runs
|
||||
@@ -54,7 +61,7 @@ internal static class ReadmeTemplate
|
||||
- `cmake/OrbisTargets.cmake` - `add_orbis_target()` / `add_orbis_package()` helpers
|
||||
- `CMakeLists.txt` - project definition
|
||||
- `.vscode/` - build task + IntelliSense configuration
|
||||
- `.gitignore` - excludes `build/`
|
||||
- `.gitignore` - excludes `build/`{{(options.IncludeDeploy ? "\n - `deploy.bat` / `deploy.sh` - FTP the built artifact to your PS4" : "")}}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ namespace OrbisForge.Gui
|
||||
private readonly SkinComboBox _typeBox = new SkinComboBox();
|
||||
private readonly SkinTextBox _outputBox = new SkinTextBox();
|
||||
private readonly CheckBox _pkgCheck = new CheckBox();
|
||||
private readonly CheckBox _deployCheck = new CheckBox();
|
||||
private readonly SkinTextBox _deployHostBox = new SkinTextBox();
|
||||
private readonly SkinTextBox _deployPortBox = new SkinTextBox();
|
||||
private readonly SkinTextBox _deployPathBox = new SkinTextBox();
|
||||
private readonly SkinLabel _envLabel = new SkinLabel();
|
||||
private readonly SkinButton _generateButton = new SkinButton();
|
||||
private readonly TextBox _statusBox = new TextBox();
|
||||
@@ -25,7 +29,7 @@ namespace OrbisForge.Gui
|
||||
public WizardForm()
|
||||
{
|
||||
Text = "OrbisForge — OpenOrbis PS4 Project Wizard";
|
||||
ClientSize = new Size(520, 460);
|
||||
ClientSize = new Size(520, 590);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
AutoScaleMode = AutoScaleMode.Dpi;
|
||||
AutoScaleDimensions = new SizeF(96F, 96F);
|
||||
@@ -69,6 +73,30 @@ namespace OrbisForge.Gui
|
||||
_pkgCheck.AutoSize = true;
|
||||
y += 28;
|
||||
|
||||
_deployCheck.Text = "Deploy to PS4 via FTP after build";
|
||||
_deployCheck.Location = new Point(left, y);
|
||||
_deployCheck.AutoSize = true;
|
||||
_deployCheck.CheckedChanged += (s, e) => UpdateDeployFieldsEnabled();
|
||||
y += 24;
|
||||
|
||||
var deployHostLabel = new SkinLabel { Text = "Host:", Location = new Point(left, y), AutoSize = true };
|
||||
var deployPortLabel = new SkinLabel { Text = "Port:", Location = new Point(left + 220, y), AutoSize = true };
|
||||
y += 20;
|
||||
_deployHostBox.Location = new Point(left, y);
|
||||
_deployHostBox.Size = new Size(200, 24);
|
||||
_deployHostBox.PlaceholderText = "192.168.1.100";
|
||||
_deployPortBox.Location = new Point(left + 220, y);
|
||||
_deployPortBox.Size = new Size(84, 24);
|
||||
_deployPortBox.Text = "21";
|
||||
y += 32;
|
||||
|
||||
var deployPathLabel = new SkinLabel { Text = "Remote path:", Location = new Point(left, y), AutoSize = true };
|
||||
y += 20;
|
||||
_deployPathBox.Location = new Point(left, y);
|
||||
_deployPathBox.Size = new Size(fieldWidth, 24);
|
||||
_deployPathBox.PlaceholderText = "/data/homebrew/my_project/";
|
||||
y += 36;
|
||||
|
||||
_envLabel.Location = new Point(left, y);
|
||||
_envLabel.AutoSize = true;
|
||||
_envLabel.Text = "Checking WSL / OpenOrbis toolchain…";
|
||||
@@ -95,11 +123,19 @@ namespace OrbisForge.Gui
|
||||
root.Controls.Add(_outputBox);
|
||||
root.Controls.Add(browseButton);
|
||||
root.Controls.Add(_pkgCheck);
|
||||
root.Controls.Add(_deployCheck);
|
||||
root.Controls.Add(deployHostLabel);
|
||||
root.Controls.Add(deployPortLabel);
|
||||
root.Controls.Add(_deployHostBox);
|
||||
root.Controls.Add(_deployPortBox);
|
||||
root.Controls.Add(deployPathLabel);
|
||||
root.Controls.Add(_deployPathBox);
|
||||
root.Controls.Add(_envLabel);
|
||||
root.Controls.Add(_generateButton);
|
||||
root.Controls.Add(_statusBox);
|
||||
|
||||
UpdatePkgCheckEnabled();
|
||||
UpdateDeployFieldsEnabled();
|
||||
Shown += (s, e) => DetectEnvironment();
|
||||
}
|
||||
|
||||
@@ -110,6 +146,14 @@ namespace OrbisForge.Gui
|
||||
if (!isExe) _pkgCheck.Checked = false;
|
||||
}
|
||||
|
||||
private void UpdateDeployFieldsEnabled()
|
||||
{
|
||||
bool enabled = _deployCheck.Checked;
|
||||
_deployHostBox.Enabled = enabled;
|
||||
_deployPortBox.Enabled = enabled;
|
||||
_deployPathBox.Enabled = enabled;
|
||||
}
|
||||
|
||||
private OrbisTargetType SelectedType()
|
||||
{
|
||||
switch (_typeBox.SelectedIndex)
|
||||
@@ -177,6 +221,26 @@ namespace OrbisForge.Gui
|
||||
toolchainOverride = _wsl.ToolchainUncPath;
|
||||
}
|
||||
|
||||
string deployHost = null;
|
||||
int deployPort = 21;
|
||||
string deployPath = "/";
|
||||
if (_deployCheck.Checked)
|
||||
{
|
||||
deployHost = _deployHostBox.Text.Trim();
|
||||
if (deployHost.Length == 0)
|
||||
{
|
||||
MessageBox.Show(this, "Enter a host/IP for FTP deploy, or uncheck the deploy option.", "OrbisForge", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
if (!int.TryParse(_deployPortBox.Text.Trim(), out deployPort) || deployPort <= 0)
|
||||
{
|
||||
MessageBox.Show(this, "FTP port must be a positive number.", "OrbisForge", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
deployPath = _deployPathBox.Text.Trim();
|
||||
if (deployPath.Length == 0) deployPath = "/";
|
||||
}
|
||||
|
||||
var options = new ProjectOptions
|
||||
{
|
||||
Name = name,
|
||||
@@ -184,6 +248,9 @@ namespace OrbisForge.Gui
|
||||
OutputDirectory = _outputBox.Text,
|
||||
IncludePackaging = includePkg,
|
||||
ToolchainPathOverride = toolchainOverride,
|
||||
DeployHost = deployHost,
|
||||
DeployPort = deployPort,
|
||||
DeployRemotePath = deployPath,
|
||||
};
|
||||
|
||||
try
|
||||
|
||||
Reference in new issue
Block a user