public static void Process(Context ctx) { var accessNeeded = FileAccess.Read; if (ctx.Options.SignSave || ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null || ctx.Options.RepackSource != null || ctx.Options.TrimSave) { accessNeeded = FileAccess.ReadWrite; } using (var file = new LocalStorage(ctx.Options.InFile, accessNeeded)) { bool signNeeded = ctx.Options.SignSave; var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true); FileSystemClient fs = ctx.FsClient; fs.Register("save".ToU8Span(), save); if (ctx.Options.Validate) { save.Verify(ctx.Logger); } if (ctx.Options.OutDir != null) { fs.Register("output".ToU8Span(), new LocalFileSystem(ctx.Options.OutDir)); FsUtils.CopyDirectoryWithProgress(fs, "save:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); fs.Unmount("output".ToU8Span()); } if (ctx.Options.DebugOutDir != null) { string dir = ctx.Options.DebugOutDir; ExportSaveDebug(ctx, dir, save); } try { if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) { string destFilename = ctx.Options.ReplaceFileDest; if (!destFilename.StartsWith("/")) { destFilename = '/' + destFilename; } using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read)) { save.OpenFile(out IFile outFile, destFilename.ToU8String(), OpenMode.ReadWrite).ThrowIfFailure(); using (outFile) { inFile.GetSize(out long inFileSize).ThrowIfFailure(); outFile.GetSize(out long outFileSize).ThrowIfFailure(); if (inFileSize != outFileSize) { outFile.SetSize(inFileSize).ThrowIfFailure(); } inFile.CopyTo(outFile, ctx.Logger); ctx.Logger.LogMessage($"Replaced file {destFilename}"); } } signNeeded = true; } if (ctx.Options.RepackSource != null) { fs.Register("input".ToU8Span(), new LocalFileSystem(ctx.Options.RepackSource)); fs.CleanDirectoryRecursively("save:/".ToU8Span()); fs.Commit("save".ToU8Span()); FsUtils.CopyDirectoryWithProgress(fs, "input:/".ToU8Span(), "save:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure(); fs.Commit("save".ToU8Span()); fs.Unmount("input".ToU8Span()); signNeeded = true; } } finally { if (signNeeded) { if (save.Commit(ctx.KeySet).IsSuccess()) { ctx.Logger.LogMessage( $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); } else { ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } signNeeded = false; } } if (ctx.Options.TrimSave) { save.FsTrim(); signNeeded = true; ctx.Logger.LogMessage("Trimmed save file"); } if (signNeeded) { if (save.Commit(ctx.KeySet).IsSuccess()) { ctx.Logger.LogMessage( $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); } else { ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } fs.Unmount("save".ToU8Span()); return; } if (ctx.Options.ListFiles) { foreach (DirectoryEntryEx entry in save.EnumerateEntries()) { ctx.Logger.LogMessage(entry.FullPath); } } ctx.Logger.LogMessage(save.Print(ctx.KeySet)); //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore)); fs.Unmount("save".ToU8Span()); } }
public static void Process(Context ctx) { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.ReadWrite)) { bool signNeeded = ctx.Options.SignSave; var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true); if (ctx.Options.Validate) { save.Verify(ctx.Logger); } if (ctx.Options.OutDir != null) { save.SaveDataFileSystemCore.Extract(ctx.Options.OutDir, ctx.Logger); } if (ctx.Options.DebugOutDir != null) { string dir = ctx.Options.DebugOutDir; ExportSaveDebug(ctx, dir, save); } try { if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) { string destFilename = ctx.Options.ReplaceFileDest; if (!destFilename.StartsWith("/")) { destFilename = '/' + destFilename; } using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read)) { using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite)) { if (inFile.GetSize() != outFile.GetSize()) { outFile.SetSize(inFile.GetSize()); } inFile.CopyTo(outFile, ctx.Logger); ctx.Logger.LogMessage($"Replaced file {destFilename}"); } } signNeeded = true; } if (ctx.Options.RepackSource != null) { var source = new LocalFileSystem(ctx.Options.RepackSource); save.CleanDirectoryRecursively("/"); save.Commit(ctx.Keyset); source.CopyFileSystem(save); signNeeded = true; } } finally { save.Commit(ctx.Keyset); } if (ctx.Options.TrimSave) { save.FsTrim(); signNeeded = true; ctx.Logger.LogMessage("Trimmed save file"); } if (signNeeded) { if (save.Commit(ctx.Keyset)) { ctx.Logger.LogMessage("Successfully signed save file"); } else { ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } return; } if (ctx.Options.ListFiles) { foreach (DirectoryEntry entry in save.EnumerateEntries()) { ctx.Logger.LogMessage(entry.FullPath); } } ctx.Logger.LogMessage(save.Print()); //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore)); } }
public static void Process(Context ctx) { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.ReadWrite)) { var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true); if (ctx.Options.Validate) { save.Verify(ctx.Logger); } if (ctx.Options.OutDir != null) { save.SaveDataFileSystemCore.Extract(ctx.Options.OutDir, ctx.Logger); } if (ctx.Options.DebugOutDir != null) { // todo string dir = ctx.Options.DebugOutDir; Directory.CreateDirectory(dir); FsLayout layout = save.Header.Layout; string mainRemapDir = Path.Combine(dir, "main_remap"); Directory.CreateDirectory(mainRemapDir); save.DataRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Data")); save.DataRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Header")); save.DataRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(mainRemapDir, "Map entries")); string metadataRemapDir = Path.Combine(dir, "metadata_remap"); Directory.CreateDirectory(metadataRemapDir); save.MetaRemapStorage.GetBaseStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Data")); save.MetaRemapStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Header")); save.MetaRemapStorage.GetMapEntryStorage().WriteAllBytes(Path.Combine(metadataRemapDir, "Map entries")); string journalDir = Path.Combine(dir, "journal"); Directory.CreateDirectory(journalDir); save.JournalStorage.GetBaseStorage().WriteAllBytes(Path.Combine(journalDir, "Data")); save.JournalStorage.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Header")); save.JournalStorage.Map.GetHeaderStorage().WriteAllBytes(Path.Combine(journalDir, "Map_header")); save.JournalStorage.Map.GetMapStorage().WriteAllBytes(Path.Combine(journalDir, "Map")); save.JournalStorage.Map.GetModifiedPhysicalBlocksStorage().WriteAllBytes(Path.Combine(journalDir, "ModifiedPhysicalBlocks")); save.JournalStorage.Map.GetModifiedVirtualBlocksStorage().WriteAllBytes(Path.Combine(journalDir, "ModifiedVirtualBlocks")); save.JournalStorage.Map.GetFreeBlocksStorage().WriteAllBytes(Path.Combine(journalDir, "FreeBlocks")); string saveDir = Path.Combine(dir, "save"); Directory.CreateDirectory(saveDir); save.SaveDataFileSystemCore.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Header")); save.SaveDataFileSystemCore.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "Save_Data")); save.SaveDataFileSystemCore.AllocationTable.GetHeaderStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_header")); save.SaveDataFileSystemCore.AllocationTable.GetBaseStorage().WriteAllBytes(Path.Combine(saveDir, "FAT_Data")); save.Header.DataIvfcMaster.WriteAllBytes(Path.Combine(saveDir, "Save_MasterHash")); IStorage saveLayer1Hash = save.MetaRemapStorage.Slice(layout.IvfcL1Offset, layout.IvfcL1Size); IStorage saveLayer2Hash = save.MetaRemapStorage.Slice(layout.IvfcL2Offset, layout.IvfcL2Size); IStorage saveLayer3Hash = save.MetaRemapStorage.Slice(layout.IvfcL3Offset, layout.IvfcL3Size); saveLayer1Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer1Hash"), ctx.Logger); saveLayer2Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer2Hash"), ctx.Logger); saveLayer3Hash.WriteAllBytes(Path.Combine(saveDir, "Save_Layer3Hash"), ctx.Logger); string duplexDir = Path.Combine(dir, "duplex"); Directory.CreateDirectory(duplexDir); save.Header.DuplexMasterBitmapA.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapA")); save.Header.DuplexMasterBitmapB.WriteAllBytes(Path.Combine(duplexDir, "MasterBitmapB")); IStorage duplexL1A = save.DataRemapStorage.Slice(layout.DuplexL1OffsetA, layout.DuplexL1Size); IStorage duplexL1B = save.DataRemapStorage.Slice(layout.DuplexL1OffsetB, layout.DuplexL1Size); IStorage duplexDataA = save.DataRemapStorage.Slice(layout.DuplexDataOffsetA, layout.DuplexDataSize); IStorage duplexDataB = save.DataRemapStorage.Slice(layout.DuplexDataOffsetB, layout.DuplexDataSize); duplexL1A.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapA"), ctx.Logger); duplexL1B.WriteAllBytes(Path.Combine(duplexDir, "L1BitmapB"), ctx.Logger); duplexDataA.WriteAllBytes(Path.Combine(duplexDir, "DataA"), ctx.Logger); duplexDataB.WriteAllBytes(Path.Combine(duplexDir, "DataB"), ctx.Logger); } if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) { string destFilename = ctx.Options.ReplaceFileDest; if (!destFilename.StartsWith("/")) { destFilename = '/' + destFilename; } using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read)) { using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite)) { if (inFile.GetSize() != outFile.GetSize()) { ctx.Logger.LogMessage($"Replacement file must be the same size as the original file. ({outFile.GetSize()} bytes)"); return; } inFile.CopyTo(outFile, ctx.Logger); ctx.Logger.LogMessage($"Replaced file {destFilename}"); } } if (save.Commit(ctx.Keyset)) { ctx.Logger.LogMessage("Successfully signed save file"); } else { ctx.Logger.LogMessage("ERROR: Unable to sign save file. Do you have all the required keys?"); } return; } if (ctx.Options.SignSave) { if (save.Commit(ctx.Keyset)) { ctx.Logger.LogMessage("Successfully signed save file"); } else { ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } return; } if (ctx.Options.ListFiles) { IDirectory dir = save.SaveDataFileSystemCore.OpenDirectory("/", OpenDirectoryMode.All); foreach (DirectoryEntry entry in dir.EnumerateEntries()) { ctx.Logger.LogMessage(entry.FullPath); } } ctx.Logger.LogMessage(save.Print()); } }
void Start(string keys, string fwPath, bool noExfat, bool verbose, bool showNcaIndex, bool fixHashes) { Config.keyset = ExternalKeyReader.ReadKeyFile(keys); Config.fwPath = fwPath; Config.noExfat = noExfat; Config.normalBisId = (noExfat) ? "0100000000000819" : "010000000000081B"; Config.safeBisId = (noExfat) ? "010000000000081A" : "010000000000081C"; Config.verbose = verbose; Config.fixHashes = fixHashes; int convertCount = 0; foreach (var foldername in Directory.GetDirectories(fwPath, "*.nca")) { convertCount++; File.Move($"{foldername}/00", $"{fwPath}/temp"); Directory.Delete(foldername); File.Move($"{fwPath}/temp", foldername); } if (convertCount > 0) { Console.WriteLine($"Converted folder ncas to files (count: {convertCount})"); } Console.WriteLine("Indexing nca files..."); NcaIndexer ncaIndex = new NcaIndexer(); VersionExtractor versionExtractor = new VersionExtractor(ncaIndex.FindNca("0100000000000809", NcaContentType.Meta)); string destFolder = $"NX-{versionExtractor.Version}"; if (!noExfat) { destFolder += "_exFAT"; } if (showNcaIndex) { ShowNcaIndex(ref ncaIndex, destFolder); return; } Console.WriteLine("\nEmmcHaccGen will now generate firmware files using the following settings:\n" + $"fw: {versionExtractor.Version}\n" + $"Exfat Support: {!noExfat}\n" + $"Key path: {keys}\n" + $"Destination folder: {destFolder}\n"); if (verbose) { Console.WriteLine($"BisIds:\nNormal: {Config.normalBisId}\nSafe: {Config.safeBisId}\n"); } // Folder creation Console.WriteLine("\nCreating folders.."); if (Directory.Exists(destFolder)) { Console.Write("Destenation folder already exists. Delete the old folder?\nY/N: "); string input = Console.ReadLine(); if (input[0].ToString().ToLower() != "y") { return; } Console.WriteLine($"Deleting {destFolder}"); Directory.Delete(destFolder, true); } foreach (string folder in FOLDERSTRUCTURE) { Directory.CreateDirectory($"{destFolder}{folder}"); } // Bis creation Console.WriteLine("\nGenerating bis.."); BisAssembler bisAssembler = new BisAssembler(ref ncaIndex, destFolder); BisFileAssembler bisFileAssembler = new BisFileAssembler($"{versionExtractor.Version}{((!noExfat) ? "_exFAT" : "")}", ref bisAssembler, $"{destFolder}/boot.bis"); // Copy fw files Console.WriteLine("\nCopying files..."); foreach (var file in Directory.EnumerateFiles(fwPath)) { File.Copy(file, $"{destFolder}/SYSTEM/Contents/registered/{file.Split(new char[] { '/', '\\' }).Last().Replace(".cnmt.nca ", ".nca ")}", true); } // Archive bit setting Console.WriteLine("\nSetting archive bits.."); SetArchiveRecursively($"{destFolder}/SYSTEM"); SetArchiveRecursively($"{destFolder}/USER"); //Imkv generation Console.WriteLine("\nGenerating imkvdb.."); Imkv imkvdb = new Imkv(ref ncaIndex); if (verbose) { imkvdb.DumpToFile($"{destFolder}/data.arc"); } File.Copy("save.stub", $"{destFolder}/SYSTEM/save/8000000000000120", true); using (IStorage outfile = new LocalStorage($"{destFolder}/SYSTEM/save/8000000000000120", FileAccess.ReadWrite)) { var save = new SaveDataFileSystem(Config.keyset, outfile, IntegrityCheckLevel.ErrorOnInvalid, true); save.OpenFile(out IFile file, new U8Span("/meta/imkvdb.arc"), OpenMode.AllowAppend | OpenMode.ReadWrite); using (file) { file.Write(0, imkvdb.bytes.ToArray(), WriteOption.Flush).ThrowIfFailure(); } save.Commit(Config.keyset).ThrowIfFailure(); } Console.WriteLine($"Wrote save with an imvkdb size of 0x{imkvdb.bytes.Count:X4}"); }