Quoted
php
//****************************************************************************************************************
// AoM Rec Game Decoder v0.3 beta
//
// ReturnRecGameXml - opens the file and if its a valid AoM rec game returns an xml file with the information
// ParseRecGameXml - Parses the xml file into a more readable form, call a print_r() on the result to see
// how it looks like
//
// This code is distributed as is. There is no waranty that it works and no waranty that it will still work in the future.
// Use it on your on risk!!!
// special thanx go out to Aleste from the Planet Age of Mythology Forum for telling me how to read the rec game
// this PHP Code is (c) 07.2003 by R. Vornholt - You can use it freely as long as this information stays.
// If you want to contact me send me a mail to doc_santonian@die-ohne-clan.de or visit my website at
// http://www.die-ohne-clan.de. If you like the code, visit at least the website and write a few lines at the forum
// or into the guestbook.
// CHANGELOG
//
// - 30.09 - Added support for The Titans Expansion
// - 18.12 - Added support for REALCIVILIZATION attribute in array (cubensis)
//****************************************************************************************************************
//XML DESCRIPTION: (I could not determin the use of a few values, if you can tell me what these values are for, please tell me)
//
// FILENAME: name of the map
// CURRENTPLAYER: Id of the player to whom this rec belongs
// - In SinglePlayer it's always -1
// SCENARIOFILENAME:
// GAMETYPE: always 2
// FILENAMECRC: CRC
// GAMESTARTTIME Starttime of game in Seconds - can be used in PHP date function
// - In Singleplayer its always 0
// MAPVISIBILITY: 0 => Normal, 1 => Revealed
// WORLDRESOURCES: it's always 1, is there a way to change it anyway?
// MAPSIZE: 0 => Normal Map, 1 => Large Map
// RESTRICTEDPAUSES: 0 => Unlimited, else the number of pauses
// GAMEMODE: 0 => Supremacy, 1 => Conquest, 2 => Ligthning, 3 => Deathmatch
// HANDICAPMODE: 0 => No Handicap, 1 => Handicap Free, 2 => Automatic
// - I guess that if the Handicap is 2 it's a rated match
// MAPSEED The Random Seed value to use to generate the same map from the RMS
// DIFFICULTY: 0 => Easy, 1 => Moderate, 2 => Hard, 3 => Titan
// NUMPLAYERS: No. of players
//
// GAMEFLAGS: LOCKEDTEAMS, ALLOWCHEATS, TEAMSHARERESOURCES, TEAMSHAREPOPULATION
// - They have no values afak, they exist or they don't, if they exist they are switched on
//
// PLAYER:
// CLIENTID: Player ID starting with 0
// CONTROLLEDPLAYER Player ID starting with 1
// NAME: Player Name
// - If Player is AI it's the type of AI (Balanced, Boomer, ...)
// RATING: Rating of that player at gametime in that gamemode
// - in Singleplayer it's always 500 for all player
// - in LAN Games it's always 0 for all players
// TYPE: 0 => HumanPlayer, 1 => AI Player, 4 => Observer (Titans only)
// TRANSFORMCOLOR1:
// TRANSFORMCOLOR2:
// TEAM: Team Info
// - In 1vs1 Rated Games both players have 255 as team
// - 255 is Random Team!!!
// CIVILIZATION (AoM): 0 => Zeus, 1 => Poseidon, 2 => Hades,
// 3 => Isis, 4=> Ra, 5 => Set,
// 6 => Odin, 7 => Thor, 8 => Loki,
// 9 => Random All,
// 10 => Random Greek, 11=> Random Norse, 12 => Random Egyptian
//
// CIVILIZATION (Titans): 0 => Zeus, 1 => Poseidon, 2 => Hades,
// 3 => Isis, 4=> Ra, 5 => Set,
// 6 => Odin, 7 => Thor, 8 => Loki,
// 9 => Cronos, 10 => Oranos, 11 => Gaia,
// 12 => Random All, 13 => Random Greek
// 14 = > Random Norse, 15 => Random Egyptian, 16 => Random Atlantean
// AIPERSONALITY: name of Computer AI
//
//
//===============================================================================================================
//
// EXTRA INFORMATION CONTAINED IN THE RECORDED GAME FILE
// -----------------------------------------------------
//
// further down in the file there comes more information but in a binary form. This is what I found out so far:
//
// Team Information:
//
// (the xml team info is 255 for all in random team)
//
// look for the string "Team #" then it goes like this:
//
// Team #ABCCCDCCCDCCCD
//
// A -> Team number in ascii
// B -> no. of players in team in binary
// C -> null value
// D -> playerid in binary
//
// the CCCD block loops B times
//
// The other bytes in the team info I could not (yet) figure out.
//
// Civilisation:
//
// After the team info there is info about each player. Look for the name and then the 11th byte is the civ id
// in binary. If someone chose random team here is the civ he got in the end.
//****************************************************************************************************************
function ReturnRecGameXml($fileName)
{
define( "GAME_SETTINGS_BLOCKSIZE", 0x400 );
define( "GAMESETTINGSOFFSET", 0x59A );
define( "TITAN_ADDITIONAL_SETTINGS_OFFSET", 0x28 );
define( "GAME_SETTINGS_STARTTAG", ASCIItoUTF16( '<GameSettings>' ) );
define( "GAME_SETTINGS_ENDTAG", ASCIItoUTF16( '</GameSettings>' ) );
global $_REAL_CIV;
$xml="";
$xPack=0; //0-> AoM, 1->Titans
$fp = fopen($fileName, "r");
//---- Make sure the file opened
if($fp == null) return false;
//---- Check the header bytes are correct = 'l33t'
$header = fread($fp, 4); //its a 4 byte header
if($header != "l33t") return false;
//---- See how many bytes the uncompressed data will expand to
$sizeBytes = fread($fp, 4);
$size = (int) ord($sizeBytes[0]) //get original file size
+ (ord($sizeBytes[1]) <<![]()
+ (ord($sizeBytes[2]) << 16)
+ (ord($sizeBytes[3]) << 24);
$compressedData = fread($fp, filesize($fileName)-8);
$uncompressed = gzuncompress($compressedData, $size);
//---- Check to make sure the uncompressed data is valid
if($uncompressed == null) return false;
//---- See if we can extract the XML data from the random mess
$uc_nullfree = substr($uncompressed, GAMESETTINGSOFFSET);
//---- Check of this as an AoM game, or an AoT game
if(strpos($uc_nullfree, GAME_SETTINGS_STARTTAG) == 2)
$xPack = 0;
else
{
$uc_nullfree = substr($uc_nullfree, TITAN_ADDITIONAL_SETTINGS_OFFSET);
if(strpos($uc_nullfree, GAME_SETTINGS_STARTTAG) == 2)
$xPack = 1;
else
return false;
}
$final = "";
$pos = 0;
$end = strpos($uc_nullfree, GAME_SETTINGS_ENDTAG);
$skipSize = 4;
while($pos < $size)
{
$final .= substr($uc_nullfree, $pos, GAME_SETTINGS_BLOCKSIZE);
$pos += GAME_SETTINGS_BLOCKSIZE + $skipSize;
if($pos > $end) //there was a skip after the xml where no skip should be... this should work
$skipSize=0;
}
//---- Extract the XML from the data
$end = strpos($final, GAME_SETTINGS_ENDTAG);
$xml = substr($final, 2, $end-2);
//---- Convert the UTF-16 UNICODE into 8 bit ASCII
$xml = UTF16toASCII($xml);
//---- BINARY FIXES
//-- This finds the Actual Team # for the players, no 255 in here!!
$start = strpos($final, "Team #", 0);
$first_start = $start;
while($start !== false)
{
$teamstr = substr($final, $start, 100);
for($m=0; $m<ord($teamstr[7]); $m++)
{
$xml .= "\t<REALTEAM TeamId=\"".($teamstr[6]-1)."\" PlayerId=\"".(ord($teamstr[7 + (4*($m+1))])-1)."\" ></REALTEAM>\n";
}
$start = strpos($final, "Team #", $start+6);
};
//-- We can't actually extract the player names yet, until we have XML decoded
//-- so we have to store the REALCIVILIZATION memory block in a global for now
//-- Grab too much, we can refine it later.. (cubensis)
if( $first_start !== false ) $_REAL_CIV = substr( $final, $first_start, 4000 );
//---- Format the XML, with the inserted tags
$ret_xml = "<GameSettings>\n";
$ret_xml .= "\t<XPACK>".$xPack."</XPACK>\n";
$ret_xml .= substr($xml, strlen("<GameSettings>"));
$ret_xml .= "</GameSettings>";
return($ret_xml);
}
function startElementHandler($parser, $name, $attribs)
{
global $xmldata;
global $i;
global $lastName;
global $playerStart;
$lastName = $name;
if($name == "GAMEFLAGS")
$xmldata["$name"] = $attribs;
else
{
switch($name)
{
case "REALTEAM":
$xmldata[$name][$attribs["PLAYERID"]] = $attribs["TEAMID"];
break;
default:
if($name == "PLAYER")
{
$i++;
$playerStart = true;
}
if($playerStart === true)
{
if($name == "PLAYER")
$xmldata["$name"][$i] = $attribs;
else
$xmldata["$name"][$i] = 0;
}
else
$xmldata["$name"] = 0;
break;
}
}
}
function endElementHandler($parser, $name)
{
}
function cdataHandler($parser, $data)
{
global $xmldata;
global $lastName;
global $playerStart;
global $i;
if((ord($data) != 10) && (ord($data) != 9))
{
switch($lastName)
{
case "REALTEAM":
//do nothing
break;
default:
if($playerStart === true)
$xmldata["$lastName"][$i] = $data;
else
$xmldata["$lastName"] = $data;
break;
}
}
}
function ParseRecGameXml($xml)
{
if( $xml === false ) return false;
global $xmldata;
global $lastName;
global $i;
global $playerStart;
$xmldata = array();
$i = -1;
$playerStart = false;
$parser = xml_parser_create();
xml_set_element_handler($parser, "startElementHandler", "endElementHandler");
xml_set_character_data_handler($parser, "cdataHandler");
if (!xml_parse($parser, $xml, true))
die(sprintf("XML error %d %d", xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
return $xmldata;
}
//- (cubensis)
function UTF16toASCII( $unicode )
{
$data = "";
//---- Implemented a slightly cleaner way of doing this, which doesn't compress 0 value pairs
$i = -2;
$len = strlen( $unicode );
while( $i < $len ) $data .= $unicode[$i+=2];
return $data;
}
//- (cubensis)
function ASCIItoUTF16( $ascii )
{
$data = "";
$len = strlen( $ascii );
for( $i = 0; $i < $len; $i++ )
{
$data .= $ascii[$i];
$data .= chr(0);
}
return $data;
}
//- (cubensis)
function InsertRealCivsIntoArray( $array )
{
if( $array === false ) return false;
global $_REAL_CIV;
//---- For each player in the NAME block, try to extract their
//---- real civilization from the binary block
foreach( $array['NAME'] as $id => $name )
{
$name_search = ASCIItoUTF16($name);
$pos = strpos( $_REAL_CIV, $name_search );
$len = strlen( $name_search );
$offset = $pos + $len + 9;
$real_civ = ord($_REAL_CIV[$offset]);
$array['REALCIVILIZATION'][$id] = $real_civ;
}
return $array;
}
//- (cubensis)
function CivNameFromID( $id, $is_titans = false )
{
$titans = array(
0 => "Zeus", 1 => "Poseidon", 2 => "Hades",
3 => "Isis", 4 => "Ra", 5 => "Set",
6 => "Odin", 7 => "Thor", 8 => "Loki",
9 => "Cronos", 10 => "Oranos", 11 => "Gaia",
12 => "Random All", 13 => "Random Greek", 14 => "Random Norse",
15 => "Random Egyptian", 16 => "Random Atlantean" );
$aom = array(
0 => "Zeus", 1 => "Poseidon", 2 => "Hades",
3 => "Isis", 4 => "Ra", 5 => "Set",
6 => "Odin", 7 => "Thor", 8 => "Loki",
9 => "Random All", 10 => "Random Greek", 11 => "Random Norse",
12 => "Random Egyptian" );
return $is_titans ? $titans[$id] : $aom[$id];
}
/*
$file = 'C:\Documents and Settings\andy\My Documents\My Games\Age of Mythology\Savegame\Recorded Game 3.rcx';
//$file = 'C:\Games\Microsoft Games\Age of Mythology\savegame\Recorded Game 1.rec';
$xml = ReturnRecGameXml( $file );
$array = ParseRecGameXml( $xml );
$array = InsertRealCivsIntoArray( $array );
print_r($array);
*/
?>
Quoted
ps@zelos:~/development/workspace/aoe3rectool/ant/build$ java -jar aoe3rectool.jar /home/ps/tmp/3v3_faith_wischn_lat_vs_inc_hackl_sd_vision.age3rec
Map: New England
MapExt: randommaps.set
Player 0:
Player 1: -WW- Killy
Player 2:
Player 3: -WW-würmchen
Player 4:
Player 5: LClan Inc
Player 6:
Player 7: Player 7
This post has been edited 3 times, last edit by "fast_tam" (Nov 15th 2005, 5:53pm)
Quoted
Map: New England
MapExt: randommaps.set
Number of Players: 6
Player 1: [ Dutch ] -WW- Killy
Player 2: [ Ottomans ] CF CF_Faithhealer
Player 3: [ Portuguese ] -WW- würmchen
Player 4: [ Germans ] LClan BeN_de
Player 5: [ Ottomans ] LClan Inc
Player 6: [ French ] NoX SD_VisionSLR
This post has been edited 7 times, last edit by "fast_tam" (Nov 16th 2005, 4:16pm)