Example #1
0
        //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();
        }
Example #2
0
        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());
        }