Compare commits

...

15 Commits

Author SHA1 Message Date
7b32a05263 Added Requirements 2025-11-15 14:59:54 +01:00
6011293c71 Updated Credits in Readme 2025-11-15 14:53:37 +01:00
07cb18d955 Add autoswap = true replacement in launch.ini 2025-11-15 14:49:59 +01:00
299007a8e3 Localize UI text and add copy progress bar 2025-11-13 19:20:28 +01:00
f71e8989db Adjust XeUnshackle download and extraction 2025-11-11 19:23:49 +01:00
9205c2a815 Changed download URLs to NiklasCFW CDN 2025-11-10 21:04:02 +01:00
a08a6e0821 Update default payload and bundle Aurora dashboard 2025-11-08 20:45:49 +01:00
Pdawg11239
6b764c0273 Update download links 2025-08-07 14:19:55 -04:00
Pdawg
b05a89ada4 Prepare README for v0.3 2025-04-02 01:56:26 -04:00
Pdawg11239
57085990bc Update XeXMenu name styling 2025-04-02 01:55:30 -04:00
Pdawg11239
55cfed148a Add 'unsupported' homebrew support & 'XeUnshackle' 2025-04-02 01:40:06 -04:00
Pdawg11239
ed41cf84d4 Update link for RBB 2025-03-30 15:11:28 -04:00
Pdawg11239
0ea82ee67c Fix copying bug 2025-03-29 20:53:04 -04:00
Pdawg11239
da78608a75 Add native format for small FAT32; fix MBR bugs 2025-03-29 20:06:48 -04:00
Pdawg
e039a2b112 Update README.md 2025-03-17 21:39:47 -04:00
17 changed files with 511 additions and 216 deletions

View File

@@ -6,6 +6,12 @@
internal const string ANSI_RESET = "\u001b[0m";
internal const long KB = 1024L;
internal const long MB = 1048576L;
internal const long GB = 1073741824L;
internal const long TB = 1099511627776L;
internal const int FMIFS_HARDDISK = 0xC;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;

View File

