Compare commits

..

9 Commits

Author SHA1 Message Date
993a27a33a Update default payload and bundle Aurora dashboard 2025-11-08 20:45:49 +01:00
Pdawg11239
fca7d4daa4 Update download links 2025-08-07 14:19:55 -04:00
Pdawg
d120ad1357 Prepare README for v0.3 2025-04-02 01:56:26 -04:00
Pdawg11239
52ee1324f2 Update XeXMenu name styling 2025-04-02 01:55:30 -04:00
Pdawg11239
d9a76cc483 Add 'unsupported' homebrew support & 'XeUnshackle' 2025-04-02 01:40:06 -04:00
Pdawg11239
39316e3078 Update link for RBB 2025-03-30 15:11:28 -04:00
Pdawg11239
e27b272aa6 Fix copying bug 2025-03-29 20:53:04 -04:00
Pdawg11239
ac42c67478 Add native format for small FAT32; fix MBR bugs 2025-03-29 20:06:48 -04:00
Pdawg
8505c06e66 Update README.md 2025-03-17 21:39:47 -04:00
15 changed files with 100 additions and 227 deletions

View File

@@ -5,7 +5,6 @@ using Windows.Win32.System.Ioctl;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.Win32.Storage.FileSystem; using Windows.Win32.Storage.FileSystem;
using System.Runtime.Versioning;
using static Windows.Win32.PInvoke; using static Windows.Win32.PInvoke;
using static BadBuilder.Formatter.Constants; using static BadBuilder.Formatter.Constants;
@@ -13,7 +12,6 @@ using static BadBuilder.Formatter.Utilities;
namespace BadBuilder.Formatter namespace BadBuilder.Formatter
{ {
[SupportedOSPlatform("windows")]
public static class DiskFormatter public static class DiskFormatter
{ {
public static unsafe string FormatVolume(char driveLetter, long diskSize) public static unsafe string FormatVolume(char driveLetter, long diskSize)
@@ -37,30 +35,30 @@ namespace BadBuilder.Formatter
process.WaitForExit(); process.WaitForExit();
if (process.ExitCode == 0) return ""; if (process.ExitCode == 0) return "";
else return Error($"Die native Formatierung ist mit dem Exitcode {process.ExitCode} fehlgeschlagen."); else return Error($"Native format failed with exit code: {process.ExitCode}");
} }
string devicePath = $"\\\\.\\{driveLetter}:"; string devicePath = $"\\\\.\\{driveLetter}:";
uint volumeID = GetVolumeID(); uint volumeID = GetVolumeID();
SafeFileHandle driveHandle = OpenDeviceHandle(devicePath); SafeFileHandle driveHandle = OpenDeviceHandle(devicePath);
if (driveHandle.IsInvalid) return Error("Gerät konnte nicht geöffnet werden. GetLastError: " + Marshal.GetLastWin32Error()); if (driveHandle.IsInvalid) return Error("Unable to open device. GetLastError: " + Marshal.GetLastWin32Error());
if (!EnableExtendedDASDIO(driveHandle) || !LockDevice(driveHandle)) if (!EnableExtendedDASDIO(driveHandle) || !LockDevice(driveHandle))
return Error($"Gerätezugriff konnte nicht initialisiert werden. GetLastError: {Marshal.GetLastWin32Error()}"); return Error($"Failed to initialize device access. GetLastError: {Marshal.GetLastWin32Error()}");
DISK_GEOMETRY diskGeometry; DISK_GEOMETRY diskGeometry;
if (!TryGetDiskGeometry(driveHandle, out diskGeometry)) if (!TryGetDiskGeometry(driveHandle, out diskGeometry))
return Error($"Datenträgergeometrie konnte nicht ermittelt werden. GetLastError: {Marshal.GetLastWin32Error()}"); return Error($"Failed to get disk geometry. GetLastError: {Marshal.GetLastWin32Error()}");
PARTITION_INFORMATION partitionInfo; PARTITION_INFORMATION partitionInfo;
bool isGPT = false; bool isGPT = false;
if (!TryGetPartitionInfo(driveHandle, ref diskGeometry, out partitionInfo, out isGPT)) if (!TryGetPartitionInfo(driveHandle, ref diskGeometry, out partitionInfo, out isGPT))
return Error($"Partitionsinformationen konnten nicht ermittelt werden. GetLastError: {Marshal.GetLastWin32Error()}"); return Error($"Failed to get partition information. GetLastError: {Marshal.GetLastWin32Error()}");
uint totalSectors = (uint)(partitionInfo.PartitionLength / diskGeometry.BytesPerSector); uint totalSectors = (uint)(partitionInfo.PartitionLength / diskGeometry.BytesPerSector);
if (!IsValidFAT32Size(totalSectors)) if (!IsValidFAT32Size(totalSectors))
return Error("Ungültige Laufwerksgröße für FAT32."); return Error("Invalid drive size for FAT32.");
FAT32BootSector bootSector = InitializeBootSector(diskGeometry, partitionInfo, totalSectors, volumeID); FAT32BootSector bootSector = InitializeBootSector(diskGeometry, partitionInfo, totalSectors, volumeID);
FAT32FsInfoSector fsInfo = InitializeFsInfo(); FAT32FsInfoSector fsInfo = InitializeFsInfo();
@@ -71,12 +69,12 @@ namespace BadBuilder.Formatter
return formatOutput; return formatOutput;
if (!UnlockDevice(driveHandle) || !DismountVolume(driveHandle)) if (!UnlockDevice(driveHandle) || !DismountVolume(driveHandle))
return Error($"Gerät konnte nicht freigegeben werden. GetLastError: {Marshal.GetLastWin32Error()}"); return Error($"Failed to release the device. GetLastError: {Marshal.GetLastWin32Error()}");
driveHandle.Dispose(); driveHandle.Dispose();
if (!SetVolumeLabel($"{driveLetter}:", "BADUPDATE")) if (!SetVolumeLabel($"{driveLetter}:", "BADUPDATE"))
return Error($"Volumenbezeichnung konnte nicht gesetzt werden. GetLastError: {Marshal.GetLastWin32Error()}"); return Error($"Unable to set volume label. GetLastError: {Marshal.GetLastWin32Error()}");
return string.Empty; return string.Empty;
} }
@@ -224,7 +222,7 @@ namespace BadBuilder.Formatter
uint clusterCount = userAreaSize / bootSector.SectorsPerCluster; uint clusterCount = userAreaSize / bootSector.SectorsPerCluster;
if (clusterCount < 65536 || clusterCount > 0x0FFFFFFF) if (clusterCount < 65536 || clusterCount > 0x0FFFFFFF)
return Error("Die Clusteranzahl des Laufwerks liegt außerhalb des gültigen Bereichs (65536 < clusterCount < 0x0FFFFFFF)."); return Error("The drive's cluster count is out of range (65536 < clusterCount < 0x0FFFFFFF)");
fsInfo.FreeClusterCount = clusterCount - 1; fsInfo.FreeClusterCount = clusterCount - 1;

