Video Guide: VSRO - Unique Parser (PHP) - YouTube
I know there are a few others out there, but this is a bit different.
Features:
- Reads evaLogs / FatalLogs
- Can be ran more than once a day for frequent updating, without adding duplicates of the unique kills. Personally I'm running it every 5 minutes.
- Proceeded evaLogs / FatalLogs (of previous days) are moved to a folder of your choice (default "finished\")
- More flexible. The other ones I tried missed a few unique kills here and there. Hence I made this one 
- Each unique kill is added to the database with the info of the killer, unique and the time.
Setting it up:
You will need the Microsoft SQL Driver for PHP (sqlsrv), it can be downloaded here: Download: Microsoft Drivers 3.0 for SQL Server for PHP - Microsoft Download Center - Download Details
Reason I use this is because it's maintained by Microsoft and works with SQL Server 2005 / 2008 and 2012
Creating the table:
PHP Code:
USE [SRO_VT_SHARD_INIT]
GO
/****** Object: Table [dbo].[UniqueKills] Script Date: 04/11/2012 23:06:39 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[UniqueKills](
[CharName16] [varchar](20) NOT NULL,
[Monster] [varchar](50) NOT NULL,
[Timestamp] [varchar](12) NOT NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
Creating the connection file:
Now we'll need to establish a connection to the SQL server.
I've created a PHP class specifically for the sqlsrv php extension to make basic queries and connections easier... It contains a few functions that isn't being used for the unique reader, but it might be useful for your other applications / web interface.
Put this in a separate file (config.php) and edit the username/password/database names (inside the __construct function) to match your sql server info
PHP Code:
<?php
// By Flowlance (danny@leetcake.net)
class mssql {
var $shard;
var $account;
var $params;
function __construct() {
// Database username
$id = "username";
// Database password
$pw = "password";
// Shard and Account database names
$con_shard = array("UID"=>$id, "PWD"=>$pw, "Database"=>"SRO_VT_SHARD_INIT");
$con_account = array("UID"=>$id, "PWD"=>$pw, "Database"=>"SRO_VT_ACCOUNT");
// Shard And Account hostname / Connection
$this->shard = sqlsrv_connect("localhost", $con_shard) or die( print_r( sqlsrv_errors(), true));
$this->account = sqlsrv_connect("localhost", $con_account) or die( print_r( sqlsrv_errors(), true));
$this->params = array("Scrollable" => SQLSRV_CURSOR_KEYSET);
}
function __destruct() {
sqlsrv_close($this->shard);
sqlsrv_close($this->account);
}
public function db($db) {
switch($db) {
case 'SHARD':
return $this->shard;
break;
case 'ACCOUNT':
return $this->account;
break;
}
}
public function query($db, $query, $values=array()) {
$db = $this->db($db);
$sql = sqlsrv_query($db, $query, $values, $this->params);
if($query === false )
return "Query error";
else
return $sql;
}
public function fetch($query) {
return sqlsrv_fetch_array($query);
}
public function get_num($db, $query) {
$db = $this->db($db);
$sql = sqlsrv_query($db, $query, array(), $this->params);
if($query === false )
return var_dump(sqlsrv_errors());
else
return sqlsrv_num_rows($sql);
}
public function num($query) {
return sqlsrv_num_rows($query);
}
public function last($query) {
sqlsrv_next_result($query);
sqlsrv_fetch($query);
return sqlsrv_get_field($query, 0);
}
public function safe($data) {
if ( !isset($data) or empty($data) ) return '';
if ( is_numeric($data) ) return $data;
$non_displayables = array(
'/%0[0-8bcef]/',
'/%1[0-9a-f]/',
'/[\x00-\x08]/',
'/\x0b/',
'/\x0c/',
'/[\x0e-\x1f]/'
);
foreach ( $non_displayables as $regex )
$data = preg_replace( $regex, '', $data );
$data = str_replace("'", "''", $data );
return $data;
}
}
$mssql = new mssql();
?>
Setting up the parser:
And for the parser itself, put this code in a separate file (unique_reader.php) and move it anywhere outside your web root to prevent web access.
At the very top of this file, you can edit:
- Timezone (get yours here http://php.net/manual/en/timezones.php)
- The path to your config.php file above (DBCONN)
- The table name where kills are being inserted (TABLE)
- Which log types you want to scan (LOGTYPE). Can be either 'EVALOGS' or 'FATALLOGS'
- Path to the directory of your log files (LOGPATH)
- Path to where proceeded log files are moved (DONEDIR). This folder will be created automatically in your log directory.
Do not forget the backslashes after a directory path.
PHP Code:
<?php
// By Flowlance (danny@leetcake.net)
date_default_timezone_set("Europe/Amsterdam"); // Your server's timezone
$settings = array(
'DBCONN' => 'C:\\inetpub\\xomnhala\\config.php', // Path to db connection file
'TABLE' => 'UniqueKills', // Unique Ranking table name
'LOGTYPE' => 'EVALOGS', // "EVALOGS" or "FATALLOGS"
'LOGPATH' => 'C:\\Server\\evaLog\\', // Path to log files
'DONEDIR' => 'finished\\' // Proceeded/Scanned logs will be moved here
);
include($settings['DBCONN']);
class UniqueReader extends mssql {
var $table;
var $logptrn;
var $fileptrn;
var $path;
var $finished;
var $logfiles;
public function setVar($settings) {
$this->table = $settings['TABLE'];
$this->logfiles = $settings['LOGTYPE'];
$this->path = $settings['LOGPATH'];
$this->finished = $settings['DONEDIR'];
if($this->logfiles == "EVALOGS") {
$this->logptrn = "/\[([0-9_\-]{1,})\].*\[([a-zA-Z0-9_]{1,})\].*\[([a-zA-Z0-9_]{1,})\]/siU";
$this->fileptrn = "/^[0-9\-]{1,}\_uniquekills.txt$/";
} else {
$this->logptrn = "/([0-9_\-]{1,}\t[0-9_:]{1,})\t.*Unique\sMonster\sKilled!\sUNIQUE\[([a-zA-Z0-9_]{1,})\].*\sby\s\[([a-zA-Z0-9_]{1,})\]/siU";
$this->fileptrn = "/^[0-9\-]{1,}\_FatalLog.txt$/";
}
}
public function ts($datestr) {
if($this->logfiles == "EVALOGS") {
$split = split("_", $datestr);
$date = $split[0] . " " . str_replace("-", ":", $split[1]);
} else {
$date = str_replace("\t", " ", $datestr);
}
return strtotime($date);
}
public function match($search) {
if(preg_match_all($this->logptrn, $search, $m)) {
return $m;
} else {
return false;
}
}
public function isToday($filename) {
$split = split("_", $filename);
$date = $this->logfiles == "EVALOGS" ? date("j-n-Y") : date("Y-m-d");
if($date==$split[0]) {
return true;
}
}
public function move($v) {
if(!$this->isToday($v)) {
if(copy($this->path . $v,$this->path . $this->finished . $v)) {
unlink($this->path . $v);
}
}
}
public function folder_check() {
if(!file_exists($this->path . $this->finished)) {
mkdir($this->path . $this->finished);
}
}
public function scan() {
$this->folder_check();
$files = scandir($this->path);
foreach($files as $v) {
if(preg_match($this->fileptrn, $v)) {
$file = fopen($this->path . $v, "r");
while($line = fgets($file)) {
$cont = $this->match($line);
if($cont!=false) {
$exists = $this->get_num("SHARD", "SELECT * FROM ".$this->table." WHERE CharName16='".$this->safe($cont[3][0])."' AND Monster='".$this->safe($cont[2][0])."' AND Timestamp='".$this->ts($cont[1][0])."'");
if($exists==0) {
$params = array($this->safe($cont[3][0]), $this->safe($cont[2][0]), $this->ts($cont[1][0]));
$this->query("SHARD", "INSERT INTO ".$this->table." (CharName16, Monster, Timestamp) VALUES (?, ?, ?)", $params);
}
}
}
fclose($file);
$this->move($v);
}
}
}
}
$reader = new UniqueReader();
$reader->setVar($settings);
$reader->scan();
?>
Running the program on a scheduled task:
- Start -> Search for "taskschd.msc"
- Click Create Task at the right hand side
General Tab:
- Name it whatever you want
- Under security options, check "Run whether user is logged on or not"
Triggers Tab:
- Begin the task: On a schedule
- Settings: Daily
- Recur every: 1 days
- Repeat task every: 5 minutes (or how often you want the rankings to be updated)

Actions Tab:
- Action: Start a program
- Program/Script (Modify this to your php path): "C\PHP\v5.3\php.exe"
- Arguments (Modify this to your unique_reader.php path: C:\cronjobs\unique_reader.php

Basic output page: (somefile.php)
- Just edit the config.php location at the top, and add which uniques you want to allow to the array. You can also edit their display names here.
PHP Code:
<?php
include("config.php");
// Add uniques you want to allow / display in the following array:
$allowed = array(
'MOB_CH_TIGERWOMAN' => 'Tiger Girl',
'MOB_OA_URUCHI' => 'Uruchi',
'MOB_KK_ISYUTARU' => 'Isyutaru',
'MOB_TK_BONELORD' => 'Lord Yarkan',
'MOB_EU_KERBEROS' => 'Cerberus',
'MOB_AM_IVY' => 'Captain Ivy',
'MOB_RM_TAHOMET' => 'Demon Shaitan'
);
foreach($allowed as $k => $v) {
$uniques = empty($uniques) ? "" : $uniques . ",";
$uniques .= "'" . $k . "'";
}
echo "Top 15 Unique Killers:<br />\n";
$query = $mssql->query("SHARD", "SELECT TOP 15 CharName16, COUNT(*) AS Kills FROM UniqueKills WHERE Monster IN(".$uniques.") GROUP BY CharName16 ORDER BY Kills DESC");
while($row=$mssql->fetch($query)) {
echo $row['CharName16'] . " - " . $row['Kills'] . " kills<br />\n";
}
echo "Last 15 Unique Kills:<br />\n";
$query = $mssql->query("SHARD", "SELECT TOP 15 * FROM UniqueKills WHERE Monster IN(".$uniques.") ORDER BY Timestamp DESC");
while($row=$mssql->fetch($query)) {
echo $row['CharName16'] . " killed " . $allowed[$row['Monster']] . " at " . date("d-m-Y H:i", $row['Timestamp']) . "<br />\n";
}
?>
You should be all set.
If you need any help with setting it up / using the functions of the mssql class / other stuff.. Let me know,
Cheers