diff --git a/BadBuilder/BadBuilder.csproj b/BadBuilder/BadBuilder.csproj
index 533bf2d..e2620de 100644
--- a/BadBuilder/BadBuilder.csproj
+++ b/BadBuilder/BadBuilder.csproj
@@ -14,10 +14,12 @@
-
+
+
+
diff --git a/BadBuilder/BadBuilder.sln b/BadBuilder/BadBuilder.sln
new file mode 100644
index 0000000..9154beb
--- /dev/null
+++ b/BadBuilder/BadBuilder.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BadBuilder", "BadBuilder.csproj", "{7F5ABAE3-7695-CBAC-99AA-67F395914E74}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7F5ABAE3-7695-CBAC-99AA-67F395914E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F5ABAE3-7695-CBAC-99AA-67F395914E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F5ABAE3-7695-CBAC-99AA-67F395914E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F5ABAE3-7695-CBAC-99AA-67F395914E74}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {262F9D09-5BF9-4841-A008-0F212104F6B0}
+ EndGlobalSection
+EndGlobal
diff --git a/BadBuilder/ConsoleExperiences/DiskMenu.cs b/BadBuilder/ConsoleExperiences/DiskExperience.cs
similarity index 82%
rename from BadBuilder/ConsoleExperiences/DiskMenu.cs
rename to BadBuilder/ConsoleExperiences/DiskExperience.cs
index fbae1e1..bebf627 100644
--- a/BadBuilder/ConsoleExperiences/DiskMenu.cs
+++ b/BadBuilder/ConsoleExperiences/DiskExperience.cs
@@ -11,7 +11,7 @@ namespace BadBuilder
{
internal partial class Program
{
- static string PromptForDiskSelection(List disks)
+ static string PromptDiskSelection(List disks)
{
var choices = new List();
foreach (var disk in disks)
@@ -38,14 +38,14 @@ namespace BadBuilder
);
}
- static async Task FormatDisk(List disks, string selectedDisk)
+ static void FormatDisk(List disks, string selectedDisk)
{
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == selectedDisk);
- await AnsiConsole.Status().StartAsync($"[#76B900]Formatting disk[/] {selectedDisk}", async ctx =>
+ AnsiConsole.Status().SpinnerStyle(LightOrangeStyle).Start($"[#76B900]Formatting disk[/] {selectedDisk}", ctx =>
{
if (diskIndex == -1) return;
- await DiskHelper.FormatDisk(disks[diskIndex]);
+ DiskHelper.FormatDisk(disks[diskIndex]).Wait();
});
}
}
diff --git a/BadBuilder/ConsoleExperiences/DownloadExperience.cs b/BadBuilder/ConsoleExperiences/DownloadExperience.cs
new file mode 100644
index 0000000..c216de6
--- /dev/null
+++ b/BadBuilder/ConsoleExperiences/DownloadExperience.cs
@@ -0,0 +1,111 @@
+using Spectre.Console;
+using BadBuilder.Helpers;
+
+using static BadBuilder.Constants;
+
+namespace BadBuilder
+{
+ internal partial class Program
+ {
+ static async Task> DownloadRequiredFiles()
+ {
+ List 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"),
+ };
+ await DownloadHelper.GetGitHubAssets(items);
+
+ List existingFiles = items.Where(item =>
+ File.Exists(Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last()))).ToList();
+
+ List choices = items.Select(item =>
+ existingFiles.Any(e => e.name == item.name)
+ ? $"{item.name} [italic gray](already exists)[/]"
+ : item.name).ToList();
+
+ var prompt = new MultiSelectionPrompt()
+ .Title("Which files do you already have? [gray](Select all that apply)[/]")
+ .PageSize(10)
+ .NotRequired()
+ .HighlightStyle(GreenStyle)
+ .AddChoices(choices);
+
+ foreach (string choice in choices)
+ {
+ if (existingFiles.Any(e => choice.StartsWith(e.name)))
+ prompt.Select(choice);
+ }
+
+ List selectedItems = AnsiConsole.Prompt(prompt)
+ .Select(choice => choice.Split(" [")[0])
+ .ToList();
+
+
+ List itemsToDownload = items.Where(item => !selectedItems.Contains(item.name)).ToList();
+
+ itemsToDownload.Sort((a, b) => b.name.Length.CompareTo(a.name.Length));
+
+ if (itemsToDownload.Any())
+ {
+ if (!Directory.Exists($"{DOWNLOAD_DIR}"))
+ Directory.CreateDirectory($"{DOWNLOAD_DIR}");
+
+ HttpClient downloadClient = new();
+
+ await AnsiConsole.Progress()
+ .Columns(
+ new TaskDescriptionColumn(),
+ new ProgressBarColumn().FinishedStyle(GreenStyle).CompletedStyle(LightOrangeStyle),
+ new PercentageColumn().CompletedStyle(GreenStyle),
+ new RemainingTimeColumn().Style(GrayStyle),
+ new TransferSpeedColumn()
+ )
+ .StartAsync(async ctx =>
+ {
+ AnsiConsole.MarkupLine("[#76B900]{0}[/] Downloading required files.", Markup.Escape("[*]"));
+ await Task.WhenAll(itemsToDownload.Select(async item =>
+ {
+ var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false });
+ await DownloadHelper.DownloadFileAsync(downloadClient, task, item.url);
+ }));
+ });
+
+ string status = "[+]";
+ AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{itemsToDownload.Count()}[/] download(s) completed.\n");
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[italic #76B900]No downloads required. All files already exist.[/]");
+ }
+
+
+ Console.WriteLine();
+ foreach (string selectedItem in selectedItems)
+ {
+ string expectedFileName = items.First(i => i.name == selectedItem).url.Split('/').Last();
+ string destinationPath = Path.Combine(DOWNLOAD_DIR, expectedFileName);
+
+ if (File.Exists(destinationPath)) continue;
+
+ string existingPath = AnsiConsole.Prompt(
+ new TextPrompt($"Enter the path for the [bold]{selectedItem}[/] archive:")
+ .PromptStyle(LightOrangeStyle)
+ .Validate(path =>
+ {
+ return File.Exists(path.Trim().Trim('"'))
+ ? ValidationResult.Success()
+ : ValidationResult.Error("[red]File does not exist.[/]");
+ })
+ ).Trim().Trim('"');
+
+ File.Copy(existingPath, destinationPath, overwrite: true);
+ AnsiConsole.MarkupLine($"[italic #76B900]Successfully copied [bold]{selectedItem}[/] to the working directory.[/]\n");
+ }
+
+
+ return items.Select(item => new ArchiveItem(item.name, Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last()))).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/ConsoleExperiences/ExtractExperience.cs b/BadBuilder/ConsoleExperiences/ExtractExperience.cs
new file mode 100644
index 0000000..7a469b3
--- /dev/null
+++ b/BadBuilder/ConsoleExperiences/ExtractExperience.cs
@@ -0,0 +1,33 @@
+using Spectre.Console;
+using BadBuilder.Helpers;
+
+namespace BadBuilder
+{
+ internal partial class Program
+ {
+ internal static async Task ExtractFiles(List filesToExtract)
+ {
+ filesToExtract.Sort((a, b) => b.name.Length.CompareTo(a.name.Length));
+
+ await AnsiConsole.Progress()
+ .Columns(
+ new TaskDescriptionColumn(),
+ new ProgressBarColumn().FinishedStyle(GreenStyle).CompletedStyle(LightOrangeStyle),
+ new PercentageColumn().CompletedStyle(GreenStyle),
+ new RemainingTimeColumn().Style(GrayStyle)
+ )
+ .StartAsync(async ctx =>
+ {
+ AnsiConsole.MarkupLine("[#76B900]{0}[/] Extracting files.", 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.");
+ }
+ }
+}
diff --git a/BadBuilder/ConsoleExperiences/HomebrewExperience.cs b/BadBuilder/ConsoleExperiences/HomebrewExperience.cs
new file mode 100644
index 0000000..b098e81
--- /dev/null
+++ b/BadBuilder/ConsoleExperiences/HomebrewExperience.cs
@@ -0,0 +1,170 @@
+using Spectre.Console;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BadBuilder
+{
+ internal partial class Program
+ {
+ static bool PromptAddHomebrew()
+ {
+ return AnsiConsole.Prompt(
+ new TextPrompt("Would you like to add homebrew programs?")
+ .AddChoice(true)
+ .AddChoice(false)
+ .DefaultValue(false)
+ .ChoicesStyle(GreenStyle)
+ .DefaultValueStyle(OrangeStyle)
+ .WithConverter(choice => choice ? "y" : "n")
+ );
+ }
+
+ public static List ManageHomebrewApps()
+ {
+ var homebrewApps = new List();
+
+ while (true)
+ {
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .PageSize(4)
+ .HighlightStyle(GreenStyle)
+ .AddChoices("Add Homebrew App", "View Added Apps", "Remove App", "Finish & Save")
+ );
+
+ switch (choice)
+ {
+ case "Add Homebrew App":
+ 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":
+ ClearConsole();
+ DisplayApps(homebrewApps);
+ break;
+
+ case "Remove App":
+ ClearConsole();
+ RemoveHomebrewApp(homebrewApps);
+ break;
+
+ case "Finish & Save":
+ if (homebrewApps.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[#ffac4d]No apps added.[/]");
+ return homebrewApps;
+ }
+
+ if (AnsiConsole.Prompt(
+ new TextPrompt("Save and exit?")
+ .AddChoice(true)
+ .AddChoice(false)
+ .DefaultValue(true)
+ .ChoicesStyle(GreenStyle)
+ .DefaultValueStyle(OrangeStyle)
+ .WithConverter(choice => choice ? "y" : "n")))
+ {
+ string status = "[+]";
+ AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] Saved [bold]{homebrewApps.Count}[/] app(s).\n");
+ return homebrewApps;
+ }
+ break;
+ }
+ }
+ }
+
+ private static HomebrewApp? AddHomebrewApp()
+ {
+ AnsiConsole.MarkupLine("[bold #76B900]Add a new homebrew app[/]\n");
+ string folderPath = AnsiConsole.Ask("[#FFA500]Enter the folder path for the app:[/]");
+ if (!Directory.Exists(folderPath))
+ {
+ AnsiConsole.MarkupLine("[#ffac4d]Invalid folder path. Please try again.[/]");
+ 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 entryPoint = xexFiles.Length switch
+ {
+ 0 => AnsiConsole.Prompt(
+ new TextPrompt("[#ffac4d]No .xex files found.[/] Enter entry point:")
+ .Validate(path => File.Exists(path) ? ValidationResult.Success() : ValidationResult.Error("File not found"))
+ ),
+ 1 => xexFiles[0],
+ _ => AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("[#FFA500]Select entry point:[/]")
+ .HighlightStyle(PeachStyle)
+ .AddChoices(xexFiles.Select(Path.GetFileName))
+ )
+ };
+
+ ClearConsole();
+
+ AnsiConsole.MarkupLine($"[#76B900]Added:[/] {folderPath.Split('\\').Last()} -> [#ffac4d]{Path.GetFileName(entryPoint)}[/]\n");
+ return (folderPath.Split('\\').Last(), folderPath, Path.Combine(folderPath, entryPoint));
+ }
+
+
+
+ private static void DisplayApps(List apps)
+ {
+ if (apps.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[#ffac4d]No homebrew apps added.[/]\n");
+ return;
+ }
+
+ var table = new Table()
+ .Title("[bold #76B900]Added Homebrew Apps[/]")
+ .AddColumn("[#4D8C00]Folder[/]")
+ .AddColumn("[#ff7200]Entry Point[/]");
+
+ foreach (var app in apps)
+ table.AddRow($"[#A1CF3E]{app.folder}[/]", $"[#ffac4d]{Path.GetFileName(app.entryPoint)}[/]");
+
+ AnsiConsole.Write(table);
+ Console.WriteLine();
+ }
+
+ private static void RemoveHomebrewApp(List apps)
+ {
+ if (apps.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[#ffac4d]No apps to remove.[/]\n");
+ return;
+ }
+
+ var appToRemove = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("[bold #76B900]Select an app to remove:[/]")
+ .PageSize(5)
+ .HighlightStyle(LightOrangeStyle)
+ .MoreChoicesText("[grey](Move up/down to scroll)[/]")
+ .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");
+ }
+ }
+}
diff --git a/BadBuilder/Constants.cs b/BadBuilder/Constants.cs
new file mode 100644
index 0000000..1d0aa23
--- /dev/null
+++ b/BadBuilder/Constants.cs
@@ -0,0 +1,11 @@
+namespace BadBuilder
+{
+ 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 ContentFolder = "Content\\0000000000000000\\";
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/Helpers/ArchiveHelper.cs b/BadBuilder/Helpers/ArchiveHelper.cs
new file mode 100644
index 0000000..ef731a0
--- /dev/null
+++ b/BadBuilder/Helpers/ArchiveHelper.cs
@@ -0,0 +1,35 @@
+using SharpCompress.Archives;
+using SharpCompress.Common;
+using Spectre.Console;
+using System.Diagnostics;
+using static BadBuilder.Constants;
+
+namespace BadBuilder.Helpers
+{
+ internal static class ArchiveHelper
+ {
+ internal static async Task ExtractFileAsync(string friendlyName, string archivePath, ProgressTask task)
+ {
+ string subFolder = Path.Combine(EXTRACTED_DIR, friendlyName);
+ Directory.CreateDirectory(subFolder);
+
+ try
+ {
+ var archive = ArchiveFactory.Open(archivePath);
+ task.MaxValue = archive.Entries.Count();
+ foreach (var entry in archive.Entries)
+ {
+ if (!entry.IsDirectory)
+ entry.WriteToDirectory(subFolder, new ExtractionOptions() { ExtractFullPath = true, Overwrite = true });
+
+ task.Increment(1);
+ }
+ }
+ catch (Exception ex)
+ {
+ // Sorry, but these exceptions are invalid. SharpCompress is mad because there's nested ZIPs in xexmenu.
+ Debug.WriteLine(ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/Helpers/DownloadHelper.cs b/BadBuilder/Helpers/DownloadHelper.cs
index 80db9f1..f19ded5 100644
--- a/BadBuilder/Helpers/DownloadHelper.cs
+++ b/BadBuilder/Helpers/DownloadHelper.cs
@@ -1,15 +1,42 @@
-using Spectre.Console;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Octokit;
+using Spectre.Console;
+
+using static BadBuilder.Constants;
namespace BadBuilder.Helpers
{
internal static class DownloadHelper
{
- internal static async Task DownloadFile(HttpClient client, ProgressTask task, string url)
+ internal static async Task GetGitHubAssets(List items)
+ {
+ GitHubClient gitClient = new(new ProductHeaderValue("BadBuilder-Downloader"));
+ List repos =
+ [
+ "grimdoomer/Xbox360BadUpdate",
+ "FreeMyXe/FreeMyXe"
+ ];
+
+ foreach (var repo in repos)
+ {
+ string[] splitRepo = repo.Split('/');
+ var latestRelease = await gitClient.Repository.Release.GetLatest(splitRepo[0], splitRepo[1]);
+
+ foreach (var asset in latestRelease.Assets)
+ {
+ string friendlyName = asset.Name switch
+ {
+ var name when name.Contains("Free", StringComparison.OrdinalIgnoreCase) => "FreeMyXe",
+ var name when name.Contains("Tools", StringComparison.OrdinalIgnoreCase) => "BadUpdate Tools",
+ var name when name.Contains("BadUpdate", StringComparison.OrdinalIgnoreCase) => "BadUpdate",
+ _ => asset.Name.Substring(0, asset.Name.Length - 4)
+ };
+
+ items.Add(new(friendlyName, asset.BrowserDownloadUrl));
+ }
+ }
+ }
+
+ internal static async Task DownloadFileAsync(HttpClient client, ProgressTask task, string url)
{
try
{
@@ -22,10 +49,10 @@ namespace BadBuilder.Helpers
string filename = url.Substring(url.LastIndexOf('/') + 1);
- using (var contentStream = await response.Content.ReadAsStreamAsync())
- using (var fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
+ using (Stream contentStream = await response.Content.ReadAsStreamAsync())
+ using (FileStream fileStream = new($"{DOWNLOAD_DIR}/{filename}", System.IO.FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
- var buffer = new byte[8192];
+ byte[] buffer = new byte[8192];
while (true)
{
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
diff --git a/BadBuilder/Helpers/FileSystemHelper.cs b/BadBuilder/Helpers/FileSystemHelper.cs
new file mode 100644
index 0000000..38b90a7
--- /dev/null
+++ b/BadBuilder/Helpers/FileSystemHelper.cs
@@ -0,0 +1,39 @@
+using BadBuilder.Models;
+
+namespace BadBuilder.Helpers
+{
+ internal static class FileSystemHelper
+ {
+ internal static async Task MirrorDirectoryAsync(string sourceDir, string destDir)
+ {
+ Directory.CreateDirectory(destDir);
+
+ string[] files = Directory.GetFiles(sourceDir);
+ foreach (var file in files)
+ {
+ string relativePath = Path.GetRelativePath(sourceDir, file);
+ string destFile = Path.Combine(destDir, relativePath);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(destFile));
+
+ await CopyFileAsync(file, destFile);
+ }
+
+ string[] directories = Directory.GetDirectories(sourceDir);
+ foreach (var dir in directories)
+ {
+ var relativePath = Path.GetRelativePath(sourceDir, dir);
+ var destSubDir = Path.Combine(destDir, relativePath);
+
+ await MirrorDirectoryAsync(dir, destSubDir);
+ }
+ }
+
+ internal static async Task CopyFileAsync(string sourceFile, string destFile)
+ {
+ using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
+ using (var destStream = new FileStream(destFile, FileMode.Create, FileAccess.Write))
+ await sourceStream.CopyToAsync(destStream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/Helpers/PatchHelper.cs b/BadBuilder/Helpers/PatchHelper.cs
new file mode 100644
index 0000000..2aff559
--- /dev/null
+++ b/BadBuilder/Helpers/PatchHelper.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BadBuilder.Helpers
+{
+ internal static class PatchHelper
+ {
+ internal static async Task PatchXexAsync(string xexPath, string xexToolPath)
+ {
+ Process process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = xexToolPath,
+ Arguments = $"-m r -r a \"{xexPath}\"",
+ RedirectStandardOutput = false,
+ RedirectStandardError = false,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ }
+ };
+
+ process.Start();
+ await process.WaitForExitAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/Helpers/ResourceHelper.cs b/BadBuilder/Helpers/ResourceHelper.cs
index 9670667..92bc13e 100644
--- a/BadBuilder/Helpers/ResourceHelper.cs
+++ b/BadBuilder/Helpers/ResourceHelper.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection;
namespace BadBuilder.Helpers
{
@@ -12,7 +7,7 @@ namespace BadBuilder.Helpers
internal static void ExtractEmbeddedBinary(string resourceName)
{
var assembly = Assembly.GetExecutingAssembly();
- string fullResourceName = $"BadBuilder.Tools.{resourceName}";
+ string fullResourceName = $"BadBuilder.Resources.{resourceName}";
using (Stream resourceStream = assembly.GetManifestResourceStream(fullResourceName))
{
diff --git a/BadBuilder/Models/ActionQueue.cs b/BadBuilder/Models/ActionQueue.cs
new file mode 100644
index 0000000..7bb263c
--- /dev/null
+++ b/BadBuilder/Models/ActionQueue.cs
@@ -0,0 +1,29 @@
+namespace BadBuilder.Models
+{
+ internal class ActionQueue
+ {
+ private SortedDictionary>> priorityQueue = new SortedDictionary>>();
+
+ internal void EnqueueAction(Func action, int priority)
+ {
+ if (!priorityQueue.ContainsKey(priority))
+ {
+ priorityQueue[priority] = new Queue>();
+ }
+ priorityQueue[priority].Enqueue(action);
+ }
+
+ internal async Task ExecuteActionsAsync()
+ {
+ foreach (var priority in priorityQueue.Keys.OrderByDescending(p => p))
+ {
+ var actions = priorityQueue[priority];
+ while (actions.Count > 0)
+ {
+ var action = actions.Dequeue();
+ await action();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/BadBuilder/Program.cs b/BadBuilder/Program.cs
index d133e66..3dca2ff 100644
--- a/BadBuilder/Program.cs
+++ b/BadBuilder/Program.cs
@@ -1,15 +1,29 @@
-using Spectre.Console;
+global using DownloadItem = (string name, string url);
+global using ArchiveItem = (string name, string path);
+global using HomebrewApp = (string name, string folder, string entryPoint);
+
+using Spectre.Console;
using BadBuilder.Models;
using BadBuilder.Helpers;
+using static BadBuilder.Constants;
+
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 GreenStyle = new Style(new Color(118, 185, 0));
static readonly Style GrayStyle = new Style(new Color(132, 133, 137));
+ static string XexToolPath = string.Empty;
+ static string TargetDriveLetter = string.Empty;
+
+ static ActionQueue actionQueue = new();
+
static void Main(string[] args)
{
ShowWelcomeMessage();
@@ -22,61 +36,146 @@ namespace BadBuilder
if (action == "Exit") Environment.Exit(0);
List disks = DiskHelper.GetDisks();
- string selectedDisk = PromptForDiskSelection(disks);
+ string selectedDisk = PromptDiskSelection(disks);
+ TargetDriveLetter = selectedDisk.Substring(0, 3);
bool confirmation = PromptFormatConfirmation(selectedDisk);
if (confirmation)
{
- FormatDisk(disks, selectedDisk).Wait();
+ FormatDisk(disks, selectedDisk);
break;
}
}
+ ClearConsole();
+
+ List downloadedFiles = DownloadRequiredFiles().Result;
+ ExtractFiles(downloadedFiles).Wait();
+
+
+ AnsiConsole.MarkupLine("\n\n[#76B900]{0}[/] Copying requried files and folders.", Markup.Escape("[*]"));
+ foreach (var folder in Directory.GetDirectories($@"{EXTRACTED_DIR}"))
+ {
+ var folderName = folder.Split("\\").Last();
+
+ switch (folderName)
+ {
+ case "XEXMenu":
+ EnqueueMirrorDirectory(
+ Path.Combine(folder, $"{ContentFolder}C0DE9999"),
+ Path.Combine(TargetDriveLetter, $"{ContentFolder}C0DE9999"),
+ 7
+ );
+ break;
+
+ case "FreeMyXe":
+ EnqueueFileCopy(
+ Path.Combine(folder, "FreeMyXe.xex"),
+ Path.Combine(TargetDriveLetter, "BadUpdatePayload", "default.xex"),
+ 9
+ );
+ break;
+
+ case "BadUpdate":
+ actionQueue.EnqueueAction(async () =>
+ {
+ using (StreamWriter writer = new(Path.Combine(TargetDriveLetter, "name.txt")))
+ {
+ writer.WriteLine("USB Storage Device");
+ }
+
+ 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");
+ }
+
+ Directory.CreateDirectory(Path.Combine(TargetDriveLetter, "Apps"));
+ await FileSystemHelper.MirrorDirectoryAsync(Path.Combine(folder, "Rock Band Blitz"), TargetDriveLetter);
+ }, 10);
+ break;
+
+ case "BadUpdate Tools":
+ XexToolPath = Path.Combine(folder, "XePatcher", "XexTool.exe");
+ break;
+
+ case "Rock Band Blitz":
+ EnqueueMirrorDirectory(
+ Path.Combine(folder, $"{ContentFolder}5841122D\\000D0000"),
+ Path.Combine(TargetDriveLetter, $"{ContentFolder}5841122D\\000D0000"),
+ 8
+ );
+ break;
+
+ case "Simple 360 NAND Flasher":
+ actionQueue.EnqueueAction(async () =>
+ {
+ await PatchHelper.PatchXexAsync(Path.Combine(folder, "Simple 360 NAND Flasher", "Default.xex"), XexToolPath);
+ await FileSystemHelper.MirrorDirectoryAsync(Path.Combine(folder, "Simple 360 NAND Flasher"), Path.Combine(TargetDriveLetter, "Apps", "Simple 360 NAND Flasher"));
+ }, 6);
+ break;
+
+ default: throw new Exception($"Unexpected directory in working folder: {folder}");
+ }
+ }
+ actionQueue.ExecuteActionsAsync().Wait();
+
+ ClearConsole();
+ if (!PromptAddHomebrew())
+ {
+ AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
+ Environment.Exit(0);
+ }
+
Console.WriteLine();
- var items = new (string name, string url)[]
- {
- ("rbblitz", "https://download.digiex.net/Consoles/Xbox360/Arcade-games/RBBlitz.zip"),
- ("BadUpdate", "https://github.com/grimdoomer/Xbox360BadUpdate/releases/download/v1.1/Xbox360BadUpdate-Retail-USB-v1.1.zip"),
- ("BadUpdate Tools", "https://github.com/grimdoomer/Xbox360BadUpdate/releases/download/v1.1/Tools.zip"),
- ("FreeMyXe", "https://github.com/FreeMyXe/FreeMyXe/releases/download/beta4/FreeMyXe-beta4.zip")
- };
+ List homebrewApps = ManageHomebrewApps();
- HttpClient client = new();
-
- AnsiConsole.Progress()
- .Columns(
- [
- new TaskDescriptionColumn(),
- new ProgressBarColumn().FinishedStyle(GreenStyle),
- new PercentageColumn().CompletedStyle(GreenStyle),
- new RemainingTimeColumn().Style(GrayStyle),
- new SpinnerColumn(Spinner.Known.Dots12).Style(OrangeStyle)
- ])
- .StartAsync(async ctx =>
+ AnsiConsole.Status()
+ .SpinnerStyle(OrangeStyle)
+ .StartAsync("Copying and patching homebrew apps.", async ctx =>
{
- await Task.WhenAll(items.Select(async item =>
+ await Task.WhenAll(homebrewApps.Select(async item =>
{
- var task = ctx.AddTask(item.name, new ProgressTaskSettings { AutoStart = false});
- await DownloadHelper.DownloadFile(client, task, item.url);
+ 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();
- Console.WriteLine("Download completed!");
+ string status = "[+]";
+ AnsiConsole.MarkupInterpolated($"[#76B900]{status}[/] [bold]{homebrewApps.Count()}[/] apps copied.\n");
+
+ AnsiConsole.MarkupLine("\n[#76B900]{0}[/] Your USB drive is ready to go.", Markup.Escape("[+]"));
}
+ static void EnqueueMirrorDirectory(string sourcePath, string destinationPath, int priority)
+ {
+ actionQueue.EnqueueAction(async () =>
+ {
+ await FileSystemHelper.MirrorDirectoryAsync(sourcePath, destinationPath);
+ }, priority);
+ }
+
+ static void EnqueueFileCopy(string sourceFile, string destinationFile, int priority)
+ {
+ actionQueue.EnqueueAction(async () =>
+ {
+ await FileSystemHelper.CopyFileAsync(sourceFile, destinationFile);
+ }, priority);
+ }
+
+
static void ShowWelcomeMessage() => AnsiConsole.Markup(
"""
- [#107c10]██████╗ █████╗ ██████╗ ██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗██████╗[/]
- [#2ca243]██╔══██╗██╔══██╗██╔══██╗██╔══██╗██║ ██║██║██║ ██╔══██╗██╔════╝██╔══██╗[/]
+ [#4D8C00]██████╗ █████╗ ██████╗ ██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗██████╗[/]
+ [#65A800]██╔══██╗██╔══██╗██╔══██╗██╔══██╗██║ ██║██║██║ ██╔══██╗██╔════╝██╔══██╗[/]
[#76B900]██████╔╝███████║██║ ██║██████╔╝██║ ██║██║██║ ██║ ██║█████╗ ██████╔╝[/]
- [#92C83E]██╔══██╗██╔══██║██║ ██║██╔══██╗██║ ██║██║██║ ██║ ██║██╔══╝ ██╔══██╗[/]
- [#a1d156]██████╔╝██║ ██║██████╔╝██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║[/]
- [#a1d156]╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝[/]
+ [#A1CF3E]██╔══██╗██╔══██║██║ ██║██╔══██╗██║ ██║██║██║ ██║ ██║██╔══╝ ██╔══██╗[/]
+ [#CCE388]██████╔╝██║ ██║██████╔╝██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║[/]
+ [#CCE388]╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝[/]
[#76B900]────────────────────────────────────────────────────────────────────────────[/]
- Xbox 360 [#FF7200]BadUpdate[/] USB Builder
- [#848589]Created by Pdawg[/]
+ ───────────────────────Xbox 360 [#FF7200]BadUpdate[/] USB Builder───────────────────────
+ [#848589]Created by Pdawg[/]
[#76B900]────────────────────────────────────────────────────────────────────────────[/]
""");
@@ -89,5 +188,12 @@ namespace BadBuilder
"Exit"
)
);
+
+ static void ClearConsole()
+ {
+ AnsiConsole.Clear();
+ ShowWelcomeMessage();
+ Console.WriteLine();
+ }
}
}
\ No newline at end of file
diff --git a/BadBuilder/Tools/fat32format.exe b/BadBuilder/Resources/fat32format.exe
similarity index 100%
rename from BadBuilder/Tools/fat32format.exe
rename to BadBuilder/Resources/fat32format.exe