diff --git a/AppApi.cs b/AppApi.cs
index e33e1a45..e24dd577 100644
--- a/AppApi.cs
+++ b/AppApi.cs
@@ -428,20 +428,6 @@ namespace VRCX
broadcastSocket.SendTo(byteBuffer, endPoint);
}
- ///
- /// Downloads the VRCX update executable from the specified URL and saves it to the AppData directory.
- ///
- /// The URL of the VRCX update to download.
- public void DownloadVRCXUpdate(string url)
- {
- var Location = Path.Combine(Program.AppDataDirectory, "update.exe");
- using (var client = new WebClient())
- {
- client.Headers.Add("user-agent", Program.Version);
- client.DownloadFile(new Uri(url), Location);
- }
- }
-
///
/// Restarts the VRCX application for an update by launching a new process with the "/Upgrade" argument and exiting the current process.
///
diff --git a/AssetBundleCacher.cs b/AssetBundleCacher.cs
index a153cd7c..8e767f09 100644
--- a/AssetBundleCacher.cs
+++ b/AssetBundleCacher.cs
@@ -26,6 +26,7 @@ namespace VRCX
public static string DownloadTempLocation;
public static string DownloadDestinationLocation;
+ public static string DownloadHashLocation;
public static int DownloadProgress;
public static int DownloadSize;
public static bool DownloadCanceled;
@@ -104,18 +105,25 @@ namespace VRCX
};
}
- public void DownloadFile(string url, int size)
+ // old asset bundle cacher downloader method reused for updating, it's not pretty
+ public void DownloadFile(string fileUrl, string hashUrl, int size)
{
+ client = new WebClient();
+ client.Headers.Add("user-agent", Program.Version);
DownloadProgress = 0;
DownloadSize = size;
DownloadCanceled = false;
DownloadTempLocation = Path.Combine(Program.AppDataDirectory, "tempDownload.exe");
DownloadDestinationLocation = Path.Combine(Program.AppDataDirectory, "update.exe");
- client = new WebClient();
- client.Headers.Add("user-agent", Program.Version);
+ DownloadHashLocation = Path.Combine(Program.AppDataDirectory, "sha256sum.txt");
+ if (File.Exists(DownloadHashLocation))
+ File.Delete(DownloadHashLocation);
+ if (!string.IsNullOrEmpty(hashUrl))
+ client.DownloadFile(new Uri(hashUrl), DownloadHashLocation);
+
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompletedCallback);
- client.DownloadFileAsync(new System.Uri(url), DownloadTempLocation);
+ client.DownloadFileAsync(new Uri(fileUrl), DownloadTempLocation);
}
public void CancelDownload()
@@ -163,6 +171,27 @@ namespace VRCX
DownloadProgress = -15;
return;
}
+ if (File.Exists(DownloadHashLocation))
+ {
+ logger.Info("Updater: Checking hash");
+ var lines = File.ReadAllLines(DownloadHashLocation);
+ var hash = lines.Length > 0 ? lines[0].Split(' ') : new[] { "" };
+ using (var sha256 = SHA256.Create())
+ using (var stream = File.OpenRead(DownloadTempLocation))
+ {
+ var hashBytes = sha256.ComputeHash(stream);
+ var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
+ if (hashString != hash[0])
+ {
+ logger.Error($"Updater: Hash check failed file:{hashString} remote:{hash[0]}");
+ // can't delete file yet because it's in use
+ DownloadProgress = -14;
+ return;
+ }
+ }
+ logger.Info("Updater: Hash check passed");
+ }
+
if (File.Exists(DownloadDestinationLocation))
File.Delete(DownloadDestinationLocation);
File.Move(DownloadTempLocation, DownloadDestinationLocation);
diff --git a/Update.cs b/Update.cs
index ab4b6dbe..5fcd75cb 100644
--- a/Update.cs
+++ b/Update.cs
@@ -20,6 +20,12 @@ namespace VRCX
{
if (Process.GetProcessesByName("VRCX_Setup").Length > 0)
Environment.Exit(0);
+ var setupHash = Path.Combine(Program.AppDataDirectory, "sha256sum.txt");
+ if (File.Exists(setupHash))
+ File.Delete(setupHash);
+ var tempDownload = Path.Combine(Program.AppDataDirectory, "tempDownload.exe");
+ if (File.Exists(tempDownload))
+ File.Delete(tempDownload);
if (File.Exists(VRCX_Setup_Executable))
File.Delete(VRCX_Setup_Executable);
if (File.Exists(Update_Executable))
diff --git a/html/src/app.js b/html/src/app.js
index d5b43f57..70b8f72f 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -21952,9 +21952,10 @@ speechSynthesis.getVoices();
this.downloadQueue.delete(ref.id);
this.downloadQueueTable.data = Array.from(this.downloadQueue.values());
- var url = this.downloadCurrent.updateZipUrl;
+ var fileUrl = this.downloadCurrent.updateSetupUrl;
+ var hashUrl = this.downloadCurrent.updateHashUrl;
var size = this.downloadCurrent.size;
- await AssetBundleCacher.DownloadFile(url, size);
+ await AssetBundleCacher.DownloadFile(fileUrl, hashUrl, size);
this.downloadFileProgress();
};
@@ -21993,9 +21994,16 @@ speechSynthesis.getVoices();
});
this.downloadFileComplete('Canceled');
return;
+ case -14:
+ this.$message({
+ message: 'Download failed, hash mismatch',
+ type: 'error'
+ });
+ this.downloadFileComplete('Failed');
+ return;
case -15:
this.$message({
- message: 'Download failed',
+ message: 'Download failed, size mismatch',
type: 'error'
});
this.downloadFileComplete('Failed');
@@ -22803,7 +22811,8 @@ speechSynthesis.getVoices();
};
$app.methods.downloadVRCXUpdate = function (
- updateZipUrl,
+ updateSetupUrl,
+ updateHashUrl,
size,
name,
type,
@@ -22816,7 +22825,8 @@ speechSynthesis.getVoices();
this.downloadQueue.set('VRCXUpdate', {
ref,
type,
- updateZipUrl,
+ updateSetupUrl,
+ updateHashUrl,
size,
autoInstall
});
@@ -22829,17 +22839,27 @@ speechSynthesis.getVoices();
$app.methods.installVRCXUpdate = function () {
for (var release of this.VRCXUpdateDialog.releases) {
if (release.name === this.VRCXUpdateDialog.release) {
+ var downloadUrl = '';
+ var hashUrl = '';
+ var size = 0;
for (var asset of release.assets) {
+ if (asset.state !== 'uploaded') {
+ continue;
+ }
if (
- (asset.content_type === 'application/x-msdownload' ||
- asset.content_type ===
- 'application/x-msdos-program') &&
- asset.state === 'uploaded'
+ asset.content_type === 'application/x-msdownload' ||
+ asset.content_type === 'application/x-msdos-program'
) {
- var downloadUrl = asset.browser_download_url;
- var size = asset.size;
+ downloadUrl = asset.browser_download_url;
+ size = asset.size;
break;
}
+ if (
+ asset.name === 'SHA256SUMS.txt' &&
+ asset.content_type === 'text/plain'
+ ) {
+ hashUrl = asset.browser_download_url;
+ }
}
if (!downloadUrl) {
return;
@@ -22849,6 +22869,7 @@ speechSynthesis.getVoices();
var autoInstall = false;
this.downloadVRCXUpdate(
downloadUrl,
+ hashUrl,
size,
name,
type,
@@ -22960,17 +22981,27 @@ speechSynthesis.getVoices();
// update already downloaded
this.VRCXUpdateDialog.updatePendingIsLatest = true;
} else if (name > this.appVersion) {
+ var downloadUrl = '';
+ var hashUrl = '';
+ var size = 0;
for (var asset of json.assets) {
+ if (asset.state !== 'uploaded') {
+ continue;
+ }
if (
- (asset.content_type === 'application/x-msdownload' ||
- asset.content_type ===
- 'application/x-msdos-program') &&
- asset.state === 'uploaded'
+ asset.content_type === 'application/x-msdownload' ||
+ asset.content_type === 'application/x-msdos-program'
) {
- var downloadUrl = asset.browser_download_url;
- var size = asset.size;
+ downloadUrl = asset.browser_download_url;
+ size = asset.size;
break;
}
+ if (
+ asset.name === 'SHA256SUMS.txt' &&
+ asset.content_type === 'text/plain'
+ ) {
+ hashUrl = asset.browser_download_url;
+ }
}
if (!downloadUrl) {
return;
@@ -22986,6 +23017,7 @@ speechSynthesis.getVoices();
var autoInstall = false;
this.downloadVRCXUpdate(
downloadUrl,
+ hashUrl,
size,
name,
type,
@@ -22995,6 +23027,7 @@ speechSynthesis.getVoices();
var autoInstall = true;
this.downloadVRCXUpdate(
downloadUrl,
+ hashUrl,
size,
name,
type,