Skip to content

Instantly share code, notes, and snippets.

@beastaugh
Created April 10, 2010 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beastaugh/362061 to your computer and use it in GitHub Desktop.
Save beastaugh/362061 to your computer and use it in GitHub Desktop.
Manages a Company of Heroes .REC (replay) file
<?php
/*
This file was written by Corsix, no copyright is claimed.
The file is in the public domain; do with it what you wish.
Updated November 16th 2006: Updated documentation and example code, patch 1.3 support, bugfixes
10 April 2010 - Benneb: Made script compatible with COH patch 2.601
*/
// Manages a Company of Heroes .REC (replay) file
/*
Create an instance of this class, then call loadFile() to parse a replay.
See example.php for an example of it in use
*/
class CRecFile
{
// ** Class variables (read them after loading a replay file with loadFile() )
// The version number of the replay file format
/*
CoH 1.2 used a version of 2
CoH 1.3 is using a version of 4
*/
var $m_replayVersion;
// The name of the file loaded
var $m_fileName;
// The date and time the replay was recorded (local date/time of the recording user as a string)
var $m_localDate;
// A currently unknown date
var $m_unknownDate;
// The name given to the replay by the user who recorded it
var $m_replayName;
// SLOC, RSST and VPTK are stored in this array
/*
m_otherVariables["SLOC"] - Starting location (0 or 1. One of those is Random, the other is Set - we dont know which)
m_otherVariables["RSST"] - Starting resources (0 or 1. One of those is Quickstart, the other is standard)
m_otherVariables["VPTK"] - Related to the VP ticker
*/
var $m_otherVariables;
// The name of the mod being run ("RelicCOH" is vanilla CoH)
var $m_modName;
// The name of the map being played (can be a Locale / .dat file reference number)
var $m_mapName;
// The blurb / description of the map being played (can be a Locale / .dat file reference number)
var $m_mapDescription;
// The filename of the map being played (eg. "DATA:scenarios\mp\2p_semois" - look in the SGA files)
var $m_mapFileName;
// The width of the map being played?
var $m_mapWidth;
// The height of the map being played?
var $m_mapHeight;
// An array storing player information
/*
Begins at 0 and goes up to $m_playerCount - 1.
m_players[0]["name"] - The name of this player
m_players[0]["race"] - The "race" of this player (eg. allies or axis)
race will be either "Allied Rifle Company" or "Axis Infantry Company" (the second part does NOT mean which command tree was picked)
*/
var $m_players;
// Is parser outputting debug information?
/*
Set to true and the parser will echo debugging information
Set to false and the parser will not
This does NOT indicate if CoH was being run in debug/dev mode
*/
var $m_debugMode;
// ** Internally used I/O functions
// Reads a 4 byte unsigned integer
/*
Used internally by the class to read a C/C++
"unsigned long" (a 4 byte unsigned integer)
from an open file
$fh - the file handle from which to read
returns - returns the value read; has no error return
*/
function read_UL4($fh)
{
$d = fread($fh, 4);
$a = unpack("Vn", $d);
return $a["n"];
}
// Reads a unicode string of specified length
/*
Used internally by the class to read a C/C++
array of "wchar_t" (a 2 byte character)
from an open file
$fh - the file handle from which to read
$len - the number of charatcers to read
returns - returns the value read; has no error return
*/
function read_unistr($fh, $len)
{
$s = "";
for($i = 0; $i < $len; ++$i)
{
$c = fread($fh, 1);
if(ord($c) != 0) $s .= $c;
fseek($fh, 1, SEEK_CUR);
}
return $s;
}
// Reads a string length and then the string
/*
Used internally by the class to read a 4 byte unsigned integer
and then read a string that many characters in length
$fh - the file handle from which to read
returns - returns the string read (not the length read); has no error return
*/
function read_lenstr($fh)
{
$str_length = $this->read_UL4($fh);
$s = fread($fh, $str_length);
return $s;
}
// Reads a string length and then the unicode string
/*
Used internally by the class to read a 4 byte unsigned integer
and then read a unicode string that many characters in length
$fh - the file handle from which to read
returns - returns the unicode string read (not the length read); has no error return
*/
function read_lenwstr($fh)
{
$str_length = $this->read_UL4($fh);
$s = $this->read_unistr($fh, $str_length);
return $s;
}
// ** loadFile() Helper functions
// Reads a "chunk" of a Relic chunky file
/*
$fh - the file handle from which to read
$level - 0 is a top level chunk, 1 is a chunk within a top level folder, 2 is a chunk within a level 1 chunk, etc.
returns - true if it read a chunk, false if the end of file was reached
*/
function parseChunk($fh, $level)
{
$chunkType = fread($fh, 8);
if( $this->m_debugMode )
{
for($i = 0; $i < $level; ++$i) echo "-";
echo $chunkType;
}
if(strncmp($chunkType,"FOLD",4) != 0 && strncmp($chunkType,"DATA",4) != 0)
{
if( $this->m_debugMode ) echo "\n";
fseek($fh, -8, SEEK_CUR);
return false;
}
$chunkVersion = $this->read_UL4($fh);
$chunkLength = $this->read_UL4($fh);
$chunkNameLength = $this->read_UL4($fh);
fseek($fh, 8, SEEK_CUR);
$chunkName = $chunkNameLength > 0 ? fread($fh, $chunkNameLength) : "";
if( $this->m_debugMode ) echo " V:" . $chunkVersion . "; L: " . $chunkLength . "; N:" . $chunkName . ";\n";
$startPosition = ftell($fh);
if(strncmp($chunkType,"FOLD",4) == 0)
{
while(ftell($fh) < ($startPosition + $chunkLength) ) $this->parseChunk($fh, $level + 1);
}
else
{
if($chunkType == "DATASDSC" && $chunkVersion == 2004)
{
$unknown = $this->read_UL4($fh);
$this->m_unknownDate = $this->read_lenwstr($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$this->m_modName = $this->read_lenstr($fh);
$this->m_mapFileName = $this->read_lenstr($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$this->m_mapName = $this->read_lenwstr($fh);
$unknown = $this->read_UL4($fh);
$this->m_mapDescription = $this->read_lenwstr($fh);
$unknown = $this->read_UL4($fh);
$this->m_mapWidth = $this->read_UL4($fh);
$this->m_mapHeight = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
}
else if($chunkType == "DATABASE" && $chunkVersion == 11)
{
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$variableCount = $this->read_UL4($fh);
for($i = 0; $i < $variableCount; ++$i)
{
$variableValue = $this->read_UL4($fh);
$variableName = strrev(fread($fh, 4));
$this->m_otherVariables[$variableName] = $variableValue;
}
$unknown = fread($fh, 1);
$this->m_replayName = $this->read_lenwstr($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
}
else if($chunkType == "DATAINFO" && $chunkVersion == 6)
{
$currentPlayer = count($this->m_players);
$this->m_players[$currentPlayer]["name"] = $this->read_lenwstr($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
$this->m_players[$currentPlayer]["race"] = $this->read_lenstr($fh);
$unknown = $this->read_UL4($fh);
$unknown = $this->read_UL4($fh);
}
fseek($fh, $startPosition + $chunkLength, SEEK_SET);
}
return true;
}
// Reads a Relic Chunky section from a REC file
/*
$fh - the file handle from which to read
returns - true on success, a string value on error
*/
function parseChunky($fh)
{
$chunkyHeader = fread($fh, 16);
if(strncmp($chunkyHeader, "Relic Chunky", 12) != 0)
{
return "Chunky header \'" . $chunkyHeader . "\' is invalid";
}
$version = $this->read_UL4($fh);
if($version != 3)
{
return "Chunky version " . $version . " not supported";
}
$unknown = $this->read_UL4($fh);
$chunkHeaderLength = $this->read_UL4($fh);
fseek($fh, $chunkHeaderLength - 28, SEEK_CUR);
while($this->parseChunk($fh, 0));
return true;
}
// ** Class functions
function CRecFile()
{
$this->m_debugMode = false;
$this->m_replayVersion = -1;
$this->m_fileName = "";
$this->m_localDate = "";
$this->m_unknownDate = "";
$this->m_replayName = "";
$this->m_modName = "";
$this->m_mapName = "";
$this->m_mapDescription = "";
$this->m_mapFileName = "";
$this->m_mapWidth = -1;
$this->m_mapHeight = -1;
$this->m_players = array();
$this->m_otherVariables = array();
}
// Loads a REC file
/*
$fileName - the name of a file to open
returns - true on success, a string value on error (use $ret === true to test for success or failure)
*/
function loadFile($fileName)
{
$this->m_fileName = $fileName;
$handle = fopen($fileName, "rwb");
if($handle)
{
$this->m_replayVersion = $this->read_UL4($handle);
if($this->m_replayVersion != 7) // 7 = 2.6 patch
{
fclose($handle);
return "Replay version " . $this->m_replayVersion . " not supported";
}
$fileHeader = fread($handle, 8);
if($fileHeader != "COH__REC")
{
fclose($handle);
return "File header \'" . $fileHeader . "\' is invalid";
}
$this->m_localDate = $this->read_unistr($handle, 16);
$unknown = $this->read_unistr($handle, 16);
$ret = $this->parseChunky($handle);
if($ret == true) $ret = $this->parseChunky($handle);
fclose($handle);
return $ret;
}
else
{
return "Could not open file \'" . $fileName . "\'";
}
}
}
?>
<?php
/*
The file is generated from Relic's CoH reteail 1.3 english locale files.
It is used under "fair use" laws - it is NOT public domain.
*/
$mapStrings = array();
$mapStrings["$60936"] = "The pre-invasion bombing of Pointe du Hoc has left ample craters and cover for the wise commander to utilize. This area has seen heavy combat due to German counter-attacks, Axis brass understanding the strategic value of the beach head. Controlling the beach head will secure supplies, but the difficulty of maintaining a line to that beachhead makes it hard to predict whether it?s the Allied defenders or the Axis on the counter-offensive who carry the advantage.";
$mapStrings["$60937"] = "Nestled in the large valley a small rural community has been torn by war. The river that runs through this community was once a place where children and adults alike would fish or wet their feet on a hot summer day, but now it is nothing more than a natural barrier that has led to stalemate after stalemate between the Allied and Axis forces stationed in the area. It has been told that the commander who takes this valley once and for all is to receive commendations of the highest order.";
$mapStrings["$60938"] = "There is no obstacle in France that favors the defense more then the Hedgerows of Normandy. A cunning commander can control these small rectangular compartments with as little as an anti-tank emplacement and a couple of machine guns; formidable obstacles, at least until the Artillery comes. \"...they say finding your way though the Hedgerows in 1944 is Nature's Way of instituting 'Survival of the fittest'... god I hope we are the fittest.\"";
$mapStrings["$60939"] = "The small farming community of Angoville has the potential to be the staging ground of a short but bloody battle. Open fields in the west would allow the swift movement of large squadrons unhindered by the terrain. Medics pray that the shelter found on the eastern end would be enough to hold off an Axis Offensive that would surely prove fatal for the wounded too weak for transportation. The farmhouses to the east have been long abandoned, farmers fearing the impending conflict.";
$mapStrings["$60940"] = "\"... and our jump was off, that's for sure. Sergeant Richards says we're outside the town of Lyon - an urban stronghold for the Germans in this region. When we managed to creep up close enough to get a look at the city though, seems that may have changed. About one-third of the town is in rubble and no sign of the enemy. We setup our HQ in...\" Partially destroyed city blocks make urban tactics a must in Lyon.";
$mapStrings["$60954"] = "\"... looking back, I recall asking my Captain why command was so interested in this little town, what possible reason could there be for taking it. I'll never forget the answer he gave me - \"Because Jerry wants it.\" The combination of rural countryside and natural chokepoints means victory will go to the side who can best exploit the landscape.";
$mapStrings["$60955"] = "The industrial complex of Sturzdorf is the primary supplier of the 88mm barrels mounted on the formidable Tiger Tanks, making it an ongoing strategic concern. Beyond this, Oberst Shmitt is extremely preoccupied with losing the large paint factory located in Sturzdorf, as it provides the Puce required for his special camouflage unit. Oberst Shmitt has given strict orders that any soldier who allows the paint factory to fall into Allied hands will be executed.";
$mapStrings["$60956"] = "\"Far be it from me, a lowly grunt, to question command's decisions - but if I were to, I would definitely want to know what the tactical importance of a leveled courtyard in the middle of hedgerow country is. Someone needs to remind the brass that those of us on the frontlines aren't as gung-ho to throw our lives away for some rocks and rubble as they are.\" Rows of Hedgerows and Structures make for effective firing lanes.";
$mapStrings["$60957"] = "\"I remember this town the day we arrived - streets packed with children playing, markets buzzing with activity in the warm midday sun. Now it's no more than a graveyard of rubble and ash; a burial plot for the innocence of mankind.\" A heavily destroyed urban landscape, Lorraine favors close-quarters combat amongst the ruins.";
$mapStrings["$60958"] = "Allied planes shot down had the inadvertent pleasure of taking out a main Axis supply shipment as they crashed to the ground. The order has been given to salvage what was left of that shipment, Axis officials hoping the roads running east to west will provide swift access to the wreckage. It has been advised that if a skirmish breaks out to utilize the eastern logging camp and surrounding forests for troop cover, and garrison the western civilian buildings to halt an Allied advance.";
$mapStrings["$60959"] = "The heavy industrial capacity provided by St.Hilaire made it a critical strategic target for both the Axis and Allied forces. Factories bordering the railway line produced much needed munitions and materiel to support the war effort, and a small port permitted the shipping of fuel resources to Axis supply depots along the river. Marshlands to the West proved a daunting challenge for infantry, forcing troop and vehicle movement towards the center of the town, where the heaviest fighting occurred.";
$mapStrings["$60960"] = "\"Command has expressed their frustration time and time again over this mound of dirt. The ridge itself has been changing hands non-stop over the last month and a half - there's not much left up there other than piles of dirt, sandbags and the occasional tank carcass. This is not going to be easy.\" Fierce, king-of-the-hill style combat dominates this map. The team that can hold the three hills will enjoy a decisive advantage.";
$mapStrings["$60961"] = "Few crossings over the Meuse River are as important as Montherme. This urban center provides numerous access roads leading inland through the thick forests that surround the rugged valley the city is constructed on. While the city dominates most of the area, savvy commanders would be wise to make use of the few trails through the forests towards the top of the ridge; perfect for avoiding the clustered streets of the town.";
$mapStrings["$60962"] = "The Region around Montargis is carved out of the rugged and hilly landscape, surrounded by a large pine forest. Several industrial sectors can be found, with various complexes connected and supporting one another. The access to these complexes and the abundance of resources make the area a region of great strategic import. \"If the Capture of Montargis is not possible, then its destruction would be the best way of denying our enemy\".";
$mapStrings["$60963"] = "Route N13 is an important supply and troop transport road that runs thought the middle of Sainte-M?re-?glise. Between the hedgerows and stone walls of the small urban setting lies a devastating strategic advantage: a direct route into northern France from the Utah and Omaha beach heads. The Commanders that control route N13 are the Commanders that control the war.";
$mapStrings["$60966"] = "Near the mouth of the SeineRiver lies Port-J?r?me, a small developing port with a large quantity of industrial resources. Both Allied and Axis officers have identified Port-J?r?me as a significant strategic location for controlling the flow of traffic along the SeineRiver, as well as controlling movement along the mainland. The importance of the docks and bridges destined to convert this once bustling dockside community into nothing but debris.";
$mapStrings["$60916"] = "Pointe du Hoc (4)";
$mapStrings["$60917"] = "Vire River Valley (4)";
$mapStrings["$60918"] = "Hedgerow Siege (6)";
$mapStrings["$60964"] = "Angoville (2)";
$mapStrings["$60965"] = "Lyon (2)";
$mapStrings["$60967"] = "Semois (2)";
$mapStrings["$60968"] = "Sturzdorf (2)";
$mapStrings["$60969"] = "McGechaen's War (4)";
$mapStrings["$60970"] = "Lorraine (4)";
$mapStrings["$60971"] = "Rails and Metal (4)";
$mapStrings["$60972"] = "Hill 331 (6)";
$mapStrings["$60973"] = "Montherme (6)";
$mapStrings["$60974"] = "Montargis Region (8)";
$mapStrings["$60975"] = "Route N13 (8)";
$mapStrings["$60976"] = "St.Hilaire (4)";
$mapStrings["$60977"] = "Seine River Docks (6)";
?>
@maboelnour
Copy link

Is there any documentation for .rec file format?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment