PHP TibiaCam file reader

Simple reader for uncompressed TibiaCam files (binary files, up to 60MB).

It’s also example of fast reading binary data from file and handling it as stream.

<?php

class BinaryData
{
    /** @var resource */
    private $data;
    /** @var int */
    private $length;

    /**
     * BinaryData constructor.
     * @param string $data
     */
    public function __construct($data)
    {
        $this->data = fopen('php://memory', 'wb+');
        fwrite($this->data, $data);
        $this->length = ftell($this->data);
        rewind($this->data);
    }

    /**
     * @return int
     */
    public function getLength()
    {
        return $this->length;
    }

    /**
     * @return bool
     */
    public function isEof()
    {
        return ftell($this->data) == $this->length;
    }

    /**
     * @param int $length
     */
    public function skip($length)
    {
        fseek($this->data, $length, SEEK_CUR);
    }

    /**
     * @return int
     */
    public function getU8()
    {
        return unpack('C', fread($this->data, 1))[1];
    }

    /**
     * @return int
     */
    public function getU16()
    {
        return unpack('v', fread($this->data, 2))[1];
    }

    /**
     * @return int
     */
    public function getU32()
    {
        return unpack('V', fread($this->data, 4))[1];
    }

    /**
     * @return int
     */
    public function getU64()
    {
        return unpack('P', fread($this->data, 8))[1];
    }

    /**
     * @param int $length
     * @return string
     */
    public function getString($length = -1)
    {
        if ($length == -1) {
            $length = $this->getU16();
        }

        return fread($this->data, $length);
    }

}

class NetworkPacket extends BinaryData
{
    /**
     * 0x01 = packets sent to player
     * 0x03 = packets received from player
     * 0x05 = refresh packets for fast cam rewinding
     */
    const TYPE_OUT = 0x01;
    const TYPE_IN = 0x03;
    const TYPE_REFRESH = 0x05;

    /** @var int */
    private $type;

    /**
     * NetworkPacket constructor.
     * @param string $data
     */
    public function __construct($data)
    {
        parent::__construct($data);
        $this->type = $this->getU8();
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->type;
    }

}

class Cam extends BinaryData
{
    /**
     * Cam constructor.
     * @param string $filePath path to cam file
     */
    public function __construct($filePath)
    {
        parent::__construct(file_get_contents($filePath));
    }

    /**
     * @return array
     */
    public function readTibiaCamAttributes()
    {
        $this->skip(8); // text: TIBIACAM

        $camVersion = $this->getU8();
        $playerId = $this->getU32();
        $playerName = $this->getString();
        $protocolVersion = $this->getU16();
        $timeCreated = $this->getU64();

        return [$camVersion, $playerId, $playerName, $protocolVersion, $timeCreated];
    }

}

$fileName = 'nowy.cam.ready';
$cam = new Cam($fileName);
$camAttributes = $cam->readTibiaCamAttributes();
var_dump($camAttributes);

while (!$cam->isEof()) {
    $packetTime = $cam->getU64();
    var_dump($packetTime);
    $packet = new NetworkPacket($cam->getString());
    var_dump($packet->getType());
    var_dump($packet->getLength());
}

OTS Restarter script

restart.sh with console logs with timestamp and binary file copy (for GDB)

#!/bin/bash
ulimit -c unlimited
while true; do

 cp kasteria bins/kasteria_`date '+%Y-%m-%d_%H-%M'`_`crc32 kasteria`
 stdbuf -o 0 ./kasteria 2>&1 | ts '%Y-%m-%d_%H-%M-%.S' | tee -a 'console/console_'`date '+%Y-%m-%d'`'.log';

 echo START SLEEP FOR 3 SECONDS, PRESS CTRL+C TO TURN OFF RESTARTER
 sleep 3
 echo END SLEEP

done

Autowalk/cavebot for OTClient

Someone asked me about script that will make character run to given position and after reaching it run to another. I made it for free, so I decided to publish it as it may be interesting for others.

It’s not ready-to-run module, but I think it’s very close. For tests I pasted parts of it to ‘battle’ module and it worked.

This code requires ‘BOT_PROTECTION’ disabled in OTClient.

local walkEvent = nil
local positionsList = {
    { x = 32608, y = 32131, z = 7 },
    { x = 32601, y = 32131, z = 7 },
    { x = 32601, y = 32138, z = 7 },
    { x = 32608, y = 32138, z = 7 },
}

