using System;
using ADOFAIRenderer.Localization;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using MelonLoader.Utils;
using O5Kit.Core;
using O5Kit.Factory;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ADOFAIRenderer.UI;
///
/// Modal dialog that installs the FFmpeg shared libraries into MelonLoader's UserLibs folder.
///
/// Shown by FirstUpdateHook when reports missing libraries. On Windows and Linux
/// the "Download" button downloads a prebuilt BtbN FFmpeg build (GPL, shared libraries) and extracts the libraries
/// into UserLibs; on macOS the button opens https://ffmpeg.org, because the dialog text tells the user to build FFmpeg
/// manually there. The libraries are found by FFmpeg.AutoGen through ffmpeg.RootPath (set to UserLibs in
/// RenderMod.OnInitializeMelon).
///
///
/// Threads: UI construction and all Unity object access happen on the main Unity thread. The download and extraction
/// run on thread-pool threads (); they post UI updates to
/// , which drains on the main Unity thread.
///
///
/// Flow: buttons -> (Download) progress bar + status text -> download to a temporary archive in UserLibs -> extract
/// libraries -> "complete" for 1 second -> dialog destroyed. On failure the status shows the error and the buttons
/// return so the user can retry or cancel. Cancel destroys the dialog at once and cancels a running download.
///
///
internal class FFmpegDownloadUI : MonoBehaviour
{
/// Row with the Download/Open Website and Cancel buttons; hidden while a download runs.
private GameObject _buttonContainerObj = null!;
/// Progress bar background (rounded, theme colors); shown when the download starts.
private GameObject _progressBarObj = null!;
/// Progress bar fill object.
private GameObject _progressBarFillObj = null!;
/// Fill rect; its anchorMax.x is the progress fraction 0..1 (see ).
private RectTransform _fillRect = null!;
/// Text centered on the progress bar (percentage or "Extracting...").
private TextMeshProUGUI _progressText = null!;
/// Status line below the progress bar; shown when the download starts.
private GameObject _statusTextObj = null!;
/// Status message, colored by state (gray starting, cyan downloading, yellow extracting, green done, red failed).
private TextMeshProUGUI _statusText = null!;
/// O5Kit window holding the dialog contents; disposed in .
private O5Window? _window;
/// True from the Download click until the download fails; prevents a second concurrent download.
private bool _isDownloading;
///
/// Archive URL for the current platform: a fixed BtbN FFmpeg-Builds autobuild (2026-05-31, FFmpeg master
/// N-124714-g49a77d37be, GPL, shared libraries). Windows gets the win64 .zip; every other platform gets the
/// linux64 .tar.xz (macOS never downloads, see ).
///
private string DownloadUrl => Application.platform switch
{
RuntimePlatform.WindowsPlayer => "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-05-31-13-22/ffmpeg-N-124714-g49a77d37be-win64-gpl-shared.zip",
_ => "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-05-31-13-22/ffmpeg-N-124714-g49a77d37be-linux64-gpl-shared.tar.xz"
};
/// Cancellation for the running download; created per download, cancelled by Cancel, disposed in OnDestroy.
private System.Threading.CancellationTokenSource? _cts;
/// Actions posted from background threads, run in order on the main Unity thread by .
private readonly System.Collections.Concurrent.ConcurrentQueue _mainThreadQueue = new();
///
/// Whether UserLibs contains at least one file for each of the five FFmpeg libraries the mod uses
/// (avcodec, avformat, avutil, swscale, swresample), matched by platform-specific file name patterns:
/// avcodec-*.dll on Windows, libavcodec.*.dylib on macOS, libavcodec.so* elsewhere.
/// Only file presence is checked, not the library version or whether it loads.
///
/// false when UserLibs does not exist or any library is missing.
public static bool CheckFFmpegExists()
{
var userLibs = MelonEnvironment.UserLibsDirectory;
if (!Directory.Exists(userLibs)) return false;
string[] searchPatterns = Application.platform switch
{
RuntimePlatform.WindowsPlayer => new[]
{
"avcodec-*.dll", "avformat-*.dll", "avutil-*.dll", "swscale-*.dll", "swresample-*.dll"
},
RuntimePlatform.OSXPlayer => new[]
{
"libavcodec.*.dylib", "libavformat.*.dylib", "libavutil.*.dylib", "libswscale.*.dylib", "libswresample.*.dylib"
},
_ => new[]
{
"libavcodec.so*", "libavformat.so*", "libavutil.so*", "libswscale.so*", "libswresample.so*"
}
};
foreach (var pattern in searchPatterns)
{
if (Directory.GetFiles(userLibs, pattern).Length == 0)
{
return false;
}
}
return true;
}
///
/// Creates the dialog on a new GameObject kept across scene loads, unless the libraries are already present.
/// Main thread only.
///
public static void ShowDownloadUI()
{
if (CheckFFmpegExists()) return;
var obj = new GameObject("FFmpegDownloadUI");
DontDestroyOnLoad(obj);
obj.AddComponent();
}
///
/// The open dialog, or null after it is destroyed. FirstUpdateHook waits while it is set and checks
/// the libraries again after it closes.
///
public static FFmpegDownloadUI? Instance { get; private set; }
/// Registers ; destroys the dialog again when the libraries already exist.
private void Awake()
{
Instance = this;
if (CheckFFmpegExists())
{
Destroy(gameObject);
}
}
///
/// Builds the canvas, backdrop and window: description, button row, and the hidden progress bar and status line.
/// Layout values are canvas units at the 1920x1080 reference resolution.
///
private void Start()
{
// Set UI layer (5, Unity's built-in "UI" layer)
gameObject.layer = 5;
// Canvas setup
var canvas = gameObject.AddComponent