OpenRakis / Spice86

Reverse engineer and rewrite real mode DOS programs!
Apache License 2.0
215 stars 18 forks source link

refactor: all classes services are injected #781

Closed maximilien-noal closed 2 weeks ago

maximilien-noal commented 1 month ago

This PR introduces dependency injection (at build time, not at runtime. We don't use Microsoft.DI) from the bottom to the top for every service.

This gets rid of the debugger visitor for the UI, and other complex initialization logic.

All the games listed in the compatibility list still work. Including Dune, Prince of Persia, and Krondor.

maximilien-noal commented 3 weeks ago

Here is what I will not put in this PR after all, DI via Reflection:

namespace Spice86.DependencyInjection;

using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;

using CommunityToolkit.Mvvm.Messaging;

using Microsoft.Extensions.DependencyInjection;

using Spice86.Core.CLI;
using Spice86.Infrastructure;
using Spice86.Logging;
using Spice86.Shared.Interfaces;

public static class ServiceCollectionExtensions {
    public static void AddConfiguration(this IServiceCollection serviceCollection, string[] args) {
        serviceCollection.AddSingleton<ICommandLineParser, CommandLineParser>();
        serviceCollection.AddSingleton<Configuration>(serviceProvider => {
            ICommandLineParser commandLineParser = serviceProvider.GetRequiredService<ICommandLineParser>();

            return commandLineParser.ParseCommandLine(args);
        });
    }

    public static void AddLogging(this IServiceCollection serviceCollection) {
        serviceCollection.AddSingleton<ILoggerService, LoggerService>(serviceProvider => {
            Configuration configuration = serviceProvider.GetRequiredService<Configuration>();
            LoggerService loggerService = new LoggerService();
            Startup.SetLoggingLevel(loggerService, configuration);
            return loggerService;
        });
    }

    public static void AddGuiInfrastructure(this IServiceCollection serviceCollection, TopLevel mainWindow) {
        serviceCollection.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);
        serviceCollection.AddSingleton<IUIDispatcher, UIDispatcher>(_ => new UIDispatcher(Dispatcher.UIThread));
        serviceCollection.AddSingleton<IStorageProvider>(_ => mainWindow.StorageProvider);
        serviceCollection.AddSingleton<IHostStorageProvider, HostStorageProvider>();
        serviceCollection.AddSingleton<ITextClipboard>(_ => new TextClipboard(mainWindow.Clipboard));
        serviceCollection.AddSingleton<IStructureViewModelFactory, StructureViewModelFactory>();
    }
}

/// <summary>
/// Entry point for Spice86 application.
/// </summary>
public class Program {
    private const string FunctionHandlerInExternalInterruptServiceKey = $"{nameof(FunctionHandler)}InExternalInterrupt";

    /// <summary>
    /// Alternate entry point to use when injecting a class that defines C# overrides of the x86 assembly code found in the target DOS program.
    /// </summary>
    /// <typeparam name="T">Type of the class that defines C# overrides of the x86 assembly code.</typeparam>
    /// <param name="args">The command-line arguments.</param>
    /// <param name="expectedChecksum">The expected checksum of the target DOS program.</param>
    [STAThread]
    public static void RunWithOverrides<T>(string[] args, string expectedChecksum) where T : class, new() {
        List<string> argsList = args.ToList();

        // Inject override
        argsList.Add($"--{nameof(Configuration.OverrideSupplierClassName)}={typeof(T).AssemblyQualifiedName}");
        argsList.Add($"--{nameof(Configuration.ExpectedChecksum)}={expectedChecksum}");
        Main(argsList.ToArray());
    }

