diff --git a/UWUVCI AIO WPF/Classes/Injection.cs b/UWUVCI AIO WPF/Classes/Injection.cs new file mode 100644 index 0000000..064a0b6 --- /dev/null +++ b/UWUVCI AIO WPF/Classes/Injection.cs @@ -0,0 +1,571 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Xml; +using UWUVCI_AIO_WPF.Properties; + +namespace UWUVCI_AIO +{ + internal static class Injection + { + public enum Console { NDS, N64, GBA, NES, SNES } + + private static readonly string tempPath = Path.Combine(Directory.GetCurrentDirectory(), "temp"); + private static readonly string baseRomPath = Path.Combine(tempPath, "baserom"); + private static readonly string imgPath = Path.Combine(tempPath, "img"); + private static readonly string toolsPath = Path.Combine(Directory.GetCurrentDirectory(), "Tools"); + + /* + * Console: Can either be NDS, N64, GBA, NES or SNES + * baseRom = Name of the BaseRom, which is the folder name too (example: Super Metroid EU will be saved at the BaseRom path under the folder SMetroidEU, so the BaseRom is in this case SMetroidEU). + * customBasePath = Path to the custom Base. Is null if no custom base is used. + * injectRomPath = Path to the Rom to be injected into the Base Game. + * bootImages = String array containing the paths for + * bootTvTex: PNG or TGA (PNG gets converted to TGA using UPNG). Needs to be in the dimensions 1280x720 and have a bit depth of 24. If null, the original BootImage will be used. + * bootDrcTex: PNG or TGA (PNG gets converted to TGA using UPNG). Needs to be in the dimensions 854x480 and have a bit depth of 24. If null, the original BootImage will be used. + * iconTex: PNG or TGA (PNG gets converted to TGA using UPNG). Needs to be in the dimensions 128x128 and have a bit depth of 32. If null, the original BootImage will be used. + * bootLogoTex: PNG or TGA (PNG gets converted to TGA using UPNG). Needs to be in the dimensions 170x42 and have a bit depth of 32. If null, the original BootImage will be used. + * gameName = The name of the final game to be entered into the .xml files. + * iniPath = Only used for N64. Path to the INI configuration. If "blank", a blank ini will be used. + * darkRemoval = Only used for N64. Indicates whether the dark filter should be removed. + */ + public static void Inject(Console console, string baseRom, string customBasePath, string injectRomPath, string[] bootImages, string gameName, string iniPath = null, bool darkRemoval = false) + { + CopyBase(baseRom, customBasePath); + switch (console) + { + case Console.NDS: + NDS(injectRomPath); + break; + + case Console.N64: + N64(injectRomPath, iniPath, darkRemoval); + break; + + case Console.GBA: + GBA(injectRomPath); + break; + + case Console.NES: + NESSNES(injectRomPath); + break; + case Console.SNES: + NESSNES(RemoveHeader(injectRomPath)); + break; + } + + EditXML(gameName); + Images(bootImages); + //MessageBox.Show(Resources.InjectionFinishedText, Resources.InjectionFinishedCaption, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public static void Clean() + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + } + + public static void Loadiine(string gameName) + { + //string outputPath = Path.Combine(Properties.Settings.Default.InjectionPath, gameName); + string outputPath = string.Empty; + int i = 0; + while (Directory.Exists(outputPath)) + { + //outputPath = Path.Combine(Properties.Settings.Default.InjectionPath, $"{gameName}_{i}"); + i++; + } + + //Directory.Move(baseRomPath,outputPath); + // MessageBox.Show(string.Format(Resources.InjectCreatedText, outputPath), Resources.InjectCreatedCaption, MessageBoxButtons.OK, MessageBoxIcon.Information); + + Clean(); + } + + public static void Packing(string gameName) + { + //string outputPath = Path.Combine(Properties.Settings.Default.InjectionPath, gameName); + string outputPath = string.Empty; + int i = 0; + while (Directory.Exists(outputPath)) + { + //outputPath = Path.Combine(Properties.Settings.Default.InjectionPath, $"{gameName}_{i}"); + i++; + } + + using (Process cnuspacker = new Process()) + { + cnuspacker.StartInfo.UseShellExecute = false; + cnuspacker.StartInfo.CreateNoWindow = true; + cnuspacker.StartInfo.FileName = Path.Combine(toolsPath, "CNUSPACKER.exe"); + //cnuspacker.StartInfo.Arguments = $"-in \"{baseRomPath}\" -out \"{outputPath}\" -encryptKeyWith {Properties.Settings.Default.CommonKey}"; + + cnuspacker.Start(); + cnuspacker.WaitForExit(); + } + + //MessageBox.Show(string.Format(Resources.InjectCreatedText, outputPath), Resources.InjectCreatedCaption, MessageBoxButtons.OK, MessageBoxIcon.Information); + + Clean(); + } + + public static void Download(string baseRom) + { + string TID = null; + //string TK = (string) Properties.Settings.Default[baseRom]; + + switch (baseRom) + { + #region NDS + case "ZSTEU": + TID = "00050000101b8d00"; + break; + case "ZSTUS": + TID = "00050000101b8c00"; + break; + case "ZPHEU": + TID = "00050000101c3800"; + break; + case "ZPHUS": + TID = "00050000101c3700"; + break; + case "WWEU": + TID = "00050000101a2000"; + break; + case "WWUS": + TID = "00050000101a1f00"; + break; + #endregion + #region N64 + case "PMEU": + TID = "0005000010199800"; + break; + case "PMUS": + TID = "0005000010199700"; + break; + case "FZXUS": + TID = "00050000101ebc00"; + break; + case "FZXJP": + TID = "00050000101ebb00"; + break; + case "DK64EU": + TID = "0005000010199300"; + break; + case "DK64US": + TID = "0005000010199200"; + break; + #endregion + #region GBA + case "ZMCEU": + TID = "000500001015e500"; + break; + case "ZMCUS": + TID = "000500001015e400"; + break; + case "MKCEU": + TID = "000500001017d200"; + break; + case "MKCUS": + TID = "000500001017d300"; + break; + #endregion + #region NES + case "POEU": + TID = "0005000010108c00"; + break; + case "POUS": + TID = "0005000010108b00"; + break; + case "SMBEU": + TID = "0005000010106e00"; + break; + case "SMBUS": + TID = "0005000010106d00"; + break; + #endregion + #region SNES + case "SMetroidEU": + TID = "000500001010a700"; + break; + case "SMetroidUS": + TID = "000500001010a600"; + break; + case "SMetroidJP": + TID = "000500001010a500"; + break; + case "EarthboundEU": + TID = "0005000010133500"; + break; + case "EarthboundUS": + TID = "0005000010133400"; + break; + case "EarthboundJP": + TID = "0005000010133000"; + break; + case "DKCEU": + TID = "0005000010109600"; + break; + case "DKCUS": + TID = "0005000010109500"; + break; + #endregion + } + + Directory.CreateDirectory(tempPath); + using (Process download = new Process()) + { + download.StartInfo.FileName = Path.Combine(toolsPath, "WiiUDownloader.exe"); + //download.StartInfo.Arguments = $"{TID} {TK} \"{Path.Combine(tempPath, "download")}\""; + + download.Start(); + download.WaitForExit(); + } + + using (Process decrypt = new Process()) + { + decrypt.StartInfo.FileName = Path.Combine(toolsPath, "Cdecrypt.exe"); + //decrypt.StartInfo.Arguments = $"{Properties.Settings.Default.CommonKey} \"{Path.Combine(tempPath, "download")}\" \"{Path.Combine(Properties.Settings.Default.BaseRomPath, baseRom)}\""; + + decrypt.Start(); + decrypt.WaitForExit(); + } + } + + // This function changes TitleID, ProductCode and GameName in app.xml (ID) and meta.xml (ID, ProductCode, Name) + private static void EditXML(string gameName) + { + string metaXml = Path.Combine(baseRomPath, "meta", "meta.xml"); + string appXml = Path.Combine(baseRomPath, "code", "app.xml"); + Random random = new Random(); + string ID = $"{random.Next(0x3000, 0x10000):X4}"; + + XmlDocument doc = new XmlDocument(); + try + { + doc.Load(metaXml); + doc.SelectSingleNode("menu/longname_ja").InnerText = gameName; + doc.SelectSingleNode("menu/longname_en").InnerText = gameName; + doc.SelectSingleNode("menu/longname_fr").InnerText = gameName; + doc.SelectSingleNode("menu/longname_de").InnerText = gameName; + doc.SelectSingleNode("menu/longname_it").InnerText = gameName; + doc.SelectSingleNode("menu/longname_es").InnerText = gameName; + doc.SelectSingleNode("menu/longname_zhs").InnerText = gameName; + doc.SelectSingleNode("menu/longname_ko").InnerText = gameName; + doc.SelectSingleNode("menu/longname_nl").InnerText = gameName; + doc.SelectSingleNode("menu/longname_pt").InnerText = gameName; + doc.SelectSingleNode("menu/longname_ru").InnerText = gameName; + doc.SelectSingleNode("menu/longname_zht").InnerText = gameName; + + doc.SelectSingleNode("menu/product_code").InnerText = $"WUP-N-{ID}"; + doc.SelectSingleNode("menu/title_id").InnerText = $"0005000010{ID}00"; + doc.SelectSingleNode("menu/group_id").InnerText = $"0000{ID}"; + + doc.SelectSingleNode("menu/shortname_ja").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_fr").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_de").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_en").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_it").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_es").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_zhs").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_ko").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_nl").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_pt").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_ru").InnerText = gameName; + doc.SelectSingleNode("menu/shortname_zht").InnerText = gameName; + doc.Save(metaXml); + } + catch (NullReferenceException) + { + //MessageBox.Show("Error when editing the meta.xml: Values seem to be missing.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + try + { + doc.Load(appXml); + doc.SelectSingleNode("app/title_id").InnerText = $"0005000010{ID}00"; + doc.SelectSingleNode("app/group_id").InnerText = $"0000{ID}"; + doc.Save(appXml); + } + catch (NullReferenceException) + { + // MessageBox.Show("Error when editing the app.xml: Values seem to be missing.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + //This function copies the custom or normal Base to the working directory + private static void CopyBase(string baserom, string customPath) + { + if (Directory.Exists(baseRomPath)) // sanity check + { + Directory.Delete(baseRomPath, true); + } + if (baserom == "Custom") + { + DirectoryCopy(customPath, baseRomPath, true); + } + else + { + //DirectoryCopy(Path.Combine(Properties.Settings.Default.BaseRomPath, baserom), baseRomPath, true); + } + } + + private static void NESSNES(string injectRomPath) + { + string rpxFile = Directory.GetFiles(Path.Combine(baseRomPath, "code"), "*.rpx")[0]; //To get the RPX path where the NES/SNES rom needs to be Injected in + + RPXdecomp(rpxFile); //Decompresses the RPX to be able to write the game into it + + using (Process retroinject = new Process()) + { + retroinject.StartInfo.UseShellExecute = false; + retroinject.StartInfo.CreateNoWindow = true; + retroinject.StartInfo.FileName = Path.Combine(toolsPath, "retroinject.exe"); + retroinject.StartInfo.Arguments = $"\"{rpxFile}\" \"{injectRomPath}\" \"{rpxFile}\""; + + retroinject.Start(); + retroinject.WaitForExit(); + } + + RPXcomp(rpxFile); //Compresses the RPX + } + + private static void GBA(string injectRomPath) + { + using (Process psb = new Process()) + { + psb.StartInfo.UseShellExecute = false; + psb.StartInfo.CreateNoWindow = true; + psb.StartInfo.FileName = Path.Combine(toolsPath, "psb.exe"); + psb.StartInfo.Arguments = $"\"{Path.Combine(baseRomPath, "content", "alldata.psb.m")}\" \"{injectRomPath}\" \"{Path.Combine(baseRomPath, "content", "alldata.psb.m")}\""; + + psb.Start(); + psb.WaitForExit(); + } + } + + private static void NDS(string injectRomPath) + { + using (ZipArchive archive = ZipFile.Open(Path.Combine(baseRomPath, "content", "0010", "rom.zip"), ZipArchiveMode.Update)) + { + string romname = archive.Entries[0].FullName; + archive.Entries[0].Delete(); + archive.CreateEntryFromFile(injectRomPath, romname); + } + + } + + private static void N64(string injectRomPath, string iniPath, bool darkRemoval) + { + string mainRomPath = Directory.GetFiles(Path.Combine(baseRomPath, "content", "rom"))[0]; + string mainIni = Path.Combine(baseRomPath, "content", "config", $"{Path.GetFileName(mainRomPath)}.ini"); + using (Process n64convert = new Process()) + { + n64convert.StartInfo.UseShellExecute = false; + n64convert.StartInfo.CreateNoWindow = true; + n64convert.StartInfo.FileName = Path.Combine(toolsPath, "N64Converter.exe"); + n64convert.StartInfo.Arguments = $"\"{injectRomPath}\" \"{mainRomPath}\""; + + n64convert.Start(); + n64convert.WaitForExit(); + } + + if (iniPath != null) + { + File.Delete(mainIni); + File.Copy((iniPath == "blank") ? Path.Combine(toolsPath, "blank.ini") : iniPath, mainIni); + } + + if (darkRemoval) + { + string filePath = Path.Combine(baseRomPath, "content", "FrameLayout.arc"); + using (BinaryWriter writer = new BinaryWriter(new FileStream(filePath, FileMode.Open))) + { + writer.Seek(0x1AD8, SeekOrigin.Begin); + writer.Write(0L); + } + } + } + + //Compressed or decompresses the RPX using wiiurpxtool + private static void RPXdecomp(string rpxpath) + { + using (Process rpxtool = new Process()) + { + rpxtool.StartInfo.UseShellExecute = false; + rpxtool.StartInfo.CreateNoWindow = true; + rpxtool.StartInfo.FileName = Path.Combine(toolsPath, "wiiurpxtool.exe"); + rpxtool.StartInfo.Arguments = $"-d \"{rpxpath}\""; + + rpxtool.Start(); + rpxtool.WaitForExit(); + } + } + + private static void RPXcomp(string rpxpath) + { + using (Process rpxtool = new Process()) + { + rpxtool.StartInfo.UseShellExecute = false; + rpxtool.StartInfo.CreateNoWindow = true; + rpxtool.StartInfo.FileName = Path.Combine(toolsPath, "wiiurpxtool.exe"); + rpxtool.StartInfo.Arguments = $"-c \"{rpxpath}\""; + + rpxtool.Start(); + rpxtool.WaitForExit(); + } + } + + private static void Images(string[] paths) + { + bool tv = false; + bool drc = false; + bool icon = false; + bool logo = false; + + if (Directory.Exists(imgPath)) // sanity check + { + Directory.Delete(imgPath, true); + } + Directory.CreateDirectory(imgPath); + + if (paths[0] != null) + { + tv = true; + CopyAndConvertImage(paths[0], Path.Combine(imgPath, "bootTvTex.tga")); + } + + if (paths[1] != null) + { + drc = true; + CopyAndConvertImage(paths[1], Path.Combine(imgPath, "bootDrcTex.tga")); + } + + if (paths[2] != null) + { + icon = true; + CopyAndConvertImage(paths[2], Path.Combine(imgPath, "iconTex.tga")); + } + + if (paths[3] != null) + { + logo = true; + CopyAndConvertImage(paths[3], Path.Combine(imgPath, "bootLogoTex.tga")); + } + + if (tv || drc || icon || logo) { + using (Process tgaverifyFixup = new Process()) + { + tgaverifyFixup.StartInfo.UseShellExecute = false; + tgaverifyFixup.StartInfo.CreateNoWindow = true; + tgaverifyFixup.StartInfo.FileName = Path.Combine(toolsPath, "tga_verify.exe"); + tgaverifyFixup.StartInfo.Arguments = $"--fixup \"{imgPath}\""; + + tgaverifyFixup.Start(); + tgaverifyFixup.WaitForExit(); + } + + if (tv) + { + File.Delete(Path.Combine(baseRomPath, "meta", "bootTvTex.tga")); + File.Move(Path.Combine(imgPath, "bootTvTex.tga"), Path.Combine(baseRomPath, "meta", "bootTvTex.tga")); + } + if (drc) + { + File.Delete(Path.Combine(baseRomPath, "meta", "bootDrcTex.tga")); + File.Move(Path.Combine(imgPath, "bootDrcTex.tga"), Path.Combine(baseRomPath, "meta", "bootDrcTex.tga")); + } + if (icon) + { + File.Delete(Path.Combine(baseRomPath, "meta", "iconTex.tga")); + File.Move(Path.Combine(imgPath, "iconTex.tga"), Path.Combine(baseRomPath, "meta", "iconTex.tga")); + } + if (logo) + { + File.Delete(Path.Combine(baseRomPath, "meta", "bootLogoTex.tga")); + File.Move(Path.Combine(imgPath, "bootLogoTex.tga"), Path.Combine(baseRomPath, "meta", "bootLogoTex.tga")); + } + } + } + + private static void CopyAndConvertImage(string inputPath, string outputPath) + { + if (inputPath.EndsWith(".tga")) + { + File.Copy(inputPath, outputPath); + } + else + { + using (Process png2tga = new Process()) + { + png2tga.StartInfo.UseShellExecute = false; + png2tga.StartInfo.CreateNoWindow = true; + png2tga.StartInfo.FileName = Path.Combine(toolsPath, "png2tga.exe"); + png2tga.StartInfo.Arguments = $"\"{inputPath}\" \"{outputPath}\""; + + png2tga.Start(); + png2tga.WaitForExit(); + } + } + } + + private static string RemoveHeader(string filePath) + { + // logic taken from snesROMUtil + using (FileStream inStream = new FileStream(filePath, FileMode.Open)) + { + byte[] header = new byte[512]; + inStream.Read(header, 0, 512); + string string1 = BitConverter.ToString(header, 8, 3); + string string2 = Encoding.ASCII.GetString(header, 0, 11); + string string3 = BitConverter.ToString(header, 30, 16); + if (string1 != "AA-BB-04" && string2 != "GAME DOCTOR" && string3 != "00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00") + return filePath; + + string newFilePath = Path.Combine(tempPath, Path.GetFileName(filePath)); + using (FileStream outStream = new FileStream(newFilePath, FileMode.OpenOrCreate)) + { + inStream.CopyTo(outStream); + } + + return newFilePath; + } + } + + private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDirName}"); + } + + // If the destination directory doesn't exist, create it. + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + // Get the files in the directory and copy them to the new location. + foreach (FileInfo file in dir.EnumerateFiles()) + { + file.CopyTo(Path.Combine(destDirName, file.Name), false); + } + + // If copying subdirectories, copy them and their contents to new location. + if (copySubDirs) + { + foreach (DirectoryInfo subdir in dir.EnumerateDirectories()) + { + DirectoryCopy(subdir.FullName, Path.Combine(destDirName, subdir.Name), copySubDirs); + } + } + } + } +} diff --git a/UWUVCI AIO WPF/UWUVCI AIO WPF.csproj b/UWUVCI AIO WPF/UWUVCI AIO WPF.csproj index 30e5949..b85b2b2 100644 --- a/UWUVCI AIO WPF/UWUVCI AIO WPF.csproj +++ b/UWUVCI AIO WPF/UWUVCI AIO WPF.csproj @@ -45,6 +45,8 @@ + + @@ -71,6 +73,7 @@ App.xaml Code + MenuWindow.xaml Code @@ -104,6 +107,7 @@ +