@@ -1,9 +1,11 @@
#pragma warning disable CA1416
using System.Text;
using System.Diagnostics;
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;
@@ -11,31 +13,54 @@ using static BadBuilder.Formatter.Utilities;
namespace BadBuilder.Formatter
{
[SupportedOSPlatform("windows")]
public static class DiskFormatter
{
public static unsafe string FormatVolume(char driveLetter)
public static unsafe string FormatVolume(char driveLetter, long diskSize)
{
if (diskSize < 31 * GB) // Just a safeguard to ensure that we never run into close calls with disk size.
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "format.com",
Arguments = $"\"{driveLetter}:\" /Q /X /Y /FS:FAT32 /V:BADUPDATE",
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
process.WaitForExit();
if (process.ExitCode == 0) return "";
else return Error($"Die native Formatierung ist mit dem Exitcode {process.ExitCode} fehlgeschlagen.");
}
string devicePath = $"\\\\.\\{driveLetter}:";
uint volumeID = GetVolumeID();
using SafeFileHandle driveHandle = OpenDeviceHandle(devicePath);
if (driveHandle.IsInvalid) return Error("Unable to open device. GetLastError: " + Marshal.GetLastWin32Error());
SafeFileHandle driveHandle = OpenDeviceHandle(devicePath);
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();
@@ -46,8 +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($"Volumenbezeichnung konnte nicht gesetzt werden. GetLastError: {Marshal.GetLastWin32Error()}");
return string.Empty;
}
@@ -109,14 +138,18 @@ namespace BadBuilder.Formatter
private static unsafe FAT32BootSector InitializeBootSector(DISK_GEOMETRY diskGeometry, PARTITION_INFORMATION partitionInfo, uint totalSectors, uint volumeID)
{
byte sectorsPerCluster = CalculateSectorsPerCluster((ulong)partitionInfo.PartitionLength, diskGeometry.BytesPerSector);
uint sectorsPerCluster = (uint)CalculateSectorsPerCluster((ulong)partitionInfo.PartitionLength, diskGeometry.BytesPerSector);
uint fatSize = CalculateFATSize(totalSectors, 32, sectorsPerCluster, 2, diskGeometry.BytesPerSector);
uint aligned = (uint)MB / diskGeometry.BytesPerSector;
uint sysAreaSize = ((34 * fatSize + aligned - 1) / aligned) * aligned;
uint reserved = sysAreaSize - 2 * fatSize;
FAT32BootSector bootSector = new FAT32BootSector
{
BytesPerSector = (ushort)diskGeometry.BytesPerSector,
SectorsPerCluster = sectorsPerCluster,
ReservedSectorCount = 32,
SectorsPerCluster = (byte)sectorsPerCluster,
ReservedSectorCount = (ushort)reserved,
NumberOfFATs = 2,
MediaDescriptor = 0xF8,
SectorsPerTrack = (ushort)diskGeometry.SectorsPerTrack,
@@ -186,16 +219,16 @@ namespace BadBuilder.Formatter
{
uint bytesPerSector = diskGeometry.BytesPerSector;
uint totalSectors = (uint)(partitionInfo.PartitionLength / bytesPerSector);
uint systemAreaSize = bootSector.ReservedSectorCount + (bootSector.NumberOfFATs * bootSector.SectorsPerFAT) + bootSector.SectorsPerCluster;
uint userAreaSize = totalSectors - systemAreaSize;
uint userAreaSize = totalSectors - bootSector.ReservedSectorCount - (bootSector.NumberOfFATs * bootSector.SectorsPerFAT);
uint zeroOut = bootSector.ReservedSectorCount + (bootSector.NumberOfFATs * bootSector.SectorsPerFAT) + bootSector.SectorsPerCluster;
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;
ZeroOutSectors(driveHandle, 0, systemAreaSize, bytesPerSector);
ZeroOutSectors(driveHandle, 0, zeroOut, bytesPerSector);
for (int i = 0; i < 2; i++)
{
@@ -210,14 +243,6 @@ namespace BadBuilder.Formatter
WriteSector(driveHandle, sectorStart, 1, bytesPerSector, UintArrayToBytes(firstFATSector));
}
if (!isGPT)
{
SET_PARTITION_INFORMATION setPartInfo = new() { PartitionType = 0x0C };
if (!DeviceIoControl(driveHandle, IOCTL_DISK_SET_PARTITION_INFO, &setPartInfo, (uint)sizeof(SET_PARTITION_INFORMATION), null, 0, null, null))
return Error($"Failed to set the drive partition information. GetLastError: {Marshal.GetLastWin32Error()}");
}
return "";
}
}

View File

@@ -5,8 +5,7 @@ DeviceIoControl
CloseHandle
VirtualAlloc
VirtualFree
GUID
SetVolumeLabelW
DISK_GEOMETRY
PARTITION_INFORMATION
PARTITION_INFORMATION_EX
SET_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
@@ -39,23 +42,27 @@ namespace BadBuilder.Formatter
return (uint)(low | (hi << 16));
}
internal static uint CalculateFATSize(uint totalSectors, uint reservedSectors, uint sectorsPerCluster, uint numberOfFATs, uint bytesPerSector)
internal static uint CalculateFATSize(uint diskSize, uint reservedSectors, uint sectorsPerCluster, uint numberOfFATs, uint bytesPerSector)
{
const ulong fatElementSize = 4;
const ulong reservedClusters = 2;
ulong numerator = fatElementSize * (totalSectors - reservedSectors);
ulong denominator = (sectorsPerCluster * bytesPerSector) + (fatElementSize * numberOfFATs);
ulong numerator = diskSize - reservedSectors + reservedClusters * sectorsPerCluster;
ulong denominator = (sectorsPerCluster * bytesPerSector / fatElementSize) + numberOfFATs;
return (uint)((numerator / denominator) + 1);
return (uint)(numerator / denominator + 1);
}
internal static byte CalculateSectorsPerCluster(ulong diskSizeBytes, uint bytesPerSector) => (diskSizeBytes / (1024 * 1024)) switch
internal static long CalculateSectorsPerCluster(ulong diskSizeBytes, uint bytesPerSector) => diskSizeBytes switch
{
var size when size > 512 => (byte)((4 * 1024) / bytesPerSector),
var size when size > 8192 => (byte)((8 * 1024) / bytesPerSector),
var size when size > 16384 => (byte)((16 * 1024) / bytesPerSector),
var size when size > 32768 => (byte)((32 * 1024) / bytesPerSector),
_ => 1
< 64 * MB => ((512) / bytesPerSector),
< 128 * MB => ((1 * KB) / bytesPerSector),
< 256 * MB => ((2 * KB) / bytesPerSector),
< 8 * GB => ((4 * KB) / bytesPerSector),
< 16 * GB => ((8 * KB) / bytesPerSector),
< 32 * GB => ((16 * KB) / bytesPerSector),
< 2 * TB => ((32 * KB) / bytesPerSector),
_ => ((64 * KB) / bytesPerSector)
};
@@ -76,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()}");
}
}
@@ -96,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,29 +23,28 @@ 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")
);
}
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;
string output = string.Empty;
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {selectedDisk}", async ctx =>
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatiere Laufwerk[/] {disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}", async ctx =>
{
if (diskIndex == -1) return;
ClearConsole();
output = DiskHelper.FormatDisk(disks[diskIndex]);
output = DiskHelper.FormatDisk(disk);
if (output != string.Empty) ret = false;
await Task.CompletedTask;
});
if (!ret)

