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