Add 'unsupported' homebrew support & 'XeUnshackle'

This commit is contained in:
Pdawg11239
2025-04-02 01:40:06 -04:00
parent ed41cf84d4
commit 55cfed148a
9 changed files with 83 additions and 42 deletions

View File

@@ -9,7 +9,6 @@ using Windows.Win32.Storage.FileSystem;
using static Windows.Win32.PInvoke; using static Windows.Win32.PInvoke;
using static BadBuilder.Formatter.Constants; using static BadBuilder.Formatter.Constants;
using static BadBuilder.Formatter.Utilities; using static BadBuilder.Formatter.Utilities;
using Windows.Win32.Foundation;
namespace BadBuilder.Formatter namespace BadBuilder.Formatter
{ {
@@ -17,7 +16,7 @@ namespace BadBuilder.Formatter
{ {
public static unsafe string FormatVolume(char driveLetter, long diskSize) public static unsafe string FormatVolume(char driveLetter, long diskSize)
{ {
if (diskSize < 32 * GB) if (diskSize < 31 * GB) // Just a safeguard to ensure that we never run into close calls with disk size.
{ {
Process process = new Process Process process = new Process
{ {

View File

@@ -33,18 +33,15 @@ namespace BadBuilder
); );
} }
static bool FormatDisk(List<DiskInfo> disks, string selectedDisk) static bool FormatDisk(DiskInfo disk)
{ {
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == selectedDisk);
bool ret = true; bool ret = true;
string output = string.Empty; string output = string.Empty;
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {selectedDisk}", async ctx => AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}", async ctx =>
{ {
if (diskIndex == -1) return;
ClearConsole(); ClearConsole();
output = DiskHelper.FormatDisk(disks[diskIndex]); output = DiskHelper.FormatDisk(disk);
if (output != string.Empty) ret = false; if (output != string.Empty) ret = false;
}); });

View File

@@ -24,9 +24,6 @@ namespace BadBuilder
await ArchiveHelper.ExtractFileAsync(item.name, item.path, task); await ArchiveHelper.ExtractFileAsync(item.name, item.path, task);
})); }));
}); });
string status = "[+]";
AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{filesToExtract.Count()}[/] files extracted.");
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace BadBuilder
); );
} }
public static List<HomebrewApp> ManageHomebrewApps() internal static List<HomebrewApp> ManageHomebrewApps()
{ {
var homebrewApps = new List<HomebrewApp>(); var homebrewApps = new List<HomebrewApp>();
@@ -37,11 +37,7 @@ namespace BadBuilder
var newApp = AddHomebrewApp(); var newApp = AddHomebrewApp();
if (newApp != null) if (newApp != null)
{
homebrewApps.Add(newApp.Value); homebrewApps.Add(newApp.Value);
AnsiConsole.Status()
.Start("Adding...", ctx => ctx.Spinner(Spinner.Known.Dots2));
}
break; break;
case "View Added Apps": case "View Added Apps":
@@ -85,31 +81,30 @@ namespace BadBuilder
string folderPath = AnsiConsole.Ask<string>("[#FFA500]Enter the folder path for the app:[/]"); string folderPath = AnsiConsole.Ask<string>("[#FFA500]Enter the folder path for the app:[/]");
if (!Directory.Exists(folderPath)) if (!Directory.Exists(folderPath))
{ {
AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.[/]"); AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.\n[/]");
return null; return null;
} }
string[] xexFiles = Directory.GetFiles(folderPath, "*.xex"); string[] xexFiles = Directory.GetFiles(folderPath, "*.xex");
if (xexFiles.Length == 0)
{
AnsiConsole.MarkupLine("[#ffac4d]No XEX files found in this folder.[/]");
return null;
}
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
string entryPoint = xexFiles.Length switch string entryPoint = xexFiles.Length switch
{ {
0 => AnsiConsole.Prompt( 0 => AnsiConsole.Prompt(
new TextPrompt<string>("[#ffac4d]No .xex files found.[/] Enter entry point:") new TextPrompt<string>("[grey]No .xex files found in this folder.[/] [#FFA500]Enter the path to the entry point:[/]")
.Validate(path => File.Exists(path) ? ValidationResult.Success() : ValidationResult.Error("File not found")) .Validate(path => File.Exists(path.Trim().Trim('"')) && Path.GetExtension(path.Trim().Trim('"')) == ".xex" ? ValidationResult.Success() : ValidationResult.Error("[#ffac4d]File not found or is not an XEX file.[/]\n"))
), ).Trim().Trim('"'),
1 => xexFiles[0], 1 => xexFiles[0],
_ => AnsiConsole.Prompt( _ => AnsiConsole.Prompt(
new SelectionPrompt<string>() new SelectionPrompt<string?>()
.Title("[#FFA500]Select entry point:[/]") .Title("[#FFA500]Select entry point:[/]")
.HighlightStyle(PeachStyle) .HighlightStyle(PeachStyle)
.AddChoices(xexFiles.Select(Path.GetFileName)) .AddChoices(xexFiles.Select(Path.GetFileName))
) )
}; };
#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
ClearConsole(); ClearConsole();

View File

@@ -7,7 +7,7 @@ namespace BadBuilder.Helpers
{ {
internal static class DiskHelper internal static class DiskHelper
{ {
public static List<DiskInfo> GetDisks() internal static List<DiskInfo> GetDisks()
{ {
var disks = new List<DiskInfo>(); var disks = new List<DiskInfo>();

View File

@@ -13,6 +13,7 @@ namespace BadBuilder.Helpers
List<string> repos = List<string> repos =
[ [
"grimdoomer/Xbox360BadUpdate", "grimdoomer/Xbox360BadUpdate",
"Byrom90/XeUnshackle",
"FreeMyXe/FreeMyXe" "FreeMyXe/FreeMyXe"
]; ];
@@ -28,6 +29,7 @@ namespace BadBuilder.Helpers
var name when name.Contains("Free", StringComparison.OrdinalIgnoreCase) => "FreeMyXe", var name when name.Contains("Free", StringComparison.OrdinalIgnoreCase) => "FreeMyXe",
var name when name.Contains("Tools", StringComparison.OrdinalIgnoreCase) => "BadUpdate Tools", var name when name.Contains("Tools", StringComparison.OrdinalIgnoreCase) => "BadUpdate Tools",
var name when name.Contains("BadUpdate", StringComparison.OrdinalIgnoreCase) => "BadUpdate", var name when name.Contains("BadUpdate", StringComparison.OrdinalIgnoreCase) => "BadUpdate",
var name when name.Contains("XeUnshackle", StringComparison.OrdinalIgnoreCase) => "XeUnshackle",
_ => asset.Name.Substring(0, asset.Name.Length - 4) _ => asset.Name.Substring(0, asset.Name.Length - 4)
}; };

View File

@@ -1,4 +1,5 @@
using System.Diagnostics; using Spectre.Console;
using System.Diagnostics;
namespace BadBuilder.Helpers namespace BadBuilder.Helpers
{ {
@@ -12,8 +13,8 @@ namespace BadBuilder.Helpers
{ {
FileName = xexToolPath, FileName = xexToolPath,
Arguments = $"-m r -r a \"{xexPath}\"", Arguments = $"-m r -r a \"{xexPath}\"",
RedirectStandardOutput = false, RedirectStandardOutput = true,
RedirectStandardError = false, RedirectStandardError = true,
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true
} }
@@ -21,6 +22,13 @@ namespace BadBuilder.Helpers
process.Start(); process.Start();
await process.WaitForExitAsync(); await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
string status = "[-]";
AnsiConsole.MarkupLineInterpolated($"\n[#FF7200]{status}[/] The program {Path.GetFileNameWithoutExtension(xexPath)} was unable to be patched. XexTool output:");
Console.WriteLine(process.StandardError.ReadToEnd());
}
} }
} }
} }

