Early firmware update support
authorChristian Weiske <cweiske@cweiske.de>
Sun, 18 Jun 2023 06:51:01 +0000 (08:51 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Sun, 18 Jun 2023 06:52:23 +0000 (08:52 +0200)
.gitignore
README.rst
bin/prepare-firmware.sh [new file with mode: 0755]
config.php.dist
www/.htaccess
www/check.php

index fb2c68046cafc800a3ab5f0583f553fc36a75fd3..8875713bc271683db1e474a53ad583f31f089c02 100644 (file)
@@ -2,3 +2,4 @@
 /config.php
 /data/profiles.sqlite3
 /README.html
+/www/firmware/*/update.img
index 12d13d6246e39cf8597e6359c94fe8cec8812fcb..a71e12f1654847e7b6330987bdfb8dec40c9f5cb 100644 (file)
@@ -25,6 +25,16 @@ Setup
 6. Setup the Apache web server and point the virtual host to the ``www/`` directory.
 
 
+Firmware updates
+================
+Firmware update diff files are needed.
+It is possible to send out full firmware files, but they
+will reset the user data.
+
+When you have such an update, put it into ``www/firmware/$version/update.img``
+and run ``./bin/prepare-firmware.sh www/firmware/$version/``.
+
+
 About
 =====
 This server software was written by `Christian Weiske <https://cweiske.de/>`_
diff --git a/bin/prepare-firmware.sh b/bin/prepare-firmware.sh
new file mode 100755 (executable)
index 0000000..de31b89
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+# Prepare a firmware update file so it can be used for GameStick firmware updates
+set -e
+
+if ! command -v xortool-xor 2>&1 > /dev/null; then
+    echo "Error: xortool-xor not found (pip install xortool)"
+    exit 3
+fi
+if ! command -v xxd 2>&1 > /dev/null; then
+    echo "Error: xxd not found"
+    exit 3
+fi
+
+if [ $# -lt 1 ]; then
+    echo "Error: Firmware directory missing (www/firmware/x.y.z)"
+    exit 1
+fi
+
+if [ ! -d "$1" ]; then
+    echo "Error: Firmware directory does not exist"
+    exit 2
+fi
+
+fwDir=$1
+fwFile=$1/update.img
+
+if [ ! -f "$fwFile" ]; then
+    echo "Error: Firmware directory has no update.img"
+    exit 2
+fi
+
+cd "$fwDir"
+rm chunk-* || true
+rm tmp-* || true
+split --bytes=102400 --numeric-suffixes --suffix-length=4 $(basename "$fwFile") tmp-part-
+
+#xor the files with the secret key
+for partfile in tmp-part-*; do
+    #"91" (hex 5b, binary 1011011) is the xor key
+    xortool-xor -h 5b --no-newline -f "$partfile" > "tmp-xor-$partfile"
+done
+#"sign" the files by prefixing the binary sha1sum
+for xorfile in tmp-xor-*; do
+    #"91" (hex 5b, binary 1011011) is the xor key
+    num=$(echo $xorfile | sed  's/tmp-xor-tmp-part-//')
+    sha1sum "$xorfile" |cut -d' ' -f 1 | xxd -r -p > chunk-$num
+    cat $xorfile >> chunk-$num
+done
+
+rm tmp-* || true
+
+#remove leading zeros
+for num in $(seq 0 999); do
+    if [ -f "chunk-0$num" ]; then
+        mv "chunk-0$num" "chunk-$num"
+    elif [ -f "chunk-00$num" ]; then
+        mv "chunk-00$num" "chunk-$num"
+    elif [ -f "chunk-000$num" ]; then
+        mv "chunk-000$num" "chunk-$num"
+    fi
+done
+
+#those files may not contain newlines
+ls -1 chunk-* | wc -l | tr -d '\n' > numchunks
+stat --printf=%s update.img > filesize
index 913d73076c32a2aebe0619c6c7a15046423f2a9a..8205473336d602a5598b15e691ae12139cdf4b49 100644 (file)
@@ -6,6 +6,9 @@ $GLOBALS['whitelistedHardwareIds'] = [
 
 $GLOBALS['verificationCodePrefix'] = '';
 
+//offer a certain firmware version, even for downgrading
+//$GLOBALS['firmwareVersion'] = '0.9.2071';
+
 //popular games. first in array means most popular
 $GLOBALS['popular'] = [
     'de.eiswuxe.blookid'
index 7426b7e76419309621752f633251a0da70d48ada..77e123fb1fbf7b382797e638ab4a51fc9ea84ade 100644 (file)
@@ -22,3 +22,13 @@ RewriteRule ^api/rest/user/game/(.*)/achievement/list/view.json;jsessionid=(.*)$
 
 RewriteRule ^connect_check.php$ - [R=204,L]
 RewriteRule ^generate_204 - [R=204,L]
+
+
+RewriteCond "%{QUERY_STRING}" "version=([^&]*)&i=-2$"
+RewriteRule ^firmware/download$ /firmware/%1/filesize [END]
+
+RewriteCond "%{QUERY_STRING}" "version=([^&]*)&i=-1$"
+RewriteRule ^firmware/download$ /firmware/%1/numchunks [END]
+
+RewriteCond "%{QUERY_STRING}" "version=([^&]*)&i=([0-9]+)$"
+RewriteRule ^firmware/download$ /firmware/%1/chunk-%2 [END]
index b228166583d73e2fabe8ca9c21ed39ae1f7e334f..e3f416125c7a20ba0609e6e4768a8f861d9f0685 100644 (file)
@@ -1,17 +1,96 @@
 <?php
-header('Content-type: application/json');
-/*
-{
-    'available': false,
-    'major': 0,
-    'minor': 9,
-    'revision': 2058,
-    'forced': false,
-    'name': 'v2058',
-    'description': 'bar',
-    'timestamp': 12345678,
-    'url': 'http://update.gamestickservices.net/firmware/v2058/GameStick-Software-v2058.img?'
+/**
+ * Firmware update check
+ *
+ * Custom firmware versions need a folder in www/firmware/,
+ * e.g. www/firmware/0.9.2071/.
+ * Each folder needs to contain:
+ * - changelog.txt
+ * - update.img
+ *
+ * $GLOBALS['forceFirmwareVersion'] can be set to a custom version
+ */
+header('HTTP/1.0 500 Internal Server Error');
+
+$rootDir = dirname(__FILE__, 2);
+require_once $rootDir . '/config.php';
+
+if (!isset($_POST['v'])) {
+    header('HTTP/1.0 400 Bad Request');
+    header('Content-Type: text/plain');
+    echo "POST data missing\n";
+    exit(1);
 }
-*/
-?>
-{"available":false}
+
+$gsData = json_decode($_POST['v']);
+if ($gsData === null) {
+    header('HTTP/1.0 400 Bad Request');
+    header('Content-Type: text/plain');
+    echo "POST data invalid\n";
+    exit(1);
+}
+
+if (!isset($gsData->hwid)
+    || !isset($gsData->major)
+    || !isset($gsData->minor)
+    || !isset($gsData->revision)
+    || !isset($gsData->platform)
+) {
+    header('HTTP/1.0 400 Bad Request');
+    header('Content-Type: text/plain');
+    echo "POST data incomplete\n";
+    exit(1);
+}
+
+$gsVersion = $gsData->major . '.' . $gsData->minor . '.' . $gsData->revision;
+
+$requireUpdate = false;
+if (isset($GLOBALS['firmwareVersion'])) {
+    $expectedVersion = $GLOBALS['firmwareVersion'];
+    if ($gsVersion != $expectedVersion) {
+        $requireUpdate = true;
+    }
+} else {
+    $expectedVersion = '0.9.2071';
+    if (version_compare($expectedVersion, $gsVersion, '>')) {
+        $requireUpdate = true;
+    }
+}
+if (!$requireUpdate) {
+    header('HTTP/1.0 200 OK');
+    header('Content-type: application/json');
+    echo '{"available":false}' . "\n";
+    exit(0);
+}
+
+list($major, $minor, $revision) = explode('.', $expectedVersion);
+
+
+$firmwareDir  = $rootDir . '/www/firmware/' . $expectedVersion;
+$firmwareFile = $firmwareDir . '/update.img';
+
+if (!file_exists($firmwareFile)) {
+    header('HTTP/1.0 200 OK');
+    header('Content-type: application/json');
+    header('X-Problem: Firmware file missing');
+    echo '{"available":false}' . "\n";
+    exit(0);
+}
+
+$data = [
+    'available'   => true,
+    'major'       => $major,
+    'minor'       => $minor,
+    'revision'    => $revision,
+    'forced'      => false,
+    'name'        => $expectedVersion,
+    'description' => file_get_contents($firmwareDir . '/changelog.txt'),
+    'timestamp'   => filemtime($firmwareFile),
+    'url'         => 'http://update.gamestickservices.net/firmware/download?version=' . $expectedVersion,
+];
+
+$json = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+
+header('HTTP/1.0 200 OK');
+header('Content-Type: application/json');
+echo $json . "\n";