• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Getting "Tibia might currently be down for maintenance." connecting to TFS master (Tibia 12)

soul4soul

Intermediate OT User
Joined
Aug 13, 2007
Messages
1,875
Solutions
3
Reaction score
128
Location
USA
Hey all,

I'm trying to run a copy of TFS locally for my own use but I'm getting the error "Login failed. Tibia might currently be down for maintenance. Please try again later." when trying to connect with the cip client.

Software & Setup:

I had been using this setup to run TFS locally when it supported 10.98 without issue. My steps to use tibia 12 where to pull master, rebuild, install login.php 12 plugin, patch tibia client.

Some things I tested and noticed
  • Verified the client is able to hit my local login.php server by changing the dummy values for stream counts and seeing them change in the cip client.
  • Verified the client is able to hit my local login.php by logging in using an invalid email/password and verified the error message 'email or password is incorrect' appeared in the cip client.
  • Verified TFS is actually running and MyACC is able to accurately detected when I have the server running.
  • Compared the json object returned by the MyACC plugin with Nekiro python login server.
    • The MyACC plugin has some extra fields dailyrewardstate and emailcoderequest but removing them didn't make a difference.
    • The MyACC plugin doesn't include the token in the sessionkey field but adding it didn't make a difference
  • Searched on the forums, there were a couple hits for "Tibia might currently be down for maintenance." but I didn't find any obvious resolutions.

Thanks for the help
 
Last edited:
Solution
Try this is new login.php for TFS 1.x, tested with client 12.85.

Code:
<?php
require_once 'common.php';
require_once 'config.php';
require_once 'config.local.php';
require_once SYSTEM . 'functions.php';
require_once SYSTEM . 'init.php';
require_once SYSTEM . 'status.php';

# error function
function sendError($message, $code = 3){
    $ret = [];
    $ret['errorCode'] = $code;
    $ret['errorMessage'] = $message;
    die(json_encode($ret));
}

