diff --git a/Dotnet/AppApi/Common/Update.cs b/Dotnet/AppApi/Common/Update.cs index 9aea4815..8c50978e 100644 --- a/Dotnet/AppApi/Common/Update.cs +++ b/Dotnet/AppApi/Common/Update.cs @@ -4,9 +4,9 @@ namespace VRCX; public partial class AppApi { - public async Task DownloadUpdate(string fileUrl, string fileName, string hashUrl, int downloadSize) + public async Task DownloadUpdate(string fileUrl, string hashString, int downloadSize) { - await Update.DownloadUpdate(fileUrl, fileName, hashUrl, downloadSize); + await Update.DownloadUpdate(fileUrl, hashString, downloadSize); } public void CancelUpdate() diff --git a/Dotnet/Update.cs b/Dotnet/Update.cs index 2711d25d..ff5996b2 100644 --- a/Dotnet/Update.cs +++ b/Dotnet/Update.cs @@ -26,7 +26,6 @@ namespace VRCX private static readonly string VrcxSetupExecutable = Path.Join(Program.AppDataDirectory, "VRCX_Setup.exe"); private static readonly string UpdateExecutable = Path.Join(Program.AppDataDirectory, "update.exe"); private static readonly string TempDownload = Path.Join(Program.AppDataDirectory, "tempDownload"); - private static readonly string HashLocation = Path.Join(Program.AppDataDirectory, "sha256sum.txt"); private static readonly HttpClient httpClient; private static CancellationToken _cancellationToken; public static int UpdateProgress; @@ -57,9 +56,7 @@ namespace VRCX { if (Process.GetProcessesByName("VRCX_Setup").Length > 0) Environment.Exit(0); - - if (File.Exists(HashLocation)) - File.Delete(HashLocation); + if (File.Exists(TempDownload)) File.Delete(TempDownload); if (File.Exists(VrcxSetupExecutable)) @@ -165,52 +162,39 @@ namespace VRCX throw new Exception("Unable to extract file name from content-disposition header."); } - public static async Task DownloadUpdate(string fileUrl, string fileName, string hashUrl, int downloadSize) + public static async Task DownloadUpdate(string fileUrl, string hashString, int downloadSize) { _cancellationToken = CancellationToken.None; const int chunkSize = 8192; if (File.Exists(TempDownload)) File.Delete(TempDownload); - if (File.Exists(HashLocation)) - File.Delete(HashLocation); - - var hashesPath = await DownloadFile(hashUrl, _cancellationToken); - if (!string.IsNullOrEmpty(hashesPath)) - File.Move(hashesPath, HashLocation); await using var destination = File.OpenWrite(TempDownload); - using (var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, _cancellationToken)) - await using (var download = await response.Content.ReadAsStreamAsync(_cancellationToken)) + using var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, _cancellationToken); + await using var download = await response.Content.ReadAsStreamAsync(_cancellationToken); + var contentLength = response.Content.Headers.ContentLength; + var buffer = new byte[chunkSize]; + long totalBytesRead = 0; + + while (true) { - var contentLength = response.Content.Headers.ContentLength; - var buffer = new byte[chunkSize]; - long totalBytesRead = 0; - - while (true) - { - int bytesRead = await download.ReadAsync(buffer, 0, chunkSize, _cancellationToken); - if (bytesRead == 0) break; - - if (_cancellationToken.IsCancellationRequested) - throw new OperationCanceledException("Download was cancelled."); - - await destination.WriteAsync(buffer.AsMemory(0, bytesRead), _cancellationToken); - totalBytesRead += bytesRead; - - if (contentLength.HasValue) - { - double percentage = Math.Round((double)totalBytesRead / contentLength.Value * 100, 2); - UpdateProgress = (int)percentage; - } - } - if (contentLength.HasValue) { - double percentage = Math.Round((double)totalBytesRead / contentLength.Value * 100, 2); + var percentage = Math.Round((double)totalBytesRead / contentLength.Value * 100, 2); UpdateProgress = (int)percentage; } + var bytesRead = await download.ReadAsync(buffer.AsMemory(0, chunkSize), _cancellationToken); + if (bytesRead == 0) + break; + + if (_cancellationToken.IsCancellationRequested) + throw new OperationCanceledException("Download was cancelled."); + + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), _cancellationToken); + totalBytesRead += bytesRead; } + destination.Close(); var data = new FileInfo(TempDownload); @@ -220,35 +204,28 @@ namespace VRCX logger.Error("Downloaded file size does not match expected size"); throw new Exception("Downloaded file size does not match expected size"); } - if (File.Exists(HashLocation)) + + if (string.IsNullOrEmpty(hashString)) + { + logger.Error("Hash string is empty, skipping hash check"); + } + else { logger.Info("Checking hash"); - var lines = await File.ReadAllLinesAsync(HashLocation, _cancellationToken); - var hashDict = new Dictionary(); - foreach (var line in lines) - { - var split = line.Split(' '); - if (split.Length == 3) - hashDict[split[2]] = split[0]; - } using (var sha256 = SHA256.Create()) await using (var stream = File.OpenRead(TempDownload)) { - var hashBytes = await sha256.ComputeHashAsync(stream, _cancellationToken); - var hashString = BitConverter.ToString(hashBytes).Replace("-", ""); - if (!hashDict.TryGetValue(fileName, out var expectedHash)) + var fileHashBytes = await sha256.ComputeHashAsync(stream, _cancellationToken); + var fileHashString = Convert.ToHexString(fileHashBytes); + if (!string.IsNullOrEmpty(fileHashString) && + !hashString.Equals(fileHashString, StringComparison.OrdinalIgnoreCase)) { - logger.Error("Hash check failed, file not found in hash file"); - } - if (!string.IsNullOrEmpty(expectedHash) && - !hashString.Equals(expectedHash, StringComparison.OrdinalIgnoreCase)) - { - logger.Error($"Hash check failed file:{hashString} web:{expectedHash}"); + logger.Error($"Hash check failed file:{fileHashString} web:{hashString}"); throw new Exception("Hash check failed"); // can't delete file yet because it's in use } } - File.Delete(HashLocation); + logger.Info("Hash check passed"); } diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue index 283e3d34..cf630798 100644 --- a/src/components/NavMenu.vue +++ b/src/components/NavMenu.vue @@ -4,7 +4,7 @@ diff --git a/src/stores/vrcxUpdater.js b/src/stores/vrcxUpdater.js index d76fb0cb..bfd908de 100644 --- a/src/stores/vrcxUpdater.js +++ b/src/stores/vrcxUpdater.js @@ -3,7 +3,6 @@ import { computed, reactive } from 'vue'; import * as workerTimers from 'worker-timers'; import { $app } from '../app'; import configRepository from '../service/config'; -import { watchState } from '../service/watchState'; import { branches } from '../shared/constants'; import { changeLogRemoveLinks } from '../shared/utils'; import { useUiStore } from './ui'; @@ -225,8 +224,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { state.VRCXUpdateDialog.updatePendingIsLatest = true; } else if (releaseName > currentVersion.value) { let downloadUrl = ''; - let downloadName = ''; - let hashUrl = ''; + let hashString = ''; let size = 0; for (const asset of json.assets) { if (asset.state !== 'uploaded') { @@ -239,7 +237,12 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { 'application/x-msdos-program') ) { downloadUrl = asset.browser_download_url; - downloadName = asset.name; + if ( + asset.digest && + asset.digest.startsWith('sha256:') + ) { + hashString = asset.digest.replace('sha256:', ''); + } size = asset.size; continue; } @@ -248,34 +251,29 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { asset.content_type === 'application/octet-stream' ) { downloadUrl = asset.browser_download_url; - downloadName = asset.name; + if ( + asset.digest && + asset.digest.startsWith('sha256:') + ) { + hashString = asset.digest.replace('sha256:', ''); + } size = asset.size; continue; } - if ( - asset.name === 'SHA256SUMS.txt' && - asset.content_type === 'text/plain' - ) { - hashUrl = asset.browser_download_url; - continue; - } } if (!downloadUrl) { return; } state.pendingVRCXUpdate = true; uiStore.notifyMenu('settings'); - const type = 'Auto'; if (state.autoUpdateVRCX === 'Notify') { // this.showVRCXUpdateDialog(); } else if (state.autoUpdateVRCX === 'Auto Download') { await downloadVRCXUpdate( downloadUrl, - downloadName, - hashUrl, + hashString, size, - releaseName, - type + releaseName ); } } @@ -344,11 +342,9 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { } async function downloadVRCXUpdate( downloadUrl, - downloadName, - hashUrl, + hashString, size, - releaseName, - type + releaseName ) { if (state.updateInProgress) { return; @@ -356,12 +352,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { try { state.updateInProgress = true; await downloadFileProgress(); - await AppApi.DownloadUpdate( - downloadUrl, - downloadName, - hashUrl, - size - ); + await AppApi.DownloadUpdate(downloadUrl, hashString, size); state.pendingVRCXInstall = releaseName; } catch (err) { console.error(err); @@ -386,8 +377,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { continue; } let downloadUrl = ''; - let downloadName = ''; - let hashUrl = ''; + let hashString = ''; let size = 0; for (const asset of release.assets) { if (asset.state !== 'uploaded') { @@ -399,7 +389,9 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { asset.content_type === 'application/x-msdos-program') ) { downloadUrl = asset.browser_download_url; - downloadName = asset.name; + if (asset.digest && asset.digest.startsWith('sha256:')) { + hashString = asset.digest.replace('sha256:', ''); + } size = asset.size; continue; } @@ -408,31 +400,18 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { asset.content_type === 'application/octet-stream' ) { downloadUrl = asset.browser_download_url; - downloadName = asset.name; + if (asset.digest && asset.digest.startsWith('sha256:')) { + hashString = asset.digest.replace('sha256:', ''); + } size = asset.size; continue; } - if ( - asset.name === 'SHA256SUMS.txt' && - asset.content_type === 'text/plain' - ) { - hashUrl = asset.browser_download_url; - continue; - } } if (!downloadUrl) { return; } const releaseName = release.name; - const type = 'Manual'; - downloadVRCXUpdate( - downloadUrl, - downloadName, - hashUrl, - size, - releaseName, - type - ); + downloadVRCXUpdate(downloadUrl, hashString, size, releaseName); break; } } diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 8854004c..0e72f3b3 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -313,8 +313,7 @@ declare global { // Update DownloadUpdate( fileUrl: string, - fileName: string, - hashUrl: string, + hashString: string, downloadSize: number ): Promise; CancelUpdate(): Promise;