Recently, I came across a situation where I need to extract the memo field data from an FPT file. After lot of googling I found this
documentation which describes the FPT structure and then I decided to write my own code to parse the FPT file and extract the data stored in it.
A memo file (FPT) file contains one header and any number of block structures. The header part is 512 bytes long but the useful information is stored in 7th and 8th byte which is the size of the block structure.
Following the header is the first memo block. A memo block consists of a memo block header and memo text. Memo block is 8 bytes long and stores the type of memo data and the length of the memo data.
A memo block can span over multiple blocks structures but a single block can’t have data of more than one memo block. It means if block is larger than the memo size then remaining block will be unused.
Here is the code to extract memo data.
public class MemoBlock
{
public MemoDataType MemoDataType { get; private set; }
public Byte[] Memo { get; private set; }
public MemoBlock(MemoDataType type, Byte[] data)
{
this.MemoDataType = type;
this.Memo = data;
}
}
public enum MemoDataType
{
Picture = 0
, Text = 1
}
public class MemoCollection : IEnumerable<MemoBlock>
{
private System.IO.Stream Stream = null;
private int blockSize = 0;
public MemoCollection(System.IO.Stream memoStream)
{
this.Stream = memoStream;
}
#region IEnumerable<MemoBlock> Members
public IEnumerator<MemoBlock> GetEnumerator()
{
return GetCollection();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private IEnumerator<MemoBlock> GetCollection()
{
Stream.Seek(0, SeekOrigin.Begin);
GetBlockSize();
while (Stream.Position < Stream.Length)
{
var memo = ReadMemo();
PositionToNextBlock();
yield return memo;
}
}
private void PositionToNextBlock()
{
int unusedbytecount = (int)(Stream.Position % blockSize);
if (unusedbytecount > 0)
Stream.Seek(blockSize - unusedbytecount, SeekOrigin.Current);
}
private MemoBlock ReadMemo()
{
Byte[] header = new Byte[8];
Stream.Read(header, 0, header.Length);
MemoDataType type = (MemoDataType)ToInteger(header.Take(4).ToArray());
int memoSize = ToInteger(header.Skip(4).Take(4).ToArray());
Byte[] memoData = new Byte[memoSize];
Stream.Read(memoData, 0, memoData.Length);
return new MemoBlock(type, memoData);
}
private void GetBlockSize()
{
Byte[] buffer = new Byte[512];
Stream.Read(buffer, 0, buffer.Length);
blockSize = ToInteger(new Byte[] { 0,0,buffer[6],buffer[7]});
}
private int ToInteger(byte[] data)
{
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt32(data,0);
}
#endregion
}