    /// <summary>
    /// Entry point of the application.
    /// </summary>
    /// <param name="args">The command-line arguments.</param>
    [STAThread]
    public static void Main(string[] args) {
        ServiceCollection serviceCollection = new();
        serviceCollection.AddConfiguration(args);
        serviceCollection.AddLogging();
        serviceCollection.AddSingleton<IPauseHandler, PauseHandler>((services) => new PauseHandler(
            services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((services) => new RecordedDataReader(
            services.GetRequiredService<Configuration>().RecordedDataDirectory,
            services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton<ExecutionFlowRecorder>((services) =>
            services.GetRequiredService<RecordedDataReader>()
                .ReadExecutionFlowRecorderFromFileOrCreate(services.GetRequiredService<Configuration>().DumpDataOnExit is not false));

        serviceCollection.AddSingleton<State>();
        serviceCollection.AddSingleton((services) => new IOPortDispatcher(
            services.GetRequiredService<State>(),
            services.GetRequiredService<ILoggerService>(),
            services.GetRequiredService<Configuration>().FailOnUnhandledPort));

        serviceCollection.AddSingleton<IMemoryDevice, Ram>((_) => new Ram(A20Gate.EndOfHighMemoryArea));
        serviceCollection.AddSingleton((services) => new A20Gate(
            services.GetRequiredService<Configuration>().A20Gate));
        serviceCollection.AddSingleton<MemoryBreakpoints>();
        serviceCollection.AddSingleton((services) => new Memory(
            services.GetRequiredService<MemoryBreakpoints>(),
            services.GetRequiredService<IMemoryDevice>(),
            services.GetRequiredService<A20Gate>(),
            services.GetRequiredService<Configuration>().InitializeDOS is true));
        serviceCollection.AddSingleton((services) => new EmulatorBreakpointsManager(
            services.GetRequiredService<MemoryBreakpoints>(),
            services.GetRequiredService<IPauseHandler>(),
            services.GetRequiredService<State>()
            ));

        serviceCollection.AddTransient((services) => new BiosDataArea(
            services.GetRequiredService<Memory>(),
            conventionalMemorySizeKb: (ushort)Math.Clamp(A20Gate.EndOfHighMemoryArea / 1024, 0, 640)));

        serviceCollection.AddSingleton((services) => new DualPic(
        services.GetRequiredService<State>(),
        services.GetRequiredService<IOPortDispatcher>(),
        services.GetRequiredService<Configuration>().FailOnUnhandledPort,
        services.GetRequiredService<Configuration>().InitializeDOS is false,
        services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((services) => new CallbackHandler(
            services.GetRequiredService<State>(),
            services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddTransient((services) => new InterruptVectorTable(
            services.GetRequiredService<Memory>()));

        serviceCollection.AddSingleton((services) => new Stack(
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>()));

        serviceCollection.AddKeyedSingleton(nameof(FunctionHandler), (services, _) => new FunctionHandler(
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>(),
            services.GetRequiredService<ExecutionFlowRecorder>(),
            services.GetRequiredService<ILoggerService>(),
            services.GetRequiredService<Configuration>().DumpDataOnExit is not false));
        serviceCollection.AddKeyedSingleton(FunctionHandlerInExternalInterruptServiceKey, (services, _) => new FunctionHandler(
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>(),
            services.GetRequiredService<ExecutionFlowRecorder>(),
            services.GetRequiredService<ILoggerService>(),
            services.GetRequiredService<Configuration>().DumpDataOnExit is not false));

        serviceCollection.AddSingleton<Cpu>((services) => new Cpu(
            services.GetRequiredService<InterruptVectorTable>(),
            services.GetRequiredService<Stack>(),
            services.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler)),
            services.GetRequiredKeyedService<FunctionHandler>(FunctionHandlerInExternalInterruptServiceKey),
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>(),
            services.GetRequiredService<DualPic>(),
            services.GetRequiredService<IOPortDispatcher>(),
            services.GetRequiredService<CallbackHandler>(),
            services.GetRequiredService<EmulatorBreakpointsManager>(),
            services.GetRequiredService<ILoggerService>(),
            services.GetRequiredService<ExecutionFlowRecorder>()));

        serviceCollection.AddSingleton((services) => new CfgCpu(
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>(),
            services.GetRequiredService<IOPortDispatcher>(),
            services.GetRequiredService<CallbackHandler>(),
            services.GetRequiredService<DualPic>(),
            services.GetRequiredService<EmulatorBreakpointsManager>(),
            services.GetRequiredService<ILoggerService>()));

        // IO devices
        serviceCollection.AddSingleton((services) => new DmaController(
            services.GetRequiredService<Memory>(),
            services.GetRequiredService<State>(),
            services.GetRequiredService<IOPortDispatcher>(),
            services.GetRequiredService<Configuration>().FailOnUnhandledPort,
            services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((services) => new Joystick(
            services.GetRequiredService<State>(),
            services.GetRequiredService<IOPortDispatcher>(),
            services.GetRequiredService<Configuration>().FailOnUnhandledPort,
            services.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton<VideoState>();
        serviceCollection.AddSingleton((s) => new VgaIoPortHandler(
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<VideoState>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort));

        serviceCollection.AddSingleton((s) => new SoftwareMixer(
            s.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((s) => new Midi(
            s.GetRequiredService<SoftwareMixer>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<IPauseHandler>(),
            s.GetRequiredService<Configuration>().Mt32RomsPath,
            s.GetRequiredService<Configuration>().FailOnUnhandledPort,
            s.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((s) => new PcSpeaker(
            s.GetRequiredService<SoftwareMixer>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort));

        serviceCollection.AddSingleton<SoundBlasterHardwareConfig>();
        serviceCollection.AddSingleton((s) => new SoundBlaster(
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<SoftwareMixer>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<DmaController>(),
            s.GetRequiredService<DualPic>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort,
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<SoundBlasterHardwareConfig>(),
            s.GetRequiredService<IPauseHandler>()));

        serviceCollection.AddSingleton((s) => new GravisUltraSound(
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort,
            s.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((s) => new VgaFunctionality(
            s.GetRequiredService<InterruptVectorTable>(),
            s.GetRequiredService<Memory>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<BiosDataArea>(),
            s.GetRequiredService<Configuration>().InitializeDOS is true));

        // memoryAsmWriter is common to InterruptInstaller and AssemblyRoutineInstaller so that they both write at the same address (Bios Segment F000)
        serviceCollection.AddSingleton((s) => new MemoryAsmWriter(
            s.GetRequiredService<Memory>(),
            new SegmentedAddress(s.GetRequiredService<Configuration>().ProvidedAsmHandlersSegment, 0),
            s.GetRequiredService<CallbackHandler>()));
        serviceCollection.AddSingleton((s) => new InterruptInstaller(
            s.GetRequiredService<InterruptVectorTable>(),
            s.GetRequiredService<MemoryAsmWriter>(),
            s.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler))));
        serviceCollection.AddSingleton((s) => new AssemblyRoutineInstaller(
            s.GetRequiredService<MemoryAsmWriter>(),
            s.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler))));

        serviceCollection.AddSingleton((s) => {
            VgaBios vgaBios = new VgaBios(
                        s.GetRequiredService<Memory>(),
                        s.GetRequiredService<Cpu>(),
                        s.GetRequiredService<VgaFunctionality>(),
                        s.GetRequiredService<BiosDataArea>(),
                        s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, vgaBios);
            return vgaBios;
        });

        serviceCollection.AddSingleton((s) => new Timer(
            s.GetRequiredService<Configuration>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<DualPic>()));

        serviceCollection.AddSingleton((s) => {
            TimerInt8Handler timerInt8Handler = new TimerInt8Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<DualPic>(),
                s.GetRequiredService<Timer>(),
                s.GetRequiredService<BiosDataArea>(),
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, timerInt8Handler);
            return timerInt8Handler;
        });

        serviceCollection.AddSingleton((s) => {
            BiosEquipmentDeterminationInt11Handler biosEquipmentDeterminationInt11Handler = new BiosEquipmentDeterminationInt11Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, biosEquipmentDeterminationInt11Handler);
            return biosEquipmentDeterminationInt11Handler;
        });

        serviceCollection.AddSingleton((s) => {
            SystemBiosInt12Handler systemBiosInt12Handler = new SystemBiosInt12Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<BiosDataArea>(),
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, systemBiosInt12Handler);
            return systemBiosInt12Handler;
        });

        serviceCollection.AddSingleton((s) => {
            SystemBiosInt15Handler systemBiosInt15Handler = new SystemBiosInt15Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<A20Gate>(),
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, systemBiosInt15Handler);
            return systemBiosInt15Handler;
        });

        serviceCollection.AddSingleton((s) => {
            SystemClockInt1AHandler systemClockInt1AHandler = new SystemClockInt1AHandler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<ILoggerService>(),
                s.GetRequiredService<TimerInt8Handler>());
            OptionallyInstallInterruptHandler(s, systemClockInt1AHandler);
            return systemClockInt1AHandler;
        });

        serviceCollection.AddTransient((s) => {
            EmulatorStateSerializer emulatorStateSerializer = new EmulatorStateSerializer(
                s.GetRequiredService<Configuration>(),
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<State>(),
                s.GetRequiredService<CallbackHandler>(),
                s.GetRequiredService<ExecutionFlowRecorder>(),
                s.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler)),
                s.GetRequiredService<ILoggerService>());
            return emulatorStateSerializer;
        });

        serviceCollection.AddSingleton<PerformanceViewModel>();
        serviceCollection.AddSingleton((s) => new MainWindowViewModel(
            s.GetRequiredService<Timer>(),
            s.GetRequiredService<IUIDispatcher>(),
            s.GetRequiredService<IHostStorageProvider>(),
            s.GetRequiredService<ITextClipboard>(),
            s.GetRequiredService<Configuration>(),
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<IPauseHandler>()
        ));
        serviceCollection.AddTransient<IStructureViewModelFactory, StructureViewModelFactory>((s) =>
            new StructureViewModelFactory(
                s.GetRequiredService<Configuration>(),
                s.GetRequiredService<ILoggerService>(),
                s.GetRequiredService<IPauseHandler>()));
        serviceCollection.AddSingleton((s) => new DebugWindowViewModel(
            s.GetRequiredService<State>(),
            s.GetRequiredService<Memory>(),
            s.GetRequiredService<Midi>(),
            s.GetRequiredService<VideoState>().DacRegisters.ArgbPalette,
            s.GetRequiredService<SoftwareMixer>(),
            s.GetRequiredService<IVgaRenderer>(),
            s.GetRequiredService<VideoState>(),
            s.GetRequiredService<CfgCpu>().ExecutionContextManager,
            s.GetRequiredService<IMessenger>(),
            s.GetRequiredService<ITextClipboard>(),
            s.GetRequiredService<IHostStorageProvider>(),
            s.GetRequiredService<IStructureViewModelFactory>(),
            s.GetRequiredService<IPauseHandler>()
        ));

        ClassicDesktopStyleApplicationLifetime desktop = CreateDesktopApp();
        MainWindow mainWindow = new();
        serviceCollection.AddGuiInfrastructure(desktop, mainWindow);

        serviceCollection.AddSingleton<IVgaRenderer, Renderer>((s) => new Renderer(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<VideoState>()));

        serviceCollection.AddSingleton((s) => new VgaCard(
            s.GetService<MainWindowViewModel>(),
            s.GetRequiredService<IVgaRenderer>(),
            s.GetRequiredService<ILoggerService>()));

        serviceCollection.AddSingleton((s) => new Keyboard(
            s.GetRequiredService<State>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<A20Gate>(),
            s.GetRequiredService<DualPic>(),
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<MainWindowViewModel>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort));

        serviceCollection.AddSingleton((s) => {
            var biosKeyboardInt9Handler = new BiosKeyboardInt9Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<DualPic>(),
                s.GetRequiredService<Keyboard>(),
                s.GetRequiredService<BiosDataArea>(),
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, biosKeyboardInt9Handler);
            return biosKeyboardInt9Handler;
        });

        serviceCollection.AddSingleton((s) => {
            KeyboardInt16Handler keyboardInt16Handler = new KeyboardInt16Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<ILoggerService>(),
                s.GetRequiredService<BiosKeyboardInt9Handler>().BiosKeyboardBuffer);
            OptionallyInstallInterruptHandler(s, keyboardInt16Handler);
            return keyboardInt16Handler;
        });

        serviceCollection.AddSingleton((s) => new Mouse(
            s.GetRequiredService<State>(),
            s.GetRequiredService<DualPic>(),
            s.GetService<MainWindowViewModel>(),
            s.GetRequiredService<Configuration>().Mouse,
            s.GetRequiredService<ILoggerService>(),
            s.GetRequiredService<Configuration>().FailOnUnhandledPort
        ));

        serviceCollection.AddSingleton((s) => {
            MouseDriver mouseDriver = new MouseDriver(
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Mouse>(),
                s.GetService<MainWindowViewModel>(),
                s.GetRequiredService<VgaFunctionality>(),
                s.GetRequiredService<ILoggerService>()
            );
            return mouseDriver;
        });

        serviceCollection.AddSingleton<MouseInt33Handler>((s) => {
            MouseInt33Handler mouseInt33Handler = new MouseInt33Handler(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<ILoggerService>(),
                s.GetRequiredService<MouseDriver>());
            OptionallyInstallInterruptHandler(s, mouseInt33Handler);
            return mouseInt33Handler;
        });

        serviceCollection.AddSingleton((s) => {
            BiosMouseInt74Handler mouseIrq12Handler = new BiosMouseInt74Handler(
                s.GetRequiredService<DualPic>(),
                s.GetRequiredService<Memory>());
            OptionallyInstallInterruptHandler(s, mouseIrq12Handler);
            if(s.GetRequiredService<Configuration>().InitializeDOS is not false) {
                MouseDriver mouseDriver = s.GetRequiredService<MouseDriver>();
                SegmentedAddress mouseDriverAddress = s.GetRequiredService<AssemblyRoutineInstaller>()
                    .InstallAssemblyRoutine(mouseDriver);
                mouseIrq12Handler.SetMouseDriverAddress(mouseDriverAddress);
            }
            return mouseIrq12Handler;
        });

        serviceCollection.AddSingleton((s) => {
            Dos dos = new Dos(
                s.GetRequiredService<Memory>(),
                s.GetRequiredService<Cpu>(),
                s.GetRequiredService<KeyboardInt16Handler>(),
                s.GetRequiredService<VgaFunctionality>(),
                s.GetRequiredService<Configuration>().CDrive,
                s.GetRequiredService<Configuration>().Exe,
                s.GetRequiredService<Configuration>().Ems,
                new Dictionary<string, string>() { { "BLASTER", s.GetRequiredService<SoundBlaster>().BlasterString } },
                s.GetRequiredService<ILoggerService>());
            OptionallyInstallInterruptHandler(s, dos.DosInt20Handler);
            OptionallyInstallInterruptHandler(s, dos.DosInt2FHandler);
            OptionallyInstallInterruptHandler(s, dos.DosInt21Handler);
            OptionallyInstallInterruptHandler(s, dos.DosInt28Handler);
            return dos;
        });

        serviceCollection.AddSingleton((s) => new Machine(
            s.GetRequiredService<BiosDataArea>(),
            s.GetRequiredService<BiosEquipmentDeterminationInt11Handler>(),
            s.GetRequiredService<BiosKeyboardInt9Handler>(),
            s.GetRequiredService<CallbackHandler>(),
            s.GetRequiredService<Cpu>(),
            s.GetRequiredService<CfgCpu>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<Dos>(),
            s.GetRequiredService<GravisUltraSound>(),
            s.GetRequiredService<IOPortDispatcher>(),
            s.GetRequiredService<Joystick>(),
            s.GetRequiredService<Keyboard>(),
            s.GetRequiredService<BiosMouseInt74Handler>(),
            s.GetRequiredService<MouseInt33Handler>(),
            s.GetRequiredService<KeyboardInt16Handler>(),
            s.GetRequiredService<EmulatorBreakpointsManager>(),
            s.GetRequiredService<Memory>(),
            s.GetRequiredService<Midi>(),
            s.GetRequiredService<PcSpeaker>(),
            s.GetRequiredService<DualPic>(),
            s.GetRequiredService<SoundBlaster>(),
            s.GetRequiredService<SystemBiosInt12Handler>(),
            s.GetRequiredService<SystemBiosInt15Handler>(),
            s.GetRequiredService<SystemClockInt1AHandler>(),
            s.GetRequiredService<Timer>(),
            s.GetRequiredService<TimerInt8Handler>(),
            s.GetRequiredService<VgaCard>(),
            s.GetRequiredService<VideoState>(),
            s.GetRequiredService<VgaIoPortHandler>(),
            s.GetRequiredService<IVgaRenderer>(),
            s.GetRequiredService<VgaBios>(),
            s.GetRequiredService<VgaFunctionality>().VgaRom,
            s.GetRequiredService<DmaController>(),
            s.GetRequiredService<SoundBlaster>().Opl3Fm,
            s.GetRequiredService<SoftwareMixer>(),
            s.GetRequiredService<Mouse>(),
            s.GetRequiredService<MouseDriver>(),
            s.GetRequiredService<VgaFunctionality>()));

        serviceCollection.AddSingleton<ProgramExecutor>((s) => new ProgramExecutor(
            s.GetRequiredService<Configuration>(),
            s.GetRequiredService<EmulatorBreakpointsManager>(),
            s.GetRequiredService<EmulatorStateSerializer>(),
            s.GetRequiredService<Memory>(),
            s.GetRequiredService<Cpu>(),
            s.GetRequiredService<State>(),
            s.GetRequiredService<DmaController>(),
            s.GetRequiredService<Timer>(),
            s.GetRequiredService<Dos>(),
            s.GetRequiredService<CallbackHandler>(),
            s.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler)),
            s.GetRequiredService<ExecutionFlowRecorder>(),
            s.GetRequiredService<IPauseHandler>(),
            s.GetService<MainWindowViewModel>(),
            s.GetRequiredService<ILoggerService>()));

        IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
        Configuration configuration = serviceProvider.GetRequiredService<Configuration>();
        ILoggerService loggerService = serviceProvider.GetRequiredService<ILoggerService>();
        RecordedDataReader reader = serviceProvider.GetRequiredService<RecordedDataReader>();
        FunctionHandler functionHandler = serviceProvider.GetRequiredKeyedService<FunctionHandler>(nameof(FunctionHandler));
        FunctionHandler functionHandlerInExternalInterrupt = serviceProvider.GetRequiredKeyedService<FunctionHandler>(FunctionHandlerInExternalInterruptServiceKey);
        using Machine machine = serviceProvider.GetRequiredService<Machine>();
        InitializeFunctionHandlers(configuration, machine,  loggerService,
            reader.ReadGhidraSymbolsFromFileOrCreate(), functionHandler, functionHandlerInExternalInterrupt);

        using ProgramExecutor programExecutor = serviceProvider.GetRequiredService<ProgramExecutor>();
        if (configuration.HeadlessMode) {
            programExecutor.Run();
        } else {
            desktop.Startup += (_, _) => {
                mainWindow.PerformanceViewModel = serviceProvider.GetRequiredService<PerformanceViewModel>();
                mainWindow.DataContext = serviceProvider.GetRequiredService<MainWindowViewModel>();
                // DebugWindow is not shown. Therefore, the instance is not used.
                // But with this alternative ctor it will be in the OwnedWindows collection.
                // This is for the ShowInternalDebuggerBehavior.
                DebugWindowViewModel debugWindowViewModel = serviceProvider.GetRequiredService<DebugWindowViewModel>();
                _ = new DebugWindow(owner: desktop.MainWindow!) {
                    DataContext = debugWindowViewModel
                };
            };
            desktop.Start(args);
        }
    }

    private static void OptionallyInstallInterruptHandler(IServiceProvider s, IInterruptHandler interruptHandler) {
        if (s.GetRequiredService<Configuration>().InitializeDOS is not false) {
            s.GetRequiredService<InterruptInstaller>().InstallInterruptHandler(interruptHandler);
        }
    }

    private static ClassicDesktopStyleApplicationLifetime CreateDesktopApp() {
        AppBuilder appBuilder = AppBuilder.Configure(() => new App())
            .UsePlatformDetect()
            .LogToTrace()
            .WithInterFont();
        ClassicDesktopStyleApplicationLifetime desktop = new ClassicDesktopStyleApplicationLifetime {
            ShutdownMode = ShutdownMode.OnMainWindowClose
        };
        appBuilder.SetupWithLifetime(desktop);
        return desktop;
    }

    private static Dictionary<SegmentedAddress, FunctionInformation> GenerateFunctionInformations(
        ILoggerService loggerService, Configuration configuration, Machine machine) {
        Dictionary<SegmentedAddress, FunctionInformation> res = new();
        if (configuration.OverrideSupplier == null) {
            return res;
        }

        if (loggerService.IsEnabled(Serilog.Events.LogEventLevel.Verbose)) {
            loggerService.Verbose("Override supplied: {OverrideSupplier}", configuration.OverrideSupplier);
        }

        foreach (KeyValuePair<SegmentedAddress, FunctionInformation> element in configuration.OverrideSupplier
                .GenerateFunctionInformations(loggerService, configuration, configuration.ProgramEntryPointSegment, machine)) {
            res.Add(element.Key, element.Value);
        }

        return res;
    }

    private static void InitializeFunctionHandlers(Configuration configuration, Machine machine, ILoggerService loggerService,
        IDictionary<SegmentedAddress, FunctionInformation> functionInformations, FunctionHandler cpuFunctionHandler, FunctionHandler cpuFunctionHandlerInExternalInterrupt) {
        if (configuration.OverrideSupplier != null) {
            DictionaryUtils.AddAll(functionInformations,
                GenerateFunctionInformations(loggerService, configuration,
                    machine));
        }

        if (functionInformations.Count == 0) {
            return;
        }

        bool useCodeOverride = configuration.UseCodeOverrideOption;
        SetupFunctionHandler(cpuFunctionHandler, functionInformations, useCodeOverride);
        SetupFunctionHandler(cpuFunctionHandlerInExternalInterrupt, functionInformations, useCodeOverride);
    }

    private static void SetupFunctionHandler(FunctionHandler functionHandler,
        IDictionary<SegmentedAddress, FunctionInformation> functionInformations, bool useCodeOverride) {
        functionHandler.FunctionInformations = functionInformations;
        functionHandler.UseCodeOverride = useCodeOverride;
    }
}

And after all that effort, the screen does not render anything.

Compared with:


/// <summary>
/// Entry point for Spice86 application.
/// </summary>
public class Program {
    /// <summary>
    /// Alternate entry point to use when injecting a class that defines C# overrides of the x86 assembly code found in the target DOS program.
    /// </summary>
    /// <typeparam name="T">Type of the class that defines C# overrides of the x86 assembly code.</typeparam>
    /// <param name="args">The command-line arguments.</param>
    /// <param name="expectedChecksum">The expected checksum of the target DOS program.</param>
    [STAThread]
    public static void RunWithOverrides<T>(string[] args, string expectedChecksum) where T : class, new() {
        List<string> argsList = args.ToList();

        // Inject override
        argsList.Add($"--{nameof(Configuration.OverrideSupplierClassName)}={typeof(T).AssemblyQualifiedName}");
        argsList.Add($"--{nameof(Configuration.ExpectedChecksum)}={expectedChecksum}");
        Main(argsList.ToArray());
    }

