Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
100 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

The File Table

The file table will be a linked list of the following data structure, sFileTableEntry, which is outlined below. Each entry will be descriptive of any one file in the PAK.

struct sFileTableEntry								
{
  char   szFileName[30];
  DWORD  dwFileSize;
  DWORD  dwOffset;
  sFileTableEntry* Next;
	
  // Constructor
  sFileTableEntry()
  {
    ZeroMemory( szFileName, sizeof(szFileName) );
    dwFileSize	= 0;
    dwOffset	= 0;
    Next		= NULL;
  }

  // Deconstructor
  ~sFileTableEntry()
  {
    ZeroMemory( szFileName, sizeof(szFileName) );
    dwFileSize	= 0;
    dwOffset	= 0;
    delete Next;
  }
};

The offset value will be the first byte of the particular file within the PAK archive. The file name, size and link to another entry are also included.

The Create Method - Part 1

The functions (including private ones) are quite large so I'll leave it in the source for you to look at and just describe them here. I feel that they are well commented though and that you should have no problems keeping up with them.

The Create() method really starts when it calls the function GenerateHFT(). GenerateHFT starts by filling in the header structure. It adds the signature, version number etc and some random results for the cipher value, unique ID and cipher direction (add or subtract). It then looks at the specified compilation directory (a parameter for Create() ) and parses it file by file. With each file that is found in the directory, it creates a new sFileTableEntry() node, fills in the filename and file size variables (with a default offset value) and adds it on to the linked list file-table. With each file found a counter is incremented. When this process is finished, the dwNumFTEntries variable of the header structure is assimilated with this counter.

The next stage of Create() is the calling of the WorkOutOffsets function. The very first file offset is calculated like this:

dwOffset = sizeof(sPakHeader) + (m_Header.dwNumFTEntries *
                                 sizeof(sFileTableEntry));

The head entry of the file-table will take this to be the value of its offset member variable. Then the size of the file (already calculated for each entry by GenerateHFT() ) is added to the offset. It is then a case of iterating through the other file table entries and taking the offset value for its member variable and then adding on the particular file sizes. It is much easier to see in the code than it is to describe here!

The Create Method - Part 2

At this stage the header and file table for the PAK are completely filled in. Now we open a file stream, using the second supplied parameter for Create(), and write an unencrypted header.

To write the encrypted file table we need to iterate through the linked list file-table one entry at a time (using a local copy of a file table entry called Current). Once we have checked that we are not on the dummy entry, we create a BYTE array the same size as sFileTableEntry like this:

BYTE* Ptr = NULL;
Ptr = new BYTE [sizeof(sFileTableEntry)];

We then copy the current file table entry in to this BYTE array as follows:

memcpy( Ptr, Current, sizeof(sFileTableEntry) );

We then iterate through each BYTE in this array, encrypt it and write it out to the PAK file. The code for this is:

for( int i = 0; i < sizeof(sFileTableEntry); i++ )
{
  // Temporary BYTE variable
  BYTE Temp = 0;

  // Make equal to the relevant byte of the FT entry
  Temp = Ptr[i];
  
  // Encrypt BYTE according to the Caesar cipher
  if( m_Header.bCipherAddition == TRUE )
    Temp += m_Header.iCypherValue;
  else
    Temp -= m_Header.iCypherValue;
  
  // Write the FT encrypted BYTE value
  fwrite( &Temp, sizeof(BYTE), 1, PAKStream );
}

Once this is done the file stream is closed and the Current variables is set to the head of the linked list file-table again. What we do now is open two file streams. One is for writing to the PAK file and one for reading in each file to be added. These will be used in conjunction with each other.

We set the position in the PAK file (for writing) according to the dwOffset value stored in the current file entries member variable. We then read in a BYTE at a time from the input stream (which was opened using the szFilename variable of the current file entry), encrypt it using the Caesar cipher as before and output it in to the PAK file using the write stream. This is, again, demonstrated in the attached source code.

Conclusion

That's it for just now. A public method could easily be added that loads the header from an existing PAK file and, using that data decrypt and load the contained file-table. With that information you could easily, say, decrypt and extract files to a specified directory and dynamically load them. The way I envision using it would be to dynamically create BYTE arrays within my program the same size as the file I want to utilise and copy the data from the PAK (unencrypted) in to this array. I could then use "load from memory" functions (like D3DXLoadMeshFromXInMemory() or D3DXCreateTextureFromFileInMemory() ) to work with the data. LUA scripts would be perfect here to tell me which files I need to load and, as an example, you could load all the files you need using this method "between levels". I could always write this load function later on if anybody desires it. I hope you find this useful and that it can be a source of inspiration for your own projects.


Contents
  The Format
  The File Table

  Source code
  Printable version
  Discuss this article