View File

@@ -6,6 +6,7 @@ CloseHandle
VirtualAlloc VirtualAlloc
VirtualFree VirtualFree
SetVolumeLabelW SetVolumeLabelW
GUID
DISK_GEOMETRY DISK_GEOMETRY
PARTITION_INFORMATION PARTITION_INFORMATION
PARTITION_INFORMATION_EX PARTITION_INFORMATION_EX

View File

@@ -1,15 +1,12 @@
#pragma warning disable CA1416 using Windows.Win32.Foundation;
using Windows.Win32.Foundation;
using Windows.Win32.System.Memory; using Windows.Win32.System.Memory;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using static Windows.Win32.PInvoke; using static Windows.Win32.PInvoke;
using static BadBuilder.Formatter.Constants; using static BadBuilder.Formatter.Constants;
namespace BadBuilder.Formatter namespace BadBuilder.Formatter
{ {
[SupportedOSPlatform("windows")]
static class Utilities static class Utilities
{ {
internal static byte[] StructToBytes<T>(T @struct) where T : struct internal static byte[] StructToBytes<T>(T @struct) where T : struct
@@ -83,7 +80,7 @@ namespace BadBuilder.Formatter
fixed (byte* pData = &data[0]) fixed (byte* pData = &data[0])
{ {
if (!WriteFile(new HANDLE(hDevice.DangerousGetHandle()), pData, numberOfSectors * bytesPerSector, null, null)) if (!WriteFile(new HANDLE(hDevice.DangerousGetHandle()), pData, numberOfSectors * bytesPerSector, null, null))
ExitWithError($"Sektoren konnten nicht auf das FAT32-Gerät geschrieben werden. Beende mit Fehler. GetLastError: {Marshal.GetLastWin32Error()}"); ExitWithError($"Unable to write sectors to FAT32 device, exiting. GetLastError: {Marshal.GetLastWin32Error()}");
} }
} }
@@ -103,7 +100,7 @@ namespace BadBuilder.Formatter
writeSize = (numberOfSectors > burstSize) ? burstSize : numberOfSectors; writeSize = (numberOfSectors > burstSize) ? burstSize : numberOfSectors;
if (!WriteFile(new HANDLE(hDevice.DangerousGetHandle()), pZeroSector, writeSize * bytesPerSector, null, null)) if (!WriteFile(new HANDLE(hDevice.DangerousGetHandle()), pZeroSector, writeSize * bytesPerSector, null, null))
ExitWithError($"Sektoren konnten nicht auf das FAT32-Gerät geschrieben werden. Beende mit Fehler. GetLastError: {Marshal.GetLastWin32Error()}"); ExitWithError($"Unable to write sectors to FAT32 device, exiting. GetLastError: {Marshal.GetLastWin32Error()}");
numberOfSectors -= writeSize; numberOfSectors -= writeSize;
} }

View File

