* 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");
}
?>