private void WriteEditedImg(FileStream tempFileStream, int editedFrameIndex, IReadOnlyList <FrameInfo> frameListOfImgEditing, FrameInfo frameMetadataOfFrameEditing, FrameInfo newFrameMetadata, byte[] newPixelData, int newPixelDataLength, byte[] frameMetadataBytes, List <NpkByteRange> frameLocationsOfImgEditing) { // Need to know exact ordering of files // Need to know ALL files, even non-image files // Need to update offset of all files with an offset greater or equal to the one we're changing // Need to update file size of file that we're changing and any that occupy the same space //.img files begin with "Neople Img File\0" in ASCII tempFileStream.Write(NpkReader.s_imgHeaderBytes, 0, NpkReader.s_imgHeaderBytes.Length); // field 1 - (36 * (# non-link frames) + 8 * (# link frames)) //uint field1Value = (uint)(36 * numberOfNonLinkFramesInImgBeforeEdit + 8 * numberOfLinkFramesInImgBeforeEdit); uint field1Value = GetImgField1Value(frameListOfImgEditing, frameMetadataOfFrameEditing, newFrameMetadata); tempFileStream.WriteUnsigned32Le(field1Value, _intWriteBuffer); // field 2 - always 0 tempFileStream.WriteUnsigned32Le(0, _intWriteBuffer); // field 3 - always 2 tempFileStream.WriteUnsigned32Le(2, _intWriteBuffer); // frame count tempFileStream.WriteUnsigned32Le((uint)frameListOfImgEditing.Count, _intWriteBuffer); // frame metadata for each frame in order for (int frameIndexToWrite = 0; frameIndexToWrite < frameListOfImgEditing.Count; frameIndexToWrite++) { if (frameIndexToWrite != editedFrameIndex) { FrameInfo frameToCopy = frameListOfImgEditing[frameIndexToWrite]; byte[] metadataBytes = GetFrameMetadataBytes(frameToCopy, frameToCopy.CompressedLength); tempFileStream.Write(metadataBytes, 0, metadataBytes.Length); } else { tempFileStream.Write(frameMetadataBytes, 0, frameMetadataBytes.Length); } } // frame pixel data for each non-link frame in order for (int frameIndexToWrite = 0; frameIndexToWrite < frameListOfImgEditing.Count; frameIndexToWrite++) { if (frameIndexToWrite != editedFrameIndex) { FrameInfo frameToCopy = frameListOfImgEditing[frameIndexToWrite]; if (frameToCopy.PixelFormat != PixelDataFormat.Link) { NpkByteRange frameToWriteOriginalLocation = frameLocationsOfImgEditing[frameIndexToWrite]; _npkStream.Seek(frameToWriteOriginalLocation.FileOffset, SeekOrigin.Begin); _npkStream.CopyToPartially(tempFileStream, frameToWriteOriginalLocation.Size); } } else { // Write the pixel data for the frame we're changing, if it's not a link frame if (newFrameMetadata.PixelFormat != PixelDataFormat.Link) { tempFileStream.Write(newPixelData, 0, newPixelDataLength); } } } }
/// <summary> /// Loads the pixels of a frame. /// </summary> /// <param name="imgPath">The Npk Path of the .img file, with the leading sprite/ if present</param> /// <param name="frameIndex"></param> /// <returns></returns> /// <exception cref="System.IO.FileNotFoundException">The img file does not exist in this .npk file /// or no frame with the given index exists in the img file.</exception> /// <exception cref="System.IO.IOException">An I/O error occurred.</exception> /// <exception cref="Dfo.Npk.NpkException">The .npk file is corrupt or the format changed.</exception> public Image GetImage(NpkPath imgPath, int frameIndex) { imgPath.ThrowIfNull("imgPath"); try { PreLoadSpriteMetadata(imgPath); IList <FrameInfo> imgFrames = m_frames[imgPath]; if (frameIndex >= imgFrames.Count || frameIndex < 0) { throw new FileNotFoundException("Cannot get frame index {0} of {1}. It only has {2} frames." .F(frameIndex, imgPath, imgFrames.Count)); } FrameInfo frameData = imgFrames[frameIndex]; int realFrameIndex = frameIndex; // Follow frame links if (frameData.LinkFrame != null) { realFrameIndex = frameData.LinkFrame.Value; if (realFrameIndex >= imgFrames.Count || realFrameIndex < 0) { throw new FileNotFoundException("Cannot get linked frame index {0} of {1}. It only has {2} frames." .F(realFrameIndex, imgPath, imgFrames.Count)); } frameData = imgFrames[realFrameIndex]; if (frameData.LinkFrame != null) { throw new NpkException( "There is a link frame to another link frame which is not allowed. {0} frame {1} links to frame {2}." .F(imgPath, frameIndex, realFrameIndex)); } } NpkByteRange pixelDataLocation = m_frameLocations[imgPath][realFrameIndex]; // Seek to the pixel data and read it Seek(pixelDataLocation.FileOffset, SeekOrigin.Begin); byte[] pixelData = new byte[pixelDataLocation.Size]; m_npkStream.ReadOrDie(pixelData, pixelData.Length); if (frameData.IsCompressed) { using (MemoryStream pixelDataMemoryStream = new MemoryStream(pixelData)) { try { using (InflaterInputStream decompressStream = new InflaterInputStream(pixelDataMemoryStream)) { byte[] decompressedPixelData = decompressStream.ReadFully(); pixelData = decompressedPixelData; } } catch (SharpZipBaseException ex) { throw new NpkException(string.Format("Inflate error: {0}", ex.Message), ex); } } } pixelData = ExpandPixelData(pixelData, frameData); return(new Image(pixelData, frameData)); } catch (EndOfStreamException ex) { throw new NpkException("Unexpected end of file.", ex); } }
/// <summary> /// Loads a sprite file's metadata, setting its value in m_images and /// m_frameLocations. The .npk file is assumed to be open. /// </summary> /// <param name="lookForErrors">Do some extra checks to look for errors in the NPK reading code. For use by automated tests.</param> /// <exception cref="System.IO.IOException">I/O error.</exception> /// <exception cref="Dfo.Npk.NpkException">The .npk file appears to be corrupt or the format has changed.</exception> private void LoadSpriteFileMetaData(NpkPath spriteFilePath, NpkByteRange spriteFileLocation) { // If already loaded, return if (m_frames.ContainsKey(spriteFilePath)) { return; } try { // Seek to the sprite file's location in the .npk Seek(spriteFileLocation.FileOffset, SeekOrigin.Begin); // .img files begin with "Neople Img File\0" in ASCII byte[] headerBuffer = new byte[s_imgHeaderBytes.Length]; m_npkStream.ReadOrDie(headerBuffer, headerBuffer.Length); string headerString = Encoding.ASCII.GetString(headerBuffer); if (!string.Equals(s_imgHeaderString, headerString, StringComparison.Ordinal)) { throw new NpkException("Did not find expected image file header when reading {0}.".F(spriteFilePath)); } // 32-bit unsigned int - this field seems to be 36 * (# non-link frames) + 8 * (# link frames). A scan of all NPKs bears this out. uint field1 = GetUnsigned32Le(); // 32-bit unsigned int - this field seems to be always 0. A scan of all NPKs bears this out. uint field2 = GetUnsigned32Le(); if (DoExtraErrorChecks && field2 != 0) { OnErrorDetected(string.Format("Field 2 in {0} is {1}, not 0 as expected.", spriteFilePath, field2)); } // 32-bit unsigned int - this field seems to be always 2 (some sort of version number perhaps?). A scan of all NPKs bears this out. uint field3 = GetUnsigned32Le(); if (DoExtraErrorChecks && field3 != 2) { OnErrorDetected(string.Format("Field 3 in {0} is {1}, not 2 as expected.", spriteFilePath, field3)); } // 32-bit unsigned int - number of frames in the .img file uint numFrames = GetUnsigned32Le(); List <FrameInfo> frames = new List <FrameInfo>((int)numFrames); List <NpkByteRange> frameLocations = new List <NpkByteRange>((int)numFrames); // Next is each frame's metadata, one after the other. for (uint frameIndex = 0; frameIndex < numFrames; frameIndex++) { FrameInfo frame = ReadFrameMetadata(); frames.Add(frame); } if (DoExtraErrorChecks) { int numLinkFrames = frames.Where(f => f.LinkFrame != null).Count(); int numNonLinkFrames = frames.Count - numLinkFrames; int expectedField1Value = 36 * numNonLinkFrames + 8 * numLinkFrames; if (field1 != expectedField1Value) { OnErrorDetected(string.Format("Field 1 in {0} is {1}, not {2} as expected.", spriteFilePath, field1, expectedField1Value)); } } // Next is each non-reference frame's pixel data, one after the other. int currentFramePosition = (int)m_npkStream.Position; for (uint frameIndex = 0; frameIndex < numFrames; frameIndex++) { FrameInfo frame = frames[(int)frameIndex]; if (frame.LinkFrame != null) { // Link frames have no pixel data // Could set this to referenced frame's data to simplify code elsewhere? frameLocations.Add(new NpkByteRange(0, 0)); continue; } NpkByteRange frameByteRange; if (frame.IsCompressed) { frameByteRange = new NpkByteRange(currentFramePosition, frame.CompressedLength); } else { int length = frame.Width * frame.Height * s_formatToBytesPerPixel[frame.PixelFormat]; frameByteRange = new NpkByteRange(currentFramePosition, length); } frameLocations.Add(frameByteRange); currentFramePosition += frameByteRange.Size; // No need to seek through the pixel data normally. // Do it when doing extra error checks to verify that after the pixel data of all the frames // is either another img file or EOF. if (DoExtraErrorChecks) { Seek(frameByteRange.Size, SeekOrigin.Current); } } if (DoExtraErrorChecks) { // Check for invalid link frames for (uint frameIndex = 0; frameIndex < numFrames; frameIndex++) { FrameInfo frame = frames[(int)frameIndex]; if (frame.LinkFrame != null && (frame.LinkFrame.Value >= numFrames || frame.LinkFrame.Value < 0)) { OnErrorDetected("{0}, invalid link frame index from {1} to {2}.".F(spriteFilePath, frameIndex, frame.LinkFrame.Value)); } if (frame.LinkFrame != null) { FrameInfo linkedFrame = frames[frame.LinkFrame.Value]; if (linkedFrame.LinkFrame != null) { OnErrorDetected("{0}, link frame to a link frame, {1} to {2}.".F(spriteFilePath, frameIndex, frame.LinkFrame.Value)); } } } // Should be "Neople Img File" or EOF byte[] nextImgHeaderBuf = new byte[15]; int bytesRead = m_npkStream.Read(nextImgHeaderBuf, 0, 15); if (bytesRead == 0) { // EOF, we're ok } else if (bytesRead != 15) { OnErrorDetected(string.Format("{0}, {1} bytes read instead of 15 or 0.", spriteFilePath.Path, bytesRead)); } else { string nextImgHeader = Encoding.ASCII.GetString(nextImgHeaderBuf); if (nextImgHeader != "Neople Img File") { OnErrorDetected(string.Format("{0}, header is not Neople Img File.", spriteFilePath.Path)); } } } m_frames[spriteFilePath] = frames; m_frameLocations[spriteFilePath] = frameLocations; } catch (EndOfStreamException ex) { throw new NpkException("Unexpected end of file.", ex); } }
/// <summary> /// Helper function that loads the first part of the .npk header. m_imagefileLocations and /// m_soundFileLocations are loaded when this function completes. The stream's read pointer will /// be right after the file table part of the header after completion. This function is only /// intended to be called from LoadNpkHeader(). /// </summary> /// <exception cref="System.IO.EndOfStreamException">Unexpected end of file.</exception> /// <exception cref="System.IO.IOException">I/O error.</exception> /// <exception cref="DFO.Npk.NpkException">The file is corrupt or the format has changed.</exception> private void LoadNpkFileTable() { // file starts with "NeoplePack_Bill\0" in ASCII try { string dirListingHeader = "NeoplePack_Bill\0"; byte[] headerBuffer = new byte[dirListingHeader.Length]; m_npkStream.ReadOrDie(headerBuffer, headerBuffer.Length); string headerString = Encoding.ASCII.GetString(headerBuffer); if (!string.Equals(dirListingHeader, headerString, StringComparison.Ordinal)) { throw new NpkException("Did not find expected directory listing header."); } // Next is a 32-bit unsigned int that is the number of files packed in the .npk uint numFiles = GetUnsigned32Le(); _files = new List <NpkFileTableEntry>((int)numFiles); byte[] subNameBuffer = new byte[256]; // Next is a listing of all the files and their location inside the file. for (uint fileIndex = 0; fileIndex < numFiles; fileIndex++) { // First is a 32-bit unsigned int that is the byte offset in the .npk of where the file is located. uint absoluteLocation = GetUnsigned32Le(); // Followed by the size of the file in bytes uint size = GetUnsigned32Le(); // And then the path of the file, including the prefix indicating whether it is an image // (sprite/) or a sound (sounds/) // There are always 256 bytes to be read here. // Each byte read is XOR'ed with the corresponding byte in the key. // Then the bytes can be treated as a null-terminated ASCII string. m_npkStream.ReadOrDie(subNameBuffer, subNameBuffer.Length); for (int keyIndex = 0; keyIndex < subNameBuffer.Length; keyIndex++) { subNameBuffer[keyIndex] ^= s_key[keyIndex]; } string subNameString = Encoding.ASCII.GetString(subNameBuffer); subNameString = subNameString.TrimEnd('\0'); NpkPath pathWithPrefix = new NpkPath(subNameString); _files.Add(new NpkFileTableEntry(pathWithPrefix, new NpkByteRange((int)absoluteLocation, (int)size))); // That gives a path like sprite/character/gunner/effect/aerialdashattack.img IList <NpkPath> pathComponents = pathWithPrefix.GetPathComponents(); if (pathComponents.Count >= 1) { NpkByteRange fileLocation = new NpkByteRange((int)absoluteLocation, (int)size); if (pathComponents[0].Equals("sprite")) { m_imageFileLocations[pathWithPrefix] = fileLocation; m_imagesInFile[pathWithPrefix] = true; } else if (pathComponents[0].Equals("sounds")) { m_soundFileLocations[pathWithPrefix] = fileLocation; } else { // Not an image or a sound. Ignore it I guess, no sense throwing an exception. // Don't break any programs just because a new file type was added or something. OnErrorDetected("Something other than a sprite or sounds file at packed file index {0}: {1}".F(fileIndex, pathComponents[0])); } } else { // empty path? O_o Ignore it I guess. OnErrorDetected("Empty path at packed file index {0}.".F(fileIndex)); } } } catch (EndOfStreamException ex) { throw new NpkException("Unexpected end of file.", ex); } }
public NpkFileTableEntry(NpkPath name, NpkByteRange location) { _name = name; _location = location; }