    /// <summary>
    /// Entry point of the application.
    /// </summary>
    /// <param name="args">The command-line arguments.</param>
    [STAThread]
    public static void Main(string[] args) {
        IMessenger messenger = WeakReferenceMessenger.Default;
        ILoggerService loggerService = new LoggerService();
        Configuration configuration = new CommandLineParser().ParseCommandLine(args);
        Startup.SetLoggingLevel(loggerService, configuration);
        IPauseHandler pauseHandler = new PauseHandler(loggerService);

        RecordedDataReader reader = new(configuration.RecordedDataDirectory, loggerService);
        ExecutionFlowRecorder executionFlowRecorder = reader.ReadExecutionFlowRecorderFromFileOrCreate(configuration.DumpDataOnExit is not false);
        State state = new();
        IOPortDispatcher ioPortDispatcher = new(state, loggerService, configuration.FailOnUnhandledPort);
        Ram ram = new(A20Gate.EndOfHighMemoryArea);
        A20Gate a20Gate = new(configuration.A20Gate);
        MemoryBreakpoints memoryBreakpoints = new();
        Memory memory = new(memoryBreakpoints, ram, a20Gate, initializeResetVector: configuration.InitializeDOS is true);
        EmulatorBreakpointsManager emulatorBreakpointsManager = new(memoryBreakpoints, pauseHandler, state);
        var biosDataArea = new BiosDataArea(memory, conventionalMemorySizeKb: (ushort)Math.Clamp(ram.Size / 1024, 0, 640));
        var dualPic = new DualPic(state, ioPortDispatcher, configuration.FailOnUnhandledPort, configuration.InitializeDOS is false, loggerService);

        CallbackHandler callbackHandler = new(state, loggerService);
        InterruptVectorTable interruptVectorTable = new(memory);
        Stack stack = new(memory, state);
        FunctionHandler functionHandler = new(memory, state, executionFlowRecorder, loggerService, configuration.DumpDataOnExit is not false);
        FunctionHandler functionHandlerInExternalInterrupt = new(memory, state, executionFlowRecorder, loggerService, configuration.DumpDataOnExit is not false);
        Cpu cpu  = new(interruptVectorTable, stack,
            functionHandler, functionHandlerInExternalInterrupt, memory, state,
            dualPic, ioPortDispatcher, callbackHandler, emulatorBreakpointsManager,
            loggerService, executionFlowRecorder);

        CfgCpu cfgCpu = new(memory, state, ioPortDispatcher, callbackHandler, dualPic, emulatorBreakpointsManager, loggerService);

        // IO devices
        DmaController dmaController = new(memory, state, ioPortDispatcher, configuration.FailOnUnhandledPort, loggerService);

        Joystick joystick = new Joystick(state, ioPortDispatcher, configuration.FailOnUnhandledPort, loggerService);

        VideoState videoState = new();
        VgaIoPortHandler videoInt10Handler = new(state, ioPortDispatcher, loggerService, videoState, configuration.FailOnUnhandledPort);
        Renderer vgaRenderer = new(memory, videoState);

        SoftwareMixer softwareMixer = new(loggerService);
        Midi midiDevice = new Midi(softwareMixer, state, ioPortDispatcher, pauseHandler, configuration.Mt32RomsPath, configuration.FailOnUnhandledPort, loggerService);

        PcSpeaker pcSpeaker = new PcSpeaker(softwareMixer, state, ioPortDispatcher, loggerService, configuration.FailOnUnhandledPort);

        var soundBlasterHardwareConfig = new SoundBlasterHardwareConfig(7, 1, 5, SbType.Sb16);
        SoundBlaster soundBlaster = new SoundBlaster(ioPortDispatcher, softwareMixer, state, dmaController, dualPic, configuration.FailOnUnhandledPort,
            loggerService, soundBlasterHardwareConfig, pauseHandler);

        GravisUltraSound gravisUltraSound = new GravisUltraSound(state, ioPortDispatcher, configuration.FailOnUnhandledPort, loggerService);

        VgaFunctionality vgaFunctionality = new VgaFunctionality(interruptVectorTable, memory, ioPortDispatcher, biosDataArea,
            bootUpInTextMode: configuration.InitializeDOS is true);
        VgaBios vgaBios = new VgaBios(memory, cpu, vgaFunctionality, biosDataArea, loggerService);

        Timer timer = new Timer(configuration, state, ioPortDispatcher, loggerService, dualPic);
        TimerInt8Handler timerInt8Handler = new TimerInt8Handler(memory, cpu, dualPic, timer, biosDataArea, loggerService);

        BiosEquipmentDeterminationInt11Handler biosEquipmentDeterminationInt11Handler = new BiosEquipmentDeterminationInt11Handler(memory, cpu, loggerService);
        SystemBiosInt12Handler systemBiosInt12Handler = new SystemBiosInt12Handler(memory, cpu, biosDataArea, loggerService);
        SystemBiosInt15Handler systemBiosInt15Handler = new SystemBiosInt15Handler(memory, cpu, a20Gate, loggerService);
        SystemClockInt1AHandler systemClockInt1AHandler = new SystemClockInt1AHandler(memory, cpu, loggerService, timerInt8Handler);

        EmulatorStateSerializer emulatorStateSerializer = new(configuration, memory, state, callbackHandler, executionFlowRecorder, functionHandler, loggerService);

        MainWindowViewModel? mainWindowViewModel = null;
        ClassicDesktopStyleApplicationLifetime? desktop = null;
        MainWindow? mainWindow = null;
        ITextClipboard? textClipboard = null;
        IHostStorageProvider? hostStorageProvider = null;
        if (!configuration.HeadlessMode) {
            desktop = CreateDesktopApp();
            PerformanceViewModel performanceViewModel = new(state, pauseHandler);
            mainWindow = new() {
                PerformanceViewModel = performanceViewModel
            };
            textClipboard = new TextClipboard(mainWindow.Clipboard);
            hostStorageProvider = new HostStorageProvider(mainWindow.StorageProvider, configuration, emulatorStateSerializer);
            mainWindowViewModel = new MainWindowViewModel(
                timer, new UIDispatcher(Dispatcher.UIThread), hostStorageProvider, textClipboard, configuration, loggerService, pauseHandler);
        }

        using (mainWindowViewModel) {
            VgaCard vgaCard = new(mainWindowViewModel, vgaRenderer, loggerService);
            Keyboard keyboard = new Keyboard(state, ioPortDispatcher, a20Gate, dualPic, loggerService, mainWindowViewModel, configuration.FailOnUnhandledPort);
            BiosKeyboardInt9Handler biosKeyboardInt9Handler = new BiosKeyboardInt9Handler(memory, cpu, dualPic, keyboard, biosDataArea, loggerService);
            Mouse mouse = new Mouse(state, dualPic, mainWindowViewModel, configuration.Mouse, loggerService, configuration.FailOnUnhandledPort);

            MouseDriver mouseDriver = new MouseDriver(cpu, memory, mouse, mainWindowViewModel, vgaFunctionality, loggerService);

            KeyboardInt16Handler keyboardInt16Handler = new KeyboardInt16Handler(memory, cpu, loggerService, biosKeyboardInt9Handler.BiosKeyboardBuffer);
            Dos dos = new Dos(memory, cpu, keyboardInt16Handler, vgaFunctionality, configuration.CDrive,
                configuration.Exe, configuration.Ems,
                new Dictionary<string, string>() { { "BLASTER", soundBlaster.BlasterString } },
                loggerService);

            if (configuration.InitializeDOS is not false) {
                // memoryAsmWriter is common to InterruptInstaller and AssemblyRoutineInstaller so that they both write at the same address (Bios Segment F000)
                MemoryAsmWriter memoryAsmWriter = new(memory, new SegmentedAddress(configuration.ProvidedAsmHandlersSegment, 0), callbackHandler);
                InterruptInstaller interruptInstaller = new InterruptInstaller(interruptVectorTable, memoryAsmWriter, cpu.FunctionHandler);
                AssemblyRoutineInstaller assemblyRoutineInstaller = new AssemblyRoutineInstaller(memoryAsmWriter, cpu.FunctionHandler);

                // Register the interrupt handlers
                interruptInstaller.InstallInterruptHandler(vgaBios);
                interruptInstaller.InstallInterruptHandler(timerInt8Handler);
                interruptInstaller.InstallInterruptHandler(biosKeyboardInt9Handler);
                interruptInstaller.InstallInterruptHandler(biosEquipmentDeterminationInt11Handler);
                interruptInstaller.InstallInterruptHandler(systemBiosInt12Handler);
                interruptInstaller.InstallInterruptHandler(systemBiosInt15Handler);
                interruptInstaller.InstallInterruptHandler(keyboardInt16Handler);
                interruptInstaller.InstallInterruptHandler(systemClockInt1AHandler);
                interruptInstaller.InstallInterruptHandler(dos.DosInt20Handler);
                interruptInstaller.InstallInterruptHandler(dos.DosInt21Handler);
                interruptInstaller.InstallInterruptHandler(dos.DosInt2FHandler);
                interruptInstaller.InstallInterruptHandler(dos.DosInt28Handler);

                var mouseInt33Handler = new MouseInt33Handler(memory, cpu, loggerService, mouseDriver);
                interruptInstaller.InstallInterruptHandler(mouseInt33Handler);

                var mouseIrq12Handler = new BiosMouseInt74Handler(dualPic, memory);
                interruptInstaller.InstallInterruptHandler(mouseIrq12Handler);

                SegmentedAddress mouseDriverAddress = assemblyRoutineInstaller.InstallAssemblyRoutine(mouseDriver);
                mouseIrq12Handler.SetMouseDriverAddress(mouseDriverAddress);
            }
            using Machine machine = new Machine(biosDataArea, biosEquipmentDeterminationInt11Handler, biosKeyboardInt9Handler,
                callbackHandler, cpu,
                cfgCpu, state, dos, gravisUltraSound, ioPortDispatcher,
                joystick, keyboard, keyboardInt16Handler, emulatorBreakpointsManager, memory, midiDevice, pcSpeaker,
                dualPic, soundBlaster, systemBiosInt12Handler, systemBiosInt15Handler, systemClockInt1AHandler, timer,
                timerInt8Handler,
                vgaCard, videoState, videoInt10Handler, vgaRenderer, vgaBios, vgaFunctionality.VgaRom,
                dmaController, soundBlaster.Opl3Fm, softwareMixer, mouse, mouseDriver,
                vgaFunctionality);

            InitializeFunctionHandlers(configuration, machine,  loggerService,
                reader.ReadGhidraSymbolsFromFileOrCreate(), functionHandler, functionHandlerInExternalInterrupt);

            using ProgramExecutor programExecutor = new(configuration, emulatorBreakpointsManager, emulatorStateSerializer, memory, cpu, state,
                dmaController, timer, dos, callbackHandler, functionHandler, executionFlowRecorder, pauseHandler, mainWindowViewModel,
                loggerService);
            if (configuration.HeadlessMode) {
                programExecutor.Run();
            } else if (mainWindowViewModel != null && mainWindow != null && desktop != null
                    && textClipboard != null && hostStorageProvider != null) {
                mainWindow.DataContext = mainWindowViewModel;
                desktop.MainWindow = mainWindow;
                DebugWindowViewModel debugWindowViewModel = new DebugWindowViewModel(state, memory,
                    midiDevice, videoState.DacRegisters.ArgbPalette, softwareMixer, vgaRenderer, videoState,
                    cfgCpu.ExecutionContextManager, messenger, textClipboard, hostStorageProvider,
                    new StructureViewModelFactory(configuration, loggerService, pauseHandler), pauseHandler);
                desktop.Startup += (_, _) => {
                    // DebugWindow is not shown. Therefore, the instance is not used.
                    // But with the alternative ctor it will be in the OwnedWindows collection.
                    // This is for the ShowInternalDebuggerBehavior.
                    _ = new DebugWindow(owner: mainWindow) {
                        DataContext = debugWindowViewModel
                    };
                };
                desktop.Start(args);
            }
        }
    }

