diff --git a/ADOFAIRenderer.csproj b/ADOFAIRenderer.csproj
index 358eafc..e399585 100644
--- a/ADOFAIRenderer.csproj
+++ b/ADOFAIRenderer.csproj
@@ -110,4 +110,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+();
+
+ foreach (var member in members.Elements("member"))
+ {
+ var nameAttr = member.Attribute("name")?.Value;
+ if (string.IsNullOrEmpty(nameAttr)) continue;
+
+ // XML 주석 형식: T:네임스페이스.타입, M:메서드, P:프로퍼티, F:필드/열거형, E:이벤트 등
+ int colonIndex = nameAttr.IndexOf(':');
+ string fullName = colonIndex >= 0 ? nameAttr.Substring(colonIndex + 1) : nameAttr;
+
+ if (!fullName.StartsWith(prefix, StringComparison.Ordinal) && !fullName.Equals(TargetNamespace, StringComparison.Ordinal))
+ {
+ toRemove.Add(member);
+ }
+ }
+
+ foreach (var elem in toRemove)
+ {
+ elem.Remove();
+ }
+
+ doc.Save(XmlFilePath);
+ Log.LogMessage(Microsoft.Build.Framework.MessageImportance.High, $"[FilterXmlDoc] Kept '{TargetNamespace}' namespace docs. Removed {toRemove.Count} other members.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.LogWarning($"[FilterXmlDoc] XML filtering failed: {ex.Message}");
+ }
+]]>
+
+
+
+
+
+
+ <_XmlDocPath Condition="'$(_XmlDocPath)' == '' and Exists('$(TargetDir)$(TargetName).xml')">$(TargetDir)$(TargetName).xml
+ <_XmlDocPath Condition="'$(_XmlDocPath)' == '' and Exists('$(DocumentationFile)')">$(DocumentationFile)
+
+
+
diff --git a/API/RenderResult.cs b/API/RenderResult.cs
index eebcddf..d1ade45 100644
--- a/API/RenderResult.cs
+++ b/API/RenderResult.cs
@@ -5,9 +5,10 @@ using System.IO;
namespace ADOFAIRenderer.API;
///
-/// Represents the result and metadata of a completed or cancelled render session.
+/// Represents the result and metadata of a completed or canceled render session.
///
[SuppressMessage("ReSharper", "UnusedType.Global")]
+[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class RenderResult
{
///
@@ -41,10 +42,20 @@ public class RenderResult
public long FileSizeBytes { get; }
///
- /// Error message or reason if rendering failed or was cancelled; null on success.
+ /// Error message or reason if rendering failed or was canceled; null on success.
///
public string? ErrorMessage { get; }
-
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Whether the render session completed successfully.
+ /// The absolute output path of the rendered video file.
+ /// Total number of video frames sent to the encoder.
+ /// Calculated video duration in seconds.
+ /// Total wall-clock time spent rendering.
+ /// File size of the output video in bytes.
+ /// Error message or reason if failed or canceled; otherwise, null.
public RenderResult(
bool success,
string outputPath,
@@ -63,7 +74,15 @@ public class RenderResult
ErrorMessage = errorMessage;
}
- public static RenderResult CreateSuccess(string outputPath, long totalFrames, double fps, TimeSpan renderDuration)
+ ///
+ /// Creates a successful instance from the specified render metrics.
+ ///
+ /// The path to the rendered video file.
+ /// Total number of video frames sent to the encoder.
+ /// The effective frames per second used to compute video duration.
+ /// Total wall-clock time spent rendering.
+ /// A new representing a successful render session.
+ internal static RenderResult CreateSuccess(string outputPath, long totalFrames, double fps, TimeSpan renderDuration)
{
var fileSizeBytes = 0L;
if (!string.IsNullOrEmpty(outputPath) && File.Exists(outputPath))
@@ -76,12 +95,24 @@ public class RenderResult
return new RenderResult(true, outputPath, totalFrames, videoDuration, renderDuration, fileSizeBytes);
}
- public static RenderResult CreateFailure(string? errorMessage, long totalFrames, double fps, TimeSpan renderDuration)
+ ///
+ /// Creates a failed or cancelled instance.
+ ///
+ /// Error message or reason for failure/cancellation.
+ /// Total number of video frames sent to the encoder prior to termination.
+ /// The effective frames per second used to compute video duration.
+ /// Total wall-clock time spent rendering prior to termination.
+ /// A new representing a failed or canceled render session.
+ internal static RenderResult CreateFailure(string? errorMessage, long totalFrames, double fps, TimeSpan renderDuration)
{
var videoDuration = fps > 0 ? totalFrames / fps : 0.0;
return new RenderResult(false, string.Empty, totalFrames, videoDuration, renderDuration, 0, errorMessage ?? "Rendering failed or was cancelled.");
}
+ ///
+ /// Returns a string summarizing the render result metrics or failure reason.
+ ///
+ /// A string representation of the .
public override string ToString()
{
return Success
diff --git a/API/RenderSession.cs b/API/RenderSession.cs
index 3319303..3ef1286 100644
--- a/API/RenderSession.cs
+++ b/API/RenderSession.cs
@@ -92,12 +92,22 @@ public class RenderSession
return this;
}
+ ///
+ /// Sets whether desktop notifications should be displayed when rendering completes.
+ ///
+ /// true to enable desktop notifications; otherwise, false.
+ /// The current instance for method chaining.
public RenderSession WithNotification(bool enabled)
{
_notification = enabled;
return this;
}
+ ///
+ /// Adds a callback action to be invoked when the render session completes or fails.
+ ///
+ /// The callback action that receives the resulting .
+ /// The current instance for method chaining.
public RenderSession OnComplete(Action callback)
{
_onComplete += callback;
@@ -109,6 +119,7 @@ public class RenderSession
///
/// The configuration settings for the render session.
/// The file path of the level to render.
+ /// Thrown when the output directory cannot be determined from the output path.
public void Start(RenderConfig renderConfig, string levelPath)
{
RenderContext.Auto = _auto;
diff --git a/Core/FirstUpdateHook.cs b/Core/FirstUpdateHook.cs
index e395d0a..caa6caf 100644
--- a/Core/FirstUpdateHook.cs
+++ b/Core/FirstUpdateHook.cs
@@ -1,66 +1,65 @@
using ADOFAIRenderer.UI;
using UnityEngine;
-namespace ADOFAIRenderer.Core
+namespace ADOFAIRenderer.Core;
+
+[DefaultExecutionOrder(int.MinValue)]
+internal class FirstUpdateHook : MonoBehaviour
{
- [DefaultExecutionOrder(int.MinValue)]
- internal class FirstUpdateHook : MonoBehaviour
+ private bool _checkedFFmpeg;
+ private bool _checkedNvidiaDriver;
+
+ private void Update()
{
- private bool _checkedFFmpeg;
- private bool _checkedNvidiaDriver;
-
- private void Update()
+ if (!_checkedFFmpeg)
{
- if (!_checkedFFmpeg)
+ if (FFmpegDownloadUI.Instance) return;
+
+ if (!FFmpegDownloadUI.CheckFFmpegExists())
{
- if (FFmpegDownloadUI.Instance) return;
-
- if (!FFmpegDownloadUI.CheckFFmpegExists())
- {
- FFmpegDownloadUI.ShowDownloadUI();
- return;
- }
- _checkedFFmpeg = true;
- }
-
- if (!_checkedNvidiaDriver)
- {
- if (CommonWarningDialogUI.Instance) return;
-
- _checkedNvidiaDriver = true;
- CheckNvidiaDriverWarning();
- }
-
- if (RenderManager.Instance)
- {
- RenderManager.Instance.OnPreRenderUpdate();
+ FFmpegDownloadUI.ShowDownloadUI();
+ return;
}
+ _checkedFFmpeg = true;
}
- private void CheckNvidiaDriverWarning()
+ if (!_checkedNvidiaDriver)
{
- var driverVer = Util.GPUUtil.GetNvidiaDriverVersion();
- if (driverVer is null or >= 610.00f) return;
+ if (CommonWarningDialogUI.Instance) return;
- var currentVersion = driverVer.Value.ToString("F2");
-
- CommonWarningDialogUI.ShowDialog(
- "NVIDIA 그래픽 드라이버 업데이트 권장",
- $"현재 감지된 NVIDIA 그래픽 드라이버 버전: {currentVersion}\n\n하드웨어 가속(NVENC)을 사용하여 영상을 렌더링하려면 드라이버 버전 610.00 이상이 필요합니다.\n\n드라이버 버전이 낮으면 인코딩이 실패하거나 소프트웨어(CPU) 인코딩으로 대체되어 렌더링이 매우 느려집니다. 드라이버 업데이트를 진행해 주시기 바랍니다.",
- new DialogButton("드라이버 업데이트", () =>
- {
- Application.OpenURL("https://www.nvidia.com/Download/index.aspx");
- Application.Quit();
- }, new Color(0.0f, 0.8f, 1.0f, 1.0f)),
- new DialogButton("기능 비활성화", () =>
- {
- RenderMod.IsFeatureDisabled = true;
- }),
- new DialogButton("이번만 그냥 진행", () =>
- {
- // 아무 동작 안 함
- })
- );
+ _checkedNvidiaDriver = true;
+ CheckNvidiaDriverWarning();
+ }
+
+ if (RenderManager.Instance)
+ {
+ RenderManager.Instance.OnPreRenderUpdate();
}
}
+
+ private void CheckNvidiaDriverWarning()
+ {
+ var driverVer = Util.GPUUtil.GetNvidiaDriverVersion();
+ if (driverVer is null or >= 610.00f) return;
+
+ var currentVersion = driverVer.Value.ToString("F2");
+
+ CommonWarningDialogUI.ShowDialog(
+ "NVIDIA 그래픽 드라이버 업데이트 권장",
+ $"현재 감지된 NVIDIA 그래픽 드라이버 버전: {currentVersion}\n\n하드웨어 가속(NVENC)을 사용하여 영상을 렌더링하려면 드라이버 버전 610.00 이상이 필요합니다.\n\n드라이버 버전이 낮으면 인코딩이 실패하거나 소프트웨어(CPU) 인코딩으로 대체되어 렌더링이 매우 느려집니다. 드라이버 업데이트를 진행해 주시기 바랍니다.",
+ new DialogButton("드라이버 업데이트", () =>
+ {
+ Application.OpenURL("https://www.nvidia.com/Download/index.aspx");
+ Application.Quit();
+ }, new Color(0.0f, 0.8f, 1.0f, 1.0f)),
+ new DialogButton("기능 비활성화", () =>
+ {
+ RenderMod.IsFeatureDisabled = true;
+ }),
+ new DialogButton("이번만 그냥 진행", () =>
+ {
+ // 아무 동작 안 함
+ })
+ );
+ }
}
\ No newline at end of file
diff --git a/Core/PostRenderHook.cs b/Core/PostRenderHook.cs
index f0a5d14..2a86d4d 100644
--- a/Core/PostRenderHook.cs
+++ b/Core/PostRenderHook.cs
@@ -3,33 +3,32 @@ using System.Reflection;
using HarmonyLib;
using UnityEngine;
-namespace ADOFAIRenderer.Core
+namespace ADOFAIRenderer.Core;
+
+[DefaultExecutionOrder(int.MaxValue)]
+internal class PostRenderHook : MonoBehaviour
{
- [DefaultExecutionOrder(int.MaxValue)]
- internal class PostRenderHook : MonoBehaviour
+ private FieldInfo? _camQuadMeshField;
+
+ private void Awake()
{
- private FieldInfo? _camQuadMeshField;
-
- private void Awake()
- {
- _camQuadMeshField = AccessTools.Field(typeof(scrCamera), "camQuadMesh");
- }
-
- private void LateUpdate()
- {
- if (RenderManager.Instance)
- RenderManager.Instance.OnLastUpdate();
-
- StartCoroutine(PostFrameCoroutine());
- }
-
- private IEnumerator PostFrameCoroutine()
- {
- yield return new WaitForEndOfFrame();
-
- var val = (MeshRenderer)_camQuadMeshField!.GetValue(scrCamera.instance);
- if (RenderManager.Instance)
- RenderManager.Instance.OnPostFrame(val.sharedMaterial.mainTexture as RenderTexture);
- }
+ _camQuadMeshField = AccessTools.Field(typeof(scrCamera), "camQuadMesh");
}
-}
+
+ private void LateUpdate()
+ {
+ if (RenderManager.Instance)
+ RenderManager.Instance.OnLastUpdate();
+
+ StartCoroutine(PostFrameCoroutine());
+ }
+
+ private IEnumerator PostFrameCoroutine()
+ {
+ yield return new WaitForEndOfFrame();
+
+ var val = (MeshRenderer)_camQuadMeshField!.GetValue(scrCamera.instance);
+ if (RenderManager.Instance)
+ RenderManager.Instance.OnPostFrame(val.sharedMaterial.mainTexture as RenderTexture);
+ }
+}
\ No newline at end of file
diff --git a/Data/DoubleLimit.cs b/Data/DoubleLimit.cs
index 4c3d728..92fe984 100644
--- a/Data/DoubleLimit.cs
+++ b/Data/DoubleLimit.cs
@@ -1,78 +1,79 @@
-namespace ADOFAIRenderer.Data
+namespace ADOFAIRenderer.Data;
+
+///
+/// Base class for double input validation limits.
+/// Multiple limits can be composed on a single input field — all are checked in order.
+///
+internal abstract class DoubleLimit
{
///
- /// Base class for double input validation limits.
- /// Multiple limits can be composed on a single input field — all are checked in order.
+ /// Validates the given double value against this limit.
///
- internal abstract class DoubleLimit
+ /// The parsed double value to validate.
+ ///
+ /// When validation fails, set to a human-readable error message.
+ /// When validation succeeds, set to null.
+ ///
+ /// true if the value satisfies this limit; false otherwise.
+ public abstract bool Validate(double value, out string errorMessage);
+}
+
+///
+/// Requires the double value to be strictly positive (> 0).
+///
+internal class PositiveOnlyDoubleLimit : DoubleLimit
+{
+ public override bool Validate(double value, out string errorMessage)
{
- ///
- /// Validates the given double value against this limit.
- ///
- /// The parsed double value to validate.
- ///
- /// When validation fails, set to a human-readable error message.
- /// When validation succeeds, set to null.
- ///
- /// true if the value satisfies this limit; false otherwise.
- public abstract bool Validate(double value, out string errorMessage);
- }
-
- ///
- /// Requires the double value to be strictly positive (> 0).
- ///
- internal class PositiveOnlyDoubleLimit : DoubleLimit
- {
- public override bool Validate(double value, out string errorMessage)
+ if (value > 0)
{
- if (value > 0)
- {
- errorMessage = null!;
- return true;
- }
-
- errorMessage = "값은 0보다 큰 양수여야 합니다.";
- return false;
- }
- }
-
- ///
- /// Requires the double value to be within an inclusive [Min, Max] range.
- /// Either bound can be null to represent an unbounded side.
- ///
- internal class RangeDoubleLimit : DoubleLimit
- {
- public double? Min { get; }
- public double? Max { get; }
-
- /// Inclusive lower bound, or null for no lower bound.
- /// Inclusive upper bound, or null for no upper bound.
- public RangeDoubleLimit(double? min, double? max)
- {
- Min = min;
- Max = max;
- }
-
- public override bool Validate(double value, out string errorMessage)
- {
- if (Min.HasValue && value < Min.Value)
- {
- errorMessage = Max.HasValue
- ? $"값은 {Min.Value}에서 {Max.Value} 사이의 숫자여야 합니다."
- : $"값은 {Min.Value} 이상의 숫자여야 합니다.";
- return false;
- }
-
- if (Max.HasValue && value > Max.Value)
- {
- errorMessage = Min.HasValue
- ? $"값은 {Min.Value}에서 {Max.Value} 사이의 숫자여야 합니다."
- : $"값은 {Max.Value} 이하의 숫자여야 합니다.";
- return false;
- }
-
errorMessage = null!;
return true;
}
+
+ errorMessage = "값은 0보다 큰 양수여야 합니다.";
+ return false;
}
}
+
+///
+/// Requires the double value to be within an inclusive [Min, Max] range.
+/// Either bound can be null to represent an unbounded side.
+///
+internal class RangeDoubleLimit : DoubleLimit
+{
+ // ReSharper disable MemberCanBePrivate.Global
+ public double? Min { get; }
+ public double? Max { get; }
+ // ReSharper restore MemberCanBePrivate.Global
+
+ /// Inclusive lower bound, or null for no lower bound.
+ /// Inclusive upper bound, or null for no upper bound.
+ public RangeDoubleLimit(double? min, double? max)
+ {
+ Min = min;
+ Max = max;
+ }
+
+ public override bool Validate(double value, out string errorMessage)
+ {
+ if (value < Min)
+ {
+ errorMessage = Max.HasValue
+ ? $"값은 {Min.Value}에서 {Max.Value} 사이의 숫자여야 합니다."
+ : $"값은 {Min.Value} 이상의 숫자여야 합니다.";
+ return false;
+ }
+
+ if (value > Max)
+ {
+ errorMessage = Min.HasValue
+ ? $"값은 {Min.Value}에서 {Max.Value} 사이의 숫자여야 합니다."
+ : $"값은 {Max.Value} 이하의 숫자여야 합니다.";
+ return false;
+ }
+
+ errorMessage = null!;
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Data/Input/DoubleInputValidator.cs b/Data/Input/DoubleInputValidator.cs
index 26eeaa2..65de249 100644
--- a/Data/Input/DoubleInputValidator.cs
+++ b/Data/Input/DoubleInputValidator.cs
@@ -3,75 +3,74 @@ using System.Linq;
using MelonLoader;
using UnityEngine.UIElements;
-namespace ADOFAIRenderer.Data.Input
+namespace ADOFAIRenderer.Data.Input;
+
+internal class DoubleInputValidator
{
- internal class DoubleInputValidator
+ private struct FieldEntry
{
- private struct FieldEntry
- {
- public List Limits;
- }
+ public List Limits;
+ }
- private readonly VisualElement _rootVisualElement;
- private readonly Dictionary _fields = new();
+ private readonly VisualElement _rootVisualElement;
+ private readonly Dictionary _fields = new();
- public DoubleInputValidator(VisualElement rootVisualElement)
- {
- _rootVisualElement = rootVisualElement;
- }
+ public DoubleInputValidator(VisualElement rootVisualElement)
+ {
+ _rootVisualElement = rootVisualElement;
+ }
- ///
- /// Registers one or more s for the field with the given name.
- /// Multiple limits can be added to the same field — they are all checked in order.
- ///
- /// The name of the DoubleField in the UXML.
- /// The limits to apply.
- public DoubleInputValidator AddLimits(string fieldName, params DoubleLimit[] limits)
+ ///
+ /// Registers one or more s for the field with the given name.
+ /// Multiple limits can be added to the same field — they are all checked in order.
+ ///
+ /// The name of the DoubleField in the UXML.
+ /// The limits to apply.
+ public DoubleInputValidator AddLimits(string fieldName, params DoubleLimit[] limits)
+ {
+ if (!_fields.TryGetValue(fieldName, out var entry))
{
- if (!_fields.TryGetValue(fieldName, out var entry))
+ entry = new FieldEntry
{
- entry = new FieldEntry
- {
- Limits = new List()
- };
+ Limits = new List()
+ };
+ }
+
+ entry.Limits.AddRange(limits);
+ _fields[fieldName] = entry;
+ return this;
+ }
+
+ ///
+ /// Queries all registered fields from the root VisualElement and
+ /// registers value-changed callbacks that rollback invalid values.
+ ///
+ public void SetupValidation()
+ {
+ foreach (var (fieldName, entry) in _fields)
+ {
+ var field = _rootVisualElement.Q(fieldName);
+ if (field == null)
+ {
+ Melon.Logger.Warning(
+ $"DoubleField '{fieldName}'을(를) 찾을 수 없습니다. 유효성 검사를 건너뜁니다.");
+ continue;
}
- entry.Limits.AddRange(limits);
- _fields[fieldName] = entry;
- return this;
- }
+ var limits = entry.Limits;
- ///
- /// Queries all registered fields from the root VisualElement and
- /// registers value-changed callbacks that rollback invalid values.
- ///
- public void SetupValidation()
- {
- foreach (var (fieldName, entry) in _fields)
+ field.RegisterValueChangedCallback(evt =>
{
- var field = _rootVisualElement.Q(fieldName);
- if (field == null)
+ if (string.IsNullOrEmpty(field.text))
{
- Melon.Logger.Warning(
- $"DoubleField '{fieldName}'을(를) 찾을 수 없습니다. 유효성 검사를 건너뜁니다.");
- continue;
+ return;
}
- var limits = entry.Limits;
-
- field.RegisterValueChangedCallback(evt =>
+ if (limits.Any(limit => !limit.Validate(evt.newValue, out _)))
{
- if (string.IsNullOrEmpty(field.text))
- {
- return;
- }
-
- if (limits.Any(limit => !limit.Validate(evt.newValue, out _)))
- {
- field.SetValueWithoutNotify(evt.previousValue);
- }
- });
- }
+ field.SetValueWithoutNotify(evt.previousValue);
+ }
+ });
}
}
-}
+}
\ No newline at end of file
diff --git a/Data/Input/EnumDropdownBinder.cs b/Data/Input/EnumDropdownBinder.cs
index 470be45..6e0e88d 100644
--- a/Data/Input/EnumDropdownBinder.cs
+++ b/Data/Input/EnumDropdownBinder.cs
@@ -1,86 +1,87 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using MelonLoader;
using UnityEngine.UIElements;
-namespace ADOFAIRenderer.Data.Input
+namespace ADOFAIRenderer.Data.Input;
+
+///
+/// Binds a UI Toolkit to an enum type.
+/// Populates choices from enum names and provides typed access to the selected value.
+///
+/// The enum type to bind.
+[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+internal class EnumDropdownBinder where T : struct, Enum
{
+ private readonly DropdownField? _dropdown;
+ private readonly List _values;
+ private Action? _onValueChanged;
+
///
- /// Binds a UI Toolkit to an enum type.
- /// Populates choices from enum names and provides typed access to the selected value.
+ /// The currently selected enum value.
///
- /// The enum type to bind.
- internal class EnumDropdownBinder where T : struct, Enum
+ public T Value => _dropdown is { index: >= 0 } && _dropdown.index < _values.Count
+ ? _values[_dropdown.index]
+ : default;
+
+ ///
+ /// Queries a by name from the root VisualElement,
+ /// populates it with enum names, and selects the default value.
+ ///
+ /// The root VisualElement loaded from the UXML bundle.
+ /// The name of the DropdownField in the UXML.
+ /// The initial selected value. Defaults to the first enum member.
+ public EnumDropdownBinder(VisualElement rootVisualElement, string fieldName, T? defaultValue = null)
{
- private readonly DropdownField? _dropdown;
- private readonly List _values;
- private Action? _onValueChanged;
+ _values = Enum.GetValues(typeof(T)).Cast().ToList();
- ///
- /// The currently selected enum value.
- ///
- public T Value => _dropdown is { index: >= 0 } && _dropdown.index < _values.Count
- ? _values[_dropdown.index]
- : default;
-
- ///
- /// Queries a by name from the root VisualElement,
- /// populates it with enum names, and selects the default value.
- ///
- /// The root VisualElement loaded from the UXML bundle.
- /// The name of the DropdownField in the UXML.
- /// The initial selected value. Defaults to the first enum member.
- public EnumDropdownBinder(VisualElement rootVisualElement, string fieldName, T? defaultValue = null)
+ _dropdown = rootVisualElement.Q(fieldName);
+ if (_dropdown == null)
{
- _values = Enum.GetValues(typeof(T)).Cast().ToList();
-
- _dropdown = rootVisualElement.Q(fieldName);
- if (_dropdown == null)
- {
- Melon.Logger.Error(
- $"DropdownField '{fieldName}'을(를) 찾을 수 없습니다.");
- return;
- }
-
- _dropdown.choices = _values.Select(v => v.ToString()).ToList();
-
- // Register the callback BEFORE setting index so the initial selection fires it too
- _dropdown.RegisterValueChangedCallback(_ =>
- {
- _onValueChanged?.Invoke(Value);
- });
-
- var initial = defaultValue ?? _values[0];
- _dropdown.index = _values.IndexOf(initial);
+ Melon.Logger.Error(
+ $"DropdownField '{fieldName}'을(를) 찾을 수 없습니다.");
+ return;
}
- ///
- /// Registers a callback that fires when the selection changes, providing the new enum value.
- ///
- public void OnValueChanged(Action callback)
- {
- _onValueChanged = callback;
+ _dropdown.choices = _values.Select(v => v.ToString()).ToList();
- // Fire immediately with current value so the caller gets the initial state
- if (_dropdown != null)
- {
- callback(Value);
- }
- }
-
- ///
- /// Sets the selected enum value and updates the dropdown index.
- ///
- /// The enum value to set.
- public void SetValue(T value)
+ // Register the callback BEFORE setting index so the initial selection fires it too
+ _dropdown.RegisterValueChangedCallback(_ =>
{
- if (_dropdown == null) return;
- int idx = _values.IndexOf(value);
- if (idx >= 0)
- {
- _dropdown.index = idx;
- }
+ _onValueChanged?.Invoke(Value);
+ });
+
+ var initial = defaultValue ?? _values[0];
+ _dropdown.index = _values.IndexOf(initial);
+ }
+
+ ///
+ /// Registers a callback that fires when the selection changes, providing the new enum value.
+ ///
+ public void OnValueChanged(Action callback)
+ {
+ _onValueChanged = callback;
+
+ // Fire immediately with current value so the caller gets the initial state
+ if (_dropdown != null)
+ {
+ callback(Value);
}
}
-}
+
+ ///
+ /// Sets the selected enum value and updates the dropdown index.
+ ///
+ /// The enum value to set.
+ public void SetValue(T value)
+ {
+ if (_dropdown == null) return;
+ int idx = _values.IndexOf(value);
+ if (idx >= 0)
+ {
+ _dropdown.index = idx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Data/Input/IntegerInputValidator.cs b/Data/Input/IntegerInputValidator.cs
index bfb378a..f19bd68 100644
--- a/Data/Input/IntegerInputValidator.cs
+++ b/Data/Input/IntegerInputValidator.cs
@@ -3,75 +3,74 @@ using System.Linq;
using MelonLoader;
using UnityEngine.UIElements;
-namespace ADOFAIRenderer.Data.Input
+namespace ADOFAIRenderer.Data.Input;
+
+internal class IntegerInputValidator
{
- internal class IntegerInputValidator
+ private struct FieldEntry
{
- private struct FieldEntry
- {
- public List Limits;
- }
+ public List Limits;
+ }
- private readonly VisualElement _rootVisualElement;
- private readonly Dictionary _fields = new();
+ private readonly VisualElement _rootVisualElement;
+ private readonly Dictionary _fields = new();
- public IntegerInputValidator(VisualElement rootVisualElement)
- {
- _rootVisualElement = rootVisualElement;
- }
+ public IntegerInputValidator(VisualElement rootVisualElement)
+ {
+ _rootVisualElement = rootVisualElement;
+ }
- ///
- /// Registers one or more s for the field with the given name.
- /// Multiple limits can be added to the same field — they are all checked in order.
- ///
- /// The name of the IntegerField in the UXML.
- /// The limits to apply.
- public IntegerInputValidator AddLimits(string fieldName, params IntegerLimit[] limits)
+ ///
+ /// Registers one or more s for the field with the given name.
+ /// Multiple limits can be added to the same field — they are all checked in order.
+ ///
+ /// The name of the IntegerField in the UXML.
+ /// The limits to apply.
+ public IntegerInputValidator AddLimits(string fieldName, params IntegerLimit[] limits)
+ {
+ if (!_fields.TryGetValue(fieldName, out var entry))
{
- if (!_fields.TryGetValue(fieldName, out var entry))
+ entry = new FieldEntry
{
- entry = new FieldEntry
- {
- Limits = new List()
- };
+ Limits = new List()
+ };
+ }
+
+ entry.Limits.AddRange(limits);
+ _fields[fieldName] = entry;
+ return this;
+ }
+
+ ///
+ /// Queries all registered fields from the root VisualElement and
+ /// registers value-changed callbacks that rollback invalid values.
+ ///
+ public void SetupValidation()
+ {
+ foreach (var (fieldName, entry) in _fields)
+ {
+ var field = _rootVisualElement.Q(fieldName);
+ if (field == null)
+ {
+ Melon.Logger.Warning(
+ $"IntegerField '{fieldName}'을(를) 찾을 수 없습니다. 유효성 검사를 건너뜁니다.");
+ continue;
}
- entry.Limits.AddRange(limits);
- _fields[fieldName] = entry;
- return this;
- }
+ var limits = entry.Limits;
- ///
- /// Queries all registered fields from the root VisualElement and
- /// registers value-changed callbacks that rollback invalid values.
- ///
- public void SetupValidation()
- {
- foreach (var (fieldName, entry) in _fields)
+ field.RegisterValueChangedCallback(evt =>
{
- var field = _rootVisualElement.Q(fieldName);
- if (field == null)
+ if (string.IsNullOrEmpty(field.text))
{
- Melon.Logger.Warning(
- $"IntegerField '{fieldName}'을(를) 찾을 수 없습니다. 유효성 검사를 건너뜁니다.");
- continue;
+ return;
}
- var limits = entry.Limits;
-
- field.RegisterValueChangedCallback(evt =>
+ if (limits.Any(limit => !limit.Validate(evt.newValue, out _)))
{
- if (string.IsNullOrEmpty(field.text))
- {
- return;
- }
-
- if (limits.Any(limit => !limit.Validate(evt.newValue, out _)))
- {
- field.SetValueWithoutNotify(evt.previousValue);
- }
- });
- }
+ field.SetValueWithoutNotify(evt.previousValue);
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/Data/IntegerLimit.cs b/Data/IntegerLimit.cs
index 285e4c6..01496b4 100644
--- a/Data/IntegerLimit.cs
+++ b/Data/IntegerLimit.cs
@@ -1,78 +1,79 @@
-namespace ADOFAIRenderer.Data
+namespace ADOFAIRenderer.Data;
+
+///
+/// Base class for integer input validation limits.
+/// Multiple limits can be composed on a single input field — all are checked in order.
+///
+internal abstract class IntegerLimit
{
///
- /// Base class for integer input validation limits.
- /// Multiple limits can be composed on a single input field — all are checked in order.
+ /// Validates the given integer value against this limit.
///
- internal abstract class IntegerLimit
+ /// The parsed integer value to validate.
+ ///
+ /// When validation fails, set to a human-readable error message.
+ /// When validation succeeds, set to null.
+ ///
+ /// true if the value satisfies this limit; false otherwise.
+ public abstract bool Validate(int value, out string errorMessage);
+}
+
+///
+/// Requires the integer value to be strictly positive (> 0).
+///
+internal class PositiveOnlyIntegerLimit : IntegerLimit
+{
+ public override bool Validate(int value, out string errorMessage)
{
- ///
- /// Validates the given integer value against this limit.
- ///
- /// The parsed integer value to validate.
- ///
- /// When validation fails, set to a human-readable error message.
- /// When validation succeeds, set to null.
- ///
- /// true if the value satisfies this limit; false otherwise.
- public abstract bool Validate(int value, out string errorMessage);
- }
-
- ///
- /// Requires the integer value to be strictly positive (> 0).
- ///
- internal class PositiveOnlyIntegerLimit : IntegerLimit
- {
- public override bool Validate(int value, out string errorMessage)
+ if (value > 0)
{
- if (value > 0)
- {
- errorMessage = null!;
- return true;
- }
-
- errorMessage = "값은 0보다 큰 양의 정수여야 합니다.";
- return false;
- }
- }
-
- ///
- /// Requires the integer value to be within an inclusive [Min, Max] range.
- /// Either bound can be null to represent an unbounded side.
- ///
- internal class RangeIntegerLimit : IntegerLimit
- {
- public int? Min { get; }
- public int? Max { get; }
-
- /// Inclusive lower bound, or null for no lower bound.
- /// Inclusive upper bound, or null for no upper bound.
- public RangeIntegerLimit(int? min, int? max)
- {
- Min = min;
- Max = max;
- }
-
- public override bool Validate(int value, out string errorMessage)
- {
- if (Min.HasValue && value < Min.Value)
- {
- errorMessage = Max.HasValue
- ? $"값은 {Min.Value}에서 {Max.Value} 사이의 정수여야 합니다."
- : $"값은 {Min.Value} 이상의 정수여야 합니다.";
- return false;
- }
-
- if (Max.HasValue && value > Max.Value)
- {
- errorMessage = Min.HasValue
- ? $"값은 {Min.Value}에서 {Max.Value} 사이의 정수여야 합니다."
- : $"값은 {Max.Value} 이하의 정수여야 합니다.";
- return false;
- }
-
errorMessage = null!;
return true;
}
+
+ errorMessage = "값은 0보다 큰 양의 정수여야 합니다.";
+ return false;
}
}
+
+///
+/// Requires the integer value to be within an inclusive [Min, Max] range.
+/// Either bound can be null to represent an unbounded side.
+///
+internal class RangeIntegerLimit : IntegerLimit
+{
+ // ReSharper disable MemberCanBePrivate.Global
+ public int? Min { get; }
+ public int? Max { get; }
+ // ReSharper restore MemberCanBePrivate.Global
+
+ /// Inclusive lower bound, or null for no lower bound.
+ /// Inclusive upper bound, or null for no upper bound.
+ public RangeIntegerLimit(int? min, int? max)
+ {
+ Min = min;
+ Max = max;
+ }
+
+ public override bool Validate(int value, out string errorMessage)
+ {
+ if (value < Min)
+ {
+ errorMessage = Max.HasValue
+ ? $"값은 {Min.Value}에서 {Max.Value} 사이의 정수여야 합니다."
+ : $"값은 {Min.Value} 이상의 정수여야 합니다.";
+ return false;
+ }
+
+ if (value > Max)
+ {
+ errorMessage = Min.HasValue
+ ? $"값은 {Min.Value}에서 {Max.Value} 사이의 정수여야 합니다."
+ : $"값은 {Max.Value} 이하의 정수여야 합니다.";
+ return false;
+ }
+
+ errorMessage = null!;
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Data/PresetManager.cs b/Data/PresetManager.cs
index 344213a..d4393d3 100644
--- a/Data/PresetManager.cs
+++ b/Data/PresetManager.cs
@@ -7,184 +7,183 @@ using MelonLoader.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace ADOFAIRenderer.Data
+namespace ADOFAIRenderer.Data;
+
+internal class RenderPreset
{
- internal class RenderPreset
+ public string Name { get; set; } = string.Empty;
+ public RenderConfig Config { get; set; } = null!;
+}
+
+internal static class PresetManager
+{
+ private static string GetPresetsDirectory()
{
- public string Name { get; set; } = string.Empty;
- public RenderConfig Config { get; set; } = null!;
+ return Path.Combine(MelonEnvironment.GameRootDirectory, "UserData", "ADOFAIRenderer", "Presets");
}
- internal static class PresetManager
+ private static string GetSafeFileName(string name)
{
- private static string GetPresetsDirectory()
+ return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, '_'));
+ }
+
+ public static void SavePreset(string name, RenderConfig config)
+ {
+ if (string.IsNullOrWhiteSpace(name))
{
- return Path.Combine(MelonEnvironment.GameRootDirectory, "UserData", "ADOFAIRenderer", "Presets");
+ throw new ArgumentException("프리셋 이름은 비어 있을 수 없습니다.");
}
- private static string GetSafeFileName(string name)
+ string dir = GetPresetsDirectory();
+ if (!Directory.Exists(dir))
{
- return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, '_'));
+ Directory.CreateDirectory(dir);
}
- public static void SavePreset(string name, RenderConfig config)
+ var preset = new RenderPreset
{
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentException("프리셋 이름은 비어 있을 수 없습니다.");
- }
+ Name = name,
+ Config = config
+ };
- string dir = GetPresetsDirectory();
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
+ string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
+ string safeName = GetSafeFileName(name);
+ string filePath = Path.Combine(dir, safeName + ".json");
+ File.WriteAllText(filePath, json);
+ }
- var preset = new RenderPreset
- {
- Name = name,
- Config = config
- };
-
- string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
- string safeName = GetSafeFileName(name);
- string filePath = Path.Combine(dir, safeName + ".json");
- File.WriteAllText(filePath, json);
- }
-
- public static List GetPresets()
- {
- var presets = new List();
- var dir = GetPresetsDirectory();
+ public static List GetPresets()
+ {
+ var presets = new List();
+ var dir = GetPresetsDirectory();
- if (!Directory.Exists(dir)) return presets;
+ if (!Directory.Exists(dir)) return presets;
- try
+ try
+ {
+ var files = Directory.GetFiles(dir, "*.json");
+ foreach (var file in files)
{
- var files = Directory.GetFiles(dir, "*.json");
- foreach (var file in files)
+ try
{
- try
- {
- var json = File.ReadAllText(file);
- var jObject = JsonConvert.DeserializeObject(json);
- if (jObject == null) continue;
+ var json = File.ReadAllText(file);
+ var jObject = JsonConvert.DeserializeObject(json);
+ if (jObject == null) continue;
- var configToken = jObject["Config"];
- if (configToken is JObject configObj)
- {
- TryChangeKey(configObj, "DelayAfterLevelEnd", "DelayAfterEnd");
+ var configToken = jObject["Config"];
+ if (configToken is JObject configObj)
+ {
+ TryChangeKey(configObj, "DelayAfterLevelEnd", "DelayAfterEnd");
- // 1. legacy EncodingConfig -> VideoConfig
- if (configObj["EncodingConfig"] is JObject oldEncodingConfigObj)
- {
- configObj["VideoConfig"] ??= oldEncodingConfigObj;
- configObj.Remove("EncodingConfig");
- }
-
- // 2. legacy top-level Codec -> VideoConfig.Codec
- var legacyCodecToken = configObj["Codec"];
- if (legacyCodecToken != null && legacyCodecToken.Type != JTokenType.Null)
- {
- if (configObj["VideoConfig"] == null || configObj["VideoConfig"]!.Type == JTokenType.Null)
- {
- configObj["VideoConfig"] = new JObject();
- }
-
- var videoConfigObj = (JObject)configObj["VideoConfig"]!;
- videoConfigObj["Codec"] ??= legacyCodecToken;
-
- configObj.Remove("Codec");
- }
-
- // 3. legacy top-level Audio properties -> AudioConfig
- string[] legacyAudioProps = { "RenderAudio", "AudioSampleRate", "AudioChannels", "AudioBitrate" };
- foreach (var prop in legacyAudioProps)
- {
- var legacyToken = configObj[prop];
- if (legacyToken != null && legacyToken.Type != JTokenType.Null)
- {
- if (configObj["AudioConfig"] == null || configObj["AudioConfig"]!.Type == JTokenType.Null)
- {
- configObj["AudioConfig"] = new JObject();
- }
-
- var audioConfigObj = (JObject)configObj["AudioConfig"]!;
- audioConfigObj[prop] ??= legacyToken;
- }
-
- configObj.Remove(prop);
- }
-
- if (configObj["AudioConfig"] != null && configObj["AudioConfig"]!.Type == JTokenType.Null)
- {
- configObj.Remove("AudioConfig");
- }
-
- // 4. legacy top-level Advanced properties -> AdvancedConfig
- string[] legacyAdvancedProps = { "RandomSeed", "ProcessAudio", "NonTile" };
- foreach (var prop in legacyAdvancedProps)
- {
- var legacyToken = configObj[prop];
- if (legacyToken != null && legacyToken.Type != JTokenType.Null)
- {
- if (configObj["AdvancedConfig"] == null || configObj["AdvancedConfig"]!.Type == JTokenType.Null)
- {
- configObj["AdvancedConfig"] = new JObject();
- }
-
- var advancedConfigObj = (JObject)configObj["AdvancedConfig"]!;
- advancedConfigObj[prop] ??= legacyToken;
- }
-
- configObj.Remove(prop);
- }
-
- if (configObj["AdvancedConfig"] != null && configObj["AdvancedConfig"]!.Type == JTokenType.Null)
- {
- configObj.Remove("AdvancedConfig");
- }
+ // 1. legacy EncodingConfig -> VideoConfig
+ if (configObj["EncodingConfig"] is JObject oldEncodingConfigObj)
+ {
+ configObj["VideoConfig"] ??= oldEncodingConfigObj;
+ configObj.Remove("EncodingConfig");
}
-
- var preset = jObject.ToObject();
- if (preset != null && !string.IsNullOrEmpty(preset.Name))
+
+ // 2. legacy top-level Codec -> VideoConfig.Codec
+ var legacyCodecToken = configObj["Codec"];
+ if (legacyCodecToken != null && legacyCodecToken.Type != JTokenType.Null)
{
- presets.Add(preset);
+ if (configObj["VideoConfig"] == null || configObj["VideoConfig"]!.Type == JTokenType.Null)
+ {
+ configObj["VideoConfig"] = new JObject();
+ }
+
+ var videoConfigObj = (JObject)configObj["VideoConfig"]!;
+ videoConfigObj["Codec"] ??= legacyCodecToken;
+
+ configObj.Remove("Codec");
+ }
+
+ // 3. legacy top-level Audio properties -> AudioConfig
+ string[] legacyAudioProps = { "RenderAudio", "AudioSampleRate", "AudioChannels", "AudioBitrate" };
+ foreach (var prop in legacyAudioProps)
+ {
+ var legacyToken = configObj[prop];
+ if (legacyToken != null && legacyToken.Type != JTokenType.Null)
+ {
+ if (configObj["AudioConfig"] == null || configObj["AudioConfig"]!.Type == JTokenType.Null)
+ {
+ configObj["AudioConfig"] = new JObject();
+ }
+
+ var audioConfigObj = (JObject)configObj["AudioConfig"]!;
+ audioConfigObj[prop] ??= legacyToken;
+ }
+
+ configObj.Remove(prop);
+ }
+
+ if (configObj["AudioConfig"] != null && configObj["AudioConfig"]!.Type == JTokenType.Null)
+ {
+ configObj.Remove("AudioConfig");
+ }
+
+ // 4. legacy top-level Advanced properties -> AdvancedConfig
+ string[] legacyAdvancedProps = { "RandomSeed", "ProcessAudio", "NonTile" };
+ foreach (var prop in legacyAdvancedProps)
+ {
+ var legacyToken = configObj[prop];
+ if (legacyToken != null && legacyToken.Type != JTokenType.Null)
+ {
+ if (configObj["AdvancedConfig"] == null || configObj["AdvancedConfig"]!.Type == JTokenType.Null)
+ {
+ configObj["AdvancedConfig"] = new JObject();
+ }
+
+ var advancedConfigObj = (JObject)configObj["AdvancedConfig"]!;
+ advancedConfigObj[prop] ??= legacyToken;
+ }
+
+ configObj.Remove(prop);
+ }
+
+ if (configObj["AdvancedConfig"] != null && configObj["AdvancedConfig"]!.Type == JTokenType.Null)
+ {
+ configObj.Remove("AdvancedConfig");
}
}
- catch (Exception ex)
+
+ var preset = jObject.ToObject();
+ if (preset != null && !string.IsNullOrEmpty(preset.Name))
{
- MelonLogger.Warning($"프리셋 파일 '{Path.GetFileName(file)}' 로드 중 오류 발생: {ex.Message}");
+ presets.Add(preset);
}
}
+ catch (Exception ex)
+ {
+ MelonLogger.Warning($"프리셋 파일 '{Path.GetFileName(file)}' 로드 중 오류 발생: {ex.Message}");
+ }
}
- catch (Exception ex)
- {
- MelonLogger.Error($"프리셋 목록을 불러오는 중 오류 발생: {ex.Message}");
- }
-
- return presets;
+ }
+ catch (Exception ex)
+ {
+ MelonLogger.Error($"프리셋 목록을 불러오는 중 오류 발생: {ex.Message}");
}
- private static void TryChangeKey(JObject configObj, string keyBefore, string targetKey)
+ return presets;
+ }
+
+ private static void TryChangeKey(JObject configObj, string keyBefore, string targetKey)
+ {
+ var legacyToken = configObj[keyBefore];
+ if (legacyToken != null && legacyToken.Type != JTokenType.Null)
+ configObj[targetKey] = legacyToken;
+
+ configObj.Remove(keyBefore);
+ }
+
+ public static void DeletePreset(string name)
+ {
+ string dir = GetPresetsDirectory();
+ string safeName = GetSafeFileName(name);
+ string filePath = Path.Combine(dir, safeName + ".json");
+
+ if (File.Exists(filePath))
{
- var legacyToken = configObj[keyBefore];
- if (legacyToken != null && legacyToken.Type != JTokenType.Null)
- configObj[targetKey] = legacyToken;
-
- configObj.Remove(keyBefore);
- }
-
- public static void DeletePreset(string name)
- {
- string dir = GetPresetsDirectory();
- string safeName = GetSafeFileName(name);
- string filePath = Path.Combine(dir, safeName + ".json");
-
- if (File.Exists(filePath))
- {
- File.Delete(filePath);
- }
+ File.Delete(filePath);
}
}
-}
+}
\ No newline at end of file
diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs
index 33235c1..b6629d8 100644
--- a/Data/RenderConfig.cs
+++ b/Data/RenderConfig.cs
@@ -1,71 +1,70 @@
using System.Diagnostics.CodeAnalysis;
using ADOFAIRenderer.Util.FFmpeg;
-namespace ADOFAIRenderer.Data
+namespace ADOFAIRenderer.Data;
+
+[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
+public class RenderConfig
{
- [SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
- public class RenderConfig
- {
- public double RenderFPS { get; set; }
- public double VideoFPS { get; set; }
+ public double RenderFPS { get; set; }
+ public double VideoFPS { get; set; }
- public int RenderWidth { get; set; }
- public int RenderHeight { get; set; }
+ public int RenderWidth { get; set; }
+ public int RenderHeight { get; set; }
- public VideoEncodingConfig VideoConfig { get; set; } = new();
- public AudioEncodingConfig AudioConfig { get; set; } = new();
- public AdvancedConfig AdvancedConfig { get; set; } = new();
+ public VideoEncodingConfig VideoConfig { get; set; } = new();
+ public AudioEncodingConfig AudioConfig { get; set; } = new();
+ public AdvancedConfig AdvancedConfig { get; set; } = new();
- public double DelayAfterEnd { get; set; }
- public bool ShowJudgementText { get; set; } = false;
- }
+ public double DelayAfterEnd { get; set; }
+ public bool ShowJudgementText { get; set; } = false;
+}
- [SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
- public class AdvancedConfig
- {
- public int? RandomSeed { get; set; } = null;
- public bool ProcessAudio { get; set; } = true;
- public bool NonTile { get; set; } = false;
- public string FileExtension { get; set; } = "mkv";
- }
+[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
+public class AdvancedConfig
+{
+ public int? RandomSeed { get; set; } = null;
+ public bool ProcessAudio { get; set; } = true;
+ public bool NonTile { get; set; } = false;
+ public string FileExtension { get; set; } = "mkv";
+}
- public class VideoEncodingConfig
- {
- public Codec Codec { get; set; } = Codec.H264;
- public RateControlMode RateControlMode { get; set; } = RateControlMode.CBR;
+public class VideoEncodingConfig
+{
+ public Codec Codec { get; set; } = Codec.H264;
+ public RateControlMode RateControlMode { get; set; } = RateControlMode.CBR;
- public int Bitrate { get; set; } = 30000;
- public int MaxBitrate { get; set; } = 50000;
- // ReSharper disable once InconsistentNaming
- public int QP { get; set; } = 18;
- public double KeyframeInterval { get; set; } = 2.0;
- public bool ForceSoftwareEncoder { get; set; }
+ public int Bitrate { get; set; } = 30000;
+ public int MaxBitrate { get; set; } = 50000;
+ // ReSharper disable once InconsistentNaming
+ public int QP { get; set; } = 18;
+ public double KeyframeInterval { get; set; } = 2.0;
+ public bool ForceSoftwareEncoder { get; set; }
+}
+
+public class AudioEncodingConfig
+{
+ private int _audioSampleRate = 44100;
+ private int _audioChannels = 2;
+ private int _audioBitrate = 256000;
+
+ public bool RenderAudio { get; set; } = true;
+
+ public int AudioSampleRate
+ {
+ get => _audioSampleRate <= 0 ? 44100 : _audioSampleRate;
+ set => _audioSampleRate = value <= 0 ? 44100 : value;
}
- public class AudioEncodingConfig
+ public int AudioChannels
{
- private int _audioSampleRate = 44100;
- private int _audioChannels = 2;
- private int _audioBitrate = 256000;
+ get => _audioChannels <= 0 ? 2 : _audioChannels;
+ set => _audioChannels = value <= 0 ? 2 : value;
+ }
- public bool RenderAudio { get; set; } = true;
-
- public int AudioSampleRate
- {
- get => _audioSampleRate <= 0 ? 44100 : _audioSampleRate;
- set => _audioSampleRate = value <= 0 ? 44100 : value;
- }
-
- public int AudioChannels
- {
- get => _audioChannels <= 0 ? 2 : _audioChannels;
- set => _audioChannels = value <= 0 ? 2 : value;
- }
-
- public int AudioBitrate
- {
- get => _audioBitrate <= 0 ? 256000 : _audioBitrate;
- set => _audioBitrate = value <= 0 ? 256000 : value;
- }
+ public int AudioBitrate
+ {
+ get => _audioBitrate <= 0 ? 256000 : _audioBitrate;
+ set => _audioBitrate = value <= 0 ? 256000 : value;
}
}
\ No newline at end of file
diff --git a/Patch/Engine/AsyncInputPatches.cs b/Patch/Engine/AsyncInputPatches.cs
index 6b1dc2d..7b48d63 100644
--- a/Patch/Engine/AsyncInputPatches.cs
+++ b/Patch/Engine/AsyncInputPatches.cs
@@ -1,21 +1,20 @@
using ADOFAIRenderer.Core;
using HarmonyLib;
-namespace ADOFAIRenderer.Patch.Engine
+namespace ADOFAIRenderer.Patch.Engine;
+
+[HarmonyPatch(typeof(AsyncInputUtils), "UpdateOffsetTime")]
+// ReSharper disable once InconsistentNaming
+public static class AsyncInputPatches
{
- [HarmonyPatch(typeof(AsyncInputUtils), "UpdateOffsetTime")]
+ [HarmonyPrefix]
// ReSharper disable once InconsistentNaming
- public static class AsyncInputPatches
+ public static bool Prefix()
{
- [HarmonyPrefix]
- // ReSharper disable once InconsistentNaming
- public static bool Prefix()
- {
- if (!RenderManager.IsActive) return true;
- // Force offsetTick to match instantly without 1/100 damping division when rendering
- AsyncInputManager.offsetTick = AsyncInputManager.currFrameTick - (ulong)(scrConductor.instance.dspTime * 10000000.0);
- AsyncInputManager.offsetTickUpdated = true;
- return false; // Skip the original damped offset calculation
- }
+ if (!RenderManager.IsActive) return true;
+ // Force offsetTick to match instantly without 1/100 damping division when rendering
+ AsyncInputManager.offsetTick = AsyncInputManager.currFrameTick - (ulong)(scrConductor.instance.dspTime * 10000000.0);
+ AsyncInputManager.offsetTickUpdated = true;
+ return false; // Skip the original damped offset calculation
}
-}
+}
\ No newline at end of file
diff --git a/Patch/Engine/AudioPatches.cs b/Patch/Engine/AudioPatches.cs
index b6f3dc8..c79495c 100644
--- a/Patch/Engine/AudioPatches.cs
+++ b/Patch/Engine/AudioPatches.cs
@@ -4,110 +4,109 @@ using HarmonyLib;
using UnityEngine;
using UnityEngine.Audio;
-namespace ADOFAIRenderer.Patch.Engine
+namespace ADOFAIRenderer.Patch.Engine;
+
+[HarmonyPatch(typeof(AudioManager), "_Play")]
+public static class AudioManagerPlayPatch
{
- [HarmonyPatch(typeof(AudioManager), "_Play")]
- public static class AudioManagerPlayPatch
+ [HarmonyPrefix]
+ public static bool Prefix(string snd, double time, AudioMixerGroup group, float volume, int priority)
{
- [HarmonyPrefix]
- public static bool Prefix(string snd, double time, AudioMixerGroup group, float volume, int priority)
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
{
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
+ // Cache the clip on the main thread
+ RenderManager.Instance.AudioMixer?.CacheClip(snd);
+ }
+ return true; // Let the original method execute to prevent NullReferenceException
+ }
+
+ [HarmonyPostfix]
+ // ReSharper disable once InconsistentNaming
+ public static void Postfix(AudioSource __result, string snd, double time, AudioMixerGroup group, float volume, int priority)
+ {
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio && __result != null)
+ {
+ // Convert absolute DSP time to relative time in seconds from render start
+ double relativeStartTime = time - RenderManager.Instance.dspTimeBase;
+
+ // Register event to the offline mixer (default pitch = 1.0f) and map it to the AudioSource
+ RenderManager.Instance.AudioMixer?.RegisterSoundEvent(__result, snd, relativeStartTime, -1.0, volume, 1.0f);
+ }
+ }
+}
+
+[HarmonyPatch(typeof(scrConductor), "PlayWithEndTime")]
+public static class PlayWithEndTimePatch
+{
+ [HarmonyPrefix]
+ public static bool Prefix(string snd, double time, double endTime, float volume, int priority)
+ {
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
+ {
+ var mixer = RenderManager.Instance.AudioMixer;
+ if (mixer != null)
{
// Cache the clip on the main thread
- RenderManager.Instance.AudioMixer?.CacheClip(snd);
- }
- return true; // Let the original method execute to prevent NullReferenceException
- }
+ mixer.CacheClip(snd);
- [HarmonyPostfix]
- // ReSharper disable once InconsistentNaming
- public static void Postfix(AudioSource __result, string snd, double time, AudioMixerGroup group, float volume, int priority)
- {
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio && __result != null)
- {
- // Convert absolute DSP time to relative time in seconds from render start
+ // Convert absolute DSP times to relative times in seconds from render start
double relativeStartTime = time - RenderManager.Instance.dspTimeBase;
+ double relativeEndTime = endTime - RenderManager.Instance.dspTimeBase;
- // Register event to the offline mixer (default pitch = 1.0f) and map it to the AudioSource
- RenderManager.Instance.AudioMixer?.RegisterSoundEvent(__result, snd, relativeStartTime, -1.0, volume, 1.0f);
+ // Register looping sound event to the offline mixer (default pitch = 1.0f)
+ mixer.RegisterSoundEvent(snd, relativeStartTime, relativeEndTime, volume, 1.0f);
}
}
+ return true; // Let the original method execute
}
+}
- [HarmonyPatch(typeof(scrConductor), "PlayWithEndTime")]
- public static class PlayWithEndTimePatch
+[HarmonyPatch(typeof(AudioSource), "pitch", MethodType.Setter)]
+public static class AudioSourcePitchPatch
+{
+ [HarmonyPostfix]
+ // ReSharper disable once InconsistentNaming
+ public static void Postfix(AudioSource __instance, float value)
{
- [HarmonyPrefix]
- public static bool Prefix(string snd, double time, double endTime, float volume, int priority)
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
{
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
+ var mixer = RenderManager.Instance.AudioMixer;
+ if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent))
{
- var mixer = RenderManager.Instance.AudioMixer;
- if (mixer != null)
- {
- // Cache the clip on the main thread
- mixer.CacheClip(snd);
-
- // Convert absolute DSP times to relative times in seconds from render start
- double relativeStartTime = time - RenderManager.Instance.dspTimeBase;
- double relativeEndTime = endTime - RenderManager.Instance.dspTimeBase;
-
- // Register looping sound event to the offline mixer (default pitch = 1.0f)
- mixer.RegisterSoundEvent(snd, relativeStartTime, relativeEndTime, volume, 1.0f);
- }
- }
- return true; // Let the original method execute
- }
- }
-
- [HarmonyPatch(typeof(AudioSource), "pitch", MethodType.Setter)]
- public static class AudioSourcePitchPatch
- {
- [HarmonyPostfix]
- // ReSharper disable once InconsistentNaming
- public static void Postfix(AudioSource __instance, float value)
- {
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
- {
- var mixer = RenderManager.Instance.AudioMixer;
- if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent))
- {
- soundEvent.Pitch = value;
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(AudioSource), "SetScheduledEndTime", typeof(double))]
- public static class AudioSourceSetScheduledEndTimePatch
- {
- [HarmonyPostfix]
- // ReSharper disable once InconsistentNaming
- public static void Postfix(AudioSource __instance, double time)
- {
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
- {
- var mixer = RenderManager.Instance.AudioMixer;
- if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent))
- {
- soundEvent.EndTime = time - RenderManager.Instance.dspTimeBase;
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(AudioSource), "Stop", new Type[0])]
- public static class AudioSourceStopPatch
- {
- [HarmonyPostfix]
- // ReSharper disable once InconsistentNaming
- public static void Postfix(AudioSource __instance)
- {
- if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
- {
- RenderManager.Instance.AudioMixer?.RemoveSourceMapping(__instance);
+ soundEvent.Pitch = value;
}
}
}
}
+
+[HarmonyPatch(typeof(AudioSource), "SetScheduledEndTime", typeof(double))]
+public static class AudioSourceSetScheduledEndTimePatch
+{
+ [HarmonyPostfix]
+ // ReSharper disable once InconsistentNaming
+ public static void Postfix(AudioSource __instance, double time)
+ {
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
+ {
+ var mixer = RenderManager.Instance.AudioMixer;
+ if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent))
+ {
+ soundEvent.EndTime = time - RenderManager.Instance.dspTimeBase;
+ }
+ }
+ }
+}
+
+[HarmonyPatch(typeof(AudioSource), "Stop", new Type[0])]
+public static class AudioSourceStopPatch
+{
+ [HarmonyPostfix]
+ // ReSharper disable once InconsistentNaming
+ public static void Postfix(AudioSource __instance)
+ {
+ if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
+ {
+ RenderManager.Instance.AudioMixer?.RemoveSourceMapping(__instance);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Patch/Engine/RenderEnvironmentPatches.cs b/Patch/Engine/RenderEnvironmentPatches.cs
index 5f26017..726aad0 100644
--- a/Patch/Engine/RenderEnvironmentPatches.cs
+++ b/Patch/Engine/RenderEnvironmentPatches.cs
@@ -1,168 +1,165 @@
using System.Diagnostics.CodeAnalysis;
using ADOFAIRenderer.Core;
-using ADOFAIRenderer.Util;
using ADOFAIRenderer.Util.Audio;
using HarmonyLib;
using UnityEngine;
-namespace ADOFAIRenderer.Patch.Engine
+namespace ADOFAIRenderer.Patch.Engine;
+
+public static class RenderEnvironmentPatches
{
- public static class RenderEnvironmentPatches
+ private static double TimeSpent => RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.RenderFPS;
+
+ [HarmonyPatch(typeof(Time), "time", MethodType.Getter)]
+ public static class TimePatch
{
- private const bool RenderPatch = true;
- private static double TimeSpent => RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.RenderFPS;
-
- [HarmonyPatch(typeof(Time), "time", MethodType.Getter)]
- public static class TimePatch
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref float __result)
{
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref float __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = (float)(TimeSpent + RenderManager.Instance.timeBase);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Time), "timeAsDouble", MethodType.Getter)]
- public static class TimeAsDoublePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref double __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = TimeSpent + RenderManager.Instance.timeBase;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Time), "unscaledTime", MethodType.Getter)]
- public static class UnscaledTimePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref float __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = (float)(TimeSpent + RenderManager.Instance.unscaledTimeBase);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Time), "unscaledTimeAsDouble", MethodType.Getter)]
- public static class UnscaledTimeAsDoublePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref double __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = TimeSpent + RenderManager.Instance.unscaledTimeBase;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(AudioSettings), "dspTime", MethodType.Getter)]
- public static class DspTimePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref double __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = TimeSpent + RenderManager.Instance.dspTimeBase;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(AudioSource), "PlayScheduled")]
- public static class PlayScheduledPatch
- {
- // ReSharper disable once InconsistentNaming
- public static bool Prefix(AudioSource __instance, double time)
- {
- return !RenderManager.IsActive;
- }
- }
-
- [HarmonyPatch(typeof(Time), "frameCount", MethodType.Getter)]
- public static class FrameCountPatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref int __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = (int)(RenderManager.Instance.frameCount + RenderManager.Instance.frameCountBase);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Time), "deltaTime", MethodType.Getter)]
- public static class DeltaTimePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref float __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = (float)(1 / RenderManager.Instance.Configuration.RenderFPS);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Time), "unscaledDeltaTime", MethodType.Getter)]
- public static class UnscaledDeltaTimePatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref float __result)
- {
- if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
- __result = (float)(1 / RenderManager.Instance.Configuration.RenderFPS);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Screen), "width", MethodType.Getter)]
- public static class ScreenWidthPatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref int __result)
- {
- if (!RenderManager.IsActive || !RenderContext.ScreenPatch || !RenderManager.Instance) return true;
- __result = RenderManager.Instance.Configuration.RenderWidth;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Screen), "height", MethodType.Getter)]
- public static class ScreenHeightPatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(ref int __result)
- {
- if (!RenderManager.IsActive || !RenderContext.ScreenPatch || !RenderManager.Instance) return true;
- __result = RenderManager.Instance.Configuration.RenderHeight;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(AudioSource), "GetSpectrumData", typeof(float[]), typeof(int), typeof(FFTWindow))]
- public static class GetSpectrumDataPatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool Prefix(AudioSource __instance, float[] samples, int channel, FFTWindow window)
- {
- if (!RenderManager.IsCapturingActive || !RenderManager.Instance.Configuration.AdvancedConfig.ProcessAudio) return true;
- AudioSpectrumGenerator.GetSpectrumData(samples, channel, window);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(AudioManager), "FindOrLoadAudioClipExternal", typeof(string), typeof(bool), typeof(float), typeof(bool))]
- public static class FindOrLoadAudioClipExternalPatch
- {
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static void Prefix(ref bool stream)
- {
- if (RenderManager.Instance && RenderManager.Instance.OverrideLoading && RenderManager.Instance.Configuration.AdvancedConfig.ProcessAudio)
- stream = false;
- }
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = (float)(TimeSpent + RenderManager.Instance.timeBase);
+ return false;
}
}
-}
+
+ [HarmonyPatch(typeof(Time), "timeAsDouble", MethodType.Getter)]
+ public static class TimeAsDoublePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref double __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = TimeSpent + RenderManager.Instance.timeBase;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Time), "unscaledTime", MethodType.Getter)]
+ public static class UnscaledTimePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref float __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = (float)(TimeSpent + RenderManager.Instance.unscaledTimeBase);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Time), "unscaledTimeAsDouble", MethodType.Getter)]
+ public static class UnscaledTimeAsDoublePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref double __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = TimeSpent + RenderManager.Instance.unscaledTimeBase;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(AudioSettings), "dspTime", MethodType.Getter)]
+ public static class DspTimePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref double __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = TimeSpent + RenderManager.Instance.dspTimeBase;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(AudioSource), "PlayScheduled")]
+ public static class PlayScheduledPatch
+ {
+ // ReSharper disable once InconsistentNaming
+ public static bool Prefix(AudioSource __instance, double time)
+ {
+ return !RenderManager.IsActive;
+ }
+ }
+
+ [HarmonyPatch(typeof(Time), "frameCount", MethodType.Getter)]
+ public static class FrameCountPatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref int __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = (int)(RenderManager.Instance.frameCount + RenderManager.Instance.frameCountBase);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Time), "deltaTime", MethodType.Getter)]
+ public static class DeltaTimePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref float __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = (float)(1 / RenderManager.Instance.Configuration.RenderFPS);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Time), "unscaledDeltaTime", MethodType.Getter)]
+ public static class UnscaledDeltaTimePatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref float __result)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderContext.TimePatch) return true;
+ __result = (float)(1 / RenderManager.Instance.Configuration.RenderFPS);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Screen), "width", MethodType.Getter)]
+ public static class ScreenWidthPatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref int __result)
+ {
+ if (!RenderManager.IsActive || !RenderContext.ScreenPatch || !RenderManager.Instance) return true;
+ __result = RenderManager.Instance.Configuration.RenderWidth;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Screen), "height", MethodType.Getter)]
+ public static class ScreenHeightPatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(ref int __result)
+ {
+ if (!RenderManager.IsActive || !RenderContext.ScreenPatch || !RenderManager.Instance) return true;
+ __result = RenderManager.Instance.Configuration.RenderHeight;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(AudioSource), "GetSpectrumData", typeof(float[]), typeof(int), typeof(FFTWindow))]
+ public static class GetSpectrumDataPatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool Prefix(AudioSource __instance, float[] samples, int channel, FFTWindow window)
+ {
+ if (!RenderManager.IsCapturingActive || !RenderManager.Instance.Configuration.AdvancedConfig.ProcessAudio) return true;
+ AudioSpectrumGenerator.GetSpectrumData(samples, channel, window);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(AudioManager), "FindOrLoadAudioClipExternal", typeof(string), typeof(bool), typeof(float), typeof(bool))]
+ public static class FindOrLoadAudioClipExternalPatch
+ {
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static void Prefix(ref bool stream)
+ {
+ if (RenderManager.Instance && RenderManager.Instance.OverrideLoading && RenderManager.Instance.Configuration.AdvancedConfig.ProcessAudio)
+ stream = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Patch/Game/EndscreenLanternsPatch.cs b/Patch/Game/EndscreenLanternsPatch.cs
index a2a8acd..5ddb24d 100644
--- a/Patch/Game/EndscreenLanternsPatch.cs
+++ b/Patch/Game/EndscreenLanternsPatch.cs
@@ -2,73 +2,72 @@ using System.Diagnostics.CodeAnalysis;
using ADOFAIRenderer.Core;
using HarmonyLib;
-namespace ADOFAIRenderer.Patch.Game
+namespace ADOFAIRenderer.Patch.Game;
+
+[HarmonyPatch(typeof(EndscreenLanterns))]
+// ReSharper disable once InconsistentNaming
+public static class EndscreenLanternsPatch
{
- [HarmonyPatch(typeof(EndscreenLanterns))]
- // ReSharper disable once InconsistentNaming
- public static class EndscreenLanternsPatch
+ [HarmonyPrefix]
+ [HarmonyPatch("Show")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static bool ForceHide(EndscreenLanterns __instance, ref int __result)
{
- [HarmonyPrefix]
- [HarmonyPatch("Show")]
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static bool ForceHide(EndscreenLanterns __instance, ref int __result)
+ if (!RenderManager.IsActive) return true;
+ if (__instance != null)
{
- if (!RenderManager.IsActive) return true;
- if (__instance != null)
- {
- __instance.Hide();
- }
- __result = 0; // Return 0 lanterns shown
- return false; // Skip original Show method
- }
-
- [HarmonyPrefix]
- [HarmonyPatch("Hide")]
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public static void PrefixHide(EndscreenLanterns __instance)
- {
- if (__instance == null) return;
- if (!RenderManager.IsActive) return;
-
- var traverse = Traverse.Create(__instance);
-
- // Kill lampSequences (private Sequence[] lampSequences) via reflection
- var lampSequencesField = traverse.Field("lampSequences");
- var lampSequencesObj = lampSequencesField.GetValue();
- if (lampSequencesObj is System.Array lampSequencesArray)
- {
- for (var i = 0; i < lampSequencesArray.Length; i++)
- {
- var item = lampSequencesArray.GetValue(i);
- if (item == null) continue;
- try
- {
- var killMethod = item.GetType().GetMethod("Kill", new[] { typeof(bool) });
- killMethod?.Invoke(item, new object[] { false });
- }
- catch
- {
- // Ignore reflection errors
- }
- lampSequencesArray.SetValue(null, i);
- }
- }
-
- // Kill chainSequence (private Sequence chainSequence) via reflection
- var chainSequenceField = traverse.Field("chainSequence");
- var chainSequenceObj = chainSequenceField.GetValue();
- if (chainSequenceObj == null) return;
-
- try
- {
- var killMethod = chainSequenceObj.GetType().GetMethod("Kill", new[] { typeof(bool) });
- killMethod?.Invoke(chainSequenceObj, new object[] { false });
- }
- catch
- {
- // Ignore reflection errors
- }
- chainSequenceField.SetValue(null);
+ __instance.Hide();
}
+ __result = 0; // Return 0 lanterns shown
+ return false; // Skip original Show method
}
-}
+
+ [HarmonyPrefix]
+ [HarmonyPatch("Hide")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static void PrefixHide(EndscreenLanterns __instance)
+ {
+ if (__instance == null) return;
+ if (!RenderManager.IsActive) return;
+
+ var traverse = Traverse.Create(__instance);
+
+ // Kill lampSequences (private Sequence[] lampSequences) via reflection
+ var lampSequencesField = traverse.Field("lampSequences");
+ var lampSequencesObj = lampSequencesField.GetValue();
+ if (lampSequencesObj is System.Array lampSequencesArray)
+ {
+ for (var i = 0; i < lampSequencesArray.Length; i++)
+ {
+ var item = lampSequencesArray.GetValue(i);
+ if (item == null) continue;
+ try
+ {
+ var killMethod = item.GetType().GetMethod("Kill", new[] { typeof(bool) });
+ killMethod?.Invoke(item, new object[] { false });
+ }
+ catch
+ {
+ // Ignore reflection errors
+ }
+ lampSequencesArray.SetValue(null, i);
+ }
+ }
+
+ // Kill chainSequence (private Sequence chainSequence) via reflection
+ var chainSequenceField = traverse.Field("chainSequence");
+ var chainSequenceObj = chainSequenceField.GetValue();
+ if (chainSequenceObj == null) return;
+
+ try
+ {
+ var killMethod = chainSequenceObj.GetType().GetMethod("Kill", new[] { typeof(bool) });
+ killMethod?.Invoke(chainSequenceObj, new object[] { false });
+ }
+ catch
+ {
+ // Ignore reflection errors
+ }
+ chainSequenceField.SetValue(null);
+ }
+}
\ No newline at end of file
diff --git a/Patch/Game/PersistencePatch.cs b/Patch/Game/PersistencePatch.cs
index f7a1e39..72106d5 100644
--- a/Patch/Game/PersistencePatch.cs
+++ b/Patch/Game/PersistencePatch.cs
@@ -1,22 +1,21 @@
using ADOFAIRenderer.Core;
using HarmonyLib;
-namespace ADOFAIRenderer.Patch.Game
+namespace ADOFAIRenderer.Patch.Game;
+
+[HarmonyPatch(typeof(Persistence))]
+public static class PersistencePatch
{
- [HarmonyPatch(typeof(Persistence))]
- public static class PersistencePatch
+ [HarmonyPostfix]
+ [HarmonyPatch("skipIntroBehavior", MethodType.Getter)]
+ // ReSharper disable once InconsistentNaming
+ public static void LockSkipIntroBehavior(ref SkipIntroBehavior __result)
{
- [HarmonyPostfix]
- [HarmonyPatch("skipIntroBehavior", MethodType.Getter)]
- // ReSharper disable once InconsistentNaming
- public static void LockSkipIntroBehavior(ref SkipIntroBehavior __result)
+ if (RenderManager.IsActive &&
+ scrConductor.instance != null &&
+ !scrConductor.instance.hasSongStarted)
{
- if (RenderManager.IsActive &&
- scrConductor.instance != null &&
- !scrConductor.instance.hasSongStarted)
- {
- __result = SkipIntroBehavior.Off;
- }
+ __result = SkipIntroBehavior.Off;
}
}
-}
+}
\ No newline at end of file
diff --git a/Patch/Game/scnEditorPatch.cs b/Patch/Game/scnEditorPatch.cs
index 263f782..ab904af 100644
--- a/Patch/Game/scnEditorPatch.cs
+++ b/Patch/Game/scnEditorPatch.cs
@@ -4,48 +4,47 @@ using MelonLoader;
using UnityEngine;
using UnityEngine.UI;
-namespace ADOFAIRenderer.Patch.Game
-{
- [HarmonyPatch(typeof(scnEditor))]
- // ReSharper disable once InconsistentNaming
- public static class scnEditorPatch
- {
- [HarmonyPostfix]
- [HarmonyPatch("Start")]
- // ReSharper disable once InconsistentNaming
- public static void AddButtonRender(scnEditor __instance)
- {
- if (RenderMod.IsFeatureDisabled) return;
- if (!__instance || !__instance.buttonExit)
- {
- Melon.Logger.Msg("scnEditor에 렌더 버튼을 만들 수 없습니다.");
- return;
- }
+namespace ADOFAIRenderer.Patch.Game;
- var cloneTarget = __instance.buttonExit!.gameObject;
- var buttonContainer = cloneTarget.transform.parent!;
-
- var buttonObj = Object.Instantiate(cloneTarget, buttonContainer)!;
- buttonObj.name = "buttonRender";
-
- var button = buttonObj.GetComponent