Repository: 100thCoin/TriCNES Branch: main Commit: f4551b3ed1d2 Files: 47 Total size: 1007.7 KB Directory structure: gitextract_r4m8qsp8/ ├── .gitattributes ├── .gitignore ├── 6502Documentation.cs ├── App.config ├── Emulator.cs ├── LICENSE ├── Program.cs ├── Properties/ │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── README.md ├── TriCNES.csproj ├── TriCNES.sln ├── forms/ │ ├── TASProperties.Designer.cs │ ├── TASProperties.cs │ ├── TASProperties.resx │ ├── TASProperties3ct.Designer.cs │ ├── TASProperties3ct.cs │ ├── TASProperties3ct.resx │ ├── TriCHexEditor.Designer.cs │ ├── TriCHexEditor.cs │ ├── TriCHexEditor.resx │ ├── TriCNESGUI.Designer.cs │ ├── TriCNESGUI.cs │ ├── TriCNESGUI.resx │ ├── TriCNTViewer.Designer.cs │ ├── TriCNTViewer.cs │ ├── TriCNTViewer.resx │ ├── TriCTASTimeline.Designer.cs │ ├── TriCTASTimeline.cs │ ├── TriCTASTimeline.resx │ ├── TriCTraceLogger.Designer.cs │ ├── TriCTraceLogger.cs │ └── TriCTraceLogger.resx ├── mappers/ │ ├── Mapper_AOROM.cs │ ├── Mapper_CNROM.cs │ ├── Mapper_FDS.cs │ ├── Mapper_FME7.cs │ ├── Mapper_MMC1.cs │ ├── Mapper_MMC2.cs │ ├── Mapper_MMC3.cs │ ├── Mapper_NROM.cs │ ├── Mapper_NULL.cs │ └── Mapper_UxROM.cs └── packages.config ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates *.env # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ [Dd]ebug/x64/ [Dd]ebugPublic/x64/ [Rr]elease/x64/ [Rr]eleases/x64/ bin/x64/ obj/x64/ [Dd]ebug/x86/ [Dd]ebugPublic/x86/ [Rr]elease/x86/ [Rr]eleases/x86/ bin/x86/ obj/x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ [Aa][Rr][Mm]64[Ee][Cc]/ bld/ [Oo]bj/ [Oo]ut/ [Ll]og/ [Ll]ogs/ # Build results on 'Bin' directories **/[Bb]in/* # Uncomment if you have tasks that rely on *.refresh files to move binaries # (https://github.com/github/gitignore/pull/3736) #!**/[Bb]in/*.refresh # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *.trx # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Approval Tests result files *.received.* # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.idb *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp # but not Directory.Build.rsp, as it configures directory-level build defaults !Directory.Build.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio 6 workspace and project file (working project files containing files to include in project) *.dsw *.dsp # Visual Studio 6 technical files *.ncb *.aps # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager **/.paket/paket.exe paket-files/ # FAKE - F# Make **/.fake/ # CodeRush personal settings **/.cr/personal # Python Tools for Visual Studio (PTVS) **/__pycache__/ *.pyc # Cake - Uncomment if you are using it #tools/** #!tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog MSBuild_Logs/ # AWS SAM Build and Temporary Artifacts folder .aws-sam # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder **/.mfractor/ # Local History for Visual Studio **/.localhistory/ # Visual Studio History (VSHistory) files .vshistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder **/.ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets # Local History for Visual Studio Code .history/ # Built Visual Studio Code Extensions *.vsix # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp *.nes *.fm3 *.fm2 *.fmv *.3ct *.bk2 bin/Debug/TriCNES.exe bin/Debug/TriCNES.exe.config bin/Debug/TriCNES.pdb bin/Release/TriCNES.exe bin/Release/TriCNES.exe.config bin/Release/TriCNES.pdb .vs/TriCNES/FileContentIndex/a1f39354-40ca-44d4-a1f4-22570f4e355b.vsidx .vs/TriCNES/FileContentIndex/4bef6b04-ae46-46c1-a5d2-a4ddde2871fe.vsidx *.vsidx *.vsidx *.cache /obj *.vsidx *.vsidx *.tasproj /bin/Release/roms /bin ================================================ FILE: 6502Documentation.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TriCNES { public class Op { public byte code; public string mnemonic; public string mode; public int length; public int affectedFlags; public string CycleByCycle; public string InstructionDocumentation; public Op(byte c, string m, string mo, int l, int a, string d, string i) { code = c; mnemonic = m; mode = mo; length = l; affectedFlags = a; CycleByCycle = d; InstructionDocumentation = i; } } public static class Documentation { // This class is exlusively referenced for debugging and trace logging information. // It's also possible I mistyped some numbers here and there, and probably shouldn't be 100% trusted. //cycle information from https://www.atarihq.com/danb/files/64doc.txt static int cFlag = 1; static int zFlag = 2; static int iFlag = 4; static int dFlag = 8; static int vFlag = 64; static int nFlag = 128; static int aChanges = 256; static int xChanges = 512; static int yChanges = 1024; static int stackPChanges = 2048; static int pcChanges = 4096; static int memChanges = 8192; static string[] CycleDocs = { //0 BRK " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away), increment PC\r\n 3 $0100,S W push PCH on stack, decrement S\r\n 4 $0100,S W push PCL on stack, decrement S\r\n 5 $0100,S W push P on stack (with B flag set), decrement S\r\n 6 $FFFE R fetch PCL\r\n 7 $FFFF R fetch PCH" , //1 RTI " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull P from stack, increment S\r\n 5 $0100,S R pull PCL from stack, increment S\r\n 6 $0100,S R pull PCH from stack" , //2 RTS " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull PCL from stack, increment S\r\n 5 $0100,S R pull PCH from stack\r\n 6 PC R increment PC" , //3 PHA, PHP " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S W push register on stack, decrement S" , //4 PLA, PLP " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull register from stack" , //5 JSR " # address R/W description\r\n --- ------- --- -------------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low address byte, increment PC\r\n 3 $0100,S R internal operation (predecrement S?)\r\n 4 $0100,S W push PCH on stack, decrement S\r\n 5 $0100,S W push PCL on stack, decrement S\r\n 6 PC R copy low address byte to PCL, fetch high address byte to PCH" , //6 Accumulator or implied addressing " Accumulator or implied addressing\r\n\r\n # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)" , //7 Immediate addressing " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch value, increment PC" , // --Absolute Instructions-- //8 JMP " # address R/W description\r\n --- ------- --- -------------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low address byte, increment PC\r\n 3 PC R copy low address byte to PCL, fetch high address byte to PCH" , //9 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address R read from effective address" , //10 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address R read from effective address\r\n 5 address W write the value back to effective address, and do the operation on it\r\n 6 address W write the new value to effective address" , //11 Write instructions (STA, STX, STY, SAX) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address W write register to effective address" , // --Zero page addressing-- //12 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from effective address" , //13 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from effective address\r\n 4 address W write the value back to effective address, and do the operation on it\r\n 5 address W write the new value to effective address" , //14 Write instructions (STA, STX, STY, SAX) " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address W write register to effective address" , // --Zero page indexed addressing-- //15 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP) " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register to it\r\n 4 address+I* R read from effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled." , //16 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- --------- --- ---------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register X to it\r\n 4 address+X* R read from effective address\r\n 5 address+X* W write the value back to effective address, and do the operation on it\r\n 6 address+X* W write the new value to effective address\r\n\r\n Note: * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled." , //17 Write instructions (STA, STX, STY, SAX) " # address R/W description\r\n --- --------- --- -------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register to it\r\n 4 address+I* W write to effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled." , // --Absolute indexed addressing-- //18 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, LAE, SHS, NOP) " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register to low address byte, increment PC\r\n 4 address+I* R read from effective address, fix the high byte of effective address\r\n 5+ address+I R re-read from effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100.\r\n\r\n + This cycle will be executed only if the effective address\r\n was invalid during cycle #4, i.e. page boundary was crossed." , //19 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register X to low address byte, increment PC\r\n 4 address+X* R read from effective address, fix the high byte of effective address\r\n 5 address+X R re-read from effective address\r\n 6 address+X W write the value back to effective address, and do the operation on it\r\n 7 address+X W write the new value to effective address\r\n\r\n Notes: * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100." , //20 Write instructions (STA, STX, STY, SHA, SHX, SHY) " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register to low address byte, increment PC\r\n 4 address+I* R read from effective address, fix the high byte of effective address\r\n 5 address+I W write to effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100. Because\r\n the processor cannot undo a write to an invalid\r\n address, it always reads from the address first." , //21 Relative addressing (BCC, BCS, BNE, BEQ, BPL, BMI, BVC, BVS) " # address R/W description\r\n --- --------- --- ---------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch operand, increment PC. If branch is not taken, the instruction has ended.\r\n 3+ PC R If branch is taken, add operand to PCL.\r\n 4! PC* R Fix PCH.\r\n Notes: * The high byte of Program Counter (PCH) may be invalid\r\n at this time, i.e. it may be smaller or bigger by $100.\r\n\r\n + If branch is taken, this cycle will be executed.\r\n\r\n ! If branch occurs to different page, this cycle will be\r\n executed." , // --Indexed indirect addressing-- //22 Read instructions (LDA, ORA, EOR, AND, ADC, CMP, SBC, LAX) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address R read from effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled." , //23 Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address R read from effective address\r\n 7 address W write the value back to effective address, and do the operation on it\r\n 8 address W write the new value to effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled." , //24 Write instructions (STA, SAX) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address W write to effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled." , // --Indirect indexed addressing-- //25 Read instructions (LDA, EOR, AND, ORA, ADC, SBC, CMP) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6+ address+Y R read from effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100.\r\n\r\n + This cycle will be executed only if the effective address\r\n was invalid during cycle #5, i.e. page boundary was crossed." , //26 Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISC, DCP) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6 address+Y R read from effective address\r\n 7 address+Y W write the value back to effective address, and do the operation on it\r\n 8 address+Y W write the new value to effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100." , //27 Write instructions (STA, SHA) " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6 address+Y W write to effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100." , // --Absolute indirect addressing-- //28 JMP " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address low, increment PC\r\n 3 PC R fetch pointer address high, increment PC\r\n 4 pointer R fetch low address to latch\r\n 5 pointer+1* R fetch PCH, copy latch to PCL\r\n\r\n Note: * The PCH will always be fetched from the same page\r\n than PCL, i.e. page boundary crossing is not handled.\r\n\r\n How Real Programmers Acknowledge Interrupts" , //29 //HLT " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, does not increment PC\r\n 2 PC R fetch opcode, does not increment PC\r\n 3 PC R fetch opcode, does not increment PC\r\n 4 PC R fetch opcode, does not increment PC\r\n 5 PC R fetch opcode, does not increment PC\r\n 6 PC R fetch opcode, does not increment PC\r\n 7 PC R fetch opcode, does not increment PC\r\n ... PC R fetch opcode, does not increment PC\r\n\r\n Notes: This process goes on forever." }; // this explains what each isntruction does by their pnuemonic static string[] InstructionDocs = { //0 BRK "Break\n\nPushes PC to the stack.\n\nPushes processor status to the stack.\nPC' = ($FFFE)\nSP' = SP-3" , //1 ORA "Bitwise OR with Accumulator\n\nA' = A|M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //2 HLT "Halt\n\nHalts the processor." , //3 NOP "No operation." , //4 ASL "Arithmetic Shift Left\n\nM' = M<<1\n\nCflag' = (M>=0x80)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)" , //5 SLO "Arithemtic Shift Left then Bitwise OR with Accumulator\n\nM' = M<<1\nA' = A|M'\n\nCflag' = (M>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //6 PHP "Push Processor\n\nPushes processor status to the stack.\n\nSP' = SP-1" , //7 ANC "Bitwise AND with Accumulator then Set Carry if Negative\n\nA' = A & M\n\nCflag' = (A'>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //8 BPL "Branch on Plus\n\nIf the negative flag is not set, branch.\n\nPC' = PC + (!NFlag ? SignedOperand : 0)" , //9 CLC "Clear Carry Flag\n\nCFlag' = false" , //10 JSR "Jump to Subroutine\n\nPushes PC to the stack\nPC' = Operand\nSP' = SP-2" , //11 AND "Bitwise AND with Accumulator\n\nA' = A&M\n\nZFlag = (A'==0)\nNFlag' = (A'>=0x80)" , //12 RLA "Rotate Left then Bitwise AND with Accumulator\n\nM' = M<<1 + CFlag\nA' = A&M'\n\nCflag' = (M>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //13 BIT "Bit Test\n\nZFlag = (A=M)\nNFlag = ((M>>7)&1==1)\nVFlag = ((M>>6)&1==1)" , //14 ROL "Rotate Left\n\nM' = M<<1 + CFlag\n\nCflag' = (M>=0x80)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)" , //15 PLP "Pull Processor\n\nPulls processor status from the stack.\n\nSP' = SP+1" , //16 BMI "Branch on Minus\n\nIf the negative flag is set, branch.\n\nPC' = PC + (NFlag ? SignedOperand : 0)" , //17 SEC "Set Carry Flag\n\nCFlag' = true" , //18 RTI "Return from Interrupt\n\nPulls the processor from the stack.\nPulls PC from the stack.\n\nSP' = SP+3" , //19 EOR "Bitwise Exclusive OR with Accumulator\n\nA' = A^M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //20 SRE "Logical Shift Right then Bitwise Exclusive OR with Accumulator\n\nM' = M>>2\nA' = A^M'\n\nCFlag' = (M&1==1)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //21 LSR "Logical Shift Right\n\nM' = M>>2\n\nCFlag' = (M&1==1)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)" , //22 PHA "Push A\n\nPushes A to the stack.\n\nSP' = SP-1" , //23 ASR "Bitwise AND with Accumulator then Logical Shift Right Accumulator\n\nA' = ((A&M)>>1)\nCFlag' = ((A&M)&1==1)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //24 JMP "Jump\n\nPC' = M" , //25 BVC "Branch on Overflow Clear\n\nIf the overflow flag is not set, branch.\n\nPC' = PC + (!VFlag ? SignedOperand : 0)" , //26 CLI "Clear Interrupt Disable Flag\n\nIFlag' = false" , //27 RTS "Return from Subroutine\n\nPulls the PC from the stack.\\SP' = SP + 2" , //28 ADC "Add with Carry\n\nA' = A + M + CFlag\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //29 RRA "Rotate Right then Add With Carry\n\nM' = (M>>1)+Cflag*0x80\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //30 ROR "Rotate Right\n\nM' = M>>1 + CFlag*0x80\n\nCflag' = (M&1)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)" , //31 PLA "Pull A\n\nPull A from the stack\n\nSP' = SP+1" , //32 ARR "Bitwise AND with A then Rotate A and check bits\n\nA' = ((A&M)>>1)+Carry*0x80\n\nCFlag = ((A'>>6)&1==1)\nVFlag = ((A'>>5)&1==1)\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //33 BVS "Branch on Overflow Set\n\nIf the overflow flag is set, branch.\n\nPC' = PC + (VFlag ? SignedOperand : 0)" , //34 SEI "Set Interrupt Disable Flag\n\nIFlag' = true" , //35 STA "Store A\n\nM' = A" , //36 SAX "Store A and X\n\nM' = A&X" , //37 STY "Store Y\n\nM' = Y" , //38 STX "Store X\n\nM' = X" , //39 DEY "Decrement Y\n\nY' = Y-1\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)" , //40 TXA "Transfer X to A\n\nA' = X\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //41 ANE "Bitwise OR A with Magic then Bitwise AND with X AND with Memory\n\nA' = (A | magic) & X & M\n\n *Note: 'Magic' depends on the chip manufacturer\n 'Magic' is usually 00, EE, EF, FE, or FF" , //42 BCC "Branch on Carry Clear\n\nIf the carry flag is not set, branch.\n\nPC' = PC + (!CFlag ? SignedOperand : 0)" , //43 TXS "Transfer X to Stack Pointer\n\nSP' = X" , //44 SHA "Store Bitwise AND X with A AND the High Byte of the Operand Plus 1\n\nM' = A & X & (HIGH(Arg)+1)" , //45 TYA "Transfer Y to A\n\nA' = Y\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //46 SHY "Store Bitwise AND Y with The High Byte of the Operand Plus 1\n\nM' = Y & (HIGH(Arg)+1)" , //47 SHS "Transfer Bitwise AND A with X to Stack Pointer then Store Bitwise And Stack Pointer with the High Byte of the Operand Plus 1\n\nSP' = A&X\nM' = SP'&(HIGH(Arg)+1)" , //48 SHX "Store Bitwise AND X with The High Byte of the Operand Plus 1\n\nM' = X & (HIGH(Arg)+1)" , //49 LDY "Load Y\n\nY' = M\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)" , //50 LDA "Load A\n\nA' = M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //51 LDX "Load X\n\nX' = M\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //52 LAX "Load A X\n\nA' = M\nX' = M\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //53 TAY "Transfer A to Y\n\nY' = A\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)" , //54 TAX "Transfer A to X\n\nX' = A\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //55 LXA "Bitwise AND with A then Transfer A to X\n\nA' = A&M\nX'=A'\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //56 BCS "Branch on Carry Set\n\nIf the carry flag is set, branch.\n\nPC' = PC + (CFlag ? SignedOperand : 0)" , //57 CLV "Clear Overflow\n\nVFlag = false" , //58 TSX "Transfer Stack Pointer to X\n\nX' = SP" , //59 LAS "Transfer Bitwise AND with Stack Pointer to A, X, and Stack Pointer\n\nA' = M&SP\nX' = M&SP\nSP' = M&SP" , //60 CPY "Compare Y\n\nZFlag' = (Y==M)\nCFlag' = (Y>=M)\nNFlag' = (Y-M)>0x80" , //61 CMP "Compare A\n\nZFlag' = (A==M)\nCFlag' = (A>=M)\nNFlag' = (A-M)>0x80" , //62 DCP "Decrement then Compare A\n\nM' = M-1\nZFlag' = (A==M')\nCFlag' = (A>=M')\nNFlag' = (A-M')>0x80" , //63 DEC "Decrement\n\nM' = M-1\n\nZFlag' = (M'==0)\nNFlag' = (M'>=0x80)" , //64 INY "Increment Y\n\nY' = Y+1\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)" , //65 DEX "Decrement X\n\nX' = X-1\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //66 AXS "Load X with Subtraction with Bitwise AND X with A\n\nX' = (A&X)-M\n\nZFlag' = (X==M)\nCFlag' = (X>=M)\nNFlag' = (X-M)>0x80" , //67 BNE "Branch on Not Equal\n\nIf the zero flag is not set, branch.\n\nPC' = PC + (!ZFlag ? SignedOperand : 0)" , //68 CLD "Clear Decimal Flag\n\nDFlag = false" , //69 CPX "Compare X\n\nZFlag' = (X==M)\nCFlag' = (X>=M)\nNFlag' = (X-M)>0x80" , //70 SBC "Subtract with Carry\n\nA' = A+(0xFF-M)+CFlag\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)" , //71 ISC "Increment then subtract from accumulator\n\nM' = M+1\nA' = A+(0xFF-M')+CFlag\n\nVFlag' = ((A ^ (M' + A + Carry)) & ((M' + A + Carry) & M') & 0x80) == 0x80\nCFlag' = A + M' > 0xFF\nZflag' = (M'==0)\nNflag' = (M'>=0x80)" , //72 INC "Increment\n\nM' = M+1\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)" , //73 INX "Increment\n\nX' = X+1\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)" , //74 BEQ "Branch on Equal\n\nIf the zero flag is set, branch.\n\nPC' = PC + (ZFlag ? SignedOperand : 0)" , //75 SED "Set Decimal\n\nDFlag = true" }; // this table is referenced in the debugging stuff. // basically, for each index into this array, you can fetch an opcode's name, addressing mode, what flags/registers it can modify, documentation, and the number of cycles before a read/write. public static Op[] OpDocs = { new Op(0x00,"BRK","i" ,2,stackPChanges | pcChanges ,CycleDocs[0] ,InstructionDocs[0]), new Op(0x01,"ORA","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[1]), new Op(0x02,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x03,"SLO","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[5]), new Op(0x04,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]), new Op(0x05,"ORA","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[1]), new Op(0x06,"ASL","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[4]), new Op(0x07,"SLO","d" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[13],InstructionDocs[5]), new Op(0x08,"PHP","i" ,1,stackPChanges ,CycleDocs[3] ,InstructionDocs[6]), new Op(0x09,"ORA","#v" ,2,nFlag | zFlag | aChanges ,CycleDocs[7] ,InstructionDocs[1]), new Op(0x0A,"ASL","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[4]), new Op(0x0B,"ANC","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[7]), new Op(0x0C,"NOP","a" ,3,0 ,CycleDocs[9] ,InstructionDocs[3]), new Op(0x0D,"ORA","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[1]), new Op(0x0E,"ASL","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[4]), new Op(0x0F,"SLO","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[5]), new Op(0x10,"BPL","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[8]), new Op(0x11,"ORA","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[1]), new Op(0x12,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x13,"SLO","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[5]), new Op(0x14,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0x15,"ORA","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[1]), new Op(0x16,"ASL","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[4]), new Op(0x17,"SLO","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[5]), new Op(0x18,"CLC","i" ,1,cFlag ,CycleDocs[6] ,InstructionDocs[9]), new Op(0x19,"ORA","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[1]), new Op(0x1A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0x1B,"SLO","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[5]), new Op(0x1C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0x1D,"ORA","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[1]), new Op(0x1E,"ASL","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[4]), new Op(0x1F,"SLO","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[5]), new Op(0x20,"JSR","a" ,3,stackPChanges | pcChanges ,CycleDocs[5] ,InstructionDocs[10]), new Op(0x21,"AND","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[11]), new Op(0x22,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x23,"RLA","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[12]), new Op(0x24,"BIT","d" ,2,nFlag | zFlag | cFlag | vFlag ,CycleDocs[12],InstructionDocs[13]), new Op(0x25,"AND","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[11]), new Op(0x26,"ROL","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[14]), new Op(0x27,"RLA","d" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[13],InstructionDocs[12]), new Op(0x28,"PLP","i" ,1,cFlag|zFlag|iFlag|dFlag|vFlag|nFlag,CycleDocs[4],InstructionDocs[15]), new Op(0x29,"AND","#v" ,2,nFlag | zFlag | aChanges ,CycleDocs[7] ,InstructionDocs[11]), new Op(0x2A,"ROL","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[14]), new Op(0x2B,"ANC","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[7]), new Op(0x2C,"BIT","a" ,3,nFlag | zFlag | cFlag | vFlag ,CycleDocs[9] ,InstructionDocs[13]), new Op(0x2D,"AND","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[11]), new Op(0x2E,"ROL","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[14]), new Op(0x2F,"RLA","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[12]), new Op(0x30,"BMI","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[16]), new Op(0x31,"AND","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[11]), new Op(0x32,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x33,"RLA","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[12]), new Op(0x34,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0x35,"AND","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[11]), new Op(0x36,"ROL","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[14]), new Op(0x37,"RLA","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[12]), new Op(0x38,"SEC","i" ,1,cFlag ,CycleDocs[6] ,InstructionDocs[17]), new Op(0x39,"AND","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[11]), new Op(0x3A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0x3B,"RLA","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[12]), new Op(0x3C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0x3D,"AND","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[11]), new Op(0x3E,"ROL","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[14]), new Op(0x3F,"RLA","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[12]), new Op(0x40,"RTI","i" ,1,0xFF | stackPChanges | pcChanges ,CycleDocs[1] ,InstructionDocs[18]), new Op(0x41,"EOR","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[19]), new Op(0x42,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x43,"SRE","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[20]), new Op(0x44,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]), new Op(0x45,"EOR","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[19]), new Op(0x46,"LSR","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[21]), new Op(0x47,"SRE","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[13],InstructionDocs[20]), new Op(0x48,"PHA","i" ,1,stackPChanges ,CycleDocs[3] ,InstructionDocs[22]), new Op(0x49,"EOR","#v" ,2,nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[19]), new Op(0x4A,"LSR","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[21]), new Op(0x4B,"ASR","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[23]), new Op(0x4C,"JMP","a" ,3,0 ,CycleDocs[8] ,InstructionDocs[24]), new Op(0x4D,"EOR","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[19]), new Op(0x4E,"LSR","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[21]), new Op(0x4F,"SRE","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[20]), new Op(0x50,"BVC","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[25]), new Op(0x51,"EOR","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[19]), new Op(0x52,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x53,"SRE","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[20]), new Op(0x54,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0x55,"EOR","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[19]), new Op(0x56,"LSR","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[21]), new Op(0x57,"SRE","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[20]), new Op(0x58,"CLI","i" ,1,iFlag ,CycleDocs[6] ,InstructionDocs[26]), new Op(0x59,"EOR","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[19]), new Op(0x5A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0x5B,"SRE","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[20]), new Op(0x5C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0x5D,"EOR","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[19]), new Op(0x5E,"LSR","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[21]), new Op(0x5F,"SRE","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[20]), new Op(0x60,"RTS","i" ,1,stackPChanges | pcChanges ,CycleDocs[2] ,InstructionDocs[27]), new Op(0x61,"ADC","(d,x)" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[22],InstructionDocs[28]), new Op(0x62,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x63,"RRA","(d,x)" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[29]), new Op(0x64,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]), new Op(0x65,"ADC","d" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[12],InstructionDocs[28]), new Op(0x66,"ROR","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[30]), new Op(0x67,"RRA","d" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[13],InstructionDocs[29]), new Op(0x68,"PLA","i" ,1,stackPChanges | aChanges ,CycleDocs[4] ,InstructionDocs[31]), new Op(0x69,"ADC","#v" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[28]), new Op(0x6A,"ROR","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[30]), new Op(0x6B,"ARR","#v" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[7] ,InstructionDocs[32]), new Op(0x6C,"JMP","(a)" ,3,0 ,CycleDocs[28],InstructionDocs[24]), new Op(0x6D,"ADC","a" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[9] ,InstructionDocs[28]), new Op(0x6E,"ROR","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[30]), new Op(0x6F,"RRA","a" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[29]), new Op(0x70,"BVS","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[33]), new Op(0x71,"ADC","(d),y" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[25],InstructionDocs[28]), new Op(0x72,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0x73,"RRA","(d),y" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[29]), new Op(0x74,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0x75,"ADC","d,x" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[15],InstructionDocs[28]), new Op(0x76,"ROR","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[30]), new Op(0x77,"RRA","d,x" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[29]), new Op(0x78,"SEI","i" ,1,iFlag ,CycleDocs[6] ,InstructionDocs[34]), new Op(0x79,"ADC","a,y" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[18],InstructionDocs[28]), new Op(0x7A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0x7B,"RRA","a,y" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[29]), new Op(0x7C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0x7D,"ADC","a,x" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[18],InstructionDocs[28]), new Op(0x7E,"ROR","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[30]), new Op(0x7F,"RRA","a,x" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[29]), new Op(0x80,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]), new Op(0x81,"STA","(d,x)" ,2,memChanges ,CycleDocs[24],InstructionDocs[35]), new Op(0x82,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]), new Op(0x83,"SAX","(d,x)" ,2,memChanges ,CycleDocs[24],InstructionDocs[36]), new Op(0x84,"STY","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[37]), new Op(0x85,"STA","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[35]), new Op(0x86,"STX","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[38]), new Op(0x87,"SAX","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[36]), new Op(0x88,"DEY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[39]), new Op(0x89,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]), new Op(0x8A,"TXA","i" ,1,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[40]), new Op(0x8B,"ANE","#v" ,2,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[41]), new Op(0x8C,"STY","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[37]), new Op(0x8D,"STA","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[35]), new Op(0x8E,"STX","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[38]), new Op(0x8F,"SAX","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[36]), new Op(0x90,"BCC","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[42]), new Op(0x91,"STA","(d),y" ,2,memChanges ,CycleDocs[27],InstructionDocs[35]), new Op(0x92,"HLT","i" ,1,memChanges ,CycleDocs[29],InstructionDocs[2]), new Op(0x93,"SHA","(d),y" ,2,memChanges ,CycleDocs[27],InstructionDocs[44]), new Op(0x94,"STY","d,x" ,2,memChanges ,CycleDocs[17],InstructionDocs[37]), new Op(0x95,"STA","d,x" ,2,memChanges ,CycleDocs[17],InstructionDocs[35]), new Op(0x96,"STX","d,y" ,2,memChanges ,CycleDocs[17],InstructionDocs[38]), new Op(0x97,"SAX","d,y" ,2,memChanges ,CycleDocs[17],InstructionDocs[36]), new Op(0x98,"TYA","i" ,1,aChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[45]), new Op(0x99,"STA","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[35]), new Op(0x9A,"TXS","i" ,1,stackPChanges ,CycleDocs[6] ,InstructionDocs[43]), new Op(0x9B,"SHS","a,y" ,3,stackPChanges | memChanges,CycleDocs[20],InstructionDocs[47]), new Op(0x9C,"SHY","a,x" ,3,memChanges ,CycleDocs[20],InstructionDocs[46]), new Op(0x9D,"STA","a,x" ,3,memChanges ,CycleDocs[20],InstructionDocs[35]), new Op(0x9E,"SHX","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[48]), new Op(0x9F,"SHA","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[44]), new Op(0xA0,"LDY","#v" ,2,yChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[49]), new Op(0xA1,"LDA","(d,x)" ,2,aChanges | nFlag | zFlag ,CycleDocs[22],InstructionDocs[50]), new Op(0xA2,"LDX","#v" ,2,xChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[51]), new Op(0xA3,"LAX","(d,x)" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[22],InstructionDocs[52]), new Op(0xA4,"LDY","d" ,2,yChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[49]), new Op(0xA5,"LDA","d" ,2,aChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[50]), new Op(0xA6,"LDX","d" ,2,xChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[51]), new Op(0xA7,"LAX","d" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[52]), new Op(0xA8,"TAY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[53]), new Op(0xA9,"LDA","#v" ,2,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[50]), new Op(0xAA,"TAX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[54]), new Op(0xAB,"LXA","#v" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[55]), new Op(0xAC,"LDY","a" ,3,yChanges | nFlag | zFlag ,CycleDocs[9] ,InstructionDocs[49]), new Op(0xAD,"LDA","a" ,3,aChanges | nFlag | zFlag ,CycleDocs[9] ,InstructionDocs[50]), new Op(0xAE,"LDX","a" ,3,xChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[51]), new Op(0xAF,"LAX","a" ,3,xChanges | aChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[52]), new Op(0xB0,"BCS","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[56]), new Op(0xB1,"LDA","(d),y" ,2,aChanges | nFlag | zFlag ,CycleDocs[25],InstructionDocs[50]), new Op(0xB2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0xB3,"LAX","(d),y" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[25],InstructionDocs[52]), new Op(0xB4,"LDY","d,x" ,2,yChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[49]), new Op(0xB5,"LDA","d,x" ,2,aChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[50]), new Op(0xB6,"LDX","d,y" ,2,xChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[51]), new Op(0xB7,"LAX","d,y" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[52]), new Op(0xB8,"CLV","i" ,1,vFlag ,CycleDocs[6] ,InstructionDocs[57]), new Op(0xB9,"LDA","a,y" ,3,aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[50]), new Op(0xBA,"TSX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[58]), new Op(0xBB,"LAS","a,y" ,3,nFlag | zFlag | aChanges | xChanges | stackPChanges ,CycleDocs[18],InstructionDocs[59]), new Op(0xBC,"LDY","a,x" ,3,yChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[49]), new Op(0xBD,"LDA","a,x" ,3,aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[50]), new Op(0xBE,"LDX","a,y" ,3,xChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[51]), new Op(0xBF,"LAX","a,y" ,3,xChanges | aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[52]), new Op(0xC0,"CPY","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[60]), new Op(0xC1,"CMP","(d,x)" ,2,nFlag | zFlag | cFlag ,CycleDocs[22],InstructionDocs[61]), new Op(0xC2,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]), new Op(0xC3,"DCP","(d,x)" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[23],InstructionDocs[62]), new Op(0xC4,"CPY","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[60]), new Op(0xC5,"CMP","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[61]), new Op(0xC6,"DEC","d" ,2,memChanges | nFlag | zFlag ,CycleDocs[13],InstructionDocs[63]), new Op(0xC7,"DCP","d" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[13],InstructionDocs[62]), new Op(0xC8,"INY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[64]), new Op(0xC9,"CMP","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[61]), new Op(0xCA,"DEX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[65]), new Op(0xCB,"AXS","#v" ,2,memChanges | nFlag | cFlag | zFlag ,CycleDocs[7] ,InstructionDocs[66]), new Op(0xCC,"CPY","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[60]), new Op(0xCD,"CMP","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[61]), new Op(0xCE,"DEC","a" ,3,memChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[63]), new Op(0xCF,"DCP","a" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[10],InstructionDocs[62]), new Op(0xD0,"BNE","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[67]), new Op(0xD1,"CMP","(d),y" ,2,nFlag | zFlag | cFlag ,CycleDocs[25],InstructionDocs[61]), new Op(0xD2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0xD3,"DCP","(d),y" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[26],InstructionDocs[62]), new Op(0xD4,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0xD5,"CMP","d,x" ,2,nFlag | zFlag | cFlag ,CycleDocs[15],InstructionDocs[61]), new Op(0xD6,"DEC","d,x" ,2,memChanges | nFlag | zFlag ,CycleDocs[16],InstructionDocs[63]), new Op(0xD7,"DCP","d,x" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[16],InstructionDocs[62]), new Op(0xD8,"CLD","i" ,1,dFlag ,CycleDocs[6] ,InstructionDocs[68]), new Op(0xD9,"CMP","a,y" ,3,nFlag | zFlag | cFlag ,CycleDocs[18],InstructionDocs[61]), new Op(0xDA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0xDB,"DCP","a,x" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[19],InstructionDocs[62]), new Op(0xDC,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0xDD,"CMP","a,x" ,3,nFlag | zFlag | cFlag ,CycleDocs[18],InstructionDocs[61]), new Op(0xDE,"DEC","a,x" ,3,memChanges | nFlag | zFlag ,CycleDocs[19],InstructionDocs[63]), new Op(0xDF,"DCP","a,x" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[19],InstructionDocs[62]), new Op(0xE0,"CPX","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[69]), new Op(0xE1,"SBC","(d,x)" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[22],InstructionDocs[70]), new Op(0xE2,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]), new Op(0xE3,"ISC","(d,x)" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[23],InstructionDocs[71]), new Op(0xE4,"CPX","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[69]), new Op(0xE5,"SBC","d" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[12],InstructionDocs[70]), new Op(0xE6,"INC","d" ,2,memChanges | nFlag | zFlag ,CycleDocs[13],InstructionDocs[72]), new Op(0xE7,"ISC","d" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[13],InstructionDocs[71]), new Op(0xE8,"INX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[73]), new Op(0xE9,"SBC","#v" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[7] ,InstructionDocs[70]), new Op(0xEA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0xEB,"SBC","#v" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[7] ,InstructionDocs[70]), new Op(0xEC,"CPX","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[69]), new Op(0xED,"SBC","a" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[9] ,InstructionDocs[70]), new Op(0xEE,"INC","a" ,3,memChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[72]), new Op(0xEF,"ISC","a" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[10],InstructionDocs[71]), new Op(0xF0,"BEQ","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[74]), new Op(0xF1,"SBC","(d),y" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[25],InstructionDocs[70]), new Op(0xF2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]), new Op(0xF3,"ISC","(d),y" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[26],InstructionDocs[71]), new Op(0xF4,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]), new Op(0xF5,"SBC","d,x" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[15],InstructionDocs[70]), new Op(0xF6,"INC","d,x" ,2,memChanges | nFlag | zFlag ,CycleDocs[16],InstructionDocs[72]), new Op(0xF7,"ISC","d,x" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[16],InstructionDocs[71]), new Op(0xF8,"SED","i" ,1,dFlag ,CycleDocs[6] ,InstructionDocs[75]), new Op(0xF9,"SBC","a,y" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[18],InstructionDocs[70]), new Op(0xFA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]), new Op(0xFB,"ISC","a,x" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[19],InstructionDocs[71]), new Op(0xFC,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]), new Op(0xFD,"SBC","a,x" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[18],InstructionDocs[70]), new Op(0xFE,"INC","a,x" ,3,memChanges | nFlag | zFlag ,CycleDocs[19],InstructionDocs[72]), new Op(0xFF,"ISC","a,x" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[19],InstructionDocs[71]) }; } } ================================================ FILE: App.config ================================================ ================================================ FILE: Emulator.cs ================================================ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Text; using TriCNES.mappers; namespace TriCNES { // Coin's Contrabulous Cartswapulator! public class Cartridge { // Since I made this emulator with mid-instruction cartridge swapping in mind, the cartridge class holds information about the cartridge that would persist when swapped in and out. public Emulator Emu; // Mostly for triggering / clearing the IRQ from mapper function. public string Name; // For debugging public byte[] ROM; // The entire .nes file public byte[] PRGROM; // The entire program rom portion of the .nes file public byte[] CHRROM; // The entire character rom portion of the .nes file public byte MemoryMapper; // Header info: what mapper chip is this cartridge using? public byte SubMapper; // Header Info: what variant of the mapper chip are we using? public byte PRG_Size; // Header info: how many kb of PRG data does this cartridge have? public byte CHR_Size; // Header info: how many kb of CHR data does this cartridge have? public byte PRG_SizeMinus1; // PRG_Size-1; This is frequently used when grabbing data from PRG banks public byte[] CHRRAM; // If this cartridge has character RAM, this array is used. public bool UsingCHRRAM; // Header info: CHR RAM doesn't exist on all cartridges. public byte[] PRGRAM; // PRG RAM / Battery backed save RAM. public bool AlternativeNametableArrangement; // Header info: Some mapper chips support "alternative nametable arrangements", which are mapper-specific. public byte[] PRGVRAM; // PRG VRAM, for the alternative nametable arrangements. public Cartridge(string filepath) // Constructor from file path { ROM = File.ReadAllBytes(filepath); // Reads the file from the provided file path, and stores every byte into an array. // The iNES header isn't actually part of the physical cartridge. // Rather, the values of the iNES header are manually added to provide extra information to emulators. // Info such as "what mapper chip", "how many CHR banks?" and even "how should we mirror the nametables?" are part of this header. MemoryMapper = (byte)(ROM[7] & 0xF0); // Parsing the iNES header to determine what mapper chip this cartridge uses. MemoryMapper |= (byte)(ROM[6] >> 4); // The upper nybble of byte 6, bitwise OR with the upper nybble of byte 7. SubMapper = (byte)((ROM[8] & 0xF0) >> 4); PRG_Size = ROM[4]; // Parsing the iNES header to determine how many kb of PRG data exists on this cartridge. CHR_Size = ROM[5]; // Parsing the iNES header to determine how many kb of CHR data exists on this cartridge. PRG_SizeMinus1 = (byte)(PRG_Size - 1); // This value is occasionally used whenever a mapper has a fixed bank from the end of the PRG data, like address $E000 in the MMC3 chip. UsingCHRRAM = CHR_Size == 0; // If CHR_Size == 0, this is using CHR RAM PRGROM = new byte[PRG_Size * 0x4000]; // 0x4000 bytes of PRG ROM, multiplied by byte 4 of the iNES header. CHRROM = new byte[CHR_Size * 0x2000]; // 0x2000 bytes of CHR ROM, multiplied by byte 5 of the iNES header. CHRRAM = new byte[0x2000]; // CHR RAM always has 2 kibibytes NametableHorizontalMirroring = ((ROM[6] & 1) == 0); // The style in which the nametable is mirrored is part of the iNES header. AlternativeNametableArrangement = ((ROM[6] & 8) != 0); // Some mappers support other arrangements. if (AlternativeNametableArrangement) { PRGVRAM = new byte[0x800]; } Array.Copy(ROM, 0x10, PRGROM, 0, PRGROM.Length); // This sets up the PRG ROM array with the values from the .nes file Array.Copy(ROM, 0x10 + PRGROM.Length, CHRROM, 0, CHRROM.Length); // This sets up the CHR ROM array with the values from the .nes file // at this point, the ROM byte array is no longer needed, so null it to free up its memory. ROM = null; PRGRAM = new byte[0x2000]; // PRG RAM probably has different lengths depending on the mapper, but this emulator doesn't yet support any mappers in which that length isn't 2 kibibytes. Name = filepath; // For debugging, it's nice to see the file name sometimes. switch (MemoryMapper) { default: case 0: MapperChip = new Mapper_NROM(); break; case 1: MapperChip = new Mapper_MMC1(); break; case 2: MapperChip = new Mapper_UxROM(); break; case 3: MapperChip = new Mapper_CNROM(); break; case 4: MapperChip = new Mapper_MMC3(); break; case 7: MapperChip = new Mapper_AOROM(); break; case 9: MapperChip = new Mapper_MMC2(); break; case 69: MapperChip = new Mapper_FME7(); break; } MapperChip.Cart = this; } public DiskDrive FDS; // The famicom disk system disk drive. public Cartridge(string filepath, string FDSBIOS_filepath) { ROM = File.ReadAllBytes(FDSBIOS_filepath); // Reads the file from the provided file path, and stores every byte into an array. FDS = new DiskDrive(); FDS.InsertDisk(filepath); PRGRAM = new byte[0x8000]; // The FDS has 32Kib of PRG RAM! CHRRAM = new byte[0x2000]; // and 8 Kib of CHR RAM. Name = filepath; // For debugging, it's nice to see the file name sometimes. MapperChip = new Mapper_FDS(ROM); MapperChip.Cart = this; FDS.Cart = this; } public bool NametableHorizontalMirroring; public Mapper MapperChip; } public class Mapper { public Cartridge Cart; public byte dataBus; public byte observedDataBus; public bool dataPinsAreNotFloating; public bool observedDataPinsAreNotFloating; // Default to NROM behavior. public virtual void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0x8000) { data = Cart.PRGROM[Address & (Cart.PRGROM.Length - 1)]; // Get the address from the ROM file. If the ROM only has $4000 bytes, this will make addresses > $BFFF mirrors of $8000 through $BFFF. notFloating = true; } //open bus if (notFloating) { EndFetchPRG(Observe, data); } return; } public virtual void StorePRG(ushort Address, byte Input) { } public virtual byte FetchCHR(ushort Address, bool Observe) { return Cart.CHRROM[Address & 0x1FFF]; } public virtual byte FetchPPU() { // This will always use the upper 8 bits of the address bus | the octal latch. This replaces the lower 8 bits of the address bus. ushort Address = (ushort)((Cart.Emu.PPU_AddressBus & 0x3F00) | Cart.Emu.PPU_OctalLatch); bool CIRAM = Address >= 0x2000; if (!CIRAM) { if (Cart.UsingCHRRAM) { Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.CHRRAM[Address]; } else { //Pattern Table Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.MapperChip.FetchCHR(Address, false); } } else // if the VRAM address is >= $2000, we need to consider nametable mirroring. { Address = MirrorNametable(Address); Address &= 0x7FF; Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.Emu.VRAM[Address]; } return (byte)Cart.Emu.PPU_AddressBus; } public virtual ushort MirrorNametable(ushort Address) { if (!Cart.NametableHorizontalMirroring) { return (ushort)(Address & 0x37FF); // mask away $0800 } else // horizontal { return (ushort)((Address & 0x33FF) | ((Address & 0x0800) >> 1)); // mask away $0C00, bit 10 becomes the former bit 11 } } public virtual List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } return State; } public virtual void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } exitIndex = p; } public virtual void PPUClock() // runs every PPU clock. (See MMC3) { } public virtual void CPUClock() // runs every CPU clock. (See Sunsoft FME-7) { } public virtual void CPUClockRise() // runs every time the CPU clock rises. (See MMC3) { } public virtual void FDS_ByteTransferFlag() { } public virtual byte FDS_Get4025() { return 0; } protected void EndFetchPRG(bool Observe, byte data) { if (!Observe) { dataPinsAreNotFloating = true; dataBus = data; } else { observedDataPinsAreNotFloating = true; observedDataBus = data; } } } public class DiskDrive { public Cartridge Cart; public byte[] Disk; public byte ShiftRegister; public byte ShiftRegisterLatch; public bool IRQ; public ushort clock; // every 1792 master clock cycles, public ushort DiskAddress; public byte DiskAddressFine; public bool Status_ByteTransferFlag; public void Clock() { clock++; if(clock % 244 == 0) { if ((Cart.MapperChip.FDS_Get4025() & 0x46) == 0x44) { // reading byte ShiftBit = (byte)((Disk[DiskAddress] << DiskAddressFine) & 1); ShiftRegister <<= 1; ShiftRegister |= ShiftBit; DiskAddressFine++; if (DiskAddressFine == 8) { DiskAddressFine = 0; DiskAddress++; } } } if(clock == 1792) { clock = 0; DiskAddressFine = 0; ShiftRegisterLatch = ShiftRegister; // disk drive is ready. // raise the byte transfer flag! Status_ByteTransferFlag = true; Cart.MapperChip.FDS_ByteTransferFlag(); // Trigger an IRQ if $4025.7 is set. } } public void InsertDisk(string filepath) { Disk = File.ReadAllBytes(filepath); // Reads the file from the provided file path, and stores every byte into an array. } } public class Emulator { public Cartridge Cart; // The idea behind this emulator is that this value could be changed at any time if you so desire. public byte PPUClock; // Counts down from 4. When it's 0, a PPU cycle occurs. public byte CPUClock; // Counts down from 12. When it's 0, a CPU cycle occurs. public byte MasterClock; // Counts up every master clock cycle. Resets at 24. public byte APUAlignment; // at power on or reset, is this a get/put, and how long until the DMC DMA? public bool APU_PutCycle = false; // The APU needs to know if this is a "get" or "put" cycle. public byte[] OAM = new byte[0x100]; // Object Attribute Memory is 256 bytes. public byte[] OAM2 = new byte[32]; // Secondary OAM is specifically the 8 objects being rendered on the current scanline. public byte SecondaryOAMSize = 0; // This is a count of how many objects are currently in secondary OAM. public byte OAM2Address = 0; // During sprite evaluation, the current SecondaryOAM Address is used to track what byte is set of a given dot. public bool SecondaryOAMFull = false; // If full and another object exists in the same scanline, the PPU Sprite OVerflow flag is set. public byte SpriteEvaluationTick = 0; // During sprite evaluation, there's a switch statement that determines what to do on a given dot. This determines which action to take. public bool OAMAddressOverflowedDuringSpriteEvaluation = false; // If the OAM address overflows during sprite evaluation, there's a few bugs that can occur. public byte[] RAM = new byte[0x800]; // There are 0x800 bytes of RAM public byte[] VRAM = new byte[0x800]; // There are 0x800 bytes of VRAM public byte[] PaletteRAM = new byte[0x20]; // there are 0x20 bytes of palette RAM public ushort programCounter = 0; // The PC. What address is currently being executed? public byte opCode = 0; // The first CPU cycle of an instruction will read the opcode. This determines how the rest of the cycles will behave. public int totalCycles; // For debugging. This is just a count of how many CPU cycles have occurred since the console booted up. public byte stackPointer = 0x00; // The Stack pointer is used during pushing/popping values with the stack. This determines which address will be read or written to. public bool flag_Carry; // The Carry flag is used in BCC and BCS instructions, and is set when the result of an operation over/underflows. public bool flag_Zero; // The Zero flag is used in BNE and BEQ instructions, and is set when the result of an operation is zero. public bool flag_Interrupt; // The Interrupt suppression flag will suppress IRQ's. public bool flag_Decimal; // The NES doesn't use this flag. public bool flag_Overflow; // The Carry flag is used in BVC and BVS instructions, and is set when the result of an operation over/underflows and the sign of the result is the same as the value before the operation. public bool flag_Negative; // The Zero flag is used in BPL and BMI instructions, and is set when the result of an operation is negative. (bit 7 is set) byte status = 0; // This is a byte representation of all the flags. public byte A = 0; // The Accumulator, or "A Register" public byte X = 0; // The X Register public byte Y = 0; // The Y Register public byte H = 0; // The High byte of the target address. A couple unofficial instructions use this value. public bool IgnoreH; // However, with a well-timed DMA, the H register isn't actually part of the equation on some of those. public byte dataBus = 0; // The Data Bus. public byte internalBus = 0; // The Data Bus (internal to address $4015) public ushort addressBus = 0;// The Address Bus. "Where are we reading/writing" public byte specialBus = 0; // The Special Bus is used in certain instructions. (The special bus is mostly used in half-CPU-cycles connecting the various registers to the alu) public byte dl = 0; // Data Latch. This holds values between CPU cycles that are used in later cycles within an instruction. public byte operationCycle = 0; // This tracks what cycle of a given instruction is being emulated. Cycle 0 fetches the opcode, and all cycles after that have specific logic depending on which cycle needs emulated next. public ushort temporaryAddress; // I use this to temporarily modify the value of the address bus for some if statements. This is mostly for checking if the low byte under/over flows. public static uint[] NesPalInts = { // each uint represents the ARGB components of a color. // there's 64 colors, but this is also how I implement specific values for the PPU's emphasis bits. // default palette: 0xFF656565, 0xFF002A84, 0xFF1513A2, 0xFF3A019E, 0xFF59007A, 0xFF6A003E, 0xFF680800, 0xFF531D00, 0xFF323400, 0xFF0D4600, 0xFF004F00, 0xFF004C09, 0xFF003F4B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAEAEAE, 0xFF175FD6, 0xFF4341FF, 0xFF7529FA, 0xFF9E1DCA, 0xFFB4207B, 0xFFB13322, 0xFF964E00, 0xFF6A6C00, 0xFF398400, 0xFF0F9000, 0xFF008D33, 0xFF007B8C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFEFFFF, 0xFF66AFFF, 0xFF9390FF, 0xFFC578FF, 0xFFEE6CFF, 0xFFFF6FCA, 0xFFFF8271, 0xFFE69E25, 0xFFBABC00, 0xFF88D501, 0xFF5EE132, 0xFF47DD82, 0xFF4ACBDC, 0xFF4E4E4E, 0xFF000000, 0xFF000000, 0xFFFEFFFF, 0xFFC0DEFF, 0xFFD2D1FF, 0xFFE7C7FF, 0xFFF8C2FF, 0xFFFFC3E9, 0xFFFFCBC4, 0xFFF5D7A5, 0xFFE2E394, 0xFFCEED96, 0xFFBCF2AA, 0xFFB3F1CB, 0xFFB4E9F0, 0xFFB6B6B6, 0xFF000000, 0xFF000000, // emphasize red: 0xFF66423E, 0xFF000D58, 0xFF150075, 0xFF380075, 0xFF560058, 0xFF670027, 0xFF680000, 0xFF530D00, 0xFF341E00, 0xFF102B00, 0xFF003000, 0xFF002B00, 0xFF001C24, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAF7E78, 0xFF19379A, 0xFF4320C1, 0xFF720FC1, 0xFF9A089A, 0xFFB10F59, 0xFFB2220F, 0xFF963700, 0xFF6C4D00, 0xFF3D5F00, 0xFF166500, 0xFF005F0C, 0xFF004B55, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFC0B8, 0xFF6878DB, 0xFF9361FF, 0xFFC24FFF, 0xFFEA49DB, 0xFFFF4F99, 0xFFFF634E, 0xFFE77808, 0xFFBC8F00, 0xFF8DA000, 0xFF65A708, 0xFF4DA04A, 0xFF4C8D95, 0xFF4F2F2B, 0xFF000000, 0xFF000000, 0xFFFFC0B8, 0xFFC1A2C6, 0xFFD399D6, 0xFFE792D6, 0xFFF78FC6, 0xFFFF92AB, 0xFFFF9A8C, 0xFFF6A26F, 0xFFE4AC5F, 0xFFD1B35F, 0xFFC0B66F, 0xFFB7B38B, 0xFFB6ABA9, 0xFFB7857E, 0xFF000000, 0xFF000000, // emphasize green: 0xFF395D2C, 0xFF002452, 0xFF000D6A, 0xFF140064, 0xFF2D0041, 0xFF3E0010, 0xFF3F0300, 0xFF301800, 0xFF162F00, 0xFF004200, 0xFF004C00, 0xFF004700, 0xFF003924, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF71A360, 0xFF005691, 0xFF1939B1, 0xFF4020A9, 0xFF61127B, 0xFF78183A, 0xFF792C00, 0xFF654800, 0xFF426600, 0xFF1B7E00, 0xFF008D00, 0xFF00860A, 0xFF007254, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAEF099, 0xFF32A3CB, 0xFF5684EB, 0xFF7E6BE3, 0xFF9E5DB5, 0xFFB66472, 0xFFB77728, 0xFFA39400, 0xFF7FB200, 0xFF57CB00, 0xFF37D900, 0xFF1FD342, 0xFF1EBF8D, 0xFF27471C, 0xFF000000, 0xFF000000, 0xFFAEF099, 0xFF7BD0AD, 0xFF8AC3BA, 0xFF9AB9B7, 0xFFA8B3A4, 0xFFB1B689, 0xFFB2BE6A, 0xFFAACA50, 0xFF9BD643, 0xFF8BE146, 0xFF7DE65A, 0xFF74E475, 0xFF73DC94, 0xFF77AA65, 0xFF000000, 0xFF000000, // emphasize red + green: 0xFF3F3F25, 0xFF000B46, 0xFF00005D, 0xFF18005A, 0xFF2F003F, 0xFF40000E, 0xFF410000, 0xFF320A00, 0xFF191A00, 0xFF002800, 0xFF002F00, 0xFF002A00, 0xFF001B1C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF797A55, 0xFF003581, 0xFF201F9F, 0xFF450D9C, 0xFF640478, 0xFF7B0A36, 0xFF7C1E00, 0xFF683200, 0xFF474900, 0xFF225B00, 0xFF036400, 0xFF005D00, 0xFF004A4A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBABB8B, 0xFF3E75B7, 0xFF605ED6, 0xFF854CD2, 0xFFA443AE, 0xFFBB4A6C, 0xFFBD5D21, 0xFFA87200, 0xFF878900, 0xFF619B00, 0xFF42A400, 0xFF2B9D34, 0xFF2A8A7F, 0xFF2C2D15, 0xFF000000, 0xFF000000, 0xFFBABB8B, 0xFF879E9D, 0xFF9595AA, 0xFFA48DA8, 0xFFB18999, 0xFFBB8C7E, 0xFFBB945F, 0xFFB39D48, 0xFFA5A63B, 0xFF96AE3D, 0xFF89B14C, 0xFF7FAF67, 0xFF7FA686, 0xFF80805A, 0xFF000000, 0xFF000000, // emphasize blue: 0xFF47477C, 0xFF001A8C, 0xFF0B0AA9, 0xFF2900A3, 0xFF410081, 0xFF4D004A, 0xFF49000D, 0xFF340400, 0xFF141500, 0xFF002800, 0xFF003300, 0xFF00331B, 0xFF002A58, 0xFF000000, 0xFF00000A, 0xFF00000A, 0xFF8584CD, 0xFF0B49E2, 0xFF3533FF, 0xFF5D1AFF, 0xFF7D0CD4, 0xFF8D0B8B, 0xFF86173A, 0xFF6B2C00, 0xFF414200, 0xFF195B00, 0xFF006904, 0xFF006A4C, 0xFF005E9E, 0xFF00000A, 0xFF00000A, 0xFF00000A, 0xFFC9C8FF, 0xFF4E8CFF, 0xFF7876FF, 0xFFA05CFF, 0xFFC14EFF, 0xFFD14DE4, 0xFFCB5A92, 0xFFAF6E4C, 0xFF848525, 0xFF5C9E2D, 0xFF3BAD5B, 0xFF2BADA5, 0xFF32A1F7, 0xFF343362, 0xFF00000A, 0xFF00000A, 0xFFC9C8FF, 0xFF96AFFF, 0xFFA8A6FF, 0xFFB89BFF, 0xFFC696FF, 0xFFCC95FF, 0xFFCA9AEA, 0xFFBEA3CD, 0xFFACACBD, 0xFF9CB7C0, 0xFF8FBDD3, 0xFF88BDF2, 0xFF8BB8FF, 0xFF8B8AD6, 0xFF00000A, 0xFF00000A, // emphasize red + blue: 0xFF46344C, 0xFF00085C, 0xFF0B007A, 0xFF260077, 0xFF3D005C, 0xFF4A0030, 0xFF480000, 0xFF340000, 0xFF140F00, 0xFF001D00, 0xFF002400, 0xFF002200, 0xFF001829, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF846B8C, 0xFF0A30A1, 0xFF3419C8, 0xFF5907C5, 0xFF7800A1, 0xFF880166, 0xFF860E23, 0xFF6B2300, 0xFF403900, 0xFF1C4C00, 0xFF005400, 0xFF00521A, 0xFF00445C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFC7A7D2, 0xFF4C6BE8, 0xFF7754FF, 0xFF9C42FF, 0xFFBB39E7, 0xFFCC3CAB, 0xFFCA4968, 0xFFAE5E23, 0xFF837500, 0xFF5E8700, 0xFF3F9023, 0xFF2E8E5F, 0xFF3080A2, 0xFF332338, 0xFF000000, 0xFF000000, 0xFFC7A7D2, 0xFF948EDB, 0xFFA685EB, 0xFFB57DEA, 0xFFC27ADB, 0xFFC97BC2, 0xFFC880A7, 0xFFBD898A, 0xFFAB927A, 0xFF9C9A7B, 0xFF8F9D8A, 0xFF889CA3, 0xFF8997BE, 0xFF8A7093, 0xFF000000, 0xFF000000, // emphasize green + blue: 0xFF304144, 0xFF00155A, 0xFF000471, 0xFF11006B, 0xFF2A0049, 0xFF36001C, 0xFF350000, 0xFF250300, 0xFF0C1300, 0xFF002600, 0xFF003100, 0xFF002F00, 0xFF002531, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF647D80, 0xFF00429E, 0xFF152CBC, 0xFF3C13B4, 0xFF5C0586, 0xFF6D074B, 0xFF6B1509, 0xFF572900, 0xFF364000, 0xFF0E5900, 0xFF006700, 0xFF006424, 0xFF005766, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF9EBEC3, 0xFF2D83E1, 0xFF4E6CFF, 0xFF7653F8, 0xFF9745C9, 0xFFA7478D, 0xFFA5554A, 0xFF916A12, 0xFF6F8100, 0xFF479A00, 0xFF27A82A, 0xFF16A566, 0xFF1898A9, 0xFF1F2E30, 0xFF000000, 0xFF000000, 0xFF9EBEC3, 0xFF6FA6CF, 0xFF7D9CDC, 0xFF8E92D8, 0xFF9B8CC5, 0xFFA28DAD, 0xFFA19391, 0xFF999C7A, 0xFF8BA56D, 0xFF7AAF70, 0xFF6DB584, 0xFF66B49C, 0xFF67AEB8, 0xFF6A8386, 0xFF000000, 0xFF000000, // emphasize red + green + blue: 0xFF343434, 0xFF00084B, 0xFF000061, 0xFF14005F, 0xFF2B0044, 0xFF380017, 0xFF360000, 0xFF270000, 0xFF0E0F00, 0xFF001D00, 0xFF002400, 0xFF002200, 0xFF001721, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6A6A6A, 0xFF003088, 0xFF1B19A7, 0xFF4007A3, 0xFF5F007F, 0xFF6F0144, 0xFF6D0E02, 0xFF592300, 0xFF383900, 0xFF134B00, 0xFF005400, 0xFF00520F, 0xFF004451, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA6A6A6, 0xFF356BC5, 0xFF5654E3, 0xFF7B42E0, 0xFF9B39BB, 0xFFAB3C80, 0xFFA9493D, 0xFF955E04, 0xFF737500, 0xFF4E8700, 0xFF2F900E, 0xFF1E8E4A, 0xFF20808D, 0xFF232323, 0xFF000000, 0xFF000000, 0xFFA6A6A6, 0xFF788EB3, 0xFF8585C0, 0xFF957DBE, 0xFFA279AF, 0xFFA87A96, 0xFFA8807B, 0xFF9F8964, 0xFF919257, 0xFF829A59, 0xFF759D68, 0xFF6E9C80, 0xFF6F979C, 0xFF707070, 0xFF000000, 0xFF000000, // colorburst 0xFF010900 }; int chosenColor; // During screen rendering, this value is the index into the color array. public DirectBitmap Screen = new DirectBitmap(256, 240); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster. public DirectBitmap NTSCScreen = new DirectBitmap(256 * 8, 240); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster. public DirectBitmap BorderedScreen = new DirectBitmap(341, 262); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster. public DirectBitmap BorderedNTSCScreen = new DirectBitmap(341 * 8, 262); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster. //Debugging public bool Logging; // If set, the tracelogger will record all instructions ran. public bool LoggingPPU; public StringBuilder DebugLog; // This is where the tracelogger is recording. public Emulator() // The instantiator for this class { RAM = new byte[0x800]; A = 0; // The A, X, and Y registers are all initialized with 0 when the console boots up. X = 0; Y = 0; VRAM = new byte[0x800]; OAM = new byte[0x100]; OAM2 = new byte[32]; for (int oam2_init = 0; oam2_init < OAM2.Length; oam2_init++) { OAM2[oam2_init] = 0xFF; } // set up RAM and PPU RAM Pattern int i = 0; while (i < 0x800) { int j = i & 0x2; bool swap = (i & 0x1F) >= 0x10; if (j < 0x2 == !swap) { VRAM[i] = 0xF0; RAM[i] = 0xF0; } else { VRAM[i] = 0x0F; RAM[i] = 0x0F; } i++; } bool BlarggPalette = false; // There's a PPU test cartridge that expects a very specific palette when you power on the console. if (BlarggPalette) { //use the palette that Blargg's NES uses PaletteRAM[0x00] = 0x09; PaletteRAM[0x01] = 0x01; PaletteRAM[0x02] = 0x00; PaletteRAM[0x03] = 0x01; PaletteRAM[0x04] = 0x00; PaletteRAM[0x05] = 0x02; PaletteRAM[0x06] = 0x02; PaletteRAM[0x07] = 0x0D; PaletteRAM[0x08] = 0x08; PaletteRAM[0x09] = 0x10; PaletteRAM[0x0A] = 0x08; PaletteRAM[0x0B] = 0x24; PaletteRAM[0x0C] = 0x00; PaletteRAM[0x0D] = 0x00; PaletteRAM[0x0E] = 0x04; PaletteRAM[0x0F] = 0x2C; PaletteRAM[0x10] = 0x09; PaletteRAM[0x11] = 0x01; PaletteRAM[0x12] = 0x34; PaletteRAM[0x13] = 0x03; PaletteRAM[0x14] = 0x00; PaletteRAM[0x15] = 0x04; PaletteRAM[0x16] = 0x00; PaletteRAM[0x17] = 0x14; PaletteRAM[0x18] = 0x08; PaletteRAM[0x19] = 0x3A; PaletteRAM[0x1A] = 0x00; PaletteRAM[0x1B] = 0x02; PaletteRAM[0x1C] = 0x00; PaletteRAM[0x1D] = 0x20; PaletteRAM[0x1E] = 0x2C; PaletteRAM[0x1F] = 0x08; } else // Except my actual console has a different palette than Blargg, so I use this palette instead. { // use the palette that my NES uses PaletteRAM[0x00] = 0x00; PaletteRAM[0x01] = 0x00; PaletteRAM[0x02] = 0x28; PaletteRAM[0x03] = 0x00; PaletteRAM[0x04] = 0x00; PaletteRAM[0x05] = 0x08; PaletteRAM[0x06] = 0x00; PaletteRAM[0x07] = 0x00; PaletteRAM[0x08] = 0x00; PaletteRAM[0x09] = 0x01; PaletteRAM[0x0A] = 0x01; PaletteRAM[0x0B] = 0x20; PaletteRAM[0x0C] = 0x00; PaletteRAM[0x0D] = 0x08; PaletteRAM[0x0E] = 0x00; PaletteRAM[0x0F] = 0x02; PaletteRAM[0x10] = 0x00; PaletteRAM[0x11] = 0x00; PaletteRAM[0x12] = 0x00; PaletteRAM[0x13] = 0x00; PaletteRAM[0x14] = 0x00; PaletteRAM[0x15] = 0x02; PaletteRAM[0x16] = 0x21; PaletteRAM[0x17] = 0x00; PaletteRAM[0x18] = 0x00; PaletteRAM[0x19] = 0x00; PaletteRAM[0x1A] = 0x00; PaletteRAM[0x1B] = 0x00; PaletteRAM[0x1C] = 0x00; PaletteRAM[0x1D] = 0x10; PaletteRAM[0x1E] = 0x00; PaletteRAM[0x1F] = 0x00; } programCounter = 0xFFFF; // Technically, this value is nondeterministic. It also doesn't matter where it is, as it will be initialized in the RESET instruction. PPU_Scanline = 0; // The PPU begins on dot 0 of scanline 0 PPU_Dot = 7; // Shouldn't this be 0? I don't know why, but this passes all the tests if this is 7, so...? PPU_OddFrame = true; // And this is technically considered an "odd" frame when it comes to even/odd frame timing. APU_DMC_SampleAddress = 0xC000; APU_DMC_AddressCounter = 0xC000; APU_DMC_SampleLength = 1; APU_DMC_ShifterBitsRemaining = 8; switch (APUAlignment & 4) { default: case 0: { APU_ChannelTimer_DMC = 1022; APU_PutCycle = true; } break; case 1: { APU_ChannelTimer_DMC = 1022; APU_PutCycle = false; } break; case 2: { APU_ChannelTimer_DMC = 1020; APU_PutCycle = true; } break; case 3: { APU_ChannelTimer_DMC = 1020; APU_PutCycle = false; } break; } DoReset = true; // This is used to force the first instruction at power on to be the RESET instruction. PPU_RESET = false; // I'm not even 100% certain my console has this behavior. I'll set it to false for now. } public bool PPU_RESET; // when pressing the reset button, this function runs public void Reset() { // The A, X, and Y registers are unchanged through reset. // most flags go unchanged as well, but the I flag is set to 1 flag_Interrupt = true; // Triangle phase gets reset, though I'm not yet emulating audio. APU_DMC_Output &= 1; // All the bits of $4015 are cleared APU_Status_DMCInterrupt = false; APU_Status_FrameInterrupt = false; APU_Status_DelayedDMC = false; APU_Status_DMC = false; APU_Status_Noise = false; APU_Status_Triangle = false; APU_Status_Pulse2 = false; APU_Status_Pulse1 = false; APU_DMC_BytesRemaining = 0; APU_LengthCounter_Noise = 0; APU_LengthCounter_Triangle = 0; APU_LengthCounter_Pulse2 = 0; APU_LengthCounter_Pulse1 = 0; APU_Framecounter = 0; // reset the frame counter // PPU registers PPUControl_NMIEnabled = false; PPUControlIncrementMode32 = false; PPU_Spritex16 = false; PPU_PatternSelect_Sprites = false; PPU_PatternSelect_Background = false; PPU_t = 0; PPU_Mask_Greyscale = false; PPU_Mask_EmphasizeRed = false; PPU_Mask_EmphasizeGreen = false; PPU_Mask_EmphasizeBlue = false; PPU_Mask_8PxShowBackground = false; PPU_Mask_8PxShowSprites = false; PPU_Mask_ShowBackground = false; PPU_Mask_ShowSprites = false; PPU_Update2005Delay = 0; PPU_FineXScroll = 0; //$2006 is unchanged PPU_ReadBuffer = 0; PPU_OddFrame = false; PPU_Dot = 0; PPU_Scanline = 0; DoDMCDMA = false; DoOAMDMA = false; operationCycle = 0; switch (APUAlignment & 4) { default: case 0: { APU_ChannelTimer_DMC = 1022; APU_PutCycle = true; } break; case 1: { APU_ChannelTimer_DMC = 1022; APU_PutCycle = false; } break; case 2: { APU_ChannelTimer_DMC = 1020; APU_PutCycle = true; } break; case 3: { APU_ChannelTimer_DMC = 1020; APU_PutCycle = false; } break; } DoReset = true; PPU_RESET = false; // I'm not even 100% certain my console has this behavior. I'll set it to false for now. // in theory, the CPU/PPU clock would be given random values. Let's just assume no changes. } public bool CPU_Read; // DMC DMA Has some specific behavior depending on if the CPU is currently reading or writing. DMA Halting fails / DMA $2007 bug. // The BRK instruction is reused in the IRQ, NMI, and RESET logic. These bools are used both to start the instruction, and also to make sure the correct logic is used. public bool DoBRK; // Set if the opcode is 00 public bool DoNMI; // Set if a Non Maskable Interrupt is occurring public bool DoIRQ; // Set if an Interrupt Request is occurring public bool DoReset; // Set when resetting the console, or power on. public bool DoOAMDMA; // If set, the Object Acctribute Memory's Direct Memory Access will occur. public bool FirstCycleOfOAMDMA; // The first cycle caa behave differently. public bool DoDMCDMA; // If set, the Delta Modulation Channel's Direct Memory Access will occur. public byte DMCDMADelay; // There's actually a slight delay between the audio chip preparing the DMA, and the CPU actually running it. public byte CannotRunDMCDMARightNow = 0; public byte DMAPage; // When running an OAM DMA, this is used to determine which "page" to read bytes from. Typically, this is page 2 (address $200 through $2FF) public byte DMAAddress; // While this DMA runs, this value is incremented until it overflows. public bool FrameAdvance_ReachedVBlank; // For debugging. If frame advancing, this is set when VBlank occurs. public bool APU_ControllerPortsStrobing; // Set to true/false depending on the value written to $4016. When true, the buttons pressed are recorded in the shift registers. public bool APU_ControllerPortsStrobed; // This bool prevents strobing from rushing through the TAS input log. // This gets set to false if the controllers are unstrobed, or if the controller ports are read. public byte ControllerPort1; // The buttons currently pressed on controller 1. These are in the "A, B, Select, Start, Up, Down, Left, Right" order. public byte ControllerPort2; // The buttons currently pressed on controller 2. These are in the "A, B, Select, Start, Up, Down, Left, Right" order. public byte ControllerShiftRegister1; // Controllers are read 1 bit at a time. First the A Button is read, then B, and so on. public byte ControllerShiftRegister2; // Whenever the shift register is read, all the bits are shifted to the left, and a '1' replaces bit 0. public byte Controller1ShiftCounter; // Subsequent CPU cycles reading from $4016 do not update the shift register. public byte Controller2ShiftCounter; // Subsequent CPU cycles reading from $4017 do not update the shift register. public bool LagFrame; // True if the controller port was not strobed in a frame. public bool TASTimelineClockFiltering; // Primarily used in the TASTimeline if you are using subframe Inputs. public void _CoreFrameAdvance() { // If we're running this emulator 1 frame at a time, this waits until VBlank and then returns. FrameAdvance_ReachedVBlank = false; LagFrame = true; // Many emulators detect "lag frames" by checking if the controller ports were strobed during this frame. while (!FrameAdvance_ReachedVBlank) { _EmulatorCore(); } } public int CycleCountForCycleTAS = 0; // If we're running a intercycle cart swapping TAS, we need to keep track of which cycle we're on. public void _CoreCycleAdvance() { // this runs 12 master clock cycles, or 1 CPU cycle. int i = 0; while (i < 12) { _EmulatorCore(); i++; } CycleCountForCycleTAS++; } public void _EmulatorCore() { // counters count down to 0, run the appropriate chip's logic, and the counter is reset. // If multiple counters read 0 at the same time, there's an order of events. // The order of events: // CPU // PPU // APU if (CPUClock == 12) { CPUClock = 0; // there is 1 CPU cycle for every 12 master clock cycles _6502(); // This is where I run the CPU totalCycles++; // for debugging mostly Cart.MapperChip.CPUClock(); // If the mapper chip does every cpu cycle... (see FME-7) } if (CPUClock == 4) { NMILine |= PPUControl_NMIEnabled && PPUStatus_VBlank; if (operationCycle == 0 && !(PPUStatus_VBlank && PPUControl_NMIEnabled)) { NMILine = false; } } if (CPUClock == 7) //M2 going low. { IRQLine = IRQ_LevelDetector; if (APU_Status_FrameInterrupt && !APU_FrameCounterInhibitIRQ) { IRQ_LevelDetector = true; // if the APU frame counter flag is never cleared, you will get another IRQ when the I flag is cleared. } Cart.MapperChip.CPUClockRise(); // If the mapper chip does something when M2 rises... (see MMC3) } if (PPUClock == 4) { PPUClock = 0; // there is 1 PPU cycle for every 12 master clock cycles _EmulatePPU(); if (PPUBus != 0) { DecayPPUDataBus(); } } if (PPUClock == 2) { _EmulateHalfPPU(); } if (CPUClock == 0) { _EmulateAPU(); APU_PutCycle = !APU_PutCycle; // the APU is actually clocked every 24 master clock cycles. // yet there's a lot of timing that happens every cpu cycle anyway?? // If the timing needs to be exactly n and a half APU cycles, then I'll just multiply the numbers by 2 and clock this twice as fast. } // Decrement the clocks. PPUClock++; CPUClock++; if(Cart.FDS != null) { Cart.FDS.Clock(); } } public void EmulateUntilEndOfRead() { // this is used during reads from some ppu registers. // run 1.75 ppu cycles. (the actual duty cycle here would result in 1 and 7/8 ppu cycles, but my emulator doesn't worry about half-master-clock-cycles. for (int i = 0; i < 7; i++) { _EmulatorCore(); } } public void EmulateNMasterClockCycles(int n) { // This does run the risk of recursion, so don't use a value of 12 or more with this. for (int i = 0; i < n; i++) { _EmulatorCore(); } } // Audio Processing Unit Variables // // APU Status is at address $4015 public bool APU_Status_DMCInterrupt; // Bit 7 of $4015 public bool APU_Status_FrameInterrupt;// Bit 6 of $4015 public bool APU_Status_DMC; // Bit 5 of $4015 public bool APU_Status_DelayedDMC; // Bit 5 of $4015, but with a slight delay. public bool APU_Status_Noise; // Bit 3 of $4015 public bool APU_Status_Triangle; // Bit 2 of $4015 public bool APU_Status_Pulse2; // Bit 1 of $4015 public bool APU_Status_Pulse1; // Bit 0 of $4015 public bool Clearing_APU_FrameInterrupt; public byte APU_DelayedDMC4015; // When writing to $4015, there's a 3 or 4 cycle delay between the APU actually changing this value. public bool APU_ImplicitAbortDMC4015; // An edge case of the DMC DMA, where regardless of the buffer being empty, there will be a 1-cycle DMA that gets aborted 2 cycles after the load DMA ends public bool APU_SetImplicitAbortDMC4015;// This is used to make that happen. public byte[] APU_Register = new byte[0x10]; // Instead of making a series of variables, I made an array here for some reason. public bool APU_FrameCounterMode; // Bit 7 of $4017 : Determines if the APU frame counter is using the 4 step or 5 step modes. public bool APU_FrameCounterInhibitIRQ; // Bit 6 of $4017 : If set, prevents the APU from creating IRQ's public byte APU_FrameCounterReset = 0xFF; // When resetting the APU Frame counter by writing to address $4017, there's a 3 (or 4) CPU cycle delay. (3 if it's an even cpu cycle, 4 if odd.) public ushort APU_Framecounter = 0; // Increments every APU cycle. Since there are events that happen at half-step intervals, I actually increment this every CPU cycle and multiplied all intervals by 2. public bool APU_QuarterFrameClock = false;// This is clocked approximately 4 times a frame, depending on the frame counter mode. public bool APU_HalfFrameClock = false; // This is clocked approximately twice a frame, depending on the frame counter mode. public bool APU_Envelope_StartFlag = false; public bool APU_Envelope_DividerClock = false; public byte APU_Envelope_DecayLevel = 0; public byte APU_LengthCounter_Pulse1 = 0; // The length counter for the APU's Pulse 1 channel. public byte APU_LengthCounter_Pulse2 = 0; // The length counter for the APU's Pulse 2 channel. public byte APU_LengthCounter_Triangle = 0; // The length counter for the APU's Triangle channel. public byte APU_LengthCounter_Noise = 0; // The length counter for the APU's Noise channel. // When a length counter's reloaded value is set by writing to $4003, $4007, $400B, or $400F, this LookUp Table is used to determine the length based on the value written. public static readonly byte[] APU_LengthCounterLUT = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 }; public bool APU_LengthCounter_HaltPulse1 = false; // set if Bit 5 of $4000 is 1 public bool APU_LengthCounter_HaltPulse2 = false; // set if Bit 5 of $4004 is 1 public bool APU_LengthCounter_HaltTriangle = false; // set if Bit 7 of $4008 is 1 public bool APU_LengthCounter_HaltNoise = false; // set if Bit 5 of $400C is 1 public bool APU_LengthCounter_ReloadPulse1 = false; // When writing to $4003 (if the pulse 1 channel is enabled) this is set to true. The value is reloaded in the next APU cycle. public bool APU_LengthCounter_ReloadPulse2 = false; // When writing to $4007 (if the pulse 2 channel is enabled) this is set to true. The value is reloaded in the next APU cycle. public bool APU_LengthCounter_ReloadTriangle = false;// When writing to $400B (if the triangle channel is enabled) this is set to true. The value is reloaded in the next APU cycle. public bool APU_LengthCounter_ReloadNoise = false; // When writing to $400F (if the noise channel is enabled) this is set to true. The value is reloaded in the next APU cycle. public byte APU_LengthCounter_ReloadValuePulse1 = 0; // When the pulse 1 channel is reloaded, the length counter will be set to this value. Modified by writing to $4003. public byte APU_LengthCounter_ReloadValuePulse2 = 0; // When the pulse 2 channel is reloaded, the length counter will be set to this value. Modified by writing to $4007. public byte APU_LengthCounter_ReloadValueTriangle = 0;// When the triangle channel is reloaded, the length counter will be set to this value. Modified by writing to $400B. public byte APU_LengthCounter_ReloadValueNoise = 0; // When the noise channel is reloaded, the length counter will be set to this value. Modified by writing to $400F. public ushort APU_ChannelTimer_Pulse1 = 0; // Decrements every "get" cycle. public ushort APU_ChannelTimer_Pulse2 = 0; // Decrements every "get" cycle. public ushort APU_ChannelTimer_Triangle = 0;// Decrements every CPU cycle. public ushort APU_ChannelTimer_Noise = 0; // Decrements every "get" cycle. public ushort APU_ChannelTimer_DMC = 0; // Decrements every CPU cycle. // $4010 public bool APU_DMC_EnableIRQ = false; // Will the DMC create IRQ's? Set by writing to address $4010 public bool APU_DMC_Loop = false; // Will DPCM samples loop? public ushort APU_DMC_Rate = 428; // The default sample rate is the slowest. // LookUp Table for how many CPU cycles are between each bit of the DPCM sample being played. (8 bits per byte, so to calculate how many cycles there are between each DMA, multiply these numbers by 8) public static readonly ushort[] APU_DMCRateLUT = { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 }; // $4011 (and DPCM stuff) public byte APU_DMC_Output; // Directly writing here (Address $4011) will set the DMC output. This is how you play PCM audio. // $4012 public ushort APU_DMC_SampleAddress = 0xC000; // Where the DPCM sample is being read from. // $4013 public ushort APU_DMC_SampleLength = 0; // How many bytes are being played in this DPCM sample? (multiplied by 64, and add 1) public ushort APU_DMC_BytesRemaining = 0; // How many bytes are left in the sample. When a sample starts or loops, this is set to APU_DMC_SampleLength. public byte APU_DMC_Buffer = 0; // The value that goes into the shift register. public ushort APU_DMC_AddressCounter = 0xC000; // What byte is fetched in the next DMA for DPCM audio? When a sample starts or loops, this is set to APU_DMC_SampleAddress. public byte APU_DMC_Shifter = 0; // The 8 bits of the sample that were fetched from the DMA. public byte APU_DMC_ShifterBitsRemaining = 8; // This tracks how many bits are left before needing to run another DMA public bool DPCM_Up; // If the next bit of the DPCM sample is a 1, the output goes up. Otherwise it goes down. public bool APU_Silent = true; // If the APU is not making any noise, this is set. void _EmulateAPU() { // This runs every 12 master clock cycles, though has different logic for even/odd CPU cycles. if (!APU_ControllerPortsStrobing) { if (Controller1ShiftCounter > 0) { Controller1ShiftCounter--; if (Controller1ShiftCounter == 0) { ControllerShiftRegister1 <<= 1; ControllerShiftRegister1 |= 1; } } if (Controller2ShiftCounter > 0) { Controller2ShiftCounter--; if (Controller2ShiftCounter == 0) { ControllerShiftRegister2 <<= 1; ControllerShiftRegister2 |= 1; } } } else { Controller1ShiftCounter = 0; Controller2ShiftCounter = 0; } if (!APU_PutCycle) { // If this is a get cycle, transitioning to a put cycle. // controller reading is handled here in the APU chip. // If a 1 was written to $4016, we are strobing the controller. if (APU_ControllerPortsStrobing) { if (!APU_ControllerPortsStrobed) { LagFrame = false; APU_ControllerPortsStrobed = true; if (TASTimelineClockFiltering) { FrameAdvance_ReachedVBlank = true; // Obviously this isn't actually VBlank, but we want to stop emulating here anyway. } // this will be reset to false if: // 1.) the controllers are un-strobed. Ready for the next strobe. // 2.) the controller ports are read, while still strobed. This allows data to be streamed in through the A button. if (TAS_ReadingTAS) // This is specifically how I load inputs from a TAS, and has nothing to do with actual NES behavior. { if (TAS_InputSequenceIndex < TAS_InputLog.Length) { ControllerPort1 = (byte)(TAS_InputLog[TAS_InputSequenceIndex] & 0xFF); ControllerPort2 = (byte)((TAS_InputLog[TAS_InputSequenceIndex] & 0xFF00) >> 8); } else // if the TAS has ended, only provide 0 as the inputs. { ControllerPort1 = 0; ControllerPort2 = 0; } if (ClockFiltering) { if (TAS_InputSequenceIndex > 0 && TAS_InputSequenceIndex < TAS_ResetLog.Length && TAS_ResetLog[TAS_InputSequenceIndex]) { Reset(); } TAS_InputSequenceIndex++; // Instead of using 1 input per frame, this just advances to the next input } } // this sets up the shift registers with the value of the controller ports. // If not set by the TAS, these are probably set outside this script in the script for the form. ControllerShiftRegister1 = ControllerPort1; ControllerShiftRegister2 = ControllerPort2; } } else { APU_ControllerPortsStrobed = false; } // clock timers APU_ChannelTimer_Pulse1--; // every APU GET cycle. APU_ChannelTimer_Pulse2--; APU_ChannelTimer_Noise--; //this happens whether a sample is playing or not APU_ChannelTimer_DMC--; APU_ChannelTimer_DMC--; // the table is in CPU cycles, but the count is in APU cycles if (APU_ChannelTimer_DMC == 0) { APU_ChannelTimer_DMC = APU_DMC_Rate; DPCM_Up = (APU_DMC_Shifter & 1) == 1; if (DPCM_Up) { if (APU_DMC_Output <= 125) // this is 7 bit, and cannot go above 127 { APU_DMC_Output += 2; } } else { if (APU_DMC_Output >= 2) // this is 7 bit, and cannot go below 0 { APU_DMC_Output -= 2; } } APU_DMC_Shifter >>= 1; // shift the bits in the shift register APU_DMC_ShifterBitsRemaining--; // and decrement the "bits remaining" counter. if (APU_DMC_ShifterBitsRemaining == 0) // If there are no bits left, { APU_DMC_ShifterBitsRemaining = 8; // it's time for a DMC DMA! if (APU_DMC_BytesRemaining > 0 || APU_SetImplicitAbortDMC4015) { if (!DoDMCDMA && CannotRunDMCDMARightNow != 2) { // if playing a sample: DoDMCDMA = true; DMCDMA_Halt = true; } if (APU_SetImplicitAbortDMC4015) { APU_ImplicitAbortDMC4015 = true; // check for weird DMA abort behavior APU_SetImplicitAbortDMC4015 = false; } APU_DMC_Shifter = APU_DMC_Buffer; // and set up the shifter with the new values. APU_Silent = false; // The APU is not silent. } else { APU_Silent = true; } } } if (CannotRunDMCDMARightNow > 0) { CannotRunDMCDMARightNow -= 2; } } else { // If this is a put cycle, transitioning to a get cycle. if (Clearing_APU_FrameInterrupt) { Clearing_APU_FrameInterrupt = false; APU_Status_FrameInterrupt = false; IRQ_LevelDetector = false; } // DMC load from 4015 if (DMCDMADelay > 0) { DMCDMADelay--; // there's a small delay beetween the write occurring and the DMA beginning if (DMCDMADelay == 0 && !DoDMCDMA) // if the DMA is already happening because of the timer { DoDMCDMA = true; DMCDMA_Halt = true; APU_DMC_Shifter = APU_DMC_Buffer; APU_Silent = false; } } } if (APU_DelayedDMC4015 > 0) { APU_DelayedDMC4015--; if (APU_DelayedDMC4015 == 0) { APU_Status_DMC = APU_Status_DelayedDMC; if (!APU_Status_DMC) { APU_DMC_BytesRemaining = 0; } } } APU_ChannelTimer_Triangle--; // every CPU cycle. // clock sequencer if ((APU_FrameCounterReset & 0x80) == 0) { APU_FrameCounterReset--; if ((APU_FrameCounterReset & 0x80) != 0) { APU_Framecounter = 0; } } APU_Framecounter++; // We're clocking the APU twice as fast in order to get the frame counter timing to allow the 'half APU cycle' timing. // these numbers are just multiplied by 2. if (APU_FrameCounterMode) { // 5 step switch (APU_Framecounter) { default: break; case 7457: APU_QuarterFrameClock = true; break; case 14913: APU_QuarterFrameClock = true; APU_HalfFrameClock = true; break; case 22371: APU_QuarterFrameClock = true; break; case 29829: break; case 37281: APU_QuarterFrameClock = true; APU_HalfFrameClock = true; break; case 37282: APU_Framecounter = 0; break; } } else { // 4 step switch (APU_Framecounter) { default: break; case 7457: APU_QuarterFrameClock = true; break; case 14913: APU_QuarterFrameClock = true; APU_HalfFrameClock = true; break; case 22371: APU_QuarterFrameClock = true; break; case 29828: APU_Status_FrameInterrupt = true; break; case 29829: APU_QuarterFrameClock = true; APU_Status_FrameInterrupt = true; IRQ_LevelDetector |= !APU_FrameCounterInhibitIRQ; APU_HalfFrameClock = true; break; case 29830: APU_Status_FrameInterrupt = !APU_FrameCounterInhibitIRQ; IRQ_LevelDetector |= !APU_FrameCounterInhibitIRQ; APU_Framecounter = 0; break; } } // perform quarter frame / half frame stuff if (APU_QuarterFrameClock) { APU_QuarterFrameClock = false; if (APU_Envelope_StartFlag) { APU_Envelope_StartFlag = false; APU_Envelope_DecayLevel = 15; } else { APU_Envelope_DividerClock = true; } } if (APU_HalfFrameClock) { if (APU_LengthCounter_ReloadPulse1 && APU_LengthCounter_Pulse1 == 0) { APU_LengthCounter_Pulse1 = APU_LengthCounter_ReloadValuePulse1; } else { APU_LengthCounter_ReloadPulse1 = false; } if (APU_LengthCounter_ReloadPulse2 && APU_LengthCounter_Pulse2 == 0) { APU_LengthCounter_Pulse2 = APU_LengthCounter_ReloadValuePulse2; } else { APU_LengthCounter_ReloadPulse2 = false; } if (APU_LengthCounter_ReloadTriangle && APU_LengthCounter_Triangle == 0) { APU_LengthCounter_Triangle = APU_LengthCounter_ReloadValueTriangle; } else { APU_LengthCounter_ReloadTriangle = false; } if (APU_LengthCounter_ReloadNoise && APU_LengthCounter_Noise == 0) { APU_LengthCounter_Noise = APU_LengthCounter_ReloadValueNoise; } else { APU_LengthCounter_ReloadNoise = false; } APU_HalfFrameClock = false; // length counters and sweep if (!APU_Status_Pulse1) { APU_LengthCounter_Pulse1 = 0; } if (!APU_Status_Pulse2) { APU_LengthCounter_Pulse2 = 0; } if (!APU_Status_Triangle) { APU_LengthCounter_Triangle = 0; } if (!APU_Status_Noise) { APU_LengthCounter_Noise = 0; } if (APU_LengthCounter_Pulse1 != 0 && !APU_LengthCounter_HaltPulse1 && !APU_LengthCounter_ReloadPulse1) { APU_LengthCounter_Pulse1--; } if (APU_LengthCounter_Pulse2 != 0 && !APU_LengthCounter_HaltPulse2 && !APU_LengthCounter_ReloadPulse2) { APU_LengthCounter_Pulse2--; } if (APU_LengthCounter_Triangle != 0 && !APU_LengthCounter_HaltTriangle && !APU_LengthCounter_ReloadTriangle) { APU_LengthCounter_Triangle--; } if (APU_LengthCounter_Noise != 0 && !APU_LengthCounter_HaltNoise && !APU_LengthCounter_ReloadNoise) { APU_LengthCounter_Noise--; } } else { if (APU_LengthCounter_ReloadPulse1) { APU_LengthCounter_Pulse1 = APU_LengthCounter_ReloadValuePulse1; } if (APU_LengthCounter_ReloadPulse2) { APU_LengthCounter_Pulse2 = APU_LengthCounter_ReloadValuePulse2; } if (APU_LengthCounter_ReloadTriangle) { APU_LengthCounter_Triangle = APU_LengthCounter_ReloadValueTriangle; } if (APU_LengthCounter_ReloadNoise) { APU_LengthCounter_Noise = APU_LengthCounter_ReloadValueNoise; } APU_LengthCounter_ReloadPulse1 = false; APU_LengthCounter_ReloadPulse2 = false; APU_LengthCounter_ReloadTriangle = false; APU_LengthCounter_ReloadNoise = false; } APU_LengthCounter_HaltPulse1 = ((APU_Register[0] & 0x20) != 0); APU_LengthCounter_HaltPulse2 = ((APU_Register[4] & 0x20) != 0); APU_LengthCounter_HaltTriangle = ((APU_Register[8] & 0x80) != 0); APU_LengthCounter_HaltNoise = ((APU_Register[0xC] & 0x20) != 0); } // and that's it for the APU cycle // PPU variables public byte PPUBus; // The databus of the Picture Processing Unit public int[] PPUBusDecay = new int[8]; const int PPUBusDecayConstant = 1786830; // 20 frames. Approximately how long it takes for the PPU bus to decay on my console. public byte PPUOAMAddress; // The address used to index into Object Attribute Memory public bool PPUStatus_VBlank; // This is set during Vblank, and cleared at the end, or if $2002 is read. This value can be read in address $2002 public bool PPUStatus_PendingSpriteZeroHit; // If a sprite zero hit occurs, this is set. This toggles PPUStatus_SpriteZeroHit on the next half-ppu-cycle. public bool PPUStatus_PendingSpriteZeroHit2; // Actually theres a 1.5 dot delay on this one. public bool PPUStatus_SpriteZeroHit; // If a sprite zero hit occurs, this is set. This value can be read in address $2002 public bool PPUStatus_SpriteZeroHit_Delayed; public bool PPUStatus_SpriteOverflow; // If a scanline had more than 8 objects in range, this is set. This value can be read in address $2002 public bool PPUStatus_SpriteOverflow_Delayed; public bool PPU_VSET; // This line is high for half a ppu cycle at the start of scanline 241. public bool PPU_VSET_Latch1; // A latch used in the timing for the VBlank flag. public bool PPU_VSET_Latch2; // A latch used in the timing for the VBlank flag. public bool PPU_Read2002; // This clears the VBlank flag. bool PPU_Spritex16; // Are sprites using 8x8 mode, or 8x16 mode? Set by writing to $2000 public ushort PPU_Scanline; // Which scanline is the PPU currently on public ushort PPU_Dot; // Which dot of the scanline is the PPU currently on public bool PPU_VRegisterChangedOutOfVBlank; // when changing the v register (Read write address) out of vblank, palettes can become corrupted public bool PPU_OAMCorruptionRenderingDisabledOutOfVBlank; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted public bool PPU_PendingOAMCorruption;// The corruption doesn't take place until rendering is re-enabled. public byte PPU_OAMCorruptionIndex; // The object that gets corrupted depends on when the data was corrupted // OAM corruption during OAM evaluation happens with the instant write to $2001 using the databus value. Other parts of sprite evaluation apparently do not. public bool PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted public bool PPU_OAMCorruptionRenderingEnabledOutOfVBlank; // If enabling rendering outside vblank, there are alignment specific effects. public bool PPU_OAMEvaluationCorruptionOddCycle; // If rendering is disabled during OAM evaluation, it matters if it was on an odd or even cycle. public bool PPU_OAMEvaluationObjectInRange; // If rendering is disabled during OAM evaluation, it matters if the most recent object evaluated was in vertical range of this scanline. public bool PPU_OAMEvaluationObjectInXRange; // If rendering is disabled during OAM evaluation, it matters if the most recent object evaluated was in vertical range of this scanline. public bool PPU_PaletteCorruptionRenderingDisabledOutOfVBlank; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted byte PPU_AttributeLatchRegister; ushort PPU_BackgroundAttributeShiftRegisterL; // 8 bit latch for the background tile attributes low bit plane. ushort PPU_BackgroundAttributeShiftRegisterH; // 8 bit latch register for the background tile attributes high bit plane. ushort PPU_BackgroundPatternShiftRegisterL; // 16 bit shift register for the background tile pattern low bit plane. ushort PPU_BackgroundPatternShiftRegisterH; // 16 bit shift register for the background tile pattern high bit plane. //TempPPUAddr public byte PPU_FineXScroll; // Set when writing to address $2005. 3 bits. This is up to a 7 pixel offset when rendering the screen. byte[] PPU_SpriteShiftRegisterL = new byte[8]; // 8 bit shift register for a sprite's low bit plane. Secondary OAM can have up to 8 object in it. byte[] PPU_SpriteShiftRegisterH = new byte[8]; // 8 bit shift register for a sprite's high bit plane. Secondary OAM can have up to 8 object in it. byte[] PPU_SpriteAttribute = new byte[8]; // Secondary OAM attribute values. Secondary OAM can have up to 8 objects in it. byte[] PPU_SpritePattern = new byte[8]; // Secondary OAM pattern values. Secondary OAM can have up to 8 objects in it. byte[] PPU_SpriteXposition = new byte[8]; // Secondary OAM x positions. Secondary OAM can have up to 8 objects in it. byte[] PPU_SpriteYposition = new byte[8]; // Secondary OAM y positions. Secondary OAM can have up to 8 objects in it. byte[] PPU_SpriteShifterCounter = new byte[8]; // This counter tracks how long until the objects are drawn. bool PPU_NextScanlineContainsSpriteZero; // If this upcoming scanline contains sprite zero bool PPU_CurrentScanlineContainsSpriteZero; // if the sprite evaluation for this current scanline contained sprite zero. Used for Sprite Zero Hit detection. public byte PPU_SpritePatternL; // Temporary value used in sprite evaluation. public byte PPU_SpritePatternH; // Temporary value used in sprite evaluation. bool PPU_Mask_Greyscale; // Set by writing to $2001. If set, only use color 00, 10, 20, or 30 when drawing a pixel. bool PPU_Mask_8PxShowBackground; // Set by writing to $2001. If set, the background will be visible in the 8 left-most pixels of the screen. bool PPU_Mask_8PxShowSprites; // Set by writing to $2001. If set, the sprites will be visible in the 8 left-most pixels of the screen. bool PPU_Mask_ShowBackground; // Set by writing to $2001. If set, the background will be visible. Anything that requires rendering to be enabled will run, even if it doesn't involve the background. bool PPU_Mask_ShowSprites; // Set by writing to $2001. If set, the sprites will be visible. Anything that requires rendering to be enabled will run, even if it doesn't involve sprites. bool PPU_Mask_EmphasizeRed; // Set by writing to $2001. Adjusts the colors on screen to be a bit more red. bool PPU_Mask_EmphasizeGreen; // Set by writing to $2001. Adjusts the colors on screen to be a bit more green. bool PPU_Mask_EmphasizeBlue; // Set by writing to $2001. Adjusts the colors on screen to be a bit more blue. bool PPU_Mask_ShowBackground_Delayed; // Sprite evaluation has a 1 ppu cycle delay on checking if rendering is enabled. bool PPU_Mask_ShowSprites_Delayed; // Sprite evaluation has a 1 ppu cycle delay on checking if rendering is enabled. bool PPU_Mask_ShowBackground_Instant; // OAM evaluation will stop immediately if writing to $2001 bool PPU_Mask_ShowSprites_Instant; // OAM evaluation will stop immediately if writing to $2001 byte PPU_LowBitPlane; // Temporary value used in background shift register preparation. byte PPU_HighBitPlane;// Temporary value used in background shift register preparation. byte PPU_Attribute; // Temporary value used in background shift register preparation. public ushort PPU_PatternAddressRegister_CHR; // PAR public ushort PPU_PatternAddressRegister_NT; // PAR public ushort PPU_PatternAddressRegister_AT; // PAR public ushort PPU_PAR_MUX; // PAR bool PPU_CanDetectSpriteZeroHit; // Only 1 sprite zero hit is allowed per frame. This gets set if a sprite zero hit occurs, and cleared at the end of vblank. public bool PPU_A12_Prev; // The MMC3 chip's IRQ counter is changed whenever bit 12 of the PPU Address is changing from a 0 to a 1. This is recorded at the start of a PPU cycle, and checked at the end. public bool PPU_OddFrame; // Every other frame is 1 ppu cycle shorter. public byte DotColor; // The pixel output is delayed by 2 dots. public byte PrevDotColor; // This is the value from last cycle. public byte PrevPrevDotColor; // And this is from 2 cycles ago. public byte PrevPrevPrevDotColor; // And this is from 2 cycles ago. public int PrevPrevPrevPrevDotColor; // This is used with NTSC signal decoding. public byte PaletteRAMAddress; public bool ThisDotReadFromPaletteRAM; public bool NMI_PinsSignal; // I'm using this to detect the rising edge of $2000.7 and $2002.7 public bool NMI_PreviousPinsSignal; // I'm using this to detect the rising edge of $2000.7 and $2002.7 public bool IRQ_LevelDetector; // If set, it's time to run an IRQ whenever this is detected public bool NMILine; // Set to true if $2000.7 and $2002.7 are both set. This is checked during the second half od a CPU cycle. public bool IRQLine; // Set during phi2 to true if the IRQ level detector is low. bool CopyV = false; // set by writes to $2006. If it occurs on the same dot the scroll values are naturally incremented, some bugs occur. bool SkippedPreRenderDot341 = false; void _EmulatePPU() { // When writing to ppu registers, there's a slight delay before resulting action is taken. // This delay can vary depending on the CPU/PPU alignment. // For instance, after writing to $2006, this delay value will either be 4 or 5. CopyV = false; if (PPU_Update2006Delay > 0) { PPU_Update2006Delay--; // this counts down, if (PPU_Update2006Delay == 0) // and when it reaches zero { ushort temp_Prev_V = PPU_v; CopyV = true; PPU_v = PPU_t; // the PPU_ReadWriteAddress is updated! PPU_AddressBus = PPU_v; // This value is the same thing. if ((temp_Prev_V & 0x3FFF) >= 0x3F00 && (PPU_AddressBus & 0x3FFF) < 0x3F00) // Palette corruption check. Are we leaving Palette ram? { if ((PPU_Scanline < 240) && PPU_Dot <= 256) // if this dot is visible { if ((temp_Prev_V & 0xF) != 0) // also, Palette corruption only happens if the previous address did not end in a 0 { PPU_VRegisterChangedOutOfVBlank = true; } } } } } // after writing to $2005, there is either a 1 or 2 cycle delay. if (PPU_Update2005Delay > 0) { PPU_Update2005Delay--; if (PPU_Update2005Delay == 0) { if (!PPUAddrLatch) { // if this is the first write to $2005 PPU_FineXScroll = (byte)(PPU_Update2005Value & 7); // This updates the fine X scroll PPU_t = (ushort)((PPU_t & 0b0111111111100000) | (PPU_Update2005Value >> 3)); // as well as changing the 't' register. } else { // if this is the second write to $2005 PPU_t = (ushort)((PPU_t & 0b0000110000011111) | (((PPU_Update2005Value & 0xF8) << 2) | ((PPU_Update2005Value & 7) << 12))); // this also writes to 't' } PPUAddrLatch = !PPUAddrLatch; // flip the latch } } // Updating the scroll registers during screen rendering if (PPU_Scanline < 240 || PPU_Scanline == 261)// if this is the pre-render line, or any line before vblank { if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) { if (PPU_Dot == 256) //The Y scroll is incremented on dot 256. { PPU_IncrementScrollY(); } else if (PPU_Dot == 257) //The X scroll is reset on dot 257. { PPU_ResetXScroll(); } if (PPU_Dot >= 280 && PPU_Dot <= 304 && PPU_Scanline == 261) //numbers from the nesdev wiki { PPU_ResetYScroll(); //The Y scroll is reset on every dot from 280 through 304 on the pre-render scanline. } } } // Increment the PPU dot PPU_Dot++; if (PPU_Dot > 340) // There are only 341 dots per scanline { PPU_Dot = 0; // reset the dot back to 0 PPU_Scanline++; // and increment the scanline // Sprite zero hits rely on the previous scanline's sprite evaluation. if (PPU_Scanline > 261) // There are 262 scanlines in a frame. { PPU_Scanline = 0; // reset to scanline 0. } } if (PPU_Scanline == 241) // If this is the first scanline of VBLank { if (PPU_Dot == 0) { // If Address $2002 is read during the next ppu cycle, the PPU Status flags aren't set. // These variables are used to check if Address $2002 is read during the next ppu cycle. // I usually refer to this as the $2002 race condition. // The more proper term would be "Vblank/NMI flag suppression". // oh- and also if we're running a fm2 TAS file, due to FCEUX's incorrect timing of the first frame, I need to prevent this from being set just a few cycles after power on. if (!SyncFM2) { PPU_PendingVBlank = true; } else { SyncFM2 = false; } } if (PPU_Dot == 1) { PPU_RESET = false; // else, address $2002 was read on this ppu cycle. no VBlank flag. if (!PPU_ShowScreenBorders) { FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else. } if (!ClockFiltering) // specifically for TASing stuff. Increment the index for the input log. { if (TAS_ReadingTAS && TAS_InputSequenceIndex > 0 && TAS_InputSequenceIndex < TAS_ResetLog.Length && TAS_ResetLog[TAS_InputSequenceIndex]) { Reset(); } // If this was using "SubFrame", TAS_InputSequenceIndex is incremented whenever the controller is strobed. // Instead, I increment the index here at the start of vblank. TAS_InputSequenceIndex++; } } } else if (PPU_Scanline == 242 && PPU_Dot == 1) { if (PPU_ShowScreenBorders && !PPU_DecodeSignal) // if we're showing the boarders, we need to wait for 2 more scanlines to render. { FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else. } } else if (PPU_Scanline == 260 && PPU_Dot == 340) { PPU_OddFrame = !PPU_OddFrame; // I guess this could happen on pretty much any cycle? } else if (PPU_Scanline == 261 && PPU_Dot == 1) { // On dot 1 of the pre-render scanline, all of these flags are cleared. // You might be looking at the results of my "$2002 Flag Clear Timing" test from the AccuracyCoin test ROM and thinking, "Hold on. That can't be right!" // Well, it is. You see, PPUStatus_VBlank is read at the beginning of the read, while PPUStatus_SpriteZeroHit and PPUStatus_SpriteOverflow are read at the end of the read. // This means about 1 and 7/8 ppu cycles pass between the start of the read and the end, so thes values are seemingly cleared on different cycles, but they are in-fact cleared at the same time. PPUStatus_VBlank = false; PPU_CanDetectSpriteZeroHit = true; PPUStatus_SpriteZeroHit = false; PPUStatus_SpriteOverflow = false; PPUStatus_SpriteZeroHit_Delayed = false; } else if (PPU_Scanline == 0 && PPU_Dot == 1) { if (PPU_ShowScreenBorders && PPU_DecodeSignal) // if we're showing the boarders, we need to wait for scanline 0. { FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else. } } PPU_VSET_Latch1 = !PPU_VSET; // VSET_Latch1 is latched with /VSET on the first half of a PPU cycle. if (PPU_VSET && !PPU_VSET_Latch2) { PPUStatus_VBlank = true; } if (PPU_Read2002) { PPU_Read2002 = false; PPUStatus_VBlank = false; } PPUStatus_SpriteOverflow_Delayed = PPUStatus_SpriteOverflow; // Right now, I'm only emulating MMC3's IRQ counter in this function. PPU_MapperSpecificFunctions(); PPU_A12_Prev = (PPU_AddressBus & 0b0001000000000000) != 0; // Record the value of the A12. This is used in the PPU_MapperSpecificFunctions(), so if this changes between here and next ppu cycle, we'll know. if (PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) { if (PPU_Scanline == 261 && PPU_Dot == 340) { // On every other frame, dot 0 of scanline 0 is skipped. // this cycle is technically (0,0), but this still makes the Nametable fetch during the last cycle of the pre-render line PPU_Scanline = 0; PPU_Dot = 0; SkippedPreRenderDot341 = true; } } if (PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline == 0 && PPU_Dot == 2) { SkippedPreRenderDot341 = false; // This variable is used for some esoteric business on dot 1 of scanline 0. } // Okay, now that we're updated all those flags, let's render stuff to the screen! // let's establish the order of operations. // Sprite evaluation // then calculate the color for the next dot. //but to complicate things, the delay after writing to $2001 happens between those 2 steps, and also on a specific alignment, this delay is 1 cycle longer for sprite evaluation. // If this is NOT phase 1 if ((CPUClock & 3) != 3) { // sprite evaluation has a 1 ppu cycle delay before recognizing these flags were set or cleared. PPU_Mask_ShowBackground_Delayed = PPU_Mask_ShowBackground; PPU_Mask_ShowSprites_Delayed = PPU_Mask_ShowSprites; } PPU_DATA_StateMachine(); if ((PPU_Scanline < 240 || PPU_Scanline == 261))// if this is the pre-render line, or any line before vblank { // Sprite evaluation if (PPU_Scanline < 241 || PPU_Scanline == 261) { PPU_Render_SpriteEvaluation(); // fill in secondary OAM, and set up various arrays of sprite properties. } } if ((CPUClock & 3) == 3) { // on phase 1, // sprite evaluation has a 2 ppu cycle delay before recognizing these flags were set or cleared. PPU_Mask_ShowBackground_Delayed = PPU_Mask_ShowBackground; PPU_Mask_ShowSprites_Delayed = PPU_Mask_ShowSprites; } if (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites) { PPU_AddressBus = PPU_v; // the address bus is always v when rendering is disabled. // TODO: Is this occuring one ppu cycle too late??? // I specifically moved this here (outside of the following if statements) because it broke nes_reset_state_detect-letters.nes on alignment 1. } // after sprite evaluation, but before screen rendering... if (PPU_Update2001Delay > 0) // if we wrote to 2001 recently { PPU_Update2001Delay--; if (PPU_Update2001Delay == 0) // if we've waited enough cycles, apply the changes { PPU_Mask_8PxShowBackground = (PPU_Update2001Value & 0x02) != 0; PPU_Mask_8PxShowSprites = (PPU_Update2001Value & 0x04) != 0; PPU_Mask_ShowBackground = (PPU_Update2001Value & 0x08) != 0; PPU_Mask_ShowSprites = (PPU_Update2001Value & 0x10) != 0; PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites; } } if (PPU_Update2001OAMCorruptionDelay > 0) // if we wrote to 2001 recently { PPU_Update2001OAMCorruptionDelay--; if (PPU_Update2001OAMCorruptionDelay == 0) // if we've waited enough cycles, apply the changes { if (PPU_WasRenderingBefore2001Write && (PPU_Update2001Value & 0x08) == 0 && (PPU_Update2001Value & 0x10) == 0) { if ((PPU_Scanline < 240 || PPU_Scanline == 261)) // if this is the pre-render line, or any line before vblank { if (!PPU_PendingOAMCorruption) // due to OAM corruption occurring inside OAM evaluation before this even occurs, make sure OAM isn't already corrupt { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = true; } } } } } if (PPU_Update2001EmphasisBitsDelay > 0) { PPU_Update2001EmphasisBitsDelay--; if (PPU_Update2001EmphasisBitsDelay == 0) { PPU_Mask_Greyscale = (PPU_Update2001Value & 0x01) != 0; PPU_Mask_EmphasizeRed = (PPU_Update2001Value & 0x20) != 0; PPU_Mask_EmphasizeGreen = (PPU_Update2001Value & 0x40) != 0; PPU_Mask_EmphasizeBlue = (PPU_Update2001Value & 0x80) != 0; } } PrevPrevPrevDotColor = PrevPrevDotColor; // Drawing a color to the screen has a 3(?) ppu cycle delay between deciding the color, and drawing it. PrevPrevDotColor = PrevDotColor; PrevDotColor = DotColor; // These variables here just record the color, and swap them through these variables so it can be used 3 cycles after it was chosen. if ((PPU_Scanline < 240 || PPU_Scanline == 261))// if this is the pre-render line, or any line before vblank { if ((PPU_Dot >= 1 && PPU_Dot <= 256) || (PPU_Dot >= 321 && PPU_Dot <= 336)) // if this is a visible pixel, or preparing the start of next scanline { if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) // if rendering background or sprites { PPU_Render_ShiftRegistersAndBitPlanes(); // update shift registers for the background. } } else if (PPU_Dot >= 337 || PPU_Dot == 0) { if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) // if rendering background or sprites { PPU_Render_ShiftRegistersAndBitPlanes_DummyNT(); } } if ((PPU_Dot > 0 && PPU_Dot <= 256)) // if this is a visible pixel, or preparing the start of next scanline { if (PPU_Scanline < 241) { PPU_Render_CalculatePixel(false); // this determines the color of the pixel being drawn. } UpdateSpriteShiftRegisters(); // update shift registers for the sprites. } else { if (PPU_ShowScreenBorders) // Draw the pixels in the boarder too. { PPU_Render_CalculatePixel(true); // this determines the color of the pixel being drawn. } } if (!PPU_ShowScreenBorders) { DrawToScreen(); if (PPU_DecodeSignal && (PPU_Dot == 0) && PPU_Scanline < 241) { ntsc_signal_of_dot_0 = ntsc_signal; chosenColor = PaletteRAM[0x00] & 0x3F; if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } // emphasis bits int emphasis = 0; if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. PrevPrevPrevPrevDotColor = chosenColor | emphasis; // set up samples for dot 1 PPU_SignalDecode(chosenColor | emphasis); } if (PPU_DecodeSignal && (PPU_Dot == 260) && PPU_Scanline < 241) { PPU_SignalDecode(PrevPrevPrevPrevDotColor); } else if (PPU_DecodeSignal && (PPU_Dot == 261) && PPU_Scanline < 241) { RenderNTSCScanline(); } } } else if (PPU_ShowScreenBorders) { PPU_Render_CalculatePixel(true); // this determines the color of the pixel being drawn. } if (PPU_ShowScreenBorders) { DrawToBorderedScreen(); } ThisDotReadFromPaletteRAM = false; PPU_DATA_StateMachine2(); if (PPU_DecodeSignal) { ntsc_signal += 8; ntsc_signal %= 12; } if (Logging && LoggingPPU) { Debug_PPU(); } } // and that's all for the PPU cycle! bool PPUActiveForShiftRegisterUpdate; void _EmulateHalfPPU() { // Oh boy, it's time for half PPU cycles. if ((PPU_Scanline < 240 || PPU_Scanline == 261))// if this is the pre-render line, or any line before vblank { if(PPU_Dot == 320) { } if ((PPU_Dot > 0 && PPU_Dot <= 257) || (PPU_Dot > 320 && PPU_Dot <= 336)) // if this is a visible pixel, or preparing the start of next scanline { if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) // if rendering background or sprites { PPU_UpdateBackgroundShiftRegisters(); // shift all the shift registers 1 bit } } } PPU_Render_CommitShiftRegistersAndBitPlanes(); PPU_VSET = false; if (PPU_PendingVBlank) { PPU_PendingVBlank = false; PPU_VSET = true; } // PPU_VSET_Latch1 gets inverted, and that becomes the state of PPU_VSET_Latch2 PPU_VSET_Latch2 = !PPU_VSET_Latch1; if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline < 240) { if (PPU_Dot == 0 || PPU_Dot > 320) { PPU_OAMBuffer = OAM2[0]; } else if (PPU_Dot > 0 && PPU_Dot <= 64) { PPU_OAMBuffer = 0xFF; } else if (PPU_Dot <= 256) { PPU_OAMBuffer = PPU_OAMLatch; } else { PPU_OAMBuffer = PPU_OAMLatch; } } PPUStatus_SpriteZeroHit_Delayed = PPUStatus_SpriteZeroHit; if (PPUStatus_PendingSpriteZeroHit2) { PPUStatus_PendingSpriteZeroHit2 = false; PPUStatus_SpriteZeroHit = true; } if (PPUStatus_PendingSpriteZeroHit) { PPUStatus_PendingSpriteZeroHit = false; PPUStatus_PendingSpriteZeroHit2 = true; } PPU_DATA_StateMachine_Half(); } public bool PPU_2007_Read; public bool PPU_2007_Read_SR; public bool[] PPU_2007_Read_Latches = new bool[5]; public bool PPU_2007_PD_RB; public bool PPU_2007_ReadALE; public bool PPU_2007_Read_H0_Latch; public bool PPU_2007_Read_XRB; public bool PPU_READ; // The lower 8 bits of the address bus are being used as data pins for a read. public bool PPU_2007_Write; public bool PPU_2007_Write_SR; public bool[] PPU_2007_Write_Latches = new bool[5]; public bool PPU_2007_DB_PAR; public bool PPU_2007_WriteALE; public bool PPU_2007_TStep_Latch; public bool PPU_2007_TStep; public bool PPU_2007_BLNK_Latch; public bool PPU_2007_PaletteRAMEnable; public byte PPU_2007_WriteData; public bool PPU_WRITE; // The lower 8 bits of the address bus are being used as data pins for a write. void PPU_DATA_StateMachine() { bool BLNK = (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites) || (PPU_Scanline >= 240 && PPU_Scanline < 261); PPU_2007_BLNK_Latch = BLNK; bool H0_DASH = (PPU_Dot - 1 & 1) != 0; PPU_2007_PaletteRAMEnable = ((PPU_AddressBus & 0x3F00) == 0x3F00) && PPU_2007_BLNK_Latch; PPU_2007_Read_XRB = PPU_2007_Read && PPU_2007_PaletteRAMEnable; PPU_2007_Read_Latches[0] = PPU_2007_Read_SR; if(PPU_2007_Read) { PPU_2007_Read = false; // I put this in an if statement for easier debugging / breakpoint placement. } PPU_2007_Read_Latches[2] = !PPU_2007_Read_Latches[1]; PPU_2007_Read_Latches[4] = !PPU_2007_Read_Latches[3]; PPU_2007_PD_RB = PPU_2007_Read_Latches[4] && !PPU_2007_Read_Latches[2]; PPU_2007_ReadALE = !PPU_2007_Read_Latches[4] && PPU_2007_Read_Latches[2]; PPU_2007_Read_H0_Latch = (PPU_Dot - 1 & 1) != 0; PPU_READ = (PPU_2007_PD_RB || (!BLNK && PPU_2007_Read_H0_Latch)); // even ppu cycles outside of blanking always read. Also read if we are reading $2007. PPU_2007_Write_Latches[0] = PPU_2007_Write_SR; if (PPU_2007_Write) { PPU_2007_Write = false; // I put this in an if statement for easier debugging / breakpoint placement. } PPU_2007_Write_Latches[2] = !PPU_2007_Write_Latches[1]; PPU_2007_Write_Latches[4] = !PPU_2007_Write_Latches[3]; PPU_2007_WriteALE = !PPU_2007_Write_Latches[4] && PPU_2007_Write_Latches[2]; PPU_2007_TStep_Latch = PPU_2007_DB_PAR; bool b = (!BLNK && !H0_DASH); // If you are on an even dot out of a blanking period PPU_ALE = (PPU_2007_ReadALE || PPU_2007_WriteALE || b); if ((PPU_2007_ReadALE || PPU_2007_WriteALE)) { if (!PPU_READ) { PPU_AddressBus = PPU_v; PPU_OctalLatch = (byte)PPU_AddressBus; } } } void PPU_DATA_StateMachine2() { if (PPU_2007_PD_RB) { PPU_ReadBuffer = Cart.MapperChip.FetchPPU(); if (PPU_ALE) { PPU_OctalLatch = (byte)PPU_AddressBus; } } } void PPU_DATA_StateMachine_Half() { PPU_2007_TStep = (PPU_2007_TStep_Latch || PPU_2007_PD_RB); if (PPU_2007_TStep) // If this occurs inside PPU_DATA_StateMachine() instead, the timing is wrong, and this breaks SMB1's title screen. { if (!PPU_2007_BLNK_Latch) { PPU_IncrementScrollY(); } else { PPU_v += (ushort)(PPUControlIncrementMode32 ? 32 : 1); } } PPU_ALE = (PPU_2007_ReadALE || PPU_2007_WriteALE); if (PPU_2007_PD_RB) { PPU_ReadBuffer = Cart.MapperChip.FetchPPU(); if (PPU_ALE) { // pretty sure this can never happen, but keep it just in case. PPU_OctalLatch = (byte)PPU_AddressBus; } } PPU_2007_Read_Latches[1] = !PPU_2007_Read_Latches[0]; PPU_2007_Read_Latches[3] = !PPU_2007_Read_Latches[2]; if (!PPU_2007_Read_Latches[3]) { PPU_2007_Read_SR = false; } PPU_2007_Write_Latches[1] = !PPU_2007_Write_Latches[0]; PPU_2007_Write_Latches[3] = !PPU_2007_Write_Latches[2]; if (!PPU_2007_Write_Latches[3]) { PPU_2007_Write_SR = false; } PPU_2007_DB_PAR = PPU_2007_Write_Latches[1] && !PPU_2007_Write_Latches[3]; PPU_WRITE = !PPU_2007_PaletteRAMEnable && PPU_2007_DB_PAR; if (PPU_2007_DB_PAR) // Using PAR instead of PPU_WRITE, since I re-use StorePPUData() for writes to palette RAM. { StorePPUData(PPU_AddressBus, PPU_2007_WriteData); } } void DrawToScreen() { if (PPU_Dot > 3 && PPU_Dot <= 259 && PPU_Scanline < 241) // the process of drawing a dot to the screen actually has a 2 ppu cycle delay, which the emphasis bits happen after { // in other words, the geryscale/emphasis bits can affect the color that was decided 2 ppu cycles ago. chosenColor = PrevPrevPrevDotColor; if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } // emphasis bits int emphasis = 0; if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. int scanline0OddFrameOffset = 0; if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) { scanline0OddFrameOffset = 1; } if (!PPU_DecodeSignal) { if (!PPU_ShowScreenBorders) { if (scanline0OddFrameOffset == 1 && PPU_Dot == 4) { // do nothing. This would be off screen. } else { Screen.SetPixel(PPU_Dot - 4 - scanline0OddFrameOffset, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color. } } else { Screen.SetPixel(PPU_Dot - 4 - scanline0OddFrameOffset, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color. } } else { if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } PPU_SignalDecode(chosenColor | emphasis); PrevPrevPrevPrevDotColor = chosenColor | emphasis; } } if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot == 259) { // draw the backdrop. chosenColor = PaletteRAM[0]; // emphasis bits int emphasis = 0; if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. if (!PPU_DecodeSignal) { Screen.SetPixel(255, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color. } else { if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } PPU_SignalDecode(chosenColor | emphasis); PrevPrevPrevPrevDotColor = chosenColor | emphasis; } } } void DrawToBorderedScreen() { int dot = PPU_Dot; int scanline = PPU_Scanline; int emphasis = 0; dot -= 3; if (dot < 0) { dot = 341 + dot; scanline--; if (scanline < 0) { scanline = 261; } } int boarderedDot = 0; int boarderedScanline = scanline; if (PPU_ShowScreenBorders && dot == 325) { ntsc_signal_of_dot_0 = ntsc_signal; } if (PPU_DecodeSignal && dot == 277) { RenderNTSCScanline(); } if (scanline < 241 || ((scanline == 241 && dot < 277)) || scanline == 261) { if (dot >= 1 && dot <= 256) // visible pixels. { if (scanline == 261) { chosenColor = 0x0F; } else { chosenColor = PrevPrevPrevDotColor; if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } // emphasis bits if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. } boarderedDot = dot + 64; boarderedScanline = scanline; } else if (dot >= 257 && dot <= 267) // right boarder { if (scanline == 261) { chosenColor = 0x0F; } else { // backdrop. chosenColor = PrevPrevPrevDotColor; if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } // emphasis bits if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. } boarderedDot = dot + 64; boarderedScanline = scanline; } else if (dot >= 268 && dot <= 276) // front porch { // black. chosenColor = 0x0F; boarderedDot = dot + 64; boarderedScanline = scanline; } else if (dot >= 277 && dot <= 301) // horizontal sync { // black. chosenColor = 0x0F; boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else if (dot >= 302 && dot <= 305) // back porch { // black. chosenColor = 0x0F; boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else if (dot >= 306 && dot <= 320) // colorburst { // extremely dark olive. chosenColor = Signal_COLORBURST; boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else if (dot >= 321 && dot <= 325) // back porch { // black. chosenColor = 0x0F; boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else if (dot == 326) // pulse { // backdrop in greyscale if (ThisDotReadFromPaletteRAM) { chosenColor = PrevPrevPrevDotColor; } else { chosenColor = PrevPrevPrevDotColor & 0x30; } // emphasis bits if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else // right boarder { // backdrop. if (scanline == 261 && dot == 0) { chosenColor = 0x0F; } else { chosenColor = PrevPrevPrevDotColor; if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active, { chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30 } // emphasis bits if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT. if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT. if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT. } if (dot != 0) { boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else { boarderedDot = dot + 64; boarderedScanline = scanline; } } } else { if (scanline >= 245 && scanline <= 247) { // black. chosenColor = 0x0F; if (dot >= 277) { boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else { boarderedDot = dot + 64; boarderedScanline = scanline; } } else { // colorburst happens on this line too. if (dot >= 306 && dot <= 320) // colorburst { // extremely dark olive. chosenColor = Signal_COLORBURST; boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else { // black. chosenColor = 0x0F; if (dot >= 277) { boarderedDot = dot - 277; boarderedScanline = scanline + 1; } else { boarderedDot = dot + 64; boarderedScanline = scanline; } } } } if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot < 277) { boarderedDot--; } if (boarderedScanline == 0x106) { boarderedScanline = 0; } if (PPU_DecodeSignal) { PPU_SignalDecode(chosenColor | emphasis); } else { BorderedScreen.SetPixel(boarderedDot, boarderedScanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color. } } public bool PPU_DecodeSignal; public bool PPU_ShowScreenBorders; static float[] Voltages = { 0.228f, 0.312f, 0.552f, 0.880f, // Signal low 0.616f, 0.840f, 1.100f, 1.100f, // Signal high 0.192f, 0.256f, 0.448f, 0.712f, // Signal low, attenuated 0.500f, 0.676f, 0.896f, 0.896f // Signal high, attenuated }; public byte ntsc_signal; public byte ntsc_signal_of_dot_0; public float[] NTSC_Samples = new float[257 * 8 + 24]; public float[] Bordered_NTSC_Samples = new float[341 * 8 + 24]; static float[] Levels = { (Voltages[0] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[1] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[2] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[3] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[4] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[5] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[6] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[7] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[8] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[9] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[10] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[11] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[12] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[13] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[14] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f, (Voltages[15] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f }; float Saturation = 0.75f; int SignalBufferWidth = 12; static double hue = 0; static float chroma_saturation_correction = 2.4f; static double[] SinTable = { Math.Sin(Math.PI* (0 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (1 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (2 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (3 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (4 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (5 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (6 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (7 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (8 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (9 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (10 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Sin(Math.PI* (11 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction }; static double[] CosTable = { Math.Cos(Math.PI* (0 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (1 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (2 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (3 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (4 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (5 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (6 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (7 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (8 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (9 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (10 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction, Math.Cos(Math.PI* (11 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction }; bool InColorPhase(int col, int DecodePhase) { return (col + DecodePhase) % 12 < 6; } static float ntsc_black = 0.312f, ntsc_white = 1.100f; static int Signal_COLORBURST = 512; static int Signal_SYNC = 513; static float Colorburst_High = (0.524f - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f; static float Colorburst_Low = (0.148f - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f; void PPU_SignalDecode(int nesColor) { bool boardered = PPU_ShowScreenBorders; byte phase = ntsc_signal; int i = 0; while (i < 8) { float sample = 0; // Decode the NES color. if (nesColor == Signal_COLORBURST) { sample = InColorPhase(0x8, phase) ? Colorburst_High : Colorburst_Low; } else { int colInd = (nesColor & 0x0F); // 0..15 "cccc" int level = (nesColor >> 4) & 3; // 0..3 "ll" int emphasis = (nesColor >> 6); // 0..7 "eee" if (colInd > 13) { level = 1; } // For colors 14..15, level 1 is forced. int attenuation = ( ((((emphasis & 1) != 0) && InColorPhase(0xC, phase)) || (((emphasis & 2) != 0) && InColorPhase(0x4, phase)) || (((emphasis & 4) != 0) && InColorPhase(0x8, phase))) && (colInd < 0xE)) ? 8 : 0; float low = Levels[0 + level + attenuation]; float high = Levels[4 + level + attenuation]; if (colInd == 0) { low = high; } // For color 0, only high level is emitted if (colInd > 12) { high = low; } // For colors 13..15, only low level is emitted sample = InColorPhase(colInd, phase) ? high : low; } if (boardered) { int dot = PPU_Dot - 3; if (dot < 0) { dot = 341 + dot; } if (dot >= 277) { dot -= 277; } else { dot += 64; } if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot < 277) { dot--; } Bordered_NTSC_Samples[dot * 8 + i] = sample; } else if (PPU_Dot <= 256 + 3) { if (PPU_Dot == 0) { NTSC_Samples[i] = sample; } else { NTSC_Samples[(PPU_Dot - 3) * 8 + i] = sample; } } phase++; phase %= 12; i++; } } public bool PPU_ShowRawNTSCSignal; void RenderNTSCScanline() { byte phase = ntsc_signal_of_dot_0; bool bordered = PPU_ShowScreenBorders; // this value could change at any moment, so it would be nice to avoid errors due to array lengths. int scanline0OddFrameOffset = 0; if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && !bordered) { scanline0OddFrameOffset = 8; } int width = bordered ? BorderedNTSCScreen.Width : NTSCScreen.Width; int i = 0; while (i < width + scanline0OddFrameOffset) { double R = 0; double G = 0; double B = 0; if (!PPU_ShowRawNTSCSignal) { int center = i + 8; int begin = center - 6; int end = center + 6; double Y = 0; double U = 0; double V = 0; for (int p = begin; p < end; ++p) // Collect and accumulate samples { float sample = bordered ? (Bordered_NTSC_Samples[p]) : (NTSC_Samples[p]); Y += sample; int rotation = (phase + p) % 12; U += (sample * SinTable[rotation]); V += (sample * CosTable[rotation]); } //U *= (0.35355339 * 2); //V *= (0.35355339 * 2); U = U * 0.5f + 0.5f; V = V * 0.5f + 0.5f; bool DebugYUV = false; if (DebugYUV) { Y = 0.5; U = (i + 0.0f) / width; V = 1 - (PPU_Scanline / 240f); U -= 0.5f; V -= 0.5f; U *= (0.35355339 * 2); V *= (0.35355339 * 2); U += 0.5f; V += 0.5f; } // convert YUV to RGB R = 1.164 * (Y - 16 / 256.0) + 1.596 * (V - 128 / 256.0); G = 1.164 * (Y - 16 / 256.0) - 0.392 * (U - 128 / 256.0) - 0.813 * (V - 128 / 256.0); B = 1.164 * (Y - 16 / 256.0) + 2.017 * (U - 128 / 256.0); // other values ? //double R = 1.164 * (Y - 16 / 256.0) + 1.14 * (V - 128 / 256.0); //double G = 1.164 * (Y - 16 / 256.0) - (1 / 1.14) * (U - 128 / 256.0) - (1 / (1.14 * 1.78)) * (V - 128 / 256.0); //double B = 1.164 * (Y - 16 / 256.0) + (1.14 * 1.78) * (U - 128 / 256.0); // convert YUV to normalized RGB //double R = 1.164 * (Y - 16 / 256.0) + 1 * (V - 128 / 256.0); //double G = 1.164 * (Y - 16 / 256.0) - 0.31764705882 * (U - 128 / 256.0) - 0.68359375 * (V - 128 / 256.0); //double B = 1.164 * (Y - 16 / 256.0) + 1 * (U - 128 / 256.0); if (R < 0) { R = 0; } if (R > 1) { R = 1; } if (G < 0) { G = 0; } if (G > 1) { G = 1; } if (B < 0) { B = 0; } if (B > 1) { B = 1; } } if (PPU_ShowScreenBorders) { if (PPU_ShowRawNTSCSignal) { R = Bordered_NTSC_Samples[i] * 12; G = Bordered_NTSC_Samples[i] * 12; B = Bordered_NTSC_Samples[i] * 12; if (R < 0) { R = 0; } if (R > 1) { R = 1; } if (G < 0) { G = 0; } if (G > 1) { G = 1; } if (B < 0) { B = 0; } if (B > 1) { B = 1; } } BorderedNTSCScreen.SetPixel(i, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color. } else { if (PPU_ShowRawNTSCSignal) { R = NTSC_Samples[i + 8] * 12; G = NTSC_Samples[i + 8] * 12; B = NTSC_Samples[i + 8] * 12; if (R < 0) { R = 0; } if (R > 1) { R = 1; } if (G < 0) { G = 0; } if (G > 1) { G = 1; } if (B < 0) { B = 0; } if (B > 1) { B = 1; } } if (scanline0OddFrameOffset == 0) { NTSCScreen.SetPixel(i, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color. } else { if (i >= 8) { NTSCScreen.SetPixel(i - 8, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color. } } } i++; } } void PPU_MapperSpecificFunctions() { Cart.MapperChip.PPUClock(); // If the mapper chip does something every ppu clock... (See MMC3) } // If OAM corruption is pending, it occurs on the first rendered dot. public void CorruptOAM() { // basically 8 entries of OAM are getting replaced (this is considered a single "row" of OAM) // PPU_OAMCorruptionIndex is the row that gets corrupted. if (PPU_OAMCorruptionIndex == 0x20) { PPU_OAMCorruptionIndex = 0; } int i = 0; while (i < 8) // 8 entries in a row { OAM[PPU_OAMCorruptionIndex * 8 + i] = OAM[i]; // The corrupted row is replaced with the values from row 0 i++; } OAM2[PPU_OAMCorruptionIndex] = OAM2[0]; // Also corrupt this byte. // this all happens in a single cycle. } bool OamCorruptedOnOddCycle; public byte PPU_OAMLatch; // is this just the ppubus? public ushort InRangeCheck; // Is this sprite in range of htis scanline? public byte PPU_OAMBuffer; // This is the value read from $2004, updated on half cycles. bool NineObjectsOnThisScanline; void PPU_Render_SpriteEvaluation() { bool SpriteEval_ReadOnly_PreRenderLine = false; if (PPU_Scanline == 261) { SpriteEval_ReadOnly_PreRenderLine = true; } if ((PPU_Mask_ShowBackground_Instant || PPU_Mask_ShowSprites_Instant)) { if (PPU_PendingOAMCorruption) // OAM corruption occurs on the visible dot after rendering was enabled. It also can happen on the pre-render line. { PPU_PendingOAMCorruption = false; if (!PPU_OAMCorruptionRenderingEnabledOutOfVBlank) { CorruptOAM(); } PPU_OAMCorruptionRenderingEnabledOutOfVBlank = false; } } if ((PPU_Dot >= 0 && PPU_Dot <= 64)) // Dots 1 through 64, not on the pre-render line. (and also dot 0 for OAM corruption purposes) { // this step is clearing secondary OAM, and writing FF to each byte in the array. if ((PPU_Dot & 1) == 1) { //odd cycles if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) { if (SpriteEval_ReadOnly_PreRenderLine) { PPU_OAMLatch = OAM2[OAM2Address]; } else { PPU_OAMLatch = 0xFF; } if (PPU_Dot == 1) { OAM2Address = 0; // if this is dot 1, reset the secondary OAM address SecondaryOAMFull = false;// also reset the flag that checks of secondary OAM is full. // in preparation for the next section, let's clear these flags too SpriteEvaluationTick = 0; OAMAddressOverflowedDuringSpriteEvaluation = false; } if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } } } else { //even cycles if (PPU_Dot > 0) { if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) { if (!SpriteEval_ReadOnly_PreRenderLine) { OAM2[OAM2Address] = PPU_OAMLatch; // store FF in secondary OAM } if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } OAM2Address++; // increment this value so on the next even cycle, we write to the next SecondaryOAM address. OAM2Address &= 0x1F; // keep the secondary OAM address in-bounds if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant && PPU_Dot == 64) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; } } else { if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } } } else { OAM2Address++; // increment this value so on the next even cycle, we write to the next SecondaryOAM address. OAM2Address &= 0x1F; // keep the secondary OAM address in-bounds } } } else if ((PPU_Dot >= 65 && PPU_Dot <= 256)) // Dots 65 through 256, not on the pre-render line { if (PPU_Dot == 65) { OAM2Address = 0; NineObjectsOnThisScanline = false; } if (PPU_Mask_ShowBackground_Instant || PPU_Mask_ShowSprites_Instant || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) // if rendering is enabled, or was *just* disabled mid evaluation { if ((PPU_Dot & 1) == 1) { //odd cycles byte PrevSpriteEvalTemp = PPU_OAMLatch; PPU_OAMLatch = OAM[PPUOAMAddress]; // read from OAM if ((PPUOAMAddress & 3) == 2) { PPU_OAMLatch &= 0xE7; // OAM address 02, 06, 0A, 0E, 12... are missing bits 3 and 4. } // If rendering was disabled *this* cycle (the odd cycle) then the even cycle will run normally, and the *next odd cycle* will have the OAM address increment. Presumably, that's when we record secondOAMAddr. if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) { PPU_OAMEvaluationCorruptionOddCycle = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; if (!SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress++; } OamCorruptedOnOddCycle = true; } } else { //even cycles if (!OAMAddressOverflowedDuringSpriteEvaluation) { byte PreIncVal = PPUOAMAddress; // for checking if PPUOAMAddress overflows if (!SecondaryOAMFull && !SpriteEval_ReadOnly_PreRenderLine) // If secondary OAM is not yet full, { OAM2[OAM2Address] = PPU_OAMLatch; // store this value at the secondary oam address. } byte OAM2READ = OAM2[OAM2Address]; if (SpriteEvaluationTick == 0) // tick 0: check if this object's y position is in range for this scanline { PPU_OAMEvaluationObjectInXRange = false; InRangeCheck = (ushort)((PPU_Scanline & 0xFF) - PPU_OAMLatch); if (!NineObjectsOnThisScanline && !SpriteEval_ReadOnly_PreRenderLine && InRangeCheck < (PPU_Spritex16 ? 16 : 8)) { PPU_OAMEvaluationObjectInRange = true; // if this sprite is within range. if (!SecondaryOAMFull) { if (!OamCorruptedOnOddCycle) { if (!SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress++; // +1 } OAM2Address++; // increment this for the next write to secondary OAM } if (!SecondaryOAMFull) // if secondary OAM is not full { OAM2Address &= 0x1F; // keep the secondary OAM address in-bounds if (OAM2Address == 0) // If we've overflowed the secondary OAM address { SecondaryOAMFull = true; // secondary OAM is now full. } } // Sprite zero hits actually have nothing to do with reading the object at OAM index 0. Rather, if an object is within range of the scanline on dot 66. // typically, the object processed on dot 66 is OAM[0], though it's possible using precisely timed writes to $2003 to have PPUOAMAddress start processing here from a different value. if (PPU_Dot == 66) { PPU_NextScanlineContainsSpriteZero = true; // this value will be transferred to PPU_PreviousScanlineContainsSpriteZero at the end of the scanline, and that variable is used in sp 0 hit detection. } } else { NineObjectsOnThisScanline = true; PPUOAMAddress++; if (!PPUStatus_SpriteOverflow)// if secondary OAM is full, yet another object is on this scanline { PPUStatus_SpriteOverflow = true; // set the sprite overflow flag } } if (!SpriteEval_ReadOnly_PreRenderLine) { SpriteEvaluationTick++; // increment the tick for next even ppu cycle. } } else { if (PPU_Dot == 66) { PPU_NextScanlineContainsSpriteZero = false; // this value will be transferred to PPU_PreviousScanlineContainsSpriteZero at the end of the scanline, and that variable is used in sp 0 hit detection. } PPU_OAMEvaluationObjectInRange = false; if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { if (SecondaryOAMFull && !NineObjectsOnThisScanline)// this behavior stops after finding the ninth object. { if ((PPUOAMAddress & 0x3) == 3) { PPUOAMAddress++; // A real hardware bug. } else { PPUOAMAddress += 4; // +4 PPUOAMAddress++; // A real hardware bug. } } else { PPUOAMAddress += 4; // +4 PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits } } } } else // ticks 1, 2, or 3 { if (SpriteEvaluationTick == 3) // tick 3: X position. { PPU_OAMEvaluationObjectInRange = false; // OAM X coordinate. // This also runs the "vertical in range check", though typically the result doesn't matter. if (PPU_Scanline - PPU_OAMLatch >= 0 && PPU_Scanline - PPU_OAMLatch < (PPU_Spritex16 ? 16 : 8)) { // if this sprite is within range. PPU_OAMEvaluationObjectInXRange = true; if (!SecondaryOAMFull) { if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress++; // +1 } } else { if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress += 4; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?) } } } else { PPU_OAMEvaluationObjectInXRange = false; if (!SecondaryOAMFull) { if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress += 1; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?) PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits } } else { PPUOAMAddress += 1; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?) PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits } } } else // ticks 1 and 2 don't make any checks. Only increment the OAM address. { if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress++; // +1 } } SpriteEvaluationTick++; // increment the tick for next even ppu cycle. SpriteEvaluationTick &= 3; // and reset the tick to 0 if it reaches 4. if (!SecondaryOAMFull && !SpriteEval_ReadOnly_PreRenderLine) // if secondary OAM is not full { OAM2Address++; // increment the secondary OAM address. OAM2Address &= 0x1F; // keep the secondary OAM address in-bounds if (OAM2Address == 0) // If we've overflowed the secondary OAM address { SecondaryOAMFull = true; // secondary OAM is now full. } } } OamCorruptedOnOddCycle = false; if (PPUOAMAddress < PreIncVal && PPUOAMAddress < 4) // If an overflow occured { OAMAddressOverflowedDuringSpriteEvaluation = true; // set this flag. } PPU_OAMLatch = OAM2READ; // When overflowed, the ppu reads instead of writing to OAM2. (Run this regardless of if OAM2 is full or not.) } else { // OAM Address Overflowed During Sprite Evaluation // fail to write to SecondaryOAM // boo womp. // also update the PPUOAMAddress. if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine) { PPUOAMAddress += 4; // +4 PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits } PPU_OAMLatch = OAM2[OAM2Address]; // When overflowed, the ppu reads instead of writing to OAM2. } if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant && !PPU_OAMEvaluationCorruptionOddCycle) // if we just disabled rendering mid OAM evaluation, the address is incremented yet again. { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; if ((OAM2Address & 3) != 0 && !OAMAddressOverflowedDuringSpriteEvaluation && !SpriteEval_ReadOnly_PreRenderLine) { OAM2Address &= 0xFC; OAM2Address += 4; } if (PPUClock == 0 || PPUClock == 3) { PPU_OAMCorruptionIndex = (byte)(OAM2Address); // this value will be used when rendering is re-enabled and the corruption occurs } if (PPUClock == 1 || PPUClock == 2) { PPU_OAMCorruptionIndex = (byte)(OAM2Address); // this value will be used when rendering is re-enabled and the corruption occurs } if (PPU_Dot == 256) { PPU_OAMCorruptionIndex = OamCorruptedOnOddCycle ? (byte)0 : (byte)1; //I have no idea. } } PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; } } } else if (PPU_Dot >= 257 && PPU_Dot <= 320) // this also happens on the pre-render line. { PPU_CurrentScanlineContainsSpriteZero = PPU_NextScanlineContainsSpriteZero; if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) { PPUOAMAddress = 0; // this is reset during every one of these cycles, 257 through 320 } if (PPU_Dot == 257) { // reset these flags for this section. OAM2Address = 0; SpriteEvaluationTick = 0; } if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank && (PPUClock == 0 || PPUClock == 3)) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } if (PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } switch (SpriteEvaluationTick) { // So each scanline can only have up to 8 sprites. // Each sprite has a Y position, Pattern, Attributes, and X position. // So there's an 8-index-long array for each of those. // Each index in the array is for a different sprite. // Sprites also have 2 "bit plane" shift registers. // These are the 8 pixels to draw for the object on this scanline. // Again, there are 8 objects, so there are 2 8-index-long arrays of bit planes. // each case is a different ppu cycle. // case 0. // next cycle, case 1. // next cycle, case 2, and so on. // case 7 then leads back to case 0. case 0: // Y position dot 257, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's Y position in the array PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteYposition[OAM2Address / 4] = PPU_OAMLatch; PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF)); PPU_PAR_MUX = PPU_PatternAddressRegister_NT; PPU_AddressBus = PPU_PAR_MUX; InRangeCheck = (ushort)((PPU_Scanline & 0xFF) - PPU_OAMLatch); } OAM2Address++; // and increment the Secondary OAM address for next cycle break; case 1: // Pattern dot 258, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's pattern in the array PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpritePattern[OAM2Address / 4] = PPU_OAMLatch; PPU_Render_ShiftRegistersAndBitPlanes(); // Dummy Nametable Fetch } OAM2Address++; // and increment the Secondary OAM address for next cycle break; case 2: // Attribute dot 259, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's attribute in the array PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteAttribute[OAM2Address / 4] = PPU_OAMLatch; PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF)); PPU_PAR_MUX = PPU_PatternAddressRegister_NT; PPU_AddressBus = PPU_PAR_MUX; } OAM2Address++; // and increment the Secondary OAM address for next cycle break; case 3: // X position dot 260, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's X position in the array PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteXposition[OAM2Address / 4] = PPU_OAMLatch; PPU_SpriteShifterCounter[OAM2Address / 4] = PPU_OAMLatch; PPU_Render_ShiftRegistersAndBitPlanes(); // Dummy Nametable Fetch } // notably, the secondary OAM address does not get incremented until case 7 break; case 4: // X position (again) dot 261, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's X position in the array... again. PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteXposition[OAM2Address / 4] = PPU_OAMLatch; // But also: Find the PPU address of this sprite's graphical data inside the Pattern Tables. PPU_SpriteEvaluation_GetSpriteAddress((byte)(OAM2Address / 4)); PPU_CheckPAR(); PPU_PatternAddressRegister_CHR &= 0b1111111110111; PPU_PAR_MUX = PPU_PatternAddressRegister_CHR; PPU_AddressBus = PPU_PAR_MUX; } break; case 5: // X position (again) dot 262, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's X position in the array... again. PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteXposition[OAM2Address / 4] = PPU_OAMLatch; // but also: set up the bit plane shift register. PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch); PPU_SpritePatternL = Cart.MapperChip.FetchPPU(); if (((PPU_SpriteAttribute[OAM2Address / 4] >> 6) & 1) == 1) // Attributes are set up to flip X { PPU_SpritePatternL = Flip(PPU_SpritePatternL); } PPU_SpriteShiftRegisterL[OAM2Address / 4] = PPU_SpritePatternL; } // in-range check. (The pre-render line ends up checking scanline 5 due to the `& 0xFF`. if (!(InRangeCheck < (PPU_Spritex16 ? 16 : 8))) { PPU_SpriteShiftRegisterL[OAM2Address / 4] = 0; // clear the value in this shift register if this object isn't in range. } break; case 6: // X position (again) dot 263, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's X position in the array... again. PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteXposition[OAM2Address / 4] = PPU_OAMLatch; // but also: add 8 to the PPU address. The other bit plane is 8 addresses away. PPU_SpriteEvaluation_GetSpriteAddress((byte)(OAM2Address / 4)); // we need to recalculate this. Slow, but accurate. (TODO: Can we test for this with a well timed write to $2000?) PPU_AddressBus |= 8; PPU_CheckPAR(); PPU_PatternAddressRegister_CHR |= 8; PPU_PAR_MUX = PPU_PatternAddressRegister_CHR; PPU_AddressBus = PPU_PAR_MUX; } break; case 7: // X position (again) dot 264, (+8), (+16) ... if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle { // set this object's X position in the array... again. PPU_OAMLatch = OAM2[OAM2Address]; // Updating PPU_SpriteEvaluationTemp so reading from $2004 works properly. PPU_SpriteXposition[OAM2Address / 4] = PPU_OAMLatch; // read X pos again // but also: set up the second bit plane PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch); PPU_SpritePatternH = Cart.MapperChip.FetchPPU(); if (((PPU_SpriteAttribute[OAM2Address / 4] >> 6) & 1) == 1) // Attributes are set up to flip X { PPU_SpritePatternH = Flip(PPU_SpritePatternH); } PPU_SpriteShiftRegisterH[OAM2Address / 4] = PPU_SpritePatternH; } // in-range check. (The pre-render line ends up checking scanline 5 due to the `& 0xFF`. if (!(InRangeCheck < (PPU_Spritex16 ? 16 : 8))) { PPU_SpriteShiftRegisterH[OAM2Address / 4] = 0; // clear the value in this shift register if this object isn't in range. } if (PPU_SpriteShiftRegisterH[OAM2Address / 4] != 0) { } OAM2Address++; // and increment the Secondary OAM address for next cycle break; } if (PPU_ALE && !PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } OAM2Address &= 0x1F; // keep the secondary OAM address in-bounds SpriteEvaluationTick++; // increment the tick, so next cycle uses the following case in the switch statement SpriteEvaluationTick &= 7; // and reset at 8 if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank && (PPUClock == 1 || PPUClock == 2)) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } } else { // cycles 320 to 340 if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false; PPU_PendingOAMCorruption = true; PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs } if (PPU_Dot == 339) { for (int i = 0; i < 8; i++) { if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground)) { } else { PPU_SpriteShifterCounter[i] = 0; } } } } // and that's all for sprite evaluation! } void PPU_SpriteEvaluation_GetSpriteAddress(byte SecondOAMSlot) { // PPU_PatternSelect_Sprites is set by writing to bit 3 of address $2000 if (!PPU_Spritex16) //8x8 sprites { // The address is $0000 or $1000 depending on the nametable. // plus the pattern value from OAM * 16 // plus the number of scanlines from the top of the object. // if the attributes are set to flip Y, it's 7 - the number of scanlines from the top of the object. if (((PPU_SpriteAttribute[SecondOAMSlot] >> 7) & 1) == 0) // Attributes are not set up to flip Y { PPU_AddressBus = (ushort)((PPU_PatternSelect_Sprites ? 0x1000 : 0) + (PPU_SpritePattern[SecondOAMSlot] << 4) + ((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot])); } else // Attributes are set up to flip Y { PPU_AddressBus = (ushort)((PPU_PatternSelect_Sprites ? 0x1000 : 0) + (PPU_SpritePattern[SecondOAMSlot] << 4) + ((7 - ((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot])) & 7)); } } else //8x16 sprites { // In 8x16 mode, instead of using PPU_PatternSelect_Sprites to determine which pattern table to fetch data from... // these sprites instead use bit 0 of the object's pattern information from OAM. // The address is $0000 or $1000 depending on the nametable. // plus (the pattern value from OAM, clearing bit 0) * 16 // plus the number of scanlines from the top of the object. // if the attributes are set to flip Y, it's 7 - the number of scanlines from the top of the object. // if we're drawing the bottom half of the sprite, add 16. if (((PPU_SpriteAttribute[SecondOAMSlot] >> 7) & 1) == 0) // Attributes are not set up to flip Y { if ((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot] < 8) { PPU_AddressBus = (ushort)((((PPU_SpritePattern[SecondOAMSlot] & 1) == 1) ? 0x1000 : 0) | ((PPU_SpritePattern[SecondOAMSlot] & 0xFE) << 4) + ((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot])); } else { PPU_AddressBus = (ushort)((((PPU_SpritePattern[SecondOAMSlot] & 1) == 1) ? 0x1000 : 0) | (((PPU_SpritePattern[SecondOAMSlot] & 0xFE) << 4) + 16) + (((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot]) & 7)); } } else // Attributes are set up to flip Y { if ((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot] < 8) { PPU_AddressBus = (ushort)((((PPU_SpritePattern[SecondOAMSlot] & 1) == 1) ? 0x1000 : 0) | (((PPU_SpritePattern[SecondOAMSlot] & 0xFE) << 4) + 16) - (((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot]) & 7) + 7); } else { PPU_AddressBus = (ushort)((((PPU_SpritePattern[SecondOAMSlot] & 1) == 1) ? 0x1000 : 0) | (((PPU_SpritePattern[SecondOAMSlot] & 0xFE) << 4) + 7) - (((PPU_Scanline & 0xFF) - PPU_SpriteYposition[SecondOAMSlot]) & 7)); } } } } void PPU_Render_CalculatePixel(bool borders) { // dots 1 through 256 if (PPU_Dot > 256) { borders = true; } if (PPU_Dot <= 256 || borders) { // there are 8 palettes in the PPU // 4 are for the background, and the other 4 are for sprites. byte Palette = 0; // each of these palettes have 4 colors byte Color = 0; if (!borders) { if (PPU_Mask_ShowBackground && (PPU_Dot > 8 || PPU_Mask_8PxShowBackground)) // if rendering is enables for this pixel { byte col0 = (byte)(((PPU_BackgroundPatternShiftRegisterL >> (15 - PPU_FineXScroll))) & 1); // take the bit from the shift register for the pattern low bit plane byte col1 = (byte)(((PPU_BackgroundPatternShiftRegisterH >> (15 - PPU_FineXScroll))) & 1); // take the bit from the shift register for the pattern high bit plane Color = (byte)((col1 << 1) | col0); byte pal0 = (byte)(((PPU_BackgroundAttributeShiftRegisterL) >> (7 - PPU_FineXScroll)) & 1); // take the bit from the shift register for the attribute low bit plane byte pal1 = (byte)(((PPU_BackgroundAttributeShiftRegisterH) >> (7 - PPU_FineXScroll)) & 1); // take the bit from the shift register for the attribute high bit plane Palette = (byte)((pal1 << 1) | pal0); if (Color == 0 && Palette != 0) // color 0 of all palettes are mirrors of color 0 of palette 0 { Palette = 0; } } } // pretty much the same thing, but for sprites instead of background byte SpritePalette = 0; byte SpriteColor = 0; bool SpritePriority = false; // if set, this sprite will be in front of background tiles. Otherwise, it will only take priority if the background is using color 0. if (!borders) { if (PPU_Mask_ShowSprites && (PPU_Dot > 8 || PPU_Mask_8PxShowSprites)) { int i = 0; // check all 8 objects in secondary OAM while (i < 8) { if (PPU_SpriteShifterCounter[i] == 0 || SkippedPreRenderDot341) // if the shifter counter == 0 (the shifter counter is decremented each ppu cycle) { bool SpixelL = ((PPU_SpriteShiftRegisterL[i]) & 0x80) != 0; // take the bit from the shift register for the pattern low bit plane bool SpixelH = ((PPU_SpriteShiftRegisterH[i]) & 0x80) != 0; // take the bit from the shift register for the pattern high bit plane SpriteColor = 0; if (SpixelL) { SpriteColor = 1; } if (SpixelH) { SpriteColor |= 2; } SpritePalette = (byte)((PPU_SpriteAttribute[i] & 0x03) | 0x04); // read the palette from secondary OAM attributes. SpritePriority = ((PPU_SpriteAttribute[i] >> 5) & 1) == 0; // read the priority from secondary OAM attributes. } else // if no objects are in range of this pixel... { i++; // try the next one continue; } if (SpriteColor != 0) // if we found an object, exit the loop. This means, objects earlier in secondary OAM hive higher priority over sprites later in secondary OAM { break; } i++; // This pixel wasn't a part of the previous object. Try the next slot in secondary oam. } // if we hit sprite zero and both rendering background and sprites are enabled... if (PPU_CanDetectSpriteZeroHit && i == 0 && PPU_CurrentScanlineContainsSpriteZero && PPU_Mask_ShowBackground && PPU_Mask_ShowSprites) { if (Color != 0 && SpriteColor != 0) // if both the background and sprites are visible on this pixel { if ((PPU_Mask_8PxShowSprites || PPU_Dot > 8) && PPU_Dot < 256) // and if this isn't on pixel 256, or in the first 8 pixels being masked away from the nametable, if that setting is enabled... { PPUStatus_PendingSpriteZeroHit = true; // we did it! sprite zero hit achieved... the flag is set on teh next half-ppu-cycle. PPU_CanDetectSpriteZeroHit = false; // another sprite zero hit cannot occur until the end of next vblank. if (Logging) // and for some debug logging... { string S = DebugLog.ToString(); // let's add text to the current line letting me know a sprite zero hit occured, and on which dot if (S.Length > 0) { S = S.Substring(0, S.Length - 2); // trim off \n DebugLog = new StringBuilder(S); DebugLog.Append(" ! Sprite Zero Hit ! (Dot " + PPU_Dot + ")\r\n"); } } } } } // which do we draw, the background or the sprite? if (Color == 0 && SpriteColor != 0) // Well, if the background was using color 0, and the sprite wasn't, always draw the sprite. { Color = SpriteColor; // I'm just reusing this background color variable. Palette = SpritePalette; // I'm also just reusing the background palette variable. } else if (SpriteColor != 0) // the background color isn't zero... { if (SpritePriority) // if the sprite has priority, always draw the sprite. { Color = SpriteColor; // I'm just reusing this cackground color variable. Palette = SpritePalette; // I'm also just reusing the background palette variable. } } } } if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline < 240) // if rendering is enabled... { PaletteRAMAddress = (byte)(Palette << 2 | Color); // the Palette RAM address is determined by the palette and color we found. } else { // rendering is disabled... if ((PPU_v & 0x3F1F) >= 0x3F00) // if v points to palette ram: { PaletteRAMAddress = (byte)(PPU_v & 0x1F); // The palette RAM address is simply wherever the v register is. (bitwise and with $1F due to palette RAM mirroring) if ((PaletteRAMAddress & 3) == 0) { PaletteRAMAddress &= 0x0F; // the transparent colors for sprites and backgrounds are shared. } } else { // EXT Pins PaletteRAMAddress = 0; // I'm not really emulating the EXT pins, and as far as I'm aware they aren't used in any games, official or homebrew. // This is typically why the background color is using Palette[0] when rendering is disabled. } } if (PPU_PaletteCorruptionRenderingDisabledOutOfVBlank || PPU_VRegisterChangedOutOfVBlank) { PPU_VRegisterChangedOutOfVBlank = false; PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = false; // PPU palette corruption! CorruptPalettes(Color, Palette); // This corruption also results in a single discolored pixel, and this occurs on all alignments. // I'm not entirely sure how this works, and I think it's the *next* pixel that gets corrupt? More research needed. } DotColor = (byte)((PaletteRAM[0x00 | PaletteRAMAddress]) & 0x3F); // Get the color by reading from Palette RAM // though this is actually drawn to the screen 2 ppu cycles from now. } } void CorruptPalettes(byte Color, byte Palette) { // Depending on the index into a color palette being used to select a color being drawn when rendering was disabled during a nametable fetch on a visible pixel with the PPU V Register (bitwise AND with $3FFF) being >= $3C00... // Palettes get "corrupted" with a specific pattern. // This pattern is determined by: // The lowest nybble of the PPU's V register, // The color index into the palette, // and if this is using a sprite palette. (TODO: emulate this part) // All of this was determined by observations with a custom test cart. // It is entirely possible that the logic defined in this functions is incorrect, or possibly there are more factors at play. // As far as I can tell though, this is "good enough" emulation of palette corruption. if ((CPUClock & 3) != 2) { // this behavior occurs on other alignments, but seems consistent on alignment 2, and very hit or miss on other alignments. // Currently, I'm only emulating this on alignment 2, but I'll probably change this in the future. return; } byte[] CorruptedPalette = new byte[PaletteRAM.Length]; for (int i = 0; i < CorruptedPalette.Length; i++) { CorruptedPalette[i] = PaletteRAM[i]; } switch (Color) { case 0: // simply take the low nybble from the V register. that's the color to corrupt. CorruptedPalette[PPU_v & 0xF] = (byte)((PaletteRAM[0] & PaletteRAM[PPU_v & 0xC]) | (PaletteRAM[0] & PaletteRAM[PPU_v & 0xF]) | (PaletteRAM[PPU_v & 0xC] & PaletteRAM[PPU_v & 0xF])); // TODO: Nybble 7 can corrupt color F. It's inconsistent though, so I'll need to circle back to this. break; case 1: // To be honest, I'm not sure what's going on, so forgive the lack of comments. // There's almost a pattern, but again- unsure on why this is how it behaves. // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research. switch (PPU_v & 0xF) { case 0: CorruptedPalette[0x0] = (byte)((PaletteRAM[0x1] & PaletteRAM[0xD]) | PaletteRAM[0x0]); CorruptedPalette[0x4] = PaletteRAM[0x5]; CorruptedPalette[0x8] = PaletteRAM[0x9]; CorruptedPalette[0xC] = PaletteRAM[0xD]; break; case 1: break; case 2: CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0xD]) & PaletteRAM[0x3]); CorruptedPalette[0x3] = (byte)((PaletteRAM[0x1] | PaletteRAM[0x2]) & PaletteRAM[0x3]); CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]); CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]); CorruptedPalette[0xE] = PaletteRAM[0xD]; CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 3: CorruptedPalette[0x3] &= (byte)(PaletteRAM[0x1] | PaletteRAM[0xD]); CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 4: CorruptedPalette[0x0] = PaletteRAM[0x1]; CorruptedPalette[0x4] = (byte)((PaletteRAM[0x5] & PaletteRAM[0xD]) | PaletteRAM[0x4]); CorruptedPalette[0x8] = PaletteRAM[0x9]; CorruptedPalette[0xC] = PaletteRAM[0xD]; break; case 5: break; case 6: CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x7]) & PaletteRAM[0xD]); CorruptedPalette[0x7] = (byte)((PaletteRAM[0x7] | PaletteRAM[0x6]) & PaletteRAM[0x5]); CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]); CorruptedPalette[0xE] = PaletteRAM[0xD]; CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 7: CorruptedPalette[0x7] &= (byte)(PaletteRAM[0x5] | PaletteRAM[0xD]); CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 8: CorruptedPalette[0x0] = PaletteRAM[0x1]; CorruptedPalette[0x4] = PaletteRAM[0x5]; CorruptedPalette[0x8] = (byte)((PaletteRAM[0x9] & PaletteRAM[0xD]) | PaletteRAM[0x8]); CorruptedPalette[0xC] = PaletteRAM[0xD]; break; case 9: break; case 0xA: CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xD]) & PaletteRAM[0x7]); CorruptedPalette[0xA] = (byte)((PaletteRAM[0xB] | PaletteRAM[0xD]) & PaletteRAM[0xA]); CorruptedPalette[0xB] = (byte)((PaletteRAM[0x9] | PaletteRAM[0xA]) & PaletteRAM[0xB]); CorruptedPalette[0xE] = PaletteRAM[0xD]; CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 0xB: CorruptedPalette[0xB] &= (byte)(PaletteRAM[0x9] | PaletteRAM[0xD]); CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 0xC: CorruptedPalette[0x0] = PaletteRAM[0x1]; CorruptedPalette[0x4] = PaletteRAM[0x5]; CorruptedPalette[0x8] = PaletteRAM[0x9]; CorruptedPalette[0xC] = PaletteRAM[0xD]; break; case 0xD: break; case 0xE: CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xD]) & PaletteRAM[0x7]); CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]); CorruptedPalette[0xE] = PaletteRAM[0xD]; CorruptedPalette[0xF] = PaletteRAM[0xD]; break; case 0xF: CorruptedPalette[0xF] = PaletteRAM[0xD]; break; } // In some tests with case A, bit 3 ($08) of color 3 can remove bit 2 ($04) from the value of color 0 for the purposes of the bitwise AND. It's inconsistent though. break; case 2: // To be honest, I'm not sure what's going on, so forgive the lack of comments. // There's almost a pattern, but again- unsure on why this is how it behaves. // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research. switch (PPU_v & 0xF) { case 0: CorruptedPalette[0x0] = (byte)(PaletteRAM[0x0] | (PaletteRAM[0x2] & PaletteRAM[0xE])); CorruptedPalette[0x4] = PaletteRAM[0x6]; CorruptedPalette[0x8] = PaletteRAM[0xA]; CorruptedPalette[0xC] = PaletteRAM[0xE]; break; case 1: CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1] | PaletteRAM[0xE]) & (PaletteRAM[0x3] | PaletteRAM[0xE])); CorruptedPalette[0x3] = (byte)((PaletteRAM[0x2] | PaletteRAM[0xE] | 0x3C) & PaletteRAM[0x3]); CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x7]) & PaletteRAM[0x5]); CorruptedPalette[0x9] = (byte)((PaletteRAM[0xA] | PaletteRAM[0xB]) & PaletteRAM[0x9]); CorruptedPalette[0xD] = PaletteRAM[0xE]; CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 2: break; case 3: CorruptedPalette[0x3] &= (byte)(PaletteRAM[0x2] | PaletteRAM[0xE]); CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 4: CorruptedPalette[0x0] = PaletteRAM[0x2]; CorruptedPalette[0x4] = (byte)(PaletteRAM[0x4] | (PaletteRAM[0x6] & PaletteRAM[0xE])); CorruptedPalette[0x8] = PaletteRAM[0xA]; CorruptedPalette[0xC] = PaletteRAM[0xE]; break; case 5: CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x5] = (byte)((PaletteRAM[0xE] | PaletteRAM[0x6]) & PaletteRAM[0x5]); CorruptedPalette[0x7] = (byte)((PaletteRAM[0xE] | PaletteRAM[0x6]) & PaletteRAM[0x7]); CorruptedPalette[0xD] = PaletteRAM[0xE]; CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 6: break; case 7: CorruptedPalette[0x7] &= (byte)(PaletteRAM[0x6] | PaletteRAM[0xE]); //CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 8: CorruptedPalette[0x0] = PaletteRAM[0x2]; CorruptedPalette[0x4] = PaletteRAM[0x6]; CorruptedPalette[0x8] = (byte)(PaletteRAM[0x8] | (PaletteRAM[0xA] & PaletteRAM[0xE])); CorruptedPalette[0xC] = PaletteRAM[0xE]; break; case 9: CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]); CorruptedPalette[0x9] = (byte)((PaletteRAM[0xE] | PaletteRAM[0xA] | 0x01) & PaletteRAM[0x9]); CorruptedPalette[0xB] = (byte)((PaletteRAM[0xE] | PaletteRAM[0xA] | 0x31) & PaletteRAM[0xB]); CorruptedPalette[0xD] = PaletteRAM[0xE]; CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 0xA: break; case 0xB: CorruptedPalette[0xB] &= (byte)(PaletteRAM[0xA] | PaletteRAM[0xE]); CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 0xC: CorruptedPalette[0x0] = PaletteRAM[0x2]; CorruptedPalette[0x4] = PaletteRAM[0x6]; CorruptedPalette[0x8] = PaletteRAM[0xA]; CorruptedPalette[0xC] = PaletteRAM[0xE]; break; case 0xD: CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]); CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]); CorruptedPalette[0x9] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]); CorruptedPalette[0xD] = PaletteRAM[0xE]; CorruptedPalette[0xF] = PaletteRAM[0xE]; break; case 0xE: break; case 0xF: CorruptedPalette[0xF] = PaletteRAM[0xE]; break; } break; case 3: // To be honest, I'm not sure what's going on, so forgive the lack of comments. // There's almost a pattern, but again- unsure on why this is how it behaves. // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research. switch (PPU_v & 0xF) { case 0: CorruptedPalette[0x0] = (byte)((PaletteRAM[0x3] | (PaletteRAM[0xF] & PaletteRAM[0x0]))); CorruptedPalette[0x4] &= PaletteRAM[0x7]; CorruptedPalette[0x8] &= (byte)(PaletteRAM[0x9] | PaletteRAM[0xA] | PaletteRAM[0xB] | PaletteRAM[0xF] | 0x22); // magic number... Probably a temperature thing? I've seen 02, 22, 2C, or 2E CorruptedPalette[0xC] = PaletteRAM[0xF]; break; case 1: CorruptedPalette[0x1] = (byte)((PaletteRAM[0x1] | PaletteRAM[0xF]) & PaletteRAM[0x3]); CorruptedPalette[0x5] = PaletteRAM[0x7]; CorruptedPalette[0x9] = PaletteRAM[0xB]; CorruptedPalette[0xD] = PaletteRAM[0xF]; break; case 2: CorruptedPalette[0x2] = (byte)((PaletteRAM[0x3] | PaletteRAM[0xF]) & PaletteRAM[0x3]); CorruptedPalette[0x6] = PaletteRAM[0x7]; CorruptedPalette[0xA] = PaletteRAM[0xB]; CorruptedPalette[0xE] = PaletteRAM[0xF]; break; case 3: break; case 4: CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x7); // magic number... I've only seen it as 07 though. CorruptedPalette[0x4] &= (byte)(PaletteRAM[0x7] | PaletteRAM[0xF]); CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | PaletteRAM[0xF] | (PaletteRAM[0xC] ^ 0xFF)); CorruptedPalette[0xC] = (byte)((PaletteRAM[0x7] & PaletteRAM[0xF]) | PaletteRAM[0xC]); break; case 5: CorruptedPalette[0x1] = PaletteRAM[0x3]; CorruptedPalette[0x5] = (byte)((PaletteRAM[0x5] | PaletteRAM[0xF]) & PaletteRAM[0x7]); CorruptedPalette[0x9] = PaletteRAM[0xB]; CorruptedPalette[0xD] = PaletteRAM[0xF]; break; case 6: CorruptedPalette[0x2] = PaletteRAM[0x3]; CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xF]) & PaletteRAM[0x7]); CorruptedPalette[0xA] = PaletteRAM[0xB]; CorruptedPalette[0xE] = PaletteRAM[0xF]; break; case 7: break; case 8: CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x23); // magic number... I've only seen it as 23 though. CorruptedPalette[0x4] = (byte)(PaletteRAM[0x7]); CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | PaletteRAM[0xF] | (PaletteRAM[0xC] ^ 0xFF)); CorruptedPalette[0xC] = (byte)((PaletteRAM[0xB] & PaletteRAM[0xF]) | PaletteRAM[0xC]); break; case 9: CorruptedPalette[0x1] = PaletteRAM[0x3]; CorruptedPalette[0x5] = PaletteRAM[0x7]; CorruptedPalette[0x9] = (byte)((PaletteRAM[0x9] | PaletteRAM[0xF]) & PaletteRAM[0xB]); CorruptedPalette[0xD] = PaletteRAM[0xF]; break; case 0xA: CorruptedPalette[0x2] = PaletteRAM[0x3]; CorruptedPalette[0x6] = PaletteRAM[0x7]; CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0xF]) & PaletteRAM[0xB]); CorruptedPalette[0xE] = PaletteRAM[0xF]; break; case 0xB: break; case 0xC: CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x37); // magic number... I've only seen it as 23 though. CorruptedPalette[0x4] = PaletteRAM[0x7]; CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | 0x2F); // Magic number. I've seen 2F and 2E CorruptedPalette[0xC] = PaletteRAM[0xF]; break; case 0xD: CorruptedPalette[0x1] = PaletteRAM[0x3]; CorruptedPalette[0x5] = PaletteRAM[0x7]; CorruptedPalette[0x9] = PaletteRAM[0xB]; CorruptedPalette[0xD] = PaletteRAM[0xF]; break; case 0xE: CorruptedPalette[0x2] = PaletteRAM[0x3]; CorruptedPalette[0x6] = PaletteRAM[0x7]; CorruptedPalette[0xA] = PaletteRAM[0xB]; CorruptedPalette[0xE] = PaletteRAM[0xF]; break; case 0xF: break; } break; } for (int i = 0; i < CorruptedPalette.Length; i++) { PaletteRAM[i] = CorruptedPalette[i]; } } byte PPU_RenderTemp; // a variable used in the following function to store information between ppu cycles. bool PPU_Commit_NametableFetch; bool PPU_Commit_AttributeFetch; bool PPU_Commit_PatternLowFetch; bool PPU_Commit_PatternHighFetch; void PPU_Render_ShiftRegistersAndBitPlanes() { byte cycleTick; // for the switch statement below, this checks which case to run on a given ppu cycle. cycleTick = (byte)((PPU_Dot+7) & 7); if (PPU_ALE && PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } switch (cycleTick) { case 0: PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF)); PPU_PAR_MUX = PPU_PatternAddressRegister_NT; PPU_AddressBus = PPU_PAR_MUX; break; case 1: // fetch byte from Nametable PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_NT & 0xFF00) | PPU_OctalLatch); PPU_RenderTemp = Cart.MapperChip.FetchPPU(); PPU_Commit_NametableFetch = true; break; case 2: PPU_PatternAddressRegister_AT = (ushort)(0x23C0 | (PPU_v & 0x0C00) | ((PPU_v >> 4) & 0x38) | ((PPU_v >> 2) & 0x07)); PPU_PAR_MUX = PPU_PatternAddressRegister_AT; PPU_AddressBus = PPU_PAR_MUX; break; case 3: // fetch attribute byte from attribute table PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_AT & 0xFF00) | PPU_OctalLatch); PPU_RenderTemp = Cart.MapperChip.FetchPPU(); PPU_Commit_AttributeFetch = true; // now we only have the 2 bits we're looking for break; case 4: PPU_CheckPAR(); PPU_PatternAddressRegister_CHR &= 0b1111111110111; PPU_PAR_MUX = PPU_PatternAddressRegister_CHR; PPU_AddressBus = PPU_PAR_MUX; break; case 5: // fetch pattern bits from value read off the nametable PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch); PPU_RenderTemp = Cart.MapperChip.FetchPPU(); PPU_Commit_PatternLowFetch = true; break; case 6: PPU_CheckPAR(); PPU_PatternAddressRegister_CHR |= 8; PPU_PAR_MUX = PPU_PatternAddressRegister_CHR; PPU_AddressBus = PPU_PAR_MUX; break; case 7: // fetch pattern bits with the new address PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch); PPU_RenderTemp = Cart.MapperChip.FetchPPU(); PPU_Commit_PatternHighFetch = true; break; } if (PPU_ALE && !PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } } void PPU_Render_CommitShiftRegistersAndBitPlanes() { if (PPU_Commit_NametableFetch) { PPU_Commit_NametableFetch = false; PPU_PatternAddressRegister_CHR &= 0b1000000001111; if (PPU_Dot < 256 || PPU_Dot > 320) { PPU_PatternAddressRegister_CHR |= (ushort)( (byte)(PPU_AddressBus) << 4); } else { PPU_PatternAddressRegister_CHR |= (ushort)(OAM2[(OAM2Address&0x1C)+1]<< 4); } } if (PPU_Commit_AttributeFetch) { PPU_Commit_AttributeFetch = false; PPU_Attribute = PPU_RenderTemp; // 1 byte of attribute data is 4 tiles worth. determine which tile this is for. if ((PPU_v & 3) >= 2) // If this is on the right tile { PPU_Attribute = (byte)(PPU_Attribute >> 2); } if ((((PPU_v & 0b0000001111100000) >> 5) & 3) >= 2) // If this is on the bottom tile { PPU_Attribute = (byte)(PPU_Attribute >> 4); } PPU_Attribute = (byte)(PPU_Attribute & 3); } if (PPU_Commit_PatternLowFetch) { PPU_Commit_PatternLowFetch = false; PPU_LowBitPlane = PPU_RenderTemp; } if (PPU_Commit_PatternHighFetch) { PPU_Commit_PatternHighFetch = false; PPU_HighBitPlane = PPU_RenderTemp; PPU_LoadShiftRegisters(); PPU_IncrementScrollX(); } } void PPU_Render_ShiftRegistersAndBitPlanes_DummyNT() { if (PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } if (PPU_Dot == 0) { PPU_CheckPAR(); PPU_PatternAddressRegister_CHR &= 0b1111111110111; PPU_AddressBus = PPU_PatternAddressRegister_CHR; } else { byte cycleTick; // for the switch statement below, this checks which case to run on a given ppu cycle. cycleTick = (byte)(PPU_Dot - 337); switch (cycleTick) { case 0: PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF)); break; case 1: // fetch byte from Nametable PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF)); PPU_RenderTemp = Cart.MapperChip.FetchPPU(); PPU_Commit_NametableFetch = true; break; case 2: PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF)); break; case 3: // fetch attribute byte from attribute table PPU_RenderTemp = Cart.MapperChip.FetchPPU(); //IGNORED NT FETCH: This actually doesn't update the NT register. break; } } if (PPU_ALE && !PPU_READ) { Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus; } } public void PPU_CheckPAR() { // Some bits in PAR change based on context: if(PPU_Dot < 256 || PPU_Dot > 320) { // Which pattern table do we use for nametable fetches? PPU_PatternAddressRegister_CHR &= 0b0111111111000; PPU_PatternAddressRegister_CHR |= (ushort)(PPU_PatternSelect_Background ? 0b1000000000000 : 0); PPU_PatternAddressRegister_CHR |= (ushort)((PPU_v & 0b0111000000000000) >> 12); } else { // Which pattern table do we use for sprite fetches? if(!PPU_Spritex16) { bool flipy = (OAM2[(OAM2Address & 0x1C) + 2] & 0x80) != 0; PPU_PatternAddressRegister_CHR &= 0b0111111111000; PPU_PatternAddressRegister_CHR |= (ushort)(PPU_PatternSelect_Sprites ? 0b1000000000000 : 0); PPU_PatternAddressRegister_CHR |= (ushort)(flipy ? 7-(InRangeCheck & 0x7) : (InRangeCheck & 0x7)); } else { bool flipy = (OAM2[(OAM2Address & 0x1C) + 2] & 0x80) != 0; PPU_PatternAddressRegister_CHR &= 0b0111111101000; PPU_PatternAddressRegister_CHR |= (ushort)(((OAM2[(OAM2Address&0x1C)+1] & 1) != 0) ? 0b1000000000000 : 0); // Bit 0 of the OAM2 Pattern PPU_PatternAddressRegister_CHR |= (ushort)(flipy ? 7 - (InRangeCheck & 0x7) : (InRangeCheck & 0x7)); PPU_PatternAddressRegister_CHR |= (ushort)(((InRangeCheck & 0x08) ^ (flipy ? 8 : 0)) <<1); } } } // in sprite evaluation, if a sprite is horizontally mirrored, we need to flip all the order of the bits in the shift register. public byte Flip(byte b) { b = (byte)(((b & 0xF0) >> 4) | ((b & 0xF) << 4)); b = (byte)(((b & 0xCC) >> 2) | ((b & 0x33) << 2)); b = (byte)(((b & 0xAA) >> 1) | ((b & 0x55) << 1)); return b; } void PPU_UpdateBackgroundShiftRegisters() { PPU_BackgroundPatternShiftRegisterL = (ushort)(PPU_BackgroundPatternShiftRegisterL << 1); // shift 1 bit to the left. Bring in a 0. PPU_BackgroundPatternShiftRegisterH = (ushort)((PPU_BackgroundPatternShiftRegisterH << 1) | 1); // shift 1 bit to the left. Bring in a 1. PPU_BackgroundAttributeShiftRegisterL = (ushort)((PPU_BackgroundAttributeShiftRegisterL << 1) | (PPU_AttributeLatchRegister & 1)); // shift 1 bit to the left. Bring in Attribute low bit. PPU_BackgroundAttributeShiftRegisterH = (ushort)((PPU_BackgroundAttributeShiftRegisterH << 1) | ((PPU_AttributeLatchRegister & 10) >> 1)); // shift 1 bit to the left. Bring in Attribute high bit. } void UpdateSpriteShiftRegisters() { if (PPU_Dot <= 256) // the shift registers for sprites are shifted after the rendering process. { // shift all 8 sprite shift registers. int i = 0; while (i < 8) { if (PPU_SpriteShifterCounter[i] > 0 && !SkippedPreRenderDot341) { PPU_SpriteShifterCounter[i]--; // decrement the X position of all objects in secondary OAM. When this is zero, the ppu can draw it. } else { if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground)) // this happens if rendering either sprites or background. { PPU_SpriteShiftRegisterL[i] = (byte)(PPU_SpriteShiftRegisterL[i] << 1); // shift 1 bit to the left. PPU_SpriteShiftRegisterH[i] = (byte)(PPU_SpriteShiftRegisterH[i] << 1); // shift 1 bit to the left. } } i++; } } } void PPU_LoadShiftRegisters() { // this runs as the first step of PPU_Render_ShiftRegistersAndBitPlanes(), using the values determined by the previous 8 steps of PPU_Render_ShiftRegistersAndBitPlanes(). PPU_BackgroundPatternShiftRegisterL = (ushort)((PPU_BackgroundPatternShiftRegisterL & 0xFF00) | PPU_LowBitPlane); PPU_BackgroundPatternShiftRegisterH = (ushort)((PPU_BackgroundPatternShiftRegisterH & 0xFF00) | PPU_HighBitPlane); PPU_AttributeLatchRegister = PPU_Attribute; } void PPU_IncrementScrollX() { // used when setting up shift registers for the background // update the v register. Either increment it, or reset the scroll if ((PPU_v & 0x001F) == 31) { PPU_v &= 0xFFE0; // resetting the scroll PPU_v ^= 0x0400; } else { PPU_v++; // increment } } void PPU_IncrementScrollY() { if (CopyV) { PPU_v = (ushort)(PPU_Update2006Value_Temp & PPU_Update2006Value); // This isn't actually accurate. More research needed. } else { if ((PPU_v & 0x7000) != 0x7000) { PPU_v += 0x1000; } else { PPU_v &= 0x0FFF; int y = (PPU_v & 0x03E0) >> 5; if (y == 29) { y = 0; // reset the Y value and also flip some other bit in the 'v' register PPU_v ^= 0x0800; } else if (y == 31) { y = 0; // reset the Y value } else { y++; // increment the Y value } PPU_v = (ushort)((PPU_v & 0xFC1F) | (y << 5)); } } } void PPU_ResetXScroll() { // If a write to $2000 occurs during this ppu cycle, PPU_TempVRAMAddress will be the incorrect value! // The value of PPU_TempVRAMAddress will be corrected on the next ppu cycle, but it's already too late. // This is the "scanline bug" : https://www.nesdev.org/wiki/PPU_glitches#PPUCTRL // The bug is only visible if the nametable mirroring is vertical. PPU_v = (ushort)((PPU_v & 0b0111101111100000) | (PPU_t & 0b0000010000011111)); } void PPU_ResetYScroll() { // The exact same issue from PPU_ResetXScroll() can happen here too, except this corrupts an entire frame. // The bug is only visible if the nametable mirroring is horizontal. //PPU_TempVRAMAddress = (ushort)((PPU_TempVRAMAddress & 0b0111110000011111) | (0b000001111000000)); //Uncomment this line to experiment with the "Attirbutes as tiles" bug. PPU_v = (ushort)((PPU_v & 0b0000010000011111) | (PPU_t & 0b0111101111100000)); } void DecayPPUDataBus() { int i = 0; while (i < PPUBusDecay.Length) { if (PPUBusDecay[i] > 0) { PPUBusDecay[i]--; if (PPUBusDecay[i] == 0) { PPUBus &= DecayBitmask[i]; } } i++; } } byte[] DecayBitmask = { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F }; // The object attribute memory DMA! bool OAMDMA_Aligned = false; bool OAMDMA_Halt = false; bool DMCDMA_Halt = false; byte OAM_InternalBus; // a data bus that's used for the OAM DMA ushort OAMAddressBus; // the address bus of the OAM DMA // The DMAs (Direct Memory Accesses) Have "get" and "put" cycles. // they can also be "halted" in which case, it will always read instead of write. // the following functions, // OAMDMA_Get() : Get cycles are reads // OAMDMA_Halted() : Halted gets and halted puts are both reads from the current address bus // OAMDMA_Put() : Put cycles are writes to OAM. // DMCDMA_Get() : Get cycles are reads // DMCDMA_Halted() : Halted gets and halted puts are both reads from the current address bus // DMCDMA_Put() : Put cycles are writes to the DMC shifter. void OAMDMA_Get() { OAMAddressBus = (ushort)(DMAPage << 8 | DMAAddress); OAMDMA_Aligned = true; // the fetch happens regardless of halt OAM_InternalBus = Fetch(OAMAddressBus); } void OAMDMA_Halted() { Fetch(addressBus); // if halted, just read from the current address bus. } void OAMDMA_Put() { if (OAMDMA_Aligned) // if the DMA is aligned { Store(OAM_InternalBus, 0x2004); // write to OAM DMAAddress++; if (DMAAddress == 0) // if we overflow the DMA address { DoOAMDMA = false; // we have completed the DMA. OAMDMA_Aligned = false; return; } } else // if this is an alignment cycle { Fetch(addressBus); // just read from the current address bus } } void DMCDMA_Get() { // now reload the DMC buffer. APU_DMC_Buffer = Fetch(APU_DMC_AddressCounter); APU_DMC_AddressCounter++; if (APU_DMC_AddressCounter == 0) { APU_DMC_AddressCounter = 0x8000; } if (APU_DMC_BytesRemaining > 0) { // due to writes to $4015 setting the BytesRemaining to 0 if disabled, this could potentially underflow without the if statement. APU_DMC_BytesRemaining--; } if (APU_DMC_BytesRemaining == 0) { //reset sample if (!APU_DMC_Loop) { APU_Status_DMC = false; if (APU_DMC_EnableIRQ) // if the DMC should fire an IRQ when it completes... { IRQ_LevelDetector = true; APU_Status_DMCInterrupt = true; } } else { StartDMCSample(); } } DoDMCDMA = false; OAMDMA_Aligned = false; CannotRunDMCDMARightNow = 2; } void DMCDMA_Halted() { Fetch(addressBus); } void DMCDMA_Put() { Fetch(addressBus); } // Typically in the last CPU cycle of an instruction, the console will check if the NMI edge detector or IRQ level detector is set. In which case, it's time to run an interrupt. // The timing on this is different for branch instructions, and the BRK instruction doesn't do this at all. void PollInterrupts() { NMI_PreviousPinsSignal = NMI_PinsSignal; NMI_PinsSignal = NMILine; if (NMI_PinsSignal && !NMI_PreviousPinsSignal) { DoNMI = true; } DoIRQ = IRQLine && !flag_Interrupt; } void PollInterrupts_CantDisableIRQ() { NMI_PreviousPinsSignal = NMI_PinsSignal; NMI_PinsSignal = NMILine; if (NMI_PinsSignal && !NMI_PreviousPinsSignal) { DoNMI = true; } if (!DoIRQ) { DoIRQ = IRQLine && !flag_Interrupt; } } void CompleteOperation() { operationCycle = 0xFF; // this will be incremented to 0. addressBus = programCounter; CPU_Read = true; IgnoreH = false; } public void _6502() { if ((DoDMCDMA && (APU_Status_DMC || APU_ImplicitAbortDMC4015) && CPU_Read) || (DoOAMDMA && CPU_Read)) // Are we running a DMA? Did it fail? Also some specific behavior can force a DMA to abort. Did that occur? { if ( (opCode == 0x93 && operationCycle == 4) || (opCode == 0x9B && operationCycle == 3) || (opCode == 0x9C && operationCycle == 3) || (opCode == 0x9E && operationCycle == 3) || (opCode == 0x9F && operationCycle == 3) ) { IgnoreH = true; } if (DoOAMDMA && FirstCycleOfOAMDMA) { FirstCycleOfOAMDMA = false; if (!APU_PutCycle) // if the first cycle of an OAM DMA is a get cycle, it's a halt cycle. { OAMDMA_Halt = true; } } if (APU_PutCycle) { // Put cycle (write) if (DoDMCDMA && DoOAMDMA) // if we're running both a DMC and OAM DMA. { if (DMCDMA_Halt && OAMDMA_Halt) // both halt cycles { OAMDMA_Halted(); } else if (!OAMDMA_Halt && DMCDMA_Halt) // only DMC halted { OAMDMA_Put(); } else if (OAMDMA_Halt && !DMCDMA_Halt) // only OAM halted { DMCDMA_Put(); // Can this logically ever happen? } else // none halted : OAM DMA has priority { OAMDMA_Put(); } } else // only performing a single DMA { if (DoDMCDMA) // only running DMC DMA { if (DMCDMA_Halt) { DMCDMA_Halted(); } else { DMCDMA_Put(); } } else // only running OAM DMA { if (OAMDMA_Halt) { OAMDMA_Halted(); } else { OAMDMA_Put(); } } } } else { // Get cycle (read) if (DoDMCDMA && DoOAMDMA) // if we're running both a DMC and OAM DMA. { if (DMCDMA_Halt && OAMDMA_Halt) // both halt cycles { DMCDMA_Halted(); } else if (!OAMDMA_Halt && DMCDMA_Halt) // only DMC halted { OAMDMA_Get(); } else if (OAMDMA_Halt && !DMCDMA_Halt) // only OAM halted { DMCDMA_Get(); } else // none halted : DMC DMA has priority { DMCDMA_Get(); } } else { // only performing a single DMA if (DoDMCDMA) // only running DMC DMA { if (DMCDMA_Halt) { DMCDMA_Halted(); } else { DMCDMA_Get(); } } else // only running OAM DMA { if (OAMDMA_Halt) { OAMDMA_Halted(); } else { OAMDMA_Get(); } } } DMCDMA_Halt = false; // both halt cycles get cleared after a get cycle. OAMDMA_Halt = false; } } else if (operationCycle == 0) // We are not running any DMAs, and this is the first cycle of an instruction. { // cycle 0. fetch opcode: addressBus = programCounter; opCode = Fetch(addressBus); // Fetch the value at the program counter. This is the opcode. if (DoNMI) // If an NMI is occurring, { opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for NMIs) } else if (DoIRQ) // If an IRQ is occurring, { opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for IRQs) } else if (DoReset) // If a RESET is occurring, { opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for RESETs) } else if (opCode == 0) // Otherwise, if an interrupt is not occurring, and the opcode is already 0 { DoBRK = true; // There's also specific behavior for the BRK instruction if it is in-fact a BRK, and not an interrupt. } if (Logging && !LoggingPPU) // For debugging only. { Debug(); // This is where the tracelogger occurs. } if ((!DoNMI && !DoIRQ && !DoReset)) // If we aren't running any interrupts... { programCounter++; // the PC is incremented to the next address addressBus = programCounter; } operationCycle++; // increment this for use in the following CPU cycle. } else { // a really big switch statement. // depending on the value of the opcode, different behavior will take place. // this is how instructions work. // All instructions are labeled. If it's an undocumented opcode, I also write "***" next to it. switch (opCode) { case 0x00: //BRK switch (operationCycle) { case 1: if (!DoBRK) { Fetch(addressBus); //dummy fetch without incrementing PC. } else { GetImmediate(); //dummy fetch and PC increment } break; case 2: if (!DoReset) { Push((byte)(programCounter >> 8)); } else { ResetReadPush(); } break; case 3: if (!DoReset) { Push((byte)programCounter); } else { ResetReadPush(); } break; case 4: if (!DoReset) { status = flag_Carry ? (byte)0x01 : (byte)0; status |= flag_Zero ? (byte)0x02 : (byte)0; status |= flag_Interrupt ? (byte)0x04 : (byte)0; status |= flag_Decimal ? (byte)0x08 : (byte)0; status |= DoBRK ? (byte)0x10 : (byte)0; status |= 0x20; status |= flag_Overflow ? (byte)0x40 : (byte)0; status |= flag_Negative ? (byte)0x80 : (byte)0; Push(status); } else { ResetReadPush(); } PollInterrupts(); // check for NMI? break; case 5: if (DoNMI) { programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFA))); } else if (DoReset) { programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFC))); } else { programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFE))); } break; case 6: if (DoNMI) { programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFB) << 8)); } else if (DoReset) { programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFD) << 8)); } else { programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFF) << 8)); } CompleteOperation(); // notably, BRK does not check the NMI edge detector at the end of the instruction DoReset = false; DoNMI = false; DoIRQ = false; IRQLine = false; DoBRK = false; flag_Interrupt = true; break; } break; case 0x01: //(ORA, X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x02: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x03: //(SLO, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x04: //DOP *** if (operationCycle == 1) { GetAddressZeroPage(); } else { // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); } break; case 0x05: //ORA zp if (operationCycle == 1) { GetAddressZeroPage(); } else { // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); } break; case 0x06: //ASL, zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_ASL(dl, addressBus); CompleteOperation(); break; } break; case 0x07: //SLO zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x08: //PHP if (operationCycle == 1) { //dummy fetch Fetch(addressBus); } else { PollInterrupts(); // read from address status = flag_Carry ? (byte)0x01 : (byte)0; status += flag_Zero ? (byte)0x02 : (byte)0; status += flag_Interrupt ? (byte)0x04 : (byte)0; status += flag_Decimal ? (byte)0x08 : (byte)0; status += 0x10; //always set in PHP status += 0x20; //always set in PHP status += flag_Overflow ? (byte)0x40 : (byte)0; status += flag_Negative ? (byte)0x80 : (byte)0; Push(status); CompleteOperation(); } break; case 0x09: //ORA Imm PollInterrupts(); GetImmediate(); Op_ORA(dl); CompleteOperation(); break; case 0x0A: //ASL A PollInterrupts(); Fetch(addressBus); // dummy read Op_ASL_A(); CompleteOperation(); break; case 0x0B: //ANC Imm *** PollInterrupts(); GetImmediate(); A = (byte)(A & dl); flag_Carry = A >= 0x80; flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0x0C: //TOP *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x0D: //ORA Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x0E: //ASL, Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ASL(dl, addressBus); CompleteOperation(); break; } break; case 0x0F: //SLO Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x10: //BPL switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (flag_Negative) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0x11: //(ORA) Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x12: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x13: //(SLO) Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x14: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x15: //ORA zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x16: //ASL, zp X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ASL(dl, addressBus); CompleteOperation(); break; } break; case 0x17: //SLO zp X *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x18: //CLC PollInterrupts(); Fetch(addressBus); // dummy read flag_Carry = false; CompleteOperation(); break; case 0x19: //ORA Abs, Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x1A: //NOP *** PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; case 0x1B: //SLO Abs Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x1C: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x1D: //ORA Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_ORA(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x1E: //ASL, Abs, X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_ASL(dl, addressBus); CompleteOperation(); break; } break; case 0x1F: //SLO Abs, X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_SLO(dl, addressBus); CompleteOperation(); break; } break; case 0x20: //JSR switch (operationCycle) { // this is pretty cursed, though according to visual6502, this is apparently what happens. case 1: // fetch the byte that will be PC low addressBus = programCounter; dl = Fetch(addressBus); programCounter++; break; case 2: // transfer stack pointer to address bus, and alu to stack pointer. I'm just reusing `dl` here, but this instruction actually uses the Arithmetic Logic Unit for this. addressBus = (ushort)(0x100 | stackPointer); stackPointer = dl; CPU_Read = false; Fetch(addressBus); // dummy read break; case 3: // push PC high to stack via address bus Store((byte)((programCounter & 0xFF00) >> 8), addressBus); addressBus = (ushort)((byte)(addressBus - 1) | 0x100); break; case 4: // push PC low to stack via address bus Store((byte)(programCounter & 0xFF), addressBus); addressBus = (ushort)((byte)(addressBus - 1) | 0x100); specialBus = (byte)addressBus; CPU_Read = true; break; case 5: // fetch PC High, transfer stack pointer to PC low, address bus to stack pointer. PollInterrupts(); addressBus = programCounter; programCounter = (ushort)((Fetch(addressBus) << 8) | stackPointer); stackPointer = specialBus; CompleteOperation(); break; } break; case 0x21: //(AND, X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x22: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x23: //(RLA, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x24: //BIT Zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); dl = Fetch(addressBus); flag_Zero = (A & dl) == 0; flag_Negative = (dl & 0x80) != 0; flag_Overflow = (dl & 0x40) != 0; CompleteOperation(); break; } break; case 0x25: //AND zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x26: //ROL zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_ROL(dl, addressBus); CompleteOperation(); break; } break; case 0x27: //RLA zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x28: //PLP switch (operationCycle) { case 1: //dummy fetch Fetch(addressBus); break; case 2: //increment S addressBus = (ushort)(0x100 + stackPointer); Fetch(addressBus); // dummy read stackPointer++; break; case 3: // read from address PollInterrupts(); addressBus = (ushort)(0x100 + stackPointer); status = Fetch(addressBus); flag_Carry = (status & 1) == 1; flag_Zero = ((status & 0x02) >> 1) == 1; flag_Interrupt = ((status & 0x04) >> 2) == 1; flag_Decimal = ((status & 0x08) >> 3) == 1; flag_Overflow = ((status & 0x40) >> 6) == 1; flag_Negative = ((status & 0x80) >> 7) == 1; CompleteOperation(); break; } break; case 0x29: //AND Imm PollInterrupts(); GetImmediate(); Op_AND(dl); CompleteOperation(); break; case 0x2A: //ROL A PollInterrupts(); Fetch(addressBus); // dummy read Op_ROL_A(); CompleteOperation(); break; case 0x2B: //ANC Imm *** (same as 0x0B) PollInterrupts(); GetImmediate(); A = (byte)(A & dl); flag_Carry = A >= 0x80; flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0x2C: //BIT Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); dl = Fetch(addressBus); flag_Zero = (A & dl) == 0; flag_Negative = (dl & 0x80) != 0; flag_Overflow = (dl & 0x40) != 0; CompleteOperation(); break; } break; case 0x2D: //AND Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x2E: //ROL Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ROL(dl, addressBus); CompleteOperation(); break; } break; case 0x2F: //RLA Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x30: //BMI switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (!flag_Negative) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0x31: //(AND), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x32: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x33: //(RLA), Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x34: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x35: //AND zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x36: //ROL zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ROL(dl, addressBus); CompleteOperation(); break; } break; case 0x37: //RLA zp, X *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x38: //SEC PollInterrupts(); Fetch(addressBus); // dummy read flag_Carry = true; CompleteOperation(); break; case 0x39: //AND Abs, Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x3A: //NOP *** PollInterrupts(); addressBus = programCounter; Fetch(addressBus); CompleteOperation(); break; case 0x3B: //RLA Abs, Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x3C: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x3D: //AND Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_AND(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x3E: //ROL Abs, X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_ROL(dl, addressBus); CompleteOperation(); break; } break; case 0x3F: //RLA Abs, X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_RLA(dl, addressBus); CompleteOperation(); break; } break; case 0x40: //RTI switch (operationCycle) { case 1: GetImmediate(); break; case 2: addressBus = (ushort)(0x100 | stackPointer); Fetch(addressBus); addressBus = (ushort)((byte)(addressBus + 1) | 0x100); break; case 3: status = Fetch(addressBus); flag_Carry = (status & 1) != 0; flag_Zero = (status & 0x02) != 0; flag_Interrupt = (status & 0x04) != 0; flag_Decimal = (status & 0x08) != 0; flag_Overflow = (status & 0x40) != 0; flag_Negative = (status & 0x80) != 0; addressBus = (ushort)((byte)(addressBus + 1) | 0x100); break; case 4: dl = Fetch(addressBus); programCounter = (ushort)((programCounter & 0xFF00) | dl); //technically not accurate, as this happens in cycle 5 addressBus = (ushort)((byte)(addressBus + 1) | 0x100); break; case 5: PollInterrupts(); dl = Fetch(addressBus); programCounter = (ushort)((programCounter & 0xFF) | (dl << 8)); stackPointer = (byte)addressBus; CompleteOperation(); break; } break; case 0x41: //(EOR X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x42: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x43: //(SRE, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x44: //DOP *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x45: //EOR zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x46: //LSR zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_LSR(dl, addressBus); CompleteOperation(); break; } break; case 0x47: //SRE zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x48: //PHA switch (operationCycle) { case 1: //dummy fetch dl = Fetch(addressBus); break; case 2: // read from address PollInterrupts(); Push(A); CompleteOperation(); break; } break; case 0x49: //EOR Imm PollInterrupts(); GetImmediate(); Op_EOR(dl); CompleteOperation(); break; case 0x4A: //LSR A PollInterrupts(); Fetch(addressBus); // dummy read Op_LSR_A(); CompleteOperation(); break; case 0x4B: //ASR Imm *** PollInterrupts(); GetImmediate(); A = (byte)(A & dl); Op_LSR_A(); CompleteOperation(); break; case 0x4C: //JMP if (operationCycle == 1) { GetAddressAbsolute(); } else { PollInterrupts(); GetAddressAbsolute(); programCounter = addressBus; CompleteOperation(); } break; case 0x4D: //EOR Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x4E: //LSR abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_LSR(dl, addressBus); CompleteOperation(); break; } break; case 0x4F: //SRE abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x50: //BVC switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (flag_Overflow) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0x51: //(EOR), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x52: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x53: //(SRE) Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x54: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x55: //EOR zp , X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x56: //LSR zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_LSR(dl, addressBus); CompleteOperation(); break; } break; case 0x57: //SRE zp X *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x58: //CLI PollInterrupts(); Fetch(addressBus); // dummy read flag_Interrupt = false; CompleteOperation(); break; case 0x59: //EOR Abs Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x5A: //NOP *** PollInterrupts(); addressBus = programCounter; Fetch(addressBus); CompleteOperation(); break; case 0x5B: //SRE abs, Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x5C: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x5D: //EOR Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_EOR(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x5E: //LSR abs, X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_LSR(dl, addressBus); CompleteOperation(); break; } break; case 0x5F: //SRE abs, X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_SRE(dl, addressBus); CompleteOperation(); break; } break; case 0x60: //RTS switch (operationCycle) { case 1: GetImmediate(); break; case 2: addressBus = (ushort)(0x100 | stackPointer); Fetch(addressBus); addressBus = (ushort)((byte)(addressBus + 1) | 0x100); break; case 3: dl = Fetch(addressBus); programCounter = (ushort)((programCounter & 0xFF00) | dl); //technically not accurate, as this happens in cycle 5 addressBus = (ushort)((byte)(addressBus + 1) | 0x100); break; case 4: dl = Fetch(addressBus); programCounter = (ushort)((programCounter & 0xFF) | (dl << 8)); break; case 5: PollInterrupts(); stackPointer = (byte)addressBus; GetImmediate(); CompleteOperation(); break; } break; case 0x61: //(ADC X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x62: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x63: //(RRA X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x64: //DOP *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x65: //ADC Zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x66: //ROR zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_ROR(dl, addressBus); CompleteOperation(); break; } break; case 0x67: //RRA zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x68: //PLA switch (operationCycle) { case 1: //dummy fetch addressBus = programCounter; Fetch(addressBus); break; case 2: // read from address addressBus = (ushort)(0x100 | (stackPointer)); Fetch(addressBus); // dummy read stackPointer++; break; case 3: // read from address PollInterrupts(); addressBus = (ushort)(0x100 | (stackPointer)); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0x69: //ADC Imm PollInterrupts(); GetImmediate(); Op_ADC(dl); CompleteOperation(); break; case 0x6A: //ROR A PollInterrupts(); Fetch(addressBus); // dummy read Op_ROR_A(); CompleteOperation(); break; case 0x6B: // ARR *** PollInterrupts(); GetImmediate(); A = (byte)(A & dl); Op_ROR_A(); flag_Zero = A == 0; flag_Carry = ((A & 0x40) >> 6) == 1; flag_Overflow = (((A & 0x20) >> 5) ^ ((A & 0x40) >> 6)) == 1; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0x6C: //JMP (indirect) switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: specialBus = Fetch(addressBus); // Okay, this doesn't actually use the SB register. I'm just reusing that variable. break; case 4: PollInterrupts(); dl = Fetch((ushort)((addressBus & 0xFF00) | (byte)(addressBus + 1))); programCounter = (ushort)((dl << 8) | specialBus); CompleteOperation(); break; } break; case 0x6D: //ADC Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x6E: //ROR Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ROR(dl, addressBus); CompleteOperation(); break; } break; case 0x6F: //RRA Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x70: //BVS switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (!flag_Overflow) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0x71: //(ADC), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x72: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x73: //(RRA) Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x74: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x75: //ADC Zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x76: //ROR zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_ROR(dl, addressBus); CompleteOperation(); break; } break; case 0x77: //RRA zp X *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x78: //SEI PollInterrupts(); Fetch(addressBus); // dummy read flag_Interrupt = true; CompleteOperation(); break; case 0x79: //ADC Abs, Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x7A: //NOP *** PollInterrupts(); addressBus = programCounter; Fetch(addressBus); CompleteOperation(); break; case 0x7B: //RRA Abs, Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x7C: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0x7D: //ADC Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_ADC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0x7E: //ROR Abs, X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_ROR(dl, addressBus); CompleteOperation(); break; } break; case 0x7F: //RRA Abs, X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_RRA(dl, addressBus); CompleteOperation(); break; } break; case 0x80: //DOP *** PollInterrupts(); GetImmediate(); CompleteOperation(); break; case 0x81: //(STA X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); if (operationCycle == 4) { CPU_Read = false; } break; case 5: // read from address PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x82: //DOP *** PollInterrupts(); GetImmediate(); CompleteOperation(); break; case 0x83: //(SAX X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); if (operationCycle == 4) { CPU_Read = false; } break; case 5: // read from address PollInterrupts(); Store((byte)(A & X), addressBus); CompleteOperation(); break; } break; case 0x84: //STY zp switch (operationCycle) { case 1: GetAddressZeroPage(); CPU_Read = false; break; case 2: // read from address PollInterrupts(); Store(Y, addressBus); CompleteOperation(); break; } break; case 0x85: //STA zp switch (operationCycle) { case 1: GetAddressZeroPage(); CPU_Read = false; break; case 2: PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x86: //STX zp switch (operationCycle) { case 1: GetAddressZeroPage(); CPU_Read = false; break; case 2: PollInterrupts(); Store(X, addressBus); CompleteOperation(); break; } break; case 0x87: //SAX zp switch (operationCycle) { case 1: GetAddressZeroPage(); CPU_Read = false; break; case 2: PollInterrupts(); Store((byte)(A & X), addressBus); CompleteOperation(); break; } break; case 0x88: //DEY PollInterrupts(); Y--; flag_Zero = Y == 0; flag_Negative = Y >= 0x80; Fetch(addressBus); // dummy read CompleteOperation(); break; case 0x89: //DOP *** PollInterrupts(); GetImmediate(); CompleteOperation(); break; case 0x8A: //TXA PollInterrupts(); A = X; flag_Zero = A == 0; flag_Negative = A >= 0x80; Fetch(addressBus); // dummy read CompleteOperation(); break; case 0x8B: //ANE PollInterrupts(); GetImmediate(); //A = (((A | 0xFF) & X) & temp); // Magic = FF A = (byte)((A | 0xFF) & X & dl); // 0xEE is also known as "MAGIC", and can supposedly be different depending on the CPU's temperature. flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0x8C: //STY Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store(Y, addressBus); CompleteOperation(); break; } break; case 0x8D: //STA Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x8E: //STX Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: PollInterrupts(); Store(X, addressBus); CompleteOperation(); break; } break; case 0x8F: //SAX Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store((byte)(A & X), addressBus); CompleteOperation(); break; } break; case 0x90: //BCC switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (flag_Carry) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0x91: //(STA), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5: PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x92: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0x93: // (SHA) Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5: // read from address PollInterrupts(); if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00)) { // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable" addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHA behavior. The A register isn't used here! } // pd = the high byte of the target address + 1 if (IgnoreH) { H = 0xFF; } Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHA behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD. CompleteOperation(); break; } break; case 0x94: //STY zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store(Y, addressBus); CompleteOperation(); break; } break; case 0x95: //STA zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x96: //STX zp, Y switch (operationCycle) { case 1: case 2: GetAddressZPOffY(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store(X, addressBus); CompleteOperation(); break; } break; case 0x97: //SAX zp, Y switch (operationCycle) { case 1: case 2: GetAddressZPOffY(); if (operationCycle == 2) { CPU_Read = false; } break; case 3: // read from address PollInterrupts(); Store((byte)(A & X), addressBus); CompleteOperation(); break; } break; case 0x98: //TYA PollInterrupts(); A = Y; Fetch(addressBus); // dummy read flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0x99: //STA Abs, Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: // read from address PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x9A: //TXS PollInterrupts(); stackPointer = X; Fetch(addressBus); // dummy read CompleteOperation(); break; case 0x9B: //SHS, Abs Y *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: // read from address PollInterrupts(); if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00)) { // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable" addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHS behavior. The A register isn't used here! } // pd = the high byte of the target address + 1 stackPointer = (byte)(A & X); if (IgnoreH) { H = 0xFF; } Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHS behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD. CompleteOperation(); break; } break; case 0x9C: //SHY Abs, X *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: PollInterrupts(); if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00)) { // if adding X to the target address crossed a page boundary, this opcode has "gone unstable" addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) & Y) << 8); } if (IgnoreH) { H = 0xFF; } Store((byte)(Y & H), addressBus); CompleteOperation(); break; } break; case 0x9D: //STA Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: PollInterrupts(); Store(A, addressBus); CompleteOperation(); break; } break; case 0x9E: // SHX Abs, Y*** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: PollInterrupts(); // Not even close to what the documentation says this instruction does. if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00)) { // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable" addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) & X) << 8); } if (IgnoreH) { H = 0xFF; } Store((byte)(X & H), addressBus); CompleteOperation(); break; } break; case 0x9F: // SHA Abs, Y*** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(false); if (operationCycle == 3) { CPU_Read = false; } break; case 4: // read from address PollInterrupts(); if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00)) { // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable" addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHA behavior. The A register isn't used here! } if (IgnoreH) { H = 0xFF; } Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHA behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD. CompleteOperation(); break; } break; case 0xA0: //LDY imm PollInterrupts(); GetImmediate(); Y = dl; flag_Zero = Y == 0; flag_Negative = Y >= 0x80; CompleteOperation(); break; case 0xA1: //(LDA, X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0xA2: //LDX imm PollInterrupts(); GetImmediate(); X = dl; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; case 0xA3: //(LAX, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: PollInterrupts(); A = Fetch(addressBus); X = A; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xA4: //LDY zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Y = Fetch(addressBus); flag_Zero = Y == 0; flag_Negative = Y >= 0x80; CompleteOperation(); break; } break; case 0xA5: //LDA zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0xA6: //LDX zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); X = Fetch(addressBus); flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xA7: //LAX zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); A = Fetch(addressBus); X = A; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xA8: //TAY PollInterrupts(); Y = A; Fetch(addressBus); // dummy read flag_Zero = A == 0; flag_Negative = Y >= 0x80; CompleteOperation(); break; case 0xA9: //LDA Imm PollInterrupts(); GetImmediate(); A = dl; flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; case 0xAA: //TAX PollInterrupts(); X = A; Fetch(addressBus); // dummy read flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; case 0xAB: //LXA *** PollInterrupts(); GetImmediate(); A = (byte)((A | 0xFF) & dl); // 0xEE is also known as "MAGIC", and can supposedly be different depending on the CPU's temperature. X = A; // this instruction is basically XAA but using LAX behavior, so X is also affected.. flag_Negative = X >= 0x80; flag_Zero = X == 0x00; CompleteOperation(); break; case 0xAC: //LDY Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Y = Fetch(addressBus); flag_Negative = Y >= 0x80; flag_Zero = Y == 0x00; CompleteOperation(); break; } break; case 0xAD: //LDA Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Negative = A >= 0x80; flag_Zero = A == 0x00; CompleteOperation(); break; } break; case 0xAE: //LDX Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); X = Fetch(addressBus); flag_Negative = X >= 0x80; flag_Zero = X == 0x00; CompleteOperation(); break; } break; case 0xAF: //LAX Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); A = Fetch(addressBus); X = A; flag_Negative = X >= 0x80; flag_Zero = X == 0x00; CompleteOperation(); break; } break; case 0xB0: //BCS switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (!flag_Carry) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0xB1: //(LDA), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: PollInterrupts(); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0xB2: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0xB3: //(LAX), Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); A = Fetch(addressBus); X = A; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xB4: //LDY zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Y = Fetch(addressBus); flag_Zero = Y == 0; flag_Negative = Y >= 0x80; CompleteOperation(); break; } break; case 0xB5: //LDA zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0xB6: //LDX zp, Y switch (operationCycle) { case 1: case 2: GetAddressZPOffY(); break; case 3: // read from address PollInterrupts(); X = Fetch(addressBus); flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xB7: //LAX zp, Y *** switch (operationCycle) { case 1: case 2: GetAddressZPOffY(); break; case 3: // read from address PollInterrupts(); A = Fetch(addressBus); X = A; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; } break; case 0xB8: //CLV PollInterrupts(); Fetch(addressBus); // dummy read flag_Overflow = false; CompleteOperation(); break; case 0xB9: //LDA abs , Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Zero = A == 0; flag_Negative = A >= 0x80; CompleteOperation(); break; } break; case 0xBA: //TSX PollInterrupts(); X = stackPointer; Fetch(addressBus); // dummy read flag_Negative = X >= 0x80; flag_Zero = X == 0; CompleteOperation(); break; case 0xBB: //LAE Abs, Y*** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); dl = Fetch(addressBus); A = (byte)(dl & stackPointer); X = (byte)(dl & stackPointer); stackPointer = (byte)(dl & stackPointer); flag_Negative = X >= 0x80; flag_Zero = X == 0; CompleteOperation(); break; } break; case 0xBC: //LDY abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Y = Fetch(addressBus); flag_Negative = Y >= 0x80; flag_Zero = Y == 0; CompleteOperation(); break; } break; case 0xBD: //LDA abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); A = Fetch(addressBus); flag_Negative = A >= 0x80; flag_Zero = A == 0; CompleteOperation(); break; } break; case 0xBE: //LDX abs , Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); X = Fetch(addressBus); flag_Negative = X >= 0x80; flag_Zero = X == 0; CompleteOperation(); break; } break; case 0xBF: //LAX Abs, Y *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); A = Fetch(addressBus); X = A; flag_Negative = X >= 0x80; flag_Zero = X == 0; CompleteOperation(); break; } break; case 0xC0: //CPY Imm PollInterrupts(); GetImmediate(); Op_CPY(dl); CompleteOperation(); break; case 0xC1: //(CMP X), switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xC2: //DOP *** PollInterrupts(); GetImmediate(); CompleteOperation(); break; case 0xC3: //(DCP, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); dl--; Store(dl, addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xC4: //CPY zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_CPY(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xC5: //CMP zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xC6: //DEC zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: dl = Fetch(addressBus); CPU_Read = false; break; case 3: Store(dl, addressBus); //dummy write break; case 4: // read from address PollInterrupts(); Op_DEC(addressBus); CompleteOperation(); break; } break; case 0xC7: //DCP zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: dl = Fetch(addressBus); CPU_Read = false; break; case 3: Store(dl, addressBus); //dummy write break; case 4: // read from address PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xC8: //INY PollInterrupts(); Y++; Fetch(addressBus); // dummy read flag_Zero = Y == 0; flag_Negative = Y >= 0x80; CompleteOperation(); break; case 0xC9: //CMP Imm PollInterrupts(); GetImmediate(); Op_CMP(dl); CompleteOperation(); break; case 0xCA: //DEX PollInterrupts(); X--; Fetch(addressBus); // dummy read flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; case 0xCB: // AXS *** PollInterrupts(); GetImmediate(); X = (byte)(X & A); flag_Carry = X >= dl; X -= dl; flag_Zero = X == 0; flag_Negative = (X >= 0x80); CompleteOperation(); break; case 0xCC: //CPY Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_CPY(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xCD: //CMP Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xCE: //DEC Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 4: // dummy write Store(dl, addressBus); break; case 5: // write PollInterrupts(); Op_DEC(addressBus); CompleteOperation(); break; } break; case 0xCF: //DCP Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 4: // dummy write Store(dl, addressBus); break; case 5: // write PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xD0: //BNE switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (flag_Zero) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0xD1: //(CMP), Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xD2: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0xD3: //(DCP) Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xD4: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0xD5: //CMP zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xD6: //DEC zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: dl = Fetch(addressBus); CPU_Read = false; break; case 4: Store(dl, addressBus); //dummy write break; case 5: // read from address PollInterrupts(); Op_DEC(addressBus); CompleteOperation(); break; } break; case 0xD7: //DCP Zp X *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: dl = Fetch(addressBus); CPU_Read = false; break; case 4: Store(dl, addressBus); //dummy write break; case 5: // read from address PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xD8: //CLD PollInterrupts(); Fetch(addressBus); // dummy read flag_Decimal = false; CompleteOperation(); break; case 0xD9: //CMP abs, Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xDA: //NOP *** PollInterrupts(); Fetch(addressBus); // dummy read CompleteOperation(); break; case 0xDB: //DCP Abs Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xDC: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0xDD: //CMP abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_CMP(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xDE: //DEC Abs X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_DEC(addressBus); CompleteOperation(); break; } break; case 0xDF: //DCP Abs X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_DEC(addressBus); Op_CMP(dl); CompleteOperation(); break; } break; case 0xE0: //CPX Imm PollInterrupts(); GetImmediate(); Op_CPX(dl); CompleteOperation(); break; case 0xE1: //(SBC X) switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xE2: //DOP *** PollInterrupts(); GetImmediate(); CompleteOperation(); break; case 0xE3: //(ISC, X) *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffX(); break; case 5: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 6: // write back to the address Store(dl, addressBus); break; // perform the operation case 7: PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xE4: //CPX zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_CPX(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xE5: //SBC Zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xE6: //INC zp switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_INC(addressBus); CompleteOperation(); break; } break; case 0xE7: //ISC zp *** switch (operationCycle) { case 1: GetAddressZeroPage(); break; case 2: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 3: //dummy write Store(dl, addressBus); break; case 4: // perform operation PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xE8: //INX PollInterrupts(); Fetch(addressBus); // dummy read X++; flag_Zero = X == 0; flag_Negative = X >= 0x80; CompleteOperation(); break; case 0xE9: //SBC Imm PollInterrupts(); GetImmediate(); Op_SBC(dl); CompleteOperation(); break; case 0xEA: //NOP PollInterrupts(); Fetch(addressBus); // dummy read CompleteOperation(); break; case 0xEB: //SBC Imm *** PollInterrupts(); GetImmediate(); Op_SBC(dl); CompleteOperation(); break; case 0xEC: //CPX Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_CPX(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xED: //SBC Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xEE: //INC Abs switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); if (addressBus == 0x4014) { } break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_INC(addressBus); CompleteOperation(); break; } break; case 0xEF: //ISC Abs *** switch (operationCycle) { case 1: case 2: GetAddressAbsolute(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xF0: //BEQ switch (operationCycle) { case 1: PollInterrupts(); GetImmediate(); if (!flag_Zero) { CompleteOperation(); } break; case 2: Fetch(addressBus); // dummy read temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl)); programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl)); addressBus = programCounter; if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00)) { CompleteOperation(); } break; case 3: // read from address PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ. Fetch(addressBus); // dummy read programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00)); CompleteOperation(); break; } break; case 0xF1: //(SBC) Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(true); break; case 5: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xF2: ///HLT *** switch (operationCycle) { case 1: dl = Fetch(addressBus); break; case 2: addressBus = 0xFFFF; Fetch(addressBus); break; case 3: case 4: addressBus = 0xFFFE; Fetch(addressBus); break; case 5: addressBus = 0xFFFF; Fetch(addressBus); break; case 6: addressBus = 0xFFFF; Fetch(addressBus); operationCycle = 5; //makes this loop infinitely. break; } break; case 0xF3: //(ISC) Y switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressIndOffY(false); break; case 5: // dummy read dl = Fetch(addressBus); CPU_Read = false; break; case 6: // dummy write Store(dl, addressBus); break; case 7: // read from address PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xF4: //DOP *** switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0xF5: //SBC Zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xF6: //INC Zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_INC(addressBus); CompleteOperation(); break; } break; case 0xF7: //ISC zp, X switch (operationCycle) { case 1: case 2: GetAddressZPOffX(); break; case 3: // read from address dl = Fetch(addressBus); CPU_Read = false; break; case 4: //dummy write Store(dl, addressBus); break; case 5: PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xF8: //SED PollInterrupts(); Fetch(addressBus); // dummy read flag_Decimal = true; CompleteOperation(); break; case 0xF9: //SBC Abs Y switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffY(true); break; case 4: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xFA: //NOP *** PollInterrupts(); Fetch(addressBus); // dummy read CompleteOperation(); break; case 0xFB: //ISC Abs Y *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffY(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; case 0xFC: //TOP *** switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Fetch(addressBus); CompleteOperation(); break; } break; case 0xFD: //SBC Abs, X switch (operationCycle) { case 1: case 2: case 3: GetAddressAbsOffX(true); break; case 4: // read from address PollInterrupts(); Op_SBC(Fetch(addressBus)); CompleteOperation(); break; } break; case 0xFE: //INC Abs, X switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_INC(addressBus); CompleteOperation(); break; } break; case 0xFF: //ISC Abs, X *** switch (operationCycle) { case 1: case 2: case 3: case 4: GetAddressAbsOffX(false); if (operationCycle == 4) { CPU_Read = false; } break; case 5:// dummy write Store(dl, addressBus); break; case 6:// read from address PollInterrupts(); Op_INC(addressBus); Op_SBC(dl); CompleteOperation(); break; } break; // And that's all 256 instructions! } operationCycle++; // increment this for next CPU cycle. } if (DoDMCDMA && APU_ImplicitAbortDMC4015) { APU_ImplicitAbortDMC4015 = false; // If this was delayed by a write cycle, it won't run at all. } } public void ResetReadPush() { // the RESET instruction has unique behavior where it reads from the stack, and decrements the stack pointer. Fetch((ushort)(0x100 + stackPointer)); stackPointer--; } public void Push(byte A) { // Store to the stack, and decrement the stack pointer. Store(A, (ushort)(0x100 + stackPointer)); stackPointer--; } // I don't have a void for pop... All instructions that pull form the stack just perform the logic. ushort PPU_VRAM_MysteryAddress; // used during consecutive write cycles to VRAM. The PPU makes 2 extra writes to VRAM, and one of them I call "the mystery write". public ushort PPU_AddressBus; // the Address Bus of the PPU public bool PPU_ALE; // Address Latch Enable public byte PPU_OctalLatch; // This is the address latch. public ushort PPU_v = 0;// PPU Internal Register 'v' public ushort PPU_t = 0; // PPU Internal Register 't'. "can also be thought of as the address of the top left onscreen tile: https://www.nesdev.org/wiki/PPU_scrolling" /* The v and t registers are 15 bits: yyy NN YYYYY XXXXX ||| || ||||| +++++-- coarse X scroll ||| || +++++-------- coarse Y scroll ||| ++-------------- nametable select +++----------------- fine Y scroll */ byte PPU_Update2006Delay; // The number of PPU cycles to wait between writing to $2006 and the ppu from updating byte PPU_Update2005Delay; // The number of PPU cycles to wait between writing to $2004 and the ppu from updating byte PPU_Update2005Value; // The value written to $2005, for use when the delay has ended. byte PPU_Update2001Value; // The value written to $2001, for use when the delay has ended. ushort PPU_Update2006Value; // The value written to $2006, for use when the delay has ended. ushort PPU_Update2006Value_Temp; byte PPU_Update2001Delay; // The number of PPU cycles to wait between writing to $2001 and the ppu from updating byte PPU_Update2001OAMCorruptionDelay; // I plan to refactor 2001 writes and remove the hard-coded delays. This will be removed eventually. byte PPU_Update2001EmphasisBitsDelay; // I plan to refactor 2001 writes and remove the hard-coded delays. This will be removed eventually. bool PPU_WasRenderingBefore2001Write; // Were we rendering before writing to $2001? (used for OAM corruption) byte PPU_ReadBuffer = 0; // when reading from $2007, this buffer holds the value from VRAM that gets read. Updated after reading from $2007. bool PPUAddrLatch = false; // Certain ppu registers take two writes to fully set things up. It's flipped when writing to $2005 and $2006. Reset when reading from $2002 bool PPUControlIncrementMode32; // Set by writing to $2000. If set, the VRAM address is incremented by 32 instead of 1 after reads/writes to $2007. bool PPUControl_NMIEnabled; // Set by writing to $2000. If set, the NMI can occur. public bool PPU_PatternSelect_Sprites; //which pattern table is used for sprites / background public bool PPU_PatternSelect_Background; //which pattern table is used for sprites / background public bool PPU_EXT_Enable; // I can toggle this boolean, but it is otherwise unimplemented. //for logging purposes. doesn't update databus. bool DebugObserve = false; public byte Observe(ushort Address) { // Reading from anywhere goes through this function. if ((Address >= 0x8000)) { // Reading from ROM. // Different mappers could rearrange the data from the ROM into different locations on the system bus. return MapperObserve(Address, Cart.MemoryMapper); } else if (Address < 0x2000) { // Reading from RAM. // Ram mirroring! Only addresses $0000 through $07FF exist in RAM, so ignore bits 11 and 12 return RAM[Address & 0x7FF]; } else if (Address >= 0x2000 && Address < 0x4000) { // PPU registers. most of these aren't meant to be read. Address = (ushort)(Address & 0x2007); switch (Address) { case 0x2000: // Write only. Return the PPU databus. return PPUBus; case 0x2001: // Write only. Return the PPU databus. return PPUBus; case 0x2002: // PPU Flags. return (byte)((((PPUStatus_VBlank ? 0x80 : 0) | (PPUStatus_SpriteZeroHit ? 0x40 : 0) | (PPUStatus_SpriteOverflow ? 0x20 : 0)) & 0xE0) + (PPUBus & 0x1F)); case 0x2003: // write only. Return the PPU databus. return PPUBus; case 0x2004: // Read from OAM return (byte)(ReadOAM()); case 0x2005: // write only. Return the PPU databus. return PPUBus; case 0x2006: // write only. Return the PPU databus. return PPUBus; case 0x2007: // Reading from VRAM. return ObservePPU(PPU_v); } } else if (Address >= 0x4000 && Address <= 0x401F) // observe the APU registers { //addressBus byte Reg = (byte)(Address & 0x1F); if (Reg == 0x15) { byte InternalBus = dataBus; InternalBus &= 0x20; InternalBus |= (byte)(APU_Status_DMCInterrupt ? 0x80 : 0); InternalBus |= (byte)(APU_Status_FrameInterrupt ? 0x40 : 0); InternalBus |= (byte)((APU_DMC_BytesRemaining != 0 && APU_Status_DelayedDMC) ? 0x10 : 0); // see footnote. InternalBus |= (byte)((APU_LengthCounter_Noise != 0) ? 0x08 : 0); InternalBus |= (byte)((APU_LengthCounter_Triangle != 0) ? 0x04 : 0); InternalBus |= (byte)((APU_LengthCounter_Pulse2 != 0) ? 0x02 : 0); InternalBus |= (byte)((APU_LengthCounter_Pulse1 != 0) ? 0x01 : 0); return InternalBus; // reading from $4015 can not affect the databus } else if (Reg == 0x16 || Reg == 0x17) { return (byte)((((Reg == 0x16) ? (ControllerShiftRegister1 & 0x80) : (ControllerShiftRegister2 & 0x80)) == 0 ? 0 : 1) | (dataBus & 0xE0)); } } else { //mapper chip stuff, but also open bus! return MapperObserve(Address, Cart.MemoryMapper); } return dataBus; } public byte Fetch(ushort Address) { dataPinsAreNotFloating = false; // assume the data pins are floating by default. // Reading from anywhere goes through this function. if ((Address >= 0x8000)) { // Reading from ROM. // Different mappers could rearrange the data from the ROM into different locations on the system bus. MapperFetch(Address, Cart.MemoryMapper); dataPinsAreNotFloating = true; } else if (Address < 0x2000) { // Reading from RAM. // Ram mirroring! Only addresses $0000 through $07FF exist in RAM, so ignore bits 11 and 12 dataBus = RAM[Address & 0x7FF]; dataPinsAreNotFloating = true; } else if (Address >= 0x2000 && Address < 0x4000) { // PPU registers. most of these aren't meant to be read. Address = (ushort)(Address & 0x2007); switch (Address) { case 0x2000: // Write only. Return the PPU databus. dataBus = PPUBus; break; case 0x2001: // Write only. Return the PPU databus. dataBus = PPUBus; break; case 0x2002: // PPU Flags. dataBus = (byte)((((PPUStatus_VBlank ? 0x80 : 0)))); // The vblank flag is read at the start of the read... PPU_Read2002 = true; EmulateUntilEndOfRead(); dataBus |= (byte)((((PPUStatus_SpriteZeroHit_Delayed ? 0x40 : 0) | (PPUStatus_SpriteOverflow_Delayed ? 0x20 : 0)) & 0xE0) + (PPUBus & 0x1F)); // ...while the sprite flags are read at the end. PPUAddrLatch = false; PPUBus = dataBus; for (int i = 5; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } break; case 0x2003: // write only. Return the PPU databus. dataBus = PPUBus; break; case 0x2004: // Read from OAM EmulateUntilEndOfRead(); dataBus = ReadOAM(); PPUBus = dataBus; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } break; case 0x2005: // write only. Return the PPU databus. dataBus = PPUBus; break; case 0x2006: // write only. Return the PPU databus. dataBus = PPUBus; break; case 0x2007: // Reading from VRAM. if ((PPU_AddressBus & 0x3FFF) >= 0x3F00) { // read from palette RAM. // Palette RAM only returns bits 0-5, so bits 6 and 7 are PPU open bus. ThisDotReadFromPaletteRAM = true; ushort PalRAMAddr = (ushort)(PPU_v & 0x3F1F); if ((PalRAMAddr & 3) == 0) { PalRAMAddr &= 0x3F0F; } dataBus = (byte)(((PaletteRAM[PalRAMAddr & 0x1F] & (PPU_Mask_Greyscale ? 0x30 : 0x3F)) | (PPUBus & 0xC0))); } else { dataBus = PPU_ReadBuffer; } PPUBus = dataBus; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } EmulateUntilEndOfRead(); PPU_2007_Read_SR = true; // set the SR latch at the end of the CPU read. Here's where the clock alignment differences begin. :) PPU_2007_Read = true; // Start the $2007 Read state machine. break; } dataPinsAreNotFloating = true; } else { //mapper chip stuff, but also open bus! MapperFetch(Address, Cart.MemoryMapper); } if (addressBus >= 0x4000 && addressBus <= 0x401F) // If APU registers are active, bus conflicts can occur. Or perhaps you are intentionally reading from the APU registers... { //addressBus byte Reg = (byte)(Address & 0x1F); if (Reg == 0x15) { internalBus &= 0x20; internalBus |= (byte)(APU_Status_DMCInterrupt ? 0x80 : 0); internalBus |= (byte)(APU_Status_FrameInterrupt ? 0x40 : 0); internalBus |= (byte)((APU_DMC_BytesRemaining != 0 && APU_Status_DelayedDMC) ? 0x10 : 0); // see footnote. internalBus |= (byte)((APU_LengthCounter_Noise != 0) ? 0x08 : 0); internalBus |= (byte)((APU_LengthCounter_Triangle != 0) ? 0x04 : 0); internalBus |= (byte)((APU_LengthCounter_Pulse2 != 0) ? 0x02 : 0); internalBus |= (byte)((APU_LengthCounter_Pulse1 != 0) ? 0x01 : 0); Clearing_APU_FrameInterrupt = true; // footnote: // Consider the following. LDA #0, STA $4015, LDA $4015. // The APU_DMC_BytesRemaining byte isn't cleared until 3 or 4 cycles after writing 0 to $4015. // However, reading from $4015 after the needs to immediately have bit 4 cleared. return internalBus; // reading from $4015 can not affect the databus } else if (Reg == 0x16 || Reg == 0x17) { byte ControllerRead = (byte)((((Reg == 0x16) ? (ControllerShiftRegister1 & 0x80) : (ControllerShiftRegister2 & 0x80)) == 0 ? 0 : 1) | (dataBus & 0xE0)); // controller ports // grab 1 bit from the controller's shift register. // also add the upper 3 bits of the databus. if (Reg == 0x16) { // if there are 2 CPU cycles in a row that read from this address, the registers don't get shifted Controller1ShiftCounter = 2; // The shift register isn't shifted until this is 0, decremented in every APU PUT cycle } else { // if there are 2 CPU cycles in a row that read from this address, the registers don't get shifted Controller2ShiftCounter = 2; // The shift register isn't shifted until this is 0, decremented in every APU PUT cycle } APU_ControllerPortsStrobed = false; // This allows data to rapidly be streamed in through the A button if the controllers are read while strobed. if (DoOAMDMA && dataPinsAreNotFloating) // If all the databus pins are floating, then the controller bits are visible. Otherwise... not so much. { return dataBus; } dataBus = ControllerRead; } } internalBus = dataBus; return dataBus; } public byte ObservePPU(ushort Address) { // A way to view PPU data for various debugging tools. if (Cart == null) { return 0; } // when reading from the PPU's Video RAM, there's a lot of mapper-specific behavior to consider. Address &= 0x3FFF; if (Address < 0x2000) { if (Cart.UsingCHRRAM) { return Cart.CHRRAM[Address]; } else { //Pattern Table return Cart.MapperChip.FetchCHR(Address, true); } } else // if the VRAM address is >= $2000, we need to consider nametable mirroring. { Address = PPUAddressWithMirroring(Address); if (Address >= 0x3F00) { // read from palette RAM. // Palette RAM only returns bits 0-5, so bits 6 and 7 are PPU open bus. return (byte)((PaletteRAM[Address & 0x1F] & 0x3F) | (PPUBus & 0xC0)); } if (Cart.AlternativeNametableArrangement) { if (Cart.MemoryMapper == 4) { if ((Address & 0x800) != 0) { // using the extra PRG VRAM. Address &= 0x7FF; return Cart.PRGVRAM[Address]; } } } Address &= 0x7FF; return VRAM[Address]; } } ushort PPUAddressWithMirroring(ushort Address) { // if the address is less than $2000, there is no mirroring. if (Address < 0x2000) { return Address; } // if the vram address is pointing to the color palettes: if (Address >= 0x3F00) { Address &= 0x3F1F; if ((Address & 3) == 0) { Address &= 0x3F0F; } return Address; } Address &= 0x2FFF; // $3000 through $3F00 is always mirrored down. Address = Cart.MapperChip.MirrorNametable(Address); return Address; } byte MapperObserve(ushort Address, byte Mapper) { Cart.MapperChip.FetchPRG(Address, true); if (Cart.MapperChip.observedDataPinsAreNotFloating) { return Cart.MapperChip.observedDataBus; } return dataBus; } void MapperFetch(ushort Address, byte Mapper) { Cart.MapperChip.FetchPRG(Address, false); dataPinsAreNotFloating = Cart.MapperChip.dataPinsAreNotFloating; if (dataPinsAreNotFloating) { dataBus = Cart.MapperChip.dataBus; } return; } byte ReadOAM() { if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline < 240) { return PPU_OAMBuffer; } return OAM[PPUOAMAddress]; } bool PPU_PendingVBlank; bool dataPinsAreNotFloating = false; // used in controller reading + OAM DMA. public bool TAS_ReadingTAS; // if we're reading inputs from a TAS, this will be set. public int TAS_InputSequenceIndex; // which index from the TAS input log will be used for this current controller strobe? public ushort[] TAS_InputLog; // controller [22222222 11111111] public bool[] TAS_ResetLog; // just a list of booleans determining if we should soft-reset on this frame or not. public bool ClockFiltering = false; // If set, TAS_InputSequenceIndex increments every time the controllers are strobed (or clocked, if the controller is held strobing). Otherwise, "latch filtering" is used, incrementing TAS_InputSequenceIndex once a frame. public bool SyncFM2; // This is set if we're running an FM2 TAS, which (due to FCEUX's very incorrect timing of the first frame after power on) I need to start execution on scanline 240, and prevent the vblank flag from being set. public void Store(byte Input, ushort Address) { // This is used whenever writing anywhere with the CPU if (Address < 0x2000) { //guaranteed to be RAM RAM[Address & 0x7FF] = Input; } else if (Address < 0x4000) { // $2000 through $3FFF writes to the PPU registers StorePPURegisters(Address, Input); } else if (Address >= 0x4000 && Address <= 0x4015) { // Writing to $4000 through $4015 are APU registers switch (Address) { default: APU_Register[Address & 0xFF] = Input; break; case 0x4003: if (APU_Status_Pulse1) { APU_LengthCounter_ReloadValuePulse1 = APU_LengthCounterLUT[Input >> 3]; APU_LengthCounter_ReloadPulse1 = true; } APU_ChannelTimer_Pulse1 |= (ushort)((Input &= 0x7) << 8); break; case 0x4007: if (APU_Status_Pulse2) { APU_LengthCounter_ReloadValuePulse2 = APU_LengthCounterLUT[Input >> 3]; APU_LengthCounter_ReloadPulse2 = true; } APU_ChannelTimer_Pulse2 |= (ushort)((Input &= 0x7) << 8); break; case 0x400B: if (APU_Status_Triangle) { APU_LengthCounter_ReloadValueTriangle = APU_LengthCounterLUT[Input >> 3]; APU_LengthCounter_ReloadTriangle = true; } APU_ChannelTimer_Triangle |= (ushort)((Input &= 0x7) << 8); break; case 0x400F: if (APU_Status_Noise) { APU_LengthCounter_ReloadValueNoise = APU_LengthCounterLUT[Input >> 3]; APU_LengthCounter_ReloadNoise = true; } break; case 0x4010: APU_DMC_EnableIRQ = (Input & 0x80) != 0; APU_DMC_Loop = (Input & 0x40) != 0; APU_DMC_Rate = APU_DMCRateLUT[Input & 0xF]; if (!APU_DMC_EnableIRQ) { APU_Status_DMCInterrupt = false; IRQ_LevelDetector = false; } break; case 0x4011: APU_DMC_Output = (byte)(Input & 0x7F); break; case 0x4012: APU_DMC_SampleAddress = (ushort)(0xC000 | (Input << 6)); break; case 0x4013: APU_DMC_SampleLength = (ushort)((Input << 4) | 1); break; case 0x4014: //OAM DMA DoOAMDMA = true; FirstCycleOfOAMDMA = true; DMAAddress = 0; // the starting address for the OAM DMC is always page aligned. DMAPage = Input; break; case 0x4015: //DMC DMA (and other audio channels) APU_Status_DelayedDMC = (Input & 0x10) != 0; APU_Status_Noise = (Input & 0x08) != 0; APU_Status_Triangle = (Input & 0x04) != 0; APU_Status_Pulse2 = (Input & 0x02) != 0; APU_Status_Pulse1 = (Input & 0x01) != 0; APU_DelayedDMC4015 = (byte)(APU_PutCycle ? 3 : 4); // Enable in 1 APU cycles, or 1.5 APU cycles. (it will be decremented later this cycle, so it's really like 2 : 3. if (APU_Status_DelayedDMC && APU_DMC_BytesRemaining == 0) { // sets up the sample bytes_remaining and sample address. StartDMCSample(); // However, the sample will only begin playing if the DMC is currently silent if (APU_Silent) { DMCDMADelay = 2; // 2 APU cycles } } if (!APU_Status_Noise) { APU_LengthCounter_Noise = 0; } if (!APU_Status_Triangle) { APU_LengthCounter_Triangle = 0; } if (!APU_Status_Pulse2) { APU_LengthCounter_Pulse2 = 0; } if (!APU_Status_Pulse1) { APU_LengthCounter_Pulse1 = 0; } APU_Status_DMCInterrupt = false; IRQ_LevelDetector = false; // Explicit abort stuff. if (!APU_Status_DelayedDMC && ((APU_ChannelTimer_DMC == 2 && !APU_PutCycle) || (APU_ChannelTimer_DMC == APU_DMC_Rate && APU_PutCycle))) // this will be the APU cycle that fires a DMC DMA { APU_DelayedDMC4015 = (byte)(APU_PutCycle ? 5 : 6); // Disable in 2.5 APU cycles, or 3 APU cycles. // basically, if the DMA has already begun, don't abort it for *this* edge case. } // Implicit abort stuff. if (APU_Status_DelayedDMC && ((APU_ChannelTimer_DMC == 10 && !APU_PutCycle) || (APU_ChannelTimer_DMC == 8 && APU_PutCycle))) { // okay, so the series of events is as follows: // the Load DMA will occur // regardless of the buffer being empty, there will be a 1-cycle DMA that gets aborted 2 cycles after the load DMA ends. APU_SetImplicitAbortDMC4015 = true; // This will occur in 8 (or 9) cpu cycles } break; } } else if (Address == 0x4016) { if (TAS_ReadingTAS) { APU_ControllerPortsStrobing = ((Input & 1) != 0); } APU_ControllerPortsStrobing = ((Input & 1) != 0); if (!APU_ControllerPortsStrobing) { APU_ControllerPortsStrobed = false; } } else if (Address == 0x4017) { APU_FrameCounterMode = (Input & 0x80) != 0; APU_FrameCounterInhibitIRQ = (Input & 0x40) != 0; if (APU_FrameCounterMode) { APU_HalfFrameClock = true; APU_QuarterFrameClock = true; } if (APU_FrameCounterInhibitIRQ) { APU_Status_FrameInterrupt = false; IRQ_LevelDetector = false; } APU_FrameCounterReset = (byte)((APU_PutCycle ? 3 : 4)); } else if (Address >= 0x4020) { // mapper chip specific stuff- but also open bus! Cart.MapperChip.StorePRG(Address, Input); //MapperStore(Input, Address, Cart.MemoryMapper); } else { // open bus! // this doesn't write anywhere, but it still updates the databus! } dataBus = Input; } public void StorePPURegisters(ushort Addr, byte In) { //EmulateNMasterClockCycles(1); // wait for PPUSEL to go high // Okay, I KNOW this shouldn't be commented out. TODO: figure out why the timing on this is off by one. ushort AddrT = (ushort)((Addr & 0x2007)); switch (AddrT) { case 0x2000: // writing here updates a large amount of PPU flags PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } if (PPU_RESET) { return; } // now that PPUSEL is high, the value of the databus is written to the PPU register. PPU_t = (ushort)((PPU_t & 0b0111001111111111) | ((dataBus & 0x3) << 10)); // This early write to the t register is the cause of the scanline bug in SMB1. PPU_EXT_Enable = (dataBus & 0x40) == 0x40; // technically this changes PPUControl_NMIEnabled here too, but it's invisible as the NMI polling has already happened and it will be re-enabled before then. EmulateNMasterClockCycles(2); // wait for the CPU databus to change. (that's right, it doesn't happen at the start of the write cycle!) PPUControl_NMIEnabled = (In & 0x80) != 0; PPUControlIncrementMode32 = (In & 0x4) != 0; PPU_Spritex16 = (In & 0x20) != 0; PPU_PatternSelect_Sprites = (In & 0x8) != 0; PPU_PatternSelect_Background = (In & 0x10) != 0; PPU_t = (ushort)((PPU_t & 0b0111001111111111) | ((In & 0x3) << 10)); // change which nametable to render. PPU_EXT_Enable = (In & 0x40) == 0x40; break; case 0x2001: // writing here updates a large amount of PPU flags // Is the background being drawn? Are sprites being drawn? Greyscale / color emphasis? PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } if (PPU_RESET) { return; } // Okay look, I *know* this hard-coded solution is jank and sloppy. // It is temporary. // I want to re-do the picture processing unit from the ground up, honestly. // In the mean time, let's go back to the hard-coded delays. I got the correct results from the tests while doing this. // And we can fix it later. /* EmulateNMasterClockCycles(1); // wait for PPUSEL to go high PPU_Mask_EmphasizeBlue = (dataBus & 0x80) != 0; PPU_Mask_Greyscale = (dataBus & 0x1) != 0; EmulateNMasterClockCycles(2); // wait for the CPU databus to change. (that's right, it doesn't happen at the start of the write cycle!) PPU_Mask_EmphasizeBlue = (In & 0x80) != 0; PPU_Mask_EmphasizeGreen = (In & 0x40) != 0; PPU_Mask_EmphasizeRed = (In & 0x20) != 0; PPU_Mask_Greyscale = (In & 0x1) != 0; EmulateNMasterClockCycles(4); // wait for PPUSEL to go low. PPU_WasRenderingBefore2001Write = PPU_Mask_ShowBackground || PPU_Mask_ShowSprites; PPU_Mask_8PxShowBackground = (In & 0x02) != 0; PPU_Mask_8PxShowSprites = (In & 0x04) != 0; PPU_Mask_ShowBackground = (In & 0x08) != 0; PPU_Mask_ShowSprites = (In & 0x10) != 0; PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites; */ switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different. { case 0: PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 2; PPU_Update2001OAMCorruptionDelay = 2; break; case 1: PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 1; PPU_Update2001OAMCorruptionDelay = 3; break; // PPU_Update2001EmphasisBitsDelay is actually 2, but different behavior than case 0 and 3. case 2: PPU_Update2001Delay = 3; PPU_Update2001EmphasisBitsDelay = 1; PPU_Update2001OAMCorruptionDelay = 3; break; // PPU_Update2001EmphasisBitsDelay is actually 2, but different behavior than case 0 and 3. case 3: PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 2; PPU_Update2001OAMCorruptionDelay = 2; break; } PPU_WasRenderingBefore2001Write = PPU_Mask_ShowBackground || PPU_Mask_ShowSprites; PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites; // TODO: Remove this hard-coded junk: bool temp_rendering = PPU_WasRenderingBefore2001Write; bool temp_renderingFromInput = ((In & 0x08) != 0) || ((In & 0x10) != 0); // disabling rendering can cause OAM corruption. if (temp_rendering && !temp_renderingFromInput) { // we are disabling rendering inside vblank if (PPU_Scanline < 241 || PPU_Scanline == 261) { PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = true; // used in the next cycle of sprite evaluation if ((PPU_Dot & 7) < 2 && PPU_Dot <= 250) { // Palette corruption only occurs if rendering was disabled during the first 2 dots of a nametable fetch // TODO: Fiskbit has enlightened me a bit on how this is actually working: // The VRAM address muxer selects between the PAR, NT address, AT address, and v. // v isn't an explicit input; it's actually the NT input when rendering is disabled. // The AT input actually sources a lot of its bits from the NT input, and this leads to an unfortunate bug where turning rendering off during an AT fetch actually results in a brief period where you have an AT input that is sourcing from v instead of an NT address. // And the address muxer ends up using this AT input briefly right after rendering is disabled. // This is why you can get palette RAM corruption when turning rendering off during an AT fetch if v was pointing into $3C00-$3EFF, despite this clearly not being palette RAM. The AT input that is being used actually points into palette RAM because those 2 bits are forced to 1. if ((PPU_v & 0x3FFF) >= 0x3C00) // palette corruption only appears to occur when disabling rendering if the VRAM address is currently greater than 3C00 { PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = true; // used in the color calculation for the next dot being drawn } } } } else if (!temp_rendering && temp_renderingFromInput) { if (PPU_Scanline < 241 || PPU_Scanline == 261) { // if re-enabling rendering outside vblank if (PPU_PendingOAMCorruption) { // If OAM corruption is going to occur if (PPUClock == 1 || PPUClock == 2) { // if on clock alignment 1 or 2, it doesn't happen! PPU_OAMCorruptionRenderingEnabledOutOfVBlank = true; } } } } // This is temp. I know it's wrong (we're not even waiting for PPUSEL here.) but I'll fix it after redoing the entire ppu or something. if (PPU_Update2001EmphasisBitsDelay == 2) { PPU_Mask_Greyscale = (dataBus & 0x01) != 0; PPU_Mask_EmphasizeBlue = (dataBus & 0x80) != 0; } else { PPU_Update2001EmphasisBitsDelay++; // it's always 2. } PPU_Mask_EmphasizeRed = (In & 0x20) != 0; PPU_Mask_EmphasizeGreen = (In & 0x40) != 0; PPU_Update2001Value = In; break; case 0x2002: // this value is Read only. PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } break; case 0x2003: // writing here updates the OAM address PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } PPUOAMAddress = PPUBus; break; case 0x2004: // writing here updates the OAM byte at the current OAM address PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } if (((PPU_Scanline >= 240 && PPU_Scanline < 261) && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) || (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites)) { if ((PPUOAMAddress & 3) == 2) { In &= 0xE3; } OAM[PPUOAMAddress] = In; PPUOAMAddress++; } else { PPUOAMAddress += 4; PPUOAMAddress &= 0xFC; } break; case 0x2005: // writing here updates the X and Y scroll PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } if (PPU_RESET) { return; } switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different. { case 0: PPU_Update2005Delay = 1; break; case 1: PPU_Update2005Delay = 1; break; case 2: PPU_Update2005Delay = 2; break; case 3: PPU_Update2005Delay = 1; break; } PPU_Update2005Value = In; // There's a slight delay before the PPU updates the scroll with the correct values. // In the meantime, it uses the value from the databus. if (!PPUAddrLatch) { PPU_FineXScroll = (byte)(dataBus & 7); PPU_t = (ushort)((PPU_t & 0b0111111111100000) | (dataBus >> 3)); } else { PPU_t = (ushort)((PPU_t & 0b0000110000011111) | (((dataBus & 0xF8) << 2) | ((dataBus & 7) << 12))); } break; case 0x2006: // writing here updates the PPU's read/write address. PPUBus = In; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } if (PPU_RESET) { return; } if (!PPUAddrLatch) { PPU_t = (ushort)((PPU_t & 0b000000011111111) | ((In & 0x3F) << 8)); } else { PPU_t = (ushort)((PPU_t & 0b0111111100000000) | (In)); PPU_Update2006Value = PPU_t; PPU_Update2006Value_Temp = PPU_v; switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different. { case 0: PPU_Update2006Delay = 4; break; case 1: PPU_Update2006Delay = 4; break; case 2: PPU_Update2006Delay = 5; break; case 3: PPU_Update2006Delay = 4; break; } } PPUAddrLatch = !PPUAddrLatch; break; case 0x2007: // writing here updates the byte at the current read/write address PPUBus = In; PPU_2007_WriteData = PPUBus; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; } EmulateNMasterClockCycles(7); // wait for PPUSEL to go low PPU_2007_Write = true; PPU_2007_Write_SR = true; // set the SR latch at the end of the CPU write. Here's where the clock alignment differences begin. :) break; // and that's it for the ppu registers! default: break; //should never happen } } void StorePPUData(ushort Address, byte In) { // writing to the PPU's VRAM. // first, check if the address has any mirroring going on: Address = PPUAddressWithMirroring(Address); if (Address < 0x2000) // if this is pointing to CHR RAM { Cart.CHRRAM[Address] = In; } else if (Address >= 0x3F00) { PaletteRAM[Address & 0x1F] = In; } else // if this is not pointing to CHR RAM or palettes { if (Cart.AlternativeNametableArrangement) { if (Cart.MemoryMapper == 4) { if ((Address & 0x800) != 0) { // using the extra PRG VRAM. Cart.PRGVRAM[Address & 0x7FF] = In; return; } } } VRAM[Address & 0x7FF] = In; } } void StartDMCSample() { // This runs when writing to $4015, or if a DPCM sample is looping and needs to restart. APU_DMC_AddressCounter = APU_DMC_SampleAddress; APU_DMC_BytesRemaining = APU_DMC_SampleLength; } #region GetAddressFunctions // these functions are used inside the giant opcode switch statement. void GetImmediate() { // Fetch the value at the program counter, store it in the DataLatch, and increment the Program Counter. dl = Fetch(programCounter); programCounter++; addressBus = programCounter; } void GetAddressAbsolute() { // Fetch the value at the PC, and write to either the High byte or Low byte of the 16 bit address bus. Also increment the Program Counter. if (operationCycle == 1) { // fetch address low dl = Fetch(programCounter); } else { // fetch address high addressBus = (ushort)(dl | (Fetch(programCounter) << 8)); } programCounter++; } void GetAddressZeroPage() { // Fetch the value at the PC, and this 8 bit value replaces the contents of the 16 bit address bus. addressBus = Fetch(programCounter); programCounter++; } void GetAddressIndOffX() { // Fetch the value from the PC, then using that value as an 8-bit address on the zero page, add the X register, then set the High byte and Low byte of the Address Bus from there. switch (operationCycle) { case 1: // fetch pointer address addressBus = Fetch(programCounter); programCounter++; break; case 2: // Add X // dummy read Fetch(addressBus); addressBus = (byte)(addressBus + X); break; case 3: // fetch address low dl = Fetch((byte)(addressBus)); break; case 4: // fetch address high addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8)); break; } } void GetAddressIndOffY(bool TakeExtraCycleOnlyIfPageBoundaryCrossed) { // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed. // either way, the general gist of this function is: // Fetch the value from the PC. use that 8 bit location on the zero page to fetch the High and Low byte of the new Address Bus location, then add Y to that. if (TakeExtraCycleOnlyIfPageBoundaryCrossed) { switch (operationCycle) { case 1: // fetch pointer address addressBus = Fetch(programCounter); programCounter++; break; case 2: // fetch address low dl = Fetch((byte)(addressBus)); break; case 3: // fetch address high, add Y to low byte addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8)); temporaryAddress = addressBus; H = (byte)(addressBus >> 8); if (((temporaryAddress + Y) & 0xFF00) == (temporaryAddress & 0xFF00)) { operationCycle++; //skip next cycle } addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF)); break; case 4: // increment high byte dl = Fetch(addressBus); // dummy read H = (byte)(addressBus >> 8); H++; // This is incremented. addressBus += 0x100; break; } } else { switch (operationCycle) { case 1: // fetch pointer address addressBus = Fetch(programCounter); programCounter++; break; case 2: // fetch address low dl = Fetch((byte)(addressBus)); break; case 3: // fetch address high, add Y to low byte addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8)); temporaryAddress = addressBus; addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF)); break; case 4: // increment high byte dl = Fetch(addressBus); // dummy read H = (byte)(addressBus >> 8); H++; // This is incremented. if (((temporaryAddress + Y) & 0xFF00) != (temporaryAddress & 0xFF00)) { addressBus += 0x100; // really, this would just replace the high byte with H, but this is less computationally expensive } break; } } } void GetAddressZPOffX() { // Fetch the value from the PC, then add X to that. if (operationCycle == 1) { // fetch address addressBus = Fetch(programCounter); programCounter++; } else { // dummy read, and add X dl = Fetch(addressBus); addressBus = (byte)(addressBus + X); } } void GetAddressZPOffY() { // Fetch the value from the PC, then add Y to that. if (operationCycle == 1) { // fetch address addressBus = Fetch(programCounter); programCounter++; } else { // dummy read, and add Y dl = Fetch(addressBus); addressBus = (byte)(addressBus + Y); } } void GetAddressAbsOffX(bool TakeExtraCycleIfPageBoundaryCrossed) { // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed. // Fetch the High and Low byte values from the byte at the PC, then add X. if (TakeExtraCycleIfPageBoundaryCrossed) { switch (operationCycle) { case 1: // fetch address low dl = Fetch(programCounter); programCounter++; break; case 2: // fetch address high, add Y to low byte addressBus = (ushort)(dl | Fetch(programCounter) << 8); temporaryAddress = addressBus; H = (byte)(addressBus >> 8); if (((temporaryAddress + X) & 0xFF00) == (temporaryAddress & 0xFF00)) { operationCycle++; //skip next cycle FixHighByte = false; } else { FixHighByte = true; } addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + X) & 0xFF)); programCounter++; break; case 3: // increment high byte dl = Fetch(addressBus); H = (byte)(addressBus >> 8); H++; if (FixHighByte) { addressBus += 0x100; } break; case 4: // dummy read dl = Fetch(addressBus); // read into pd break; } } else { switch (operationCycle) { case 1: // fetch address low dl = Fetch(programCounter); programCounter++; break; case 2: // fetch address high, add Y to low byte addressBus = (ushort)(dl | Fetch(programCounter) << 8); temporaryAddress = addressBus; addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + X) & 0xFF)); programCounter++; break; case 3: // fix high byte if applicable dl = Fetch(addressBus); // read into pd H = (byte)(addressBus >> 8); H++; if (((temporaryAddress + X) & 0xFF00) != (temporaryAddress & 0xFF00)) { addressBus += 0x100; } break; case 4: // dummy read dl = Fetch(addressBus); // read into pd break; } } } bool FixHighByte = false; void GetAddressAbsOffY(bool TakeExtraCycleIfPageBoundaryCrossed) { // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed. // Fetch the High and Low byte values from the byte at the PC, then add Y. if (TakeExtraCycleIfPageBoundaryCrossed) { switch (operationCycle) { case 1: // fetch address low dl = Fetch(programCounter); programCounter++; break; case 2: // fetch address high, add Y to low byte addressBus = (ushort)(dl | Fetch(programCounter) << 8); temporaryAddress = addressBus; H = (byte)(addressBus >> 8); if (((temporaryAddress + Y) & 0xFF00) == (temporaryAddress & 0xFF00)) { operationCycle++; //skip next cycle FixHighByte = false; } else { FixHighByte = true; } addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF)); programCounter++; break; case 3: // increment high byte dl = Fetch(addressBus); H = (byte)(addressBus >> 8); H++; if (FixHighByte) { addressBus += 0x100; } break; case 4: // dummy read dl = Fetch(addressBus); // read into databus break; } } else { switch (operationCycle) { case 1: // fetch address low dl = Fetch(programCounter); programCounter++; break; case 2: // fetch address high, add Y to low byte addressBus = (ushort)(dl | Fetch(programCounter) << 8); temporaryAddress = addressBus; addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF)); programCounter++; break; case 3: // fix high byte if applicable dl = Fetch(addressBus); // read into pd H = (byte)(addressBus >> 8); H++; if (((temporaryAddress + Y) & 0xFF00) != (temporaryAddress & 0xFF00)) { addressBus += 0x100; } break; case 4: // dummy read dl = Fetch(addressBus); // read into pd break; } } } #endregion #region OpFunctions // This is not every instruction!!! // These are just the ones that have frequently repeated logic. // Instructions like STA just simply `Store(A, Address);`, which doesn't need a jump somewhere to do that. // Many undocumented opcodes have unique behavior that is also just handled in the switch statement, instead of jumping to a unique function. void Op_ORA(byte Input) { // Bitwise OR A with some value A |= Input; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_ASL(byte Input, ushort Address) { // Arithmetic shift left. flag_Carry = Input >= 0x80; // If bit 7 was set before the shift Input <<= 1; Store(Input, Address); // store the result at the target address flag_Negative = Input >= 0x80; // if bit 7 of the result is set flag_Zero = Input == 0x00; // if all bits are cleared } void Op_ASL_A() { // Arithmetic shift left the Accumulator flag_Carry = A >= 0x80; // If bit 7 was set before the shift A <<= 1; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_SLO(byte Input, ushort Address) { // Undocumented Opcode: equivalent to ASL + ORA Op_ASL(Input, Address); Op_ORA(dataBus); } void Op_AND(byte Input) { // Bitwise AND with A A &= Input; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_ROL(byte Input, ushort Address) { // Rotate Left bool Futureflag_Carry = Input >= 0x80; Input <<= 1; if (flag_Carry) { Input |= 1; // Put the old carry flag value into bit 0 } Store(Input, Address); // store the result at the target address flag_Carry = Futureflag_Carry; // if bit 7 of the initial value was set flag_Negative = Input >= 0x80; // if bit 7 of the result is set flag_Zero = Input == 0x00; // if all bits are cleared } void Op_ROL_A() { // Rotate Left the Accumulator bool Futureflag_Carry = A >= 0x80; A <<= 1; if (flag_Carry) { A |= 1; // Put the old carry flag value into bit 0 } flag_Carry = Futureflag_Carry; // if bit 7 of the initial value was set flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_RLA(byte Input, ushort Address) { // Undocumented Opcode: equivalent to ROL + AND Op_ROL(Input, Address); Op_AND(dataBus); } void Op_EOR(byte Input) { // Bitwise Exclusive OR A A ^= Input; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_LSR(byte Input, ushort Address) { // Logical Shift Right flag_Carry = (Input & 1) == 1; // If bit 0 of the initial value is set Input >>= 1; Store(Input, Address); // store the result at the target address flag_Negative = Input >= 0x80; // if bit 7 of the result is set flag_Zero = Input == 0x00; // if all bits are cleared } void Op_LSR_A() { // Logical Shift Right the Accumulator flag_Carry = (A & 1) == 1; // If bit 0 of the initial value is set A >>= 1; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_SRE(byte Input, ushort Address) { // Undocumented Opcode: equivalent to LSR + EOR Op_LSR(Input, Address); Op_EOR(dataBus); } void Op_ADC(byte Input) { // Add with Carry int Intput = Input + A + (flag_Carry ? 1 : 0); flag_Overflow = (~(A ^ Input) & (A ^ Intput) & 0x80) != 0; flag_Carry = Intput > 0xFF; A = (byte)Intput; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_ROR(byte Input, ushort Address) { // Rotate Right bool FutureFlag_Carry = (Input & 1) == 1; // if bit 0 was set before the shift Input >>= 1; if (flag_Carry) { Input |= 0x80; // put the old carry flag into bit 7 } Store(Input, Address); flag_Carry = FutureFlag_Carry; // if bit 0 was set before the shift flag_Negative = Input >= 0x80; // if bit 7 of the result is set flag_Zero = Input == 0x00; // if all bits are cleared } void Op_ROR_A() { bool FutureFlag_Carry = (A & 1) == 1; A >>= 1; if (flag_Carry) { A |= 0x80; // put the old carry flag into bit 7 } flag_Carry = FutureFlag_Carry; // if bit 0 was set before the shift flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_RRA(byte Input, ushort Address) { // Undocumented Opcode: equivalent to ROR + ADC Op_ROR(Input, Address); Op_ADC(dataBus); } void Op_CMP(byte Input) { // Compare A flag_Zero = A == Input; // if A is equal to the value being compared flag_Carry = A >= Input;// if A is greater than the value being compared flag_Negative = ((byte)(A - Input) >= 0x80); // if A - the value being compared would leave bit 7 set } void Op_CPY(byte Input) { // Compare Y flag_Zero = Y == Input; // if Y is equal to the value being compared flag_Carry = Y >= Input;// if Y is greater than the value being compared flag_Negative = ((byte)(Y - Input) >= 0x80); // if Y - the value being compared would leave bit 7 set } void Op_CPX(byte Input) { // Compare X flag_Zero = X == Input; // if X is equal to the value being compared flag_Carry = X >= Input;// if X is greater than the value being compared flag_Negative = ((byte)(X - Input) >= 0x80); // if X - the value being compared would leave bit 7 set } void Op_SBC(byte Input) { // Subtract with Carry int Intput = A - Input; if (!flag_Carry) { Intput -= 1; } flag_Overflow = ((A ^ Input) & (A ^ Intput) & 0x80) != 0; flag_Carry = Intput >= 0; A = (byte)Intput; flag_Negative = A >= 0x80; // if bit 7 of the result is set flag_Zero = A == 0x00; // if all bits are cleared } void Op_INC(ushort Address) { // Increment dl++; // The value read is currently stored in the PreDecode register flag_Zero = dl == 0; // if all bits are cleared flag_Negative = dl >= 0x80; // if bit 7 of the result is set Store(dl, Address); } void Op_DEC(ushort Address) { // Decrement dl--; // The value read is currently stored in the PreDecode register flag_Zero = dl == 0; // if all bits are cleared flag_Negative = dl >= 0x80; // if bit 7 of the result is set Store(dl, Address); } #endregion // this is the tracelogger. // I call this function during the first cycle of every instruction. public ushort DebugRange_Low = 0x0000; public ushort DebugRange_High = 0xFFFF; public bool OnlyDebugInRange = false; void Debug() { if (OnlyDebugInRange) { if (programCounter < DebugRange_Low || programCounter > DebugRange_High) { return; } } string addr = programCounter.ToString("X4"); string bytes = ""; int b = 0; while (b < Documentation.OpDocs[opCode].length) { string t = Observe((ushort)(programCounter + b)).ToString("X"); if (t.Length == 1) { t = "0" + t; } t += " "; bytes = bytes + t; b++; } if (bytes.Length < 7) { bytes += "\t"; } string sA = A.ToString("X2"); string sX = X.ToString("X2"); string sY = Y.ToString("X2"); string sS = stackPointer.ToString("X2"); string Flags = ""; Flags += flag_Negative ? "N" : "n"; Flags += flag_Overflow ? "V" : "v"; Flags += "--"; Flags += flag_Decimal ? "D" : "d"; Flags += flag_Interrupt ? "I" : "i"; Flags += flag_Zero ? "Z" : "z"; Flags += flag_Carry ? "C" : "c"; if (DebugLog == null) { DebugLog = new StringBuilder(); } string instruction = Documentation.OpDocs[opCode].mnemonic + " "; if (opCode == 0) { if (DoReset) { instruction = "RESET"; bytes = "--\t"; } else if (DoNMI) { instruction = "NMI"; bytes = "--\t"; } else if (DoIRQ) { instruction = "IRQ"; bytes = "--\t"; } } ushort Target = 0; switch (Documentation.OpDocs[opCode].mode) { case "i": //implied break; case "d": //zp instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = Observe((ushort)(programCounter + 1)); break; case "a": //abs instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = (ushort)((Observe((ushort)(programCounter + 2)) << 8) | Observe((ushort)(programCounter + 1))); break; case "r": //relative instruction += "$" + ((ushort)(programCounter + (sbyte)Observe((ushort)(programCounter + 1))) + 2).ToString("X4"); Target = (ushort)((ushort)(programCounter + (sbyte)Observe((ushort)(programCounter + 1))) + 2); break; case "#v": //imm instruction += "#" + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = Observe((ushort)(programCounter + 1)); break; case "A": //A instruction += "A"; break; case "(a)": //(ind) instruction += "($" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ") -> $" + (Observe((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100)) + Observe((ushort)((Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100) + 1)) * 0x100).ToString("X4"); Target = (ushort)(Observe((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100)) + Observe((ushort)((Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100) + 1)) * 0x100); break; case "d,x": //zp, x instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X -> $" + (Observe((ushort)(programCounter + 1)) + X).ToString("X2"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + X); break; case "d,y": //zp, y instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", Y -> $" + (Observe((ushort)(programCounter + 1)) + Y).ToString("X2"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Y); break; case "a,x": //abs, x instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X -> $" + ((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + X)).ToString("X4"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + X); break; case "a,y": //abs, Y instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ", Y -> $" + ((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + Y)).ToString("X4"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + Y); break; case "(d),y": //(zp), Y instruction += "($00" + Observe((ushort)(programCounter + 1)).ToString("X2") + "), Y -> $" + ((ushort)(Observe(Observe((ushort)(programCounter + 1))) + Observe((ushort)((byte)(Observe((ushort)(programCounter + 1)) + 1) + (ushort)((Observe((ushort)(programCounter + 1)) & 0xFF00)))) * 0x100) + Y).ToString("X4"); Target = (ushort)((ushort)(Observe(Observe((ushort)(programCounter + 1))) + Observe((ushort)((byte)(Observe((ushort)(programCounter + 1)) + 1) + (ushort)((Observe((ushort)(programCounter + 1)) & 0xFF00)))) * 0x100) + Y); break; case "(d,x)": //(zp, X) instruction += "($00" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X) -> $" + (Observe((byte)(Observe((ushort)(programCounter + 1)) + X)) + Observe((ushort)((byte)((byte)(Observe((ushort)(programCounter + 1)) + X) + 1) + (ushort)(((byte)(Observe((ushort)(programCounter + 1)) + X) & 0xFF00)))) * 0x100).ToString("X4"); Target = (ushort)(Observe((byte)(Observe((ushort)(programCounter + 1)) + X)) + Observe((ushort)((byte)((byte)(Observe((ushort)(programCounter + 1)) + X) + 1) + (ushort)(((byte)(Observe((ushort)(programCounter + 1)) + X) & 0xFF00)))) * 0x100); break; } if (Target == 0x2007) { instruction += " | PPU[$" + PPU_v.ToString("X4") + "]"; } if (instruction.Length < 8) { instruction += "\t"; } if (instruction.Length < 17) { instruction += "\t"; } int PPUCycle = 0; String PPUPos = "(" + PPU_Scanline + ", " + PPU_Dot + ")"; if (totalCycles < 27395) { PPUCycle = PPU_Scanline * 341 + PPU_Dot; } else { if (PPU_Scanline >= 241) { PPUCycle = (PPU_Scanline - 241) * 341 + PPU_Dot; } else { PPUCycle = (PPU_Scanline + 21) * 341 + PPU_Dot; } } if ((PPUPos.Length + PPUCycle.ToString().Length + 1) < 13) { PPUPos += "\t"; } string LogLine = "$" + addr + "\t" + bytes + "\t" + instruction + "\tA:" + sA + "\tX:" + sX + "\tY:" + sY + "\tSP:" + sS + "\t" + Flags + "\tCycle: " + totalCycles; bool LogExtra = true; if (LogExtra) { string TempLine_APU_Full = LogLine + "\t" + "DMC :: S_Addr: $" + APU_DMC_SampleAddress.ToString("X4") + "\t S_Length:" + APU_DMC_SampleLength.ToString() + "\t AddrCounter: $" + APU_DMC_AddressCounter.ToString("X4") + "\t BytesLeft:" + APU_DMC_BytesRemaining.ToString() + "\t Shifter:" + APU_DMC_Shifter.ToString() + ":" + APU_DMC_ShifterBitsRemaining.ToString() + "\tDMC_Timer:" + (APU_PutCycle ? APU_ChannelTimer_DMC : (APU_ChannelTimer_DMC - 1)).ToString(); string TempLine_APUFrameCounter_IRQs = LogLine + " \t$4015: " + Observe(0x4015).ToString("X2") + "\t APU_FrameCounter: " + APU_Framecounter.ToString() + " \tEvenCycle = : " + APU_PutCycle + " \tDoIRQ = " + DoIRQ; string TempLine_PPU = LogLine + "\t$2000:" + Observe(0x2000).ToString("X2") + "\t$2001:" + Observe(0x2001).ToString("X2") + "\t$2002:" + Observe(0x2002).ToString("X2") + "\tR/W Addr:" + PPU_v.ToString("X4") + "\tPPUAddrLatch:" + PPUAddrLatch + "\tPPU AddressBus: " + PPU_AddressBus.ToString("X4"); string TempLine_PPU2 = LogLine + "\tVRAMAddress:" + PPU_v.ToString("X4") + "\tPPUReadBuffer:" + PPU_ReadBuffer.ToString("X2"); string TempLine_PPU3 = LogLine + "\tPPU_Coords (" + PPU_Scanline + ", " + PPU_Dot + ")\todd:" + PPU_OddFrame.ToString() + "\tv: " + PPU_v.ToString("X4"); //string TempLine_MMC3IRQ = LogLine + "\tPPU_Coords (" + PPU_Scanline + ", " + PPU_Dot + ")\tIRQTimer:" + Cart.Mapper_4_IRQCounter + "\tIRQLatch: " + Cart.Mapper_4_IRQLatch + "\tIRQEnabled: " + Cart.Mapper_4_EnableIRQ + "\tDoIRQ: " + DoIRQ + "\tPPU_ADDR_Prev: " + (PPU_A12_Prev ? "1" : "0"); DebugLog.AppendLine(TempLine_PPU3); } else { DebugLog.AppendLine(LogLine); } } void Debug_PPU() { string dotColor = ""; if (PPU_ShowScreenBorders || (PPU_Scanline < 240 && PPU_Dot <= 256 && PPU_Dot > 0)) { dotColor = "COLOR: " + DotColor.ToString("X2") + "\t"; } string MMC3 = ""; string Addr = "Address: " + PPU_AddressBus.ToString("X4") + "\t"; string Octal = "OctalLatch: " + PPU_OctalLatch.ToString("X2") + "\t"; string enabled = "[" + (PPU_Mask_ShowSprites ? "S" : "-") + (PPU_Mask_ShowBackground ? "B" : "-") + "]\t"; string ALE = "ALE: " + (PPU_ALE?"1":"0") + "\t"; string RD = "RD: " + (PPU_READ ? "1" : "0") + "\t"; string BSR_Lo = "BSRL: " + Convert.ToString(PPU_BackgroundPatternShiftRegisterL, 2).PadLeft(16, '0') + "\t"; string BSR_Hi = "BSRH: " + Convert.ToString(PPU_BackgroundPatternShiftRegisterH, 2).PadLeft(16, '0') + "\t"; string EightCycleRead = ""; if ((PPU_Dot >= 1 && PPU_Dot <= 256) || (PPU_Dot >= 321 && PPU_Dot <= 336)) // if this is a visible pixel, or preparing the start of next scanline { if(PPU_Mask_ShowSprites || PPU_Mask_ShowBackground) { EightCycleRead = "8CycleReadTick: " + ((byte)((PPU_Dot + 7) & 7)).ToString() + "\t"; } else { EightCycleRead = "8CycleReadTick: -\t"; } } string LogLine = "(" + PPU_Scanline.ToString() + ", " + PPU_Dot.ToString() + ") \t" + Addr + Octal + EightCycleRead + dotColor + enabled + MMC3 + ALE + RD + BSR_Lo + BSR_Hi; DebugLog.AppendLine(LogLine); } public List SaveState() { List State = new List(); State.Add((byte)programCounter); State.Add((byte)(programCounter >> 8)); State.Add((byte)addressBus); State.Add((byte)(addressBus >> 8)); State.Add((byte)temporaryAddress); State.Add((byte)(temporaryAddress >> 8)); State.Add((byte)OAMAddressBus); State.Add((byte)(OAMAddressBus >> 8)); State.Add((byte)PPU_v); State.Add((byte)(PPU_v >> 8)); State.Add((byte)PPU_t); State.Add((byte)(PPU_t >> 8)); State.Add((byte)totalCycles); State.Add((byte)(totalCycles >> 8)); State.Add((byte)(totalCycles >> 16)); State.Add((byte)(totalCycles >> 24)); State.Add(PPUClock); State.Add(CPUClock); State.Add(operationCycle); State.Add(opCode); State.Add(dl); State.Add(dataBus); State.Add(A); State.Add(X); State.Add(Y); State.Add(stackPointer); status = flag_Carry ? (byte)0x01 : (byte)0; status += flag_Zero ? (byte)0x02 : (byte)0; status += flag_Interrupt ? (byte)0x04 : (byte)0; status += flag_Decimal ? (byte)0x08 : (byte)0; status += flag_Overflow ? (byte)0x40 : (byte)0; status += flag_Negative ? (byte)0x80 : (byte)0; State.Add(status); State.Add(specialBus); State.Add(H); State.Add((byte)(IgnoreH ? 1 : 0)); State.Add((byte)(CPU_Read ? 1 : 0)); State.Add((byte)(DoBRK ? 1 : 0)); State.Add((byte)(DoNMI ? 1 : 0)); State.Add((byte)(DoIRQ ? 1 : 0)); State.Add((byte)(DoReset ? 1 : 0)); State.Add((byte)(DoOAMDMA ? 1 : 0)); State.Add((byte)(FirstCycleOfOAMDMA ? 1 : 0)); State.Add((byte)(DoDMCDMA ? 1 : 0)); State.Add(DMCDMADelay); State.Add(CannotRunDMCDMARightNow); State.Add(DMAPage); State.Add(DMAAddress); State.Add((byte)(APU_ControllerPortsStrobing ? 1 : 0)); State.Add((byte)(APU_ControllerPortsStrobed ? 1 : 0)); State.Add(ControllerPort1); State.Add(ControllerPort2); State.Add(ControllerShiftRegister1); State.Add(ControllerShiftRegister2); State.Add(Controller1ShiftCounter); State.Add(Controller2ShiftCounter); State.Add((byte)(dataPinsAreNotFloating ? 1 : 0)); State.Add((byte)(APU_PutCycle ? 1 : 0)); State.Add((byte)(APU_Status_DMCInterrupt ? 1 : 0)); State.Add((byte)(APU_Status_FrameInterrupt ? 1 : 0)); State.Add((byte)(APU_Status_DMC ? 1 : 0)); State.Add((byte)(APU_Status_DelayedDMC ? 1 : 0)); State.Add((byte)(APU_Status_Noise ? 1 : 0)); State.Add((byte)(APU_Status_Triangle ? 1 : 0)); State.Add((byte)(APU_Status_Pulse2 ? 1 : 0)); State.Add((byte)(APU_Status_Pulse1 ? 1 : 0)); State.Add((byte)(Clearing_APU_FrameInterrupt ? 1 : 0)); State.Add(APU_DelayedDMC4015); State.Add((byte)(APU_ImplicitAbortDMC4015 ? 1 : 0)); State.Add((byte)(APU_SetImplicitAbortDMC4015 ? 1 : 0)); foreach (Byte b in APU_Register) { State.Add(b); } State.Add((byte)(APU_FrameCounterMode ? 1 : 0)); State.Add((byte)(APU_FrameCounterInhibitIRQ ? 1 : 0)); State.Add(APU_FrameCounterReset); State.Add((byte)APU_Framecounter); State.Add((byte)(APU_Framecounter >> 8)); State.Add((byte)(APU_QuarterFrameClock ? 1 : 0)); State.Add((byte)(APU_HalfFrameClock ? 1 : 0)); State.Add((byte)(APU_Envelope_StartFlag ? 1 : 0)); State.Add((byte)(APU_Envelope_DividerClock ? 1 : 0)); State.Add(APU_Envelope_DecayLevel); State.Add(APU_LengthCounter_Pulse1); State.Add(APU_LengthCounter_Pulse2); State.Add(APU_LengthCounter_Triangle); State.Add(APU_LengthCounter_Noise); State.Add((byte)(APU_LengthCounter_HaltPulse1 ? 1 : 0)); State.Add((byte)(APU_LengthCounter_HaltPulse2 ? 1 : 0)); State.Add((byte)(APU_LengthCounter_HaltTriangle ? 1 : 0)); State.Add((byte)(APU_LengthCounter_HaltNoise ? 1 : 0)); State.Add((byte)(APU_LengthCounter_ReloadPulse1 ? 1 : 0)); State.Add((byte)(APU_LengthCounter_ReloadPulse2 ? 1 : 0)); State.Add((byte)(APU_LengthCounter_ReloadTriangle ? 1 : 0)); State.Add((byte)(APU_LengthCounter_ReloadNoise ? 1 : 0)); State.Add(APU_LengthCounter_ReloadValuePulse1); State.Add(APU_LengthCounter_ReloadValuePulse2); State.Add(APU_LengthCounter_ReloadValueTriangle); State.Add(APU_LengthCounter_ReloadValueNoise); State.Add((byte)APU_ChannelTimer_Pulse1); State.Add((byte)(APU_ChannelTimer_Pulse1 >> 8)); State.Add((byte)APU_ChannelTimer_Pulse2); State.Add((byte)(APU_ChannelTimer_Pulse2 >> 8)); State.Add((byte)APU_ChannelTimer_Triangle); State.Add((byte)(APU_ChannelTimer_Triangle >> 8)); State.Add((byte)APU_ChannelTimer_Noise); State.Add((byte)(APU_ChannelTimer_Noise >> 8)); State.Add((byte)APU_ChannelTimer_DMC); State.Add((byte)(APU_ChannelTimer_DMC >> 8)); State.Add((byte)(APU_DMC_EnableIRQ ? 1 : 0)); State.Add((byte)(APU_DMC_Loop ? 1 : 0)); State.Add((byte)APU_DMC_Rate); State.Add((byte)(APU_DMC_Rate >> 8)); State.Add(APU_DMC_Output); State.Add((byte)APU_DMC_SampleAddress); State.Add((byte)(APU_DMC_SampleAddress >> 8)); State.Add((byte)APU_DMC_SampleLength); State.Add((byte)(APU_DMC_SampleLength >> 8)); State.Add((byte)APU_DMC_BytesRemaining); State.Add((byte)(APU_DMC_BytesRemaining >> 8)); State.Add(APU_DMC_Buffer); State.Add((byte)APU_DMC_AddressCounter); State.Add((byte)(APU_DMC_AddressCounter >> 8)); State.Add(APU_DMC_Shifter); State.Add(APU_DMC_ShifterBitsRemaining); State.Add((byte)(APU_Silent ? 1 : 0)); State.Add(SecondaryOAMSize); State.Add(OAM2Address); State.Add((byte)(SecondaryOAMFull ? 1 : 0)); State.Add(SpriteEvaluationTick); State.Add((byte)(OAMAddressOverflowedDuringSpriteEvaluation ? 1 : 0)); State.Add(OAM2Address); State.Add(PPUBus); for (int i = 0; i < 8; i++) { State.Add((byte)PPUBusDecay[i]); State.Add((byte)(PPUBusDecay[i] >> 8)); State.Add((byte)(PPUBusDecay[i] >> 16)); State.Add((byte)(PPUBusDecay[i] >> 24)); } State.Add(PPUOAMAddress); State.Add((byte)(PPUStatus_VBlank ? 1 : 0)); State.Add((byte)(PPUStatus_SpriteZeroHit ? 1 : 0)); State.Add((byte)(PPUStatus_SpriteOverflow ? 1 : 0)); State.Add((byte)(PPUStatus_PendingSpriteZeroHit ? 1 : 0)); State.Add((byte)(PPUStatus_PendingSpriteZeroHit2 ? 1 : 0)); State.Add((byte)(PPUStatus_SpriteZeroHit_Delayed ? 1 : 0)); State.Add((byte)(PPUStatus_SpriteOverflow_Delayed ? 1 : 0)); State.Add((byte)(PPU_Spritex16 ? 1 : 0)); State.Add((byte)PPU_Scanline); State.Add((byte)(PPU_Scanline >> 8)); State.Add((byte)PPU_Dot); State.Add((byte)(PPU_Dot >> 8)); State.Add((byte)(PPU_VRegisterChangedOutOfVBlank ? 1 : 0)); State.Add((byte)(PPU_OAMCorruptionRenderingDisabledOutOfVBlank ? 1 : 0)); State.Add((byte)(PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant ? 1 : 0)); State.Add((byte)(PPU_PendingOAMCorruption ? 1 : 0)); State.Add(PPU_OAMCorruptionIndex); State.Add((byte)(PPU_OAMCorruptionRenderingEnabledOutOfVBlank ? 1 : 0)); State.Add((byte)(PPU_OAMEvaluationCorruptionOddCycle ? 1 : 0)); State.Add((byte)(PPU_OAMEvaluationObjectInRange ? 1 : 0)); State.Add((byte)(PPU_OAMEvaluationObjectInXRange ? 1 : 0)); State.Add((byte)(PPU_PaletteCorruptionRenderingDisabledOutOfVBlank ? 1 : 0)); State.Add(PPU_AttributeLatchRegister); State.Add((byte)PPU_BackgroundAttributeShiftRegisterL); State.Add((byte)(PPU_BackgroundAttributeShiftRegisterL >> 8)); State.Add((byte)PPU_BackgroundAttributeShiftRegisterH); State.Add((byte)(PPU_BackgroundAttributeShiftRegisterH >> 8)); State.Add((byte)PPU_BackgroundPatternShiftRegisterL); State.Add((byte)(PPU_BackgroundPatternShiftRegisterL >> 8)); State.Add((byte)PPU_BackgroundPatternShiftRegisterH); State.Add((byte)(PPU_BackgroundPatternShiftRegisterH >> 8)); State.Add(PPU_FineXScroll); for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShiftRegisterL[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShiftRegisterH[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteAttribute[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpritePattern[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteXposition[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteYposition[i]); } for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShifterCounter[i]); } State.Add((byte)(PPU_NextScanlineContainsSpriteZero ? 1 : 0)); State.Add((byte)(PPU_CurrentScanlineContainsSpriteZero ? 1 : 0)); State.Add(PPU_SpritePatternL); State.Add(PPU_SpritePatternH); State.Add((byte)(PPU_Mask_Greyscale ? 1 : 0)); State.Add((byte)(PPU_Mask_8PxShowBackground ? 1 : 0)); State.Add((byte)(PPU_Mask_8PxShowSprites ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowBackground ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowSprites ? 1 : 0)); State.Add((byte)(PPU_Mask_EmphasizeRed ? 1 : 0)); State.Add((byte)(PPU_Mask_EmphasizeGreen ? 1 : 0)); State.Add((byte)(PPU_Mask_EmphasizeBlue ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowBackground_Delayed ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowSprites_Delayed ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowBackground_Instant ? 1 : 0)); State.Add((byte)(PPU_Mask_ShowSprites_Instant ? 1 : 0)); State.Add(PPU_LowBitPlane); State.Add(PPU_HighBitPlane); State.Add(PPU_Attribute); State.Add((byte)(PPU_CanDetectSpriteZeroHit ? 1 : 0)); State.Add((byte)(PPU_A12_Prev ? 1 : 0)); State.Add((byte)(PPU_OddFrame ? 1 : 0)); State.Add(PaletteRAMAddress); State.Add((byte)(ThisDotReadFromPaletteRAM ? 1 : 0)); State.Add((byte)(NMI_PinsSignal ? 1 : 0)); State.Add((byte)(NMI_PreviousPinsSignal ? 1 : 0)); State.Add((byte)(IRQ_LevelDetector ? 1 : 0)); State.Add((byte)(NMILine ? 1 : 0)); State.Add((byte)(IRQLine ? 1 : 0)); State.Add((byte)(CopyV ? 1 : 0)); State.Add((byte)(SkippedPreRenderDot341 ? 1 : 0)); State.Add((byte)(OamCorruptedOnOddCycle ? 1 : 0)); State.Add(PPU_OAMLatch); State.Add(PPU_RenderTemp); State.Add((byte)(PPU_Commit_NametableFetch ? 1 : 0)); State.Add((byte)(PPU_Commit_AttributeFetch ? 1 : 0)); State.Add((byte)(PPU_Commit_PatternLowFetch ? 1 : 0)); State.Add((byte)(PPU_Commit_PatternHighFetch ? 1 : 0)); State.Add((byte)PPU_VRAM_MysteryAddress); State.Add((byte)(PPU_VRAM_MysteryAddress >> 8)); State.Add((byte)PPU_AddressBus); State.Add((byte)(PPU_AddressBus >> 8)); State.Add(PPU_Update2006Delay); State.Add(PPU_Update2005Delay); State.Add(PPU_Update2005Value); State.Add(PPU_Update2001Value); State.Add((byte)PPU_Update2006Value); State.Add((byte)(PPU_Update2006Value >> 8)); State.Add((byte)PPU_Update2006Value_Temp); State.Add((byte)(PPU_Update2006Value_Temp >> 8)); State.Add((byte)(PPU_WasRenderingBefore2001Write ? 1 : 0)); State.Add(PPU_ReadBuffer); State.Add((byte)(PPUAddrLatch ? 1 : 0)); State.Add((byte)(PPUControlIncrementMode32 ? 1 : 0)); State.Add((byte)(PPUControl_NMIEnabled ? 1 : 0)); State.Add((byte)(PPU_PatternSelect_Sprites ? 1 : 0)); State.Add((byte)(PPU_PatternSelect_Background ? 1 : 0)); State.Add((byte)(PPU_PendingVBlank ? 1 : 0)); State.Add((byte)(PPU_VSET ? 1 : 0)); State.Add((byte)(PPU_VSET_Latch1 ? 1 : 0)); State.Add((byte)(PPU_VSET_Latch2 ? 1 : 0)); State.Add((byte)(PPU_Read2002 ? 1 : 0)); State.Add((byte)(OAMDMA_Aligned ? 1 : 0)); State.Add((byte)(OAMDMA_Halt ? 1 : 0)); State.Add((byte)(DMCDMA_Halt ? 1 : 0)); State.Add(OAM_InternalBus); State.Add((byte)PPU_PatternAddressRegister_CHR); State.Add((byte)(PPU_PatternAddressRegister_CHR >> 8)); State.Add((byte)(PPU_ALE ? 1 : 0)); State.Add(PPU_OctalLatch); State.Add((byte)(PPU_2007_Read ? 1 : 0)); State.Add((byte)(PPU_2007_Read_SR ? 1 : 0)); for (int i = 0; i < PPU_2007_Read_Latches.Length; i++) { State.Add((byte)(PPU_2007_Read_Latches[i] ? 1 : 0)); } State.Add((byte)(PPU_2007_PD_RB ? 1 : 0)); State.Add((byte)(PPU_2007_ReadALE ? 1 : 0)); State.Add((byte)(PPU_2007_Read_H0_Latch ? 1 : 0)); State.Add((byte)(PPU_2007_Read_XRB ? 1 : 0)); State.Add((byte)(PPU_READ ? 1 : 0)); State.Add((byte)(PPU_2007_Write ? 1 : 0)); State.Add((byte)(PPU_2007_Write_SR ? 1 : 0)); for (int i = 0; i < PPU_2007_Write_Latches.Length; i++) { State.Add((byte)(PPU_2007_Write_Latches[i] ? 1 : 0)); } State.Add((byte)(PPU_2007_DB_PAR ? 1 : 0)); State.Add((byte)(PPU_2007_WriteALE ? 1 : 0)); State.Add((byte)(PPU_2007_TStep_Latch ? 1 : 0)); State.Add((byte)(PPU_2007_TStep ? 1 : 0)); State.Add((byte)(PPU_2007_BLNK_Latch ? 1 : 0)); State.Add((byte)(PPU_2007_PaletteRAMEnable ? 1 : 0)); State.Add(PPU_2007_WriteData); State.Add((byte)(PPU_WRITE ? 1 : 0)); State.Add(PPU_Update2001Delay); // TEMPORARY State.Add(PPU_Update2001OAMCorruptionDelay); // TEMPORARY State.Add(PPU_Update2001EmphasisBitsDelay); // TEMPORARY foreach (Byte b in RAM) { State.Add(b); } foreach (Byte b in VRAM) { State.Add(b); } foreach (Byte b in OAM) { State.Add(b); } foreach (Byte b in OAM2) { State.Add(b); } foreach (Byte b in PaletteRAM) { State.Add(b); } List MapperBytes = Cart.MapperChip.SaveMapperRegisters(); for (int i = 0; i < MapperBytes.Count; i++) { State.Add(MapperBytes[i]); } return State; } public void LoadState(List State) { int p = 0; programCounter = State[p++]; programCounter |= (ushort)(State[p++] << 8); addressBus = State[p++]; addressBus |= (ushort)(State[p++] << 8); temporaryAddress = State[p++]; temporaryAddress |= (ushort)(State[p++] << 8); OAMAddressBus = State[p++]; OAMAddressBus |= (ushort)(State[p++] << 8); PPU_v = State[p++]; PPU_v |= (ushort)(State[p++] << 8); PPU_t = State[p++]; PPU_t |= (ushort)(State[p++] << 8); totalCycles = State[p++]; totalCycles |= (State[p++] << 8); totalCycles |= (State[p++] << 16); totalCycles |= (State[p++] << 24); PPUClock = State[p++]; CPUClock = State[p++]; operationCycle = State[p++]; opCode = State[p++]; dl = State[p++]; dataBus = State[p++]; A = State[p++]; X = State[p++]; Y = State[p++]; stackPointer = State[p++]; status = State[p++]; flag_Carry = (status & 1) == 1; flag_Zero = ((status & 0x02) >> 1) == 1; flag_Interrupt = ((status & 0x04) >> 2) == 1; flag_Decimal = ((status & 0x08) >> 3) == 1; flag_Overflow = ((status & 0x40) >> 6) == 1; flag_Negative = ((status & 0x80) >> 7) == 1; specialBus = State[p++]; H = State[p++]; IgnoreH = (State[p++] & 1) == 1; CPU_Read = (State[p++] & 1) == 1; DoBRK = (State[p++] & 1) == 1; DoNMI = (State[p++] & 1) == 1; DoIRQ = (State[p++] & 1) == 1; DoReset = (State[p++] & 1) == 1; DoOAMDMA = (State[p++] & 1) == 1; FirstCycleOfOAMDMA = (State[p++] & 1) == 1; DoDMCDMA = (State[p++] & 1) == 1; DMCDMADelay = State[p++]; CannotRunDMCDMARightNow = State[p++]; DMAPage = State[p++]; DMAAddress = State[p++]; APU_ControllerPortsStrobing = (State[p++] & 1) == 1; APU_ControllerPortsStrobed = (State[p++] & 1) == 1; ControllerPort1 = State[p++]; ControllerPort2 = State[p++]; ControllerShiftRegister1 = State[p++]; ControllerShiftRegister2 = State[p++]; Controller1ShiftCounter = State[p++]; Controller2ShiftCounter = State[p++]; dataPinsAreNotFloating = (State[p++] & 1) == 1; APU_PutCycle = (State[p++] & 1) == 1; APU_Status_DMCInterrupt = (State[p++] & 1) == 1; APU_Status_FrameInterrupt = (State[p++] & 1) == 1; APU_Status_DMC = (State[p++] & 1) == 1; APU_Status_DelayedDMC = (State[p++] & 1) == 1; APU_Status_Noise = (State[p++] & 1) == 1; APU_Status_Triangle = (State[p++] & 1) == 1; APU_Status_Pulse2 = (State[p++] & 1) == 1; APU_Status_Pulse1 = (State[p++] & 1) == 1; Clearing_APU_FrameInterrupt = (State[p++] & 1) == 1; APU_DelayedDMC4015 = State[p++]; APU_ImplicitAbortDMC4015 = (State[p++] & 1) == 1; APU_SetImplicitAbortDMC4015 = (State[p++] & 1) == 1; for (int i = 0; i < APU_Register.Length; i++) { APU_Register[i] = State[p++]; } APU_FrameCounterMode = (State[p++] & 1) == 1; APU_FrameCounterInhibitIRQ = (State[p++] & 1) == 1; APU_FrameCounterReset = State[p++]; APU_Framecounter = State[p++]; APU_Framecounter |= (ushort)(State[p++] << 8); APU_QuarterFrameClock = (State[p++] & 1) == 1; APU_HalfFrameClock = (State[p++] & 1) == 1; APU_Envelope_StartFlag = (State[p++] & 1) == 1; APU_Envelope_DividerClock = (State[p++] & 1) == 1; APU_Envelope_DecayLevel = State[p++]; APU_LengthCounter_Pulse1 = State[p++]; APU_LengthCounter_Pulse2 = State[p++]; APU_LengthCounter_Triangle = State[p++]; APU_LengthCounter_Noise = State[p++]; APU_LengthCounter_HaltPulse1 = (State[p++] & 1) == 1; APU_LengthCounter_HaltPulse2 = (State[p++] & 1) == 1; APU_LengthCounter_HaltTriangle = (State[p++] & 1) == 1; APU_LengthCounter_HaltNoise = (State[p++] & 1) == 1; APU_LengthCounter_ReloadPulse1 = (State[p++] & 1) == 1; APU_LengthCounter_ReloadPulse2 = (State[p++] & 1) == 1; APU_LengthCounter_ReloadTriangle = (State[p++] & 1) == 1; APU_LengthCounter_ReloadNoise = (State[p++] & 1) == 1; APU_LengthCounter_ReloadValuePulse1 = State[p++]; APU_LengthCounter_ReloadValuePulse2 = State[p++]; APU_LengthCounter_ReloadValueTriangle = State[p++]; APU_LengthCounter_ReloadValueNoise = State[p++]; APU_ChannelTimer_Pulse1 = State[p++]; APU_ChannelTimer_Pulse1 |= (ushort)(State[p++] << 8); APU_ChannelTimer_Pulse2 = State[p++]; APU_ChannelTimer_Pulse2 |= (ushort)(State[p++] << 8); APU_ChannelTimer_Triangle = State[p++]; APU_ChannelTimer_Triangle |= (ushort)(State[p++] << 8); APU_ChannelTimer_Noise = State[p++]; APU_ChannelTimer_Noise |= (ushort)(State[p++] << 8); APU_ChannelTimer_DMC = State[p++]; APU_ChannelTimer_DMC |= (ushort)(State[p++] << 8); APU_DMC_EnableIRQ = (State[p++] & 1) == 1; APU_DMC_Loop = (State[p++] & 1) == 1; APU_DMC_Rate = State[p++]; APU_DMC_Rate |= (ushort)(State[p++] << 8); APU_DMC_Output = State[p++]; APU_DMC_SampleAddress = State[p++]; APU_DMC_SampleAddress |= (ushort)(State[p++] << 8); APU_DMC_SampleLength = State[p++]; APU_DMC_SampleLength |= (ushort)(State[p++] << 8); APU_DMC_BytesRemaining = State[p++]; APU_DMC_BytesRemaining |= (ushort)(State[p++] << 8); APU_DMC_Buffer = State[p++]; APU_DMC_AddressCounter = State[p++]; APU_DMC_AddressCounter |= (ushort)(State[p++] << 8); APU_DMC_Shifter = State[p++]; APU_DMC_ShifterBitsRemaining = State[p++]; APU_Silent = (State[p++] & 1) == 1; SecondaryOAMSize = State[p++]; OAM2Address = State[p++]; SecondaryOAMFull = (State[p++] & 1) == 1; SpriteEvaluationTick = State[p++]; OAMAddressOverflowedDuringSpriteEvaluation = (State[p++] & 1) == 1; OAM2Address = State[p++]; PPUBus = State[p++]; for (int i = 0; i < 8; i++) { PPUBusDecay[i] = State[p++]; PPUBusDecay[i] |= (State[p++] << 8); PPUBusDecay[i] |= (State[p++] << 16); PPUBusDecay[i] |= (State[p++] << 24); } PPUOAMAddress = State[p++]; PPUStatus_VBlank = (State[p++] & 1) == 1; PPUStatus_SpriteZeroHit = (State[p++] & 1) == 1; PPUStatus_SpriteOverflow = (State[p++] & 1) == 1; PPUStatus_PendingSpriteZeroHit = (State[p++] & 1) == 1; PPUStatus_PendingSpriteZeroHit2 = (State[p++] & 1) == 1; PPUStatus_SpriteZeroHit_Delayed = (State[p++] & 1) == 1; PPUStatus_SpriteOverflow_Delayed = (State[p++] & 1) == 1; PPU_Spritex16 = (State[p++] & 1) == 1; PPU_Scanline = State[p++]; PPU_Scanline |= (ushort)(State[p++] << 8); PPU_Dot = State[p++]; PPU_Dot |= (ushort)(State[p++] << 8); PPU_VRegisterChangedOutOfVBlank = (State[p++] & 1) == 1; PPU_OAMCorruptionRenderingDisabledOutOfVBlank = (State[p++] & 1) == 1; PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = (State[p++] & 1) == 1; PPU_PendingOAMCorruption = (State[p++] & 1) == 1; PPU_OAMCorruptionIndex = State[p++]; PPU_OAMCorruptionRenderingEnabledOutOfVBlank = (State[p++] & 1) == 1; PPU_OAMEvaluationCorruptionOddCycle = (State[p++] & 1) == 1; PPU_OAMEvaluationObjectInRange = (State[p++] & 1) == 1; PPU_OAMEvaluationObjectInXRange = (State[p++] & 1) == 1; PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = (State[p++] & 1) == 1; PPU_AttributeLatchRegister = State[p++]; PPU_BackgroundAttributeShiftRegisterL = State[p++]; PPU_BackgroundAttributeShiftRegisterL |= (ushort)(State[p++] << 8); PPU_BackgroundAttributeShiftRegisterH = State[p++]; PPU_BackgroundAttributeShiftRegisterH |= (ushort)(State[p++] << 8); PPU_BackgroundPatternShiftRegisterL = State[p++]; PPU_BackgroundPatternShiftRegisterL |= (ushort)(State[p++] << 8); PPU_BackgroundPatternShiftRegisterH = State[p++]; PPU_BackgroundPatternShiftRegisterH |= (ushort)(State[p++] << 8); PPU_FineXScroll = State[p++]; for (int i = 0; i < 8; i++) { PPU_SpriteShiftRegisterL[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpriteShiftRegisterH[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpriteAttribute[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpritePattern[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpriteXposition[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpriteYposition[i] = State[p++]; } for (int i = 0; i < 8; i++) { PPU_SpriteShifterCounter[i] = State[p++]; } PPU_NextScanlineContainsSpriteZero = (State[p++] & 1) == 1; PPU_CurrentScanlineContainsSpriteZero = (State[p++] & 1) == 1; PPU_SpritePatternL = State[p++]; PPU_SpritePatternH = State[p++]; PPU_Mask_Greyscale = (State[p++] & 1) == 1; PPU_Mask_8PxShowBackground = (State[p++] & 1) == 1; PPU_Mask_8PxShowSprites = (State[p++] & 1) == 1; PPU_Mask_ShowBackground = (State[p++] & 1) == 1; PPU_Mask_ShowSprites = (State[p++] & 1) == 1; PPU_Mask_EmphasizeRed = (State[p++] & 1) == 1; PPU_Mask_EmphasizeGreen = (State[p++] & 1) == 1; PPU_Mask_EmphasizeBlue = (State[p++] & 1) == 1; PPU_Mask_ShowBackground_Delayed = (State[p++] & 1) == 1; PPU_Mask_ShowSprites_Delayed = (State[p++] & 1) == 1; PPU_Mask_ShowBackground_Instant = (State[p++] & 1) == 1; PPU_Mask_ShowSprites_Instant = (State[p++] & 1) == 1; PPU_LowBitPlane = State[p++]; PPU_HighBitPlane = State[p++]; PPU_Attribute = State[p++]; PPU_CanDetectSpriteZeroHit = (State[p++] & 1) == 1; PPU_A12_Prev = (State[p++] & 1) == 1; PPU_OddFrame = (State[p++] & 1) == 1; PaletteRAMAddress = State[p++]; ThisDotReadFromPaletteRAM = (State[p++] & 1) == 1; NMI_PinsSignal = (State[p++] & 1) == 1; NMI_PreviousPinsSignal = (State[p++] & 1) == 1; IRQ_LevelDetector = (State[p++] & 1) == 1; NMILine = (State[p++] & 1) == 1; IRQLine = (State[p++] & 1) == 1; CopyV = (State[p++] & 1) == 1; SkippedPreRenderDot341 = (State[p++] & 1) == 1; OamCorruptedOnOddCycle = (State[p++] & 1) == 1; PPU_OAMLatch = State[p++]; PPU_RenderTemp = State[p++]; PPU_Commit_NametableFetch = (State[p++] & 1) == 1; PPU_Commit_AttributeFetch = (State[p++] & 1) == 1; PPU_Commit_PatternLowFetch = (State[p++] & 1) == 1; PPU_Commit_PatternHighFetch = (State[p++] & 1) == 1; PPU_VRAM_MysteryAddress = State[p++]; PPU_VRAM_MysteryAddress |= (ushort)(State[p++] << 8); PPU_AddressBus = State[p++]; PPU_AddressBus |= (ushort)(State[p++] << 8); PPU_Update2006Delay = State[p++]; PPU_Update2005Delay = State[p++]; PPU_Update2005Value = State[p++]; PPU_Update2001Value = State[p++]; PPU_Update2006Value = State[p++]; PPU_Update2006Value |= (ushort)(State[p++] << 8); PPU_Update2006Value_Temp = State[p++]; PPU_Update2006Value_Temp |= (ushort)(State[p++] << 8); PPU_WasRenderingBefore2001Write = (State[p++] & 1) == 1; PPU_ReadBuffer = State[p++]; PPUAddrLatch = (State[p++] & 1) == 1; PPUControlIncrementMode32 = (State[p++] & 1) == 1; PPUControl_NMIEnabled = (State[p++] & 1) == 1; PPU_PatternSelect_Sprites = (State[p++] & 1) == 1; PPU_PatternSelect_Background = (State[p++] & 1) == 1; PPU_PendingVBlank = (State[p++] & 1) == 1; PPU_VSET = (State[p++] & 1) == 1; PPU_VSET_Latch1 = (State[p++] & 1) == 1; PPU_VSET_Latch2 = (State[p++] & 1) == 1; PPU_Read2002 = (State[p++] & 1) == 1; OAMDMA_Aligned = (State[p++] & 1) == 1; OAMDMA_Halt = (State[p++] & 1) == 1; DMCDMA_Halt = (State[p++] & 1) == 1; OAM_InternalBus = State[p++]; PPU_PatternAddressRegister_CHR = State[p++]; PPU_PatternAddressRegister_CHR |= (ushort)(State[p++] << 8); PPU_ALE = (State[p++] & 1) == 1; PPU_OctalLatch = State[p++]; PPU_2007_Read = (State[p++] & 1) == 1; PPU_2007_Read_SR = (State[p++] & 1) == 1; for (int i = 0; i < PPU_2007_Read_Latches.Length; i++) { PPU_2007_Read_Latches[i] = (State[p++] & 1) == 1; } PPU_2007_PD_RB = (State[p++] & 1) == 1; PPU_2007_ReadALE = (State[p++] & 1) == 1; PPU_2007_Read_H0_Latch = (State[p++] & 1) == 1; PPU_2007_Read_XRB = (State[p++] & 1) == 1; PPU_READ = (State[p++] & 1) == 1; PPU_2007_Write = (State[p++] & 1) == 1; PPU_2007_Write_SR = (State[p++] & 1) == 1; for (int i = 0; i < PPU_2007_Write_Latches.Length; i++) { PPU_2007_Write_Latches[i] = (State[p++] & 1) == 1; } PPU_2007_DB_PAR = (State[p++] & 1) == 1; PPU_2007_WriteALE = (State[p++] & 1) == 1; PPU_2007_TStep_Latch = (State[p++] & 1) == 1; PPU_2007_TStep = (State[p++] & 1) == 1; PPU_2007_BLNK_Latch = (State[p++] & 1) == 1; PPU_2007_PaletteRAMEnable = (State[p++] & 1) == 1; PPU_2007_WriteData = State[p++]; PPU_WRITE = (State[p++] & 1) == 1; PPU_Update2001Delay = State[p++]; //TOMPORARY PPU_Update2001OAMCorruptionDelay = State[p++]; //TOMPORARY PPU_Update2001EmphasisBitsDelay = State[p++]; //TOMPORARY for (int i = 0; i < RAM.Length; i++) { RAM[i] = State[p++]; } for (int i = 0; i < VRAM.Length; i++) { VRAM[i] = State[p++]; } for (int i = 0; i < OAM.Length; i++) { OAM[i] = State[p++]; } for (int i = 0; i < OAM2.Length; i++) { OAM2[i] = State[p++]; } for (int i = 0; i < PaletteRAM.Length; i++) { PaletteRAM[i] = State[p++]; } Cart.MapperChip.LoadMapperRegisters(State, p, out p); } public void Dispose() { Cart = null; Screen.Dispose(); BorderedScreen.Dispose(); NTSCScreen.Dispose(); BorderedNTSCScreen.Dispose(); } } public class DirectBitmap : IDisposable { // This class was copied from Stack Overflow // Writing to the standard Bitmap class is slow, so this class exists as a faster alternative. public Bitmap Bitmap { get; private set; } public Int32[] Bits { get; private set; } public bool Disposed { get; private set; } public int Height { get; private set; } public int Width { get; private set; } protected GCHandle BitsHandle { get; private set; } public DirectBitmap(int width, int height) { Width = width; Height = height; Bits = new Int32[width * height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); } public void SetPixel(int x, int y, Color color) { int index = x + (y * Width); int col = color.ToArgb(); Bits[index] = col; } public void SetPixel(int x, int y, int colorRGBA) { int index = x + (y * Width); Bits[index] = colorRGBA; } public Color GetPixel(int x, int y) { int index = x + (y * Width); int col = Bits[index]; Color result = Color.FromArgb(col); return result; } public void Dispose() { if (Disposed) return; Disposed = true; Bitmap.Dispose(); BitsHandle.Free(); } } } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Chris Siebert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Program.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; namespace TriCNES { internal static class Program { /// /// The main entry point for the application. /// [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new TriCNESGUI()); } } } ================================================ FILE: Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("TriCNES")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TriCNES")] [assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b53c8a3b-8f4f-4837-99a0-47d7d9fe757f")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace TriCNES.Properties { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if ((resourceMan == null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TriCNES.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } } } ================================================ FILE: Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ================================================ FILE: Properties/Settings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace TriCNES.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { return defaultInstance; } } } } ================================================ FILE: Properties/Settings.settings ================================================  ================================================ FILE: README.md ================================================ # TriCNES TriCNES, or "Coin's Contrabulous Cartswapulator", is a Nintendo Entertainment System emulator written by Chris "100th_Coin" Siebert with a focus on test-driven accuracy. This emulator was originally made in order to experiment with a theorhetical arbitary code exploit I created titled "Intercycle Cartridge Swapping" where the NES cartridge is replaced every CPU cycle in order to run custom code. Intercycle Cartridge Swapping was later verified to work on real hardware by Youtube user Decrazyo. This NES emulator was built from the ground up starting from a blank .net winforms project # Limitations This emulator does not produce audio. This emulator only accepts inputs in the form of a TAS file. This emulator can only run NTSC cartridges properly. This emulator only supports the following mapper chips: * 0: NROM * 1: MMC1 * 2: UxROM * 3: CNROM * 4: MMC3 (MMC6 support in the dev build) * 7: AOROM * 9: MMC2 (dev build) * 69: Sunsoft FME-7 # Supported TAS file types Due to varying emulator accuracy, this emulator is not guaranteed to sync all TAS files. Despite this, it supports loading inputs from the following formats: * .3c2 (TriCNES) (dev build) * .3c3 (TriCNES TAS Timeline) (dev build) * .bk2 (Bizhawk) * .tasproj (Bizhawk's TAStudio) * .fm2 (FCEUX) * .fm3 (FCEUX's TAS Editor) * .fmv (Famtasia) * .r08 (Replay Device) In addition to those TAS file types, my emulator can also load my very own intercycle-cart-swapping TAS format, .3ct. # The .3ct TAS file format TAS files that were made with the intention of swapping cartridges between cycles are stored in the following format. The first line of the file is an integer, indicating how many cartridges are being used for this TAS. This integer will be called 'n'. The following 'n' lines are local file paths to the ROMs you wish to use. This will set up an array of cartridges. The remaining lines until the end of the file are in the following format: "x y" where an integer 'x' is seperated from an integer 'y' with a space. When the TAS is being played back, before CPU cycle 'x', swap to the cartridge at index 'y' into the cartridge array. Here's an example:
5
Super Mario Bros. [!].nes
Dash Galaxy in the Alien Asylum (U) [!].nes
Kung Fu (JU) [!].nes
Pipe Dream (U) [!].nes
Super Mario Bros. 3 (U) (V1.1) [!].nes
6 0
7 1
8 2
9 3
10 4
In this example, there are 5 cartridges. Before cycle 6, the emulator will swap to `Super Mario Bros. [!].nes`, as that is index 0 into the cartridge array. Before cycle 7, the emulator will swap to index 1, `Dash Galaxy in the Alien Asylum (U) [!].nes`, and so on. # Screenshots ![Screenshot](https://github.com/user-attachments/assets/56a25c5d-5c2f-493f-85bd-90bb192b1322) ![Screenshot2](https://github.com/user-attachments/assets/5e6771fe-0696-4e27-9fdc-b16fd1b407ef) ![Screenshot3](https://github.com/user-attachments/assets/1689f379-7eb8-445e-9632-81c3a3de2301) ================================================ FILE: TriCNES.csproj ================================================  Debug AnyCPU {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F} WinExe TriCNES TriCNES v4.8 512 true true AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU pdbonly true bin\Release\ TRACE prompt 4 icon.ico 8 packages\ppy.SDL2-CS.1.0.82\lib\netstandard2.0\SDL2-CS.dll True Form TASProperties.cs Form TASProperties3ct.cs Form TriCHexEditor.cs Form TriCNESGUI.cs Form TriCNTViewer.cs Form TriCTASTimeline.cs Form TriCTraceLogger.cs TASProperties.cs TASProperties3ct.cs TriCHexEditor.cs TriCNESGUI.cs ResXFileCodeGenerator Resources.Designer.cs Designer True Resources.resx TriCNTViewer.cs TriCTASTimeline.cs TriCTraceLogger.cs SettingsSingleFileGenerator Settings.Designer.cs True Settings.settings True Always ================================================ FILE: TriCNES.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35013.160 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TriCNES", "TriCNES.csproj", "{B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ED419257-E7C5-4EAC-80C7-78320132F3F4}" ProjectSection(SolutionItems) = preProject icon.ico = icon.ico EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0A57AF3A-82C3-46E2-B3C8-8550E13D4B10} EndGlobalSection EndGlobal ================================================ FILE: forms/TASProperties.Designer.cs ================================================ namespace TriCNES { partial class TASProperties { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TASProperties)); this.b_RunTAS = new System.Windows.Forms.Button(); this.tb_FilePath = new System.Windows.Forms.TextBox(); this.l_FilePath = new System.Windows.Forms.Label(); this.rb_LatchFiltering = new System.Windows.Forms.RadioButton(); this.rb_ClockFiltering = new System.Windows.Forms.RadioButton(); this.panel1 = new System.Windows.Forms.Panel(); this.TASPropTooltips = new System.Windows.Forms.ToolTip(this.components); this.cb_ClockAlignment = new System.Windows.Forms.ComboBox(); this.cb_CpuClock = new System.Windows.Forms.ComboBox(); this.b_BrowseFile = new System.Windows.Forms.Button(); this.l_InputCount = new System.Windows.Forms.Label(); this.l_FamtasiaWarning = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.cb_fceuxFrame0 = new System.Windows.Forms.CheckBox(); this.panel1.SuspendLayout(); this.SuspendLayout(); // // b_RunTAS // this.b_RunTAS.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.b_RunTAS.Location = new System.Drawing.Point(24, 255); this.b_RunTAS.Name = "b_RunTAS"; this.b_RunTAS.Size = new System.Drawing.Size(300, 40); this.b_RunTAS.TabIndex = 0; this.b_RunTAS.Text = "Run TAS"; this.b_RunTAS.UseVisualStyleBackColor = true; this.b_RunTAS.Click += new System.EventHandler(this.b_RunTAS_Click); // // tb_FilePath // this.tb_FilePath.Anchor = System.Windows.Forms.AnchorStyles.Top; this.tb_FilePath.Location = new System.Drawing.Point(53, 6); this.tb_FilePath.Name = "tb_FilePath"; this.tb_FilePath.ReadOnly = true; this.tb_FilePath.Size = new System.Drawing.Size(206, 20); this.tb_FilePath.TabIndex = 1; // // l_FilePath // this.l_FilePath.AutoSize = true; this.l_FilePath.Location = new System.Drawing.Point(26, 9); this.l_FilePath.Name = "l_FilePath"; this.l_FilePath.Size = new System.Drawing.Size(23, 13); this.l_FilePath.TabIndex = 2; this.l_FilePath.Text = "File"; // // rb_LatchFiltering // this.rb_LatchFiltering.AutoSize = true; this.rb_LatchFiltering.Location = new System.Drawing.Point(5, 3); this.rb_LatchFiltering.Name = "rb_LatchFiltering"; this.rb_LatchFiltering.Size = new System.Drawing.Size(91, 17); this.rb_LatchFiltering.TabIndex = 3; this.rb_LatchFiltering.TabStop = true; this.rb_LatchFiltering.Text = "Latch Filtering"; this.TASPropTooltips.SetToolTip(this.rb_LatchFiltering, "Latch filtering will provide a single input per frame."); this.rb_LatchFiltering.UseVisualStyleBackColor = true; // // rb_ClockFiltering // this.rb_ClockFiltering.AutoSize = true; this.rb_ClockFiltering.Location = new System.Drawing.Point(5, 26); this.rb_ClockFiltering.Name = "rb_ClockFiltering"; this.rb_ClockFiltering.Size = new System.Drawing.Size(91, 17); this.rb_ClockFiltering.TabIndex = 4; this.rb_ClockFiltering.TabStop = true; this.rb_ClockFiltering.Text = "Clock Filtering"; this.TASPropTooltips.SetToolTip(this.rb_ClockFiltering, "Clock filtering will provide multiple inputs per frame. This is used in \"subframe" + "\" TASes."); this.rb_ClockFiltering.UseVisualStyleBackColor = true; // // panel1 // this.panel1.Controls.Add(this.rb_LatchFiltering); this.panel1.Controls.Add(this.rb_ClockFiltering); this.panel1.Location = new System.Drawing.Point(24, 200); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(100, 49); this.panel1.TabIndex = 6; // // cb_ClockAlignment // this.cb_ClockAlignment.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cb_ClockAlignment.FormattingEnabled = true; this.cb_ClockAlignment.Items.AddRange(new object[] { "Phase 0", "Phase 1", "Phase 2", "Phase 3"}); this.cb_ClockAlignment.Location = new System.Drawing.Point(176, 174); this.cb_ClockAlignment.Name = "cb_ClockAlignment"; this.cb_ClockAlignment.Size = new System.Drawing.Size(65, 21); this.cb_ClockAlignment.TabIndex = 10; this.TASPropTooltips.SetToolTip(this.cb_ClockAlignment, "Some runs may desync depending on alignment. Different emulators use different al" + "ignments. Only change this if you know what you are doing."); // // cb_CpuClock // this.cb_CpuClock.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cb_CpuClock.FormattingEnabled = true; this.cb_CpuClock.Items.AddRange(new object[] { "Phase 0", "Phase 1", "Phase 2", "Phase 3", "Phase 4", "Phase 5", "Phase 6", "Phase 7", "Phase 8", "Phase 9", "Phase 10", "Phase 11"}); this.cb_CpuClock.Location = new System.Drawing.Point(176, 151); this.cb_CpuClock.Name = "cb_CpuClock"; this.cb_CpuClock.Size = new System.Drawing.Size(65, 21); this.cb_CpuClock.TabIndex = 13; this.TASPropTooltips.SetToolTip(this.cb_CpuClock, "Some runs may desync depending on alignment. Different emulators use different al" + "ignments. Only change this if you know what you are doing."); // // b_BrowseFile // this.b_BrowseFile.Location = new System.Drawing.Point(265, 6); this.b_BrowseFile.Name = "b_BrowseFile"; this.b_BrowseFile.Size = new System.Drawing.Size(59, 21); this.b_BrowseFile.TabIndex = 7; this.b_BrowseFile.Text = "Browse..."; this.b_BrowseFile.UseVisualStyleBackColor = true; // // l_InputCount // this.l_InputCount.AutoSize = true; this.l_InputCount.Location = new System.Drawing.Point(26, 29); this.l_InputCount.Name = "l_InputCount"; this.l_InputCount.Size = new System.Drawing.Size(44, 13); this.l_InputCount.TabIndex = 8; this.l_InputCount.Text = "0 inputs"; // // l_FamtasiaWarning // this.l_FamtasiaWarning.AutoSize = true; this.l_FamtasiaWarning.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.l_FamtasiaWarning.Location = new System.Drawing.Point(12, 73); this.l_FamtasiaWarning.Name = "l_FamtasiaWarning"; this.l_FamtasiaWarning.Size = new System.Drawing.Size(325, 26); this.l_FamtasiaWarning.TabIndex = 9; this.l_FamtasiaWarning.Text = "Warning!\r\nFamtasia is an old emulator, and the TAS is not guaranteed to sync!"; this.l_FamtasiaWarning.TextAlign = System.Drawing.ContentAlignment.TopCenter; // // label3 // this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(21, 177); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(149, 13); this.label3.TabIndex = 11; this.label3.Text = "PPU / Master clock alignment"; // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(21, 154); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(149, 13); this.label1.TabIndex = 12; this.label1.Text = "CPU / Master clock alignment"; // // cb_fceuxFrame0 // this.cb_fceuxFrame0.AutoSize = true; this.cb_fceuxFrame0.Checked = true; this.cb_fceuxFrame0.CheckState = System.Windows.Forms.CheckState.Checked; this.cb_fceuxFrame0.Location = new System.Drawing.Point(24, 124); this.cb_fceuxFrame0.Name = "cb_fceuxFrame0"; this.cb_fceuxFrame0.Size = new System.Drawing.Size(175, 17); this.cb_fceuxFrame0.TabIndex = 14; this.cb_fceuxFrame0.Text = "Use FCEUX\'s Frame 0 behavior"; this.TASPropTooltips.SetToolTip(this.cb_fceuxFrame0, "FCEUX inaccurately emulates the first frame from the beginning of VBlank rather t" + "han the end.\r\nUnchecking this box can potentially lead to a desync, though it wi" + "ll be more accurate."); this.cb_fceuxFrame0.UseVisualStyleBackColor = true; // // TASProperties // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(350, 307); this.Controls.Add(this.cb_fceuxFrame0); this.Controls.Add(this.cb_CpuClock); this.Controls.Add(this.label1); this.Controls.Add(this.label3); this.Controls.Add(this.cb_ClockAlignment); this.Controls.Add(this.l_FamtasiaWarning); this.Controls.Add(this.l_InputCount); this.Controls.Add(this.b_BrowseFile); this.Controls.Add(this.panel1); this.Controls.Add(this.l_FilePath); this.Controls.Add(this.tb_FilePath); this.Controls.Add(this.b_RunTAS); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "TASProperties"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "TAS Properties"; this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Button b_RunTAS; private System.Windows.Forms.TextBox tb_FilePath; private System.Windows.Forms.Label l_FilePath; private System.Windows.Forms.RadioButton rb_LatchFiltering; private System.Windows.Forms.RadioButton rb_ClockFiltering; private System.Windows.Forms.ToolTip TASPropTooltips; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button b_BrowseFile; private System.Windows.Forms.Label l_InputCount; private System.Windows.Forms.Label l_FamtasiaWarning; private System.Windows.Forms.ComboBox cb_ClockAlignment; private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label1; private System.Windows.Forms.ComboBox cb_CpuClock; private System.Windows.Forms.CheckBox cb_fceuxFrame0; } } ================================================ FILE: forms/TASProperties.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO.Compression; namespace TriCNES { public partial class TASProperties : Form { public TASProperties() { InitializeComponent(); } public string TasFilePath; public ushort[] TasInputLog; public bool[] TasResetLog; public TriCNESGUI MainGUI; public bool SubframeInputs() { return rb_ClockFiltering.Checked; } public bool UseFCEUXFrame0Timing() // this only applies to TASes using the .fm2 or .fm3 file format. { return cb_fceuxFrame0.Checked; } public byte GetPPUClockPhase() { return (byte)cb_ClockAlignment.SelectedIndex; } public byte GetCPUClockPhase() { return (byte)cb_CpuClock.SelectedIndex; } public string extension; public void Init() { tb_FilePath.Text = TasFilePath; // determine file type extension = Path.GetExtension(TasFilePath); // create list of inputs from the tas file, and make any settings changes if needed. byte[] ByteArray = File.ReadAllBytes(TasFilePath); List TASInputs = new List(); // Low byte is player 1, High byte is player 2. rb_ClockFiltering.Checked = false; rb_LatchFiltering.Checked = true; l_FamtasiaWarning.Visible = false; cb_ClockAlignment.SelectedIndex = 0; cb_ClockAlignment.Update(); cb_CpuClock.SelectedIndex = 0; cb_CpuClock.Update(); cb_fceuxFrame0.Enabled = false; switch (extension) { case ".bk2": case ".tasproj": { cb_CpuClock.SelectedIndex = 8; cb_CpuClock.Update(); } break; case ".fm2": { cb_fceuxFrame0.Enabled = true; // change the alignment to use FCEUX's cb_CpuClock.SelectedIndex = 0; cb_CpuClock.Update(); } break; case ".fm3": { } break; case ".fmv": { l_FamtasiaWarning.Visible = true; } break; case ".r08": { } break; case ".3c2": { } break; case ".3c3": { } break; // TODO: ask if the .tasd file format is a thing yet } List Resets = new List(); TASInputs = MainGUI.ParseTasFile(TasFilePath, out Resets); // okay cool, now we have the entire input log. TasInputLog = TASInputs.ToArray(); TasResetLog = Resets.ToArray(); l_InputCount.Text = TasInputLog.Length + " Inputs"; } private void b_RunTAS_Click(object sender, EventArgs e) { MainGUI.StartTAS(); } } } ================================================ FILE: forms/TASProperties.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17, 17 AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TASProperties3ct.Designer.cs ================================================ namespace TriCNES { partial class TASProperties3ct { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TASProperties3ct)); this.cb_CpuClock = new System.Windows.Forms.ComboBox(); this.label1 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.cb_ClockAlignment = new System.Windows.Forms.ComboBox(); this.l_InputCount = new System.Windows.Forms.Label(); this.b_BrowseFile = new System.Windows.Forms.Button(); this.l_FilePath = new System.Windows.Forms.Label(); this.tb_FilePath = new System.Windows.Forms.TextBox(); this.b_RunTAS = new System.Windows.Forms.Button(); this.b_LoadCartridges = new System.Windows.Forms.Button(); this.panel1 = new System.Windows.Forms.Panel(); this.rb_FromPOW = new System.Windows.Forms.RadioButton(); this.rb_FromRES = new System.Windows.Forms.RadioButton(); this.panel1.SuspendLayout(); this.SuspendLayout(); // // cb_CpuClock // this.cb_CpuClock.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cb_CpuClock.FormattingEnabled = true; this.cb_CpuClock.Items.AddRange(new object[] { "Phase 0", "Phase 1", "Phase 2", "Phase 3", "Phase 4", "Phase 5", "Phase 6", "Phase 7", "Phase 8", "Phase 9", "Phase 10", "Phase 11"}); this.cb_CpuClock.Location = new System.Drawing.Point(177, 56); this.cb_CpuClock.Name = "cb_CpuClock"; this.cb_CpuClock.Size = new System.Drawing.Size(65, 21); this.cb_CpuClock.TabIndex = 24; // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(22, 59); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(149, 13); this.label1.TabIndex = 23; this.label1.Text = "CPU / Master clock alignment"; // // label3 // this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(22, 82); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(149, 13); this.label3.TabIndex = 22; this.label3.Text = "PPU / Master clock alignment"; // // cb_ClockAlignment // this.cb_ClockAlignment.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cb_ClockAlignment.FormattingEnabled = true; this.cb_ClockAlignment.Items.AddRange(new object[] { "Phase 0", "Phase 1", "Phase 2", "Phase 3"}); this.cb_ClockAlignment.Location = new System.Drawing.Point(177, 79); this.cb_ClockAlignment.Name = "cb_ClockAlignment"; this.cb_ClockAlignment.Size = new System.Drawing.Size(65, 21); this.cb_ClockAlignment.TabIndex = 21; // // l_InputCount // this.l_InputCount.AutoSize = true; this.l_InputCount.Location = new System.Drawing.Point(27, 32); this.l_InputCount.Name = "l_InputCount"; this.l_InputCount.Size = new System.Drawing.Size(44, 13); this.l_InputCount.TabIndex = 19; this.l_InputCount.Text = "0 inputs"; // // b_BrowseFile // this.b_BrowseFile.Location = new System.Drawing.Point(266, 9); this.b_BrowseFile.Name = "b_BrowseFile"; this.b_BrowseFile.Size = new System.Drawing.Size(59, 21); this.b_BrowseFile.TabIndex = 18; this.b_BrowseFile.Text = "Browse..."; this.b_BrowseFile.UseVisualStyleBackColor = true; // // l_FilePath // this.l_FilePath.AutoSize = true; this.l_FilePath.Location = new System.Drawing.Point(27, 12); this.l_FilePath.Name = "l_FilePath"; this.l_FilePath.Size = new System.Drawing.Size(23, 13); this.l_FilePath.TabIndex = 16; this.l_FilePath.Text = "File"; // // tb_FilePath // this.tb_FilePath.Anchor = System.Windows.Forms.AnchorStyles.Top; this.tb_FilePath.Location = new System.Drawing.Point(54, 9); this.tb_FilePath.Name = "tb_FilePath"; this.tb_FilePath.ReadOnly = true; this.tb_FilePath.Size = new System.Drawing.Size(206, 20); this.tb_FilePath.TabIndex = 15; // // b_RunTAS // this.b_RunTAS.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.b_RunTAS.Enabled = false; this.b_RunTAS.Location = new System.Drawing.Point(25, 212); this.b_RunTAS.Name = "b_RunTAS"; this.b_RunTAS.Size = new System.Drawing.Size(300, 40); this.b_RunTAS.TabIndex = 14; this.b_RunTAS.Text = "Run TAS"; this.b_RunTAS.UseVisualStyleBackColor = true; this.b_RunTAS.Click += new System.EventHandler(this.b_RunTAS_Click); // // b_LoadCartridges // this.b_LoadCartridges.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.b_LoadCartridges.Location = new System.Drawing.Point(25, 154); this.b_LoadCartridges.Name = "b_LoadCartridges"; this.b_LoadCartridges.Size = new System.Drawing.Size(300, 40); this.b_LoadCartridges.TabIndex = 25; this.b_LoadCartridges.Text = "Load Cartridges"; this.b_LoadCartridges.UseVisualStyleBackColor = true; this.b_LoadCartridges.Click += new System.EventHandler(this.b_LoadCartridges_Click); // // panel1 // this.panel1.Controls.Add(this.rb_FromPOW); this.panel1.Controls.Add(this.rb_FromRES); this.panel1.Location = new System.Drawing.Point(25, 98); this.panel1.Name = "panel1"; this.panel1.Size = new System.Drawing.Size(100, 49); this.panel1.TabIndex = 26; // // rb_FromPOW // this.rb_FromPOW.AutoSize = true; this.rb_FromPOW.Location = new System.Drawing.Point(5, 3); this.rb_FromPOW.Name = "rb_FromPOW"; this.rb_FromPOW.Size = new System.Drawing.Size(92, 17); this.rb_FromPOW.TabIndex = 3; this.rb_FromPOW.TabStop = true; this.rb_FromPOW.Text = "From POWER"; this.rb_FromPOW.UseVisualStyleBackColor = true; // // rb_FromRES // this.rb_FromRES.AutoSize = true; this.rb_FromRES.Location = new System.Drawing.Point(5, 26); this.rb_FromRES.Name = "rb_FromRES"; this.rb_FromRES.Size = new System.Drawing.Size(87, 17); this.rb_FromRES.TabIndex = 4; this.rb_FromRES.TabStop = true; this.rb_FromRES.Text = "From RESET"; this.rb_FromRES.UseVisualStyleBackColor = true; // // TASProperties3ct // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(350, 264); this.Controls.Add(this.panel1); this.Controls.Add(this.b_LoadCartridges); this.Controls.Add(this.cb_CpuClock); this.Controls.Add(this.label1); this.Controls.Add(this.label3); this.Controls.Add(this.cb_ClockAlignment); this.Controls.Add(this.l_InputCount); this.Controls.Add(this.b_BrowseFile); this.Controls.Add(this.l_FilePath); this.Controls.Add(this.tb_FilePath); this.Controls.Add(this.b_RunTAS); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "TASProperties3ct"; this.Text = "3CT TAS Properties"; this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.ComboBox cb_CpuClock; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label3; private System.Windows.Forms.ComboBox cb_ClockAlignment; private System.Windows.Forms.Label l_InputCount; private System.Windows.Forms.Button b_BrowseFile; private System.Windows.Forms.Label l_FilePath; private System.Windows.Forms.TextBox tb_FilePath; private System.Windows.Forms.Button b_RunTAS; private System.Windows.Forms.Button b_LoadCartridges; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.RadioButton rb_FromPOW; private System.Windows.Forms.RadioButton rb_FromRES; } } ================================================ FILE: forms/TASProperties3ct.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using TriCNES.mappers; namespace TriCNES { public partial class TASProperties3ct : Form { public TASProperties3ct() { InitializeComponent(); } public string TasFilePath; public ushort[] TasInputLog; public TriCNESGUI MainGUI; public byte GetPPUClockPhase() { return (byte)cb_ClockAlignment.SelectedIndex; } public byte GetCPUClockPhase() { return (byte)cb_CpuClock.SelectedIndex; } public bool FromRESET() { return rb_FromRES.Checked; } public Cartridge[] CartridgeArray; public void Init() { tb_FilePath.Text = TasFilePath; cb_ClockAlignment.SelectedIndex = 0; cb_ClockAlignment.Update(); cb_CpuClock.SelectedIndex = 0; cb_CpuClock.Update(); rb_FromPOW.Checked = true; rb_FromPOW.Update(); } Cartridge BackupCart; private void b_RunTAS_Click(object sender, EventArgs e) { if (rb_FromPOW.Checked) { int i = 0; while (i < CartridgeArray.Length) { CartridgeArray[i].PRGRAM = new byte[0x2000]; CartridgeArray[i].CHRRAM = new byte[0x2000]; Mapper MapperChip; // clear all mapper stuff. switch (CartridgeArray[i].MemoryMapper) { default: case 0: MapperChip = new Mapper_NROM(); break; case 1: MapperChip = new Mapper_MMC1(); break; case 2: MapperChip = new Mapper_UxROM(); break; case 3: MapperChip = new Mapper_CNROM(); break; case 4: MapperChip = new Mapper_MMC3(); break; case 7: MapperChip = new Mapper_AOROM(); break; case 9: MapperChip = new Mapper_MMC2(); break; case 69: MapperChip = new Mapper_FME7(); break; } MapperChip.Cart = CartridgeArray[i]; CartridgeArray[i].MapperChip = MapperChip; i++; } } MainGUI.Start3CTTAS(); } public List CyclesToSwapOn; public List CartsToSwapIn; private void b_LoadCartridges_Click(object sender, EventArgs e) { bool error = false; // check if rom folder is empty string Dir = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\")) { Dir += @"roms\"; if(Directory.GetFiles(Dir).Length == 0) { MessageBox.Show("Loading a .3ct TAS requires your roms to be located in the TriCNES roms folder."); return; } } // rom folder isn't empty! StringReader SR = new StringReader(File.ReadAllText(tb_FilePath.Text)); string l = SR.ReadLine(); int count = int.Parse(l); CartridgeArray = new Cartridge[count]; int i = 0; while(i < count) { l = SR.ReadLine(); if(File.Exists(Dir+l)) { if(i ==0) { BackupCart = new Cartridge(Dir + l); } if (MainGUI.EMU != null && MainGUI.EMU.Cart.Name == (Dir + l)) { CartridgeArray[i] = MainGUI.EMU.Cart; // If running a TAS from RESET, we want to use the currently loaded cartridge } else { CartridgeArray[i] = new Cartridge(Dir + l); } } else { MessageBox.Show("TriCNES roms folder is missing a required ROM for this TAS!\n\nMissing ROM: \"" + l + "\""); return; } i++; } // if all carts are now loaded. // let's also prepare the cycles to swap on, and the carts to swap in CyclesToSwapOn = new List(); CartsToSwapIn = new List(); l = SR.ReadLine(); while (l != null) { // the format here is: //x y //x and y could be any length, but there's a space between them. string s = l.Substring(0, l.IndexOf(" ")); CyclesToSwapOn.Add(int.Parse(s)); s = l.Remove(0,s.Length+1); CartsToSwapIn.Add(int.Parse(s)); l = SR.ReadLine(); } b_RunTAS.Enabled = true; } } } ================================================ FILE: forms/TASProperties3ct.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TriCHexEditor.Designer.cs ================================================ namespace TriCNES { partial class TriCHexEditor { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCHexEditor)); this.pb_hexView = new System.Windows.Forms.PictureBox(); this.vScrollBar1 = new System.Windows.Forms.VScrollBar(); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.scopeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.rAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.cPUAddressSpaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.vRAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.pPUAddressSpaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.oAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.paletteRAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyToClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)(this.pb_hexView)).BeginInit(); this.menuStrip1.SuspendLayout(); this.SuspendLayout(); // // pb_hexView // this.pb_hexView.Location = new System.Drawing.Point(12, 37); this.pb_hexView.Name = "pb_hexView"; this.pb_hexView.Size = new System.Drawing.Size(312, 512); this.pb_hexView.TabIndex = 0; this.pb_hexView.TabStop = false; // // vScrollBar1 // this.vScrollBar1.LargeChange = 32; this.vScrollBar1.Location = new System.Drawing.Point(327, 57); this.vScrollBar1.Maximum = 128; this.vScrollBar1.Name = "vScrollBar1"; this.vScrollBar1.Size = new System.Drawing.Size(17, 492); this.vScrollBar1.TabIndex = 1; // // menuStrip1 // this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem, this.settingsToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Size = new System.Drawing.Size(356, 24); this.menuStrip1.TabIndex = 2; this.menuStrip1.Text = "menuStrip1"; // // settingsToolStripMenuItem // this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.scopeToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); this.settingsToolStripMenuItem.Text = "Settings"; // // scopeToolStripMenuItem // this.scopeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.rAMToolStripMenuItem, this.cPUAddressSpaceToolStripMenuItem, this.vRAMToolStripMenuItem, this.pPUAddressSpaceToolStripMenuItem, this.oAMToolStripMenuItem, this.paletteRAMToolStripMenuItem}); this.scopeToolStripMenuItem.Name = "scopeToolStripMenuItem"; this.scopeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.scopeToolStripMenuItem.Text = "Scope"; // // rAMToolStripMenuItem // this.rAMToolStripMenuItem.Checked = true; this.rAMToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.rAMToolStripMenuItem.Name = "rAMToolStripMenuItem"; this.rAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.rAMToolStripMenuItem.Text = "RAM"; this.rAMToolStripMenuItem.Click += new System.EventHandler(this.rAMToolStripMenuItem_Click); // // cPUAddressSpaceToolStripMenuItem // this.cPUAddressSpaceToolStripMenuItem.Name = "cPUAddressSpaceToolStripMenuItem"; this.cPUAddressSpaceToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.cPUAddressSpaceToolStripMenuItem.Text = "CPU Address Space"; this.cPUAddressSpaceToolStripMenuItem.Click += new System.EventHandler(this.cPUAddressSpaceToolStripMenuItem_Click); // // vRAMToolStripMenuItem // this.vRAMToolStripMenuItem.Name = "vRAMToolStripMenuItem"; this.vRAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.vRAMToolStripMenuItem.Text = "VRAM"; this.vRAMToolStripMenuItem.Click += new System.EventHandler(this.vRAMToolStripMenuItem_Click); // // pPUAddressSpaceToolStripMenuItem // this.pPUAddressSpaceToolStripMenuItem.Name = "pPUAddressSpaceToolStripMenuItem"; this.pPUAddressSpaceToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.pPUAddressSpaceToolStripMenuItem.Text = "PPU Address Space"; this.pPUAddressSpaceToolStripMenuItem.Click += new System.EventHandler(this.pPUAddressSpaceToolStripMenuItem_Click); // // oAMToolStripMenuItem // this.oAMToolStripMenuItem.Name = "oAMToolStripMenuItem"; this.oAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.oAMToolStripMenuItem.Text = "OAM"; this.oAMToolStripMenuItem.Click += new System.EventHandler(this.oAMToolStripMenuItem_Click); // // paletteRAMToolStripMenuItem // this.paletteRAMToolStripMenuItem.Name = "paletteRAMToolStripMenuItem"; this.paletteRAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22); this.paletteRAMToolStripMenuItem.Text = "Palette RAM"; this.paletteRAMToolStripMenuItem.Click += new System.EventHandler(this.paletteRAMToolStripMenuItem_Click); // // fileToolStripMenuItem // this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.copyToClipboardToolStripMenuItem}); this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.fileToolStripMenuItem.Text = "File"; // // copyToClipboardToolStripMenuItem // this.copyToClipboardToolStripMenuItem.Name = "copyToClipboardToolStripMenuItem"; this.copyToClipboardToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.copyToClipboardToolStripMenuItem.Text = "Copy to Clipboard"; this.copyToClipboardToolStripMenuItem.Click += new System.EventHandler(this.copyToClipboardToolStripMenuItem_Click); // // TriCHexEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(356, 561); this.Controls.Add(this.vScrollBar1); this.Controls.Add(this.pb_hexView); this.Controls.Add(this.menuStrip1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip1; this.MinimumSize = new System.Drawing.Size(16, 135); this.Name = "TriCHexEditor"; this.Text = "Hex Editor"; ((System.ComponentModel.ISupportInitialize)(this.pb_hexView)).EndInit(); this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.PictureBox pb_hexView; private System.Windows.Forms.VScrollBar vScrollBar1; private System.Windows.Forms.MenuStrip menuStrip1; private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem scopeToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem rAMToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem cPUAddressSpaceToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem vRAMToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem pPUAddressSpaceToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem oAMToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem paletteRAMToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem copyToClipboardToolStripMenuItem; } } ================================================ FILE: forms/TriCHexEditor.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using static System.Windows.Forms.VisualStyles.VisualStyleElement; namespace TriCNES { public partial class TriCHexEditor : Form { public TriCHexEditor() { InitializeComponent(); hexBitmap = new Bitmap(312,512); G = Graphics.FromImage(hexBitmap); Font_Consolas = new Font("Consolas", 8); Scope = "RAM"; Resize += TriCHexEditor_Resize; vScrollBar1.ValueChanged += Scrollbar_ValueChanged; } public TriCNESGUI MainGUI; public Graphics G; public Bitmap hexBitmap; public Font Font_Consolas; public int Scroll; public string Scope; ScopeType scopeType = ScopeType.RAM; public int MaxRows = 32; private void TriCHexEditor_Resize(object sender, EventArgs e) { MaxRows = (Size.Height - 115) / 15; hexBitmap = new Bitmap(312, Size.Height - 88); pb_hexView.Size = new Size(312, Size.Height - 88); vScrollBar1.Size = new Size(vScrollBar1.Width,Size.Height-108); vScrollBar1.LargeChange = MaxRows; G = Graphics.FromImage(hexBitmap); RefreshEntireHexView(); } private void Scrollbar_ValueChanged(object sender, EventArgs e) { Scroll = vScrollBar1.Value; RefreshEntireHexView(); } enum ScopeType { RAM, CPU_Address_Space, VRAM, PPU_Address_Space, OAM, Palette_RAM }; public void Update() { MethodInvoker upd = delegate { RefreshEntireHexView(); }; try { this.Invoke(upd); } catch(Exception e) { } } public void RefreshEntireHexView() { if(MainGUI.EMU == null) { return; } vScrollBar1.Enabled = MaxRows < vScrollBar1.Maximum; vScrollBar1.Update(); G.FillRectangle(Brushes.WhiteSmoke,new Rectangle(0,0, 312, Size.Height - 88)); G.DrawString(Scope + ":", Font_Consolas, Brushes.Black, new Point(0, 0)); for (int x = 0; x < 0x10; x++) { G.DrawString(" " + x.ToString("X"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 16)); } switch (scopeType) { case ScopeType.RAM: { int i = Scroll*0x10; int y = 0; while(i < 0x800 && y < MaxRows) { // print $xy0: G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32+y*15)); for(int x=0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.RAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x*15, 32 + y * 15)); i++; } y++; } } break; case ScopeType.CPU_Address_Space: { int i = Scroll * 0x10; int y = 0; while (i < 0x10000 && y < MaxRows) { // print $xyz0: G.DrawString("$" + (i).ToString("X4") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15)); for (int x = 0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.Observe((ushort)i).ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15)); i++; } y++; } } break; case ScopeType.VRAM: { int i = Scroll * 0x10; int y = 0; while (i < 0x800 && y < MaxRows) { // print $xy0: G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15)); for (int x = 0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.VRAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15)); i++; } y++; } } break; case ScopeType.PPU_Address_Space: { int i = Scroll * 0x10; int y = 0; while (i < 0x4000 && y < MaxRows) { // print $xyz0: G.DrawString("$" + (i).ToString("X4") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15)); for (int x = 0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.ObservePPU((ushort)i).ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15)); i++; } y++; } } break; case ScopeType.OAM: { int i = Scroll * 0x10; int y = 0; while (i < 0x100 && y < MaxRows) { // print $xy0: G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15)); for (int x = 0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.OAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15)); i++; } y++; } } break; case ScopeType.Palette_RAM: { int i = Scroll * 0x10; int y = 0; while (i < 0x20 && y < MaxRows) { // print $xy0: G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15)); for (int x = 0; x < 0x10; x++) { G.DrawString(MainGUI.EMU.PaletteRAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15)); i++; } y++; } } break; } pb_hexView.Image = hexBitmap; pb_hexView.Update(); } void ChangeScope(ScopeType st) { Scroll = 0; vScrollBar1.Value = 0; vScrollBar1.Update(); rAMToolStripMenuItem.Checked = st == ScopeType.RAM; cPUAddressSpaceToolStripMenuItem.Checked = st == ScopeType.CPU_Address_Space; vRAMToolStripMenuItem.Checked = st == ScopeType.VRAM; pPUAddressSpaceToolStripMenuItem.Checked = st == ScopeType.PPU_Address_Space; oAMToolStripMenuItem.Checked = st == ScopeType.OAM; paletteRAMToolStripMenuItem.Checked = st == ScopeType.Palette_RAM; scopeType = st; switch(st) { case ScopeType.RAM: vScrollBar1.Maximum = 0x80; Scope = "RAM"; break; case ScopeType.CPU_Address_Space: vScrollBar1.Maximum = 0x1000; Scope = "CPU Address Space"; break; case ScopeType.VRAM: vScrollBar1.Maximum = 0x80; Scope = "VRAM"; break; case ScopeType.PPU_Address_Space: vScrollBar1.Maximum = 0x400; Scope = "PPU Address Space"; break; case ScopeType.OAM: vScrollBar1.Maximum = 0x10; Scope = "OAM"; break; case ScopeType.Palette_RAM: vScrollBar1.Maximum = 0x2; Scope = "Palette RAM"; break; } RefreshEntireHexView(); } private void rAMToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.RAM); } private void cPUAddressSpaceToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.CPU_Address_Space); } private void vRAMToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.VRAM); } private void pPUAddressSpaceToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.PPU_Address_Space); } private void oAMToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.OAM); } private void paletteRAMToolStripMenuItem_Click(object sender, EventArgs e) { ChangeScope(ScopeType.Palette_RAM); } private void copyToClipboardToolStripMenuItem_Click(object sender, EventArgs e) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < vScrollBar1.Maximum*0x10;i++) { switch (scopeType) { case ScopeType.RAM: sb.Append(MainGUI.EMU.RAM[i].ToString("X2") + " "); break; case ScopeType.CPU_Address_Space: sb.Append(MainGUI.EMU.Observe((ushort)i).ToString("X2") + " "); break; case ScopeType.VRAM: sb.Append(MainGUI.EMU.VRAM[i].ToString("X2") + " "); break; case ScopeType.PPU_Address_Space: sb.Append(MainGUI.EMU.ObservePPU((ushort)i).ToString("X2") + " "); break; case ScopeType.OAM: sb.Append(MainGUI.EMU.OAM[i].ToString("X2") + " "); break; case ScopeType.Palette_RAM: sb.Append(MainGUI.EMU.PaletteRAM[i].ToString("X2") + " "); break; } } Clipboard.SetText(sb.ToString()); } } } ================================================ FILE: forms/TriCHexEditor.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17, 17 AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TriCNESGUI.Designer.cs ================================================ using System.Threading; using System.Windows.Forms; namespace TriCNES { partial class TriCNESGUI { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCNESGUI)); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.consoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.loadROMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.resetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.powerCycleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.screenshotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.saveStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.loadStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.tASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.loadTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.load3ctToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.pPUClockToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.phase0ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.phase1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.phase2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.phase3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.decodeNTSCSignalsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.trueToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.showRawSignalsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.falseToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.viewBoarderToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolstrip_ViewBorders_True = new System.Windows.Forms.ToolStripMenuItem(); this.toolstrip_ViewBorders_False = new System.Windows.Forms.ToolStripMenuItem(); this.viewScaleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem3 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem5 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem6 = new System.Windows.Forms.ToolStripMenuItem(); this.xToolStripMenuItem7 = new System.Windows.Forms.ToolStripMenuItem(); this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.traceLoggerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.nametableViewerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.tASTimelineToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.pb_Screen = new TriCNES.PictureBoxWithInterpolationMode(); this.hexEditorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pb_Screen)).BeginInit(); this.SuspendLayout(); // // menuStrip1 // this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.consoleToolStripMenuItem, this.tASToolStripMenuItem, this.settingsToolStripMenuItem, this.toolsToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Size = new System.Drawing.Size(256, 24); this.menuStrip1.TabIndex = 0; this.menuStrip1.Text = "menuStrip1"; // // consoleToolStripMenuItem // this.consoleToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.loadROMToolStripMenuItem, this.resetToolStripMenuItem, this.powerCycleToolStripMenuItem, this.screenshotToolStripMenuItem, this.saveStateToolStripMenuItem, this.loadStateToolStripMenuItem}); this.consoleToolStripMenuItem.Name = "consoleToolStripMenuItem"; this.consoleToolStripMenuItem.Size = new System.Drawing.Size(62, 20); this.consoleToolStripMenuItem.Text = "Console"; // // loadROMToolStripMenuItem // this.loadROMToolStripMenuItem.Name = "loadROMToolStripMenuItem"; this.loadROMToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.loadROMToolStripMenuItem.Text = "Load ROM"; this.loadROMToolStripMenuItem.Click += new System.EventHandler(this.loadROMToolStripMenuItem_Click); // // resetToolStripMenuItem // this.resetToolStripMenuItem.Name = "resetToolStripMenuItem"; this.resetToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.resetToolStripMenuItem.Text = "Reset"; this.resetToolStripMenuItem.Click += new System.EventHandler(this.resetToolStripMenuItem_Click); // // powerCycleToolStripMenuItem // this.powerCycleToolStripMenuItem.Name = "powerCycleToolStripMenuItem"; this.powerCycleToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.powerCycleToolStripMenuItem.Text = "Power Cycle"; this.powerCycleToolStripMenuItem.Click += new System.EventHandler(this.powerCycleToolStripMenuItem_Click); // // screenshotToolStripMenuItem // this.screenshotToolStripMenuItem.Name = "screenshotToolStripMenuItem"; this.screenshotToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.screenshotToolStripMenuItem.Text = "Screenshot"; this.screenshotToolStripMenuItem.Click += new System.EventHandler(this.screenshotToolStripMenuItem_Click); // // saveStateToolStripMenuItem // this.saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem"; this.saveStateToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.saveStateToolStripMenuItem.Text = "Save State"; this.saveStateToolStripMenuItem.Click += new System.EventHandler(this.saveStateToolStripMenuItem_Click); // // loadStateToolStripMenuItem // this.loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem"; this.loadStateToolStripMenuItem.Size = new System.Drawing.Size(139, 22); this.loadStateToolStripMenuItem.Text = "Load State"; this.loadStateToolStripMenuItem.Click += new System.EventHandler(this.loadStateToolStripMenuItem_Click); // // tASToolStripMenuItem // this.tASToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.loadTASToolStripMenuItem, this.load3ctToolStripMenuItem}); this.tASToolStripMenuItem.Name = "tASToolStripMenuItem"; this.tASToolStripMenuItem.Size = new System.Drawing.Size(38, 20); this.tASToolStripMenuItem.Text = "TAS"; // // loadTASToolStripMenuItem // this.loadTASToolStripMenuItem.Name = "loadTASToolStripMenuItem"; this.loadTASToolStripMenuItem.Size = new System.Drawing.Size(144, 22); this.loadTASToolStripMenuItem.Text = "Load TAS"; this.loadTASToolStripMenuItem.Click += new System.EventHandler(this.loadTASToolStripMenuItem_Click); // // load3ctToolStripMenuItem // this.load3ctToolStripMenuItem.Name = "load3ctToolStripMenuItem"; this.load3ctToolStripMenuItem.Size = new System.Drawing.Size(144, 22); this.load3ctToolStripMenuItem.Text = "Load .3ct TAS"; this.load3ctToolStripMenuItem.Click += new System.EventHandler(this.load3ctToolStripMenuItem_Click); // // settingsToolStripMenuItem // this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.pPUClockToolStripMenuItem, this.decodeNTSCSignalsToolStripMenuItem, this.viewBoarderToolStripMenuItem, this.viewScaleToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); this.settingsToolStripMenuItem.Text = "Settings"; // // pPUClockToolStripMenuItem // this.pPUClockToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.phase0ToolStripMenuItem, this.phase1ToolStripMenuItem, this.phase2ToolStripMenuItem, this.phase3ToolStripMenuItem}); this.pPUClockToolStripMenuItem.Name = "pPUClockToolStripMenuItem"; this.pPUClockToolStripMenuItem.Size = new System.Drawing.Size(186, 22); this.pPUClockToolStripMenuItem.Text = "PPU Clock"; // // phase0ToolStripMenuItem // this.phase0ToolStripMenuItem.Checked = true; this.phase0ToolStripMenuItem.CheckOnClick = true; this.phase0ToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.phase0ToolStripMenuItem.Name = "phase0ToolStripMenuItem"; this.phase0ToolStripMenuItem.Size = new System.Drawing.Size(114, 22); this.phase0ToolStripMenuItem.Text = "Phase 0"; this.phase0ToolStripMenuItem.Click += new System.EventHandler(this.phase0ToolStripMenuItem_Click); // // phase1ToolStripMenuItem // this.phase1ToolStripMenuItem.CheckOnClick = true; this.phase1ToolStripMenuItem.Name = "phase1ToolStripMenuItem"; this.phase1ToolStripMenuItem.Size = new System.Drawing.Size(114, 22); this.phase1ToolStripMenuItem.Text = "Phase 1"; this.phase1ToolStripMenuItem.Click += new System.EventHandler(this.phase1ToolStripMenuItem_Click); // // phase2ToolStripMenuItem // this.phase2ToolStripMenuItem.CheckOnClick = true; this.phase2ToolStripMenuItem.Name = "phase2ToolStripMenuItem"; this.phase2ToolStripMenuItem.Size = new System.Drawing.Size(114, 22); this.phase2ToolStripMenuItem.Text = "Phase 2"; this.phase2ToolStripMenuItem.Click += new System.EventHandler(this.phase2ToolStripMenuItem_Click); // // phase3ToolStripMenuItem // this.phase3ToolStripMenuItem.CheckOnClick = true; this.phase3ToolStripMenuItem.Name = "phase3ToolStripMenuItem"; this.phase3ToolStripMenuItem.Size = new System.Drawing.Size(114, 22); this.phase3ToolStripMenuItem.Text = "Phase 3"; this.phase3ToolStripMenuItem.Click += new System.EventHandler(this.phase3ToolStripMenuItem_Click); // // decodeNTSCSignalsToolStripMenuItem // this.decodeNTSCSignalsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.trueToolStripMenuItem, this.showRawSignalsToolStripMenuItem, this.falseToolStripMenuItem}); this.decodeNTSCSignalsToolStripMenuItem.Name = "decodeNTSCSignalsToolStripMenuItem"; this.decodeNTSCSignalsToolStripMenuItem.Size = new System.Drawing.Size(186, 22); this.decodeNTSCSignalsToolStripMenuItem.Text = "Decode NTSC Signals"; // // trueToolStripMenuItem // this.trueToolStripMenuItem.Name = "trueToolStripMenuItem"; this.trueToolStripMenuItem.Size = new System.Drawing.Size(164, 22); this.trueToolStripMenuItem.Text = "True"; this.trueToolStripMenuItem.Click += new System.EventHandler(this.trueToolStripMenuItem_Click); // // showRawSignalsToolStripMenuItem // this.showRawSignalsToolStripMenuItem.Name = "showRawSignalsToolStripMenuItem"; this.showRawSignalsToolStripMenuItem.Size = new System.Drawing.Size(164, 22); this.showRawSignalsToolStripMenuItem.Text = "Show raw signals"; this.showRawSignalsToolStripMenuItem.Click += new System.EventHandler(this.showRawSignalsToolStripMenuItem_Click); // // falseToolStripMenuItem // this.falseToolStripMenuItem.Checked = true; this.falseToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.falseToolStripMenuItem.Name = "falseToolStripMenuItem"; this.falseToolStripMenuItem.Size = new System.Drawing.Size(164, 22); this.falseToolStripMenuItem.Text = "False"; this.falseToolStripMenuItem.Click += new System.EventHandler(this.falseToolStripMenuItem_Click); // // viewBoarderToolStripMenuItem // this.viewBoarderToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolstrip_ViewBorders_True, this.toolstrip_ViewBorders_False}); this.viewBoarderToolStripMenuItem.Name = "viewBoarderToolStripMenuItem"; this.viewBoarderToolStripMenuItem.Size = new System.Drawing.Size(186, 22); this.viewBoarderToolStripMenuItem.Text = "View Border"; // // toolstrip_ViewBorders_True // this.toolstrip_ViewBorders_True.Name = "toolstrip_ViewBorders_True"; this.toolstrip_ViewBorders_True.Size = new System.Drawing.Size(100, 22); this.toolstrip_ViewBorders_True.Text = "True"; this.toolstrip_ViewBorders_True.Click += new System.EventHandler(this.trueToolStripMenuItem1_Click); // // toolstrip_ViewBorders_False // this.toolstrip_ViewBorders_False.Checked = true; this.toolstrip_ViewBorders_False.CheckState = System.Windows.Forms.CheckState.Checked; this.toolstrip_ViewBorders_False.Name = "toolstrip_ViewBorders_False"; this.toolstrip_ViewBorders_False.Size = new System.Drawing.Size(100, 22); this.toolstrip_ViewBorders_False.Text = "False"; this.toolstrip_ViewBorders_False.Click += new System.EventHandler(this.falseToolStripMenuItem1_Click); // // viewScaleToolStripMenuItem // this.viewScaleToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.xToolStripMenuItem, this.xToolStripMenuItem1, this.xToolStripMenuItem2, this.xToolStripMenuItem3, this.xToolStripMenuItem4, this.xToolStripMenuItem5, this.xToolStripMenuItem6, this.xToolStripMenuItem7}); this.viewScaleToolStripMenuItem.Name = "viewScaleToolStripMenuItem"; this.viewScaleToolStripMenuItem.Size = new System.Drawing.Size(186, 22); this.viewScaleToolStripMenuItem.Text = "View Scale"; // // xToolStripMenuItem // this.xToolStripMenuItem.Name = "xToolStripMenuItem"; this.xToolStripMenuItem.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem.Text = "1x"; this.xToolStripMenuItem.Click += new System.EventHandler(this.xToolStripMenuItem_Click); // // xToolStripMenuItem1 // this.xToolStripMenuItem1.Name = "xToolStripMenuItem1"; this.xToolStripMenuItem1.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem1.Text = "2x"; this.xToolStripMenuItem1.Click += new System.EventHandler(this.xToolStripMenuItem1_Click); // // xToolStripMenuItem2 // this.xToolStripMenuItem2.Name = "xToolStripMenuItem2"; this.xToolStripMenuItem2.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem2.Text = "3x"; this.xToolStripMenuItem2.Click += new System.EventHandler(this.xToolStripMenuItem2_Click); // // xToolStripMenuItem3 // this.xToolStripMenuItem3.Name = "xToolStripMenuItem3"; this.xToolStripMenuItem3.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem3.Text = "4x"; this.xToolStripMenuItem3.Click += new System.EventHandler(this.xToolStripMenuItem3_Click); // // xToolStripMenuItem4 // this.xToolStripMenuItem4.Name = "xToolStripMenuItem4"; this.xToolStripMenuItem4.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem4.Text = "5x"; this.xToolStripMenuItem4.Click += new System.EventHandler(this.xToolStripMenuItem4_Click); // // xToolStripMenuItem5 // this.xToolStripMenuItem5.Name = "xToolStripMenuItem5"; this.xToolStripMenuItem5.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem5.Text = "6x"; this.xToolStripMenuItem5.Click += new System.EventHandler(this.xToolStripMenuItem5_Click); // // xToolStripMenuItem6 // this.xToolStripMenuItem6.Name = "xToolStripMenuItem6"; this.xToolStripMenuItem6.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem6.Text = "7x"; this.xToolStripMenuItem6.Click += new System.EventHandler(this.xToolStripMenuItem6_Click); // // xToolStripMenuItem7 // this.xToolStripMenuItem7.Name = "xToolStripMenuItem7"; this.xToolStripMenuItem7.Size = new System.Drawing.Size(86, 22); this.xToolStripMenuItem7.Text = "8x"; this.xToolStripMenuItem7.Click += new System.EventHandler(this.xToolStripMenuItem7_Click); // // toolsToolStripMenuItem // this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.traceLoggerToolStripMenuItem, this.nametableViewerToolStripMenuItem, this.tASTimelineToolStripMenuItem, this.hexEditorToolStripMenuItem}); this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; this.toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20); this.toolsToolStripMenuItem.Text = "Tools"; // // traceLoggerToolStripMenuItem // this.traceLoggerToolStripMenuItem.Name = "traceLoggerToolStripMenuItem"; this.traceLoggerToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.traceLoggerToolStripMenuItem.Text = "TraceLogger"; this.traceLoggerToolStripMenuItem.Click += new System.EventHandler(this.traceLoggerToolStripMenuItem_Click); // // nametableViewerToolStripMenuItem // this.nametableViewerToolStripMenuItem.Name = "nametableViewerToolStripMenuItem"; this.nametableViewerToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.nametableViewerToolStripMenuItem.Text = "Nametable Viewer"; this.nametableViewerToolStripMenuItem.Click += new System.EventHandler(this.nametableViewerToolStripMenuItem_Click); // // tASTimelineToolStripMenuItem // this.tASTimelineToolStripMenuItem.Name = "tASTimelineToolStripMenuItem"; this.tASTimelineToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.tASTimelineToolStripMenuItem.Text = "TAS Timeline"; this.tASTimelineToolStripMenuItem.Click += new System.EventHandler(this.tASTimelineToolStripMenuItem_Click); // // pb_Screen // this.pb_Screen.AllowDrop = true; this.pb_Screen.BackColor = System.Drawing.Color.Black; this.pb_Screen.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; this.pb_Screen.Location = new System.Drawing.Point(0, 27); this.pb_Screen.Name = "pb_Screen"; this.pb_Screen.Size = new System.Drawing.Size(256, 240); this.pb_Screen.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; this.pb_Screen.TabIndex = 1; this.pb_Screen.TabStop = false; // // hexEditorToolStripMenuItem // this.hexEditorToolStripMenuItem.Name = "hexEditorToolStripMenuItem"; this.hexEditorToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.hexEditorToolStripMenuItem.Text = "Hex Editor"; this.hexEditorToolStripMenuItem.Click += new System.EventHandler(this.hexEditorToolStripMenuItem_Click); // // TriCNESGUI // this.AllowDrop = true; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange; this.ClientSize = new System.Drawing.Size(256, 267); this.Controls.Add(this.pb_Screen); this.Controls.Add(this.menuStrip1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.KeyPreview = true; this.MainMenuStrip = this.menuStrip1; this.MaximizeBox = false; this.MaximumSize = new System.Drawing.Size(272, 306); this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(272, 306); this.Name = "TriCNESGUI"; this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "TriCNES GUI"; this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.pb_Screen)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.MenuStrip menuStrip1; private System.Windows.Forms.ToolStripMenuItem consoleToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem tASToolStripMenuItem; private PictureBoxWithInterpolationMode pb_Screen; private System.Windows.Forms.ToolStripMenuItem loadROMToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem loadTASToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem load3ctToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem resetToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem powerCycleToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem screenshotToolStripMenuItem; private ToolStripMenuItem settingsToolStripMenuItem; private ToolStripMenuItem pPUClockToolStripMenuItem; private ToolStripMenuItem phase0ToolStripMenuItem; private ToolStripMenuItem phase1ToolStripMenuItem; private ToolStripMenuItem phase2ToolStripMenuItem; private ToolStripMenuItem phase3ToolStripMenuItem; private ToolStripMenuItem decodeNTSCSignalsToolStripMenuItem; private ToolStripMenuItem trueToolStripMenuItem; private ToolStripMenuItem falseToolStripMenuItem; private ToolStripMenuItem viewScaleToolStripMenuItem; private ToolStripMenuItem xToolStripMenuItem; private ToolStripMenuItem xToolStripMenuItem1; private ToolStripMenuItem xToolStripMenuItem2; private ToolStripMenuItem xToolStripMenuItem3; private ToolStripMenuItem xToolStripMenuItem4; private ToolStripMenuItem xToolStripMenuItem5; private ToolStripMenuItem xToolStripMenuItem6; private ToolStripMenuItem xToolStripMenuItem7; private ToolStripMenuItem toolsToolStripMenuItem; private ToolStripMenuItem traceLoggerToolStripMenuItem; private ToolStripMenuItem viewBoarderToolStripMenuItem; private ToolStripMenuItem toolstrip_ViewBorders_True; private ToolStripMenuItem toolstrip_ViewBorders_False; private ToolStripMenuItem nametableViewerToolStripMenuItem; private ToolStripMenuItem showRawSignalsToolStripMenuItem; private ToolStripMenuItem saveStateToolStripMenuItem; private ToolStripMenuItem loadStateToolStripMenuItem; private ToolStripMenuItem tASTimelineToolStripMenuItem; private ToolStripMenuItem hexEditorToolStripMenuItem; } } ================================================ FILE: forms/TriCNESGUI.cs ================================================ using SDL2; using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; using System.Windows.Forms; using System.Windows.Input; namespace TriCNES { public partial class TriCNESGUI : Form { // This is the the main window for a user to interact with this emulator. // The logic for the emulator is contained entirely in a single C# file, for easy use importing it into other projects. // this form here is intended to be used an an example. // The intended use for this emulator is to run your own code specifically to collect data, but do with it as you please. // Cheers! ~ Chris "100th_Coin" Siebert public TriCNESGUI() { InitializeComponent(); pb_Screen.DragEnter += new DragEventHandler(pb_Screen_DragEnter); pb_Screen.DragDrop += new DragEventHandler(pb_Screen_DragDrop); FormClosing += new FormClosingEventHandler(TriCNESGUI_Closing); SDL.SDL_Init(SDL.SDL_INIT_GAMECONTROLLER); SDL.SDL_GameControllerEventState(SDL.SDL_ENABLE); SDL.SDL_GameControllerUpdate(); int c = SDL.SDL_NumJoysticks(); if (c != 0) { joystickptr = SDL.SDL_JoystickOpen(0); gameControllerPrt = SDL.SDL_GameControllerOpen(0); } } IntPtr joystickptr; IntPtr gameControllerPrt; bool settings_ntsc; bool settings_ntscRaw; bool settings_border; byte settings_alignment; public Emulator EMU; public Thread EmuClock; string filePath; bool FDS; TASProperties TASPropertiesForm; TASProperties3ct TASPropertiesForm3ct; public TriCTraceLogger? TraceLogger; public TriCNTViewer? NametableViewer; public TriCTASTimeline? TasTimeline; public TriCHexEditor? HexExditor; void RunUpkeep() { if (PendingScreenshot) { PendingScreenshot = false; if (EMU.PPU_DecodeSignal) { if (EMU.PPU_ShowScreenBorders) { Clipboard.SetImage(EMU.BorderedNTSCScreen.Bitmap); } else { Clipboard.SetImage(EMU.NTSCScreen.Bitmap); } } else { if (EMU.PPU_ShowScreenBorders) { Clipboard.SetImage(EMU.BorderedScreen.Bitmap); } else { Clipboard.SetImage(EMU.Screen.Bitmap); } } } if (Pending_ShowScreenBorders) { Pending_ShowScreenBorders = false; EMU.PPU_ShowScreenBorders = true; BeginInvoke(new MethodInvoker(delegate () { ResizeWindow(ScreenMult); })); } if (Pending_HideScreenBorders) { Pending_HideScreenBorders = false; EMU.PPU_ShowScreenBorders = false; BeginInvoke(new MethodInvoker(delegate () { ResizeWindow(ScreenMult); })); } if (PendingSaveState) { PendingSaveState = false; Savestate = EMU.SaveState(); } if (PendingLoadState && Savestate != null && Savestate.Count > 0) { PendingLoadState = false; EMU.LoadState(Savestate); } if (TraceLogger != null) { EMU.Logging = TraceLogger.Logging; if (EMU.DebugLog == null) { EMU.DebugLog = new StringBuilder(); } EMU.DebugRange_Low = TraceLogger.RangeLow; EMU.DebugRange_High = TraceLogger.RangeHigh; EMU.OnlyDebugInRange = TraceLogger.OnlyDebugInRange(); EMU.LoggingPPU = TraceLogger.LogPPUCycles(); } else if(EMU.Logging) { EMU.Logging = false; EMU.DebugLog = new StringBuilder(); } if(HexExditor != null) { HexExditor.Update(); } } void RunPostFramePhase() { if (TraceLogger != null) { if (TraceLogger.Logging) { TraceLogger.Update(); if (TraceLogger.ClearEveryFrame()) { EMU.DebugLog = new StringBuilder(); } } } if (NametableViewer != null && !NametableViewer.IsDisposed) { RenderNametable(); NametableViewer.Update(NametableBitmap.Bitmap); } if (pb_Screen.InvokeRequired) { pb_Screen.BeginInvoke(new MethodInvoker( delegate () { if (EMU.PPU_DecodeSignal) { if (EMU.PPU_ShowScreenBorders) { pb_Screen.Image = EMU.BorderedNTSCScreen.Bitmap; } else { pb_Screen.Image = EMU.NTSCScreen.Bitmap; } } else { if (EMU.PPU_ShowScreenBorders) { pb_Screen.Image = EMU.BorderedScreen.Bitmap; } else { pb_Screen.Image = EMU.Screen.Bitmap; } } pb_Screen.Update(); })); } else { if (EMU.PPU_DecodeSignal) { if (EMU.PPU_ShowScreenBorders) { pb_Screen.Image = EMU.BorderedNTSCScreen.Bitmap; } else { pb_Screen.Image = EMU.NTSCScreen.Bitmap; } } else { if (EMU.PPU_ShowScreenBorders) { pb_Screen.Image = EMU.BorderedScreen.Bitmap; } else { pb_Screen.Image = EMU.Screen.Bitmap; } } pb_Screen.Update(); } } bool[] ControllerInputs() { bool[] joystickButtons = new bool[8]; int c = SDL.SDL_NumJoysticks(); if (c != 0) { SDL.SDL_GameControllerUpdate(); joystickButtons[0] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0; joystickButtons[1] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0; joystickButtons[2] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0; joystickButtons[3] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP) != 0; joystickButtons[4] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START) != 0; joystickButtons[5] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK) != 0; joystickButtons[6] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X) != 0; joystickButtons[7] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) != 0; } else { for (int i = 0; i < 8; i++) { joystickButtons[i] = false; } } return joystickButtons; } byte RealtimeInputs() { bool[] joystickButtons = ControllerInputs(); byte controller1 = 0; if (joystickButtons[7] || Keyboard.IsKeyDown(Key.X)) { controller1 |= 0x80; } if (joystickButtons[6] || Keyboard.IsKeyDown(Key.Z)) { controller1 |= 0x40; } if (joystickButtons[5] || Keyboard.IsKeyDown(Key.RightShift)) { controller1 |= 0x20; } if (joystickButtons[4] || Keyboard.IsKeyDown(Key.Enter)) { controller1 |= 0x10; } if (joystickButtons[3] || Keyboard.IsKeyDown(Key.Up)) { controller1 |= 0x08; } if (joystickButtons[2] || Keyboard.IsKeyDown(Key.Down)) { controller1 |= 0x04; } if (joystickButtons[1] || Keyboard.IsKeyDown(Key.Left)) { controller1 |= 0x02; } if (joystickButtons[0] || Keyboard.IsKeyDown(Key.Right)) { controller1 |= 0x01; } return controller1; } CancellationTokenSource cancel; void ClockEmulator(CancellationToken ct) { int frameCount = 0; while (!ct.IsCancellationRequested) { if (Form.ActiveForm != null) { if (Keyboard.IsKeyDown(Key.Q)) { PendingSaveState = true; } if (Keyboard.IsKeyDown(Key.W)) { PendingLoadState = true; } EMU.ControllerPort1 = RealtimeInputs(); } RunUpkeep(); EMU._CoreFrameAdvance(); RunPostFramePhase(); frameCount++; } } DirectBitmap NametableBitmap; public Bitmap RenderNametable() { if (NametableBitmap != null) { NametableBitmap.Dispose(); } NametableBitmap = new DirectBitmap(512, 480); if (EMU.Cart == null) { return NametableBitmap.Bitmap; } int tx = 0; int ty = 0; int x = 0; int y = 0; int px = 0; int py = 0; int PatternTile; int pal = 0; bool ForceBackdropOnIndex0 = NametableViewer.UseBackdrop(); while (ty < 2) { while (tx < 2) { while (y < 30) { while (x < 32) { PatternTile = EMU.ObservePPU((ushort)(0x2000 + 0x400 * tx + 0x800 * ty + x + y * 32)); pal = EMU.ObservePPU((ushort)(0x2000 + 0x400 * (tx + 1) + 0x800 * ty - 0x40 + x / 4 + (y / 4) * 8)); if ((x & 3) >= 2) { pal = pal >> 2; } if ((y & 3) >= 2) { pal = pal >> 4; } pal = pal & 3; while (py < 8) { while (px < 8) { int k = ((EMU.ObservePPU((ushort)(py + PatternTile * 16 + (!EMU.PPU_PatternSelect_Background ? 0 : 0x1000))) >> (7 - px)) & 1) + 2 * ((EMU.ObservePPU((ushort)(py + 8 + PatternTile * 16 + (!EMU.PPU_PatternSelect_Background ? 0 : 0x1000))) >> (7 - px)) & 1); if (k == 0 && ForceBackdropOnIndex0) { k = EMU.ObservePPU(0x3F00); } else { k = EMU.ObservePPU((ushort)(0x3F00 + k + pal * 4)); } int col = unchecked((int)Emulator.NesPalInts[k & 0x3F]); NametableBitmap.SetPixel(tx * 0x100 + x * 8 + px, ty * 0xF0 + y * 8 + py, col); px++; } px = 0; py++; } py = 0; x++; } x = 0; y++; } y = 0; tx++; } tx = 0; ty++; } bool DrawScreenBoundary = NametableViewer.DrawBoundary(); if (DrawScreenBoundary) { // convert the t register into X,Y coordinates /* The v and t registers are 15 bits: yyy NN YYYYY XXXXX ||| || ||||| +++++-- coarse X scroll ||| || +++++-------- coarse Y scroll ||| ++-------------- nametable select +++----------------- fine Y scroll */ int X = ((EMU.PPU_t & 0b11111) << 3) | EMU.PPU_FineXScroll | ((EMU.PPU_t & 0b10000000000) >> 2); int Y = ((EMU.PPU_t & 0b1111100000) >> 2) | ((EMU.PPU_t & 0b111000000000000) >> 12) | ((EMU.PPU_t & 0b100000000000) >> 4); int i = 0; while (i <= 257) { NametableBitmap.SetPixel((X + 511 + i) & 511, (Y + 479) % 480, Color.White); NametableBitmap.SetPixel((X + 511 + i) & 511, (Y + 240) % 480, Color.White); i++; } i = 0; while (i <= 241) { NametableBitmap.SetPixel((X + 511) & 511, (Y + 479 + i) % 480, Color.White); NametableBitmap.SetPixel((X + 256) & 511, (Y + 479 + i) % 480, Color.White); i++; } } if (NametableViewer.OverlayScreen()) { int X = ((EMU.PPU_t & 0b11111) << 3) | EMU.PPU_FineXScroll | ((EMU.PPU_t & 0b10000000000) >> 2); int Y = ((EMU.PPU_t & 0b1111100000) >> 2) | ((EMU.PPU_t & 0b111000000000000) >> 12) | ((EMU.PPU_t & 0b100000000000) >> 4); for (int xx = 0; xx < 256; xx++) { for (int yy = 0; yy < 240; yy++) { NametableBitmap.SetPixel((X + xx) & 511, (Y + yy) % 480, EMU.Screen.GetPixel(xx, yy)); } } } return NametableBitmap.Bitmap; } string fds_bios; // file path to the FDS bios if one is loaded. public bool LoadROM(string FilePath) { if (FDS) { if (fds_bios == null || fds_bios.Length == 0) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\")) { InitDirectory += @"roms\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Title = "Select FDS BIOS", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { fds_bios = ofd.FileName; byte[] FDS_BIOS = File.ReadAllBytes(fds_bios); if (FDS_BIOS.Length != 0x2000) { fds_bios = ""; return false; } } else { return false; } } Cartridge Cart = new Cartridge(filePath, fds_bios); EMU.Cart = Cart; Cart.Emu = EMU; return true; } else { Cartridge Cart = new Cartridge(filePath); EMU.Cart = Cart; Cart.Emu = EMU; return true; } return false; } public void InsertDisk(string filepath) { if(EMU.Cart.FDS != null) { EMU.Cart.FDS.InsertDisk(filepath); } } void ClockEmulator3CT(CancellationToken ct) { Cartridge[] CartArray = TASPropertiesForm3ct.CartridgeArray; int[] CyclesToSwapOn = TASPropertiesForm3ct.CyclesToSwapOn.ToArray(); int[] CartsToSwapIn = TASPropertiesForm3ct.CartsToSwapIn.ToArray(); EMU.Cart = CartArray[0]; int i = 1; // what cycle is being executed next? int j = 0; // what step of the .3ct TAS is this? while (j < CyclesToSwapOn.Length) { if (i == CyclesToSwapOn[j]) // if there's a cart swap on this cycle { EMU.Cart = CartArray[CartsToSwapIn[j]]; // swap the cartridge to the next one in the list j++; } EMU._CoreCycleAdvance(); i++; } // once the .3ct TAS is completed, continue running the emulator with whatever cartridge is loaded last. while (!ct.IsCancellationRequested) { RunUpkeep(); EMU._CoreFrameAdvance(); RunPostFramePhase(); } } private void loadROMToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\")) { InitDirectory += @"roms\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Filter = "NES ROM files (*.nes)|*.nes", Title = "Select file", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } if (EMU != null) { EMU.Dispose(); GC.Collect(); } filePath = ofd.FileName; FDS = Path.GetExtension(ofd.FileName) == ".fds"; EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; EMU.PPUClock = settings_alignment; if (!LoadROM(filePath)) { return; } cancel = new CancellationTokenSource(); EmuClock = new Thread(() => ClockEmulator(cancel.Token)); EmuClock.SetApartmentState(ApartmentState.STA); EmuClock.IsBackground = true; EmuClock.Start(); GC.Collect(); } } private void loadTASToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Filter = "All TAS Files (.3c2, .3c3, .bk2, .tasproj, .fm2, .fm3, .fmv, .r08)|*.3c2;*.3c3;*.bk2;*.tasproj;*.fm2;*.fm3;*.fmv;*.r08" + "|TriCNES TAS File (.3c2, .3c3)|*.3c2;*.3c3" + "|Bizhawk Movie (.bk2)|*.bk2" + "|Bizhawk TAStudio (.tasproj)|*.tasproj" + "|FCEUX Movie (.fm2)|*.fm2" + "|FCEUX TAS Editor (.fm3)|*.fm3" + "|Famtastia Movie (.fmv)|*.fmv" + "|Replay Device (.r08)|*.r08", Title = "Select file", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { if (TASPropertiesForm != null) { TASPropertiesForm.Close(); TASPropertiesForm.Dispose(); } TASPropertiesForm = new TASProperties(); TASPropertiesForm.TasFilePath = ofd.FileName; TASPropertiesForm.MainGUI = this; TASPropertiesForm.Init(); TASPropertiesForm.Show(); TASPropertiesForm.Location = Location; } } public void StartTAS() { if (filePath == "" || filePath == null) { MessageBox.Show("You need to select a ROM before running a TAS."); return; } if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } if (EMU != null) { EMU.Dispose(); GC.Collect(); } EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; if (!LoadROM(filePath)) { return; } EMU.TAS_ReadingTAS = true; EMU.TAS_InputLog = TASPropertiesForm.TasInputLog; EMU.TAS_ResetLog = TASPropertiesForm.TasResetLog; EMU.ClockFiltering = TASPropertiesForm.SubframeInputs(); EMU.PPUClock = TASPropertiesForm.GetPPUClockPhase(); EMU.CPUClock = TASPropertiesForm.GetCPUClockPhase(); EMU.TAS_InputSequenceIndex = 0; switch (TASPropertiesForm.extension) { case ".bk2": case ".tasproj": { int i = 0; while (i < EMU.RAM.Length) //bizhawk RAM pattern { if ((i & 7) > 4) { EMU.RAM[i] = 0xFF; } else { EMU.RAM[i] = 0; } i++; } } break; case ".fm2": case ".fm3": { if (TASPropertiesForm.UseFCEUXFrame0Timing()) { // FCEUX incorrectly starts at the beginning of scanline 240, and cycle 0 is *after* the reset instruction. // However, I think there's some other incorrect timing going on with FCEUX, and in order to sync TASes, I need to start at scanline 239, dot 312 EMU.PPU_Scanline = 239; EMU.PPU_Dot = 312; // but of course, by starting here, the VBlank flag will be incorrectly set early. EMU.SyncFM2 = true; // so this bool prevents that. EMU.TAS_InputSequenceIndex--; // since this runs an extra vblank, this needs to be offset by 1 } else { EMU.TAS_InputSequenceIndex++; EMU.PPU_Dot = 0; } // FCEUX also starts with this RAM pattern int i = 0; while (i < EMU.RAM.Length) //bizhawk RAM pattern { if ((i & 7) > 4) { EMU.RAM[i] = 0xFF; } else { EMU.RAM[i] = 0; } i++; } } break; case ".r08": { // This following comment block can be removed if you want to set up RAM for the Bad Apple TAS's .r08 file. /* string s = "0000000000000C000000000000000000E2000000001D1E000000000001000000984820BEFE68A8A5F7A6F8600000000010400000000000000000000000000000A2A58EFF07A216EA8EFD07020000000020200091318A11319131C8C430D0F14C40000000000000000101030000000000000000000000000000000000000000000000000000F000000000020000A0A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000100000000000000000000000000000000000035000000008E002001008A4820BEFE68AA0C000000004C4000000001A804D9B4B4070004DAB4B4030004DBB4B4030005DCB4B4030004DDB4B4030004DEB4B4030004DFB4B4030004E0B4B4030004E1B4B4030004E2B4B4030004E3B4B4030004E4B4B4030004E5B4B4030004E6B4C886A080F5D000D00B00003F2FC7F8C8FE0024000F5200FB0400A9018D164085C04A8D1640AD16404A26C090F8A5C060A202206B0195C1CA10F8A000206B0191C2C8C4C190F6206B01F0E5206B0185C3206B0185C26CC200FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB003AFB00FB00FB00FB10D2A27DA07DF50400040004D93525D8F70000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8410000F8410000F8250000F8250000F8410000F8410000F8250000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000787A2021047F1918470000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F722CC891000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600A5"; int i = 0; while (i < 0x800) { EMU.RAM[i] = byte.Parse(s.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); i++; } */ break; } } cancel = new CancellationTokenSource(); EmuClock = new Thread(() => ClockEmulator(cancel.Token)); EmuClock.SetApartmentState(ApartmentState.STA); EmuClock.IsBackground = true; EmuClock.Start(); GC.Collect(); } public void Start3CTTAS() { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); EMU.Dispose(); } if (TASPropertiesForm3ct.FromRESET()) { if (EMU == null) { MessageBox.Show("The emulator needs to be powered on before running from RESET."); return; } EMU.Reset(); } else { if (EMU != null) { EMU.Dispose(); GC.Collect(); } EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; EMU.PPUClock = settings_alignment; } foreach(Cartridge c in TASPropertiesForm3ct.CartridgeArray) { c.Emu = EMU; } cancel = new CancellationTokenSource(); EmuClock = new Thread(() => ClockEmulator3CT(cancel.Token)); EmuClock.IsBackground = true; EmuClock.Start(); } private void load3ctToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Filter = "3CT TAS Files (.3ct)|*.3ct", Title = "Select file", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { if (TASPropertiesForm3ct != null) { TASPropertiesForm3ct.Close(); TASPropertiesForm3ct.Dispose(); } TASPropertiesForm3ct = new TASProperties3ct(); TASPropertiesForm3ct.TasFilePath = ofd.FileName; TASPropertiesForm3ct.MainGUI = this; TASPropertiesForm3ct.Init(); TASPropertiesForm3ct.Show(); TASPropertiesForm3ct.Location = Location; } } private void resetToolStripMenuItem_Click(object sender, EventArgs e) { if (EMU != null) { EMU.Reset(); } } private void powerCycleToolStripMenuItem_Click(object sender, EventArgs e) { if (EMU != null) { Emulator Emu2 = new Emulator(); Emu2.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; Emu2.PPU_ShowScreenBorders = settings_border; Emu2.PPUClock = settings_alignment; Emu2.Cart = EMU.Cart; Emu2.Cart.Emu = Emu2; EMU = Emu2; } } bool PendingScreenshot; private void screenshotToolStripMenuItem_Click(object sender, EventArgs e) { PendingScreenshot = true; } private void pb_Screen_DragEnter(object sender, DragEventArgs e) { var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false); if (Path.GetExtension(filenames[0]) == ".nes" || Path.GetExtension(filenames[0]) == ".NES" || Path.GetExtension(filenames[0]) == ".fds" || Path.GetExtension(filenames[0]) == ".FDS") e.Effect = DragDropEffects.All; else e.Effect = DragDropEffects.None; } private void pb_Screen_DragDrop(object sender, DragEventArgs e) { var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false); string filename = filenames[0]; filePath = filename; bool prev_FDS = FDS; FDS = Path.GetExtension(filePath).ToLower() == ".fds"; if (!FDS || !prev_FDS) { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } if (EMU != null) { EMU.Dispose(); GC.Collect(); } } if (FDS && prev_FDS) { InsertDisk(filePath); } else { EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; EMU.PPUClock = settings_alignment; if (!LoadROM(filePath)) { return; } cancel = new CancellationTokenSource(); EmuClock = new Thread(() => ClockEmulator(cancel.Token)); EmuClock.SetApartmentState(ApartmentState.STA); EmuClock.IsBackground = true; EmuClock.Start(); } GC.Collect(); } private void TriCNESGUI_Closing(Object sender, FormClosingEventArgs e) { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } if (TASPropertiesForm != null) { TASPropertiesForm.Dispose(); } if (TASPropertiesForm3ct != null) { TASPropertiesForm3ct.Dispose(); } if (TraceLogger != null) { TraceLogger.Dispose(); } if (NametableViewer != null) { NametableViewer.Dispose(); } if(TasTimeline != null) { TasTimeline.Dispose(); } if (HexExditor != null) { HexExditor.Dispose(); } Application.Exit(); } private void phase0ToolStripMenuItem_Click(object sender, EventArgs e) { phase0ToolStripMenuItem.Checked = true; phase1ToolStripMenuItem.Checked = false; phase2ToolStripMenuItem.Checked = false; phase3ToolStripMenuItem.Checked = false; RebootWithAlignment(0); } private void phase1ToolStripMenuItem_Click(object sender, EventArgs e) { phase0ToolStripMenuItem.Checked = false; phase1ToolStripMenuItem.Checked = true; phase2ToolStripMenuItem.Checked = false; phase3ToolStripMenuItem.Checked = false; RebootWithAlignment(1); } private void phase2ToolStripMenuItem_Click(object sender, EventArgs e) { phase0ToolStripMenuItem.Checked = false; phase1ToolStripMenuItem.Checked = false; phase2ToolStripMenuItem.Checked = true; phase3ToolStripMenuItem.Checked = false; RebootWithAlignment(2); } private void phase3ToolStripMenuItem_Click(object sender, EventArgs e) { phase0ToolStripMenuItem.Checked = false; phase1ToolStripMenuItem.Checked = false; phase2ToolStripMenuItem.Checked = false; phase3ToolStripMenuItem.Checked = true; RebootWithAlignment(3); } private void RebootWithAlignment(byte Alignment) { if (EMU != null) { Emulator Emu2 = new Emulator(); Emu2.Cart = EMU.Cart; Emu2.Cart.Emu = Emu2; EMU = Emu2; EMU.PPUClock = Alignment; EMU.CPUClock = 0; EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; } settings_alignment = Alignment; } private void trueToolStripMenuItem_Click(object sender, EventArgs e) { falseToolStripMenuItem.Checked = false; showRawSignalsToolStripMenuItem.Checked = false; trueToolStripMenuItem.Checked = true; if (EMU != null) { EMU.PPU_DecodeSignal = true; EMU.PPU_ShowRawNTSCSignal = false; } settings_ntsc = true; settings_ntscRaw = false; } private void falseToolStripMenuItem_Click(object sender, EventArgs e) { trueToolStripMenuItem.Checked = false; showRawSignalsToolStripMenuItem.Checked = false; falseToolStripMenuItem.Checked = true; if (EMU != null) { EMU.PPU_DecodeSignal = false; EMU.PPU_ShowRawNTSCSignal = false; } settings_ntsc = false; settings_ntscRaw = false; } private void showRawSignalsToolStripMenuItem_Click(object sender, EventArgs e) { trueToolStripMenuItem.Checked = false; showRawSignalsToolStripMenuItem.Checked = true; falseToolStripMenuItem.Checked = false; if (EMU != null) { EMU.PPU_DecodeSignal = true; EMU.PPU_ShowRawNTSCSignal = true; } settings_ntsc = true; settings_ntscRaw = true; } public void ResizeWindow(int scale) { int w = 256; int h = 240; if (EMU != null) { if (EMU.PPU_ShowScreenBorders) { w = 341; h = 262; } } Size pbs = new Size(); pbs.Width = w * scale; pbs.Height = h * scale; Size ws = new Size(); ws.Width = w * scale + 16; ws.Height = h * scale + 66; MinimumSize = ws; MaximumSize = ws; pb_Screen.Size = pbs; Width = ws.Width; Height = ws.Height; } int ScreenMult = 1; private void xToolStripMenuItem_Click(object sender, EventArgs e) { ScreenMult = 1; ResizeWindow(1); } private void xToolStripMenuItem1_Click(object sender, EventArgs e) { ScreenMult = 2; ResizeWindow(2); } private void xToolStripMenuItem2_Click(object sender, EventArgs e) { ScreenMult = 3; ResizeWindow(3); } private void xToolStripMenuItem3_Click(object sender, EventArgs e) { ScreenMult = 4; ResizeWindow(4); } private void xToolStripMenuItem4_Click(object sender, EventArgs e) { ScreenMult = 5; ResizeWindow(5); } private void xToolStripMenuItem5_Click(object sender, EventArgs e) { ScreenMult = 6; ResizeWindow(6); } private void xToolStripMenuItem6_Click(object sender, EventArgs e) { ScreenMult = 7; ResizeWindow(7); } private void xToolStripMenuItem7_Click(object sender, EventArgs e) { ScreenMult = 8; ResizeWindow(8); } private void traceLoggerToolStripMenuItem_Click(object sender, EventArgs e) { if(TraceLogger != null) { TraceLogger.Focus(); return; } TraceLogger = new TriCTraceLogger(); TraceLogger.MainGUI = this; TraceLogger.Init(); TraceLogger.Show(); TraceLogger.Location = Location; } bool Pending_ShowScreenBorders; private void trueToolStripMenuItem1_Click(object sender, EventArgs e) { toolstrip_ViewBorders_False.Checked = false; toolstrip_ViewBorders_True.Checked = true; if (EMU != null) { Pending_ShowScreenBorders = true; } settings_border = true; } bool Pending_HideScreenBorders; private void falseToolStripMenuItem1_Click(object sender, EventArgs e) { toolstrip_ViewBorders_False.Checked = true; toolstrip_ViewBorders_True.Checked = false; if (EMU != null) { Pending_HideScreenBorders = true; } settings_border = false; } private void nametableViewerToolStripMenuItem_Click(object sender, EventArgs e) { if(NametableViewer != null) { NametableViewer.Focus(); return; } NametableViewer = new TriCNTViewer(); NametableViewer.MainGUI = this; NametableViewer.Show(); NametableViewer.Location = Location; } private void hexEditorToolStripMenuItem_Click(object sender, EventArgs e) { if (HexExditor != null) { HexExditor.Focus(); return; } HexExditor = new TriCHexEditor(); HexExditor.MainGUI = this; HexExditor.Show(); HexExditor.Location = Location; } List Savestate = new List(); bool PendingSaveState = false; private void saveStateToolStripMenuItem_Click(object sender, EventArgs e) { PendingSaveState = true; } bool PendingLoadState = false; private void loadStateToolStripMenuItem_Click(object sender, EventArgs e) { if (TasTimeline == null) // this would cause a desync otherwise, so forcefully prevent this. { PendingLoadState = true; } } private void tASTimelineToolStripMenuItem_Click(object sender, EventArgs e) { if(TasTimeline != null) { TasTimeline.Focus(); return; } bool EMUExists = (EMU != null); if (!EMUExists) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\")) { InitDirectory += @"roms\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Filter = "NES ROM files (*.nes)|*.nes", Title = "Select file", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } filePath = ofd.FileName; FDS = Path.GetExtension(ofd.FileName) == ".fds"; TasTimeline = new TriCTASTimeline(this); TasTimeline.Show(); TasTimeline.Location = Location; } } else { TasTimeline = new TriCTASTimeline(this); TasTimeline.Show(); TasTimeline.Location = Location; } } public List ParseTasFile(string TasFilePath, out List Resets) { // determine file type string extension = Path.GetExtension(TasFilePath); // create list of inputs from the tas file, and make any settings changes if needed. byte[] ByteArray = File.ReadAllBytes(TasFilePath); List TASInputs = new List(); // Low byte is player 1, High byte is player 2. List TASResets = new List(); switch (extension) { case ".bk2": case ".tasproj": { // .bk2 files are actually just .zip files! // Let's yoink "Input Log.txt" from this .bk2 file StringReader InputLog = new StringReader(new string(new StreamReader(ZipFile.OpenRead(TasFilePath).Entries.Where(x => x.Name.Equals("Input Log.txt", StringComparison.InvariantCulture)).FirstOrDefault().Open(), Encoding.UTF8).ReadToEnd().ToArray())); // now to parse the input log! InputLog.ReadLine(); // "[Input]" string key = InputLog.ReadLine(); // "LogKey: ... " bool Bk2_Port1 = key.Contains("P1"); bool Bk2_Port2 = key.Contains("P2"); string ln = InputLog.ReadLine(); ushort u = 0; while (ln != null && ln.Length > 3) { int pipeIndex = ln.Substring(1, ln.Length - 1).IndexOf('|') + 1; char[] lnCharArray = ln.ToCharArray(); bool reset = lnCharArray[pipeIndex - 1] == 'r'; u = 0; if (Bk2_Port1) { u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x08 : 0); u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x04 : 0); u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x02 : 0); u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x01 : 0); u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x10 : 0); u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x20 : 0); u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x40 : 0); u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x80 : 0); } else if (Bk2_Port2) // Are there any NES TASes that only feature controller 2? { u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x0800 : 0); u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x0400 : 0); u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x0200 : 0); u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x0100 : 0); u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x1000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x2000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x4000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x8000 : 0); } if (Bk2_Port1 && Bk2_Port2) { pipeIndex = ln.Substring(pipeIndex + 1, ln.Length - 1 - pipeIndex).IndexOf('|') + pipeIndex + 1; u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x0800 : 0); u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x0400 : 0); u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x0200 : 0); u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x0100 : 0); u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x1000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x2000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x4000 : 0); u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x8000 : 0); } TASInputs.Add(u); TASResets.Add(reset); ln = InputLog.ReadLine(); if (ln == "[/Input]") { break; } } } break; case ".fm2": { // change the alignment to use FCEUX's // header info of varying size // Every line of a header ends in $0A // Every header section is named. Example: $0A "romFileName" // Since the input log begins with "|" and none of the header section names begin with "|", I can assume $0A"|" is the start of the input log bool fm2_UsePort0 = false; bool fm2_UsePort1 = false; int i = 0; while (i < ByteArray.Length) { // parse for "port0 ?" if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x70 && ByteArray[i + 2] == 0x6F && ByteArray[i + 3] == 0x72 && ByteArray[i + 4] == 0x74 && ByteArray[i + 5] == 0x30 && ByteArray[i + 6] == 0x20 ) { fm2_UsePort0 = ByteArray[i + 7] == 0x31; } // parse for "port1 ?" if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x70 && ByteArray[i + 2] == 0x6F && ByteArray[i + 3] == 0x72 && ByteArray[i + 4] == 0x74 && ByteArray[i + 5] == 0x31 && ByteArray[i + 6] == 0x20 ) { fm2_UsePort1 = ByteArray[i + 7] == 0x31; } if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x7C) { break; } i++; } ushort u = 0; int Port0Index = 0; int Port1Index = 0; while (i < ByteArray.Length) { if (ByteArray[i] == 0x0A) { if (i == ByteArray.Length - 1) { break; } if (ByteArray[i + 1] == 0x0A) { // The .fm2 TAS file format supports empty rows. Formatting quirk? i++; continue; } if (ByteArray[i + 1] == 0x23) { // The .fm2 TAS file format supports comments, in the following format: //\n### Comment // so basically, check for `#` as the next character. // And now we skip until the next new line. i++; continue; } bool reset = (ByteArray[i + 2] & 1) == 1; if (fm2_UsePort0) { Port0Index = i + 4; if (fm2_UsePort1) { Port1Index = i + 0xD; } } else if (fm2_UsePort1) { Port1Index = i + 0x6; } u = 0; if (fm2_UsePort0) { u |= (ushort)(ByteArray[Port0Index] == 0x2E ? 0 : 1); u |= (ushort)(ByteArray[Port0Index + 1] == 0x2E ? 0 : 2); u |= (ushort)(ByteArray[Port0Index + 2] == 0x2E ? 0 : 4); u |= (ushort)(ByteArray[Port0Index + 3] == 0x2E ? 0 : 8); u |= (ushort)(ByteArray[Port0Index + 4] == 0x2E ? 0 : 0x10); u |= (ushort)(ByteArray[Port0Index + 5] == 0x2E ? 0 : 0x20); u |= (ushort)(ByteArray[Port0Index + 6] == 0x2E ? 0 : 0x40); u |= (ushort)(ByteArray[Port0Index + 7] == 0x2E ? 0 : 0x80); } if (fm2_UsePort1) { u |= (ushort)(ByteArray[Port1Index] == 0x2E ? 0 : 0x100); u |= (ushort)(ByteArray[Port1Index + 1] == 0x2E ? 0 : 0x200); u |= (ushort)(ByteArray[Port1Index + 2] == 0x2E ? 0 : 0x400); u |= (ushort)(ByteArray[Port1Index + 3] == 0x2E ? 0 : 0x800); u |= (ushort)(ByteArray[Port1Index + 4] == 0x2E ? 0 : 0x1000); u |= (ushort)(ByteArray[Port1Index + 5] == 0x2E ? 0 : 0x2000); u |= (ushort)(ByteArray[Port1Index + 6] == 0x2E ? 0 : 0x4000); u |= (ushort)(ByteArray[Port1Index + 7] == 0x2E ? 0 : 0x8000); } TASInputs.Add(u); TASResets.Add(reset); } i++; } } break; case ".fm3": { // similar to fm2, this has a header of varying length. // But it also contains significantly more metadata after the input log. // we need to parse $0A"length " bool fm3_UsePort0 = false; bool fm3_UsePort1 = false; int i = 0; while (i < ByteArray.Length) { if (ByteArray[i] == 0x0A) { if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x70 && ByteArray[i + 2] == 0x6F && ByteArray[i + 3] == 0x72 && ByteArray[i + 4] == 0x74 && ByteArray[i + 5] == 0x30 && ByteArray[i + 6] == 0x20 ) { fm3_UsePort0 = ByteArray[i + 7] == 0x31; } // parse for "port1 ?" if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x70 && ByteArray[i + 2] == 0x6F && ByteArray[i + 3] == 0x72 && ByteArray[i + 4] == 0x74 && ByteArray[i + 5] == 0x31 && ByteArray[i + 6] == 0x20 ) { fm3_UsePort1 = ByteArray[i + 7] == 0x31; } // check if this is the header info for "length" if (ByteArray[i] == 0x0A) { if (ByteArray[i + 1] == 0x6C && ByteArray[i + 2] == 0x65 && ByteArray[i + 3] == 0x6E && ByteArray[i + 4] == 0x67 && ByteArray[i + 5] == 0x74 && ByteArray[i + 6] == 0x68 && ByteArray[i + 7] == 0x20) { // okay, so the length is in ascii... // let's figure out where the next $0A character is int next0A = i + 8; while (next0A < ByteArray.Length) { if (ByteArray[next0A] == 0x0A) { break; } next0A++; } // okay, so the string from i+8 though next0A is the length. byte[] StringArray = new byte[next0A - (i + 8)]; Array.Copy(ByteArray, i + 8, StringArray, 0, StringArray.Length); int InputLogLength = int.Parse(Encoding.Default.GetString(StringArray)); i = next0A + 2; int tempMul = 1; if (fm3_UsePort0) { tempMul++; } if (fm3_UsePort1) { tempMul++; } int InputLogByteLength = InputLogLength * tempMul; // first byte is always zero? // next byte is controller 1 (if enabled) // next byte is controller 2 (if enabled) ushort u = 0; while (i < next0A + 2 + InputLogByteLength) { bool reset = (ByteArray[i] & 1) == 1; i++;// dummy byte (?) u = 0; if (fm3_UsePort0) { u = ByteArray[i]; i++; } if (fm3_UsePort1) { u |= (ushort)(ByteArray[i] << 8); i++; } TASInputs.Add(u); TASResets.Add(reset); } } } } i++; } } break; case ".fmv": { int i = 0x90; // there's a 144 byte header bool fmv_UseController2 = (ByteArray[5] & 0b00010000) != 0; if (fmv_UseController2) { while (i < ByteArray.Length) { ushort u = (ushort)(FamtasiaInput2Standard(ByteArray[i]) | (FamtasiaInput2Standard(ByteArray[i + 1]) << 8)); TASInputs.Add(u); i += 2; } } else { while (i < ByteArray.Length) { TASInputs.Add(FamtasiaInput2Standard(ByteArray[i])); i++; } } } break; case ".3c2": { // The .3c2 format is pretty much identical to the .r08 file format, but with a 1-byte header. // Bit 0: 0 = Latch Filtering. 1 = Clock Filtering. // Bit 1: 0 = Only controller 1. 1 = Controller 1 and controller 2. // Bit 2: 0 = No reset button. 1 = The reset button is used in this TAS. bool UseController2 = (ByteArray[0] & 2) != 0; bool UseReset = (ByteArray[0] & 4) != 0; byte b = 0; byte b2 = 0; int i = 1; while (i < ByteArray.Length) { b = ByteArray[i]; i++; if (UseController2) { b2 = ByteArray[i]; i++; } TASInputs.Add((ushort)(b | (b2 << 8))); if (UseReset) { bool res = (ByteArray[i] & 0x80) == 0x80; // I use bit 7 for the reset button. (bit 0 is for lag frames in the .3c3 format.) TASResets.Add(res); i++; } } TASInputs.Add(0); // append a zero to the end for safe measure. } break; case ".r08": { // the .r08 file format is conveniently already in the format I want for my emulator. byte b = 0; byte b2 = 0; int i = 0; while (i < ByteArray.Length) { b = ByteArray[i]; b2 = ByteArray[i + 1]; TASInputs.Add((ushort)(b | (b2 << 8))); i += 2; } TASInputs.Add(0); // append a zero to the end for safe measure. } break; case ".3c3": { // .3c3 is the format for my TAS timeline. // The big differences are: // - .3c3 saves the savestate information // - .3c3 saves the "lag frame" information as well. (So every frame is 3 bytes now.) // .3c3 has a 16 byte header. // It's just little-endian 32-bit integers, and the same 1-byte header used in .3c2's. // The first one determines how many bytes are in every savestate. // the second one determinines how many frames there are in this TAS. // I guess that means there's a limit of 2,147,483,647 frames in a .3c3 TAS file. God help me if I ever feel compelled to challenge this. // Then there's a handful of unused bytes. ByteArray[15] is the same format as the 1-byte header used in 3c2's. // Bit 0: 0 = Latch Filtering. 1 = Clock Filtering. // Bit 1: 0 = Only controller 1. 1 = Controller 1 and controller 2. // Bit 2: 0 = No reset button. 1 = The reset button is used in this TAS. int SavestateLength = ByteArray[0] | (ByteArray[1] << 8) | (ByteArray[2] << 16) | (ByteArray[3] << 24); int rerecords = ByteArray[4] | (ByteArray[5] << 8) | (ByteArray[6] << 16) | (ByteArray[7] << 24); int frameCount = ByteArray[8] | (ByteArray[9] << 8) | (ByteArray[10] << 16) | (ByteArray[11] << 24); bool UseController2 = (ByteArray[15] & 2) != 0; bool UseReset = (ByteArray[15] & 4) != 0; List> saveStates = new List>(); List> saveStates2 = new List>(); List lagFrames = new List(); byte b = 0; byte b2 = 0; int i = 16; while (i < frameCount * 3 + 16) { b = ByteArray[i]; i++; if (UseController2) { b2 = ByteArray[i]; i++; } TASInputs.Add((ushort)(b | (b2 << 8))); bool lagframe = (ByteArray[i] & 1) == 1; // I use bit 0 for the lag frame info. lagFrames.Add(lagframe); if (UseReset) { bool res = (ByteArray[i] & 0x80) == 0x80; // I use bit 7 for the reset button. TASResets.Add(res); } i++; saveStates.Add(new List()); saveStates2.Add(new List()); } // and from here until you reach the end of the file, the data is arranged in the following format: // [32-bit int declaring the frame number, and 'n' bytes for the save state at that frame.] while (i < ByteArray.Length) { // read the 4 byte header. int frameIndex = ByteArray[i] | (ByteArray[i + 1] << 8) | (ByteArray[i + 2] << 16) | (ByteArray[i + 3] << 24); i += 4; int j = 0; while (j < SavestateLength) { saveStates[frameIndex].Add(ByteArray[i]); i++; j++; } } if (TasTimeline != null) { TriCTASTimeline.TimelineSavestates = saveStates; TriCTASTimeline.TimelineTempSavestates = saveStates2; // the empty list. TriCTASTimeline.LagFrames = lagFrames; TasTimeline.highestFrameEmulatedEver = frameCount - 1; TasTimeline.frameEmulated = frameCount - 1; TasTimeline.Rerecords = rerecords; } } break; // TODO: ask if the .tasd file format is a thing yet } if (TASResets.Count == 0) // If not using Resets, we still want to initialize the Resets list, in case they are added to the TAS timeline at a later point. { TASResets = new List(new bool[TASInputs.Count]); } Resets = TASResets; return TASInputs; } byte FamtasiaInput2Standard(byte input) { //famtasia format is SsABDULR byte b0 = (byte)(input & 0x8); byte b1 = (byte)(input & 0x4); byte b2 = (byte)(input & 0xC0); byte b3 = (byte)(input & 0x30); b0 >>= 1; b1 <<= 1; byte b4 = (byte)(b2 & 0x80); byte b5 = (byte)(b2 & 0x40); b4 >>= 1; b5 <<= 1; b2 = (byte)(b4 | b5); b2 >>= 2; b3 <<= 2; byte b = (byte)(b2 | b3 | b0 | b1 | (input & 0x3)); return b; } public void CreateTASTimelineEmulator() { if (EmuClock != null) { cancel.Cancel(); EmuClock.Join(); } if (EMU != null) { EMU.Dispose(); GC.Collect(); } Timeline_Paused = true; EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; EMU.PPUClock = settings_alignment; if (!LoadROM(filePath)) { return; } cancel = new CancellationTokenSource(); EmuClock = new Thread(() => ClockTimelineEmulator(cancel.Token)); EmuClock.SetApartmentState(ApartmentState.STA); EmuClock.IsBackground = true; EmuClock.Start(); } public bool Timeline_PendingPause; public bool Timeline_PendingResume; public bool Timeline_Paused; public bool Timeline_PendingFrameAdvance; public bool Timeline_PendingLoadState; public int Timeline_PendingFrameNumber; public bool Timeline_PendingHardReset; public bool Timeline_PendingArbitrarySavestate; public bool Timeline_AutoPlayUntilTarget; public int Timeline_AutoPlayTarget; public bool Timeline_PendingMouseDown; public bool Timeline_PendingMouseHeld; public bool Timeline_PendingResetScreen; public bool Timeline_PendingClockFiltering; public bool Timeline_HotkeyHeld_RShoulder; public bool Timeline_HotkeyHeld_RTrigger; public bool Timeline_HotkeyHeld_LTrigger; public List Timeline_LoadState; void ClockTimelineEmulator(CancellationToken ct) { while (!ct.IsCancellationRequested) { if (Timeline_PendingHardReset) { Timeline_PendingHardReset = false; if (EMU != null) { EMU.Dispose(); GC.Collect(); } EMU = new Emulator(); EMU.PPU_DecodeSignal = settings_ntsc; EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw; EMU.PPU_ShowScreenBorders = settings_border; EMU.PPUClock = settings_alignment; if (!LoadROM(filePath)) { return; } if (Timeline_PendingClockFiltering) { Timeline_PendingClockFiltering = false; EMU.TASTimelineClockFiltering = true; } } if (Timeline_PendingArbitrarySavestate) { Timeline_PendingArbitrarySavestate = false; // pretty much only ever set when loading a TAS. List state = EMU.SaveState(); TriCTASTimeline.TimelineSavestates.Add(state); TriCTASTimeline.TimelineTempSavestates.Add(new List()); } bool[] ControllerHotkeys = OtherControllerHotkeys(); if (!Timeline_HotkeyHeld_RShoulder && ControllerHotkeys[0]) { Timeline_PendingPause = !Timeline_Paused; Timeline_PendingResume = Timeline_Paused; } if ((ControllerHotkeys[2] && ControllerHotkeys[1]) || (!Timeline_HotkeyHeld_RTrigger && ControllerHotkeys[2])) { Timeline_PendingFrameAdvance = true; Timeline_PendingPause = !Timeline_Paused; } bool rewinding = false; if ((ControllerHotkeys[3] && ControllerHotkeys[1]) || (!Timeline_HotkeyHeld_LTrigger && ControllerHotkeys[3])) { TasTimeline.FrameRewind(); Timeline_PendingPause = !Timeline_Paused; rewinding = true; } Timeline_HotkeyHeld_RShoulder = ControllerHotkeys[0]; Timeline_HotkeyHeld_RTrigger = ControllerHotkeys[2]; Timeline_HotkeyHeld_LTrigger = ControllerHotkeys[3]; if (Timeline_PendingPause) { Timeline_PendingPause = false; Timeline_Paused = true; TasTimeline.ChangePlayPauseButtonText("Paused"); } if (Timeline_PendingResume) { Timeline_PendingResume = false; Timeline_Paused = false; TasTimeline.ChangePlayPauseButtonText("Running"); } if (Timeline_PendingMouseDown) { Timeline_PendingMouseDown = false; TasTimeline.TimelineMouseDownEvent(); } if (Timeline_PendingMouseHeld) { Timeline_PendingMouseHeld = false; TasTimeline.TimelineMouseHeldEvent(); } if (Timeline_PendingLoadState) { Timeline_PendingLoadState = false; PendingLoadState = true; Savestate = Timeline_LoadState; TasTimeline.frameIndex = Timeline_PendingFrameNumber; } if (Timeline_PendingResetScreen) { Timeline_PendingResetScreen = false; pb_Screen.Invoke(new MethodInvoker( delegate () { Bitmap b = new Bitmap(pb_Screen.Image); for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { b.SetPixel(x, y, Color.Black); } } pb_Screen.Image = b; pb_Screen.Update(); })); } bool FrameAdvance = false; if (Timeline_PendingFrameAdvance) { Timeline_PendingFrameAdvance = false; FrameAdvance = true; } if (Timeline_AutoPlayUntilTarget && TasTimeline.frameIndex >= Timeline_AutoPlayTarget) { Timeline_AutoPlayUntilTarget = false; } RunUpkeep(); if (Timeline_Paused && !FrameAdvance && !Timeline_AutoPlayUntilTarget) { Thread.Sleep(50); } else { if (TasTimeline.frameIndex == TriCTASTimeline.TimelineSavestates.Count) { // create a savestate for the previous frame. if (TasTimeline.SavestateEveryFrame()) { List state = EMU.SaveState(); TriCTASTimeline.TimelineSavestates.Add(state); TasTimeline.SavestateLength = state.Count; } else { List state = new List(); TriCTASTimeline.TimelineSavestates.Add(state); } if (TriCTASTimeline.TimelineSavestates[TasTimeline.frameIndex].Count > 0) { // if this savestate is not empty List state = new List(); TriCTASTimeline.TimelineTempSavestates.Add(state); TasTimeline.TrimTempSavestates(); //TriCTASTimeline.TEMPRerecordTracker.Add(TasTimeline.Rerecords); } else { // if this savestate is empty List state = EMU.SaveState(); TriCTASTimeline.TimelineTempSavestates.Add(state); TasTimeline.TrimTempSavestates(); //TriCTASTimeline.TEMPRerecordTracker.Add(TasTimeline.Rerecords); } } else if (TasTimeline.frameIndex < TriCTASTimeline.TimelineTempSavestates.Count && TriCTASTimeline.TimelineTempSavestates[TasTimeline.frameIndex].Count == 0) { List state = EMU.SaveState(); TriCTASTimeline.TimelineTempSavestates[TasTimeline.frameIndex] = state; TasTimeline.TrimTempSavestates(); } if (TasTimeline.RecordInputs() && !rewinding) { byte realtimeInputs = RealtimeInputs(); if (TasTimeline.Player2()) { EMU.ControllerPort2 = realtimeInputs; EMU.ControllerPort1 = 0; } else { EMU.ControllerPort1 = realtimeInputs; EMU.ControllerPort2 = 0; } ushort rimputs = (ushort)((EMU.ControllerPort2 << 8) | EMU.ControllerPort1); TriCTASTimeline.Inputs[TasTimeline.frameIndex] = rimputs; int row = TasTimeline.frameIndex - TasTimeline.TopFrame; if (row >= 0 && row < 40) { TasTimeline.RecalculateTimelineRow(row, rimputs); TasTimeline.RedrawTimelineRow(row, false); } if (TasTimeline.frameIndex < TasTimeline.frameEmulated) { TasTimeline.MarkStale(TasTimeline.frameIndex); Timeline_PendingLoadState = false; // the MarkStale() function typically loads a savestate, but we don't want that here. } } else { EMU.ControllerPort1 = (byte)(TriCTASTimeline.Inputs[TasTimeline.frameIndex] & 0xFF); EMU.ControllerPort2 = (byte)((TriCTASTimeline.Inputs[TasTimeline.frameIndex] & 0xFF00) >> 8); } EMU._CoreFrameAdvance(); RunPostFramePhase(); if (TasTimeline.frameIndex < TriCTASTimeline.Resets.Count && TriCTASTimeline.Resets[TasTimeline.frameIndex]) { EMU.Reset(); } if (!EMU.TASTimelineClockFiltering || !EMU.LagFrame) { TasTimeline.FrameAdvance(); } else { //Timeline_PendingFrameAdvance = true; // keep running until a non-lag frame. } } } } public bool[] OtherControllerHotkeys() { bool[] joystickButtons = new bool[4]; int c = SDL.SDL_NumJoysticks(); if (c != 0) { SDL.SDL_GameControllerUpdate(); joystickButtons[0] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0; joystickButtons[1] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0; joystickButtons[2] = SDL.SDL_GameControllerGetAxis(gameControllerPrt, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > 0.2; joystickButtons[3] = SDL.SDL_GameControllerGetAxis(gameControllerPrt, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT) > 0.2; } else { for (int i = 0; i < 4; i++) { joystickButtons[i] = false; } } return joystickButtons; } } /// /// Inherits from PictureBox; adds Interpolation Mode Setting /// public class PictureBoxWithInterpolationMode : PictureBox { public InterpolationMode InterpolationMode { get; set; } public PictureBoxWithInterpolationMode() { SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.Opaque, true); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); } protected override void OnPaint(PaintEventArgs paintEventArgs) { paintEventArgs.Graphics.PixelOffsetMode = PixelOffsetMode.Half; paintEventArgs.Graphics.InterpolationMode = InterpolationMode; base.OnPaint(paintEventArgs); } } } ================================================ FILE: forms/TriCNESGUI.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17, 17 True True True AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TriCNTViewer.Designer.cs ================================================ namespace TriCNES { partial class TriCNTViewer { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCNTViewer)); this.pictureBox1 = new System.Windows.Forms.PictureBox(); this.cb_ForcePal0ToBackdrop = new System.Windows.Forms.CheckBox(); this.cb_ScreenBoundary = new System.Windows.Forms.CheckBox(); this.cb_OverlayScreen = new System.Windows.Forms.CheckBox(); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.screenshotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.menuStrip1.SuspendLayout(); this.SuspendLayout(); // // pictureBox1 // this.pictureBox1.Location = new System.Drawing.Point(0, 21); this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Size = new System.Drawing.Size(512, 480); this.pictureBox1.TabIndex = 0; this.pictureBox1.TabStop = false; // // cb_ForcePal0ToBackdrop // this.cb_ForcePal0ToBackdrop.AutoSize = true; this.cb_ForcePal0ToBackdrop.Location = new System.Drawing.Point(13, 508); this.cb_ForcePal0ToBackdrop.Name = "cb_ForcePal0ToBackdrop"; this.cb_ForcePal0ToBackdrop.Size = new System.Drawing.Size(121, 17); this.cb_ForcePal0ToBackdrop.TabIndex = 1; this.cb_ForcePal0ToBackdrop.Text = "Use Backdrop Color"; this.cb_ForcePal0ToBackdrop.UseVisualStyleBackColor = true; // // cb_ScreenBoundary // this.cb_ScreenBoundary.AutoSize = true; this.cb_ScreenBoundary.Location = new System.Drawing.Point(12, 531); this.cb_ScreenBoundary.Name = "cb_ScreenBoundary"; this.cb_ScreenBoundary.Size = new System.Drawing.Size(136, 17); this.cb_ScreenBoundary.TabIndex = 2; this.cb_ScreenBoundary.Text = "Draw Screen Boundary"; this.cb_ScreenBoundary.UseVisualStyleBackColor = true; // // cb_OverlayScreen // this.cb_OverlayScreen.AutoSize = true; this.cb_OverlayScreen.Location = new System.Drawing.Point(12, 554); this.cb_OverlayScreen.Name = "cb_OverlayScreen"; this.cb_OverlayScreen.Size = new System.Drawing.Size(99, 17); this.cb_OverlayScreen.TabIndex = 3; this.cb_OverlayScreen.Text = "Overlay Screen"; this.cb_OverlayScreen.UseVisualStyleBackColor = true; // // menuStrip1 // this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Size = new System.Drawing.Size(512, 24); this.menuStrip1.TabIndex = 4; this.menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.screenshotToolStripMenuItem}); this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.fileToolStripMenuItem.Text = "File"; // // screenshotToolStripMenuItem // this.screenshotToolStripMenuItem.Name = "screenshotToolStripMenuItem"; this.screenshotToolStripMenuItem.Size = new System.Drawing.Size(132, 22); this.screenshotToolStripMenuItem.Text = "Screenshot"; this.screenshotToolStripMenuItem.Click += new System.EventHandler(this.screenshotToolStripMenuItem_Click); // // TriCNTViewer // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(512, 579); this.Controls.Add(this.cb_OverlayScreen); this.Controls.Add(this.cb_ScreenBoundary); this.Controls.Add(this.cb_ForcePal0ToBackdrop); this.Controls.Add(this.pictureBox1); this.Controls.Add(this.menuStrip1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip1; this.MaximizeBox = false; this.MaximumSize = new System.Drawing.Size(528, 618); this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(528, 618); this.Name = "TriCNTViewer"; this.Text = "Nametable Viewer"; ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.PictureBox pictureBox1; private System.Windows.Forms.CheckBox cb_ForcePal0ToBackdrop; private System.Windows.Forms.CheckBox cb_ScreenBoundary; private System.Windows.Forms.CheckBox cb_OverlayScreen; private System.Windows.Forms.MenuStrip menuStrip1; private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem screenshotToolStripMenuItem; } } ================================================ FILE: forms/TriCNTViewer.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TriCNES { public partial class TriCNTViewer : Form { public TriCNESGUI MainGUI; public TriCNTViewer() { InitializeComponent(); FormClosing += new FormClosingEventHandler(TriCNTViewer_Closing); } private void TriCNTViewer_Closing(Object sender, FormClosingEventArgs e) { MainGUI.NametableViewer = null; this.Dispose(); } public void Update(Bitmap b) { MethodInvoker upd = delegate { pictureBox1.Image = b; pictureBox1.Update(); }; try { this.Invoke(upd); } catch (Exception e) { } } public bool UseBackdrop() { return cb_ForcePal0ToBackdrop.Checked; } public bool DrawBoundary() { return cb_ScreenBoundary.Checked; } public bool OverlayScreen() { return cb_OverlayScreen.Checked; } private void screenshotToolStripMenuItem_Click(object sender, EventArgs e) { Clipboard.SetImage(pictureBox1.Image); } } } ================================================ FILE: forms/TriCNTViewer.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17, 17 AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TriCTASTimeline.Designer.cs ================================================ using System.Windows.Forms; namespace TriCNES { partial class TriCTASTimeline { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCTASTimeline)); this.pb_Timeline = new System.Windows.Forms.PictureBox(); this.timelineScrollbar = new System.Windows.Forms.VScrollBar(); this.b_FrameAdvance = new System.Windows.Forms.Button(); this.b_FrameBack = new System.Windows.Forms.Button(); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.tASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.loadTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.saveTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.saveWithSavestatesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exportTor08ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.cellFrequencyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.perVBlankToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.perControllerStrobeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.b_play = new System.Windows.Forms.Button(); this.contextMenuStrip_Timeline = new System.Windows.Forms.ContextMenuStrip(this.components); this.deleteFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.insertFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.truncateMovieToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.savestateThisFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.b_JumptoCursor = new System.Windows.Forms.Button(); this.cb_FollowCursor = new System.Windows.Forms.CheckBox(); this.tb_FollowDistance = new System.Windows.Forms.TrackBar(); this.cb_SavestateEveryFrame = new System.Windows.Forms.CheckBox(); this.tb_TempSavestates = new System.Windows.Forms.TextBox(); this.label_TempSavestates = new System.Windows.Forms.Label(); this.Tooltip_ = new System.Windows.Forms.ToolTip(this.components); this.label_AutoSavestateThreshold = new System.Windows.Forms.Label(); this.tb_AutoSavestateThreshold = new System.Windows.Forms.TextBox(); this.cb_RecordInputs = new System.Windows.Forms.CheckBox(); this.cb_player2 = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)(this.pb_Timeline)).BeginInit(); this.menuStrip1.SuspendLayout(); this.contextMenuStrip_Timeline.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.tb_FollowDistance)).BeginInit(); this.SuspendLayout(); // // pb_Timeline // this.pb_Timeline.Location = new System.Drawing.Point(12, 27); this.pb_Timeline.Name = "pb_Timeline"; this.pb_Timeline.Size = new System.Drawing.Size(353, 657); this.pb_Timeline.TabIndex = 7; this.pb_Timeline.TabStop = false; // // timelineScrollbar // this.timelineScrollbar.Location = new System.Drawing.Point(372, 27); this.timelineScrollbar.Name = "timelineScrollbar"; this.timelineScrollbar.Size = new System.Drawing.Size(19, 657); this.timelineScrollbar.TabIndex = 2; // // b_FrameAdvance // this.b_FrameAdvance.Location = new System.Drawing.Point(601, 38); this.b_FrameAdvance.Name = "b_FrameAdvance"; this.b_FrameAdvance.Size = new System.Drawing.Size(75, 23); this.b_FrameAdvance.TabIndex = 1; this.b_FrameAdvance.Text = ">"; this.b_FrameAdvance.UseVisualStyleBackColor = true; this.b_FrameAdvance.Click += new System.EventHandler(this.b_FrameAdvance_Click); // // b_FrameBack // this.b_FrameBack.Location = new System.Drawing.Point(439, 38); this.b_FrameBack.Name = "b_FrameBack"; this.b_FrameBack.Size = new System.Drawing.Size(75, 23); this.b_FrameBack.TabIndex = 2; this.b_FrameBack.Text = "<"; this.b_FrameBack.UseVisualStyleBackColor = true; this.b_FrameBack.Click += new System.EventHandler(this.b_FrameBack_Click); // // menuStrip1 // this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tASToolStripMenuItem, this.settingsToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Size = new System.Drawing.Size(745, 24); this.menuStrip1.TabIndex = 3; this.menuStrip1.Text = "menuStrip1"; // // tASToolStripMenuItem // this.tASToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.loadTASToolStripMenuItem, this.saveTASToolStripMenuItem, this.saveWithSavestatesToolStripMenuItem, this.exportTor08ToolStripMenuItem}); this.tASToolStripMenuItem.Name = "tASToolStripMenuItem"; this.tASToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.tASToolStripMenuItem.Text = "File"; // // loadTASToolStripMenuItem // this.loadTASToolStripMenuItem.Name = "loadTASToolStripMenuItem"; this.loadTASToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.loadTASToolStripMenuItem.Text = "Load"; this.loadTASToolStripMenuItem.Click += new System.EventHandler(this.loadTASToolStripMenuItem_Click); // // saveTASToolStripMenuItem // this.saveTASToolStripMenuItem.Name = "saveTASToolStripMenuItem"; this.saveTASToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.saveTASToolStripMenuItem.Text = "Save"; this.saveTASToolStripMenuItem.Click += new System.EventHandler(this.saveTASToolStripMenuItem_Click); // // saveWithSavestatesToolStripMenuItem // this.saveWithSavestatesToolStripMenuItem.Name = "saveWithSavestatesToolStripMenuItem"; this.saveWithSavestatesToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.saveWithSavestatesToolStripMenuItem.Text = "Save with savestates"; this.saveWithSavestatesToolStripMenuItem.Click += new System.EventHandler(this.saveWithSavestatesToolStripMenuItem_Click); // // exportTor08ToolStripMenuItem // this.exportTor08ToolStripMenuItem.Name = "exportTor08ToolStripMenuItem"; this.exportTor08ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.exportTor08ToolStripMenuItem.Text = "Export to .r08"; this.exportTor08ToolStripMenuItem.Click += new System.EventHandler(this.exportTor08ToolStripMenuItem_Click); // // settingsToolStripMenuItem // this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.cellFrequencyToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); this.settingsToolStripMenuItem.Text = "Settings"; // // cellFrequencyToolStripMenuItem // this.cellFrequencyToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.perVBlankToolStripMenuItem, this.perControllerStrobeToolStripMenuItem}); this.cellFrequencyToolStripMenuItem.Name = "cellFrequencyToolStripMenuItem"; this.cellFrequencyToolStripMenuItem.Size = new System.Drawing.Size(152, 22); this.cellFrequencyToolStripMenuItem.Text = "Cell Frequency"; // // perVBlankToolStripMenuItem // this.perVBlankToolStripMenuItem.Checked = true; this.perVBlankToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; this.perVBlankToolStripMenuItem.Name = "perVBlankToolStripMenuItem"; this.perVBlankToolStripMenuItem.Size = new System.Drawing.Size(184, 22); this.perVBlankToolStripMenuItem.Text = "Per VBlank"; this.perVBlankToolStripMenuItem.Click += new System.EventHandler(this.perVBlankToolStripMenuItem_Click); // // perControllerStrobeToolStripMenuItem // this.perControllerStrobeToolStripMenuItem.Name = "perControllerStrobeToolStripMenuItem"; this.perControllerStrobeToolStripMenuItem.Size = new System.Drawing.Size(184, 22); this.perControllerStrobeToolStripMenuItem.Text = "Per Controller Strobe"; this.perControllerStrobeToolStripMenuItem.Click += new System.EventHandler(this.perControllerStrobeToolStripMenuItem_Click); // // b_play // this.b_play.Location = new System.Drawing.Point(521, 38); this.b_play.Name = "b_play"; this.b_play.Size = new System.Drawing.Size(75, 23); this.b_play.TabIndex = 4; this.b_play.Text = "Paused"; this.b_play.UseVisualStyleBackColor = true; this.b_play.Click += new System.EventHandler(this.b_play_Click); // // contextMenuStrip_Timeline // this.contextMenuStrip_Timeline.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.deleteFrameToolStripMenuItem, this.insertFrameToolStripMenuItem, this.truncateMovieToolStripMenuItem, this.savestateThisFrameToolStripMenuItem}); this.contextMenuStrip_Timeline.Name = "contextMenuStrip_Timeline"; this.contextMenuStrip_Timeline.Size = new System.Drawing.Size(184, 92); // // deleteFrameToolStripMenuItem // this.deleteFrameToolStripMenuItem.Name = "deleteFrameToolStripMenuItem"; this.deleteFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22); this.deleteFrameToolStripMenuItem.Text = "Delete Frame"; this.deleteFrameToolStripMenuItem.Click += new System.EventHandler(this.deleteFrameToolStripMenuItem_Click); // // insertFrameToolStripMenuItem // this.insertFrameToolStripMenuItem.Name = "insertFrameToolStripMenuItem"; this.insertFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22); this.insertFrameToolStripMenuItem.Text = "Insert Frame"; this.insertFrameToolStripMenuItem.Click += new System.EventHandler(this.insertFrameToolStripMenuItem_Click); // // truncateMovieToolStripMenuItem // this.truncateMovieToolStripMenuItem.Name = "truncateMovieToolStripMenuItem"; this.truncateMovieToolStripMenuItem.Size = new System.Drawing.Size(183, 22); this.truncateMovieToolStripMenuItem.Text = "Truncate Movie"; this.truncateMovieToolStripMenuItem.Click += new System.EventHandler(this.truncateMovieToolStripMenuItem_Click); // // savestateThisFrameToolStripMenuItem // this.savestateThisFrameToolStripMenuItem.Name = "savestateThisFrameToolStripMenuItem"; this.savestateThisFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22); this.savestateThisFrameToolStripMenuItem.Text = "Savestate This Frame"; this.savestateThisFrameToolStripMenuItem.Click += new System.EventHandler(this.savestateThisFrameToolStripMenuItem_Click); // // b_JumptoCursor // this.b_JumptoCursor.Location = new System.Drawing.Point(236, 699); this.b_JumptoCursor.Name = "b_JumptoCursor"; this.b_JumptoCursor.Size = new System.Drawing.Size(99, 23); this.b_JumptoCursor.TabIndex = 5; this.b_JumptoCursor.Text = "Jump to Cursor"; this.b_JumptoCursor.UseVisualStyleBackColor = true; this.b_JumptoCursor.Click += new System.EventHandler(this.b_JumptoCursor_Click); // // cb_FollowCursor // this.cb_FollowCursor.AutoSize = true; this.cb_FollowCursor.Checked = true; this.cb_FollowCursor.CheckState = System.Windows.Forms.CheckState.Checked; this.cb_FollowCursor.Location = new System.Drawing.Point(26, 703); this.cb_FollowCursor.Name = "cb_FollowCursor"; this.cb_FollowCursor.Size = new System.Drawing.Size(89, 17); this.cb_FollowCursor.TabIndex = 6; this.cb_FollowCursor.Text = "Follow Cursor"; this.cb_FollowCursor.UseVisualStyleBackColor = true; // // tb_FollowDistance // this.tb_FollowDistance.LargeChange = 1; this.tb_FollowDistance.Location = new System.Drawing.Point(118, 699); this.tb_FollowDistance.Maximum = 39; this.tb_FollowDistance.Name = "tb_FollowDistance"; this.tb_FollowDistance.Size = new System.Drawing.Size(104, 45); this.tb_FollowDistance.TabIndex = 8; this.tb_FollowDistance.Value = 20; this.tb_FollowDistance.Scroll += new System.EventHandler(this.tb_FollowDistance_Scroll); // // cb_SavestateEveryFrame // this.cb_SavestateEveryFrame.AutoSize = true; this.cb_SavestateEveryFrame.Location = new System.Drawing.Point(409, 112); this.cb_SavestateEveryFrame.Name = "cb_SavestateEveryFrame"; this.cb_SavestateEveryFrame.Size = new System.Drawing.Size(136, 17); this.cb_SavestateEveryFrame.TabIndex = 9; this.cb_SavestateEveryFrame.Text = "Savestate Every Frame"; this.cb_SavestateEveryFrame.UseVisualStyleBackColor = true; // // tb_TempSavestates // this.tb_TempSavestates.Location = new System.Drawing.Point(410, 132); this.tb_TempSavestates.MaxLength = 5; this.tb_TempSavestates.Name = "tb_TempSavestates"; this.tb_TempSavestates.Size = new System.Drawing.Size(41, 20); this.tb_TempSavestates.TabIndex = 10; this.tb_TempSavestates.Text = "120"; this.Tooltip_.SetToolTip(this.tb_TempSavestates, resources.GetString("tb_TempSavestates.ToolTip")); this.tb_TempSavestates.TextChanged += new System.EventHandler(this.tb_TempSavestates_TextChanged); this.tb_TempSavestates.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tb_FilterForNumbers); // // label_TempSavestates // this.label_TempSavestates.AutoSize = true; this.label_TempSavestates.Location = new System.Drawing.Point(457, 139); this.label_TempSavestates.Name = "label_TempSavestates"; this.label_TempSavestates.Size = new System.Drawing.Size(139, 13); this.label_TempSavestates.TabIndex = 11; this.label_TempSavestates.Text = "Temporary Savestate Count"; this.Tooltip_.SetToolTip(this.label_TempSavestates, resources.GetString("label_TempSavestates.ToolTip")); // // Tooltip_ // this.Tooltip_.AutoPopDelay = 500000; this.Tooltip_.InitialDelay = 500; this.Tooltip_.ReshowDelay = 100; // // label_AutoSavestateThreshold // this.label_AutoSavestateThreshold.AutoSize = true; this.label_AutoSavestateThreshold.Location = new System.Drawing.Point(457, 165); this.label_AutoSavestateThreshold.Name = "label_AutoSavestateThreshold"; this.label_AutoSavestateThreshold.Size = new System.Drawing.Size(130, 13); this.label_AutoSavestateThreshold.TabIndex = 13; this.label_AutoSavestateThreshold.Text = "Auto-Savestate Threshold"; this.Tooltip_.SetToolTip(this.label_AutoSavestateThreshold, "Automatically make a savestate every \'n\' frames.\r\nA larger value will consume les" + "s RAM, but leave gaps that will need to be re-emulated when loading earlier fram" + "es.\r\n"); // // tb_AutoSavestateThreshold // this.tb_AutoSavestateThreshold.Location = new System.Drawing.Point(410, 158); this.tb_AutoSavestateThreshold.MaxLength = 5; this.tb_AutoSavestateThreshold.Name = "tb_AutoSavestateThreshold"; this.tb_AutoSavestateThreshold.Size = new System.Drawing.Size(41, 20); this.tb_AutoSavestateThreshold.TabIndex = 12; this.tb_AutoSavestateThreshold.Text = "500"; this.Tooltip_.SetToolTip(this.tb_AutoSavestateThreshold, "Automatically make a savestate every \'n\' frames.\r\nA larger value will consume les" + "s RAM, but leave gaps that will need to be re-emulated when loading earlier fram" + "es."); this.tb_AutoSavestateThreshold.TextChanged += new System.EventHandler(this.tb_AutoSavestateThreshold_TextChanged); this.tb_AutoSavestateThreshold.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tb_FilterForNumbers); // // cb_RecordInputs // this.cb_RecordInputs.AutoSize = true; this.cb_RecordInputs.Location = new System.Drawing.Point(409, 89); this.cb_RecordInputs.Name = "cb_RecordInputs"; this.cb_RecordInputs.Size = new System.Drawing.Size(93, 17); this.cb_RecordInputs.TabIndex = 14; this.cb_RecordInputs.Text = "Record Inputs"; this.Tooltip_.SetToolTip(this.cb_RecordInputs, "If checked, the inputs you provide will be recorded on the timeline.\r\nThis will o" + "verwrite older frames if you rewind."); this.cb_RecordInputs.UseVisualStyleBackColor = true; // // cb_player2 // this.cb_player2.AutoSize = true; this.cb_player2.Location = new System.Drawing.Point(508, 89); this.cb_player2.Name = "cb_player2"; this.cb_player2.Size = new System.Drawing.Size(64, 17); this.cb_player2.TabIndex = 15; this.cb_player2.Text = "Player 2"; this.Tooltip_.SetToolTip(this.cb_player2, "If checked, the inputs you provide will be recorded on the timeline.\r\nThis will o" + "verwrite older frames if you rewind."); this.cb_player2.UseVisualStyleBackColor = true; // // TriCTASTimeline // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(745, 743); this.Controls.Add(this.cb_player2); this.Controls.Add(this.cb_RecordInputs); this.Controls.Add(this.label_AutoSavestateThreshold); this.Controls.Add(this.tb_AutoSavestateThreshold); this.Controls.Add(this.label_TempSavestates); this.Controls.Add(this.tb_TempSavestates); this.Controls.Add(this.cb_SavestateEveryFrame); this.Controls.Add(this.tb_FollowDistance); this.Controls.Add(this.timelineScrollbar); this.Controls.Add(this.pb_Timeline); this.Controls.Add(this.cb_FollowCursor); this.Controls.Add(this.b_JumptoCursor); this.Controls.Add(this.b_play); this.Controls.Add(this.menuStrip1); this.Controls.Add(this.b_FrameBack); this.Controls.Add(this.b_FrameAdvance); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "TriCTASTimeline"; this.Text = "TAS Timeline"; ((System.ComponentModel.ISupportInitialize)(this.pb_Timeline)).EndInit(); this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); this.contextMenuStrip_Timeline.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.tb_FollowDistance)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.Button b_FrameAdvance; private System.Windows.Forms.VScrollBar timelineScrollbar; private System.Windows.Forms.Button b_FrameBack; private System.Windows.Forms.MenuStrip menuStrip1; private System.Windows.Forms.ToolStripMenuItem tASToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem loadTASToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem saveTASToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem exportTor08ToolStripMenuItem; private System.Windows.Forms.Button b_play; private System.Windows.Forms.ContextMenuStrip contextMenuStrip_Timeline; private System.Windows.Forms.ToolStripMenuItem deleteFrameToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem insertFrameToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem truncateMovieToolStripMenuItem; private System.Windows.Forms.Button b_JumptoCursor; private System.Windows.Forms.CheckBox cb_FollowCursor; private System.Windows.Forms.PictureBox pb_Timeline; private System.Windows.Forms.TrackBar tb_FollowDistance; private System.Windows.Forms.ToolStripMenuItem savestateThisFrameToolStripMenuItem; private System.Windows.Forms.CheckBox cb_SavestateEveryFrame; private System.Windows.Forms.TextBox tb_TempSavestates; private System.Windows.Forms.Label label_TempSavestates; private System.Windows.Forms.ToolTip Tooltip_; private System.Windows.Forms.Label label_AutoSavestateThreshold; private System.Windows.Forms.TextBox tb_AutoSavestateThreshold; private System.Windows.Forms.ToolStripMenuItem saveWithSavestatesToolStripMenuItem; private CheckBox cb_RecordInputs; private ToolStripMenuItem cellFrequencyToolStripMenuItem; private ToolStripMenuItem perVBlankToolStripMenuItem; private ToolStripMenuItem perControllerStrobeToolStripMenuItem; private CheckBox cb_player2; } } ================================================ FILE: forms/TriCTASTimeline.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Timers; using System.Windows.Forms; using static System.Windows.Forms.AxHost; namespace TriCNES { public partial class TriCTASTimeline : Form { public Font Font_Consolas; public Brush Brush_LeftColumn; public Brush Brush_LeftColumn_Saved; public Brush Brush_LeftColumn_TempSaved; public Brush Brush_HighlightedCell; public Brush Brush_WhiteCellP1; public Brush Brush_WhiteCellP2; public Brush Brush_GreenCellP1; public Brush Brush_GreenCellP2; public Brush Brush_GreenCellP1_Stale; public Brush Brush_GreenCellP2_Stale; public Brush Brush_RedCellP1; public Brush Brush_RedCellP2; public Brush Brush_RedCellP1_Stale; public Brush Brush_RedCellP2_Stale; struct Vector2 { public int x; public int y; public Vector2(int X, int Y) { x = X; y = Y; } public override string ToString() { return x.ToString() + ", " + y.ToString(); } } public struct TimelineCell { public bool Checked; public bool LagFrame; public bool Stale; public bool Emulated; public TimelineCell(bool t) { Checked = false; LagFrame = false; Stale = false; Emulated = false; } } public int SavestateLength; public int Rerecords; public TriCTASTimeline(TriCNESGUI Maingui) { MainGUI = Maingui; InitializeComponent(); Font_Consolas = new Font("Consolas", 8); Brush_LeftColumn = new SolidBrush(Color.LightGray); Brush_LeftColumn_Saved = new SolidBrush(Color.Wheat); Brush_LeftColumn_TempSaved = new SolidBrush(Color.LemonChiffon); Brush_HighlightedCell = new SolidBrush(Color.LightBlue); Brush_WhiteCellP1 = new SolidBrush(Color.WhiteSmoke); Brush_WhiteCellP2 = new SolidBrush(Color.FromArgb(255, 230, 230, 230)); Brush_GreenCellP1 = new SolidBrush(Color.FromArgb(255, 200, 240, 200)); Brush_GreenCellP2 = new SolidBrush(Color.FromArgb(255, 190, 232, 190)); Brush_GreenCellP1_Stale = new SolidBrush(Color.FromArgb(255, 205, 220, 205)); Brush_GreenCellP2_Stale = new SolidBrush(Color.FromArgb(255, 195, 215, 195)); Brush_RedCellP1 = new SolidBrush(Color.FromArgb(255, 240, 200, 200)); Brush_RedCellP2 = new SolidBrush(Color.FromArgb(255, 232, 190, 190)); Brush_RedCellP1_Stale = new SolidBrush(Color.FromArgb(255, 220, 205, 205)); Brush_RedCellP2_Stale = new SolidBrush(Color.FromArgb(255, 215, 195, 195)); Inputs = new List(); Start(); Inputs.Add(0); Resets.Add(false); timelineBitmap = new Bitmap(80 + 16 * 17 + 1, 41 * 16 + 1); G = Graphics.FromImage(timelineBitmap); pb_Timeline.Image = timelineBitmap; pb_Timeline.MouseDown += mouseDownEvent; pb_Timeline.MouseUp += mouseUpEvent; pb_Timeline.ContextMenuStrip = contextMenuStrip_Timeline; pb_Timeline.MouseWheel += mouseWheelEvent; timelineScrollbar.ValueChanged += timelineScrollbar_ValueChanged; //loop timer loopTimer = new System.Timers.Timer(); loopTimer.Interval = 50;// interval in milliseconds loopTimer.Enabled = false; loopTimer.Elapsed += loopTimerEvent; loopTimer.AutoReset = true; autosave = new System.Timers.Timer(); autosave.Interval = 60000; autosave.Elapsed += autosaveEvent; autosave.AutoReset = true; autosave.Enabled = true; Shown += TriCTASTimeline_Shown; } private void mouseWheelEvent(object sender, MouseEventArgs e) { int result = timelineScrollbar.Value - Math.Sign(e.Delta); if (result > timelineScrollbar.Maximum - timelineScrollbar.LargeChange + 1) { result = timelineScrollbar.Maximum - timelineScrollbar.LargeChange + 1; } else if (result < 0) { result = 0; } timelineScrollbar.Value = result; } void TriCTASTimeline_Shown(object sender, EventArgs e) { RefreshTopOfTimeline(); } void RefreshTopOfTimeline() { Rectangle[] GridOverlay = new Rectangle[] { new Rectangle(0,0,80,16), new Rectangle(80 + 16*0,0,16,16), new Rectangle(80 + 16*1,0,16,16), new Rectangle(80 + 16*2,0,16,16), new Rectangle(80 + 16*3,0,16,16), new Rectangle(80 + 16*4,0,16,16), new Rectangle(80 + 16*5,0,16,16), new Rectangle(80 + 16*6,0,16,16), new Rectangle(80 + 16*7,0,16,16), new Rectangle(80 + 16*8,0,16,16), new Rectangle(80 + 16*9,0,16,16), new Rectangle(80 + 16*10,0,16,16), new Rectangle(80 + 16*11,0,16,16), new Rectangle(80 + 16*12,0,16,16), new Rectangle(80 + 16*13,0,16,16), new Rectangle(80 + 16*14,0,16,16), new Rectangle(80 + 16*15,0,16,16), new Rectangle(80 + 16*16,0,16,16) }; G.FillRectangle(Brush_LeftColumn, new Rectangle(0, 0, 80 + 16 * 17, 16)); G.DrawRectangles(Pens.Black, GridOverlay); G.DrawString(ClockFiltering ? "Input #" : "Frame #", Font_Consolas, Brushes.Black, 0, 0); G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 0, 0); G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 1, 0); G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 2, 0); G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 3, 0); G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 4, 0); G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 5, 0); G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 6, 0); G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 7, 0); G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 8, 0); G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 9, 0); G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 10, 0); G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 11, 0); G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 12, 0); G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 13, 0); G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 14, 0); G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 15, 0); G.DrawString("r", Font_Consolas, Brushes.Black, 80 + 16 * 16, 0); RefreshTimeline(); } public Graphics G; public Bitmap timelineBitmap; private static System.Timers.Timer loopTimer; private static System.Timers.Timer autosave; private void loopTimerEvent(Object source, ElapsedEventArgs e) { //this does whatever you want to happen while clicking on the button MainGUI.Timeline_PendingMouseHeld = true; } private void autosaveEvent(Object source, ElapsedEventArgs e) { if (Inputs.Count > 1000) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } InitDirectory += "autosave.3c2"; FileStream fs = File.OpenWrite(InitDirectory); for (int i = 0; i < Inputs.Count; i++) { fs.WriteByte((byte)Inputs[i]); fs.WriteByte((byte)(Inputs[i] >> 8)); } fs.Close(); } } public void TimelineMouseHeldEvent() { MethodInvoker upd = delegate { int mouseX = MousePosition.X - Left - pb_Timeline.Left - 8; int mouseY = MousePosition.Y - Top - pb_Timeline.Top - 48; int Column = mouseX >= 80 ? (mouseX - 80) / 16 : -1; if (Column > 15) { Column = 15; } int Row = mouseY >= 0 ? mouseY / 16 : -1; if (Row > 39) { Row = 39; } Vector2 mousePos = new Vector2(Column, Row); if (mouseHeld_initPos.y >= 0) { if (mouseHeld_initPos.x >= 0) { // we clicked on a cell int spos = Math.Min(mousePos.y, mouseHeld_initPos.y); int tpos = Math.Max(mousePos.y, mouseHeld_initPos.y); if (spos < 0) { spos = 0; } for (int i = spos; i <= tpos; i++) { if (mouseHeld_initPos.x == 16) { TimelineGrid[i][17].Checked = mouseHeld_setInput; RedrawTimelineRow(i, false); int frame = i + TopFrame; while (frame >= Inputs.Count) { Inputs.Add(0); Resets.Add(false); } if (Inputs.Count + 39 > timelineScrollbar.Maximum) { timelineScrollbar.Maximum = Inputs.Count + 38; } Resets[frame] = mouseHeld_setInput; } else { bool state = GetCellInputStatus(new Vector2(mouseHeld_initPos.x, i)); if (state != mouseHeld_setInput) { ushort input = SetCellInputStatus(new Vector2(mouseHeld_initPos.x, i), mouseHeld_setInput); TimelineGrid[i][mouseHeld_initPos.x + 1].Checked = mouseHeld_setInput; RecalculateTimelineRow(i, input); RedrawTimelineRow(i, false); } } } int checkStale = spos + TopFrame; if (checkStale < frameEmulated) { MarkStale(checkStale); } } else { // we clicked on the frame number. } } }; this.Invoke(upd); } public int TopFrame = 0; Vector2 mouseHeld_initPos; bool mouseHeld_setInput; private void mouseDownEvent(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { return; } loopTimer.Enabled = true; int mouseX = MousePosition.X - Left - pb_Timeline.Left - 8; int mouseY = MousePosition.Y - Top - pb_Timeline.Top - 48; int Column = mouseX >= 80 ? (mouseX - 80) / 16 : -1; if (Column > 16) { Column = 16; } int Row = mouseY >= 0 ? mouseY / 16 : -1; if (Row > 39) { Row = 39; } mouseHeld_initPos = new Vector2(Column, Row); MainGUI.Timeline_PendingMouseDown = true; } public void TimelineMouseDownEvent() { MethodInvoker upd = delegate { if (mouseHeld_initPos.y >= 0) { if (mouseHeld_initPos.x >= 0) { // we clicked on a cell mouseHeld_setInput = !GetCellInputStatus(mouseHeld_initPos); int frameClicked = TopFrame + mouseHeld_initPos.y; if (mouseHeld_initPos.x == 16) { TimelineGrid[mouseHeld_initPos.y][17].Checked = mouseHeld_setInput; RedrawTimelineRow(mouseHeld_initPos.y, false); while (frameClicked >= Inputs.Count) { Inputs.Add(0); Resets.Add(false); } if (Inputs.Count + 39 > timelineScrollbar.Maximum) { timelineScrollbar.Maximum = Inputs.Count + 38; } Resets[frameClicked] = mouseHeld_setInput; } else { // calculate if that cell had an input or not. ushort input = SetCellInputStatus(mouseHeld_initPos, mouseHeld_setInput); TimelineGrid[mouseHeld_initPos.y][mouseHeld_initPos.x + 1].Checked = mouseHeld_setInput; RecalculateTimelineRow(mouseHeld_initPos.y, input); RedrawTimelineRow(mouseHeld_initPos.y, false); } if (frameClicked < frameEmulated) { MarkStale(frameClicked); } } else { // we clicked on the frame number. // TODO: Move the cursor to this frame. // - If there exists a savestate for this frame, then simply laod it. // - Otherwise, we haven't seen this frame yet. Load the last savestate in the list, then emulate to this frame. int frameClicked = TopFrame + mouseHeld_initPos.y; if (frameClicked == frameIndex) { return; // we're already on that frame! } if (frameClicked < frameEmulated && (TimelineSavestates[frameClicked].Count == SavestateLength || TimelineTempSavestates[frameClicked].Count == SavestateLength)) { int prevrow = frameIndex - TopFrame; frameIndex = frameClicked - 1; // and we're good to go. if (frameIndex == -1) { frameIndex = 0; MainGUI.Timeline_LoadState = TimelineSavestates[0]; MainGUI.Timeline_PendingLoadState = true; MainGUI.Timeline_PendingFrameNumber = 0; UpdateTimelineRowStatus(prevrow); RedrawTimelineRow(prevrow, false); UpdateTimelineRowStatus(0); RedrawTimelineRow(0, false); MainGUI.Timeline_PendingResetScreen = true; } else { MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex MainGUI.Timeline_PendingLoadState = true; MainGUI.Timeline_PendingFrameNumber = frameIndex; MainGUI.Timeline_PendingFrameAdvance = true; UpdateTimelineRowStatus(prevrow); RedrawTimelineRow(prevrow, false); } } else { int row = frameIndex - TopFrame; int prevFrame = frameIndex; frameIndex = frameClicked; if (frameIndex > frameEmulated) { frameIndex = frameEmulated; } UpdateTimelineRowStatus(row); RedrawTimelineRow(row, false); if (frameIndex < 0) { frameIndex = 0; } else { bool Unneeded = false; MainGUI.Timeline_LoadState = GrabMostRecentSavestate(prevFrame, out Unneeded); // This function updates frameIndex if (!Unneeded) { MainGUI.Timeline_PendingLoadState = true; MainGUI.Timeline_PendingFrameNumber = frameIndex; } } // Now we need to emulate all the way until we reach the target frame. MainGUI.Timeline_AutoPlayUntilTarget = true; MainGUI.Timeline_AutoPlayTarget = frameClicked; row = frameIndex - TopFrame; UpdateTimelineRowStatus(row); RedrawTimelineRow(row, false); if (frameIndex > frameEmulated) { frameEmulated = frameIndex; } if (frameIndex > highestFrameEmulatedEver) { highestFrameEmulatedEver = frameIndex; } while (frameIndex >= Inputs.Count) { Inputs.Add(0); Resets.Add(false); } if (Inputs.Count + 39 > timelineScrollbar.Maximum) { timelineScrollbar.Maximum = Inputs.Count + 38; } } } } }; this.Invoke(upd); // Use delegates here because this could otherwise modify the state of the timeline mid-rendering the timeline. (causing runtime errors) } public void MarkStale(int Frame) { frameEmulated = Frame; // mark everything after this as "stale" if (frameIndex >= Frame) { Rerecords++; frameIndex = Frame; TimelineSavestates.RemoveRange(frameIndex + 1, TimelineSavestates.Count - (frameIndex + 1)); TimelineTempSavestates.RemoveRange(frameIndex + 1, TimelineTempSavestates.Count - (frameIndex + 1)); //TEMPRerecordTracker.RemoveRange(frameIndex + 1, TEMPRerecordTracker.Count - (frameIndex + 1)); MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex System.GC.Collect(); MainGUI.Timeline_PendingLoadState = true; MainGUI.Timeline_PendingFrameNumber = frameIndex; } RefreshTimeline(); } private void mouseUpEvent(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { return; } loopTimer.Enabled = false; } bool GetCellInputStatus(Vector2 pos) { if (pos.x < 0 || pos.y < 0) { return false; // should never happen } int frame = pos.y + TopFrame; // get frame index if (frame >= Inputs.Count) { return false; } if (pos.x == 16) { return Resets[frame]; } int shift = (7 - (pos.x & 7)) | (pos.x & 8); return ((Inputs[frame] >> shift) & 1) == 1; } ushort SetCellInputStatus(Vector2 pos, bool state) { if (pos.x < 0 || pos.y < 0) { return 0; // should never happen } // get frame index int frame = pos.y + TopFrame; int shift = (7 - (pos.x & 7)) | (pos.x & 8); int prevInputs = Inputs.Count; while (frame >= Inputs.Count) { // Add new inputs until you reach this frame if (state) { //Inputs.Add((ushort)(1 << shift)); Inputs.Add(0); Resets.Add(false); } else { Inputs.Add(0); Resets.Add(false); } } if ((Inputs.Count > prevInputs)) { timelineScrollbar.Maximum = Inputs.Count + 38; } if (state) { Inputs[frame] |= (ushort)(1 << shift); } else { Inputs[frame] &= (ushort)(0xFFFF ^ (1 << shift)); } return Inputs[frame]; } public TriCNESGUI MainGUI; public static List LagFrames; public static List TimelineGrid; public static List> TimelineSavestates; public static List> TimelineTempSavestates; public static List TEMPRerecordTracker; public static List Resets; public static List Inputs; // high byte = controller 2 public int frameIndex; public int highestFrameEmulatedEver; // highest emulated frame public int frameEmulated; // highest emulated frame for the purposes of tracking stale frames int AutoSavestateThreshold = 500; int TempSavestates = 120; public void Start() { TimelineGrid = new List(); TimelineSavestates = new List>(); TimelineTempSavestates = new List>(); LagFrames = new List(); Resets = new List(); //TEMPRerecordTracker = new List(); for (int i = 0; i < 40; i++) { TimelineCell[] t = new TimelineCell[18]; for (int j = 0; j < t.Length; j++) { t[0] = new TimelineCell(false); } TimelineGrid.Add(t); } Rerecords = 0; frameIndex = 0; timelineScrollbar.Maximum = Inputs.Count + 38; timelineScrollbar.LargeChange = 40; MainGUI.CreateTASTimelineEmulator(); List state = MainGUI.EMU.SaveState(); TimelineSavestates.Add(state); TimelineTempSavestates.Add(new List()); SavestateLength = state.Count; //TEMPRerecordTracker.Add(Rerecords); } int ScrollbarValue = 0; private void timelineScrollbar_ValueChanged(object sender, EventArgs e) { TopFrame = timelineScrollbar.Value; if (TopFrame != ScrollbarValue) { ScrollbarValue = TopFrame; RefreshTimeline(); } } private void b_FrameAdvance_Click(object sender, EventArgs e) { MainGUI.Timeline_PendingFrameAdvance = true; } private void b_FrameBack_Click(object sender, EventArgs e) { FrameRewind(); } public void FrameRewind() { int row = frameIndex - TopFrame; int rowm1 = row - 1; frameIndex -= 2; if (frameIndex < 0) { frameIndex = 0; } UpdateTimelineRowStatus(row); RedrawTimelineRow(row, false); MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex MainGUI.Timeline_PendingLoadState = true; MainGUI.Timeline_PendingFrameNumber = frameIndex; if (frameIndex == 0) { UpdateTimelineRowStatus(0); RedrawTimelineRow(0, false); MainGUI.Timeline_PendingResetScreen = true; } else { MainGUI.Timeline_PendingFrameNumber = frameIndex; MainGUI.Timeline_PendingFrameAdvance = true; } if (frameIndex != rowm1) { MainGUI.Timeline_AutoPlayUntilTarget = true; MainGUI.Timeline_AutoPlayTarget = rowm1; } } public void FrameAdvance() { frameIndex++; if (frameIndex > LagFrames.Count) { LagFrames.Add(MainGUI.EMU.LagFrame); } else { LagFrames[frameIndex - 1] = MainGUI.EMU.LagFrame; } if (frameIndex > Resets.Count) { Resets.Add(false); } if (frameIndex > frameEmulated) { frameEmulated = frameIndex; } if (frameIndex > highestFrameEmulatedEver) { highestFrameEmulatedEver = frameIndex; } if (frameIndex == TimelineSavestates.Count) { // create a savestate for the previous frame. if (cb_SavestateEveryFrame.Checked || frameIndex % AutoSavestateThreshold == 0) { List state = MainGUI.EMU.SaveState(); TimelineSavestates.Add(state); } else { List state = new List(); TimelineSavestates.Add(state); } if (TimelineSavestates[frameIndex].Count > 0) { // if this savestate is not empty List state = new List(); TimelineTempSavestates.Add(state); } else { // if this savestate is empty List state = MainGUI.EMU.SaveState(); TimelineTempSavestates.Add(state); } //TEMPRerecordTracker.Add(Rerecords); } else if (TimelineSavestates[frameIndex].Count != SavestateLength && cb_SavestateEveryFrame.Checked) { List state = MainGUI.EMU.SaveState(); TimelineSavestates[frameIndex] = state; //TEMPRerecordTracker[frameIndex] = Rerecords; } TrimTempSavestates(); while (frameIndex >= Inputs.Count) { Inputs.Add(0); Resets.Add(false); MethodInvoker upd = delegate { timelineScrollbar.Maximum = Inputs.Count + 38; }; this.BeginInvoke(upd); } int row = frameIndex - TopFrame; bool didFullRefresh = false; if (cb_FollowCursor.Checked) { if (row >= TimelineGrid.Count || row > FollowDistance) { TopFrame = frameIndex - FollowDistance; if (TopFrame != ScrollbarValue && TopFrame >= 0) { didFullRefresh = true; RefreshTimeline(); MethodInvoker upd = delegate { ScrollbarValue = TopFrame; timelineScrollbar.Value = TopFrame; }; this.Invoke(upd); } } } if (!didFullRefresh) { UpdateTimelineRowStatus(row - 1); RedrawTimelineRow(row - 1, false); UpdateTimelineRowStatus(row); RedrawTimelineRow(row, false); } } private void loadTASToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } OpenFileDialog ofd = new OpenFileDialog() { FileName = "", Filter = "TriCNES TAS File (.3c2, .3c3)|*.3c2;*.3c3" + "|Bizhawk Movie (.bk2)|*.bk2" + "|Bizhawk TAStudio (.tasproj)|*.tasproj" + "|FCEUX Movie (.fm2)|*.fm2" + "|FCEUX TAS Editor (.fm3)|*.fm3" + "|Famtastia Movie (.fmv)|*.fmv" + "|Replay Device (.r08)|*.r08" + "|All TAS Files (.3c2, .3c3, .bk2, .tasproj, .fm2, .fm3, .fmv, .r08)|*.3c2;*.3c3;*.bk2;*.tasproj;*.fm2;*.fm3;*.fmv;*.r08", Title = "Select file", InitialDirectory = InitDirectory }; if (ofd.ShowDialog() == DialogResult.OK) { frameIndex = 0; Inputs = MainGUI.ParseTasFile(ofd.FileName, out Resets); string extension = Path.GetExtension(ofd.FileName); timelineScrollbar.Maximum = Inputs.Count + 38; MainGUI.Timeline_PendingHardReset = true; if (extension != ".3c3") { LagFrames = new List(); TimelineSavestates = new List>(); TimelineTempSavestates = new List>(); // savestates are initialized in the Timeline_PendingArbitrarySavestate MainGUI.Timeline_PendingArbitrarySavestate = true; ScrollbarValue = 0; timelineScrollbar.Value = 0; TopFrame = 0; highestFrameEmulatedEver = 0; frameEmulated = 0; } if (extension == ".3c2") { byte[] b = File.ReadAllBytes(ofd.FileName); // Terribly inefficient to load the entire file a second time, but whatever. ClockFiltering = (b[0] & 1) == 1; } if (extension == ".3c3") { byte[] b = File.ReadAllBytes(ofd.FileName); // Terribly inefficient to load the entire file a second time, but whatever. ClockFiltering = (b[15] & 1) == 1; } RefreshTimeline(); GC.Collect(); } } private void saveTASToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } SaveFileDialog sfd = new SaveFileDialog() { FileName = "", Filter = "TriCNES TAS File (.3c2)|*.3c2", Title = "Save a .3c2 TAS File", InitialDirectory = InitDirectory }; sfd.ShowDialog(); if (sfd.FileName != "") { FileStream fs = (FileStream)sfd.OpenFile(); // Determine if controller 2 is used. bool UseController2 = false; for (int i = 0; i < Inputs.Count; i++) { if ((Inputs[i] & 0xFF00) != 0) { UseController2 = true; break; } } // Determine if the RESET button is used. bool UseResets = false; for (int i = 0; i < Inputs.Count; i++) { if (i < Resets.Count && Resets[i]) { UseResets = true; break; } } byte Header = 0; Header |= (byte)(ClockFiltering ? 1 : 0); Header |= (byte)(UseController2 ? 2 : 0); Header |= (byte)(UseResets ? 4 : 0); fs.WriteByte(Header); for (int i = 0; i < Inputs.Count; i++) { fs.WriteByte((byte)Inputs[i]); if (UseController2) { fs.WriteByte((byte)(Inputs[i] >> 8)); } if (UseResets) { fs.WriteByte((byte)(Resets[i] ? 0x80 : 0)); } } fs.Close(); } } private void saveWithSavestatesToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } SaveFileDialog sfd = new SaveFileDialog() { FileName = "", Filter = "TriCNES TAS File (.3c3)|*.3c3", Title = "Save a .3c3 TAS File", InitialDirectory = InitDirectory }; sfd.ShowDialog(); if (sfd.FileName != "") { FileStream fs = (FileStream)sfd.OpenFile(); // save the length of the savestates fs.WriteByte((byte)SavestateLength); fs.WriteByte((byte)(SavestateLength >> 8)); fs.WriteByte((byte)(SavestateLength >> 16)); fs.WriteByte((byte)(SavestateLength >> 24)); // save the number of rerecords. (I personally don't care about this statistic, but other people do, so I'll add it!) fs.WriteByte((byte)Rerecords); fs.WriteByte((byte)(Rerecords >> 8)); fs.WriteByte((byte)(Rerecords >> 16)); fs.WriteByte((byte)(Rerecords >> 24)); // save the length of the TAS fs.WriteByte((byte)Inputs.Count); fs.WriteByte((byte)(Inputs.Count >> 8)); fs.WriteByte((byte)(Inputs.Count >> 16)); fs.WriteByte((byte)(Inputs.Count >> 24)); fs.WriteByte(0); // 3 currently unused bytes. fs.WriteByte(0); fs.WriteByte(0); // Determine if controller 2 is used. bool UseController2 = false; for (int i = 0; i < Inputs.Count; i++) { if ((Inputs[i] & 0xFF00) != 0) { UseController2 = true; break; } } // Determine if the RESET button is used. bool UseResets = false; for (int i = 0; i < Inputs.Count; i++) { if (i < Resets.Count && Resets[i]) { UseResets = true; break; } } byte Header15 = 0; Header15 |= (byte)(ClockFiltering ? 1 : 0); Header15 |= (byte)(UseController2 ? 2 : 0); Header15 |= (byte)(UseResets ? 4 : 0); fs.WriteByte(Header15); for (int i = 0; i < Inputs.Count; i++) { fs.WriteByte((byte)Inputs[i]); if (UseController2) { fs.WriteByte((byte)(Inputs[i] >> 8)); } if (UseResets) { fs.WriteByte((byte)((Resets[i] ? 0x80 : 0) | (i < LagFrames.Count ? (LagFrames[i] ? 1 : 0) : 0))); } } for (int i = 0; i < TimelineSavestates.Count; i++) { if (TimelineSavestates[i].Count != 0) { // save the frame index fs.WriteByte((byte)i); fs.WriteByte((byte)(i >> 8)); fs.WriteByte((byte)(i >> 16)); fs.WriteByte((byte)(i >> 24)); // save every byte of the savestate for (int j = 0; j < SavestateLength; j++) { fs.WriteByte(TimelineSavestates[i][j]); } } } fs.Close(); } } private void exportTor08ToolStripMenuItem_Click(object sender, EventArgs e) { string InitDirectory = AppDomain.CurrentDomain.BaseDirectory; if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\")) { InitDirectory += @"tas\"; } SaveFileDialog sfd = new SaveFileDialog() { FileName = "", Filter = "Replay Device (.r08)|*.r08", Title = "Save a .r08 TAS File", InitialDirectory = InitDirectory }; sfd.ShowDialog(); if (sfd.FileName != "") { FileStream fs = (FileStream)sfd.OpenFile(); if (ClockFiltering) { // the .r08 file format is identical to my input list. for (int i = 0; i < Inputs.Count; i++) { fs.WriteByte((byte)Inputs[i]); fs.WriteByte((byte)(Inputs[i] >> 8)); } } else { if (LagFrames.Count < Inputs.Count) { MessageBox.Show("The .r08 exporter needs to know which frames are lag frames.\nOnly frames that have been emulated on the timeline will be exported."); } for (int i = 0; i < Inputs.Count; i++) { // the .r08 file format doesn't include lag frames. if (i < LagFrames.Count && !LagFrames[i]) { fs.WriteByte((byte)Inputs[i]); fs.WriteByte((byte)(Inputs[i] >> 8)); } } } fs.Close(); } } bool Paused = true; private void b_play_Click(object sender, EventArgs e) { Paused = !Paused; b_play.Text = Paused ? "Paused" : "Running"; MainGUI.Timeline_PendingResume = !Paused; MainGUI.Timeline_PendingPause = Paused; if (Paused) { GC.Collect(); } } private void deleteFrameToolStripMenuItem_Click(object sender, EventArgs e) { Inputs.RemoveAt(frameIndex); int HighlightedFrame = frameIndex; if (HighlightedFrame < frameEmulated) { MarkStale(HighlightedFrame); } timelineScrollbar.Maximum = Inputs.Count + 38; RefreshTimeline(); } private void insertFrameToolStripMenuItem_Click(object sender, EventArgs e) { Inputs.Insert(frameIndex, 0); int HighlightedFrame = frameIndex; if (HighlightedFrame < frameEmulated) { MarkStale(HighlightedFrame); } timelineScrollbar.Maximum = Inputs.Count + 38; RefreshTimeline(); } public bool Player2() { return cb_player2.Checked; } private void truncateMovieToolStripMenuItem_Click(object sender, EventArgs e) { Inputs.RemoveRange(frameIndex + 1, Inputs.Count - (frameIndex + 1)); if (TimelineSavestates.Count > frameIndex) { TimelineSavestates.RemoveRange(frameIndex + 1, TimelineSavestates.Count - (frameIndex + 1)); } if (TimelineTempSavestates.Count > frameIndex) { TimelineTempSavestates.RemoveRange(frameIndex + 1, TimelineTempSavestates.Count - (frameIndex + 1)); } if (LagFrames.Count > frameIndex) { LagFrames.RemoveRange(frameIndex, LagFrames.Count - (frameIndex)); } System.GC.Collect(); highestFrameEmulatedEver = frameIndex; frameEmulated = frameIndex; timelineScrollbar.Maximum = Inputs.Count + 38; RefreshTimeline(); } private void b_JumptoCursor_Click(object sender, EventArgs e) { int scroll = frameIndex - FollowDistance; if (scroll < 0) { scroll = 0; } timelineScrollbar.Value = scroll; TopFrame = scroll; RefreshTimeline(); } public void RefreshTimeline() { // redraw the entire timeline's bitmap. for (int i = 0; i < 40; i++) { int frame = TopFrame + i; RecalculateTimelineRow(i, frame < Inputs.Count ? Inputs[frame] : (ushort)0); TimelineGrid[i][17].Checked = false; if (frame >= 0 && frame < Resets.Count) { TimelineGrid[i][17].Checked = Resets[frame]; } RedrawTimelineRow(i, true); } MethodInvoker upd = delegate { pb_Timeline.Image = timelineBitmap; pb_Timeline.Update(); }; this.Invoke(upd); } public void RedrawTimelineRow(int row, bool batch) { if (row < 0 || row >= TimelineGrid.Count) { return; } MethodInvoker upd = delegate { int rowp1 = row + 1; Rectangle[] GridOverlay = new Rectangle[] { new Rectangle(0,rowp1*16,80,16), new Rectangle(80 + 16*0,rowp1*16,16,16), new Rectangle(80 + 16*1,rowp1*16,16,16), new Rectangle(80 + 16*2,rowp1*16,16,16), new Rectangle(80 + 16*3,rowp1*16,16,16), new Rectangle(80 + 16*4,rowp1*16,16,16), new Rectangle(80 + 16*5,rowp1*16,16,16), new Rectangle(80 + 16*6,rowp1*16,16,16), new Rectangle(80 + 16*7,rowp1*16,16,16), new Rectangle(80 + 16*8,rowp1*16,16,16), new Rectangle(80 + 16*9,rowp1*16,16,16), new Rectangle(80 + 16*10,rowp1*16,16,16), new Rectangle(80 + 16*11,rowp1*16,16,16), new Rectangle(80 + 16*12,rowp1*16,16,16), new Rectangle(80 + 16*13,rowp1*16,16,16), new Rectangle(80 + 16*14,rowp1*16,16,16), new Rectangle(80 + 16*15,rowp1*16,16,16), new Rectangle(80 + 16*16,rowp1*16,16,16) }; if (frameIndex - TopFrame == row) { G.FillRectangle(Brush_HighlightedCell, new Rectangle(0, rowp1 * 16, 80 + 16 * 17, 16)); } else if (!TimelineGrid[row][0].Emulated) { G.FillRectangle(Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16)); G.FillRectangle(Brush_WhiteCellP1, new Rectangle(80, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(Brush_WhiteCellP2, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(Brush_WhiteCellP1, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16)); } else if (TimelineGrid[row][0].Stale) { G.FillRectangle(Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1_Stale : Brush_GreenCellP1_Stale, new Rectangle(80, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP2_Stale : Brush_GreenCellP2_Stale, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1_Stale : Brush_GreenCellP1_Stale, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16)); } else { G.FillRectangle(TimelineSavestates[row + TopFrame].Count == SavestateLength ? Brush_LeftColumn_Saved : TimelineTempSavestates[row + TopFrame].Count == SavestateLength ? Brush_LeftColumn_TempSaved : Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1 : Brush_GreenCellP1, new Rectangle(80, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP2 : Brush_GreenCellP2, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16)); G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1 : Brush_GreenCellP1, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16)); } G.DrawRectangles(Pens.Black, GridOverlay); string rownum = (row + TopFrame).ToString(); //if(row + TopFrame < TEMPRerecordTracker.Count) //{ // rownum += " (" + TEMPRerecordTracker[row + TopFrame].ToString() + ")"; //} G.DrawString(rownum, Font_Consolas, Brushes.Black, 0, rowp1 * 16); if (TimelineGrid[row][1].Checked) { G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 0, rowp1 * 16); } if (TimelineGrid[row][2].Checked) { G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 1, rowp1 * 16); } if (TimelineGrid[row][3].Checked) { G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 2, rowp1 * 16); } if (TimelineGrid[row][4].Checked) { G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 3, rowp1 * 16); } if (TimelineGrid[row][5].Checked) { G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 4, rowp1 * 16); } if (TimelineGrid[row][6].Checked) { G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 5, rowp1 * 16); } if (TimelineGrid[row][7].Checked) { G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 6, rowp1 * 16); } if (TimelineGrid[row][8].Checked) { G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 7, rowp1 * 16); } if (TimelineGrid[row][9].Checked) { G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 8, rowp1 * 16); } if (TimelineGrid[row][10].Checked) { G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 9, rowp1 * 16); } if (TimelineGrid[row][11].Checked) { G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 10, rowp1 * 16); } if (TimelineGrid[row][12].Checked) { G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 11, rowp1 * 16); } if (TimelineGrid[row][13].Checked) { G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 12, rowp1 * 16); } if (TimelineGrid[row][14].Checked) { G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 13, rowp1 * 16); } if (TimelineGrid[row][15].Checked) { G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 14, rowp1 * 16); } if (TimelineGrid[row][16].Checked) { G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 15, rowp1 * 16); } if (TimelineGrid[row][17].Checked) { G.DrawString("r", Font_Consolas, Brushes.Black, 80 + 16 * 16, rowp1 * 16); } if (!batch) { pb_Timeline.Image = timelineBitmap; pb_Timeline.Update(); } }; this.BeginInvoke(upd); } public void RecalculateTimelineRow(int row, ushort input) { // redraw one row of the timeline if (row < 0 || row >= TimelineGrid.Count) { return; } TimelineGrid[row][8].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][7].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][6].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][5].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][4].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][3].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][2].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][1].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][16].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][15].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][14].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][13].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][12].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][11].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][10].Checked = (input & 1) == 1; input >>= 1; TimelineGrid[row][9].Checked = (input & 1) == 1; UpdateTimelineRowStatus(row); } public void UpdateTimelineRowStatus(int row) { if (row < 0 || row >= TimelineGrid.Count) { return; } // redraw one row of the timeline int frame = (TopFrame + row); bool Emulated = frame < highestFrameEmulatedEver; bool Stale = Emulated && (frame > frameEmulated); bool LagFrame = false; if (LagFrames.Count > frame) { LagFrame = Emulated && (LagFrames[frame]); } TimelineGrid[row][0].Emulated = Emulated; TimelineGrid[row][0].Stale = Stale; TimelineGrid[row][0].LagFrame = LagFrame; } int FollowDistance = 20; private void tb_FollowDistance_Scroll(object sender, EventArgs e) { FollowDistance = tb_FollowDistance.Value; } public bool SavestateEveryFrame() { return cb_SavestateEveryFrame.Checked; } public bool RecordInputs() { return cb_RecordInputs.Checked; } List GrabMostRecentSavestate() { int l = TimelineSavestates[frameIndex].Count; if (l != SavestateLength) { l = TimelineTempSavestates[frameIndex].Count; if (l == SavestateLength) { return TimelineTempSavestates[frameIndex]; } } bool temp = false; while (l != SavestateLength) { frameIndex--; l = TimelineSavestates[frameIndex].Count; if (l != SavestateLength) { l = TimelineTempSavestates[frameIndex].Count; if (l == SavestateLength) { temp = true; break; } } } return temp ? TimelineTempSavestates[frameIndex] : TimelineSavestates[frameIndex]; } List GrabMostRecentSavestate(int TargetFrame, out bool HitTargetFrame) { if (frameIndex == TargetFrame) { HitTargetFrame = true; return null; } bool temp = false; int l = TimelineSavestates[frameIndex].Count; if (l != SavestateLength) { l = TimelineTempSavestates[frameIndex].Count; if (l == SavestateLength) { HitTargetFrame = false; return TimelineTempSavestates[frameIndex]; } } while (l != SavestateLength) { frameIndex--; if (frameIndex == TargetFrame) { HitTargetFrame = true; return null; } l = TimelineSavestates[frameIndex].Count; if (l != SavestateLength) { l = TimelineTempSavestates[frameIndex].Count; if (l == SavestateLength) { temp = true; break; } } } HitTargetFrame = false; return temp ? TimelineTempSavestates[frameIndex] : TimelineSavestates[frameIndex]; } private void savestateThisFrameToolStripMenuItem_Click(object sender, EventArgs e) { if (frameIndex == TimelineSavestates.Count) { List state = MainGUI.EMU.SaveState(); TimelineSavestates.Add(state); if (TimelineTempSavestates[frameIndex].Count > 0) { TimelineTempSavestates[frameIndex] = new List(); // remove temp savestate for this frame } } else { List state = MainGUI.EMU.SaveState(); TimelineSavestates[frameIndex] = state; if (TimelineTempSavestates[frameIndex].Count > 0) { TimelineTempSavestates[frameIndex] = new List(); // remove temp savestate for this frame } GC.Collect(); } } public void TrimTempSavestates() { int deletion = frameIndex - TempSavestates; if (deletion >= 0 && deletion < TimelineTempSavestates.Count && TimelineTempSavestates[deletion].Count > 0) { TimelineTempSavestates[deletion] = new List(); // remove temp savestate for this frame int row = deletion - TopFrame; if (row >= 0 && row < 40) { UpdateTimelineRowStatus(row); RedrawTimelineRow(row, false); } } } private void tb_FilterForNumbers(object sender, KeyPressEventArgs e) { if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar)) { e.Handled = true; } } private void tb_AutoSavestateThreshold_TextChanged(object sender, EventArgs e) { int i = 0; if (int.TryParse(tb_AutoSavestateThreshold.Text, out i)) { AutoSavestateThreshold = i; } } private void tb_TempSavestates_TextChanged(object sender, EventArgs e) { int i = 0; if (int.TryParse(tb_TempSavestates.Text, out i)) { TempSavestates = i; } } public void ChangePlayPauseButtonText(string str) { MethodInvoker upd = delegate { b_play.Text = str; b_play.Update(); }; this.Invoke(upd); } public bool ClockFiltering; private void perVBlankToolStripMenuItem_Click(object sender, EventArgs e) { perVBlankToolStripMenuItem.Checked = true; perControllerStrobeToolStripMenuItem.Checked = false; ClockFiltering = false; // reset the TAS and mark everything as stale! ResetTASAndMarkEverythingStale(); MainGUI.Timeline_PendingClockFiltering = false; RefreshTopOfTimeline(); } private void perControllerStrobeToolStripMenuItem_Click(object sender, EventArgs e) { perVBlankToolStripMenuItem.Checked = false; perControllerStrobeToolStripMenuItem.Checked = true; ClockFiltering = true; ResetTASAndMarkEverythingStale(); MainGUI.Timeline_PendingClockFiltering = true; RefreshTopOfTimeline(); } void ResetTASAndMarkEverythingStale() { frameIndex = 0; timelineScrollbar.Maximum = Inputs.Count + 38; MainGUI.Timeline_PendingHardReset = true; LagFrames = new List(); TimelineSavestates = new List>(); TimelineTempSavestates = new List>(); // savestates are initialized in the Timeline_PendingArbitrarySavestate MainGUI.Timeline_PendingArbitrarySavestate = true; ScrollbarValue = 0; timelineScrollbar.Value = 0; TopFrame = 0; highestFrameEmulatedEver = 0; frameEmulated = 0; RefreshTimeline(); } } } ================================================ FILE: forms/TriCTASTimeline.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17, 17 True 132, 17 331, 17 In order to move backwards on the timeline, the emulator needs to keep savestetes for earlier frames. Savestates require copies of every variable in the emulator, and can be a bit RAM consuming. These temporary savestates will record the previous 'n' frames before being deleted, allowing you to fix mistakes in the previous 'n' frames. You could also choose to create a savestate on every frame if you would prefer. In order to move backwards on the timeline, the emulator needs to keep savestetes for earlier frames. Savestates require copies of every variable in the emulator, and can be a bit RAM consuming. These temporary savestates will record the previous 'n' frames before being deleted, allowing you to fix mistakes in the previous 'n' frames. You could also choose to create a savestate on every frame if you would prefer. AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: forms/TriCTraceLogger.Designer.cs ================================================ namespace TriCNES { partial class TriCTraceLogger { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCTraceLogger)); this.b_ToggleButton = new System.Windows.Forms.CheckBox(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.rtb_TraceLog = new System.Windows.Forms.RichTextBox(); this.cb_LogInRange = new System.Windows.Forms.CheckBox(); this.tb_RangeLow = new System.Windows.Forms.TextBox(); this.tb_RangeHigh = new System.Windows.Forms.TextBox(); this.cb_ClearEveryFrame = new System.Windows.Forms.CheckBox(); this.cb_LogPPU = new System.Windows.Forms.CheckBox(); this.groupBox1.SuspendLayout(); this.SuspendLayout(); // // b_ToggleButton // this.b_ToggleButton.Appearance = System.Windows.Forms.Appearance.Button; this.b_ToggleButton.AutoSize = true; this.b_ToggleButton.Location = new System.Drawing.Point(12, 497); this.b_ToggleButton.Name = "b_ToggleButton"; this.b_ToggleButton.Size = new System.Drawing.Size(80, 23); this.b_ToggleButton.TabIndex = 0; this.b_ToggleButton.Text = "Start Logging"; this.b_ToggleButton.UseVisualStyleBackColor = true; this.b_ToggleButton.CheckedChanged += new System.EventHandler(this.b_ToggleButton_CheckedChanged); // // groupBox1 // this.groupBox1.Controls.Add(this.rtb_TraceLog); this.groupBox1.Location = new System.Drawing.Point(12, 27); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System.Drawing.Size(931, 464); this.groupBox1.TabIndex = 1; this.groupBox1.TabStop = false; this.groupBox1.Text = "Trace Log"; // // rtb_TraceLog // this.rtb_TraceLog.DetectUrls = false; this.rtb_TraceLog.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.rtb_TraceLog.Location = new System.Drawing.Point(7, 20); this.rtb_TraceLog.Name = "rtb_TraceLog"; this.rtb_TraceLog.Size = new System.Drawing.Size(918, 438); this.rtb_TraceLog.TabIndex = 56; this.rtb_TraceLog.Text = ""; this.rtb_TraceLog.WordWrap = false; // // cb_LogInRange // this.cb_LogInRange.AutoSize = true; this.cb_LogInRange.Location = new System.Drawing.Point(165, 498); this.cb_LogInRange.Name = "cb_LogInRange"; this.cb_LogInRange.Size = new System.Drawing.Size(139, 17); this.cb_LogInRange.TabIndex = 2; this.cb_LogInRange.Text = "Only Log Within Range:"; this.cb_LogInRange.UseVisualStyleBackColor = true; this.cb_LogInRange.CheckedChanged += new System.EventHandler(this.cb_LogInRange_CheckedChanged); // // tb_RangeLow // this.tb_RangeLow.Enabled = false; this.tb_RangeLow.Location = new System.Drawing.Point(298, 495); this.tb_RangeLow.MaxLength = 4; this.tb_RangeLow.Name = "tb_RangeLow"; this.tb_RangeLow.ReadOnly = true; this.tb_RangeLow.Size = new System.Drawing.Size(32, 20); this.tb_RangeLow.TabIndex = 3; this.tb_RangeLow.Text = "0000"; this.tb_RangeLow.TextChanged += new System.EventHandler(this.tb_RangeLow_TextChanged); // // tb_RangeHigh // this.tb_RangeHigh.Enabled = false; this.tb_RangeHigh.Location = new System.Drawing.Point(336, 495); this.tb_RangeHigh.MaxLength = 4; this.tb_RangeHigh.Name = "tb_RangeHigh"; this.tb_RangeHigh.ReadOnly = true; this.tb_RangeHigh.Size = new System.Drawing.Size(32, 20); this.tb_RangeHigh.TabIndex = 4; this.tb_RangeHigh.Text = "FFFF"; this.tb_RangeHigh.TextChanged += new System.EventHandler(this.tb_RangeHigh_TextChanged); // // cb_ClearEveryFrame // this.cb_ClearEveryFrame.AutoSize = true; this.cb_ClearEveryFrame.Checked = true; this.cb_ClearEveryFrame.CheckState = System.Windows.Forms.CheckState.Checked; this.cb_ClearEveryFrame.Location = new System.Drawing.Point(165, 521); this.cb_ClearEveryFrame.Name = "cb_ClearEveryFrame"; this.cb_ClearEveryFrame.Size = new System.Drawing.Size(133, 17); this.cb_ClearEveryFrame.TabIndex = 5; this.cb_ClearEveryFrame.Text = "Clear Log Every Frame"; this.cb_ClearEveryFrame.UseVisualStyleBackColor = true; // // cb_LogPPU // this.cb_LogPPU.AutoSize = true; this.cb_LogPPU.Location = new System.Drawing.Point(165, 548); this.cb_LogPPU.Name = "cb_LogPPU"; this.cb_LogPPU.Size = new System.Drawing.Size(103, 17); this.cb_LogPPU.TabIndex = 6; this.cb_LogPPU.Text = "Log PPU Cycles"; this.cb_LogPPU.UseVisualStyleBackColor = true; // // TriCTraceLogger // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(953, 577); this.Controls.Add(this.cb_LogPPU); this.Controls.Add(this.cb_ClearEveryFrame); this.Controls.Add(this.tb_RangeHigh); this.Controls.Add(this.tb_RangeLow); this.Controls.Add(this.cb_LogInRange); this.Controls.Add(this.groupBox1); this.Controls.Add(this.b_ToggleButton); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MaximizeBox = false; this.MaximumSize = new System.Drawing.Size(969, 616); this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(969, 616); this.Name = "TriCTraceLogger"; this.Text = "Trace Logger"; this.groupBox1.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.CheckBox b_ToggleButton; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.RichTextBox rtb_TraceLog; private System.Windows.Forms.CheckBox cb_LogInRange; private System.Windows.Forms.TextBox tb_RangeLow; private System.Windows.Forms.TextBox tb_RangeHigh; private System.Windows.Forms.CheckBox cb_ClearEveryFrame; private System.Windows.Forms.CheckBox cb_LogPPU; } } ================================================ FILE: forms/TriCTraceLogger.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TriCNES { public partial class TriCTraceLogger : Form { public TriCNESGUI MainGUI; public bool Logging; public TriCTraceLogger() { InitializeComponent(); FormClosing += new FormClosingEventHandler(TriCTraceLogger_Closing); } private void TriCTraceLogger_Closing(Object sender, FormClosingEventArgs e) { MainGUI.TraceLogger = null; } public void Init() { rtb_TraceLog.SelectionTabs = new int[] { 0, 56, 56 * 2, 56 * 3, 56 * 4, 56 * 5, 56 * 6, 56 * 7, 56 * 8, 56 * 9, 56 * 10 }; } String Log; public void Update() { if (MainGUI.EMU.DebugLog != null) { Log = MainGUI.EMU.DebugLog.ToString(); MethodInvoker upd = delegate { rtb_TraceLog.Text = Log; }; try { this.Invoke(upd); } catch (Exception e) { } } } private void b_ToggleButton_CheckedChanged(object sender, EventArgs e) { Logging = b_ToggleButton.Checked; b_ToggleButton.Text = Logging ? "Stop Logging" : "Start Logging"; } private void cb_LogInRange_CheckedChanged(object sender, EventArgs e) { tb_RangeHigh.ReadOnly = !cb_LogInRange.Checked; tb_RangeHigh.Enabled = cb_LogInRange.Checked; tb_RangeLow.ReadOnly = !cb_LogInRange.Checked; tb_RangeLow.Enabled = cb_LogInRange.Checked; } private void tb_RangeLow_TextChanged(object sender, EventArgs e) { RangeLow = 0; ushort.TryParse(tb_RangeLow.Text, System.Globalization.NumberStyles.HexNumber, null, out RangeLow); } private void tb_RangeHigh_TextChanged(object sender, EventArgs e) { RangeHigh = 0xFFFF; ushort.TryParse(tb_RangeHigh.Text, System.Globalization.NumberStyles.HexNumber, null, out RangeHigh); } public ushort RangeLow; public ushort RangeHigh; public bool OnlyDebugInRange() { return cb_LogInRange.Checked; } public bool ClearEveryFrame() { return cb_ClearEveryFrame.Checked; } public bool LogPPUCycles() { return cb_LogPPU.Checked; } } } ================================================ FILE: forms/TriCTraceLogger.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD///////////// //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP// /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA//// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP// ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////// //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP// ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////// //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD///////////// ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////// //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////// //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////// /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP//////// //////////////////////////////////8= ================================================ FILE: mappers/Mapper_AOROM.cs ================================================ using System; using System.Collections.Generic; using System.Net; namespace TriCNES.mappers { public class Mapper_AOROM : Mapper { // ines Mapper 7 public byte Mapper_7_BankSelect; public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0x8000) { dataPinsAreNotFloating = true; ushort tempo = (ushort)(Address & 0x7FFF); dataBus = Cart.PRGROM[(0x8000 * (Mapper_7_BankSelect & 0x07) + tempo) & (Cart.PRGROM.Length - 1)]; } // AOROM doesn't have any PRG RAM if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address >= 0x8000) { Mapper_7_BankSelect = Input; } } public override ushort MirrorNametable(ushort Address) { if ((Mapper_7_BankSelect & 0x10) == 0) // show nametable 0 { Address &= 0x33FF; } else // show nametable 1 { Address &= 0x33FF; Address |= 0x400; } return Address; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_7_BankSelect); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_7_BankSelect = State[p++]; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_CNROM.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_CNROM : Mapper { // ines Mapper 3 public byte Mapper_3_CHRBank; public override void StorePRG(ushort Address, byte Input) { if (Address >= 0x8000) { Mapper_3_CHRBank = (byte)(Input & 0x3); } } public override byte FetchCHR(ushort Address, bool Observe) { return Cart.CHRROM[(Mapper_3_CHRBank * 0x2000 + Address) & (Cart.CHRROM.Length - 1)]; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_3_CHRBank); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_3_CHRBank = State[p++]; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_FDS.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_FDS : Mapper { // The Famicom Disk System public byte[] FDS_BIOS; public byte FDS_4023_IOEnable; public byte FDS_4025_Control; public Mapper_FDS(byte[] fds_bios) { FDS_BIOS = fds_bios; } public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0xE000) { // read from the FDS BIOS notFloating = true; data = FDS_BIOS[Address & 0x1FFF]; } else if (Address >= 0x6000) { // read from the FDS PRG RAM notFloating = true; data = Cart.PRGRAM[Address-0x6000]; } else if (Address >= 4030 && Address <= 0x403F) { // Read from the FDS Registers Address &= 0xF; switch (Address) { default: break; case 0: { // FDS Status ($4030) notFloating = true; data = 0; data |= (byte)((FDS_4025_Control >> 3) & 1); // 4030.3 = 4025.3 data |= (byte)(Cart.FDS.Status_ByteTransferFlag ? 0x80 : 0); // 4030.7 = Byte Transfer Flag } break; case 1: { // Disk Data Input ($4031) notFloating = true; data = Cart.FDS.ShiftRegisterLatch; Cart.FDS.Status_ByteTransferFlag = false; Cart.Emu.IRQ_LevelDetector = false; //acknowledge the IRQ } break; case 2: { // Disk Drive Status ($4032) } break; case 3: { // External Connector Input ($4033) notFloating = true; data = 0x80; // The battery is good. } break; } } if (notFloating) { EndFetchPRG(Observe, data); } return; } public override byte FetchCHR(ushort Address, bool Observe) { return Cart.CHRRAM[Address]; } public override void StorePRG(ushort Address, byte Input) { if (Address >= 0x6000 && Address < 0xE000) { Cart.PRGRAM[Address-0x6000] = Input; return; } else if (Address > 0x401F) { ushort tempo = (ushort)(Address & 0x40FF); switch (tempo) { case 0x4023: FDS_4023_IOEnable = Input; if((FDS_4023_IOEnable & 1) == 0) { // Disable disk I/O registers Cart.Emu.IRQ_LevelDetector = false; //acknowledge the IRQ FDS_4025_Control = 6; } return; case 0x4024: Cart.FDS.Status_ByteTransferFlag = false; return; case 0x4025: FDS_4025_Control = Input; return; } } } public override void FDS_ByteTransferFlag() { if((FDS_4025_Control & 0x80) != 0) { Cart.Emu.IRQ_LevelDetector = true; } } public override byte FDS_Get4025() { return FDS_4025_Control; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(FDS_4025_Control); State.Add((byte)Cart.FDS.clock); State.Add((byte)(Cart.FDS.clock >> 8)); State.Add((byte)Cart.FDS.ShiftRegister); State.Add((byte)Cart.FDS.ShiftRegisterLatch); State.Add((byte)Cart.FDS.DiskAddress); State.Add((byte)(Cart.FDS.DiskAddress >> 8)); State.Add((byte)Cart.FDS.DiskAddressFine); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } FDS_4025_Control = State[p++]; Cart.FDS.clock = State[p++]; Cart.FDS.clock |= (ushort)(State[p++] << 8); Cart.FDS.ShiftRegister = State[p++]; Cart.FDS.ShiftRegisterLatch = State[p++]; Cart.FDS.DiskAddress = State[p++]; Cart.FDS.DiskAddress |= (ushort)(State[p++] << 8); Cart.FDS.DiskAddressFine = State[p++]; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_FME7.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_FME7 : Mapper { // ines Mapper 69 public byte Mapper_69_CMD; public byte Mapper_69_CHR_1K0; public byte Mapper_69_CHR_1K1; public byte Mapper_69_CHR_1K2; public byte Mapper_69_CHR_1K3; public byte Mapper_69_CHR_1K4; public byte Mapper_69_CHR_1K5; public byte Mapper_69_CHR_1K6; public byte Mapper_69_CHR_1K7; public byte Mapper_69_Bank_6; public bool Mapper_69_Bank_6_isRAM; public bool Mapper_69_Bank_6_isRAMEnabled; public byte Mapper_69_Bank_8; public byte Mapper_69_Bank_A; public byte Mapper_69_Bank_C; public byte Mapper_69_NametableMirroring; // 0 = Vertical 1 = Horizontal 2 = One Screen Mirroring from $2000 ("1ScA") 3 = One Screen Mirroring from $2400 ("1ScB") public bool Mapper_69_EnableIRQ; public bool Mapper_69_EnableIRQCounterDecrement; public ushort Mapper_69_IRQCounter; // When enabled the 16-bit IRQ counter is decremented once per CPU cycle. When the IRQ counter is decremented from $0000 to $FFFF an IRQ is generated. public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0x6000) { ushort tempo = (ushort)(Address % 0x2000); if (Address >= 0x6000) { //actions if (Address < 0x8000) { if (Mapper_69_Bank_6_isRAM) { if (Mapper_69_Bank_6_isRAMEnabled) { notFloating = true; data = Cart.PRGRAM[Address & 0x1FFF]; } } else { //read from ROM notFloating = true; data = Cart.PRGROM[(Mapper_69_Bank_6 * 0x2000 + tempo) % Cart.PRGROM.Length]; } } else if (Address < 0xA000) { notFloating = true; data = Cart.PRGROM[(Mapper_69_Bank_8 * 0x2000 + tempo) % Cart.PRGROM.Length]; } else if (Address < 0xC000) { notFloating = true; data = Cart.PRGROM[(Mapper_69_Bank_A * 0x2000 + tempo) % Cart.PRGROM.Length]; } else if (Address < 0xE000) { notFloating = true; data = Cart.PRGROM[(Mapper_69_Bank_C * 0x2000 + tempo) % Cart.PRGROM.Length]; } else { notFloating = true; data = Cart.PRGROM[Cart.PRGROM.Length - 0x2000 + tempo]; } } } if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address >= 0x6000) { //actions if (Address < 0x8000) { if (Mapper_69_Bank_6_isRAM) { if (Mapper_69_Bank_6_isRAMEnabled) { //writing to RAM Cart.PRGRAM[Address & 0x1FFF] = Input; } //else, writing to open bus } //else it's ROM. writing here does nothing. } else if (Address < 0xA000) { Mapper_69_CMD = (byte)(Input & 0x0F); } else if (Address < 0xC000) { switch (Mapper_69_CMD) { case 0: Mapper_69_CHR_1K0 = Input; break; case 1: Mapper_69_CHR_1K1 = Input; break; case 2: Mapper_69_CHR_1K2 = Input; break; case 3: Mapper_69_CHR_1K3 = Input; break; case 4: Mapper_69_CHR_1K4 = Input; break; case 5: Mapper_69_CHR_1K5 = Input; break; case 6: Mapper_69_CHR_1K6 = Input; break; case 7: Mapper_69_CHR_1K7 = Input; break; case 8: Mapper_69_Bank_6 = (byte)(Input & 0x3F); Mapper_69_Bank_6_isRAM = (Input & 0x40) != 0; Mapper_69_Bank_6_isRAMEnabled = (Input & 0x80) != 0; break; case 9: Mapper_69_Bank_8 = (byte)(Input & 0x3F); break; case 10: Mapper_69_Bank_A = (byte)(Input & 0x3F); break; case 11: Mapper_69_Bank_C = (byte)(Input & 0x3F); break; case 12: Mapper_69_NametableMirroring = (byte)(Input & 0x3); break; case 13: Mapper_69_EnableIRQ = (Input & 0x1) != 0; Mapper_69_EnableIRQCounterDecrement = (Input & 0x80) != 0; Cart.Emu.IRQ_LevelDetector = false; break; case 14: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF00) | Input); break; case 15: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF) | (Input << 8)); break; } } // else do nothing } } public override byte FetchCHR(ushort Address, bool Observe) { if (Address < 0x400) { return Cart.CHRROM[(Mapper_69_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x800) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K1 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0xC00) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K2 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1000) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K3 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1400) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1800) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K5 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1C00) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K6 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else { Address &= 0x3FF; return Cart.CHRROM[(Mapper_69_CHR_1K7 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } } public override ushort MirrorNametable(ushort Address) { switch (Mapper_69_NametableMirroring) { case 0: //vertical Address &= 0x37FF; // mask away $0800 break; case 1: //horizontal Address = (ushort)((Address & 0x33FF) | ((Address & 0x0800) >> 1)); // mask away $0C00, bit 10 becomes the former bit 11 break; case 2: //one-screen A Address &= 0x33FF; break; case 3: //one-screen B Address &= 0x33FF; Address |= 0x400; break; } return Address; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_69_CMD); State.Add(Mapper_69_CHR_1K0); State.Add(Mapper_69_CHR_1K1); State.Add(Mapper_69_CHR_1K2); State.Add(Mapper_69_CHR_1K3); State.Add(Mapper_69_CHR_1K4); State.Add(Mapper_69_CHR_1K5); State.Add(Mapper_69_CHR_1K6); State.Add(Mapper_69_CHR_1K7); State.Add(Mapper_69_Bank_6); State.Add((byte)(Mapper_69_Bank_6_isRAM ? 1 : 0)); State.Add((byte)(Mapper_69_Bank_6_isRAMEnabled ? 1 : 0)); State.Add(Mapper_69_Bank_8); State.Add(Mapper_69_Bank_A); State.Add(Mapper_69_Bank_C); State.Add(Mapper_69_NametableMirroring); State.Add((byte)(Mapper_69_EnableIRQ ? 1 : 0)); State.Add((byte)(Mapper_69_EnableIRQCounterDecrement ? 1 : 0)); State.Add((byte)Mapper_69_IRQCounter); State.Add((byte)(Mapper_69_IRQCounter >> 8)); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_69_CMD = State[p++]; Mapper_69_CHR_1K0 = State[p++]; Mapper_69_CHR_1K1 = State[p++]; Mapper_69_CHR_1K2 = State[p++]; Mapper_69_CHR_1K3 = State[p++]; Mapper_69_CHR_1K4 = State[p++]; Mapper_69_CHR_1K5 = State[p++]; Mapper_69_CHR_1K6 = State[p++]; Mapper_69_CHR_1K7 = State[p++]; Mapper_69_Bank_6 = State[p++]; Mapper_69_Bank_6_isRAM = (State[p++] & 1) == 1; Mapper_69_Bank_6_isRAMEnabled = (State[p++] & 1) == 1; Mapper_69_Bank_8 = State[p++]; Mapper_69_Bank_A = State[p++]; Mapper_69_Bank_C = State[p++]; Mapper_69_NametableMirroring = State[p++]; Mapper_69_EnableIRQ = (State[p++] & 1) == 1; Mapper_69_EnableIRQCounterDecrement = (State[p++] & 1) == 1; Mapper_69_IRQCounter = State[p++]; Mapper_69_IRQCounter |= (ushort)(State[p++] << 8); exitIndex = p; } public override void CPUClock() { // The sunsoft FME-7 mapper chip has an IRQ counter that ticks down once per CPU cycle. if (Mapper_69_EnableIRQCounterDecrement) { ushort temp = Mapper_69_IRQCounter; Mapper_69_IRQCounter--; if (Mapper_69_EnableIRQ && temp < Mapper_69_IRQCounter) { Cart.Emu.IRQ_LevelDetector = true; } } } } } ================================================ FILE: mappers/Mapper_MMC1.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_MMC1 : Mapper { // ines Mapper 1 public byte Mapper_1_ShiftRegister; public byte Mapper_1_Control = 0x0C; //0x8000 public byte Mapper_1_CHR0; //0xA000 public byte Mapper_1_CHR1; //0xC000 public byte Mapper_1_PRG; //0xE000 public bool Mapper_1_PB; public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0x8000) { notFloating = true; // The bank mode for MMC1: byte MMC1PRGROMBankMode = (byte)((Mapper_1_Control & 0b01100) >> 2); switch (MMC1PRGROMBankMode) { case 0: case 1: { // switch 32 KB at $8000, ignoring low bit of bank number ushort tempo = (ushort)(Address & 0x7FFF); data = Cart.PRGROM[(0x8000 * (Mapper_1_PRG & 0x0E) + tempo) % Cart.PRGROM.Length]; } break; case 2: // fix first bank at $8000 and switch 16 KB bank at $C000 if (Address >= 0xC000) { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[0x4000 * (Mapper_1_PRG) + tempo]; } else { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[tempo]; } break; case 3: // fix last bank at $C000 and switch 16 KB bank at $8000 if (Address >= 0xC000) { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; } else { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[(0x4000 * (Mapper_1_PRG & 0x0F) + tempo) & (Cart.PRGROM.Length - 1)]; } break; } } else // if the address is < $8000 { if (((Mapper_1_PRG & 0x10) == 0) && Address >= 0x6000) // if Work RAM is enabled { data = Cart.PRGRAM[Address & 0x1FFF]; notFloating = true; } // else, open bus. } //open bus if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address < 0x8000) //WRAM not available on MMC1A { if (((Mapper_1_PRG & 0x10) == 0) /*&& Mapper != 1*/) { //Battery backed RAM Cart.PRGRAM[Address & 0x1FFF] = Input; return; } else { return; //do nothing } } else { // shift the shirftRegister and add the new bit Mapper_1_PB = (Mapper_1_ShiftRegister & 1) == 1; Mapper_1_ShiftRegister >>= 1; Mapper_1_ShiftRegister |= (byte)((Input & 1) << 4); } if (Mapper_1_PB) // if the '1' that was initialized in bit 4 is shifted into the bus { // copy shift register to the desired internal register. switch (Address & 0xE000) { case 0x8000: //control Mapper_1_Control = Mapper_1_ShiftRegister; break; case 0xA000: //CHR0 Mapper_1_CHR0 = Mapper_1_ShiftRegister; break; case 0xC000: //CHR1 Mapper_1_CHR1 = Mapper_1_ShiftRegister; break; case 0xE000: //PRG Mapper_1_PRG = Mapper_1_ShiftRegister; break; } Mapper_1_ShiftRegister = 0b10000; } if ((Input & 0b10000000) != 0) { Mapper_1_ShiftRegister = 0b10000; Mapper_1_Control |= 0b01100; } } public override byte FetchCHR(ushort Address, bool Observe) { // bit 4 of Mapper_1_Control controls how the pattern tables are swapped. if set, 2 banks of 4Kib. Otherwise, 1 8Kib bank if ((Mapper_1_Control & 0x10) != 0) { // with the MMC1 chip, you can swap out the pattern tables. // address < 0x1000 is the first pattern table, else, the second pattern table. // if the final write for the MMC1 shift register was in the $A000 - $BFFF, this updates Mapper_1_CHR0 // if the final write for the MMC1 shift register was in the $B000 - $CFFF, this updates Mapper_1_CHR1 if (Address < 0x1000) { return Cart.CHRROM[((Mapper_1_CHR0 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1)]; } else { Address &= 0xFFF; return Cart.CHRROM[((Mapper_1_CHR1 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1)]; } } else // one swappable bank that changes both pattern tables. { // this uses the value written to Mapper_1_CHR0 return Cart.CHRROM[((Mapper_1_CHR0 & 0b11111110) * 0x2000 + Address) & (Cart.CHRROM.Length - 1)]; } } public override ushort MirrorNametable(ushort Address) { switch (Mapper_1_Control & 3) { case 0: //one screen, low Address &= 0x33FF; break; case 1: //one screen, high Address &= 0x33FF; Address |= 0x400; break; case 2: //vertical Address &= 0x37FF; // mask away $0800 break; case 3: //horizontal Address = (ushort)((Address & 0x33FF) | ((Address & 0x0800) >> 1)); // mask away $0C00, bit 10 becomes the former bit 11 break; } return Address; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_1_ShiftRegister); State.Add(Mapper_1_Control); State.Add(Mapper_1_CHR0); State.Add(Mapper_1_CHR1); State.Add(Mapper_1_ShiftRegister); State.Add((byte)(Mapper_1_PB ? 1 : 0)); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_1_ShiftRegister = State[p++]; Mapper_1_Control = State[p++]; Mapper_1_CHR0 = State[p++]; Mapper_1_CHR1 = State[p++]; Mapper_1_ShiftRegister = State[p++]; Mapper_1_PB = (State[p++] & 1) == 1; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_MMC2.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_MMC2 : Mapper { // ines Mapper 9 public byte Mapper_9_BankSelect; public byte Mapper_9_CHR0_FD; public byte Mapper_9_CHR0_FE; public byte Mapper_9_CHR1_FD; public byte Mapper_9_CHR1_FE; public bool Mapper_9_NametableMirroring; public bool Mapper_9_Latch0_FE; public bool Mapper_9_Latch1_FE; public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0xA000) { notFloating = true; data = Cart.PRGROM[((Cart.PRG_Size - 2) << 14) | (Address & 0x7FFF)]; } else if(Address >= 0x8000) { notFloating = true; data = Cart.PRGROM[(Mapper_9_BankSelect << 13) | (Address & 0x1FFF)]; } if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address < 0xA000) { // nothing } else if (Address < 0xB000) // PRG Bank select { Mapper_9_BankSelect = (byte)(Input & 0x0F); } else if (Address < 0xC000) // CHR0 Bank select { Mapper_9_CHR0_FD = (byte)(Input & 0x1F); } else if (Address < 0xD000) // CHR0 Bank select { Mapper_9_CHR0_FE = (byte)(Input & 0x1F); } else if (Address < 0xE000) // CHR1 Bank select { Mapper_9_CHR1_FD = (byte)(Input & 0x1F); } else if (Address < 0xF000) // CHR1 Bank select { Mapper_9_CHR1_FE = (byte)(Input & 0x1F); } else // Nametable mirroring { Mapper_9_NametableMirroring = (Input & 0x1) == 1; } } public override byte FetchCHR(ushort Address, bool Observe) { byte temp = 0; ushort Addr = Address; if (Address < 0x1000) { temp = Cart.CHRROM[(Mapper_9_Latch0_FE ? Mapper_9_CHR0_FE : Mapper_9_CHR0_FD) * 0x1000 + Addr]; } else { Addr &= 0xFFF; temp = Cart.CHRROM[(Mapper_9_Latch1_FE ? Mapper_9_CHR1_FE : Mapper_9_CHR1_FD) * 0x1000 + Addr]; } if (!Observe) { if (Address == 0x0FD8) { Mapper_9_Latch0_FE = false; } else if (Address == 0x0FE8) { Mapper_9_Latch0_FE = true; } else if (Address >= 0x1FD8 && Address <= 0x1FDF) { Mapper_9_Latch1_FE = false; } else if (Address >= 0x1FE8 && Address <= 0x1FEF) { Mapper_9_Latch1_FE = true; } } return temp; } public override ushort MirrorNametable(ushort Address) { if (Mapper_9_NametableMirroring) //horizontal { Address = (ushort)((Address & 0x33FF) | ((Address & 0x0800) >> 1)); // mask away $0C00, bit 10 becomes the former bit 11 } else //vertical { Address &= 0x37FF; // mask away $0800 } return Address; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_9_BankSelect); State.Add(Mapper_9_CHR0_FD); State.Add(Mapper_9_CHR0_FE); State.Add(Mapper_9_CHR1_FD); State.Add(Mapper_9_CHR1_FE); State.Add((byte)(Mapper_9_NametableMirroring ? 1 : 0)); State.Add((byte)(Mapper_9_Latch0_FE ? 1 : 0)); State.Add((byte)(Mapper_9_Latch1_FE ? 1 : 0)); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_9_BankSelect = State[p++]; Mapper_9_CHR0_FD = State[p++]; Mapper_9_CHR0_FE = State[p++]; Mapper_9_CHR1_FD = State[p++]; Mapper_9_CHR1_FE = State[p++]; Mapper_9_NametableMirroring = (State[p++] & 1) == 1; Mapper_9_Latch0_FE = (State[p++] & 1) == 1; Mapper_9_Latch1_FE = (State[p++] & 1) == 1; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_MMC3.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_MMC3 : Mapper { // ines Mapper 4 public byte Mapper_4_8000; // The value written to $8000 (or any even address between $8000 and $9FFE) public byte Mapper_4_BankA; // The PRG bank between $A000 and $BFFF public byte Mapper_4_Bank8C; // The PRG bank that could either be at $8000 through 9FFF, or $C000 through $DFFF public byte Mapper_4_CHR_2K0; public byte Mapper_4_CHR_2K8; public byte Mapper_4_CHR_1K0; public byte Mapper_4_CHR_1K4; public byte Mapper_4_CHR_1K8; public byte Mapper_4_CHR_1KC; public byte Mapper_4_IRQLatch; public byte Mapper_4_IRQCounter; public bool Mapper_4_EnableIRQ; public bool Mapper_4_ReloadIRQCounter; public bool Mapper_4_NametableMirroring; // MMC3 has it's own way of controlling how the nametables are mirrored. public byte Mapper_4_PRGRAMProtect; public byte Mapper_4_M2Filter; public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0xE000) // This bank is fixed the the final PRG bank of the ROM { notFloating = true; data = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x3FFF)]; } else if (Address >= 0xC000) { notFloating = true; if ((Mapper_4_8000 & 0x40) == 0x40) { //$C000 swappable data = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; } else { //$8000 swappable data = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; } } else if (Address >= 0xA000) { notFloating = true; //$8000 swappable data = Cart.PRGROM[(Mapper_4_BankA << 13) | (Address & 0x1FFF)]; } else if (Address >= 0x8000) { notFloating = true; if ((Mapper_4_8000 & 0x40) == 0x40) { //$8000 swappable data = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; } else { //$C000 swappable data = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; } } else if (Address >= 0x6000) { if (Cart.SubMapper == 1) // MMC6 { if ((Mapper_4_8000 & 0x20) != 0) { // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM if (Address >= 0x7000 && Address <= 0x71FF) { if ((Mapper_4_PRGRAMProtect & 0x20) != 0) { notFloating = true; data = Cart.PRGRAM[Address & 0x3FF]; } } else if (Address >= 0x7200 && Address <= 0x73FF) { if ((Mapper_4_PRGRAMProtect & 0x80) != 0) { notFloating = true; data = Cart.PRGRAM[Address & 0x3FF]; } } } } else { if ((Mapper_4_PRGRAMProtect & 0x80) != 0) { notFloating = true; data = Cart.PRGRAM[Address & 0x1FFF]; } } } //else, open bus if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address < 0x8000) { //Battery backed RAM if (Cart.SubMapper == 1) // MMC6 { // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM if ((Mapper_4_8000 & 0x20) != 0) { if (Address >= 0x7000 && Address <= 0x71FF) { if ((Mapper_4_PRGRAMProtect & 0x10) != 0) { Cart.PRGRAM[Address & 0x3FF] = Input; } } else if (Address >= 0x7200 && Address <= 0x73FF) { if ((Mapper_4_PRGRAMProtect & 0x40) != 0) { Cart.PRGRAM[Address & 0x3FF] = Input; } } } } else if ((Mapper_4_PRGRAMProtect & 0xC0) != 0) // bit 7 enables PRG RAM, bit 6 enables writing there. { Cart.PRGRAM[Address & 0x1FFF] = Input; } return; } else { ushort tempo = (ushort)(Address & 0xE001); switch (tempo) { case 0x8000: Mapper_4_8000 = Input; return; case 0x8001: byte mode = (byte)(Mapper_4_8000 & 7); switch (mode) { case 0: //PPU ($0000 - $07FF) ?+ $1000 Mapper_4_CHR_2K0 = (byte)(Input & 0xFE); return; case 1: //PPU ($0800 - $0FFF) ?+ $1000 Mapper_4_CHR_2K8 = (byte)(Input & 0xFE); return; case 2: //PPU ($1000 - $13FF) ?- $1000 Mapper_4_CHR_1K0 = Input; return; case 3: //PPU ($1400 - $17FF) ?- $1000 Mapper_4_CHR_1K4 = Input; return; case 4: //PPU ($1800 - $1BFF) ?- $1000 Mapper_4_CHR_1K8 = Input; return; case 5: //PPU ($1C00 - $1FFF) ?- $1000 Mapper_4_CHR_1KC = Input; return; case 6: //PRG ($8000 - $9FFF) ?+ 0x4000 Mapper_4_Bank8C = (byte)(Input & (Cart.PRG_Size * 2 - 1)); return; case 7: //PRG ($A000 - $BFFF) Mapper_4_BankA = (byte)(Input & (Cart.PRG_Size * 2 - 1)); return; } return; case 0xA000: Mapper_4_NametableMirroring = (Input & 1) == 1; return; case 0xA001: Mapper_4_PRGRAMProtect = Input; return; case 0xC000: Mapper_4_IRQLatch = Input; return; case 0xC001: Mapper_4_IRQCounter = 0xFF; Mapper_4_ReloadIRQCounter = true; return; case 0xE000: Mapper_4_EnableIRQ = false; Cart.Emu.IRQ_LevelDetector = false; return; case 0xE001: Mapper_4_EnableIRQ = true; return; } } } public override byte FetchCHR(ushort Address, bool Observe) { //Writes to $8000 determine the mode, writes to $8001 determine the banks if ((Mapper_4_8000 & 0x80) == 0) // bit 7 of the previous write to $8000 determines which pattern table is 2 2kb banks, and which is 4 1kb banks. { if (Address < 0x800) { return Cart.CHRROM[(Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1000) { Address &= 0x7FF; return Cart.CHRROM[(Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1400) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1800) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1C00) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } } else { if (Address < 0x400) { return Cart.CHRROM[(Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x800) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0xC00) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1000) { Address &= 0x3FF; return Cart.CHRROM[(Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else if (Address < 0x1800) { Address &= 0x7FF; return Cart.CHRROM[(Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } else { Address &= 0x7FF; return Cart.CHRROM[(Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1)]; } } } public override byte FetchPPU() { // This will always use the upper 8 bits of the address bus | the octal latch. This replaces the lower 8 bits of the address bus. ushort Address = (ushort)((Cart.Emu.PPU_AddressBus & 0x3F00) | Cart.Emu.PPU_OctalLatch); bool CIRAM = Address >= 0x2000; if (!CIRAM) { if (Cart.UsingCHRRAM) { Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.CHRRAM[Address]; } else { //Pattern Table Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.MapperChip.FetchCHR(Address, false); } } else // if the VRAM address is >= $2000, we need to consider nametable mirroring. { Address = MirrorNametable(Address); if (Cart.AlternativeNametableArrangement) { if ((Address & 0x800) != 0) { // using the extra PRG VRAM. Address &= 0x7FF; Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.PRGVRAM[Address]; } else { Address &= 0x7FF; Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.Emu.VRAM[Address]; } } else { Address &= 0x7FF; Cart.Emu.PPU_AddressBus &= 0xFF00; Cart.Emu.PPU_AddressBus |= Cart.Emu.VRAM[Address]; } } return (byte)Cart.Emu.PPU_AddressBus; } public override ushort MirrorNametable(ushort Address) { if (!Cart.AlternativeNametableArrangement) { if (Mapper_4_NametableMirroring) //horizontal { return (ushort)((Address & 0x33FF) | ((Address & 0x0800) >> 1)); // mask away $0C00, bit 10 becomes the former bit 11 } else //vertical { return (ushort)(Address & 0x37FF); // mask away $0800 } } return Address; } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } if (Cart.PRGVRAM != null) { foreach (Byte b in Cart.PRGVRAM) { State.Add(b); } } State.Add(Mapper_4_8000); State.Add(Mapper_4_BankA); State.Add(Mapper_4_Bank8C); State.Add(Mapper_4_CHR_2K0); State.Add(Mapper_4_CHR_2K8); State.Add(Mapper_4_CHR_1K0); State.Add(Mapper_4_CHR_1K4); State.Add(Mapper_4_CHR_1K8); State.Add(Mapper_4_CHR_1KC); State.Add(Mapper_4_IRQLatch); State.Add(Mapper_4_IRQCounter); State.Add((byte)(Mapper_4_EnableIRQ ? 1 : 0)); State.Add((byte)(Mapper_4_ReloadIRQCounter ? 1 : 0)); State.Add((byte)(Mapper_4_NametableMirroring ? 1 : 0)); State.Add(Mapper_4_PRGRAMProtect); State.Add(Mapper_4_M2Filter); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } if (Cart.PRGVRAM != null) { for (int i = 0; i < Cart.PRGVRAM.Length; i++) { Cart.PRGVRAM[i] = State[p++]; } } Mapper_4_8000 = State[p++]; Mapper_4_BankA = State[p++]; Mapper_4_Bank8C = State[p++]; Mapper_4_CHR_2K0 = State[p++]; Mapper_4_CHR_2K8 = State[p++]; Mapper_4_CHR_1K0 = State[p++]; Mapper_4_CHR_1K4 = State[p++]; Mapper_4_CHR_1K8 = State[p++]; Mapper_4_CHR_1KC = State[p++]; Mapper_4_IRQLatch = State[p++]; Mapper_4_IRQCounter = State[p++]; Mapper_4_EnableIRQ = (State[p++] & 1) == 1; Mapper_4_ReloadIRQCounter = (State[p++] & 1) == 1; Mapper_4_NametableMirroring = (State[p++] & 1) == 1; Mapper_4_PRGRAMProtect = State[p++]; Mapper_4_M2Filter = State[p++]; exitIndex = p; } public override void PPUClock() { // if bit 12 of the ppu address bus (A12) changes: if (!Cart.Emu.PPU_A12_Prev && ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) != 0) && Mapper_4_M2Filter == 3) { if (Mapper_4_ReloadIRQCounter) { // If we're reloading the IRQ counter Mapper_4_IRQCounter = Mapper_4_IRQLatch; // The latch is the reset value. Mapper_4_ReloadIRQCounter = false; if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ. { if (Mapper_4_EnableIRQ) // if setting the value to zero, run an IRQ { Cart.Emu.IRQ_LevelDetector = true; } } } else { // decrement the counter Mapper_4_IRQCounter--; if (Mapper_4_IRQCounter == 0) // if decrementing the counter moved it to 0... { if (Mapper_4_EnableIRQ) // and the MMC3 IRQ is enabled... { Cart.Emu.IRQ_LevelDetector = true; // Run an IRQ! } } else if (Mapper_4_IRQCounter == 255) // if the counter underflows... { Mapper_4_IRQCounter = Mapper_4_IRQLatch; // reset the irq counter if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ... again { if (Mapper_4_EnableIRQ) { Cart.Emu.IRQ_LevelDetector = true; } } } } } if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) != 0) { Mapper_4_M2Filter = 0; } } public override void CPUClockRise() { if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) == 0) { if (Mapper_4_M2Filter < 3) { Mapper_4_M2Filter++; } } } } } ================================================ FILE: mappers/Mapper_NROM.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_NROM : Mapper { // ines Mapper 0 } } ================================================ FILE: mappers/Mapper_NULL.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_NULL : Mapper { // There is not a cartridge inserted in the console. public override void FetchPRG(ushort Address, bool Observe) { dataPinsAreNotFloating = false; // the data pins are always floating. There's no cartridge inserted! return; } public override byte FetchCHR(ushort Address, bool Observe) { // there's no cartridge. TODO: Look into this. Supposedly this would likely be the lower 8 bits of the address bus, but CIRAM enable is also floating. return 0; } public override ushort MirrorNametable(ushort Address) { return Address; } public override List SaveMapperRegisters() { List State = new List(); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; exitIndex = p; } } } ================================================ FILE: mappers/Mapper_UxROM.cs ================================================ using System; using System.Collections.Generic; namespace TriCNES.mappers { public class Mapper_UxROM : Mapper { // ines Mapper 2 public byte Mapper_2_BankSelect; public override void FetchPRG(ushort Address, bool Observe) { bool notFloating = false; byte data = 0; if (!Observe) { dataPinsAreNotFloating = false; } else { observedDataPinsAreNotFloating = false; } // Observing can happen on a different thread, so we need to ensure that observing doesn't overwrite the data bus or floating pins status. if (Address >= 0x8000) { notFloating = true; if (Address >= 0xC000) { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; } else { ushort tempo = (ushort)(Address & 0x3FFF); data = Cart.PRGROM[0x4000 * (Mapper_2_BankSelect & 0x0F) + tempo]; } } if (notFloating) { EndFetchPRG(Observe, data); } return; } public override void StorePRG(ushort Address, byte Input) { if (Address >= 0x8000) { Mapper_2_BankSelect = (byte)(Input & 0xF); } } public override List SaveMapperRegisters() { List State = new List(); foreach (Byte b in Cart.PRGRAM) { State.Add(b); } foreach (Byte b in Cart.CHRRAM) { State.Add(b); } State.Add(Mapper_2_BankSelect); return State; } public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) { int p = startIndex; for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; } for (int i = 0; i < Cart.CHRRAM.Length; i++) { Cart.CHRRAM[i] = State[p++]; } Mapper_2_BankSelect = State[p++]; exitIndex = p; } } } ================================================ FILE: packages.config ================================================