Skip to content

Instantly share code, notes, and snippets.

@TorbenKoehn
Created September 11, 2016 15:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TorbenKoehn/6715a0c419815ffa1e2f0fde72955892 to your computer and use it in GitHub Desktop.
Save TorbenKoehn/6715a0c419815ffa1e2f0fde72955892 to your computer and use it in GitHub Desktop.
A class to quickly read Ragnarok Online's GRF Format and extract files from it. Supports version 0x200 and unencrypted files only.
<?php
class FastGrf
{
private $files;
private $path;
private $handle;
public function __construct($path)
{
$this->files = [];
$this->path = $path;
$this->handle = fopen($path, 'rb');
//Jump to important info directly
fseek($this->handle, 30);
list($ftOffset, $seed, $fCount) = array_values(unpack('VftOffset/Vseed/VfCount', fread($this->handle, 12)));
//Jump to FT offset
fseek($this->handle, $ftOffset + 46);
//Unpack FT
$cSize = unpack('V', fread($this->handle, 4))[1];
fseek($this->handle, 4, SEEK_CUR);
//Read the compressed file table
$cft = fread($this->handle, $cSize);
//Decompress table (Not using the second parameter, it leads to insufficient memory errors :()
$ft = @zlib_decode($cft);
$cft = null;
$fCount = $fCount - $seed - 7;
for ($i = 0, $o = 0; $i < $fCount; $i++) {
//read filename until 0x00
$path = '';
$null = chr(0x00);
while (($char = substr($ft, $o, 1)) !== $null) {
$path .= $char;
$o++;
}
$o++;
$path = mb_convert_encoding($path, 'UTF-8', 'EUC-KR');
list($cSize, $aSize, $size, $flags, $offset) = array_values(unpack(
'VcSize/VaSize/Vsize/Cflags/Voffset',
substr($ft, $o, 17)
));
$o += 17;
$file = [
'cSize' => $cSize,
'aSize' => $aSize,
'size' => $size,
'flags' => $flags,
'offset' => $offset
];
$file['crc32'] = sprintf('%u', crc32(implode(':', [$path, $file['size']])));
$this->files[$path] = $file;
}
$ft = null;
}
public function close()
{
if (is_resource($this->handle))
fclose($this->handle);
}
public function getFiles()
{
return $this->files;
}
/**
* @param $path
* @return bool
*/
public function hasFile($path)
{
return isset($this->files[$path]);
}
/**
* @param $path
* @return array
*/
public function getFileInfo($path)
{
return $this->files[$path];
}
public function getFileHandle($path)
{
$fInfo = $this->files[$path];
if ($fInfo['flags'] !== 1)
throw new \RuntimeException(
"Encrypted GRF file $path. Unencrypt prior to using it."
);
$fp = fopen('php://memory', 'rb+');
fseek($this->handle, $fInfo['offset'] + 46);
$cContent = fread($this->handle, $fInfo['aSize']);
$content = zlib_decode($cContent);
$cContent = null;
fwrite($fp, $content);
fseek($fp, 0, SEEK_SET);
$content = null;
return $fp;
}
public function getFileTextContent($path)
{
$content = stream_get_contents($this->getFileHandle($path));
return mb_convert_encoding($content, 'UTF-8', 'EUC-KR');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment