Setup/DBCReader

* port extended client file handling from other branch
 * class DBC -> DBCReader now initializes a DBCFile
   which itself is a BinaryFile
 * update DBCReader to use the new DB wrappers multi-insert feature
This commit is contained in:
Sarjuuk 2026-01-19 22:34:59 +01:00
parent 69df50619a
commit c85675e181
11 changed files with 766 additions and 524 deletions

View file

@ -0,0 +1,104 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
abstract class Primitive
{
public const /* int */ SIZE = 1;
public const /* string */ PACK_FMT = 'x';
protected string $data;
public function __construct(BinaryFile|string $data)
{
if (is_string($data))
$this->data = $data;
else
$this->data = $data->read(static::SIZE);
}
public function pack() : string
{
return $this->data;
}
public function unpack() : mixed
{
return current(unpack(static::PACK_FMT, $this->data));
}
public function __debugInfo() : array
{
return [$this->unpack()];
}
}
class Char extends Primitive
{
public const /* int */ SIZE = 1;
public const /* string */ PACK_FMT = 'C';
public function unpack() : string
{
return chr(parent::unpack());
}
}
class Boolean extends Primitive
{
public const /* int */ SIZE = 1;
public const /* string */ PACK_FMT = 'C';
public function unpack() : string
{
return !!(parent::unpack());
}
}
class UInt8 extends Primitive
{
public const /* int */ SIZE = 1;
public const /* string */ PACK_FMT = 'C';
}
class Int8 extends Primitive
{
public const /* int */ SIZE = 1;
public const /* string */ PACK_FMT = 'c';
}
class UInt16 extends Primitive
{
public const /* int */ SIZE = 2;
public const /* string */ PACK_FMT = 'v';
}
class Int16 extends Primitive
{
public const /* int */ SIZE = 2;
public const /* string */ PACK_FMT = 's';
}
class UInt32 extends Primitive
{
public const /* int */ SIZE = 4;
public const /* string */ PACK_FMT = 'V';
}
class Int32 extends Primitive
{
public const /* int */ SIZE = 4;
public const /* string */ PACK_FMT = 'l';
}
class Double extends Primitive
{
public const /* int */ SIZE = 4;
public const /* string */ PACK_FMT = 'f';
}
?>

View file

@ -0,0 +1,202 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class BinaryFile
{
private /*res*/ $handle = null;
private string $data = '';
private int $pos = 0;
protected int $filesize = 0;
public string $error = '';
public function __construct(string $file, private bool $inRAM = true)
{
if (!file_exists($file))
{
$this->error = 'file '.$file.' not found';
return;
}
if (!$this->handle = fopen($file, 'rb'))
{
$this->error = 'failed to open file '.$file;
return;
}
$this->filesize = filesize($file);
if ($inRAM)
$this->data = file_get_contents($file);
}
public function __destruct()
{
$this->close();
}
/**********************/
/* direct file access */
/**********************/
public function read(int $bytes) : ?string
{
if ($this->error || !is_resource($this->handle) || $bytes < 0)
return null;
$start = $this->pos;
$this->pos += $bytes;
if ($this->inRAM)
return substr($this->data, $start, $bytes);
else
return fread($this->handle, $bytes);
}
public function readOffset(int $bytes, int $offset, bool $jumpBack = true) : ?string
{
if ($this->error || !is_resource($this->handle))
return null;
if ($jumpBack)
$curPos = $this->inRAM ? $this->pos : ftell($this->handle);
$this->seek($offset);
$str = $this->read($bytes);
if ($jumpBack)
$this->seek($curPos);
return $str;
}
public function seek(int $pos) : int
{
if (!is_resource($this->handle))
return 0;
if ($pos < 0)
$pos = 0;
if ($pos > $this->filesize)
$pos = $this->filesize;
$this->pos = $pos;
if (!$this->inRAM)
fseek($this->handle, $pos, SEEK_SET);
return $pos;
}
public function ffwd(int $bytes) : int
{
if (!is_resource($this->handle))
return 0;
$curPos = $this->inRAM ? $this->pos : ftell($this->handle);
if ($curPos + $bytes < 0)
$bytes -= $curPos;
if ($curPos + $bytes > $this->filesize)
$bytes -= $this->filesize;
$this->pos += $bytes;
if ($this->inRAM)
return $this->pos;
fseek($this->handle, $bytes, SEEK_CUR);
return ftell($this->handle);
}
public function close() : void
{
if (is_resource($this->handle))
fclose($this->handle);
}
public function tell() : int
{
if (!is_resource($this->handle))
return 0;
return $this->inRAM ? $this->pos : ftell($this->handle);
}
/******************/
/* read Primitive */
/******************/
public function readInt8() : ?Int8
{
if (!is_resource($this->handle))
return null;
return new Int8($this);
}
public function readInt16() : ?Int16
{
if (!is_resource($this->handle))
return null;
return new Int16($this);
}
public function readInt32() : ?Int32
{
if (!is_resource($this->handle))
return null;
return new Int32($this);
}
public function readUInt8() : ?UInt8
{
if (!is_resource($this->handle))
return null;
return new UInt8($this);
}
public function readUInt16() : ?UInt16
{
if (!is_resource($this->handle))
return null;
return new UInt16($this);
}
public function readUInt32() : ?UInt32
{
if (!is_resource($this->handle))
return null;
return new UInt32($this);
}
public function readFloat() : ?Double
{
if (!is_resource($this->handle))
return null;
return new Double($this);
}
public function readChar() : ?Char
{
if (!is_resource($this->handle))
return null;
return new Char($this);
}
public function readBool() : ?Boolean
{
if (!is_resource($this->handle))
return null;
return new Boolean($this);
}
}
?>

View file

@ -0,0 +1,83 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class DBCFile extends BinaryFile
{
private const /* string */ MAGIC = 'WDBC';
private const /* int */ HEADER_SIZE = 16;
private readonly int $stringSize;
private readonly int $stringOffset;
public readonly int $nCols;
public readonly int $nRows;
public readonly int $recordSize;
public function __construct(string $file)
{
parent::__construct($file);
if ($this->filesize < strlen(self::MAGIC) + self::HEADER_SIZE)
{
$this->error = 'file '.$file.' too small for a dbc';
$this->close();
return;
}
if ($this->read(4) != self::MAGIC)
{
$this->error = 'file '.$file.' has incorrect magic bytes';
$this->close();
return;
}
[, $this->nRows, $this->nCols, $this->recordSize, $this->stringSize] = unpack(UInt32::PACK_FMT.'4', $this->read(self::HEADER_SIZE));
$this->stringOffset = strlen(self::MAGIC) + self::HEADER_SIZE + $this->recordSize * $this->nRows;
if ($this->stringOffset + $this->stringSize != $this->filesize)
{
$this->error = 'file '.$file.' has unexpected size - expected: '.($this->stringOffset + $this->stringSize).' has: '.$this->filesize;
$this->close();
return;
}
}
public function readRecord(string $colFmt = "V*") : array
{
return unpack($colFmt, $this->read($this->recordSize));
}
public function readString() : ?string
{
$x = $this->readUInt32();
if (is_null($x))
return null;
return $this->getStringFromBlock($x->unpack());
}
public function getStringFromBlock(int $offset) : ?string
{
$curPos = $this->tell();
$this->seek($this->stringOffset + $offset);
// apparently it is more efficient to read more than one byte at once..?
$str = '';
while (($pos = strpos($str, "\0")) === false)
$str .= $this->read(255);
$str = substr($str, 0, $pos);
$this->seek($curPos);
return $pos ? $str : null;
}
}
?>