diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs
index 397c5ea..8e2a90c 100644
--- a/Data/RenderConfig.cs
+++ b/Data/RenderConfig.cs
@@ -95,6 +95,13 @@ public class AdvancedConfig
///
public bool NonTile { get; set; } = false;
+ ///
+ /// When true, the game's bloom (VideoBloom) computes its blur size as if the frame were 720 pixels high, so the
+ /// blur passes on the 198-pixel-high bloom buffer are the same as in a 720p frame at any render height
+ /// (VideoBloomPatch). Default false.
+ ///
+ public bool HighResBloomCorrection { get; set; } = false;
+
/// File extension of the default output file name, without the dot (e.g. "mkv", "mp4"); selects the container.
public string FileExtension { get; set; } = "mkv";
@@ -120,6 +127,7 @@ public class AdvancedConfig
RandomSeed = source.RandomSeed;
ProcessAudio = source.ProcessAudio;
NonTile = source.NonTile;
+ HighResBloomCorrection = source.HighResBloomCorrection;
FileExtension = source.FileExtension;
firePlanetColor = source.firePlanetColor;
icePlanetColor = source.icePlanetColor;
diff --git a/Localization/Locale/en.json b/Localization/Locale/en.json
index 9a126f7..ecae246 100644
--- a/Localization/Locale/en.json
+++ b/Localization/Locale/en.json
@@ -42,6 +42,7 @@
"ui.render_config.file_extension": "File Extension",
"ui.render_config.process_audio": "Process Audio",
"ui.render_config.non_tile": "Non-Tile Mode",
+ "ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction",
"ui.render_config.random_seed": "Random Seed (Optional)",
"ui.render_config.validation_title": "Invalid Settings",
"ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0",
diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json
index d42bb03..c54434d 100644
--- a/Localization/Locale/ko.json
+++ b/Localization/Locale/ko.json
@@ -42,6 +42,7 @@
"ui.render_config.file_extension": "파일 확장자",
"ui.render_config.process_audio": "오디오 처리",
"ui.render_config.non_tile": "논타일 모드",
+ "ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정",
"ui.render_config.random_seed": "랜덤 시드 (선택 사항)",
"ui.render_config.validation_title": "설정 오류",
"ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.",
diff --git a/Patch/Game/VideoBloomPatch.cs b/Patch/Game/VideoBloomPatch.cs
new file mode 100644
index 0000000..8a497d6
--- /dev/null
+++ b/Patch/Game/VideoBloomPatch.cs
@@ -0,0 +1,59 @@
+using ADOFAIRenderer.Core;
+using HarmonyLib;
+using UnityEngine;
+
+namespace ADOFAIRenderer.Patch.Game;
+
+///
+/// Makes the game's bloom compute its blur size as if the frame were 720 pixels high
+/// ("High-Resolution Bloom Correction" option).
+///
+/// Patched member: VideoBloom.OnRenderImage(RenderTexture source, RenderTexture destination). The game scales the
+/// frame down to a bloom buffer 198 pixels high (scale = 198 / source.height) and sets the blur radius in bloom
+/// buffer pixels to KernelSize * MediumKernelScale * scale (and KernelSize * LargeKernelScale * scale).
+/// BloomBlit turns the radius into the number of 5-tap blur passes and the blend amount of the last pass.
+///
+///
+/// While the render is active () and AdvancedConfig.HighResBloomCorrection is
+/// enabled, the prefix multiplies KernelSize by source.height / 720 for the duration of the call, so the
+/// radius equals KernelSize * MediumKernelScale * 198 / 720 and the blur passes on the bloom buffer are the same
+/// as in a 720p frame. The finalizer restores the original KernelSize. The bloom buffer size and the downscale
+/// from the source frame keep the game's behavior.
+///
+///
+[HarmonyPatch(typeof(VideoBloom))]
+public class VideoBloomPatch
+{
+ /// Frame height in pixels the blur size is computed for.
+ private const float ReferenceHeight = 720f;
+
+ /// Prefix of VideoBloom.OnRenderImage.
+ /// The bloom component on the game camera.
+ /// The camera frame; its height selects the multiplier.
+ /// Original KernelSize, or null when it was not changed.
+ [HarmonyPrefix]
+ [HarmonyPatch("OnRenderImage")]
+ // ReSharper disable InconsistentNaming
+ public static void ScaleKernelSize(VideoBloom __instance, RenderTexture source, out float? __state)
+ // ReSharper restore InconsistentNaming
+ {
+ __state = null;
+ if (!source || !RenderManager.IsActive || !RenderManager.Instance.Configuration.AdvancedConfig.HighResBloomCorrection)
+ return;
+
+ __state = __instance.KernelSize;
+ __instance.KernelSize *= source.height / ReferenceHeight;
+ }
+
+ /// Finalizer of VideoBloom.OnRenderImage: restores KernelSize when the prefix changed it.
+ /// The bloom component on the game camera.
+ /// Original KernelSize stored by the prefix, or null.
+ [HarmonyFinalizer]
+ [HarmonyPatch("OnRenderImage")]
+ // ReSharper disable InconsistentNaming
+ public static void RestoreKernelSize(VideoBloom __instance, float? __state)
+ // ReSharper restore InconsistentNaming
+ {
+ if (__state.HasValue) __instance.KernelSize = __state.Value;
+ }
+}
diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs
index 28a81f9..ef29a38 100644
--- a/UI/Window/RenderConfigWindow.cs
+++ b/UI/Window/RenderConfigWindow.cs
@@ -139,6 +139,7 @@ internal static class RenderConfigWindow
private static O5InputField? _randomSeedInput;
private static O5Toggle? _processAudioToggle;
private static O5Toggle? _nonTileToggle;
+ private static O5Toggle? _highResBloomCorrectionToggle;
// Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores
// null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker.
@@ -698,6 +699,7 @@ internal static class RenderConfigWindow
// Section 6: Advanced Settings card (RenderConfig.AdvancedConfig)
// File Extension | Random Seed
// Process Audio | Non-Tile Mode
+ // High-Resolution Bloom Correction |
// "Planet Colors" label, then three columns: Fire | Ice | Wind (toggle + color picker each)
// ---------------------------------------------------------------------------------------------
// 7. Advanced Settings (Collapsible Card)
@@ -739,6 +741,23 @@ internal static class RenderConfigWindow
generateLabel: false
);
+ // High-Resolution Bloom Correction (AdvancedConfig.HighResBloomCorrection): the game's bloom computes its blur
+ // size as if the frame were 720 pixels high (VideoBloomPatch)
+ _highResBloomCorrectionToggle = CreateToggleField(
+ ctx, CreateFieldSlot(advLeft),
+ labelKey: "",
+ defaultValue: false,
+ value: config.AdvancedConfig.HighResBloomCorrection,
+ toggleTextKey: "ui.render_config.high_res_bloom_correction",
+ id: "toggle_high_res_bloom_correction",
+ onChanged: val =>
+ {
+ config.AdvancedConfig.HighResBloomCorrection = val;
+ MarkConfigModified();
+ },
+ generateLabel: false
+ );
+
// Random Seed (AdvancedConfig.RandomSeed): integer passed to UnityEngine.Random.InitState before the game scene
// loads; empty text stores null (no seeding). Text that is neither empty nor an integer keeps the previous value.
_randomSeedInput = CreateInputField(
@@ -980,6 +999,7 @@ internal static class RenderConfigWindow
_randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false);
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
+ _highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false);
// Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is
// blocked without an override
@@ -1161,6 +1181,7 @@ internal static class RenderConfigWindow
}
if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value;
if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value;
+ if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value;
// Planet Colors
target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)