Advertisement
jamieyello

NES emu

Jun 4th, 2018
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.27 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. /// <summary>
  5. /// JNES is an emulator designed for educational purposes, designed with simplicity
  6. /// and readibility in mind.
  7. /// </summary>
  8.  
  9. namespace NES
  10. {
  11. class Machine
  12. {
  13. const int BYTES_IN_KILOBYTE = 0x400;
  14. byte EMPTY_BYTE = 0;
  15.  
  16. /// <summary>
  17. /// The loaded ROM
  18. /// </summary>
  19. byte[] prg_rom;
  20. byte[] chr_rom;
  21. byte[] rom_header;
  22. byte[] ram;
  23.  
  24. /// <summary>
  25. /// Pointer array to various memory addresses above. (Except copied, C# is annoying)
  26. /// </summary>
  27. byte[] cpu_mapped_memory;
  28.  
  29. int prg_ram_start;
  30. int prg_ram_end;
  31.  
  32. /// <summary>
  33. /// An array since the ROM can be segmented
  34. /// </summary>
  35. int[] prg_rom_start;
  36. int[] prg_rom_end;
  37.  
  38. int chr_rom_start;
  39. int chr_rom_end;
  40.  
  41. enum NesFileType {
  42. nothing,
  43. nes_2,
  44. ines,
  45. nes_arch
  46. }
  47. NesFileType nesFileType = NesFileType.nothing;
  48.  
  49. bool flag_mirror;
  50. bool flag_cartridge_memory;
  51. bool flag_trainer;
  52. bool flag_ignore_mirroring;
  53. bool flag_vs_unisystem;
  54. bool flag_playchoice_10;
  55. bool flag_pal;
  56.  
  57. byte mapper_type;
  58.  
  59.  
  60. /// <summary>
  61. /// In bytes
  62. /// </summary>
  63. int prg_ram_size;
  64.  
  65.  
  66. /// <summary>
  67. /// The A register (general purpose, 8-bit)
  68. /// </summary>
  69. byte A_reg;
  70.  
  71. /// <summary>
  72. /// The X register (general purpose, 8-bit)
  73. /// </summary>
  74. byte X_reg;
  75.  
  76. /// <summary>
  77. /// The Y register (general purpose, 8-bit)
  78. /// </summary>
  79. byte Y_reg;
  80.  
  81. /// <summary>
  82. /// The P register (status, 8-bit)
  83. /// </summary>
  84. byte P_reg;
  85.  
  86. /// <summary>
  87. /// The SP register (stack pointer, 8-bit)
  88. /// </summary>
  89. byte stack_pointer;
  90.  
  91. /// <summary>
  92. /// The PC register (program counter, 16-bit)
  93. /// </summary>
  94. int program_counter;
  95.  
  96. /// <summary>
  97. /// Set to true to skip a cpu cycle. Used by opcode EA
  98. /// </summary>
  99. bool cpu_nop;
  100.  
  101. List<int> stack= new List<int>();
  102.  
  103. ///<summary>
  104. ///<para>THE STATUS REGISTER http://nesdev.com/6502.txt</para>
  105. ///
  106. ///<br> This register consists of eight "flags" (a flag = something that indi-</br>
  107. ///<br>cates whether something has, or has not occurred). Bits of this register</br>
  108. ///<br>are altered depending on the result of arithmetic and logical operations.</br>
  109. ///<br>These bits are described below:</br>
  110. ///<br></br>
  111. ///<br> Bit No. 7 6 5 4 3 2 1 0</br>
  112. ///<br> S V B D I Z C</br>
  113. /// </summary>
  114.  
  115. // While this should be a byte like the rest of the registers, using a struct with booleans is easier/faster
  116. //to use.
  117. //byte status_flags;
  118.  
  119. struct StatusFlagsRegister {
  120. public bool S_Negative;
  121. public bool V_Overflow;
  122. public bool Break;
  123. public bool Decimal_Mode;
  124. public bool Interrupt;
  125. public bool Zero;
  126. public bool Carry;
  127. } StatusFlagsRegister status_flags;
  128.  
  129. /// <summary>
  130. /// THE ACCUMULATOR http://nesdev.com/6502.txt
  131. ///
  132. /// This is THE most important register in the microprocessor.Various ma-
  133. /// chine language instructions allow you to copy the contents of a memory
  134. /// location into the accumulator, copy the contents of the accumulator into
  135. /// a memory location, modify the contents of the accumulator or some other
  136. /// register directly, without affecting any memory. And the accumulator is
  137. /// the only register that has instructions for performing math.
  138. ///
  139. /// </summary>
  140. byte accumulator;
  141.  
  142. public Machine()
  143. {
  144. }
  145.  
  146. ~Machine()
  147. {
  148.  
  149. }
  150.  
  151. public int Initiate(String file)
  152. {
  153. try
  154. {
  155. LoadRom(file);
  156. ram = new byte[BYTES_IN_KILOBYTE * 2];
  157. cpu_mapped_memory = new byte[0xFFFF];
  158. SetMapper();
  159. }
  160. catch (Exception e)
  161. {
  162. Console.WriteLine("Fatal error in initialization: " + e.ToString());
  163. return -1;
  164. }
  165.  
  166. return prg_rom.Length;
  167. }
  168.  
  169. private void LoadRom(String file)
  170. {
  171. // http://wiki.nesdev.com/w/index.php/INES
  172.  
  173. Console.WriteLine("Loading " + file);
  174. byte[] nesFile = System.IO.File.ReadAllBytes(file);
  175.  
  176. int file_pos = 0;
  177.  
  178. // Load header:
  179. rom_header = new byte[16];
  180. Buffer.BlockCopy(nesFile, file_pos, rom_header, 0, 16);
  181. file_pos += 16;
  182.  
  183. Console.WriteLine("Rom header: ");
  184. for (int i = 0; i < 16; i++)
  185. {
  186. Console.WriteLine(i.ToString() + " " + rom_header[i].ToString("X"));
  187. }
  188.  
  189. if ((rom_header[7] == 8) && (rom_header[12] == 8)) {
  190. Console.WriteLine("Nes 2.0 detected");
  191. nesFileType = NesFileType.nes_2;
  192. }
  193.  
  194. if ((rom_header[7] == 0) && (rom_header[12] == 0))
  195. {
  196. Console.WriteLine("iNES detected");
  197. nesFileType = NesFileType.ines;
  198. }
  199.  
  200. if (nesFileType == NesFileType.nothing) {
  201. Console.WriteLine("Archaic ines detected");
  202. nesFileType = NesFileType.nes_arch;
  203. }
  204.  
  205. // Parse header:
  206. // 0-3: Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file)
  207. // 4: Size of PRG ROM in 16 KB units
  208. int prg_size = rom_header[4] * BYTES_IN_KILOBYTE * 16;
  209.  
  210. // 5: Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
  211. int chr_size = rom_header[5] * BYTES_IN_KILOBYTE * 8;
  212.  
  213. // 6: flags
  214. {
  215. // 0 - 0: horizontal (vertical arrangement) (CIRAM A10 = PPU A11) 1: vertical (horizontal arrangement) (CIRAM A10 = PPU A10)
  216. flag_mirror = BinaryTools.GetBit(rom_header[6], 0);
  217.  
  218. // 1 - 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
  219. flag_cartridge_memory = BinaryTools.GetBit(rom_header[6], 1);
  220.  
  221. // 2 - 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
  222. flag_trainer = BinaryTools.GetBit(rom_header[6], 2);
  223.  
  224. // 3 - 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
  225. flag_ignore_mirroring = BinaryTools.GetBit(rom_header[6], 3);
  226.  
  227. // 4-7 - Lower nybble of mapper number
  228. for (byte i = 0; i < 4 ; i++)
  229. {
  230. mapper_type += Convert.ToByte(
  231. Convert.ToByte(BinaryTools.GetBit(rom_header[6], 4 + i)) * (2 ^ i)
  232. );
  233. }
  234. }
  235.  
  236. // 7: flags
  237. {
  238. // 0 - VS Unisystem
  239. flag_vs_unisystem = BinaryTools.GetBit(rom_header[7], 0);
  240.  
  241. // 1 - PlayChoice-10 (8KB of Hint Screen data stored after CHR data)
  242. flag_playchoice_10 = BinaryTools.GetBit(rom_header[7], 1);
  243.  
  244. // 2-3 - If equal to 2, flags 8-15 are in NES 2.0 format
  245. // (instructions unclear, ignoring)
  246.  
  247. // 4-7 - Upper nybble of mapper number
  248. for (byte i = 0; i < 4; i++)
  249. {
  250. mapper_type += Convert.ToByte(
  251. Convert.ToByte(BinaryTools.GetBit(rom_header[7], 4 + i)) * (2 ^ (i + 4))
  252. );
  253. }
  254.  
  255. Console.WriteLine("Mapper number calculated to be " + mapper_type.ToString());
  256. }
  257.  
  258. // 8: Size of PRG RAM in 8 KB units (Value 0 infers 8 KB for compatibility; see PRG RAM circuit)
  259. prg_ram_size = rom_header[8] * BYTES_IN_KILOBYTE * 8;
  260. if (prg_ram_size == 0) { prg_ram_size = BYTES_IN_KILOBYTE * 8; }
  261.  
  262. // 9: flags
  263. {
  264. // 0 - TV system (0: NTSC; 1: PAL)
  265. flag_pal = BinaryTools.GetBit(rom_header[9], 0);
  266.  
  267. // 0-7 Reserved, set to zero
  268. }
  269.  
  270. // 10: flags (unofficial, skipped)
  271.  
  272. // 11-15: Zero filled
  273.  
  274. // Load trainer (skipped)
  275. if (flag_trainer) {
  276. Console.WriteLine("Warning: Trainer support not implemented.");
  277. file_pos += 512;
  278. }
  279.  
  280. // Load PRG ROM
  281. prg_rom = new byte[prg_size];
  282. Buffer.BlockCopy(nesFile, file_pos, prg_rom, 0, prg_size);
  283. file_pos += prg_size;
  284.  
  285. Console.WriteLine(".nes file size = " + nesFile.Length.ToString());
  286. Console.WriteLine("Program size = " + prg_rom.Length.ToString());
  287.  
  288. // Load CHR ROM
  289. chr_rom = new byte[chr_size];
  290. Buffer.BlockCopy(nesFile, file_pos, chr_rom, 0, chr_size);
  291. file_pos += chr_size;
  292. Console.WriteLine("CHR size = " + chr_rom.Length.ToString());
  293.  
  294. // Playchoice INST-ROM, if present (0 or 8192 bytes)
  295.  
  296. // PlayChoice PROM, if present(16 bytes Data, 16 bytes CounterOut)(this is often missing, see PC10 ROM - Images for details)
  297. }
  298.  
  299. private void SetMapper()
  300. {
  301. // https://wiki.nesdev.com/w/index.php/Mapper
  302.  
  303. // Set to default
  304. prg_rom_start = new int[0];
  305. prg_rom_end = new int[0];
  306. prg_ram_start = 0x0000;
  307. prg_ram_end = 0x0000;
  308. chr_rom_start = 0x0000;
  309. chr_rom_end = 0x0000;
  310.  
  311. switch (mapper_type) {
  312. case 0:
  313. prg_rom_start = new int[2];
  314. prg_rom_end = new int[2];
  315.  
  316. prg_ram_start = 0x6000;
  317. prg_ram_end = 0x7FFF;
  318. prg_rom_start[0] = 0x8000;
  319. prg_rom_end[0] = 0xBFFF;
  320. prg_rom_start[1] = 0xC000;
  321. prg_rom_end[1] = 0xFFFF;
  322. chr_rom_start = 0x0000;
  323. chr_rom_end = 0x1FFF;
  324. break;
  325. case 5: // MMC5
  326. // Get PRG mode
  327. switch (prg_rom[0x5100]) {
  328. case 0:
  329. prg_rom_start = new int[1];
  330. prg_rom_end = new int[1];
  331.  
  332. prg_rom_start[0] = 0x8000;
  333. prg_rom_end[0] = 0xFFFF;
  334. break;
  335. default:
  336. throw new Exception("Invalid ROM: (Error while mapping MMC5, unimplemented mode)");
  337. }
  338.  
  339. //throw new Exception("Unsupported mapper 5.");
  340. break;
  341. default:
  342. throw new Exception("Unsupported mapper.");
  343. }
  344.  
  345. // Map the PRG ROM
  346. int pr = 0;
  347. for (int rom_map = 0; rom_map < prg_rom_start.Length; rom_map++)
  348. {
  349. for (int i = prg_rom_start[rom_map]; i < prg_rom_end[rom_map]; i++)
  350. {
  351. cpu_mapped_memory[i] = prg_rom[pr++];
  352. }
  353.  
  354. Console.WriteLine("mapped rom addresses = " + pr);
  355. if (pr >= prg_rom.Length) { break; }
  356. }
  357.  
  358. // Map the CHR ROM
  359. int cr = 0;
  360. for (int i = chr_rom_start; i < chr_rom_end; i++)
  361. {
  362. cpu_mapped_memory[i] = chr_rom[cr];
  363. cr++;
  364. }
  365.  
  366. program_counter = prg_rom_start[0];
  367. }
  368.  
  369. /// <summary>
  370. /// Meant to be called once per frame (every 1/60th second)
  371. /// </summary>
  372. /// <param name="input"></param>
  373. public void Step(InputFrame input)
  374. {
  375.  
  376. CPUTick();
  377. // Master Clock: 21,477,272 ticks per second, 357954.533333 per frame
  378. for (int i = 0; i < 357955; i++)
  379. {
  380. // CPU, master clock divided by 12
  381. if ((i % 12) == 0)
  382. {
  383. //CPUTick();
  384. }
  385. }
  386. }
  387.  
  388. private void CPUTick()
  389. {
  390. if (cpu_nop) { Console.WriteLine("Skipped CPU cycle."); cpu_nop = false; return; }
  391.  
  392. Console.WriteLine("");
  393.  
  394. // Hack, skip opcode 0s
  395. int skipped_0_opcode = 0;
  396. while (cpu_mapped_memory[program_counter] == 0) { program_counter++; skipped_0_opcode++; }
  397. if (skipped_0_opcode > 0) { Console.WriteLine("Skipped " + skipped_0_opcode + " BRK opcode(s) (00)"); }
  398.  
  399. byte code = cpu_mapped_memory[program_counter++];
  400. Console.WriteLine("Executing code " + String.Format("{0:X}", code) + " at address " + String.Format("{0:X}", program_counter - 1));
  401.  
  402. // http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php/6502_Opcodes
  403. // http://www.6502.org/tutorials/6502opcodes.html
  404. // http://www.obelisk.me.uk/6502/reference.html
  405. switch (code) {
  406. case 0x10: // BPL (Branch on PLus)
  407. if (!status_flags.S_Negative) { program_counter += cpu_mapped_memory[program_counter]; }
  408. else { program_counter++; }
  409. break;
  410.  
  411. case 0x20: // JSR, jump to new location, save return address on stack. Fixed three bytes.
  412. int return_address = program_counter + 2;
  413. stack.Add(return_address);
  414. program_counter = cpu_mapped_memory[program_counter++] + cpu_mapped_memory[program_counter++] * 0x100;
  415. Console.WriteLine("JSR jumping to " + String.Format("{0:X}", program_counter));
  416. break; // Working
  417.  
  418. case 0x2C: // BIT (Absolute), fixed 3 bytes
  419. int address = cpu_mapped_memory[program_counter++] + cpu_mapped_memory[program_counter++] * 0x100;
  420. byte value = cpu_mapped_memory[address];
  421. status_flags.S_Negative = value.GetBit(7);
  422. status_flags.V_Overflow = value.GetBit(6);
  423. if ((accumulator & value) == 0) { status_flags.Zero = true; } else { status_flags.Zero = false; }
  424. Console.WriteLine("BIT Absolute");
  425. break; // Working
  426.  
  427. case 0xEA: // NOP, does nothing for 2 frames. One byte.
  428. cpu_nop = true;
  429. break;
  430.  
  431. default:
  432. Console.WriteLine("Unsupported opcode: " + String.Format("{0:X}", code));
  433. break;
  434. }
  435. }
  436.  
  437. }
  438. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement