/// <summary> /// Parse a flipnote's raw bytes /// </summary> /// <param name="bytes">Raw Flipnote Bytes</param> public void Parse(byte[] bytes) { var br = new BinaryReader(new MemoryStream(bytes)); if (!br.ReadChars(4).SequenceEqual(FileMagic)) { throw new Exception("Unexpected file format"); } AnimationDataSize = br.ReadUInt32(); SoundDataSize = br.ReadUInt32(); FrameCount = (ushort)(br.ReadUInt16() + 1); FormatVersion = br.ReadUInt16(); IsLocked = br.ReadUInt16() != 0; ThumbnailFrameIndex = br.ReadUInt16(); string rootname = br.ReadWChars(11); string parentname = br.ReadWChars(11); string currentname = br.ReadWChars(11); ulong parentid = br.ReadUInt64(); ulong currentid = br.ReadUInt64(); ParentFilename = br.ReadPPMFilename(); CurrentFilename = br.ReadPPMFilename(); ulong rootid = br.ReadUInt64(); RootAuthor = new PPMAuthor(rootname, rootid); ParentAuthor = new PPMAuthor(parentname, parentid); CurrentAuthor = new PPMAuthor(currentname, currentid); RootFileFragment = br.ReadPPMFileFragment(); Timestamp = br.ReadPPMTimestamp(); br.ReadUInt16(); // 0x9E Thumbnail = br.ReadPPMThumbnail(); FrameOffsetTableSize = br.ReadUInt16(); br.ReadUInt32(); //0x6A2 - always 0 AnimationFlags = br.ReadUInt16(); var oCnt = FrameOffsetTableSize / 4.0 - 1; _animationOffset = new uint[(int)oCnt + 1]; Frames = new PPMLib.PPMFrame[FrameCount]; for (var x = 0; x <= oCnt; x++) { _animationOffset[x] = br.ReadUInt32(); } long framePos0 = br.BaseStream.Position; var offset = framePos0; //&H6A8 + FrameOffsetTableSize for (var x = 0; x <= oCnt; x++) { if (offset + _animationOffset[x] == 4288480943) { throw new Exception("Data corrupted (possible memory pit?)"); } br.BaseStream.Seek(offset + _animationOffset[x], SeekOrigin.Begin); Frames[x] = br.ReadPPMFrame(); Frames[x].AnimationIndex = Array.IndexOf(_animationOffset, _animationOffset[x]); if (x > 0) { Frames[x].Overwrite(Frames[x - 1]); } } // Read Sound Data if (SoundDataSize == 0) { return; } offset = 0x6A0 + AnimationDataSize; br.BaseStream.Seek(offset, SeekOrigin.Begin); SoundEffectFlags = new byte[Frames.Length]; Audio = new PPMAudio(); for (int i = 0; i < Frames.Length; i++) { SoundEffectFlags[i] = br.ReadByte(); } offset += Frames.Length; // make the next offset dividable by 4 br.ReadBytes((int)((4 - offset % 4) % 4)); Audio.SoundData = new _SoundData(); Audio.SoundHeader = new _SoundHeader(); Audio.SoundHeader.BGMTrackSize = br.ReadUInt32(); Audio.SoundHeader.SE1TrackSize = br.ReadUInt32(); Audio.SoundHeader.SE2TrackSize = br.ReadUInt32(); Audio.SoundHeader.SE3TrackSize = br.ReadUInt32(); Audio.SoundHeader.CurrentFramespeed = (byte)(8 - br.ReadByte()); Audio.SoundHeader.RecordingBGMFramespeed = (byte)(8 - br.ReadByte()); // Framerate = PPM_FRAMERATES[Audio.SoundHeader.CurrentFramespeed]; BGMRate = PPM_FRAMERATES[Audio.SoundHeader.RecordingBGMFramespeed]; br.ReadBytes(14); Audio.SoundData.RawBGM = br.ReadBytes((int)Audio.SoundHeader.BGMTrackSize); Audio.SoundData.RawSE1 = br.ReadBytes((int)Audio.SoundHeader.SE1TrackSize); Audio.SoundData.RawSE2 = br.ReadBytes((int)Audio.SoundHeader.SE2TrackSize); Audio.SoundData.RawSE3 = br.ReadBytes((int)Audio.SoundHeader.SE3TrackSize); // Read Signature (Will implement later) if (br.BaseStream.Position == br.BaseStream.Length) { // file is RSA unsigned -> do something... } else { // Next 0x80 bytes = RSA-1024 SHA-1 signature Signature = br.ReadBytes(0x80); var padding = br.ReadBytes(0x10); // Next 0x10 bytes are filled with 0 } }
public static PPMFile Create(PPMAuthor author, List <PPMFrame> frames, byte[] audio, bool ignoreMetadata = false) { var file = new PPMFile(); file.FrameCount = (ushort)(frames.Count - 1); file.FormatVersion = 0x24; if (!ignoreMetadata) { file.RootAuthor = author; file.ParentAuthor = author; file.CurrentAuthor = author; string mac6 = string.Join("", BitConverter.GetBytes(author.Id).Take(3).Reverse().Select(t => t.ToString("X2"))); var asm = Assembly.GetEntryAssembly().GetName().Version; var dt = DateTime.UtcNow; var fnVM = ((byte)asm.Major).ToString("X2"); var fnVm = ((byte)asm.Minor).ToString("X2"); var fnYY = (byte)(dt.Year - 2009); var fnMD = dt.Month * 32 + dt.Day; var fnTi = (((dt.Hour * 3600 + dt.Minute * 60 + dt.Second) % 4096) >> 1) + (fnMD > 255 ? 1 : 0); fnMD = (byte)fnMD; var fnYMD = (fnYY << 9) + fnMD; var H6_9 = fnYMD.ToString("X4"); var H89 = ((byte)fnMD).ToString("X2"); var HABC = fnTi.ToString("X3"); string _13str = $"80{fnVM}{fnVm}{H6_9}{HABC}"; string nEdited = 0.ToString().PadLeft(3, '0'); var filename = $"{mac6}_{_13str}_{nEdited}.ppm"; var rawfn = new byte[18]; for (int i = 0; i < 3; i++) { rawfn[i] = byte.Parse("" + mac6[2 * i] + mac6[2 * i + 1], System.Globalization.NumberStyles.HexNumber); } for (int i = 3; i < 16; i++) { rawfn[i] = (byte)_13str[i - 3]; } rawfn[16] = rawfn[17] = 0; file.ParentFilename = new PPMFilename(rawfn); file.CurrentFilename = new PPMFilename(rawfn); var ByteRootFileFragment = new byte[8]; for (int i = 0; i < 3; i++) { ByteRootFileFragment[i] = byte.Parse("" + mac6[2 * i] + mac6[2 * i + 1], System.Globalization.NumberStyles.HexNumber); } for (int i = 3; i < 8; i++) { ByteRootFileFragment[i] = (byte)((byte.Parse("" + _13str[2 * (i - 3)], System.Globalization.NumberStyles.HexNumber) << 4) + byte.Parse("" + _13str[2 * (i - 3) + 1], System.Globalization.NumberStyles.HexNumber)); } file.RootFileFragment = new PPMFileFragment(ByteRootFileFragment); file.Timestamp = new PPMTimestamp((uint)((dt - new DateTime(2000, 1, 1, 0, 0, 0)).TotalSeconds)); file.Thumbnail = new PPMThumbnail(new byte[0x600]); file.ThumbnailFrameIndex = 0; } // write the audio data uint animDataSize = (uint)(8 + 4 * frames.Count); file.AnimationFlags = 0x43; file.FrameOffsetTableSize = (ushort)(4 * frames.Count); file.Frames = new PPMFrame[frames.Count]; //SoundEffectFlags = new byte[frames.Count]; for (int i = 0; i < frames.Count; i++) { file.Frames[i] = frames[i]; animDataSize += (uint)file.Frames[i].ToByteArray().Length; } while ((animDataSize & 0x3) != 0) { animDataSize++; } file.AnimationDataSize = animDataSize; file.Audio = new PPMAudio(); file.Audio.SoundData.RawBGM = audio; file.Audio.SoundHeader.BGMTrackSize = (uint)file.Audio.SoundData.RawBGM.Length; file.Audio.SoundHeader.SE1TrackSize = 0; file.Audio.SoundHeader.SE2TrackSize = 0; file.Audio.SoundHeader.SE2TrackSize = 0; file.SoundDataSize = (uint)file.Audio.SoundData.RawBGM.Length; file.Audio.SoundHeader.CurrentFramespeed = 0; file.Audio.SoundHeader.RecordingBGMFramespeed = 0; return(file); }