public void Load(string spcPath) { using BinaryReader reader = new BinaryReader(new FileStream(spcPath, FileMode.Open)); // Verify the magic value, it could either be "CPS." (the one we want) or "$CFH" (most files in the console version, unusable for now) string magic = new ASCIIEncoding().GetString(reader.ReadBytes(4)); if (magic == "$CFH") { // decompress using SRD method first, then resume return; } if (magic != "CPS.") { Console.WriteLine("ERROR: Not a valid SPC file, magic number invalid."); return; } // Read the first set of data Unknown1 = reader.ReadBytes(0x24); int fileCount = reader.ReadInt32(); Unknown2 = reader.ReadInt32(); reader.BaseStream.Seek(0x10, SeekOrigin.Current); // Verify file table header, should be "Root" if (!new ASCIIEncoding().GetString(reader.ReadBytes(4)).Equals("Root")) { Console.WriteLine("ERROR: Not a valid SPC file, table header invalid."); return; } reader.BaseStream.Seek(0x0C, SeekOrigin.Current); // For each subfile in the table, read the corresponding data for (int i = 0; i < fileCount; ++i) { SpcSubfile subfile = new SpcSubfile { CompressionFlag = reader.ReadInt16(), UnknownFlag = reader.ReadInt16(), CurrentSize = reader.ReadInt32(), OriginalSize = reader.ReadInt32() }; int nameLength = reader.ReadInt32(); reader.BaseStream.Seek(0x10, SeekOrigin.Current); int namePadding = (0x10 - (nameLength + 1) % 0x10) % 0x10; subfile.Name = new ASCIIEncoding().GetString(reader.ReadBytes(nameLength)); reader.BaseStream.Seek(namePadding + 1, SeekOrigin.Current); // Discard the null terminator int dataPadding = (0x10 - subfile.CurrentSize % 0x10) % 0x10; subfile.Data = reader.ReadBytes(subfile.CurrentSize); reader.BaseStream.Seek(dataPadding, SeekOrigin.Current); Subfiles.Add(subfile); } }
/// <summary> /// Asynchronously inserts a file into the SPC archive. If a file with the same name already exists within the archive, it will be replaced. /// </summary> /// <param name="filename">The path of the file to be inserted into the SPC archive.</param> /// <param name="compress">Whether the subfile should be compressed before inserting. Unless you know what you're doing, leave this set to "true".</param> /// <param name="confirmation">An async delegate whose output will determine whether a file will be replaced. If null, there is no confirmation.</param> public async Task InsertSubfileAsync(string filename, bool compress = true, OverwriteConfirmationAsync confirmation = null) { FileInfo info = new FileInfo(filename); if (!info.Exists) { Console.WriteLine($"ERROR: Target file\"{info.FullName}\" does not exist."); return; } // Check if a subfile already exists with the specified name int existingIndex = -1; for (int s = 0; s < Subfiles.Count; ++s) { if (info.Name == Subfiles[s].Name) { if (confirmation == null || await confirmation()) { existingIndex = s; break; } } } using BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open)); int subfileSize = (int)reader.BaseStream.Length; SpcSubfile subfileToInject = new SpcSubfile { CompressionFlag = 1, UnknownFlag = (short)(subfileSize > ushort.MaxValue ? 8 : 4), // seems like this flag might relate to size? This is a BIG guess though. CurrentSize = subfileSize, OriginalSize = subfileSize, Name = info.Name, Data = reader.ReadBytes(subfileSize) }; reader.Close(); if (compress) { subfileToInject.Compress(); } // Check if a subfile already exists with the specified name and replace if (existingIndex != -1) { Subfiles[existingIndex] = subfileToInject; return; } // We should only reach this code if there is not an existing subfile with the same name Subfiles.Add(subfileToInject); }