* sudo chown :http data/save * sudo chmod g+w data/save * */ define("SAVEDIR", "data/save"); /** * Recursively generates an XML tree from an array. * If objects are found, call addToXML() methods which should serialize the object as a child of the XML tree passed as parameter. * * @param array $table an array with mixed type elements to convert to XML * @param SimpleXMLElement $xml root XML to add the elements to * @return void */ function genXML($table, $xml) { foreach($table as $k => $v) { if(is_object($v)) { // Object: either Item or Inventory if(is_callable(array($v, "addToXML"))) // Check if the object has a addToXML method $v->addToXML($xml->addChild($k)); } elseif(is_array($v)) // Nested array genXML($v, $xml->addChild($k)); else // Single value (numeric or string) $xml->addChild($k, $v); } } /** * Generates the XML save tree from the session. * * @return void */ function genSave() { $save = new SimpleXMLElement(""); genXML($_SESSION, $save); return $save; } /** * Generates the save file name using current date/time. * The date function will use the server's timezone which could be inconsistent with the client timezone. * * @return string the generated file name */ function genFilename() { return "craftmine-".date("d-m-Y_H-i-s").".save.xml"; } /** * Save the XML save tree as an XML file named after the results of genFilename in SAVEDIR. * Fails and send an error the the client if permissions are incorrectly set. Watch for errors in PHP log. * * @return void */ function saveGame() { $save = genSave(); if($save->asXML(SAVEDIR."/".genFilename())) sendInfo("gamesave_ok"); else sendError("gamesave_error"); } /** * Sends the current game or a specific save file given by the filename GET parameter to the client. * * @return void */ function downSave() { $save = ""; $filename = ""; if(empty($_GET["filename"])) { $filename = genFilename(); $save = genSave()->asXML(); // Send current game save file if no file specified } else { $filename = $_GET["filename"]; $save = file_get_contents(SAVEDIR . "/" . $filename); if(empty($save)) return; // Probably file not found } header("Content-Type: application/xml"); // Force browser to intepret the file as XML header("Content-Disposition: attachment; filename=".$filename); // Force download with specific filename header("Cache-Control: no-cache"); // Avoid storing save file in proxy/browser cache echo $save; } /** * Sends the list of available saves to the client. * Warning: this function changes directory. * * @return void */ function listSaves() { chdir(SAVEDIR); // Go to SAVEDIR folder, avoiding leading folder name in file list echo json_encode(glob("*.save.xml")); } /** * Reads an XML tree and deserializes it to an array. * * @param SimpleXMLElement $xml root XML to read the elements from * @param array $table an empty array to stores the elements to, passed by reference * @return void */ function parseSave($xml, &$table) { // Passing $table by reference foreach($xml as $k => $v) { if($v->count() == 0) { // No child, treat as string $v = (string)$v; if(is_numeric($v)) $v = +$v; // If it is in fact a number, treat it that way using PHP unary '+' coercion $table[$k] = $v; } elseif($k == "inventory") { // Special case for inventory: objects need to be created foreach($v as $item) { for($i=0; $i<+$item->count; $i++) // Add the right count of items to Inventory Inventory::addItem(Item::fromXML($item)); } } else { // If nested array $table[$k] = array(); parseSave($v, $table[$k]); } // Other types unsupported (unused) } } /** * Deletes a save file given as the filename POST parameter. * * @return void */ function deleteSave() { if(empty($_POST["filename"])) return; $path = SAVEDIR . "/" . basename($_POST["filename"]); // remove any leading directory if(file_exists($path) && unlink($path)) sendInfo("gamesave_delete_success"); else sendError("gamesave_delete_fail"); } /** * Loads a save file given as the filename POST parameter to the session. * Empties the session beforehand. * * @return void */ function loadSave() { if(empty($_POST["filename"])) return; $xml = simplexml_load_file(SAVEDIR . "/" . $_POST["filename"]); if(empty($xml)) { sendError("gamesave_not_found"); return; } $_SESSION = array(); // drop current game parseSave($xml, $_SESSION); } /** * Reads a save file sent by the client. * Parse the received file then generate it again to clean it for any unwanted * and make sure that it is a valid XML save file. * XML errors are simply ignored and an error is sent to the client if something wrong happens. * * @return void */ function uploadSave() { $fname = basename($_FILES['savefile']['name']); $src = $_FILES['savefile']['tmp_name']; libxml_use_internal_errors(true); // Ignore errors when loading the received file $xml = simplexml_load_file($src); libxml_use_internal_errors(false); if(!$xml) { sendError("upload_fail"); return; } $table = array(); parseSave($xml, $table); // Parse received file $save = new SimpleXMLElement(""); genXML($table, $save); // Regenerate it if($save->asXML(SAVEDIR."/".$fname)) sendInfo("upload_success", array($fname)); else sendError("upload_error"); } ?>