} = 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 }); }
/** * 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(); }
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]; } }