using System; using System.IO; using VirtualNes.Core.Debug; namespace VirtualNes.Core { public class ROM { protected NESHEADER header; protected NSFHEADER nsfheader; protected string path; protected string name; protected string fullpath; protected bool bPAL; protected bool bNSF; protected int NSF_PAGE_SIZE; protected byte[] lpPRG; protected byte[] lpCHR; protected byte[] lpTrainer; protected byte[] lpDiskBios; protected byte[] lpDisk; protected uint crc; protected uint crcall; protected uint crcvrom; protected int mapper; protected int diskno; protected uint fdsmakerID; protected uint fdsgameID; public ROM(string fname) { Stream fp = null; byte[] temp = null; byte[] bios = null; long FileSize = 0; header = NESHEADER.GetDefault(); path = string.Empty; name = string.Empty; bPAL = false; bNSF = false; NSF_PAGE_SIZE = 0; lpPRG = lpCHR = lpTrainer = lpDiskBios = lpDisk = null; crc = crcall = 0; mapper = 0; diskno = 0; try { fp = Supporter.S.OpenRom(fname); if (fp == null) { throw new System.Exception($"Open Rom Failed:[{fname}]"); } FileSize = fp.Length; if (FileSize < 17) { throw new System.Exception($"File too small:[{fname}]"); } temp = new byte[FileSize]; fp.Read(temp, 0, temp.Length); fp.Dispose(); header = NESHEADER.Read(temp); if (!header.CheckValid()) throw new Exception($"rom file is not valid:[{fname}]"); ulong PRGoffset, CHRoffset; long PRGsize = 0, CHRsize = 0; var romType = header.GetRomType(); if (romType == EnumRomType.NES) { PRGsize = (long)header.PRG_PAGE_SIZE * 0x4000; CHRsize = (long)header.CHR_PAGE_SIZE * 0x2000; PRGoffset = (ulong)NESHEADER.SizeOf(); CHRoffset = PRGoffset + (ulong)PRGsize; if (IsTRAINER()) { PRGoffset += 512; CHRoffset += 512; } if (PRGsize <= 0 || (PRGsize + CHRsize) > FileSize) { throw new Exception($"Invalid NesHeader:[{fname}]"); } //PRG BANK lpPRG = new byte[PRGsize]; Array.Copy(temp, (int)PRGoffset, lpPRG, 0, PRGsize); //CHR BANK if (CHRsize > 0) { lpCHR = new byte[CHRsize]; if (FileSize >= (long)CHRoffset + CHRsize) { Array.Copy(temp, (int)CHRoffset, lpCHR, 0, CHRsize); } else { //CHR Bank太少... CHRsize -= ((long)CHRoffset + CHRsize - FileSize); Array.Copy(temp, (int)CHRoffset, lpCHR, 0, CHRsize); } } else { lpCHR = null; } if (IsTRAINER()) { lpTrainer = new byte[512]; Array.Copy(temp, NESHEADER.SizeOf(), lpTrainer, 0, 512); } else { lpTrainer = null; } } else if (romType == EnumRomType.FDS) { diskno = header.PRG_PAGE_SIZE; if (FileSize < (16 + 65500 * diskno)) { throw new Exception($"Illegal Disk Size:[{fname}]"); } if (diskno > 8) { throw new Exception($"Unsupport disk:[{fname}]"); } header = NESHEADER.GetDefault(); header.ID[0] = (byte)'N'; header.ID[1] = (byte)'E'; header.ID[2] = (byte)'S'; header.ID[3] = 0x1A; header.PRG_PAGE_SIZE = (byte)(diskno * 4); header.CHR_PAGE_SIZE = 0; header.control1 = 0x40; header.control2 = 0x10; PRGsize = NESHEADER.SizeOf() + 65500 * diskno; //PRG BANK lpPRG = new byte[PRGsize]; lpDisk = new byte[PRGsize]; lpCHR = null; var headerBuffer = header.DataToBytes(); Array.Copy(headerBuffer, lpPRG, headerBuffer.Length); Array.Copy(temp, NESHEADER.SizeOf(), lpPRG, NESHEADER.SizeOf(), 65500 * diskno); lpPRG[0] = (byte)'F'; lpPRG[1] = (byte)'D'; lpPRG[2] = (byte)'S'; lpPRG[3] = 0x1A; lpPRG[4] = (byte)diskno; fp = Supporter.S.OpenFile_DISKSYS(); if (fp == null) { throw new Exception($"Not found DISKSYS.ROM for [{fname}]"); } FileSize = fp.Length; if (FileSize < 17) { throw new Exception($"Small File Of DISKSYS.ROM"); } bios = new byte[FileSize]; fp.Read(bios, 0, (int)FileSize); fp.Dispose(); lpDiskBios = new byte[8 * 1024]; if (bios[0] == 'N' && bios[1] == 'E' && bios[2] == 'S' && bios[3] == 0x1A) { Array.Copy(bios, 0x6010, lpDiskBios, 0, lpDiskBios.Length); } else { Array.Copy(bios, lpDiskBios, lpDiskBios.Length); } bios = null; } else if (romType == EnumRomType.NSF) { bNSF = true; header = NESHEADER.GetDefault(); nsfheader = NSFHEADER.GetDefault(); PRGsize = FileSize - NSFHEADER.SizeOf(); Debuger.Log($"PRGSIZE:{PRGsize}"); PRGsize = (PRGsize + 0x0FFF) & ~0x0FFF; Debuger.Log($"PRGSIZE:{PRGsize}"); lpPRG = new byte[PRGsize]; Array.Copy(temp, NSFHEADER.SizeOf(), lpPRG, 0, FileSize - NSFHEADER.SizeOf()); NSF_PAGE_SIZE = (int)(PRGsize >> 12); Debuger.Log($"PAGESIZE:{NSF_PAGE_SIZE}"); } else { throw new Exception($"Unsupport format:[{fname}]"); } Supporter.S.GetRomPathInfo(fname, out fullpath, out path); name = Path.GetFileNameWithoutExtension(fullpath); if (!bNSF) { mapper = (header.control1 >> 4) | (header.control2 & 0xF0); crc = crcall = crcvrom = 0; if (mapper != 20) { Span sTemp = temp; if (IsTRAINER()) { crcall = CRC.CrcRev((int)(512 + PRGsize + CHRsize), sTemp.Slice(NESHEADER.SizeOf())); crc = CRC.CrcRev((int)(512 + PRGsize), sTemp); if (CHRsize > 0) crcvrom = CRC.CrcRev((int)CHRsize, sTemp.Slice((int)(PRGsize + 512 + NESHEADER.SizeOf()))); } else { crcall = CRC.CrcRev((int)(PRGsize + CHRsize), sTemp.Slice(NESHEADER.SizeOf())); crc = CRC.CrcRev((int)(PRGsize), sTemp.Slice(NESHEADER.SizeOf())); if (CHRsize > 0) crcvrom = CRC.CrcRev((int)CHRsize, sTemp.Slice((int)(PRGsize + NESHEADER.SizeOf()))); } FileNameCheck(fname); if (Supporter.S.TryGetMapperNo(this, out int mapperNo)) { Debuger.Log($"ROMDB Set Mapper #{mapper:000} to #{mapperNo:000}"); mapper = mapperNo; } RomPatch.DoPatch(ref crc, ref lpPRG, ref lpCHR, ref mapper, ref header); fdsmakerID = fdsgameID = 0; } else //mapper==20 { crc = crcall = crcvrom = 0; fdsmakerID = lpPRG[0x1F]; fdsgameID = (uint)((lpPRG[0x20] << 24) | (lpPRG[0x21] << 16) | (lpPRG[0x22] << 8) | (lpPRG[0x23] << 0)); } } else //NSF { mapper = 0x0100; // Private mapper crc = crcall = crcvrom = 0; fdsmakerID = fdsgameID = 0; } temp = null; } catch (Exception ex) { fp?.Dispose(); temp = null; bios = null; lpPRG = null; lpCHR = null; lpTrainer = null; lpDiskBios = null; lpDisk = null; throw ex; } } public void Dispose() { lpPRG = null; lpCHR = null; lpTrainer = null; lpDiskBios = null; lpDisk = null; } public bool IsTRAINER() { return (header.control1 & (byte)EnumRomControlByte1.ROM_TRAINER) > 0; } public bool IsNSF() { return bNSF; } public bool IsPAL() { return bPAL; } public bool IsSAVERAM() { return (header.control1 & (byte)EnumRomControlByte1.ROM_SAVERAM) > 0; } protected void FileNameCheck(string fname) { if (fname.Contains("(E)")) { bPAL = true; return; } } internal string GetRomName() { return name; } internal int GetMapperNo() { return mapper; } internal byte[] GetPROM() { return lpPRG; } internal byte[] GetVROM() { return lpCHR; } internal byte[] GetDISK() { return lpDisk; } internal int GetDiskNo() { return diskno; } internal void SetDiskNo(int v) { diskno = v; } internal uint GetGameID() { return fdsgameID; } internal void SetGameID(uint id) { fdsgameID = id; } internal uint GetMakerID() { return fdsmakerID; } internal void SetMakerID(uint id) { fdsmakerID = id; } internal bool IsVSUNISYSTEM() { return (header.control2 & (byte)EnumRomControlByte2.ROM_VSUNISYSTEM) != 0; } public uint GetPROM_CRC() { return crc; } public void SetPROM_CRC(uint v) { crc = v; } internal byte GetPROM_SIZE() { return header.PRG_PAGE_SIZE; } internal byte GetVROM_SIZE() { return header.CHR_PAGE_SIZE; } internal bool Is4SCREEN() { return (header.control1 & (byte)EnumRomControlByte1.ROM_4SCREEN) != 0; } internal bool IsVMIRROR() { return (header.control1 & (byte)EnumRomControlByte1.ROM_VMIRROR) != 0; } internal byte[] GetTRAINER() { return lpTrainer; } internal NSFHEADER GetNsfHeader() { return nsfheader; } internal string GetRomPath() { return path; } internal uint GetVROM_CRC() { return crcvrom; } } }