Skip to content

Instantly share code, notes, and snippets.

@kasuganosoras
Created April 24, 2024 17:18
Show Gist options
  • Save kasuganosoras/e0e54c9b7a6f1e5e14e66d8237ccb990 to your computer and use it in GitHub Desktop.
Save kasuganosoras/e0e54c9b7a6f1e5e14e66d8237ccb990 to your computer and use it in GitHub Desktop.
A PHP lib for read & write GTAV GXT2 format files.
<?php
/**
* GXT2 file reader and writer
*
* @package GXT2
* @version 1.0.0
* @author Akkariin
* @license MIT
* @link https://github.com/kasuganosoras/
* @example
* require_once "gxt2.inc.php";
* $gxt2 = new GXT2("global.gxt2");
* $data = $gxt2->Load();
* if ($data) {
* $data["ZERODREAM"] = "ZERODREAM233";
* $newGxt2 = new GXT2("global2.gxt2");
* $newGxt2->Save($data);
* }
*/
class GXT2
{
private $file;
/**
* GXT2 constructor.
* @param $file
*/
public function __construct($file)
{
$this->file = fopen($file, "rw+");
}
/**
* GXT2 destructor
*/
public function __destruct()
{
fclose($this->file);
}
/**
* Load GXT2 file
* @return array|void
* @throws Exception
*/
public function Load()
{
$head = $this->ReadUInt32();
if ($head !== 1196971058) {
throw new Exception("Invalid GXT2 file");
return;
}
$entryCount = unpack("V", fread($this->file, 4))[1];
$textEntries = [];
for ($i = 0; $i < $entryCount; $i++) {
$hash = $this->ReadUInt32();
$offset = $this->ReadUInt32();
$textEntries[] = [
"hash" => $hash,
"offset" => $offset
];
}
$gxt2 = $this->ReadUInt32();
if ($gxt2 !== 1196971058) {
throw new Exception("Invalid GXT2 file");
return;
}
$endPos = $this->ReadUInt32();
$textList = [];
foreach ($textEntries as $entry) {
fseek($this->file, $entry["offset"]);
$text = "";
while (ftell($this->file) < $endPos) {
$char = fread($this->file, 1);
if ($char === "\0") {
break;
}
$text .= $char;
}
$textList[$entry["hash"]] = $text;
}
return $textList;
}
/**
* Save GXT2 file
* @param $textList
*/
public function Save($textList)
{
$newTextList = [];
foreach ($textList as $hash => $text) {
if (!is_int($hash)) {
$hash = $this->GetHashKey($hash);
}
$newTextList[$hash] = $text;
}
$textList = $newTextList;
$entryCount = count($textList);
$offset = 16 + ($entryCount * 8);
$datas = [];
fwrite($this->file, pack("V", 1196971058));
fwrite($this->file, pack("V", $entryCount));
foreach ($textList as $hash => $text) {
fwrite($this->file, pack("V", $hash));
fwrite($this->file, pack("V", $offset));
$datas[] = [
"hash" => $hash,
"text" => $text . "\0"
];
$offset += strlen($text) + 1;
}
fwrite($this->file, pack("V", 1196971058));
fwrite($this->file, pack("V", $offset));
foreach ($datas as $data) {
fwrite($this->file, $data["text"]);
}
}
/**
* Read UInt32
* @return mixed
*/
private function ReadUInt32()
{
return unpack("V", fread($this->file, 4))[1];
}
/**
* Get hash key
* @param $name
* @param bool $format
* @return int|string
*/
private function GetHashKey($name, $format = false)
{
$num = 0;
$byte = unpack('C*', mb_convert_encoding($name, 'UTF-8', 'auto'));
for ($i = 1; $i <= count($byte); $i++) {
$num += $byte[$i];
$num += $num << 10;
$num &= 0xFFFFFFFF;
$num ^= $num >> 6;
}
$num += $num << 3;
$num &= 0xFFFFFFFF;
$num ^= $num >> 11;
$result = ($num + ($num << 15));
$result &= 0xFFFFFFFF;
if ($result > 2147483647) {
$result -= 4294967296;
}
return $format ? sprintf("0x%08X", $result) : $result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment