using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("U-FESNOS")]
[assembly: AssemblyProduct("MP4Export")]
[assembly: AssemblyTitle("MP4Export")]
[assembly: AssemblyCopyright("U-FESNOS")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[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]
[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;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace CWMp4Export
{
[ContentWarningPlugin("U-FESNOS.MP4Export", "1.0.1", true)]
[BepInPlugin("U-FESNOS.MP4Export", "CWMp4Export", "1.0.1")]
public class Plugin : BaseUnityPlugin
{
private sealed class Job
{
public string FFmpeg = string.Empty;
public string Input = string.Empty;
public string Encoder = "libx264";
public int Quality = 20;
public string Preset = "veryfast";
public string AudioBitrate = "160k";
public bool DeleteInput;
}
public const string Guid = "U-FESNOS.MP4Export";
public const string Name = "CWMp4Export";
public const string Version = "1.0.1";
internal static ManualLogSource Log = null;
private static string pluginDir = string.Empty;
private static ConfigEntry<string> ffmpegPath = null;
private static ConfigEntry<bool> deleteWebm = null;
private static ConfigEntry<string> videoEncoder = null;
private static ConfigEntry<int> quality = null;
private static ConfigEntry<string> x264Preset = null;
private static ConfigEntry<string> audioBitrate = null;
private static ConfigEntry<bool> showStatus = null;
private static readonly object StatusLock = new object();
private static string statusText = string.Empty;
private static DateTime statusUntil = DateTime.MinValue;
private void Awake()
{
//IL_0145: Unknown result type (might be due to invalid IL or missing references)
//IL_015c: Unknown result type (might be due to invalid IL or missing references)
//IL_0169: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
pluginDir = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath;
ffmpegPath = ((BaseUnityPlugin)this).Config.Bind<string>("General", "FFmpegPath", string.Empty, "ffmpeg.exe のフルパス。空欄なら自動検索 (このModのフォルダ -> PATH -> winget -> choco -> scoop)");
deleteWebm = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DeleteWebmAfterConvert", false, "変換に成功したら元の .webm を削除する");
showStatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowStatus", true, "画面左上に変換の状態を表示する");
videoEncoder = ((BaseUnityPlugin)this).Config.Bind<string>("Encode", "VideoEncoder", "libx264", "映像エンコーダ。libx264 (CPU, 標準) / h264_nvenc (NVIDIA GPU) など");
quality = ((BaseUnityPlugin)this).Config.Bind<int>("Encode", "Quality", 20, "画質。小さいほど高画質で容量が大きい (libx264 は CRF, nvenc は CQ。目安 18-28)");
x264Preset = ((BaseUnityPlugin)this).Config.Bind<string>("Encode", "X264Preset", "veryfast", "libx264 の速度プリセット (ultrafast, veryfast, fast, medium, slow ...)");
audioBitrate = ((BaseUnityPlugin)this).Config.Bind<string>("Encode", "AudioBitrate", "160k", "AAC 音声のビットレート");
MethodInfo methodInfo = AccessTools.Method("CameraRecording:SaveToDesktop", (Type[])null, (Type[])null);
if (methodInfo == null)
{
Log.LogError((object)"CameraRecording.SaveToDesktop が見つかりません。ゲームの更新で構造が変わった可能性があります。");
return;
}
new Harmony("U-FESNOS.MP4Export").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "AfterSaveToDesktop", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
string text = FindFFmpeg();
if (text == null)
{
Log.LogWarning((object)"ffmpeg.exe が見つかりません。インストールするか、設定の FFmpegPath を指定してください。");
}
else
{
Log.LogInfo((object)("ffmpeg: " + text));
}
}
private static void AfterSaveToDesktop(bool __result, ref string __0)
{
if (!__result || string.IsNullOrEmpty(__0))
{
return;
}
string text = (Path.IsPathRooted(__0) ? __0 : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), __0));
if (!File.Exists(text))
{
Log.LogWarning((object)("保存されたファイルが見つかりません: " + text));
return;
}
string text2 = FindFFmpeg();
if (text2 == null)
{
Log.LogError((object)"ffmpeg.exe が見つからないため変換できません。");
SetStatus("MP4: ffmpeg not found (see LogOutput.log)", 8.0);
return;
}
Job job = new Job
{
FFmpeg = text2,
Input = text,
Encoder = videoEncoder.Value.Trim(),
Quality = quality.Value,
Preset = x264Preset.Value.Trim(),
AudioBitrate = audioBitrate.Value.Trim(),
DeleteInput = deleteWebm.Value
};
Thread thread = new Thread((ThreadStart)delegate
{
Convert(job);
});
thread.IsBackground = true;
thread.Name = "CWMp4Export";
thread.Start();
}
private static void Convert(Job job)
{
string text = Path.ChangeExtension(job.Input, ".mp4");
string text2 = Path.Combine(Path.GetDirectoryName(text) ?? string.Empty, Path.GetFileNameWithoutExtension(text) + ".converting.mp4");
SetStatus("MP4: converting...", 600.0);
Log.LogInfo((object)("Converting: " + job.Input));
try
{
if (File.Exists(text2))
{
File.Delete(text2);
}
string text3;
int exitCode;
using (Process process = Process.Start(new ProcessStartInfo
{
FileName = job.FFmpeg,
Arguments = BuildArguments(job, text2),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true
}))
{
if (process == null)
{
throw new Exception("ffmpeg を起動できませんでした。");
}
try
{
process.PriorityClass = ProcessPriorityClass.BelowNormal;
}
catch
{
}
text3 = process.StandardError.ReadToEnd();
process.WaitForExit();
exitCode = process.ExitCode;
}
if (exitCode != 0 || !File.Exists(text2) || new FileInfo(text2).Length == 0L)
{
throw new Exception($"ffmpeg exit code {exitCode}: {Tail(text3)}");
}
if (File.Exists(text))
{
File.Delete(text);
}
File.Move(text2, text);
if (job.DeleteInput)
{
File.Delete(job.Input);
}
Log.LogInfo((object)("Converted: " + text));
SetStatus("MP4: saved " + Path.GetFileName(text), 6.0);
}
catch (Exception ex)
{
Log.LogError((object)("Conversion failed: " + ex.Message));
try
{
if (File.Exists(text2))
{
File.Delete(text2);
}
}
catch
{
}
SetStatus("MP4: conversion failed (see LogOutput.log)", 8.0);
}
}
private static string BuildArguments(Job job, string output)
{
string text = ((job.Encoder.IndexOf("nvenc", StringComparison.OrdinalIgnoreCase) >= 0) ? $"-c:v {job.Encoder} -preset p5 -rc vbr -cq {job.Quality} -b:v 0" : ((!job.Encoder.Equals("libx264", StringComparison.OrdinalIgnoreCase)) ? ("-c:v " + job.Encoder) : $"-c:v libx264 -preset {job.Preset} -crf {job.Quality}"));
return "-hide_banner -loglevel error -nostdin -y -i \"" + job.Input + "\" -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" " + text + " -pix_fmt yuv420p -c:a aac -b:a " + job.AudioBitrate + " -movflags +faststart \"" + output + "\"";
}
private static string? FindFFmpeg()
{
string text = (ffmpegPath.Value ?? string.Empty).Trim().Trim('"');
if (text.Length > 0)
{
if (File.Exists(text))
{
return text;
}
Log.LogWarning((object)("FFmpegPath に指定されたファイルがありません: " + text));
return null;
}
List<string> list = new List<string> { Path.Combine(pluginDir, "ffmpeg.exe") };
string[] array = (Environment.GetEnvironmentVariable("PATH") ?? string.Empty).Split(Path.PathSeparator);
for (int i = 0; i < array.Length; i++)
{
string text2 = array[i].Trim().Trim('"');
if (text2.Length > 0)
{
list.Add(text2 + Path.DirectorySeparatorChar + "ffmpeg.exe");
}
}
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string folderPath2 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
list.Add(Path.Combine(folderPath, "Microsoft", "WinGet", "Links", "ffmpeg.exe"));
list.Add("C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe");
list.Add(Path.Combine(folderPath2, "scoop", "shims", "ffmpeg.exe"));
foreach (string item in list)
{
try
{
if (File.Exists(item))
{
return item;
}
}
catch
{
}
}
return null;
}
private static string Tail(string text)
{
text = (text ?? string.Empty).Trim();
if (text.Length > 400)
{
return text.Substring(text.Length - 400);
}
return text;
}
private static void SetStatus(string text, double seconds)
{
lock (StatusLock)
{
statusText = text;
statusUntil = DateTime.UtcNow.AddSeconds(seconds);
}
}
private void OnGUI()
{
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
if (!showStatus.Value)
{
return;
}
string text;
lock (StatusLock)
{
if (DateTime.UtcNow > statusUntil)
{
return;
}
text = statusText;
}
GUI.Box(new Rect(10f, 10f, 460f, 26f), text);
}
}
}