Compare commits

6 Commits
v0.31 ... main

15 changed files with 227 additions and 100 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ namespace BadBuilder.Helpers
internal static string FormatDisk(DiskInfo disk)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
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 "\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 DiskFormatter.FormatVolume(disk.DriveLetter[0], disk.TotalSize);
}

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ namespace BadBuilder
static string XexToolPath = string.Empty;
static string TargetDriveLetter = string.Empty;
static bool RequiresDashboardUpdate = false;
static ActionQueue actionQueue = new();
@@ -37,7 +38,7 @@ namespace BadBuilder
Console.WriteLine();
string action = PromptForAction();
if (action == "Exit") Environment.Exit(0);
if (action == "Beenden") Environment.Exit(0);
List<DiskInfo> disks = DiskHelper.GetDisks();
string selectedDisk = PromptDiskSelection(disks);
@@ -59,13 +60,38 @@ namespace BadBuilder
ClearConsole();
string selectedDefaultApp = "XeUnshackle";
AnsiConsole.MarkupLine("[#76B900]{0}[/] Default payload set to [bold]XeUnshackle[/].", Markup.Escape("[*]"));
AnsiConsole.MarkupLine("[#76B900]{0}[/] Standard-Payload auf [bold]XeUnshackle[/] gesetzt.", Markup.Escape("[*]"));
AnsiConsole.MarkupLine("[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]"));
AnsiConsole.MarkupLine("[#76B900]{0}[/] Kopiere erforderliche Dateien und Ordner.", Markup.Escape("[*]"));
foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}"))
{
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":
string avatarPayloadPath = Path.Combine(folder, "BadUpdatePayload");
if (Directory.Exists(avatarPayloadPath))
@@ -153,23 +179,39 @@ namespace BadBuilder
break;
case "XeUnshackle":
string subFolderPath = Directory.GetDirectories(folder).FirstOrDefault();
File.Delete(Path.Combine(subFolderPath, "README - IMPORTANT.txt"));
string readmePath = Path.Combine(folder, "README - IMPORTANT.txt");
if (File.Exists(readmePath))
{
File.Delete(readmePath);
}
foreach (string subDirectory in Directory.GetDirectories(folder))
{
EnqueueMirrorDirectory(
subFolderPath,
TargetDriveLetter,
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;
case "BadUpdate":
actionQueue.EnqueueAction(async () =>
{
using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "name.txt")))
writer.WriteLine("USB Storage Device");
writer.WriteLine("USB-Speichergerät");
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\nConfiguration: \n- BadUpdate target binary: {selectedDefaultApp}");
writer.WriteLine($"Dieses Laufwerk wurde mit BadBuilder von Pdawg erstellt.\nWeitere Informationen: https://github.com/Pdawg-bytes/BadBuilder\nKonfiguration: \n- BadUpdate-Ziel-Binary: {selectedDefaultApp}");
Directory.CreateDirectory(Path.Combine(TargetDriveLetter, "Apps"));
await FileSystemHelper.MirrorDirectoryAsync(Path.Combine(folder, "Rock Band Blitz"), TargetDriveLetter);
@@ -196,7 +238,7 @@ namespace BadBuilder
}, 6);
break;
default: throw new Exception($"[-] Unexpected directory in working folder: {folder}");
default: throw new Exception($"[-] Unerwartetes Verzeichnis im Arbeitsordner: {folder}");
}
}
actionQueue.ExecuteActionsAsync().Wait();
@@ -230,17 +272,26 @@ 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.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");
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"), $"- Gesamtkapazität des Laufwerks: {targetDisk.TotalSize} Bytes\n");
ClearConsole();
WriteHomebrewLog(1);
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Dein USB-Laufwerk ist einsatzbereit.", Markup.Escape("[+]"));
Console.Write("\nPress any key to exit...");
Console.Write("\nBeliebige Taste zum Beenden drücken...");
Console.ReadKey();
}
@@ -263,7 +314,7 @@ namespace BadBuilder
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";
string logEntry = $"- {count} Homebrew-App(s) hinzugefügt (einschließlich Simple 360 NAND Flasher)\n";
File.AppendAllText(logPath, logEntry);
}
@@ -288,8 +339,8 @@ namespace BadBuilder
new SelectionPrompt<string>()
.HighlightStyle(GreenStyle)
.AddChoices(
"Build exploit USB",
"Exit"
"Exploit-USB erstellen",
"Beenden"
)
);

View File

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

View File

@@ -21,6 +21,9 @@ 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.
- Includes optional content packs such as **ABadAvatar** and the **Aurora Dashboard**.
## Requirements
- **.NET 8.0 Desktop Runtime** (required to run framework-dependent builds)
## Build
Run these commands from the repository root:
@@ -89,6 +92,7 @@ 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.
### Credits
- **Pdawg-bytes:** [BadBuilder](https://github.com/Pdawg-bytes)
- **Grimdoomer:** [BadUpdate](https://github.com/grimdoomer/Xbox360BadUpdate)
- **shutterbug2000:** [ABadAvatar](https://github.com/shutterbug2000/ABadAvatar)
- **Phoenix:** [Aurora Dashboard](https://phoenix.xboxunity.net/#/news)