    private static Dictionary<SegmentedAddress, FunctionInformation> GenerateFunctionInformations(
        ILoggerService loggerService, Configuration configuration, Machine machine) {
        Dictionary<SegmentedAddress, FunctionInformation> res = new();
        if (configuration.OverrideSupplier == null) {
            return res;
        }

        if (loggerService.IsEnabled(Serilog.Events.LogEventLevel.Verbose)) {
            loggerService.Verbose("Override supplied: {OverrideSupplier}", configuration.OverrideSupplier);
        }

        foreach (KeyValuePair<SegmentedAddress, FunctionInformation> element in configuration.OverrideSupplier
                .GenerateFunctionInformations(loggerService, configuration, configuration.ProgramEntryPointSegment, machine)) {
            res.Add(element.Key, element.Value);
        }

        return res;
    }

    private static void InitializeFunctionHandlers(Configuration configuration, Machine machine, ILoggerService loggerService,
        IDictionary<SegmentedAddress, FunctionInformation> functionInformations, FunctionHandler cpuFunctionHandler, FunctionHandler cpuFunctionHandlerInExternalInterrupt) {
        if (configuration.OverrideSupplier != null) {
            DictionaryUtils.AddAll(functionInformations,
                GenerateFunctionInformations(loggerService, configuration,
                    machine));
        }

        if (functionInformations.Count == 0) {
            return;
        }

        bool useCodeOverride = configuration.UseCodeOverrideOption;
        SetupFunctionHandler(cpuFunctionHandler, functionInformations, useCodeOverride);
        SetupFunctionHandler(cpuFunctionHandlerInExternalInterrupt, functionInformations, useCodeOverride);
    }