# event schedule function
function parseEvent($table1, $date, $table2)
{
    if ($table1) {
        if ($date) {
            if ($table2) {
                $date = $table1->getAttribute('startdate');
                return date_create("{$date}")->format('U');
            } else {...
Try:
Ugh I really didn't want to install python and go though the trouble when I have a perfectly good web server running... What a pain so for some reason I can't run tornado on port 80 as a unprivileged user on WSL1. So I tried 8080 but that didn't work since its all for local dev anyway I installed the pip packages for the privileged user and ran the web server using sudo and what do you know it works. Tricky that your service uses account number instead of email but it worked!

my-acc doesn't have login.php and that plugin was made for otbr, I need to check this as I recommended my-aac in my FAQ 🤔
Yes, I actually followed your guide to figure out exactly what I needed for tibia 12. I was already running myacc and your guide said it was good to use. Seems like it doesn't quite work for TFS I can try to look I can't imagine its anything more then the SQL queries being a little off.
 
Try this is new login.php for TFS 1.x, tested with client 12.85.

Code:
<?php
require_once 'common.php';
require_once 'config.php';
require_once 'config.local.php';
require_once SYSTEM . 'functions.php';
require_once SYSTEM . 'init.php';
require_once SYSTEM . 'status.php';

# error function
function sendError($message, $code = 3){
    $ret = [];
    $ret['errorCode'] = $code;
    $ret['errorMessage'] = $message;
    die(json_encode($ret));
}

# event schedule function
function parseEvent($table1, $date, $table2)
{
    if ($table1) {
        if ($date) {
            if ($table2) {
                $date = $table1->getAttribute('startdate');
                return date_create("{$date}")->format('U');
            } else {
                $date = $table1->getAttribute('enddate');
                return date_create("{$date}")->format('U');
            }
        } else {
            foreach($table1 as $attr) {
                if ($attr) {
                    return $attr->getAttribute($table2);
                }
            }
        }
    }
    return 'error';
}

$request = json_decode(file_get_contents('php://input'));
$action = $request->type ?? '';

/** @var OTS_Base_DB $db */
/** @var array $config */

switch ($action) {
    case 'cacheinfo':
        $playersonline = $db->query("select count(*) from `players_online`")->fetchAll();
        die(json_encode([
            'playersonline' => (intval($playersonline[0][0])),
            'twitchstreams' => 0,
            'twitchviewer' => 0,
            'gamingyoutubestreams' => 0,
            'gamingyoutubeviewer' => 0
        ]));

    case 'eventschedule':
        $eventlist = [];
        $file_path = config('server_path') . 'data/XML/events.xml';
        if (!file_exists($file_path)) {
            die(json_encode([]));
        }
        $xml = new DOMDocument;
        $xml->load($file_path);
        $tmplist = [];
        $tableevent = $xml->getElementsByTagName('event');

        foreach ($tableevent as $event) {
            if ($event) { $tmplist = [
            'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
            'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
            'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
            'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
            'enddate' => intval(parseEvent($event, true, false)),
            'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
            'name' => $event->getAttribute('name'),
            'startdate' => intval(parseEvent($event, true, true)),
            'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
                ];
            $eventlist[] = $tmplist; } }
        die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));

    case 'boostedcreature':
        $boostDB = $db->query("select * from " . $db->tableName('boosted_creature'))->fetchAll();
        foreach ($boostDB as $Tableboost) {
        die(json_encode([
            'boostedcreature' => true,
            'raceid' => intval($Tableboost['raceid'])
        ]));
        }
    break;

    case 'login':

        $port = $config['lua']['gameProtocolPort'];

        // default world info
        $world = [
            'id' => 0,
            'name' => $config['lua']['serverName'],
            'externaladdress' => $config['lua']['ip'],
            'externalport' => $port,
            'externaladdressprotected' => $config['lua']['ip'],
            'externalportprotected' => $port,
            'externaladdressunprotected' => $config['lua']['ip'],
            'externalportunprotected' => $port,
            'previewstate' => 0,
            'location' => 'BRA', // BRA, EUR, USA
            'anticheatprotection' => false,
            'pvptype' => array_search($config['lua']['worldType'], ['pvp', 'no-pvp', 'pvp-enforced']),
            'istournamentworld' => false,
            'restrictedstore' => false,
            'currenttournamentphase' => 2
        ];

        $characters = [];
        $account = new OTS_Account();

        $inputEmail = $request->email ?? false;
        $inputAccountName = $request->accountname ?? false;
        $inputToken = $request->token ?? false;

        if ($inputEmail != false) { // login by email
            $account->findByEmail($request->email);
        }
        else if($inputAccountName != false) { // login by account name
            $account->find($inputAccountName);
        }

        $config_salt_enabled = fieldExist('salt', 'accounts');
        $current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $request->password);

        if (!$account->isLoaded() || $account->getPassword() != $current_password) {
            sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
        }

        //log_append('test.log', var_export($account->getCustomField('secret'), true));
        $accountHasSecret = false;
        if (fieldExist('secret', 'accounts')) {
            $accountSecret = $account->getCustomField('secret');
            if ($accountSecret != null && $accountSecret != '') {
                $accountHasSecret = true;
                if ($inputToken === false) {
                    sendError('Submit a valid two-factor authentication token.', 6);
                } else {
                    require_once LIBS . 'rfc6238.php';
                    if (TokenAuth6238::verify($accountSecret, $inputToken) !== true) {
                        sendError('Two-factor authentication failed, token is wrong.', 6);
                    }
                }
            }
        }

        // common columns
        $columns = 'id, name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons';

        if (fieldExist('isreward', 'accounts')) {
            $columns .= ', isreward';
        }

        if (fieldExist('istutorial', 'accounts')) {
            $columns .= ', istutorial';
        }

        $players = $db->query("select {$columns} from players where account_id = " . $account->getId() . " AND deletion = 0");
        if($players && $players->rowCount() > 0) {
            $players = $players->fetchAll();

            $highestLevelId = 0;
            $highestLevel = 0;
            foreach ($players as $player) {
                if ($player['level'] >= $highestLevel) {
                    $highestLevel = $player['level'];
                    $highestLevelId = $player['id'];
                }
            }

            foreach ($players as $player) {
                $characters[] = create_char($player, $highestLevelId);
            }
        }

        if (fieldExist('premdays', 'accounts') && fieldExist('lastday', 'accounts')) {
            $save = false;
            $timeNow = time();
            $query = $db->query("select `premdays`, `lastday` from `accounts` where `id` = " . $account->getId());
            if ($query->rowCount() > 0) {
                $query = $query->fetch();
                $premDays = (int)$query['premdays'];
                $lastDay = (int)$query['lastday'];
                $lastLogin = $lastDay;
            } else {
                sendError("Error while fetching your account data. Please contact admin.");
            }
            if ($premDays != 0 && $premDays != PHP_INT_MAX) {
                if ($lastDay == 0) {
                    $lastDay = $timeNow;
                    $save = true;
                } else {
                    $days = (int)(($timeNow - $lastDay) / 86400);
                    if ($days > 0) {
                        if ($days >= $premDays) {
                            $premDays = 0;
                            $lastDay = 0;
                        } else {
                            $premDays -= $days;
                            $reminder = ($timeNow - $lastDay) % 86400;
                            $lastDay = $timeNow - $reminder;
                        }

                        $save = true;
                    }
                }
            } else if ($lastDay != 0) {
                $lastDay = 0;
                $save = true;
            }
            if ($save) {
                $db->query("update `accounts` set `premdays` = " . $premDays . ", `lastday` = " . $lastDay . " where `id` = " . $account->getId());
            }
        }

        $worlds = [$world];
        $playdata = compact('worlds', 'characters');

        $sessionKey = ($inputEmail !== false) ? $inputEmail."\n".$request->password : $inputAccountName."\n".$request->password;
        $sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? "\n".$inputToken : "\n";
        $sessionKey .= "\n".floor(time() / 30);

        $session = [
            'sessionkey' => $sessionKey,
            'lastlogintime' => 0,
            'ispremium' => $config['lua']['freePremium'] || $account->isPremium(),
            'premiumuntil' => ($account->getPremDays()) > 0 ? (time() + ($account->getPremDays() * 86400)) : 0,
            'status' => 'active', // active, frozen or suspended
            'returnernotification' => false,
            'showrewardnews' => true,
            'isreturner' => true,
            'fpstracking' => false,
            'optiontracking' => false,
            'tournamentticketpurchasestate' => 0,
            'emailcoderequest' => false
        ];
        die(json_encode(compact('session', 'playdata')));

    default:
        sendError("Unrecognized event {$action}.");
    break;
}

function create_char($player, $highestLevelId) {
    global $config;
    return [
        'worldid' => 0,
        'name' => $player['name'],
        'ismale' => intval($player['sex']) === 1,
        'tutorial' => isset($player['istutorial']) && $player['istutorial'],
        'level' => intval($player['level']),
        'vocation' => $config['vocations'][$player['vocation']],
        'outfitid' => intval($player['looktype']),
        'headcolor' => intval($player['lookhead']),
        'torsocolor' => intval($player['lookbody']),
        'legscolor' => intval($player['looklegs']),
        'detailcolor' => intval($player['lookfeet']),
        'addonsflags' => intval($player['lookaddons']),
        'ishidden' => isset($player['deletion']) && (int)$player['deletion'] === 1,
        'istournamentparticipant' => false,
        'ismaincharacter' => $highestLevelId == $player['id'],
        'dailyrewardstate' => isset($player['isreward']) ? intval($player['isreward']) : 0,
        'remainingdailytournamentplaytime' => 0
    ];
}
 
Solution
Try this is new login.php for TFS 1.x, tested with client 12.85.
Damn, just came back here to say I was fixing it up locally and you beat me to fixing it all together. I never worked on an AAC but a few things seemed weird

  • why not use the OTS_PLAYER class instead of direct DB calls for the player fields, since OTS_ACCOUNT returns that information with getPlayersList()
  • OTS_ACCOUNT has a lastday but it's not being used here and the lastlogintime is now being set to 0 before it was set. Its available on TFS and otservbr
  • ishidden can is being calculated but the query is already for non deleted players
  • In the block of code to update the premDays and lastday in the DB, the premDays doesn't really need to be queried for as $accounts->getPremDays should have it covered.
    • This block of code is weird, why is lastlogin set to lastday? Returning an error doesn't appear necessary. Running the query and and directly setting lastlogin should be enough without the conditional check for rows and returning an error. If the lastday isn't set this code will set and fix it.
  • Whats the new checks at the top about accountName vs Email? From what I can tell the client only sends email. In my local copy I made it check both starting with the email for convenience.
  • OTS_ACCOUNT has some code about premiumEnd so it would be nice to use that field but sadly it is just a documentation about the field existing, and a getter to a missing function, and a setter to a missing function. Maybe MyACC can fix that in the next release and move the calculation for premiumEnd out of the plugin.
  • Using the highest level to determine main is a cool idea since its otherwise unused for otservers, instead of only setting the last one as the main maybe set all of the chars that are the same highest level as main.
  • For session key, is the last part suppose to be based on time or a nonce? If a nonce using rand() might be better.
Besides those comments I can confirm the login.php is working with TFS master using client 12.86
 
Last edited:
Try this is new login.php for TFS 1.x, tested with client 12.85.

Code:
<?php
require_once 'common.php';
require_once 'config.php';
require_once 'config.local.php';
require_once SYSTEM . 'functions.php';
require_once SYSTEM . 'init.php';
require_once SYSTEM . 'status.php';

# error function
function sendError($message, $code = 3){
    $ret = [];
    $ret['errorCode'] = $code;
    $ret['errorMessage'] = $message;
    die(json_encode($ret));
}

# event schedule function
function parseEvent($table1, $date, $table2)
{
    if ($table1) {
        if ($date) {
            if ($table2) {
                $date = $table1->getAttribute('startdate');
                return date_create("{$date}")->format('U');
            } else {
                $date = $table1->getAttribute('enddate');
                return date_create("{$date}")->format('U');
            }
        } else {
            foreach($table1 as $attr) {
                if ($attr) {
                    return $attr->getAttribute($table2);
                }
            }
        }
    }
    return 'error';
}

$request = json_decode(file_get_contents('php://input'));
$action = $request->type ?? '';

/** @var OTS_Base_DB $db */
/** @var array $config */

switch ($action) {
    case 'cacheinfo':
        $playersonline = $db->query("select count(*) from `players_online`")->fetchAll();
        die(json_encode([
            'playersonline' => (intval($playersonline[0][0])),
            'twitchstreams' => 0,
            'twitchviewer' => 0,
            'gamingyoutubestreams' => 0,
            'gamingyoutubeviewer' => 0
        ]));

    case 'eventschedule':
        $eventlist = [];
        $file_path = config('server_path') . 'data/XML/events.xml';
        if (!file_exists($file_path)) {
            die(json_encode([]));
        }
        $xml = new DOMDocument;
        $xml->load($file_path);
        $tmplist = [];
        $tableevent = $xml->getElementsByTagName('event');

        foreach ($tableevent as $event) {
            if ($event) { $tmplist = [
            'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
            'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
            'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
            'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
            'enddate' => intval(parseEvent($event, true, false)),
            'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
            'name' => $event->getAttribute('name'),
            'startdate' => intval(parseEvent($event, true, true)),
            'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
                ];
            $eventlist[] = $tmplist; } }
        die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));

    case 'boostedcreature':
        $boostDB = $db->query("select * from " . $db->tableName('boosted_creature'))->fetchAll();
        foreach ($boostDB as $Tableboost) {
        die(json_encode([
            'boostedcreature' => true,
            'raceid' => intval($Tableboost['raceid'])
        ]));
        }
    break;

    case 'login':

        $port = $config['lua']['gameProtocolPort'];

        // default world info
        $world = [
            'id' => 0,
            'name' => $config['lua']['serverName'],
            'externaladdress' => $config['lua']['ip'],
            'externalport' => $port,
            'externaladdressprotected' => $config['lua']['ip'],
            'externalportprotected' => $port,
            'externaladdressunprotected' => $config['lua']['ip'],
            'externalportunprotected' => $port,
            'previewstate' => 0,
            'location' => 'BRA', // BRA, EUR, USA
            'anticheatprotection' => false,
            'pvptype' => array_search($config['lua']['worldType'], ['pvp', 'no-pvp', 'pvp-enforced']),
            'istournamentworld' => false,
            'restrictedstore' => false,
            'currenttournamentphase' => 2
        ];

        $characters = [];
        $account = new OTS_Account();

        $inputEmail = $request->email ?? false;
        $inputAccountName = $request->accountname ?? false;
        $inputToken = $request->token ?? false;

        if ($inputEmail != false) { // login by email
            $account->findByEmail($request->email);
        }
        else if($inputAccountName != false) { // login by account name
            $account->find($inputAccountName);
        }

        $config_salt_enabled = fieldExist('salt', 'accounts');
        $current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $request->password);

        if (!$account->isLoaded() || $account->getPassword() != $current_password) {
            sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
        }

        //log_append('test.log', var_export($account->getCustomField('secret'), true));
        $accountHasSecret = false;
        if (fieldExist('secret', 'accounts')) {
            $accountSecret = $account->getCustomField('secret');
            if ($accountSecret != null && $accountSecret != '') {
                $accountHasSecret = true;
                if ($inputToken === false) {
                    sendError('Submit a valid two-factor authentication token.', 6);
                } else {
                    require_once LIBS . 'rfc6238.php';
                    if (TokenAuth6238::verify($accountSecret, $inputToken) !== true) {
                        sendError('Two-factor authentication failed, token is wrong.', 6);
                    }
                }
            }
        }

        // common columns
        $columns = 'id, name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons';

        if (fieldExist('isreward', 'accounts')) {
            $columns .= ', isreward';
        }

        if (fieldExist('istutorial', 'accounts')) {
            $columns .= ', istutorial';
        }

        $players = $db->query("select {$columns} from players where account_id = " . $account->getId() . " AND deletion = 0");
        if($players && $players->rowCount() > 0) {
            $players = $players->fetchAll();

            $highestLevelId = 0;
            $highestLevel = 0;
            foreach ($players as $player) {
                if ($player['level'] >= $highestLevel) {
                    $highestLevel = $player['level'];
                    $highestLevelId = $player['id'];
                }
            }

            foreach ($players as $player) {
                $characters[] = create_char($player, $highestLevelId);
            }
        }

        if (fieldExist('premdays', 'accounts') && fieldExist('lastday', 'accounts')) {
            $save = false;
            $timeNow = time();
            $query = $db->query("select `premdays`, `lastday` from `accounts` where `id` = " . $account->getId());
            if ($query->rowCount() > 0) {
                $query = $query->fetch();
                $premDays = (int)$query['premdays'];
                $lastDay = (int)$query['lastday'];
                $lastLogin = $lastDay;
            } else {
                sendError("Error while fetching your account data. Please contact admin.");
            }
            if ($premDays != 0 && $premDays != PHP_INT_MAX) {
                if ($lastDay == 0) {
                    $lastDay = $timeNow;
                    $save = true;
                } else {
                    $days = (int)(($timeNow - $lastDay) / 86400);
                    if ($days > 0) {
                        if ($days >= $premDays) {
                            $premDays = 0;
                            $lastDay = 0;
                        } else {
                            $premDays -= $days;
                            $reminder = ($timeNow - $lastDay) % 86400;
                            $lastDay = $timeNow - $reminder;
                        }

                        $save = true;
                    }
                }
            } else if ($lastDay != 0) {
                $lastDay = 0;
                $save = true;
            }
            if ($save) {
                $db->query("update `accounts` set `premdays` = " . $premDays . ", `lastday` = " . $lastDay . " where `id` = " . $account->getId());
            }
        }

        $worlds = [$world];
        $playdata = compact('worlds', 'characters');

        $sessionKey = ($inputEmail !== false) ? $inputEmail."\n".$request->password : $inputAccountName."\n".$request->password;
        $sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? "\n".$inputToken : "\n";
        $sessionKey .= "\n".floor(time() / 30);

        $session = [
            'sessionkey' => $sessionKey,
            'lastlogintime' => 0,
            'ispremium' => $config['lua']['freePremium'] || $account->isPremium(),
            'premiumuntil' => ($account->getPremDays()) > 0 ? (time() + ($account->getPremDays() * 86400)) : 0,
            'status' => 'active', // active, frozen or suspended
            'returnernotification' => false,
            'showrewardnews' => true,
            'isreturner' => true,
            'fpstracking' => false,
            'optiontracking' => false,
            'tournamentticketpurchasestate' => 0,
            'emailcoderequest' => false
        ];
        die(json_encode(compact('session', 'playdata')));

    default:
        sendError("Unrecognized event {$action}.");
    break;
}

