Compare commits
9 Commits
993a27a33a
...
a08a6e0821
| Author | SHA1 | Date | |
|---|---|---|---|
| a08a6e0821 | |||
|
|
6b764c0273 | ||
|
|
b05a89ada4 | ||
|
|
57085990bc | ||
|
|
55cfed148a | ||
|
|
ed41cf84d4 | ||
|
|
0ea82ee67c | ||
|
|
da78608a75 | ||
|
|
e039a2b112 |
@@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
internal const string ANSI_RESET = "\u001b[0m";
|
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_READ = 0x80000000;
|
||||||
internal const uint GENERIC_WRITE = 0x40000000;
|
internal const uint GENERIC_WRITE = 0x40000000;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma warning disable CA1416
|
#pragma warning disable CA1416
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Diagnostics;
|
||||||
using Windows.Win32.System.Ioctl;
|
using Windows.Win32.System.Ioctl;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using Microsoft.Win32.SafeHandles;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -13,12 +14,34 @@ namespace BadBuilder.Formatter
|
|||||||
{
|
{
|
||||||
public static class DiskFormatter
|
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($"Native format failed with exit code: {process.ExitCode}");
|
||||||
|
}
|
||||||
|
|
||||||
string devicePath = $"\\\\.\\{driveLetter}:";
|
string devicePath = $"\\\\.\\{driveLetter}:";
|
||||||
uint volumeID = GetVolumeID();
|
uint volumeID = GetVolumeID();
|
||||||
|
|
||||||
using SafeFileHandle driveHandle = OpenDeviceHandle(devicePath);
|
SafeFileHandle driveHandle = OpenDeviceHandle(devicePath);
|
||||||
if (driveHandle.IsInvalid) return Error("Unable to open device. 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))
|
||||||
@@ -48,6 +71,10 @@ namespace BadBuilder.Formatter
|
|||||||
if (!UnlockDevice(driveHandle) || !DismountVolume(driveHandle))
|
if (!UnlockDevice(driveHandle) || !DismountVolume(driveHandle))
|
||||||
return Error($"Failed to release the device. GetLastError: {Marshal.GetLastWin32Error()}");
|
return Error($"Failed to release the device. GetLastError: {Marshal.GetLastWin32Error()}");
|
||||||
|
|
||||||
|
driveHandle.Dispose();
|
||||||
|
|
||||||
|
if (!SetVolumeLabel($"{driveLetter}:", "BADUPDATE"))
|
||||||
|
return Error($"Unable to set volume label. GetLastError: {Marshal.GetLastWin32Error()}");
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
@@ -109,14 +136,18 @@ namespace BadBuilder.Formatter
|
|||||||
|
|
||||||
private static unsafe FAT32BootSector InitializeBootSector(DISK_GEOMETRY diskGeometry, PARTITION_INFORMATION partitionInfo, uint totalSectors, uint volumeID)
|
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 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
|
FAT32BootSector bootSector = new FAT32BootSector
|
||||||
{
|
{
|
||||||
BytesPerSector = (ushort)diskGeometry.BytesPerSector,
|
BytesPerSector = (ushort)diskGeometry.BytesPerSector,
|
||||||
SectorsPerCluster = sectorsPerCluster,
|
SectorsPerCluster = (byte)sectorsPerCluster,
|
||||||
ReservedSectorCount = 32,
|
ReservedSectorCount = (ushort)reserved,
|
||||||
NumberOfFATs = 2,
|
NumberOfFATs = 2,
|
||||||
MediaDescriptor = 0xF8,
|
MediaDescriptor = 0xF8,
|
||||||
SectorsPerTrack = (ushort)diskGeometry.SectorsPerTrack,
|
SectorsPerTrack = (ushort)diskGeometry.SectorsPerTrack,
|
||||||
@@ -186,8 +217,8 @@ namespace BadBuilder.Formatter
|
|||||||
{
|
{
|
||||||
uint bytesPerSector = diskGeometry.BytesPerSector;
|
uint bytesPerSector = diskGeometry.BytesPerSector;
|
||||||
uint totalSectors = (uint)(partitionInfo.PartitionLength / bytesPerSector);
|
uint totalSectors = (uint)(partitionInfo.PartitionLength / bytesPerSector);
|
||||||
uint systemAreaSize = bootSector.ReservedSectorCount + (bootSector.NumberOfFATs * bootSector.SectorsPerFAT) + bootSector.SectorsPerCluster;
|
uint userAreaSize = totalSectors - bootSector.ReservedSectorCount - (bootSector.NumberOfFATs * bootSector.SectorsPerFAT);
|
||||||
uint userAreaSize = totalSectors - systemAreaSize;
|
uint zeroOut = bootSector.ReservedSectorCount + (bootSector.NumberOfFATs * bootSector.SectorsPerFAT) + bootSector.SectorsPerCluster;
|
||||||
uint clusterCount = userAreaSize / bootSector.SectorsPerCluster;
|
uint clusterCount = userAreaSize / bootSector.SectorsPerCluster;
|
||||||
|
|
||||||
if (clusterCount < 65536 || clusterCount > 0x0FFFFFFF)
|
if (clusterCount < 65536 || clusterCount > 0x0FFFFFFF)
|
||||||
@@ -195,7 +226,7 @@ namespace BadBuilder.Formatter
|
|||||||
|
|
||||||
fsInfo.FreeClusterCount = clusterCount - 1;
|
fsInfo.FreeClusterCount = clusterCount - 1;
|
||||||
|
|
||||||
ZeroOutSectors(driveHandle, 0, systemAreaSize, bytesPerSector);
|
ZeroOutSectors(driveHandle, 0, zeroOut, bytesPerSector);
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
@@ -210,14 +241,6 @@ namespace BadBuilder.Formatter
|
|||||||
WriteSector(driveHandle, sectorStart, 1, bytesPerSector, UintArrayToBytes(firstFATSector));
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ DeviceIoControl
|
|||||||
CloseHandle
|
CloseHandle
|
||||||
VirtualAlloc
|
VirtualAlloc
|
||||||
VirtualFree
|
VirtualFree
|
||||||
|
SetVolumeLabelW
|
||||||
GUID
|
GUID
|
||||||
DISK_GEOMETRY
|
DISK_GEOMETRY
|
||||||
PARTITION_INFORMATION
|
PARTITION_INFORMATION
|
||||||
PARTITION_INFORMATION_EX
|
PARTITION_INFORMATION_EX
|
||||||
SET_PARTITION_INFORMATION
|
|
||||||
@@ -39,23 +39,27 @@ namespace BadBuilder.Formatter
|
|||||||
return (uint)(low | (hi << 16));
|
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 fatElementSize = 4;
|
||||||
|
const ulong reservedClusters = 2;
|
||||||
|
|
||||||
ulong numerator = fatElementSize * (totalSectors - reservedSectors);
|
ulong numerator = diskSize - reservedSectors + reservedClusters * sectorsPerCluster;
|
||||||
ulong denominator = (sectorsPerCluster * bytesPerSector) + (fatElementSize * numberOfFATs);
|
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),
|
< 64 * MB => ((512) / bytesPerSector),
|
||||||
var size when size > 8192 => (byte)((8 * 1024) / bytesPerSector),
|
< 128 * MB => ((1 * KB) / bytesPerSector),
|
||||||
var size when size > 16384 => (byte)((16 * 1024) / bytesPerSector),
|
< 256 * MB => ((2 * KB) / bytesPerSector),
|
||||||
var size when size > 32768 => (byte)((32 * 1024) / bytesPerSector),
|
< 8 * GB => ((4 * KB) / bytesPerSector),
|
||||||
_ => 1
|
< 16 * GB => ((8 * KB) / bytesPerSector),
|
||||||
|
< 32 * GB => ((16 * KB) / bytesPerSector),
|
||||||
|
< 2 * TB => ((32 * KB) / bytesPerSector),
|
||||||
|
_ => ((64 * KB) / bytesPerSector)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,18 +33,15 @@ namespace BadBuilder
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool FormatDisk(List<DiskInfo> disks, string selectedDisk)
|
static bool FormatDisk(DiskInfo disk)
|
||||||
{
|
{
|
||||||
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == selectedDisk);
|
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
string output = string.Empty;
|
string output = string.Empty;
|
||||||
|
|
||||||
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {selectedDisk}", async ctx =>
|
AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}", async ctx =>
|
||||||
{
|
{
|
||||||
if (diskIndex == -1) return;
|
|
||||||
|
|
||||||
ClearConsole();
|
ClearConsole();
|
||||||
output = DiskHelper.FormatDisk(disks[diskIndex]);
|
output = DiskHelper.FormatDisk(disk);
|
||||||
if (output != string.Empty) ret = false;
|
if (output != string.Empty) ret = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ namespace BadBuilder
|
|||||||
{
|
{
|
||||||
List<DownloadItem> items = new()
|
List<DownloadItem> items = new()
|
||||||
{
|
{
|
||||||
("XEXMenu", "https://consolemods.org/wiki/images/3/35/XeXmenu_12.7z"),
|
("ABadAvatar", "https://cdn.niklascfw.de/files/ABadAvatar-publicbeta1.0.zip"),
|
||||||
("Rock Band Blitz", "https://download.digiex.net/Consoles/Xbox360/Arcade-games/RBBlitz.zip"),
|
("Aurora Dashboard", "https://cdn.niklascfw.de/files/Aurora%200.7b.2%20-%20Release%20Package.rar"),
|
||||||
("Simple 360 NAND Flasher", "https://www.consolemods.org/wiki/images/f/ff/Simple_360_NAND_Flasher.7z"),
|
("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"),
|
||||||
};
|
};
|
||||||
await DownloadHelper.GetGitHubAssets(items);
|
await DownloadHelper.GetGitHubAssets(items);
|
||||||
|
|
||||||
@@ -47,11 +49,11 @@ namespace BadBuilder
|
|||||||
|
|
||||||
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}"))
|
||||||
|
Directory.CreateDirectory($"{DOWNLOAD_DIR}");
|
||||||
|
|
||||||
if (itemsToDownload.Any())
|
if (itemsToDownload.Any())
|
||||||
{
|
{
|
||||||
if (!Directory.Exists($"{DOWNLOAD_DIR}"))
|
|
||||||
Directory.CreateDirectory($"{DOWNLOAD_DIR}");
|
|
||||||
|
|
||||||
HttpClient downloadClient = new();
|
HttpClient downloadClient = new();
|
||||||
|
|
||||||
await AnsiConsole.Progress()
|
await AnsiConsole.Progress()
|
||||||
@@ -90,7 +92,7 @@ namespace BadBuilder
|
|||||||
if (File.Exists(destinationPath)) continue;
|
if (File.Exists(destinationPath)) continue;
|
||||||
|
|
||||||
string existingPath = AnsiConsole.Prompt(
|
string existingPath = AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>($"Enter the path for the [bold]{selectedItem}[/] archive:")
|
new TextPrompt<string>($"Enter the path to the [bold]{selectedItem}[/] archive:")
|
||||||
.PromptStyle(LightOrangeStyle)
|
.PromptStyle(LightOrangeStyle)
|
||||||
.Validate(path =>
|
.Validate(path =>
|
||||||
{
|
{
|
||||||
@@ -100,10 +102,17 @@ namespace BadBuilder
|
|||||||
})
|
})
|
||||||
).Trim().Trim('"');
|
).Trim().Trim('"');
|
||||||
|
|
||||||
File.Copy(existingPath, destinationPath, overwrite: true);
|
try
|
||||||
AnsiConsole.MarkupLine($"[italic #76B900]Successfully copied [bold]{selectedItem}[/] to the working directory.[/]\n");
|
{
|
||||||
}
|
File.Copy(existingPath, destinationPath, overwrite: true);
|
||||||
|
AnsiConsole.MarkupLine($"[italic #76B900]Successfully copied [bold]{selectedItem}[/] to the working directory.[/]\n");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[italic red]Failed to copy [bold]{selectedItem}[/] to the working directory. Exception: {ex.Message}[/]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ namespace BadBuilder
|
|||||||
await ArchiveHelper.ExtractFileAsync(item.name, item.path, task);
|
await ArchiveHelper.ExtractFileAsync(item.name, item.path, task);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
string status = "[+]";
|
|
||||||
AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{filesToExtract.Count()}[/] files extracted.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace BadBuilder
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<HomebrewApp> ManageHomebrewApps()
|
internal static List<HomebrewApp> ManageHomebrewApps()
|
||||||
{
|
{
|
||||||
var homebrewApps = new List<HomebrewApp>();
|
var homebrewApps = new List<HomebrewApp>();
|
||||||
|
|
||||||
@@ -37,11 +37,7 @@ namespace BadBuilder
|
|||||||
|
|
||||||
var newApp = AddHomebrewApp();
|
var newApp = AddHomebrewApp();
|
||||||
if (newApp != null)
|
if (newApp != null)
|
||||||
{
|
|
||||||
homebrewApps.Add(newApp.Value);
|
homebrewApps.Add(newApp.Value);
|
||||||
AnsiConsole.Status()
|
|
||||||
.Start("Adding...", ctx => ctx.Spinner(Spinner.Known.Dots2));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "View Added Apps":
|
case "View Added Apps":
|
||||||
@@ -85,31 +81,30 @@ namespace BadBuilder
|
|||||||
string folderPath = AnsiConsole.Ask<string>("[#FFA500]Enter the folder path for the app:[/]");
|
string folderPath = AnsiConsole.Ask<string>("[#FFA500]Enter the folder path for the app:[/]");
|
||||||
if (!Directory.Exists(folderPath))
|
if (!Directory.Exists(folderPath))
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.[/]");
|
AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.\n[/]");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] xexFiles = Directory.GetFiles(folderPath, "*.xex");
|
string[] xexFiles = Directory.GetFiles(folderPath, "*.xex");
|
||||||
if (xexFiles.Length == 0)
|
|
||||||
{
|
|
||||||
AnsiConsole.MarkupLine("[#ffac4d]No XEX files found in this folder.[/]");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||||
|
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
|
||||||
string entryPoint = xexFiles.Length switch
|
string entryPoint = xexFiles.Length switch
|
||||||
{
|
{
|
||||||
0 => AnsiConsole.Prompt(
|
0 => AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>("[#ffac4d]No .xex files found.[/] Enter entry point:")
|
new TextPrompt<string>("[grey]No .xex files found in this folder.[/] [#FFA500]Enter the path to the entry point:[/]")
|
||||||
.Validate(path => File.Exists(path) ? ValidationResult.Success() : ValidationResult.Error("File not found"))
|
.Validate(path => File.Exists(path.Trim().Trim('"')) && Path.GetExtension(path.Trim().Trim('"')) == ".xex" ? ValidationResult.Success() : ValidationResult.Error("[#ffac4d]File not found or is not an XEX file.[/]\n"))
|
||||||
),
|
).Trim().Trim('"'),
|
||||||
1 => xexFiles[0],
|
1 => xexFiles[0],
|
||||||
_ => AnsiConsole.Prompt(
|
_ => AnsiConsole.Prompt(
|
||||||
new SelectionPrompt<string>()
|
new SelectionPrompt<string?>()
|
||||||
.Title("[#FFA500]Select entry point:[/]")
|
.Title("[#FFA500]Select entry point:[/]")
|
||||||
.HighlightStyle(PeachStyle)
|
.HighlightStyle(PeachStyle)
|
||||||
.AddChoices(xexFiles.Select(Path.GetFileName))
|
.AddChoices(xexFiles.Select(Path.GetFileName))
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
|
||||||
|
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||||
|
|
||||||
ClearConsole();
|
ClearConsole();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace BadBuilder.Helpers
|
|||||||
{
|
{
|
||||||
internal static class DiskHelper
|
internal static class DiskHelper
|
||||||
{
|
{
|
||||||
public static List<DiskInfo> GetDisks()
|
internal static List<DiskInfo> GetDisks()
|
||||||
{
|
{
|
||||||
var disks = new List<DiskInfo>();
|
var disks = new List<DiskInfo>();
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ namespace BadBuilder.Helpers
|
|||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
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 Formatting is currently only supported on Windows. Please format your drive manually and try again.";
|
||||||
|
|
||||||
return DiskFormatter.FormatVolume(disk.DriveLetter[0]);
|
return DiskFormatter.FormatVolume(disk.DriveLetter[0], disk.TotalSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace BadBuilder.Helpers
|
|||||||
List<string> repos =
|
List<string> repos =
|
||||||
[
|
[
|
||||||
"grimdoomer/Xbox360BadUpdate",
|
"grimdoomer/Xbox360BadUpdate",
|
||||||
"FreeMyXe/FreeMyXe"
|
"Byrom90/XeUnshackle"
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (var repo in repos)
|
foreach (var repo in repos)
|
||||||
@@ -28,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)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ namespace BadBuilder.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
byte[] downloadBuffer = new byte[8192];
|
||||||
using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
|
using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
|
||||||
{
|
{
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
@@ -52,22 +54,22 @@ namespace BadBuilder.Helpers
|
|||||||
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
|
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
|
||||||
using (FileStream fileStream = new($"{DOWNLOAD_DIR}/{filename}", System.IO.FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
|
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)
|
while (true)
|
||||||
{
|
{
|
||||||
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
|
var read = await contentStream.ReadAsync(downloadBuffer, 0, downloadBuffer.Length);
|
||||||
if (read == 0)
|
if (read == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
task.Increment(read);
|
task.Increment(read);
|
||||||
await fileStream.WriteAsync(buffer, 0, read);
|
await fileStream.WriteAsync(downloadBuffer, 0, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine($"[red]Error downloading:[/] {ex}");
|
AnsiConsole.MarkupLine($"[red]Error downloading file from [bold]{url}[/]: {ex}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using Spectre.Console;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace BadBuilder.Helpers
|
namespace BadBuilder.Helpers
|
||||||
{
|
{
|
||||||
@@ -12,8 +13,8 @@ namespace BadBuilder.Helpers
|
|||||||
{
|
{
|
||||||
FileName = xexToolPath,
|
FileName = xexToolPath,
|
||||||
Arguments = $"-m r -r a \"{xexPath}\"",
|
Arguments = $"-m r -r a \"{xexPath}\"",
|
||||||
RedirectStandardOutput = false,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = false,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true
|
CreateNoWindow = true
|
||||||
}
|
}
|
||||||
@@ -21,6 +22,13 @@ namespace BadBuilder.Helpers
|
|||||||
|
|
||||||
process.Start();
|
process.Start();
|
||||||
await process.WaitForExitAsync();
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
{
|
||||||
|
string status = "[-]";
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"\n[#FF7200]{status}[/] The program {Path.GetFileNameWithoutExtension(xexPath)} was unable to be patched. XexTool output:");
|
||||||
|
Console.WriteLine(process.StandardError.ReadToEnd());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,29 +2,32 @@
|
|||||||
global using ArchiveItem = (string name, string path);
|
global using ArchiveItem = (string name, string path);
|
||||||
global using HomebrewApp = (string name, string folder, string entryPoint);
|
global using HomebrewApp = (string name, string folder, string entryPoint);
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using BadBuilder.Models;
|
using BadBuilder.Models;
|
||||||
using BadBuilder.Helpers;
|
using BadBuilder.Helpers;
|
||||||
|
using BadBuilder.Utilities;
|
||||||
|
|
||||||
using static BadBuilder.Utilities.Constants;
|
using static BadBuilder.Utilities.Constants;
|
||||||
using BadBuilder.Utilities;
|
|
||||||
|
|
||||||
namespace BadBuilder
|
namespace BadBuilder
|
||||||
{
|
{
|
||||||
internal partial class Program
|
internal partial class Program
|
||||||
{
|
{
|
||||||
static readonly Style OrangeStyle = new Style(new Color(255, 114, 0));
|
static readonly Style OrangeStyle = new(new Color(255, 114, 0));
|
||||||
static readonly Style LightOrangeStyle = new Style(new Color(255, 172, 77));
|
static readonly Style LightOrangeStyle = new(new Color(255, 172, 77));
|
||||||
static readonly Style PeachStyle = new Style(new Color(255, 216, 153));
|
static readonly Style PeachStyle = new(new Color(255, 216, 153));
|
||||||
|
|
||||||
static readonly Style GreenStyle = new Style(new Color(118, 185, 0));
|
static readonly Style GreenStyle = new(new Color(118, 185, 0));
|
||||||
static readonly Style GrayStyle = new Style(new Color(132, 133, 137));
|
static readonly Style GrayStyle = new(new Color(132, 133, 137));
|
||||||
|
|
||||||
static string XexToolPath = string.Empty;
|
static string XexToolPath = string.Empty;
|
||||||
static string TargetDriveLetter = string.Empty;
|
static string TargetDriveLetter = string.Empty;
|
||||||
|
|
||||||
static ActionQueue actionQueue = new();
|
static ActionQueue actionQueue = new();
|
||||||
|
|
||||||
|
static DiskInfo targetDisk = new("Z:\\", "Fixed", 0, "", 0, int.MaxValue); // Default values just incase.
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
ShowWelcomeMessage();
|
ShowWelcomeMessage();
|
||||||
@@ -38,12 +41,15 @@ namespace BadBuilder
|
|||||||
|
|
||||||
List<DiskInfo> disks = DiskHelper.GetDisks();
|
List<DiskInfo> disks = DiskHelper.GetDisks();
|
||||||
string selectedDisk = PromptDiskSelection(disks);
|
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);
|
bool confirmation = PromptFormatConfirmation(selectedDisk);
|
||||||
if (confirmation)
|
if (confirmation)
|
||||||
{
|
{
|
||||||
if (!FormatDisk(disks, selectedDisk)) continue;
|
if (!FormatDisk(targetDisk)) continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,15 +57,94 @@ namespace BadBuilder
|
|||||||
List<ArchiveItem> downloadedFiles = DownloadRequiredFiles().Result;
|
List<ArchiveItem> downloadedFiles = DownloadRequiredFiles().Result;
|
||||||
ExtractFiles(downloadedFiles).Wait();
|
ExtractFiles(downloadedFiles).Wait();
|
||||||
|
|
||||||
|
ClearConsole();
|
||||||
|
string selectedDefaultApp = "XeUnshackle";
|
||||||
|
AnsiConsole.MarkupLine("[#76B900]{0}[/] Default payload set to [bold]XeUnshackle[/].", Markup.Escape("[*]"));
|
||||||
|
|
||||||
AnsiConsole.MarkupLine("\n\n[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]"));
|
AnsiConsole.MarkupLine("[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]"));
|
||||||
foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}"))
|
foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}"))
|
||||||
{
|
{
|
||||||
var folderName = folder.Split("\\").Last();
|
switch (folder.Split("\\").Last())
|
||||||
|
|
||||||
switch (folderName)
|
|
||||||
{
|
{
|
||||||
case "XEXMenu":
|
case "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(
|
EnqueueMirrorDirectory(
|
||||||
Path.Combine(folder, $"{ContentFolder}C0DE9999"),
|
Path.Combine(folder, $"{ContentFolder}C0DE9999"),
|
||||||
Path.Combine(TargetDriveLetter, $"{ContentFolder}C0DE9999"),
|
Path.Combine(TargetDriveLetter, $"{ContentFolder}C0DE9999"),
|
||||||
@@ -67,10 +152,12 @@ namespace BadBuilder
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "FreeMyXe":
|
case "XeUnshackle":
|
||||||
EnqueueFileCopy(
|
string subFolderPath = Directory.GetDirectories(folder).FirstOrDefault();
|
||||||
Path.Combine(folder, "FreeMyXe.xex"),
|
File.Delete(Path.Combine(subFolderPath, "README - IMPORTANT.txt"));
|
||||||
Path.Combine(TargetDriveLetter, "BadUpdatePayload", "default.xex"),
|
EnqueueMirrorDirectory(
|
||||||
|
subFolderPath,
|
||||||
|
TargetDriveLetter,
|
||||||
9
|
9
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -79,14 +166,10 @@ namespace BadBuilder
|
|||||||
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 Storage Device");
|
writer.WriteLine("USB Storage Device");
|
||||||
}
|
|
||||||
|
|
||||||
using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "info.txt")))
|
using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "info.txt")))
|
||||||
{
|
writer.WriteLine($"This drive was created with BadBuilder by Pdawg.\nFind more info here: https://github.com/Pdawg-bytes/BadBuilder\nConfiguration: \n- BadUpdate target binary: {selectedDefaultApp}");
|
||||||
writer.WriteLine("This drive was created with BadBuilder by Pdawg.\nFind more info here: https://github.com/Pdawg-bytes/BadBuilder");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -113,38 +196,48 @@ namespace BadBuilder
|
|||||||
}, 6);
|
}, 6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: throw new Exception($"Unexpected directory in working folder: {folder}");
|
default: throw new Exception($"[-] Unexpected directory in working folder: {folder}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actionQueue.ExecuteActionsAsync().Wait();
|
actionQueue.ExecuteActionsAsync().Wait();
|
||||||
|
|
||||||
ClearConsole();
|
string launchIniPath = Path.Combine(TargetDriveLetter, "launch.ini");
|
||||||
if (!PromptAddHomebrew())
|
if (File.Exists(launchIniPath))
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
|
List<string> launchIniLines = File.ReadAllLines(launchIniPath).ToList();
|
||||||
Console.Write("\nPress any key to exit...");
|
|
||||||
Console.ReadKey();
|
for (int i = 0; i < launchIniLines.Count; i++)
|
||||||
Environment.Exit(0);
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllLines(launchIniPath, launchIniLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine();
|
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");
|
||||||
List<HomebrewApp> homebrewApps = ManageHomebrewApps();
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
|
ClearConsole();
|
||||||
|
WriteHomebrewLog(1);
|
||||||
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
|
AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
|
||||||
|
|
||||||
Console.Write("\nPress any key to exit...");
|
Console.Write("\nPress any key to exit...");
|
||||||
@@ -167,6 +260,13 @@ namespace BadBuilder
|
|||||||
}, priority);
|
}, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WriteHomebrewLog(int count)
|
||||||
|
{
|
||||||
|
string logPath = Path.Combine(TargetDriveLetter, "info.txt");
|
||||||
|
string logEntry = $"- {count} homebrew app(s) added (including Simple 360 NAND Flasher)\n";
|
||||||
|
File.AppendAllText(logPath, logEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void ShowWelcomeMessage() => AnsiConsole.Markup(
|
static void ShowWelcomeMessage() => AnsiConsole.Markup(
|
||||||
"""
|
"""
|
||||||
@@ -177,12 +277,12 @@ namespace BadBuilder
|
|||||||
[#CCE388]██████╔╝██║ ██║██████╔╝██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║[/]
|
[#CCE388]██████╔╝██║ ██║██████╔╝██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║[/]
|
||||||
[#CCE388]╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝[/]
|
[#CCE388]╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝[/]
|
||||||
|
|
||||||
[#76B900]──────────────────────────────────────────────────────────────────────v0.10a[/]
|
[#76B900]───────────────────────────────────────────────────────────────────────v0.31[/]
|
||||||
───────────────────────Xbox 360 [#FF7200]BadUpdate[/] USB Builder───────────────────────
|
───────────────────────Xbox 360 [#FF7200]BadUpdate[/] USB Builder───────────────────────
|
||||||
[#848589]Created by Pdawg[/]
|
[#848589]Created by Pdawg | Forked by NiklasCFW[/]
|
||||||
[#76B900]────────────────────────────────────────────────────────────────────────────[/]
|
[#76B900]────────────────────────────────────────────────────────────────────────────[/]
|
||||||
|
|
||||||
""");
|
""");
|
||||||
|
|
||||||
static string PromptForAction() => AnsiConsole.Prompt(
|
static string PromptForAction() => AnsiConsole.Prompt(
|
||||||
new SelectionPrompt<string>()
|
new SelectionPrompt<string>()
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
internal static class Constants
|
internal static class Constants
|
||||||
{
|
{
|
||||||
internal const string WORKING_DIR = "Work";
|
internal const string WORKING_DIR = "Work";
|
||||||
internal const string DOWNLOAD_DIR = $@"{WORKING_DIR}\\Download";
|
internal const string DOWNLOAD_DIR = $@"{WORKING_DIR}\Download";
|
||||||
internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\\Extract";
|
internal const string EXTRACTED_DIR = $@"{WORKING_DIR}\Extract";
|
||||||
|
|
||||||
internal const string ContentFolder = "Content\\0000000000000000\\";
|
internal const string ContentFolder = "Content\\0000000000000000\\";
|
||||||
|
|
||||||
|
internal const long KB = 1024L;
|
||||||
|
internal const long MB = 1048576L;
|
||||||
|
internal const long GB = 1073741824L;
|
||||||
|
internal const long TB = 1099511627776L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
70
README.md
70
README.md
@@ -13,30 +13,52 @@ BadBuilder is a tool for creating a BadUpdate USB drive for the Xbox 360. It aut
|
|||||||
- Detects and downloads the latest required files automatically.
|
- Detects and downloads the latest required files automatically.
|
||||||
- Recognizes previously downloaded files and reuses them by default.
|
- Recognizes previously downloaded files and reuses them by default.
|
||||||
- Allows specifying custom paths for required files if they are already on your system.
|
- 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.
|
> 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
|
### File Extraction & Copying
|
||||||
- Extracts all necessary files automatically.
|
- Extracts all necessary files automatically.
|
||||||
- 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.
|
||||||
### Homebrew Support
|
- Includes optional content packs such as **ABadAvatar** and the **Aurora Dashboard**.
|
||||||
- Allows adding homebrew applications by specifying their root folder.
|
|
||||||
- Automatically searches for the entry point (`.xex`) file within the folder.
|
## Build
|
||||||
- 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.
|
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
|
## How to Use
|
||||||
1. **Launch the executable**. It will open inside of a Terminal window.
|
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 it’s larger than 32GB.
|
2. **Formatting (Windows Only):** BadBuilder will format your USB drive as FAT32, even if it’s 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.
|
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. **Configure Payload:** BadBuilder automatically configures [XeUnshackle](https://github.com/Byrom90/XeUnshackle) as the default payload for BadUpdate.
|
||||||
5. **Add Homebrew (Optional):**
|
6. **Copy Files:** BadBuilder will copy all of the extracted files to the correct locations, including deploying the Aurora Dashboard to `Apps\Aurora`.
|
||||||
- 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, you’ll 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.
|
|
||||||
|
|
||||||
## Example Homebrew Folder Structure
|
## Example Homebrew Folder Structure
|
||||||
If you want to add Aurora, you would select the **root folder**, like:
|
If you want to add Aurora, you would select the **root folder**, like:
|
||||||
@@ -60,16 +82,16 @@ Aurora 0.7b.2 - Release Package/
|
|||||||
```
|
```
|
||||||
BadBuilder will detect `Aurora.xex` as the entry point and patch it accordingly.
|
BadBuilder will detect `Aurora.xex` as the entry point and patch it accordingly.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!IMPORTANT]
|
||||||
> Homebrew apps which do not contain the entry point in the root folder are not currently supported.
|
> 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.
|
||||||
|
|
||||||
### 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"`
|
|
||||||
|
|
||||||
## Reporting Issues
|
## 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
|
||||||
|
- **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
|
||||||
Reference in New Issue
Block a user