//TODO: Allow saving files in PSP2i format (using partial encrypt and deflate instead of PRS) public void saveFile(Stream fileToWrite, bool compressNmll, bool compressTmll, bool saveUnmodified) { BinaryWriter beta; if (isBigEndian) { beta = new BigEndianBinaryWriter(fileToWrite); } else { beta = new BinaryWriter(fileToWrite); } //A bit of logic just in case we're loading naked TMLL chunks for some reason. bool startsWithNmll = (chunks[0].chunkID.StartsWith("NML")); uint tmllHeaderLength = 0; uint tmllLength = 0; uint tmllCompressed = 0; int tmllFiles = 0; for (int i = 0; i < chunks.Count; i++) { if (chunks[i].chunkID.StartsWith("NML")) { chunks[i].compressed = compressNmll; } if (chunks[i].chunkID.StartsWith("TML")) { chunks[i].compressed = compressTmll; } byte[] currentChunkData = chunks[i].SaveFile(saveUnmodified); //Fill in TMLL info for NMLL header (yep) //Ignoring the case where you have multiple TMLLs (this is a damaged file, anyway) if (chunks[i].chunkID.StartsWith("TML")) { tmllHeaderLength = BitConverter.ToUInt32(currentChunkData, 0x8); tmllLength = BitConverter.ToUInt32(currentChunkData, 0x10); tmllCompressed = BitConverter.ToUInt32(currentChunkData, 0x14); tmllFiles = chunks[i].fileContents.Count; } beta.Write(currentChunkData); } if (startsWithNmll) { fileToWrite.Seek(0x20, SeekOrigin.Begin); beta.Write(tmllHeaderLength); beta.Write(tmllLength); beta.Write(tmllCompressed); beta.Write(tmllFiles); } beta.Close(); }
public byte[] SaveFile(bool discardChanges) { //First, combine all the files. MemoryStream groupFileStream = new MemoryStream(); MemoryStream groupHeaderStream = new MemoryStream(); BinaryWriter groupHeaderWriter; if (bigEndian) { groupHeaderWriter = new BigEndianBinaryWriter(groupHeaderStream); } else { groupHeaderWriter = new BinaryWriter(groupHeaderStream); } int paddingAmount = versionNumber == 0x1002 ? 0x3F : 0x7FF; uint mask = versionNumber == 0x1002 ? 0xFFFFFFC0 : 0xFFFFF800; if (this.chunkID.StartsWith("NML")) { groupHeaderStream.Seek(0x30, SeekOrigin.Begin); } else if (this.chunkID.StartsWith("TML")) { groupHeaderStream.Seek(0x20, SeekOrigin.Begin); } NblLoader.FileHeader[] headers = new NblLoader.FileHeader[this.fileContents.Count]; List <RawFile> savedFiles = new List <RawFile>(this.fileContents); List <int> pointers = new List <int>(); //Annoying, this one has to be a running tally, can't do it any other way. ushort filenamelength = 0; for (int i = 0; i < fileContents.Count; i++) { //Figure out whether to take the cached copy or the original if (this.loadedFileCache.ContainsKey(fileContents[i].filename) && !discardChanges) { savedFiles[i] = loadedFileCache[fileContents[i].filename].ToRawFile((uint)groupFileStream.Position); savedFiles[i].chunkSize = fileContents[i].chunkSize; } else if (savedFiles[i].fileOffset != (uint)groupFileStream.Position) { savedFiles[i].RebaseFile((uint)groupFileStream.Position); } //Let's not use a FileHeader any more. Just put all the data straight into the file. //Guessing on identifier--this SHOULD be true, generally, but...? //This needs to be stored in the file class. string identifier = "STD\0"; if (this.chunkID.StartsWith("TML")) { identifier = "NNVR"; } else if (savedFiles[i].subHeader != null && savedFiles[i].subHeader[0] == 0x4E) //'N'--so hopefully just NXIF or NUIF { identifier = Path.GetExtension(savedFiles[i].filename).Substring(1).ToUpper().PadRight(4, '\0'); } groupHeaderWriter.Write(ASCIIEncoding.ASCII.GetBytes(identifier)); groupHeaderWriter.Write(savedFiles[i].chunkSize); groupHeaderWriter.Write((int)0); //"unknown1" groupHeaderWriter.Write((int)0); //"unknown2" groupHeaderWriter.Write(ASCIIEncoding.ASCII.GetBytes(savedFiles[i].filename.PadRight(0x20, '\0'))); groupHeaderWriter.Write(savedFiles[i].fileOffset); groupHeaderWriter.Write(savedFiles[i].fileContents.Length); groupHeaderWriter.Write(pointers.Count * 4); groupHeaderWriter.Write(savedFiles[i].pointers.Count * 4); if (savedFiles[i].subHeader == null) { groupHeaderWriter.Write(new byte[0x20]); } else { groupHeaderWriter.Write(savedFiles[i].subHeader); } //Update filename length (include \0 terminator). if (this.chunkID.StartsWith("NML")) { filenamelength += (ushort)(savedFiles[i].filename.Length + 1); } //Now put the data into the file pieces. groupFileStream.Write(savedFiles[i].fileContents, 0, savedFiles[i].fileContents.Length); //Padding out to nearest 0x10 groupFileStream.Seek((int)(groupFileStream.Position + 0x1F) & 0xFFFFFFE0, SeekOrigin.Begin); pointers.AddRange(savedFiles[i].pointers); } int headerLength = (int)groupHeaderStream.Position; groupHeaderStream.Seek((groupHeaderStream.Position + paddingAmount) & mask, SeekOrigin.Begin); int uncompressedSize = (int)groupFileStream.Position; byte[] rawData; if (compressed) { if (versionNumber == 0x1002) // PSP2 files use Deflate. { groupFileStream.Seek(0, SeekOrigin.Begin); MemoryStream compressedStream = new MemoryStream(); using (DeflateStream ds = new DeflateStream(compressedStream, CompressionMode.Compress)) { groupFileStream.CopyTo(ds); } rawData = compressedStream.ToArray(); } else //PSU uses PRS. { rawData = PrsCompDecomp.compress(groupFileStream.ToArray()); } } else { rawData = groupFileStream.ToArray(); } int fileLength = rawData.Length; groupHeaderWriter.Write(rawData); //Write out pointers (if applicable) groupHeaderStream.Seek((groupHeaderStream.Position + paddingAmount) & mask, SeekOrigin.Begin); for (int i = 0; i < pointers.Count; i++) { groupHeaderWriter.Write(pointers[i]); } groupHeaderWriter.Write(new byte[((groupHeaderStream.Position + paddingAmount) & mask) - groupHeaderStream.Position]); //Now fill in the header (leaving the space if necessary). groupHeaderStream.Seek(0, SeekOrigin.Begin); groupHeaderWriter.Write(ASCIIEncoding.ASCII.GetBytes(this.chunkID)); groupHeaderWriter.Write(this.versionNumber); groupHeaderWriter.Write(filenamelength); groupHeaderWriter.Write(headerLength); groupHeaderWriter.Write(this.fileContents.Count); groupHeaderWriter.Write(uncompressedSize); if (compressed) { groupHeaderWriter.Write(rawData.Length); } else { groupHeaderWriter.Write((int)0); } groupHeaderWriter.Write(pointers.Count * 4); groupHeaderWriter.Write((int)0); //Still enforcing no encryption. return(groupHeaderStream.ToArray()); }