@@ -14,7 +14,7 @@ namespace BadBuilder
return AnsiConsole.Prompt( return AnsiConsole.Prompt(
new SelectionPrompt<string>() new SelectionPrompt<string>()
.Title("Wähle ein Laufwerk zum Formatieren aus:") .Title("Select a disk to format:")
.HighlightStyle(GreenStyle) .HighlightStyle(GreenStyle)
.AddChoices(choices) .AddChoices(choices)
); );
@@ -23,13 +23,13 @@ namespace BadBuilder
static bool PromptFormatConfirmation(string selectedDisk) static bool PromptFormatConfirmation(string selectedDisk)
{ {
return AnsiConsole.Prompt( return AnsiConsole.Prompt(
new TextPrompt<bool>($"[#FF7200 bold]WARNUNG: [/]Möchtest du [bold]{selectedDisk.Substring(0, 3)}[/] wirklich formatieren? Alle Daten auf diesem Laufwerk gehen verloren.") new TextPrompt<bool>($"[#FF7200 bold]WARNING: [/]Are you sure you would like to format [bold]{selectedDisk.Substring(0, 3)}[/]? All data on this drive will be lost.")
.AddChoice(true) .AddChoice(true)
.AddChoice(false) .AddChoice(false)
.DefaultValue(false) .DefaultValue(false)
.ChoicesStyle(GreenStyle) .ChoicesStyle(GreenStyle)
.DefaultValueStyle(OrangeStyle) .DefaultValueStyle(OrangeStyle)
.WithConverter(choice => choice ? "j" : "n") .WithConverter(choice => choice ? "y" : "n")
); );
} }
@@ -38,13 +38,11 @@ namespace BadBuilder
bool ret = true; bool ret = true;
string output = string.Empty; string output = string.Empty;
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatiere Laufwerk[/] {disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}", async ctx => AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}", async ctx =>
{ {
ClearConsole(); ClearConsole();
output = DiskHelper.FormatDisk(disk); output = DiskHelper.FormatDisk(disk);
if (output != string.Empty) ret = false; if (output != string.Empty) ret = false;
await Task.CompletedTask;
}); });
if (!ret) if (!ret)

View File