View File

@@ -9,11 +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()
{
("XEXMenu", "https://consolemods.org/wiki/images/3/35/XeXmenu_12.7z"),
("Rock Band Blitz", "https://download.digiex.net/Consoles/Xbox360/Arcade-games/RBBlitz.zip"),
("Simple 360 NAND Flasher", "https://www.consolemods.org/wiki/images/f/ff/Simple_360_NAND_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);
@@ -22,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)
@@ -45,13 +65,24 @@ 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}"))
Directory.CreateDirectory($"{DOWNLOAD_DIR}");
if (itemsToDownload.Any())
{
if (!Directory.Exists($"{DOWNLOAD_DIR}"))
Directory.CreateDirectory($"{DOWNLOAD_DIR}");
HttpClient downloadClient = new();
await AnsiConsole.Progress()
@@ -64,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 });
@@ -73,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.[/]");
}
@@ -90,22 +121,39 @@ namespace BadBuilder
if (File.Exists(destinationPath)) continue;
string existingPath = AnsiConsole.Prompt(
new TextPrompt<string>($"Enter the path for 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('"');
File.Copy(existingPath, destinationPath, overwrite: true);
AnsiConsole.MarkupLine($"[italic #76B900]Successfully copied [bold]{selectedItem}[/] to the working directory.[/]\n");
try
{
File.Copy(existingPath, destinationPath, overwrite: true);
AnsiConsole.MarkupLine($"[italic #76B900][bold]{selectedItem}[/] wurde erfolgreich in das Arbeitsverzeichnis kopiert.[/]\n");
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"[italic red]Fehler beim Kopieren von [bold]{selectedItem}[/] in das Arbeitsverzeichnis. Ausnahme: {ex.Message}[/]\n");
}
}
List<ArchiveItem> archives = items
.Select(item => new ArchiveItem(item.name, Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last())))
.ToList();
return 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,16 +17,13 @@ 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 });
await ArchiveHelper.ExtractFileAsync(item.name, item.path, task);
}));
});
string status = "[+]";
AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{filesToExtract.Count()}[/] files extracted.");
}
}
}

View File

