aboutsummaryrefslogtreecommitdiffstats
path: root/inc/savegame.inc
blob: 3c5613b8115752d5ebd772b94d0932209ebc047d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
<?php
/**
 * Load and save the game.
 *
 * @author     Alexandre Renoux
 * @author     Pierre-Emmanuel Novac
 */

require_once("inc/messages.inc");
require_once("inc/Inventory.inc");
require_once("inc/Item.inc");

/**
 * Directory to save to and load from.
 * The PHP/Apache user must have write permission on this directory.
 * Use the following commands to give write permissions to the http group:
 * <samp>
 * sudo chown :http data/save
 * sudo chmod g+w data/save
 * </samp>
 */
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("<save/>");
	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("<save/>");
	genXML($table, $save); // Regenerate it
	if($save->asXML(SAVEDIR."/".$fname)) sendInfo("upload_success", array($fname));
	else sendError("upload_error");
}
?>