@@ -9,31 +9,13 @@ namespace BadBuilder
{ {
static async Task<List<ArchiveItem>> DownloadRequiredFiles() static async Task<List<ArchiveItem>> DownloadRequiredFiles()
{ {
bool hasUpdatedDashboard = AnsiConsole.Prompt(
new TextPrompt<bool>("Hast du bereits auf Dashboard-Version [bold]17559[/] aktualisiert?")
.AddChoice(true)
.AddChoice(false)
.DefaultValue(true)
.ChoicesStyle(GreenStyle)
.DefaultValueStyle(OrangeStyle)
.WithConverter(choice => choice ? "j" : "n")
);
ClearConsole();
RequiresDashboardUpdate = !hasUpdatedDashboard;
DownloadItem? dashboardUpdateItem = RequiresDashboardUpdate
? ("Dashboard Update 17559", "https://cdn.niklascfw.de/files/xbox360/SystemUpdate_17559.zip")
: null;
List<DownloadItem> items = new() List<DownloadItem> items = new()
{ {
("ABadAvatar", "https://cdn.niklascfw.de/files/xbox360/ABadAvatar-publicbeta1.0.zip"), ("ABadAvatar", "https://cdn.niklascfw.de/files/ABadAvatar-publicbeta1.0.zip"),
("Aurora Dashboard", "https://cdn.niklascfw.de/files/xbox360/Aurora%200.7b.2%20-%20Release%20Package.rar"), ("Aurora Dashboard", "https://cdn.niklascfw.de/files/Aurora%200.7b.2%20-%20Release%20Package.rar"),
("XeXmenu", "https://cdn.niklascfw.de/files/xbox360/MenuData.7z"), ("XeXmenu", "https://github.com/Pdawg-bytes/BadBuilder/releases/download/v0.10a/MenuData.7z"),
("Rock Band Blitz", "https://cdn.niklascfw.de/files/xbox360/GameData.zip"), ("Rock Band Blitz", "https://github.com/Pdawg-bytes/BadBuilder/releases/download/v0.10a/GameData.zip"),
("Simple 360 NAND Flasher", "https://cdn.niklascfw.de/files/xbox360/Flasher.7z"), ("Simple 360 NAND Flasher", "https://github.com/Pdawg-bytes/BadBuilder/releases/download/v0.10a/Flasher.7z"),
("XeUnshackle", "https://cdn.niklascfw.de/files/xbox360/XeUnshackle-BETA-v1_03.zip"),
}; };
await DownloadHelper.GetGitHubAssets(items); await DownloadHelper.GetGitHubAssets(items);
@@ -42,11 +24,11 @@ namespace BadBuilder
List<string> choices = items.Select(item => List<string> choices = items.Select(item =>
existingFiles.Any(e => e.name == item.name) existingFiles.Any(e => e.name == item.name)
? $"{item.name} [italic gray](bereits vorhanden)[/]" ? $"{item.name} [italic gray](already exists)[/]"
: item.name).ToList(); : item.name).ToList();
var prompt = new MultiSelectionPrompt<string>() var prompt = new MultiSelectionPrompt<string>()
.Title("Welche Dateien hast du bereits? [gray](Mehrfachauswahl möglich)[/]") .Title("Which files do you already have? [gray](Select all that apply)[/]")
.PageSize(10) .PageSize(10)
.NotRequired() .NotRequired()
.HighlightStyle(GreenStyle) .HighlightStyle(GreenStyle)
@@ -65,17 +47,6 @@ namespace BadBuilder
List<DownloadItem> itemsToDownload = items.Where(item => !selectedItems.Contains(item.name)).ToList(); List<DownloadItem> itemsToDownload = items.Where(item => !selectedItems.Contains(item.name)).ToList();
if (dashboardUpdateItem.HasValue)
{
string updateFileName = dashboardUpdateItem.Value.url.Split('/').Last();
string updateDestination = Path.Combine(DOWNLOAD_DIR, updateFileName);
if (!File.Exists(updateDestination))
{
itemsToDownload.Add(dashboardUpdateItem.Value);
}
}
itemsToDownload.Sort((a, b) => b.name.Length.CompareTo(a.name.Length)); itemsToDownload.Sort((a, b) => b.name.Length.CompareTo(a.name.Length));
if (!Directory.Exists($"{DOWNLOAD_DIR}")) if (!Directory.Exists($"{DOWNLOAD_DIR}"))
@@ -95,7 +66,7 @@ namespace BadBuilder
) )
.StartAsync(async ctx => .StartAsync(async ctx =>
{ {
AnsiConsole.MarkupLine("[#76B900]{0}[/] Lade erforderliche Dateien herunter.", Markup.Escape("[*]")); AnsiConsole.MarkupLine("[#76B900]{0}[/] Downloading required files.", Markup.Escape("[*]"));
await Task.WhenAll(itemsToDownload.Select(async item => await Task.WhenAll(itemsToDownload.Select(async item =>
{ {
var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false }); var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false });
@@ -104,11 +75,11 @@ namespace BadBuilder
}); });
string status = "[+]"; string status = "[+]";
AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{itemsToDownload.Count()}[/] Downloads abgeschlossen.\n"); AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{itemsToDownload.Count()}[/] download(s) completed.\n");
} }
else else
{ {
AnsiConsole.MarkupLine("[italic #76B900]Keine Downloads erforderlich. Alle Dateien sind bereits vorhanden.[/]"); AnsiConsole.MarkupLine("[italic #76B900]No downloads required. All files already exist.[/]");
} }
@@ -121,39 +92,29 @@ namespace BadBuilder
if (File.Exists(destinationPath)) continue; if (File.Exists(destinationPath)) continue;
string existingPath = AnsiConsole.Prompt( string existingPath = AnsiConsole.Prompt(
new TextPrompt<string>($"Gib den Pfad zum Archiv [bold]{selectedItem}[/] ein:") new TextPrompt<string>($"Enter the path to the [bold]{selectedItem}[/] archive:")
.PromptStyle(LightOrangeStyle) .PromptStyle(LightOrangeStyle)
.Validate(path => .Validate(path =>
{ {
return File.Exists(path.Trim().Trim('"')) return File.Exists(path.Trim().Trim('"'))
? ValidationResult.Success() ? ValidationResult.Success()
: ValidationResult.Error("[red]Datei wurde nicht gefunden.[/]"); : ValidationResult.Error("[red]File does not exist.[/]");
}) })
).Trim().Trim('"'); ).Trim().Trim('"');
try try
{ {
File.Copy(existingPath, destinationPath, overwrite: true); File.Copy(existingPath, destinationPath, overwrite: true);
AnsiConsole.MarkupLine($"[italic #76B900][bold]{selectedItem}[/] wurde erfolgreich in das Arbeitsverzeichnis kopiert.[/]\n"); AnsiConsole.MarkupLine($"[italic #76B900]Successfully copied [bold]{selectedItem}[/] to the working directory.[/]\n");
} }
catch (Exception ex) catch (Exception ex)
{ {
AnsiConsole.MarkupLine($"[italic red]Fehler beim Kopieren von [bold]{selectedItem}[/] in das Arbeitsverzeichnis. Ausnahme: {ex.Message}[/]\n"); AnsiConsole.MarkupLine($"[italic red]Failed to copy [bold]{selectedItem}[/] to the working directory. Exception: {ex.Message}[/]\n");
} }
} }
List<ArchiveItem> archives = items return items.Select(item => new ArchiveItem(item.name, Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last()))).ToList();
.Select(item => new ArchiveItem(item.name, Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last())))
.ToList();
if (dashboardUpdateItem.HasValue)
{
string updatePath = Path.Combine(DOWNLOAD_DIR, dashboardUpdateItem.Value.url.Split('/').Last());
archives.Add(new ArchiveItem(dashboardUpdateItem.Value.name, updatePath));
}
return archives;
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace BadBuilder
) )
.StartAsync(async ctx => .StartAsync(async ctx =>
{ {
AnsiConsole.MarkupLine("[#76B900]{0}[/] Entpacke Dateien.", Markup.Escape("[*]")); AnsiConsole.MarkupLine("[#76B900]{0}[/] Extracting files.", Markup.Escape("[*]"));
await Task.WhenAll(filesToExtract.Select(async item => await Task.WhenAll(filesToExtract.Select(async item =>
{ {
var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false }); var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false });

View File

@@ -7,13 +7,13 @@ namespace BadBuilder
static bool PromptAddHomebrew() static bool PromptAddHomebrew()
{ {
return AnsiConsole.Prompt( return AnsiConsole.Prompt(
new TextPrompt<bool>("Möchtest du Homebrew-Programme hinzufügen?") new TextPrompt<bool>("Would you like to add homebrew programs?")
.AddChoice(true) .AddChoice(true)
.AddChoice(false) .AddChoice(false)
.DefaultValue(false) .DefaultValue(false)
.ChoicesStyle(GreenStyle) .ChoicesStyle(GreenStyle)
.DefaultValueStyle(OrangeStyle) .DefaultValueStyle(OrangeStyle)
.WithConverter(choice => choice ? "j" : "n") .WithConverter(choice => choice ? "y" : "n")
); );
} }
@@ -27,12 +27,12 @@ namespace BadBuilder
new SelectionPrompt<string>() new SelectionPrompt<string>()
.PageSize(4) .PageSize(4)
.HighlightStyle(GreenStyle) .HighlightStyle(GreenStyle)
.AddChoices("Homebrew-App hinzufügen", "Hinzugefügte Apps anzeigen", "App entfernen", "Fertig & Speichern") .AddChoices("Add Homebrew App", "View Added Apps", "Remove App", "Finish & Save")
); );
switch (choice) switch (choice)
{ {
case "Homebrew-App hinzufügen": case "Add Homebrew App":
ClearConsole(); ClearConsole();
var newApp = AddHomebrewApp(); var newApp = AddHomebrewApp();
@@ -40,34 +40,34 @@ namespace BadBuilder
homebrewApps.Add(newApp.Value); homebrewApps.Add(newApp.Value);
break; break;
case "Hinzugefügte Apps anzeigen": case "View Added Apps":
ClearConsole(); ClearConsole();
DisplayApps(homebrewApps); DisplayApps(homebrewApps);
break; break;
case "App entfernen": case "Remove App":
ClearConsole(); ClearConsole();
RemoveHomebrewApp(homebrewApps); RemoveHomebrewApp(homebrewApps);
break; break;
case "Fertig & Speichern": case "Finish & Save":
if (homebrewApps.Count == 0) if (homebrewApps.Count == 0)
{ {
AnsiConsole.MarkupLine("[#ffac4d]Keine Apps hinzugefügt.[/]"); AnsiConsole.MarkupLine("[#ffac4d]No apps added.[/]");
return homebrewApps; return homebrewApps;
} }
if (AnsiConsole.Prompt( if (AnsiConsole.Prompt(
new TextPrompt<bool>("Speichern und beenden?") new TextPrompt<bool>("Save and exit?")
.AddChoice(true) .AddChoice(true)
.AddChoice(false) .AddChoice(false)
.DefaultValue(true) .DefaultValue(true)
.ChoicesStyle(GreenStyle) .ChoicesStyle(GreenStyle)
.DefaultValueStyle(OrangeStyle) .DefaultValueStyle(OrangeStyle)
.WithConverter(choice => choice ? "j" : "n"))) .WithConverter(choice => choice ? "y" : "n")))
{ {
string status = "[+]"; string status = "[+]";
AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{homebrewApps.Count}[/] App(s) gespeichert.\n"); AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] Saved [bold]{homebrewApps.Count}[/] app(s).\n");
return homebrewApps; return homebrewApps;
} }
break; break;
@@ -77,11 +77,11 @@ namespace BadBuilder
private static HomebrewApp? AddHomebrewApp() private static HomebrewApp? AddHomebrewApp()
{ {
AnsiConsole.MarkupLine("[bold #76B900]Neue Homebrew-App hinzufügen[/]\n"); AnsiConsole.MarkupLine("[bold #76B900]Add a new homebrew app[/]\n");
string folderPath = AnsiConsole.Ask<string>("[#FFA500]Gib den Ordnerpfad für die App ein:[/]"); string folderPath = AnsiConsole.Ask<string>("[#FFA500]Enter the folder path for the app:[/]");
if (!Directory.Exists(folderPath)) if (!Directory.Exists(folderPath))
{ {
AnsiConsole.MarkupLine("[#ffac4d]Ungültiger Ordnerpfad. Bitte erneut versuchen.\n[/]"); AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.\n[/]");
return null; return null;
} }
@@ -92,23 +92,23 @@ namespace BadBuilder
string entryPoint = xexFiles.Length switch string entryPoint = xexFiles.Length switch
{ {
0 => AnsiConsole.Prompt( 0 => AnsiConsole.Prompt(
new TextPrompt<string>("[grey]Keine .xex-Dateien in diesem Ordner gefunden.[/] [#FFA500]Gib den Pfad zum Einstiegspunkt ein:[/]") new TextPrompt<string>("[grey]No .xex files found in this folder.[/] [#FFA500]Enter the path to the entry point:[/]")
.Validate(path => File.Exists(path.Trim().Trim('"')) && Path.GetExtension(path.Trim().Trim('"')) == ".xex" ? ValidationResult.Success() : ValidationResult.Error("[#ffac4d]Datei nicht gefunden oder keine XEX-Datei.[/]\n")) .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('"'), ).Trim().Trim('"'),
1 => xexFiles[0], 1 => xexFiles[0],
_ => AnsiConsole.Prompt( _ => AnsiConsole.Prompt(
new SelectionPrompt<string?>() new SelectionPrompt<string?>()
.Title("[#FFA500]Einstiegspunkt auswählen:[/]") .Title("[#FFA500]Select entry point:[/]")
.HighlightStyle(PeachStyle) .HighlightStyle(PeachStyle)
.AddChoices(xexFiles.Select(file => Path.GetFileName(file) ?? file)) .AddChoices(xexFiles.Select(Path.GetFileName))
) ?? throw new InvalidOperationException("Die Auswahl des Einstiegspunkts war null.") )
}; };
#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 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. #pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
ClearConsole(); ClearConsole();
AnsiConsole.MarkupLine($"[#76B900]Hinzugefügt:[/] {folderPath.Split('\\').Last()} -> [#ffac4d]{Path.GetFileName(entryPoint)}[/]\n"); AnsiConsole.MarkupLine($"[#76B900]Added:[/] {folderPath.Split('\\').Last()} -> [#ffac4d]{Path.GetFileName(entryPoint)}[/]\n");
return (folderPath.Split('\\').Last(), folderPath, Path.Combine(folderPath, entryPoint)); return (folderPath.Split('\\').Last(), folderPath, Path.Combine(folderPath, entryPoint));
} }
@@ -118,14 +118,14 @@ namespace BadBuilder
{ {
if (apps.Count == 0) if (apps.Count == 0)
{ {
AnsiConsole.MarkupLine("[#ffac4d]Keine Homebrew-Apps hinzugefügt.[/]\n"); AnsiConsole.MarkupLine("[#ffac4d]No homebrew apps added.[/]\n");
return; return;
} }
var table = new Table() var table = new Table()
.Title("[bold #76B900]Hinzugefügte Homebrew-Apps[/]") .Title("[bold #76B900]Added Homebrew Apps[/]")
.AddColumn("[#4D8C00]Ordner[/]") .AddColumn("[#4D8C00]Folder[/]")
.AddColumn("[#ff7200]Einstiegspunkt[/]"); .AddColumn("[#ff7200]Entry Point[/]");
foreach (var app in apps) foreach (var app in apps)
table.AddRow($"[#A1CF3E]{app.folder}[/]", $"[#ffac4d]{Path.GetFileName(app.entryPoint)}[/]"); table.AddRow($"[#A1CF3E]{app.folder}[/]", $"[#ffac4d]{Path.GetFileName(app.entryPoint)}[/]");
@@ -138,23 +138,23 @@ namespace BadBuilder
{ {
if (apps.Count == 0) if (apps.Count == 0)
{ {
AnsiConsole.MarkupLine("[#ffac4d]Keine Apps zum Entfernen.[/]\n"); AnsiConsole.MarkupLine("[#ffac4d]No apps to remove.[/]\n");
return; return;
} }
var appToRemove = AnsiConsole.Prompt( var appToRemove = AnsiConsole.Prompt(
new SelectionPrompt<string>() new SelectionPrompt<string>()
.Title("[bold #76B900]Wähle eine App zum Entfernen:[/]") .Title("[bold #76B900]Select an app to remove:[/]")
.PageSize(5) .PageSize(5)
.HighlightStyle(LightOrangeStyle) .HighlightStyle(LightOrangeStyle)
.MoreChoicesText("[grey](Mit Hoch/Runter blättern)[/]") .MoreChoicesText("[grey](Move up/down to scroll)[/]")
.AddChoices(apps.Select(app => $"{Path.GetFileName(app.folder)}")) .AddChoices(apps.Select(app => $"{Path.GetFileName(app.folder)}"))
); );
var selectedApp = apps.First(app => $"{Path.GetFileName(app.folder)}" == appToRemove); var selectedApp = apps.First(app => $"{Path.GetFileName(app.folder)}" == appToRemove);
apps.Remove(selectedApp); apps.Remove(selectedApp);
AnsiConsole.MarkupLine($"[#ffac4d]Entfernt:[/] {selectedApp.folder.Split('\\').Last()}\n"); AnsiConsole.MarkupLine($"[#ffac4d]Removed:[/] {selectedApp.folder.Split('\\').Last()}\n");
} }
} }
} }

View File

@@ -25,8 +25,6 @@ namespace BadBuilder.Helpers
task.Increment(1); task.Increment(1);
} }
await Task.CompletedTask;
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -32,7 +32,7 @@ namespace BadBuilder.Helpers
internal static string FormatDisk(DiskInfo disk) internal static string FormatDisk(DiskInfo disk)
{ {
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "\u001b[38;2;255;114;0m[-]\u001b[0m Das Formatieren wird derzeit nur unter Windows unterstützt. Bitte formatiere dein Laufwerk manuell und versuche es erneut."; return "\u001b[38;2;255;114;0m[-]\u001b[0m Formatting is currently only supported on Windows. Please format your drive manually and try again.";
return DiskFormatter.FormatVolume(disk.DriveLetter[0], disk.TotalSize); return DiskFormatter.FormatVolume(disk.DriveLetter[0], disk.TotalSize);
} }

View File

@@ -12,7 +12,8 @@ namespace BadBuilder.Helpers
GitHubClient gitClient = new(new ProductHeaderValue("BadBuilder-Downloader")); GitHubClient gitClient = new(new ProductHeaderValue("BadBuilder-Downloader"));
List<string> repos = List<string> repos =
[ [
"grimdoomer/Xbox360BadUpdate" "grimdoomer/Xbox360BadUpdate",
"Byrom90/XeUnshackle"
]; ];
foreach (var repo in repos) foreach (var repo in repos)
@@ -27,6 +28,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)
}; };
@@ -67,7 +69,7 @@ namespace BadBuilder.Helpers
} }
catch (Exception ex) catch (Exception ex)
{ {
AnsiConsole.MarkupLine($"[red]Fehler beim Herunterladen der Datei von [bold]{url}[/]: {ex}[/]"); AnsiConsole.MarkupLine($"[red]Error downloading file from [bold]{url}[/]: {ex}[/]");
} }
} }
} }

