Supports reading, editing, creating pak files. Simple access. Alpha sorts directory entries. Has a "fast edit" mode (appends new data, just removes directory entries of deleted files.)
Should you care? Not really. I can't imagine anything really needing this. Except something odd I'm making to auto-convert and palettize pak and wad textures from separate folders and do it to separate WADs and PAKs based on the folder name and do it fast.
Code: Select all
//**********************************************************************************************************\
//
// archive_pak.c: Pak files
//
//**********************************************************************************************************/
#include "project.h"
#define MAX_PAK_FILES_IS_4096 4096
#define MAX_PAK_FILENAME_IS_55 55
// Pak file structure
// 1. Header
// 2. File bytes one after another
// 3. Directory
typedef struct
{
char id[4];
int directoryOffset;
int directoryLength;
} packheader_t;
typedef struct
{
char name[56];
int filepos;
int filelen;
} packentry_t;
typedef struct pak_access_s
{
packheader_t header;
packentry_t directory[MAX_PAK_FILES_IS_4096];
char fileName[MAX_PATH_IS_1024];
FILE* fileHandle;
int fileMode; // ARCHIVE_CREATE, ARCHIVE_EDIT, ARCHIVE_READ
fbool isDirty; // To avoid re-saving a Pack_File_Edit where nothing of substance was done
int totalEntries;
int dataSegmentSize;
} pak_access_t;
const char* accessCodes[] =
{
"wb", // ARCHIVE_CREATE 0
"rb", // ARCHIVE_READ 1
"r+b", // ARCHIVE_EDIT 2
"r+b", // ARCHIVE_FASTEDIT 3
};
#define OPEN_FILE_1 1
#define PAK_CHECK_2 2
#define DIR_SIZE_3 3
const char* pak_open_error[] =
{
"Unknown",
"Couldn't open file",
"Not a pak file",
"Too many files, 4096 max",
};
#define CHECK_MODE_1 1
#define CHECK_LENGTH_2 2
#define CHECK_NUM_FILES_3 3
#define CHECK_APPEND_4 4
const char* pak_add_error[] =
{
"Unknown",
"Pak mode is read-only, cannot write",
"Filename for pak directory exceeds 55 characters",
"Too many files in pak, 4096 max",
"Couldn't append data from file to pak",
};
//
// SUPPORTING FUNCTIONS
//
static fbool PakFile_Close_Directory_AlphaSort (pak_access_t* myPack)
{
fbool didWeSort = False;
if (myPack->fileMode == ARCHIVE_READ || myPack->totalEntries == 0)
return False; // Read can't write or crazy situation with no entries
didWeSort = Sort_Memory_Blocks_By_Name ((char*)myPack->directory, sizeof(myPack->directory[0]), myPack->totalEntries, 0);
printf ("Did we alpha sort directory? %s ...\n", didWeSort ? "Yes" : "No" );
return didWeSort; // We might have sorted. We might have not needed to.
}
static fbool PakFile_AlphaSort_Test (pak_access_t* myPack)
{
fbool didWeSort = False;
if (myPack->totalEntries == 0)
return False; // Read can't write or crazy situation with no entries
didWeSort = Sort_Memory_Blocks_By_Name ((char*)myPack->directory, sizeof(myPack->directory[0]), myPack->totalEntries, 0);
return didWeSort; // We might have sorted. We might have not needed to.
}
// Look through the pack and see if there is empty space
static fbool PakFile_Close_Check_For_Empty_Space (pak_access_t* myPack)
{
// Assumes cursor is at the right place. Create, Edit, FastEdit this is always true. Read? Well, make it so.
int directoryStart = File_GetCursor (myPack->fileHandle);
int expectedDataSize = directoryStart - sizeof(myPack->header);
int foundSize = 0;
fbool isEmptySpace = False;
int i;
if (myPack->fileMode == ARCHIVE_READ && directoryStart != myPack->header.directoryOffset)
printf ("Hey chief, the cursor into the file seems to be in wrong place");
for (i = 0; i < myPack->totalEntries; i ++)
foundSize += myPack->directory[i].filelen;
if (foundSize != expectedDataSize)
isEmptySpace = True;
// printf ("Needs compressed? %s ...\n", isEmptySpace ? "Yes" : "No" );
return isEmptySpace;
}
// Removes directory entries of deleted files
static fbool PakFile_Close_Directory_Fixup (pak_access_t* myPack)
{
fbool didAnything = False;
int newNumEntries = 0;
int i, j;
if (myPack->fileMode == ARCHIVE_CREATE || myPack->fileMode == ARCHIVE_READ)
return False; // "Create" doesn't support delete and read doesn't write at all
// Determine actual number of entries
for (i = 0; i < myPack->totalEntries; i ++)
if (myPack->directory[i].name[0])
newNumEntries ++;
// Recover space moving entries
for (i = 0; i < newNumEntries; i ++)
if (!myPack->directory[i].name[0]) // Is blank entry?
for (j = i + 1; i < myPack->totalEntries; i ++) // // Find a non-blank entry and move it in there
if (myPack->directory[i].name[0])
{
// Relocate and erase
memcpy (&myPack->directory[i], &myPack->directory[j], sizeof(myPack->directory[i]) );
memset (&myPack->directory[j], 0, sizeof(myPack->directory[j]) );
didAnything = True;
break; // Get out
}
myPack->totalEntries = newNumEntries;
printf ("Did we clean up deleted files? %s ...\n", didAnything ? "Yes" : "No" );
return didAnything;
}
// Make a copy without the extra space (where deleted data lives)
static void PakFile_Close_Compress (pak_access_t* myPack)
{
const char* tempFileName = "temp.tmp";
FILE* fout = fopen (tempFileName, "wb"); // Write-only
int cursor = 0;
int oldsize = myPack->header.directoryOffset - sizeof(myPack->header);
int i, newsize, recoveredsize;
printf ("Compressing ...\n");
if (!fout)
{
printf ("Space recover: Couldn't open temp file for writing\n"); // Let's see if this happens?
return; // This can't be a fatal error. Otherwise we lose additions, deletions and such.
}
cursor += File_Safe_Write (fout, &myPack->header, sizeof(myPack->header) ); // Just to keep things easy
for (i = 0; i < myPack->totalEntries; i ++)
{
File_Append_FileBlock (fout, myPack->fileHandle, myPack->directory[i].filepos, myPack->directory[i].filelen);
myPack->directory[i].filepos = cursor; // Update the file position
cursor += myPack->directory[i].filelen;
}
// Write temp file directory and re-write the temp file header after refreshing it.
{
int directoryStart = File_GetCursor (fout);
int directoryByteLength = sizeof(myPack->directory[0]) * myPack->totalEntries;
newsize = directoryStart - sizeof(myPack->header);
recoveredsize = oldsize - newsize;
myPack->header.id[0] = 'P'; // Construct final header
myPack->header.id[1] = 'A';
myPack->header.id[2] = 'C';
myPack->header.id[3] = 'K';
myPack->header.directoryOffset = LittleLong(directoryStart);
myPack->header.directoryLength = LittleLong(directoryByteLength);
File_Safe_Write (fout, &myPack->directory, directoryByteLength); // Write directory
File_SetCursor_ToStart (fout); // Return to beginning of file
File_Safe_Write (fout, &myPack->header, sizeof(myPack->header) ); // Write header
}
fclose (fout); // New file completed.
fclose (myPack->fileHandle); // Old pack data is stale.
File_Delete (myPack->fileName);
File_Rename (tempFileName, myPack->fileName);
myPack->fileHandle = fopen (myPack->fileName, "rb"); // Re-open file, but in read-mode.
printf ("Compress pack old size %i, new size %i, recovered size %i:", oldsize, newsize, recoveredsize);
}
// Browse through in-memory representation
static int PakFile_Find_File_Index (pak_access_t* myPack, const char* storedName)
{
int i;
for (i = 0; i < myPack->totalEntries; i++)
if (StringMatch (storedName, myPack->directory[i].name))
return i;
return -1;
}
//
// ACTUAL FUNCTIONS (EXTERNAL)
//
fbool PakFile_File_Info (const char* myPakFileName)
{
if (File_Exists (myPakFileName) == False)
{
printf ("File '%s' does not exist\n", myPakFileName);
return False;
}
do
{
int fileSize = File_Size (myPakFileName);
pak_access_t* myPack = PakFile_Open (myPakFileName, ARCHIVE_READ);
int i;
int dataSegmentSize = 0;
printf ( "\n-------------------------\n" );
printf ( "File name: %s\n", myPack->fileName);
printf ( "File size on disk: %i\n", fileSize);
printf ( "------------------------\n" );
printf ( "CONTENT \n" );
printf ( "------------------------\n" );
printf ( "Dir Offset: %i\n", myPack->header.directoryOffset );
printf ( "Dir Length: %i\n", myPack->header.directoryLength );
printf ( "Entry Length: %i\n", (int)sizeof(myPack->directory[0]) );
printf ( "Num Files (Calc): %i\n", ( myPack->header.directoryLength / sizeof(myPack->directory[0] ) ) );
printf ( "------------------------\n" );
printf ( "SEGMENTS \n" );
printf ( "------------------------\n" );
printf ( "Size of header: %i\n", sizeof(myPack->header) );
printf ( "Size of data segment: %i\n", (myPack->header.directoryOffset - sizeof(myPack->header)) );
printf ( "Size of directory: %i\n", fileSize - myPack->header.directoryOffset );
printf ( " --\n");
printf ( "TOTAL: %i\n", sizeof(myPack->header) + (myPack->header.directoryOffset - sizeof(myPack->header)) + (fileSize - myPack->header.directoryOffset) );
printf ( "MATCHES FILESIZE: %s\n", (fileSize == sizeof(myPack->header) + (myPack->header.directoryOffset - sizeof(myPack->header)) + (fileSize - myPack->header.directoryOffset)) ? "YES" : "**NO**" );
printf ( "------------------------\n");
printf ( "Directory \n");
for (i = 0; i < myPack->totalEntries; i ++)
{
dataSegmentSize += myPack->directory[i].filelen;
printf ("%i: %8i %s \n", i, myPack->directory[i].filelen, myPack->directory[i].name);
}
printf ( " --\n");
printf ( "TOTAL COMPUTED %i\n", dataSegmentSize );
#define IsEqualYesNo(x, y) ((x) == (y)) ? "Yes" : "No"
printf ( "MATCHES EXPECTED: %s\n", IsEqualYesNo(dataSegmentSize, myPack->header.directoryOffset - sizeof(myPack->header)) );
printf ( "DIFF:: %i\n", (myPack->header.directoryOffset - sizeof(myPack->header) ) - dataSegmentSize );
printf ( "------------------------\n");
File_SetCursor (myPack->fileHandle, myPack->header.directoryOffset);
printf ( "Needs Compressed? %i \n", (int)PakFile_Close_Check_For_Empty_Space(myPack));
PakFile_Close (myPack);
} while (0);
//
return True;
}
struct pak_access_s* PakFile_Open (const char* myPakFileName, int myFileMode)
{
pak_access_t* myPack = Memory_calloc (sizeof(pak_access_t), 1, "In-Memory Pack File Struct");
const char* accessCode = accessCodes[myFileMode];
int stage = OPEN_FILE_1;
if ( !(myPack->fileHandle = fopen (myPakFileName, accessCode) ) ) goto pak_open_fail; // Assign filehandle, but if NULL then error out
StringLCopy (myPack->fileName, myPakFileName);
myPack->fileMode = myFileMode; // Edit. Create, write, read handled differently
if (myFileMode == ARCHIVE_CREATE) // If Create just write a dummy header and get out ...
{
File_Safe_Write (myPack->fileHandle, &myPack->header, sizeof(myPack->header) );
myPack->isDirty = True;
}
else
{ // ... ekse read header/directory, set cursor to start of directory (EDIT may write new data there)
File_Safe_Read (myPack->fileHandle, &myPack->header, sizeof(myPack->header) );
stage = PAK_CHECK_2; if (memcmp(myPack->header.id, "PACK", 4)) goto pak_open_fail;
stage = DIR_SIZE_3; if (myPack->header.directoryLength > sizeof(myPack->directory) ) goto pak_open_fail;
File_SetCursor (myPack->fileHandle, myPack->header.directoryOffset); // Set cursor
File_Safe_Read (myPack->fileHandle, myPack->directory, myPack->header.directoryLength); // Get directory
File_SetCursor (myPack->fileHandle, myPack->header.directoryOffset); // Move cursor to beginning of directory
myPack->totalEntries = myPack->header.directoryLength / sizeof(myPack->directory[0]);
myPack->dataSegmentSize = myPack->header.directoryOffset - sizeof(myPack->header);
}
return myPack; // Everything ok. Get out with struct calloc'd and file open ...
pak_open_fail:
printf ("'%s': %s", myPakFileName, pak_open_error[stage]);
if (myPack->fileHandle) fclose (myPack->fileHandle); // Close file if we had one open
myPack = Memory_free(myPack);
return NULL;
}
fbool PakFile_Add_File_As (struct pak_access_s* myPack, const char* diskFileName, const char* storeAsName)
{
int i = myPack->totalEntries, stage, oldwritecursor = File_GetCursor (myPack->fileHandle);
stage = CHECK_MODE_1; if (myPack->fileMode == ARCHIVE_READ) goto pak_add_fail;
stage = CHECK_LENGTH_2; if (strlen(storeAsName) > MAX_PAK_FILENAME_IS_55) goto pak_add_fail;
stage = CHECK_NUM_FILES_3; if (myPack->totalEntries >= MAX_PAK_FILES_IS_4096) goto pak_add_fail;
myPack->directory[i].filepos = File_GetCursor (myPack->fileHandle); // LittleLong
myPack->directory[i].filelen = File_Append_BinaryFile (myPack->fileHandle, storeAsName); // This writes the file. LittleLong
stage = CHECK_APPEND_4; if (myPack->directory[i].filelen == 0) goto pak_add_fail;
// Everything OK
StringLCopy (myPack->directory[i].name, storeAsName); // Finalize the name
myPack->isDirty = True; // Edit file needs written
myPack->dataSegmentSize += myPack->directory[i].filelen;// Increase data segment count
myPack->totalEntries ++; // Total entries + 1
return True;
pak_add_fail:
File_SetCursor (myPack->fileHandle, oldwritecursor); // Set the cursor back to where it was just in case
printf ("Can't store '%s' as '%s': %s" , diskFileName, storeAsName, pak_add_error[stage]);
return False;
}
fbool PakFile_Rename_File (struct pak_access_s* myPack, const char* storeAsName, const char* newName)
{
int i = PakFile_Find_File_Index (myPack, storeAsName);
if (i == -1)
{
printf ("Couldn't rename '%s' because couldn't find it in pak\n", storeAsName);
return False; // Couldn't find it so nothing to do.
}
if (myPack->fileMode != ARCHIVE_EDIT && myPack->fileMode != ARCHIVE_FASTEDIT)
{
printf ("Access mode is not EDIT and does not allow entry rename\n");
return False; // Can't delete
}
if (strlen(newName) > MAX_PAK_FILENAME_IS_55)
{
printf ("New file name too long '%s' is %i characters (MAX %i)\n", newName, strlen(newName), MAX_PAK_FILENAME_IS_55);
return False; // Rename
}
StringLCopy (myPack->directory[i].name, newName);
myPack->isDirty = True;
return True;
}
fbool PakFile_Delete_File (struct pak_access_s* myPack, const char* storeAsName)
{
int i = PakFile_Find_File_Index (myPack, storeAsName);
if (i == -1) return False; // Nothing to do.
if (myPack->fileMode != ARCHIVE_EDIT)
{
printf ("Access mode is not EDIT and does not allow entry delete\n");
return False; // Can't delete
}
myPack->directory[i].name[0] = '\0'; // Set name to zero length
myPack->isDirty = True;
return True;
}
// File cursor must be at end of file
struct pak_access_s* PakFile_Close (struct pak_access_s* myPack)
{
fbool directoryWasCompressed = PakFile_Close_Directory_Fixup (myPack);
fbool directoryWasAlphaSorted = PakFile_Close_Directory_AlphaSort (myPack);
fbool pakNeedsCompressed = PakFile_Close_Check_For_Empty_Space (myPack);
if (myPack->isDirty == True) // Store the offset then write the directory.
{
int directoryStart = File_GetCursor (myPack->fileHandle);
int directoryByteLength = sizeof(myPack->directory[0]) * myPack->totalEntries;
myPack->header.id[0] = 'P'; // Construct final header
myPack->header.id[1] = 'A';
myPack->header.id[2] = 'C';
myPack->header.id[3] = 'K';
myPack->header.directoryOffset = LittleLong(directoryStart);
myPack->header.directoryLength = LittleLong(directoryByteLength);
File_Safe_Write (myPack->fileHandle, &myPack->directory, directoryByteLength); // Write directory
File_SetCursor_ToStart (myPack->fileHandle); // Return to beginning of file
File_Safe_Write (myPack->fileHandle, &myPack->header, sizeof(myPack->header) ); // Write header
}
if (myPack->fileMode == ARCHIVE_EDIT && pakNeedsCompressed) // Create can't need it, read can't do it, fastedit doesn't want it
PakFile_Close_Compress (myPack); // Recover deleted space. Re-writes whole file.
fclose (myPack->fileHandle); // close the file
// Free the memory (this should effective return NULL, since Memory_free returns NULL)
return Memory_free (myPack);
}
Is this boring as hell? Yes. That's just what you have to do sometimes though ...