From 9cd1b09348fccf34982518b9e99004431f8a83da Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Wed, 2 Jul 2025 08:01:13 +1000 Subject: [PATCH 1/5] Code style fixes to `{FFmpeg,RAIntegration}DownloaderForm` --- .../AVOut/FFmpegDownloaderForm.cs | 43 ++++++++--------- .../RAIntegrationDownloaderForm.cs | 48 +++++++++---------- 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs index f908be73542..bb899e83567 100644 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Net; using System.Threading; using System.Windows.Forms; @@ -44,24 +45,16 @@ private void Download() using var fs = File.Create(FFmpegService.FFmpegPath); // check writable before bothering with the download using (var evt = new ManualResetEvent(false)) { - using (var client = new System.Net.WebClient()) + using (var client = new WebClient()) { - System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; client.DownloadFileAsync(new Uri(FFmpegService.Url), fn); - client.DownloadProgressChanged += (object sender, System.Net.DownloadProgressChangedEventArgs e) => - { - pct = e.ProgressPercentage; - }; - client.DownloadFileCompleted += (object sender, System.ComponentModel.AsyncCompletedEventArgs e) => - { - //we don't really need a status. we'll just try to unzip it when it's done - evt.Set(); - }; + client.DownloadProgressChanged += (_, progressArgs) => pct = progressArgs.ProgressPercentage; + client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done - for (; ; ) + while (true) { - if (evt.WaitOne(10)) - break; + if (evt.WaitOne(10)) break; //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge if (exiting) @@ -74,11 +67,10 @@ private void Download() } } - //throw new Exception("test of download failure"); +// throw new Exception("test of download failure"); //if we were ordered to exit, bail without wasting any more time - if (exiting) - return; + if (exiting) return; //try acquiring file using (var hf = new HawkFile(fn)) @@ -86,8 +78,7 @@ private void Download() using (var exe = OSTailoredCode.IsUnixHost ? hf.BindArchiveMember("ffmpeg") : hf.BindFirstOf(".exe")) { //last chance. exiting, don't dump the new ffmpeg file - if (exiting) - return; + if (exiting) return; exe!.GetStream().CopyTo(fs); fs.Dispose(); if (OSTailoredCode.IsUnixHost) @@ -110,8 +101,14 @@ private void Download() } finally { - try { File.Delete(fn); } - catch { } + try + { + File.Delete(fn); + } + catch + { + // ignore + } } } @@ -141,8 +138,7 @@ protected override void OnClosed(EventArgs e) private void timer1_Tick(object sender, EventArgs e) { //if it's done, close the window. the user will be smart enough to reopen it - if (succeeded) - Close(); + if (succeeded) Close(); if (failed) { failed = false; @@ -159,4 +155,3 @@ private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs } } } - diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs index cea938757ed..b37956a8749 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Net; using System.Threading; using System.Windows.Forms; @@ -30,12 +31,13 @@ public RAIntegrationDownloaderForm(string url) private bool _exiting = false; private bool _succeeded = false; private bool _failed = false; - private Thread _thread; + + private Thread/*?*/ _thread = null; public bool DownloadSucceeded() { // block until the thread dies - while (_thread?.IsAlive ?? false) + while (_thread is { IsAlive: true }) { Thread.Sleep(1); } @@ -57,23 +59,15 @@ private void Download() { using (var evt = new ManualResetEvent(false)) { - using var client = new System.Net.WebClient(); - System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + using var client = new WebClient(); + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; client.DownloadFileAsync(new Uri(_url), fn); - client.DownloadProgressChanged += (object sender, System.Net.DownloadProgressChangedEventArgs e) => - { - _pct = e.ProgressPercentage; - }; - client.DownloadFileCompleted += (object sender, System.ComponentModel.AsyncCompletedEventArgs e) => - { - //we don't really need a status. we'll just try to unzip it when it's done - evt.Set(); - }; + client.DownloadProgressChanged += (_, progressArgs) => _pct = progressArgs.ProgressPercentage; + client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done - for (; ; ) + while (true) { - if (evt.WaitOne(10)) - break; + if (evt.WaitOne(10)) break; //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge if (_exiting) @@ -85,11 +79,10 @@ private void Download() } } - //throw new Exception("test of download failure"); +// throw new Exception("test of download failure"); //if we were ordered to exit, bail without wasting any more time - if (_exiting) - return; + if (_exiting) return; //try acquiring file using (var dll = new HawkFile(fn)) @@ -97,8 +90,7 @@ private void Download() var data = dll!.ReadAllBytes(); //last chance. exiting, don't dump the new RAIntegration file - if (_exiting) - return; + if (_exiting) return; DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); if (!parentDir.Exists) parentDir.Create(); @@ -114,8 +106,14 @@ private void Download() } finally { - try { File.Delete(fn); } - catch { } + try + { + File.Delete(fn); + } + catch + { + // ignore + } } } @@ -145,8 +143,7 @@ protected override void OnClosed(EventArgs e) private void timer1_Tick(object sender, EventArgs e) { //if it's done, close the window. the user will be smart enough to reopen it - if (_succeeded) - Close(); + if (_succeeded) Close(); if (_failed) { _failed = false; @@ -163,4 +160,3 @@ private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs } } } - From 129390f34821c50dd4b503025c1139d8bf7cb7ad Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Wed, 2 Jul 2025 08:07:55 +1000 Subject: [PATCH 2/5] Copy improvements between `{FFmpeg,RAIntegration}DownloaderForm` --- .../AVOut/FFmpegDownloaderForm.cs | 82 ++++++++++--------- .../RAIntegrationDownloaderForm.cs | 15 ++-- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs index bb899e83567..af549b16156 100644 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs @@ -15,18 +15,24 @@ public partial class FFmpegDownloaderForm : Form { public FFmpegDownloaderForm() { + _path = FFmpegService.FFmpegPath; + _url = FFmpegService.Url; + InitializeComponent(); - txtLocation.Text = FFmpegService.FFmpegPath; - txtUrl.Text = FFmpegService.Url; + txtLocation.Text = _path; + txtUrl.Text = _url; if (OSTailoredCode.IsUnixHost) textBox1.Text = string.Join("\n", textBox1.Text.Split('\n').Take(3)) + "\n\n(Linux user: If installing manually, you can use a symlink.)"; } - private int pct = 0; - private bool exiting = false; - private bool succeeded = false; - private bool failed = false; + private readonly string _path; + private readonly string _url; + + private int _pct = 0; + private bool _exiting = false; + private bool _succeeded = false; + private bool _failed = false; private void ThreadProc() { @@ -40,29 +46,29 @@ private void Download() try { - DirectoryInfo parentDir = new(Path.GetDirectoryName(FFmpegService.FFmpegPath)!); + DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); if (!parentDir.Exists) parentDir.Create(); - using var fs = File.Create(FFmpegService.FFmpegPath); // check writable before bothering with the download + // check writable before bothering with the download + if (File.Exists(_path)) File.Delete(_path); + using var fs = File.Create(_path); using (var evt = new ManualResetEvent(false)) { - using (var client = new WebClient()) + using var client = new WebClient(); + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + client.DownloadFileAsync(new Uri(_url), fn); + client.DownloadProgressChanged += (_, progressArgs) => _pct = progressArgs.ProgressPercentage; + client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done + + while (true) { - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - client.DownloadFileAsync(new Uri(FFmpegService.Url), fn); - client.DownloadProgressChanged += (_, progressArgs) => pct = progressArgs.ProgressPercentage; - client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done + if (evt.WaitOne(10)) break; - while (true) + //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge + if (_exiting) { - if (evt.WaitOne(10)) break; - - //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge - if (exiting) - { - client.CancelAsync(); - evt.WaitOne(); - break; - } + client.CancelAsync(); + evt.WaitOne(); + break; } } } @@ -70,7 +76,7 @@ private void Download() // throw new Exception("test of download failure"); //if we were ordered to exit, bail without wasting any more time - if (exiting) return; + if (_exiting) return; //try acquiring file using (var hf = new HawkFile(fn)) @@ -78,12 +84,12 @@ private void Download() using (var exe = OSTailoredCode.IsUnixHost ? hf.BindArchiveMember("ffmpeg") : hf.BindFirstOf(".exe")) { //last chance. exiting, don't dump the new ffmpeg file - if (exiting) return; + if (_exiting) return; exe!.GetStream().CopyTo(fs); fs.Dispose(); if (OSTailoredCode.IsUnixHost) { - OSTailoredCode.ConstructSubshell("chmod", $"+x {FFmpegService.FFmpegPath}", checkStdout: false).Start(); + OSTailoredCode.ConstructSubshell("chmod", $"+x {_path}", checkStdout: false).Start(); Thread.Sleep(50); // Linux I/O flush idk } } @@ -92,11 +98,11 @@ private void Download() //make sure it worked if (!FFmpegService.QueryServiceAvailable()) throw new Exception("download failed"); - succeeded = true; + _succeeded = true; } catch (Exception e) { - failed = true; + _failed = true; Util.DebugWriteLine($"FFmpeg download failed with:\n{e}"); } finally @@ -116,9 +122,9 @@ private void btnDownload_Click(object sender, EventArgs e) { btnDownload.Text = "Downloading..."; btnDownload.Enabled = false; - failed = false; - succeeded = false; - pct = 0; + _failed = false; + _succeeded = false; + _pct = 0; var t = new Thread(ThreadProc); t.Start(); } @@ -132,26 +138,26 @@ protected override void OnClosed(EventArgs e) { //inform the worker thread that it needs to try terminating without doing anything else //(it will linger on in background for a bit til it can service this) - exiting = true; + _exiting = true; } private void timer1_Tick(object sender, EventArgs e) { //if it's done, close the window. the user will be smart enough to reopen it - if (succeeded) Close(); - if (failed) + if (_succeeded) Close(); + if (_failed) { - failed = false; - pct = 0; + _failed = false; + _pct = 0; btnDownload.Text = "FAILED - Download Again"; btnDownload.Enabled = true; } - progressBar1.Value = pct; + progressBar1.Value = _pct; } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - Util.OpenUrlExternal(FFmpegService.Url); + Util.OpenUrlExternal(_url); } } } diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs index b37956a8749..1740f4a8413 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs @@ -57,6 +57,11 @@ private void Download() try { + DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); + if (!parentDir.Exists) parentDir.Create(); + // check writable before bothering with the download + if (File.Exists(_path)) File.Delete(_path); + using var fs = File.Create(_path); using (var evt = new ManualResetEvent(false)) { using var client = new WebClient(); @@ -87,22 +92,18 @@ private void Download() //try acquiring file using (var dll = new HawkFile(fn)) { - var data = dll!.ReadAllBytes(); - //last chance. exiting, don't dump the new RAIntegration file if (_exiting) return; - DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); - if (!parentDir.Exists) parentDir.Create(); - if (File.Exists(_path)) File.Delete(_path); - File.WriteAllBytes(_path, data); + dll.GetStream().CopyTo(fs); } _succeeded = true; } - catch + catch (Exception e) { _failed = true; + Util.DebugWriteLine($"RAIntegration download failed with:\n{e}"); } finally { From 86cf9d4eb12f4e8e08e135794cbbce3a85a8af00 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Wed, 2 Jul 2025 08:29:03 +1000 Subject: [PATCH 3/5] Extract parent class from `{FFmpeg,RAIntegration}DownloaderForm` --- .../AVOut/FFmpegDownloaderForm.cs | 167 ++-------------- .../AVOut/FFmpegDownloaderForm.resx | 132 ------------- ...Designer.cs => DownloaderForm.Designer.cs} | 13 +- src/BizHawk.Client.EmuHawk/DownloaderForm.cs | 185 ++++++++++++++++++ ...ownloaderForm.resx => DownloaderForm.resx} | 3 - .../RAIntegrationDownloaderForm.Designer.cs | 170 ---------------- .../RAIntegrationDownloaderForm.cs | 159 ++------------- 7 files changed, 222 insertions(+), 607 deletions(-) delete mode 100644 src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.resx rename src/BizHawk.Client.EmuHawk/{AVOut/FFmpegDownloaderForm.Designer.cs => DownloaderForm.Designer.cs} (92%) create mode 100644 src/BizHawk.Client.EmuHawk/DownloaderForm.cs rename src/BizHawk.Client.EmuHawk/{RetroAchievements/RAIntegrationDownloaderForm.resx => DownloaderForm.resx} (94%) delete mode 100644 src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs index af549b16156..49291cc6b18 100644 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs @@ -1,163 +1,34 @@ using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Windows.Forms; using BizHawk.Common; namespace BizHawk.Client.EmuHawk { - /// - /// Downloads FFmpeg - /// - public partial class FFmpegDownloaderForm : Form + public sealed class FFmpegDownloaderForm : DownloaderForm { - public FFmpegDownloaderForm() - { - _path = FFmpegService.FFmpegPath; - _url = FFmpegService.Url; - - InitializeComponent(); - - txtLocation.Text = _path; - txtUrl.Text = _url; - - if (OSTailoredCode.IsUnixHost) textBox1.Text = string.Join("\n", textBox1.Text.Split('\n').Take(3)) + "\n\n(Linux user: If installing manually, you can use a symlink.)"; - } - - private readonly string _path; - private readonly string _url; - - private int _pct = 0; - private bool _exiting = false; - private bool _succeeded = false; - private bool _failed = false; - - private void ThreadProc() - { - Download(); - } - - private void Download() - { - //the temp file is owned by this thread - var fn = TempFileManager.GetTempFilename("ffmpeg_download", ".7z", false); - - try - { - DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); - if (!parentDir.Exists) parentDir.Create(); - // check writable before bothering with the download - if (File.Exists(_path)) File.Delete(_path); - using var fs = File.Create(_path); - using (var evt = new ManualResetEvent(false)) - { - using var client = new WebClient(); - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - client.DownloadFileAsync(new Uri(_url), fn); - client.DownloadProgressChanged += (_, progressArgs) => _pct = progressArgs.ProgressPercentage; - client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done - - while (true) - { - if (evt.WaitOne(10)) break; - - //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge - if (_exiting) - { - client.CancelAsync(); - evt.WaitOne(); - break; - } - } - } + protected override string ComponentName + => "FFmpeg"; -// throw new Exception("test of download failure"); + protected override string DownloadTemp { get; } + = TempFileManager.GetTempFilename("ffmpeg_download", ".7z", delete: false); - //if we were ordered to exit, bail without wasting any more time - if (_exiting) return; - - //try acquiring file - using (var hf = new HawkFile(fn)) - { - using (var exe = OSTailoredCode.IsUnixHost ? hf.BindArchiveMember("ffmpeg") : hf.BindFirstOf(".exe")) - { - //last chance. exiting, don't dump the new ffmpeg file - if (_exiting) return; - exe!.GetStream().CopyTo(fs); - fs.Dispose(); - if (OSTailoredCode.IsUnixHost) - { - OSTailoredCode.ConstructSubshell("chmod", $"+x {_path}", checkStdout: false).Start(); - Thread.Sleep(50); // Linux I/O flush idk - } - } - } - - //make sure it worked - if (!FFmpegService.QueryServiceAvailable()) throw new Exception("download failed"); - - _succeeded = true; - } - catch (Exception e) - { - _failed = true; - Util.DebugWriteLine($"FFmpeg download failed with:\n{e}"); - } - finally - { - try - { - File.Delete(fn); - } - catch - { - // ignore - } - } - } - - private void btnDownload_Click(object sender, EventArgs e) - { - btnDownload.Text = "Downloading..."; - btnDownload.Enabled = false; - _failed = false; - _succeeded = false; - _pct = 0; - var t = new Thread(ThreadProc); - t.Start(); - } - - private void btnCancel_Click(object sender, EventArgs e) - { - Close(); - } - - protected override void OnClosed(EventArgs e) + public FFmpegDownloaderForm() { - //inform the worker thread that it needs to try terminating without doing anything else - //(it will linger on in background for a bit til it can service this) - _exiting = true; + Description = "BizHawk relies on a specific version of FFmpeg. No other version will do. The wrong version will be ignored. There is no way to override this behavior." + + "\n\nThe required version could not be found." + + (OSTailoredCode.IsUnixHost + ? "\n\n(Linux user: If installing manually, you can use a symlink.)" + : "\n\nUse this dialog to download it automatically, or download it yourself from the URL below and place it in the specified location."); + DownloadFrom = FFmpegService.Url; + DownloadTo = FFmpegService.FFmpegPath; } - private void timer1_Tick(object sender, EventArgs e) - { - //if it's done, close the window. the user will be smart enough to reopen it - if (_succeeded) Close(); - if (_failed) - { - _failed = false; - _pct = 0; - btnDownload.Text = "FAILED - Download Again"; - btnDownload.Enabled = true; - } - progressBar1.Value = _pct; - } + protected override Stream GetExtractionStream(HawkFile downloaded) + => (OSTailoredCode.IsUnixHost + ? downloaded.BindArchiveMember("ffmpeg")! + : downloaded.BindFirstOf(".exe")).GetStream(); - private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - Util.OpenUrlExternal(_url); - } + protected override bool PostChmodCheck() + => FFmpegService.QueryServiceAvailable(); } } diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.resx b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.resx deleted file mode 100644 index a14f7748ebb..00000000000 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - BizHawk relies on a specific version of FFmpeg. No other version will do. The wrong version will be ignored. There is no way to override this behavior. - -The required version could not be found. - -Use this dialog to download it automatically, or download it yourself from the URL below and place it in the specified location - - - - - 17, 17 - - \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.Designer.cs b/src/BizHawk.Client.EmuHawk/DownloaderForm.Designer.cs similarity index 92% rename from src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.Designer.cs rename to src/BizHawk.Client.EmuHawk/DownloaderForm.Designer.cs index 79f079f9efe..3ebda1cbecb 100644 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/DownloaderForm.Designer.cs @@ -1,6 +1,6 @@ namespace BizHawk.Client.EmuHawk { - partial class FFmpegDownloaderForm + partial class DownloaderForm { /// /// Required designer variable. @@ -29,7 +29,6 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FFmpegDownloaderForm)); this.btnCancel = new System.Windows.Forms.Button(); this.btnDownload = new System.Windows.Forms.Button(); this.textBox1 = new System.Windows.Forms.TextBox(); @@ -73,7 +72,7 @@ private void InitializeComponent() this.textBox1.ReadOnly = true; this.textBox1.Size = new System.Drawing.Size(699, 95); this.textBox1.TabIndex = 10; - this.textBox1.Text = resources.GetString("textBox1.Text"); + this.textBox1.Text = "%description%"; // // label3 // @@ -112,6 +111,7 @@ private void InitializeComponent() this.txtLocation.ReadOnly = true; this.txtLocation.Size = new System.Drawing.Size(613, 20); this.txtLocation.TabIndex = 15; + this.txtLocation.Text = "%downloadTo%"; // // label1 // @@ -138,8 +138,9 @@ private void InitializeComponent() this.txtUrl.ReadOnly = true; this.txtUrl.Size = new System.Drawing.Size(613, 20); this.txtUrl.TabIndex = 18; + this.txtUrl.Text = "%downloadFrom%"; // - // FFmpegDownloaderForm + // DownloaderForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; @@ -159,10 +160,10 @@ private void InitializeComponent() this.MaximizeBox = false; this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(300, 160); - this.Name = "FFmpegDownloaderForm"; + this.Name = "DownloaderForm"; this.ShowIcon = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Download FFmpeg"; + this.Text = "%windowTitle%"; this.ResumeLayout(false); this.PerformLayout(); diff --git a/src/BizHawk.Client.EmuHawk/DownloaderForm.cs b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs new file mode 100644 index 00000000000..db80ad988cd --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs @@ -0,0 +1,185 @@ +using System.IO; +using System.Net; +using System.Threading; +using System.Windows.Forms; + +using BizHawk.Common; + +namespace BizHawk.Client.EmuHawk +{ + public partial class DownloaderForm : Form + { + protected virtual string ComponentName { get; } = null!; + + public string Description + { + get => textBox1.Text; + init => textBox1.Text = value; + } + + public string DownloadFrom + { + get => txtUrl.Text; + init => txtUrl.Text = value; + } + + protected virtual string DownloadTemp { get; } = null!; + + public string DownloadTo + { + get => txtLocation.Text; + init => txtLocation.Text = value; + } + + public DownloaderForm() + { + InitializeComponent(); + Load += (_, _) => Text = $"Download {ComponentName ?? "COMPONENT UNSET"}"; + } + + private int _pct = 0; + private bool _exiting = false; + private bool _succeeded = false; + private bool _failed = false; + + private Thread/*?*/ _thread = null; + + public bool DownloadSucceeded() + { + // block until the thread dies + while (_thread is { IsAlive: true }) + { + Thread.Sleep(1); + } + + return _succeeded; + } + + private void ThreadProc() + { + Download(); + } + + private void Download() + { + try + { + DirectoryInfo parentDir = new(Path.GetDirectoryName(DownloadTo)!); + if (!parentDir.Exists) parentDir.Create(); + // check writable before bothering with the download + if (File.Exists(DownloadTo)) File.Delete(DownloadTo); + using var fs = File.Create(DownloadTo); + using (var evt = new ManualResetEvent(false)) + { + using var client = new WebClient(); + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + client.DownloadFileAsync(new Uri(DownloadFrom), DownloadTemp); + client.DownloadProgressChanged += (_, progressArgs) => _pct = progressArgs.ProgressPercentage; + client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done + + while (true) + { + if (evt.WaitOne(10)) break; + + //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge + if (_exiting) + { + client.CancelAsync(); + evt.WaitOne(); + break; + } + } + } + +// throw new Exception("test of download failure"); + + //if we were ordered to exit, bail without wasting any more time + if (_exiting) return; + + //try acquiring file + using (HawkFile hf = new(DownloadTemp)) + { + using var exStream = GetExtractionStream(hf); + //last chance. exiting, don't dump the new file + if (_exiting) return; + exStream.CopyTo(fs); + fs.Dispose(); + if (OSTailoredCode.IsUnixHost) + { + OSTailoredCode.ConstructSubshell("chmod", $"+x {DownloadTo}", checkStdout: false).Start(); + Thread.Sleep(50); // Linux I/O flush idk + } + } + + //make sure it worked + if (!PostChmodCheck()) throw new Exception("download failed"); + + _succeeded = true; + } + catch (Exception e) + { + _failed = true; + Util.DebugWriteLine($"{ComponentName} download failed with:\n{e}"); + } + finally + { + try + { + File.Delete(DownloadTemp); + } + catch + { + // ignore + } + } + } + + protected virtual Stream GetExtractionStream(HawkFile downloaded) + => throw new NotImplementedException(); + + protected virtual bool PostChmodCheck() + => true; + + private void btnDownload_Click(object sender, EventArgs e) + { + btnDownload.Text = "Downloading..."; + btnDownload.Enabled = false; + _failed = false; + _succeeded = false; + _pct = 0; + _thread = new(ThreadProc); + _thread.Start(); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + //inform the worker thread that it needs to try terminating without doing anything else + //(it will linger on in background for a bit til it can service this) + _exiting = true; + } + + private void timer1_Tick(object sender, EventArgs e) + { + //if it's done, close the window. the user will be smart enough to reopen it + if (_succeeded) Close(); + if (_failed) + { + _failed = false; + _pct = 0; + btnDownload.Text = "FAILED - Download Again"; + btnDownload.Enabled = true; + } + progressBar1.Value = _pct; + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Util.OpenUrlExternal(DownloadFrom); + } + } +} diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.resx b/src/BizHawk.Client.EmuHawk/DownloaderForm.resx similarity index 94% rename from src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.resx rename to src/BizHawk.Client.EmuHawk/DownloaderForm.resx index aac33d5a29f..29dcb1b3a35 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.resx +++ b/src/BizHawk.Client.EmuHawk/DownloaderForm.resx @@ -117,7 +117,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs deleted file mode 100644 index ad2438afc4a..00000000000 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.Designer.cs +++ /dev/null @@ -1,170 +0,0 @@ -namespace BizHawk.Client.EmuHawk -{ - partial class RAIntegrationDownloaderForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.btnCancel = new System.Windows.Forms.Button(); - this.btnDownload = new System.Windows.Forms.Button(); - this.label3 = new BizHawk.WinForms.Controls.LocLabelEx(); - this.linkLabel1 = new System.Windows.Forms.LinkLabel(); - this.progressBar1 = new System.Windows.Forms.ProgressBar(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.txtLocation = new System.Windows.Forms.TextBox(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.txtUrl = new System.Windows.Forms.TextBox(); - this.SuspendLayout(); - // - // btnCancel - // - this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.Location = new System.Drawing.Point(667, 86); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(75, 23); - this.btnCancel.TabIndex = 7; - this.btnCancel.Text = "Cancel"; - this.btnCancel.UseVisualStyleBackColor = true; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // btnDownload - // - this.btnDownload.Location = new System.Drawing.Point(9, 86); - this.btnDownload.Name = "btnDownload"; - this.btnDownload.Size = new System.Drawing.Size(186, 23); - this.btnDownload.TabIndex = 6; - this.btnDownload.Text = "Download"; - this.btnDownload.UseVisualStyleBackColor = true; - this.btnDownload.Click += new System.EventHandler(this.btnDownload_Click); - // - // label3 - // - this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.label3.Location = new System.Drawing.Point(6, -35); - this.label3.MaximumSize = new System.Drawing.Size(260, 0); - this.label3.Name = "label3"; - // - // linkLabel1 - // - this.linkLabel1.AutoSize = true; - this.linkLabel1.Location = new System.Drawing.Point(715, 61); - this.linkLabel1.Name = "linkLabel1"; - this.linkLabel1.Size = new System.Drawing.Size(27, 13); - this.linkLabel1.TabIndex = 12; - this.linkLabel1.TabStop = true; - this.linkLabel1.Text = "Link"; - this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); - // - // progressBar1 - // - this.progressBar1.Location = new System.Drawing.Point(201, 86); - this.progressBar1.Name = "progressBar1"; - this.progressBar1.Size = new System.Drawing.Size(186, 23); - this.progressBar1.TabIndex = 13; - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Tick += new System.EventHandler(this.timer1_Tick); - // - // txtLocation - // - this.txtLocation.Location = new System.Drawing.Point(95, 12); - this.txtLocation.Name = "txtLocation"; - this.txtLocation.ReadOnly = true; - this.txtLocation.Size = new System.Drawing.Size(613, 20); - this.txtLocation.TabIndex = 15; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(9, 15); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(80, 13); - this.label1.TabIndex = 16; - this.label1.Text = "Local Location:"; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(57, 41); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(32, 13); - this.label2.TabIndex = 17; - this.label2.Text = "URL:"; - // - // txtUrl - // - this.txtUrl.Location = new System.Drawing.Point(96, 38); - this.txtUrl.Name = "txtUrl"; - this.txtUrl.ReadOnly = true; - this.txtUrl.Size = new System.Drawing.Size(613, 20); - this.txtUrl.TabIndex = 18; - // - // RAIntegrationDownloaderForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.btnCancel; - this.ClientSize = new System.Drawing.Size(754, 121); - this.Controls.Add(this.progressBar1); - this.Controls.Add(this.txtUrl); - this.Controls.Add(this.btnDownload); - this.Controls.Add(this.label2); - this.Controls.Add(this.linkLabel1); - this.Controls.Add(this.label1); - this.Controls.Add(this.txtLocation); - this.Controls.Add(this.btnCancel); - this.Controls.Add(this.label3); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(300, 160); - this.Name = "RAIntegrationDownloaderForm"; - this.ShowIcon = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Download RAIntegration"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - private BizHawk.WinForms.Controls.LocLabelEx label3; - private System.Windows.Forms.Button btnCancel; - private System.Windows.Forms.Button btnDownload; - private System.Windows.Forms.LinkLabel linkLabel1; - private System.Windows.Forms.ProgressBar progressBar1; - private System.Windows.Forms.Timer timer1; - private System.Windows.Forms.TextBox txtLocation; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.TextBox txtUrl; - } -} \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs index 1740f4a8413..0b72ca395aa 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs @@ -1,163 +1,26 @@ using System.IO; -using System.Net; -using System.Threading; -using System.Windows.Forms; using BizHawk.Common; using BizHawk.Common.PathExtensions; namespace BizHawk.Client.EmuHawk { - /// - /// Downloads RAIntegration (largely a copy paste of ffmpeg's downloader) - /// - public partial class RAIntegrationDownloaderForm : Form + public sealed class RAIntegrationDownloaderForm : DownloaderForm { - public RAIntegrationDownloaderForm(string url) - { - _path = Path.Combine(PathUtils.DataDirectoryPath, "dll", "RA_Integration-x64.dll"); - _url = url; - - InitializeComponent(); - - txtLocation.Text = _path; - txtUrl.Text = _url; - } - - private readonly string _path; - private readonly string _url; - - private int _pct = 0; - private bool _exiting = false; - private bool _succeeded = false; - private bool _failed = false; - - private Thread/*?*/ _thread = null; - - public bool DownloadSucceeded() - { - // block until the thread dies - while (_thread is { IsAlive: true }) - { - Thread.Sleep(1); - } + protected override string ComponentName + => "RAIntegration"; - return _succeeded; - } + protected override string DownloadTemp { get; } + = TempFileManager.GetTempFilename("RAIntegration_download", ".dll", delete: false); - private void ThreadProc() + public RAIntegrationDownloaderForm(string downloadFrom) { - Download(); + Description = string.Empty; + DownloadFrom = downloadFrom; + DownloadTo = Path.Combine(PathUtils.DataDirectoryPath, "dll", "RA_Integration-x64.dll"); } - private void Download() - { - //the temp file is owned by this thread - var fn = TempFileManager.GetTempFilename("RAIntegration_download", ".dll", false); - - try - { - DirectoryInfo parentDir = new(Path.GetDirectoryName(_path)!); - if (!parentDir.Exists) parentDir.Create(); - // check writable before bothering with the download - if (File.Exists(_path)) File.Delete(_path); - using var fs = File.Create(_path); - using (var evt = new ManualResetEvent(false)) - { - using var client = new WebClient(); - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - client.DownloadFileAsync(new Uri(_url), fn); - client.DownloadProgressChanged += (_, progressArgs) => _pct = progressArgs.ProgressPercentage; - client.DownloadFileCompleted += (_, _) => evt.Set(); // we don't really need a status, we'll just try to unzip it when it's done - - while (true) - { - if (evt.WaitOne(10)) break; - - //if the gui thread ordered an exit, cancel the download and wait for it to acknowledge - if (_exiting) - { - client.CancelAsync(); - evt.WaitOne(); - break; - } - } - } - -// throw new Exception("test of download failure"); - - //if we were ordered to exit, bail without wasting any more time - if (_exiting) return; - - //try acquiring file - using (var dll = new HawkFile(fn)) - { - //last chance. exiting, don't dump the new RAIntegration file - if (_exiting) return; - - dll.GetStream().CopyTo(fs); - } - - _succeeded = true; - } - catch (Exception e) - { - _failed = true; - Util.DebugWriteLine($"RAIntegration download failed with:\n{e}"); - } - finally - { - try - { - File.Delete(fn); - } - catch - { - // ignore - } - } - } - - private void btnDownload_Click(object sender, EventArgs e) - { - btnDownload.Text = "Downloading..."; - btnDownload.Enabled = false; - _failed = false; - _succeeded = false; - _pct = 0; - _thread = new Thread(ThreadProc); - _thread.Start(); - } - - private void btnCancel_Click(object sender, EventArgs e) - { - Close(); - } - - protected override void OnClosed(EventArgs e) - { - //inform the worker thread that it needs to try terminating without doing anything else - //(it will linger on in background for a bit til it can service this) - _exiting = true; - } - - private void timer1_Tick(object sender, EventArgs e) - { - //if it's done, close the window. the user will be smart enough to reopen it - if (_succeeded) Close(); - if (_failed) - { - _failed = false; - _pct = 0; - btnDownload.Text = "FAILED - Download Again"; - btnDownload.Enabled = true; - } - progressBar1.Value = _pct; - } - - private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - Util.OpenUrlExternal(_url); - } + protected override Stream GetExtractionStream(HawkFile downloaded) + => downloaded.GetStream(); } } From d91b5acb2398185084d9313dab8469ce9c8b7e01 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Wed, 2 Jul 2025 08:17:16 +1000 Subject: [PATCH 4/5] `WaitForExit` instead of arbitrarily sleeping in `DownloaderForm` --- src/BizHawk.Client.EmuHawk/DownloaderForm.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/DownloaderForm.cs b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs index db80ad988cd..6370216dd7c 100644 --- a/src/BizHawk.Client.EmuHawk/DownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs @@ -106,8 +106,9 @@ private void Download() fs.Dispose(); if (OSTailoredCode.IsUnixHost) { - OSTailoredCode.ConstructSubshell("chmod", $"+x {DownloadTo}", checkStdout: false).Start(); - Thread.Sleep(50); // Linux I/O flush idk + var proc = OSTailoredCode.ConstructSubshell("chmod", $"+x {DownloadTo}", checkStdout: false); + proc.Start(); + proc.WaitForExit(); } } From 05a7e1176560a0b3f3a61b4873d5f5acdac68248 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Thu, 10 Apr 2025 21:17:03 +1000 Subject: [PATCH 5/5] Add some more validation to `{FFmpeg,RAIntegration}DownloaderForm` see #4275 --- src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs | 4 ++++ src/BizHawk.Client.EmuHawk/DownloaderForm.cs | 7 ++++++- .../RetroAchievements/RAIntegrationDownloaderForm.cs | 6 ++++++ src/BizHawk.Common/FFmpegService.cs | 7 +++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs index 49291cc6b18..f534f4f711f 100644 --- a/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/AVOut/FFmpegDownloaderForm.cs @@ -1,6 +1,7 @@ using System.IO; using BizHawk.Common; +using BizHawk.Common.IOExtensions; namespace BizHawk.Client.EmuHawk { @@ -30,5 +31,8 @@ protected override Stream GetExtractionStream(HawkFile downloaded) protected override bool PostChmodCheck() => FFmpegService.QueryServiceAvailable(); + + protected override bool PreChmodCheck(FileStream extracted) + => SHA256Checksum.ComputeDigestHex(extracted.ReadAllBytes()) == FFmpegService.DownloadSHA256Checksum; } } diff --git a/src/BizHawk.Client.EmuHawk/DownloaderForm.cs b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs index 6370216dd7c..d406fc13662 100644 --- a/src/BizHawk.Client.EmuHawk/DownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/DownloaderForm.cs @@ -103,6 +103,8 @@ private void Download() //last chance. exiting, don't dump the new file if (_exiting) return; exStream.CopyTo(fs); + fs.Position = 0L; + if (!PreChmodCheck(fs)) throw new Exception("download failed (pre-chmod validation)"); fs.Dispose(); if (OSTailoredCode.IsUnixHost) { @@ -113,7 +115,7 @@ private void Download() } //make sure it worked - if (!PostChmodCheck()) throw new Exception("download failed"); + if (!PostChmodCheck()) throw new Exception("download failed (post-chmod validation)"); _succeeded = true; } @@ -141,6 +143,9 @@ protected virtual Stream GetExtractionStream(HawkFile downloaded) protected virtual bool PostChmodCheck() => true; + protected virtual bool PreChmodCheck(FileStream extracted) + => true; + private void btnDownload_Click(object sender, EventArgs e) { btnDownload.Text = "Downloading..."; diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs index 0b72ca395aa..44a1a4f9a63 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RAIntegrationDownloaderForm.cs @@ -2,6 +2,7 @@ using BizHawk.Common; using BizHawk.Common.PathExtensions; +using BizHawk.Common.StringExtensions; namespace BizHawk.Client.EmuHawk { @@ -15,6 +16,11 @@ protected override string ComponentName public RAIntegrationDownloaderForm(string downloadFrom) { + var downloadDomainName = downloadFrom.RemovePrefix("https://").SubstringBefore('/'); + if (!(downloadDomainName is "retroachievements.org" || downloadDomainName.EndsWith(".retroachievements.org"))) + { + throw new ArgumentException(paramName: nameof(downloadFrom), message: "untrusted hostname"); + } Description = string.Empty; DownloadFrom = downloadFrom; DownloadTo = Path.Combine(PathUtils.DataDirectoryPath, "dll", "RA_Integration-x64.dll"); diff --git a/src/BizHawk.Common/FFmpegService.cs b/src/BizHawk.Common/FFmpegService.cs index 8557cc150be..f6e71e7179b 100644 --- a/src/BizHawk.Common/FFmpegService.cs +++ b/src/BizHawk.Common/FFmpegService.cs @@ -17,8 +17,15 @@ public static class FFmpegService private const string BIN_HOST_URI_WIN_X64 = "https://github.com/TASEmulators/ffmpeg-binaries/raw/master/ffmpeg-4.4.1-static-windows-x64.7z"; + private const string BIN_SHA256_LINUX_X64 = "3EA58083710F63BF920B16C7D5D24AE081E7D731F57A656FED11AF0410D4EB48"; + + private const string BIN_SHA256_WIN_X64 = "8436760AF8F81C95EFF92D854A7684E6D3CEDB872888420359FC45C8EB2664AC"; + private const string VERSION = "ffmpeg version 4.4.1"; + public static string DownloadSHA256Checksum + => OSTailoredCode.IsUnixHost ? BIN_SHA256_LINUX_X64 : BIN_SHA256_WIN_X64; + public static string FFmpegPath => Path.Combine(PathUtils.DataDirectoryPath, "dll", OSTailoredCode.IsUnixHost ? "ffmpeg" : "ffmpeg.exe"); public static readonly string Url = OSTailoredCode.IsUnixHost ? BIN_HOST_URI_LINUX_X64 : BIN_HOST_URI_WIN_X64;