diff --git a/CMakeLists.txt b/CMakeLists.txt index da90e53..4ed46ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.30) -project(stremio VERSION "5.0.0") +project(stremio VERSION "5.0.1") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/") diff --git a/build/build_checksums.js b/build/build_checksums.js new file mode 100644 index 0000000..376aa25 --- /dev/null +++ b/build/build_checksums.js @@ -0,0 +1,214 @@ +/* + generate_sums.js + Usage: + node generate_sums.js "C:\\Program Files\\OpenSSL-Win64\\bin" "5.0.0-beta.1" "5.0.0" 4.20.11 + + This script: + 1) Validates four arguments: + -- OPENSSL_BIN (path to folder containing openssl.exe) + -- GIT_TAG (e.g. "5.0.0-beta.1") + -- SHELL_VERSION (e.g. "5.0.0") + -- SERVER_VERSION (e.g. "4.20.11") + 2) Locates and verifies "openssl.exe" in OPENSSL_BIN. + 3) Computes sha256 checksums of "Stremio .exe" and "server.js" using + "openssl dgst -sha256". + 4) Updates version-details.json to: + shellVersion = SHELL_VERSION + windows.url = https://github.com/Zaarrg/stremio-desktop-v5/releases/download//Stremio..exe + windows.checksum = + server.js.url = https://dl.strem.io/server//desktop/server.js + server.js.checksum = + 5) Signs version-details.json with private_key.pem, base64-encodes the signature, + inserts that signature into version.json. + 6) Cleans up the signature files (version-details.json.sig / .sig.b64). + 7) Exits 0 on success; 1 on any error. + + No external packages needed; we only use Node's built-in fs, path, child_process [[1]]. +*/ + +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +// Parse CLI arguments +// e.g. node generate_sums.js "C:\\OpenSSL\\bin" "5.0.0-beta.1" "5.0.0" 4.20.11 +const [,, OPENSSL_BIN, GIT_TAG, SHELL_VERSION, SERVER_VERSION] = process.argv; + +(async function main() { + // 1) Validate args + if (!OPENSSL_BIN || !GIT_TAG || !SHELL_VERSION || !SERVER_VERSION) { + console.error("Usage: node generate_sums.js "); + console.error('Example: node generate_sums.js "C:\\Program Files\\OpenSSL-Win64\\bin" "5.0.0-beta.1" "5.0.0" 4.20.11'); + process.exit(1); + } + + // 2) Verify openssl.exe + const opensslExe = path.join(OPENSSL_BIN, "openssl.exe"); + if (!fs.existsSync(opensslExe)) { + console.error("ERROR: Cannot find openssl.exe in:", opensslExe); + process.exit(1); + } + + console.log("Using OpenSSL at:", OPENSSL_BIN); + console.log("Git Tag:", GIT_TAG); + console.log("Shell Version:", SHELL_VERSION); + console.log("server.js Version:", SERVER_VERSION); + console.log(); + + // 3) Build paths + // Assume this script is in /build; go up one directory for the project root + const scriptDir = path.dirname(__filename); + const projectRoot = path.resolve(scriptDir, ".."); + + // For Windows .exe, the local file name uses the Shell Version (5.0.0), not the Git tag + const EXE_PATH = path.join(projectRoot, "utils", `Stremio ${SHELL_VERSION}.exe`); + const SERVERJS_PATH = path.join(projectRoot, "utils", "windows", "server.js"); + const VERSION_DETAILS_PATH = path.join(projectRoot, "version", "version-details.json"); + const VERSION_JSON_PATH = path.join(projectRoot, "version", "version.json"); + const PRIVATE_KEY = path.join(projectRoot, "private_key.pem"); + + // 4) Generate SHA-256 for the .exe and server.js + checkFileExists(EXE_PATH, "Stremio .exe"); + const exeHash = computeSha256(opensslExe, EXE_PATH); + + checkFileExists(SERVERJS_PATH, "server.js"); + const serverHash = computeSha256(opensslExe, SERVERJS_PATH); + + console.log("EXE sha256 =", exeHash); + console.log("server.js sha256 =", serverHash); + console.log(); + + // 5) Update version-details.json + checkFileExists(VERSION_DETAILS_PATH, "version-details.json"); + let versionDetails; + try { + versionDetails = JSON.parse(fs.readFileSync(VERSION_DETAILS_PATH, "utf8")); + } catch (err) { + console.error("ERROR: Unable to parse version-details.json:", err.message); + process.exit(1); + } + + // Update: + // versionDetails.shellVersion = SHELL_VERSION + // versionDetails.files.windows.url => uses GIT_TAG + // versionDetails.files.windows.checksum => exeHash + // versionDetails.files["server.js"].url => uses SERVER_VERSION + // versionDetails.files["server.js"].checksum => serverHash + versionDetails.shellVersion = SHELL_VERSION; + if (!versionDetails.files) { + console.error("ERROR: version-details.json missing property 'files'"); + process.exit(1); + } + if (!versionDetails.files.windows) versionDetails.files.windows = {}; + versionDetails.files.windows.url = `https://github.com/Zaarrg/stremio-desktop-v5/releases/download/${GIT_TAG}/Stremio.${SHELL_VERSION}.exe`; + versionDetails.files.windows.checksum = exeHash; + + if (!versionDetails.files["server.js"]) versionDetails.files["server.js"] = {}; + versionDetails.files["server.js"].url = `https://dl.strem.io/server/${SERVER_VERSION}/desktop/server.js`; + versionDetails.files["server.js"].checksum = serverHash; + + // Save updated version-details.json + try { + fs.writeFileSync(VERSION_DETAILS_PATH, JSON.stringify(versionDetails, null, 2), "utf8"); + } catch (e) { + console.error("ERROR: Failed writing version-details.json:", e.message); + process.exit(1); + } + + // 6) Sign version-details.json & base64-encode + checkFileExists(PRIVATE_KEY, "private_key.pem"); + process.chdir(path.join(projectRoot, "version")); + + const sigFile = path.join(process.cwd(), "version-details.json.sig"); + const sigB64 = path.join(process.cwd(), "version-details.json.sig.b64"); + if (fs.existsSync(sigFile)) fs.unlinkSync(sigFile); + if (fs.existsSync(sigB64)) fs.unlinkSync(sigB64); + + console.log(`Signing version-details.json with ${PRIVATE_KEY}...`); + try { + execFileSync(opensslExe, ["dgst", "-sha256", "-sign", PRIVATE_KEY, "-out", "version-details.json.sig", "version-details.json"], { stdio: "inherit" }); + } catch (err) { + console.error("ERROR: Signing failed:", err.message); + process.exit(1); + } + + try { + execFileSync(opensslExe, ["base64", "-in", "version-details.json.sig", "-out", "version-details.json.sig.b64"], { stdio: "inherit" }); + } catch (err) { + console.error("ERROR: Base64 encoding failed:", err.message); + process.exit(1); + } + + if (!fs.existsSync(sigB64)) { + console.error("ERROR: Could not create signature file:", sigB64); + process.exit(1); + } + process.chdir(projectRoot); + + // 7) Insert signature into version.json + checkFileExists(VERSION_JSON_PATH, "version.json"); + console.log(`Updating signature in "${VERSION_JSON_PATH}"...`); + let signatureB64; + try { + signatureB64 = fs.readFileSync(sigB64, "utf8").replace(/\r?\n/g, ""); + } catch (err) { + console.error("ERROR: Unable to read version-details.json.sig.b64:", err.message); + process.exit(1); + } + + let versionJson; + try { + versionJson = JSON.parse(fs.readFileSync(VERSION_JSON_PATH, "utf8")); + } catch (err) { + console.error("ERROR: Unable to parse version.json:", err.message); + process.exit(1); + } + + versionJson.signature = signatureB64; + try { + fs.writeFileSync(VERSION_JSON_PATH, JSON.stringify(versionJson, null, 2), "utf8"); + } catch (err) { + console.error("ERROR: Unable to write version.json:", err.message); + process.exit(1); + } + + // 8) Cleanup ephemeral files + try { + if (fs.existsSync(sigFile)) fs.unlinkSync(sigFile); + if (fs.existsSync(sigB64)) fs.unlinkSync(sigB64); + } catch (cleanupErr) { + console.error("WARNING: Could not remove signature files:", cleanupErr.message); + } + + console.log("\nSuccess! Checksums and signature have been updated, ephemeral signature files removed."); + process.exit(0); + +})().catch(err => { + console.error("Unexpected error:", err); + process.exit(1); +}); + +// Helper: checks that filePath exists, else exits +function checkFileExists(filePath, label) { + if (!fs.existsSync(filePath)) { + console.error(`ERROR: ${label} file not found at: ${filePath}`); + process.exit(1); + } +} + +// Helper: runs "openssl dgst -sha256 " and parses the output +function computeSha256(opensslExe, filePath) { + try { + const output = execFileSync(opensslExe, ["dgst", "-sha256", filePath], { encoding: "utf8" }); + // Typically "SHA256(file)= " + const match = output.match(/=.\s*([0-9a-fA-F]+)/); + if (!match) { + console.error("ERROR: Unexpected openssl dgst output for", filePath, "-", output); + process.exit(1); + } + return match[1].toLowerCase(); + } catch (err) { + console.error(`ERROR: openssl dgst failed for ${filePath}:`, err.message); + process.exit(1); + } +} \ No newline at end of file diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..ba9b1c5 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,21 @@ +# Releasing New Version + +--- + +## 🚀 Quick Overview + +1. Bump version in ``cmakelists`` +2. Build new ``runtime`` and `installer` +3. Make sure `installer` is in `/utils` and `server.js` in `/utils/windows` +4. Run ``build/build_checksums.js`` this will generate `version.json` and `version-details.json` needed for the auto updater +``` +node build_checksums.js +node build_checksums.js "C:\Program Files\OpenSSL-Win64\bin" "5.0.0-beta.1" "5.0.0" v4.20.8 +``` +> **⏳Note:** Only Windows at the moment + +5. Commit Changes +6. Make new release with the Git tag used when running ``build_checksums.js`` + +> **⏳Note:** Alternatively u can separate the version bump commit. Instead: +> Commit - Release - Build Checksums - Commit Built Checksums \ No newline at end of file diff --git a/docs/WINDOWS.md b/docs/WINDOWS.md index 4f92c58..31cb79a 100644 --- a/docs/WINDOWS.md +++ b/docs/WINDOWS.md @@ -97,9 +97,9 @@ Ensure the following are installed on your system: 4. Build distributable ```cmd - build_windows_vcpkg.bat {cmake-build-folder} {openssl-bin} + build_windows.bat {cmake-build-folder} {openssl-bin} - build_windows_vcpkg.bat cmake-build-release + build_windows.bat cmake-build-release "C:\Program Files\OpenSSL-Win64\bin" ``` diff --git a/src/main.qml b/src/main.qml index dadb7ff..7b96279 100644 --- a/src/main.qml +++ b/src/main.qml @@ -237,7 +237,7 @@ ApplicationWindow { } } if (ev === "autoupdater-notif-clicked" && autoUpdater.onNotifClicked) { - autoUpdater.onNotifClicked(); + autoUpdater.onNotifClicked(); //Event not used. We use qt updateScreen notification instead } if (ev === "screensaver-toggle") shouldDisableScreensaver(args.disabled) if (ev === "file-close") fileDialog.close() @@ -362,20 +362,149 @@ ApplicationWindow { id: splashScreen; color: "#0c0b11"; anchors.fill: parent; - Image { - id: splashLogo - source: "qrc:///images/stremio.png" - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - SequentialAnimation { - id: pulseOpacity - running: true - NumberAnimation { target: splashLogo; property: "opacity"; to: 1.0; duration: 600; - easing.type: Easing.Linear; } - NumberAnimation { target: splashLogo; property: "opacity"; to: 0.3; duration: 600; - easing.type: Easing.Linear; } - loops: Animation.Infinite + Column { + anchors.centerIn: parent + spacing: 20 + Image { + id: splashLogo + source: "qrc:///images/stremio.png" + anchors.horizontalCenter: parent.horizontalCenter + + SequentialAnimation { + id: pulseOpacity + running: true + NumberAnimation { target: splashLogo; property: "opacity"; to: 1.0; duration: 600; + easing.type: Easing.Linear; } + NumberAnimation { target: splashLogo; property: "opacity"; to: 0.3; duration: 600; + easing.type: Easing.Linear; } + loops: Animation.Infinite + } + } + + Column { + height: 90 //Height of updateScreen elements 45 + 30 + 15 + width: 100 + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + //Connection to show update Screen. Needed because autoupdater runs in different thread + QtObject { + id: autoUpdateTransport + signal showUpdateScreen() + } + + Connections { + target: autoUpdateTransport + onShowUpdateScreen: { + updateScreen.visible = true; + updateScreen.focus = true; + } + } + + // + // Update screen + // Must be over the UI + // + Rectangle { + id: updateScreen + color: "#0c0b11" + anchors.fill: parent + visible: false + + MouseArea { + anchors.fill: parent + hoverEnabled: true + } + + Column { + anchors.centerIn: parent + spacing: 20 + + Image { + id: updateLogo + source: "qrc:///images/stremio.png" + anchors.horizontalCenter: parent.horizontalCenter // Align like splashLogo + } + + Column { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 15 + + Text { + text: "Stremio update available!" + color: "white" + font.bold: true + font.pointSize: 14 + height: 30 + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 25 + + Button { + width: 80 + height: 45 + background: Rectangle { + id: updateButtonBg + color: parent.hovered ? "#6B64F2" : "#5351D9" + radius: 5 + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onEntered: parent.hovered = true + onExited: parent.hovered = false + } + } + contentItem: Text { + text: "Update" + color: "white" + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + } + onClicked: { + updateScreen.visible = false + autoUpdater.onNotifClicked(); + } + } + + Button { + width: 80 + height: 45 + background: Rectangle { + id: laterButtonBg + color: parent.hovered ? "#4A4A4A" : "#353637" + radius: 5 + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onEntered: parent.hovered = true + onExited: parent.hovered = false + } + } + contentItem: Text { + text: "Later" + color: "white" + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + } + onClicked: { + // Handle postpone action + updateScreen.visible = false + webView.visible = true + webView.focus = true + } + } + } } } } diff --git a/src/ssl/publickey.h b/src/ssl/publickey.h index 7636760..f5549c6 100644 --- a/src/ssl/publickey.h +++ b/src/ssl/publickey.h @@ -1,14 +1,9 @@ static const char key[] = "-----BEGIN PUBLIC KEY-----\n" -"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA265O2NgcDI5hk1n90xzH\n" -"P7nZU+sSQoJcpOVd71bnqhjTyrmEHk9gJzyB0O8OJPlNonE16lQfM+a0ncH+XLt7\n" -"HKF73ZwsPCHg1MX/lGpSQtfC3jUGZdFssGhKBo06Thtp8coptRiRl2ZAtvgPNKME\n" -"NZLHzck+tI15UwKkKfUL3zfxgapJEPfd7XKT7NCj+n9fa3MXFnSIiDjMvb6f2oeC\n" -"YPpE7pqAaSgde/ZeveJGWgexHzOgA27nz+LuBo3ITYp3HFu9Q0xymQs0fxUFazw+\n" -"YIRJZD5yNNitPOsrRrYgaprNrVS6AvB7lhauMjz+R/r2OdbaszO1Gi9wqGWGoIcy\n" -"VfBCWf75SNwanf8/V5RiQP32bKFiHPlQkqMjrrS3FwPqbXwj/W0rFGgy6GolrFIJ\n" -"rdWeaLSgIY5M3qhowArB7aTMPZItQhOIPqZgTt79GCsa4zKrBvFr7TM7dTRVbLzG\n" -"2yA/cS88sQpnsO/Li/oNunG7Z1DRmWc3otUtsVVvmxFT5p62aoGEpFvbDQuel2yO\n" -"o8VDGK1klqEzRvH7SGLA8VLyvk3gwhXRJxep6l5LLiPXSVw7DqNJ+QhtsCPkteBU\n" -"sVdpnLa3TQFB8Ztn/ZC0L/foupvn95OF4bC6/Owc16kQjSs9V+GgtzGTGxjuGR96\n" -"kKc4nu1CZCwxqHpRpquQIu8CAwEAAQ==\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXoJRQ81xOT3Gx6+hsWM\n" +"ZiD4PwtLdxxNhEdL/iK0yp6AdO/L0kcSHk9YCPPx0XPK9sssjSV5vCbNE/2IJxnh\n" +"/mV+3GAMmXgMvTL+DZgrHafnxe1K50M+8Z2z+uM5YC9XDLppgnC6OrUjwRqNHrKI\n" +"T1vcgKf16e/TdKj8xlgadoHBECjv6dr87nbHW115bw8PVn2tSk/zC+QdUud+p6KV\n" +"zA6+FT9ZpHJvdS3R0V0l7snr2cwapXF6J36aLGjJ7UviRFVWEEsQaKtAAtTTBzdD\n" +"4B9FJ2IJb/ifdnVzeuNTDYApCSE1F89XFWN9FoDyw7Jkk+7u4rsKjpcnCDTd9ziG\n" +"kwIDAQAB\n" "-----END PUBLIC KEY-----"; \ No newline at end of file diff --git a/src/updater/autoupdater.cpp b/src/updater/autoupdater.cpp index 745dbba..1702958 100644 --- a/src/updater/autoupdater.cpp +++ b/src/updater/autoupdater.cpp @@ -107,17 +107,9 @@ int AutoUpdater::executeCmd(QString cmd, QStringList args, bool noWait = false) // CHECK FOR UPDATES void AutoUpdater::checkForUpdatesPerform(QString endpoint, QString userAgent) { - QByteArray serverHash = getFileChecksum(QCoreApplication::applicationDirPath() + QDir::separator() + SERVER_FNAME); - QByteArray asarHash = getFileChecksum(QCoreApplication::applicationDirPath() + QDir::separator() + ASAR_FNAME); - QUrl url = QUrl(endpoint); - QUrlQuery query = QUrlQuery(url); + qDebug() << "AUTOUPDATER: Checking for updates..."; - query.addQueryItem("serverSum", serverHash.toHex()); - query.addQueryItem("asarSum", asarHash.toHex()); - query.addQueryItem("shellVersion", QCoreApplication::applicationVersion()); - - url.setQuery(query); auto request = QNetworkRequest(QUrl(url)); request.setRawHeader("User-Agent", userAgent.toUtf8()); currentCheck = manager->get(request); @@ -218,14 +210,19 @@ void AutoUpdater::prepareUpdate(QJsonDocument versionDescDoc) { QJsonObject versionDesc = versionDescDoc.object(); QJsonObject files = versionDesc.value("files").toObject(); + QByteArray serverHash = getFileChecksum(QCoreApplication::applicationDirPath() + QDir::separator() + SERVER_FNAME); + QString serverHashHex = QString::fromLatin1(serverHash.toHex()); QVector toDownload; if (forceFullUpdate || versionDesc.value("shellVersion").toString() != QCoreApplication::applicationVersion() ) { toDownload = FULL_UPDATE_FILES; - } else { + } else if (files.value(PARTIAL_UPDATE_FILES).toObject().value("checksum").toString() != serverHashHex) { toDownload = PARTIAL_UPDATE_FILES; + } else { + qDebug() << "Everything is up to date"; + return; } if (! toDownload.length()) { diff --git a/src/updater/autoupdater.h b/src/updater/autoupdater.h index 588e550..824a041 100644 --- a/src/updater/autoupdater.h +++ b/src/updater/autoupdater.h @@ -28,9 +28,9 @@ extern "C" { // TODO Move to somewhere? Document that we can override? #define SERVER_FNAME "server.js" -#define ASAR_FNAME "stremio.asar" +//#define ASAR_FNAME "stremio.asar" -#define PARTIAL_UPDATE_FILES { SERVER_FNAME, ASAR_FNAME } +#define PARTIAL_UPDATE_FILES { SERVER_FNAME } #if defined(Q_OS_WIN) #define FULL_UPDATE_FILES { "windows" } diff --git a/src/updater/autoupdater.js b/src/updater/autoupdater.js index f2be19d..d589d50 100644 --- a/src/updater/autoupdater.js +++ b/src/updater/autoupdater.js @@ -8,8 +8,7 @@ var errorCounter = MAX_ERROR_COUNT; - var endpoints = ["https://www.strem.io/updater/check", "https://www.stremio.com/updater/check", - "https://www.stremio.net/updater/check"]; + var endpoints = ["https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/master/version/version.json"]; var fallbackSite = "https://www.stremio.com/?fromFailedAutoupdate=true"; var doAutoupdate = autoUpdater.isInstalled() @@ -93,7 +92,7 @@ // a notification event once it loads // Then, we must set .onNotifClicked to what we'll do when the notification is clicked - if (preparedFiles.length == 2) { + if (firstFile && firstFile.match(".js")) { // // Prepare partial auto-update // @@ -104,7 +103,11 @@ autoUpdaterErr("preparing partial update failed", null) return } - transport.queueEvent("autoupdater-show-notif", { mode: "reload" }) + //transport.queueEvent("autoupdater-show-notif", { mode: "reload" }) + Qt.callLater(function () { + autoUpdateTransport.showUpdateScreen(); + }); + autoUpdater.onNotifClicked = function() { splashScreen.visible = true pulseOpacity.running = true @@ -141,7 +144,10 @@ return; } - transport.queueEvent("autoupdater-show-notif", { mode: "restart" }) + //transport.queueEvent("autoupdater-show-notif", { mode: "restart" }) + Qt.callLater(function () { + autoUpdateTransport.showUpdateScreen(); + }); autoUpdater.onNotifClicked = function() { autoUpdater.executeCmd("/bin/sh", ["-c", "sleep 5; open -n /Applications/Stremio.app"], true) quitApp(); @@ -150,7 +156,10 @@ // // Prepare launch-based auto-update (launch new installer/appimage on Windows) // - transport.queueEvent("autoupdater-show-notif", { mode: "launchNew" }) + //transport.queueEvent("autoupdater-show-notif", { mode: "launchNew" }) + Qt.callLater(function () { + autoUpdateTransport.showUpdateScreen(); + }); autoUpdater.onNotifClicked = function() { Qt.openUrlExternally("file:///"+firstFile.replace(/\\/g,'/')) quitApp(); @@ -168,7 +177,10 @@ autoUpdaterErr("preparing Linux .appimage failed", null); return; } - transport.queueEvent("autoupdater-show-notif", { mode: "launchNew" }) + //transport.queueEvent("autoupdater-show-notif", { mode: "launchNew" }) + Qt.callLater(function () { + autoUpdateTransport.showUpdateScreen(); + }); autoUpdater.onNotifClicked = function() { autoUpdater.executeCmd("/bin/sh", ["-c", "$HOME/'"+baseName+"'"], true) // crappy, but otherwise we have to write code to get env var diff --git a/version/version-details.json b/version/version-details.json new file mode 100644 index 0000000..c75dea8 --- /dev/null +++ b/version/version-details.json @@ -0,0 +1,13 @@ +{ + "shellVersion": "5.0.1", + "files": { + "windows": { + "url": "https://github.com/Zaarrg/stremio-desktop-v5/releases/download/5.0.0-beta.2/Stremio.5.0.1.exe", + "checksum": "38f11ab6dcb4fc93cbe54c88a4f8042fef8c09fc9ba83938b916dbe5ba3fe45f" + }, + "server.js": { + "url": "https://dl.strem.io/server/v4.20.8/desktop/server.js", + "checksum": "7113200f5775c958fd141bc502a808ab00ebfbb53799a13c3ab0aca84c5fb476" + } + } +} \ No newline at end of file diff --git a/version/version.json b/version/version.json new file mode 100644 index 0000000..3d833a1 --- /dev/null +++ b/version/version.json @@ -0,0 +1,5 @@ +{ + "upToDate": false, + "versionDesc": "https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/master/version/version-details.json", + "signature": "bRMydlBNCo4eqIIQwTQ1O1vgOs20fpfRh6ymmu9G9rqQtJlEC5uDe8uavu7wmDJC/ARHgsSQxzb8tw/HCbR4knI6gPQs1oasDhSn6o4b5YyWdLJo0vfyicrJj+xI5cngdS5NLnyrCxVf7sO5NRi1NtLWJ0XcF8M8f93VfqTClVAR/SDbX5vFod+CppZDNbpdKHvyCdk/AT232Pv9q6+2TQyLQk7rLvlBXXH/8OCU+tmmTe7arTSKNr6NA8k5N+hevjay+cuhXluSomesXIjJVIPFolu7yaC3MX6doxE3hBhTA3wnUlm5nDZOSPplYRQfHubA8ms+8BOzZE32sZw2qA==" +} \ No newline at end of file