public ImgInfo(string npkFileName, NpkPath path, int frameCount) : this() { NpkFileName = npkFileName; Path = path; FrameCount = frameCount; }
private static List<ConstAnimationFrame> GetFrameInfo(CommandLineArgs cmdLine, NpkReader npk, NpkPath imgPath) { List<ConstAnimationFrame> frameInfo = new List<ConstAnimationFrame>(); List<FrameInfo> frames = npk.Frames[imgPath].ToList(); if (cmdLine.UseAllFrames) { for (int frameIndex = 0; frameIndex < frames.Count; frameIndex++) { frameInfo.Add(new AnimationFrame() { DelayInMs = cmdLine.FrameDelayInMs, Image = new ImageIdentifier(imgPath, frameIndex) }.AsConst()); } } else { foreach (int frameIndex in cmdLine.FrameIndexes) { if (frameIndex >= frames.Count) { Console.Error.WriteLine("{0} in {1} has {2} frames in it, so frame index {3} is not valid.", imgPath, cmdLine.NpkPath, frames.Count, frameIndex); Environment.Exit(1); } frameInfo.Add(new AnimationFrame() { DelayInMs = cmdLine.FrameDelayInMs, Image = new ImageIdentifier(imgPath, frameIndex) }.AsConst()); } } return frameInfo; }
private static NpkPath GetNpkPath(string packageDir, string pkgName) { var newbePk = NugetHelper.GetLastestVersionPackage(packageDir, pkgName); var npkPath = new NpkPath(Path.Combine(packageDir, $"{newbePk.Id}.{newbePk.Version.ToFullString()}")); return(npkPath); }
/// <summary> /// Preloads frame metadata for the .img file with the given path with a leading sprite/ if present. /// If the metadata has already been loaded, does nothing. /// Metadata for an .img's frames are loaded on demand otherwise. /// </summary> /// <param name="spriteFilePath">NPK path of the .img file to preload. Must contain a leading sprite/ if present in the actual path.</param> /// <exception cref="System.IO.FileNotFoundException">There is no .img file in the NPK with the given path.</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 void PreLoadSpriteMetadata(NpkPath spriteFilePath) { NpkByteRange spriteFileLocation; if (!m_imageFileLocations.TryGetValue(spriteFilePath, out spriteFileLocation)) { throw new FileNotFoundException(string.Format("There is no .img file with path {0} in this NPK.", spriteFilePath)); } LoadSpriteFileMetaData(spriteFilePath, spriteFileLocation); }
private static NpkPath GetImgPath(CommandLineArgs cmdLine, NpkReader npk) { if (cmdLine.ImgPath != null) { NpkPath imgPath = new NpkPath(cmdLine.ImgPath); IList <NpkPath> imgPathComponents = imgPath.GetPathComponents(); if (imgPathComponents.Count >= 1 && !imgPathComponents[0].Path.Equals("sprite", StringComparison.OrdinalIgnoreCase)) { // add sprite/ prefix if present imgPath = NpkPath.Combine("sprite", imgPath); } if (!npk.Images.ContainsKey(imgPath)) { Console.Error.WriteLine("There is no img file with path {0} in NPK file {1}", cmdLine.ImgPath, cmdLine.NpkPath); Environment.Exit(1); } return(imgPath); } else { List <NpkPath> matchingPaths = new List <NpkPath>(); // Only the .img name was given. Look for it. foreach (NpkPath path in npk.Images.Keys) { if (path.GetFileName().Path.Equals(cmdLine.ImgName, StringComparison.OrdinalIgnoreCase)) { matchingPaths.Add(path); } } if (matchingPaths.Count == 1) { return(matchingPaths[0]); } else if (matchingPaths.Count == 0) { Console.Error.WriteLine("There is no img file called {0} in NPK file {1}", cmdLine.ImgName, cmdLine.NpkPath); Environment.Exit(1); return(null); // not reached } else { Console.Error.WriteLine("There are multiple img files matching the name {0} in NPK file {1}: {2}", cmdLine.ImgName, cmdLine.NpkPath, string.Join(", ", matchingPaths)); Environment.Exit(1); return(null); // not reached } } }
private static NpkPath GetImgPath(CommandLineArgs cmdLine, NpkReader npk) { if (cmdLine.ImgPath != null) { NpkPath imgPath = new NpkPath(cmdLine.ImgPath); IList<NpkPath> imgPathComponents = imgPath.GetPathComponents(); if (imgPathComponents.Count >= 1 && !imgPathComponents[0].Path.Equals("sprite", StringComparison.OrdinalIgnoreCase)) { // add sprite/ prefix if present imgPath = NpkPath.Combine("sprite", imgPath); } if (!npk.Images.ContainsKey(imgPath)) { Console.Error.WriteLine("There is no img file with path {0} in NPK file {1}", cmdLine.ImgPath, cmdLine.NpkPath); Environment.Exit(1); } return imgPath; } else { List<NpkPath> matchingPaths = new List<NpkPath>(); // Only the .img name was given. Look for it. foreach (NpkPath path in npk.Images.Keys) { if (path.GetFileName().Path.Equals(cmdLine.ImgName, StringComparison.OrdinalIgnoreCase)) { matchingPaths.Add(path); } } if (matchingPaths.Count == 1) { return matchingPaths[0]; } else if (matchingPaths.Count == 0) { Console.Error.WriteLine("There is no img file called {0} in NPK file {1}", cmdLine.ImgName, cmdLine.NpkPath); Environment.Exit(1); return null; // not reached } else { Console.Error.WriteLine("There are multiple img files matching the name {0} in NPK file {1}: {2}", cmdLine.ImgName, cmdLine.NpkPath, string.Join(", ", matchingPaths)); Environment.Exit(1); return null; // not reached } } }
static void Main(string[] args) { try { CommandLineArgs cmdLine = new CommandLineArgs(args); using (NpkReader npk = LoadNpk(cmdLine.NpkPath)) { NpkPath imgPath = GetImgPath(cmdLine, npk); RawAnimation animationData = new RawAnimation(); animationData.Loop = true; List <ConstAnimationFrame> frameInfo = GetFrameInfo(cmdLine, npk, imgPath); animationData.Frames = frameInfo; CreateOutputDir(cmdLine.OutputPath); using (FileStream gifOutputStream = OpenOutput(cmdLine.OutputPath)) using (GifMaker giffer = new GifMaker(npk, disposeImageSource: false)) { try { giffer.Create(animationData.AsConst(), gifOutputStream); } catch (Exception ex) { Console.Error.WriteLine("Error creating GIF: {0}", Utils.GetExceptionMessageWithInnerExceptions(ex)); Console.Error.WriteLine(ex.StackTrace); giffer.Dispose(); gifOutputStream.Dispose(); npk.Dispose(); Environment.Exit(1); } } } Console.WriteLine("GIF saved to {0}", cmdLine.OutputPath); } catch (OptionException ex) { Console.Error.WriteLine(ex.Message); Console.Error.WriteLine("Run with -h for help"); } catch (Exception ex) { Console.Error.WriteLine("Unexpected error: {0}", Utils.GetExceptionMessageWithInnerExceptions(ex)); Console.Error.WriteLine(ex.StackTrace); } }
public bool TryGetValue(NpkPath key, out IReadOnlyList <FrameInfo> value) { if (m_npk.Images.ContainsKey(key)) { m_npk.PreLoadSpriteMetadata(key); value = m_npk.m_frames[key]; return(true); } else { value = null; return(false); } }
/// <summary> /// Creates an animated GIF and writes it to <paramref name="outputStream"/>. /// </summary> /// <param name="animation"></param> /// <param name="outputStream"></param> public void Create(ConstRawAnimation animation, Stream outputStream) { List <Image> rawFrames = new List <Image>(); // Load each image in the animation. foreach (ConstAnimationFrame frame in animation.Frames) { NpkPath frameImagePath = frame.Image.ImageFilePath; DFO.Common.Images.Image rawFrame = m_imageSource.GetImage(frameImagePath, frame.Image.FrameIndex); rawFrames.Add(rawFrame); } int smallestX; int largestX; int smallestY; int largestY; // Frames can have different start positions and widths/heights. Normalize the images to a common coordinate system. FrameInfo.GetNormalizedCoordinates(rawFrames.Select(image => image.Attributes), out smallestX, out largestX, out smallestY, out largestY); int normalizedWidth = largestX - smallestX + 1; int normalizedHeight = largestY - smallestY + 1; List <MagickImage> renderedFrames = new List <MagickImage>(); try { // Composite each frame on top of a canvas of normalized width and height. for (int frameIndex = 0; frameIndex < animation.Frames.Count; frameIndex++) { Image rawFrameImage = rawFrames[frameIndex]; ConstAnimationFrame frameAnimationInfo = animation.Frames[frameIndex]; MagickImage renderedFrame = RenderFrame(rawFrameImage, frameAnimationInfo, smallestX, largestX, smallestY, largestY, normalizedWidth, normalizedHeight); renderedFrames.Add(renderedFrame); } // Make the GIF from the frames and write it out to the stream. using (MagickImageCollection frameCollection = new GraphicsMagick.MagickImageCollection(renderedFrames)) { frameCollection.Write(outputStream, MagickFormat.Gif); } } finally { renderedFrames.ForEach(f => f.Dispose()); } }
public IReadOnlyList <FrameInfo> this[NpkPath key] { get { try { m_npk.PreLoadSpriteMetadata(key); } catch (FileNotFoundException ex) { throw new KeyNotFoundException(ex.Message, ex); } return(m_npk.m_frames[key]); } }
/// <summary> /// Exports a frame as a PNG file. /// </summary> /// <param name="imageSource">Source of images. Normally an NPK reader, but could be also be a source that reads from /// an extraction or a mock source.</param> /// <param name="imgPath"></param> /// <param name="frameIndex"></param> /// <param name="outputStream">stream to write the PNG to</param> /// <exception cref="System.IO.FileNotFoundException">Image with the given path and frame index does not exist.</exception> public static void ToPng(IImageSource imageSource, NpkPath imgPath, int frameIndex, Stream outputStream) { Image image = imageSource.GetImage(imgPath, frameIndex); MagickReadSettings pixelDataSettings = new MagickReadSettings() { ColorSpace = ColorSpace.RGB, Width = image.Attributes.Width, Height = image.Attributes.Height, PixelStorage = new PixelStorageSettings(StorageType.Char, "RGBA") }; using (MagickImage magickImage = new MagickImage(image.PixelData, pixelDataSettings)) { magickImage.Write(outputStream, MagickFormat.Png); } }
public void TestGetNpkName() { NpkPath testPath = "Interface/Emoticon/Against.img"; Assert.That(testPath.GetImageNpkName(), Is.EqualTo("sprite_Interface_Emoticon.npk")); testPath = "sprite/Interface/Emoticon/Against.img"; Assert.That(testPath.GetImageNpkName(), Is.EqualTo("sprite_Interface_Emoticon.npk")); testPath = "equip/armor/cloth_touch.wav"; Assert.That(testPath.GetSoundNpkName(), Is.EqualTo("sounds_equip_armor.npk")); testPath = "sounds/equip/armor/cloth_touch.wav"; Assert.That(testPath.GetSoundNpkName(), Is.EqualTo("sounds_equip_armor.npk")); testPath = "/"; Assert.That(testPath.GetImageNpkName(), Is.EqualTo("sprite.npk")); Assert.That(testPath.GetSoundNpkName(), Is.EqualTo("sounds.npk")); }
private void WriteEditedNPK(FileStream tempFileStream, NpkPath imgPath, NpkFileTableEntry entryOfImgEditing, int editedFrameIndex, IReadOnlyList <FrameInfo> frameListOfImgEditing, FrameInfo frameMetadataOfFrameEditing, FrameInfo newFrameMetadata, byte[] newPixelData, int newPixelDataLength, byte[] frameMetadataBytes) { int imgByteCountChange = GetImgByteCountChange(frameMetadataOfFrameEditing, newFrameMetadata, newPixelDataLength); List <NpkByteRange> frameLocationsOfImgEditing = _reader.FrameLocations[imgPath]; // Header is same tempFileStream.Write(NpkReader.s_headerBytes, 0, NpkReader.s_headerBytes.Length); // number of files is same tempFileStream.WriteUnsigned32Le((uint)_reader.Files.Count, _intWriteBuffer); byte[] imgPathBytes = new byte[256]; // Write file table WritedEditedFileTable(tempFileStream, entryOfImgEditing, imgByteCountChange, imgPathBytes); // Write from original stream until offset of file we're changing // Write from current until currentEntry.Location.FileOffset _npkStream.Seek(tempFileStream.Position, SeekOrigin.Begin); int numBytesToCopy = (int)(entryOfImgEditing.Location.FileOffset - tempFileStream.Position); _npkStream.CopyToPartially(tempFileStream, numBytesToCopy); // Now write the new .img WriteEditedImg(tempFileStream, editedFrameIndex, frameListOfImgEditing, frameMetadataOfFrameEditing, newFrameMetadata, newPixelData, newPixelDataLength, frameMetadataBytes, frameLocationsOfImgEditing); // now write the rest of the original file that's after this .img if (entryOfImgEditing.Location.FileOffset + entryOfImgEditing.Location.Size < _npkStream.Length) { _npkStream.Seek(entryOfImgEditing.Location.FileOffset + entryOfImgEditing.Location.Size, SeekOrigin.Begin); numBytesToCopy = (int)_npkStream.Length - (entryOfImgEditing.Location.FileOffset + entryOfImgEditing.Location.Size); _npkStream.CopyToPartially(tempFileStream, numBytesToCopy); } }
/// <summary> /// Preloads frame metadata for the .img file with the given path with a leading sprite/ if present. /// If the metadata has already been loaded, does nothing. /// Metadata for an .img's frames are loaded on demand otherwise. /// </summary> /// <param name="spriteFilePath">NPK path of the .img file to preload. Must contain a leading sprite/ if present in the actual path.</param> /// <exception cref="System.IO.FileNotFoundException">There is no .img file in the NPK with the given path.</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 void PreLoadSpriteMetadata(NpkPath spriteFilePath) { ThrowIfDisposed(); ThrowIfNoFileOpen(); _reader.PreLoadSpriteMetadata(spriteFilePath); }
/// <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) { ThrowIfDisposed(); ThrowIfNoFileOpen(); return(_reader.GetImage(imgPath, frameIndex)); }
/// <summary> /// /// </summary> /// <param name="imgPath"></param> /// <param name="frameIndex"></param> /// <param name="newFrameMetadata">If this indicates a link frame, <paramref name="newFramePixels"/> is ignored. The IsCompressed flag is honored, compressing the image if it is set. The CompressedLength field is not used.</param> /// <param name="newFramePixels">Readable stream consisting solely of the pixel data, in the format indicated by the metadata.</param> public void EditFrame(NpkPath imgPath, int frameIndex, FrameInfo newFrameMetadata, Stream newFramePixels) { ThrowIfDisposed(); ThrowIfNoFileOpen(); if (!_reader.Frames.ContainsKey(imgPath)) { throw new ArgumentException("{0} is not in the NPK.".F(imgPath)); } if (frameIndex >= _reader.Frames[imgPath].Count) { throw new ArgumentException("{0} does not have a frame {1}.".F(imgPath, frameIndex)); } NpkFileTableEntry entryOfImgEditing = _reader.Files.Where(f => f.Name.Equals(imgPath)).First(); IReadOnlyList <FrameInfo> frameListOfImgEditing = _reader.Frames[imgPath]; FrameInfo frameMetadataOfFrameEditing = frameListOfImgEditing[frameIndex]; // Render the new frame in memory // pixelData is null if it's a link frame // pixelData length may be bigger than the actual pixel data. Use newPixelDataLength instead of newPixelData.Length int newPixelDataLength; byte[] newPixelData = GetPixelData(newFrameMetadata, newFramePixels, out newPixelDataLength); byte[] frameMetadataBytes = GetFrameMetadataBytes(newFrameMetadata, newPixelDataLength); string tempNpkPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".NPK"); using (FileStream tempFileStream = File.OpenWrite(tempNpkPath)) { WriteEditedNPK(tempFileStream, imgPath, entryOfImgEditing, frameIndex, frameListOfImgEditing, frameMetadataOfFrameEditing, newFrameMetadata, newPixelData, newPixelDataLength, frameMetadataBytes); } // temp file now has the new NPK! // close _reader // close _npkStream // delete original file // move temp file // TODO: "refresh" it _reader.Dispose(); _npkStream.Dispose(); // TODO: Error handling File.Delete(_openFilePath); File.Move(tempNpkPath, _openFilePath); // reopen try { _reader = new NpkReader(_openFilePath); } catch (Exception) { _openFilePath = null; _reader = null; _npkStream = null; throw; } try { _npkStream = new FileStream(_openFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (Exception) { _openFilePath = null; _reader.Dispose(); _reader = null; _npkStream = null; throw; } }
/// <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); } }
/// <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); } }
private static List <ConstAnimationFrame> GetFrameInfo(CommandLineArgs cmdLine, NpkReader npk, NpkPath imgPath) { List <ConstAnimationFrame> frameInfo = new List <ConstAnimationFrame>(); List <FrameInfo> frames = npk.Frames[imgPath].ToList(); if (cmdLine.UseAllFrames) { for (int frameIndex = 0; frameIndex < frames.Count; frameIndex++) { frameInfo.Add(new AnimationFrame() { DelayInMs = cmdLine.FrameDelayInMs, Image = new ImageIdentifier(imgPath, frameIndex) }.AsConst()); } } else { foreach (int frameIndex in cmdLine.FrameIndexes) { if (frameIndex >= frames.Count) { Console.Error.WriteLine("{0} in {1} has {2} frames in it, so frame index {3} is not valid.", imgPath, cmdLine.NpkPath, frames.Count, frameIndex); Environment.Exit(1); } frameInfo.Add(new AnimationFrame() { DelayInMs = cmdLine.FrameDelayInMs, Image = new ImageIdentifier(imgPath, frameIndex) }.AsConst()); } } return(frameInfo); }
public NpkFileTableEntry(NpkPath name, NpkByteRange location) { _name = name; _location = location; }
/// <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); } }
public bool ContainsKey(NpkPath key) { return(m_npk.Images.ContainsKey(key)); }
public ImageIdentifier(NpkPath imageFilePath, int frameIndex) { ImageFilePath = imageFilePath; FrameIndex = frameIndex; }
/// <summary> /// /// </summary> /// <param name="imgPath"></param> /// <param name="frameIndex"></param> /// <param name="pngFilePath"></param> /// <exception cref="DFOToolbox.DFOToolboxException">Something went wrong while editing. Message is suitable for UI display.</exception> /// <exception cref="System.Exception">Other errors resulting from incorrect usage of this function, such as passing null arguments or trying to edit a frame while no file is open.</exception> public void EditFrame(string imgPath, int frameIndex, string pngFilePath) { // validate that we have a file open for editing, that it has the img, that it has the frame index, that the file exists imgPath.ThrowIfNull("imgPath"); pngFilePath.ThrowIfNull("pngFileName"); if (!_editor.IsOpen) { throw new InvalidOperationException("Cannot edit a frame because no file is currently open."); } NpkPath npkPath = imgPath; if (!_editor.Frames.ContainsKey(npkPath)) { throw new KeyNotFoundException("There is no img with path {0}.".F(imgPath)); } if (frameIndex >= _editor.Frames[npkPath].Count) { throw new ArgumentOutOfRangeException("{0} does not have a frame with index {1}.".F(imgPath, frameIndex)); } FrameInfo uneditedFrameMetadata = _editor.Frames[npkPath][frameIndex]; // Use same pixel format as original. // If it's a link frame, we're turning it into a non-link frame, so use 8888 as that will preserve the colors of the new image. PixelDataFormat pixelFormatToUse = uneditedFrameMetadata.PixelFormat; if (pixelFormatToUse == PixelDataFormat.Link) { pixelFormatToUse = PixelDataFormat.EightEightEightEight; } if (!File.Exists(pngFilePath)) { throw new DFOToolboxException("{0} does not exist.".F(pngFilePath)); } byte[] newImageBytesInCorrectFormat; int newImageWidth; int newImageHeight; // Load image pixels into memory // TODO: catch exceptions using (System.Drawing.Bitmap inputImage = new System.Drawing.Bitmap(pngFilePath)) { // need to get pixels into format used by image newImageBytesInCorrectFormat = PixelConversion.Convert(inputImage, pixelFormatToUse); newImageWidth = inputImage.Width; newImageHeight = inputImage.Height; } using (MemoryStream newImageBytesInCorrectFormatStream = new MemoryStream(newImageBytesInCorrectFormat)) { FrameInfo newFrameMetadata = new FrameInfo( isCompressed: uneditedFrameMetadata.IsCompressed, compressedLength: -1, // not used pixelFormat: pixelFormatToUse, width: newImageWidth, height: newImageHeight, locationX: uneditedFrameMetadata.LocationX, locationY: uneditedFrameMetadata.LocationY, maxWidth: uneditedFrameMetadata.MaxWidth, maxHeight: uneditedFrameMetadata.MaxHeight ); _editor.EditFrame(npkPath, frameIndex, newFrameMetadata, newImageBytesInCorrectFormatStream); } // XXX: This code assumes the imgPath passed in is currently selected RefreshFrameList(); FrameList.MoveCurrentToPosition(frameIndex); }
/// <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); } }