daily pastebin goal
71%
SHARE
TWEET

NES emu

jamieyello Jun 4th, 2018 8 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top