using Age.Engine.Model; using Age.Engine.Persistence; using Age.Engine.Sys4; using Age.Engine.Vm; using System.Text; public class AudioMixerOpcodeTests { private static readonly OpcodeTable Table = OpcodeTableJson.Load(Paths.OpcodesJson); private static Operand G(int address) => new(3, address); private static Operand I(long value) => new(0, value); private static (int, Operand[]) Exit() => (0x2, Array.Empty()); [Fact] public void NativeDefaultsUseUnconfiguredVolumesAndEnabledRoutes() { var settings = new AudioMixerSettings(); for (int category = 0; category < AudioMixerSettings.CategoryCount; category++) { Assert.True(settings.TryGetVolume(category, out int volume)); Assert.Equal(AudioMixerSettings.UnconfiguredVolume, volume); } for (int category = (int)AudioMixerCategory.Music; category < AudioMixerSettings.CategoryCount; category++) { Assert.True(settings.TryGetRouteEnabled(category, out bool enabled)); Assert.True(enabled); } } [Fact] public void MixerOpcodesRoundTripCategoriesAndApplyLiveChanges() { var settings = new AudioMixerSettings(); var host = new RecordingHost(); Script scene = ScriptAssembler.Assemble(Table, "CONFIG_MIXER", [ (0xc6, [I(0), I(8000)]), (0xc6, [I(3), I(4500)]), (0x1ba, [I(1), I(0)]), (0xc5, [I(0), G(10)]), (0xc5, [I(3), G(11)]), (0xc5, [I(2), G(12)]), (0xc7, [I(1), G(13)]), (0xc7, [I(2), G(14)]), Exit(), ], []); var vm = new VirtualMachine(scene, Table, host, audioMixerSettings: settings); vm.Run(); Assert.Equal(8000, vm.Globals[10]); Assert.Equal(4500, vm.Globals[11]); Assert.Equal(AudioMixerSettings.UnconfiguredVolume, vm.Globals[12]); Assert.Equal(0, vm.Globals[13]); Assert.Equal(1, vm.Globals[14]); Assert.Equal([(0, 8000), (3, 4500)], host.AudioVolumeChanges); Assert.Equal([(1, false)], host.AudioRouteChanges); } [Fact] public void RouteSetterIsIdempotentLikeNativeWorkers() { var host = new RecordingHost(); Script scene = ScriptAssembler.Assemble(Table, "CONFIG_ROUTE", [ (0x1ba, [I(2), I(0)]), (0x1ba, [I(2), I(0)]), (0x1ba, [I(2), I(1)]), (0x1ba, [I(2), I(1)]), Exit(), ], []); new VirtualMachine(scene, Table, host).Run(); Assert.Equal([(2, false), (2, true)], host.AudioRouteChanges); } [Fact] public void GameSessionCarriesMixerStateAcrossFreshSceneVms() { var session = new GameSession(); Script setter = ScriptAssembler.Assemble(Table, "CONFIG_SET", [ (0xc6, [I(2), I(6250)]), (0x1ba, [I(3), I(0)]), Exit(), ], []); Script getter = ScriptAssembler.Assemble(Table, "CONFIG_GET", [ (0xc5, [I(2), G(20)]), (0xc7, [I(3), G(21)]), Exit(), ], []); session.RunScene(setter, Table, new RecordingHost()); session.RunScene(getter, Table, new RecordingHost()); Assert.Equal(6250, session.Globals[20]); Assert.Equal(0, session.Globals[21]); } [Fact] public void NativeSys4RegIniRoundTripsAudioAndPreservesOtherOptions() { string directory = Path.Combine(Path.GetTempPath(), $"age-audio-settings-{Guid.NewGuid():N}"); string path = Path.Combine(directory, Sys4RegIniStore.FileName); try { Directory.CreateDirectory(directory); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding cp932 = Encoding.GetEncoding(932); const string original = "[display]\r\nScreenMode=1\r\n\r\n" + "[sound]\r\nSound=1\r\nMusic=2\r\nSE=7\r\nVoice=1\r\nMovie=1\r\n" + "Volume0=5500\r\nVolume1=2500\r\nVolume2=3500\r\nVolume3=3500\r\nVolume4=-1\r\n" + "UseDirectSound=1\r\n\r\n" + "[message]\r\nFont=MS 明朝\r\nMessageSpeed=50\r\n"; File.WriteAllText(path, original, cp932); var store = new Sys4RegIniStore(path, defaultMusicRouteValue: 2); AudioMixerSettings settings = store.Load(); settings.Changed += store.Save; Assert.True(settings.TrySetVolume((int)AudioMixerCategory.Master, 9000)); Assert.True(settings.TrySetVolume((int)AudioMixerCategory.Movie, 3750)); Assert.True(settings.TrySetRouteEnabled( (int)AudioMixerCategory.Voice, false, out bool changed)); Assert.True(changed); Assert.True(settings.TrySetRouteEnabled( (int)AudioMixerCategory.Music, false, out changed)); Assert.True(changed); Assert.Contains("Music=-1\r\n", File.ReadAllText(path, cp932)); Assert.True(settings.TrySetRouteEnabled( (int)AudioMixerCategory.Music, true, out changed)); Assert.True(changed); AudioMixerSettings loaded = store.Load(); string rewritten = File.ReadAllText(path, cp932); Assert.True(loaded.TryGetVolume((int)AudioMixerCategory.Master, out int master)); Assert.True(loaded.TryGetVolume((int)AudioMixerCategory.Movie, out int movie)); Assert.True(loaded.TryGetRouteEnabled((int)AudioMixerCategory.Voice, out bool voice)); Assert.Equal(9000, master); Assert.Equal(3750, movie); Assert.False(voice); Assert.Contains("[display]\r\nScreenMode=1\r\n", rewritten); Assert.Contains("Sound=1\r\n", rewritten); Assert.Contains("Music=2\r\n", rewritten); Assert.Contains("SE=7\r\n", rewritten); Assert.Contains("UseDirectSound=1\r\n", rewritten); Assert.Contains("[message]\r\nFont=MS 明朝\r\nMessageSpeed=50\r\n", rewritten); Assert.DoesNotContain("engine-settings", rewritten); } finally { if (Directory.Exists(directory)) Directory.Delete(directory, recursive: true); } } [Fact] public void NativePersistencePathsComeFromIndependentSys4IniProfileValues() { Sys4AssetCatalog catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini); string localAppData = Path.Combine(Path.GetTempPath(), "native-appdata-root"); Sys4PersistencePaths paths = Sys4PersistencePaths.ResolveNative( catalog.StartupSettings, "C:\\unused-game-root", localAppData); Assert.Equal( Path.Combine(localAppData, "Eushully", "姫狩りダンジョンマイスター", "SAVE"), paths.SaveDirectory); Assert.Equal( Path.Combine(localAppData, "Eushully", "姫狩りダンジョンマイスター", "SYS4REG.INI"), paths.Sys4RegIniPath); } [Fact] public void ProfileOverrideRedirectsSaveAndSettingsAsOneNativeLayout() { Sys4AssetCatalog catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini); string profileRoot = Path.Combine(Path.GetTempPath(), "redirected-profile-root"); Sys4PersistencePaths paths = Sys4PersistencePaths.ResolveProfileOverride( catalog.StartupSettings, profileRoot); Sys4RegIniStore store = Sys4RegIniStore.ForPath( catalog.StartupSettings, paths.Sys4RegIniPath); Assert.Equal(Path.Combine(profileRoot, "SAVE"), paths.SaveDirectory); Assert.Equal(Path.Combine(profileRoot, "SYS4REG.INI"), paths.Sys4RegIniPath); Assert.Equal(paths.Sys4RegIniPath, store.FilePath); } [Fact] public void InvalidMixerCategoriesWarnAndLeaveOutputsUntouched() { var host = new RecordingHost(); Script scene = ScriptAssembler.Assemble(Table, "CONFIG_INVALID", [ (0xc5, [I(5), G(30)]), (0xc6, [I(-1), I(5000)]), (0xc7, [I(0), G(31)]), (0x1ba, [I(5), I(1)]), Exit(), ], []); var vm = new VirtualMachine(scene, Table, host); vm.Globals[30] = 123; vm.Globals[31] = 456; vm.Run(); Assert.Equal(123, vm.Globals[30]); Assert.Equal(456, vm.Globals[31]); Assert.Equal(4, host.Warnings.Count); Assert.Empty(host.AudioVolumeChanges); Assert.Empty(host.AudioRouteChanges); } }