In this tutorial you will learn how file reading and writing works through CryPak.
After finishing this tutorial you will be capable of adding new files to your project, reading files from file system and pak archives and writing files to the file system.
Before you can start, you need a file which is going to be added to a pak archive. To keep it simple, create a text file named ExampleText.txt and write the following text into it:
Sample was read from pak archive
In the next step, create a new sub-folder called Examples inside GameSDK and add the file into it: <root>\GameSDK\Examples\ExampleText.txt
Now, make use of 7za.exe which is located in <root>\Tools
to create a new archive called Examples.pak inside GameSDK. This archive will contain Examples\ExampleText.txt
only. Alternatively, you can also use the Obsolete PAK Manager.
..\Tools\7za.exe a -tzip -r -mx0 Examples.pak Examples
This tutorial demonstrates two different methods of loading files - either from a pak archive or directly from the file system.
To verify which file is loaded, the example makes use of the content inside the text file. Hence, change the current text inside <root>\GameSDK\Examples\ExampleText.txt
to something different than before:
Sample was read from file system
Now, there are two different text files with the same destination path - one is stored directly in the file system, the other one is nested in the archive.
CryPak is able to find both instances depending on the pakPriority.
The default pakPriority depends on the configuration settings of your build, but it can also manually be changed by assigning 0, 1, 2 or 3 to the cvar sys_PakPriority.
enum EPakPriority
{
ePakPriorityFileFirst = 0,
ePakPriorityPakFirst = 1,
ePakPriorityPakOnly = 2,
ePakPriorityFileFirstModsOnly = 3,
};
The main reason for adding the new pak file to the GameSDK folder in this example is because here it is guaranteed that all pak files are loaded.
Pak loading order:
Pak search order:
The module CrySystem initializes CryPak and makes sure that pak files can be accessed.
To be precise, this happens in CSystem::Init when the following functions are called:
This is good because it ensures that pak files can be accessed from the game code at anytime. A good spot to demonstrate that is at the beginning of CGame::Init inside Game.cpp.
Let's write some lines of code to read the information of ExampleText.txt, which has been created above.
char* fileContent = NULL;
if (!ReadFromExampleFile(&fileContent))
{
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "ReadFromExampleFile() failed");
}
else
{
CryLogAlways("ExampleText contains %s", fileContent);
[...] // this line will be added later on
}
bool ReadFromExampleFile(char** fileContent)
{
CCryFile file;
size_t fileSize = 0;
const char* filename = "examples/exampletext.txt";
[...]
}
char str[1024];
if (!file.Open(filename, "r"))
{
sprintf(str, "Can't open file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
fileSize = file.GetLength();
if (fileSize <= 0)
{
sprintf(str, "File is empty, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
char* content = new char[fileSize + 1];
content[fileSize] = '\0';
if (file.ReadRaw(content, fileSize) != fileSize)
{
delete[] content;
sprintf(str, "Can't read file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
file.Close();
*fileContent = content;
return true;
Finally, we close the file handle, make sure that the locally created data can be used outside the function by setting the fileContent pointer and return true since the reading was successful.
In our example the caller of ReadFromExampleFile() is responsible for freeing the heap memory which has been allocated to store the data from the text file. Add delete[] fileContent; after the info has been used.
If you run the game now, you can check if reading worked by checking the Game.log.
char* fileContent = NULL;
if (!ReadFromExampleFile(&fileContent))
{
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "ReadFromExampleFile() failed");
}
else
{
CryLogAlways("ExampleText contains %s", fileContent);
delete[] fileContent;
}
bool ReadFromExampleFile(char** fileContent)
{
CCryFile file;
size_t fileSize = 0;
const char* filename = "examples/exampletext.txt";
char str[1024];
if (!file.Open(filename, "r"))
{
sprintf(str, "Can't open file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
fileSize = file.GetLength();
if (fileSize <= 0)
{
sprintf(str, "File is empty, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
char* content = new char[fileSize + 1];
content[fileSize] = '\0';
if (file.ReadRaw(content, fileSize) != fileSize)
{
delete[] content;
sprintf(str, "Can't read file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
file.Close();
*fileContent = content;
return true;
}
CCryFile::Write always writes to the file system and never to pak archives. If you want to write files to a compressed or uncompressed archives is please skip this chapter.
Writing a file is similar as reading. First, let's call a function which will be implemented in the next step.
char* newContent = "File has been modified";
bool appendToFile = false;
if (!WriteToExampleFile(newContent, strlen(newContent), appendToFile))
{
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "WriteToExampleFile() failed");
}
else
{
CryLogAlways("Text has been written to file, %s", newContent);
}
Respectively to file reading, the framework of WriteToExampleFile() looks like the following:
bool WriteToExampleFile(char* text, int bytes, bool appendToFile)
{
CCryFile file;
const char* filename = "examples/exampletext.txt";
assert(bytes > 0);
char* mode = NULL;
if (appendToFile)
mode = "a";
else
mode = "w";
char str[1024];
if (!file.Open(filename, mode))
{
sprintf(str, "Can't open file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
[...]
file.Close();
return true;
}
int bytesWritten = file.Write(text, bytes);
assert(bytesWritten == bytes);
if (bytesWritten == 0)
{
sprintf(str, "Can't write to file, (%s)", filename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return false;
}
The following tutorial explains how new pak files are generated at runtime. Furthermore, the example shows how files are added, updated and removed from the archive.
In the example code we are intentionally using the USER folder instead of GameSDK. Pak files inside the USER folder are not loaded by default at startup whereas all the *.pak files inside GameSDK will be loaded and thus marked as Read-Only.
string pakFilename = PathUtil::AddSlash("%USER%") + "Examples.pak";
const char* filename = "Examples/ExampleText.txt";
char* text = "File has been modified by CryArchive";
unsigned length = strlen(text);
_smart_ptr<ICryArchive> pCryArchive = gEnv->pCryPak->OpenArchive(pakFilename.c_str(), ICryArchive::FLAGS_RELATIVE_PATHS_ONLY | ICryArchive::FLAGS_CREATE_NEW);
if (pCryArchive)
{
pCryArchive->UpdateFile(filename, text, length, ICryArchive::METHOD_STORE, 0);
}