VPP Specification by Heiko Herrmann: Difference between revisions
m (Goober moved page VPP File Specification to .vpp) |
No edit summary |
||
Line 1: | Line 1: | ||
{{Template:RF1Editing}} | |||
==VPP Specification by Heiko Herrmann== | ==VPP Specification by Heiko Herrmann== | ||
'''NOTE:''' If you're looking for the source code for the VPPBuilder32 software (which utilizes this file specification), you can find it here: [https://www.factionfiles.com/ff.php?action=file&id=517652 VPPBuilder32 Source Code]. | '''NOTE:''' If you're looking for the source code for the VPPBuilder32 software (which utilizes this file specification), you can find it here: [https://www.factionfiles.com/ff.php?action=file&id=517652 VPPBuilder32 Source Code]. |
Revision as of 18:40, 6 November 2020
VPP Specification by Heiko Herrmann
NOTE: If you're looking for the source code for the VPPBuilder32 software (which utilizes this file specification), you can find it here: VPPBuilder32 Source Code.
Introduction
VPP files are essentially file archives like *.ZIP or *.RAR files. However VPP files are not compressed. They serve to "package" all the files needed by the game (maps, interface graphics, models, textures, music and all other game data).
The VPP file format is similar but still different from the VP format, that Volition Inc. used in the FreeSpace series. The biggest differences are probably the clustering into 0x0800 blocks (see below) and the missing of a directory structure. As a minor difference you will also note that VPPs do not save timestamps for the files anymore and that filenames' maximum length has been extended from 32 to 60 characters.
Please note that the following information is unconfirmed and was hacked out by myself (Heiko Herrmann) of the Summoner Demo PC V1.0 summoner.vpp file. While it seems to be unchanged in Summoner Full Version (both PC and PlayStation), as well as all versions of Red Faction, I give absolutely no guarantee that the information on this page is correct.
File format
The file has the following structure:
//At offset 0x0000: Header int signature; //0x51890ace int version; //0x00000001 int num_files; int filesize; //Size of the complete .VPP file ...2032 unused bytes... //At offset 0x0800: Directory (64=0x0040 bytes long for each entry) for(int i=0;i<num_files;i++) { char filename[60]; int size; }
The first file starts at the next offset adress after the directory that is dividable by 0x0800. The second file starts again at the next offset adress after (offset of first file+size of first file) that is dividable by 0x0800.
Example: Summoner Demo PC 1.0 has 0x0962 files, so the directory ends at 0x00000800+0x0962*0x0040=0x00026080. So the first file starts at 0x00026800, since that is the next adress that is dividable by 0x0800. The first file is 0x00010515 bytes long, so it ends at 0x00026800+0x00010515=0x00036d15. So the second file starts at 0x00037000.
In other words, Volition uses "clusters" of 0x0800 (=2048 bytes) in Summoner for each file, the directory and even the header. The non-used space is just filled up with "00" bytes. In the worst case this means that 2047 bytes are wasted, which happens when a file is 1 or 2049 or 4097 or... bytes long. I am not sure why they do this, but my guess would be that there are performance reasons reading files in 2048-byte-blocks into memory.
Here is some example code:
//Load header f->Read(&m;_Signature,4); ASSERT(m_Signature==0x51890ace); f->Read(&m;_Version,4); ASSERT(m_Version==0x00000001); f->Read(&m;_NumFiles,4); f->Read(&m;_Filesize,4); f->Seek(_2048isize(f->GetPosition()),CFile::begin); int offset=_2048isize(2048+64*m_NumFiles); //Load directory for(int i=0;i<m_NumFiles;i++) { m_Files''.filename=ReadString(f,60); f->Read(&m;_Files''.filesize,4); m_Files''.offset=offset; offset+=_2048isize(m_Files''.filesize); //TRACE("File %s, size %i\n",m_Files''.filename,m_Files''.filesize); } (...) //Chunks int _2048isize(int x) { if(x%2048==0) return x; x-=x%2048; x+=2048; return x; } //Read a null-terminated but fixed-length string CString ReadString(CFile *f, int len) { char *t=new char[len]; f->Read(t,len); CString ret=""; BOOL _0passed=FALSE; for(int i=0;i