function create_char($player, $highestLevelId) {
    global $config;
    return [
        'worldid' => 0,
        'name' => $player['name'],
        'ismale' => intval($player['sex']) === 1,
        'tutorial' => isset($player['istutorial']) && $player['istutorial'],
        'level' => intval($player['level']),
        'vocation' => $config['vocations'][$player['vocation']],
        'outfitid' => intval($player['looktype']),
        'headcolor' => intval($player['lookhead']),
        'torsocolor' => intval($player['lookbody']),
        'legscolor' => intval($player['looklegs']),
        'detailcolor' => intval($player['lookfeet']),
        'addonsflags' => intval($player['lookaddons']),
        'ishidden' => isset($player['deletion']) && (int)$player['deletion'] === 1,
        'istournamentparticipant' => false,
        'ismaincharacter' => $highestLevelId == $player['id'],
        'dailyrewardstate' => isset($player['isreward']) ? intval($player['isreward']) : 0,
        'remainingdailytournamentplaytime' => 0
    ];
}
& for 13.10? tested and cant enter to the game just load all character list + pacc days
 
Hey I wanted to see if you had been able to fix this, and maybe share how you set it up? I'm super new to this, I know a little bit about sql but just enough to fix database crashes for corp IT lol. I followed the guide to get the basic server set up, then grabbed a map file of real tibia, got the account creation figured out and got to login on a 10.98 client but crashed when entering the game.

That aside, I'm trying to make a real tibia private server for me and one other person to play, I can tinker with rates and everything, but I wanted to get something recent, before they added audio but still have the nice stuff like hot bars and the xp and loot analyzers and such, so a client version and map between 2019 and 2021 would be great. If you know of any resources that have those figured out already too, I just want to enjoy the game privately with some convenience features and custom rates and stuff, free of the predatory mtx lol
 
Back
Top