├── LICENSE ├── README.md ├── dub.json └── source ├── blip_buf.d └── nes ├── apu.d ├── cartridge.d ├── color.d ├── console.d ├── controller.d ├── cpu.d ├── image.d ├── ines.d ├── mapper.d ├── mapper1.d ├── mapper2.d ├── mapper225.d ├── mapper3.d ├── mapper4.d ├── mapper7.d ├── memory.d ├── palette.d └── ppu.d /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | nes emulation library Copyright (c) 2018 blahness 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | blip_buf library Copyright (C) 2003-2009 Shay Green. 26 | 27 | GNU LESSER GENERAL PUBLIC LICENSE 28 | Version 2.1, February 1999 29 | 30 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 31 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 32 | Everyone is permitted to copy and distribute verbatim copies 33 | of this license document, but changing it is not allowed. 34 | 35 | [This is the first released version of the Lesser GPL. It also counts 36 | as the successor of the GNU Library Public License, version 2, hence 37 | the version number 2.1.] 38 | 39 | Preamble 40 | 41 | The licenses for most software are designed to take away your 42 | freedom to share and change it. By contrast, the GNU General Public 43 | Licenses are intended to guarantee your freedom to share and change 44 | free software--to make sure the software is free for all its users. 45 | 46 | This license, the Lesser General Public License, applies to some 47 | specially designated software packages--typically libraries--of the 48 | Free Software Foundation and other authors who decide to use it. You 49 | can use it too, but we suggest you first think carefully about whether 50 | this license or the ordinary General Public License is the better 51 | strategy to use in any particular case, based on the explanations below. 52 | 53 | When we speak of free software, we are referring to freedom of use, 54 | not price. Our General Public Licenses are designed to make sure that 55 | you have the freedom to distribute copies of free software (and charge 56 | for this service if you wish); that you receive source code or can get 57 | it if you want it; that you can change the software and use pieces of 58 | it in new free programs; and that you are informed that you can do 59 | these things. 60 | 61 | To protect your rights, we need to make restrictions that forbid 62 | distributors to deny you these rights or to ask you to surrender these 63 | rights. These restrictions translate to certain responsibilities for 64 | you if you distribute copies of the library or if you modify it. 65 | 66 | For example, if you distribute copies of the library, whether gratis 67 | or for a fee, you must give the recipients all the rights that we gave 68 | you. You must make sure that they, too, receive or can get the source 69 | code. If you link other code with the library, you must provide 70 | complete object files to the recipients, so that they can relink them 71 | with the library after making changes to the library and recompiling 72 | it. And you must show them these terms so they know their rights. 73 | 74 | We protect your rights with a two-step method: (1) we copyright the 75 | library, and (2) we offer you this license, which gives you legal 76 | permission to copy, distribute and/or modify the library. 77 | 78 | To protect each distributor, we want to make it very clear that 79 | there is no warranty for the free library. Also, if the library is 80 | modified by someone else and passed on, the recipients should know 81 | that what they have is not the original version, so that the original 82 | author's reputation will not be affected by problems that might be 83 | introduced by others. 84 | 85 | Finally, software patents pose a constant threat to the existence of 86 | any free program. We wish to make sure that a company cannot 87 | effectively restrict the users of a free program by obtaining a 88 | restrictive license from a patent holder. Therefore, we insist that 89 | any patent license obtained for a version of the library must be 90 | consistent with the full freedom of use specified in this license. 91 | 92 | Most GNU software, including some libraries, is covered by the 93 | ordinary GNU General Public License. This license, the GNU Lesser 94 | General Public License, applies to certain designated libraries, and 95 | is quite different from the ordinary General Public License. We use 96 | this license for certain libraries in order to permit linking those 97 | libraries into non-free programs. 98 | 99 | When a program is linked with a library, whether statically or using 100 | a shared library, the combination of the two is legally speaking a 101 | combined work, a derivative of the original library. The ordinary 102 | General Public License therefore permits such linking only if the 103 | entire combination fits its criteria of freedom. The Lesser General 104 | Public License permits more lax criteria for linking other code with 105 | the library. 106 | 107 | We call this license the "Lesser" General Public License because it 108 | does Less to protect the user's freedom than the ordinary General 109 | Public License. It also provides other free software developers Less 110 | of an advantage over competing non-free programs. These disadvantages 111 | are the reason we use the ordinary General Public License for many 112 | libraries. However, the Lesser license provides advantages in certain 113 | special circumstances. 114 | 115 | For example, on rare occasions, there may be a special need to 116 | encourage the widest possible use of a certain library, so that it becomes 117 | a de-facto standard. To achieve this, non-free programs must be 118 | allowed to use the library. A more frequent case is that a free 119 | library does the same job as widely used non-free libraries. In this 120 | case, there is little to gain by limiting the free library to free 121 | software only, so we use the Lesser General Public License. 122 | 123 | In other cases, permission to use a particular library in non-free 124 | programs enables a greater number of people to use a large body of 125 | free software. For example, permission to use the GNU C Library in 126 | non-free programs enables many more people to use the whole GNU 127 | operating system, as well as its variant, the GNU/Linux operating 128 | system. 129 | 130 | Although the Lesser General Public License is Less protective of the 131 | users' freedom, it does ensure that the user of a program that is 132 | linked with the Library has the freedom and the wherewithal to run 133 | that program using a modified version of the Library. 134 | 135 | The precise terms and conditions for copying, distribution and 136 | modification follow. Pay close attention to the difference between a 137 | "work based on the library" and a "work that uses the library". The 138 | former contains code derived from the library, whereas the latter must 139 | be combined with the library in order to run. 140 | 141 | GNU LESSER GENERAL PUBLIC LICENSE 142 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 143 | 144 | 0. This License Agreement applies to any software library or other 145 | program which contains a notice placed by the copyright holder or 146 | other authorized party saying it may be distributed under the terms of 147 | this Lesser General Public License (also called "this License"). 148 | Each licensee is addressed as "you". 149 | 150 | A "library" means a collection of software functions and/or data 151 | prepared so as to be conveniently linked with application programs 152 | (which use some of those functions and data) to form executables. 153 | 154 | The "Library", below, refers to any such software library or work 155 | which has been distributed under these terms. A "work based on the 156 | Library" means either the Library or any derivative work under 157 | copyright law: that is to say, a work containing the Library or a 158 | portion of it, either verbatim or with modifications and/or translated 159 | straightforwardly into another language. (Hereinafter, translation is 160 | included without limitation in the term "modification".) 161 | 162 | "Source code" for a work means the preferred form of the work for 163 | making modifications to it. For a library, complete source code means 164 | all the source code for all modules it contains, plus any associated 165 | interface definition files, plus the scripts used to control compilation 166 | and installation of the library. 167 | 168 | Activities other than copying, distribution and modification are not 169 | covered by this License; they are outside its scope. The act of 170 | running a program using the Library is not restricted, and output from 171 | such a program is covered only if its contents constitute a work based 172 | on the Library (independent of the use of the Library in a tool for 173 | writing it). Whether that is true depends on what the Library does 174 | and what the program that uses the Library does. 175 | 176 | 1. You may copy and distribute verbatim copies of the Library's 177 | complete source code as you receive it, in any medium, provided that 178 | you conspicuously and appropriately publish on each copy an 179 | appropriate copyright notice and disclaimer of warranty; keep intact 180 | all the notices that refer to this License and to the absence of any 181 | warranty; and distribute a copy of this License along with the 182 | Library. 183 | 184 | You may charge a fee for the physical act of transferring a copy, 185 | and you may at your option offer warranty protection in exchange for a 186 | fee. 187 | 188 | 2. You may modify your copy or copies of the Library or any portion 189 | of it, thus forming a work based on the Library, and copy and 190 | distribute such modifications or work under the terms of Section 1 191 | above, provided that you also meet all of these conditions: 192 | 193 | a) The modified work must itself be a software library. 194 | 195 | b) You must cause the files modified to carry prominent notices 196 | stating that you changed the files and the date of any change. 197 | 198 | c) You must cause the whole of the work to be licensed at no 199 | charge to all third parties under the terms of this License. 200 | 201 | d) If a facility in the modified Library refers to a function or a 202 | table of data to be supplied by an application program that uses 203 | the facility, other than as an argument passed when the facility 204 | is invoked, then you must make a good faith effort to ensure that, 205 | in the event an application does not supply such function or 206 | table, the facility still operates, and performs whatever part of 207 | its purpose remains meaningful. 208 | 209 | (For example, a function in a library to compute square roots has 210 | a purpose that is entirely well-defined independent of the 211 | application. Therefore, Subsection 2d requires that any 212 | application-supplied function or table used by this function must 213 | be optional: if the application does not supply it, the square 214 | root function must still compute square roots.) 215 | 216 | These requirements apply to the modified work as a whole. If 217 | identifiable sections of that work are not derived from the Library, 218 | and can be reasonably considered independent and separate works in 219 | themselves, then this License, and its terms, do not apply to those 220 | sections when you distribute them as separate works. But when you 221 | distribute the same sections as part of a whole which is a work based 222 | on the Library, the distribution of the whole must be on the terms of 223 | this License, whose permissions for other licensees extend to the 224 | entire whole, and thus to each and every part regardless of who wrote 225 | it. 226 | 227 | Thus, it is not the intent of this section to claim rights or contest 228 | your rights to work written entirely by you; rather, the intent is to 229 | exercise the right to control the distribution of derivative or 230 | collective works based on the Library. 231 | 232 | In addition, mere aggregation of another work not based on the Library 233 | with the Library (or with a work based on the Library) on a volume of 234 | a storage or distribution medium does not bring the other work under 235 | the scope of this License. 236 | 237 | 3. You may opt to apply the terms of the ordinary GNU General Public 238 | License instead of this License to a given copy of the Library. To do 239 | this, you must alter all the notices that refer to this License, so 240 | that they refer to the ordinary GNU General Public License, version 2, 241 | instead of to this License. (If a newer version than version 2 of the 242 | ordinary GNU General Public License has appeared, then you can specify 243 | that version instead if you wish.) Do not make any other change in 244 | these notices. 245 | 246 | Once this change is made in a given copy, it is irreversible for 247 | that copy, so the ordinary GNU General Public License applies to all 248 | subsequent copies and derivative works made from that copy. 249 | 250 | This option is useful when you wish to copy part of the code of 251 | the Library into a program that is not a library. 252 | 253 | 4. You may copy and distribute the Library (or a portion or 254 | derivative of it, under Section 2) in object code or executable form 255 | under the terms of Sections 1 and 2 above provided that you accompany 256 | it with the complete corresponding machine-readable source code, which 257 | must be distributed under the terms of Sections 1 and 2 above on a 258 | medium customarily used for software interchange. 259 | 260 | If distribution of object code is made by offering access to copy 261 | from a designated place, then offering equivalent access to copy the 262 | source code from the same place satisfies the requirement to 263 | distribute the source code, even though third parties are not 264 | compelled to copy the source along with the object code. 265 | 266 | 5. A program that contains no derivative of any portion of the 267 | Library, but is designed to work with the Library by being compiled or 268 | linked with it, is called a "work that uses the Library". Such a 269 | work, in isolation, is not a derivative work of the Library, and 270 | therefore falls outside the scope of this License. 271 | 272 | However, linking a "work that uses the Library" with the Library 273 | creates an executable that is a derivative of the Library (because it 274 | contains portions of the Library), rather than a "work that uses the 275 | library". The executable is therefore covered by this License. 276 | Section 6 states terms for distribution of such executables. 277 | 278 | When a "work that uses the Library" uses material from a header file 279 | that is part of the Library, the object code for the work may be a 280 | derivative work of the Library even though the source code is not. 281 | Whether this is true is especially significant if the work can be 282 | linked without the Library, or if the work is itself a library. The 283 | threshold for this to be true is not precisely defined by law. 284 | 285 | If such an object file uses only numerical parameters, data 286 | structure layouts and accessors, and small macros and small inline 287 | functions (ten lines or less in length), then the use of the object 288 | file is unrestricted, regardless of whether it is legally a derivative 289 | work. (Executables containing this object code plus portions of the 290 | Library will still fall under Section 6.) 291 | 292 | Otherwise, if the work is a derivative of the Library, you may 293 | distribute the object code for the work under the terms of Section 6. 294 | Any executables containing that work also fall under Section 6, 295 | whether or not they are linked directly with the Library itself. 296 | 297 | 6. As an exception to the Sections above, you may also combine or 298 | link a "work that uses the Library" with the Library to produce a 299 | work containing portions of the Library, and distribute that work 300 | under terms of your choice, provided that the terms permit 301 | modification of the work for the customer's own use and reverse 302 | engineering for debugging such modifications. 303 | 304 | You must give prominent notice with each copy of the work that the 305 | Library is used in it and that the Library and its use are covered by 306 | this License. You must supply a copy of this License. If the work 307 | during execution displays copyright notices, you must include the 308 | copyright notice for the Library among them, as well as a reference 309 | directing the user to the copy of this License. Also, you must do one 310 | of these things: 311 | 312 | a) Accompany the work with the complete corresponding 313 | machine-readable source code for the Library including whatever 314 | changes were used in the work (which must be distributed under 315 | Sections 1 and 2 above); and, if the work is an executable linked 316 | with the Library, with the complete machine-readable "work that 317 | uses the Library", as object code and/or source code, so that the 318 | user can modify the Library and then relink to produce a modified 319 | executable containing the modified Library. (It is understood 320 | that the user who changes the contents of definitions files in the 321 | Library will not necessarily be able to recompile the application 322 | to use the modified definitions.) 323 | 324 | b) Use a suitable shared library mechanism for linking with the 325 | Library. A suitable mechanism is one that (1) uses at run time a 326 | copy of the library already present on the user's computer system, 327 | rather than copying library functions into the executable, and (2) 328 | will operate properly with a modified version of the library, if 329 | the user installs one, as long as the modified version is 330 | interface-compatible with the version that the work was made with. 331 | 332 | c) Accompany the work with a written offer, valid for at 333 | least three years, to give the same user the materials 334 | specified in Subsection 6a, above, for a charge no more 335 | than the cost of performing this distribution. 336 | 337 | d) If distribution of the work is made by offering access to copy 338 | from a designated place, offer equivalent access to copy the above 339 | specified materials from the same place. 340 | 341 | e) Verify that the user has already received a copy of these 342 | materials or that you have already sent this user a copy. 343 | 344 | For an executable, the required form of the "work that uses the 345 | Library" must include any data and utility programs needed for 346 | reproducing the executable from it. However, as a special exception, 347 | the materials to be distributed need not include anything that is 348 | normally distributed (in either source or binary form) with the major 349 | components (compiler, kernel, and so on) of the operating system on 350 | which the executable runs, unless that component itself accompanies 351 | the executable. 352 | 353 | It may happen that this requirement contradicts the license 354 | restrictions of other proprietary libraries that do not normally 355 | accompany the operating system. Such a contradiction means you cannot 356 | use both them and the Library together in an executable that you 357 | distribute. 358 | 359 | 7. You may place library facilities that are a work based on the 360 | Library side-by-side in a single library together with other library 361 | facilities not covered by this License, and distribute such a combined 362 | library, provided that the separate distribution of the work based on 363 | the Library and of the other library facilities is otherwise 364 | permitted, and provided that you do these two things: 365 | 366 | a) Accompany the combined library with a copy of the same work 367 | based on the Library, uncombined with any other library 368 | facilities. This must be distributed under the terms of the 369 | Sections above. 370 | 371 | b) Give prominent notice with the combined library of the fact 372 | that part of it is a work based on the Library, and explaining 373 | where to find the accompanying uncombined form of the same work. 374 | 375 | 8. You may not copy, modify, sublicense, link with, or distribute 376 | the Library except as expressly provided under this License. Any 377 | attempt otherwise to copy, modify, sublicense, link with, or 378 | distribute the Library is void, and will automatically terminate your 379 | rights under this License. However, parties who have received copies, 380 | or rights, from you under this License will not have their licenses 381 | terminated so long as such parties remain in full compliance. 382 | 383 | 9. You are not required to accept this License, since you have not 384 | signed it. However, nothing else grants you permission to modify or 385 | distribute the Library or its derivative works. These actions are 386 | prohibited by law if you do not accept this License. Therefore, by 387 | modifying or distributing the Library (or any work based on the 388 | Library), you indicate your acceptance of this License to do so, and 389 | all its terms and conditions for copying, distributing or modifying 390 | the Library or works based on it. 391 | 392 | 10. Each time you redistribute the Library (or any work based on the 393 | Library), the recipient automatically receives a license from the 394 | original licensor to copy, distribute, link with or modify the Library 395 | subject to these terms and conditions. You may not impose any further 396 | restrictions on the recipients' exercise of the rights granted herein. 397 | You are not responsible for enforcing compliance by third parties with 398 | this License. 399 | 400 | 11. If, as a consequence of a court judgment or allegation of patent 401 | infringement or for any other reason (not limited to patent issues), 402 | conditions are imposed on you (whether by court order, agreement or 403 | otherwise) that contradict the conditions of this License, they do not 404 | excuse you from the conditions of this License. If you cannot 405 | distribute so as to satisfy simultaneously your obligations under this 406 | License and any other pertinent obligations, then as a consequence you 407 | may not distribute the Library at all. For example, if a patent 408 | license would not permit royalty-free redistribution of the Library by 409 | all those who receive copies directly or indirectly through you, then 410 | the only way you could satisfy both it and this License would be to 411 | refrain entirely from distribution of the Library. 412 | 413 | If any portion of this section is held invalid or unenforceable under any 414 | particular circumstance, the balance of the section is intended to apply, 415 | and the section as a whole is intended to apply in other circumstances. 416 | 417 | It is not the purpose of this section to induce you to infringe any 418 | patents or other property right claims or to contest validity of any 419 | such claims; this section has the sole purpose of protecting the 420 | integrity of the free software distribution system which is 421 | implemented by public license practices. Many people have made 422 | generous contributions to the wide range of software distributed 423 | through that system in reliance on consistent application of that 424 | system; it is up to the author/donor to decide if he or she is willing 425 | to distribute software through any other system and a licensee cannot 426 | impose that choice. 427 | 428 | This section is intended to make thoroughly clear what is believed to 429 | be a consequence of the rest of this License. 430 | 431 | 12. If the distribution and/or use of the Library is restricted in 432 | certain countries either by patents or by copyrighted interfaces, the 433 | original copyright holder who places the Library under this License may add 434 | an explicit geographical distribution limitation excluding those countries, 435 | so that distribution is permitted only in or among countries not thus 436 | excluded. In such case, this License incorporates the limitation as if 437 | written in the body of this License. 438 | 439 | 13. The Free Software Foundation may publish revised and/or new 440 | versions of the Lesser General Public License from time to time. 441 | Such new versions will be similar in spirit to the present version, 442 | but may differ in detail to address new problems or concerns. 443 | 444 | Each version is given a distinguishing version number. If the Library 445 | specifies a version number of this License which applies to it and 446 | "any later version", you have the option of following the terms and 447 | conditions either of that version or of any later version published by 448 | the Free Software Foundation. If the Library does not specify a 449 | license version number, you may choose any version ever published by 450 | the Free Software Foundation. 451 | 452 | 14. If you wish to incorporate parts of the Library into other free 453 | programs whose distribution conditions are incompatible with these, 454 | write to the author to ask for permission. For software which is 455 | copyrighted by the Free Software Foundation, write to the Free 456 | Software Foundation; we sometimes make exceptions for this. Our 457 | decision will be guided by the two goals of preserving the free status 458 | of all derivatives of our free software and of promoting the sharing 459 | and reuse of software generally. 460 | 461 | NO WARRANTY 462 | 463 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 464 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 465 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 466 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 467 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 468 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 469 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 470 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 471 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 472 | 473 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 474 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 475 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 476 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 477 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 478 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 479 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 480 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 481 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 482 | DAMAGES. 483 | 484 | END OF TERMS AND CONDITIONS 485 | 486 | How to Apply These Terms to Your New Libraries 487 | 488 | If you develop a new library, and you want it to be of the greatest 489 | possible use to the public, we recommend making it free software that 490 | everyone can redistribute and change. You can do so by permitting 491 | redistribution under these terms (or, alternatively, under the terms of the 492 | ordinary General Public License). 493 | 494 | To apply these terms, attach the following notices to the library. It is 495 | safest to attach them to the start of each source file to most effectively 496 | convey the exclusion of warranty; and each file should have at least the 497 | "copyright" line and a pointer to where the full notice is found. 498 | 499 | 500 | Copyright (C) 501 | 502 | This library is free software; you can redistribute it and/or 503 | modify it under the terms of the GNU Lesser General Public 504 | License as published by the Free Software Foundation; either 505 | version 2.1 of the License, or (at your option) any later version. 506 | 507 | This library is distributed in the hope that it will be useful, 508 | but WITHOUT ANY WARRANTY; without even the implied warranty of 509 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 510 | Lesser General Public License for more details. 511 | 512 | You should have received a copy of the GNU Lesser General Public 513 | License along with this library; if not, write to the Free Software 514 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 515 | 516 | Also add information on how to contact you by electronic and paper mail. 517 | 518 | You should also get your employer (if you work as a programmer) or your 519 | school, if any, to sign a "copyright disclaimer" for the library, if 520 | necessary. Here is a sample; alter the names: 521 | 522 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 523 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 524 | 525 | , 1 April 1990 526 | Ty Coon, President of Vice 527 | 528 | That's all there is to it! 529 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Summary 3 | 4 | This is an NES emulator library written in D. It started as a port to D from Go of [github.com/fogleman/nes](https://github.com/fogleman/nes/). It's pure D & has no dependencies. 5 | 6 | ### Installation 7 | 8 | Just add "nes": "\~>0.2.0" or "nes" version="\~>0.2.0" to the dependencies section of your dub.json or dub.sdl file. 9 | 10 | ### Usage 11 | 12 | See [github.com/blahness/nes_test](https://github.com/blahness/nes_test/) for an example usage. 13 | 14 | ### Mappers 15 | 16 | The following mappers have been implemented: 17 | 18 | * NROM (0) 19 | * MMC1 (1) 20 | * UNROM (2) 21 | * CNROM (3) 22 | * MMC3 (4) 23 | * AOROM (7) 24 | * 255 25 | 26 | ### Test ROM Results 27 | 28 | #### CPU Tests 29 | 30 | | Name | Results | 31 | | ---------------------------- | -------------------- | 32 | | branch_timing_tests |
3/3 Pass1.Branch_Basics :heavy_check_mark:
2.Backward_Branch :heavy_check_mark:
3.Forward_Branch :heavy_check_mark:
| 33 | | cpu_interrupts_v2 |
6/6 Passcpu_interrupts :heavy_check_mark:
1-cli_latency :heavy_check_mark:
2-nmi_and_brk :heavy_check_mark:
3-nmi_and_irq :heavy_check_mark:
4-irq_and_dma :heavy_check_mark:
5-branch_delays_irq :heavy_check_mark:
| 34 | | cpu_timing_test6 | Pass | 35 | | instr_test-v5 |
18/18 Passall_instrs :heavy_check_mark:
official_only :heavy_check_mark:
01-basics :heavy_check_mark:
02-implied :heavy_check_mark:
03-immediate :heavy_check_mark:
04-zero_page :heavy_check_mark:
05-zp_xy :heavy_check_mark:
06-absolute :heavy_check_mark:
07-abs_xy :heavy_check_mark:
08-ind_x :heavy_check_mark:
09-ind_y :heavy_check_mark:
10-branches :heavy_check_mark:
11-stack :heavy_check_mark:
12-jmp_jsr :heavy_check_mark:
13-rts :heavy_check_mark:
14-rti :heavy_check_mark:
15-brk :heavy_check_mark:
16-special :heavy_check_mark:
| 36 | | instr_timing |
3/3 Passinstr_timing :heavy_check_mark:
1-instr_timing :heavy_check_mark:
2-branch_timing :heavy_check_mark:
| 37 | | nestest | Pass | 38 | 39 | #### PPU Tests 40 | 41 | | Name | Results | 42 | | ---------------------------- | -------------------- | 43 | | blargg_ppu_tests_2005.09.15b |
5/5 Passpalette_ram :heavy_check_mark:
power_up_palette :heavy_check_mark:
sprite_ram :heavy_check_mark:
vbl_clear_time :heavy_check_mark:
vram_access :heavy_check_mark:
| 44 | | nmi_sync |
0/2 Passdemo_ntsc :x:
demo_pal :x:
| 45 | | ppu_sprite_hit |
11/11 Passppu_sprite_hit :heavy_check_mark:
01-basics :heavy_check_mark:
02-alignment :heavy_check_mark:
03-corners :heavy_check_mark:
04-flip :heavy_check_mark:
05-left_clip :heavy_check_mark:
06-right_edge :heavy_check_mark:
07-screen_bottom :heavy_check_mark:
08-double_height :heavy_check_mark:
09-timing :heavy_check_mark:
10-timing_order :heavy_check_mark:
| 46 | | ppu_vbl_nmi |
11/11 Passppu_vbl_nmi :heavy_check_mark:
01-vbl_basics :heavy_check_mark:
02-vbl_set_time :heavy_check_mark:
03-vbl_clear_time :heavy_check_mark:
04-nmi_control :heavy_check_mark:
05-nmi_timing :heavy_check_mark:
06-suppression :heavy_check_mark:
07-nmi_on_timing :heavy_check_mark:
08-nmi_off_timing :heavy_check_mark:
09-even_odd_frames :heavy_check_mark:
10-even_odd_timing :heavy_check_mark:
| 47 | | scanline | Pass | 48 | 49 | #### APU Tests 50 | 51 | | Name | Results | Notes | 52 | | ---------------------------- | -------------------- | -------------------- | 53 | | apu_mixer |
4/4 Passdmc :heavy_check_mark:
noise :heavy_check_mark:
square :heavy_check_mark:
triangle :heavy_check_mark:
| | 54 | | apu_phase_reset | Pass | | 55 | | apu_reset |
6/6 Pass4015_cleared :heavy_check_mark:
4017_timing :heavy_check_mark:
4017_written :heavy_check_mark:
irq_flag_cleared :heavy_check_mark:
len_ctrs_enabled :heavy_check_mark:
works_immediately :heavy_check_mark:
| | 56 | | apu_test |
9/9 Passapu_test :heavy_check_mark:
1-len_ctr :heavy_check_mark:
2-len_table :heavy_check_mark:
3-irq_flag :heavy_check_mark:
4-jitter :heavy_check_mark:
5-len_timing :heavy_check_mark:
6-irq_flag_timing :heavy_check_mark:
7-dmc_basics :heavy_check_mark:
8-dmc_rates :heavy_check_mark:
| | 57 | | blargg_apu_2005.07.30 |
11/11 Pass01.len_ctr :heavy_check_mark:
02.len_table :heavy_check_mark:
03.irq_flag :heavy_check_mark:
04.clock_jitter :heavy_check_mark:
05.len_timing_mode0 :heavy_check_mark:
06.len_timing_mode1 :heavy_check_mark:
07.irq_flag_timing :heavy_check_mark:
08.irq_timing :heavy_check_mark:
09.reset_timing :heavy_check_mark:
10.len_halt_timing :heavy_check_mark:
11.len_reload_timing :heavy_check_mark:
| | 58 | | dmc_tests |
4/4 Passbuffer_retained :heavy_check_mark:
latency :heavy_check_mark:
status :heavy_check_mark:
status_irq :heavy_check_mark:
| | 59 | | dpcmletterbox | Pass | HSYNC timing issues create flicker when scrolling | 60 | | fadeout_and_triangle_test | Pass | | 61 | | square_timer_div2 | Pass | | 62 | | test_apu_2 |
10/10 Passtest_1 :heavy_check_mark:
test_2 :heavy_check_mark:
test_3 :heavy_check_mark:
test_4 :heavy_check_mark:
test_5 :heavy_check_mark:
test_6 :heavy_check_mark:
test_7 :heavy_check_mark:
test_8 :heavy_check_mark:
test_9 :heavy_check_mark:
test_10 :heavy_check_mark:
| | 63 | | test_apu_env | Pass | | 64 | | test_apu_m |
3/3 Passtest_9 :heavy_check_mark:
test_10 :heavy_check_mark:
test_11 :heavy_check_mark:
| | 65 | | test_apu_sweep |
2/2 Passsweep_cutoff :heavy_check_mark:
sweep_sub :heavy_check_mark:
| | 66 | | test_apu_timers |
4/4 Passdmc_pitch :heavy_check_mark:
noise_pitch :heavy_check_mark:
square_pitch :heavy_check_mark:
triangle_pitch :heavy_check_mark:
| | 67 | | test_tri_lin_ctr | Pass | | 68 | | volume_tests | Pass | | 69 | 70 | ### Known Issues 71 | 72 | * there are some minor issues with PPU timing, but most games work OK anyway 73 | * the APU emulation isn't quite perfect, but not far off 74 | * only NTSC timing supported for now -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nes", 3 | "description": "NES emulator library written in D", 4 | "authors": ["Blahness"], 5 | "license": "MIT License", 6 | "targetType": "library", 7 | "targetName": "nes" 8 | } -------------------------------------------------------------------------------- /source/blip_buf.d: -------------------------------------------------------------------------------- 1 | /* blip_buf 1.1.0. http://www.slack.net/~ant/ */ 2 | 3 | module blip_buf; 4 | 5 | import core.stdc.stdlib; 6 | import core.stdc.string; 7 | 8 | /* Library Copyright (C) 2003-2009 Shay Green. This library is free software; 9 | you can redistribute it and/or modify it under the terms of the GNU Lesser 10 | General Public License as published by the Free Software Foundation; either 11 | version 2.1 of the License, or (at your option) any later version. This 12 | library is distributed in the hope that it will be useful, but WITHOUT ANY 13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | details. You should have received a copy of the GNU Lesser General Public 16 | License along with this module; if not, write to the Free Software Foundation, 17 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 18 | 19 | 20 | /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, 21 | clock_rate must not be greater than sample_rate*blip_max_ratio. */ 22 | enum blip_max_ratio = 1 << 20; 23 | 24 | /** Maximum number of samples that can be generated from one time frame. */ 25 | enum blip_max_frame = 4000; 26 | 27 | alias fixed_t = ulong; 28 | enum pre_shift = 32; 29 | 30 | enum time_bits = pre_shift + 20; 31 | 32 | const fixed_t time_unit = cast(fixed_t)(1) << time_bits; 33 | 34 | enum bass_shift = 9; /* affects high-pass filter breakpoint frequency */ 35 | enum end_frame_extra = 2; /* allows deltas slightly after frame length */ 36 | 37 | enum half_width = 8; 38 | enum buf_extra = half_width*2 + end_frame_extra; 39 | enum phase_bits = 5; 40 | enum phase_count = 1 << phase_bits; 41 | enum delta_bits = 15; 42 | enum delta_unit = 1 << delta_bits; 43 | enum frac_bits = time_bits - pre_shift; 44 | 45 | /* We could eliminate avail and encode whole samples in offset, but that would 46 | limit the total buffered samples to blip_max_frame. That could only be 47 | increased by decreasing time_bits, which would reduce resample ratio accuracy. 48 | */ 49 | 50 | /** Sample buffer that resamples to output rate and accumulates samples 51 | until they're read out */ 52 | struct blip_t { 53 | fixed_t factor; 54 | fixed_t offset; 55 | int avail; 56 | int size; 57 | int integrator; 58 | } 59 | 60 | alias buf_t = int; 61 | 62 | /* probably not totally portable */ 63 | buf_t* SAMPLES(blip_t* buf) { 64 | return cast(buf_t*)(buf + 1); 65 | } 66 | 67 | /* Arithmetic (sign-preserving) right shift */ 68 | int ARITH_SHIFT(int n, int shift) { 69 | return n >> shift; 70 | } 71 | 72 | enum max_sample = +32767; 73 | enum min_sample = -32768; 74 | 75 | void CLAMP(ref int n) { 76 | if (cast(short)n != n) 77 | n = ARITH_SHIFT(n, 16) ^ max_sample; 78 | } 79 | 80 | void check_assumptions() { 81 | int n; 82 | 83 | assert(int.max >= 0x7FFFFFFF && uint.max >= 0xFFFFFFFF, "int must be at least 32 bits"); 84 | 85 | assert((-3 >> 1) == -2); /* right shift must preserve sign */ 86 | 87 | n = max_sample * 2; 88 | CLAMP(n); 89 | assert(n == max_sample); 90 | 91 | n = min_sample * 2; 92 | CLAMP(n); 93 | assert(n == min_sample); 94 | 95 | assert(blip_max_ratio <= time_unit); 96 | assert(blip_max_frame <= cast(fixed_t)-1 >> time_bits); 97 | } 98 | 99 | blip_t* blip_new(int size) { 100 | blip_t* m; 101 | assert(size >= 0); 102 | 103 | m = cast(blip_t*) malloc((*m).sizeof + (size + buf_extra) * buf_t.sizeof); 104 | if (m) { 105 | m.factor = time_unit / blip_max_ratio; 106 | m.size = size; 107 | blip_clear(m); 108 | check_assumptions(); 109 | } 110 | return m; 111 | } 112 | 113 | void blip_delete(blip_t* m) { 114 | if (m != null) { 115 | /* Clear fields in case user tries to use after freeing */ 116 | memset(m, 0, (*m).sizeof); 117 | free(m); 118 | } 119 | } 120 | 121 | void blip_set_rates(blip_t* m, double clock_rate, double sample_rate) { 122 | double factor = time_unit * sample_rate / clock_rate; 123 | m.factor = cast(fixed_t)factor; 124 | 125 | /* Fails if clock_rate exceeds maximum, relative to sample_rate */ 126 | assert(0 <= factor - m.factor && factor - m.factor < 1); 127 | 128 | /* Avoid requiring math.h. Equivalent to 129 | m.factor = (int) ceil(factor) */ 130 | if (m.factor < factor) 131 | m.factor++; 132 | 133 | /* At this point, factor is most likely rounded up, but could still 134 | have been rounded down in the floating-point calculation. */ 135 | } 136 | 137 | void blip_clear(blip_t* m) { 138 | /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if 139 | factor is rounded up. factor-1 is suitable if factor is rounded down. 140 | Since we don't know rounding direction, factor/2 accommodates either, 141 | with the slight loss of showing an error in half the time. Since for 142 | a 64-bit factor this is years, the halving isn't a problem. */ 143 | 144 | m.offset = m.factor / 2; 145 | m.avail = 0; 146 | m.integrator = 0; 147 | memset(SAMPLES(m), 0, (m.size + buf_extra) * buf_t.sizeof); 148 | } 149 | 150 | int blip_clocks_needed(const blip_t* m, int samples) { 151 | fixed_t needed; 152 | 153 | /* Fails if buffer can't hold that many more samples */ 154 | assert(samples >= 0 && m.avail + samples <= m.size); 155 | 156 | needed = cast(fixed_t) samples * time_unit; 157 | if (needed < m.offset) 158 | return 0; 159 | 160 | return cast(int)((needed - m.offset + m.factor - 1) / m.factor); 161 | } 162 | 163 | void blip_end_frame(blip_t* m, uint t) { 164 | fixed_t off = t * m.factor + m.offset; 165 | m.avail += off >> time_bits; 166 | m.offset = off & (time_unit - 1); 167 | 168 | /* Fails if buffer size was exceeded */ 169 | assert(m.avail <= m.size); 170 | } 171 | 172 | int blip_samples_avail(const blip_t* m) { 173 | return m.avail; 174 | } 175 | 176 | void remove_samples(blip_t* m, int count) { 177 | buf_t* buf = SAMPLES(m); 178 | int remain = m.avail + buf_extra - count; 179 | m.avail -= count; 180 | 181 | memmove(&buf [0], &buf[count], remain * buf[0].sizeof); 182 | memset(&buf[remain], 0, count * buf[0].sizeof); 183 | } 184 | 185 | int blip_read_samples(blip_t* m, short* _out, int count, int stereo) { 186 | assert(count >= 0); 187 | 188 | if (count > m.avail) 189 | count = m.avail; 190 | 191 | if (count) { 192 | const int step = stereo ? 2 : 1; 193 | const(buf_t)* _in = SAMPLES(m); 194 | const(buf_t)* end = _in + count; 195 | int sum = m.integrator; 196 | do { 197 | /* Eliminate fraction */ 198 | int s = ARITH_SHIFT(sum, delta_bits); 199 | 200 | sum += *_in++; 201 | 202 | CLAMP(s); 203 | 204 | *_out = cast(short)s; 205 | _out += step; 206 | 207 | /* High-pass filter */ 208 | sum -= s << (delta_bits - bass_shift); 209 | } while (_in != end); 210 | m.integrator = sum; 211 | 212 | remove_samples(m, count); 213 | } 214 | 215 | return count; 216 | } 217 | 218 | /* Sinc_Generator(0.9, 0.55, 4.5) */ 219 | immutable short[half_width][phase_count + 1] bl_step = 220 | [ 221 | [ 43, -115, 350, -488, 1136, -914, 5861,21022], 222 | [ 44, -118, 348, -473, 1076, -799, 5274,21001], 223 | [ 45, -121, 344, -454, 1011, -677, 4706,20936], 224 | [ 46, -122, 336, -431, 942, -549, 4156,20829], 225 | [ 47, -123, 327, -404, 868, -418, 3629,20679], 226 | [ 47, -122, 316, -375, 792, -285, 3124,20488], 227 | [ 47, -120, 303, -344, 714, -151, 2644,20256], 228 | [ 46, -117, 289, -310, 634, -17, 2188,19985], 229 | [ 46, -114, 273, -275, 553, 117, 1758,19675], 230 | [ 44, -108, 255, -237, 471, 247, 1356,19327], 231 | [ 43, -103, 237, -199, 390, 373, 981,18944], 232 | [ 42, -98, 218, -160, 310, 495, 633,18527], 233 | [ 40, -91, 198, -121, 231, 611, 314,18078], 234 | [ 38, -84, 178, -81, 153, 722, 22,17599], 235 | [ 36, -76, 157, -43, 80, 824, -241,17092], 236 | [ 34, -68, 135, -3, 8, 919, -476,16558], 237 | [ 32, -61, 115, 34, -60, 1006, -683,16001], 238 | [ 29, -52, 94, 70, -123, 1083, -862,15422], 239 | [ 27, -44, 73, 106, -184, 1152,-1015,14824], 240 | [ 25, -36, 53, 139, -239, 1211,-1142,14210], 241 | [ 22, -27, 34, 170, -290, 1261,-1244,13582], 242 | [ 20, -20, 16, 199, -335, 1301,-1322,12942], 243 | [ 18, -12, -3, 226, -375, 1331,-1376,12293], 244 | [ 15, -4, -19, 250, -410, 1351,-1408,11638], 245 | [ 13, 3, -35, 272, -439, 1361,-1419,10979], 246 | [ 11, 9, -49, 292, -464, 1362,-1410,10319], 247 | [ 9, 16, -63, 309, -483, 1354,-1383, 9660], 248 | [ 7, 22, -75, 322, -496, 1337,-1339, 9005], 249 | [ 6, 26, -85, 333, -504, 1312,-1280, 8355], 250 | [ 4, 31, -94, 341, -507, 1278,-1205, 7713], 251 | [ 3, 35, -102, 347, -506, 1238,-1119, 7082], 252 | [ 1, 40, -110, 350, -499, 1190,-1021, 6464], 253 | [ 0, 43, -115, 350, -488, 1136, -914, 5861] 254 | ]; 255 | 256 | /* Shifting by pre_shift allows calculation using unsigned int rather than 257 | possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. 258 | And by having pre_shift 32, a 32-bit platform can easily do the shift by 259 | simply ignoring the low half. */ 260 | 261 | void blip_add_delta(blip_t* m, uint time, int delta) { 262 | uint fixed = cast(uint) ((time * m.factor + m.offset) >> pre_shift); 263 | buf_t* _out = SAMPLES(m) + m.avail + (fixed >> frac_bits); 264 | 265 | const int phase_shift = frac_bits - phase_bits; 266 | int phase = fixed >> phase_shift & (phase_count - 1); 267 | const(short)* _in = bl_step[phase].ptr; 268 | const(short)* rev = bl_step[phase_count - phase].ptr; 269 | 270 | int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); 271 | int delta2 = (delta * interp) >> delta_bits; 272 | delta -= delta2; 273 | 274 | /* Fails if buffer size was exceeded */ 275 | assert(_out <= &SAMPLES(m) [m.size + end_frame_extra]); 276 | 277 | _out [0] += _in[0]*delta + _in[half_width+0]*delta2; 278 | _out [1] += _in[1]*delta + _in[half_width+1]*delta2; 279 | _out [2] += _in[2]*delta + _in[half_width+2]*delta2; 280 | _out [3] += _in[3]*delta + _in[half_width+3]*delta2; 281 | _out [4] += _in[4]*delta + _in[half_width+4]*delta2; 282 | _out [5] += _in[5]*delta + _in[half_width+5]*delta2; 283 | _out [6] += _in[6]*delta + _in[half_width+6]*delta2; 284 | _out [7] += _in[7]*delta + _in[half_width+7]*delta2; 285 | 286 | _in = rev; 287 | _out [ 8] += _in[7]*delta + _in[7-half_width]*delta2; 288 | _out [ 9] += _in[6]*delta + _in[6-half_width]*delta2; 289 | _out [10] += _in[5]*delta + _in[5-half_width]*delta2; 290 | _out [11] += _in[4]*delta + _in[4-half_width]*delta2; 291 | _out [12] += _in[3]*delta + _in[3-half_width]*delta2; 292 | _out [13] += _in[2]*delta + _in[2-half_width]*delta2; 293 | _out [14] += _in[1]*delta + _in[1-half_width]*delta2; 294 | _out [15] += _in[0]*delta + _in[0-half_width]*delta2; 295 | } 296 | 297 | void blip_add_delta_fast(blip_t* m, uint time, int delta) { 298 | uint fixed = cast(uint) ((time * m.factor + m.offset) >> pre_shift); 299 | buf_t* _out = SAMPLES(m) + m.avail + (fixed >> frac_bits); 300 | 301 | int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); 302 | int delta2 = delta * interp; 303 | 304 | /* Fails if buffer size was exceeded */ 305 | assert(_out <= &SAMPLES(m)[m.size + end_frame_extra]); 306 | 307 | _out [7] += delta * delta_unit - delta2; 308 | _out [8] += delta2; 309 | } 310 | -------------------------------------------------------------------------------- /source/nes/apu.d: -------------------------------------------------------------------------------- 1 | module nes.apu; 2 | 3 | import std.conv; 4 | import std.math; 5 | import std.stdio; 6 | 7 | import nes.console; 8 | import nes.cpu; 9 | 10 | import blip_buf; 11 | 12 | enum MAX_SAMPLE_RATE = 96000; 13 | 14 | immutable ulong[6][2] stepCycles = [[7457, 14913, 22371, 29828, 29829, 29830], 15 | [7457, 14913, 22371, 29829, 37281, 37282]]; 16 | 17 | immutable ubyte[] lengthTable = [ 18 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 19 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 20 | ]; 21 | 22 | immutable ubyte[][] dutyTable = [ 23 | [0, 1, 0, 0, 0, 0, 0, 0], 24 | [0, 1, 1, 0, 0, 0, 0, 0], 25 | [0, 1, 1, 1, 1, 0, 0, 0], 26 | [1, 0, 0, 1, 1, 1, 1, 1] 27 | ]; 28 | 29 | immutable ubyte[] triangleTable = [ 30 | 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 31 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 32 | ]; 33 | 34 | immutable ushort[] noiseTable = [ 35 | 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 36 | ]; 37 | 38 | immutable ubyte[] dmcTable = [ 39 | 214, 190, 170, 160, 143, 127, 113, 107, 95, 80, 71, 64, 53, 42, 36, 27 40 | ]; 41 | 42 | immutable float[31] pulseTable = 43 | () { 44 | float[31] result; 45 | foreach (i; 0 .. 31) 46 | result[i] = 95.52 / (8128.0 / cast(float)i + 100); 47 | return result; 48 | }(); 49 | 50 | immutable float[203] tndTable = 51 | () { 52 | float[203] result; 53 | foreach (i; 0 .. 203) 54 | result[i] = 163.67 / (24329.0 / cast(float)i + 100); 55 | return result; 56 | }(); 57 | 58 | alias void delegate(short) ApuCallbackFuncType; 59 | 60 | class APU { 61 | this(Console console) { 62 | this.console = console; 63 | this.pulse1.channel = 1; 64 | this.pulse2.channel = 2; 65 | this.dmc.cpu = console.cpu; 66 | 67 | this.blipBuf = blip_new(MAX_SAMPLE_RATE); 68 | 69 | this.reset(true); 70 | } 71 | 72 | ~this() { 73 | blip_delete(this.blipBuf); 74 | } 75 | 76 | void step() { 77 | auto cycle1 = this.cycle; 78 | this.cycle++; 79 | auto cycle2 = this.cycle; 80 | 81 | this.counter++; 82 | 83 | if (this.counter >= stepCycles[this.stepMode][this.currentStep]) { 84 | this.stepFrameCounter(); 85 | } 86 | 87 | if (this.frameCounterValue >= 0 && this.frameCounterDelay > 0) 88 | tryDelayedFrameCounterWrite(); 89 | 90 | if (this.blockFrameCounterTick > 0) { 91 | this.blockFrameCounterTick--; 92 | } 93 | 94 | this.stepTimer(); 95 | 96 | short currentOutput = floatSampleToShort(this.output()); 97 | 98 | short delta = cast(short)(currentOutput - this.blipPrevOutput); 99 | 100 | if (delta != 0) blip_add_delta(this.blipBuf, this.outputTick, delta); 101 | 102 | this.blipPrevOutput = currentOutput; 103 | 104 | this.outputTick++; 105 | 106 | auto s1 = cast(int)(cast(double)cycle1 / this.ticksPerSample); 107 | auto s2 = cast(int)(cast(double)cycle2 / this.ticksPerSample); 108 | if (s1 != s2) { 109 | this.sendSample(); 110 | this.outputTick = 0; 111 | } 112 | } 113 | 114 | void reset(bool powerUp = false) { 115 | this.cycle = 0; 116 | this.counter = 0; 117 | this.currentStep = 0; 118 | 119 | this.inhibitIRQ = false; 120 | this.frameIRQ = false; 121 | 122 | this.frameCounterDelay = -1; 123 | this.frameCounterValue = -1; 124 | this.blockFrameCounterTick = 0; 125 | 126 | this.writeControl(0); 127 | 128 | this.pulse1.reset(); 129 | this.pulse2.reset(); 130 | this.triangle.reset(powerUp); 131 | this.noise.reset(); 132 | this.dmc.reset(); 133 | 134 | this.outputTick = 0; 135 | 136 | blip_clear(this.blipBuf); 137 | 138 | foreach (_; 0 .. 8) 139 | this.step(); 140 | } 141 | 142 | ubyte readRegister(ushort address) { 143 | switch (address) { 144 | case 0x4015: 145 | return this.readStatus(); 146 | default: 147 | break; 148 | // default: 149 | // log.Fatalf("unhandled apu register read at address: 0x%04X", address) 150 | } 151 | return 0; 152 | } 153 | 154 | void writeRegister(ushort address, ubyte value) { 155 | switch (address) { 156 | case 0x4000: 157 | this.pulse1.writeControl(value); 158 | break; 159 | case 0x4001: 160 | this.pulse1.writeSweep(value); 161 | break; 162 | case 0x4002: 163 | this.pulse1.writeTimerLow(value); 164 | break; 165 | case 0x4003: 166 | this.pulse1.writeTimerHigh(value); 167 | break; 168 | case 0x4004: 169 | this.pulse2.writeControl(value); 170 | break; 171 | case 0x4005: 172 | this.pulse2.writeSweep(value); 173 | break; 174 | case 0x4006: 175 | this.pulse2.writeTimerLow(value); 176 | break; 177 | case 0x4007: 178 | this.pulse2.writeTimerHigh(value); 179 | break; 180 | case 0x4008: 181 | this.triangle.writeControl(value); 182 | break; 183 | case 0x4009: 184 | break; 185 | case 0x4010: 186 | this.dmc.writeControl(value); 187 | break; 188 | case 0x4011: 189 | this.dmc.writeValue(value); 190 | break; 191 | case 0x4012: 192 | this.dmc.writeAddress(value); 193 | break; 194 | case 0x4013: 195 | this.dmc.writeLength(value); 196 | break; 197 | case 0x400A: 198 | this.triangle.writeTimerLow(value); 199 | break; 200 | case 0x400B: 201 | this.triangle.writeTimerHigh(value); 202 | break; 203 | case 0x400C: 204 | this.noise.writeControl(value); 205 | break; 206 | case 0x400D: 207 | break; 208 | case 0x400E: 209 | this.noise.writePeriod(value); 210 | break; 211 | case 0x400F: 212 | this.noise.writeLength(value); 213 | break; 214 | case 0x4015: 215 | this.writeControl(value); 216 | break; 217 | case 0x4017: 218 | this.writeFrameCounter(value); 219 | break; 220 | default: 221 | break; 222 | // default: 223 | // log.Fatalf("unhandled apu register write at address: 0x%04X", address) 224 | } 225 | } 226 | 227 | void setAudioSampleRate(double sampleRate) { 228 | if (sampleRate != 0) { 229 | if (sampleRate > MAX_SAMPLE_RATE) sampleRate = MAX_SAMPLE_RATE; 230 | 231 | // Convert samples per second to cpu steps per sample 232 | this.ticksPerSample = CPUFrequency / sampleRate; 233 | 234 | blip_set_rates(this.blipBuf, CPUFrequency, sampleRate); 235 | } 236 | } 237 | 238 | void save(string[string] state) { 239 | state["apu.cycle"] = to!string(this.cycle); 240 | state["apu.frameIRQ"] = to!string(this.frameIRQ); 241 | state["apu.inhibitIRQ"] = to!string(this.inhibitIRQ); 242 | 243 | state["apu.counter"] = to!string(this.counter); 244 | state["apu.stepMode"] = to!string(this.stepMode); 245 | state["apu.currentStep"] = to!string(this.currentStep); 246 | state["apu.frameCounterValue"] = to!string(this.frameCounterValue); 247 | state["apu.frameCounterDelay"] = to!string(this.frameCounterDelay); 248 | state["apu.blockFrameCounterTick"] = to!string(this.blockFrameCounterTick); 249 | 250 | this.pulse1.save(state); 251 | this.pulse2.save(state); 252 | this.triangle.save(state); 253 | this.noise.save(state); 254 | this.dmc.save(state); 255 | } 256 | 257 | void load(string[string] state) { 258 | this.cycle = to!ulong(state["apu.cycle"]); 259 | this.frameIRQ = to!bool(state["apu.frameIRQ"]); 260 | this.inhibitIRQ = to!bool(state["apu.inhibitIRQ"]); 261 | 262 | this.counter = to!ulong(state["apu.counter"]); 263 | this.stepMode = to!uint(state["apu.stepMode"]); 264 | this.currentStep = to!uint(state["apu.currentStep"]); 265 | this.frameCounterValue = to!short(state["apu.frameCounterValue"]); 266 | this.frameCounterDelay = to!byte(state["apu.frameCounterDelay"]); 267 | this.blockFrameCounterTick = to!ubyte(state["apu.blockFrameCounterTick"]); 268 | 269 | this.pulse1.load(state); 270 | this.pulse2.load(state); 271 | this.triangle.load(state); 272 | this.noise.load(state); 273 | this.dmc.load(state); 274 | } 275 | 276 | package: 277 | ApuCallbackFuncType callback; 278 | 279 | Console console; 280 | Pulse pulse1; 281 | Pulse pulse2; 282 | Triangle triangle; 283 | Noise noise; 284 | DMC dmc; 285 | ulong cycle, counter; 286 | bool inhibitIRQ; 287 | bool frameIRQ; 288 | 289 | private: 290 | uint stepMode, currentStep; 291 | short frameCounterValue; 292 | byte frameCounterDelay; 293 | ubyte blockFrameCounterTick; 294 | blip_t* blipBuf; 295 | double ticksPerSample; 296 | uint outputTick; 297 | short blipOutput, blipPrevOutput; 298 | short[MAX_SAMPLE_RATE] outBuf; 299 | 300 | void sendSample() { 301 | if (this.callback == null) return; 302 | 303 | blip_end_frame(this.blipBuf, this.outputTick); 304 | 305 | auto sampleCount = blip_read_samples(this.blipBuf, outBuf.ptr, 306 | MAX_SAMPLE_RATE, 0); 307 | 308 | for (uint i = 0; i < sampleCount; i++) { 309 | this.callback(outBuf[i]); 310 | } 311 | } 312 | 313 | short floatSampleToShort(float f) { 314 | if (f > 1.0) f = 1.0; 315 | if (f < -1.0) f = -1.0; 316 | return cast(short)(f * 0x7fff); 317 | } 318 | 319 | float output() { 320 | auto p1 = this.pulse1.output(); 321 | auto p2 = this.pulse2.output(); 322 | auto t = this.triangle.output(); 323 | auto n = this.noise.output(); 324 | auto d = this.dmc.output(); 325 | 326 | float pulseOut, tndOut; 327 | 328 | pulseOut = pulseTable[p1 + p2]; 329 | tndOut = tndTable[3 * t + 2 * n + d]; 330 | 331 | return pulseOut + tndOut; 332 | } 333 | 334 | // mode 0: mode 1: function 335 | // --------- ----------- ----------------------------- 336 | // - - - f - - - - - IRQ (if bit 6 is clear) 337 | // - l - l l - l - - Length counter and sweep 338 | // e e e e e e e e - Envelope and linear counter 339 | void stepFrameCounter() { 340 | if (this.currentStep == 0 || this.currentStep == 2) { 341 | if (!this.blockFrameCounterTick) { 342 | this.stepEnvelope(); 343 | 344 | this.blockFrameCounterTick = 2; 345 | } 346 | } 347 | else if (this.currentStep == 1 || this.currentStep == 4) { 348 | if (!this.blockFrameCounterTick) { 349 | this.stepEnvelope(); 350 | 351 | this.stepSweep(); 352 | this.stepLength(); 353 | 354 | this.blockFrameCounterTick = 2; 355 | } 356 | 357 | if (this.currentStep == 4 && this.stepMode == 0 && !this.inhibitIRQ) { 358 | this.frameIRQ = true; 359 | } 360 | } 361 | else if (this.currentStep == 3 || this.currentStep == 5) { 362 | if (this.stepMode == 0) { 363 | if (!this.inhibitIRQ) { 364 | this.frameIRQ = true; 365 | 366 | if (this.currentStep == 3) { 367 | this.console.cpu.addIrqSource(IrqSource.FrameCounter); 368 | } 369 | } 370 | } 371 | } 372 | 373 | this.currentStep++; 374 | if (this.currentStep == 6) { 375 | this.currentStep = 0; 376 | this.counter = 0; 377 | } 378 | } 379 | 380 | void stepTimer() { 381 | if (this.cycle % 2 == 0) { 382 | this.pulse1.stepTimer(); 383 | this.pulse2.stepTimer(); 384 | this.noise.stepTimer(); 385 | this.dmc.stepTimer(); 386 | } 387 | this.triangle.stepTimer(); 388 | } 389 | 390 | void stepEnvelope() { 391 | this.pulse1.stepEnvelope(); 392 | this.pulse2.stepEnvelope(); 393 | this.triangle.stepCounter(); 394 | this.noise.stepEnvelope(); 395 | } 396 | 397 | void stepSweep() { 398 | this.pulse1.stepSweep(); 399 | this.pulse2.stepSweep(); 400 | } 401 | 402 | void stepLength() { 403 | this.pulse1.stepLength(); 404 | this.pulse2.stepLength(); 405 | this.triangle.stepLength(); 406 | this.noise.stepLength(); 407 | } 408 | 409 | ubyte readStatus() { 410 | ubyte result; 411 | 412 | if (this.pulse1.lengthValue > 0) { 413 | result |= 1; 414 | } 415 | if (this.pulse2.lengthValue > 0) { 416 | result |= 2; 417 | } 418 | if (this.triangle.lengthValue > 0) { 419 | result |= 4; 420 | } 421 | if (this.noise.lengthValue > 0) { 422 | result |= 8; 423 | } 424 | if (this.dmc.currentLength > 0) { 425 | result |= 16; 426 | } 427 | if (this.frameIRQ) { 428 | result |= 64; 429 | } 430 | if (this.console.cpu.hasIrqSource(IrqSource.DMC)) { 431 | result |= 128; 432 | } 433 | 434 | this.frameIRQ = false; 435 | this.console.cpu.clearIrqSource(IrqSource.FrameCounter); 436 | 437 | return result; 438 | } 439 | 440 | void writeControl(ubyte value) { 441 | this.console.cpu.clearIrqSource(IrqSource.DMC); 442 | 443 | this.pulse1.enabled = (value & 1) == 1; 444 | this.pulse2.enabled = (value & 2) == 2; 445 | this.triangle.enabled = (value & 4) == 4; 446 | this.noise.enabled = (value & 8) == 8; 447 | this.dmc.enabled = (value & 16) == 16; 448 | if (!this.pulse1.enabled) { 449 | this.pulse1.lengthValue = 0; 450 | } 451 | if (!this.pulse2.enabled) { 452 | this.pulse2.lengthValue = 0; 453 | } 454 | if (!this.triangle.enabled) { 455 | this.triangle.lengthValue = 0; 456 | } 457 | if (!this.noise.enabled) { 458 | this.noise.lengthValue = 0; 459 | } 460 | if (!this.dmc.enabled) { 461 | this.dmc.currentLength = 0; 462 | } else { 463 | if (this.dmc.currentLength == 0) { 464 | this.dmc.restart(); 465 | } 466 | } 467 | } 468 | 469 | void writeFrameCounter(ubyte value) { 470 | this.frameCounterValue = value; 471 | 472 | /** 473 | * If the write occurs during an APU cycle, the effects occur 3 CPU cycles after the $4017 write cycle, 474 | * and if the write occurs between APU cycles, the effects occurs 4 CPU cycles after the write cycle. 475 | * First CPU cycle we see is odd so even cycles are APU cycles. 476 | */ 477 | this.frameCounterDelay = (this.console.cpu.cycles & 0x01) == 1 ? 4 : 3; 478 | 479 | this.inhibitIRQ = ((value >> 6) & 1) == 1; 480 | 481 | if (this.inhibitIRQ) { 482 | this.frameIRQ = false; 483 | this.console.cpu.clearIrqSource(IrqSource.FrameCounter); 484 | } 485 | } 486 | 487 | void tryDelayedFrameCounterWrite() { 488 | this.frameCounterDelay--; 489 | 490 | if (this.frameCounterDelay == 0) { 491 | ubyte value = cast(ubyte)this.frameCounterValue; 492 | 493 | this.stepMode = ((value >> 7) & 1) ? 1 : 0; 494 | 495 | if (this.stepMode == 1 && !this.blockFrameCounterTick) { 496 | this.stepEnvelope(); 497 | this.stepSweep(); 498 | this.stepLength(); 499 | 500 | this.blockFrameCounterTick = 2; 501 | } 502 | 503 | this.currentStep = 0; 504 | this.counter = 0; 505 | this.frameCounterDelay = -1; 506 | this.frameCounterValue = -1; 507 | } 508 | } 509 | } 510 | 511 | // Pulse 512 | 513 | struct Pulse { 514 | bool enabled; 515 | ubyte channel; 516 | bool lengthEnabled; 517 | bool lengthEnabledNewValue; 518 | ubyte lengthValue; 519 | ubyte lengthValueNew; 520 | ubyte lengthValuePrev; 521 | ushort timerPeriod; 522 | ushort timerValue; 523 | ubyte dutyMode; 524 | ubyte dutyValue; 525 | bool sweepReload; 526 | bool sweepEnabled; 527 | bool sweepNegate; 528 | ubyte sweepShift; 529 | ubyte sweepPeriod; 530 | uint sweepTargetPeriod; 531 | ubyte sweepValue; 532 | bool envelopeEnabled; 533 | bool envelopeLoop; 534 | bool envelopeStart; 535 | byte envelopeDivider; 536 | ubyte envelopeCounter; 537 | ubyte envelopeConstantVolume; 538 | 539 | void reset() { 540 | this.dutyMode = 0; 541 | this.dutyValue = 0; 542 | 543 | this.timerPeriod = 0; 544 | 545 | this.sweepEnabled = false; 546 | this.sweepPeriod = 1; 547 | this.sweepNegate = false; 548 | this.sweepShift = 0; 549 | this.sweepReload = true; 550 | this.sweepValue = 0; 551 | this.sweepTargetPeriod = 0; 552 | this.updateSweepTargetPeriod(); 553 | 554 | this.lengthEnabled = true; 555 | this.lengthEnabledNewValue = true; 556 | this.lengthValue = 0; 557 | this.lengthValueNew = 0; 558 | this.lengthValuePrev = 0; 559 | 560 | this.envelopeEnabled = false; 561 | this.envelopeLoop = false; 562 | this.envelopeConstantVolume = 0; 563 | this.envelopeCounter = 0; 564 | this.envelopeStart = false; 565 | this.envelopeDivider = 0; 566 | } 567 | 568 | // 0x4000 & 0x4004 569 | void writeControl(ubyte value) { 570 | this.dutyMode = (value >> 6) & 3; 571 | this.lengthEnabledNewValue = ((value >> 5) & 1) == 0; 572 | this.envelopeLoop = ((value >> 5) & 1) == 1; 573 | this.envelopeEnabled = ((value >> 4) & 1) == 0; 574 | this.envelopeConstantVolume = value & 15; 575 | } 576 | 577 | // 0x4001 & 0x4005 578 | void writeSweep(ubyte value) { 579 | this.sweepEnabled = ((value >> 7) & 1) == 1; 580 | this.sweepPeriod = ((value >> 4) & 7) + 1; 581 | this.sweepNegate = ((value >> 3) & 1) == 1; 582 | this.sweepShift = value & 7; 583 | this.sweepReload = true; 584 | this.updateSweepTargetPeriod(); 585 | } 586 | 587 | // 0x4002 & 0x4006 588 | void writeTimerLow(ubyte value) { 589 | this.timerPeriod = (this.timerPeriod & 0xFF00) | cast(ushort)value; 590 | this.updateSweepTargetPeriod(); 591 | } 592 | 593 | // 0x4003 & 0x4007 594 | void writeTimerHigh(ubyte value) { 595 | if (this.enabled) { 596 | this.lengthValueNew = lengthTable[value >> 3]; 597 | this.lengthValuePrev = this.lengthValue; 598 | } 599 | 600 | this.timerPeriod = (this.timerPeriod & 0x00FF) | (cast(ushort)(value & 7) << 8); 601 | this.updateSweepTargetPeriod(); 602 | this.envelopeStart = true; 603 | this.dutyValue = 0; 604 | } 605 | 606 | void stepTimer() { 607 | if (this.timerValue == 0) { 608 | this.timerValue = this.timerPeriod; 609 | this.dutyValue = (this.dutyValue + 1) % 8; 610 | } else { 611 | this.timerValue--; 612 | } 613 | } 614 | 615 | void stepEnvelope() { 616 | if (this.envelopeStart) { 617 | this.envelopeStart = false; 618 | this.envelopeCounter = 15; 619 | this.envelopeDivider = this.envelopeConstantVolume; 620 | } else { 621 | this.envelopeDivider--; 622 | 623 | if (this.envelopeDivider < 0) { 624 | this.envelopeDivider = this.envelopeConstantVolume; 625 | if (this.envelopeCounter > 0) { 626 | this.envelopeCounter--; 627 | } 628 | else if (this.envelopeLoop) 629 | this.envelopeCounter = 15; 630 | } 631 | } 632 | } 633 | 634 | void stepSweep() { 635 | this.sweepValue--; 636 | if (this.sweepValue == 0) { 637 | if (this.sweepShift > 0 && this.sweepEnabled && this.timerPeriod >= 8 && this.sweepTargetPeriod <= 0x7ff) { 638 | this.timerPeriod = cast(ushort)this.sweepTargetPeriod; 639 | this.updateSweepTargetPeriod(); 640 | } 641 | this.sweepValue = this.sweepPeriod; 642 | } 643 | 644 | if (this.sweepReload) { 645 | this.sweepValue = this.sweepPeriod; 646 | this.sweepReload = false; 647 | } 648 | } 649 | 650 | void stepLength() { 651 | if (this.lengthEnabled && this.lengthValue > 0) { 652 | this.lengthValue--; 653 | } 654 | } 655 | 656 | void updateSweepTargetPeriod() { 657 | auto delta = this.timerPeriod >> this.sweepShift; 658 | if (this.sweepNegate) { 659 | this.sweepTargetPeriod = this.timerPeriod - delta; 660 | if (this.channel == 1) { 661 | this.sweepTargetPeriod--; 662 | } 663 | } else { 664 | this.sweepTargetPeriod = this.timerPeriod + delta; 665 | } 666 | } 667 | 668 | ubyte output() { 669 | // Emulate 1 cycle delay 670 | this.lengthEnabled = this.lengthEnabledNewValue; 671 | 672 | // Emulate 1 cycle delay 673 | if (this.lengthValueNew) { 674 | if (this.lengthValue == this.lengthValuePrev) { 675 | this.lengthValue = this.lengthValueNew; 676 | } 677 | 678 | this.lengthValueNew = 0; 679 | } 680 | 681 | if (!this.enabled) { 682 | return 0; 683 | } 684 | if (this.lengthValue == 0) { 685 | return 0; 686 | } 687 | if (this.timerPeriod < 8 || (!this.sweepNegate && this.sweepTargetPeriod > 0x7FF)) { 688 | return 0; 689 | } 690 | if (this.envelopeEnabled) { 691 | return cast(ubyte)(dutyTable[this.dutyMode][this.dutyValue] * this.envelopeCounter); 692 | } else { 693 | return cast(ubyte)(dutyTable[this.dutyMode][this.dutyValue] * this.envelopeConstantVolume); 694 | } 695 | } 696 | 697 | void save(string[string] state) { 698 | auto id = "apu.pulse" ~ to!string(this.channel); 699 | 700 | state[id ~ ".enabled"] = to!string(this.enabled); 701 | state[id ~ ".channel"] = to!string(this.channel); 702 | state[id ~ ".lengthEnabled"] = to!string(this.lengthEnabled); 703 | state[id ~ ".lengthEnabledNewValue"] = to!string(this.lengthEnabledNewValue); 704 | state[id ~ ".lengthValue"] = to!string(this.lengthValue); 705 | state[id ~ ".lengthValueNew"] = to!string(this.lengthValueNew); 706 | state[id ~ ".lengthValuePrev"] = to!string(this.lengthValuePrev); 707 | state[id ~ ".timerPeriod"] = to!string(this.timerPeriod); 708 | state[id ~ ".timerValue"] = to!string(this.timerValue); 709 | state[id ~ ".dutyMode"] = to!string(this.dutyMode); 710 | state[id ~ ".dutyValue"] = to!string(this.dutyValue); 711 | state[id ~ ".sweepReload"] = to!string(this.sweepReload); 712 | state[id ~ ".sweepEnabled"] = to!string(this.sweepEnabled); 713 | state[id ~ ".sweepNegate"] = to!string(this.sweepNegate); 714 | state[id ~ ".sweepShift"] = to!string(this.sweepShift); 715 | state[id ~ ".sweepPeriod"] = to!string(this.sweepPeriod); 716 | state[id ~ ".sweepTargetPeriod"] = to!string(this.sweepTargetPeriod); 717 | state[id ~ ".sweepValue"] = to!string(this.sweepValue); 718 | state[id ~ ".envelopeEnabled"] = to!string(this.envelopeEnabled); 719 | state[id ~ ".envelopeLoop"] = to!string(this.envelopeLoop); 720 | state[id ~ ".envelopeStart"] = to!string(this.envelopeStart); 721 | state[id ~ ".envelopeDivider"] = to!string(this.envelopeDivider); 722 | state[id ~ ".envelopeCounter"] = to!string(this.envelopeCounter); 723 | state[id ~ ".envelopeConstantVolume"] = to!string(this.envelopeConstantVolume); 724 | } 725 | 726 | void load(string[string] state) { 727 | auto id = "apu.pulse" ~ to!string(this.channel); 728 | 729 | this.enabled = to!bool(state[id ~ ".enabled"]); 730 | this.channel = to!ubyte(state[id ~ ".channel"]); 731 | this.lengthEnabled = to!bool(state[id ~ ".lengthEnabled"]); 732 | this.lengthEnabledNewValue = to!bool(state[id ~ ".lengthEnabledNewValue"]); 733 | this.lengthValue = to!ubyte(state[id ~ ".lengthValue"]); 734 | this.lengthValueNew = to!ubyte(state[id ~ ".lengthValueNew"]); 735 | this.lengthValuePrev = to!ubyte(state[id ~ ".lengthValuePrev"]); 736 | this.timerPeriod = to!ushort(state[id ~ ".timerPeriod"]); 737 | this.timerValue = to!ushort(state[id ~ ".timerValue"]); 738 | this.dutyMode = to!ubyte(state[id ~ ".dutyMode"]); 739 | this.dutyValue = to!ubyte(state[id ~ ".dutyValue"]); 740 | this.sweepReload = to!bool(state[id ~ ".sweepReload"]); 741 | this.sweepEnabled = to!bool(state[id ~ ".sweepEnabled"]); 742 | this.sweepNegate = to!bool(state[id ~ ".sweepNegate"]); 743 | this.sweepShift = to!ubyte(state[id ~ ".sweepShift"]); 744 | this.sweepPeriod = to!ubyte(state[id ~ ".sweepPeriod"]); 745 | this.sweepTargetPeriod = to!uint(state[id ~ ".sweepTargetPeriod"]); 746 | this.sweepValue = to!ubyte(state[id ~ ".sweepValue"]); 747 | this.envelopeEnabled = to!bool(state[id ~ ".envelopeEnabled"]); 748 | this.envelopeLoop = to!bool(state[id ~ ".envelopeLoop"]); 749 | this.envelopeStart = to!bool(state[id ~ ".envelopeStart"]); 750 | this.envelopeDivider = to!ubyte(state[id ~ ".envelopeDivider"]); 751 | this.envelopeCounter = to!ubyte(state[id ~ ".envelopeCounter"]); 752 | this.envelopeConstantVolume = to!ubyte(state[id ~ ".envelopeConstantVolume"]); 753 | } 754 | } 755 | 756 | // Triangle 757 | 758 | struct Triangle { 759 | CPU cpu; 760 | bool enabled; 761 | bool lengthEnabled; 762 | bool lengthEnabledNewValue; 763 | ubyte lengthValue; 764 | ubyte lengthValueNew; 765 | ubyte lengthValuePrev; 766 | ushort timerPeriod; 767 | ushort timerValue; 768 | ubyte dutyValue; 769 | ubyte counterPeriod; 770 | ubyte counterValue; 771 | bool counterReload; 772 | 773 | void reset(bool powerUp) { 774 | this.enabled = false; 775 | 776 | this.timerPeriod = 0; 777 | this.timerValue = 0; 778 | 779 | // apu_reset: len_ctrs_enabled 780 | // "At reset, length counters should be enabled, triangle unaffected" 781 | if (powerUp) { 782 | this.lengthEnabled = true; 783 | this.lengthEnabledNewValue = true; 784 | this.lengthValue = 0; 785 | this.lengthValueNew = 0; 786 | this.lengthValuePrev = 0; 787 | } 788 | 789 | this.counterValue = 0; 790 | this.counterPeriod = 0; 791 | this.counterReload = false; 792 | 793 | this.dutyValue = 0; // hack to "fix" apu_mixer test 794 | } 795 | 796 | // 0x4008 797 | void writeControl(ubyte value) { 798 | this.lengthEnabledNewValue = ((value >> 7) & 1) == 0; 799 | this.counterPeriod = value & 0x7F; 800 | } 801 | 802 | // 0x400A 803 | void writeTimerLow(ubyte value) { 804 | this.timerPeriod = (this.timerPeriod & 0xFF00) | cast(ushort)value; 805 | } 806 | 807 | // 0x400B 808 | void writeTimerHigh(ubyte value) { 809 | if (this.enabled) { 810 | this.lengthValueNew = lengthTable[value >> 3]; 811 | this.lengthValuePrev = this.lengthValue; 812 | } 813 | 814 | this.timerPeriod = (this.timerPeriod & 0x00FF) | (cast(ushort)(value & 7) << 8); 815 | 816 | this.counterReload = true; 817 | } 818 | 819 | void stepTimer() { 820 | if (this.timerValue == 0) { 821 | this.timerValue = this.timerPeriod; 822 | if (this.lengthValue > 0 && this.counterValue > 0) { 823 | this.dutyValue = (this.dutyValue + 1) % 32; 824 | } 825 | } else { 826 | this.timerValue--; 827 | } 828 | } 829 | 830 | void stepLength() { 831 | if (this.lengthEnabled && this.lengthValue > 0) { 832 | this.lengthValue--; 833 | } 834 | } 835 | 836 | void stepCounter() { 837 | if (this.counterReload) { 838 | this.counterValue = this.counterPeriod; 839 | } else if (this.counterValue > 0) { 840 | this.counterValue--; 841 | } 842 | if (this.lengthEnabled) { 843 | this.counterReload = false; 844 | } 845 | } 846 | 847 | ubyte output() { 848 | // Emulate 1 cycle delay 849 | this.lengthEnabled = this.lengthEnabledNewValue; 850 | 851 | // Emulate 1 cycle delay 852 | if (this.lengthValueNew) { 853 | if (this.lengthValue == this.lengthValuePrev) { 854 | this.lengthValue = this.lengthValueNew; 855 | } 856 | 857 | this.lengthValueNew = 0; 858 | } 859 | 860 | if (!this.enabled) { 861 | return 0; 862 | } 863 | 864 | return triangleTable[this.dutyValue]; 865 | } 866 | 867 | void save(string[string] state) { 868 | state["apu.triangle.enabled"] = to!string(this.enabled); 869 | state["apu.triangle.lengthEnabled"] = to!string(this.lengthEnabled); 870 | state["apu.triangle.lengthEnabledNewValue"] = to!string(this.lengthEnabledNewValue); 871 | state["apu.triangle.lengthValue"] = to!string(this.lengthValue); 872 | state["apu.triangle.lengthValueNew"] = to!string(this.lengthValueNew); 873 | state["apu.triangle.lengthValuePrev"] = to!string(this.lengthValuePrev); 874 | state["apu.triangle.timerPeriod"] = to!string(this.timerPeriod); 875 | state["apu.triangle.timerValue"] = to!string(this.timerValue); 876 | state["apu.triangle.dutyValue"] = to!string(this.dutyValue); 877 | state["apu.triangle.counterPeriod"] = to!string(this.counterPeriod); 878 | state["apu.triangle.counterValue"] = to!string(this.counterValue); 879 | state["apu.triangle.counterReload"] = to!string(this.counterReload); 880 | } 881 | 882 | void load(string[string] state) { 883 | this.enabled = to!bool(state["apu.triangle.enabled"]); 884 | this.lengthEnabled = to!bool(state["apu.triangle.lengthEnabled"]); 885 | this.lengthEnabledNewValue = to!bool(state["apu.triangle.lengthEnabledNewValue"]); 886 | this.lengthValue = to!ubyte(state["apu.triangle.lengthValue"]); 887 | this.lengthValueNew = to!ubyte(state["apu.triangle.lengthValueNew"]); 888 | this.lengthValuePrev = to!ubyte(state["apu.triangle.lengthValuePrev"]); 889 | this.timerPeriod = to!ushort(state["apu.triangle.timerPeriod"]); 890 | this.timerValue = to!ushort(state["apu.triangle.timerValue"]); 891 | this.dutyValue = to!ubyte(state["apu.triangle.dutyValue"]); 892 | this.counterPeriod = to!ubyte(state["apu.triangle.counterPeriod"]); 893 | this.counterValue = to!ubyte(state["apu.triangle.counterValue"]); 894 | this.counterReload = to!bool(state["apu.triangle.counterReload"]); 895 | } 896 | } 897 | 898 | // Noise 899 | 900 | struct Noise { 901 | bool enabled; 902 | bool mode; 903 | ushort shiftRegister; 904 | bool lengthEnabled; 905 | bool lengthEnabledNewValue; 906 | ubyte lengthValue; 907 | ubyte lengthValueNew; 908 | ubyte lengthValuePrev; 909 | ushort timerPeriod; 910 | ushort timerValue; 911 | bool envelopeEnabled; 912 | bool envelopeLoop; 913 | bool envelopeStart; 914 | byte envelopeDivider; 915 | ubyte envelopeCounter; 916 | ubyte envelopeConstantVolume; 917 | 918 | void reset() { 919 | this.timerPeriod = cast(ushort)(noiseTable[0] - 1); 920 | this.shiftRegister = 1; 921 | this.mode = false; 922 | 923 | this.lengthEnabled = true; 924 | this.lengthEnabledNewValue = true; 925 | this.lengthValue = 0; 926 | this.lengthValueNew = 0; 927 | this.lengthValuePrev = 0; 928 | 929 | this.envelopeEnabled = false; 930 | this.envelopeLoop = false; 931 | this.envelopeConstantVolume = 0; 932 | this.envelopeCounter = 0; 933 | this.envelopeStart = false; 934 | this.envelopeDivider = 0; 935 | } 936 | 937 | // 0x400C 938 | void writeControl(ubyte value) { 939 | this.lengthEnabledNewValue = ((value >> 5) & 1) == 0; 940 | this.envelopeLoop = ((value >> 5) & 1) == 1; 941 | this.envelopeEnabled = ((value >> 4) & 1) == 0; 942 | this.envelopeConstantVolume = value & 15; 943 | } 944 | 945 | // 0x400E 946 | void writePeriod(ubyte value) { 947 | this.mode = (value & 0x80) == 0x80; 948 | this.timerPeriod = cast(ushort)(noiseTable[value & 0x0F] - 1); 949 | } 950 | 951 | // 0x400F 952 | void writeLength(ubyte value) { 953 | if (this.enabled) { 954 | this.lengthValueNew = lengthTable[value >> 3]; 955 | this.lengthValuePrev = this.lengthValue; 956 | } 957 | 958 | this.envelopeStart = true; 959 | } 960 | 961 | void stepTimer() { 962 | if (this.timerValue == 0) { 963 | this.timerValue = this.timerPeriod; 964 | 965 | ushort feedback = (this.shiftRegister & 0x01) ^ ((this.shiftRegister >> (this.mode ? 6 : 1)) & 0x01); 966 | this.shiftRegister >>= 1; 967 | this.shiftRegister |= (feedback << 14); 968 | } else { 969 | this.timerValue--; 970 | } 971 | } 972 | 973 | void stepEnvelope() { 974 | if (this.envelopeStart) { 975 | this.envelopeStart = false; 976 | this.envelopeCounter = 15; 977 | this.envelopeDivider = this.envelopeConstantVolume; 978 | } else { 979 | this.envelopeDivider--; 980 | 981 | if (this.envelopeDivider < 0) { 982 | this.envelopeDivider = this.envelopeConstantVolume; 983 | if (this.envelopeCounter > 0) { 984 | this.envelopeCounter--; 985 | } 986 | else if (this.envelopeLoop) 987 | this.envelopeCounter = 15; 988 | } 989 | } 990 | } 991 | 992 | void stepLength() { 993 | if (this.lengthEnabled && this.lengthValue > 0) { 994 | this.lengthValue--; 995 | } 996 | } 997 | 998 | ubyte output() { 999 | // Emulate 1 cycle delay 1000 | this.lengthEnabled = this.lengthEnabledNewValue; 1001 | 1002 | // Emulate 1 cycle delay 1003 | if (this.lengthValueNew) { 1004 | if (this.lengthValue == this.lengthValuePrev) { 1005 | this.lengthValue = this.lengthValueNew; 1006 | } 1007 | 1008 | this.lengthValueNew = 0; 1009 | } 1010 | 1011 | if (!this.enabled) { 1012 | return 0; 1013 | } 1014 | if (this.lengthValue == 0) { 1015 | return 0; 1016 | } 1017 | if ((this.shiftRegister & 1) == 1) { 1018 | return 0; 1019 | } 1020 | if (this.envelopeEnabled) { 1021 | return this.envelopeCounter; 1022 | } else { 1023 | return this.envelopeConstantVolume; 1024 | } 1025 | } 1026 | 1027 | void save(string[string] state) { 1028 | state["apu.noise.enabled"] = to!string(this.enabled); 1029 | state["apu.noise.mode"] = to!string(this.mode); 1030 | state["apu.noise.shiftRegister"] = to!string(this.shiftRegister); 1031 | state["apu.noise.lengthEnabled"] = to!string(this.lengthEnabled); 1032 | state["apu.noise.lengthEnabledNewValue"] = to!string(this.lengthEnabledNewValue); 1033 | state["apu.noise.lengthValue"] = to!string(this.lengthValue); 1034 | state["apu.noise.lengthValueNew"] = to!string(this.lengthValueNew); 1035 | state["apu.noise.lengthValuePrev"] = to!string(this.lengthValuePrev); 1036 | state["apu.noise.timerPeriod"] = to!string(this.timerPeriod); 1037 | state["apu.noise.timerValue"] = to!string(this.timerValue); 1038 | state["apu.noise.envelopeEnabled"] = to!string(this.envelopeEnabled); 1039 | state["apu.noise.envelopeLoop"] = to!string(this.envelopeLoop); 1040 | state["apu.noise.envelopeStart"] = to!string(this.envelopeStart); 1041 | state["apu.noise.envelopeDivider"] = to!string(this.envelopeDivider); 1042 | state["apu.noise.envelopeCounter"] = to!string(this.envelopeCounter); 1043 | state["apu.noise.envelopeConstantVolume"] = to!string(this.envelopeConstantVolume); 1044 | } 1045 | 1046 | void load(string[string] state) { 1047 | this.enabled = to!bool(state["apu.noise.enabled"]); 1048 | this.mode = to!bool(state["apu.noise.mode"]); 1049 | this.shiftRegister = to!ushort(state["apu.noise.shiftRegister"]); 1050 | this.lengthEnabled = to!bool(state["apu.noise.lengthEnabled"]); 1051 | this.lengthEnabledNewValue = to!bool(state["apu.noise.lengthEnabledNewValue"]); 1052 | this.lengthValue = to!ubyte(state["apu.noise.lengthValue"]); 1053 | this.lengthValueNew = to!ubyte(state["apu.noise.lengthValueNew"]); 1054 | this.lengthValuePrev = to!ubyte(state["apu.noise.lengthValuePrev"]); 1055 | this.timerPeriod = to!ushort(state["apu.noise.timerPeriod"]); 1056 | this.timerValue = to!ushort(state["apu.noise.timerValue"]); 1057 | this.envelopeEnabled = to!bool(state["apu.noise.envelopeEnabled"]); 1058 | this.envelopeLoop = to!bool(state["apu.noise.envelopeLoop"]); 1059 | this.envelopeStart = to!bool(state["apu.noise.envelopeStart"]); 1060 | this.envelopeDivider = to!ubyte(state["apu.noise.envelopeDivider"]); 1061 | this.envelopeCounter = to!ubyte(state["apu.noise.envelopeCounter"]); 1062 | this.envelopeConstantVolume = to!ubyte(state["apu.noise.envelopeConstantVolume"]); 1063 | } 1064 | } 1065 | 1066 | // DMC 1067 | 1068 | struct DMC { 1069 | CPU cpu; 1070 | bool enabled; 1071 | ubyte value; 1072 | ushort sampleAddress; 1073 | ushort sampleLength; 1074 | ushort currentAddress; 1075 | ushort currentLength; 1076 | ubyte shiftRegister; 1077 | ubyte bitCount; 1078 | ubyte tickPeriod; 1079 | ubyte tickValue; 1080 | bool loop; 1081 | bool irq; 1082 | 1083 | void reset() { 1084 | this.tickPeriod = cast(ubyte)(dmcTable[0] - 1); 1085 | this.bitCount = 8; 1086 | 1087 | this.value = 0; 1088 | this.sampleAddress = 0; 1089 | this.sampleLength = 0; 1090 | this.currentAddress = 0; 1091 | this.currentLength = 0; 1092 | this.shiftRegister = 0; 1093 | this.loop = false; 1094 | this.irq = false; 1095 | } 1096 | 1097 | // 0x4010 1098 | void writeControl(ubyte value) { 1099 | this.irq = (value & 0x80) == 0x80; 1100 | this.loop = (value & 0x40) == 0x40; 1101 | this.tickPeriod = cast(ubyte)(dmcTable[value & 0x0F] - 1); 1102 | 1103 | if (!this.irq) this.cpu.clearIrqSource(IrqSource.DMC); 1104 | } 1105 | 1106 | // 0x4011 1107 | void writeValue(ubyte value) { 1108 | this.value = value & 0x7F; 1109 | } 1110 | 1111 | // 0x4012 1112 | void writeAddress(ubyte value) { 1113 | // Sample address = %11AAAAAA.AA000000 1114 | this.sampleAddress = 0xC000 | (cast(ushort)value << 6); 1115 | } 1116 | 1117 | // 0x4013 1118 | void writeLength(ubyte value) { 1119 | // Sample length = %0000LLLL.LLLL0001 1120 | this.sampleLength = (cast(ushort)value << 4) | 1; 1121 | } 1122 | 1123 | void restart() { 1124 | this.currentAddress = this.sampleAddress; 1125 | this.currentLength = this.sampleLength; 1126 | } 1127 | 1128 | void stepTimer() { 1129 | if (!this.enabled) { 1130 | return; 1131 | } 1132 | this.stepReader(); 1133 | if (this.tickValue == 0) { 1134 | this.tickValue = this.tickPeriod; 1135 | this.stepShifter(); 1136 | } else { 1137 | this.tickValue--; 1138 | } 1139 | } 1140 | 1141 | void stepReader() { 1142 | if (this.currentLength > 0 && this.bitCount == 0) { 1143 | this.cpu.stall += 4; 1144 | this.shiftRegister = this.cpu.read(this.currentAddress); 1145 | this.bitCount = 8; 1146 | this.currentAddress++; 1147 | if (this.currentAddress == 0) { 1148 | this.currentAddress = 0x8000; 1149 | } 1150 | this.currentLength--; 1151 | if (this.currentLength == 0) { 1152 | if (this.loop) { 1153 | this.restart(); 1154 | } 1155 | else if (this.irq) { 1156 | this.cpu.addIrqSource(IrqSource.DMC); 1157 | } 1158 | } 1159 | } 1160 | } 1161 | 1162 | void stepShifter() { 1163 | if (this.bitCount == 0) { 1164 | return; 1165 | } 1166 | if ((this.shiftRegister & 1) == 1) { 1167 | if (this.value <= 125) { 1168 | this.value += 2; 1169 | } 1170 | } else { 1171 | if (this.value >= 2) { 1172 | this.value -= 2; 1173 | } 1174 | } 1175 | this.shiftRegister >>= 1; 1176 | this.bitCount--; 1177 | } 1178 | 1179 | ubyte output() { 1180 | return this.value; 1181 | } 1182 | 1183 | void save(string[string] state) { 1184 | state["apu.dmc.enabled"] = to!string(this.enabled); 1185 | state["apu.dmc.value"] = to!string(this.value); 1186 | state["apu.dmc.sampleAddress"] = to!string(this.sampleAddress); 1187 | state["apu.dmc.sampleLength"] = to!string(this.sampleLength); 1188 | state["apu.dmc.currentAddress"] = to!string(this.currentAddress); 1189 | state["apu.dmc.currentLength"] = to!string(this.currentLength); 1190 | state["apu.dmc.shiftRegister"] = to!string(this.shiftRegister); 1191 | state["apu.dmc.bitCount"] = to!string(this.bitCount); 1192 | state["apu.dmc.tickPeriod"] = to!string(this.tickPeriod); 1193 | state["apu.dmc.tickValue"] = to!string(this.tickValue); 1194 | state["apu.dmc.loop"] = to!string(this.loop); 1195 | state["apu.dmc.irq"] = to!string(this.irq); 1196 | } 1197 | 1198 | void load(string[string] state) { 1199 | this.enabled = to!bool(state["apu.dmc.enabled"]); 1200 | this.value = to!ubyte(state["apu.dmc.value"]); 1201 | this.sampleAddress = to!ushort(state["apu.dmc.sampleAddress"]); 1202 | this.sampleLength = to!ushort(state["apu.dmc.sampleLength"]); 1203 | this.currentAddress = to!ushort(state["apu.dmc.currentAddress"]); 1204 | this.currentLength = to!ushort(state["apu.dmc.currentLength"]); 1205 | this.shiftRegister = to!ubyte(state["apu.dmc.shiftRegister"]); 1206 | this.bitCount = to!ubyte(state["apu.dmc.bitCount"]); 1207 | this.tickPeriod = to!ubyte(state["apu.dmc.tickPeriod"]); 1208 | this.tickValue = to!ubyte(state["apu.dmc.tickValue"]); 1209 | this.loop = to!bool(state["apu.dmc.loop"]); 1210 | this.irq = to!bool(state["apu.dmc.irq"]); 1211 | } 1212 | } 1213 | -------------------------------------------------------------------------------- /source/nes/cartridge.d: -------------------------------------------------------------------------------- 1 | module nes.cartridge; 2 | 3 | import std.base64; 4 | import std.conv; 5 | import std.stdio; 6 | 7 | class Cartridge { 8 | ubyte[] prg; // PRG-ROM banks 9 | ubyte[] chr; // CHR-ROM/RAM banks 10 | ubyte[] sram; // Save RAM 11 | ubyte mapper; // mapper type 12 | ubyte mirror; // mirroring mode 13 | ubyte battery; // battery present 14 | bool chrIsRam; // CHR-RAM present 15 | 16 | this(ubyte[] prg, ubyte[]chr, ubyte mapper, ubyte mirror, ubyte battery, bool chrIsRam) { 17 | this.prg = prg; 18 | this.chr = chr; 19 | this.sram = new ubyte[0x2000]; 20 | this.mapper = mapper; 21 | this.mirror = mirror; 22 | this.battery = battery; 23 | this.chrIsRam = chrIsRam; 24 | } 25 | 26 | void save(string[string] state) { 27 | state["cartridge.sram"] = Base64.encode(this.sram); 28 | state["cartridge.mirror"] = to!string(this.mirror); 29 | 30 | if (this.chrIsRam) 31 | state["cartridge.chr"] = Base64.encode(this.chr); 32 | } 33 | 34 | void load(string[string] state) { 35 | this.sram = Base64.decode(state["cartridge.sram"]); 36 | this.mirror = to!ubyte(state["cartridge.mirror"]); 37 | 38 | if (this.chrIsRam) 39 | this.chr = Base64.decode(state["cartridge.chr"]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/nes/color.d: -------------------------------------------------------------------------------- 1 | module nes.color; 2 | 3 | struct RGBA { 4 | ubyte r, g, b, a; 5 | } 6 | -------------------------------------------------------------------------------- /source/nes/console.d: -------------------------------------------------------------------------------- 1 | module nes.console; 2 | 3 | import std.base64; 4 | import std.conv; 5 | import std.file; 6 | import std.zlib; 7 | 8 | import nes.apu; 9 | import nes.cartridge; 10 | import nes.color; 11 | import nes.controller; 12 | import nes.cpu; 13 | import nes.image; 14 | import nes.ines; 15 | import nes.mapper; 16 | import nes.palette; 17 | import nes.ppu; 18 | 19 | class Console { 20 | CPU cpu; 21 | APU apu; 22 | PPU ppu; 23 | Cartridge cartridge; 24 | Controller controller1; 25 | Controller controller2; 26 | Mapper mapper; 27 | ubyte[] ram; 28 | 29 | this(string path) { 30 | this.cartridge = LoadNESFile(path); 31 | 32 | this.ram = new ubyte[2048]; 33 | this.controller1 = new Controller(); 34 | this.controller2 = new Controller(); 35 | 36 | this.mapper = NewMapper(this); 37 | 38 | this.cpu = new CPU(this); 39 | this.apu = new APU(this); 40 | this.ppu = new PPU(this); 41 | } 42 | 43 | void reset() { 44 | this.cpu.reset(); 45 | this.ppu.reset(); 46 | this.apu.reset(); 47 | } 48 | 49 | int step() { 50 | auto prevCycles = this.cpu.cycles; 51 | this.cpu.step(); 52 | return cast(int)(this.cpu.cycles - prevCycles); 53 | } 54 | 55 | int stepFrame() { 56 | auto cpuCycles = 0; 57 | auto frame = this.ppu.frame; 58 | 59 | while (frame == this.ppu.frame) { 60 | cpuCycles += this.step(); 61 | } 62 | 63 | return cpuCycles; 64 | } 65 | 66 | void stepSeconds(double seconds) { 67 | auto cycles = cast(int)(CPUFrequency * seconds); 68 | 69 | while (cycles > 0) { 70 | cycles -= this.step(); 71 | } 72 | } 73 | 74 | ImageRGBA buffer() { 75 | return this.ppu.front; 76 | } 77 | 78 | RGBA backgroundColor() { 79 | return Palette[this.ppu.readPalette(0) % 64]; 80 | } 81 | 82 | void setButtons1(bool[8] buttons) { 83 | this.controller1.setButtons(buttons); 84 | } 85 | 86 | void setButtons2(bool[8] buttons) { 87 | this.controller2.setButtons(buttons); 88 | } 89 | 90 | void setAudioCallback(ApuCallbackFuncType callback) { 91 | this.apu.callback = callback; 92 | } 93 | 94 | void setAudioSampleRate(double sampleRate) { 95 | this.apu.setAudioSampleRate(sampleRate); 96 | } 97 | 98 | void saveState(string fileName) { 99 | string[string] state = ["version": "2"]; 100 | 101 | this.save(state); 102 | 103 | auto stateText = to!string(state); 104 | 105 | auto data = std.zlib.compress(cast(void[])stateText); 106 | 107 | write(fileName, data); 108 | } 109 | 110 | void loadState(string fileName) { 111 | auto stateData = read(fileName); 112 | 113 | stateData = cast(ubyte[])std.zlib.uncompress(cast(void[])stateData); 114 | 115 | string[string] state = to!(string[string])(cast(string)stateData); 116 | 117 | if (state["version"] != "2") return; 118 | 119 | load(state); 120 | } 121 | 122 | void saveBatteryBackedRam(string fileName) { 123 | if (!this.cartridge.battery) return; 124 | 125 | auto data = std.zlib.compress(cast(void[])this.cartridge.sram); 126 | 127 | write(fileName, data); 128 | } 129 | 130 | void loadBatteryBackedRam(string fileName) { 131 | if (!this.cartridge.battery) return; 132 | 133 | auto data = read(fileName); 134 | 135 | this.cartridge.sram = cast(ubyte[])std.zlib.uncompress(cast(void[])data); 136 | } 137 | 138 | private: 139 | void save(string[string] state) { 140 | state["console.ram"] = Base64.encode(this.ram); 141 | 142 | this.cpu.save(state); 143 | this.apu.save(state); 144 | this.ppu.save(state); 145 | this.cartridge.save(state); 146 | this.mapper.save(state); 147 | } 148 | 149 | void load(string[string] state) { 150 | this.ram = Base64.decode(state["console.ram"]); 151 | 152 | this.cpu.load(state); 153 | this.apu.load(state); 154 | this.ppu.load(state); 155 | this.cartridge.load(state); 156 | this.mapper.load(state); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /source/nes/controller.d: -------------------------------------------------------------------------------- 1 | module nes.controller; 2 | 3 | enum { 4 | ButtonA, 5 | ButtonB, 6 | ButtonSelect, 7 | ButtonStart, 8 | ButtonUp, 9 | ButtonDown, 10 | ButtonLeft, 11 | ButtonRight 12 | } 13 | 14 | class Controller { 15 | 16 | void setButtons(bool[8] buttons) { 17 | this.buttons = buttons; 18 | } 19 | 20 | ubyte read() { 21 | ubyte value; 22 | if (this.index < 8 && this.buttons[this.index]) { 23 | value = 1; 24 | } 25 | this.index++; 26 | if ((this.strobe & 1) == 1) { 27 | this.index = 0; 28 | } 29 | return value; 30 | } 31 | 32 | void write(ubyte value) { 33 | this.strobe = value; 34 | if ((this.strobe & 1) == 1) { 35 | this.index = 0; 36 | } 37 | } 38 | 39 | private: 40 | bool[8] buttons; 41 | ubyte index; 42 | ubyte strobe; 43 | } 44 | -------------------------------------------------------------------------------- /source/nes/cpu.d: -------------------------------------------------------------------------------- 1 | module nes.cpu; 2 | 3 | import std.conv; 4 | import std.format; 5 | import std.stdio; 6 | 7 | import nes.console; 8 | import nes.memory; 9 | 10 | enum CPUFrequency = 1789773; 11 | 12 | enum IrqSource : uint { 13 | External = 1, 14 | FrameCounter = 2, 15 | DMC = 4 16 | } 17 | 18 | // addressing modes 19 | enum { 20 | modeAbsolute = 1, 21 | modeAbsoluteX, 22 | modeAbsoluteXRead, 23 | modeAbsoluteY, 24 | modeAbsoluteYRead, 25 | modeAccumulator, 26 | modeImmediate, 27 | modeImplied, 28 | modeIndexedIndirect, 29 | modeIndirect, 30 | modeIndirectIndexed, 31 | modeIndirectIndexedRead, 32 | modeRelative, 33 | modeZeroPage, 34 | modeZeroPageX, 35 | modeZeroPageY 36 | } 37 | 38 | // instructionModes indicates the addressing mode for each instruction 39 | immutable ubyte[256] instructionModes = [ 40 | modeImplied, modeIndexedIndirect, modeImplied, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeAccumulator, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 41 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX, 42 | modeAbsolute, modeIndexedIndirect, modeImplied, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeAccumulator, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 43 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX, 44 | modeImplied, modeIndexedIndirect, modeImplied, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeAccumulator, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 45 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX, 46 | modeImplied, modeIndexedIndirect, modeImplied, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeAccumulator, modeImmediate, modeIndirect, modeAbsolute, modeAbsolute, modeAbsolute, 47 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX, 48 | modeImmediate, modeIndexedIndirect, modeImmediate, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeImplied, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 49 | modeRelative, modeIndirectIndexed, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageY, modeZeroPageY, modeImplied, modeAbsoluteY, modeImplied, modeAbsoluteY, modeAbsoluteX, modeAbsoluteX, modeAbsoluteY, modeAbsoluteY, 50 | modeImmediate, modeIndexedIndirect, modeImmediate, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeImplied, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 51 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexedRead, modeZeroPageX, modeZeroPageX, modeZeroPageY, modeZeroPageY, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteYRead, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteYRead, modeAbsoluteYRead, 52 | modeImmediate, modeIndexedIndirect, modeImmediate, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeImplied, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 53 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX, 54 | modeImmediate, modeIndexedIndirect, modeImmediate, modeIndexedIndirect, modeZeroPage, modeZeroPage, modeZeroPage, modeZeroPage, modeImplied, modeImmediate, modeImplied, modeImmediate, modeAbsolute, modeAbsolute, modeAbsolute, modeAbsolute, 55 | modeRelative, modeIndirectIndexedRead, modeImplied, modeIndirectIndexed, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeZeroPageX, modeImplied, modeAbsoluteYRead, modeImplied, modeAbsoluteY, modeAbsoluteXRead, modeAbsoluteXRead, modeAbsoluteX, modeAbsoluteX 56 | ]; 57 | 58 | // instructionSizes indicates the size of each instruction in bytes 59 | immutable ubyte[256] instructionSizes = [ 60 | 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 61 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 62 | 3, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 63 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 64 | 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 4, 3, 3, 3, 65 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 66 | 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 67 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 68 | 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 69 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 70 | 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 71 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 72 | 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 73 | 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 74 | 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 75 | 2, 2, 1, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3 76 | ]; 77 | 78 | // instructionNames indicates the name of each instruction 79 | immutable string[256] instructionNames = [ 80 | "BRK", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO", "PHP", "ORA", "ASL", "AAC", "NOP", "ORA", "ASL", "SLO", 81 | "BPL", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO", "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", 82 | "JSR", "AND", "KIL", "RLA", "BIT", "AND", "ROL", "RLA", "PLP", "AND", "ROL", "AAC", "BIT", "AND", "ROL", "RLA", 83 | "BMI", "AND", "KIL", "RLA", "NOP", "AND", "ROL", "RLA", "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", 84 | "RTI", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE", "PHA", "EOR", "LSR", "ASR", "JMP", "EOR", "LSR", "SRE", 85 | "BVC", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE", "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", 86 | "RTS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA", "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", 87 | "BVS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA", "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", 88 | "NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", 89 | "BCC", "STA", "KIL", "AHX", "STY", "STA", "STX", "SAX", "TYA", "STA", "TXS", "TAS", "SYA", "STA", "SXA", "AHX", 90 | "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", 91 | "BCS", "LDA", "KIL", "LAX", "LDY", "LDA", "LDX", "LAX", "CLV", "LDA", "ATX", "LAS", "LDY", "LDA", "LDX", "LAX", 92 | "CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", 93 | "BNE", "CMP", "KIL", "DCP", "NOP", "CMP", "DEC", "DCP", "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", 94 | "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", "INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", 95 | "BEQ", "SBC", "KIL", "ISC", "NOP", "SBC", "INC", "ISC", "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC" 96 | ]; 97 | 98 | // stepInfo contains information that the instruction functions use 99 | struct stepInfo { 100 | ushort address; 101 | ushort pc; 102 | ubyte mode; 103 | } 104 | 105 | alias void delegate(stepInfo*) InstructionFuncType; 106 | 107 | // pagesDiffer returns true if the two addresses reference different pages 108 | bool pagesDiffer(ushort a, ushort b) { 109 | return (a & 0xFF00) != (b & 0xFF00); 110 | } 111 | 112 | class CPU : CPUMemory { 113 | ulong cycles; // number of cycles 114 | ushort pc; // program counter 115 | ubyte sp; // stack pointer 116 | ubyte a; // accumulator 117 | ubyte x; // x register 118 | ubyte y; // y register 119 | ubyte c; // carry flag 120 | ubyte z; // zero flag 121 | ubyte i; // interrupt disable flag 122 | ubyte d; // decimal mode flag 123 | ubyte b; // break command flag 124 | ubyte u; // unused flag 125 | ubyte v; // overflow flag 126 | ubyte n; // negative flag 127 | 128 | int stall; // number of cycles to stall 129 | 130 | this(Console console) { 131 | super(console); 132 | this.createTable(); 133 | this.reset(); 134 | } 135 | 136 | // reset resets the CPU to its initial powerup state 137 | void reset() { 138 | this.pc = this.read16(0xFFFC); 139 | this.sp = 0xFD; 140 | this.setFlags(0x24); 141 | 142 | this.nmiFlag = false; 143 | this.irqFlag = 0; 144 | 145 | this.cycles = 0; 146 | this.stall = 0; 147 | 148 | this.spriteDmaTransferRunning = false; 149 | this.spriteDmaCounter = 0; 150 | } 151 | 152 | string disassembleInstruction() { 153 | auto opcode = this.read(this.pc); 154 | auto name = instructionNames[opcode]; 155 | auto mode = instructionModes[opcode]; 156 | 157 | string r; 158 | 159 | switch (mode) { 160 | case modeImplied: 161 | r = name; 162 | break; 163 | 164 | case modeAccumulator: 165 | r = name ~ " A"; 166 | break; 167 | 168 | case modeImmediate: 169 | auto address = this.read(cast(ushort)(this.pc + 1)); 170 | 171 | r = format("%s #$%02X", name, address); 172 | break; 173 | 174 | case modeZeroPage: 175 | auto address = this.read(cast(ushort)(this.pc + 1)); 176 | 177 | r = format("%s $%02X", name, address); 178 | break; 179 | 180 | case modeZeroPageX: 181 | auto address = this.read(cast(ushort)(this.pc + 1)); 182 | 183 | r = format("%s $%02X,X", name, address); 184 | break; 185 | 186 | case modeZeroPageY: 187 | auto address = this.read(cast(ushort)(this.pc + 1)); 188 | 189 | r = format("%s $%02X,Y", name, address); 190 | break; 191 | 192 | case modeRelative: 193 | auto offset = this.read(cast(ushort)(this.pc + 1)); 194 | ushort address; 195 | 196 | if (offset < 0x80) { 197 | address = cast(ushort)(this.pc + 2 + offset); 198 | } else { 199 | address = cast(ushort)(this.pc + 2 + offset - 0x100); 200 | } 201 | 202 | //r = format("%s *%s", name, cast(byte)offset); 203 | r = format("%s $%04X", name, address); 204 | break; 205 | 206 | case modeAbsolute: 207 | auto address = this.read16(cast(ushort)(this.pc + 1)); 208 | 209 | r = format("%s $%04X", name, address); 210 | break; 211 | 212 | case modeAbsoluteX, modeAbsoluteXRead: 213 | auto address = this.read16(cast(ushort)(this.pc + 1)); 214 | 215 | r = format("%s $%04X,X", name, address); 216 | break; 217 | 218 | case modeAbsoluteY, modeAbsoluteYRead: 219 | auto address = this.read16(cast(ushort)(this.pc + 1)); 220 | 221 | r = format("%s $%04X,Y", name, address); 222 | break; 223 | 224 | case modeIndirect: 225 | auto address = this.read16(cast(ushort)(this.pc + 1)); 226 | 227 | r = format("%s ($%04X)", name, address); 228 | break; 229 | 230 | case modeIndexedIndirect: 231 | auto address = this.read(cast(ushort)(this.pc + 1)); 232 | 233 | r = format("%s ($%02X,X)", name, address); 234 | break; 235 | 236 | case modeIndirectIndexed, modeIndirectIndexedRead: 237 | auto address = this.read(cast(ushort)(this.pc + 1)); 238 | 239 | r = format("%s ($%02X),Y", name, address); 240 | break; 241 | 242 | default: 243 | break; 244 | } 245 | 246 | return r; 247 | } 248 | 249 | // printInstruction prints the current CPU state 250 | void printInstruction() { 251 | auto opcode = this.read(this.pc); 252 | auto bytes = instructionSizes[opcode]; 253 | auto name = instructionNames[opcode]; 254 | auto w0 = format("%02X", this.read(this.pc + 0)); 255 | auto w1 = format("%02X", this.read(cast(ushort)(this.pc + 1))); 256 | auto w2 = format("%02X", this.read(cast(ushort)(this.pc + 2))); 257 | if (bytes < 2) { 258 | w1 = " "; 259 | } 260 | if (bytes < 3) { 261 | w2 = " "; 262 | } 263 | writef( 264 | "%4X %s %s %s %s %28s" ~ 265 | "A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%3d\n", 266 | this.pc, w0, w1, w2, name, "", 267 | this.a, this.x, this.y, this.flags(), this.sp, (this.cycles * 3) % 341); 268 | } 269 | 270 | // flags returns the processor status flags 271 | ubyte flags() { 272 | ubyte flags; 273 | flags |= this.c << 0; 274 | flags |= this.z << 1; 275 | flags |= this.i << 2; 276 | flags |= this.d << 3; 277 | flags |= this.b << 4; 278 | flags |= this.u << 5; 279 | flags |= this.v << 6; 280 | flags |= this.n << 7; 281 | return flags; 282 | } 283 | 284 | // setFlags sets the processor status flags 285 | void setFlags(ubyte flags) { 286 | this.c = (flags >> 0) & 1; 287 | this.z = (flags >> 1) & 1; 288 | this.i = (flags >> 2) & 1; 289 | this.d = (flags >> 3) & 1; 290 | this.b = (flags >> 4) & 1; 291 | this.u = (flags >> 5) & 1; 292 | this.v = (flags >> 6) & 1; 293 | this.n = (flags >> 7) & 1; 294 | } 295 | 296 | // read16 reads two bytes using Read to return a double-word value 297 | ushort read16(ushort address) { 298 | auto lo = cast(ushort)this.read(address); 299 | auto hi = cast(ushort)this.read(cast(ushort)(address + 1)); 300 | 301 | return cast(ushort)(hi << 8 | lo); 302 | } 303 | 304 | // Step executes a single CPU instruction 305 | void step() { 306 | while (this.stall > 0) { 307 | this.stall--; 308 | this.cycles++; 309 | 310 | foreach (_; 0 .. 3) { 311 | this.console.ppu.step(); 312 | this.console.mapper.step(); 313 | } 314 | 315 | this.console.apu.step(); 316 | } 317 | 318 | auto cycles = this.cycles; 319 | 320 | auto opcode = this.memoryRead(this.pc); 321 | auto mode = instructionModes[opcode]; 322 | this.pc++; 323 | 324 | ushort address; 325 | bool pageCrossed; 326 | switch (mode) { 327 | case modeAbsolute: 328 | address = this.memoryRead16(this.pc); 329 | this.pc += 2; 330 | break; 331 | case modeAbsoluteX: 332 | address = cast(ushort)(this.memoryRead16(this.pc) + this.x); 333 | this.pc += 2; 334 | this.memoryRead(address); // dummy read 335 | break; 336 | case modeAbsoluteXRead: 337 | address = cast(ushort)(this.memoryRead16(this.pc) + this.x); 338 | this.pc += 2; 339 | 340 | pageCrossed = pagesDiffer(cast(ushort)(address - this.x), address); 341 | 342 | if (pageCrossed) 343 | this.memoryRead(cast(ushort)(address - 0x100)); // dummy read 344 | break; 345 | case modeAbsoluteY: 346 | address = cast(ushort)(this.memoryRead16(this.pc) + this.y); 347 | this.pc += 2; 348 | this.memoryRead(address); // dummy read 349 | break; 350 | case modeAbsoluteYRead: 351 | address = cast(ushort)(this.memoryRead16(this.pc) + this.y); 352 | this.pc += 2; 353 | 354 | pageCrossed = pagesDiffer(cast(ushort)(address - this.y), address); 355 | 356 | if (pageCrossed) 357 | this.memoryRead(cast(ushort)(address - 0x100)); // dummy read 358 | break; 359 | case modeAccumulator: 360 | this.memoryRead(this.pc); // dummy read 361 | address = 0; 362 | break; 363 | case modeImmediate: 364 | address = this.pc; 365 | this.pc++; 366 | break; 367 | case modeImplied: 368 | this.memoryRead(this.pc); // dummy read 369 | address = 0; 370 | break; 371 | // Indirect,X 372 | case modeIndexedIndirect: 373 | ubyte zero = this.memoryRead(this.pc); 374 | this.pc++; 375 | this.memoryRead(zero); // dummy read 376 | zero += this.x; 377 | if (zero == 0xFF) { 378 | address = this.memoryRead(0xFF) | this.memoryRead(0x00) << 8; 379 | } else { 380 | address = this.memoryRead16(zero); 381 | } 382 | break; 383 | case modeIndirect: 384 | // JMP is the ONLY opcode to use this addressing mode 385 | address = this.memoryRead16(this.pc); 386 | this.pc += 2; 387 | if((address & 0xFF) == 0xFF) { 388 | auto lo = this.memoryRead(address); 389 | auto hi = this.memoryRead(cast(ushort)(address - 0xFF)); 390 | address = (lo | hi << 8); 391 | } else { 392 | address = this.memoryRead16(address); 393 | } 394 | 395 | break; 396 | // Indirect,Y 397 | case modeIndirectIndexed: 398 | ubyte zero = this.memoryRead(this.pc); 399 | this.pc++; 400 | 401 | if (zero == 0xFF) { 402 | address = this.memoryRead(0xFF) | this.memoryRead(0x00) << 8; 403 | } else { 404 | address = this.memoryRead16(zero); 405 | } 406 | 407 | address += this.y; 408 | 409 | this.memoryRead(address); // dummy read 410 | break; 411 | case modeIndirectIndexedRead: 412 | ubyte zero = this.memoryRead(this.pc); 413 | this.pc++; 414 | 415 | if (zero == 0xFF) { 416 | address = this.memoryRead(0xFF) | this.memoryRead(0x00) << 8; 417 | } else { 418 | address = this.memoryRead16(zero); 419 | } 420 | 421 | address += this.y; 422 | 423 | pageCrossed = pagesDiffer(cast(ushort)(address - this.y), address); 424 | 425 | if (pageCrossed) 426 | this.memoryRead(cast(ushort)(address - 0x100)); // dummy read 427 | 428 | break; 429 | case modeRelative: 430 | auto offset = cast(ushort)this.memoryRead(this.pc); 431 | this.pc++; 432 | if (offset < 0x80) { 433 | address = cast(ushort)(this.pc + offset); 434 | } else { 435 | address = cast(ushort)(this.pc + offset - 0x100); 436 | } 437 | break; 438 | case modeZeroPage: 439 | address = cast(ushort)this.memoryRead(this.pc); 440 | this.pc++; 441 | break; 442 | case modeZeroPageX: 443 | address = cast(ushort)(this.memoryRead(this.pc) + this.x) & 0xff; 444 | this.pc++; 445 | this.memoryRead(cast(ushort)(address - this.x)); // dummy read 446 | break; 447 | case modeZeroPageY: 448 | address = cast(ushort)(this.memoryRead(this.pc) + this.y) & 0xff; 449 | this.pc++; 450 | this.memoryRead(cast(ushort)(address - this.y)); // dummy read 451 | break; 452 | default: 453 | break; 454 | } 455 | 456 | auto info = stepInfo(address, this.pc, mode); 457 | this.table[opcode](&info); 458 | 459 | if (prevRunIrq) { 460 | auto startCycles = this.cycles; 461 | 462 | this.irq(); 463 | 464 | assert(this.cycles - startCycles == 7); 465 | } 466 | } 467 | 468 | void setNmiFlag() { 469 | this.nmiFlag = true; 470 | } 471 | 472 | void clearNmiFlag() { 473 | this.nmiFlag = false; 474 | } 475 | 476 | void addIrqSource(IrqSource source) { 477 | this.irqFlag |= source; 478 | } 479 | 480 | void clearIrqSource(IrqSource source) { 481 | this.irqFlag &= ~source; 482 | } 483 | 484 | bool hasIrqSource(IrqSource source) { 485 | return (this.irqFlag & source) != 0; 486 | } 487 | 488 | void save(string[string] state) { 489 | state["cpu.cycles"] = to!string(this.cycles); 490 | state["cpu.pc"] = to!string(this.pc); 491 | state["cpu.sp"] = to!string(this.sp); 492 | state["cpu.a"] = to!string(this.a); 493 | state["cpu.x"] = to!string(this.x); 494 | state["cpu.y"] = to!string(this.y); 495 | state["cpu.c"] = to!string(this.c); 496 | state["cpu.z"] = to!string(this.z); 497 | state["cpu.i"] = to!string(this.i); 498 | state["cpu.d"] = to!string(this.d); 499 | state["cpu.b"] = to!string(this.b); 500 | state["cpu.u"] = to!string(this.u); 501 | state["cpu.v"] = to!string(this.v); 502 | state["cpu.n"] = to!string(this.n); 503 | state["cpu.stall"] = to!string(this.stall); 504 | 505 | state["cpu.nmiFlag"] = to!string(this.nmiFlag); 506 | state["cpu.irqFlag"] = to!string(this.irqFlag); 507 | state["cpu.runIrq"] = to!string(this.runIrq); 508 | state["cpu.prevRunIrq"] = to!string(this.prevRunIrq); 509 | state["cpu.spriteDmaTransferRunning"] = to!string(this.spriteDmaTransferRunning); 510 | state["cpu.spriteDmaCounter"] = to!string(this.spriteDmaCounter); 511 | } 512 | 513 | void load(string[string] state) { 514 | this.cycles = to!ulong(state["cpu.cycles"]); 515 | this.pc = to!ushort(state["cpu.pc"]); 516 | this.sp = to!ubyte(state["cpu.sp"]); 517 | this.a = to!ubyte(state["cpu.a"]); 518 | this.x = to!ubyte(state["cpu.x"]); 519 | this.y = to!ubyte(state["cpu.y"]); 520 | this.c = to!ubyte(state["cpu.c"]); 521 | this.z = to!ubyte(state["cpu.z"]); 522 | this.i = to!ubyte(state["cpu.i"]); 523 | this.d = to!ubyte(state["cpu.d"]); 524 | this.b = to!ubyte(state["cpu.b"]); 525 | this.u = to!ubyte(state["cpu.u"]); 526 | this.v = to!ubyte(state["cpu.v"]); 527 | this.n = to!ubyte(state["cpu.n"]); 528 | this.stall = to!int(state["cpu.stall"]); 529 | 530 | this.nmiFlag = to!bool(state["cpu.nmiFlag"]); 531 | this.irqFlag = to!uint(state["cpu.irqFlag"]); 532 | this.runIrq = to!bool(state["cpu.runIrq"]); 533 | this.prevRunIrq = to!bool(state["cpu.prevRunIrq"]); 534 | this.spriteDmaTransferRunning = to!bool(state["cpu.spriteDmaTransferRunning"]); 535 | this.spriteDmaCounter = to!ushort(state["cpu.spriteDmaCounter"]); 536 | } 537 | 538 | package: 539 | void spriteDmaTransfer(ubyte value) { 540 | this.spriteDmaTransferRunning = true; 541 | 542 | if ((this.cycles & 1) == 0) { 543 | this.memoryRead(this.pc); // dummy read 544 | } 545 | 546 | this.memoryRead(this.pc); // dummy read 547 | 548 | this.spriteDmaCounter = 256; 549 | 550 | for (int i = 0; i < 0x100; i++) { 551 | auto readValue = this.memoryRead(cast(ushort)(value * 0x100 + i)); 552 | 553 | this.memoryWrite(0x2004, readValue); 554 | 555 | this.spriteDmaCounter--; 556 | } 557 | 558 | this.spriteDmaTransferRunning = false; 559 | } 560 | 561 | private: 562 | bool nmiFlag; 563 | uint irqFlag; 564 | bool runIrq, prevRunIrq; 565 | InstructionFuncType[256] table; 566 | bool spriteDmaTransferRunning; 567 | ushort spriteDmaCounter; 568 | 569 | // createTable builds a function table for each instruction 570 | void createTable() { 571 | this.table = [ 572 | &this.brk, &this.ora, &this.kil, &this.slo, &this.nop, &this.ora, &this.asl, &this.slo, &this.php, &this.ora, &this.asl, &this.aac, &this.nop, &this.ora, &this.asl, &this.slo, 573 | &this.bpl, &this.ora, &this.kil, &this.slo, &this.nop, &this.ora, &this.asl, &this.slo, &this.clc, &this.ora, &this.nop, &this.slo, &this.nop, &this.ora, &this.asl, &this.slo, 574 | &this.jsr, &this.and, &this.kil, &this.rla, &this.bit, &this.and, &this.rol, &this.rla, &this.plp, &this.and, &this.rol, &this.aac, &this.bit, &this.and, &this.rol, &this.rla, 575 | &this.bmi, &this.and, &this.kil, &this.rla, &this.nop, &this.and, &this.rol, &this.rla, &this.sec, &this.and, &this.nop, &this.rla, &this.nop, &this.and, &this.rol, &this.rla, 576 | &this.rti, &this.eor, &this.kil, &this.sre, &this.nop, &this.eor, &this.lsr, &this.sre, &this.pha, &this.eor, &this.lsr, &this.asr, &this.jmp, &this.eor, &this.lsr, &this.sre, 577 | &this.bvc, &this.eor, &this.kil, &this.sre, &this.nop, &this.eor, &this.lsr, &this.sre, &this.cli, &this.eor, &this.nop, &this.sre, &this.nop, &this.eor, &this.lsr, &this.sre, 578 | &this.rts, &this.adc, &this.kil, &this.rra, &this.nop, &this.adc, &this.ror, &this.rra, &this.pla, &this.adc, &this.ror, &this.arr, &this.jmp, &this.adc, &this.ror, &this.rra, 579 | &this.bvs, &this.adc, &this.kil, &this.rra, &this.nop, &this.adc, &this.ror, &this.rra, &this.sei, &this.adc, &this.nop, &this.rra, &this.nop, &this.adc, &this.ror, &this.rra, 580 | &this.nop, &this.sta, &this.nop, &this.sax, &this.sty, &this.sta, &this.stx, &this.sax, &this.dey, &this.nop, &this.txa, &this.xaa, &this.sty, &this.sta, &this.stx, &this.sax, 581 | &this.bcc, &this.sta, &this.kil, &this.ahx, &this.sty, &this.sta, &this.stx, &this.sax, &this.tya, &this.sta, &this.txs, &this.tas, &this.sya, &this.sta, &this.sxa, &this.ahx, 582 | &this.ldy, &this.lda, &this.ldx, &this.lax, &this.ldy, &this.lda, &this.ldx, &this.lax, &this.tay, &this.lda, &this.tax, &this.atx, &this.ldy, &this.lda, &this.ldx, &this.lax, 583 | &this.bcs, &this.lda, &this.kil, &this.lax, &this.ldy, &this.lda, &this.ldx, &this.lax, &this.clv, &this.lda, &this.tsx, &this.las, &this.ldy, &this.lda, &this.ldx, &this.lax, 584 | &this.cpy, &this.cmp, &this.nop, &this.dcp, &this.cpy, &this.cmp, &this.dec, &this.dcp, &this.iny, &this.cmp, &this.dex, &this.axs, &this.cpy, &this.cmp, &this.dec, &this.dcp, 585 | &this.bne, &this.cmp, &this.kil, &this.dcp, &this.nop, &this.cmp, &this.dec, &this.dcp, &this.cld, &this.cmp, &this.nop, &this.dcp, &this.nop, &this.cmp, &this.dec, &this.dcp, 586 | &this.cpx, &this.sbc, &this.nop, &this.isc, &this.cpx, &this.sbc, &this.inc, &this.isc, &this.inx, &this.sbc, &this.nop, &this.sbc, &this.cpx, &this.sbc, &this.inc, &this.isc, 587 | &this.beq, &this.sbc, &this.kil, &this.isc, &this.nop, &this.sbc, &this.inc, &this.isc, &this.sed, &this.sbc, &this.nop, &this.isc, &this.nop, &this.sbc, &this.inc, &this.isc 588 | ]; 589 | } 590 | 591 | ubyte memoryRead(ushort address) { 592 | this.nextCycle(); 593 | 594 | return this.read(address); 595 | } 596 | 597 | ushort memoryRead16(ushort address) { 598 | auto lo = cast(ushort)this.memoryRead(address); 599 | auto hi = cast(ushort)this.memoryRead(cast(ushort)(address + 1)); 600 | 601 | return cast(ushort)(hi << 8 | lo); 602 | } 603 | 604 | void memoryWrite(ushort address, ubyte value) { 605 | this.nextCycle(); 606 | 607 | this.write(address, value); 608 | } 609 | 610 | void nextCycle() { 611 | this.cycles++; 612 | 613 | foreach (_; 0 .. 3) { 614 | this.console.ppu.step(); 615 | this.console.mapper.step(); 616 | } 617 | 618 | this.console.apu.step(); 619 | 620 | if (!this.spriteDmaTransferRunning) { 621 | this.prevRunIrq = this.runIrq; 622 | this.runIrq = this.nmiFlag || (this.irqFlag && this.i == 0); 623 | } 624 | } 625 | 626 | // addBranchCycles adds a cycle for taking a branch and adds another cycle 627 | // if the branch jumps to a new page 628 | void addBranchCycles(stepInfo* info) { 629 | if(runIrq && !prevRunIrq) { 630 | runIrq = false; 631 | } 632 | 633 | this.memoryRead(this.pc); // dummy read 634 | if (pagesDiffer(info.pc, info.address)) { 635 | this.memoryRead(this.pc); // dummy read 636 | } 637 | } 638 | 639 | void compare(ubyte a, ubyte b) { 640 | this.setZN(cast(ubyte)(a - b)); 641 | if (a >= b) { 642 | this.c = 1; 643 | } else { 644 | this.c = 0; 645 | } 646 | } 647 | 648 | // push pushes a byte onto the stack 649 | void push(ubyte value) { 650 | this.memoryWrite(0x100 + cast(ushort)this.sp, value); 651 | this.sp--; 652 | } 653 | 654 | // pull pops a byte from the stack 655 | ubyte pull() { 656 | this.sp++; 657 | return this.memoryRead(0x100 + cast(ushort)this.sp); 658 | } 659 | 660 | // push16 pushes two bytes onto the stack 661 | void push16(ushort value) { 662 | auto hi = cast(ubyte)(value >> 8); 663 | auto lo = cast(ubyte)(value & 0xFF); 664 | this.push(hi); 665 | this.push(lo); 666 | } 667 | 668 | // pull16 pops two bytes from the stack 669 | ushort pull16() { 670 | auto lo = cast(ushort)this.pull(); 671 | auto hi = cast(ushort)this.pull(); 672 | return cast(ushort)(hi << 8 | lo); 673 | } 674 | 675 | // setZ sets the zero flag if the argument is zero 676 | void setZ(ubyte value) { 677 | if (value == 0) { 678 | this.z = 1; 679 | } else { 680 | this.z = 0; 681 | } 682 | } 683 | 684 | // setN sets the negative flag if the argument is negative (high bit is set) 685 | void setN(ubyte value) { 686 | if ((value & 0x80) != 0) { 687 | this.n = 1; 688 | } else { 689 | this.n = 0; 690 | } 691 | } 692 | 693 | // setZN sets the zero flag and the negative flag 694 | void setZN(ubyte value) { 695 | this.setZ(value); 696 | this.setN(value); 697 | } 698 | 699 | // NMI - Non-Maskable Interrupt 700 | void nmi() { 701 | this.memoryRead(this.pc); // dummy read 702 | this.memoryRead(this.pc); // dummy read 703 | this.push16(this.pc); 704 | this.push(this.flags()); 705 | this.i = 1; 706 | this.pc = this.memoryRead16(0xFFFA); 707 | } 708 | 709 | // IRQ - IRQ Interrupt 710 | void irq() { 711 | this.memoryRead(this.pc); // dummy read 712 | this.memoryRead(this.pc); // dummy read 713 | this.push16(this.pc); 714 | 715 | if (this.nmiFlag) { 716 | this.push(this.flags()); 717 | this.i = 1; 718 | this.pc = this.memoryRead16(0xFFFA); 719 | this.nmiFlag = false; 720 | } else { 721 | this.push(this.flags()); 722 | this.i = 1; 723 | this.pc = this.memoryRead16(0xFFFE); 724 | } 725 | } 726 | 727 | // ADC - Add with Carry 728 | void adc(stepInfo* info) { 729 | auto a = this.a; 730 | auto b = this.memoryRead(info.address); 731 | auto c = this.c; 732 | this.a = cast(ubyte)(a + b + c); 733 | this.setZN(this.a); 734 | if (cast(int)a + cast(int)b + cast(int)c > 0xFF) { 735 | this.c = 1; 736 | } else { 737 | this.c = 0; 738 | } 739 | if (((a ^ b) & 0x80) == 0 && ((a ^ this.a) & 0x80) != 0) { 740 | this.v = 1; 741 | } else { 742 | this.v = 0; 743 | } 744 | } 745 | 746 | // AND - Logical AND 747 | void and(stepInfo* info) { 748 | this.a = this.a & this.memoryRead(info.address); 749 | this.setZN(this.a); 750 | } 751 | 752 | // ASL - Arithmetic Shift Left 753 | void asl(stepInfo* info) { 754 | if (info.mode == modeAccumulator) { 755 | this.c = (this.a >> 7) & 1; 756 | this.a <<= 1; 757 | this.setZN(this.a); 758 | } else { 759 | auto value = this.memoryRead(info.address); 760 | this.memoryWrite(info.address, value); // dummy write 761 | this.c = (value >> 7) & 1; 762 | value <<= 1; 763 | this.setZN(value); 764 | this.memoryWrite(info.address, value); 765 | } 766 | } 767 | 768 | // BCC - Branch if Carry Clear 769 | void bcc(stepInfo* info) { 770 | if (this.c == 0) { 771 | this.pc = info.address; 772 | this.addBranchCycles(info); 773 | } 774 | } 775 | 776 | // BCS - Branch if Carry Set 777 | void bcs(stepInfo* info) { 778 | if (this.c != 0) { 779 | this.pc = info.address; 780 | this.addBranchCycles(info); 781 | } 782 | } 783 | 784 | // BEQ - Branch if Equal 785 | void beq(stepInfo* info) { 786 | if (this.z != 0) { 787 | this.pc = info.address; 788 | this.addBranchCycles(info); 789 | } 790 | } 791 | 792 | // BIT - Bit Test 793 | void bit(stepInfo* info) { 794 | auto value = this.memoryRead(info.address); 795 | this.z = this.v = this.n = 0; 796 | this.v = (value >> 6) & 1; 797 | this.setZ(value & this.a); 798 | this.setN(value); 799 | } 800 | 801 | // BMI - Branch if Minus 802 | void bmi(stepInfo* info) { 803 | if (this.n != 0) { 804 | this.pc = info.address; 805 | this.addBranchCycles(info); 806 | } 807 | } 808 | 809 | // BNE - Branch if Not Equal 810 | void bne(stepInfo* info) { 811 | if (this.z == 0) { 812 | this.pc = info.address; 813 | this.addBranchCycles(info); 814 | } 815 | } 816 | 817 | // BPL - Branch if Positive 818 | void bpl(stepInfo* info) { 819 | if (this.n == 0) { 820 | this.pc = info.address; 821 | this.addBranchCycles(info); 822 | } 823 | } 824 | 825 | // BRK - Force Interrupt 826 | void brk(stepInfo* info) { 827 | this.push16(cast(ushort)(this.pc + 1)); 828 | 829 | if (this.nmiFlag) { 830 | this.push(this.flags() | 0x10); 831 | this.i = 1; 832 | 833 | this.pc = this.memoryRead16(0xFFFA); 834 | } else { 835 | this.push(this.flags() | 0x10); 836 | this.i = 1; 837 | 838 | this.pc = this.memoryRead16(0xFFFE); 839 | } 840 | 841 | this.prevRunIrq = false; 842 | } 843 | 844 | // BVC - Branch if Overflow Clear 845 | void bvc(stepInfo* info) { 846 | if (this.v == 0) { 847 | this.pc = info.address; 848 | this.addBranchCycles(info); 849 | } 850 | } 851 | 852 | // BVS - Branch if Overflow Set 853 | void bvs(stepInfo* info) { 854 | if (this.v != 0) { 855 | this.pc = info.address; 856 | this.addBranchCycles(info); 857 | } 858 | } 859 | 860 | // CLC - Clear Carry Flag 861 | void clc(stepInfo* info) { 862 | this.c = 0; 863 | } 864 | 865 | // CLD - Clear Decimal Mode 866 | void cld(stepInfo* info) { 867 | this.d = 0; 868 | } 869 | 870 | // CLI - Clear Interrupt Disable 871 | void cli(stepInfo* info) { 872 | this.i = 0; 873 | } 874 | 875 | // CLV - Clear Overflow Flag 876 | void clv(stepInfo* info) { 877 | this.v = 0; 878 | } 879 | 880 | // CMP - Compare 881 | void cmp(stepInfo* info) { 882 | auto value = this.memoryRead(info.address); 883 | this.compare(this.a, value); 884 | } 885 | 886 | // CPX - Compare X Register 887 | void cpx(stepInfo* info) { 888 | auto value = this.memoryRead(info.address); 889 | this.compare(this.x, value); 890 | } 891 | 892 | // CPY - Compare Y Register 893 | void cpy(stepInfo* info) { 894 | auto value = this.memoryRead(info.address); 895 | this.compare(this.y, value); 896 | } 897 | 898 | // DEC - Decrement Memory 899 | void dec(stepInfo* info) { 900 | auto value = this.memoryRead(info.address); 901 | this.memoryWrite(info.address, value); // dummy write 902 | value--; 903 | this.setZN(value); 904 | this.memoryWrite(info.address, value); 905 | } 906 | 907 | // DEX - Decrement X Register 908 | void dex(stepInfo* info) { 909 | this.x--; 910 | this.setZN(this.x); 911 | } 912 | 913 | // DEY - Decrement Y Register 914 | void dey(stepInfo* info) { 915 | this.y--; 916 | this.setZN(this.y); 917 | } 918 | 919 | // EOR - Exclusive OR 920 | void eor(stepInfo* info) { 921 | this.a = this.a ^ this.memoryRead(info.address); 922 | this.setZN(this.a); 923 | } 924 | 925 | // INC - Increment Memory 926 | void inc(stepInfo* info) { 927 | auto value = this.memoryRead(info.address); 928 | this.memoryWrite(info.address, value); // dummy write 929 | value++; 930 | this.setZN(value); 931 | this.memoryWrite(info.address, value); 932 | } 933 | 934 | // INX - Increment X Register 935 | void inx(stepInfo* info) { 936 | this.x++; 937 | this.setZN(this.x); 938 | } 939 | 940 | // INY - Increment Y Register 941 | void iny(stepInfo* info) { 942 | this.y++; 943 | this.setZN(this.y); 944 | } 945 | 946 | // JMP - Jump 947 | void jmp(stepInfo* info) { 948 | this.pc = info.address; 949 | } 950 | 951 | // JSR - Jump to Subroutine 952 | void jsr(stepInfo* info) { 953 | this.memoryRead(this.pc); // dummy read 954 | this.push16(cast(ushort)(this.pc - 1)); 955 | this.pc = info.address; 956 | } 957 | 958 | // LDA - Load Accumulator 959 | void lda(stepInfo* info) { 960 | this.a = this.memoryRead(info.address); 961 | this.setZN(this.a); 962 | } 963 | 964 | // LDX - Load X Register 965 | void ldx(stepInfo* info) { 966 | this.x = this.memoryRead(info.address); 967 | this.setZN(this.x); 968 | } 969 | 970 | // LDY - Load Y Register 971 | void ldy(stepInfo* info) { 972 | this.y = this.memoryRead(info.address); 973 | this.setZN(this.y); 974 | } 975 | 976 | // LSR - Logical Shift Right 977 | void lsr(stepInfo* info) { 978 | if (info.mode == modeAccumulator) { 979 | this.c = this.a & 1; 980 | this.a >>= 1; 981 | this.setZN(this.a); 982 | } else { 983 | auto value = this.memoryRead(info.address); 984 | this.memoryWrite(info.address, value); // dummy write 985 | this.c = value & 1; 986 | value >>= 1; 987 | this.setZN(value); 988 | this.memoryWrite(info.address, value); 989 | } 990 | } 991 | 992 | // NOP - No Operation 993 | void nop(stepInfo* info) { 994 | if (info.mode != modeAccumulator && 995 | info.mode != modeImplied && 996 | info.mode != modeRelative) 997 | { 998 | this.memoryRead(info.address); // dummy read 999 | } 1000 | } 1001 | 1002 | // ORA - Logical Inclusive OR 1003 | void ora(stepInfo* info) { 1004 | this.a = this.a | this.memoryRead(info.address); 1005 | this.setZN(this.a); 1006 | } 1007 | 1008 | // PHA - Push Accumulator 1009 | void pha(stepInfo* info) { 1010 | this.push(this.a); 1011 | } 1012 | 1013 | // PHP - Push Processor Status 1014 | void php(stepInfo* info) { 1015 | this.push(this.flags() | 0x10); 1016 | } 1017 | 1018 | // PLA - Pull Accumulator 1019 | void pla(stepInfo* info) { 1020 | this.memoryRead(this.pc); // dummy read 1021 | this.a = this.pull(); 1022 | this.setZN(this.a); 1023 | } 1024 | 1025 | // PLP - Pull Processor Status 1026 | void plp(stepInfo* info) { 1027 | this.memoryRead(this.pc); // dummy read 1028 | this.setFlags((this.pull() & 0xCF) | 0x20); 1029 | } 1030 | 1031 | // ROL - Rotate Left 1032 | void rol(stepInfo* info) { 1033 | if (info.mode == modeAccumulator) { 1034 | auto c = this.c; 1035 | this.c = cast(ubyte)((this.a >> 7) & 1); 1036 | this.a = cast(ubyte)((this.a << 1) | c); 1037 | this.setZN(this.a); 1038 | } else { 1039 | auto c = this.c; 1040 | auto value = this.memoryRead(info.address); 1041 | this.memoryWrite(info.address, value); // dummy write 1042 | this.c = (value >> 7) & 1; 1043 | value = cast(ubyte)((value << 1) | c); 1044 | this.setZN(value); 1045 | this.memoryWrite(info.address, value); 1046 | } 1047 | } 1048 | 1049 | // ROR - Rotate Right 1050 | void ror(stepInfo* info) { 1051 | if (info.mode == modeAccumulator) { 1052 | auto c = this.c; 1053 | this.c = this.a & 1; 1054 | this.a = cast(ubyte)((this.a >> 1) | (c << 7)); 1055 | this.setZN(this.a); 1056 | } else { 1057 | auto c = this.c; 1058 | auto value = this.memoryRead(info.address); 1059 | this.memoryWrite(info.address, value); // dummy write 1060 | this.c = value & 1; 1061 | value = cast(ubyte)((value >> 1) | (c << 7)); 1062 | this.setZN(value); 1063 | this.memoryWrite(info.address, value); 1064 | } 1065 | } 1066 | 1067 | // RTI - Return from Interrupt 1068 | void rti(stepInfo* info) { 1069 | this.memoryRead(this.pc); // dummy read 1070 | this.setFlags((this.pull() & 0xCF) | 0x20); 1071 | this.pc = this.pull16(); 1072 | } 1073 | 1074 | // RTS - Return from Subroutine 1075 | void rts(stepInfo* info) { 1076 | auto addr = cast(ushort)(this.pull16() + 1); 1077 | this.memoryRead(this.pc); // dummy read 1078 | this.memoryRead(this.pc); // dummy read 1079 | this.pc = addr; 1080 | } 1081 | 1082 | // SBC - Subtract with Carry 1083 | void sbc(stepInfo* info) { 1084 | auto a = this.a; 1085 | auto b = this.memoryRead(info.address); 1086 | auto c = this.c; 1087 | this.a = cast(ubyte)(a - b - (1 - c)); 1088 | this.setZN(this.a); 1089 | if (cast(int)a - cast(int)b - cast(int)(1 - c) >= 0) { 1090 | this.c = 1; 1091 | } else { 1092 | this.c = 0; 1093 | } 1094 | if (((a ^ b) & 0x80) != 0 && ((a ^ this.a) & 0x80) != 0) { 1095 | this.v = 1; 1096 | } else { 1097 | this.v = 0; 1098 | } 1099 | } 1100 | 1101 | // SEC - Set Carry Flag 1102 | void sec(stepInfo* info) { 1103 | this.c = 1; 1104 | } 1105 | 1106 | // SED - Set Decimal Flag 1107 | void sed(stepInfo* info) { 1108 | this.d = 1; 1109 | } 1110 | 1111 | // SEI - Set Interrupt Disable 1112 | void sei(stepInfo* info) { 1113 | this.i = 1; 1114 | } 1115 | 1116 | // STA - Store Accumulator 1117 | void sta(stepInfo* info) { 1118 | this.memoryWrite(info.address, this.a); 1119 | } 1120 | 1121 | // STX - Store X Register 1122 | void stx(stepInfo* info) { 1123 | this.memoryWrite(info.address, this.x); 1124 | } 1125 | 1126 | // STY - Store Y Register 1127 | void sty(stepInfo* info) { 1128 | this.memoryWrite(info.address, this.y); 1129 | } 1130 | 1131 | // TAX - Transfer Accumulator to X 1132 | void tax(stepInfo* info) { 1133 | this.x = this.a; 1134 | this.setZN(this.x); 1135 | } 1136 | 1137 | // TAY - Transfer Accumulator to Y 1138 | void tay(stepInfo* info) { 1139 | this.y = this.a; 1140 | this.setZN(this.y); 1141 | } 1142 | 1143 | // TSX - Transfer Stack Pointer to X 1144 | void tsx(stepInfo* info) { 1145 | this.x = this.sp; 1146 | this.setZN(this.x); 1147 | } 1148 | 1149 | // TXA - Transfer X to Accumulator 1150 | void txa(stepInfo* info) { 1151 | this.a = this.x; 1152 | this.setZN(this.a); 1153 | } 1154 | 1155 | // TXS - Transfer X to Stack Pointer 1156 | void txs(stepInfo* info) { 1157 | this.sp = this.x; 1158 | } 1159 | 1160 | // TYA - Transfer Y to Accumulator 1161 | void tya(stepInfo* info) { 1162 | this.a = this.y; 1163 | this.setZN(this.a); 1164 | } 1165 | 1166 | // illegal opcodes below 1167 | 1168 | // AKA AXA 1169 | void ahx(stepInfo* info) { 1170 | this.memoryWrite(info.address, 1171 | ((info.address >> 8) + 1) & this.a & this.x); 1172 | } 1173 | 1174 | void aac(stepInfo* info) { 1175 | // Not sure if this is correct 1176 | auto value = this.memoryRead(info.address); 1177 | this.a &= value; 1178 | this.setZN(this.a); 1179 | this.c = this.n; 1180 | } 1181 | 1182 | void asr(stepInfo* info) { 1183 | this.c = 0; 1184 | auto value = this.memoryRead(info.address); 1185 | this.a &= value; 1186 | this.setZN(this.a); 1187 | 1188 | if (this.a & 0x01) this.c = 1; 1189 | 1190 | this.a >>= 1; 1191 | this.setZN(this.a); 1192 | } 1193 | 1194 | void arr(stepInfo* info) { 1195 | // Not sure if this is correct 1196 | auto value = this.memoryRead(info.address); 1197 | 1198 | this.a = ((this.a & value) >> 1) | (this.c ? 0x80 : 0x00); 1199 | 1200 | this.setZN(this.a); 1201 | this.c = 0; 1202 | this.v = 0; 1203 | 1204 | if (this.a & 0x40) this.c = 1; 1205 | if ((this.c ? 0x01 : 0x00) ^ ((this.a >> 5) & 0x01)) 1206 | this.v = 1; 1207 | } 1208 | 1209 | void atx(stepInfo* info) { 1210 | // Not sure if this is correct 1211 | auto value = this.memoryRead(info.address); 1212 | this.a = value; 1213 | this.x = this.a; 1214 | this.setZN(this.a); 1215 | 1216 | } 1217 | 1218 | void axs(stepInfo* info) { 1219 | // Not sure if this is correct 1220 | auto orgValue = this.memoryRead(info.address); 1221 | ubyte value = cast(ubyte)((this.a & this.x) - orgValue); 1222 | 1223 | this.c = 0; 1224 | if ((this.a & this.x) >= orgValue) this.c = 1; 1225 | 1226 | this.x = value; 1227 | this.setZN(this.x); 1228 | } 1229 | 1230 | void dcp(stepInfo* info) { 1231 | // Not sure if this is correct 1232 | auto value = this.memoryRead(info.address); 1233 | this.memoryWrite(info.address, value); // dummy write 1234 | value--; 1235 | this.compare(this.a, value); 1236 | this.memoryWrite(info.address, value); 1237 | } 1238 | 1239 | void isc(stepInfo* info) { 1240 | // Not sure if this is correct 1241 | auto value = this.memoryRead(info.address); 1242 | this.memoryWrite(info.address, value); // dummy write 1243 | value++; 1244 | 1245 | // SBC 1246 | auto a = this.a; 1247 | auto b = value; 1248 | auto c = this.c; 1249 | this.a = cast(ubyte)(a - b - (1 - c)); 1250 | this.setZN(this.a); 1251 | if (cast(int)a - cast(int)b - cast(int)(1 - c) >= 0) { 1252 | this.c = 1; 1253 | } else { 1254 | this.c = 0; 1255 | } 1256 | if (((a ^ b) & 0x80) != 0 && ((a ^ this.a) & 0x80) != 0) { 1257 | this.v = 1; 1258 | } else { 1259 | this.v = 0; 1260 | } 1261 | 1262 | this.memoryWrite(info.address, value); 1263 | } 1264 | 1265 | void kil(stepInfo* info) { 1266 | } 1267 | 1268 | // AKA LAR 1269 | void las(stepInfo* info) { 1270 | auto value = this.memoryRead(info.address); 1271 | this.a = value & this.sp; 1272 | this.x = this.a; 1273 | this.setZN(this.x); 1274 | this.sp = this.a; 1275 | } 1276 | 1277 | void lax(stepInfo* info) { 1278 | auto value = this.memoryRead(info.address); 1279 | this.x = value; 1280 | this.a = value; 1281 | this.setZN(value); 1282 | } 1283 | 1284 | void rla(stepInfo* info) { 1285 | // Not sure if this is correct 1286 | auto value = this.memoryRead(info.address); 1287 | this.memoryWrite(info.address, value); // dummy write 1288 | 1289 | // ROL 1290 | auto c = this.c; 1291 | this.c = (value >> 7) & 1; 1292 | value = cast(ubyte)((value << 1) | c); 1293 | this.setZN(value); 1294 | 1295 | this.a &= value; 1296 | this.setZN(this.a); 1297 | 1298 | this.memoryWrite(info.address, value); 1299 | } 1300 | 1301 | void rra(stepInfo* info) { 1302 | // Not sure if this is correct 1303 | auto value = this.memoryRead(info.address); 1304 | this.memoryWrite(info.address, value); // dummy write 1305 | 1306 | // ROR 1307 | auto c = this.c; 1308 | this.c = value & 1; 1309 | value = cast(ubyte)((value >> 1) | (c << 7)); 1310 | this.setZN(value); 1311 | 1312 | // ADC 1313 | auto a = this.a; 1314 | auto b = value; 1315 | c = this.c; 1316 | this.a = cast(ubyte)(a + b + c); 1317 | this.setZN(this.a); 1318 | if (cast(int)a + cast(int)b + cast(int)c > 0xFF) { 1319 | this.c = 1; 1320 | } else { 1321 | this.c = 0; 1322 | } 1323 | if (((a ^ b) & 0x80) == 0 && ((a ^ this.a) & 0x80) != 0) { 1324 | this.v = 1; 1325 | } else { 1326 | this.v = 0; 1327 | } 1328 | 1329 | this.memoryWrite(info.address, value); 1330 | } 1331 | 1332 | // AKA AAX 1333 | void sax(stepInfo* info) { 1334 | // Not sure if this is correct 1335 | this.memoryWrite(info.address, this.a & this.x); 1336 | } 1337 | 1338 | // AKA SHX 1339 | void sxa(stepInfo* info) { 1340 | ubyte hi = info.address >> 8; 1341 | ubyte lo = info.address & 0xFF; 1342 | ubyte value = this.x & (hi + 1); 1343 | this.memoryWrite(((this.x & (hi + 1)) << 8) | lo, value); 1344 | } 1345 | 1346 | // AKA SHY 1347 | void sya(stepInfo* info) { 1348 | ubyte hi = info.address >> 8; 1349 | ubyte lo = info.address & 0xFF; 1350 | ubyte value = this.y & (hi + 1); 1351 | 1352 | this.memoryWrite(((this.y & (hi + 1)) << 8) | lo, value); 1353 | } 1354 | 1355 | void slo(stepInfo* info) { 1356 | // Not sure if this is correct 1357 | auto value = this.memoryRead(info.address); 1358 | this.memoryWrite(info.address, value); // dummy write 1359 | 1360 | // ASL 1361 | this.c = (value >> 7) & 1; 1362 | value <<= 1; 1363 | this.setZN(value); 1364 | 1365 | // ORA 1366 | this.a = this.a | value; 1367 | this.setZN(this.a); 1368 | 1369 | this.memoryWrite(info.address, value); 1370 | } 1371 | 1372 | void sre(stepInfo* info) { 1373 | // Not sure if this is correct 1374 | auto value = this.memoryRead(info.address); 1375 | this.memoryWrite(info.address, value); // dummy write 1376 | 1377 | // LSR 1378 | this.c = value & 1; 1379 | value >>= 1; 1380 | this.setZN(value); 1381 | 1382 | // EOR 1383 | this.a = this.a ^ value; 1384 | this.setZN(this.a); 1385 | 1386 | this.memoryWrite(info.address, value); 1387 | } 1388 | 1389 | void tas(stepInfo* info) { 1390 | this.sp = this.x & this.a; 1391 | this.memoryWrite(info.address, this.sp & ((info.address >> 8) + 1)); 1392 | } 1393 | 1394 | void xaa(stepInfo* info) { 1395 | this.memoryRead(this.pc); // dummy read 1396 | } 1397 | } 1398 | -------------------------------------------------------------------------------- /source/nes/image.d: -------------------------------------------------------------------------------- 1 | module nes.image; 2 | 3 | import std.algorithm; 4 | 5 | import nes.color; 6 | 7 | struct Point { 8 | int x, y; 9 | } 10 | 11 | struct Rectangle { 12 | Point min, max; 13 | 14 | int dx() { 15 | return this.max.x - this.min.x; 16 | } 17 | 18 | int dy() { 19 | return this.max.y - this.min.y; 20 | } 21 | } 22 | 23 | // PointIn reports whether p is in r. 24 | bool PointIn(Point p, Rectangle r) { 25 | return r.min.x <= p.x && p.x < r.max.x && 26 | r.min.y <= p.y && p.y < r.max.y; 27 | } 28 | 29 | class ImageRGBA { 30 | ubyte[] pix; 31 | int stride; 32 | Rectangle rect; 33 | 34 | this(Rectangle r) { 35 | auto w = r.dx(); 36 | auto h = r.dy(); 37 | 38 | this.pix = new ubyte[4 * w * h]; 39 | this.stride = 4 * w; 40 | this.rect = r; 41 | } 42 | 43 | // PixOffset returns the index of the first element of Pix that corresponds to 44 | // the pixel at (x, y). 45 | int pixOffset(int x, int y) { 46 | return (y - this.rect.min.y) * this.stride + (x - this.rect.min.x) * 4; 47 | } 48 | 49 | void setRGBA(int x, int y, RGBA c) { 50 | if (!PointIn(Point(x, y), this.rect)) { 51 | return; 52 | } 53 | 54 | auto i = this.pixOffset(x, y); 55 | this.pix[i + 0] = c.r; 56 | this.pix[i + 1] = c.g; 57 | this.pix[i + 2] = c.b; 58 | this.pix[i + 3] = c.a; 59 | } 60 | } 61 | 62 | Rectangle Rect(int x0, int y0, int x1, int y1) { 63 | if (x0 > x1) { 64 | swap(x0, x1); 65 | } 66 | 67 | if (y0 > y1) { 68 | swap(y0, y1); 69 | } 70 | 71 | return Rectangle(Point(x0, y0), Point(x1, y1)); 72 | } 73 | -------------------------------------------------------------------------------- /source/nes/ines.d: -------------------------------------------------------------------------------- 1 | module nes.ines; 2 | 3 | import std.stdio; 4 | 5 | import nes.cartridge; 6 | 7 | enum iNESFileMagic = 0x1a53454e; 8 | 9 | align(1) struct iNESFileHeader { 10 | uint magic; // iNES magic number 11 | ubyte numPRG; // number of PRG-ROM banks (16KB each) 12 | ubyte numCHR; // number of CHR-ROM banks (8KB each) 13 | ubyte control1; // control bits 14 | ubyte control2; // control bits 15 | ubyte numRAM; // PRG-RAM size (x 8KB) 16 | ubyte[7] padding; // unused padding 17 | } 18 | 19 | class NesFileException : Exception 20 | { 21 | import std.exception : basicExceptionCtors; 22 | 23 | mixin basicExceptionCtors; 24 | } 25 | 26 | // LoadNESFile reads an iNES file (.nes) and returns a Cartridge on success. 27 | // http://wiki.nesdev.com/w/index.php/INES 28 | // http://nesdev.com/NESDoc.pdf (page 28) 29 | Cartridge LoadNESFile(string path) { 30 | // open file 31 | auto file = File(path); 32 | 33 | // read file header 34 | iNESFileHeader[1] headers; 35 | file.rawRead(headers); 36 | 37 | // verify header magic number 38 | if (headers[0].magic != iNESFileMagic) { 39 | throw new NesFileException("invalid .nes file"); 40 | } 41 | 42 | // mapper type 43 | auto mapper1 = cast(ubyte)(headers[0].control1 >> 4); 44 | auto mapper2 = cast(ubyte)(headers[0].control2 >> 4); 45 | auto mapper = cast(ubyte)(mapper1 | mapper2 << 4); 46 | 47 | // mirroring type 48 | auto mirror1 = cast(ubyte)(headers[0].control1 & 1); 49 | auto mirror2 = cast(ubyte)((headers[0].control1 >> 3) & 1); 50 | auto mirror = cast(ubyte)(mirror1 | mirror2 << 1); 51 | 52 | // battery-backed RAM 53 | auto battery = cast(ubyte)((headers[0].control1 >> 1) & 1); 54 | 55 | // read trainer if present (unused) 56 | if ((headers[0].control1 & 4) == 4) { 57 | auto trainer = new ubyte[512]; 58 | file.rawRead(trainer); 59 | } 60 | 61 | // read prg-rom bank(s) 62 | auto prg = new ubyte[cast(int)headers[0].numPRG * 16384]; 63 | file.rawRead(prg); 64 | 65 | // read chr-rom bank(s) 66 | bool chrRAM; 67 | ubyte[] chr; 68 | 69 | if (headers[0].numCHR > 0) { 70 | chr = new ubyte[cast(int)headers[0].numCHR * 8192]; 71 | file.rawRead(chr); 72 | } else { 73 | // provide chr-ram if not in file 74 | chr = new ubyte[8192]; 75 | chrRAM = true; 76 | } 77 | 78 | // success 79 | return new Cartridge(prg, chr, mapper, mirror, battery, chrRAM); 80 | } 81 | -------------------------------------------------------------------------------- /source/nes/mapper.d: -------------------------------------------------------------------------------- 1 | module nes.mapper; 2 | 3 | import std.format; 4 | 5 | import nes.console; 6 | import nes.mapper1; 7 | import nes.mapper2; 8 | import nes.mapper3; 9 | import nes.mapper4; 10 | import nes.mapper7; 11 | import nes.mapper225; 12 | 13 | interface Mapper { 14 | ubyte read(ushort address); 15 | void write(ushort address, ubyte value); 16 | void step(); 17 | void save(string[string] state); 18 | void load(string[string] state); 19 | } 20 | 21 | class MapperException : Exception 22 | { 23 | import std.exception : basicExceptionCtors; 24 | 25 | mixin basicExceptionCtors; 26 | } 27 | 28 | Mapper NewMapper(Console console) { 29 | auto cartridge = console.cartridge; 30 | 31 | switch (cartridge.mapper) { 32 | case 0: 33 | return new Mapper2(cartridge); 34 | case 1: 35 | return new Mapper1(cartridge); 36 | case 2: 37 | return new Mapper2(cartridge); 38 | case 3: 39 | return new Mapper3(cartridge); 40 | case 4: 41 | return new Mapper4(console, cartridge); 42 | case 7: 43 | return new Mapper7(cartridge); 44 | case 225: 45 | return new Mapper225(cartridge); 46 | default: 47 | throw new MapperException(format("unsupported mapper: %d", cartridge.mapper)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/nes/mapper1.d: -------------------------------------------------------------------------------- 1 | module nes.mapper1; 2 | 3 | import std.base64; 4 | import std.conv; 5 | import std.format; 6 | import std.stdio; 7 | 8 | import nes.cartridge; 9 | import nes.mapper; 10 | import nes.memory; 11 | 12 | class Mapper1 : Mapper { 13 | this(Cartridge cartridge) { 14 | this.cart = cartridge; 15 | this.shiftRegister = 0x10; 16 | this.prgOffsets[1] = this.prgBankOffset(-1); 17 | } 18 | 19 | void step() { 20 | } 21 | 22 | ubyte read(ushort address) { 23 | if (address < 0x2000) { 24 | auto bank = address / 0x1000; 25 | auto offset = address % 0x1000; 26 | return this.cart.chr[this.chrOffsets[bank] + cast(int)offset]; 27 | } 28 | else if (address >= 0x8000) { 29 | address = cast(ushort)(address - 0x8000); 30 | auto bank = address / 0x4000; 31 | auto offset = address % 0x4000; 32 | return this.cart.prg[this.prgOffsets[bank] + cast(int)offset]; 33 | } 34 | else if (address >= 0x6000) { 35 | return this.cart.sram[cast(int)address - 0x6000]; 36 | } 37 | else { 38 | throw new MapperException(format("unhandled mapper1 read at address: 0x%04X", address)); 39 | } 40 | } 41 | 42 | void write(ushort address, ubyte value) { 43 | import std.string : fromStringz; 44 | 45 | if (address < 0x2000) { 46 | auto bank = address / 0x1000; 47 | auto offset = address % 0x1000; 48 | this.cart.chr[this.chrOffsets[bank] + cast(int)offset] = value; 49 | } 50 | else if (address >= 0x8000) { 51 | this.loadRegister(address, value); 52 | } 53 | else if (address >= 0x6000) { 54 | this.cart.sram[cast(int)address - 0x6000] = value; 55 | } 56 | else { 57 | throw new MapperException(format("unhandled mapper1 write at address: 0x%04X", address)); 58 | } 59 | } 60 | 61 | void loadRegister(ushort address, ubyte value) { 62 | if ((value & 0x80) == 0x80) { 63 | this.shiftRegister = 0x10; 64 | this.writeControl(this.control | 0x0C); 65 | } else { 66 | auto complete = (this.shiftRegister & 1) == 1; 67 | this.shiftRegister >>= 1; 68 | this.shiftRegister |= (value & 1) << 4; 69 | if (complete) { 70 | this.writeRegister(address, this.shiftRegister); 71 | this.shiftRegister = 0x10; 72 | } 73 | } 74 | } 75 | 76 | void writeRegister(ushort address, ubyte value) { 77 | if (address <= 0x9FFF) { 78 | this.writeControl(value); 79 | } 80 | else if (address <= 0xBFFF) { 81 | this.writeCHRBank0(value); 82 | } 83 | else if (address <= 0xDFFF) { 84 | this.writeCHRBank1(value); 85 | } 86 | else if (address <= 0xFFFF) { 87 | this.writePRGBank(value); 88 | } 89 | } 90 | 91 | // Control (internal, $8000-$9FFF) 92 | void writeControl(ubyte value) { 93 | this.control = value; 94 | this.chrMode = (value >> 4) & 1; 95 | this.prgMode = (value >> 2) & 3; 96 | auto mirror = value & 3; 97 | 98 | switch (mirror) { 99 | case 0: 100 | this.cart.mirror = MirrorSingle0; 101 | break; 102 | case 1: 103 | this.cart.mirror = MirrorSingle1; 104 | break; 105 | case 2: 106 | this.cart.mirror = MirrorVertical; 107 | break; 108 | case 3: 109 | this.cart.mirror = MirrorHorizontal; 110 | break; 111 | default: 112 | break; 113 | } 114 | 115 | this.updateOffsets(); 116 | } 117 | 118 | // CHR bank 0 (internal, $A000-$BFFF) 119 | void writeCHRBank0(ubyte value) { 120 | this.chrBank0 = value; 121 | this.updateOffsets(); 122 | } 123 | 124 | // CHR bank 1 (internal, $C000-$DFFF) 125 | void writeCHRBank1(ubyte value) { 126 | this.chrBank1 = value; 127 | this.updateOffsets(); 128 | } 129 | 130 | // PRG bank (internal, $E000-$FFFF) 131 | void writePRGBank(ubyte value) { 132 | this.prgBank = value & 0x0F; 133 | this.updateOffsets(); 134 | } 135 | 136 | int prgBankOffset(int index) { 137 | if (index >= 0x80) { 138 | index -= 0x100; 139 | } 140 | index %= this.cart.prg.length / 0x4000; 141 | auto offset = index * 0x4000; 142 | if (offset < 0) { 143 | offset += this.cart.prg.length; 144 | } 145 | return offset; 146 | } 147 | 148 | int chrBankOffset(int index) { 149 | if (index >= 0x80) { 150 | index -= 0x100; 151 | } 152 | index %= this.cart.chr.length / 0x1000; 153 | auto offset = index * 0x1000; 154 | if (offset < 0) { 155 | offset += this.cart.chr.length; 156 | } 157 | return offset; 158 | } 159 | 160 | // PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number; 161 | // 2: fix first bank at $8000 and switch 16 KB bank at $C000; 162 | // 3: fix last bank at $C000 and switch 16 KB bank at $8000) 163 | // CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks) 164 | void updateOffsets() { 165 | switch (this.prgMode) { 166 | case 0, 1: 167 | this.prgOffsets[0] = this.prgBankOffset(cast(int)(this.prgBank & 0xFE)); 168 | this.prgOffsets[1] = this.prgBankOffset(cast(int)(this.prgBank | 0x01)); 169 | break; 170 | case 2: 171 | this.prgOffsets[0] = 0; 172 | this.prgOffsets[1] = this.prgBankOffset(cast(int)this.prgBank); 173 | break; 174 | case 3: 175 | this.prgOffsets[0] = this.prgBankOffset(cast(int)this.prgBank); 176 | this.prgOffsets[1] = this.prgBankOffset(-1); 177 | break; 178 | default: 179 | break; 180 | } 181 | 182 | switch (this.chrMode) { 183 | case 0: 184 | this.chrOffsets[0] = this.chrBankOffset(cast(int)(this.chrBank0 & 0xFE)); 185 | this.chrOffsets[1] = this.chrBankOffset(cast(int)(this.chrBank0 | 0x01)); 186 | break; 187 | case 1: 188 | this.chrOffsets[0] = this.chrBankOffset(cast(int)this.chrBank0); 189 | this.chrOffsets[1] = this.chrBankOffset(cast(int)this.chrBank1); 190 | break; 191 | default: 192 | break; 193 | } 194 | } 195 | 196 | void save(string[string] state) { 197 | state["mapper1.shiftRegister"] = to!string(this.shiftRegister); 198 | state["mapper1.control"] = to!string(this.control); 199 | state["mapper1.prgMode"] = to!string(this.prgMode); 200 | state["mapper1.chrMode"] = to!string(this.chrMode); 201 | state["mapper1.prgBank"] = to!string(this.prgBank); 202 | state["mapper1.chrBank0"] = to!string(this.chrBank0); 203 | state["mapper1.chrBank1"] = to!string(this.chrBank1); 204 | state["mapper1.prgOffsets"] = to!string(this.prgOffsets); 205 | state["mapper1.chrOffsets"] = to!string(this.chrOffsets); 206 | } 207 | 208 | void load(string[string] state) { 209 | this.shiftRegister = to!ubyte(state["mapper1.shiftRegister"]); 210 | this.control = to!ubyte(state["mapper1.control"]); 211 | this.prgMode = to!ubyte(state["mapper1.prgMode"]); 212 | this.chrMode = to!ubyte(state["mapper1.chrMode"]); 213 | this.prgBank = to!ubyte(state["mapper1.prgBank"]); 214 | this.chrBank0 = to!ubyte(state["mapper1.chrBank0"]); 215 | this.chrBank1 = to!ubyte(state["mapper1.chrBank1"]); 216 | this.prgOffsets = to!(int[2])(state["mapper1.prgOffsets"]); 217 | this.chrOffsets = to!(int[2])(state["mapper1.chrOffsets"]); 218 | } 219 | 220 | private: 221 | Cartridge cart; 222 | ubyte shiftRegister; 223 | ubyte control; 224 | ubyte prgMode; 225 | ubyte chrMode; 226 | ubyte prgBank; 227 | ubyte chrBank0; 228 | ubyte chrBank1; 229 | int[2] prgOffsets; 230 | int[2] chrOffsets; 231 | } 232 | -------------------------------------------------------------------------------- /source/nes/mapper2.d: -------------------------------------------------------------------------------- 1 | module nes.mapper2; 2 | 3 | import std.conv; 4 | import std.format; 5 | 6 | import nes.cartridge; 7 | import nes.mapper; 8 | 9 | class Mapper2 : Mapper { 10 | this(Cartridge cartridge) { 11 | this.cart = cartridge; 12 | this.prgBanks = cast(int)(cartridge.prg.length / 0x4000); 13 | this.prgBank1 = 0; 14 | this.prgBank2 = this.prgBanks - 1; 15 | } 16 | 17 | void step() { 18 | } 19 | 20 | ubyte read(ushort address) { 21 | if (address < 0x2000) { 22 | return this.cart.chr[address]; 23 | } 24 | else if (address >= 0xC000) { 25 | auto index = this.prgBank2 * 0x4000 + cast(int)(address - 0xC000); 26 | return this.cart.prg[index]; 27 | } 28 | else if (address >= 0x8000) { 29 | auto index = this.prgBank1 * 0x4000 + cast(int)(address - 0x8000); 30 | return this.cart.prg[index]; 31 | } 32 | else if (address >= 0x6000) { 33 | auto index = cast(int)address - 0x6000; 34 | return this.cart.sram[index]; 35 | } 36 | else { 37 | throw new MapperException(format("unhandled mapper2 read at address: 0x%04X", address)); 38 | } 39 | } 40 | 41 | void write(ushort address, ubyte value) { 42 | if (address < 0x2000) { 43 | this.cart.chr[address] = value; 44 | } 45 | else if (address >= 0x8000) { 46 | this.prgBank1 = cast(int)value % this.prgBanks; 47 | } 48 | else if (address >= 0x6000) { 49 | auto index = cast(int)address - 0x6000; 50 | this.cart.sram[index] = value; 51 | } 52 | else { 53 | throw new MapperException(format("unhandled mapper2 write at address: 0x%04X", address)); 54 | } 55 | } 56 | 57 | void save(string[string] state) { 58 | state["mapper2.prgBanks"] = to!string(this.prgBanks); 59 | state["mapper2.prgBank1"] = to!string(this.prgBank1); 60 | state["mapper2.prgBank2"] = to!string(this.prgBank2); 61 | } 62 | 63 | void load(string[string] state) { 64 | this.prgBanks = to!int(state["mapper2.prgBanks"]); 65 | this.prgBank1 = to!int(state["mapper2.prgBank1"]); 66 | this.prgBank2 = to!int(state["mapper2.prgBank2"]); 67 | } 68 | 69 | private: 70 | Cartridge cart; 71 | int prgBanks; 72 | int prgBank1; 73 | int prgBank2; 74 | } 75 | -------------------------------------------------------------------------------- /source/nes/mapper225.d: -------------------------------------------------------------------------------- 1 | module nes.mapper225; 2 | 3 | import std.conv; 4 | import std.format; 5 | import std.stdio; 6 | 7 | import nes.cartridge; 8 | import nes.mapper; 9 | import nes.memory; 10 | 11 | class Mapper225 : Mapper { 12 | this(Cartridge cartridge) { 13 | this.cart = cartridge; 14 | this.chrBank = 0; 15 | this.prgBank1 = 0; 16 | 17 | auto prgBanks = cast(int)(cartridge.prg.length / 0x4000); 18 | 19 | this.prgBank2 = prgBanks - 1; 20 | } 21 | 22 | void step() { 23 | } 24 | 25 | ubyte read(ushort address) { 26 | if (address < 0x2000) { 27 | auto index = this.chrBank * 0x2000 + cast(int)address; 28 | return this.cart.chr[index]; 29 | } 30 | else if (address >= 0xC000) { 31 | auto index = this.prgBank2 * 0x4000 + cast(int)(address - 0xC000); 32 | return this.cart.prg[index]; 33 | } 34 | else if (address >= 0x8000) { 35 | auto index = this.prgBank1 * 0x4000 + cast(int)(address - 0x8000); 36 | return this.cart.prg[index]; 37 | } 38 | else if (address >= 0x6000) { 39 | auto index = cast(int)address - 0x6000; 40 | return this.cart.sram[index]; 41 | } 42 | else { 43 | throw new MapperException(format("unhandled mapper225 read at address: 0x%04X", address)); 44 | } 45 | } 46 | 47 | void write(ushort address, ubyte value) { 48 | if (address < 0x8000) { 49 | return; 50 | } 51 | 52 | auto a = cast(int)address; 53 | auto bank = (a >> 14) & 1; 54 | this.chrBank = (a & 0x3f) | (bank << 6); 55 | auto prg = ((a >> 6) & 0x3f) | (bank << 6); 56 | auto mode = (a >> 12) & 1; 57 | if (mode == 1) { 58 | this.prgBank1 = prg; 59 | this.prgBank2 = prg; 60 | } else { 61 | this.prgBank1 = prg; 62 | this.prgBank2 = prg + 1; 63 | } 64 | 65 | auto mirr = (a >> 13) & 1; 66 | if (mirr == 1) { 67 | this.cart.mirror = MirrorHorizontal; 68 | } else { 69 | this.cart.mirror = MirrorVertical; 70 | } 71 | } 72 | 73 | void save(string[string] state) { 74 | state["mapper225.chrBank"] = to!string(this.chrBank); 75 | state["mapper225.prgBank1"] = to!string(this.prgBank1); 76 | state["mapper225.prgBank2"] = to!string(this.prgBank2); 77 | } 78 | 79 | void load(string[string] state) { 80 | this.chrBank = to!int(state["mapper225.chrBank"]); 81 | this.prgBank1 = to!int(state["mapper225.prgBank1"]); 82 | this.prgBank2 = to!int(state["mapper225.prgBank2"]); 83 | } 84 | 85 | private: 86 | Cartridge cart; 87 | int chrBank; 88 | int prgBank1; 89 | int prgBank2; 90 | } 91 | -------------------------------------------------------------------------------- /source/nes/mapper3.d: -------------------------------------------------------------------------------- 1 | module nes.mapper3; 2 | 3 | import std.conv; 4 | import std.format; 5 | 6 | import nes.cartridge; 7 | import nes.mapper; 8 | 9 | class Mapper3 : Mapper { 10 | this(Cartridge cartridge) { 11 | this.cart = cartridge; 12 | this.chrBank = 0; 13 | this.prgBank1 = 0; 14 | 15 | auto prgBanks = cast(int)(cartridge.prg.length / 0x4000); 16 | 17 | this.prgBank2 = prgBanks - 1; 18 | } 19 | 20 | void step() { 21 | } 22 | 23 | ubyte read(ushort address) { 24 | if (address < 0x2000) { 25 | auto index = this.chrBank * 0x2000 + cast(int)address; 26 | return this.cart.chr[index]; 27 | } 28 | else if (address >= 0xC000) { 29 | auto index = this.prgBank2 * 0x4000 + cast(int)(address - 0xC000); 30 | return this.cart.prg[index]; 31 | } 32 | else if (address >= 0x8000) { 33 | auto index = this.prgBank1 * 0x4000 + cast(int)(address - 0x8000); 34 | return this.cart.prg[index]; 35 | } 36 | else if (address >= 0x6000) { 37 | auto index = cast(int)address - 0x6000; 38 | return this.cart.sram[index]; 39 | } 40 | else { 41 | throw new MapperException(format("unhandled mapper3 read at address: 0x%04X", address)); 42 | } 43 | } 44 | 45 | void write(ushort address, ubyte value) { 46 | if (address < 0x2000) { 47 | auto index = this.chrBank * 0x2000 + cast(int)address; 48 | this.cart.chr[index] = value; 49 | } 50 | else if (address >= 0x8000) { 51 | this.chrBank = cast(int)(value & 3); 52 | } 53 | else if (address >= 0x6000) { 54 | auto index = cast(int)address - 0x6000; 55 | this.cart.sram[index] = value; 56 | } 57 | else { 58 | throw new MapperException(format("unhandled mapper3 write at address: 0x%04X", address)); 59 | } 60 | } 61 | 62 | void save(string[string] state) { 63 | state["mapper3.chrBank"] = to!string(this.chrBank); 64 | state["mapper3.prgBank1"] = to!string(this.prgBank1); 65 | state["mapper3.prgBank2"] = to!string(this.prgBank2); 66 | } 67 | 68 | void load(string[string] state) { 69 | this.chrBank = to!int(state["mapper3.chrBank"]); 70 | this.prgBank1 = to!int(state["mapper3.prgBank1"]); 71 | this.prgBank2 = to!int(state["mapper3.prgBank2"]); 72 | } 73 | 74 | private: 75 | Cartridge cart; 76 | int chrBank; 77 | int prgBank1; 78 | int prgBank2; 79 | } 80 | -------------------------------------------------------------------------------- /source/nes/mapper4.d: -------------------------------------------------------------------------------- 1 | module nes.mapper4; 2 | 3 | import std.conv; 4 | import std.format; 5 | 6 | import nes.cartridge; 7 | import nes.console; 8 | import nes.cpu; 9 | import nes.mapper; 10 | import nes.memory; 11 | 12 | class Mapper4 : Mapper { 13 | this(Console console, Cartridge cartridge) { 14 | this.console = console; 15 | this.cart = cartridge; 16 | 17 | this.prgOffsets[0] = this.prgBankOffset(0); 18 | this.prgOffsets[1] = this.prgBankOffset(1); 19 | this.prgOffsets[2] = this.prgBankOffset(-2); 20 | this.prgOffsets[3] = this.prgBankOffset(-1); 21 | } 22 | 23 | void step() { 24 | auto ppu = this.console.ppu; 25 | if (ppu.cycle != 280) { // TODO: this *should* be 260 26 | return; 27 | } 28 | if (ppu.scanLine > 239 && ppu.scanLine < 261) { 29 | return; 30 | } 31 | if (ppu.flagShowBackground == 0 && ppu.flagShowSprites == 0) { 32 | return; 33 | } 34 | this.handleScanLine(); 35 | } 36 | 37 | void handleScanLine() { 38 | if (this.counter == 0) { 39 | this.counter = this.reload; 40 | } else { 41 | this.counter--; 42 | if (this.counter == 0 && this.irqEnable) { 43 | this.console.cpu.addIrqSource(IrqSource.External); 44 | } 45 | } 46 | } 47 | 48 | ubyte read(ushort address) { 49 | if (address < 0x2000) { 50 | auto bank = address / 0x0400; 51 | auto offset = address % 0x0400; 52 | return this.cart.chr[this.chrOffsets[bank] + cast(int)offset]; 53 | } 54 | else if (address >= 0x8000) { 55 | address = cast(ushort)(address - 0x8000); 56 | auto bank = address / 0x2000; 57 | auto offset = address % 0x2000; 58 | return this.cart.prg[this.prgOffsets[bank] + cast(int)offset]; 59 | } 60 | else if (address >= 0x6000) { 61 | auto index = cast(int)address - 0x6000; 62 | return this.cart.sram[index]; 63 | } 64 | else { 65 | throw new MapperException(format("unhandled mapper4 read at address: 0x%04X", address)); 66 | } 67 | } 68 | 69 | void write(ushort address, ubyte value) { 70 | if (address < 0x2000) { 71 | auto bank = address / 0x0400; 72 | auto offset = address % 0x0400; 73 | this.cart.chr[this.chrOffsets[bank] + cast(int)offset] = value; 74 | } 75 | else if (address >= 0x8000) { 76 | this.writeRegister(address, value); 77 | } 78 | else if (address >= 0x6000) { 79 | auto index = cast(int)address - 0x6000; 80 | this.cart.sram[index] = value; 81 | } 82 | else { 83 | throw new MapperException(format("unhandled mapper4 write at address: 0x%04X", address)); 84 | } 85 | } 86 | 87 | void save(string[string] state) { 88 | state["mapper4.register"] = to!string(this.register); 89 | state["mapper4.registers"] = to!string(this.registers); 90 | state["mapper4.prgMode"] = to!string(this.prgMode); 91 | state["mapper4.chrMode"] = to!string(this.chrMode); 92 | state["mapper4.prgOffsets"] = to!string(this.prgOffsets); 93 | state["mapper4.chrOffsets"] = to!string(this.chrOffsets); 94 | state["mapper4.reload"] = to!string(this.reload); 95 | state["mapper4.counter"] = to!string(this.counter); 96 | state["mapper4.irqEnable"] = to!string(this.irqEnable); 97 | } 98 | 99 | void load(string[string] state) { 100 | this.register = to!ubyte(state["mapper4.register"]); 101 | this.registers = to!(ubyte[8])(state["mapper4.registers"]); 102 | this.prgMode = to!ubyte(state["mapper4.prgMode"]); 103 | this.chrMode = to!ubyte(state["mapper4.chrMode"]); 104 | this.prgOffsets = to!(int[4])(state["mapper4.prgOffsets"]); 105 | this.chrOffsets = to!(int[8])(state["mapper4.chrOffsets"]); 106 | this.reload = to!ubyte(state["mapper4.reload"]); 107 | this.counter = to!ubyte(state["mapper4.counter"]); 108 | this.irqEnable = to!bool(state["mapper4.irqEnable"]); 109 | } 110 | 111 | private: 112 | Cartridge cart; 113 | Console console; 114 | ubyte register; 115 | ubyte[8] registers; 116 | ubyte prgMode; 117 | ubyte chrMode; 118 | int[4] prgOffsets; 119 | int[8] chrOffsets; 120 | ubyte reload; 121 | ubyte counter; 122 | bool irqEnable; 123 | 124 | void writeRegister(ushort address, ubyte value) { 125 | if (address <= 0x9FFF && (address % 2) == 0) 126 | this.writeBankSelect(value); 127 | else if (address <= 0x9FFF && (address % 2) == 1) 128 | this.writeBankData(value); 129 | else if (address <= 0xBFFF && (address % 2) == 0) 130 | this.writeMirror(value); 131 | else if (address <= 0xBFFF && (address % 2) == 1) 132 | this.writeProtect(value); 133 | else if (address <= 0xDFFF && (address % 2) == 0) 134 | this.writeIRQLatch(value); 135 | else if (address <= 0xDFFF && (address % 2) == 1) 136 | this.writeIRQReload(value); 137 | else if (address <= 0xFFFF && (address % 2) == 0) 138 | this.writeIRQDisable(value); 139 | else if (address <= 0xFFFF && (address % 2) == 1) 140 | this.writeIRQEnable(value); 141 | } 142 | 143 | void writeBankSelect(ubyte value) { 144 | this.prgMode = (value >> 6) & 1; 145 | this.chrMode = (value >> 7) & 1; 146 | this.register = value & 7; 147 | this.updateOffsets(); 148 | } 149 | 150 | void writeBankData(ubyte value) { 151 | this.registers[this.register] = value; 152 | this.updateOffsets(); 153 | } 154 | 155 | void writeMirror(ubyte value) { 156 | switch (value & 1) { 157 | case 0: 158 | this.cart.mirror = MirrorVertical; 159 | break; 160 | case 1: 161 | this.cart.mirror = MirrorHorizontal; 162 | break; 163 | default: 164 | break; 165 | } 166 | } 167 | 168 | void writeProtect(ubyte value) { 169 | } 170 | 171 | void writeIRQLatch(ubyte value) { 172 | this.reload = value; 173 | } 174 | 175 | void writeIRQReload(ubyte value) { 176 | this.counter = 0; 177 | } 178 | 179 | void writeIRQDisable(ubyte value) { 180 | this.irqEnable = false; 181 | this.console.cpu.clearIrqSource(IrqSource.External); 182 | } 183 | 184 | void writeIRQEnable(ubyte value) { 185 | this.irqEnable = true; 186 | } 187 | 188 | int prgBankOffset(int index) { 189 | if (index >= 0x80) { 190 | index -= 0x100; 191 | } 192 | index %= cast(int)(this.cart.prg.length / 0x2000); 193 | int offset = index * 0x2000; 194 | if (offset < 0) { 195 | offset += this.cart.prg.length; 196 | } 197 | return offset; 198 | } 199 | 200 | int chrBankOffset(int index) { 201 | if (index >= 0x80) { 202 | index -= 0x100; 203 | } 204 | index %= cast(int)(this.cart.chr.length / 0x0400); 205 | int offset = index * 0x0400; 206 | if (offset < 0) { 207 | offset += this.cart.chr.length; 208 | } 209 | return offset; 210 | } 211 | 212 | void updateOffsets() { 213 | switch (this.prgMode) { 214 | case 0: 215 | this.prgOffsets[0] = this.prgBankOffset(cast(int)this.registers[6]); 216 | this.prgOffsets[1] = this.prgBankOffset(cast(int)this.registers[7]); 217 | this.prgOffsets[2] = this.prgBankOffset(-2); 218 | this.prgOffsets[3] = this.prgBankOffset(-1); 219 | break; 220 | case 1: 221 | this.prgOffsets[0] = this.prgBankOffset(-2); 222 | this.prgOffsets[1] = this.prgBankOffset(cast(int)this.registers[7]); 223 | this.prgOffsets[2] = this.prgBankOffset(cast(int)this.registers[6]); 224 | this.prgOffsets[3] = this.prgBankOffset(-1); 225 | break; 226 | default: 227 | break; 228 | } 229 | switch (this.chrMode) { 230 | case 0: 231 | this.chrOffsets[0] = this.chrBankOffset(cast(int)(this.registers[0] & 0xFE)); 232 | this.chrOffsets[1] = this.chrBankOffset(cast(int)(this.registers[0] | 0x01)); 233 | this.chrOffsets[2] = this.chrBankOffset(cast(int)(this.registers[1] & 0xFE)); 234 | this.chrOffsets[3] = this.chrBankOffset(cast(int)(this.registers[1] | 0x01)); 235 | this.chrOffsets[4] = this.chrBankOffset(cast(int)this.registers[2]); 236 | this.chrOffsets[5] = this.chrBankOffset(cast(int)this.registers[3]); 237 | this.chrOffsets[6] = this.chrBankOffset(cast(int)this.registers[4]); 238 | this.chrOffsets[7] = this.chrBankOffset(cast(int)this.registers[5]); 239 | break; 240 | case 1: 241 | this.chrOffsets[0] = this.chrBankOffset(cast(int)this.registers[2]); 242 | this.chrOffsets[1] = this.chrBankOffset(cast(int)this.registers[3]); 243 | this.chrOffsets[2] = this.chrBankOffset(cast(int)this.registers[4]); 244 | this.chrOffsets[3] = this.chrBankOffset(cast(int)this.registers[5]); 245 | this.chrOffsets[4] = this.chrBankOffset(cast(int)(this.registers[0] & 0xFE)); 246 | this.chrOffsets[5] = this.chrBankOffset(cast(int)(this.registers[0] | 0x01)); 247 | this.chrOffsets[6] = this.chrBankOffset(cast(int)(this.registers[1] & 0xFE)); 248 | this.chrOffsets[7] = this.chrBankOffset(cast(int)(this.registers[1] | 0x01)); 249 | break; 250 | default: 251 | break; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /source/nes/mapper7.d: -------------------------------------------------------------------------------- 1 | module nes.mapper7; 2 | 3 | import std.conv; 4 | import std.format; 5 | 6 | import nes.cartridge; 7 | import nes.mapper; 8 | import nes.memory; 9 | 10 | class Mapper7 : Mapper { 11 | this(Cartridge cartridge) { 12 | this.cart = cartridge; 13 | this.prgBank = 0; 14 | } 15 | 16 | void step() { 17 | } 18 | 19 | ubyte read(ushort address) { 20 | if (address < 0x2000) { 21 | return this.cart.chr[address]; 22 | } 23 | else if (address >= 0x8000) { 24 | auto index = this.prgBank * 0x8000 + cast(int)(address - 0x8000); 25 | return this.cart.prg[index]; 26 | } 27 | else if (address >= 0x6000) { 28 | auto index = cast(int)address - 0x6000; 29 | return this.cart.sram[index]; 30 | } 31 | else { 32 | throw new MapperException(format("unhandled mapper7 read at address: 0x%04X", address)); 33 | } 34 | } 35 | 36 | void write(ushort address, ubyte value) { 37 | if (address < 0x2000) { 38 | this.cart.chr[address] = value; 39 | } 40 | else if (address >= 0x8000) { 41 | this.prgBank = cast(int)(value & 7); 42 | switch (value & 0x10) { 43 | case 0x00: 44 | this.cart.mirror = MirrorSingle0; 45 | break; 46 | case 0x10: 47 | this.cart.mirror = MirrorSingle1; 48 | break; 49 | default: 50 | break; 51 | } 52 | } 53 | else if (address >= 0x6000) { 54 | auto index = cast(int)address - 0x6000; 55 | this.cart.sram[index] = value; 56 | } 57 | else { 58 | throw new MapperException(format("unhandled mapper7 write at address: 0x%04X", address)); 59 | } 60 | } 61 | 62 | void save(string[string] state) { 63 | state["mapper7.prgBank"] = to!string(this.prgBank); 64 | } 65 | 66 | void load(string[string] state) { 67 | this.prgBank = to!int(state["mapper7.prgBank"]); 68 | } 69 | 70 | private: 71 | Cartridge cart; 72 | int prgBank; 73 | } 74 | -------------------------------------------------------------------------------- /source/nes/memory.d: -------------------------------------------------------------------------------- 1 | module nes.memory; 2 | 3 | import std.experimental.logger; 4 | import std.format; 5 | import std.stdio; 6 | 7 | import nes.console; 8 | 9 | interface Memory { 10 | ubyte read(ushort address); 11 | void write(ushort address, ubyte value); 12 | } 13 | 14 | class MemoryException : Exception 15 | { 16 | import std.exception : basicExceptionCtors; 17 | 18 | mixin basicExceptionCtors; 19 | } 20 | 21 | class CPUMemory : Memory { 22 | this(Console console) { 23 | this.console = console; 24 | } 25 | 26 | ubyte read(ushort address) { 27 | if (address < 0x2000) { 28 | return this.console.ram[address % 0x0800]; 29 | } 30 | else if (address < 0x4000) { 31 | return this.console.ppu.readRegister(0x2000 + address % 8); 32 | } 33 | else if (address == 0x4014) { 34 | return this.console.ppu.readRegister(address); 35 | } 36 | else if (address == 0x4015) { 37 | return this.console.apu.readRegister(address); 38 | } 39 | else if (address == 0x4016) { 40 | return this.console.controller1.read(); 41 | } 42 | else if (address == 0x4017) { 43 | return this.console.controller2.read(); 44 | } 45 | else if (address < 0x6000) { 46 | // TODO: I/O registers 47 | } 48 | else if (address >= 0x6000) { 49 | return this.console.mapper.read(address); 50 | } 51 | else { 52 | throw new MemoryException(format("unhandled cpu memory read at address: 0x%04X", address)); 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | void write(ushort address, ubyte value) { 59 | if (address < 0x2000) { 60 | this.console.ram[address % 0x0800] = value; 61 | } 62 | else if (address < 0x4000) { 63 | this.console.ppu.writeRegister(0x2000 + address % 8, value); 64 | } 65 | else if (address < 0x4014) { 66 | this.console.apu.writeRegister(address, value); 67 | } 68 | else if (address == 0x4014) { 69 | this.console.ppu.writeRegister(address, value); 70 | } 71 | else if (address == 0x4015) { 72 | this.console.apu.writeRegister(address, value); 73 | } 74 | else if (address == 0x4016) { 75 | this.console.controller1.write(value); 76 | this.console.controller2.write(value); 77 | } 78 | else if (address == 0x4017) { 79 | this.console.apu.writeRegister(address, value); 80 | } 81 | else if (address < 0x6000) { 82 | // TODO: I/O registers 83 | } 84 | else if (address >= 0x6000) { 85 | this.console.mapper.write(address, value); 86 | } 87 | else { 88 | throw new MemoryException(format("unhandled cpu memory write at address: 0x%04X", address)); 89 | } 90 | } 91 | 92 | package Console console; 93 | } 94 | 95 | class PPUMemory : Memory { 96 | this(Console console) { 97 | this.console = console; 98 | } 99 | 100 | ubyte read(ushort address) { 101 | address = address % 0x4000; 102 | 103 | if (address < 0x2000) { 104 | return this.console.mapper.read(address); 105 | } 106 | else if (address < 0x3F00) { 107 | auto mode = this.console.cartridge.mirror; 108 | return this.console.ppu.nameTableData[MirrorAddress(mode, address) % 2048]; 109 | } 110 | else if (address < 0x4000) { 111 | return this.console.ppu.readPalette(address % 32); 112 | } 113 | else { 114 | throw new MemoryException(format("unhandled ppu memory read at address: 0x%04X", address)); 115 | } 116 | } 117 | 118 | void write(ushort address, ubyte value) { 119 | address = address % 0x4000; 120 | 121 | if (address < 0x2000) { 122 | this.console.mapper.write(address, value); 123 | } 124 | else if (address < 0x3F00) { 125 | auto mode = this.console.cartridge.mirror; 126 | this.console.ppu.nameTableData[MirrorAddress(mode, address) % 2048] = value; 127 | } 128 | else if (address < 0x4000) { 129 | this.console.ppu.writePalette(address % 32, value); 130 | } 131 | else { 132 | throw new MemoryException(format("unhandled ppu memory write at address: 0x%04X", address)); 133 | } 134 | } 135 | 136 | private Console console; 137 | } 138 | 139 | // Mirroring Modes 140 | 141 | enum { 142 | MirrorHorizontal = 0, 143 | MirrorVertical = 1, 144 | MirrorSingle0 = 2, 145 | MirrorSingle1 = 3, 146 | MirrorFour = 4 147 | } 148 | 149 | immutable ushort[4][] MirrorLookup = [ 150 | [0, 0, 1, 1], 151 | [0, 1, 0, 1], 152 | [0, 0, 0, 0], 153 | [1, 1, 1, 1], 154 | [0, 1, 2, 3] 155 | ]; 156 | 157 | ushort MirrorAddress(ubyte mode, ushort address) { 158 | address = cast(ushort)(address - 0x2000) % 0x1000; 159 | auto table = address / 0x0400; 160 | auto offset = address % 0x0400; 161 | 162 | return cast(ushort)(0x2000 + MirrorLookup[mode][table] * 0x0400 + offset); 163 | } 164 | -------------------------------------------------------------------------------- /source/nes/palette.d: -------------------------------------------------------------------------------- 1 | module nes.palette; 2 | 3 | import nes.color; 4 | 5 | RGBA[64] Palette = 6 | () { 7 | RGBA[64] result; 8 | uint[] colors = [ 9 | 0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 0x6E0040, 0x6C0600, 0x561D00, 10 | 0x333500, 0x0B4800, 0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000, 0x000000, 11 | 0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00, 12 | 0x6B6D00, 0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000, 0x000000, 0x000000, 13 | 0xFFFEFF, 0x64B0FF, 0x9290FF, 0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22, 14 | 0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 0x4F4F4F, 0x000000, 0x000000, 15 | 0xFFFEFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5, 0xF7D8A5, 16 | 0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000 17 | ]; 18 | foreach (i, c; colors) { 19 | auto r = cast(ubyte)(c >> 16); 20 | auto g = cast(ubyte)(c >> 8); 21 | auto b = cast(ubyte)(c); 22 | result[i] = RGBA(r, g, b, 0xFF); 23 | } 24 | return result; 25 | }(); 26 | -------------------------------------------------------------------------------- /source/nes/ppu.d: -------------------------------------------------------------------------------- 1 | module nes.ppu; 2 | 3 | import std.algorithm; 4 | import std.conv; 5 | import std.stdio; 6 | 7 | import nes.console; 8 | import nes.image; 9 | import nes.memory; 10 | import nes.palette; 11 | 12 | struct SpritePixel { 13 | ubyte index; 14 | ubyte color; 15 | } 16 | 17 | class PPU : PPUMemory { 18 | this(Console console) { 19 | super(console); 20 | this.console = console; 21 | this.front = new ImageRGBA(Rect(0, 0, 256, 240)); 22 | this.back = new ImageRGBA(Rect(0, 0, 256, 240)); 23 | this.reset(); 24 | } 25 | 26 | void reset() { 27 | this.cycle = 340; 28 | this.scanLine = 261; 29 | this.frame = 0; 30 | 31 | this.renderingEnabled = false; 32 | this.prevRenderingEnabled = false; 33 | this.renderingEnabledUpdated = false; 34 | 35 | // NMI flags 36 | this.nmiOccurred = false; 37 | this.nmiOutput = false; 38 | this.nmiPrevious = false; 39 | 40 | // PPU registers 41 | this.v = 0; // current vram address (15 bit) 42 | this.t = 0; // temporary vram address (15 bit) 43 | this.x = 0; // fine x scroll (3 bit) 44 | this.w = 0; // write toggle (1 bit) 45 | this.f = 1; // even/odd frame flag (1 bit) 46 | 47 | this.vWriteDelay = 0; 48 | this.vWriteValue = 0; 49 | this.vReadIgnoreCounter = 0; 50 | 51 | this.writeControl(0); // $2000 PPUCTRL 52 | this.writeMask(0); // $2001 PPUMASK 53 | this.writeOAMAddress(0); // $2003: OAMADDR 54 | 55 | this.register = 0; 56 | 57 | // background temporary variables 58 | this.nameTableByte = 0; 59 | this.attributeTableByte = 0; 60 | this.lowTileByte = 0; 61 | this.highTileByte = 0; 62 | this.tileData = 0; 63 | 64 | this.tileAddress = 0; 65 | 66 | // sprite temporary variables 67 | this.spriteCount = 0; 68 | this.spritePatterns = [0, 0, 0, 0, 0, 0, 0, 0]; 69 | this.spritePositions = [0, 0, 0, 0, 0, 0, 0, 0]; 70 | this.spritePriorities = [0, 0, 0, 0, 0, 0, 0, 0]; 71 | this.spriteIndexes = [0, 0, 0, 0, 0, 0, 0, 0]; 72 | 73 | // $2002 PPUSTATUS 74 | this.flagSpriteZeroHit = 0; 75 | this.flagSpriteOverflow = 0; 76 | 77 | // $2007 PPUDATA 78 | this.bufferedData = 0; // for buffered reads 79 | 80 | foreach (i; nameTableData) nameTableData[i] = 0; 81 | paletteData = [0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 0x08, 82 | 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 0x09, 0x01, 83 | 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 0x08, 0x3A, 0x00, 84 | 0x02, 0x00, 0x20, 0x2C, 0x08]; 85 | foreach (i; oamData) oamData[i] = 0; 86 | foreach (i; front.pix) front.pix[i] = 0; 87 | foreach (i; back.pix) back.pix[i] = 0; 88 | 89 | foreach (_; 0 .. 28) 90 | this.step(); 91 | } 92 | 93 | // Step executes a single PPU cycle 94 | void step() { 95 | this.tick(); 96 | 97 | auto preLine = this.scanLine == 261; 98 | auto visibleLine = this.scanLine < 240; 99 | auto renderLine = preLine || visibleLine; 100 | auto preFetchCycle = this.cycle >= 321 && this.cycle <= 336; 101 | auto visibleCycle = this.cycle >= 1 && this.cycle <= 256; 102 | auto fetchCycle = preFetchCycle || visibleCycle; 103 | 104 | // background logic 105 | if (this.renderingEnabled) { 106 | if (visibleLine && visibleCycle) { 107 | this.renderPixel(); 108 | } 109 | if (renderLine && fetchCycle) { 110 | this.tileData <<= 4; 111 | switch ((this.cycle - 1) & 0x07) { 112 | case 0: 113 | this.fetchNameTableByte(); 114 | 115 | ushort fineY = (this.v >> 12) & 7; 116 | auto table = this.flagBackgroundTable; 117 | auto tile = this.nameTableByte; 118 | this.tileAddress = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + fineY); 119 | break; 120 | case 2: 121 | this.fetchAttributeTableByte(); 122 | break; 123 | case 3: 124 | this.fetchLowTileByte(); 125 | break; 126 | case 5: 127 | this.fetchHighTileByte(); 128 | break; 129 | case 7: 130 | this.storeTileData(); 131 | break; 132 | default: 133 | break; 134 | } 135 | } 136 | if (preLine && this.cycle >= 280 && this.cycle <= 304) { 137 | this.copyY(); 138 | } 139 | if (renderLine) { 140 | if (fetchCycle && this.cycle % 8 == 0) { 141 | this.incrementX(); 142 | } 143 | if (this.cycle == 256) { 144 | this.incrementY(); 145 | } 146 | if (this.cycle == 257) { 147 | this.copyX(); 148 | } 149 | } 150 | } 151 | 152 | // sprite logic 153 | if (this.renderingEnabled) { 154 | if (this.cycle == 257) { 155 | if (visibleLine) { 156 | this.evaluateSprites(); 157 | } else { 158 | this.spriteCount = 0; 159 | } 160 | } 161 | } 162 | 163 | // vblank logic 164 | 165 | if (this.renderingEnabledUpdated) { 166 | this.updateRenderingEnabled(); 167 | } 168 | 169 | if (this.vWriteDelay > 0) { 170 | this.vWriteDelay--; 171 | if (this.vWriteDelay == 0) { 172 | this.v = this.vWriteValue; 173 | // vram read should happen here 174 | } 175 | } 176 | 177 | if (this.vReadIgnoreCounter > 0) 178 | this.vReadIgnoreCounter--; 179 | } 180 | 181 | ubyte readRegister(ushort address) { 182 | switch (address) { 183 | case 0x2002: 184 | return this.readStatus(); 185 | case 0x2004: 186 | return this.readOAMData(); 187 | case 0x2007: 188 | return this.readData(); 189 | default: 190 | break; 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | void writeRegister(ushort address, ubyte value) { 197 | this.register = value; 198 | 199 | switch (address) { 200 | case 0x2000: 201 | this.writeControl(value); 202 | break; 203 | case 0x2001: 204 | this.writeMask(value); 205 | break; 206 | case 0x2003: 207 | this.writeOAMAddress(value); 208 | break; 209 | case 0x2004: 210 | this.writeOAMData(value); 211 | break; 212 | case 0x2005: 213 | this.writeScroll(value); 214 | break; 215 | case 0x2006: 216 | this.writeAddress(value); 217 | break; 218 | case 0x2007: 219 | this.writeData(value); 220 | break; 221 | case 0x4014: 222 | this.writeDMA(value); 223 | break; 224 | default: 225 | break; 226 | } 227 | } 228 | 229 | ubyte readPalette(ushort address) { 230 | if (address >= 16 && address % 4 == 0) { 231 | address -= 16; 232 | } 233 | 234 | if (this.flagGrayscale) return this.paletteData[address] & 0x30; 235 | 236 | return this.paletteData[address]; 237 | } 238 | 239 | void writePalette(ushort address, ubyte value) { 240 | if (address >= 16 && address % 4 == 0) { 241 | address -= 16; 242 | } 243 | 244 | this.paletteData[address] = value; 245 | } 246 | 247 | void save(string[string] state) { 248 | state["ppu.cycle"] = to!string(this.cycle); 249 | state["ppu.scanLine"] = to!string(this.scanLine); 250 | state["ppu.frame"] = to!string(this.frame); 251 | state["ppu.paletteData"] = to!string(this.paletteData); 252 | state["ppu.nameTableData"] = to!string(this.nameTableData); 253 | state["ppu.oamData"] = to!string(this.oamData); 254 | state["ppu.v"] = to!string(this.v); 255 | state["ppu.t"] = to!string(this.t); 256 | state["ppu.x"] = to!string(this.x); 257 | state["ppu.w"] = to!string(this.w); 258 | state["ppu.f"] = to!string(this.f); 259 | state["ppu.register"] = to!string(this.register); 260 | state["ppu.nmiOccurred"] = to!string(this.nmiOccurred); 261 | state["ppu.nmiOutput"] = to!string(this.nmiOutput); 262 | state["ppu.nmiPrevious"] = to!string(this.nmiPrevious); 263 | state["ppu.nameTableByte"] = to!string(this.nameTableByte); 264 | state["ppu.attributeTableByte"] = to!string(this.attributeTableByte); 265 | state["ppu.lowTileByte"] = to!string(this.lowTileByte); 266 | state["ppu.highTileByte"] = to!string(this.highTileByte); 267 | state["ppu.tileData"] = to!string(this.tileData); 268 | state["ppu.spriteCount"] = to!string(this.spriteCount); 269 | state["ppu.spritePatterns"] = to!string(this.spritePatterns); 270 | state["ppu.spritePositions"] = to!string(this.spritePositions); 271 | state["ppu.spritePriorities"] = to!string(this.spritePriorities); 272 | state["ppu.spriteIndexes"] = to!string(this.spriteIndexes); 273 | state["ppu.flagNameTable"] = to!string(this.flagNameTable); 274 | state["ppu.flagIncrement"] = to!string(this.flagIncrement); 275 | state["ppu.flagSpriteTable"] = to!string(this.flagSpriteTable); 276 | state["ppu.flagBackgroundTable"] = to!string(this.flagBackgroundTable); 277 | state["ppu.flagSpriteSize"] = to!string(this.flagSpriteSize); 278 | state["ppu.flagMasterSlave"] = to!string(this.flagMasterSlave); 279 | state["ppu.flagGrayscale"] = to!string(this.flagGrayscale); 280 | state["ppu.flagShowLeftBackground"] = to!string(this.flagShowLeftBackground); 281 | state["ppu.flagShowLeftSprites"] = to!string(this.flagShowLeftSprites); 282 | state["ppu.flagShowBackground"] = to!string(this.flagShowBackground); 283 | state["ppu.flagShowSprites"] = to!string(this.flagShowSprites); 284 | state["ppu.flagRedTint"] = to!string(this.flagRedTint); 285 | state["ppu.flagGreenTint"] = to!string(this.flagGreenTint); 286 | state["ppu.flagBlueTint"] = to!string(this.flagBlueTint); 287 | state["ppu.flagSpriteZeroHit"] = to!string(this.flagSpriteZeroHit); 288 | state["ppu.flagSpriteOverflow"] = to!string(this.flagSpriteOverflow); 289 | state["ppu.oamAddress"] = to!string(this.oamAddress); 290 | state["ppu.bufferedData"] = to!string(this.bufferedData); 291 | state["ppu.renderingEnabled"] = to!string(this.renderingEnabled); 292 | state["ppu.prevRenderingEnabled"] = to!string(this.prevRenderingEnabled); 293 | state["ppu.renderingEnabledUpdated"] = to!string(this.renderingEnabledUpdated); 294 | 295 | state["ppu.vWriteValue"] = to!string(this.vWriteValue); 296 | state["ppu.vWriteDelay"] = to!string(this.vWriteDelay); 297 | state["ppu.vReadIgnoreCounter"] = to!string(this.vReadIgnoreCounter); 298 | state["ppu.tileAddress"] = to!string(this.tileAddress); 299 | } 300 | 301 | void load(string[string] state) { 302 | this.cycle = to!int(state["ppu.cycle"]); 303 | this.scanLine = to!int(state["ppu.scanLine"]); 304 | this.frame = to!ulong(state["ppu.frame"]); 305 | this.paletteData = to!(ubyte[32])(state["ppu.paletteData"]); 306 | this.nameTableData = to!(ubyte[2048])(state["ppu.nameTableData"]); 307 | this.oamData = to!(ubyte[256])(state["ppu.oamData"]); 308 | this.v = to!ushort(state["ppu.v"]); 309 | this.t = to!ushort(state["ppu.t"]); 310 | this.x = to!ubyte(state["ppu.x"]); 311 | this.w = to!ubyte(state["ppu.w"]); 312 | this.f = to!ubyte(state["ppu.f"]); 313 | this.register = to!ubyte(state["ppu.register"]); 314 | this.nmiOccurred = to!bool(state["ppu.nmiOccurred"]); 315 | this.nmiOutput = to!bool(state["ppu.nmiOutput"]); 316 | this.nmiPrevious = to!bool(state["ppu.nmiPrevious"]); 317 | this.nameTableByte = to!ubyte(state["ppu.nameTableByte"]); 318 | this.attributeTableByte = to!ubyte(state["ppu.attributeTableByte"]); 319 | this.lowTileByte = to!ubyte(state["ppu.lowTileByte"]); 320 | this.highTileByte = to!ubyte(state["ppu.highTileByte"]); 321 | this.tileData = to!ulong(state["ppu.tileData"]); 322 | this.spriteCount = to!int(state["ppu.spriteCount"]); 323 | this.spritePatterns = to!(uint[8])(state["ppu.spritePatterns"]); 324 | this.spritePositions = to!(ubyte[8])(state["ppu.spritePositions"]); 325 | this.spritePriorities = to!(ubyte[8])(state["ppu.spritePriorities"]); 326 | this.spriteIndexes = to!(ubyte[8])(state["ppu.spriteIndexes"]); 327 | this.flagNameTable = to!ubyte(state["ppu.flagNameTable"]); 328 | this.flagIncrement = to!ubyte(state["ppu.flagIncrement"]); 329 | this.flagSpriteTable = to!ubyte(state["ppu.flagSpriteTable"]); 330 | this.flagBackgroundTable = to!ubyte(state["ppu.flagBackgroundTable"]); 331 | this.flagSpriteSize = to!ubyte(state["ppu.flagSpriteSize"]); 332 | this.flagMasterSlave = to!ubyte(state["ppu.flagMasterSlave"]); 333 | this.flagGrayscale = to!ubyte(state["ppu.flagGrayscale"]); 334 | this.flagShowLeftBackground = to!ubyte(state["ppu.flagShowLeftBackground"]); 335 | this.flagShowLeftSprites = to!ubyte(state["ppu.flagShowLeftSprites"]); 336 | this.flagShowBackground = to!ubyte(state["ppu.flagShowBackground"]); 337 | this.flagShowSprites = to!ubyte(state["ppu.flagShowSprites"]); 338 | this.flagRedTint = to!ubyte(state["ppu.flagRedTint"]); 339 | this.flagGreenTint = to!ubyte(state["ppu.flagGreenTint"]); 340 | this.flagBlueTint = to!ubyte(state["ppu.flagBlueTint"]); 341 | this.flagSpriteZeroHit = to!ubyte(state["ppu.flagSpriteZeroHit"]); 342 | this.flagSpriteOverflow = to!ubyte(state["ppu.flagSpriteOverflow"]); 343 | this.oamAddress = to!ubyte(state["ppu.oamAddress"]); 344 | this.bufferedData = to!ubyte(state["ppu.bufferedData"]); 345 | this.renderingEnabled = to!bool(state["ppu.renderingEnabled"]); 346 | this.prevRenderingEnabled = to!bool(state["ppu.prevRenderingEnabled"]); 347 | this.renderingEnabledUpdated = to!bool(state["ppu.renderingEnabledUpdated"]); 348 | 349 | this.vWriteValue = to!ushort(state["ppu.vWriteValue"]); 350 | this.vWriteDelay = to!ubyte(state["ppu.vWriteDelay"]); 351 | this.vReadIgnoreCounter = to!ubyte(state["ppu.vReadIgnoreCounter"]); 352 | this.tileAddress = to!ushort(state["ppu.tileAddress"]); 353 | } 354 | 355 | package: 356 | Console console; 357 | ubyte[2048] nameTableData; 358 | ImageRGBA front; 359 | ImageRGBA back; 360 | 361 | int cycle; // 0-340 362 | int scanLine; // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre 363 | ulong frame; // frame counter 364 | 365 | bool renderingEnabled; 366 | bool prevRenderingEnabled; 367 | bool renderingEnabledUpdated; 368 | 369 | // storage variables 370 | ubyte[32] paletteData; 371 | ubyte[256] oamData; 372 | 373 | // PPU registers 374 | ushort v; // current vram address (15 bit) 375 | ushort t; // temporary vram address (15 bit) 376 | ubyte x; // fine x scroll (3 bit) 377 | ubyte w; // write toggle (1 bit) 378 | ubyte f; // even/odd frame flag (1 bit) 379 | 380 | ushort vWriteValue; 381 | ubyte vWriteDelay; 382 | ubyte vReadIgnoreCounter; 383 | 384 | ubyte register; 385 | 386 | // NMI flags 387 | bool nmiOccurred; 388 | bool nmiOutput; 389 | bool nmiPrevious; 390 | 391 | // background temporary variables 392 | ubyte nameTableByte; 393 | ubyte attributeTableByte; 394 | ubyte lowTileByte; 395 | ubyte highTileByte; 396 | ulong tileData; 397 | 398 | ushort tileAddress; 399 | 400 | // sprite temporary variables 401 | int spriteCount; 402 | uint[8] spritePatterns; 403 | ubyte[8] spritePositions; 404 | ubyte[8] spritePriorities; 405 | ubyte[8] spriteIndexes; 406 | 407 | // $2000 PPUCTRL 408 | ubyte flagNameTable; // 0: $2000; 1: $2400; 2: $2800; 3: $2C00 409 | ubyte flagIncrement; // 0: add 1; 1: add 32 410 | ubyte flagSpriteTable; // 0: $0000; 1: $1000; ignored in 8x16 mode 411 | ubyte flagBackgroundTable; // 0: $0000; 1: $1000 412 | ubyte flagSpriteSize; // 0: 8x8; 1: 8x16 413 | ubyte flagMasterSlave; // 0: read EXT; 1: write EXT 414 | 415 | // $2001 PPUMASK 416 | ubyte flagShowBackground; // 0: hide; 1: show 417 | ubyte flagShowSprites; // 0: hide; 1: show 418 | ubyte flagGrayscale; // 0: color; 1: grayscale 419 | ubyte flagShowLeftBackground; // 0: hide; 1: show 420 | ubyte flagShowLeftSprites; // 0: hide; 1: show 421 | ubyte flagRedTint; // 0: normal; 1: emphasized 422 | ubyte flagGreenTint; // 0: normal; 1: emphasized 423 | ubyte flagBlueTint; // 0: normal; 1: emphasized 424 | 425 | // $2002 PPUSTATUS 426 | ubyte flagSpriteZeroHit; 427 | ubyte flagSpriteOverflow; 428 | 429 | // $2003 OAMADDR 430 | ubyte oamAddress; 431 | 432 | // $2007 PPUDATA 433 | ubyte bufferedData; // for buffered reads 434 | 435 | private: 436 | // $2000: PPUCTRL 437 | void writeControl(ubyte value) { 438 | auto orgFlagIncrement = this.flagIncrement; 439 | auto orgFlagBackgroundTable = this.flagBackgroundTable; 440 | 441 | this.flagNameTable = (value >> 0) & 3; 442 | this.flagIncrement = (value >> 2) & 1; 443 | this.flagSpriteTable = (value >> 3) & 1; 444 | this.flagBackgroundTable = (value >> 4) & 1; 445 | this.flagSpriteSize = (value >> 5) & 1; 446 | this.flagMasterSlave = (value >> 6) & 1; 447 | auto prevNmiOutput = this.nmiOutput; 448 | this.nmiOutput = ((value >> 7) & 1) == 1; 449 | this.nmiChange(); 450 | // t: ....BA.. ........ = d: ......BA 451 | this.t = (this.t & 0xF3FF) | ((cast(ushort)(value) & 0x03) << 10); 452 | 453 | // Too late to turn off NMI 454 | if (this.scanLine == 241 && this.cycle < 3 && !this.nmiOutput) { 455 | this.console.cpu.clearNmiFlag(); 456 | } 457 | 458 | // Too late to turn on NMI 459 | if (this.scanLine == 261 && this.cycle == 0 && this.nmiOutput) { 460 | this.console.cpu.clearNmiFlag(); 461 | } 462 | } 463 | 464 | // $2001: PPUMASK 465 | void writeMask(ubyte value) { 466 | this.flagGrayscale = (value >> 0) & 1; 467 | this.flagShowLeftBackground = (value >> 1) & 1; 468 | this.flagShowLeftSprites = (value >> 2) & 1; 469 | this.flagShowBackground = (value >> 3) & 1; 470 | this.flagShowSprites = (value >> 4) & 1; 471 | this.flagRedTint = (value >> 5) & 1; 472 | this.flagGreenTint = (value >> 6) & 1; 473 | this.flagBlueTint = (value >> 7) & 1; 474 | 475 | if (this.renderingEnabled != (this.flagShowBackground | this.flagShowSprites)) { 476 | this.renderingEnabledUpdated = true; 477 | } 478 | } 479 | 480 | // $2002: PPUSTATUS 481 | ubyte readStatus() { 482 | ubyte result = this.register & 0x1F; 483 | result |= this.flagSpriteOverflow << 5; 484 | result |= this.flagSpriteZeroHit << 6; 485 | if (this.nmiOccurred) { 486 | result |= (1 << 7); 487 | } 488 | 489 | if (this.scanLine == 241 && this.cycle < 3) { 490 | this.console.cpu.clearNmiFlag(); 491 | 492 | if (this.cycle == 0) { 493 | result = this.register & 0x1F; 494 | result |= this.flagSpriteOverflow << 5; 495 | result |= this.flagSpriteZeroHit << 6; 496 | } 497 | } 498 | 499 | this.nmiOccurred = false; 500 | this.nmiChange(); 501 | // w: = 0 502 | this.w = 0; 503 | 504 | return result; 505 | } 506 | 507 | // $2003: OAMADDR 508 | void writeOAMAddress(ubyte value) { 509 | this.oamAddress = value; 510 | } 511 | 512 | // $2004: OAMDATA (read) 513 | ubyte readOAMData() { 514 | return this.oamData[this.oamAddress]; 515 | } 516 | 517 | // $2004: OAMDATA (write) 518 | void writeOAMData(ubyte value) { 519 | this.oamData[this.oamAddress] = value; 520 | this.oamAddress++; 521 | } 522 | 523 | // $2005: PPUSCROLL 524 | void writeScroll(ubyte value) { 525 | if (this.w == 0) { 526 | // t: ........ ...HGFED = d: HGFED... 527 | // x: CBA = d: .....CBA 528 | // w: = 1 529 | this.t = (this.t & 0xFFE0) | (cast(ushort)value >> 3); 530 | this.x = value & 0x07; 531 | this.w = 1; 532 | } else { 533 | // t: .CBA..HG FED..... = d: HGFEDCBA 534 | // w: = 0 535 | this.t = (this.t & 0x8FFF) | ((cast(ushort)value & 0x07) << 12); 536 | this.t = (this.t & 0xFC1F) | ((cast(ushort)value & 0xF8) << 2); 537 | this.w = 0; 538 | } 539 | } 540 | 541 | // $2006: PPUADDR 542 | void writeAddress(ubyte value) { 543 | if (this.w == 0) { 544 | // t: ..FEDCBA ........ = d: ..FEDCBA 545 | // t: .X...... ........ = 0 546 | // w: = 1 547 | this.t = (this.t & 0x80FF) | ((cast(ushort)value & 0x3F) << 8); 548 | this.w = 1; 549 | } else { 550 | // t: ........ HGFEDCBA = d: HGFEDCBA 551 | // v = t 552 | // w: = 0 553 | this.t = (this.t & 0xFF00) | cast(ushort)value; 554 | this.vWriteDelay = 2; 555 | this.vWriteValue = this.t; 556 | this.w = 0; 557 | } 558 | } 559 | 560 | // $2007: PPUDATA (read) 561 | ubyte readData() { 562 | if (this.vReadIgnoreCounter == 0) { 563 | auto value = this.read(this.v); 564 | // emulate buffered reads 565 | if (this.v % 0x4000 < 0x3F00) { 566 | auto buffered = this.bufferedData; 567 | this.bufferedData = value; 568 | value = buffered; 569 | } else { 570 | this.bufferedData = this.read(cast(ushort)(this.v - 0x1000)); 571 | } 572 | 573 | // increment address 574 | if (this.scanLine >= 240 || !this.renderingEnabled) { 575 | if (this.flagIncrement == 0) { 576 | this.v += 1; 577 | } else { 578 | this.v += 32; 579 | } 580 | // should do a vram read here 581 | } else { 582 | this.incrementX(); 583 | this.incrementY(); 584 | } 585 | 586 | return value; 587 | } else { 588 | return 0; 589 | } 590 | } 591 | 592 | // $2007: PPUDATA (write) 593 | void writeData(ubyte value) { 594 | this.write(this.v, value); 595 | 596 | if (this.scanLine >= 240 || !this.renderingEnabled) { 597 | if (this.flagIncrement == 0) { 598 | this.v += 1; 599 | } else { 600 | this.v += 32; 601 | } 602 | } else { 603 | this.incrementX(); 604 | this.incrementY(); 605 | } 606 | } 607 | 608 | // $4014: OAMDMA 609 | void writeDMA(ubyte value) { 610 | this.console.cpu.spriteDmaTransfer(value); 611 | } 612 | 613 | // NTSC Timing Helper Functions 614 | 615 | void incrementX() { 616 | // increment hori(v) 617 | // if coarse X == 31 618 | if ((this.v & 0x001F) == 31) { 619 | // coarse X = 0 620 | this.v &= 0xFFE0; 621 | // switch horizontal nametable 622 | this.v ^= 0x0400; 623 | } else { 624 | // increment coarse X 625 | this.v++; 626 | } 627 | } 628 | 629 | void incrementY() { 630 | // increment vert(v) 631 | // if fine Y < 7 632 | if ((this.v & 0x7000) != 0x7000) { 633 | // increment fine Y 634 | this.v += 0x1000; 635 | } else { 636 | // fine Y = 0 637 | this.v &= 0x8FFF; 638 | 639 | // let y = coarse Y 640 | ushort y = (this.v & 0x03E0) >> 5; 641 | if (y == 29) { 642 | // coarse Y = 0 643 | y = 0; 644 | // switch vertical nametable 645 | this.v ^= 0x0800; 646 | } else if (y == 31) { 647 | // coarse Y = 0, nametable not switched 648 | y = 0; 649 | } else { 650 | // increment coarse Y 651 | y++; 652 | } 653 | // put coarse Y back into v 654 | this.v = cast(ushort)((this.v & 0xFC1F) | (y << 5)); 655 | } 656 | } 657 | 658 | void copyX() { 659 | // hori(v) = hori(t) 660 | // v: .....F.. ...EDCBA = t: .....F.. ...EDCBA 661 | this.v = (this.v & 0xFBE0) | (this.t & 0x041F); 662 | } 663 | 664 | void copyY() { 665 | // vert(v) = vert(t) 666 | // v: .IHGF.ED CBA..... = t: .IHGF.ED CBA..... 667 | this.v = (this.v & 0x841F) | (this.t & 0x7BE0); 668 | } 669 | 670 | void nmiChange() { 671 | auto nmi = this.nmiOutput && this.nmiOccurred; 672 | if (nmi && !this.nmiPrevious) { 673 | this.console.cpu.setNmiFlag(); 674 | } 675 | this.nmiPrevious = nmi; 676 | } 677 | 678 | void setVerticalBlank() { 679 | swap(this.front, this.back); 680 | this.nmiOccurred = true; 681 | this.nmiChange(); 682 | } 683 | 684 | void clearVerticalBlank() { 685 | this.nmiOccurred = false; 686 | this.nmiChange(); 687 | } 688 | 689 | void fetchNameTableByte() { 690 | auto v = this.v; 691 | ushort address = 0x2000 | (v & 0x0FFF); 692 | this.nameTableByte = this.read(address); 693 | } 694 | 695 | void fetchAttributeTableByte() { 696 | auto v = this.v; 697 | ushort address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07); 698 | ubyte shift = ((v >> 4) & 4) | (v & 2); 699 | this.attributeTableByte = ((this.read(address) >> shift) & 3) << 2; 700 | } 701 | 702 | void fetchLowTileByte() { 703 | this.lowTileByte = this.read(this.tileAddress); 704 | } 705 | 706 | void fetchHighTileByte() { 707 | this.highTileByte = this.read(cast(ushort)(this.tileAddress + 8)); 708 | } 709 | 710 | void storeTileData() { 711 | uint data; 712 | foreach (_; 0 .. 8) { 713 | auto a = this.attributeTableByte; 714 | ubyte p1 = (this.lowTileByte & 0x80) >> 7; 715 | ubyte p2 = (this.highTileByte & 0x80) >> 6; 716 | this.lowTileByte <<= 1; 717 | this.highTileByte <<= 1; 718 | data <<= 4; 719 | data |= cast(uint)(a | p1 | p2); 720 | } 721 | this.tileData |= cast(ulong)data; 722 | } 723 | 724 | uint fetchTileData() { 725 | return cast(uint)(this.tileData >> 32); 726 | } 727 | 728 | ubyte backgroundPixel() { 729 | if (this.flagShowBackground == 0) { 730 | return 0; 731 | } 732 | uint data = this.fetchTileData() >> ((7 - this.x) * 4); 733 | return cast(ubyte)(data & 0x0F); 734 | } 735 | 736 | SpritePixel spritePixel() { 737 | if (this.flagShowSprites == 0) { 738 | return SpritePixel(0, 0); 739 | } 740 | foreach (i; 0 .. this.spriteCount) { 741 | int offset = (this.cycle - 1) - cast(int)this.spritePositions[i]; 742 | if (offset < 0 || offset > 7) { 743 | continue; 744 | } 745 | offset = 7 - offset; 746 | ubyte color = cast(ubyte)((this.spritePatterns[i] >> cast(ubyte)(offset * 4)) & 0x0F); 747 | if (color % 4 == 0) { 748 | continue; 749 | } 750 | return SpritePixel(cast(ubyte)i, color); 751 | } 752 | return SpritePixel(0, 0); 753 | } 754 | 755 | void renderPixel() { 756 | auto x = this.cycle - 1; 757 | auto y = this.scanLine; 758 | 759 | auto background = this.backgroundPixel(); 760 | auto sp = this.spritePixel(); 761 | auto i = sp.index; 762 | auto sprite = sp.color; 763 | if (x < 8 && this.flagShowLeftBackground == 0) { 764 | background = 0; 765 | } 766 | if (x < 8 && this.flagShowLeftSprites == 0) { 767 | sprite = 0; 768 | } 769 | auto b = background % 4 != 0; 770 | auto s = sprite % 4 != 0; 771 | ubyte color; 772 | if (!b && !s) { 773 | color = 0; 774 | } else if (!b && s) { 775 | color = sprite | 0x10; 776 | } else if (b && !s) { 777 | color = background; 778 | } else { 779 | if (this.spriteIndexes[i] == 0 && x < 255) { 780 | this.flagSpriteZeroHit = 1; 781 | } 782 | if (this.spritePriorities[i] == 0) { 783 | color = sprite | 0x10; 784 | } else { 785 | color = background; 786 | } 787 | } 788 | auto c = Palette[this.readPalette(cast(ushort)color) % 64]; 789 | this.back.setRGBA(x, y, c); 790 | } 791 | 792 | uint fetchSpritePattern(int i, int row) { 793 | auto tile = this.oamData[i * 4 + 1]; 794 | auto attributes = this.oamData[i * 4 + 2]; 795 | ushort address; 796 | if (this.flagSpriteSize == 0) { 797 | if ((attributes & 0x80) == 0x80) { 798 | row = 7 - row; 799 | } 800 | auto table = this.flagSpriteTable; 801 | address = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + cast(ushort)row); 802 | } else { 803 | if ((attributes & 0x80) == 0x80) { 804 | row = 15 - row; 805 | } 806 | auto table = tile & 1; 807 | tile &= 0xFE; 808 | if (row > 7) { 809 | tile++; 810 | row -= 8; 811 | } 812 | address = cast(ushort)(0x1000 * cast(ushort)table + cast(ushort)tile * 16 + cast(ushort)row); 813 | } 814 | auto a = (attributes & 3) << 2; 815 | auto lowTileByte = this.read(address); 816 | auto highTileByte = this.read(cast(ushort)(address + 8)); 817 | uint data; 818 | foreach (_; 0 .. 8) { 819 | ubyte p1, p2; 820 | if ((attributes & 0x40) == 0x40) { 821 | p1 = (lowTileByte & 1) << 0; 822 | p2 = (highTileByte & 1) << 1; 823 | lowTileByte >>= 1; 824 | highTileByte >>= 1; 825 | } else { 826 | p1 = (lowTileByte & 0x80) >> 7; 827 | p2 = (highTileByte & 0x80) >> 6; 828 | lowTileByte <<= 1; 829 | highTileByte <<= 1; 830 | } 831 | data <<= 4; 832 | data |= cast(uint)(a | p1 | p2); 833 | } 834 | return data; 835 | } 836 | 837 | void evaluateSprites() { 838 | int h; 839 | if (this.flagSpriteSize == 0) { 840 | h = 8; 841 | } else { 842 | h = 16; 843 | } 844 | auto count = 0; 845 | foreach (i; 0 .. 64) { 846 | auto y = this.oamData[i*4+0]; 847 | auto a = this.oamData[i*4+2]; 848 | auto x = this.oamData[i*4+3]; 849 | auto row = this.scanLine - cast(int)y; 850 | if (row < 0 || row >= h) { 851 | continue; 852 | } 853 | if (count < 8) { 854 | this.spritePatterns[count] = this.fetchSpritePattern(i, row); 855 | this.spritePositions[count] = x; 856 | this.spritePriorities[count] = (a >> 5) & 1; 857 | this.spriteIndexes[count] = cast(ubyte)i; 858 | } 859 | count++; 860 | } 861 | if (count > 8) { 862 | count = 8; 863 | this.flagSpriteOverflow = 1; 864 | } 865 | this.spriteCount = count; 866 | } 867 | 868 | void updateRenderingEnabled() { 869 | this.renderingEnabledUpdated = false; 870 | 871 | this.prevRenderingEnabled = this.renderingEnabled; 872 | 873 | this.renderingEnabled = this.flagShowBackground || this.flagShowSprites; 874 | 875 | if (this.prevRenderingEnabled != this.renderingEnabled) { 876 | this.renderingEnabledUpdated = true; 877 | } 878 | } 879 | 880 | // tick updates Cycle, ScanLine and Frame counters 881 | void tick() { 882 | // we start on cycle 1 since we inc first 883 | 884 | if (this.cycle > 339) { 885 | // Cycle 0 886 | this.cycle = 0; 887 | 888 | this.scanLine++; 889 | 890 | if (this.scanLine == 240) { 891 | this.frame++; 892 | this.f ^= 1; 893 | } else if (this.scanLine == 241) { 894 | this.setVerticalBlank(); 895 | } else if (this.scanLine == 261) { 896 | this.flagSpriteZeroHit = 0; 897 | this.flagSpriteOverflow = 0; 898 | } else if (this.scanLine > 261) { 899 | this.scanLine = 0; 900 | } 901 | } else { 902 | // Cycle > 0 903 | this.cycle++; 904 | 905 | if (this.cycle == 1 && this.scanLine == 261) { 906 | this.clearVerticalBlank(); 907 | } else if (this.cycle == 339 && this.scanLine == 261) { 908 | if (this.f == 1 && (this.renderingEnabled)) { 909 | this.cycle = 340; 910 | } 911 | } 912 | } 913 | } 914 | } 915 | --------------------------------------------------------------------------------