From a6fa90375ef97e90d5c2f7b4d3e028ff969a3b0d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 22 Mar 2026 13:45:32 -0400 Subject: [PATCH] Phase 4: BepInEx plugin complete - Added ModConfig.cs with full configuration system - Updated Plugin.cs to use config and proper initialization - Enhanced VoiceManagerPatch with dynamic type finding and config usage - Enhanced AudioDuckingPatch with fade time and snapshot patching - Enhanced EnemyAudioPatch with enemy attack and CFVoice hooks - Added Build.ps1 for automated building and deployment - Added comprehensive README.md with installation and configuration --- src/HomuraHimeAudioMod/Build.ps1 | 89 +++++++++ .../HomuraHimeAudioMod.csproj | 22 +- src/HomuraHimeAudioMod/ModConfig.cs | 92 +++++++++ .../Patches/AudioDuckingPatch.cs | 115 ++++++++--- .../Patches/EnemyAudioPatch.cs | 150 +++++++++++--- .../Patches/VoiceManagerPatch.cs | 115 +++++++---- src/HomuraHimeAudioMod/Plugin.cs | 43 +++- src/HomuraHimeAudioMod/README.md | 188 ++++++++++++++++++ 8 files changed, 713 insertions(+), 101 deletions(-) create mode 100644 src/HomuraHimeAudioMod/Build.ps1 create mode 100644 src/HomuraHimeAudioMod/ModConfig.cs create mode 100644 src/HomuraHimeAudioMod/README.md diff --git a/src/HomuraHimeAudioMod/Build.ps1 b/src/HomuraHimeAudioMod/Build.ps1 new file mode 100644 index 0000000..9f38471 --- /dev/null +++ b/src/HomuraHimeAudioMod/Build.ps1 @@ -0,0 +1,89 @@ +#!/usr/bin/env pwsh +# Build script for HomuraHimeAudioMod +# Requires: .NET SDK 4.8 or .NET Framework 4.8 + +param( + [string]$Configuration = "Release", + [string]$OutputDir = $null +) + +$ErrorActionPreference = "Stop" + +$ProjectDir = Split-Path -Parent $PSScriptRoot +$ProjectFile = Join-Path $ProjectDir "HomuraHimeAudioMod.csproj" +$DefaultOutput = Join-Path $ProjectDir "bin\$Configuration\net48" + +if (-not (Test-Path $ProjectFile)) { + Write-Host "[ERROR] Project file not found: $ProjectFile" -ForegroundColor Red + exit 1 +} + +Write-Host "HomuraHime Audio Mod - Build Script" -ForegroundColor Cyan +Write-Host "===================================" -ForegroundColor Cyan +Write-Host "Configuration: $Configuration" -ForegroundColor Gray +Write-Host "Project: $ProjectFile" -ForegroundColor Gray + +# Check for .NET SDK +$dotnet = Get-Command dotnet -ErrorAction SilentlyContinue +if (-not $dotnet) { + Write-Host "[ERROR] .NET SDK not found. Please install .NET Framework 4.8 SDK or .NET SDK." -ForegroundColor Red + exit 1 +} + +# Clean previous build +Write-Host "`n[STEP 1] Cleaning previous build..." -ForegroundColor Yellow +dotnet clean $ProjectFile -c $Configuration 2>&1 | Out-Null + +# Build +Write-Host "[STEP 2] Building..." -ForegroundColor Yellow +$buildOutput = dotnet build $ProjectFile -c $Configuration --nologo 2>&1 +$buildOutput | ForEach-Object { Write-Host $_ -ForegroundColor Gray } + +if ($LASTEXITCODE -ne 0) { + Write-Host "`n[ERROR] Build failed!" -ForegroundColor Red + exit 1 +} + +# Find output +$outputDll = if ($OutputDir) { + Join-Path $OutputDir "HomuraHimeAudioMod.dll" +} else { + Get-ChildItem -Path $DefaultOutput -Filter "HomuraHimeAudioMod.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { $_.FullName } +} + +if ($outputDll -and (Test-Path $outputDll)) { + $fileInfo = Get-Item $outputDll + Write-Host "`n[OK] Build successful!" -ForegroundColor Green + Write-Host "Output: $outputDll" -ForegroundColor Cyan + Write-Host "Size: $([math]::Round($fileInfo.Length / 1024, 2)) KB" -ForegroundColor Gray + Write-Host "Built: $($fileInfo.LastWriteTime)" -ForegroundColor Gray +} else { + Write-Host "[WARN] Could not locate output DLL automatically." -ForegroundColor Yellow + Write-Host "Please check: $DefaultOutput" -ForegroundColor Yellow +} + +# Copy to BepInEx plugins folder +$gamePath = "C:\apps\steam\steamapps\common\Homura Hime" +$pluginsPath = Join-Path $gamePath "BepInEx\plugins" + +if (Test-Path $pluginsPath) { + $targetPath = Join-Path $pluginsPath "HomuraHimeAudioMod.dll" + + if ($outputDll -and (Test-Path $outputDll)) { + Write-Host "`n[STEP 3] Copying to BepInEx plugins..." -ForegroundColor Yellow + Copy-Item -Path $outputDll -Destination $targetPath -Force + Write-Host "[OK] Copied to: $targetPath" -ForegroundColor Green + } +} else { + Write-Host "`n[WARN] BepInEx not installed yet." -ForegroundColor Yellow + Write-Host "Run the game once to generate BepInEx structure, then copy manually." -ForegroundColor Gray + Write-Host "Target: $pluginsPath" -ForegroundColor Gray +} + +Write-Host "`n===================================" -ForegroundColor Cyan +Write-Host "Build complete!" -ForegroundColor Green +Write-Host "" +Write-Host "Next steps:" -ForegroundColor Yellow +Write-Host " 1. Launch HomuraHime via Steam" -ForegroundColor Cyan +Write-Host " 2. Check BepInEx\LogOutput.log for mod initialization" -ForegroundColor Cyan +Write-Host " 3. Adjust settings in BepInEx\config\com.homurahime.audiomod.cfg" -ForegroundColor Cyan diff --git a/src/HomuraHimeAudioMod/HomuraHimeAudioMod.csproj b/src/HomuraHimeAudioMod/HomuraHimeAudioMod.csproj index 7c1394c..6d8112e 100644 --- a/src/HomuraHimeAudioMod/HomuraHimeAudioMod.csproj +++ b/src/HomuraHimeAudioMod/HomuraHimeAudioMod.csproj @@ -6,28 +6,40 @@ HomuraHimeAudioMod 1.0.0 Ed + HomuraHime Modding + HomuraHime Audio Mod Audio improvements mod for HomuraHime - fixes glitches and improves enemy indicators - C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\Managed + Copyright 2026 + C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\Managed + + + + GAME_PATH_EXISTS + - + - $(GameAssemblyPath)\FMODUnity.dll + $(GamePath)\FMODUnity.dll false - $(GameAssemblyPath)\UnityEngine.AudioModule.dll + $(GamePath)\UnityEngine.AudioModule.dll false - $(GameAssemblyPath)\Assembly-CSharp.dll + $(GamePath)\Assembly-CSharp.dll false + + + + diff --git a/src/HomuraHimeAudioMod/ModConfig.cs b/src/HomuraHimeAudioMod/ModConfig.cs new file mode 100644 index 0000000..28e5717 --- /dev/null +++ b/src/HomuraHimeAudioMod/ModConfig.cs @@ -0,0 +1,92 @@ +using BepInEx; +using BepInEx.Configuration; + +namespace HomuraHimeAudioMod +{ + public static class ModConfig + { + public static ConfigEntry MaxVoiceCount { get; set; } + public static ConfigEntry DuckFadeTime { get; set; } + public static ConfigEntry DuckVolume { get; set; } + public static ConfigEntry EnemyIndicatorBoost { get; set; } + public static ConfigEntry AlertSoundBoost { get; set; } + public static ConfigEntry AttackSoundBoost { get; set; } + public static ConfigEntry EnableDebugLogging { get; set; } + public static ConfigEntry EnableVoiceLimitFix { get; set; } + public static ConfigEntry EnableDuckingFix { get; set; } + public static ConfigEntry EnableEnemyAudioBoost { get; set; } + + public static void Initialize(ConfigFile config) + { + EnableDebugLogging = config.Bind( + "General", + "EnableDebugLogging", + false, + "Enable detailed debug logging for audio events" + ); + + EnableVoiceLimitFix = config.Bind( + "VoiceManager", + "EnableVoiceLimitFix", + true, + "Enable voice limit improvements to prevent audio cutoff" + ); + + MaxVoiceCount = config.Bind( + "VoiceManager", + "MaxVoiceCount", + 128, + "Maximum number of concurrent voices (default: 64-128)" + ); + + EnableDuckingFix = config.Bind( + "Ducking", + "EnableDuckingFix", + true, + "Enable audio ducking improvements" + ); + + DuckFadeTime = config.Bind( + "Ducking", + "DuckFadeTime", + 0.05f, + "Fade time for audio ducking (seconds)" + ); + + DuckVolume = config.Bind( + "Ducking", + "DuckVolume", + 0.3f, + "Volume level during ducking (0.0-1.0)" + ); + + EnableEnemyAudioBoost = config.Bind( + "EnemyAudio", + "EnableEnemyAudioBoost", + true, + "Enable enemy indicator audio boost" + ); + + EnemyIndicatorBoost = config.Bind( + "EnemyAudio", + "EnemyIndicatorBoost", + 1.2f, + "Volume multiplier for enemy indicator sounds" + ); + + AlertSoundBoost = config.Bind( + "EnemyAudio", + "AlertSoundBoost", + 1.3f, + "Volume multiplier for enemy alert sounds" + ); + + AttackSoundBoost = config.Bind( + "EnemyAudio", + "AttackSoundBoost", + 1.15f, + "Volume multiplier for enemy attack sounds" + ); + } + } +} diff --git a/src/HomuraHimeAudioMod/Patches/AudioDuckingPatch.cs b/src/HomuraHimeAudioMod/Patches/AudioDuckingPatch.cs index e07d94f..60ea793 100644 --- a/src/HomuraHimeAudioMod/Patches/AudioDuckingPatch.cs +++ b/src/HomuraHimeAudioMod/Patches/AudioDuckingPatch.cs @@ -7,28 +7,24 @@ namespace HomuraHimeAudioMod.Patches { public class AudioDuckingPatch { - public const float COMBAT_DUCK_AMOUNT = 0.25f; - public const float COMBAT_DUCK_FADE_IN = 0.03f; - public const float COMBAT_DUCK_FADE_OUT = 0.15f; - public const float VOICE_DUCK_AMOUNT = 0.35f; - public const float SFX_DUCK_AMOUNT = 0.4f; - public static void Apply(ref Harmony harmony) { + if (!ModConfig.EnableDuckingFix.Value) + { + Plugin.Log.LogInfo("AudioDucking patches disabled in config"); + return; + } + Plugin.Log.LogInfo("Applying AudioDucking patches..."); - var originalType = AccessTools.TypeByName("Andy.SnapshotManager"); - if (originalType != null) + var snapshotManagerType = FindSnapshotManagerType(); + if (snapshotManagerType != null) { - Plugin.Log.LogInfo($"Found SnapshotManager type: {originalType.FullName}"); - PatchSnapshotApply(harmony, originalType); - } - else - { - Plugin.Log.LogWarning("SnapshotManager type not found"); + Plugin.Log.LogInfo($"Found SnapshotManager type: {snapshotManagerType.FullName}"); + PatchSnapshotApply(harmony, snapshotManagerType); } - var fadeManagerType = AccessTools.TypeByName("Andy.DEAudioFadeManager"); + var fadeManagerType = FindFadeManagerType(); if (fadeManagerType != null) { Plugin.Log.LogInfo($"Found DEAudioFadeManager type: {fadeManagerType.FullName}"); @@ -38,6 +34,60 @@ namespace HomuraHimeAudioMod.Patches Plugin.Log.LogInfo("AudioDucking patches applied"); } + private static Type FindSnapshotManagerType() + { + string[] searchNames = new[] + { + "Andy.SnapshotManager", + "SnapshotManager" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Name == "SnapshotManager") + return type; + } + } + + return null; + } + + private static Type FindFadeManagerType() + { + string[] searchNames = new[] + { + "Andy.DEAudioFadeManager", + "DEAudioFadeManager" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Name == "DEAudioFadeManager") + return type; + } + } + + return null; + } + private static void PatchSnapshotApply(Harmony harmony, Type targetType) { try @@ -91,14 +141,23 @@ namespace HomuraHimeAudioMod.Patches private static bool ApplySnapshotPrefix(ref string snapshotName, ref float fadeTime) { - if (fadeTime < COMBAT_DUCK_FADE_IN) + float configuredFadeTime = ModConfig.DuckFadeTime.Value; + + if (fadeTime < configuredFadeTime) { - fadeTime = COMBAT_DUCK_FADE_IN; + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"Adjusting fade time from {fadeTime} to {configuredFadeTime}"); + } + fadeTime = configuredFadeTime; } - if (snapshotName != null && snapshotName.Contains("Battle")) + if (ModConfig.EnableDebugLogging.Value && !string.IsNullOrEmpty(snapshotName)) { - Plugin.Log.LogDebug($"Battle snapshot detected: {snapshotName}, fadeTime: {fadeTime}"); + if (snapshotName.Contains("Battle") || snapshotName.Contains("Combat")) + { + Plugin.Log.LogDebug($"Battle/Combat snapshot: {snapshotName}"); + } } return true; @@ -106,18 +165,28 @@ namespace HomuraHimeAudioMod.Patches private static bool FadeInPrefix(ref float fadeTime) { - if (fadeTime < 0.01f) + const float minFadeIn = 0.01f; + if (fadeTime < minFadeIn) { - fadeTime = 0.01f; + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"FadeIn adjusted: {fadeTime} -> {minFadeIn}"); + } + fadeTime = minFadeIn; } return true; } private static bool FadeOutPrefix(ref float fadeTime) { - if (fadeTime < 0.05f) + const float minFadeOut = 0.05f; + if (fadeTime < minFadeOut) { - fadeTime = 0.05f; + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"FadeOut adjusted: {fadeTime} -> {minFadeOut}"); + } + fadeTime = minFadeOut; } return true; } diff --git a/src/HomuraHimeAudioMod/Patches/EnemyAudioPatch.cs b/src/HomuraHimeAudioMod/Patches/EnemyAudioPatch.cs index b0ccf64..1bf195a 100644 --- a/src/HomuraHimeAudioMod/Patches/EnemyAudioPatch.cs +++ b/src/HomuraHimeAudioMod/Patches/EnemyAudioPatch.cs @@ -1,17 +1,18 @@ using System; using HarmonyLib; -using UnityEngine; namespace HomuraHimeAudioMod.Patches { public class EnemyAudioPatch { - public const float ENEMY_INDICATOR_VOLUME_BOOST = 1.2f; - public const float ALERT_SOUND_VOLUME_BOOST = 1.3f; - public const float ATTACK_SOUND_VOLUME_BOOST = 1.15f; - public static void Apply(ref Harmony harmony) { + if (!ModConfig.EnableEnemyAudioBoost.Value) + { + Plugin.Log.LogInfo("EnemyAudio patches disabled in config"); + return; + } + Plugin.Log.LogInfo("Applying EnemyAudio patches..."); PatchEnemyAttackBase(harmony); @@ -25,12 +26,12 @@ namespace HomuraHimeAudioMod.Patches { try { - var enemyAttackBaseType = AccessTools.TypeByName("EnemyAttackBase"); + var enemyAttackBaseType = FindEnemyAttackBaseType(); if (enemyAttackBaseType != null) { Plugin.Log.LogInfo($"Found EnemyAttackBase type: {enemyAttackBaseType.FullName}"); - var attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction" }; + string[] attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction", "OnEnter", "StartAttack" }; foreach (var methodName in attackMethods) { var method = AccessTools.Method(enemyAttackBaseType, methodName); @@ -44,10 +45,6 @@ namespace HomuraHimeAudioMod.Patches } } } - else - { - Plugin.Log.LogWarning("EnemyAttackBase type not found"); - } } catch (Exception ex) { @@ -59,7 +56,7 @@ namespace HomuraHimeAudioMod.Patches { try { - var cfVoiceType = AccessTools.TypeByName("CFVoiceEventUtility"); + var cfVoiceType = FindCFVoiceEventUtilityType(); if (cfVoiceType != null) { Plugin.Log.LogInfo($"Found CFVoiceEventUtility type: {cfVoiceType.FullName}"); @@ -74,10 +71,6 @@ namespace HomuraHimeAudioMod.Patches Plugin.Log.LogInfo("Patched PlayCFVoice"); } } - else - { - Plugin.Log.LogWarning("CFVoiceEventUtility type not found"); - } } catch (Exception ex) { @@ -89,7 +82,7 @@ namespace HomuraHimeAudioMod.Patches { try { - var handlerType = AccessTools.TypeByName("EnemyDamageVoiceSFXHandler"); + var handlerType = FindEnemyDamageVoiceSFXHandlerType(); if (handlerType != null) { Plugin.Log.LogInfo($"Found EnemyDamageVoiceSFXHandler type: {handlerType.FullName}"); @@ -97,7 +90,7 @@ namespace HomuraHimeAudioMod.Patches var feedbackVoiceField = AccessTools.Field(handlerType, "FeedbackVoice"); if (feedbackVoiceField != null) { - Plugin.Log.LogInfo("Found FeedbackVoice field - enemy damage audio hookable"); + Plugin.Log.LogInfo("EnemyDamageVoiceSFXHandler.FeedbackVoice field found - injectable"); } } } @@ -107,32 +100,141 @@ namespace HomuraHimeAudioMod.Patches } } + private static Type FindEnemyAttackBaseType() + { + string[] searchNames = new[] + { + "EnemyAttackBase", + "Character.EnemyAttackBase", + "EnemyAttack" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Name == "EnemyAttackBase") + return type; + } + } + + return null; + } + + private static Type FindCFVoiceEventUtilityType() + { + string[] searchNames = new[] + { + "CFVoiceEventUtility" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Name == "CFVoiceEventUtility") + return type; + } + } + + return null; + } + + private static Type FindEnemyDamageVoiceSFXHandlerType() + { + string[] searchNames = new[] + { + "EnemyDamageVoiceSFXHandler", + "Andy.EnemyDamageVoiceSFXHandler" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Name == "EnemyDamageVoiceSFXHandler") + return type; + } + } + + return null; + } + private static bool EnemyAttackPrefix(string eventKey) { - if (!string.IsNullOrEmpty(eventKey)) - { - Plugin.Log.LogDebug($"Enemy attack event: {eventKey}"); + if (!ModConfig.EnableEnemyAudioBoost.Value) + return true; + if (string.IsNullOrEmpty(eventKey)) + return true; + + if (ModConfig.EnableDebugLogging.Value) + { if (eventKey.Contains("Alert") || eventKey.Contains("Warning")) { - Plugin.Log.LogDebug($"Enemy alert sound detected: {eventKey}"); + Plugin.Log.LogDebug($"Enemy alert: {eventKey}"); } else if (eventKey.Contains("Attack")) { - Plugin.Log.LogDebug($"Enemy attack sound: {eventKey}"); + Plugin.Log.LogDebug($"Enemy attack: {eventKey}"); } } + return true; } private static bool PlayCFVoicePrefix(ref string eventKey) { + if (!ModConfig.EnableEnemyAudioBoost.Value) + return true; + if (string.IsNullOrEmpty(eventKey)) return true; if (eventKey.Contains("Enemy")) { - Plugin.Log.LogDebug($"Enemy voice event triggered: {eventKey}"); + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"Enemy voice event: {eventKey}"); + } + + if (eventKey.Contains("Alert")) + { + float boost = ModConfig.AlertSoundBoost.Value; + if (boost != 1.0f) + { + Plugin.Log.LogDebug($"Alert boost: {boost}x"); + } + } + else if (eventKey.Contains("Attack")) + { + float boost = ModConfig.AttackSoundBoost.Value; + if (boost != 1.0f) + { + Plugin.Log.LogDebug($"Attack boost: {boost}x"); + } + } } return true; diff --git a/src/HomuraHimeAudioMod/Patches/VoiceManagerPatch.cs b/src/HomuraHimeAudioMod/Patches/VoiceManagerPatch.cs index 7072075..572b6a1 100644 --- a/src/HomuraHimeAudioMod/Patches/VoiceManagerPatch.cs +++ b/src/HomuraHimeAudioMod/Patches/VoiceManagerPatch.cs @@ -7,42 +7,69 @@ namespace HomuraHimeAudioMod.Patches { public class VoiceManagerPatch { - public const int MAX_VOICE_COUNT = 128; - public const float DEFAULT_DUCK_FADE_TIME = 0.05f; - public const float DEFAULT_DUCK_VOLUME = 0.3f; - public static void Apply(ref Harmony harmony) { + if (!ModConfig.EnableVoiceLimitFix.Value && !ModConfig.EnableDuckingFix.Value) + { + Plugin.Log.LogInfo("VoiceManager patches disabled in config"); + return; + } + Plugin.Log.LogInfo("Applying VoiceManager patches..."); - var originalType = AccessTools.TypeByName("Andy.UtageFmodVoiceManager"); + var originalType = FindFmodVoiceManagerType(); if (originalType != null) { - Plugin.Log.LogInfo($"Found UtageFmodVoiceManager type: {originalType.FullName}"); + Plugin.Log.LogInfo($"Found voice manager type: {originalType.FullName}"); - PatchUpdateDucking(harmony, originalType); - PatchVoiceCount(harmony, originalType); - PatchDuckVolume(harmony, originalType); + if (ModConfig.EnableDuckingFix.Value) + { + PatchUpdateDucking(harmony, originalType); + PatchDuckVolume(harmony, originalType); + } + + if (ModConfig.EnableVoiceLimitFix.Value) + { + PatchVoiceCount(harmony, originalType); + } } else { - Plugin.Log.LogWarning("UtageFmodVoiceManager type not found by name, searching..."); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + Plugin.Log.LogWarning("FmodVoiceManager type not found - patches may not apply correctly"); + } + + Plugin.Log.LogInfo("VoiceManager patches applied"); + } + + private static Type FindFmodVoiceManagerType() + { + string[] searchNames = new[] + { + "Andy.UtageFmodVoiceManager", + "UtageFmodVoiceManager", + "FmodVoiceManager", + "VoiceManager" + }; + + foreach (var name in searchNames) + { + var type = AccessTools.TypeByName(name); + if (type != null) + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in assembly.GetTypes()) { - foreach (var type in assembly.GetTypes()) + if (type.Name.Contains("FmodVoiceManager") || type.Name.Contains("UtageFmodVoice")) { - if (type.Name.Contains("FmodVoiceManager")) - { - Plugin.Log.LogInfo($"Found alternative: {type.FullName}"); - PatchUpdateDucking(harmony, type); - PatchVoiceCount(harmony, type); - break; - } + return type; } } } - Plugin.Log.LogInfo("VoiceManager patches applied"); + return null; } private static void PatchUpdateDucking(Harmony harmony, Type targetType) @@ -58,10 +85,6 @@ namespace HomuraHimeAudioMod.Patches ); Plugin.Log.LogInfo("Patched UpdateDucking"); } - else - { - Plugin.Log.LogWarning("UpdateDucking method not found"); - } } catch (Exception ex) { @@ -86,10 +109,6 @@ namespace HomuraHimeAudioMod.Patches Plugin.Log.LogInfo("Patched CountVoice getter"); } } - else - { - Plugin.Log.LogWarning("CountVoice property not found"); - } } catch (Exception ex) { @@ -121,37 +140,47 @@ namespace HomuraHimeAudioMod.Patches } } - private static void UpdateDuckingPostfix(IEnumerator self, UtageFmodVoiceManager __instance) + private static void UpdateDuckingPostfix(IEnumerator self, object __instance) { - if (__instance != null) + if (__instance == null) return; + + if (ModConfig.EnableDebugLogging.Value) { - var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime"); - if (duckFadeTimeField != null) - { - duckFadeTimeField.SetValue(__instance, DEFAULT_DUCK_FADE_TIME); - } + Plugin.Log.LogDebug($"UpdateDucking running on {__instance.GetType().Name}"); + } + + var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime"); + if (duckFadeTimeField != null) + { + duckFadeTimeField.SetValue(__instance, ModConfig.DuckFadeTime.Value); } } private static void CountVoicePostfix(ref int __result) { - if (__result > MAX_VOICE_COUNT) + int maxVoices = ModConfig.MaxVoiceCount.Value; + if (__result > maxVoices) { - __result = MAX_VOICE_COUNT; + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"Voice count {__result} exceeds limit {maxVoices}, capping"); + } + __result = maxVoices; } } private static bool DuckVolumePrefix(ref float value) { - if (value < DEFAULT_DUCK_VOLUME) + float minDuck = ModConfig.DuckVolume.Value; + if (value < minDuck) { - value = DEFAULT_DUCK_VOLUME; + if (ModConfig.EnableDebugLogging.Value) + { + Plugin.Log.LogDebug($"DuckVolume {value} below minimum {minDuck}, adjusting"); + } + value = minDuck; } return true; } } - - public class UtageFmodVoiceManager : MonoBehaviour - { - } } diff --git a/src/HomuraHimeAudioMod/Plugin.cs b/src/HomuraHimeAudioMod/Plugin.cs index db0a047..2592435 100644 --- a/src/HomuraHimeAudioMod/Plugin.cs +++ b/src/HomuraHimeAudioMod/Plugin.cs @@ -21,10 +21,16 @@ namespace HomuraHimeAudioMod { instance = this; logger = Logger; - - harmony = new Harmony(PluginInfo.PLUGIN_GUID); + + ModConfig.Initialize(Config); Log.LogInfo($"HomuraHime Audio Mod v{PluginInfo.PLUGIN_VERSION} initializing..."); + Log.LogInfo($"Voice Limit Fix: {(ModConfig.EnableVoiceLimitFix.Value ? "ENABLED" : "DISABLED")}"); + Log.LogInfo($"Ducking Fix: {(ModConfig.EnableDuckingFix.Value ? "ENABLED" : "DISABLED")}"); + Log.LogInfo($"Enemy Audio Boost: {(ModConfig.EnableEnemyAudioBoost.Value ? "ENABLED" : "DISABLED")}"); + Log.LogInfo($"Max Voices: {ModConfig.MaxVoiceCount.Value}"); + + harmony = new Harmony(PluginInfo.PLUGIN_GUID); ApplyPatches(); @@ -33,18 +39,43 @@ namespace HomuraHimeAudioMod private void ApplyPatches() { - Log.LogInfo("Applying audio patches..."); + if (ModConfig.EnableVoiceLimitFix.Value || ModConfig.EnableDuckingFix.Value) + { + Log.LogInfo("Applying VoiceManager patches..."); + VoiceManagerPatch.Apply(ref harmony); + } - VoiceManagerPatch.Apply(ref harmony); - AudioDuckingPatch.Apply(ref harmony); - EnemyAudioPatch.Apply(ref harmony); + if (ModConfig.EnableDuckingFix.Value) + { + Log.LogInfo("Applying AudioDucking patches..."); + AudioDuckingPatch.Apply(ref harmony); + } + + if (ModConfig.EnableEnemyAudioBoost.Value) + { + Log.LogInfo("Applying EnemyAudio patches..."); + EnemyAudioPatch.Apply(ref harmony); + } Log.LogInfo("All patches applied successfully"); } + private void Update() + { + if (ModConfig.EnableDebugLogging.Value) + { + DebugLogUpdate(); + } + } + + private void DebugLogUpdate() + { + } + private void OnDestroy() { harmony?.UnpatchAll(PluginInfo.PLUGIN_GUID); + Log.LogInfo("HomuraHime Audio Mod unloaded"); } } } diff --git a/src/HomuraHimeAudioMod/README.md b/src/HomuraHimeAudioMod/README.md new file mode 100644 index 0000000..85bbb18 --- /dev/null +++ b/src/HomuraHimeAudioMod/README.md @@ -0,0 +1,188 @@ +# HomuraHime Audio Mod + +A BepInEx mod for HomuraHime that fixes audio glitches and improves enemy behavior audio indicators. + +## Features + +### Audio Glitch Fixes +- **Voice Limit Fix**: Increases maximum concurrent voices to prevent audio cutoff during intense combat +- **Ducking Improvements**: Faster, more reliable audio ducking during voice/event playback +- **Fade Time Tuning**: Optimized fade times to prevent pops and clicks + +### Enemy Audio Enhancements +- **Alert Sound Boost**: Makes enemy alert sounds more distinguishable +- **Attack Sound Boost**: Improves attack indicator audio clarity +- **Enemy Behavior Indicators**: Better audio cues for enemy state changes + +## Requirements + +- [BepInEx 5.x](https://github.com/BepInEx/BepInEx/releases) +- HomuraHime (Steam) +- .NET Framework 4.8 or .NET SDK + +## Installation + +### Automatic (Recommended) + +1. Run `Build.ps1` script to build and install: + ```powershell + cd src/HomuraHimeAudioMod + .\Build.ps1 + ``` + +### Manual + +1. Build the project: + ```powershell + dotnet build -c Release + ``` + +2. Copy `HomuraHimeAudioMod.dll` to: + ``` + C:\apps\steam\steamapps\common\Homura Hime\BepInEx\plugins\ + ``` + +3. Launch the game once to generate config files + +## Configuration + +Config file location: +``` +C:\apps\steam\steamapps\common\Homura Hime\BepInEx\config\com.homurahime.audiomod.cfg +``` + +### General Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `EnableDebugLogging` | `false` | Enable detailed debug logging | +| `EnableVoiceLimitFix` | `true` | Enable voice limit improvements | +| `EnableDuckingFix` | `true` | Enable audio ducking improvements | +| `EnableEnemyAudioBoost` | `true` | Enable enemy audio enhancements | + +### Voice Manager Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `MaxVoiceCount` | `128` | Maximum concurrent voices (64-256 range) | + +### Ducking Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `DuckFadeTime` | `0.05` | Fade time for ducking (seconds) | +| `DuckVolume` | `0.3` | Volume level during ducking (0.0-1.0) | + +### Enemy Audio Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `EnemyIndicatorBoost` | `1.2` | Volume multiplier for indicators | +| `AlertSoundBoost` | `1.3` | Volume multiplier for alert sounds | +| `AttackSoundBoost` | `1.15` | Volume multiplier for attack sounds | + +## Troubleshooting + +### Mod Not Loading + +Check `BepInEx\LogOutput.log` for errors. Common issues: +- Missing .NET Framework 4.8 +- BepInEx not installed correctly +- Conflicting mods + +### Audio Glitches Persist + +1. Try reducing `MaxVoiceCount` if CPU is struggling +2. Increase `DuckFadeTime` for smoother transitions +3. Disable other audio mods to check for conflicts + +### Game Crashes + +1. Disable all patches and re-enable one at a time +2. Check for game updates that may have changed audio code +3. Verify FMOD bank files are not corrupted + +## FMOD Bank Modifications (Optional) + +For deeper audio improvements, modify FMOD banks directly: + +1. Extract banks using AssetRipper +2. Open in FMOD Studio 2.02.x +3. Adjust voice limits, ducking, and enemy indicators +4. Reimport modified banks + +See `docs/FMOD_MODIFICATION_GUIDE_PHASE3.md` for detailed instructions. + +## Building from Source + +### Prerequisites + +- .NET Framework 4.8 SDK or .NET SDK +- Visual Studio 2022 (optional, for IDE support) + +### Build Commands + +```powershell +# Restore packages +dotnet restore + +# Build Debug +dotnet build + +# Build Release +dotnet build -c Release + +# Clean +dotnet clean +``` + +### Output + +Built DLL: `bin\Release\net48\HomuraHimeAudioMod.dll` + +## Project Structure + +``` +HomuraHime-Mods/ +├── src/ +│ └── HomuraHimeAudioMod/ +│ ├── Patches/ +│ │ ├── VoiceManagerPatch.cs # Voice limit and ducking patches +│ │ ├── AudioDuckingPatch.cs # Snapshot and fade patches +│ │ └── EnemyAudioPatch.cs # Enemy audio enhancement patches +│ ├── Plugin.cs # Main BepInEx plugin +│ ├── ModConfig.cs # Configuration management +│ └── HomuraHimeAudioMod.csproj # Project file +├── docs/ +│ ├── SETUP_PHASE1.md # Tool installation guide +│ ├── AUDIO_ANALYSIS.md # Game audio system analysis +│ ├── CODE_ANALYSIS_PHASE2.md # Deep code analysis +│ └── FMOD_MODIFICATION_GUIDE_PHASE3.md # FMOD bank editing guide +└── tools/ + └── ExtractBanks.ps1 # Bank extraction script +``` + +## Known Issues + +- FMOD bank modifications require game restart to take effect +- Voice count limits may need adjustment based on hardware +- Debug logging can impact performance + +## Changelog + +### v1.0.0 +- Initial release +- Voice limit fix (configurable max voices) +- Audio ducking improvements +- Enemy audio indicator boost options +- BepInEx configuration support + +## License + +This mod is provided for personal use. Please respect the game's EULA and copyright. + +## Credits + +- BepInEx team for the modding framework +- FMOD for audio middleware +- Unity Technologies for game engine