View File

@@ -25,6 +25,8 @@ namespace BadBuilder
static ActionQueue actionQueue = new(); static ActionQueue actionQueue = new();
static DiskInfo targetDisk = new("Z:\\", "Fixed", 0, "", 0, int.MaxValue); // Default values just incase.
static void Main(string[] args) static void Main(string[] args)
{ {
ShowWelcomeMessage(); ShowWelcomeMessage();
@@ -40,10 +42,13 @@ namespace BadBuilder
string selectedDisk = PromptDiskSelection(disks); string selectedDisk = PromptDiskSelection(disks);
TargetDriveLetter = selectedDisk.Substring(0, 3); TargetDriveLetter = selectedDisk.Substring(0, 3);
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == selectedDisk);
targetDisk = disks[diskIndex];
bool confirmation = PromptFormatConfirmation(selectedDisk); bool confirmation = PromptFormatConfirmation(selectedDisk);
if (confirmation) if (confirmation)
{ {
if (!FormatDisk(disks, selectedDisk)) continue; if (!FormatDisk(targetDisk)) continue;
break; break;
} }
} }
@@ -51,13 +56,21 @@ namespace BadBuilder
List<ArchiveItem> downloadedFiles = DownloadRequiredFiles().Result; List<ArchiveItem> downloadedFiles = DownloadRequiredFiles().Result;
ExtractFiles(downloadedFiles).Wait(); ExtractFiles(downloadedFiles).Wait();
ClearConsole();
string selectedDefaultApp = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Which program should be launched by BadUpdate?")
.HighlightStyle(GreenStyle)
.AddChoices(
"FreeMyXe",
"XeUnshackle"
)
);
AnsiConsole.MarkupLine("\n\n[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]")); AnsiConsole.MarkupLine("[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]"));
foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}")) foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}"))
{ {
var folderName = folder.Split("\\").Last(); switch (folder.Split("\\").Last())
switch (folderName)
{ {
case "XEXMenu": case "XEXMenu":
EnqueueMirrorDirectory( EnqueueMirrorDirectory(
@@ -68,6 +81,7 @@ namespace BadBuilder
break; break;
case "FreeMyXe": case "FreeMyXe":
if (selectedDefaultApp != "FreeMyXe") break;
EnqueueFileCopy( EnqueueFileCopy(
Path.Combine(folder, "FreeMyXe.xex"), Path.Combine(folder, "FreeMyXe.xex"),
Path.Combine(TargetDriveLetter, "BadUpdatePayload", "default.xex"), Path.Combine(TargetDriveLetter, "BadUpdatePayload", "default.xex"),
@@ -75,6 +89,17 @@ namespace BadBuilder
); );
break; break;
case "XeUnshackle":
if (selectedDefaultApp != "XeUnshackle") break;
string subFolderPath = Directory.GetDirectories(folder).FirstOrDefault();
File.Delete(Path.Combine(subFolderPath, "README - IMPORTANT.txt"));
EnqueueMirrorDirectory(
subFolderPath,
TargetDriveLetter,
9
);
break;
case "BadUpdate": case "BadUpdate":
actionQueue.EnqueueAction(async () => actionQueue.EnqueueAction(async () =>
{ {
@@ -82,7 +107,7 @@ namespace BadBuilder
writer.WriteLine("USB Storage Device"); writer.WriteLine("USB Storage Device");
using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "info.txt"))) using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "info.txt")))
writer.WriteLine("This drive was created with BadBuilder by Pdawg.\nFind more info here: https://github.com/Pdawg-bytes/BadBuilder"); writer.WriteLine($"This drive was created with BadBuilder by Pdawg.\nFind more info here: https://github.com/Pdawg-bytes/BadBuilder\nConfiguration: \n- BadUpdate target binary: {selectedDefaultApp}");
Directory.CreateDirectory(Path.Combine(TargetDriveLetter, "Apps")); Directory.CreateDirectory(Path.Combine(TargetDriveLetter, "Apps"));
await FileSystemHelper.MirrorDirectoryAsync(Path.Combine(folder, "Rock Band Blitz"), TargetDriveLetter); await FileSystemHelper.MirrorDirectoryAsync(Path.Combine(folder, "Rock Band Blitz"), TargetDriveLetter);
@@ -109,14 +134,18 @@ namespace BadBuilder
}, 6); }, 6);
break; break;
default: throw new Exception($"Unexpected directory in working folder: {folder}"); default: throw new Exception($"[-] Unexpected directory in working folder: {folder}");
} }
} }
actionQueue.ExecuteActionsAsync().Wait(); actionQueue.ExecuteActionsAsync().Wait();
File.AppendAllText(Path.Combine(TargetDriveLetter, "info.txt"), $"- Disk formatted using {(targetDisk.TotalSize < 31 * GB ? "Windows \"format.com\"" : "BadBuilder Large FAT32 formatter")}\n");
File.AppendAllText(Path.Combine(TargetDriveLetter, "info.txt"), $"- Disk total size: {targetDisk.TotalSize} bytes\n");
ClearConsole(); ClearConsole();
if (!PromptAddHomebrew()) if (!PromptAddHomebrew())
{ {
WriteHomebrewLog(1);
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]")); AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
Console.Write("\nPress any key to exit..."); Console.Write("\nPress any key to exit...");
Console.ReadKey(); Console.ReadKey();
@@ -134,12 +163,14 @@ namespace BadBuilder
await Task.WhenAll(homebrewApps.Select(async item => await Task.WhenAll(homebrewApps.Select(async item =>
{ {
await FileSystemHelper.MirrorDirectoryAsync(item.folder, Path.Combine(TargetDriveLetter, "Apps", item.name)); await FileSystemHelper.MirrorDirectoryAsync(item.folder, Path.Combine(TargetDriveLetter, "Apps", item.name));
await PatchHelper.PatchXexAsync(Path.Combine(TargetDriveLetter, "Apps", item.name, Path.GetFileName(item.entryPoint)), XexToolPath); await PatchHelper.PatchXexAsync(item.entryPoint, XexToolPath);
})); }));
}).Wait(); }).Wait();
WriteHomebrewLog(homebrewApps.Count + 1);
string status = "[+]"; string status = "[+]";
AnsiConsole.MarkupInterpolated($"\n[#76B900]{status}[/] [bold]{homebrewApps.Count()}[/] apps copied.\n"); AnsiConsole.MarkupLineInterpolated($"\n[#76B900]{status}[/] [bold]{homebrewApps.Count}[/] apps copied.");
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]")); AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
@@ -163,6 +194,13 @@ namespace BadBuilder
}, priority); }, priority);
} }
static void WriteHomebrewLog(int count)
{
string logPath = Path.Combine(TargetDriveLetter, "info.txt");
string logEntry = $"- {count} homebrew app(s) added (including Simple 360 NAND Flasher)\n";
File.AppendAllText(logPath, logEntry);
}
static void ShowWelcomeMessage() => AnsiConsole.Markup( static void ShowWelcomeMessage() => AnsiConsole.Markup(
""" """
@@ -173,12 +211,12 @@ namespace BadBuilder
[#CCE388] [/] [#CCE388] [/]
[#CCE388] [/] [#CCE388] [/]
[#76B900]v0.21a[/] [#76B900]v0.30[/]
Xbox 360 [#FF7200]BadUpdate[/] USB Builder Xbox 360 [#FF7200]BadUpdate[/] USB Builder
[#848589]Created by Pdawg[/] [#848589]Created by Pdawg[/]
[#76B900][/] [#76B900][/]
"""); """);
static string PromptForAction() => AnsiConsole.Prompt( static string PromptForAction() => AnsiConsole.Prompt(
new SelectionPrompt<string>() new SelectionPrompt<string>()

View File

@@ -7,5 +7,10 @@
internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\Extract"; internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\Extract";
internal const string ContentFolder = "Content\\0000000000000000\\"; internal const string ContentFolder = "Content\\0000000000000000\\";
internal const long KB = 1024L;
internal const long MB = 1048576L;
internal const long GB = 1073741824L;
internal const long TB = 1099511627776L;
} }
} }