The PKF Scripting Language

Table of Contents

Introduction

The purpose of the PKF command language is to specify a reliable and robust mechanism for batch processing of PKR files, the format of which is specified elsewhere. Included in this standard is a set of reference source code in C++ which can serve as the foundation for utilities that use this command language.

Overview

In the beginning, there was the THPS2 PC demo. The demo contained a large file full of data, and that file's name was called DEMO.PKR, and it was having a lot of cool stuff in it. So I reverse-engineered it and wrote UnPKR, which a lot of people downloaded and used to hack apart their demo's datafiles.

When the full game was released, the new file's name was called ALL.PKR, but UnPKR still worked on it, and people could pull all kinds of things out of the file. But they didn't have any way to put them back in. So they came to me and they said "you must make a RePKR program so we can change our PKR files". But while a RePKR was reasonably easy to write, making one easy to use was another thing altogether. So I didn't release anything, and people whined and bitched for months.

Then I posted the PKR format on the main THPS forum at Delphi. Over the next several weeks, three RePKR style programs were released by other people. The first one ran on the command line, so nobody liked it because they had to be able to read to use it. The second one was a nice happy graphical interface for Windows, and people liked it because it was easier to use, but it couldn't put files into the PKR unless a file with the same name was already there and the new data was no larger than the existing data. The third one could put larger files into the PKR, but it was a command line program and nobody liked it any better than the first one even if it was technically superior.

Now, I don't mean to sound arrogant here, but I'm really disappointed. I wanted to watch the community solve three major problems that I could see in the RePKR idea:

  1. Packaging modified files *only* from a PKR into some commonly supported format. The existing process is the same as it's always been: manually compress the proper files into a ZIP file, naming them according to the internal structure of the PKR file.
  2. Inserting modified files in that format into the PKR easily and quickly. The existing process is barely tolerable: individually import each desired file into the PKR file, using the system filenames to indicate which files they should replace.
  3. Automating the process of packaging files so mod authors could update their distribution files easily and quickly. Absolutely NOTHING has been done toward this goal. Until now, that is.

There's nothing flashy here for you to download yet. I'm more concerned with addressing the need for those three problems above to be solved. Toward this end, I have designed the PKF command language and stated some simple rules for mod distribution files.

The rules are very simple indeed. A PKR file can contain fewer files than the ALL.PKR in THPS2 came with, as clearly evidenced by DEMO.PKR in the demo. All we need to do is agree on a file extension standard: when a PKR file is complete, containing everything you need to rename it ALL.PKR and use it in THPS2, the file is of course named with a PKR extension. When the file is INCOMPLETE, and must be merged with a complete file to produce a playable data set, the file is named with the PKD extension.

Given that, we need only examine the PKR/PKD file format and define a series of commands that can be placed in a script file and used by mod developers to create distribution files automatically. The same command scripting language can be used to combine these distribution files with the ALL.PKR file for use in the game. So without further ado, let's look at the file format.

The PKR/PKD File Format

A PKR or PKD file is a composite file consisting of zero or more directories, each of which contains zero or more files. Information about these is contained in a 16-byte header at the top of all PKR and PKD files.

This information is followed by a series of 40-byte directory entries. The number of these entries is specified in the third field of the header, and each contains the following information:

This information is followed by a series of 48-byte file entries. The number of these entries is specified in the fourth field of the header, and each contains the following information:

From the three structures above, we can determine a few basic assumptions about the PKR/PKD file format which always hold true if the file is properly written.

Some basic C structures to represent this data are as follows:

	typedef struct PK_HEAD_STRUC
	{
		char ident[4];
		int unknown;	// always 1
		int num_dirs;
		int num_files;
	}
	PK_HEAD;
	
	typedef struct PK_DIR_STRUC
	{
		char path[32];
		int file_offset;
		int file_count;
	}
	PK_DIR;
	
	typedef struct PK_FILE_STRUC
	{
		char name[32];
		int unknown; 	// always -2
		int data_offset;
		int data_len1;
		int data_len2; 	// always == data_len2
	}
	PK_FILE;
	
	typedef struct PK_INDEX_STRUC
	{
		PK_HEAD head;
		PK_DIR *dir;
		PK_FILE *file;
	}
	PK_INDEX;

A useful C++ source file which implements several useful PKR/PKD functions as a class interface will be available in the near future. As of February 17, 2001 this class interface is entering the debugging and testing phase.

