} = 0x0000;                                           // 16-bit

        public AudioHardware(ROMData romData, ROMSet romset = ROMSet.PacMan)
        {
            if (romset != ROMSet.PacMan && romset != ROMSet.MsPacMan)
            {
                throw new ArgumentException($"Unexpected ROM set: {romset}");
            }

            _romset = romset;

            var rom1 = romData.Data[ROMs.PAC_MAN_SOUND_1.FileName];
            var rom2 = romData.Data[ROMs.PAC_MAN_SOUND_2.FileName];

            _soundROMs = new byte[512];
            Array.Copy(rom1, 0, _soundROMs, 0, 256);
            Array.Copy(rom2, 0, _soundROMs, 256, 256);
        }
        public VideoHardware(ROMData romData, ROMSet romset = ROMSet.PacMan)
        {
            if (romset != ROMSet.PacMan && romset != ROMSet.MsPacMan)
            {
                throw new ArgumentException($"Unexpected ROM set: {romset}");
            }

            _romset = romset;

            _colorROM   = romData.Data[ROMs.PAC_MAN_COLOR.FileName];
            _paletteROM = romData.Data[ROMs.PAC_MAN_PALETTE.FileName];
            _tileROM    = romData.Data[romset == ROMSet.MsPacMan ? ROMs.MS_PAC_MAN_TILE.FileName : ROMs.PAC_MAN_TILE.FileName];
            _spriteROM  = romData.Data[romset == ROMSet.MsPacMan ? ROMs.MS_PAC_MAN_SPRITE.FileName : ROMs.PAC_MAN_SPRITE.FileName];

            _frameBuffer = new Image <Rgba32>(RESOLUTION_WIDTH, RESOLUTION_HEIGHT, new Rgba32()
            {
                R = 0, G = 0, B = 0,  A = 255
            });
        }
Ejemplo n.º 3
0
        /**
         * Used to start execution of the CPU with the given ROM and optional emulator state.
         * The emulator's hardware loop will run on a spereate thread, and therefore, this method
         * is non-blocking.
         */
        public void Start(ROMData romData, EmulatorState state = null)
        {
            if (_thread != null)
            {
                throw new Exception("Emulator cannot be started because it was already running.");
            }

            if (romData == null || romData.Data == null || romData.Data.Count == 0)
            {
                throw new Exception("romData is required.");
            }

            if (ROMSet != ROMSet.PacMan && ROMSet != ROMSet.MsPacMan)
            {
                throw new ArgumentException($"Unexpected ROM set: {ROMSet}");
            }

            // The initial configuration of the CPU.
            var cpuConfig = new CPUConfig()
            {
                Registers = new CPURegisters()
                {
                    PC = 0x0000,

                    // Hardcode the stackpointer to the top of the RAM.
                    // TODO: Is this different for each game that runs on the Pac-Man hardware?
                    SP = 0x4FEF,
                },

                // Interrupts are initially disabled, and will be enabled by the program ROM when ready.
                InterruptsEnabled = false,

                // Diagnostics is only for unit tests.
                EnableDiagnosticsMode = false,
            };

            // Initialize the CPU and subscribe to device events.
            _cpu = new CPU(cpuConfig);
            _cpu.OnDeviceRead        += CPU_OnDeviceRead;
            _cpu.OnDeviceWrite       += CPU_OnDeviceWrite;
            _cyclesSinceLastInterrupt = 0;

            // Fetch the ROM data; we trust the contents were validated with a CRC32 check elsewhere, but
            // since the CRC check can be bypassed, we at least need to ensure the file sizes are correct
            // since this classes' implementation of IMemory is expecting certain addreses.

            var codeRom1 = romData.Data[ROMs.PAC_MAN_CODE_1.FileName];
            var codeRom2 = romData.Data[ROMs.PAC_MAN_CODE_2.FileName];
            var codeRom3 = romData.Data[ROMs.PAC_MAN_CODE_3.FileName];
            var codeRom4 = romData.Data[ROMs.PAC_MAN_CODE_4.FileName];

            if (codeRom1.Length != 4096 || codeRom2.Length != 4096 || codeRom3.Length != 4096 || codeRom4.Length != 4096)
            {
                throw new Exception("All code ROMs must be exactly 4KB in size.");
            }

            // Define our addressable memory space, which includes the game code ROMS and RAM.

            var addressableMemorySize =
                codeRom1.Length     // Code ROM 1
                + codeRom2.Length   // Code ROM 2
                + codeRom3.Length   // Code ROM 3
                + codeRom4.Length   // Code ROM 4
                + 1024              // Video RAM (tile information)
                + 1024              // Video RAM (tile palettes)
                + 2032              // RAM
                + 16;               // Sprite numbers

            _memory = new byte[addressableMemorySize];

            // Map the code ROM into the lower 16K of the memory space.
            Array.Copy(codeRom1, 0, _memory, 0, codeRom1.Length);
            Array.Copy(codeRom2, 0, _memory, codeRom1.Length, codeRom2.Length);
            Array.Copy(codeRom3, 0, _memory, codeRom1.Length + codeRom2.Length, codeRom3.Length);
            Array.Copy(codeRom4, 0, _memory, codeRom1.Length + codeRom2.Length + codeRom3.Length, codeRom4.Length);

            // Load and decrypt the Ms. Pac-Man daughterboard ROMs and apply patches to the base Pac-Man ROMs.
            // The patches will be applied to a seperate memory instance because we still need the original,
            // unmodified Pac-Man code ROMs present to boot and pass the self-test. See PacManPCB::Read().
            if (ROMSet == ROMSet.MsPacMan)
            {
                _auxBoard = new MsPacManAuxBoard();
                _auxBoard.LoadAuxROMs(romData);
            }

            // This class implements the IMemory interface, which the CPU needs to determine how to read and
            // write data. We set the reference to this class instance (whose implementation uses _memory).
            _cpu.Memory = this;

            // Initialize video hardware.
            _video = new VideoHardware(romData, ROMSet);
            _video.Initialize();

            // Initialize audio hardware.
            _audio = new AudioHardware(romData, ROMSet);

            if (state != null)
            {
                LoadState(state);
            }

            _cancelled   = false;
            _thread      = new Thread(new ThreadStart(HardwareLoop));
            _thread.Name = "Pac-Man Hardware";
            _thread.Start();
        }
