Game Development Community

Simple package file format and unpackager

by Nick \"ShotgunNinja\" Iannone · in Torque Game Engine · 07/31/2007 (4:23 pm) · 3 replies

LPF is a file format for a simple package file that I have come up with. It's short for Liberator Package File, which is based on the name of my game development group, Liberator Studios Ltd. I came up with the idea for a package file while writing my in-game download script, which can only send one file at a time. With a package file, I could send a self-extracting group of files all in one fell swoop, and by keeping it in ASCII instead of binary, I could allow the user to easily write or construct one either in-game or in a text editor.

In LPF, individual files are separated by declaration lines. These are either stand alone, or are followed by their value (in the next line of the file). For example:

#1
07/31/2007 (4:23 pm)
:: Drive ::
C:/
:: Directory ::
Program Files/Torque/common/
:: File ::
scripts.cs
:: Options ::
1
:: Begin ::
   // The contents of the packaged file
:: End ::
:: End Package ::

And here is the decompiler code:

function UnpackPackFile(%pakfile)
{
	%this = new FileObject();
	if (%this.openForRead(%pakfile))
	{
		Echo("Pakfile opened: " @ %pakfile @ "/nCreating Write Buffer...");
		%writer = new FileObject();
		while (!%this.IsEOF())
		{
			%line = %this.readLine();
			switch$(%line)
			{
				case ":: Drive ::":
					if (!%this.IsEOF())
					{
						%drive = %this.readLine();
						%writer.drive = ExpandEscape(%drive);
						Echo("Drive: " @ %writer.drive);
					}
					else
					{
						Warn("File ended prematurely! Canceling...");
						return false;
					}
				case ":: Directory ::":
					if (!%this.IsEOF())
					{
						%dir = %this.readLine();
						%writer.dir = ExpandEscape(%dir);
						Echo("Directory: " @ %writer.dir);
					}
					else
					{
						Warn("File ended prematurely! Canceling...");
						return false;
					}
				case ":: File ::":
					if (!%this.IsEOF())
					{
						%file = %this.readLine();
						%writer.file = ExpandEscape(%file);
						Echo("Extracting file: " @ %writer.file);
						if (!ExtractFile(%this, %writer, %pakfile))
						{
							Warn("File extraction error! Canceling...");
							return false;
						}
					}
					else
					{
						Warn("File ended prematurely. Canceling...");
						return false;
					}
				case ":: Program Root Drive ::":
					%writer.drive = CollapseEscape(".//");
					Echo("Resetting drive to program's root directory...");
				case ":: Mod Path Drive ::":
					%writer.drive = CollapseEscape("~//");
					Echo("Resetting drive to current mod path...");
				case ":: End Package ::":
					Echo("Package complete.");
					%writer.isDone = true;
				default:
					%writer.blankLines++;
			}
			if (%writer.isDone)
				break;
		}
		Echo("Empty lines: " @ %writer.blankLines);
		Echo("Deleting Write Buffer...");
		%writer.delete();
	}
	else
	{
		Warn("Invalid pakfile. Canceling...");
		return false;
	}
	%this.delete();
	Echo("File unpacking complete.");
	return true;
}

function ExtractFile(%this, %writer, %pakfile)
{
	%drive = CollapseEscape(%writer.drive);
	%dir = CollapseEscape(%writer.dir);
	%file = CollapseEscape(%writer.file);
	if (!IsWriteableFileName(%drive @ %dir @ %file))
	{
		Warn("ERROR: Invalid output file name.");
		return false;
	}
	else
		%name = %drive @ %dir @ %file;
	%argline = %this.readLine();
	if (%argline $= ":: Options ::" && (!%this.IsEOF()))
	{
		%val = %this.readLine();
		switch$(%val)
		{
			case "1":
				%writer.close();
				Echo("Append:");
				%open = %writer.openForAppend(%name);
			case "2":
				%writer.close();
				Echo("Delete and Overwrite:");
				if (!DeleteFile(%name))
					Echo("File delete unsuccessful.");
				else
					Echo("File delete successful!");
				%open = %writer.openForWrite(%name);
			default:
				%writer.close();
				Echo("Overwrite:");
				%open = %writer.openForWrite(%name);
		}
		if (%open && (!%this.IsEOF()))
		{
			%argline = %this.readLine();
			if (%argline $= ":: Begin ::")
			{
				while (%line !$= ":: End ::")
				{
					%line = %this.readLine();
					if (%line !$= ":: End ::")
						%writer.writeLine(%line);
				}
				Echo("File output successful.");
				return true;
			}
		}
		else
		{
			Warn("File output interrupted! Canceling...");
			return false;
		}
	}
	else
	{
		Warn("No output mode selected! Canceling...");
		return false;
	}
}

function DeleteFile(%name)
{
	%read = new FileObject();
	%write = new FileObject();
	if (IsFile(%name) or (!IsWriteableFileName(%name)))
		return false;
	if (%read.openForRead(%name))
	{
		if (%write.openForWrite(%name))
		{
			while (!%read.IsEOF())
			{
				%read.readLine();
				%write.writeLine("");
			}
			return true;
		}
		return false;
	}
	return false;
}
#2
08/05/2007 (1:42 pm)
Sorry for the lack of explanation, I was short on time and allowed characters per post.
#3
08/05/2007 (2:16 pm)
Really good example on how to use FileObject.