Thursday, October 6, 2011

Looking under the hood of the .FCW file format

First a warning – The .FCW file format is BINARY!  If you do not feel comfortable playing with bits and bytes, this may not be for you.  But if you do enjoy this type of challenge, the .FCW file format is one of the best ways to output to CC3.
Imagine you have a random maze generator and you want to output it to CC3.  You could export a script.  It would redraw your maze one line at a time.  
But there are also some problems with scripts:
  • You have to run them.  This may sound obvious but consider that you have to either know the text command to open and run a script file, or you need to know where in the menu system it is.
  • Scripts are slow.  CC3 will have to take your script and run it line-by-line.  Its as if you had set down and typed in the commands directly into CC3.
  • Scripts are not exactly fragile, but they are not very robust either.  And, if your script fails, your users are the ones that are going to get frustrated.
Where as if you exported a .FCW file, simply the action of opening the file is all that is needed. So … if you are still with me, here we go!
One last twist – the .FCW file could be compressed.
The .FCW file format is made up of many different “Blocks” of data.  The first 4 bytes of each block (except for the first block) contains the number of bytes that the following block contains.  It starts with the FileID block.  The FileID block is the only block that is guaranteed to be uncompressed.  This 128 byte block contains quite a bit of general info on the file.  It identifies what type of file it is to other programs, and the version and sub-version number of the file format is was built with.  Last, but not least, it informs the reader that bytes after byte 128 are compressed or not.  (To save an uncompressed file, after you have clicked "Save As ...", you will be presented with the save file dialog.  If you click on the options button, you will be presented with a small dialog box.  Uncheck the "File Compression" option)
For this first blog post on the .FCW file format, I will show you how to read a binary file into a byte array and how to display it, byte-by-byte in a textbox similar to all the binary editors display it.  Being able to look inside a binary file will come in very handy in the future.  Last but not least I will show you FILEID object.
Code Snippet – How to read an entire file into a byte array
  1. public byte[] ConvertFileToByteArray(string fileName)
  2. {
  3.     byte[] buffer;

  4.     using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
  5.     {
  6.         using (var binaryReader = new BinaryReader(fileStream))
  7.         {
  8.             buffer = binaryReader.ReadBytes((int)new FileInfo(fileName).Length);

  9.             binaryReader.Close();
  10.         }

  11.         fileStream.Close();
  12.     }

  13.     return buffer;
  14. }
Once the file is read into a byte array, I pull the first 128 bytes out of the file byte array, using an extension method to Arrays to create sub-arrays.
Code Snippet – SubArray extension method
  1. public static class Extensions
  2. {
  3.     public static T[] SubArray<T>(this T[] data, int index, int length)
  4.     {
  5.         var result = new T[length];

  6.         Array.Copy(data, index, result, 0, length);

  7.         return result;
  8.     }
  9. }
Then I feed the sub-array to my FILEID object.  This object converts the bytes into the fields of the FILEID object.  We can then check the Compressed property.  If the file is compressed, the rest of the data is meaningless, so if the file is compressed, I set the background color of the textbox to a Rose color.
Code Snippet
  1. using System;
  2. using System.Text;

  3. namespace DisplayBinaryFile
  4. {
  5.     public class FILEID
  6.     {
  7.         #region Fields
  8.         public char[] ProgId = new char[26];
  9.         public char[] VerText = new char[4];
  10.         public char[] VerTextS = new char[14];
  11.         public byte[] DosChars = new byte[3];
  12.         public byte DBVer;
  13.         public bool Compressed;
  14.         public byte[] Filler = new byte[78];
  15.         public byte EndFileID;
  16.         #endregion

  17.         public int Length { get; set; }

  18.         public FILEID(byte[] buffer)
  19.         {
  20.             ProgId = Encoding.ASCII.GetChars(buffer.SubArray(0, 26));
  21.             VerText = Encoding.ASCII.GetChars(buffer.SubArray(26, 4));
  22.             VerTextS = Encoding.ASCII.GetChars(buffer.SubArray(30, 14));
  23.             DosChars = buffer.SubArray(44, 3);
  24.             DBVer = buffer.SubArray(47, 1)[0];
  25.             Compressed = BitConverter.ToBoolean(buffer.SubArray(48, 1), 0);
  26.             Filler = buffer.SubArray(49, 78);
  27.             EndFileID = buffer.SubArray(127, 1)[0];

  28.             Length = 128;
  29.         }

  30.         public byte[] GetBytes()
  31.         {
  32.             var returnValue = new byte[Length];

  33.             Array.Copy(Encoding.ASCII.GetBytes(ProgId), 0, returnValue, 0, 26);
  34.             Array.Copy(Encoding.ASCII.GetBytes(VerText), 0, returnValue, 26, 4);
  35.             Array.Copy(Encoding.ASCII.GetBytes(VerTextS), 0, returnValue, 30, 14);
  36.             Array.Copy(DosChars, 0, returnValue, 44, 3);
  37.             Array.Copy(BitConverter.GetBytes(DBVer), 0, returnValue, 47, 1);
  38.             Array.Copy(BitConverter.GetBytes(Compressed), 0, returnValue, 48, 1);
  39.             Array.Copy(Filler, 0, returnValue, 49, 78);
  40.             Array.Copy(BitConverter.GetBytes(EndFileID), 0, returnValue, 127, 1);

  41.             return returnValue;
  42.         }
  43.     }
  44. }
Here is a link to the entire Visual Studio 2010 project

No comments:

Post a Comment

Post a Comment