View File

@@ -12,7 +12,7 @@
string relativePath = Path.GetRelativePath(sourceDir, file); string relativePath = Path.GetRelativePath(sourceDir, file);
string destFile = Path.Combine(destDir, relativePath); string destFile = Path.Combine(destDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(destFile)!); Directory.CreateDirectory(Path.GetDirectoryName(destFile));
await CopyFileAsync(file, destFile); await CopyFileAsync(file, destFile);
} }

View File

@@ -26,7 +26,7 @@ namespace BadBuilder.Helpers
if (process.ExitCode != 0) if (process.ExitCode != 0)
{ {
string status = "[-]"; string status = "[-]";
AnsiConsole.MarkupLineInterpolated($"\n[#FF7200]{status}[/] Das Programm {Path.GetFileNameWithoutExtension(xexPath)} konnte nicht gepatcht werden. XexTool-Ausgabe:"); AnsiConsole.MarkupLineInterpolated($"\n[#FF7200]{status}[/] The program {Path.GetFileNameWithoutExtension(xexPath)} was unable to be patched. XexTool output:");
Console.WriteLine(process.StandardError.ReadToEnd()); Console.WriteLine(process.StandardError.ReadToEnd());
} }
} }

View File

@@ -23,7 +23,6 @@ namespace BadBuilder
static string XexToolPath = string.Empty; static string XexToolPath = string.Empty;
static string TargetDriveLetter = string.Empty; static string TargetDriveLetter = string.Empty;
static bool RequiresDashboardUpdate = false;
static ActionQueue actionQueue = new(); static ActionQueue actionQueue = new();
@@ -38,7 +37,7 @@ namespace BadBuilder
Console.WriteLine(); Console.WriteLine();
string action = PromptForAction(); string action = PromptForAction();
if (action == "Beenden") Environment.Exit(0); if (action == "Exit") Environment.Exit(0);
List<DiskInfo> disks = DiskHelper.GetDisks(); List<DiskInfo> disks = DiskHelper.GetDisks();
string selectedDisk = PromptDiskSelection(disks); string selectedDisk = PromptDiskSelection(disks);
@@ -60,38 +59,13 @@ namespace BadBuilder
ClearConsole(); ClearConsole();
string selectedDefaultApp = "XeUnshackle"; string selectedDefaultApp = "XeUnshackle";
AnsiConsole.MarkupLine("[#76B900]{0}[/] Standard-Payload auf [bold]XeUnshackle[/] gesetzt.", Markup.Escape("[*]")); AnsiConsole.MarkupLine("[#76B900]{0}[/] Default payload set to [bold]XeUnshackle[/].", Markup.Escape("[*]"));
AnsiConsole.MarkupLine("[#76B900]{0}[/] Kopiere erforderliche Dateien und Ordner.", 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}"))
{ {
switch (folder.Split("\\").Last()) switch (folder.Split("\\").Last())
{ {
case "Dashboard Update 17559":
if (!RequiresDashboardUpdate)
{
break;
}
foreach (string subDirectory in Directory.GetDirectories(folder))
{
EnqueueMirrorDirectory(
subDirectory,
Path.Combine(TargetDriveLetter, Path.GetFileName(subDirectory)),
1
);
}
foreach (string file in Directory.GetFiles(folder))
{
EnqueueFileCopy(
file,
Path.Combine(TargetDriveLetter, Path.GetFileName(file)),
1
);
}
break;
case "ABadAvatar": case "ABadAvatar":
string avatarPayloadPath = Path.Combine(folder, "BadUpdatePayload"); string avatarPayloadPath = Path.Combine(folder, "BadUpdatePayload");
if (Directory.Exists(avatarPayloadPath)) if (Directory.Exists(avatarPayloadPath))
@@ -179,39 +153,23 @@ namespace BadBuilder
break; break;
case "XeUnshackle": case "XeUnshackle":
string readmePath = Path.Combine(folder, "README - IMPORTANT.txt"); string subFolderPath = Directory.GetDirectories(folder).FirstOrDefault();
if (File.Exists(readmePath)) File.Delete(Path.Combine(subFolderPath, "README - IMPORTANT.txt"));
{ EnqueueMirrorDirectory(
File.Delete(readmePath); subFolderPath,
} TargetDriveLetter,
9
foreach (string subDirectory in Directory.GetDirectories(folder)) );
{
EnqueueMirrorDirectory(
subDirectory,
Path.Combine(TargetDriveLetter, Path.GetFileName(subDirectory)),
9
);
}
foreach (string file in Directory.GetFiles(folder))
{
EnqueueFileCopy(
file,
Path.Combine(TargetDriveLetter, Path.GetFileName(file)),
9
);
}
break; break;
case "BadUpdate": case "BadUpdate":
actionQueue.EnqueueAction(async () => actionQueue.EnqueueAction(async () =>
{ {
using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "name.txt"))) using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "name.txt")))
writer.WriteLine("USB-Speichergerät"); 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($"Dieses Laufwerk wurde mit BadBuilder von Pdawg erstellt.\nWeitere Informationen: https://github.com/Pdawg-bytes/BadBuilder\nKonfiguration: \n- BadUpdate-Ziel-Binary: {selectedDefaultApp}"); 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);
@@ -238,7 +196,7 @@ namespace BadBuilder
}, 6); }, 6);
break; break;
default: throw new Exception($"[-] Unerwartetes Verzeichnis im Arbeitsordner: {folder}"); default: throw new Exception($"[-] Unexpected directory in working folder: {folder}");
} }
} }
actionQueue.ExecuteActionsAsync().Wait(); actionQueue.ExecuteActionsAsync().Wait();
@@ -272,26 +230,17 @@ namespace BadBuilder
} }
} }
for (int i = 0; i < launchIniLines.Count; i++)
{
if (launchIniLines[i].TrimStart().StartsWith("autoswap", StringComparison.OrdinalIgnoreCase))
{
launchIniLines[i] = "autoswap = true";
break;
}
}
File.WriteAllLines(launchIniPath, launchIniLines); File.WriteAllLines(launchIniPath, launchIniLines);
} }
File.AppendAllText(Path.Combine(TargetDriveLetter, "info.txt"), $"- Laufwerk formatiert mit {(targetDisk.TotalSize < 31 * GB ? "Windows \"format.com\"" : "BadBuilder Large FAT32 formatter")}\n"); 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"), $"- Gesamtkapazität des Laufwerks: {targetDisk.TotalSize} Bytes\n"); File.AppendAllText(Path.Combine(TargetDriveLetter, "info.txt"), $"- Disk total size: {targetDisk.TotalSize} bytes\n");
ClearConsole(); ClearConsole();
WriteHomebrewLog(1); WriteHomebrewLog(1);
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Dein USB-Laufwerk ist einsatzbereit.", Markup.Escape("[+]")); AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
Console.Write("\nBeliebige Taste zum Beenden drücken..."); Console.Write("\nPress any key to exit...");
Console.ReadKey(); Console.ReadKey();
} }
@@ -314,7 +263,7 @@ namespace BadBuilder
static void WriteHomebrewLog(int count) static void WriteHomebrewLog(int count)
{ {
string logPath = Path.Combine(TargetDriveLetter, "info.txt"); string logPath = Path.Combine(TargetDriveLetter, "info.txt");
string logEntry = $"- {count} Homebrew-App(s) hinzugefügt (einschließlich Simple 360 NAND Flasher)\n"; string logEntry = $"- {count} homebrew app(s) added (including Simple 360 NAND Flasher)\n";
File.AppendAllText(logPath, logEntry); File.AppendAllText(logPath, logEntry);
} }
@@ -330,7 +279,7 @@ namespace BadBuilder
[#76B900]v0.31[/] [#76B900]v0.31[/]
Xbox 360 [#FF7200]BadUpdate[/] USB Builder Xbox 360 [#FF7200]BadUpdate[/] USB Builder
[#848589]Created by Pdawg | Forked by NiklasCFW[/] [#848589]Created by Pdawg | Forked by NiklasCFW[/]
[#76B900][/] [#76B900][/]
"""); """);
@@ -339,8 +288,8 @@ namespace BadBuilder
new SelectionPrompt<string>() new SelectionPrompt<string>()
.HighlightStyle(GreenStyle) .HighlightStyle(GreenStyle)
.AddChoices( .AddChoices(
"Exploit-USB erstellen", "Build exploit USB",
"Beenden" "Exit"
) )
); );

View File

@@ -1,11 +1,8 @@
using Spectre.Console; namespace BadBuilder.Utilities
using System.Linq;
namespace BadBuilder.Utilities
{ {
internal class ActionQueue internal class ActionQueue
{ {
private readonly SortedDictionary<int, Queue<Func<Task>>> priorityQueue = new(); private SortedDictionary<int, Queue<Func<Task>>> priorityQueue = new SortedDictionary<int, Queue<Func<Task>>>();
internal void EnqueueAction(Func<Task> action, int priority) internal void EnqueueAction(Func<Task> action, int priority)
{ {
@@ -18,39 +15,15 @@ namespace BadBuilder.Utilities
internal async Task ExecuteActionsAsync() internal async Task ExecuteActionsAsync()
{ {
int totalActions = priorityQueue.Values.Sum(queue => queue.Count); foreach (var priority in priorityQueue.Keys.OrderByDescending(p => p))
if (totalActions == 0)
{ {
return; var actions = priorityQueue[priority];
} while (actions.Count > 0)
await AnsiConsole.Progress()
.Columns(
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn()
)
.StartAsync(async ctx =>
{ {
var copyTask = ctx.AddTask("Dateien auf das Laufwerk kopieren", new ProgressTaskSettings var action = actions.Dequeue();
{ await action();
AutoStart = true, }
MaxValue = totalActions }
});
foreach (var priority in priorityQueue.Keys.OrderByDescending(p => p))
{
var actions = priorityQueue[priority];
while (actions.Count > 0)
{
var action = actions.Dequeue();
await action();
copyTask.Increment(1);
}
}
});
priorityQueue.Clear();
} }
} }
} }

View File

@@ -21,9 +21,6 @@ BadBuilder is a tool for creating a BadUpdate USB drive for the Xbox 360. It aut
- Prepares the USB drive for the BadUpdate exploit by copying all required files. - Prepares the USB drive for the BadUpdate exploit by copying all required files.
- Includes optional content packs such as **ABadAvatar** and the **Aurora Dashboard**. - Includes optional content packs such as **ABadAvatar** and the **Aurora Dashboard**.
## Requirements
- **.NET 8.0 Desktop Runtime** (required to run framework-dependent builds)
## Build ## Build
Run these commands from the repository root: Run these commands from the repository root:
@@ -92,7 +89,6 @@ BadBuilder will detect `Aurora.xex` as the entry point and patch it accordingly.
If you encounter any problems, please create a new issue with details about your setup and the problem. If you encounter any problems, please create a new issue with details about your setup and the problem.
### Credits ### Credits
- **Pdawg-bytes:** [BadBuilder](https://github.com/Pdawg-bytes)
- **Grimdoomer:** [BadUpdate](https://github.com/grimdoomer/Xbox360BadUpdate) - **Grimdoomer:** [BadUpdate](https://github.com/grimdoomer/Xbox360BadUpdate)
- **shutterbug2000:** [ABadAvatar](https://github.com/shutterbug2000/ABadAvatar) - **shutterbug2000:** [ABadAvatar](https://github.com/shutterbug2000/ABadAvatar)
- **Phoenix:** [Aurora Dashboard](https://phoenix.xboxunity.net/#/news) - **Phoenix:** [Aurora Dashboard](https://phoenix.xboxunity.net/#/news)