@@ -7,17 +7,17 @@ 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")
);
}
public static List<HomebrewApp> ManageHomebrewApps()
internal static List<HomebrewApp> ManageHomebrewApps()
{
var homebrewApps = new List<HomebrewApp>();
@@ -27,51 +27,47 @@ 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();
if (newApp != null)
{
homebrewApps.Add(newApp.Value);
AnsiConsole.Status()
.Start("Adding...", ctx => ctx.Spinner(Spinner.Known.Dots2));
}
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;
@@ -81,39 +77,38 @@ 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.[/]");
AnsiConsole.MarkupLine("[#ffac4d]Ungültiger Ordnerpfad. Bitte erneut versuchen.\n[/]");
return null;
}
string[] xexFiles = Directory.GetFiles(folderPath, "*.xex");
if (xexFiles.Length == 0)
{
AnsiConsole.MarkupLine("[#ffac4d]No XEX files found in this folder.[/]");
return null;
}
string[] xexFiles = Directory.GetFiles(folderPath, "*.xex");
#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
{
0 => AnsiConsole.Prompt(
new TextPrompt<string>("[#ffac4d]No .xex files found.[/] Enter entry point:")
.Validate(path => File.Exists(path) ? ValidationResult.Success() : ValidationResult.Error("File not found"))
),
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:[/]")
new SelectionPrompt<string?>()
.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));
}
@@ -123,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)}[/]");
@@ -143,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