local isAttacking = 0
local isFollowing = 0
local currentTargetPositionId = 1

local autowalkTargetPosition = positionsList[currentTargetPositionId]

function init()
    connect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    connect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function terminate()
    disconnect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    disconnect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function walkToTarget()
    if not g_game.isOnline() then
        walkEvent = scheduleEvent(walkToTarget, 500)
        return
    end

    if g_game.getLocalPlayer():getStepTicksLeft() > 0 then
        -- wait until walk animation finish
        walkEvent = scheduleEvent(walkToTarget, g_game.getLocalPlayer():getStepTicksLeft())
        return
    end

    if isAttacking or isFollowing or not autowalkTargetPosition then
        -- do not walk with selected attack/follow target or when there is no target position
        walkEvent = scheduleEvent(walkToTarget, 100)
        return
    end

    -- fast search path on minimap (known tiles)
    steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 5000, 0)
    if result == PathFindResults.Ok then
        g_game.walk(steps[1], true)
    elseif result == PathFindResults.Position then
        -- on target position
        currentTargetPositionId = currentTargetPositionId + 1
        autowalkTargetPosition = positionsList[(currentTargetPositionId % #positionsList) + 1]
    else
        -- slow search path on minimap, if not found, start 'scanning' map
        steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 25000, 1)
        if result == PathFindResults.Ok then
            g_game.walk(steps[1], true)
        end
    end

    -- limit steps to 10 per second (100 ms between steps)
    walkEvent = scheduleEvent(walkToTarget, math.max(100, g_game.getLocalPlayer():getStepTicksLeft()))
end

function onCreaturePositionChange(creature, newPos, oldPos)
    if not walkEvent then
        -- start automoving 1 second after logging into game
        walkEvent = scheduleEvent(walkToTarget, 1000)
    end
end

function onAttack(creature)
    isAttacking = creature and creature:getId() or 0
end

function onFollow(creature)
    isFollowing = creature and creature:getId() or 0
end

function onCreatureDisappear(creature)
    if isAttacking == creature:getId() then
        isAttacking = 0
    end
    if isFollowing == creature:getId() then
        isFollowing = 0
    end
end

PayPal (is a) scam – removing my PayPal account

PayPal wants to take fee for not realized transactions?! No more PayPal for me.

Tommorow I will remove my PayPal account and link all subscriptions directly to Visa – Gesior.pl

https://www.paypal.com/us/webapps/mpp/ua/upcoming-policies-full

Amendments to the PayPal Account User Agreement

We’re removing the flat rate pricing for sending money to friends and family members who have PayPal accounts in a country other than the United States and introducing a new variable fee of 5% based on the amount you send with a minimum of $0.99 and a maximum of $4.99 per transaction. We’re also removing any variation depending on the recipient’s country. 

We are changing the currency conversion spread to 3.25% over a base exchange rate in situations where you are a sender of money in a PayPal transaction.

We’re changing how we treat refunds. If you refund (partially or fully) a transaction to a buyer or a donation to a donor, there are no fees to make the refund, but the fees you originally paid as the seller will not be returned to you.

Eloth start scripts for GMs

ROOK

Create fast monster spawn

Spawn “Snake” every second (1000 ms) for 5 seconds:

!lua function fms(m, p, c) doSummonCreature(m, p, true, true) if (c > 1) then addEvent(fms, 1000, m, p, c-1) end end fms("Snake", getCreaturePosition(cid), 5)

Spawn temple NPC

For FACC towns:

/s The Oracle

For PACC towns:

/s The Gatekeeper

MAIN

Open Desert Quest

Create teleports that allow 20+ levels do quest solo without items:

!lua doCreateTeleport(1387, Position(32672, 32102, 8), Position(32647, 32092, 7)) doCreateTeleport(1387, Position(32672, 32070, 8), Position(32673, 32089, 8))

Generate big depot

LUA code to generate backpack full of backpacks:

function fill(container, level)
	for i = 1, 20 do
		local innerContainer = doAddContainerItem(container, 2000)
		if level > 0 then
			fill(innerContainer, level - 1)
		end
	end
end
container = doCreateItem(2000, getThingPos(cid)) fill(container, 2)

One-liner for !lua debug talkaction:

function fill(c, l) for i = 1, 20 do ic = doAddContainerItem(c, 2000) if l > 0 then fill(ic, l -1) end end end c = doCreateItem(2000, getThingPos(cid)) fill(c, 2)

Then put it in parcel and send to depot.