[
  {
    "path": ".gitignore",
    "content": ".vscode/\nobj/\nbin/\nbulid/\npublish/\n\nextra/\ntest/v1/\n\n*.bin\n*.nes"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Bot Randomness\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NET-NES.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>net6.0</TargetFramework>\n    <RootNamespace>NET_NES</RootNamespace>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <!-- Enabling unsafe code -->\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Raylib-cs\" Version=\"6.1.1\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n    <PackageReference Include=\"ImGui.NET\" Version=\"1.90.9.1\" />\n    <PackageReference Include=\"rlImgui-cs\" Version=\"2.1.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Include=\"res\\**\\**\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </Content>\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "README.md",
    "content": "<!-- PROJECT LOGO -->\n<h1 align=\"center\">\n  <br>\n  <a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Logo.png\" alt=\"NET-NESLogo\" width=\"400\"></a>\n  <br>\n  <b>NET-NES</b>\n  <br>\n  <sub><sup><b>NET-NES, a NES emulator, written in C#.</b></sup></sub>\n  <br>\n\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://github.com/BotRandomness/NET-NES\">\n      <img src=\"git-res/Game.png\" alt=\"ProjectImage\" width=\"85%\" height=\"85%\">\n  </a>\n</p>\n\n<p align=\"center\">\n<strong>NET-NES is a NES emulator, capable of running some of the best games for the NES.</strong> <em>To start off</em>, after making my Gameboy emulator, <a href=\"https://github.com/BotRandomness/CODE-DMG\">CODE-DMG</a>, I wanted to create the next step up. As I thought to myself, <strong>\"It only made sense to create a NES emulator!\"</strong> It felt such a natural pairing making a Gameboy emulator, and now NET-NES, a NES emulator. The NES to me is a very fascinating console. The NES not only made a impact in gaming history, but also in electronics, which captivated me. Not to mention how the NES is home to some of the best and well known games! So why not take on the <strong>challenge</strong> and emulate it, it would be perfect! Now, <em>some quick history and background information.</em> The NES, <em>(Nintendo Entertainment System)</em>, launched first in Japan as the <strong>FamiCom</strong> <em>(Family Computer)</em> in <strong>1983</strong>, later coming into <strong>North America in 1985</strong>. At this point in North America, the <em>Video Game Crash of 1983</em> was hitting North America hard, with home console gaming market being in shambles. However, <strong>Nintendo</strong> in 1985 decided to launch their console in North America, change the name to NES, advertise it as a \"toy\", and <strong>boom!</strong> That was able to <strong>bring life back</strong> into the gaming industry in North America! Pretty cool history if I say so myself. Now into the hardware itself, the NES houses a <strong>8-bit CPU</strong>, a <strong>Ricoh 2A03</strong>, which runs at around <strong>1.79 Mhz</strong>. This CPU is pretty much a clone of the <strong>MOS 6502</strong>, but with the <strong>decimal mode removed, and a APU added</strong>. The <strong>PPU</strong>, the <strong>Ricoh 2C02</strong>, runs around <strong>3 times faster then the CPU</strong>, and is reponsible for display graphics on a frame of <strong>256x240</strong> with palletes being able to pick from <strong>64 colours</strong>. When it comes to <strong>memory</strong>, there is <strong>2 KB of RAM</strong>, and <strong>2 KB of VRAM</strong> with a <strong>16-bit address bus</strong>. NES cartridges can also contain a <strong>mapper chip</strong> to support larger ROMs, larger graphics data, and more RAM. Coming back to this project, this was really fun to work on! Now you may have a few questions like, <em>\"Ok, so why the name NET-NES?\"</em> Well I made it using C# and Dotnet, so I thought it's <strong>pretty unique and fun to say!</strong> Now you may ask, <em>\"Cool, but why C# and Raylib again? Why not C/C++, or even Java?\"</em> <strong>C# has been was wonderful to use</strong> and something I keep coming back to, and the <strong>community is great</strong>. As for Raylib, <strong>I just keep finding it fun! :)</strong>\n</p>\n\n</div>\n\n<!-- ABOUT THE PROJECT -->\n\n## Getting Started\nWant to use it, and mess around? Here's how you can get started! </br>\n\n### Download\n1. Download it from [here](https://github.com/BotRandomness/NET-NES/releases), or on the releases page. (win-x64, win-x86, osx-x64, osx-arm64, linux-x64)\n2. Unzip the folder\n3. Launch the executable\n   - On MacOS, there might be a pop up saying \"Apple could not verify\", this is normal. Simply right click on the app, then open, then open again. You also go to System Settings, then security, then allow. You only need to do this once during the first time. \n   - You may also need to enable execute permission on \"Unix-like Oses\"\n   - If you want to use the <strong>terminal</strong>: Point your terminal to the application and run, Windows: `NET-NES --nes <string:rom>`, Unix-like Oses `./NET-NES --nes <string:rom>`\n4. You are ready to go!\n\n### Controls\n- (A) = X\n- (B) = Z\n- [START] = [ENTER]\n- [SELECT] = [RSHIFT]\n- D-Pad = ArrowKeys\n\n<em>Note: Pressing [SPACE] Bar toggles the top menu bar.</em>\n### Usage\nSimply launching the executable will show the basic GUI. Going `File -> Open ROM` will bring up a file launcher to select your ROM. `Help -> Manual` will show all the basic instructions on how to use, and what to know. More information in the Compatibility section.\n#### Flags\nNET-NES flags when using terminal. Note: these flags can be passed in any order, and in any combination.\n- `--nes <string:path>`: Starts up the emulator given a rom file\n- `--json <string>`: Runs a CPU test for a instruction given a JSON file in test/v1\n- `-s <int>`, `--scale <int>`: Scale window size by factor (2 is default)\n- `-f`, `--fps`: Enables FPS counter (off is default)\n- `-rl`, `--raylib-log`: Enables Raylib logs (off is default)\n- `-d`, `--debug`: Enables debug mode (off is default)\n- `-a`, `--about`: Shows about\n- `-v`, `--version`: Shows version number\n- `-h`, `--help`: Shows help screen\n\n## Screenshots\n<a href=\"https://github.com/BotRandomness/NET-NES\">\n    <img src=\"git-res/List.png\" alt=\"GameShowcase\" width=\"4120%\" height=\"700%\">\n</a>\n<p align=\"center\"> Showcase of running games </p>\n\n### Demo Gameplay\n<table>\n    <tr>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/DD.gif\" alt=\"DonkeyKong\" width=\"340%\" height=\"342%\"></a></td>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/SMB.gif\" alt=\"SMB\" width=\"340%\" height=\"342%\"></a></td>\n    </tr>\n  <tr>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Zelda.gif\" alt=\"Zelda\" width=\"340%\" height=\"342%\"></a></td>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Met.gif\" alt=\"Metroid\" width=\"340%\" height=\"342%\"></a></td>\n    </tr>\n  <tr>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Kriby.gif\" alt=\"Kriby\" width=\"340%\" height=\"342%\"></a></td>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Duck.gif\" alt=\"DuckTale\" width=\"340%\" height=\"342%\"></a></td>\n    </tr>\n  <tr>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/Contra.gif\" alt=\"Boot\" width=\"340%\" height=\"342%\"></a></td>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/MM.gif\" alt=\"Tetris\" width=\"340%\" height=\"342%\"></a></td>\n    </tr>\n  <tr>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/SMB3.gif\" alt=\"SMB3\" width=\"340%\" height=\"342%\"></a></td>\n        <td><a href=\"https://github.com/BotRandomness/NET-NES\"><img src=\"git-res/NG2.gif\" alt=\"Tetris\" width=\"340%\" height=\"342%\"></a></td>\n    </tr>\n</table>\n\n## Compatibility\n<p align=\"center\">\n  <a href=\"https://github.com/BotRandomness/NET-NES\">\n      <img src=\"git-res/NesTest.png\" alt=\"nestest\" width=\"50%\" height=\"50%\">\n  </a>\n</p>\n<p align=\"center\">Passes nestest CPU instruction tests</p>\nMost NES cartridges came with a mapper chip to support larger size of ROM and graphics data, as well as bulit in RAM on the cartridges. Mapper 0 (NROM) is most simple, and should work. Mapper 1, Mapper 2, and Mapper 4 all have been implemented and they work, but they should be consider experimental.</em>\n\n</br>\n\nMost games I tried out works. Some games may have some graphical glitches, or might just freeze. This is because even though I have emulated the base NES system, the emulation itself is not fully accurate. This because the PPU emulation I did runs by scanline timing, and not by dot by dot, meaning there is some timing inaccuracy that can effect a few games. More infomation in the Program Architecture section. However, most games should run fine, here is list of games I tested:\n```\nBalloon Fight - Works\nBubble Bobble - Works\nCastlevania - Works\nCastlevania II - Works\nContra - Works\nDonkey Kong - Works\nDr. Mario - Works\nDuckTales - Works\nExecitebike - Works\nFinal Fantasy - Works\nFinal Fantasy II - Freeze at first Battle (Due to inaccurate Sprite0 Hit timming)\nFinal Fantasy III - Works\nGalaga - Works\nIce Climber - Works\nKirby's Adventure - Works\nThe Legend of Zelda - Works\nMario Bros. - Works\nMega Man - Works\nMega Man 2 - Works\nMega Man 6 - Works\nMetroid - Works\nNinja Gaiden - Freeze at Act 1 Screen (Due to inaccurate Sprite0 Hit timming), However you can get it to work if in debug mode you disable \"Sprite0 Hit Check\"\nNinja Gaiden II - Works\nNinja Gaiden III - Works\nPac-Man - Works\nSuper Mario Bros. - Works\nSuper Mario Bros. 2 - Works\nSuper Mario Bros. 3 - Works\nTeenage Mutant Ninja Turtles Tournament Fighters - Works\nTetris - Works\nTetris 2 - Works\n```\n\n## Compile\nWant to tinker around, modify, make your own, learn a bit about emulation development, or contribute? Here's how you can get started with the code and compile.\n\nTo get started, you need to have dontnet install. For reference, I used dotnet 6.0\n\n1. Download dotnet: https://dotnet.microsoft.com/en-us/\n2. Clone this repository, and point your terminal to the root directory of the repository\n3. Run `dotnet run` to compile, and it should run right after! You also do `dotnet run -- --nes <string:rom>` if want to load up a rom at startup!\n\nRaylib-cs (the C# binding (made by Chris Dill) for Raylib), does not need to be installed, as dotnet will automatically install any dependences from NuGet. For more information on raylib-cs can be found here on Github: https://github.com/chrisdill/raylib-cs. This also applies to [Rlimgui-cs](https://github.com/raylib-extras/rlImGui-cs) and [ImGui.NET](https://github.com/ImGuiNET/ImGui.NET).\n\n### Build\n- For your own platform, framework dependent: `dotnet publish`\n- For other platform, single file, not framework dependent:</br> `dotnet publish -r <RID> --self-contained -o bulid/<RID-Name> /p:PublishSingleFile=true`\n- For other platform, single file, framework dependent:</br> `dotnet publish -r <RID> --no-self-contained -o bulid/<RID-Name> /p:PublishSingleFile=true`\n</br>\nThe reason to have both dotnet dependent or not is the file size. If the user already has dotnet, the lighter file size is the best option. If the user does not have dotnet, it's more convenient to bundle in the dotnet as self contained even if the file size is larger. It's best to put PublishSingleFile for convenience, especially for self contained dotnet as that will have 224 dll files all in the root of the executable.\n</br></br>\nFor more see the dotnet publish documentation: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish, RID: https://learn.microsoft.com/en-us/dotnet/core/rid-catalog, SingleFile: https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/design.md </br> </br>\n\nFor <strong>MacOS</strong>, building requires signing, <em>especially for Apple Silicon</em>. This part is going to be a general note. Dotnet and C# when running and building will automatically sign the binaries with a simple \"ad-hoc\" (meaning needed or for this) signature. This needs to be done for using and distributing. On other platforms, compiling for Macs would not be signed, since Apple's `codesign` tool is only on Macs <em>(though others have made open source versions of the tool for cross-platform use)</em>. If you have a unsigned binary compiled on a non-Mac platform now on a Mac platform, a simple \"ad-hoc\" signing will do and can done by `codesign -s - <BinaryPath>`. When it comes `.app` bundles, signing is also required in the same way. You can bulid the `.app` bundle manually since it's just a directory (Mac build of NET-NES for reference). However, even if the `.app` bundle is made up with already signed binary, you will have to re-sign the <strong>whole</strong> `.app` bundle. This can be done with `codesign --force --deep -s - <AppPath.app>`. \n\n### Program Architecture\nHere's a little information on the program layout! \n\nIt can seem a bit complex at first, but it's quite simple. I like to write my code, where it should be easy enough for anyone to understand, no matter their knowledge with emulation, or code! With that in mind, this portion itself is written to be simple, no matter your skill level, so anybody should get the idea of the program works, it's sort of the reason why I write these parts! :)\n\nC# object oriented design allows us to think all the componets of the NES hardware, and make them into classes, which can be used to represent a object. The root of the `src` contains all the main code. With that, we have the following:\n- `CPU.cs`: The 8-Bit NES CPU (Ricoh 2A03)\n- `PPU.cs`: The PPU (Picture Processing Unit) of the NES is responsible for drawing the graphics on screen\n- `Bus.cs`: Contains the `Read()` and `Write()` functions, directing bus for RAM, VRAM, and the cartridge, this also there we can \"wire up\" some our componets here that rely on memory access\n- `Cartridge.cs`: Contains memory for PRG and CHR, set's up the cartidge state depending on the iNES header\n- `src/mapper`: Contains the different memory bank controllers needed, all to make the NES support larger ROMs\n  - `Mapper0.cs`: NROM\n  - `Mapper1.cs`: MMC1\n  - `Mapper2.cs`: UxROM\n  - `Mapper4.cs`: MMC3\n- `Input.cs`: Handles input for the NES\n- `NES.cs`: Where we \"wire up\" all the componets\n- `Test.cs`: Used for JSON test for the CPU\n- `TestBus.cs`: A simplified bus for simple memory access for testing\n- `TestRunner.cs`: Runs the JSON test from `Test.cs`\n- `GUI.cs`: A simple GUI to wrap the NES core\n- `Helper.cs`: Static class to hold global values\n- `Program.cs`: The entry point of the emulator\n\n<em>This emulator is <strong>not meant to be cycle accurate</strong>, as the PPU does not run dot by dot, but by scanline.</em>\n\nEach component should stick to it's task. The core of this emulator is written in plain C#, with the only outside library being raylib-cs which is used very minimally. It would be something I may do where I refactor the code to make the core more independent, which should be easy enough to do.\n\nTo see how the main loop works, we can look into the `NES.cs` in the `Run()` function: \n```cs\npublic void Run() {\n  int cycles = 0;\n\n  bus.input.UpdateController();\n\n  while (cycles < 29828) {\n    int used = bus.cpu.ExecuteInstruction();\n    cycles += used;\n    bus.ppu.Step(used * 3);\n  }\n\n  bus.ppu.DrawFrame(Helper.scale);\n}\n```\nThe loop is quite simple, and uses a simple approach of having the \"CPU drive\". This is done my the CPU's `ExecuteInstruction()` function, where after every instruction, it return it's cycle count. We can pass this count into the PPU's `Step()` function as it's important to keep the CPU and PPU in sync. We do times by three here because the PPU runs 3x faster then the CPU. The PPU's `Step()` function using the cycle count keeps tracks of it's own cycles to perform the correct behaviour at certain cycle threshold. We have this loop run for 29828 cycles, since having 29828 cycles done represent a frame worth of cylces for around 60 FPS (NTSC).\n\nAll the other components should follows their own sturucture to do it's job. For example the `CPU` will execute a instruction using the `Bus`, reading the opcode to see what instruction to do, then writing to memory if needed or handle a interrupt. The `PPU` will take in the number of cycles for tracking, and perform certain behaviour such as after cycle relative to scanline, `scanlineCycle >= 341`, it can render a scanline. The `Bus` is what connects the `CPU` and `PPU` to memory, and it's all three components can \"talk\" to each other using MMIO which are get special places in memory where both componets can read and write depending on use, for example like PPUDATA `$2000` which where VRAM data can we read and write to. Let's look at `CPU.cs` as a example. A constructor is made to set the default state of the componet:\n```cs\npublic CPU(IBus bus) {\n     A = X = Y = 0;\n     PC = 0x0000;\n     SP = 0x0000;\n     status = 0;\n\n     this.bus = bus;\n\n     irqRequested = false;\n     nmiRequested = false;\n\n     Console.WriteLine(\"CPU init\");\n}\n```\nNotice how other component can rely on others, and how they need to be passed in. In this case of the CPU, the Bus is passed in, as many insturction need to access to the memory. Each component then had the \"main\" method they would be invoking. For PPU called `Step()` as we step every cycle passed in. For the CPU, we have `ExecuteInstruction()`:\n```cs\nprivate byte Fetch() {\n  return bus.Read(PC++);\n}\n\npublic int ExecuteInstruction() {\n  ...\n  byte opcode = Fetch();\n\n  switch (opcode) {\n  //LDA, LDX, LDY, STA, STX, STY\n  case 0xA9: return LDR(ref A, Immediate, 2);\n  case 0xA5: return LDR(ref A, ZeroPage, 3);\n  case 0xB5: return LDR(ref A, ZeroPageX, 4);\n  ...\n\nprivate int LDR(ref byte r, Func<AddrResult> mode, int baseCycles) {\n  var addr = mode();\n  r = bus.Read(addr.address);\n  SetZN(r);\n\n  return baseCycles + addr.extraCycles;\n}\n```\nIf you have seen my Gameboy emulator, CODE-DMG, all of this may seem familiar. If you go over each component step by step, it's pretty each to follow along.\n## Credits/Resources\nNET-NES wouldn't be possible without these resources:\n- NESDEV Wiki: https://www.nesdev.org/wiki/Nesdev_Wiki\n- 6502 Opcodes: https://www.masswerk.at/6502/6502_instruction_set.html\n- Obelisk 6502 Documentation: https://www.nesdev.org/obelisk-6502-guide/\n- SingleStepTest JSON Test 65x02: https://github.com/SingleStepTests/65x02\n- MOS Microcomputer Programming Manual: https://archive.org/details/mos_microcomputers_programming_manual\n- MOS Microcomputer Hardware Manual: https://web.archive.org/web/20221106105459if_/http://archive.6502.org/books/mcs6500_family_hardware_manual.pdf\n- Rodrigo Copetti's NES/Famicom Architecture A Practical Analysis: https://www.copetti.org/writings/consoles/nes/\n\nThese documentations were so useful, I recommend anyone to use them!\nAlso shoutout to the EmuDev community! \n\n## Upcoming Features\n- [ ] Debugger\n  - VRAM viewer, Memory viewer, step mode\n- [ ] Add More Mappers\n  - Mapper 3\n- [ ] Player 2 Controller Support\n- [ ] Audio\n- Post any feature request in the Issues tab!\n\n## Known issues\n- [ ] Upgrade PPU to dot by dot\n  - Will make it more accurate\n- [ ] Improve interrupt logic\n- If you find other bugs/issues, open up a issue in the Issue tab\n\n## Remarks\nWhat a adventure it was to make Gameboy emulator and a NES emulator back to back. I find hard to believe that a year ago I just finished making my first emulator for the CHIP-8 not knowing anything about low-level hardware, and now after making four emulators <em>(CHIP-8, Intel 8080, Gameboy, NES)</em>, it feels great. As with anything I make, I not only like to share the software itself, but also like the aspect of how other furture NES emulator developers can reference if they get stuck on something, or if anyone is just interested on how emulation works. Emulation is one of those things that seem like magic, even for a experienced developer. That's why I always have the goal to write code as simple and clear as possible, and hope it can help someone else in the future :)\n\nIf anyone reading is curious, here is how I went about making my NES emulator. So before I actually talk about that, I want to give my two cent on a common question asked in emulation development. *Should one make a Gameboy emulator first or a NES emulator first? Which one is \"easier\"?* To that, I say it really depends on your goals. Making a Gameboy emulator can take a bit longer to have fully playable games, but once you get a simple game like Tetris, most games starts to work. The NES, you can get games like Donkey Kong playable in early development of your emulator, but to emulate it *perfectly* to play all games is much more work. This because for a Gameboy emulator, you can make a scanline based render without cycle accuracy, and most games would work perfectly. For the NES, that's really not the case. You can get a lot of games working with scanline timing, but having a dot by dot is pretty important if you want most. Another thing to note, I personally found more documenation and information for the Gameboy to be eaier to follow than the NES. When it comes to difficulty of implementation, in hindsight, they both felt about the same. I found it good that I made a Gameboy emulator first, and so then using that knowledge to make a NES emulator was useful. With that in mind, my first goal when it came to making a NES emulator is making sure I had a completed 6502 CPU that were passing the JSON test. Making the CPU was pretty easy since it had less instructions then the Gameboy's CPU. The only thing I had to look out for was the addressing modes. I made a small bug early on where I had a extra cycle, which when it came to doing basic PPU rendering, caused graphical glitches. After realizing that, a quick fix got everything back on track. Now when I started on the PPU, instead of jumping right into scanline rendering, I made a simple full frame renderer. I did this because I already knew the NES's PPU can be a bit tricky to implement in full. Games like Donkey Kong will work perfectly fine with a simple full frame rendering, with only the basic behaviour of the PPU. Then after confirming my simple renderer works with basic inputs, I upgraded it a scaline based one, then added scrolling using Super Mario Bros as a test for that. At this point Mapper 0 games worked fine, so I decided to support the other mappers. I hope reading this little simple remark and looking though this repository was useful to someone on their own emulation journey or someone who is just interested. Of course, the best resource for making a NES emulator is the [NesDev](https://www.nesdev.org/wiki/Nesdev_Wiki) Wiki, it's so helpful. Thank you for reading!\n```\n ____________________________\n│ │  NES               │---│ │\n│ │____________________│___│ │\n│____________________________│\n|                     1  2   |\n \\ ■ [ ] [ ]          ▒  ▒  /\n  ∙------------------------∙\nBotRandomness\n\n^ ASCII NES art I made myself\nFree to use. If used, credit is not needed, but is appreciated :)\n```\n## Contributing\n\nThis project is open-source under the MIT License, meaning your free to do what ever you want with it. This project is freely available for anyone to contribute, emulations experts, Nintendo fans, NES lovers, retro enthusiast, or someone who is new to it all.\n\nIf you plan on contributing, a good place to start is to look at upcoming wanted features, and known issues. If you find a new bug, or have feature ideas of your own, posted first to the Issues tab before hand. You can even fork it and make it your own! </br>\n\nTo get started on contributing:\n\n1. Fork or Clone the Project\n2. Once you have your own repository (it can be a public repository) to work in, you can get started on what you want to do!\n3. Make sure you git Add and git Commit your Changes to your repository\n4. Then git push to your repository\n5. Open a Pull Request in this repository, where your changes will be look at to be approved\n6. Once it's approved, it will be in a development branch, soon to be merge to the main branch\n\n<!-- LICENSE -->\n\n## License\n\nDistributed under the MIT License. See `LICENSE` for more information.\n"
  },
  {
    "path": "src/Bus.cs",
    "content": "public class Bus : IBus{\n    public CPU cpu;\n    public PPU ppu;\n    public Cartridge cartridge;\n\n    public byte[] ram; //2KB RAM\n\n    public Input input = new Input();\n\n    public Bus(Cartridge cartridge) {\n        this.cartridge = cartridge;\n        cpu = new CPU(this);\n        ppu = new PPU(this);\n\n        ram = new byte[2048];\n\n        Console.WriteLine(\"Bus init\");\n    }\n\n    public byte Read(ushort address) {\n        if (address == 0x4016) {\n            return input.Read4016(); //NES controller input\n        }\n\n        if (address >= 0x2000 && address <= 0x3FFF) {\n            ushort reg = (ushort)(0x2000 + (address & 0x0007));\n            byte result = ppu.ReadPPURegister(reg);\n            return result;\n        } else if (address >= 0x0000 && address < 0x2000) {\n            return ram[address & 0x07FF];\n        } else if (address >= 0x6000 && address <= 0xFFFF) {\n            return cartridge.CPURead(address);\n        }\n\n        return 0;\n    }\n\n    public void Write(ushort address, byte value) {\n        if (address == 0x4016) {\n            input.Write4016(value);\n            return;\n        }\n\n        if (address == 0x4014) {\n            ppu.WriteOAMDMA(value);\n            return;\n        }\n\n        if (address >= 0x2000 && address <= 0x3FFF) {\n            ushort reg = (ushort)(0x2000 + (address & 0x0007));\n            ppu.WritePPURegister(reg, value);\n        } else if (address >= 0x0000 && address < 0x2000) {\n            ram[address & 0x07FF] = value;\n        } else if (address >= 0x6000 && address <= 0xFFFF) {\n            cartridge.CPUWrite(address, value);\n        }\n    }\n}"
  },
  {
    "path": "src/CPU.cs",
    "content": "public class CPU {\n    public byte A, X, Y;\n    public ushort PC, SP;\n    public byte status; //Flags (P)\n\n    private const int FLAG_C = 0; //Carry\n    private const int FLAG_Z = 1; //Zero\n    private const int FLAG_I = 2; //Interrupt\n    private const int FLAG_D = 3; //Decimal Mode (Unused in NES)\n    private const int FLAG_B = 4; //Break Command\n    private const int FLAG_UNUSED = 5; //Used bit 5 (always set)\n    private const int FLAG_V = 6; //Overflow\n    private const int FLAG_N = 7; //Negative\n\n    private IBus bus;\n\n    private bool irqRequested;\n    private bool nmiRequested;\n\n    public CPU(IBus bus) {\n        A = X = Y = 0;\n        PC = 0x0000;\n        SP = 0x0000;\n        status = 0;\n\n        this.bus = bus;\n\n        irqRequested = false;\n        nmiRequested = false;\n\n        //Reset();\n\n        Console.WriteLine(\"CPU init\");\n    }\n\n    public void Reset() {\n        A = X = Y = 0;\n        SP = 0xFD;\n        status = 0x24;\n\n        byte low = bus.Read(0xFFFC);\n        byte high = bus.Read(0xFFFD);\n        PC = (ushort)((high << 8) | low);\n    }\n\n    public void SetFlag(int bit, bool value) {\n        if (value) {\n            status |= (byte)(1 << bit);\n        } else {\n            status &= (byte)~(1 << bit);\n        }\n    }\n\n    public bool GetFlag(int bit) {\n        return (status & (1 << bit)) != 0;\n    }\n\n    public void SetZN(byte value) {\n        SetFlag(FLAG_Z, value == 0); //Zero\n        SetFlag(FLAG_N, (value & 0x80) != 0); //Negative\n    }\n\n    /*\n    public void Log() {\n        ushort op1 = (PC);\n        ushort op2 = (ushort)(PC + 1);\n        ushort op3 = (ushort)(PC + 2);\n        ushort op4 = (ushort)(PC + 3);\n        //Console.WriteLine(\"A: \" + A.ToString(\"X2\") + \" X: \" + X.ToString(\"X2\") + \" Y: \" + Y.ToString(\"X2\") + \" SP: \" + SP.ToString(\"X4\") + \" PC: \" + \"00:\" + PC.ToString(\"X4\") + \" (\" + mmu.Read(op1).ToString(\"X2\") + \" \" + mmu.Read(op2).ToString(\"X2\") + \" \" + mmu.Read(op3).ToString(\"X2\") + \" \" + mmu.Read(op4).ToString(\"X2\") + \")\");\n        byte b1 = bus.Read(op1);\n        byte b2 = bus.Read(op2);\n        byte b3 = bus.Read(op3);\n        byte b4 = bus.Read(op4);\n\n        Console.WriteLine(\"A: \" + A.ToString(\"X2\") + \" X: \" + X.ToString(\"X2\") +\" Y: \" + Y.ToString(\"X2\") +  \"P: \"+ Convert.ToString(status, 2).PadLeft(8, '0') + \" SP: \" + SP.ToString(\"X4\") + \" PC: :\" + PC.ToString(\"X4\") + \" (\" + b1.ToString(\"X2\") + \" \" + b2.ToString(\"X2\") + \" \" + b3.ToString(\"X2\") + \" \" + b4.ToString(\"X2\") + \")\");\n    }\n    */\n\n    private byte Fetch() {\n        return bus.Read(PC++);\n    }\n\n    public ushort Fetch16Bits() {\n        byte low = Fetch();\n        byte high = Fetch();\n        return (ushort)((high << 8) | low);\n    }\n\n    \n    public void RequestIRQ(bool line) {\n        irqRequested = line;\n    }\n\n    public void RequestNMI() {\n        nmiRequested = true;\n    }\n\n    public int ExecuteInstruction() {\n        if (nmiRequested) {\n            nmiRequested = false;\n            return NMI();\n        }\n\n        if (GetFlag(FLAG_I) == false && irqRequested) {\n            irqRequested = false;\n            return IRQ();\n        }\n\n        byte opcode = Fetch();\n\n        switch (opcode) {\n            //BRK, NOP, RTI\n            case 0x00: return BRK();\n            case 0xEA: return NOP();\n            case 0x40: return RTI();\n            \n            //LDA, LDX, LDY, STA, STX, STY\n            case 0xA9: return LDR(ref A, Immediate, 2);\n            case 0xA5: return LDR(ref A, ZeroPage, 3);\n            case 0xB5: return LDR(ref A, ZeroPageX, 4);\n            case 0xAD: return LDR(ref A, Absolute, 4);\n            case 0xBD: return LDR(ref A, AbsoluteX, 4);\n            case 0xB9: return LDR(ref A, AbsoluteY, 4);\n            case 0xA1: return LDR(ref A, IndirectX, 6);\n            case 0xB1: return LDR(ref A, IndirectY, 5);\n            case 0xA2: return LDR(ref X, Immediate, 2);\n            case 0xA6: return LDR(ref X, ZeroPage, 3);      \n            case 0xB6: return LDR(ref X, ZeroPageY, 4);\n            case 0xAE: return LDR(ref X, Absolute, 4);\n            case 0xBE: return LDR(ref X, AbsoluteY, 4);\n            case 0xA0: return LDR(ref Y, Immediate, 2);\n            case 0xA4: return LDR(ref Y, ZeroPage, 3);      \n            case 0xB4: return LDR(ref Y, ZeroPageX, 4);\n            case 0xAC: return LDR(ref Y, Absolute, 4);\n            case 0xBC: return LDR(ref Y, AbsoluteX, 4);\n            case 0x85: return STR(ref A, ZeroPage, 3);\n            case 0x95: return STR(ref A, ZeroPageX, 4);\n            case 0x8D: return STR(ref A, Absolute, 4);\n            case 0x9D: return STR(ref A, AbsoluteX, 5);\n            case 0x99: return STR(ref A, AbsoluteY, 5);\n            case 0x81: return STR(ref A, IndirectX, 6);\n            case 0x91: return STR(ref A, IndirectY, 6);\n            case 0x86: return STR(ref X, ZeroPage, 3);\n            case 0x96: return STR(ref X, ZeroPageY, 4);\n            case 0x8E: return STR(ref X, Absolute, 4);\n            case 0x84: return STR(ref Y, ZeroPage, 3);\n            case 0x94: return STR(ref Y, ZeroPageX, 4);\n            case 0x8C: return STR(ref Y, Absolute, 4);\n            \n            //TAX, TAY, TXA, TYA\n            case 0xAA: return TRR(ref X, ref A, Implied, 2);\n            case 0xA8: return TRR(ref Y, ref A, Implied, 2);\n            case 0x8A: return TRR(ref A, ref X, Implied, 2);\n            case 0x98: return TRR(ref A, ref Y, Implied, 2);\n\n            //TSX, TXS, PHA, PHP, PLA, PLP\n            case 0xBA: return TSX(Implied, 2);\n            case 0x9A: return TXS(Implied, 2);\n            case 0x48: return PHA(Implied, 3);\n            case 0x08: return PHP(Implied, 3);\n            case 0x68: return PLA(Implied, 4);\n            case 0x28: return PLP(Implied, 4);\n\n            //AND, EOR, ORA, BIT\n            case 0x29: return AND(Immediate, 2);\n            case 0x25: return AND(ZeroPage, 3);\n            case 0x35: return AND(ZeroPageX, 4);\n            case 0x2D: return AND(Absolute, 4);\n            case 0x3D: return AND(AbsoluteX, 4);\n            case 0x39: return AND(AbsoluteY, 4);\n            case 0x21: return AND(IndirectX, 6);\n            case 0x31: return AND(IndirectY, 5);\n            case 0x49: return EOR(Immediate, 2);\n            case 0x45: return EOR(ZeroPage, 3);\n            case 0x55: return EOR(ZeroPageX, 4);\n            case 0x4D: return EOR(Absolute, 4);\n            case 0x5D: return EOR(AbsoluteX, 4);\n            case 0x59: return EOR(AbsoluteY, 4);\n            case 0x41: return EOR(IndirectX, 6);\n            case 0x51: return EOR(IndirectY, 5);\n            case 0x09: return ORA(Immediate, 2);\n            case 0x05: return ORA(ZeroPage, 3);\n            case 0x15: return ORA(ZeroPageX, 4);\n            case 0x0D: return ORA(Absolute, 4);\n            case 0x1D: return ORA(AbsoluteX, 4);\n            case 0x19: return ORA(AbsoluteY, 4);\n            case 0x01: return ORA(IndirectX, 6);\n            case 0x11: return ORA(IndirectY, 5);\n            case 0x24: return BIT(ZeroPage, 3);\n            case 0x2C: return BIT(Absolute, 4);\n\n            //ADC, SBC, CMP, CPX, CPY\n            case 0x69: return ADC(Immediate, 2);\n            case 0x65: return ADC(ZeroPage, 3);\n            case 0x75: return ADC(ZeroPageX, 4);\n            case 0x6D: return ADC(Absolute, 4);\n            case 0x7D: return ADC(AbsoluteX, 4);\n            case 0x79: return ADC(AbsoluteY, 4);\n            case 0x61: return ADC(IndirectX, 6);\n            case 0x71: return ADC(IndirectY, 5);\n            case 0xE9: return SBC(Immediate, 2);\n            case 0xE5: return SBC(ZeroPage, 3);\n            case 0xF5: return SBC(ZeroPageX, 4);\n            case 0xED: return SBC(Absolute, 4);\n            case 0xFD: return SBC(AbsoluteX, 4);\n            case 0xF9: return SBC(AbsoluteY, 4);\n            case 0xE1: return SBC(IndirectX, 6);\n            case 0xF1: return SBC(IndirectY, 5);\n            case 0xC9: return CPR(A, Immediate, 2);\n            case 0xC5: return CPR(A, ZeroPage, 3);\n            case 0xD5: return CPR(A, ZeroPageX, 4);\n            case 0xCD: return CPR(A, Absolute, 4);\n            case 0xDD: return CPR(A, AbsoluteX, 4);\n            case 0xD9: return CPR(A, AbsoluteY, 4);\n            case 0xC1: return CPR(A, IndirectX, 6);\n            case 0xD1: return CPR(A, IndirectY, 5);\n            case 0xE0: return CPR(X, Immediate, 2);\n            case 0xE4: return CPR(X, ZeroPage, 3);\n            case 0xEC: return CPR(X, Absolute, 4);\n            case 0xC0: return CPR(Y, Immediate, 2);\n            case 0xC4: return CPR(Y, ZeroPage, 3);\n            case 0xCC: return CPR(Y, Absolute, 4);\n\n            //INC, INX, INY, DEC, DEX, DEY\n            case 0xE6: return INC(ZeroPage, 5);\n            case 0xF6: return INC(ZeroPageX, 6);\n            case 0xEE: return INC(Absolute, 6);\n            case 0xFE: return INC(AbsoluteX, 7);\n            case 0xE8: return INR(ref X, Implied, 2);\n            case 0xC8: return INR(ref Y, Implied, 2);\n            case 0xC6: return DEC(ZeroPage, 5);\n            case 0xD6: return DEC(ZeroPageX, 6);\n            case 0xCE: return DEC(Absolute, 6);\n            case 0xDE: return DEC(AbsoluteX, 7);\n            case 0xCA: return DER(ref X, Implied, 2);\n            case 0x88: return DER(ref Y, Implied, 2);\n\n            //ASL, LSR, ROL, ROR\n            case 0x0A: return ASL(Accumulator, 2);\n            case 0x06: return ASL(ZeroPage, 5);\n            case 0x16: return ASL(ZeroPageX, 6);\n            case 0x0E: return ASL(Absolute, 6);\n            case 0x1E: return ASL(AbsoluteX, 7);\n            case 0x4A: return LSR(Accumulator, 2);\n            case 0x46: return LSR(ZeroPage, 5);\n            case 0x56: return LSR(ZeroPageX, 6);\n            case 0x4E: return LSR(Absolute, 6);\n            case 0x5E: return LSR(AbsoluteX, 7);\n            case 0x2A: return ROL(Accumulator, 2);\n            case 0x26: return ROL(ZeroPage, 5);\n            case 0x36: return ROL(ZeroPageX, 6);\n            case 0x2E: return ROL(Absolute, 6);\n            case 0x3E: return ROL(AbsoluteX, 7);\n            case 0x6A: return ROR(Accumulator, 2);\n            case 0x66: return ROR(ZeroPage, 5);\n            case 0x76: return ROR(ZeroPageX, 6);\n            case 0x6E: return ROR(Absolute, 6);\n            case 0x7E: return ROR(AbsoluteX, 7);\n\n            //JMP, JSR, RTS\n            case 0x4C: return JMP(Absolute, 3);\n            case 0x6C: return JMP(Indirect, 5);\n            case 0x20: return JSR();\n            case 0x60: return RTS();\n            \n            //BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS\n            case 0x90: return BIF(!GetFlag(FLAG_C), Relative, 2);\n            case 0xB0: return BIF(GetFlag(FLAG_C), Relative, 2);\n            case 0xF0: return BIF(GetFlag(FLAG_Z), Relative, 2);\n            case 0x30: return BIF(GetFlag(FLAG_N), Relative, 2);\n            case 0xD0: return BIF(!GetFlag(FLAG_Z), Relative, 2);\n            case 0x10: return BIF(!GetFlag(FLAG_N), Relative, 2);\n            case 0x50: return BIF(!GetFlag(FLAG_V), Relative, 2);\n            case 0x70: return BIF(GetFlag(FLAG_V), Relative, 2);\n\n            //CLC, CLD, CLI, CLV, SEC, SED, SEI\n            case 0x18: return FSC(FLAG_C, false, Implied, 2);\n            case 0xD8: return FSC(FLAG_D, false, Implied, 2);\n            case 0x58: return FSC(FLAG_I, false, Implied, 2);\n            case 0xB8: return FSC(FLAG_V, false, Implied, 2);\n            case 0x38: return FSC(FLAG_C, true, Implied, 2);\n            case 0xF8: return FSC(FLAG_D, true, Implied, 2);\n            case 0x78: return FSC(FLAG_I, true, Implied, 2);\n            default:\n                Console.WriteLine(\"Unimplemented Opcode: \" + opcode.ToString(\"X2\") + \" , PC: \" + (PC-1).ToString(\"X4\"));\n                Environment.Exit(1);\n                return 0;\n        }\n    }\n\n    //Load/Store Operations\n    private int LDR(ref byte r, Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        //r = addr.value;\n        r = bus.Read(addr.address);\n        SetZN(r);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int STR(ref byte r, Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        bus.Write(addr.address, r);\n\n        return baseCycles; //No extra cycle to add\n    }\n\n    //Register Transfer\n    private int TRR(ref byte r1, ref byte r2, Func<AddrResult> mode, int baseCycles) {\n        r1 = r2;\n        SetZN(r1);\n\n        return baseCycles;\n    }\n\n    //Stack Operations\n    private void StackPush(byte value) {\n        bus.Write((ushort)(0x0100 + SP), value);\n        SP--;\n        SP &= 0x00FF;\n    }\n\n    private byte StackPop() {\n        SP++;\n        SP &= 0x00FF;\n        return bus.Read((ushort)(0x0100 + SP));\n    }\n\n    private int TSX(Func<AddrResult> mode, int baseCycles) {\n        X = (byte)SP;\n        SetZN(X);\n        return baseCycles;\n    }\n\n    private int TXS(Func<AddrResult> mode, int baseCycles) {\n        SP = X;\n        return baseCycles;\n    }\n\n    private int PHA(Func<AddrResult> mode, int baseCycles) {\n        StackPush(A);\n        return baseCycles;\n    }\n\n    private int PHP(Func<AddrResult> mode, int baseCycles) {\n        StackPush((byte)(status | (1 << FLAG_B) | (1 << FLAG_UNUSED)));\n        return baseCycles;\n    }\n\n    private int PLA(Func<AddrResult> mode, int baseCycles) {\n        A = StackPop();\n        SetZN(A);\n        return baseCycles;\n    }\n\n    private int PLP(Func<AddrResult> mode, int baseCycles) {\n        status = StackPop();\n        SetFlag(FLAG_UNUSED, true);\n        SetFlag(FLAG_B, false);\n        return baseCycles;\n    }\n\n    //Logical\n    private int AND(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        A = (byte)(A & bus.Read(addr.address));\n        SetZN(A);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int EOR(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        A = (byte)(A ^ bus.Read(addr.address));\n        SetZN(A);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int ORA(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        A = (byte)(A | bus.Read(addr.address));\n        SetZN(A);\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int BIT(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte value = bus.Read(addr.address);\n\n        SetFlag(FLAG_Z, (A & value) == 0);\n        SetFlag(FLAG_N, (value & 0x80) != 0);\n        SetFlag(FLAG_V, (value & 0x40) != 0);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    //Arithmetic\n    private int ADC(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        ushort sum = (ushort)(A + bus.Read(addr.address) + (GetFlag(FLAG_C) ? 1 : 0));\n\n        SetFlag(FLAG_C, sum > 0xFF);\n        SetFlag(FLAG_Z, (sum & 0xFF) == 0);\n        SetFlag(FLAG_N, (sum & 0x80) != 0);\n        SetFlag(FLAG_V, (~(A ^ bus.Read(addr.address)) & (A ^ sum) & 0x80) != 0);\n\n        A = (byte)(sum & 0xFF);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int SBC(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        ushort value = (ushort)(bus.Read(addr.address) ^ 0xFF);\n        ushort sum = (ushort)(A + value + (GetFlag(FLAG_C) ? 1 : 0));\n\n        SetFlag(FLAG_C, sum > 0xFF);\n        SetFlag(FLAG_Z, (sum & 0xFF) == 0);\n        SetFlag(FLAG_N, (sum & 0x80) != 0);\n        SetFlag(FLAG_V, ((A ^ sum) & (value ^ sum) & 0x80) != 0);\n\n        A = (byte)(sum & 0xFF);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    private int CPR(byte r, Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte M = bus.Read(addr.address);\n        ushort temp = (ushort)(r - M);\n\n        SetFlag(FLAG_C, r >= M);\n        SetFlag(FLAG_Z, (temp & 0xFF) == 0);\n        SetFlag(FLAG_N, (temp & 0x80) != 0);\n\n        return baseCycles + addr.extraCycles;\n    }\n\n    //Increments and Decrements\n    private int INC(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte result = (byte)(bus.Read(addr.address) + 1);\n        bus.Write(addr.address, result);\n        SetZN(result);\n\n        return baseCycles; //No extra cycle to add\n    }\n\n    private int DEC(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte result = (byte)(bus.Read(addr.address) - 1);\n        bus.Write(addr.address, result);\n        SetZN(result);\n\n        return baseCycles; //No extra cycle to add\n    }\n\n    private int INR(ref byte r, Func<AddrResult> mode, int baseCycles) {\n        r++;\n        SetZN(r);\n        return baseCycles;\n    }\n\n    private int DER(ref byte r, Func<AddrResult> mode, int baseCycles) {\n        r--;\n        SetZN(r);\n        return baseCycles;\n    }\n\n    //Shifts\n    private int ASL(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte value = mode == Accumulator ? A : bus.Read(addr.address);\n        SetFlag(FLAG_C, (value & 0x80) != 0);\n        byte result = (byte)(value << 1);\n\n        if (mode == Accumulator) {\n            A = result;\n        } else {\n            bus.Write(addr.address, result);\n        }\n\n        SetZN(result);\n\n        return baseCycles;\n    }\n\n    private int LSR(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte value = mode == Accumulator ? A : bus.Read(addr.address);\n        SetFlag(FLAG_C, (value & 0x01) != 0);\n        byte result = (byte)(value >> 1);\n\n        if (mode == Accumulator) {\n            A = result;\n        } else {\n            bus.Write(addr.address, result);\n        }\n\n        SetZN(result);\n\n        return baseCycles;\n    }\n\n    private int ROL(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte value = mode == Accumulator ? A : bus.Read(addr.address);\n        bool oldCarry = GetFlag(FLAG_C);\n        SetFlag(FLAG_C, (value & 0x80) != 0);\n        byte result = (byte)((value << 1) | (oldCarry ? 1 : 0));\n\n        if (mode == Accumulator) {\n            A = result;\n        } else {\n            bus.Write(addr.address, result);\n        }\n\n        SetZN(result);\n\n        return baseCycles;\n    }\n\n    private int ROR(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        byte value = mode == Accumulator ? A : bus.Read(addr.address);\n        bool oldCarry = GetFlag(FLAG_C);\n        SetFlag(FLAG_C, (value & 0x01) != 0);\n        byte result = (byte)((value >> 1) | (oldCarry ? 0x80 : 0));\n\n        if (mode == Accumulator) {\n            A = result;\n        } else {\n            bus.Write(addr.address, result);\n        }\n\n        SetZN(result);\n\n        return baseCycles;\n    }\n\n    //Jumps and Calls\n    private int JMP(Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        PC = addr.address;\n        return baseCycles;\n    }\n\n    private int JSR() {\n        ushort targetLow = Fetch();\n        ushort targetHigh = Fetch();\n\n        ushort targetAddr = (ushort)((targetHigh << 8) | targetLow);\n\n        ushort returnAddr = (ushort)(PC - 1);\n\n        StackPush((byte)((returnAddr >> 8) & 0xFF));\n        StackPush((byte)(returnAddr & 0xFF));\n\n        PC = targetAddr;\n        return 6;\n    }\n\n    private int RTS() {\n        byte low = StackPop();\n        byte high = StackPop();\n        PC = (ushort)(((high << 8) | low) + 1);\n        return 6;\n    }\n\n    //Branches\n    private int BIF(bool condition, Func<AddrResult> mode, int baseCycles) {\n        var addr = mode();\n        int extra = 0;\n\n        if (condition) {\n            PC = addr.address;\n            extra = 1 + addr.extraCycles;\n        }\n\n        return baseCycles + extra;\n    }\n\n    //Status Flag Changes\n    private int FSC(int bit, bool state, Func<AddrResult> mode, int baseCycles) {\n        SetFlag(bit, state);\n        return baseCycles;\n    }\n    \n    //System Functions\n    private int NOP() {\n        return 2;\n    }\n\n    private int BRK() {\n        PC++;\n    \n        StackPush((byte)((PC >> 8) & 0xFF));\n        StackPush((byte)(PC & 0xFF));\n        \n        byte pushedStatus = (byte)(status | (1 << FLAG_B) | (1 << FLAG_UNUSED));\n        StackPush(pushedStatus);\n\n        SetFlag(FLAG_B, false);\n\n        SetFlag(FLAG_I, true);\n\n        byte lo = bus.Read(0xFFFE);\n        byte hi = bus.Read(0xFFFF);\n        PC = (ushort)((hi << 8) | lo);\n\n        return 7;\n    }\n\n    private int RTI() {\n        status = StackPop();\n        SetFlag(FLAG_UNUSED, true);\n        SetFlag(FLAG_B, false);\n\n        byte low = StackPop();\n        byte high = StackPop();\n        PC = (ushort)((high << 8) | low);\n\n        return 6;\n    }\n\n    public int IRQ() {\n        if (GetFlag(FLAG_I) == false) {\n            StackPush((byte)((PC >> 8) & 0xFF));\n            StackPush((byte)(PC & 0xFF));\n\n            SetFlag(FLAG_B, false);\n            SetFlag(FLAG_UNUSED, true);\n            StackPush(status);\n\n            SetFlag(FLAG_I, true);\n\n            byte low = bus.Read(0xFFFE);\n            byte high = bus.Read(0xFFFF);\n            PC = (ushort)((high << 8) | low);\n\n            return 7;\n        }\n\n        return 0;\n    }\n\n    public int NMI() {\n        StackPush((byte)((PC >> 8) & 0xFF));\n        StackPush((byte)(PC & 0xFF));\n\n        SetFlag(FLAG_B, false);\n        SetFlag(FLAG_UNUSED, true);\n        StackPush(status);\n\n        SetFlag(FLAG_I, true);\n\n        byte low = bus.Read(0xFFFA);\n        byte high = bus.Read(0xFFFB);\n        PC = (ushort)((high << 8) | low);\n\n        return 7;\n    }\n\n    private struct AddrResult {\n        public ushort address;\n        public int extraCycles;\n\n        public AddrResult(ushort addr, int extra) {\n            address = addr;\n            extraCycles = extra;\n        }\n    }\n\n    private AddrResult Implied() {\n        return new AddrResult(0, 0);\n    }\n\n    private AddrResult Accumulator() {\n        return new AddrResult(0, 0);\n    }\n\n    private AddrResult Immediate() {\n        return new AddrResult(PC++, 0);\n    }\n\n    private AddrResult ZeroPage() {\n        byte addr = Fetch();\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult ZeroPageX() {\n        byte baseAddr = Fetch();\n        byte addr = (byte)(baseAddr + X);\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult ZeroPageY() {\n        byte baseAddr = Fetch();\n        byte addr = (byte)(baseAddr + Y);\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult Absolute() {\n        ushort addr = Fetch16Bits();\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult AbsoluteX() {\n        ushort baseAddr = Fetch16Bits();\n        ushort effective = (ushort)(baseAddr + X);\n        int penalty = HasPageCrossPenalty(baseAddr, effective) ? 1 : 0;\n        return new AddrResult(effective, penalty);\n    }\n\n    private AddrResult AbsoluteY() {\n        ushort baseAddr = Fetch16Bits();\n        ushort effective = (ushort)(baseAddr + Y);\n        int penalty = HasPageCrossPenalty(baseAddr, effective) ? 1 : 0;\n        return new AddrResult(effective, penalty);\n    }\n\n    private AddrResult IndirectX() {\n        byte zp = Fetch();\n        byte ptr = (byte)(zp + X);\n        ushort addr = (ushort)(bus.Read(ptr) | (bus.Read((byte)(ptr + 1)) << 8));\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult IndirectY() {\n        byte zp = Fetch();\n        ushort baseAddr = (ushort)(bus.Read(zp) | (bus.Read((byte)(zp + 1)) << 8));\n        ushort effective = (ushort)(baseAddr + Y);\n        int penalty = HasPageCrossPenalty(baseAddr, effective) ? 1 : 0;\n        return new AddrResult(effective, penalty);\n    }\n\n    private AddrResult Indirect() {\n        ushort ptr = Fetch16Bits();\n        byte lo = bus.Read(ptr);\n        byte hi = (ptr & 0x00FF) == 0x00FF ? bus.Read((ushort)(ptr & 0xFF00)) : bus.Read((ushort)(ptr + 1));\n        ushort addr = (ushort)((hi << 8) | lo);\n        return new AddrResult(addr, 0);\n    }\n\n    private AddrResult Relative() {\n        sbyte offset = (sbyte)Fetch();\n        ushort target = (ushort)(PC + offset);\n        int penalty = HasPageCrossPenalty(PC, target) ? 1 : 0;\n        return new AddrResult(target, penalty);\n    }\n\n    private bool HasPageCrossPenalty(ushort baseAddr, ushort effectiveAddr) {\n        return (baseAddr & 0xFF00) != (effectiveAddr & 0xFF00);\n    }\n}"
  },
  {
    "path": "src/Cartridge.cs",
    "content": "public class Cartridge {\n    public byte[] rom;\n\n    public byte[] prgROM;\n    public byte[] chrROM;\n\n    public int prgBanks;\n    public int chrBanks;\n    public int mapperID;\n    public bool mirrorHorizontal;\n    public bool mirrorVertical;\n    public Mirroring mirroringMode;\n    public bool hasBattery;\n\n    public byte[] prgRAM;\n    public byte[] chrRAM;\n\n    public IMapper mapper;\n\n    public Cartridge(string romPath) {\n        rom = File.ReadAllBytes(romPath);\n\n        if (rom[0] != 'N' || rom[1] != 'E' || rom[2] != 'S' || rom[3] != 0x1A) {\n            Console.WriteLine(\"Invalid iNES Header!\");\n            Environment.Exit(1);\n        }\n\n        prgBanks = rom[4];\n        chrBanks = rom[5];\n\n        byte flag6 = rom[6];\n        byte flag7 = rom[7];\n\n        mirrorVertical = (flag6 & 0x01) != 0;\n        mirrorHorizontal = !mirrorVertical;\n        hasBattery = (flag6 & 0x02) != 0;\n\n        if ((flag6 & 0x08) != 0) {\n            \n        } else if ((flag6 & 0x01) != 0) {\n            mirroringMode = Mirroring.Vertical;\n        } else {\n            mirroringMode = Mirroring.Horizontal;\n        }\n\n        mapperID = flag6 >> 4 | ((flag7 >> 4) << 4);\n\n        int prgSize = prgBanks * 16 * 1024;\n        int chrSize = chrBanks * 8 * 1024;\n\n        int offset = 16; //iNES rom is 16 bytes\n        prgROM = new byte[prgSize];\n        Array.Copy(rom, offset, prgROM, 0, prgSize);\n\n        offset += prgSize;\n        chrROM = new byte[chrSize];\n        Array.Copy(rom, offset, chrROM, 0, chrSize);\n\n        prgRAM = new byte[8 * 1024];\n        chrRAM = new byte[8 * 1024];\n\n        switch (mapperID) {\n            case 0:\n                mapper = new Mapper0(this);\n                break;\n            case 1:\n                mapper = new Mapper1(this);\n                break;\n            case 2:\n                mapper = new Mapper2(this);\n                break;\n            case 4:\n                mapper = new Mapper4(this);\n                break;\n            default:\n                Console.WriteLine(\"Mapper \" + mapperID + \" is not supported\");\n                Environment.Exit(1);\n                break;\n        }\n        mapper.Reset();\n\n        //Console.WriteLine($\"Cartridge loaded: Mapper {mapperID}, PRG {prgBanks * 16}KB, CHR {(chrSize > 0 ? chrBanks * 8 : 8)}KB\");\n        Console.WriteLine($\"Cartridge loaded: Mapper {mapperID}, PRG-ROM {prgBanks * 16}KB, {(chrSize > 0 ? $\"{chrBanks * 8}KB CHR-ROM\" : \"CHR-RAM\")}\");\n    }\n\n    public byte CPURead(ushort address) {\n        return mapper.CPURead(address);\n    }\n\n    public void CPUWrite(ushort address, byte value) {\n        mapper.CPUWrite(address, value);\n    }\n\n    public byte PPURead(ushort address) {\n        return mapper.PPURead(address);\n    } \n    public void PPUWrite(ushort address, byte value) {\n        mapper.PPUWrite(address, value);\n    }\n\n    public void SetMirroring(Mirroring mode) {\n        mirroringMode = mode;\n        mirrorVertical = mode == Mirroring.Vertical;\n        mirrorHorizontal = mode == Mirroring.Horizontal;\n    }\n}\n\npublic enum Mirroring {\n    Horizontal,\n    Vertical,\n    SingleScreenA,\n    SingleScreenB\n}"
  },
  {
    "path": "src/GUI.cs",
    "content": "#pragma warning disable\n\nusing Raylib_cs;\nusing rlImGui_cs;\nusing ImGuiNET;\n\npublic class GUI {\n    private NES nes;\n\n    private FileDialog fileDialog;\n    private string selectedFilePath = \"\";\n\n    private bool showScaleWindow = false;\n    private bool showAboutWindow = false;\n    private bool showManualWindow = false;\n\n    Image icon;\n    Texture2D backgroundTexture;\n\n    public GUI() {\n        if (!Helper.raylibLog) Raylib.SetTraceLogLevel(TraceLogLevel.None);\n        \n        Raylib.InitWindow(256 * Helper.scale, 240 * Helper.scale, \"NES\");\n        Raylib.SetTargetFPS(60);\n\n        rlImGui.Setup(true);\n\n        ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;\n\n        //Unsafe access for no imgui.ini files\n        var io = ImGui.GetIO();\n        unsafe {\n            IntPtr ioPtr = (IntPtr)io.NativePtr;\n            ImGuiIO* imguiIO = (ImGuiIO*)ioPtr.ToPointer();\n            imguiIO->IniFilename = null;\n        }\n\n        fileDialog = new FileDialog(Directory.GetCurrentDirectory());\n\n        icon = Raylib.LoadImage(Path.Combine(AppContext.BaseDirectory, \"res\", \"Logo.png\"));\n        backgroundTexture = Raylib.LoadTexture(Path.Combine(AppContext.BaseDirectory, \"res\", \"Background.png\"));\n\n        Raylib.SetWindowIcon(icon);\n    }\n\n    public void Run() {\n        while (!Raylib.WindowShouldClose()) {\n            Raylib.SetWindowSize(256 * Helper.scale, 240 * Helper.scale);\n            Raylib.BeginDrawing();\n            rlImGui.Begin();\n\n            Raylib.ClearBackground(Color.Black);\n\n            MenuBar();\n\n            if (Helper.romPath.Length != 0 && Helper.insertingRom == false) {\n                nes.Run();\n            } else if (Helper.insertingRom == true) {\n                nes = new NES();\n                Helper.insertingRom = false;\n            } else {\n                Raylib.ClearBackground(Color.DarkGray);\n                Raylib.DrawTextureEx(backgroundTexture, new System.Numerics.Vector2(0, -5), 0, (float)(Helper.scale*0.50), Color.White);\n            }\n\n            if (Raylib.IsKeyPressed(KeyboardKey.Space)) Helper.showMenuBar = !Helper.showMenuBar;\n            \n            if (Helper.fpsEnable) Raylib.DrawFPS(0, Helper.showMenuBar ? 19 : 0);\n\n            rlImGui.End();\n            Raylib.EndDrawing();\n        }\n\n        Raylib.CloseWindow();\n    }\n\n    public void MenuBar() {\n        if (Helper.showMenuBar) {\n            if (ImGui.BeginMainMenuBar()) {\n                ImGui.Text(\"NET-NES\");\n\n                ImGui.Separator();\n                    \n                if (ImGui.BeginMenu(\"File\")) {\n                    if (ImGui.MenuItem(\"Open ROM\")) {\n                        fileDialog.Open();\n                        Helper.showMenuBar = false;\n                    }\n                    ImGui.EndMenu();\n                }\n                if (ImGui.BeginMenu(\"Config\")) {\n                    if (ImGui.MenuItem(\"Window Size\")) {\n                        showScaleWindow = true;\n                    }\n                    ImGui.EndMenu();\n                }\n                if (ImGui.BeginMenu(\"Debug\")) {\n                    if (ImGui.MenuItem(\"FPS Enable\", null, Helper.fpsEnable)) {\n                        Helper.fpsEnable = !Helper.fpsEnable;\n                    }\n                    if (ImGui.MenuItem(\"Sprite0 Hit Disable\", null, Helper.debugs0h)) {\n                        Helper.debugs0h = !Helper.debugs0h;\n                        Console.WriteLine(\"Sprite0 Hit Check Disable: \" + Helper.debugs0h);\n                    }\n                    ImGui.EndMenu();\n                }\n                if (ImGui.BeginMenu(\"Help\")) {\n                    if (ImGui.MenuItem(\"Manual\")) {\n                        showManualWindow = true;\n                    }\n                    if (ImGui.MenuItem(\"About\")) {\n                        showAboutWindow = true;\n                    }\n                    ImGui.EndMenu();\n                }\n            }\n        } else {\n            Helper.showMenuBar = ImGui.GetMousePos().Y <= 20.0f && ImGui.GetMousePos().Y != 0;\n        }\n\n        ImGui.SetNextWindowPos(new System.Numerics.Vector2(0, 0), ImGuiCond.Appearing);\n        ImGui.SetNextWindowSize(new System.Numerics.Vector2(256 * Helper.scale, 240 * Helper.scale), ImGuiCond.Appearing);\n        if (fileDialog.Show(ref selectedFilePath)) {\n            Helper.romPath = selectedFilePath;\n            Helper.insertingRom = true;\n        }\n\n        ScaleWindow();\n        ManualWindow();\n        AboutWindow();\n    }\n\n    public void ScaleWindow() {\n        if (showScaleWindow) {\n            ImGui.SetNextWindowPos(new System.Numerics.Vector2(0, 20), ImGuiCond.Appearing);\n            ImGui.SetNextWindowSize(new System.Numerics.Vector2(125 * 2, 50 * 2), ImGuiCond.Appearing);\n            if (ImGui.Begin(\"Window Size Config\", ref showScaleWindow)) {\n                ImGui.SliderInt(\"Scale\", ref Helper.scale, 1, 10);\n\n                ImGui.Spacing();\n\n                if (ImGui.Button(\"Close\")) {\n                    showScaleWindow = false;\n                }\n            }\n            ImGui.End();\n        }\n    }\n\n    public void AboutWindow() {\n        if (showAboutWindow) {\n            ImGui.SetNextWindowPos(new System.Numerics.Vector2(0, 20), ImGuiCond.Appearing);\n            ImGui.SetNextWindowSize(new System.Numerics.Vector2(125 * 2, 120 * 2), ImGuiCond.Appearing);\n            if (ImGui.Begin(\"About\", ref showAboutWindow)) {\n                ImGui.Text(\"NET-NES\");\n                ImGui.Text(Helper.version.ToString());\n                ImGui.Text(\"Made by Bot Randomness :)\");\n\n                ImGui.Text(\" ____________________________ \");\n                ImGui.Text(\"| |  NES               |---| |\");\n                ImGui.Text(\"| |____________________|___| |\");\n                ImGui.Text(\"|____________________________|\");\n                ImGui.Text(\"|                     1  2   |\");\n                ImGui.Text(\" \\\\ O [ ] [ ]          D  D  / \");\n                ImGui.Text(\"  *------------------------*  \");\n\n                if (ImGui.Button(\"Close\")) {\n                    showAboutWindow = false;\n                }\n            }\n            ImGui.End();\n        }\n    }\n\n    public void ManualWindow() {\n        if (showManualWindow) {\n            ImGui.SetNextWindowPos(new System.Numerics.Vector2(0, 20), ImGuiCond.Appearing);\n            ImGui.SetNextWindowSize(new System.Numerics.Vector2(125 * 2, 120 * 2), ImGuiCond.Appearing);\n            if (ImGui.Begin(\"Manual\", ref showManualWindow)) {\n                ImGui.Text(\"Controls: (A)=X, (B)=Z, \\nD-Pad=ArrowKeys, [START]=ENTER, \\n[SELECT]=SHIFT\");\n                ImGui.Spacing();\n                ImGui.Text(\"Press [SPACE] to toggle the Menu \\nBar.\");\n                ImGui.Spacing();\n                ImGui.Text(\"In Debug: Toggle Sprite0 Hit Check. \\nTry this out if a game freezes\");\n                ImGui.Spacing();\n                ImGui.Text(\"Only Catridge Mapper ID Supported: \\n0, 1, 2, 4\");\n\n                if (ImGui.Button(\"Close\")) {\n                    showManualWindow = false;\n                }\n            }\n            ImGui.End();\n        }\n    }\n\n    class FileDialog {\n        /*\n        *   File Dialog for ImGui.NET\n        *   This is a simple file dialog, it's flexible and expandable\n        *   This can also easily be ported to other languages for other bindings or the original Dear ImGui\n        *\n        *   Basic Usage:\n        *   FileDialog fileDialog = new FileDialog();\n        *\n        *   string selectedFilePath = \"\";\n        *\n        *   fileDialog.Open(); //Trigger the file dialog to open\n        *\n        *   if (fileDialog.Show(ref selectedFilePath)) {\n        *       //Do something with string path\n        *   }\n        *   \n        *   Made by Bot Randomness\n        */\n\n        private string currentDirectory;\n        private string selectedFilePath = \"\";\n        private bool isOpen = false;\n\n        private bool canCancel = true;\n\n        public FileDialog(string startDirectory, bool canCancel = true) {\n            currentDirectory = Directory.Exists(startDirectory) ? startDirectory : Directory.GetCurrentDirectory();\n            this.canCancel = canCancel;\n        }\n\n        public bool Show(ref string resultFilePath) {\n            if (!isOpen) return false;\n\n            bool fileSelected = false;\n\n            if (ImGui.Begin(\"File Dialog\", ImGuiWindowFlags.HorizontalScrollbar)) {\n                ImGui.Text(\"Select a file:\");\n\n                string[] directories = Directory.GetDirectories(currentDirectory);\n                string[] files = Directory.GetFiles(currentDirectory, \"*.*\");\n\n                ImGui.InputText(\"File Path\", ref selectedFilePath, 260);\n\n                if (ImGui.Button(\"Select File\")) {\n                    if (File.Exists(selectedFilePath)) {\n                        resultFilePath = selectedFilePath;\n                        fileSelected = true;\n                        isOpen = false;\n                    }\n                }\n\n                ImGui.SameLine();\n\n                if (canCancel) {\n                    if (ImGui.Button(\"Cancel\")) {\n                        isOpen = false;\n                    }\n                }\n\n                if (Path.GetPathRoot(currentDirectory) != currentDirectory) {\n                    if (ImGui.Button(\"Back\")) {\n                        currentDirectory = Directory.GetParent(currentDirectory)?.FullName ?? currentDirectory;\n                    }\n                }\n\n                foreach (var dir in directories) {\n                    if (ImGui.Selectable(\"[DIR] \" + Path.GetFileName(dir))) {\n                        currentDirectory = dir;\n                    }\n                }\n\n                foreach (var file in files) {\n                    if (ImGui.Selectable(Path.GetFileName(file))) {\n                        selectedFilePath = file;\n                    }\n                }\n\n                if (directories.Length == 0 && files.Length == 0) {\n                    ImGui.Text(\"No files or folders found.\");\n                }\n            }\n            ImGui.End();\n\n            return fileSelected;\n        }\n\n        public void Open() {\n            isOpen = true;\n        }\n    }\n}\n\n#pragma warning restore"
  },
  {
    "path": "src/Helper.cs",
    "content": "public class Helper {\n    public static int scale = 2;\n    public static string romPath = \"\";\n    public static bool debug = false;\n    public static bool debugs0h = false;\n    public static bool fpsEnable = false;\n    public static bool raylibLog = false;\n    public static int mode = 1;\n    public static string jsonPath = \"\";\n    public static int flagArraySize = -1;\n    public static bool insertingRom = false;\n    public static bool showMenuBar = true; \n    public static Version version = new Version(1, 0, 0);\n\n    public static void Flags(string[] args) {\n        flagArraySize = args.Length;\n        if (args.Length >= 1) {\n            for (int i = 0; i < args.Length; i++) {\n                if (args[i] == \"--nes\") {\n                    if (i + 1 < args.Length) {\n                        romPath = args[i + 1];\n                        if (!File.Exists(romPath)) {\n                            Console.WriteLine(\"ROM path \\\"\" + romPath + \"\\\" is invalid\");\n                            Environment.Exit(1);\n                        }\n                        insertingRom = true;\n                        mode = 1;\n                        i += 1;\n                    } else {\n                        Console.WriteLine(\"No ROM passed in\");\n                        Console.WriteLine(\"Usage: --nes <string:rom>\");\n                        //Environment.Exit(1);\n                    }\n                }\n                if (args[i] == \"--json\") {\n                    if (i + 1 < args.Length) {\n                        jsonPath = args[i + 1];\n                        i += 1;\n                    }\n\n                    mode = 2;\n                }\n                if (args[i] == \"-s\" || args[i] == \"--scale\") {\n                    if (i + 1 < args.Length && int.TryParse(args[i + 1], out int parsedScale)) {\n                        scale = parsedScale;\n                        i += 1;\n                    } else {\n                        Console.WriteLine(\"No scale integer passed in\");\n                        Console.WriteLine(\"Usage: -s <int:scale>, --scale <int:scale>\");\n                        Environment.Exit(1);\n                    }\n                }\n                if (args[i] == \"-f\" || args[i] == \"--fps\") {\n                    fpsEnable = true;\n                }\n                if (args[i] == \"-rl\" || args[i] == \"-raylib-log\") {\n                    raylibLog = true;\n                }\n                if (args[i] == \"-d\" || args[i] == \"--debug\") {\n                    debug = true;\n                    Console.WriteLine(\"Press [SPACE] to toggle Sprite0 Hit Check\");\n                }\n                if (args[i] == \"-v\" || args[i] == \"--version\") {\n                    Console.WriteLine(version);\n                    Console.WriteLine(\"Made by Bot Randomness :)\");\n                    ASCII_NES();\n                    Environment.Exit(1);\n                }\n                if (args[i] == \"-a\" || args[i] == \"--about\") {\n                    Console.WriteLine(\"Made by Bot Randomness :)\");\n                    Console.WriteLine(version);\n                    ASCII_NES();\n                    Environment.Exit(1);\n                }\n                if (args[i] == \"-h\" || args[i] == \"--help\") {\n                    Console.WriteLine(\"NES Help:\");\n                    Console.WriteLine(\"--nes <string:rom>: Start up the emulator with given ROM passed in. Consider as mode 1\");\n                    Console.WriteLine(\"--json <string:json>: Runs the JSON Test. The tests must be in \\\"test\\\\v1\\\". Consider as mode 2\");\n                    Console.WriteLine(\"-s <int>, --scale <int>: Scale window size by factor (2 is default)\");\n                    Console.WriteLine(\"-f, --fps: Enables FPS counter (off is default)\");\n                    Console.WriteLine(\"-rl, --raylib-log: Enables Raylib logs (off is default)\");\n                    Console.WriteLine(\"-d, --debug: Enables debug mode (off is default)\");\n                    Console.WriteLine(\"-v, --version: Shows version number\");\n                    Console.WriteLine(\"-a, --about: Show about screen\");\n                    Console.WriteLine(\"-h, --help: Show help screen (What you are seeing now)\");\n                    Console.WriteLine(\"Controls: (A)=X, (B)=Z, D-Pad=ArrowKeys, [START]=ENTER, [SELECT]=SHIFT\");\n                    Console.WriteLine(\"In debug mode: Press [SPACE] to toggle Sprite0 Hit Check. Try this out if a game freezes\");\n                    Console.WriteLine(\"ROMs must have a iNES header!\");\n                    Console.WriteLine(\"In GUI, look at Help -> Manual\");\n                    Environment.Exit(1);\n                }\n            }\n\n            if (mode == 0) {\n                Console.WriteLine(\"Error: No mode passed in\");\n                Console.WriteLine(\"Mode: --nes <string:rom> or --json <string:json>\");\n                Console.WriteLine(\"Use -h or --help to bring up help options.\");\n                Environment.Exit(1);\n            }\n        } else {\n            Console.WriteLine(\"To get started, use -h or --help to bring up help options.\");\n            Console.WriteLine(\"Or use the GUI. \\\"Help -> Manual\\\"\");\n            //Environment.Exit(1);\n        }\n        Console.WriteLine();\n    }\n\n    public static void ASCII_NES() {\n        Console.WriteLine(\" ____________________________ \");\n        Console.WriteLine(\"│ │  NES               │---│ │\");\n        Console.WriteLine(\"│ │____________________│___│ │\");\n        Console.WriteLine(\"│____________________________│\");\n        Console.WriteLine(\"|                     1  2   |\");\n        Console.WriteLine(\" \\\\ ■ [ ] [ ]          ▒  ▒  / \");\n        Console.WriteLine(\"  ∙------------------------∙  \");\n    }\n}"
  },
  {
    "path": "src/Input.cs",
    "content": "using Raylib_cs;\n\npublic class Input {\n    private byte controllerState = 0;\n    private byte controllerShift = 0;\n\n    public void UpdateController() {\n        controllerState = 0;\n        if (Raylib.IsKeyDown(KeyboardKey.X)) controllerState |= 1 << 0; // A\n        if (Raylib.IsKeyDown(KeyboardKey.Z)) controllerState |= 1 << 1; // B\n        if (Raylib.IsKeyDown(KeyboardKey.RightShift) || Raylib.IsKeyDown(KeyboardKey.LeftShift)) controllerState |= 1 << 2; // Select\n        if (Raylib.IsKeyDown(KeyboardKey.Enter)) controllerState |= 1 << 3; // Start\n        if (Raylib.IsKeyDown(KeyboardKey.Up)) controllerState |= 1 << 4; // Up\n        if (Raylib.IsKeyDown(KeyboardKey.Down)) controllerState |= 1 << 5; // Down\n        if (Raylib.IsKeyDown(KeyboardKey.Left)) controllerState |= 1 << 6; // Left\n        if (Raylib.IsKeyDown(KeyboardKey.Right)) controllerState |= 1 << 7; // Right\n    }\n\n    public void Write4016(byte value) {\n        if ((value & 1) != 0) {\n            controllerShift = controllerState;\n        }\n    }\n\n    public byte Read4016() {\n        byte result = (byte)(controllerShift & 1);\n        controllerShift >>= 1;\n        return result;\n    }\n}"
  },
  {
    "path": "src/NES.cs",
    "content": "public class NES {\n    Cartridge cartridge;\n    Bus bus;\n\n    public NES() {\n        cartridge = new Cartridge(Helper.romPath);\n        bus = new Bus(cartridge);\n\n        bus.cpu.Reset();\n        \n        Console.WriteLine(\"NES\");\n    }\n\n    public void Run() {\n        int cycles = 0;\n\n        bus.input.UpdateController();\n\n        while (cycles < 29828) {\n            int used = bus.cpu.ExecuteInstruction();\n            cycles += used;\n            bus.ppu.Step(used * 3);\n        }\n\n        bus.ppu.DrawFrame(Helper.scale);\n    }\n}"
  },
  {
    "path": "src/PPU.cs",
    "content": "using Raylib_cs;\n\npublic class PPU {\n    private Bus bus;\n\n    private byte[] vram; //2KB VRAM\n    private byte[] paletteRAM; //32 bytes Palette RAM\n    private byte[] oam; //256 bytes OAM\n\n    private const int ScreenWidth = 256;\n    private const int ScreenHeight = 240;\n    private const int CyclesPerScanlines = 341;\n    private const int TotalScanlines = 262;\n\n    private byte PPUCTRL; //$2000\n    private byte PPUMASK; //$2001\n    private byte PPUSTATUS; //$2002\n    private byte OAMADDR; //$2003\n    private byte OAMDATA; //$2004\n    private byte PPUSCROLLX, PPUSCROLLY; //$2005\n    private ushort PPUADDR; //$2006\n    private byte PPUDATA; //$2007\n\n    private bool addrLatch = false;\n    private byte ppuDataBuffer;\n\n    private byte fineX; //x\n    private bool scrollLatch; //w\n    private ushort v; //current VRAM address\n    private ushort t; //temp VRAM address\n\n    private int scanlineCycle;\n    private int scanline;\n\n    private Image image;\n    private Texture2D texture;\n    public int textureX = 0;\n    public int textureY = 0;\n\n    private Color[] frameBuffer;\n    Color[] scanlineBuffer = new Color[ScreenWidth];\n\n    public PPU(Bus bus) {\n        this.bus = bus;\n\n        vram = new byte[2048];\n        paletteRAM = new byte[32];\n        oam = new byte[256];\n\n        image = Raylib.GenImageColor(ScreenWidth, ScreenHeight, Color.Black);\n        texture = Raylib.LoadTextureFromImage(image);\n        frameBuffer = new Color[ScreenWidth * ScreenHeight];\n\n        PPUADDR = 0x0000;\n        PPUCTRL = 0x00;\n        PPUSTATUS = 0x00;\n        PPUMASK = 0x00;\n\n        ppuDataBuffer = 0x00;\n\n        scanlineCycle = 0;\n        scanline = 0;\n\n        Console.WriteLine(\"PPU init\");\n    }\n\n    public void Step(int elapsedCycles) {\n        for (int i = 0; i < elapsedCycles; i++) {\n            if (scanline == 0 && scanlineCycle == 0) {\n                PPUSTATUS &= 0x3F;\n            }\n\n            if (scanline >= 0 && scanline < 240 && scanlineCycle == 260) {\n                if ((PPUMASK & 0x18) != 0 && bus.cartridge.mapper is Mapper4) {\n                    Mapper4 mmc3 = (Mapper4)bus.cartridge.mapper;\n                    mmc3.RunScanlineIRQ();\n                    if (mmc3.IRQPending()) {\n                        bus.cpu.RequestIRQ(true);\n                        mmc3.ClearIRQ();\n                    }\n                }\n            }\n\n            scanlineCycle++;\n\n            if (scanlineCycle >= 341) {\n                scanlineCycle = 0;\n\n                if (scanline >= 0 && scanline < 240) {\n                    CopyXFromTToV();\n                    RenderScanline(scanline);\n                    IncrementY();\n                }\n\n                if (scanline == 241) {\n                    PPUSTATUS |= 0x80;\n                    if ((PPUCTRL & 0x80) != 0) {\n                        bus.cpu.RequestNMI();\n                    }\n                }\n\n                if (scanline == 261) {\n                    v = t;\n                }\n\n                scanline++;\n                if (scanline == TotalScanlines) {\n                    scanline = 0;\n                }\n            }\n        }\n    }\n\n    bool[] bgMask = new bool[ScreenWidth];\n    private void RenderScanline(int scanline) {\n        Array.Clear(scanlineBuffer, 0, ScreenWidth);\n        Array.Clear(bgMask, 0, ScreenWidth);\n        \n        RenderBackground(bgMask);\n        RenderSprite(bgMask);\n\n        Array.Copy(scanlineBuffer, 0, frameBuffer, scanline * ScreenWidth, ScreenWidth);\n    }\n\n    public void RenderBackground(bool[] bgMask) {\n        if ((PPUMASK & 0x08) == 0) return;\n\n        ushort renderV = v;\n\n        for (int tile = 0; tile < 33; tile++) {\n            int coarseX = renderV & 0x001F;\n            int coarseY = (renderV >> 5) & 0x001F;\n            int nameTable = (renderV >> 10) & 0x0003;\n\n            int baseNTAddr = 0x2000 + (nameTable * 0x400);\n            int tileAddr = baseNTAddr + (coarseY * 32) + coarseX;\n            byte tileIndex = Read((ushort)tileAddr);\n\n            int fineY = (renderV >> 12) & 0x7;\n            int patternTable = (PPUCTRL & 0x10) != 0 ? 0x1000 : 0x0000;\n            int patternAddr = patternTable + (tileIndex * 16) + fineY;\n            byte plane0 = Read((ushort)patternAddr);\n            byte plane1 = Read((ushort)(patternAddr + 8));\n\n            int attributeX = coarseX / 4;\n            int attributeY = coarseY / 4;\n            int attrAddr = baseNTAddr + 0x3C0 + attributeY * 8 + attributeX;\n            byte attrByte = Read((ushort)attrAddr);\n\n            int attrShift = ((coarseY % 4) / 2) * 4 + ((coarseX % 4) / 2) * 2;\n            int paletteIndex = (attrByte >> attrShift) & 0x03;\n\n            for (int i = 0; i < 8; i++) {\n                int pixel = tile * 8 + i - fineX;\n                if (pixel < 0 || pixel >= ScreenWidth) continue;\n\n                int bitIndex = 7 - i;\n                int bit0 = (plane0 >> bitIndex) & 1;\n                int bit1 = (plane1 >> bitIndex) & 1;\n                int colorIndex = bit0 | (bit1 << 1);\n\n                if (colorIndex != 0) bgMask[pixel] = true;\n\n                scanlineBuffer[pixel] = GetColorFromPalette(colorIndex, paletteIndex);\n            }\n\n            IncrementX(ref renderV);\n        }\n    }\n\n    public void RenderSprite(bool[] bgMask) {\n        bool showSprites = (PPUMASK & 0x10) != 0;\n\n        if (showSprites) {\n            bool isSprite8x16 = (PPUCTRL & 0x20) != 0;\n            \n            bool[] spritePixelDrawn = new bool[ScreenWidth];\n\n            for (int i = 0; i < 64; i++) {\n                int offset = i * 4;\n                byte spriteY = oam[offset];\n                byte tileIndex = oam[offset + 1];\n                byte attributes = oam[offset + 2];\n                byte spriteX = oam[offset + 3];\n\n                int paletteIndex = attributes & 0b11;\n                bool flipX = (attributes & 0x40) != 0;\n                bool flipY = (attributes & 0x80) != 0;\n                bool priority  = (attributes & 0x20) == 0;\n\n                int tileHeight = isSprite8x16 ? 16 : 8;\n                if (scanline < spriteY || scanline >= spriteY + tileHeight)\n                    continue;\n\n                int subY = scanline - spriteY;\n                if (flipY) subY = tileHeight - 1 - subY;\n\n                int subTileIndex = isSprite8x16 ? (tileIndex & 0xFE) + (subY / 8) : tileIndex;\n                int patternTable = isSprite8x16\n                    ? ((tileIndex & 1) != 0 ? 0x1000 : 0x0000)\n                    : ((PPUCTRL & 0x08) != 0 ? 0x1000 : 0x0000);\n                int baseAddr = patternTable + subTileIndex * 16;\n\n                byte plane0 = Read((ushort)(baseAddr + (subY % 8)));\n                byte plane1 = Read((ushort)(baseAddr + (subY % 8) + 8));\n\n                for (int x = 0; x < 8; x++) {\n                    int bit = flipX ? x : 7 - x;\n                    int bit0 = (plane0 >> bit) & 1;\n                    int bit1 = (plane1 >> bit) & 1;\n                    int color = bit0 | (bit1 << 1);\n                    if (color == 0) continue;\n\n                    int px = spriteX + x;\n                    \n                    if (px < 0 || px >= ScreenWidth) continue;\n\n                    //Sprite 0 hit detection\n                    if (i == 0 && bgMask[px] && color != 0 && Helper.debugs0h == false) {\n                        PPUSTATUS |= 0x40;\n                    } else if (Helper.debugs0h == true) { //Debug to skip check for Sprite0 Hit\n                        PPUSTATUS |= 0x40;\n                    }\n\n                    if (spritePixelDrawn[px]) continue;\n                    \n                    bool shouldDraw = true;\n                    if (!priority  && bgMask[px]) {\n                        shouldDraw = false;\n                    }\n\n                    if (shouldDraw) {\n                        scanlineBuffer[px] = GetSpriteColor(color, paletteIndex);\n                        spritePixelDrawn[px] = true;\n                    }\n                }\n            }\n        }\n    }\n\n    public void WritePPURegister(ushort address, byte value) {\n        switch (address) {\n            case 0x2000:\n                PPUCTRL = value;\n                t = (ushort)((t & 0xF3FF) | ((value & 0x03) << 10));\n                break;\n            case 0x2001:\n                PPUMASK = value;\n                break;\n            case 0x2002:\n                PPUSTATUS &= 0x7F;\n                scrollLatch = false;\n                break;\n            case 0x2003:\n                OAMADDR = value;\n                break;\n            case 0x2004:\n                OAMDATA = value;\n                oam[OAMADDR++] = OAMDATA;\n                break;\n            case 0x2005:\n                if (!scrollLatch) {\n                    PPUSCROLLX = value;\n                    fineX = (byte)(value & 0x07);\n                    t = (ushort)((t & 0xFFE0) | (value >> 3));\n                } else {\n                    PPUSCROLLY = value;\n                    t = (ushort)((t & 0x8FFF) | ((value & 0x07) << 12));\n                    t = (ushort)((t & 0xFC1F) | ((value & 0xF8) << 2));\n                }\n                scrollLatch = !scrollLatch;\n                break;\n            case 0x2006:\n                if (!addrLatch) {\n                    t = (ushort)((value << 8) | (t & 0x00FF));\n                    PPUADDR = t;\n                } else {\n                    t = (ushort)((t & 0xFF00) | value);\n                    PPUADDR = t;\n                    v = t;\n                }\n                addrLatch = !addrLatch;\n                break;\n            case 0x2007:\n                PPUDATA = value;\n                Write(PPUADDR, PPUDATA);\n                PPUADDR += ((PPUCTRL & 0x04) != 0) ? (ushort)32 : (ushort)1;\n                v = PPUADDR;\n                break;\n        }\n    }\n\n    public byte ReadPPURegister(ushort address) {\n        byte result = 0x00;\n\n        switch (address) {\n            case 0x2000:\n                result = PPUCTRL;\n                break;\n            case 0x2002:\n                result = PPUSTATUS;\n                PPUSTATUS &= 0x3F;\n                addrLatch = false;\n                break;\n            case 0x2004:\n                result = oam[OAMADDR];\n                break;\n            case 0x2006:\n                result = (byte)(PPUADDR >> 8);\n                break;\n            case 0x2007:\n                result = ppuDataBuffer;\n                ppuDataBuffer = Read(PPUADDR);\n                \n                if (PPUADDR >= 0x3F00) {\n                    result = ppuDataBuffer;\n                }\n                \n                PPUADDR += ((PPUCTRL & 0x04) != 0) ? (ushort)32 : (ushort)1;\n                return result;\n        }\n        return result;\n    }\n\n    public byte Read(ushort address) {\n        address = (ushort)(address & 0x3FFF);\n\n        if (address < 0x2000) {\n            return bus.cartridge.PPURead(address);\n        } else if (address >= 0x2000 && address <= 0x3EFF) {\n            ushort mirrored = MirrorVRAMAddress(address);\n            return vram[mirrored];\n        } else if (address >= 0x3F00 && address <= 0x3FFF) {\n            ushort mirrored = (ushort)(address & 0x1F);\n            if (mirrored >= 0x10 && (mirrored % 4) == 0) mirrored -= 0x10;\n            return paletteRAM[mirrored];\n        }\n\n        return 0;\n    }\n\n    public void Write(ushort address, byte value) {\n        address = (ushort)(address & 0x3FFF);\n\n        if (address < 0x2000) {\n            bus.cartridge.PPUWrite(address, value);\n        } else if (address >= 0x2000 && address <= 0x3EFF) {\n            ushort mirrored = MirrorVRAMAddress(address);\n            vram[mirrored] = value;\n        } else if (address >= 0x3F00 && address <= 0x3FFF) {\n            ushort mirrored = (ushort)(address & 0x1F);\n            if (mirrored >= 0x10 && (mirrored % 4) == 0) mirrored -= 0x10;\n            paletteRAM[mirrored] = value;\n        }\n    }\n\n    private ushort MirrorVRAMAddress(ushort address) {\n        ushort offset = (ushort)(address & 0x0FFF);\n\n        int ntIndex = offset / 0x400;\n        int innerOffset = offset % 0x400;\n\n        switch (bus.cartridge.mirroringMode) {\n            case Mirroring.Vertical:\n                return (ushort)((ntIndex % 2) * 0x400 + innerOffset);\n            case Mirroring.Horizontal:\n                return (ushort)(((ntIndex / 2) * 0x400) + innerOffset);\n            case Mirroring.SingleScreenA:\n                return (ushort)(innerOffset);\n            case Mirroring.SingleScreenB:\n                return (ushort)(0x400 + innerOffset);\n            default:\n                return offset;\n        }\n    }\n\n    public void WriteOAMDMA(byte page) {\n        ushort baseAddr = (ushort)(page << 8);\n        for (int i = 0; i < 256; i++) {\n            byte value = bus.Read((ushort)(baseAddr + i));\n            oam[OAMADDR++] = value;\n        }\n    }\n\n    private void IncrementY() {\n        if ((v & 0x7000) != 0x7000) {\n            v += 0x1000;\n        } else {\n            v &= 0x8FFF;\n            int y = (v & 0x03E0) >> 5;\n            if (y == 29) {\n                y = 0;\n                v ^= 0x0800;\n            } else if (y == 31) {\n                y = 0;\n            } else {\n                y += 1;\n            }\n            v = (ushort)((v & 0xFC1F) | (y << 5));\n        }\n    }\n\n    private void IncrementX(ref ushort addr) {\n        if ((addr & 0x001F) == 31) {\n            addr &= 0xFFE0;\n            addr ^= 0x0400;\n        } else {\n            addr++;\n        }\n    }\n\n    private void CopyXFromTToV() {\n        v = (ushort)((v & 0xFBE0) | (t & 0x041F));\n    }\n\n    private Color GetSpriteColor(int colorIndex, int paletteIndex) {\n        int paletteBase = 0x11 + paletteIndex * 4;\n        byte paletteColor = paletteRAM[paletteBase + (colorIndex - 1)];\n        return NesPalette[paletteColor % 64];\n    }\n\n    private Color GetColorFromPalette(int colorIndex, int paletteIndex) {\n        if (colorIndex == 0) {\n            byte bgColorIndex = paletteRAM[0];\n            return NesPalette[bgColorIndex % 64];\n        }\n\n        int paletteBase = 1 + (paletteIndex * 4);\n        byte paletteColorIndex = paletteRAM[(paletteBase + colorIndex - 1) % 32];\n        return NesPalette[paletteColorIndex % 64];\n    }    \n\n    public void DrawFrame(int scale) {\n        for (int y = 0; y < ScreenHeight; y++) {\n            for (int x = 0; x < ScreenWidth; x++) {\n                Color color = frameBuffer[y * ScreenWidth + x];\n                Raylib.ImageDrawPixel(ref image, x, y, color);\n            }\n        }\n\n        unsafe {\n            Raylib.UpdateTexture(texture, image.Data);\n        }\n\n        Raylib.DrawTextureEx(texture, new System.Numerics.Vector2(textureX, textureY), 0.0f, scale, Color.White);\n    }\n\n    //NES 64 Color Palette\n    static readonly Color[] NesPalette = new Color[] {\n        new Color(84, 84, 84, 255),    new Color(0, 30, 116, 255),   new Color(8, 16, 144, 255),   new Color(48, 0, 136, 255),\n        new Color(68, 0, 100, 255),    new Color(92, 0, 48, 255),    new Color(84, 4, 0, 255),     new Color(60, 24, 0, 255),\n        new Color(32, 42, 0, 255),     new Color(8, 58, 0, 255),     new Color(0, 64, 0, 255),     new Color(0, 60, 0, 255),\n        new Color(0, 50, 60, 255),     new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255),\n        new Color(152, 150, 152, 255), new Color(8, 76, 196, 255),   new Color(48, 50, 236, 255),  new Color(92, 30, 228, 255),\n        new Color(136, 20, 176, 255),  new Color(160, 20, 100, 255), new Color(152, 34, 32, 255),  new Color(120, 60, 0, 255),\n        new Color(84, 90, 0, 255),     new Color(40, 114, 0, 255),   new Color(8, 124, 0, 255),    new Color(0, 118, 40, 255),\n        new Color(0, 102, 120, 255),   new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255),\n        new Color(236, 238, 236, 255), new Color(76, 154, 236, 255), new Color(120, 124, 236, 255),new Color(176, 98, 236, 255),\n        new Color(228, 84, 236, 255),  new Color(236, 88, 180, 255), new Color(236, 106, 100, 255),new Color(212, 136, 32, 255),\n        new Color(160, 170, 0, 255),   new Color(116, 196, 0, 255),  new Color(76, 208, 32, 255),  new Color(56, 204, 108, 255),\n        new Color(56, 180, 204, 255),  new Color(60, 60, 60, 255),   new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255),\n        new Color(236, 238, 236, 255), new Color(168, 204, 236, 255),new Color(188, 188, 236, 255),new Color(212, 178, 236, 255),\n        new Color(236, 174, 236, 255), new Color(236, 174, 212, 255),new Color(236, 180, 176, 255),new Color(228, 196, 144, 255),\n        new Color(204, 210, 120, 255), new Color(180, 222, 120, 255),new Color(168, 226, 144, 255),new Color(152, 226, 180, 255),\n        new Color(160, 214, 228, 255), new Color(160, 162, 160, 255),new Color(0, 0, 0, 255),      new Color(0, 0, 0, 255)\n    };\n}"
  },
  {
    "path": "src/Program.cs",
    "content": "﻿public class Program {\n    public static void Main(string[] args) {\n        Console.WriteLine(\"NET-NES\");\n\n        Helper.Flags(args);\n\n        if (Helper.mode == 1) {\n           GUI gui = new GUI();\n\n           gui.Run();\n        } else if (Helper.mode == 2) {\n            TestRunner testRunner = new TestRunner();\n            \n            testRunner.Run(Helper.jsonPath);\n        }\n    }\n}"
  },
  {
    "path": "src/Test.cs",
    "content": "using Newtonsoft.Json;\n\nclass JSONTest {\n    public class ProcessorState {\n        public int pc { get; set; }\n        public int s { get; set; }\n        public int a { get; set; }\n        public int x { get; set; }\n        public int y { get; set; }\n        public int p { get; set; }\n\n        public List<List<int>> ram { get; set; } = new List<List<int>>();\n    }\n    public class Test {\n        public string name { get; set; } = \"\";\n        public ProcessorState initial { get; set; } = new ProcessorState();\n        public ProcessorState final { get; set; } = new ProcessorState();\n        public List<List<object>> cycles { get; set; } = new List<List<object>>();\n    }\n\n    public IBus bus;\n    public CPU cpu;\n\n    public JSONTest() {\n        bus = new TestBus();\n        cpu = new CPU(bus);\n    }\n\n    public void Run(string jsonPath) {\n        string filePath = jsonPath;\n        \n        var json = File.ReadAllText(filePath);\n        var tests = JsonConvert.DeserializeObject<List<Test>>(json) ?? new List<Test>();\n        foreach (var test in tests) {\n            Console.WriteLine(test.name);\n\n            cpu.PC = (ushort)test.initial.pc;\n            cpu.SP = (ushort)test.initial.s;\n            cpu.A = (byte)test.initial.a;\n            cpu.X = (byte)test.initial.x;\n            cpu.Y = (byte)test.initial.y;\n            cpu.status = (byte)test.initial.p;\n\n            string initCPU16Reg = $\"PC: {cpu.PC}, SP: {cpu.SP}\";\n            string initCPUReg = $\"A: {cpu.A}, X: {cpu.X}, Y: {cpu.Y}, P: {cpu.status}\";\n            string initRAM = \"\";\n\n            foreach (var entry in test.initial.ram) {\n                bus.Write((ushort)entry[0], (byte)entry[1]);\n                initRAM += $\"Address: {entry[0]}, Value: {entry[1]}\\n\";\n            }\n\n            int actualCycleCount = cpu.ExecuteInstruction();\n\n            string finalCPU16Reg = $\"PC: {cpu.PC}, SP: {cpu.SP}\";\n            string finalCPUReg = $\"A: {cpu.A}, X: {cpu.X}, Y: {cpu.Y}, P: {cpu.status}\";\n            string finalRAM = \"\";\n\n            bool isMismatch = false;\n            if (cpu.A != test.final.a) { Console.WriteLine($\"Mismatch in A: Expected {test.final.a}, Found {cpu.A}\"); isMismatch = true; }\n            if (cpu.X != test.final.x) { Console.WriteLine($\"Mismatch in X: Expected {test.final.x}, Found {cpu.X}\"); isMismatch = true; }\n            if (cpu.Y != test.final.y) { Console.WriteLine($\"Mismatch in Y: Expected {test.final.y}, Found {cpu.Y}\"); isMismatch = true; }\n            if (cpu.status != test.final.p) { Console.WriteLine($\"Mismatch in P: Expected {test.final.p}, Found {cpu.status}\"); isMismatch = true; }\n            if (cpu.PC != test.final.pc) { Console.WriteLine($\"Mismatch in Pc: Expected {test.final.pc}, Found {cpu.PC}\"); isMismatch = true; }\n            if (cpu.SP != test.final.s) { Console.WriteLine($\"Mismatch in Sp: Expected {test.final.s}, Found {cpu.SP}\"); isMismatch = true; }\n            \n            foreach (var entry in test.final.ram) {\n                int valueInMMU = bus.Read((ushort)entry[0]);\n                finalRAM += $\"Address: {entry[0]}, Value: {entry[1]}\\n\";\n\n                if (valueInMMU != entry[1]) {\n                    Console.WriteLine($\"Mismatch in RAM at Address {entry[0]}: Expected {entry[1]}, Found {valueInMMU}\");\n                    isMismatch = true;\n                }\n            }\n\n            int expectedCycleCount = test.cycles.Count;\n\n            if (expectedCycleCount != actualCycleCount) {\n                Console.WriteLine($\"Mismatch in cycles: Expected {expectedCycleCount}, Found {actualCycleCount}\");\n                isMismatch = true;\n            }\n\n            if (isMismatch) {\n                //To compare init and final values to JSON for full detail if init properly or anyother\n                Console.WriteLine(\"\\nCPU and RAM init:\");\n                Console.WriteLine(initCPU16Reg);\n                Console.WriteLine(initCPUReg);\n                Console.WriteLine(initRAM);\n\n                Console.WriteLine(\"CPU and RAM final:\");\n                Console.WriteLine(finalCPU16Reg);\n                Console.WriteLine(finalCPUReg);\n                Console.WriteLine(finalRAM);\n                \n                Console.WriteLine(\"JSON Test:\");\n                string testJson = JsonConvert.SerializeObject(test, Formatting.Indented);\n                Console.WriteLine(testJson);\n\n                Environment.Exit(1);\n            }\n        }\n\n        Console.WriteLine(\"All tests passed!\");\n    }\n}"
  },
  {
    "path": "src/TestBus.cs",
    "content": "public class TestBus : IBus {\n    public byte[] ram; //64 KB RAM\n\n    public TestBus() {\n        ram = new byte[65536];\n\n        Console.WriteLine(\"Test Bus init\");\n    }\n\n    public void Write(ushort address, byte value) {\n        ram[address] = value;\n    }\n    public byte Read(ushort address) {\n        return ram[address];\n    }\n}"
  },
  {
    "path": "src/TestRunner.cs",
    "content": "public class TestRunner {\n\n    public TestRunner() {\n\n    }\n\n    public void Run(string test) {\n        if (test != \"all\") {\n            if (!File.Exists(Path.Combine(\"test\", \"v1\", test))) {\n                Console.WriteLine(\"Test file \\\"\" + Path.Combine(\"test\", \"v1\", test) + \"\\\" does not exist\");\n                Console.WriteLine(\"Provide JSON test file, or to test all, pass in \\\"all\\\"\");\n                Environment.Exit(1);\n            }\n            JSONTest jsonTest = new JSONTest();\n            jsonTest.Run(Path.Combine(\"test\", \"v1\", test));\n        } else {\n            if (!Directory.Exists(Path.Combine(\"test\", \"v1\"))) {\n                Console.WriteLine(\"Could not find directory \\\"\" + Path.Combine(\"test\", \"v1\") + \"\\\"\");\n                Console.WriteLine(\"Provide JSON test file, or to test all, pass in \\\"all\\\"\");\n                Environment.Exit(1);\n            }\n\n            string[] testFiles = Directory.GetFiles(Path.Combine(\"test\", \"v1\"));\n\n            string[] skipArray = {\n                \"80.json\", \"89.json\", \"9e.json\",\n                \"02.json\", \"03.json\", \"04.json\", \"07.json\", \"xx.json\", \"0b.json\", \"0c.json\", \"0f.json\",\n                \"12.json\", \"13.json\", \"14.json\", \"17.json\", \"1a.json\", \"1b.json\", \"1c.json\", \"1f.json\",\n                \"22.json\", \"23.json\", \"xx.json\", \"27.json\", \"xx.json\", \"2b.json\", \"xx.json\", \"2f.json\",\n                \"32.json\", \"33.json\", \"34.json\", \"37.json\", \"3a.json\", \"3b.json\", \"3c.json\", \"3f.json\",\n                \"42.json\", \"43.json\", \"44.json\", \"47.json\", \"xx.json\", \"4b.json\", \"xx.json\", \"4f.json\",\n                \"52.json\", \"53.json\", \"54.json\", \"57.json\", \"5a.json\", \"5b.json\", \"5c.json\", \"5f.json\",\n                \"62.json\", \"63.json\", \"64.json\", \"67.json\", \"xx.json\", \"6b.json\", \"xx.json\", \"6f.json\",\n                \"72.json\", \"73.json\", \"74.json\", \"77.json\", \"7a.json\", \"7b.json\", \"7c.json\", \"7f.json\",\n                \"82.json\", \"83.json\", \"xx.json\", \"87.json\", \"xx.json\", \"8b.json\", \"xx.json\", \"8f.json\",\n                \"92.json\", \"93.json\", \"xx.json\", \"97.json\", \"xx.json\", \"9b.json\", \"9c.json\", \"9f.json\",\n                \"xx.json\", \"a3.json\", \"xx.json\", \"a7.json\", \"xx.json\", \"ab.json\", \"xx.json\", \"af.json\",\n                \"b2.json\", \"b3.json\", \"xx.json\", \"b7.json\", \"xx.json\", \"bb.json\", \"xx.json\", \"bf.json\",\n                \"c2.json\", \"c3.json\", \"xx.json\", \"c7.json\", \"xx.json\", \"cb.json\", \"xx.json\", \"cf.json\",\n                \"d2.json\", \"d3.json\", \"d4.json\", \"d7.json\", \"da.json\", \"db.json\", \"dc.json\", \"df.json\",\n                \"e2.json\", \"e3.json\", \"xx.json\", \"e7.json\", \"xx.json\", \"eb.json\", \"xx.json\", \"ef.json\",\n                \"f2.json\", \"f3.json\", \"f4.json\", \"f7.json\", \"fa.json\", \"fb.json\", \"fc.json\", \"ff.json\"\n            };\n\n            using StreamWriter log = new StreamWriter(\"log.txt\", append: false) {AutoFlush = true};\n            int tested = 0;\n            int skipped = 0;\n\n            JSONTest jsonTest = new JSONTest();\n\n            foreach (string filePath in testFiles) {\n                if (skipArray.Contains(Path.GetFileName(filePath), StringComparer.OrdinalIgnoreCase)) {\n                    Console.WriteLine($\"Skipping test: {Path.GetFileName(filePath)}\");\n                    log.WriteLine($\"Skipping test: {Path.GetFileName(filePath)}\");\n                    skipped += 1;\n                    continue;\n                }\n\n                Console.WriteLine($\"Running test: {Path.GetFileName(filePath)}\");\n                log.WriteLine($\"Running test: {Path.GetFileName(filePath)}\");\n                tested += 1;\n                jsonTest.Run(filePath);\n            }\n\n            Console.WriteLine($\"Number of Tested Opcodes: {tested}, Number of Skipped Opcodes: {skipped}\");\n            log.WriteLine($\"Number of Tested Opcodes: {tested}, Number of Skipped Opcodes: {skipped}\");\n        }\n    }\n}"
  },
  {
    "path": "src/interface/IBus.cs",
    "content": "public interface IBus {\n    void Write(ushort address, byte value);\n    byte Read(ushort address);\n}"
  },
  {
    "path": "src/interface/IMapper.cs",
    "content": "public interface IMapper {\n    void Reset();\n    \n    byte CPURead(ushort address);\n    void CPUWrite(ushort address, byte value);\n\n    byte PPURead(ushort address);\n    void PPUWrite(ushort address, byte value);\n}"
  },
  {
    "path": "src/mappers/Mapper0.cs",
    "content": "public class Mapper0 : IMapper { //NROM\n    private Cartridge cartridge;\n\n    public Mapper0(Cartridge cart) {\n        cartridge = cart;\n    }\n\n    public void Reset() {\n\n    }\n\n    public byte CPURead(ushort address) {\n        if (address >= 0x6000 && address <= 0x7FFF) {\n            return cartridge.prgRAM[address - 0x6000];\n        } else if (address >= 0x8000 && address <= 0xFFFF) {\n            if (cartridge.prgBanks == 1) {\n                return cartridge.prgROM[address & 0x3FFF];\n            } else {\n                return cartridge.prgROM[address - 0x8000];\n            }\n        }\n        return 0;\n    }\n\n    public void CPUWrite(ushort address, byte value) {\n        if (address >= 0x6000 && address <= 0x7FFF) {\n            cartridge.prgRAM[address - 0x6000] = value;\n        }\n    }\n\n    public byte PPURead(ushort address) {\n        if (address < 0x2000) {\n            if (cartridge.chrBanks != 0) {\n                return cartridge.chrROM[address];\n            } else {\n                return cartridge.chrRAM[address];\n            }\n            \n        }\n        return 0;\n    }\n\n    public void PPUWrite(ushort address, byte value) {\n        if (address < 0x2000 && cartridge.chrBanks == 0) {\n            cartridge.chrRAM[address] = value;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Mapper1.cs",
    "content": "public class Mapper1 : IMapper { //MMC1 (Experimenal)\n    private Cartridge cartridge;\n\n    private byte shiftRegister = 0x10;\n    private byte control = 0x0C;\n    private byte chrBank0, chrBank1, prgBank;\n    private int shiftCount = 0;\n\n    private int prgBankOffset0, prgBankOffset1;\n    private int chrBankOffset0, chrBankOffset1;\n\n    public Mapper1(Cartridge cart) {\n        cartridge = cart;\n        //Reset();\n    }\n\n    public void Reset() {\n        shiftRegister = 0x10;\n        control = 0x0C;\n        chrBank0 = chrBank1 = prgBank = 0;\n        shiftCount = 0;\n        ApplyMirroring();\n        ApplyBanks();\n    }\n\n    public byte CPURead(ushort addr) {\n        if (addr >= 0x6000 && addr <= 0x7FFF) {\n            return cartridge.prgRAM[addr - 0x6000];\n        } else if (addr >= 0x8000 && addr <= 0xBFFF) {\n            int index = prgBankOffset0 + (addr - 0x8000);\n            return cartridge.prgROM[index];\n        } else if (addr >= 0xC000 && addr <= 0xFFFF) {\n            int index = prgBankOffset1 + (addr - 0xC000);\n            return cartridge.prgROM[index];\n        }\n        return 0;\n    }\n\n    public void CPUWrite(ushort addr, byte val) {\n        if (addr >= 0x6000 && addr <= 0x7FFF) {\n            cartridge.prgRAM[addr - 0x6000] = val;\n            return;\n        }\n\n        if (addr < 0x8000) return;\n\n        if ((val & 0x80) != 0) {\n            shiftRegister = 0x10;\n            control |= 0x0C;\n            shiftCount = 0;\n            ApplyBanks();\n            return;\n        }\n\n        shiftRegister = (byte)((shiftRegister >> 1) | ((val & 1) << 4));\n        shiftCount++;\n\n        if (shiftCount == 5) {\n            int reg = (addr >> 13) & 0x03;\n            switch (reg) {\n                case 0:\n                    control = (byte)(shiftRegister & 0x1F);\n                    ApplyMirroring();\n                    break;\n                case 1:\n                    chrBank0 = (byte)(shiftRegister & 0x1F);\n                    ApplyMirroring();\n                    break;\n                case 2:\n                    chrBank1 = (byte)(shiftRegister & 0x1F);\n                    break;\n                case 3:\n                    prgBank = (byte)(shiftRegister & 0x0F);\n                    break;\n            }\n            shiftRegister = 0x10;\n            shiftCount = 0;\n            ApplyBanks();\n        }\n    }\n\n    public byte PPURead(ushort addr) {\n        if (addr < 0x2000) {\n            if (cartridge.chrBanks == 0) {\n                return cartridge.chrRAM[addr];\n            }\n            \n            int chrMode = (control >> 4) & 1;\n            if (chrMode == 0) {\n                int offset = (chrBank0 & 0x1E) * 0x1000;\n                return cartridge.chrROM[(addr + offset) % cartridge.chrROM.Length];\n            } else {\n                if (addr < 0x1000) {\n                    return cartridge.chrROM[(addr + chrBankOffset0) % cartridge.chrROM.Length];\n                } else {\n                    return cartridge.chrROM[((addr - 0x1000) + chrBankOffset1) % cartridge.chrROM.Length];\n                }\n            }\n        }\n        return 0;\n    }\n\n    public void PPUWrite(ushort addr, byte val) {\n        if (addr < 0x2000 && cartridge.chrBanks == 0) {\n            cartridge.chrRAM[addr] = val;\n        }\n    }\n\n    private void ApplyMirroring() {\n        switch (control & 0x03) {\n            case 0: cartridge.SetMirroring(Mirroring.SingleScreenA); break;\n            case 1: cartridge.SetMirroring(Mirroring.SingleScreenB); break;\n            case 2: cartridge.SetMirroring(Mirroring.Vertical); break;\n            case 3: cartridge.SetMirroring(Mirroring.Horizontal); break;\n        }\n    }\n\n    private void ApplyBanks() {\n        int chrMode = (control >> 4) & 1;\n        if (chrMode == 0) {\n            chrBankOffset0 = (chrBank0 & 0x1E) * 0x1000;\n            chrBankOffset1 = chrBankOffset0 + 0x1000;\n        } else {\n            chrBankOffset0 = chrBank0 * 0x1000;\n            chrBankOffset1 = chrBank1 * 0x1000;\n        }\n\n        if (cartridge.chrBanks > 0) {\n            chrBankOffset0 %= cartridge.chrROM.Length;\n            chrBankOffset1 %= cartridge.chrROM.Length;\n        }\n\n        int prgMode = (control >> 2) & 0x03;\n        int prgBankCount = cartridge.prgROM.Length / 0x4000;\n\n        switch (prgMode) {\n            case 0:\n            case 1:\n                int bank = (prgBank & 0x0E) % Math.Max(1, prgBankCount);\n                prgBankOffset0 = bank * 0x4000;\n                prgBankOffset1 = prgBankOffset0 + 0x4000;\n                break;\n            case 2:\n                prgBankOffset0 = 0;\n                prgBankOffset1 = (prgBank % Math.Max(1, prgBankCount)) * 0x4000;\n                break;\n            case 3:\n                prgBankOffset0 = (prgBank % Math.Max(1, prgBankCount)) * 0x4000;\n                prgBankOffset1 = (prgBankCount - 1) * 0x4000;\n                break;\n        }\n        \n        prgBankOffset0 %= cartridge.prgROM.Length;\n        prgBankOffset1 %= cartridge.prgROM.Length;\n    }\n}"
  },
  {
    "path": "src/mappers/Mapper2.cs",
    "content": "public class Mapper2 : IMapper { //UxROM (Experimental)\n    private Cartridge cartridge;\n    private byte prgBank;\n\n    public Mapper2(Cartridge cart) {\n        cartridge = cart;\n        prgBank = 0;\n    }\n\n    public void Reset() {\n        prgBank = 0;\n    }\n\n    public byte CPURead(ushort addr) {\n        if (addr >= 0x8000 && addr <= 0xBFFF) {\n            int index = (prgBank * 0x4000) + (addr - 0x8000);\n            return index < cartridge.prgROM.Length ? cartridge.prgROM[index] : (byte)0xFF;\n        } else if (addr >= 0xC000 && addr <= 0xFFFF) {\n            int fixedBankStart = cartridge.prgROM.Length - 0x4000;\n            int index = fixedBankStart + (addr - 0xC000);\n            return index < cartridge.prgROM.Length ? cartridge.prgROM[index] : (byte)0xFF;\n        }\n        return 0;\n    }\n\n    public void CPUWrite(ushort addr, byte val) {\n        if (addr >= 0x8000) {\n            prgBank = (byte)(val & 0x0F);\n        }\n    }\n\n    public byte PPURead(ushort addr) {\n        if (addr < 0x2000) {\n            if (cartridge.chrBanks == 0)\n                return cartridge.chrRAM[addr];\n            return cartridge.chrROM[addr % cartridge.chrROM.Length];\n        }\n        return 0;\n    }\n\n    public void PPUWrite(ushort addr, byte val) {\n        if (cartridge.chrBanks == 0 && addr < 0x2000) {\n            cartridge.chrRAM[addr] = val;\n        }\n    }\n}\n"
  },
  {
    "path": "src/mappers/Mapper4.cs",
    "content": "public class Mapper4 : IMapper { //MMC3 (Experimental)\n    private Cartridge cartridge;\n\n    private byte bankSelect;\n    private byte[] bankData = new byte[8];\n    private int[] prgBankOffsets = new int[4];\n    private int[] chrBankOffsets = new int[8];\n\n    private bool prgMode;\n    private bool chrMode;\n    private bool prgRamEnable;\n    private bool prgRamWriteProtect;\n\n    private byte irqLatch;\n    private byte irqCounter;\n    private bool irqEnable;\n    private bool irqReloadPending;\n    private bool irqAsserted;\n\n    public Mapper4(Cartridge cart) {\n        cartridge = cart;\n        //Reset();\n    }\n\n    public void Reset() {\n        bankSelect = 0;\n\n        for (int i = 0; i < bankData.Length; i++) {\n            bankData[i] = 0;\n        }\n\n        prgMode = false;\n        chrMode = false;\n        prgRamEnable = true;\n        prgRamWriteProtect = false;\n        \n        irqLatch = 0;\n        irqCounter = 0;\n        irqEnable = false;\n        irqReloadPending = false;\n        irqAsserted = false;\n        \n        ApplyBankMapping();\n    }\n\n    public void RunScanlineIRQ() {\n        if (irqCounter == 0) {\n            irqCounter = irqLatch;\n        } else {\n            irqCounter--;\n            if (irqCounter == 0 && irqEnable) {\n                irqAsserted = true;\n            }\n        }\n\n        if (irqReloadPending) {\n            irqCounter = irqLatch;\n            irqReloadPending = false;\n        }\n    }\n\n    public bool IRQPending() {\n        return irqAsserted;\n    }\n\n    public void ClearIRQ() {\n        irqAsserted = false;\n    }\n\n    public byte CPURead(ushort address) {\n        if (address >= 0x6000 && address <= 0x7FFF) {\n            if (prgRamEnable) {\n                int ramOffset = (address - 0x6000) % cartridge.prgRAM.Length;\n                return cartridge.prgRAM[ramOffset];\n            }\n            return 0xFF;\n        }\n\n        if (address >= 0x8000 && address <= 0xFFFF) {\n            int bankIndex = (address - 0x8000) / 0x2000;\n            int bankOffset = prgBankOffsets[bankIndex];\n            int addressOffset = address % 0x2000;\n            \n            int finalOffset = (bankOffset + addressOffset) % cartridge.prgROM.Length;\n            return cartridge.prgROM[finalOffset];\n        }\n\n        return 0;\n    }\n\n    public void CPUWrite(ushort address, byte value) {\n        if (address >= 0x6000 && address <= 0x7FFF) {\n            if (prgRamEnable && !prgRamWriteProtect) {\n                int ramOffset = (address - 0x6000) % cartridge.prgRAM.Length;\n                cartridge.prgRAM[ramOffset] = value;\n            }\n            return;\n        }\n\n        switch (address & 0xE001) {\n            case 0x8000:\n                bankSelect = value;\n                prgMode = (value & 0x40) != 0;\n                chrMode = (value & 0x80) != 0;\n                ApplyBankMapping();\n                break;\n            case 0x8001:\n                int reg = bankSelect & 0x07;\n                bankData[reg] = value;\n                ApplyBankMapping();\n                break;\n            case 0xA000:\n                if ((value & 1) == 0)\n                    cartridge.SetMirroring(Mirroring.Vertical);\n                else\n                    cartridge.SetMirroring(Mirroring.Horizontal);\n                break;\n            case 0xA001:\n                prgRamEnable = (value & 0x80) != 0;\n                prgRamWriteProtect = (value & 0x40) != 0;\n                break;\n            case 0xC000:\n                irqLatch = value;\n                break;\n            case 0xC001:\n                irqReloadPending = true;\n                break;\n            case 0xE000:\n                irqEnable = false;\n                irqAsserted = false;\n                break;\n            case 0xE001:\n                irqEnable = true;\n                break;\n        }\n    }\n\n    public byte PPURead(ushort address) {\n        if (address >= 0x2000) return 0;\n\n        if (cartridge.chrBanks == 0) {\n            return cartridge.chrRAM[address % cartridge.chrRAM.Length];\n        }\n\n        int bank = address / 0x0400;\n        int bankOffset = chrBankOffsets[bank];\n        int addressOffset = address % 0x0400;\n        \n        int finalOffset = (bankOffset + addressOffset) % cartridge.chrROM.Length;\n        return cartridge.chrROM[finalOffset];\n    }\n\n    public void PPUWrite(ushort address, byte value) {\n        if (address < 0x2000) {\n            if (cartridge.chrBanks == 0) {\n                cartridge.chrRAM[address] = value;\n            }\n        }\n    }\n\n    private void ApplyBankMapping() {        \n        if (chrMode) {\n            chrBankOffsets[0] = bankData[2] * 0x400;\n            chrBankOffsets[1] = bankData[3] * 0x400;\n            chrBankOffsets[2] = bankData[4] * 0x400;\n            chrBankOffsets[3] = bankData[5] * 0x400;\n\n            chrBankOffsets[4] = (bankData[0] & 0xFE) * 0x400;\n            chrBankOffsets[5] = chrBankOffsets[4] + 0x400;\n            chrBankOffsets[6] = (bankData[1] & 0xFE) * 0x400;\n            chrBankOffsets[7] = chrBankOffsets[6] + 0x400;\n        } else {\n            chrBankOffsets[0] = (bankData[0] & 0xFE) * 0x400;\n            chrBankOffsets[1] = chrBankOffsets[0] + 0x400;\n            chrBankOffsets[2] = (bankData[1] & 0xFE) * 0x400;\n            chrBankOffsets[3] = chrBankOffsets[2] + 0x400;\n\n            chrBankOffsets[4] = bankData[2] * 0x400;\n            chrBankOffsets[5] = bankData[3] * 0x400;\n            chrBankOffsets[6] = bankData[4] * 0x400;\n            chrBankOffsets[7] = bankData[5] * 0x400;\n        }\n\n        int bankCount = cartridge.prgROM.Length / 0x2000;\n        int lastBank = bankCount - 1;\n\n        int bank6 = bankData[6] % bankCount;\n        int bank7 = bankData[7] % bankCount;\n\n        if (prgMode) {\n            prgBankOffsets[0] = (lastBank - 1) * 0x2000;\n            prgBankOffsets[1] = bank7 * 0x2000;\n            prgBankOffsets[2] = bank6 * 0x2000;\n            prgBankOffsets[3] = lastBank * 0x2000;\n        } else {\n            prgBankOffsets[0] = bank6 * 0x2000;\n            prgBankOffsets[1] = bank7 * 0x2000;\n            prgBankOffsets[2] = (lastBank - 1) * 0x2000;\n            prgBankOffsets[3] = lastBank * 0x2000;\n        }\n\n        if (cartridge.chrBanks > 0) {\n            for (int i = 0; i < 8; i++) {\n                chrBankOffsets[i] %= cartridge.chrROM.Length;\n            }\n        }\n        \n        for (int i = 0; i < 4; i++) {\n            prgBankOffsets[i] %= cartridge.prgROM.Length;\n        }\n    }\n}"
  },
  {
    "path": "test/README.txt",
    "content": "JSON test goes here.\ntest/v1/XX.json\n\nJSON test repo used from SingleStepTest on GitHub for 6502: https://github.com/SingleStepTests/65x02"
  }
]