    private static void SetupFunctionHandler(FunctionHandler functionHandler,
        IDictionary<SegmentedAddress, FunctionInformation> functionInformations, bool useCodeOverride) {
        functionHandler.FunctionInformations = functionInformations;
        functionHandler.UseCodeOverride = useCodeOverride;
    }

    private static ClassicDesktopStyleApplicationLifetime CreateDesktopApp() {
        AppBuilder appBuilder = AppBuilder.Configure(() => new App())
            .UsePlatformDetect()
            .LogToTrace()
            .WithInterFont();
        ClassicDesktopStyleApplicationLifetime desktop = new ClassicDesktopStyleApplicationLifetime {
            ShutdownMode = ShutdownMode.OnMainWindowClose
        };
        appBuilder.SetupWithLifetime(desktop);
        return desktop;
    }
}

Which still removes all the complex initialization logic, and the UI double dispatch, without introducing a massive headache, new properties in the Machine so the mouse works (otherwise interrupt handler 0x33 wasn't found), and regressions such as the screen being black all the time.

I'll take the simple Program.cs, and move on to the important Debugger features, and then the Adlib Gold and/or CFGCPU, whatever I can work on for Dune.

I just think reflection-based DI is a lot of work and has too much risks, and not much reward. Especially for a very long list of 99% singletons.