Complete program flow
This commit is contained in:
@@ -14,10 +14,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Tools\fat32format.exe" />
|
||||
<EmbeddedResource Include="Resources\fat32format.exe" />
|
||||
</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="System.Management" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
24
BadBuilder/BadBuilder.sln
Normal file
24
BadBuilder/BadBuilder.sln
Normal 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
|
||||
@@ -11,7 +11,7 @@ namespace BadBuilder
|
||||
{
|
||||
internal partial class Program
|
||||
{
|
||||
static string PromptForDiskSelection(List<DiskInfo> disks)
|
||||
static string PromptDiskSelection(List<DiskInfo> disks)
|
||||
{
|
||||
var choices = new List<string>();
|
||||
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);
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
111
BadBuilder/ConsoleExperiences/DownloadExperience.cs
Normal file
111
BadBuilder/ConsoleExperiences/DownloadExperience.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
33
BadBuilder/ConsoleExperiences/ExtractExperience.cs
Normal file
33
BadBuilder/ConsoleExperiences/ExtractExperience.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
170
BadBuilder/ConsoleExperiences/HomebrewExperience.cs
Normal file
170
BadBuilder/ConsoleExperiences/HomebrewExperience.cs
Normal 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
11
BadBuilder/Constants.cs
Normal 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\\";
|
||||
}
|
||||
}
|
||||
35
BadBuilder/Helpers/ArchiveHelper.cs
Normal file
35
BadBuilder/Helpers/ArchiveHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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
|
||||
{
|
||||
@@ -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);
|
||||
|
||||
39
BadBuilder/Helpers/FileSystemHelper.cs
Normal file
39
BadBuilder/Helpers/FileSystemHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
BadBuilder/Helpers/PatchHelper.cs
Normal file
31
BadBuilder/Helpers/PatchHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
29
BadBuilder/Models/ActionQueue.cs
Normal file
29
BadBuilder/Models/ActionQueue.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,60 +36,145 @@ namespace BadBuilder
|
||||
if (action == "Exit") Environment.Exit(0);
|
||||
|
||||
List<DiskInfo> 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<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();
|
||||
|
||||
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<HomebrewApp> 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
|
||||
───────────────────────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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user