Complete program flow

This commit is contained in:
Pdawg11239
2025-03-16 04:34:20 -04:00
parent 9d8072d767
commit 642fd5e1ed
15 changed files with 668 additions and 55 deletions

View File

@@ -14,10 +14,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Tools\fat32format.exe" /> <EmbeddedResource Include="Resources\fat32format.exe" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Octokit" Version="14.0.0" />
<PackageReference Include="SharpCompress" Version="0.39.0" />
<PackageReference Include="Spectre.Console" Version="0.49.1" /> <PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="System.Management" Version="9.0.3" /> <PackageReference Include="System.Management" Version="9.0.3" />
</ItemGroup> </ItemGroup>

24
BadBuilder/BadBuilder.sln Normal file
View File

@@ -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

View File

@@ -11,7 +11,7 @@ namespace BadBuilder
{ {
internal partial class Program internal partial class Program
{ {
static string PromptForDiskSelection(List<DiskInfo> disks) static string PromptDiskSelection(List<DiskInfo> disks)
{ {
var choices = new List<string>(); var choices = new List<string>();
foreach (var disk in disks) foreach (var disk in disks)
@@ -38,14 +38,14 @@ namespace BadBuilder
); );
} }
static async Task FormatDisk(List<DiskInfo> disks, string selectedDisk) static void FormatDisk(List<DiskInfo> disks, string selectedDisk)
{ {
int diskIndex = disks.FindIndex(disk => $"{disk.DriveLetter} ({disk.SizeFormatted}) - {disk.Type}" == 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; if (diskIndex == -1) return;
await DiskHelper.FormatDisk(disks[diskIndex]); DiskHelper.FormatDisk(disks[diskIndex]).Wait();
}); });
} }
} }

View File

@@ -0,0 +1,111 @@
using Spectre.Console;
using BadBuilder.Helpers;
using static BadBuilder.Constants;
namespace BadBuilder
{
internal partial class Program
{
static async Task<List<ArchiveItem>> DownloadRequiredFiles()
{
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"),
};
await DownloadHelper.GetGitHubAssets(items);
List<DownloadItem> existingFiles = items.Where(item =>
File.Exists(Path.Combine(DOWNLOAD_DIR, item.url.Split('/').Last()))).ToList();
List<string> choices = items.Select(item =>
existingFiles.Any(e => e.name == item.name)
? $"{item.name} [italic gray](already exists)[/]"
: item.name).ToList();
var prompt = new MultiSelectionPrompt<string>()
.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<string> selectedItems = AnsiConsole.Prompt(prompt)
.Select(choice => choice.Split(" [")[0])
.ToList();
List<DownloadItem> 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<string>($"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();
}
}
}

View File

@@ -0,0 +1,33 @@
using Spectre.Console;
using BadBuilder.Helpers;
namespace BadBuilder
{
internal partial class Program
{
internal static async Task ExtractFiles(List<ArchiveItem> 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.");
}
}
}

View File

