using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BestestTVModPlugin;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using YoutubeDLSharp;
using YoutubeDLSharp.Metadata;
using YoutubeDLSharp.Options;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("YoutubeDownloader")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0e14460f7cfc4423db0502c178e3415750870e39")]
[assembly: AssemblyProduct("YoutubeDownloader")]
[assembly: AssemblyTitle("YoutubeDownloader")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
}
namespace YoutubeDownloader
{
[BepInPlugin("DeathWrench.YoutubeDownloader", "\u200bYoutubeDownloader", "0.0.4")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
private ConfigEntry<bool> playlistModeActivated;
private ConfigEntry<bool> skipDownloadEnabled;
private ConfigEntry<bool> deleteTelevisionVideos;
private ConfigEntry<Key> downloadKeyBind;
private ConfigEntry<string> defaultVideoUrl;
private ConfigEntry<int> startOfPlaylist;
private ConfigEntry<int> endOfPlaylist;
public static ManualLogSource Log = new ManualLogSource("YoutubeDownloader");
private static readonly Harmony Harmony = new Harmony("DeathWrench.YoutubeDownloader");
private YoutubeDL ytdl;
private InputAction downloadAction;
private static string pluginPath = Paths.PluginPath + Path.DirectorySeparatorChar + "DeathWrench-YoutubeDownloader";
private static string libraryPath = Path.Combine(pluginPath, "lib");
private string ytDlpPath = Path.Combine(libraryPath, "yt-dlp.exe");
private string televisionVideosPath = Path.Combine(pluginPath, "Television Videos");
private async void Start()
{
Log.LogInfo((object)"Plugin DeathWrench.YoutubeDownloader is loaded!");
playlistModeActivated = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", false, "Download playlists?");
skipDownloadEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", false, "Enable skipping downloads if the file already exists. This is ignored in Playlist Mode.");
downloadKeyBind = ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", (Key)6, "Which key to press to initiate YouTube video download.");
defaultVideoUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", "https://www.youtube.com/watch?v=4Nty0riqSOs", "YouTube video URL for downloading.");
startOfPlaylist = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", 1, "Select specific videos in a playlist to download. Setting to 5 will download the 5th video in the playlist.");
endOfPlaylist = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", 999999, "Which video to stop downloading at. Setting to 7 with start set to 5 will only download the 5th, 6th, and 7th videos.");
deleteTelevisionVideos = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", false, "Delete the downloaded videos every time the game starts?");
if (!Directory.Exists(televisionVideosPath))
{
Directory.CreateDirectory(televisionVideosPath);
}
if (deleteTelevisionVideos.Value)
{
string[] filesToDelete = Directory.GetFiles(televisionVideosPath, "*.mp4");
string[] array = filesToDelete;
foreach (string fileToDelete in array)
{
File.Delete(fileToDelete);
}
}
if (!Directory.Exists(libraryPath))
{
Directory.CreateDirectory(libraryPath);
}
if (!File.Exists(libraryPath + "\\yt-dlp.exe"))
{
await DownloadLatestYtDlpRelease(libraryPath + "\\yt-dlp.exe");
}
else
{
Log.LogInfo((object)"yt-dlp already exists. Skipping download.");
}
if (!File.Exists(libraryPath + "\\ffmpeg.exe"))
{
await DownloadAndExtractFFmpeg(libraryPath + "\\ffmpeg.exe");
}
else
{
Log.LogInfo((object)"FFmpeg already exists. Skipping download.");
}
ytdl = new YoutubeDL((byte)4);
ytdl.YoutubeDLPath = libraryPath + "\\yt-dlp.exe";
ytdl.FFmpegPath = libraryPath + "\\ffmpeg.exe";
Key downloadKey = downloadKeyBind.Value;
downloadAction = new InputAction((string)null, (InputActionType)0, $"<Keyboard>/{downloadKey}", "press", (string)null, (string)null);
downloadAction.Enable();
downloadAction.performed += async delegate(CallbackContext ctx)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
if (((CallbackContext)(ref ctx)).ReadValueAsButton())
{
if (CheckDiskSpace(videoSize: await GetVideoSize(defaultVideoUrl.Value), path: televisionVideosPath))
{
if (!playlistModeActivated.Value)
{
HUDManager.Instance.DisplayTip("Download in Progress", "Please wait while the video is being downloaded...", false, false, "DownloadProgressTip");
await DownloadVideo(defaultVideoUrl.Value, televisionVideosPath);
}
else
{
HUDManager.Instance.DisplayTip("Download in Progress", "Please wait while the playlist is being downloaded...", false, false, "DownloadProgressTip");
await DownloadPlaylist(defaultVideoUrl.Value, televisionVideosPath);
}
}
else
{
HUDManager.Instance.DisplayTip("Download Error", "Not enough free disk space to download the video.", false, false, "DownloadErrorTip");
}
}
};
Harmony.PatchAll();
}
private void Update()
{
if (CheckForConfigChanges())
{
UpdateSettings();
}
if (CheckForNewFiles())
{
HandleNewFiles();
}
if (Keyboard.current.ctrlKey.isPressed && ((ButtonControl)Keyboard.current.vKey).wasPressedThisFrame)
{
PasteTextFromClipboard();
}
}
private void PasteTextFromClipboard()
{
string systemCopyBuffer = GUIUtility.systemCopyBuffer;
Debug.Log((object)("Pasting text from clipboard: " + systemCopyBuffer));
}
private bool CheckForConfigChanges()
{
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Unknown result type (might be due to invalid IL or missing references)
if (playlistModeActivated.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", playlistModeActivated.Value, (ConfigDescription)null).Value || skipDownloadEnabled.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", skipDownloadEnabled.Value, (ConfigDescription)null).Value || downloadKeyBind.Value != ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", downloadKeyBind.Value, (ConfigDescription)null).Value || defaultVideoUrl.Value != ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", defaultVideoUrl.Value, (ConfigDescription)null).Value || startOfPlaylist.Value != ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", startOfPlaylist.Value, (ConfigDescription)null).Value || endOfPlaylist.Value != ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", endOfPlaylist.Value, (ConfigDescription)null).Value || deleteTelevisionVideos.Value != ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", deleteTelevisionVideos.Value, (ConfigDescription)null).Value)
{
return true;
}
return false;
}
private void UpdateSettings()
{
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
playlistModeActivated.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Playlist Mode", playlistModeActivated.Value, (ConfigDescription)null).Value;
skipDownloadEnabled.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Skip Downloads", skipDownloadEnabled.Value, (ConfigDescription)null).Value;
downloadKeyBind.Value = ((BaseUnityPlugin)this).Config.Bind<Key>("Settings", "Download Keybind", downloadKeyBind.Value, (ConfigDescription)null).Value;
defaultVideoUrl.Value = ((BaseUnityPlugin)this).Config.Bind<string>("Settings", "Video URL", defaultVideoUrl.Value, (ConfigDescription)null).Value;
startOfPlaylist.Value = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "Start of Playlist", startOfPlaylist.Value, (ConfigDescription)null).Value;
endOfPlaylist.Value = ((BaseUnityPlugin)this).Config.Bind<int>("Settings", "End of Playlist", endOfPlaylist.Value, (ConfigDescription)null).Value;
deleteTelevisionVideos.Value = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Delete Videos", deleteTelevisionVideos.Value, (ConfigDescription)null).Value;
}
private bool CheckForNewFiles()
{
string[] files = Directory.GetFiles(televisionVideosPath, "*.mp4");
if (files.Length > VideoManager.Videos.Count)
{
return true;
}
return false;
}
private void HandleNewFiles()
{
VideoManager.Videos.Clear();
VideoManager.Load();
if (ConfigManager.reloadedVideosHUD.Value)
{
HUDManager.Instance.DisplayTip("Reloaded Videos", "Video list has been reloaded.", false, false, "ReloadVideosTip");
}
}
private async Task DownloadLatestYtDlpRelease(string destinationFolder)
{
string latestReleaseUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(latestReleaseUrl);
request.AllowAutoRedirect = true;
try
{
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
string redirectedUrl = response.ResponseUri.AbsoluteUri;
string releaseDateOrVersion = redirectedUrl.Split('/')[^1];
string downloadUrl = "https://github.com/yt-dlp/yt-dlp/releases/download/" + releaseDateOrVersion + "/yt-dlp.exe";
string downloadPath = ytDlpPath;
using WebClient client = new WebClient();
client.DownloadFile(downloadUrl, downloadPath);
Log.LogInfo((object)"Download completed successfully.");
}
catch (Exception ex2)
{
Exception ex = ex2;
Log.LogError((object)("Error: " + ex.Message));
}
}
private async Task DownloadAndExtractFFmpeg(string ffmpegPath)
{
Log.LogInfo((object)"Downloading FFmpeg...");
string releaseUrl = "https://github.com/GyanD/codexffmpeg/releases/latest/";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(releaseUrl);
request.AllowAutoRedirect = true;
string actualReleaseUrl;
try
{
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
actualReleaseUrl = response.ResponseUri.AbsoluteUri;
}
catch (Exception ex4)
{
Exception ex3 = ex4;
Log.LogError((object)("Error getting release URL: " + ex3.Message));
return;
}
string releaseVersion = actualReleaseUrl.Split('/')[^1];
string ffmpegZipUrl = "https://github.com/GyanD/codexffmpeg/releases/download/" + releaseVersion + "/ffmpeg-" + releaseVersion + "-essentials_build.zip";
string tempZipPath = Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build.zip");
using (WebClient client = new WebClient())
{
await client.DownloadFileTaskAsync(ffmpegZipUrl, tempZipPath);
}
try
{
ZipFile.ExtractToDirectory(tempZipPath, pluginPath);
Log.LogInfo((object)"FFmpeg downloaded and extracted successfully.");
string binDirectory = Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build", "bin");
string[] files = Directory.GetFiles(binDirectory);
foreach (string filePath in files)
{
string destinationPath = Path.Combine(path2: Path.GetFileName(filePath), path1: libraryPath);
File.Move(filePath, destinationPath);
Log.LogInfo((object)("FFmpeg binary moved to: " + destinationPath));
}
}
catch (Exception ex2)
{
Log.LogError((object)("Failed to extract FFmpeg zip: " + ex2.Message));
}
try
{
File.Delete(tempZipPath);
Directory.Delete(Path.Combine(pluginPath, "ffmpeg-" + releaseVersion + "-essentials_build"), recursive: true);
}
catch (Exception ex)
{
Log.LogWarning((object)("Failed to delete temporary files: " + ex.Message));
}
}
private async Task<long> GetVideoSize(string videoUrl)
{
using HttpClient client = new HttpClient();
HttpRequestMessage headRequest = new HttpRequestMessage(HttpMethod.Head, videoUrl);
HttpResponseMessage response = await client.SendAsync(headRequest);
if (response.IsSuccessStatusCode && response.Content.Headers.ContentLength.HasValue)
{
long videoSize = response.Content.Headers.ContentLength.Value;
Log.LogInfo((object)$"Video size obtained: {videoSize} bytes");
return videoSize;
}
Log.LogError((object)"Failed to obtain video size. Default size set to 0 bytes");
return 0L;
}
private bool CheckDiskSpace(string path, long videoSize)
{
DriveInfo driveInfo = new DriveInfo(Path.GetPathRoot(path));
long availableFreeSpace = driveInfo.AvailableFreeSpace;
bool flag = availableFreeSpace >= videoSize;
if (!flag)
{
Log.LogWarning((object)"Not enough free disk space to download the video.");
Log.LogWarning((object)$"Required space: {videoSize} bytes, Available space: {availableFreeSpace} bytes");
}
else
{
Log.LogInfo((object)"Sufficient disk space available for download.");
Log.LogInfo((object)$"Required space: {videoSize} bytes, Available space: {availableFreeSpace} bytes");
}
return flag;
}
private async Task<bool> IsVideoAlreadyDownloaded(string videoUrl, string destinationFolder)
{
RunResult<VideoData> res = await ytdl.RunVideoDataFetch(videoUrl, default(CancellationToken), true, false, (OptionSet)null);
if (res == null)
{
Log.LogError((object)("Failed to fetch video metadata for URL: " + videoUrl));
return false;
}
VideoData video = res.Data;
string title = video.Title;
string videoId = video.ID;
string fileNamePattern = "\\[" + Regex.Escape(videoId) + "\\]\\.mp4";
Regex regex = new Regex(fileNamePattern);
string[] files = Directory.GetFiles(destinationFolder);
string[] array = files;
foreach (string filePath in array)
{
string fileName = Path.GetFileName(filePath);
if (regex.IsMatch(fileName))
{
Log.LogInfo((object)("Video '" + title + "' already exists. Skipping download."));
return true;
}
}
return false;
}
private async Task DownloadPlaylist(string playlistUrl, string destinationFolder)
{
Log.LogInfo((object)"Downloading playlist...");
ytdl.OutputFolder = destinationFolder;
OptionSet options = new OptionSet
{
Format = "best",
RecodeVideo = (VideoRecodeFormat)1
};
if (await ytdl.RunVideoPlaylistDownload(playlistUrl, (int?)startOfPlaylist.Value, (int?)endOfPlaylist.Value, (int[])null, "bestvideo+bestaudio/best", (VideoRecodeFormat)0, default(CancellationToken), (IProgress<DownloadProgress>)null, (IProgress<string>)null, options) == null)
{
HUDManager.Instance.DisplayTip("Download Error", "Failed to download the playlist.", false, false, "DownloadErrorTip");
return;
}
HUDManager.Instance.DisplayTip("Download Complete", "The playlist has been successfully downloaded.", false, false, "DownloadCompleteTip");
VideoManager.Videos.Clear();
VideoManager.Load();
}
private async Task DownloadVideo(string videoUrl, string destinationFolder)
{
Log.LogInfo((object)"Downloading video...");
if (skipDownloadEnabled.Value && await IsVideoAlreadyDownloaded(videoUrl, destinationFolder))
{
HUDManager.Instance.DisplayTip("Download Skipped", "The video is already downloaded.", false, false, "DownloadSkippedTip");
return;
}
ytdl.OutputFolder = destinationFolder;
OptionSet options = new OptionSet
{
Format = "best",
RecodeVideo = (VideoRecodeFormat)1
};
if (await ytdl.RunVideoDownload(videoUrl, "bestvideo+bestaudio/best", (DownloadMergeFormat)0, (VideoRecodeFormat)0, default(CancellationToken), (IProgress<DownloadProgress>)null, (IProgress<string>)null, options) == null)
{
HUDManager.Instance.DisplayTip("Download Error", "Failed to download the video.", false, false, "DownloadErrorTip");
return;
}
HUDManager.Instance.DisplayTip("Download Complete", "The video has been successfully downloaded.", false, false, "DownloadCompleteTip");
VideoManager.Videos.Clear();
VideoManager.Load();
}
private void OnDestroy()
{
downloadAction.Disable();
Log.LogInfo((object)"YoutubeDownloader plugin destroyed or disabled. Download action disabled.");
}
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}