@@ -7,7 +7,7 @@ namespace BadBuilder.Helpers
{
internal static class DiskHelper
{
public static List<DiskInfo> GetDisks()
internal static List<DiskInfo> GetDisks()
{
var disks = new List<DiskInfo>();
@@ -32,9 +32,9 @@ 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]);
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",
"FreeMyXe/FreeMyXe"
"grimdoomer/Xbox360BadUpdate"
];
foreach (var repo in repos)
@@ -40,6 +39,7 @@ namespace BadBuilder.Helpers
{
try
{
byte[] downloadBuffer = new byte[8192];
using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
@@ -52,22 +52,22 @@ namespace BadBuilder.Helpers
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
using (FileStream fileStream = new($"{DOWNLOAD_DIR}/{filename}", System.IO.FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
byte[] buffer = new byte[8192];
Array.Clear(downloadBuffer);
while (true)
{
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
var read = await contentStream.ReadAsync(downloadBuffer, 0, downloadBuffer.Length);
if (read == 0)
break;
task.Increment(read);
await fileStream.WriteAsync(buffer, 0, read);
await fileStream.WriteAsync(downloadBuffer, 0, read);
}
}
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"[red]Error downloading:[/] {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

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

View File

@@ -2,29 +2,33 @@
global using ArchiveItem = (string name, string path);
global using HomebrewApp = (string name, string folder, string entryPoint);
using System.Linq;
using Spectre.Console;
using BadBuilder.Models;
using BadBuilder.Helpers;
using BadBuilder.Utilities;
using static BadBuilder.Utilities.Constants;
using BadBuilder.Utilities;
namespace BadBuilder
{
internal partial class Program
{
static readonly Style OrangeStyle = new Style(new Color(255, 114, 0));
static readonly Style LightOrangeStyle = new Style(new Color(255, 172, 77));
static readonly Style PeachStyle = new Style(new Color(255, 216, 153));
static readonly Style OrangeStyle = new(new Color(255, 114, 0));
static readonly Style LightOrangeStyle = new(new Color(255, 172, 77));
static readonly Style PeachStyle = new(new Color(255, 216, 153));
static readonly Style GreenStyle = new Style(new Color(118, 185, 0));
static readonly Style GrayStyle = new Style(new Color(132, 133, 137));
static readonly Style GreenStyle = new(new Color(118, 185, 0));
static readonly Style GrayStyle = new(new Color(132, 133, 137));
static string XexToolPath = string.Empty;
static string TargetDriveLetter = string.Empty;
static bool RequiresDashboardUpdate = false;
static ActionQueue actionQueue = new();
static DiskInfo targetDisk = new("Z:\\", "Fixed", 0, "", 0, int.MaxValue); // Default values just incase.
static void Main(string[] args)
{
ShowWelcomeMessage();
@@ -34,16 +38,19 @@ 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);
TargetDriveLetter = selectedDisk.Substring(0, 3);
TargetDriveLetter = selectedDisk[..3];
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == selectedDisk);
targetDisk = disks[diskIndex];
bool confirmation = PromptFormatConfirmation(selectedDisk);
if (confirmation)
{
if (!FormatDisk(disks, selectedDisk)) continue;
if (!FormatDisk(targetDisk)) continue;
break;
}
}
@@ -51,15 +58,119 @@ namespace BadBuilder
List<ArchiveItem> downloadedFiles = DownloadRequiredFiles().Result;
ExtractFiles(downloadedFiles).Wait();
ClearConsole();
string selectedDefaultApp = "XeUnshackle";
AnsiConsole.MarkupLine("[#76B900]{0}[/] Standard-Payload auf [bold]XeUnshackle[/] gesetzt.", Markup.Escape("[*]"));
AnsiConsole.MarkupLine("\n\n[#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}"))
{
var folderName = folder.Split("\\").Last();
switch (folderName)
switch (folder.Split("\\").Last())
{
case "XEXMenu":
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))
{
EnqueueMirrorDirectory(
avatarPayloadPath,
Path.Combine(TargetDriveLetter, "BadUpdatePayload"),
5
);
}
string avatarContentPath = Path.Combine(folder, "Content");
if (Directory.Exists(avatarContentPath))
{
EnqueueMirrorDirectory(
avatarContentPath,
Path.Combine(TargetDriveLetter, "Content"),
5
);
}
break;
case "Aurora Dashboard":
string auroraDestination = Path.Combine(TargetDriveLetter, "Apps", "Aurora");
Directory.CreateDirectory(auroraDestination);
bool auroraRootHasXex = Directory.GetFiles(folder, "*.xex", SearchOption.TopDirectoryOnly).Any();
if (auroraRootHasXex)
{
EnqueueMirrorDirectory(
folder,
auroraDestination,
4
);
break;
}
string[] auroraSubDirs = Directory.GetDirectories(folder);
string[] auroraRootFiles = Directory.GetFiles(folder);
string? primaryAuroraDir = auroraSubDirs.FirstOrDefault(dir =>
Directory.GetFiles(dir, "*.xex", SearchOption.TopDirectoryOnly).Any());
if (primaryAuroraDir == null)
{
EnqueueMirrorDirectory(
folder,
auroraDestination,
4
);
break;
}
EnqueueMirrorDirectory(
primaryAuroraDir,
auroraDestination,
4
);
foreach (string subDir in auroraSubDirs.Where(dir => dir != primaryAuroraDir))
{
EnqueueMirrorDirectory(
subDir,
Path.Combine(auroraDestination, Path.GetFileName(subDir)),
4
);
}
foreach (string file in auroraRootFiles)
{
EnqueueFileCopy(
file,
Path.Combine(auroraDestination, Path.GetFileName(file)),
4
);
}
break;
case "XeXmenu":
EnqueueMirrorDirectory(
Path.Combine(folder, $"{ContentFolder}C0DE9999"),
Path.Combine(TargetDriveLetter, $"{ContentFolder}C0DE9999"),
@@ -67,26 +178,40 @@ namespace BadBuilder
);
break;
case "FreeMyXe":
EnqueueFileCopy(
Path.Combine(folder, "FreeMyXe.xex"),
Path.Combine(TargetDriveLetter, "BadUpdatePayload", "default.xex"),
9
);
case "XeUnshackle":
string readmePath = Path.Combine(folder, "README - IMPORTANT.txt");
if (File.Exists(readmePath))
{
File.Delete(readmePath);
}
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;
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");
}
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);
@@ -113,41 +238,60 @@ 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();
ClearConsole();
if (!PromptAddHomebrew())
string launchIniPath = Path.Combine(TargetDriveLetter, "launch.ini");
if (File.Exists(launchIniPath))
{
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
Console.Write("\nPress any key to exit...");
Console.ReadKey();
Environment.Exit(0);
List<string> launchIniLines = File.ReadAllLines(launchIniPath).ToList();
for (int i = 0; i < launchIniLines.Count; i++)
{
if (launchIniLines[i].TrimStart().StartsWith("Default =", StringComparison.OrdinalIgnoreCase))
{
launchIniLines[i] = @"Default = Usb:\Apps\Aurora\Aurora.xex";
break;
}
}
const string commentText = "; Hallo von NiklasCFW :D";
int autoContIndex = launchIniLines.FindIndex(line =>
line.Trim().Equals("autocont = false", StringComparison.OrdinalIgnoreCase));
if (autoContIndex >= 0)
{
bool commentAlreadyPresent = autoContIndex > 0 &&
launchIniLines[autoContIndex - 1].Trim().Equals(commentText, StringComparison.OrdinalIgnoreCase);
if (!commentAlreadyPresent)
{
launchIniLines.Insert(autoContIndex, commentText);
}
}
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);
}
Console.WriteLine();
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");
List<HomebrewApp> homebrewApps = ManageHomebrewApps();
ClearConsole();
WriteHomebrewLog(1);
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Dein USB-Laufwerk ist einsatzbereit.", Markup.Escape("[+]"));
AnsiConsole.Status()
.SpinnerStyle(OrangeStyle)
.StartAsync("Copying and patching homebrew apps.", async ctx =>
{
await Task.WhenAll(homebrewApps.Select(async item =>
{
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);
}));
}).Wait();
string status = "[+]";
AnsiConsole.MarkupInterpolated($"\n[#76B900]{status}[/] [bold]{homebrewApps.Count()}[/] apps copied.\n");
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
Console.Write("\nPress any key to exit...");
Console.Write("\nBeliebige Taste zum Beenden drücken...");
Console.ReadKey();
}
@@ -167,6 +311,13 @@ namespace BadBuilder
}, priority);
}
static void WriteHomebrewLog(int count)
{
string logPath = Path.Combine(TargetDriveLetter, "info.txt");
string logEntry = $"- {count} Homebrew-App(s) hinzugefügt (einschließlich Simple 360 NAND Flasher)\n";
File.AppendAllText(logPath, logEntry);
}
static void ShowWelcomeMessage() => AnsiConsole.Markup(
"""
@@ -177,19 +328,19 @@ namespace BadBuilder
[#CCE388] [/]
[#CCE388] [/]
[#76B900]v0.10a[/]
[#76B900]v0.31[/]
Xbox 360 [#FF7200]BadUpdate[/] USB Builder
[#848589]Created by Pdawg[/]
[#848589]Created by Pdawg | Forked by NiklasCFW[/]
[#76B900][/]
""");
""");
static string PromptForAction() => AnsiConsole.Prompt(
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,15 +18,39 @@
internal async Task ExecuteActionsAsync()
{
foreach (var priority in priorityQueue.Keys.OrderByDescending(p => p))
int totalActions = priorityQueue.Values.Sum(queue => queue.Count);
if (totalActions == 0)
{
var actions = priorityQueue[priority];
while (actions.Count > 0)
{
var action = actions.Dequeue();
await action();
}
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];
while (actions.Count > 0)
{
var action = actions.Dequeue();
await action();
copyTask.Increment(1);
}
}
});
priorityQueue.Clear();
}
}
}
}

View File

@@ -3,9 +3,14 @@
internal static class Constants
{
internal const string WORKING_DIR = "Work";
internal const string DOWNLOAD_DIR = $@"{WORKING_DIR}\\Download";
internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\\Extract";
internal const string DOWNLOAD_DIR = $@"{WORKING_DIR}\Download";
internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\Extract";
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;
}
}

View File

@@ -13,30 +13,55 @@ BadBuilder is a tool for creating a BadUpdate USB drive for the Xbox 360. It aut
- Detects and downloads the latest required files automatically.
- Recognizes previously downloaded files and reuses them by default.
- Allows specifying custom paths for required files if they are already on your system.
> [!WARNING]
> [!IMPORTANT]
> BadBuilder does not dynamically locate files inside ZIP archives. If your provided archive has a different folder structure than expected, the process will fail abruptly. Ensure your archive matches the expected format if specifying an existing copy.
### File Extraction & Copying
- Extracts all necessary files automatically.
- Prepares the USB drive for the BadUpdate exploit by copying all required files.
### Homebrew Support
- Allows adding homebrew applications by specifying their root folder.
- Automatically searches for the entry point (`.xex`) file within the folder.
- If multiple `.xex` files are found, BadBuilder will prompt you to select the correct one.
- Copies all necessary files and patches the entry `.xex` using the downloaded XexTool.
- 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:
```
dotnet restore
dotnet build BadBuilder.sln
```
To publish a self-contained single-file release for 64-bit Windows:
```
dotnet publish BadBuilder/BadBuilder.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
```
If you want the runtime bundled but with trimming and compression enabled (smaller exe, may require extra testing):
```
dotnet publish BadBuilder/BadBuilder.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:EnableCompressionInSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false
```
To create framework-dependent single-file publishes (requires .NET 8 runtime on the target machine), omit `--self-contained true` and pick the architecture you need:
```
dotnet publish BadBuilder/BadBuilder.csproj -c Release -r win-x64 -p:SelfContained=false -p:PublishSingleFile=true
dotnet publish BadBuilder/BadBuilder.csproj -c Release -r win-x86 -p:SelfContained=false -p:PublishSingleFile=true
dotnet publish BadBuilder/BadBuilder.csproj -c Release -r win-arm64 -p:SelfContained=false -p:PublishSingleFile=true
```
## How to Use
1. **Launch the executable**. It will open inside of a Terminal window.
2. **Formatting (Windows Only):** BadBuilder will format your USB drive as FAT32, even if its larger than 32GB.
3. **Download Files:** BadBuilder will fetch the required exploit files or let you specify an existing location.
> [!CAUTION]
> Formatting a disk means that all data will be lost. Make sure you have selected the right drive before confirming the format. I am not responsible for any data loss.
3. **Download Files:** BadBuilder will fetch the required exploit files or let you specify an existing location. This includes the ABadAvatar payload, XeUnshackle, Aurora Dashboard, Rock Band Blitz, XeXMenu, and Simple 360 NAND Flasher.
4. **Extract Files:** BadBuilder will automatically extract everything needed.
5. **Copy files:** BadBuilder will copy all of the extracted files to the correct locations.
5. **Add Homebrew (Optional):**
- Specify the root folder of your homebrew application (e.g., `D:\Aurora 0.7b.2 - Release Package`).
- BadBuilder will locate the `.xex` file inside.
- If multiple `.xex` files exist, youll be prompted to choose the correct entry point.
- First, all necessary files will be copied, then, the `.xex` file will be patched using **XexTool**.
- This ensures that the original copy of the homebrew program will **not** be modified, as it is instead done in-place on the USB drive.
5. **Configure Payload:** BadBuilder automatically configures [XeUnshackle](https://github.com/Byrom90/XeUnshackle) as the default payload for BadUpdate.
6. **Copy Files:** BadBuilder will copy all of the extracted files to the correct locations, including deploying the Aurora Dashboard to `Apps\Aurora`.
## Example Homebrew Folder Structure
If you want to add Aurora, you would select the **root folder**, like:
@@ -60,16 +85,17 @@ Aurora 0.7b.2 - Release Package/
```
BadBuilder will detect `Aurora.xex` as the entry point and patch it accordingly.
> [!NOTE]
> Homebrew apps which do not contain the entry point in the root folder are not currently supported.
### Using unsupported homebrew
If you would still like to use unsupported homebrew apps, you'll have to copy them manually. To do so, perform these steps:
1. Copy the entire root folder into the `Apps` folder on your target drive.
2. Find and copy the path of the entry point.
3. Open a terminal window and construct the following command:
- **Command:** `"<path_to_folder_of_BadBuilder.exe>\Work\Extract\BadUpdate Tools\XePatcher\XexTool.exe" -m r -r a "<path_to_entry_point>"`
- **Example:** `"C:\BadBuilder\Work\Extract\BadUpdate Tools\XePatcher\XexTool.exe" -m r -r a "H:\Apps\Aurora 0.7b.2 - Release Package\Aurora.xex"`
> [!IMPORTANT]
> Homebrew apps which do not contain the entry point in the root folder will require you to manually enter the path of the entry point.
## Reporting Issues
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
- **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)
- **Byrom90:** [XeUnshackle](https://github.com/Byrom90/XeUnshackle)
- **Swizzy:** [Simple 360 NAND Flasher](https://github.com/Swizzy/XDK_Projects)
- **Team XeDEV:** XeXMenu