Command Reference

Given the reasonable simplicity of the format, only a few commands are really needed. After all, the only thing we ever need to say to build the PKR/PKD file is what to call the data in the index, where to get the data that goes into the file in the first place, and whether this file should be included in PKD builds. That could be handled with one simple command, in a pinch, but we're trying to make this easier -- so we'll use seven. (Trust me, it really is easier.)

PKF

The PKF command specifies the version number of the PKF file. It must be the first line in the file, without preceding comments or blank lines.

Syntax: PKF {version}

{version}
A series of four decimal digits uniquely identifying the version of this standard used to produce the file initially. These digits are not separated from one another in any way, and appear as MMmm where MM indicates the major version and mm indicates the minor version. A leading or trailing zero is added when either the major or minor version consists of only a single digit, so version 1.0 of the standard is written as 0100 and version 1.1 is written 0110.

It is considered appropriate (though not, strictly speaking, part of the standard) to follow the PKF command with a comment identifying the generating application. The format of this comment is unspecified.

ONFAIL

The ONFAIL command specifies the proper action to be taken when a command fails.

Syntax: ONFAIL {action}

{action}
One of the following case-insensitive actions.

The ONFAIL activity actually takes place in only one circumstance with version 1.0 of the command language, and that circumstance is that a specified source path in the PKF file was not found. This may be extended to an ONERROR {error} {action} syntax in future versions, if the extensions warrant more complex error handling.

IMPORT

The IMPORT command specifies entries to be saved in PKR files, but not PKD files.

Syntax: IMPORT {name},{path}

{name}
The entry to place in the final file. All {name} parameters share the same syntax, consisting of one or more subdirectories delimited by the slash character / (ASCII 47), and a filename. The maximum length of the filename is 31 characters, and the maximum length of all subdirectories is also 31 characters. This string can therefore never be more than 62 characters long. The string is enclosed in double quote " characters (ASCII 34), and is case sensitive, so "AUDIO" is not the same as "audio". Most notably, there is no initial / character in the name: "/audio/" is an illegal {name} parameter. To specify the top-level or root directory of the layout, use a pair of double quotes with no text between them: "".

{path}
The path to the data for this entry. The path is an absolute filesystem path enclosed within double quotes, including the drive, all subdirectories (delimited by \ characters and NOT / characters), and the name of the file on the local drive. If the local file indicated is a PKR or PKD file, individual data files within it may be specified by adding a | character (ASCII 124) and appending the {name} identifying the appropriate data file. For example, to load audio/BONELESS09.WAV from the all.pkr file on drive C in the \THPSPC directory, you would use a {path} parameter of "C:\THPSPC\all.pkr|audio/BONELESS09.WAV".

Both the {name} and {path} parameters can be "wildcarded" by leaving off the final filename on both parameters to execute the command on all files and subdirectories of the specified path. If the name is left off on only one parameter, a matching name is added to the other parameter. Examples:

EXPORT

The EXPORT command specifies entries to be saved in both PKR and PKD files. It is primarily useful when entire directories are imported and only a few files within those directories are desired in PKD files.

Syntax: EXPORT {name}

{name}
The entry to export to the final file. See the {name} parameter under IMPORT.

INCLUDE

The INCLUDE command specifies entries to be saved in both PKR and PKD files. It is equivalent to executing both an IMPORT and EXPORT command on the same {name} parameter.

Syntax: INCLUDE {name},{path}

{name}
The entry to place in the final file. See the {name} parameter under IMPORT.

{path}
The path to the data for this entry. See the {path} parameter under IMPORT.

EXCLUDE

The EXCLUDE command specifies entries to be saved in PKR files, but not PKD files. Much like the EXPORT command, this is primarily used to fine-tune wildcarded INCLUDE statements. Executing an INCLUDE and EXCLUDE on the same {name} parameter is equivalent to the IMPORT command.

Syntax: EXCLUDE {name}

{name}
The entry to exclude from the final file. See the {name} parameter under IMPORT.

REMOVE

REMOVE removes entries from the PKR layout, once again for fine-tuning wildcard imports and exports. Situations may arise in which your imported or included directories have contents you would like to leave out of saved files completely -- subdirectories or archives with backups of previous versions, for example.

Syntax: REMOVE {name}

{name}
The entry to remove in the final file. See the {name} parameter under IMPORT.

Version History

February 16, 2001: Initial Release, 1.0

Credits