@@ -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<bool>("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<HomebrewApp> ManageHomebrewApps()
{
var homebrewApps = new List<HomebrewApp>();
while (true)
{
var choice = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.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<bool>("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<string>("[#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<string>("[#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<string>()
.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<HomebrewApp> 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<HomebrewApp> apps)
{
if (apps.Count == 0)
{
AnsiConsole.MarkupLine("[#ffac4d]No apps to remove.[/]\n");
return;
}
var appToRemove = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.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");
}
}
}

11
BadBuilder/Constants.cs Normal file
View File

@@ -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\\";
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -1,15 +1,42 @@
using Spectre.Console; using Octokit;
using System; using Spectre.Console;
using System.Collections.Generic;
using System.Linq; using static BadBuilder.Constants;
using System.Text;
using System.Threading.Tasks;
namespace BadBuilder.Helpers namespace BadBuilder.Helpers
{ {
internal static class DownloadHelper internal static class DownloadHelper
{ {
internal static async Task DownloadFile(HttpClient client, ProgressTask task, string url) internal static async Task GetGitHubAssets(List<DownloadItem> items)
{
GitHubClient gitClient = new(new ProductHeaderValue("BadBuilder-Downloader"));
List<string> 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 try
{ {
@@ -22,10 +49,10 @@ namespace BadBuilder.Helpers
string filename = url.Substring(url.LastIndexOf('/') + 1); string filename = url.Substring(url.LastIndexOf('/') + 1);
using (var contentStream = await response.Content.ReadAsStreamAsync()) using (Stream contentStream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(filename, 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))
{ {
var buffer = new byte[8192]; byte[] buffer = new byte[8192];
while (true) while (true)
{ {
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length); var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,9 +1,4 @@
using System; using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace BadBuilder.Helpers namespace BadBuilder.Helpers
{ {
@@ -12,7 +7,7 @@ namespace BadBuilder.Helpers
internal static void ExtractEmbeddedBinary(string resourceName) internal static void ExtractEmbeddedBinary(string resourceName)
{ {
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
string fullResourceName = $"BadBuilder.Tools.{resourceName}"; string fullResourceName = $"BadBuilder.Resources.{resourceName}";
using (Stream resourceStream = assembly.GetManifestResourceStream(fullResourceName)) using (Stream resourceStream = assembly.GetManifestResourceStream(fullResourceName))
{ {

View File

@@ -0,0 +1,29 @@
namespace BadBuilder.Models
{
internal class ActionQueue
{
private SortedDictionary<int, Queue<Func<Task>>> priorityQueue = new SortedDictionary<int, Queue<Func<Task>>>();
internal void EnqueueAction(Func<Task> action, int priority)
{
if (!priorityQueue.ContainsKey(priority))
{
priorityQueue[priority] = new Queue<Func<Task>>();
}
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();
}
}
}
}
}

View File

@@ -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.Models;
using BadBuilder.Helpers; using BadBuilder.Helpers;
using static BadBuilder.Constants;
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 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 GreenStyle = new Style(new Color(118, 185, 0));
static readonly Style GrayStyle = new Style(new Color(132, 133, 137)); 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) static void Main(string[] args)
{ {
ShowWelcomeMessage(); ShowWelcomeMessage();
@@ -22,61 +36,146 @@ namespace BadBuilder
if (action == "Exit") Environment.Exit(0); if (action == "Exit") Environment.Exit(0);
List<DiskInfo> disks = DiskHelper.GetDisks(); List<DiskInfo> disks = DiskHelper.GetDisks();
string selectedDisk = PromptForDiskSelection(disks); string selectedDisk = PromptDiskSelection(disks);
TargetDriveLetter = selectedDisk.Substring(0, 3);
bool confirmation = PromptFormatConfirmation(selectedDisk); bool confirmation = PromptFormatConfirmation(selectedDisk);
if (confirmation) if (confirmation)
{ {
FormatDisk(disks, selectedDisk).Wait(); FormatDisk(disks, selectedDisk);
break; break;
} }
} }
ClearConsole();
List<ArchiveItem> 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(); Console.WriteLine();
var items = new (string name, string url)[] List<HomebrewApp> homebrewApps = ManageHomebrewApps();
{
("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")
};
HttpClient client = new(); AnsiConsole.Status()
.SpinnerStyle(OrangeStyle)
AnsiConsole.Progress() .StartAsync("Copying and patching homebrew apps.", async ctx =>
.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 =>
{ {
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 FileSystemHelper.MirrorDirectoryAsync(item.folder, Path.Combine(TargetDriveLetter, "Apps", item.name));
await DownloadHelper.DownloadFile(client, task, item.url); await PatchHelper.PatchXexAsync(Path.Combine(TargetDriveLetter, "Apps", item.name, Path.GetFileName(item.entryPoint)), XexToolPath);
})); }));
}).Wait(); }).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( static void ShowWelcomeMessage() => AnsiConsole.Markup(
""" """
[#107c10] [/] [#4D8C00] [/]
[#2ca243] [/] [#65A800] [/]
[#76B900] [/] [#76B900] [/]
[#92C83E] [/] [#A1CF3E] [/]
[#a1d156] [/] [#CCE388] [/]
[#a1d156] [/] [#CCE388] [/]
[#76B900][/] [#76B900][/]
Xbox 360 [#FF7200]BadUpdate[/] USB Builder Xbox 360 [#FF7200]BadUpdate[/] USB Builder
[#848589]Created by Pdawg[/] [#848589]Created by Pdawg[/]
[#76B900][/] [#76B900][/]
"""); """);
@@ -89,5 +188,12 @@ namespace BadBuilder
"Exit" "Exit"
) )
); );
static void ClearConsole()
{
AnsiConsole.Clear();
ShowWelcomeMessage();
Console.WriteLine();
}
} }
} }