PAK Format (Westwood)
Westwood's PAK Format is a simple group file used in their games from 1991-1994. It is efficient in the sense that it only stores file names and starting positions in the header, so file lengths must be inferred. It does not support compression. There are three different versions of the header layouts which are nearly identical and backward-compatible with the previous version. Each update makes it slightly easier to read the header.
- Version 1: Eye of the Beholder 1
- Version 2: Dune II, Legend of Kyrandia 1
- Version 3: Legend of Kyrandia 2, Lands of Lore 1, Legend of Kyrandia 3
PAK files have no signature. They are usually given the extension .PAK, but not always.
The header contains a list of files consisting of a 32-bit file offset and a null-terminating file name. Depending on the version, the header ends either when the file pointer equals the offset of the first file (version 1), or when you encounter a file offset of 0 (versions 2 and 3).
|char cFilename||File name (8.3 style), null terminated, variable width.|
|UINT32LE iOffset||File's starting offset in the PAK archive.|
Each file's size must be calculated by subtracting it from the next file's offset. Version 1 PAK files have a final file offset equal to the size of the file. Version 2 PAK files replace this offset with a end of header flag (0x0000), so the end of the last file must be inferred from the size of the file itself. Version 3 PAK files have an additional file offset with an empty file name (just a null) to determine the size of the last file and then feature a end of header flag (0x0000). Because of this, version 2 PAK files cannot contain hidden files after the last file.
This FreeBASIC code will extract all of the files from a version 1, 2, or 3 PAK file into the folder you specify.
' This program will extract the files stored in a Westwood PAK file used in various games in the first half of the 1990s. ' There are three versions of the PAK file. Version 1 was used only in Eye of the Beholder 1. To determine the ' end of the header, just wait until the file pointer equals the position of the first file. The last file offset is the ' length of the file. Version 2 was first used in Dune II. It's header ends when the file offset is zero (0x0000). ' Version 3 was first used in The Legend of Kyrandia 2. It has an additional empty file name with a file offset so you ' can easily determine the size of the last file rather than relying on the length of the file, like in version 2. Dim As String PAKFile = "C:\\Games\\Kyrandia\\dat.pak" Dim As String ExportFolder = "C:\\Games\\Kyrandia\\dat" Open PAKFile For Binary As #1 Dim As UInteger FileStart(1000) Dim As String FileName(1000) Dim As UInteger Position Dim As String Char Dim As UInteger FileCount = 0 Dim As UInteger FileNo Dim As ULong FileSize Dim As UInteger ByteNo ' Loop through the header. Do ' 4-byte file start position. Get #1, , FileStart(FileCount) Position = Seek(1) ' Trap for version 2 and 3 PAK files. If FileStart(FileCount) = 0 Then Exit Do Else ' Trap for version 1 PAK files. If (Position - 1) = FileStart(0) Then FileCount = FileCount + 1 Exit Do Else ' Read the file name until we hit a null. FileName(FileCount) = "" Do Char = " " Get #1, , Char If Asc(Char) <> 0 Then FileName(FileCount) = FileName(FileCount) + Char End If Loop While Asc(Char) <> 0 FileCount = FileCount + 1 End If End If Loop FileCount = FileCount - 1 MKDir(ExportFolder) For FileNo = 0 To FileCount ' Read the previous file from the PAK. ' Get the file size. If FileNo = FileCount Then ' Trap for version 1 and 2 PAK files. FileSize = LoF(1) - FileStart(FileNo) Else FileSize = FileStart(FileNo + 1) - FileStart(FileNo) End If Print Using "###) File: \\ \\ Offset: ########,, Size: ########,"; FileNo; FileName(FileNo); FileStart(FileNo); FileSize ' Trap for version 3 PAK files. If FileSize > 0 Then ' Create a buffer to store the next file. ReDim As Byte FileData(0 To FileSize) ' Load the file from the PAK into the buffer. Seek 1, FileStart(FileNo) + 1 For ByteNo = 0 To (FileSize - 1) Get #1, , FileData(ByteNo) Next ByteNo ' Save the buffer to the export folder. Open ExportFolder + "\\" + FileName(FileNo) For Binary As #2 For ByteNo = 0 To (FileSize - 1) Put #2, , FileData(ByteNo) Next ByteNo Close #2 End If Next FileNo Close #1 Sleep
This archive format was reverse engineered by TheAlmightyGuru. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)