Ejemplo n.º 4
0
        public static ROMData LoadFromDisk(ROMSet romset, string directoryPath, bool enforceValidChecksum = true)
        {
            var romData = new ROMData();

            List <ROMFile> romFiles = null;

            if (romset == ROMSet.PacMan)
            {
                romFiles = ROMs.PAC_MAN;
            }
            else if (romset == ROMSet.MsPacMan)
            {
                romFiles = ROMs.MS_PAC_MAN;
            }
            else
            {
                throw new ArgumentException($"Unexpected romset: {romset}");
            }

            foreach (var romFile in romFiles)
            {
                var    path          = Path.Combine(directoryPath, romFile.FileName);
                string alternatePath = null;

                if (!String.IsNullOrWhiteSpace(romFile.AlternateFileName))
                {
                    alternatePath = Path.Combine(directoryPath, romFile.AlternateFileName);
                }

                byte[] rom = null;

                // Attempt to load the ROM file data from the primary and secondary file names.

                if (File.Exists(path))
                {
                    rom = File.ReadAllBytes(path);
                }

                if (!String.IsNullOrWhiteSpace(alternatePath) && File.Exists(alternatePath))
                {
                    rom = File.ReadAllBytes(alternatePath);
                }

                var alternateFileNameMessage = romFile.AlternateFileName == null ? "" : $"(or alternate name '{romFile.AlternateFileName}')";

                if (rom == null)
                {
                    throw new Exception($"Could not locate the '{romFile.Description}' ROM file '{romFile.FileName}'{alternateFileNameMessage} with CRC32 of '{romFile.CRC32}' at the location: {path}");
                }

                // The ROM size should always match.

                if (romFile.Size != rom.Length)
                {
                    throw new Exception($"The file size for '{romFile.Description}' ROM file '{romFile.FileName}'{alternateFileNameMessage} at the location: {path} was {rom.Length} bytes, but we are expecting {romFile.Size} bytes.");
                }

                // Perform a quick checksum to determine if we got the correct file.

                var crc32    = new CRC32();
                var checksum = crc32.Get(rom).ToString("X8");

                if (checksum != romFile.CRC32)
                {
                    var message = $"The CRC32 checksum for '{romFile.Description}' ROM file '{romFile.FileName}'{alternateFileNameMessage} at the location: {path} was calculated as '{checksum}', but we are expecting '{romFile.CRC32}'.";

                    if (enforceValidChecksum)
                    {
                        throw new Exception(message);
                    }
                    else
                    {
                        Console.WriteLine($"[WARNING] {message}");
                    }
                }

                // Add the binary ROM data to the set indexed by file name.
                romData.Data[romFile.FileName] = rom;
            }

            return(romData);
        }
        public void LoadAuxROMs(ROMData romData)
        {
            // Decrypt Ms. Pac-Man aux board ROMs U5, U6, U7.

            var u5 = romData.Data[ROMs.MS_PAC_MAN_AUX_U5.FileName];
            var u6 = romData.Data[ROMs.MS_PAC_MAN_AUX_U6.FileName];
            var u7 = romData.Data[ROMs.MS_PAC_MAN_AUX_U7.FileName];

            // Original ROMs: 16K
            // New ROMs: 10K
            AuxROMs = new byte[(16 + 10) * 1024];

            for (var i = 0; i < 0x1000; i++)
            {
                AuxROMs[DecryptAddress1((uint)i) + 0x4000] = (byte)DecryptData(u7[i]);
                AuxROMs[DecryptAddress1((uint)i) + 0x5000] = (byte)DecryptData(u6[i]);
            }

            for (var i = 0; i < 0x0800; i++)
            {
                AuxROMs[DecryptAddress2((uint)i) + 0x6000] = (byte)DecryptData(u5[i]);
            }

            // Copy the original Pac-Man ROM, expect for code ROM 4 (6J) which is replaced
            // completely by aux ROM U7. We'll also be applying patches to these below.

            var codeRom1 = romData.Data[ROMs.PAC_MAN_CODE_1.FileName];
            var codeRom2 = romData.Data[ROMs.PAC_MAN_CODE_2.FileName];
            var codeRom3 = romData.Data[ROMs.PAC_MAN_CODE_3.FileName];

            Array.Copy(codeRom1, 0, AuxROMs, 0x0000, 0x1000);
            Array.Copy(codeRom2, 0, AuxROMs, 0x1000, 0x1000);
            Array.Copy(codeRom3, 0, AuxROMs, 0x2000, 0x1000);
            Array.Copy(AuxROMs, 0x4000, AuxROMs, 0x3000, 0x1000);

            // The U5 ROM contains patches to the original Pac-Man ROMs 1-3 (6E/6F/6H).
            // This list of patch locations was determined by looking at MAME and PIE.
            // https://www.walkofmind.com/programming/pie/pie.htm
            // https://github.com/mamedev/mame/blob/master/src/mame/drivers/pacman.cpp#L7366

            // Technically the aux board has latches that "overlay" reads from these addresses
            // however, it's good enough to just replace the bytes in our case.

            for (var i = 0; i < 8; i++)
            {
                AuxROMs[0x0410 + i] = AuxROMs[0x6008 + i];
                AuxROMs[0x08E0 + i] = AuxROMs[0x61D8 + i];
                AuxROMs[0x0A30 + i] = AuxROMs[0x6118 + i];
                AuxROMs[0x0BD0 + i] = AuxROMs[0x60D8 + i];
                AuxROMs[0x0C20 + i] = AuxROMs[0x6120 + i];
                AuxROMs[0x0E58 + i] = AuxROMs[0x6168 + i];
                AuxROMs[0x0EA8 + i] = AuxROMs[0x6198 + i];

                AuxROMs[0x1000 + i] = AuxROMs[0x6020 + i];
                AuxROMs[0x1008 + i] = AuxROMs[0x6010 + i];
                AuxROMs[0x1288 + i] = AuxROMs[0x6098 + i];
                AuxROMs[0x1348 + i] = AuxROMs[0x6048 + i];
                AuxROMs[0x1688 + i] = AuxROMs[0x6088 + i];
                AuxROMs[0x16B0 + i] = AuxROMs[0x6188 + i];
                AuxROMs[0x16D8 + i] = AuxROMs[0x60C8 + i];
                AuxROMs[0x16F8 + i] = AuxROMs[0x61C8 + i];
                AuxROMs[0x19A8 + i] = AuxROMs[0x60A8 + i];
                AuxROMs[0x19B8 + i] = AuxROMs[0x61A8 + i];

                AuxROMs[0x2060 + i] = AuxROMs[0x6148 + i];
                AuxROMs[0x2108 + i] = AuxROMs[0x6018 + i];
                AuxROMs[0x21A0 + i] = AuxROMs[0x61A0 + i];
                AuxROMs[0x2298 + i] = AuxROMs[0x60A0 + i];
                AuxROMs[0x23E0 + i] = AuxROMs[0x60E8 + i];
                AuxROMs[0x2418 + i] = AuxROMs[0x6000 + i];
                AuxROMs[0x2448 + i] = AuxROMs[0x6058 + i];
                AuxROMs[0x2470 + i] = AuxROMs[0x6140 + i];
                AuxROMs[0x2488 + i] = AuxROMs[0x6080 + i];
                AuxROMs[0x24B0 + i] = AuxROMs[0x6180 + i];
                AuxROMs[0x24D8 + i] = AuxROMs[0x60C0 + i];
                AuxROMs[0x24F8 + i] = AuxROMs[0x61C0 + i];
                AuxROMs[0x2748 + i] = AuxROMs[0x6050 + i];
                AuxROMs[0x2780 + i] = AuxROMs[0x6090 + i];
                AuxROMs[0x27B8 + i] = AuxROMs[0x6190 + i];
                AuxROMs[0x2800 + i] = AuxROMs[0x6028 + i];
                AuxROMs[0x2B20 + i] = AuxROMs[0x6100 + i];
                AuxROMs[0x2B30 + i] = AuxROMs[0x6110 + i];
                AuxROMs[0x2BF0 + i] = AuxROMs[0x61D0 + i];
                AuxROMs[0x2CC0 + i] = AuxROMs[0x60D0 + i];
                AuxROMs[0x2CD8 + i] = AuxROMs[0x60E0 + i];
                AuxROMs[0x2CF0 + i] = AuxROMs[0x61E0 + i];
                AuxROMs[0x2D60 + i] = AuxROMs[0x6160 + i];
            }
        }