/// <summary> /// Loads an existing executable file system from the given data. /// </summary> /// <param name="data">Accessor to the raw data to load as a executable file system</param> /// <returns>The executable file system the given data represents</returns> public static async Task <ExeFs> Load(IReadOnlyBinaryDataAccessor exeFsData) { var exefs = new ExeFs(exeFsData); await exefs.Initialize().ConfigureAwait(false); return(exefs); }
/// <summary> /// Builds a new executable file system from the given directory /// </summary> /// <param name="directory">Directory from which to load the files</param> /// <param name="fileSystem">File system from which to load the files</param> /// <returns>A newly built executable file system</returns> public static async Task <ExeFs> Build(string directory, IFileSystem fileSystem, ProcessingProgressedToken?progressReportToken = null) { var files = fileSystem.GetFiles(directory, "*", true).ToList(); if (files.Count > 10) { throw new ArgumentException(Properties.Resources.ExeFs_ExceededMaximumFileCount, nameof(directory)); } if (progressReportToken != null) { progressReportToken.TotalFileCount = files.Count; } var exefs = new ExeFs(); foreach (var file in files) { exefs.Files.Add(Path.GetFileName(file), null); } await files.RunAsyncForEach(file => { exefs.Files[Path.GetFileName(file)] = new ExeFsEntry(fileSystem.ReadAllBytes(file)); if (progressReportToken != null) { progressReportToken.IncrementProcessedFileCount(); } }).ConfigureAwait(false); return(exefs); }
public static async Task <ExeFs> Load(IBinaryDataAccessor exeFsData) { var exefs = new ExeFs(exeFsData); await exefs.Initialize(); return(exefs); }
public NcchPartition(RomFs romfs = null, ExeFs exefs = null, NcchHeader header = null, NcchExtendedHeader exheader = null, string plainRegion = null, byte[] logo = null) { RomFs = romfs; ExeFs = exefs; Header = header; ExHeader = exheader; PlainRegion = plainRegion; Logo = logo; }
public static async Task <bool> IsExeFs(IReadOnlyBinaryDataAccessor file) { try { if (file.Length < 0x200) { return(false); } var exefs = await ExeFs.Load(file).ConfigureAwait(false); return(exefs.Files.Any() && exefs.AreAllHashesValid()); } catch (Exception) { return(false); } }
public async Task Initialize(IReadOnlyBinaryDataAccessor data) { if (Header != null && Header.RomFsSize > 0) { if (Header.ExeFsOffset > 0 && Header.ExeFsSize > 0) { ExeFs = await ExeFs.Load(data.GetReadOnlyDataReference((long)Header.ExeFsOffset *MediaUnitSize, (long)Header.ExeFsSize *MediaUnitSize)); } if (Header.RomFsOffset > 0 && Header.RomFsOffset > 0) { RomFs = await RomFs.Load(data.GetReadOnlyDataReference((long)Header.RomFsOffset *MediaUnitSize, (long)Header.RomFsSize *MediaUnitSize)); } if (Header.ExHeaderSize > 0) { ExHeader = data.GetReadOnlyDataReference(0x200, Header.ExHeaderSize); } } }
public static async Task <bool> IsExeFs(IBinaryDataAccessor file) { try { if (file.Length < 0x200) { return(false); } var exefsHeaders = (await ExeFs.Load(file)).Headers.Where(h => !string.IsNullOrEmpty(h.Filename)); if (!exefsHeaders.Any()) { return(false); } return(exefsHeaders.All(h => h.Offset >= 0x200 && (h.Offset + h.FileSize) < file.Length)); } catch (Exception) { return(false); } }
public async Task Initialize(IReadOnlyBinaryDataAccessor data) { this.RawData = data; if (Header != null && Header.RomFsSize > 0) { if (Header.ExeFsOffset > 0 && Header.ExeFsSize > 0) { ExeFs = await ExeFs.Load(data.Slice((long)Header.ExeFsOffset *MediaUnitSize, (long)Header.ExeFsSize *MediaUnitSize)); } if (Header.RomFsOffset > 0 && Header.RomFsOffset > 0) { RomFs = await RomFs.Load(data.Slice((long)Header.RomFsOffset *MediaUnitSize, (long)Header.RomFsSize *MediaUnitSize)); } if (Header.ExHeaderSize > 0) { ExHeader = await NcchExtendedHeader.Load(data.Slice(0x200, Header.ExHeaderSize)); } PlainRegion = await data.ReadStringAsync(Header.PlainRegionOffset *MediaUnitSize, Header.PlainRegionSize *MediaUnitSize, Encoding.ASCII); Logo = await data.ReadArrayAsync(Header.LogoRegionOffset *MediaUnitSize, Header.LogoRegionSize *MediaUnitSize); } }
/// <summary> /// Builds a new NCCH partition from the given directory /// </summary> /// <param name="fileSystem">File system from which to load the files</param> /// <returns>A newly built NCCH partition</returns> public static async Task <NcchPartition> Build(string headerFilename, string exHeaderFilename, string?exeFsDirectory, string?romFsDiretory, string?plainRegionFilename, string?logoFilename, IFileSystem fileSystem, ProcessingProgressedToken?progressToken = null) { ProcessingProgressedToken?exefsToken = null; ProcessingProgressedToken?romfsToken = null; void ReportProgress() { if (progressToken != null) { progressToken.TotalFileCount = (exefsToken?.TotalFileCount + romfsToken?.TotalFileCount).GetValueOrDefault(); progressToken.ProcessedFileCount = (exefsToken?.ProcessedFileCount + romfsToken?.ProcessedFileCount).GetValueOrDefault(); } }; Task <ExeFs?> exeFsTask; if (!string.IsNullOrEmpty(exeFsDirectory)) { if (progressToken != null) { exefsToken = new ProcessingProgressedToken(); exefsToken.FileCountChanged += (sender, e) => ReportProgress(); } exeFsTask = Task.Run <ExeFs?>(async() => await ExeFs.Build(exeFsDirectory, fileSystem, exefsToken).ConfigureAwait(false)); } else { exeFsTask = Task.FromResult <ExeFs?>(null); } Task <RomFs?> romFsTask; if (!string.IsNullOrEmpty(romFsDiretory)) { if (progressToken != null) { romfsToken = new ProcessingProgressedToken(); romfsToken.FileCountChanged += (sender, e) => ReportProgress(); } romFsTask = Task.Run <RomFs?>(async() => await RomFs.Build(romFsDiretory, fileSystem, romfsToken).ConfigureAwait(false)); } else { romFsTask = Task.FromResult <RomFs?>(null); } var header = new NcchHeader(fileSystem.ReadAllBytes(headerFilename)); NcchExtendedHeader?exHeader = null; if (!string.IsNullOrEmpty(exHeaderFilename)) { using var exHeaderData = new BinaryFile(fileSystem.ReadAllBytes(exHeaderFilename)); exHeader = await NcchExtendedHeader.Load(exHeaderData); } string?plainRegion = null; if (!string.IsNullOrEmpty(plainRegionFilename)) { plainRegion = fileSystem.ReadAllText(plainRegionFilename); } byte[]? logo = null; if (!string.IsNullOrEmpty(logoFilename)) { logo = fileSystem.ReadAllBytes(logoFilename); } return(new NcchPartition(await romFsTask, await exeFsTask, header, exHeader, plainRegion, logo)); }
/// <summary> /// Writes the current state of the NCCH partition to the given binary data accessor /// </summary> /// <param name="data">Data accessor to receive the binary data</param> /// <returns>A long representing the total length of data written</returns> public async Task <long> WriteBinary(IWriteOnlyBinaryDataAccessor data) { // Get the data var exheader = ExHeader?.ToByteArray(); var plainRegion = !string.IsNullOrEmpty(PlainRegion) ? Encoding.ASCII.GetBytes(PlainRegion) : null; var plainRegionOffset = 0; var logoRegionOffset = 0; var exeFs = ExeFs?.ToByteArray(); var exeFsOffset = 0; var romFs = RomFs?.Data; var romFsOffset = 0; // Write the data var offset = 0x200; // Skip the header, write it last if (exheader != null) { await data.WriteAsync(offset, exheader); offset += exheader.Length; } if (plainRegion != null) { plainRegionOffset = offset; await data.WriteAsync(offset, plainRegion); offset += plainRegion.Length; var padding = new byte[0x200 - plainRegion.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (Logo != null) { logoRegionOffset = offset; await data.WriteAsync(offset, Logo); offset += Logo.Length; var padding = new byte[0x200 - Logo.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (exeFs != null) { exeFsOffset = offset; await data.WriteAsync(offset, exeFs); offset += exeFs.Length; var padding = new byte[0x200 - exeFs.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } if (romFs != null) { romFsOffset = offset; const int bufferSize = 1024 * 1024; for (int i = 0; i < romFs.Length; i += bufferSize) { int length = (int)Math.Min(bufferSize, romFs.Length - i); var block = await romFs.ReadArrayAsync(i, length); await data.WriteAsync(offset, block); offset += length; } var padding = new byte[0x200 - romFs.Length % 0x200]; await data.WriteAsync(offset, padding); offset += padding.Length; } // Create a new header using var sha = SHA256.Create(); var header = NcchHeader.Copy(this.Header); header.Signature = new byte[0x100]; // We lack the 3DS's private key, so leave out the signature header.ContentSize = (offset + MediaUnitSize - 1) / MediaUnitSize; // offset/MediaUnitSize, but rounding up header.ContentLockSeedHash = 0; // Unknown, left blank by SciresM's 3DS Builder if (Logo != null) { header.LogoRegionHash = sha.ComputeHash(Logo); } else { header.LogoRegionHash = new byte[0x20]; } if (exheader != null) { header.ExHeaderHash = NcchExtendedHeader.GetSuperblockHash(sha, exheader); header.ExHeaderSize = NcchExtendedHeader.ExHeaderDataSize; } else { header.ExHeaderHash = new byte[0x20]; header.ExHeaderSize = 0; } header.PlainRegionOffset = (plainRegionOffset + MediaUnitSize - 1) / MediaUnitSize; header.PlainRegionSize = ((plainRegion?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.LogoRegionOffset = (logoRegionOffset + MediaUnitSize - 1) / MediaUnitSize; header.LogoRegionSize = ((Logo?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsOffset = (exeFsOffset + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsSize = ((exeFs?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsHashRegionSize = 1; // Static 0x200 for exefs superblock header.RomFsOffset = (romFsOffset + MediaUnitSize - 1) / MediaUnitSize; header.RomFsSize = ((int)(romFs?.Length ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.RomFsHashRegionSize = ((RomFs?.Header?.MasterHashSize ?? 0) + MediaUnitSize - 1) / MediaUnitSize; header.ExeFsSuperblockHash = ExeFs?.GetSuperblockHash() ?? new byte[0x20]; header.RomFsSuperblockHash = RomFs != null ? await RomFs.GetSuperblockHash(sha, romFs, RomFs.Header) : new byte[0x20]; var headerData = await header.ToBinary().ReadArrayAsync(); await data.WriteAsync(0, headerData); return(offset); }
public NcchPartition(RomFs romfs = null, ExeFs exefs = null, IReadOnlyBinaryDataAccessor exheader = null) { RomFs = romfs; ExeFs = exefs; ExHeader = exheader; }