20090814 Update: Switched to a new API.
20081119 Update: Tile images from http://image.xboxlive.com/ are now PNGs instead of JPEGs. (Thanks, NXE!)
Gamercards are handy for showing off how much time you waste playing Xbox games, but the cards available from Microsoft and third parties like MyGamerCard are ugly and impersonal. Luckily, anybody with a web server, a bit of PHP experience, and minimal image editing skills can create their own!
I used to recommend Xbox Live Nation, but their API seems to have disappeared. Luckily, Duncan Mackenzie has a much better one that wasn’t around when I first wrote this page.
Creating the Base: The hard part, because this is entirely up to you. Use your favorite image editor, maybe GIMP or Paint.NET, to create a base image for your custom gamertag. It should have space for your username, points, county, zone, last game played, and a blank 64×64 pixel square for your picture. Scour your computer or google for a TrueType (.ttf) font you’d like to use, and size your text areas appropriately.
Creating Files: Create a directory on your web space, upload Gamercard.php, create a blank xboxlive.xml, and upload your base image(s). Gamercard.php must have write access to xboxlive.xml. Double-check http://xboxapi.duncanmackenzie.net/gamertag.ashx?GamerTag=(yourtag) to be sure it returns the correct data, like this:
<XboxInfo> <AccountStatus>Gold</AccountStatus> <PresenceInfo> <Valid>true</Valid> <Info>Last seen 08/12/09 playing Xbox 360 Dashboard</Info> <Info2/> <LastSeen>2009-08-12T18:52:09+00:00</LastSeen> <Online>false</Online> <StatusText>Offline</StatusText> <Title>Xbox 360 Dashboard</Title> </PresenceInfo> <State>Valid</State> <Gamertag>okeeblow</Gamertag> <ProfileUrl>http://live.xbox.com/member/okeeblow</ProfileUrl> <TileUrl> http://image.xboxlive.com/global/t.fffe07c3/tile/0/280c6 </TileUrl> <Country>United States</Country> <Reputation>99.91526</Reputation> <Bio>I'm a science genius girl.</Bio> <Location>Network Neighborhood</Location> <ReputationImageUrl> http://live.xbox.com/xweb/lib/images/gc_repstars_external_20.gif </ReputationImageUrl> <GamerScore>20755</GamerScore> <Zone>Recreation</Zone> </XboxInfo>
Basic settings: At the top of Gamertag.php, fill in the file name of your XML file, the file name of your font, the name(s) of your base images, and your gamertag.
$baseDirectory = $_SERVER["DOCUMENT_ROOT"].dirname($_SERVER['PHP_SELF'])."/"; $xblXml = $baseDirectory."xboxlive.xml"; $gamertag = "okeeblow"; $font = $baseDirectory."font.ttf"; $base = "Base.png";
How it Works: Gamercard.php checks to see if our xboxlive.xml is more than ten minutes old. If so it fetches and parses a new copy. If not it parses the one we have. The script uses cURL twice, once to save xboxlive.xml and once to save the attached gamer picture provided it doesn’t already exist. fix_font() was necessary to clear up annoyances with the font I chose. Feel free to excise it.
function fix_font($text)
{
//Fix any irregularities in the font of your choice. Mine has some weird capitals,
//So I force everything to lowercase and replace the numeral 1 with a letter
//http://www.dafont.com/kaileen.font
$text = strtolower($text);
$text = str_replace("1", "l ", $text);
$text = str_replace("/", "-", $text);
return $text;
}
if (file_exists($xblXml))
{
$filetime = filemtime($xblXml);
$time = time();
$difference = ($time - $filetime);
if($difference > 600 && !$_GET["r"]) //Ten minutes (timestamp)
{
$curl = curl_init();
$localXml = fopen($xblXml, "w+");
curl_setopt($curl, CURLOPT_URL, "http://xboxapi.duncanmackenzie.net/gamertag.ashx?GamerTag=".$gamertag);
curl_setopt($curl, CURLOPT_FILE, $localXml);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_exec($curl);
curl_close($curl);
header("Location: http://".$_SERVER["SERVER_NAME"].$_SERVER["SCRIPT_NAME"]."?r=".time());
}
else
{
header("Location: " . dirname($_SERVER['PHP_SELF'])."/Gamercard.png");
}
$tagInfo = simplexml_load_file($xblXml);
$cardTag = fix_font($tagInfo->Gamertag);
$cardScore = fix_font($tagInfo->GamerScore);
$cardZone = fix_font($tagInfo->Zone);
$cardCountry = fix_font($tagInfo->Country);
$cardTile = $tagInfo->TileUrl;
$cardLiveurl = $tagInfo->ProfileUrl;
$cardLast = fix_font($tagInfo->PresenceInfo->Title);
$cardTileName = explode("/", $cardTile); //We only want "/<whatever>.png"
if(!file_exists($baseDirectory.$cardTileName[count($cardTileName) - 1]))
{
$curl = curl_init();
$localTile = fopen($baseDirectory.$cardTileName[count($cardTileName) - 1], "w+");
curl_setopt($curl, CURLOPT_URL, $cardTile);
curl_setopt($curl, CURLOPT_FILE, $localTile);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_exec($curl);
curl_close($curl);
header("Location: http://".$_SERVER["SERVER_NAME"].$_SERVER["SCRIPT_NAME"]."?r=".time());
exit();
}
Attaching to the base image: This is basically trial and error as you attempt different sizes and positions of text. The pixel numbers provided fit my Gamercard and will undoubtably be different for you. I’ve commented this as best I can, since it’s admittedly pretty dense. This setup picks a random base image from the $bases array. Refresh the page and mine will probably change!
$baseRand = array_rand($bases); $baseSize = getimagesize($baseDirectory.$bases[$baseRand]); $base = imagecreatefrompng($baseDirectory.$bases[$baseRand]); $scratch = imagecreatetruecolor($baseSize[0], $baseSize[1]); $tile = imagecreatefrompng($baseDirectory.$cardTileName[count($cardTileName) - 1]); //Pick a color! Anything! http://www.php.net/imagecolorallocate $textColor = imagecolorallocate($scratch, 110, 60, 40); //Copy the base image as the first layer of $scratch imagecopy($scratch, $base, 0, 0, 0, 0, $baseSize[0], $baseSize[1]); //Copy our icon onto its spot in the base image at 53px from left, 47px from top, 64x64 imagecopy($scratch, $tile, 53, 47, 0, 0, 64, 64); //Center our text. I measured a clean area of 112 pixels starting at 118 from the left, past the icon. $cardTagBbox = imagettfbbox(30, 0, $font, $cardTag); //Start username text at 118 from the left or (110 - halfSize) + 120 $cardTagXPos = ($cardTagBbox[4] - $cardTagBbox[6] >= 112) ? 118 : ((110 - $cardTagBbox[4] - $cardTagBbox[6]) / 2) + 120; //Measure our username at 30pt and reduce it to 20pt if it doesn't fit in 112px $cardTagFontSize = ($cardTagBbox[4] - $cardTagBbox[6] >= 112) ? 20 : 30; //Copy our LIVE username to the image imagettftext($scratch, $cardTagFontSize, 0, $cardTagXPos, 55, $textColor, $font, $cardTag); //Copy our gamerpoints to the image, 120px from left, 75px from top imagettftext($scratch, 18, 0, 120, 75, $textColor, $font, "ego: ".$cardScore); //Copy our zone to the image, 120px from left, 90px from top imagettftext($scratch, 18, 0, 120, 90, $textColor, $font, "zone: ".$cardZone); //Copy our country to the image, 120px from left, 105px from top imagettftext($scratch, 18, 0, 120, 105, $textColor, $font, $cardCountry); //The clean area is 182 pixels wide starting at 50 from the left $cardLastBbox = imagettfbbox(18, 0, $font, "Last: ".$cardLast); //Measure the last played title at 18pt and reduce it to 16pt if it doesn't fit $cardLastFontSize = ($cardLastBbox[4] - $cardLastBbox[6] >= 182) ? 16 : 18; //Copy this to 130px down at 16pt and 132px down at 18. Picky, I know. $cardLastYPos = ($cardLastBbox[4] - $cardLastBbox[6] >= 182) ? 130 : 132; //Copy our last played game to the image, 50px from the left imagettftext($scratch, $cardLastFontSize, 0, 50, $cardLastYPos, $textColor, $font, "last: ".$cardLast);
Image output and cleanup: These cache settings instruct browsers not to save the image since we want it to update constantly. Do they work? I don’t know! (Google says yes).
//Cache voodoo
header("Cache-Control: no-cache, no-store, max-age=0, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Or any other date in the past
header("Pragma: no-cache");
header("Content-type: image/png");
imagepng($scratch);
imagedestroy($scratch);
imagedestroy($tile);
The product:
Mine is pop’n music 8-styled ♥
Caveat: Some web forums/services disallow the use of a script in an [img] tag. In that case, modify imagepng() to output a file instead of to the browser, then call the script every hour or so with cURL in your crontab. Annoying, but whatever!