diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e2d39e9 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +cmosher = Christopher A. Mosher diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..6b7e9d6 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.other b/COPYING.other new file mode 100644 index 0000000..39efce9 --- /dev/null +++ b/COPYING.other @@ -0,0 +1,20 @@ +For the analogtv class, portions were inspired by Trevor Blackwell's analog +TV module for xscreensaver. For those portions: +/* analogtv, Copyright (c) 2003, 2004 Trevor Blackwell + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ +Thanks, Trevor, for your code and your inspiration. + + + +The ADC and SBC instructions of the CPU class were based on those taken +from Verhille Arnaud's POM1 Apple 1 emulator. For those portions: +// Copyright (C) 2000, by Verhille Arnaud, GPLv2 license. +Thanks, Verhille. diff --git a/COPYING.sdl b/COPYING.sdl new file mode 100644 index 0000000..d849b3e --- /dev/null +++ b/COPYING.sdl @@ -0,0 +1,180 @@ +EPPLE ][ makes use of the Simple DirectMedia Layer (SDL) library. + +SDL is included under the terms of the LGPL license, a copy of which +is provided below. + +Windows: The EPPLE ][ executable (epple2.exe) links against SDL.dll. +Linux: The EPPLE ][ executable (/bin/epple2) links against libSDL.so. +These are standard versions of the SDL libaray, which are available +from the SDL web site: http://wsw.libsdl.org + +Many thanks to those who are responsible for making SDL what it is today, +and making it freely available for others. + + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..02a4a07 --- /dev/null +++ b/INSTALL @@ -0,0 +1,167 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Type `make install' to install the programs and any data files and + documentation. + + 4. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..aca1227 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,25 @@ +SUBDIRS = src conf installer + +NAME = epple2 +VERSION = 1.0 +ARCH = i386 + +EXTRA_DIST = COPYING.sdl COPYING.other epple2.spec + +RPM := $(abspath rpm) + +package: $(NAME).spec dist + mkdir -p $(RPM)/BUILD + mkdir -p $(RPM)/BUILDROOT + mkdir -p $(RPM)/RPMS + mkdir -p $(RPM)/SOURCES + mkdir -p $(RPM)/SPECS + mkdir -p $(RPM)/SRPMS + mkdir -p $(RPM)/VPATH + cp $(NAME)-$(VERSION).tar.gz $(RPM)/SOURCES + touch .rpmmacros + echo "%_prefix $(prefix)" >>$(RPM)/.rpmmacros + echo "%_sysconfdir $(sysconfdir)" >>$(RPM)/.rpmmacros + echo "%_topdir $(RPM)" >>$(RPM)/.rpmmacros + HOME=$(RPM) rpmbuild -ba --clean --buildroot $(RPM)/BUILDROOT $< + fakeroot alien $(RPM)/RPMS/$(ARCH)/$(NAME)-$(VERSION)-1.$(ARCH).rpm diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..96293f5 --- /dev/null +++ b/README @@ -0,0 +1,135 @@ +epple2 (Emulated Apple ][) +-------------------------- + + +epple2 is an emulator of the Apple ][ and Apple ][ plus +computers from Apple, Inc. It strives to faithfully simulate +much of the internal workings of the original Apple ][, +including the individual cycles of the 6502 CPU, and the +NTSC video signal generation, including the "strange orange +line", other color anomalies, and the "floating data bus." +Understanding the Apple ][, by Jim Sather, is the primary +source for information about the internals of the Apple ][. + +The CPU and video timings are synchronized, and the emulator +is designed to run at the same speed as the original +machine (if your computer is fast enough). It also emulates +several different types of television screens and monitors +for the display. + +It includes emulation of a Disk ][ controller card, a ROM +firmware card, and a RAM "Language" card, as well as a +simple clock card. + +epple2 is written in C++, and is designed to be buildable +on Windows (with MSYS/MinGW or Visual C++ 2008, Express +Edition) or Linux (gcc). + +The SDL library (http://www.libsdl.org) is the only dependency +of epple2 (see below). + +The epple2 is released under GPLv3. + + + +RUNNING +------- +SDL: +To run epple2, you need to have the SDL libraries installed. +See http://www.libsdl.org for information on SDL. +You'll need to download and install the "Runtime Libraries" +for your platform (Win32 or Linux). + +ROM: +With epple2 and SDL, you can run the emulator, but it won't +do too much without the appropriate Apple ROM code. You can +use ROM images from your own original Apple ][ or ][ plus, or +you can find some on the Internet (check the Asimov Apple +repository). +Alternatively, you can build the ROMs yourself from assembly +language source files I've created; these are available in +the apple2src project. (See that project for details). + +Disks: +You will also probably want floppy disk images. Again, you can +download some from the Internet, especially from Asimov. The +epple2 only reads NIBBLE IMAGES. If you only have 16 sector +images, you will need to convert them to nibble images; you +can use CiderPress (http://ciderpress.sourceforge.net). + + + + +BUILDING +-------- +See INSTALL for generic instructions on building and installing +a normal distribution. + +If you want to build epple2 from the source distribution, +you'll need to have the SDL development libraries installed. +See http://www.libsdl.org for information on SDL. +You'll need to download and install the "Development Libraries" +for your platform (Win32 or Linux). + +Developers fetching directly from the repository instead of +downloading the distribution will need to execute the bootstrap +script before running ./configure or make. + + + +PLATFORM-SPECIFIC BUILDING +-------------------------- + +Windows/MSYS +-------------- +Install MSYS into C:\MSYS\1.0 +Install MinGW into C:\mingw + +Install SDL. At http://www.libsdl.org/ find the download page. +Download the "Development Libraries" for Win32, mingw, for example: +SDL-devel-1.2.13-mingw32.tar.gz (Mingw32) +And save it to your MSYS home directory. +Untar the SDL archive to your home directory: +$ cd +$ tar xzvf SDL-devel-1.2.13-mingw32.tar.gz +Then follow the instructions to install SDL, for example: +$ cd SDL-1.2.13 +$ make native + + + +Run MSYS, and at the bash prompt, go to your home directory and +untar the epple2 source distribution tar file and cd to the +epple2 directory. For example: +$ cd +$ tar xzvf epple2-1.0.tar.gz + +NOTE: If you checked out epple2 from subversion, rather +than downloading the distribution, you need to first run +the bootstrap script. But first you'll need to install +autoconf, automake, etc. +$ cd +$ cd epple2 +$ ./bootstrap + + + +Then build as follows. I recommend doing a VPATH build, in +which you build into a different directory that the source +directory. To accomplish this, create a new directory for +building, somewhere, such as: +$ cd +$ mkdir buildepple2 +$ cd buildepple2 +Then run configure from there. For the MSYS build to work, +you need some extra parameters to the build, and you could +also add some compiler options to optimize the build. For +example (from the buildepple2 directory you just created): +$ ~/epple2/configure --prefix= \ +CXXFLAGS="-I/usr/include -O4 -msse3" \ +CFLAGS="-I/usr/include" \ +LDFLAGS="-L/usr/lib -mconsole -mthreads -mno-cygwin" +$ make +$ make install + +Then follow the instruction above under "RUNNING" for ROM and disks. diff --git a/conf/Makefile.am b/conf/Makefile.am new file mode 100644 index 0000000..0b482e7 --- /dev/null +++ b/conf/Makefile.am @@ -0,0 +1,25 @@ +etcdir = $(sysconfdir)/epple2 + +if HAVE_WINDOWS +CONF_PREFIX = +else +CONF_PREFIX = $(prefix)/ +endif + +%.conf: %.conf.in + sed -e 's,$$(PREFIX),$(CONF_PREFIX),g' <$< >$@ + +CONF_FILES = \ + epple2.conf \ + epple2.a2bare.conf \ + epple2.a2dos33.conf \ + epple2.a2pbare.conf \ + epple2.a2ploaded.conf \ + epple2.rev0bare.conf \ + epple2.a2dos31.conf \ + epple2.a2loaded.conf \ + epple2.a2pdos33.conf + +etc_DATA = $(CONF_FILES) + +EXTRA_DIST = $(etc_DATA) diff --git a/conf/epple2.a2bare.conf.in b/conf/epple2.a2bare.conf.in new file mode 100644 index 0000000..4c28e29 --- /dev/null +++ b/conf/epple2.a2bare.conf.in @@ -0,0 +1,11 @@ +# Apple ][ (with revision 1 or later motherboard), bare +# +# This set up is a standard Apple ][ (with a newer motherboard, +# that is, not an original revision zero motherboard), and +# nothing in any slots. It has Integer BASIC and the old Monitor ROMs. + + + +import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 diff --git a/conf/epple2.a2dos31.conf.in b/conf/epple2.a2dos31.conf.in new file mode 100644 index 0000000..0da593e --- /dev/null +++ b/conf/epple2.a2dos31.conf.in @@ -0,0 +1,24 @@ +# Apple ][ with (13-sector) DOS 3.1 (the original version of DOS 3.3). +# +# After power-up, type C600G to boot the DOS 3.1 System Master disk. +# +# Since this System Master disk has "APPLESOFT" on it, you can type +# FP at the Integer BASIC prompt to run Applesoft BASIC. When you do +# it this way, you get an interesting title page for Applesoft, showing +# copyright notices for Apple and Microsoft. + + + +# Integer BASIC and old Monitor ROMs +import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + + + +# Disk ][ card with 13-sector ROMs +slot 6 disk +import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/13sector/controller/disk2.ex65 + +# Insert DOS 3.1 System Master disk into drive 1 +load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/13sector/disks/dos310/clean31sysmas_stock_rawdos.nib diff --git a/conf/epple2.a2dos33.conf.in b/conf/epple2.a2dos33.conf.in new file mode 100644 index 0000000..bc80066 --- /dev/null +++ b/conf/epple2.a2dos33.conf.in @@ -0,0 +1,29 @@ +# Apple ][ with language card and DOS 3.3 (original version) +# +# After power-on, type C600G to boot the DOS 3.3 System Master disk. +# Notice it shows "APPLE II STANDARD" for this machine, and then it +# loads APPLESOFT into the language card. +# +# You can use FP to switch to Applesoft, and INT to switch +# back to Integer BASIC. As you do this, notice the "R" reading +# indicator on the language card line turning on or off. + + +# Integer BASIC and old Monitor ROMs +import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + + + +# Language card in slot 0 +slot 0 language + + + +# Disk ][ controller card in slot 6, with 16-sector ROMs +slot 6 disk +import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/16sector/controller/disk2.ex65 + +# Insert DOS 3.3 System Master disk (original version) in drive 1 +load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/16sector/disks/dos330/clean330sysmas.nib diff --git a/conf/epple2.a2loaded.conf.in b/conf/epple2.a2loaded.conf.in new file mode 100644 index 0000000..95d9924 --- /dev/null +++ b/conf/epple2.a2loaded.conf.in @@ -0,0 +1,68 @@ +# Apple ][, loaded +# +# This is an Apple ][ (Integer BASIC and old Monitor), with +# every card provided by the EPPLE ][ Emulator installed. +# +# Since this machine doesn't have the Autostart Monitor, after powering +# it on you'll need to type C600G to boot the DOS 3.3 System Master, +# or C500G to boot the DOS 3.1 System Master. + + + +# Integer BASIC and old Monitor ROMs +import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + + + +# Language card in slot 0. When the DOS 3.3 System Master boots, +# it will load Applesoft into the language card. +slot 0 language + + + +# Standard-output "card" in slot 1, so typing +# PR#1 will start sending characters to standard output of the emulator +slot 1 stdout +import slot 1 rom 0 $(PREFIX)lib/epple2/cards/stdout.ex65 + + + +# Standard-input "card" in slot 2, so typing +# IN#2 will start reading characters from standard input of the emulator +slot 2 stdin +import slot 2 rom 0 $(PREFIX)lib/epple2/cards/stdin.ex65 + + + +# Clock card in slot 4 +slot 4 clock +import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.ex65 + + + +# Disk ][ controller card in slot 5, with 13-sector ROMs. +# This will read (DOS 3.1, 3.2, and 3.2.1) disks, which +# have 13 sectors per track. +slot 5 disk +import slot 5 rom 0 $(PREFIX)lib/apple2/dos3x/13sector/controller/disk2.ex65 +# Insert the DOS 3.1 System Master disk into drive 1 of slot 5 +load slot 5 drive 1 $(PREFIX)lib/apple2/dos3x/13sector/disks/dos310/clean31sysmas_stock_rawdos.nib + + + +# Disk ][ controller card in slot 6, with 16-sector ROMs. +# This will read (DOS 3.3) disks, which have 16 sectors per track. +slot 6 disk +import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/16sector/controller/disk2.ex65 +# Insert the DOS 3.3 System Master disk (original version) into slot 6 +load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/16sector/disks/dos330/clean330sysmas.nib + + + +# Firmware card with Applesoft and Autostart Monitor. +# DOS will not use this firmware card, because it is not in slot 0. +slot 7 firmware +import slot 7 rombank 0000 $(PREFIX)lib/apple2/system/applesoft/applesoft.ex65 +import slot 7 rombank 2800 $(PREFIX)lib/apple2/system/monitor/apple2plus/monitor.ex65 diff --git a/conf/epple2.a2pbare.conf.in b/conf/epple2.a2pbare.conf.in new file mode 100644 index 0000000..00af74e --- /dev/null +++ b/conf/epple2.a2pbare.conf.in @@ -0,0 +1,10 @@ +# Apple ][ plus, bare +# +# This set up is a standard Apple ][ plus, and +# nothing in any slots. It has Applesoft BASIC +# and the Autostart Monitor ROMs. + + + +import motherboard rom 0000 $(PREFIX)lib/apple2/system/applesoft/applesoft.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2plus/monitor.ex65 diff --git a/conf/epple2.a2pdos33.conf.in b/conf/epple2.a2pdos33.conf.in new file mode 100644 index 0000000..a938cdb --- /dev/null +++ b/conf/epple2.a2pdos33.conf.in @@ -0,0 +1,28 @@ +# Apple ][ plus, with language card and DOS 3.3 (original version) +# +# After power-on, it will boot the DOS 3.3 System Master disk. +# Notice it shows "APPLE II PLUS OR ROMCARD" for this machine, and then it +# loads Integer BASIC into the language card. +# +# You can use FP to switch to Applesoft, and INT to switch +# back to Integer BASIC. As you do this, notice the "R" reading +# indicator on the language card line turning on or off. + + +# Applesoft BASIC and Autostart Monitor ROMs +import motherboard rom 0000 $(PREFIX)lib/apple2/system/applesoft/applesoft.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2plus/monitor.ex65 + + + +# Language card in slot 0 +slot 0 language + + + +# Disk ][ controller card in slot 6, with 16-sector ROMs +slot 6 disk +import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/16sector/controller/disk2.ex65 + +# Insert DOS 3.3 System Master disk (original version) in drive 1 +load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/16sector/disks/dos330/clean330sysmas.nib diff --git a/conf/epple2.a2ploaded.conf.in b/conf/epple2.a2ploaded.conf.in new file mode 100644 index 0000000..2883262 --- /dev/null +++ b/conf/epple2.a2ploaded.conf.in @@ -0,0 +1,68 @@ +# Apple ][ plus, loaded +# +# This is an Apple ][ plus (Applesoft BASIC and Autostart Monitor), with +# every card provided by the EPPLE ][ Emulator installed. +# +# This machine has two Disk ][ controller cards; the Autostart Monitor +# searches from slot 7 downwards for a Disk ][ card, so it will boot +# the disk in slot 6, the DOS 3.3 System Master. + + + +# Applesoft BASIC and Autostart Monitor ROMs +import motherboard rom 0000 $(PREFIX)lib/apple2/system/applesoft/applesoft.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2plus/monitor.ex65 + + + +# Language card in slot 0. When the DOS 3.3 System Master boots, +# it will load Integer BASIC into the language card. +slot 0 language + + + +# Standard-output "card" in slot 1, so typing +# PR#1 will start sending characters to standard output of the emulator +slot 1 stdout +import slot 1 rom 0 $(PREFIX)lib/epple2/cards/stdout.ex65 + + + +# Standard-input "card" in slot 2, so typing +# IN#2 will start reading characters from standard input of the emulator +slot 2 stdin +import slot 2 rom 0 $(PREFIX)lib/epple2/cards/stdin.ex65 + + + +# Clock card in slot 4 +slot 4 clock +import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.ex65 + + + +# Disk ][ controller card in slot 5, with 13-sector ROMs. +# This will read (DOS 3.1, 3.2, and 3.2.1) disks, which +# have 13 sectors per track. +slot 5 disk +import slot 5 rom 0 $(PREFIX)lib/apple2/dos3x/13sector/controller/disk2.ex65 +# Insert the DOS 3.1 System Master disk into drive 1 of slot 5 +load slot 5 drive 1 $(PREFIX)lib/apple2/dos3x/13sector/disks/dos310/clean31sysmas_stock_rawdos.nib + + + +# Disk ][ controller card in slot 6, with 16-sector ROMs. +# This will read (DOS 3.3) disks, which have 16 sectors per track. +slot 6 disk +import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/16sector/controller/disk2.ex65 +# Insert the DOS 3.3 System Master disk (original version) into slot 6 +load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/16sector/disks/dos330/clean330sysmas.nib + + + +# Firmware card with Integer BASIC and the old Monitor. +# DOS will not use this firmware card, because it is not in slot 0. +slot 7 firmware +import slot 7 rombank 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import slot 7 rombank 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import slot 7 rombank 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 diff --git a/conf/epple2.conf.in b/conf/epple2.conf.in new file mode 100644 index 0000000..bcbbb98 --- /dev/null +++ b/conf/epple2.conf.in @@ -0,0 +1,55 @@ +# epple2.conf +# Copyright 2009, by Chris Mosher +# GPLv3 +# +# Configuration file for the Epple 2 emulator. +# +# This sample file is the default setup for the emulator. +# It does not load any proprietary ROMs, only GPLv3 ROMs +# written by the author of the emulator. +# + + + +# Demo system ROM for the emulator. This is only to allow the +# emulator to do something useful when there are no real Apple ROM +# images provided. +# +import motherboard rom 2C00 $(PREFIX)lib/epple2/system/epple2sys.ex65 + +# These are how to load the real (proprietary) Apple ROMs. +# There ROMs are not distributed with the Epple 2 emulator. +# These two lines are for an Apple ][ plus: +#import motherboard rom 0000 $(PREFIX)lib/apple2/system/applesoft/applesoft.ex65 +#import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2plus/monitor.ex65 +# or instead, use these three lines are for an Apple ][: +#import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +#import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +#import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + +slot 0 language + +slot 1 stdout +import slot 1 rom 0 $(PREFIX)lib/epple2/cards/stdout.ex65 + +slot 2 stdin +import slot 2 rom 0 $(PREFIX)lib/epple2/cards/stdin.ex65 + +slot 4 clock +import slot 4 rom 0 $(PREFIX)lib/epple2/cards/clock.ex65 + +#slot 5 disk +#import slot 5 rom 0 $(PREFIX)lib/apple2/dos3x/13sector/controller/disk2.ex65 +#load slot 5 drive 1 $(PREFIX)lib/apple2/dos3x/13sector/disks/dos310/clean31sysmas_stock_rawdos.nib + +#slot 6 disk +#import slot 6 rom 0 $(PREFIX)lib/apple2/dos3x/16sector/controller/disk2.ex65 +#load slot 6 drive 1 $(PREFIX)lib/apple2/dos3x/16sector/disks/dos330/clean330sysmas.nib + +#slot 7 firmware +#import slot 7 rombank 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +#import slot 7 rombank 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +#import slot 7 rombank 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + +#revision 0 +revision 1 diff --git a/conf/epple2.rev0bare.conf.in b/conf/epple2.rev0bare.conf.in new file mode 100644 index 0000000..28a4512 --- /dev/null +++ b/conf/epple2.rev0bare.conf.in @@ -0,0 +1,21 @@ +# Apple ][ with revision zero motherboard +# +# This sets up a revision zero motherboard, and loads +# Integer BASIC and the old Monitor ROMs. This set up +# has nothing in any slots; it's a minimalist Apple ][. +# +# With a revision zero motherboard, powering on the machine +# doesn't start it running; you need to press RESET (Break) +# to get it actually running. + + + +# Load Integer BASIC and old Monitor ROMs +import motherboard rom 1000 $(PREFIX)lib/apple2/system/intbasic/intbasic.ex65 +import motherboard rom 2425 $(PREFIX)lib/apple2/system/other/other.ex65 +import motherboard rom 2800 $(PREFIX)lib/apple2/system/monitor/apple2/monitor.ex65 + + + +# Use an original, revision zero, motherboard +revision 0 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f47c5ea --- /dev/null +++ b/configure.ac @@ -0,0 +1,50 @@ +# Process this file with autoconf to produce a configure script. +AC_INIT(src/apple2.cpp) +AC_PREREQ(2.59) + +AC_CONFIG_SRCDIR([config.h.in]) + +AM_CONFIG_HEADER(config.h) +AM_INIT_AUTOMAKE(epple2, 1.0) + + + +# Checks for programs. +AC_PROG_CXX +AC_LANG_CPLUSPLUS +AC_PROG_CC +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_RANLIB +AM_PROG_CC_C_O + +# Checks for libraries. +AC_CHECK_LIB([SDL],[SDL_Init],,[AC_MSG_ERROR([cannot find libsdl])]) +AC_CHECK_LIB([X11],[XOpenDisplay],,[AC_MSG_ERROR([cannot find X11])]) + + + +# Checks for header files. +AC_HEADER_STDBOOL +AC_C_CONST +AC_C_INLINE +AC_TYPE_SIZE_T +AC_CHECK_HEADERS([windows.h]) +AC_CHECK_HEADER([windows.h],[WINDOWS=true].[WINDOWS=]) +AM_CONDITIONAL([HAVE_WINDOWS],test "$WINDOWS") + +# Checks for library functions. +AC_HEADER_STDC +AC_TYPE_SIGNAL +AC_CHECK_FUNCS([memset sqrt]) + + + +# Checks for typedefs, structures, and compiler characteristics. + + + +AC_CONFIG_FILES([Makefile src/Makefile conf/Makefile installer/Makefile]) +AC_OUTPUT diff --git a/epple2.spec b/epple2.spec new file mode 100644 index 0000000..378147c --- /dev/null +++ b/epple2.spec @@ -0,0 +1,31 @@ +Summary: EPPLE ][, The Emulated Apple ][ +Name: epple2 +Version: 1.0 +Release: 1 +Source: %{name}-%{version}.tar.gz +License: GPL +Group: System/Emulators/Other + +%description +EPPLE ][ is an emulator of Apple ][ and Apple ][ plus computers. + +%prep +%setup -q + +%build +cd ../../VPATH +if [ -x ./config.status ] +then + ./config.status +else + ../BUILD/%{name}-%{version}/configure --prefix=%{_prefix} --sysconfdir=%{_sysconfdir} +fi +make + +%install +cd ../../VPATH +make install DESTDIR=%{buildroot} + +%files +%{_bindir}/%{name} +%{_sysconfdir}/%{name} diff --git a/installer/Makefile.am b/installer/Makefile.am new file mode 100644 index 0000000..5154faa --- /dev/null +++ b/installer/Makefile.am @@ -0,0 +1,17 @@ +EXTRA_DIST = epple2.wxs + +if HAVE_WINDOWS + +SUFFIXES = .wxs .wixobj .msi + +all: epple2.msi + +epple2.msi: epple2.wixobj ../src/epple2.exe + strip -o epple2.exe ../src/epple2.exe + light $< -out $@ + cp $@ $(basename $@)-$(VERSION)$(suffix $@) + +epple2.wixobj: epple2.wxs + candle $< -out $@ -dSDLDIR=/bin -dMINGWMDIR=/mingw/bin + +endif diff --git a/installer/epple2.wxs b/installer/epple2.wxs new file mode 100644 index 0000000..32db71b --- /dev/null +++ b/installer/epple2.wxs @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..decdcd0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,32 @@ +METASOURCES=AUTO + + + +bin_PROGRAMS=epple2 + +AM_CPPFLAGS=$(all_includes) +epple2_LDFLAGS=$(all_libraries) + +epple2_CPPFLAGS = $(AM_CPPFLAGS) -DETCDIR=\"$(sysconfdir)\" + +epple2_SOURCES = a2colorsobserved.cpp addressbus.cpp analogtv.cpp apple2.cpp \ + applentsc.cpp card.cpp cassette.cpp clipboardhandler.cpp clockcard.cpp \ + configep2.cpp cpu.cpp diskbytes.cpp diskcontroller.cpp drive.cpp \ + emptyslot.cpp emulator.cpp firmwarecard.cpp gui.cpp hypermode.cpp \ + keyboard.cpp keyboardbuffermode.cpp languagecard.cpp lowpass_1_5_mhz.cpp \ + lowpass_3_58_mhz.cpp main.cpp memory.cpp paddlebuttonstates.cpp \ + paddles.cpp picturegenerator.cpp powerupreset.cpp raminitializer.cpp \ + screenimage.cpp slots.cpp speakerclicker.cpp standardin.cpp \ + standardinproducer.cpp standardout.cpp steppermotor.cpp textcharacters.cpp \ + timable.cpp video.cpp videoaddressing.cpp videomode.cpp \ + videostaticgenerator.cpp SDL_win32_main.c + +noinst_HEADERS = a2colorsobserved.h addressbus.h analogtv.h apple2.h applentsc.h \ + card.h cassette.h clipboardhandler.h clockcard.h configep2.h cpu.h diskbytes.h \ + diskcontroller.h drive.h e2const.h emptyslot.h emulator.h firmwarecard.h font3x5.h gui.h \ + hypermode.h keyboardbuffermode.h keyboard.h languagecard.h lowpass_1_5_mhz.h \ + lowpass_3_58_mhz.h memory.h paddlebuttonstates.h paddles.h picturegenerator.h \ + powerupreset.h raminitializer.h screenimage.h slots.h speakerclicker.h \ + standardin.h standardinproducer.h standardout.h steppermotor.h \ + textcharacterimages.h textcharacters.h timable.h util.h \ + videoaddressing.h video.h videomode.h videostaticgenerator.h diff --git a/src/SDL_win32_main.c b/src/SDL_win32_main.c new file mode 100644 index 0000000..2a042f8 --- /dev/null +++ b/src/SDL_win32_main.c @@ -0,0 +1,392 @@ +/* + SDL_main.c, placed in the public domain by Sam Lantinga 4/13/98 + + The WinMain function -- calls your program's main() function +*/ +#include "config.h" + +#ifdef HAVE_WINDOWS_H +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// Chris Mosher added this: +#define NO_STDIO_REDIRECT + +#ifdef _WIN32_WCE +# define DIR_SEPERATOR TEXT("\\") +# undef _getcwd +# define _getcwd(str,len) wcscpy(str,TEXT("")) +# define setbuf(f,b) +# define setvbuf(w,x,y,z) +# define fopen _wfopen +# define freopen _wfreopen +# define remove(x) DeleteFile(x) +#else +# define DIR_SEPERATOR TEXT("/") +# include +#endif + +/* Include the SDL main definition header */ +#include "SDL/SDL.h" +#include "SDL/SDL_main.h" + +#ifdef main +# ifndef _WIN32_WCE_EMULATION +# undef main +# endif /* _WIN32_WCE_EMULATION */ +#endif /* main */ + +/* The standard output files */ +#define STDOUT_FILE TEXT("stdout.txt") +#define STDERR_FILE TEXT("stderr.txt") + +#ifndef NO_STDIO_REDIRECT +# ifdef _WIN32_WCE + static wchar_t stdoutPath[MAX_PATH]; + static wchar_t stderrPath[MAX_PATH]; +# else + static char stdoutPath[MAX_PATH]; + static char stderrPath[MAX_PATH]; +# endif +#endif + +#if defined(_WIN32_WCE) && _WIN32_WCE < 300 +/* seems to be undefined in Win CE although in online help */ +#define isspace(a) (((CHAR)a == ' ') || ((CHAR)a == '\t')) +#endif /* _WIN32_WCE < 300 */ + +static void UnEscapeQuotes( char *arg ) +{ + char *last = NULL; + + while( *arg ) { + if( *arg == '"' && *last == '\\' ) { + char *c_curr = arg; + char *c_last = last; + + while( *c_curr ) { + *c_last = *c_curr; + c_last = c_curr; + c_curr++; + } + *c_last = '\0'; + } + last = arg; + arg++; + } +} + +/* Parse a command line buffer into arguments */ +static int ParseCommandLine(char *cmdline, char **argv) +{ + char *bufp; + char *lastp = NULL; + int argc, last_argc; + + argc = last_argc = 0; + for ( bufp = cmdline; *bufp; ) { + /* Skip leading whitespace */ + while ( isspace(*bufp) ) { + ++bufp; + } + /* Skip over argument */ + if ( *bufp == '"' ) { + ++bufp; + if ( *bufp ) { + if ( argv ) { + argv[argc] = bufp; + } + ++argc; + } + /* Skip over word */ + while ( *bufp && ( *bufp != '"' || *lastp == '\\' ) ) { + lastp = bufp; + ++bufp; + } + } else { + if ( *bufp ) { + if ( argv ) { + argv[argc] = bufp; + } + ++argc; + } + /* Skip over word */ + while ( *bufp && ! isspace(*bufp) ) { + ++bufp; + } + } + if ( *bufp ) { + if ( argv ) { + *bufp = '\0'; + } + ++bufp; + } + + /* Strip out \ from \" sequences */ + if( argv && last_argc != argc ) { + UnEscapeQuotes( argv[last_argc] ); + } + last_argc = argc; + } + if ( argv ) { + argv[argc] = NULL; + } + return(argc); +} + +/* Show an error message */ +static void ShowError(const char *title, const char *message) +{ +/* If USE_MESSAGEBOX is defined, you need to link with user32.lib */ +#ifdef USE_MESSAGEBOX + MessageBox(NULL, message, title, MB_ICONEXCLAMATION|MB_OK); +#else + fprintf(stderr, "%s: %s\n", title, message); +#endif +} + +/* Pop up an out of memory message, returns to Windows */ +static BOOL OutOfMemory(void) +{ + ShowError("Fatal Error", "Out of memory - aborting"); + return FALSE; +} + +/* SDL_Quit() shouldn't be used with atexit() directly because + calling conventions may differ... */ +static void cleanup(void) +{ + SDL_Quit(); +} + +/* Remove the output files if there was no output written */ +static void cleanup_output(void) +{ +#ifndef NO_STDIO_REDIRECT + FILE *file; + int empty; +#endif + + /* Flush the output in case anything is queued */ + fclose(stdout); + fclose(stderr); + +#ifndef NO_STDIO_REDIRECT + /* See if the files have any output in them */ + if ( stdoutPath[0] ) { + file = fopen(stdoutPath, TEXT("rb")); + if ( file ) { + empty = (fgetc(file) == EOF) ? 1 : 0; + fclose(file); + if ( empty ) { + remove(stdoutPath); + } + } + } + if ( stderrPath[0] ) { + file = fopen(stderrPath, TEXT("rb")); + if ( file ) { + empty = (fgetc(file) == EOF) ? 1 : 0; + fclose(file); + if ( empty ) { + remove(stderrPath); + } + } + } +#endif +} + +#if defined(_MSC_VER) && !defined(_WIN32_WCE) +/* The VC++ compiler needs main defined */ +#define console_main main +#endif + +/* This is where execution begins [console apps] */ +int console_main(int argc, char *argv[]) +{ + size_t n; + char *bufp, *appname; + int status; + + /* Get the class name from argv[0] */ + appname = argv[0]; + if ( (bufp=SDL_strrchr(argv[0], '\\')) != NULL ) { + appname = bufp+1; + } else + if ( (bufp=SDL_strrchr(argv[0], '/')) != NULL ) { + appname = bufp+1; + } + + if ( (bufp=SDL_strrchr(appname, '.')) == NULL ) + n = SDL_strlen(appname); + else + n = (bufp-appname); + + bufp = SDL_stack_alloc(char, n+1); + if ( bufp == NULL ) { + return OutOfMemory(); + } + SDL_strlcpy(bufp, appname, n+1); + appname = bufp; + + /* Load SDL dynamic link library */ + if ( SDL_Init(SDL_INIT_NOPARACHUTE) < 0 ) { + ShowError("WinMain() error", SDL_GetError()); + return(FALSE); + } + atexit(cleanup_output); + atexit(cleanup); + + /* Sam: + We still need to pass in the application handle so that + DirectInput will initialize properly when SDL_RegisterApp() + is called later in the video initialization. + */ + SDL_SetModuleHandle(GetModuleHandle(NULL)); + + /* Run the application main() code */ + status = SDL_main(argc, argv); + + /* Exit cleanly, calling atexit() functions */ + exit(status); + + /* Hush little compiler, don't you cry... */ + return 0; +} + +/* This is where execution begins [windowed apps] */ +#ifdef _WIN32_WCE +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int sw) +#else +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) +#endif +{ + HINSTANCE handle; + char **argv; + int argc; + char *cmdline; +#ifdef _WIN32_WCE + wchar_t *bufp; + int nLen; +#else + char *bufp; + size_t nLen; +#endif +#ifndef NO_STDIO_REDIRECT + DWORD pathlen; +#ifdef _WIN32_WCE + wchar_t path[MAX_PATH]; +#else + char path[MAX_PATH]; +#endif + FILE *newfp; +#endif + + /* Start up DDHELP.EXE before opening any files, so DDHELP doesn't + keep them open. This is a hack.. hopefully it will be fixed + someday. DDHELP.EXE starts up the first time DDRAW.DLL is loaded. + */ + handle = LoadLibrary(TEXT("DDRAW.DLL")); + if ( handle != NULL ) { + FreeLibrary(handle); + } + +#ifndef NO_STDIO_REDIRECT + pathlen = GetModuleFileName(NULL, path, SDL_arraysize(path)); + while ( pathlen > 0 && path[pathlen] != '\\' ) { + --pathlen; + } + path[pathlen] = '\0'; + +#ifdef _WIN32_WCE + wcsncpy( stdoutPath, path, SDL_arraysize(stdoutPath) ); + wcsncat( stdoutPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) ); +#else + SDL_strlcpy( stdoutPath, path, SDL_arraysize(stdoutPath) ); + SDL_strlcat( stdoutPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) ); +#endif + + /* Redirect standard input and standard output */ + newfp = freopen(stdoutPath, TEXT("w"), stdout); + +#ifndef _WIN32_WCE + if ( newfp == NULL ) { /* This happens on NT */ +#if !defined(stdout) + stdout = fopen(stdoutPath, TEXT("w")); +#else + newfp = fopen(stdoutPath, TEXT("w")); + if ( newfp ) { + *stdout = *newfp; + } +#endif + } +#endif /* _WIN32_WCE */ + +#ifdef _WIN32_WCE + wcsncpy( stderrPath, path, SDL_arraysize(stdoutPath) ); + wcsncat( stderrPath, DIR_SEPERATOR STDOUT_FILE, SDL_arraysize(stdoutPath) ); +#else + SDL_strlcpy( stderrPath, path, SDL_arraysize(stderrPath) ); + SDL_strlcat( stderrPath, DIR_SEPERATOR STDERR_FILE, SDL_arraysize(stderrPath) ); +#endif + + newfp = freopen(stderrPath, TEXT("w"), stderr); +#ifndef _WIN32_WCE + if ( newfp == NULL ) { /* This happens on NT */ +#if !defined(stderr) + stderr = fopen(stderrPath, TEXT("w")); +#else + newfp = fopen(stderrPath, TEXT("w")); + if ( newfp ) { + *stderr = *newfp; + } +#endif + } +#endif /* _WIN32_WCE */ + + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* Line buffered */ + setbuf(stderr, NULL); /* No buffering */ +#endif /* !NO_STDIO_REDIRECT */ + +#ifdef _WIN32_WCE + nLen = wcslen(szCmdLine)+128+1; + bufp = SDL_stack_alloc(wchar_t, nLen*2); + wcscpy (bufp, TEXT("\"")); + GetModuleFileName(NULL, bufp+1, 128-3); + wcscpy (bufp+wcslen(bufp), TEXT("\" ")); + wcsncpy(bufp+wcslen(bufp), szCmdLine,nLen-wcslen(bufp)); + nLen = wcslen(bufp)+1; + cmdline = SDL_stack_alloc(char, nLen); + if ( cmdline == NULL ) { + return OutOfMemory(); + } + WideCharToMultiByte(CP_ACP, 0, bufp, -1, cmdline, nLen, NULL, NULL); +#else + /* Grab the command line */ + bufp = GetCommandLine(); + nLen = SDL_strlen(bufp)+1; + cmdline = SDL_stack_alloc(char, nLen); + if ( cmdline == NULL ) { + return OutOfMemory(); + } + SDL_strlcpy(cmdline, bufp, nLen); +#endif + + /* Parse it into argv and argc */ + argc = ParseCommandLine(cmdline, NULL); + argv = SDL_stack_alloc(char*, argc+1); + if ( argv == NULL ) { + return OutOfMemory(); + } + ParseCommandLine(cmdline, argv); + + /* Run the main program (after a little SDL initialization) */ + console_main(argc, argv); + + /* Hush little compiler, don't you cry... */ + return 0; +} +#endif diff --git a/src/a2colorsobserved.cpp b/src/a2colorsobserved.cpp new file mode 100644 index 0000000..a018d61 --- /dev/null +++ b/src/a2colorsobserved.cpp @@ -0,0 +1,93 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "a2colorsobserved.h" + +static unsigned char tobyt(float x) +{ + x *= 256.0f; + x += 0.0001f; + int xi = (int)x; + + if (xi > 255) + xi = 255; + + return (unsigned char)xi; +} + +// 0 <= h < 360 degrees; 0 <= s,v <= 1 +static int HSVtoRGB(const int h, const float s, const float v) +{ + const float f = (h % 60) / 60.0; + + float r, g, b; + switch (h / 60) + { + case 0: + r = v; + g = v * (1 - s * (1 - f)); + b = v * (1 - s); + break; + case 1: + r = v * (1 - s * f); + g = v; + b = v * (1 - s); + break; + case 2: + r = v * (1 - s); + g = v; + b = v * (1 - s * (1 - f)); + break; + case 3: + r = v * (1 - s); + g = v * (1 - s * f); + b = v; + break; + case 4: + r = v * (1 - s * (1 - f)); + g = v * (1 - s); + b = v; + break; + case 5: + r = v; + g = v * (1 - s); + b = v * (1 - s * f); + break; + } + + return (tobyt(r) << 16) | (tobyt(g) << 8) | (tobyt(b)); +} + +A2ColorsObserved::A2ColorsObserved(): + COLOR(0x10) +{ +// const unsigned int clr[] = { 0x1, 0xB, 0x3, 0x2, 0x7, 0x6, 0x4, 0xE, 0xC, 0x8, 0xD, 0x9, 0x5, 0xA, 0xF, 0x0 }; + const unsigned int map[] = { 0xF, 0x0, 0x3, 0x2, 0x6, 0xC, 0x5, 0x4, 0x9, 0xB, 0xD, 0x1, 0x8, 0xA, 0x7, 0xE }; + const unsigned int hue[] = { 342, 342, 277, 233, 233, 213, 160, 160, 75, 33, 52, 24, 0, 0, 0, 0 }; + const unsigned int sat[] = { 100, 50, 75, 100, 50, 100, 100, 100, 100, 100, 100, 100, 0, 0, 0, 0 }; + const unsigned int val[] = { 67, 100, 100, 75, 100, 100, 33, 100, 75, 50, 100, 100, 50, 50, 100, 0 }; + + for (unsigned int i(0); i < COLOR.size(); ++i) + { + COLOR[i] = HSVtoRGB(hue[map[i]],sat[map[i]]/100.0f,val[map[i]]/100.0f); + } + +} + +A2ColorsObserved::~A2ColorsObserved() +{ +} diff --git a/src/a2colorsobserved.h b/src/a2colorsobserved.h new file mode 100644 index 0000000..515fb08 --- /dev/null +++ b/src/a2colorsobserved.h @@ -0,0 +1,55 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef A2COLORSOBSERVED_H +#define A2COLORSOBSERVED_H + +#include + +class A2ColorsObserved +{ +private: + std::vector COLOR; + +public: + A2ColorsObserved(); + ~A2ColorsObserved(); + + const std::vector& c() { return this->COLOR; } + + enum + { + BLACK, + DARK_MAGENTA, + DARK_BLUE, + HIRES_VIOLET, + DARK_BLUE_GREEN, + GREY, + HIRES_BLUE, + LIGHT_BLUE, + DARK_BROWN, + HIRES_ORANGE, + GREY_ALTERNATE, + LIGHT_MAGENTA, + HIRES_GREEN, + LIGHT_BROWN, + LIGHT_BLUE_GREEN, + WHITE, + }; +}; + +#endif diff --git a/src/addressbus.cpp b/src/addressbus.cpp new file mode 100644 index 0000000..954e7b7 --- /dev/null +++ b/src/addressbus.cpp @@ -0,0 +1,229 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "addressbus.h" +#include "memory.h" +#include "keyboard.h" +#include "videomode.h" +#include "paddles.h" +#include "paddlebuttonstates.h" +#include "speakerclicker.h" +#include "cassette.h" +#include "slots.h" + +AddressBus::AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, Cassette& cassette, Slots& slts): + ram(ram), rom(rom), kbd(kbd), vid(vid), paddles(paddles), paddleButtonStates(paddleButtonStates), speaker(speaker), cassette(cassette), slts(slts) +{ +} + + +AddressBus::~AddressBus() +{ +} + + + + + + + + + +unsigned char AddressBus::read(const unsigned short address) +{ + if ((address >> 14 == 3)) // >= $C000 + { + if ((address >> 12) == 0xC) + { + // 11007sssxxxxxxxx + const bool seventh = address & 0x0800; + const int slot = (address >> 8) & 7; + if (seventh) + { + this->data = this->slts.readSeventhRom(address & 0x07FF, this->data); + } + else if (slot == 0) + { + this->data = readSwitch(address & 0x00FF); + } + else + { + this->data = this->slts.readRom(slot,address & 0x00FF, this->data); + } + } + else + { + this->data = this->slts.ioBankRom(address - 0xD000,this->data,false); + if (!this->slts.inhibitMotherboardRom()) + { + this->data = this->rom.read(address - 0xD000); + } + } + } + else // < $C000 + { + this->data = this->ram.read(address); + } + + return this->data; +} + +void AddressBus::write(const unsigned short address, const unsigned char d) +{ + this->data = d; + + if ((address >> 14 == 3)) // >= $C000 + { + if ((address >> 12) == 0xC) + { + // 11007sssxxxxxxxx + const bool seventh = address & 0x0800; + const int slot = (address >> 8) & 7; + if (!seventh && slot == 0) + { + writeSwitch(address & 0x00FF); + } + } + else + { + this->slts.ioBankRom(address - 0xD000,this->data,true); + } + } + else // < $C000 + { + this->ram.write(address,this->data); + } +} + + + + + + + + +unsigned char AddressBus::readSwitch(unsigned short address) +{ + if (address < 0x80) + { + const int islot = (address >> 4) & 0xF; + const int iswch = (address & 0xF); + if (islot == 0x0) + { + this->data = this->kbd.get(); + } + else if (islot == 0x1) + { + this->kbd.clear(); + } + else if (islot == 0x2) + { + this->cassette.output(); + } + else if (islot == 0x3) + { + this->speaker.click(); + } + else if (islot == 0x4) + { + // ignore utility strobe + } + else if (islot == 0x5) + { + if (iswch < 0x8) + { + this->data = this->vid.io(address,this->data); + } + else + { + // ignore annunciator outputs + } + } + else if (islot == 0x6) + { + int sw2 = iswch & 0x7; + if (sw2 == 0) + { + setD7(this->cassette.input()); + } + else if (sw2 < 4) + { + setD7(this->paddleButtonStates.isDown(sw2-1)); + } + else + { + sw2 &= 3; + setD7(!this->paddles.isTimedOut(sw2)); + } + } + else if (islot == 0x7) + { + this->paddles.startTimers(); + setD7(true); + } + } + else + { + // slot I/O switches + address &= 0x7F; + const int islot = (address >> 4) & 0xF; + const int iswch = (address & 0xF); + this->data = this->slts.io(islot,iswch,this->data,false); + } + + return this->data; +} + +void AddressBus::setD7(const bool set) +{ + if (set) + { + this->data |= 0x80; + } + else + { + this->data &= 0x7F; + } +} + +void AddressBus::writeSwitch(unsigned short address) +{ + if (address < 0x80) + { + const int islot = (address >> 4) & 0xF; + const int iswch = (address & 0xF); + + if (islot == 0x1) + { + this->kbd.clear(); + } + else if (islot == 0x5) + { + if (iswch < 0x8) + this->vid.io(address,this->data); + } + // ignore all other switch writes + } + else + { + // slot I/O switches + address &= 0x7F; + const int islot = (address >> 4) & 0xF; + const int iswch = (address & 0xF); + this->slts.io(islot,iswch,this->data,true); + } +} diff --git a/src/addressbus.h b/src/addressbus.h new file mode 100644 index 0000000..b7d0e65 --- /dev/null +++ b/src/addressbus.h @@ -0,0 +1,62 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef ADDRESSBUS_H +#define ADDRESSBUS_H + +class Memory; +class Keyboard; +class VideoMode; +class Paddles; +class PaddleButtonStates; +class SpeakerClicker; +class Cassette; +class Slots; + +class AddressBus +{ +private: + Memory& ram; + Memory& rom; + Keyboard& kbd; + VideoMode& vid; + Paddles& paddles; + PaddleButtonStates& paddleButtonStates; + SpeakerClicker& speaker; + Cassette& cassette; + Slots& slts; + + unsigned char data; // this emulates the (floating) data bus + +public: + AddressBus(Memory& ram, Memory& rom, Keyboard& kbd, VideoMode& vid, Paddles& paddles, PaddleButtonStates& paddleButtonStates, SpeakerClicker& speaker, Cassette& cassette, Slots& slts); + ~AddressBus(); + + unsigned char read(const unsigned short address); + void write(const unsigned short address, const unsigned char d); + unsigned char readSwitch(unsigned short address); + void setD7(const bool set); + void writeSwitch(unsigned short address); + enum { MOTHERBOARD_RAM_BAS = 0x00000 } ; + enum { MOTHERBOARD_RAM_LIM = 0x0C000 } ; + enum { MOTHERBOARD_RAM_SIZ = MOTHERBOARD_RAM_LIM-MOTHERBOARD_RAM_BAS }; + enum { MOTHERBOARD_ROM_BAS = 0x0D000 } ; + enum { MOTHERBOARD_ROM_LIM = 0x10000 } ; + enum { MOTHERBOARD_ROM_SIZ = MOTHERBOARD_ROM_LIM-MOTHERBOARD_ROM_BAS } ; +}; + +#endif diff --git a/src/analogtv.cpp b/src/analogtv.cpp new file mode 100644 index 0000000..f4db4ce --- /dev/null +++ b/src/analogtv.cpp @@ -0,0 +1,611 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + This class was highly inspired by Trevor Blackwell's Analog TV + module ("hack") of the XScreenSaver collection, therefore, portions + Copyright (C) 2003, 2004, by Trevor Blackwell + (Thanks, Trevor!) + For this class, I removed the "interference" type processing, and + just took the raw color processing functionality. See the drawTVOld + method definition in this file for the analog TV processing. + I also added processing for the other ("new") TV, and various monitors. +*/ +#include "analogtv.h" +#include "screenimage.h" +#include "applentsc.h" +#include "lowpass_3_58_mhz.h" +#include "lowpass_1_5_mhz.h" + +#include + +#include +#include +#include + + + +AnalogTV::AnalogTV(ScreenImage& image): + image(image), + on(false), + noise(false), + bleed_down(false) +{ + hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_GREEN]); + hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_ORANGE]); + hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_VIOLET]); + hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_BLUE]); + + loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BROWN]); + loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_MAGENTA]); + loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BLUE]); + loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BLUE_GREEN]); + + loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BLUE_GREEN]); + loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BROWN]); + loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_MAGENTA]); + loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BLUE]); +} + + +AnalogTV::~AnalogTV() +{ +} + +void AnalogTV::powerOn(bool b) +{ + this->on = b; + this->image.notifyObservers(); +} + +void AnalogTV::setType(DisplayType type) +{ + this->type = type; +} + +void AnalogTV::cycleType() +{ + this->type = (DisplayType)((((int)this->type)+1)%NUM_DISPLAY_TYPES); +} + +void AnalogTV::toggleBleedDown() +{ + this->bleed_down = !this->bleed_down; + this->image.blank(); + this->image.notifyObservers(); +} + + + + + + + + + +// void dump() +// { +// const int[] yiq = new int[AppleNTSC::H]; +// for (int row = 0; row < 192; ++row) +// { +// const CB cb = get_cb(row); +// const IQ iq_factor = get_iq_factor(cb); +// ntsc_to_yiq(row*AppleNTSC::H+350,AppleNTSC::H-2-350,iq_factor,yiq); +// for (int col = 350; col < AppleNTSC::H-2; ++col) +// { +// const int sig = this->signal[row*AppleNTSC::H+col]; +// System.out.printf(" %+04d",sig); +// const int yiqv = yiq[col-350]; +// int y = (yiqv&0xFF)-IQINTOFF; +// int i = ((yiqv>>8)&0xFF)-IQINTOFF; +// int q = ((yiqv>>16)&0xFF)-IQINTOFF; +// System.out.printf("(%+04d,%+04d,%+04d)",y,i,q); +// +// const int rgb = yiq2rgb(yiqv); +// const int r = (rgb >> 16) & 0xff; +// const int g = (rgb >> 8) & 0xff; +// const int b = (rgb ) & 0xff; +// System.out.printf("[%06X:%03d,%03d,%03d]",rgb,r,g,b); +// } +// System.out.println(); +// } +// +// } + + + + + + + + +class IQ +{ +public: + double iq[4]; + IQ() + { + for (int i = 0; i < 4; ++i) + iq[i] = 0; + } + IQ(double aiq[]) + { + for (int i = 0; i < 4; ++i) + iq[i] = aiq[i]; + } + double get(int i) const { return this->iq[i]; } +}; + +const IQ& AnalogTV::BLACK_AND_WHITE = IQ(); + +static const int CB_EXTRA(32); + +class CB +{ +public: + std::vector cb; + + CB(const int acb[]): + cb(AppleNTSC::CB_END-AppleNTSC::CB_START-CB_EXTRA) + { + for (std::vector::size_type i(0); i < this->cb.size(); ++i) + { + this->cb[i] = acb[i]; + } + } + int get(const int i) const { return this->cb[i]; } + int length() const { return this->cb.size(); } + void getPhase(double phase[]) const + { + { + for (int i = 0; i < 4; ++i) + { + phase[i & 3] = 0; + } + } + { + for (int i = 0; i < length(); ++i) + { + phase[i & 3] += this->cb[i]; + } + } + double tot = 0; + { + for (int i = 0; i < 4; ++i) + { + tot += phase[i] * phase[i]; + } + } + const double tsrt = sqrt(tot); + if (tsrt < .0001) + { + return; + } + for (int i = 0; i < 4; i++) + { + phase[i] /= tsrt; + } + } + bool isColor() const + { + int tot = 0; + for (int i = 0; i < length(); ++i) + { + const int icb = this->cb[i]; + if (icb < 0) + tot += -icb; + else + tot += icb; + } + return 220 < tot && tot < 260; + } + + bool operator<(const CB& that) const + { + return this->cb < that.cb; + } +}; + + + + + + + +void AnalogTV::drawCurrent() +{ + if (this->on) + { + switch (this->type) + { + case MONITOR_COLOR: drawMonitorColor(); break; + case MONITOR_WHITE: drawMonitorWhite(); break; + case MONITOR_GREEN: drawMonitorGreen(); break; + case MONITOR_ORANGE: drawMonitorOrange(); break; + case TV_OLD_COLOR: drawTVOld(); break; + case TV_OLD_BW: drawTVOld(); break; + case TV_NEW_COLOR: drawTVNew(); break; + case TV_NEW_BW: drawTVNew(); break; + } + } + else + { + drawBlank(); + } + this->image.notifyObservers(); +} + + + +static const int D_IP(AppleNTSC::H-2-350); + +void AnalogTV::drawMonitorColor() +{ + unsigned int *rgb = new unsigned int[AppleNTSC::H]; + int ip = 0; + for (int row = 0; row < 192; ++row) + { + const CB cb = get_cb(row); + const bool removeColor = (this->type == TV_NEW_BW || !cb.isColor()); + ntsc_to_rgb_monitor(row*AppleNTSC::H+350,AppleNTSC::H-350,rgb); + for (int col = 350; col < AppleNTSC::H-2; ++col) + { + int rgbv = rgb[col-350]; + if (removeColor && rgbv != 0) + { + rgbv = 0xFFFFFF; + } + this->image.setElem(ip,rgbv); + if (bleed_down) + this->image.setElem(ip+D_IP,rgbv); // display same pixel on next row + ++ip; + } + ip += D_IP; + } + delete [] rgb; +} + +void AnalogTV::drawMonitorWhite() +{ + drawMonitorMonochrome(colors.c()[A2ColorsObserved::WHITE]); +} + +void AnalogTV::drawMonitorGreen() +{ + drawMonitorMonochrome(colors.c()[A2ColorsObserved::HIRES_GREEN]); +} + +void AnalogTV::drawMonitorOrange() +{ + drawMonitorMonochrome(colors.c()[A2ColorsObserved::HIRES_ORANGE]); +} + +void AnalogTV::drawMonitorMonochrome(const unsigned int color) +{ + int ip = 0; + for (int row = 0; row < 192; ++row) + { + for (int col = 350; col < AppleNTSC::H-2; ++col) + { + const int is = row*AppleNTSC::H+col; + const unsigned int rgb = this->signal[is] > 50 ? color : 0; + this->image.setElem(ip,rgb); + if (bleed_down) + this->image.setElem(ip+D_IP,rgb); + ++ip; + } + ip += D_IP; + } +} + +void AnalogTV::drawTVOld() +{ + int *yiq = new int[AppleNTSC::H]; + int ip = 0; + for (int row = 0; row < 192; ++row) + { + IQ iq_factor; + if (this->type == TV_OLD_COLOR) + { + const CB cb = get_cb(row); + iq_factor = get_iq_factor(cb); + } + else + { + iq_factor = BLACK_AND_WHITE; + } + ntsc_to_yiq(row*AppleNTSC::H+350,AppleNTSC::H-350,iq_factor,yiq); + for (int col = 350; col < AppleNTSC::H-2; ++col) + { + const int rgb = yiq2rgb(yiq[col-348]); // shift display left 1 pixel + this->image.setElem(ip,rgb); + if (bleed_down) + this->image.setElem(ip+D_IP,rgb); + ++ip; + } + ip += D_IP; + } + delete [] yiq; +} + +void AnalogTV::drawTVNew() +{ + unsigned int *rgb = new unsigned int[AppleNTSC::H]; + int ip = 0; + for (int row = 0; row < 192; ++row) + { + const CB cb = get_cb(row); + const bool removeColor = (this->type == TV_NEW_BW || !cb.isColor()); + ntsc_to_rgb_newtv(row*AppleNTSC::H+350,AppleNTSC::H-350,rgb); + for (int col = 350; col < AppleNTSC::H-2; ++col) + { + int rgbv = rgb[col-350]; + if (removeColor) + { + rgbv = color2bw(rgbv); + } + this->image.setElem(ip,rgbv); + if (bleed_down) + this->image.setElem(ip+D_IP,rgbv); + ++ip; + } + ip += D_IP; + } + delete [] rgb; +} + +void AnalogTV::drawBlank() +{ + this->image.blank(); +} + + + + + +void AnalogTV::ntsc_to_rgb_monitor(const int isignal, const int siglen, unsigned int rgb[]) +{ + int s0, s1, se; + s0 = s1 = isignal; + se = isignal+siglen; + while (s1 < se) + { + // no signal (black) + while (this->signal[s0] < 50 && s0signal[s1] > 50 && s1= 4) + { + c = 0xFFFFFF; + } + else if (slen == 1) + { + if (this->signal[s0-2] > 50 && this->signal[s0+2] > 50) + c = 0xFFFFFF; + else + c = loresdarkcolor[s0 % 4]; + } + else if (slen == 2) + { + c = hirescolor[s0 % 4]; + } + else if (slen == 3) + { + c = loreslightcolor[s0 % 4]; + } + else + { + ++s1; + } + + for (int i = s0; i < s1; ++i) + rgb[i-isignal] = c; + s0 = s1; + } +} + +void AnalogTV::ntsc_to_rgb_newtv(const int isignal, const int siglen, unsigned int rgb[]) +{ + int sp, s0, s1, se; + sp = s0 = s1 = isignal; + se = isignal+siglen; + unsigned int c = 0; + while (s1 < se) + { + // no signal; black... + sp = s0; + while (this->signal[s0] < 50 && s0signal[s1] > 50 && s1= 4) + { + c = 0xFFFFFF; + } + else if (slen == 1) + { + if (this->signal[s0-2] > 50 && this->signal[s0+2] > 50) + c = 0x808080; + else + c = loresdarkcolor[s0 % 4]; + } + else if (slen == 2) + { + c = hirescolor[s0 % 4]; + } + else if (slen == 3) + { + c = loreslightcolor[s0 % 4]; + } + else + { + ++s1; + } + + for (int i = s0; i < s1; ++i) + rgb[i-isignal] = c; + s0 = s1; + } +} + + + + + + +int *AnalogTV::rcb = new int[AppleNTSC::CB_END-AppleNTSC::CB_START-CB_EXTRA]; +CB AnalogTV::get_cb(int lineno) +{ + const int isp = lineno * AppleNTSC::H; + for (int i = AppleNTSC::CB_START + CB_EXTRA/2; i < AppleNTSC::CB_END - CB_EXTRA/2; ++i) + { + this->rcb[i-(AppleNTSC::CB_START + CB_EXTRA/2)] = this->signal[isp + i]; + } + return CB(this->rcb); +} + + +std::map cacheCB; + +const double AnalogTV::IQ_OFFSET_DEGREES = 33; +const double AnalogTV::IQ_OFFSET_RADIANS = AnalogTV::IQ_OFFSET_DEGREES * 3.1415927 / 180; +const double AnalogTV::TINT_I = -cos(AnalogTV::IQ_OFFSET_RADIANS); +const double AnalogTV::TINT_Q = +sin(AnalogTV::IQ_OFFSET_RADIANS); + +const double AnalogTV::COLOR_THRESH(1.4); + +IQ AnalogTV::get_iq_factor(const CB& cb) +{ + std::map::iterator hit = cacheCB.find(cb); + if (hit != cacheCB.end()) + { + return hit->second; + } + + double cb_phase[4]; + cb.getPhase(cb_phase); + const double cb_i = cb_phase[2]-cb_phase[0]; + const double cb_q = cb_phase[3]-cb_phase[1]; + + if ((cb_i*cb_i) + (cb_q*cb_q) < COLOR_THRESH) + { + return BLACK_AND_WHITE; + } + + double iq_factor[4]; + + iq_factor[0] = cb_i * TINT_I + cb_q * TINT_Q; + iq_factor[2] = -iq_factor[0]; + iq_factor[1] = cb_q * TINT_I - cb_i * TINT_Q; + iq_factor[3] = -iq_factor[1]; + + const IQ iq(iq_factor); + if (!this->noise) + { + cacheCB[cb] = iq; + } + return iq; +} + +const int AnalogTV::IQINTOFF(130); + +void AnalogTV::ntsc_to_yiq(const int isignal, const int siglen, const IQ& iq_factor, int yiq[]) +{ + Lowpass_3_58_MHz filterY; + Lowpass_1_5_MHz filterI; + Lowpass_1_5_MHz filterQ; + for (int off = 0; off < siglen; ++off) + { + const int sig = this->signal[isignal + off]; + const int y = filterY.next(sig); // + 40; // to show blacker-than-black levels + int i; + int q; + if (y < -2) + { + i = 0; + q = 0; + } + else + { + i = filterI.next((int)(sig * iq_factor.get(off & 3))); + q = filterQ.next((int)(sig * iq_factor.get((off + 3) & 3))); + } + + yiq[off] = (((q+IQINTOFF)&0xff) << 16) | (((i+IQINTOFF)&0xff) << 8) | ((y+IQINTOFF)&0xff); + } +} + +int inline AnalogTV::yiq2rgb(const int yiq) +{ + double r = (((yiq)&0xFF)-IQINTOFF) + 0.956 * (((yiq>>8)&0xFF)-IQINTOFF) + 0.621 * (((yiq>>16)&0xFF)-IQINTOFF); + double g = (((yiq)&0xFF)-IQINTOFF) - 0.272 * (((yiq>>8)&0xFF)-IQINTOFF) - 0.647 * (((yiq>>16)&0xFF)-IQINTOFF); + double b = (((yiq)&0xFF)-IQINTOFF) - 1.105 * (((yiq>>8)&0xFF)-IQINTOFF) + 1.702 * (((yiq>>16)&0xFF)-IQINTOFF); + + const int rgb = + (calc_color(r) << 16)| + (calc_color(g) << 8)| + (calc_color(b) << 0); + + return rgb; +} + +int inline AnalogTV::color2bw(const int rgb) +{ + const int y = rgb2y(rgb); + return y<<16 | y<<8 | y; +} + +int inline AnalogTV::rgb2y(const int rgb) // y in range 0-255 +{ + return (int)((0.299*((rgb>>16)&0xFF) + 0.587*((rgb>>8)&0xFF) + 0.114*((rgb)&0xFF))/1.04); +} + +int inline AnalogTV::calc_color(const double color) +{ + int x = (int)(color * 0x100 / AppleNTSC::LEVEL_RANGE + .5); + x = clamp(0,x,0x100); + return x & 0xFF; +} + +int inline AnalogTV::clamp(int min, int x, int lim) +{ + if (x < min) + return min; + if (lim <= x) + return lim-1; + return x; +} diff --git a/src/analogtv.h b/src/analogtv.h new file mode 100644 index 0000000..54c343a --- /dev/null +++ b/src/analogtv.h @@ -0,0 +1,111 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef ANALOGTV_H +#define ANALOGTV_H + +#include "analogtv.h" +#include "applentsc.h" +#include "a2colorsobserved.h" + +#include + + +class ScreenImage; +class IQ; +class CB; + +class AnalogTV +{ +public: + enum DisplayType + { + MONITOR_COLOR, + MONITOR_WHITE, + MONITOR_GREEN, + MONITOR_ORANGE, + TV_OLD_COLOR, + TV_OLD_BW, + TV_NEW_COLOR, + TV_NEW_BW, + + NUM_DISPLAY_TYPES + }; + +private: + ScreenImage& image; + + bool on; + bool noise; + DisplayType type; + bool bleed_down; + + static int* rcb; + + A2ColorsObserved colors; + std::vector hirescolor; + std::vector loreslightcolor; + std::vector loresdarkcolor; + + static const int IQINTOFF; + static const double IQ_OFFSET_DEGREES; + static const double IQ_OFFSET_RADIANS; + static const double TINT_I; + static const double TINT_Q; + static const double COLOR_THRESH; + static const IQ& BLACK_AND_WHITE; + + void drawMonitorColor(); + void drawMonitorWhite(); + void drawMonitorGreen(); + void drawMonitorOrange(); + void drawMonitorMonochrome(const unsigned int color); + void drawTVOld(); + void drawTVNew(); + void drawBlank(); + void ntsc_to_rgb_monitor(const int isignal, const int siglen, unsigned int rgb[]); + void ntsc_to_rgb_newtv(const int isignal, const int siglen, unsigned int rgb[]); + CB get_cb(int lineno); + IQ get_iq_factor(const CB& cb); + void ntsc_to_yiq(const int isignal, const int siglen, const IQ& iq_factor, int yiq[]); + static int yiq2rgb(const int yiq); + static int color2bw(const int rgb); + static int rgb2y(const int rgb); // ;y in range 0-255 + static int calc_color(const double color); + static int clamp(int min, int x, int lim); + +public: + void drawCurrent(); + signed char* signal; + + AnalogTV(ScreenImage& image); + ~AnalogTV(); + + bool isOn() const + { + return this->on; + } + + void powerOn(bool b); + void toggleBleedDown(); + void restartSignal(); + void setType(DisplayType type); + void cycleType(); + void setNoise(bool noise) { this->noise = noise; } +}; + +#endif diff --git a/src/apple2.cpp b/src/apple2.cpp new file mode 100644 index 0000000..e76c29c --- /dev/null +++ b/src/apple2.cpp @@ -0,0 +1,91 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "apple2.h" +#include "slots.h" +#include "videomode.h" +#include "keyboard.h" +#include "addressbus.h" +#include "memory.h" +#include "picturegenerator.h" +#include "textcharacters.h" +#include "video.h" +#include "cpu.h" +#include "paddles.h" +#include "paddlebuttonstates.h" +#include "speakerclicker.h" +#include "analogtv.h" +#include "powerupreset.h" +#include "diskcontroller.h" +#include "languagecard.h" +#include "screenimage.h" + +#include +#include + +Apple2::Apple2(KeypressQueue& keypresses, PaddleButtonStates& paddleButtonStates, AnalogTV& tv, HyperMode& fhyper, KeyboardBufferMode& buffered, ScreenImage& gui): + slts(gui), + kbd(keypresses,fhyper,buffered), + rom(AddressBus::MOTHERBOARD_ROM_SIZ), + ram(AddressBus::MOTHERBOARD_RAM_SIZ), + cassette(gui), + addressBus(ram,rom,kbd,videoMode,paddles,paddleButtonStates,speaker,cassette,slts), + picgen(tv,videoMode,this->revision), + video(videoMode,addressBus,picgen,textRows), + cpu(addressBus), + powerUpReset(*this), + revision(1) +{ +} + +Apple2::~Apple2() +{ +} + + +void Apple2::tick() +{ + this->cpu.tick(); + this->video.tick(); + this->paddles.tick(); + this->speaker.tick(); + this->cassette.tick(); + + if (this->revision > 0) + this->powerUpReset.tick(); +} + +void Apple2::powerOn() +{ + this->ram.powerOn(); + this->cpu.powerOn(); + this->videoMode.powerOn(); + this->video.powerOn(); + this->picgen.powerOn(); + this->powerUpReset.powerOn(); +} + +void Apple2::powerOff() +{ + this->ram.powerOff(); +} + +void Apple2::reset() +{ + this->cpu.reset(); + this->slts.reset(); +} diff --git a/src/apple2.h b/src/apple2.h new file mode 100644 index 0000000..66fd2ad --- /dev/null +++ b/src/apple2.h @@ -0,0 +1,71 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef APPLE2_H +#define APPLE2_H + +#include "timable.h" +#include "slots.h" +#include "videomode.h" +#include "keyboard.h" +#include "addressbus.h" +#include "memory.h" +#include "picturegenerator.h" +#include "textcharacters.h" +#include "video.h" +#include "cpu.h" +#include "paddles.h" +#include "paddlebuttonstates.h" +#include "speakerclicker.h" +#include "analogtv.h" +#include "powerupreset.h" +#include "cassette.h" +class Emulator; +class ScreenImage; + +class Apple2 : public Timable +{ + Slots slts; + VideoMode videoMode; + Keyboard kbd; + Paddles paddles; + SpeakerClicker speaker; + Memory rom; + Memory ram; + Cassette cassette; + AddressBus addressBus; + PictureGenerator picgen; + TextCharacters textRows; + Video video; + CPU cpu; + PowerUpReset powerUpReset; + int revision; + +public: + Apple2(KeypressQueue& keypresses, PaddleButtonStates& paddleButtonStates, AnalogTV& tv, HyperMode& fhyper, KeyboardBufferMode& buffered, ScreenImage& gui); + ~Apple2(); + + void powerOn(); + void powerOff(); + void reset(); + + virtual void tick(); + + friend class Emulator; +}; + +#endif diff --git a/src/applentsc.cpp b/src/applentsc.cpp new file mode 100644 index 0000000..3e4ac13 --- /dev/null +++ b/src/applentsc.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "applentsc.h" diff --git a/src/applentsc.h b/src/applentsc.h new file mode 100644 index 0000000..ffc59d4 --- /dev/null +++ b/src/applentsc.h @@ -0,0 +1,51 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef APPLENTSC_H +#define APPLENTSC_H + +class AppleNTSC +{ +private: + AppleNTSC() {} + +public: + enum { V = 262, H = (25+40)*14+2 }; + enum { SIGNAL_LEN = V*H }; + + enum + { + FP_START = 0, + SYNC_START = FP_START+126, + BP_START = SYNC_START+112, + CB_START = BP_START+0, + CB_END = CB_START+56, + SPIKE = CB_END+34, + PIC_START = CB_END+56 + }; + + enum + { + WHITE_LEVEL = 100, + BLANK_LEVEL = 0, + SYNC_LEVEL = -40, + CB_LEVEL = 20, + LEVEL_RANGE = WHITE_LEVEL-SYNC_LEVEL + }; +}; + +#endif diff --git a/src/card.cpp b/src/card.cpp new file mode 100644 index 0000000..e990275 --- /dev/null +++ b/src/card.cpp @@ -0,0 +1,102 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "card.h" +#include "configep2.h" + +Card::Card(): + rom(0x0100), + seventhRom(0x0800) +{ +} + + +Card::~Card() +{ +} + + +void Card::reset() +{ +} + + + +unsigned char Card::io(const unsigned short /*address*/, const unsigned char data, const bool /*writing*/) +{ + return data; +} + + + +unsigned char Card::readRom(const unsigned short address, const unsigned char data) +{ + this->activeSeventhRom = true; + return this->rom.read(address); +} + +void Card::readSeventhRom(const unsigned short address, unsigned char* const pb) +{ + if (address == 0x7FF) + { + this->activeSeventhRom = false; + } + else if (this->activeSeventhRom && hasSeventhRom()) + { + *pb = this->seventhRom.read(address); + } +} + +void Card::loadRom(const unsigned short base, std::istream& in) +{ + this->rom.load(base,in); +} + +void Card::loadSeventhRom(const unsigned short base, std::istream& in) +{ + this->seventhRom.load(base,in); +} + + + +bool Card::inhibitMotherboardRom() +{ + return false; +} + + + +void Card::ioBankRom(const unsigned short /*addr*/, unsigned char* const /*pb*/, const bool /*write*/) +{ +} + + + +void Card::loadBankRom(const unsigned short /*base*/, std::istream& /*in*/) +{ + throw ConfigException("This card has no $D000 ROM"); +} + +std::string Card::getName() +{ + return ""; +} + +bool Card::isDirty() +{ + return false; +} diff --git a/src/card.h b/src/card.h new file mode 100644 index 0000000..71bc7e8 --- /dev/null +++ b/src/card.h @@ -0,0 +1,51 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CARD_H +#define CARD_H + +#include "memory.h" + +#include +#include + +class Card +{ +private: + bool activeSeventhRom; +protected: + Memory rom; + Memory seventhRom; + +public: + Card(); + virtual ~Card(); + virtual void reset(); + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + virtual unsigned char readRom(const unsigned short address, const unsigned char data); + virtual bool hasSeventhRom() { return false; } + virtual void readSeventhRom(const unsigned short address, unsigned char* const pb); + virtual void loadRom(const unsigned short base, std::istream& in); + virtual void loadSeventhRom(const unsigned short base, std::istream& in); + virtual bool inhibitMotherboardRom(); + virtual void ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write); + virtual void loadBankRom(const unsigned short base, std::istream& in); + virtual bool isDirty(); + virtual std::string getName(); +}; + +#endif diff --git a/src/cassette.cpp b/src/cassette.cpp new file mode 100644 index 0000000..8142b44 --- /dev/null +++ b/src/cassette.cpp @@ -0,0 +1,209 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + + +/* +cassette tape image file format +------------------------------- +Each byte represents one half cycle, in 10-microsoecond units. +For example, consider the following values in the file (decimal): + +65 65 65 65 65 20 25 50 50 25 25 25 25 50 50 + +This represents the following half-cycles (in microseconds) +650 650 650 650 650 200 250 500 500 250 250 250 250 500 500 +which has the following meaning: + +|--------HEADER-----|--sync-|-1-bit-|-0-bit-|-0-bit-|-1-bit-| +| | | | | | | +|650 650 650 650 650|200 250|500 500|250 250|250 250|500 500| +*/ + +#include +#include +#include "cassette.h" + +Cassette::Cassette(ScreenImage& gui): + gui(gui), t(0), prevT(0), markT(0), positive(false) +{ + unload(); +} + + +Cassette::~Cassette() +{ +} + +void Cassette::tick() +{ + ++this->t; + // TODO: check for roll-over of tick-count in Cassette??? + // if (this->t == 0) +} + +void Cassette::output() +{ + if (isWriteProtected() || !isLoaded()) + { + return; + } + if (this->half_cycles.size() <= this->pos) + { + this->half_cycles.push_back(getHalfCycleTime()); + this->pos = this->half_cycles.size(); + this->gui.setCassettePos(this->pos,this->half_cycles.size()); + this->modified = true; + this->gui.setCassetteDirty(true); + } + else + { + // TODO should we allow overwriting of cassestte tape data? + // If so, need to watch out because while reading Apple will + // be calling Cassette::output, too. + } + + this->prevT = this->t; +} + +unsigned char Cassette::getHalfCycleTime() // in 10-microsecond units +{ + const unsigned int delta_cycles(this->t-this->prevT); + if (delta_cycles < 225) + return 20; + if (delta_cycles < 375) + return 25; + if (delta_cycles < 575) + return 50; + return 65; +} + +bool Cassette::input() +{ + if (!isLoaded()) + { + // no tape loaded + return true; + } + + if (this->markT <= this->t) // we've hit our mark + { + this->positive = !this->positive; + if (this->pos < this->half_cycles.size()) + { + // set our next mark to be at the end of next half-cycle read from tape + this->markT = this->t+this->half_cycles[this->pos++]*10; + this->gui.setCassettePos(this->pos,this->half_cycles.size()); + } + } + + return this->positive; +} + +void Cassette::rewind() +{ + this->pos = 0; + this->gui.setCassettePos(this->pos,this->half_cycles.size()); +} + +bool Cassette::newFile(const std::string& filePath) +{ + std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in); + if (in.is_open()) + { + in.close(); + std::cerr << "Error creating file; file already exists: " << filePath << std::endl; + return false; + } + std::ofstream out(filePath.c_str(),std::ios::binary|std::ios::out); + out.close(); + return load(filePath); +} + +bool Cassette::load(const std::string& filePath) +{ +// TODO better I/O error handling during cassette loading and saving + std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in|std::ios::ate); + if (!in.is_open()) + { + std::cerr << "Error loading " << filePath << std::endl; + return false; + } + if (isLoaded()) + { + unload(); + } + + const std::ifstream::pos_type size = in.tellg(); + if (size > 0) + { + this->half_cycles.resize(size); + in.seekg(0,std::ios::beg); + in.read((char*)&this->half_cycles[0],size); +// std::cerr << "Read " << size << " bytes from " << filePath << std::endl; + } + in.close(); + + this->filePath = filePath; + + checkForWriteProtection(); + + this->loaded = true; + this->modified = false; + + this->gui.setCassetteFile(filePath); + this->gui.setCassetteDirty(false); + this->gui.setCassettePos(this->pos,size); + + return true; +} + +void Cassette::checkForWriteProtection() +{ + std::ofstream outf(filePath.c_str(),std::ios::binary|std::ios::app); + this->writable = outf.is_open(); + outf.close(); +} + +void Cassette::save() +{ + if (isWriteProtected() || !isLoaded()) + { + return; + } + std::ofstream out(filePath.c_str(),std::ios::binary); + out.write((char*)&this->half_cycles[0],this->half_cycles.size()); + out.flush(); + out.close(); + + this->modified = false; + this->gui.setCassetteDirty(false); +} + +void Cassette::unload() +{ + this->pos = 0; + this->writable = true; + this->loaded = false; + this->filePath = ""; + this->modified = false; + this->half_cycles.clear(); + this->gui.setCassetteFile(""); + this->gui.setCassetteDirty(false); + this->gui.setCassettePos(this->pos,this->half_cycles.size()); +} diff --git a/src/cassette.h b/src/cassette.h new file mode 100644 index 0000000..b0644c3 --- /dev/null +++ b/src/cassette.h @@ -0,0 +1,83 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CASSETTE_H +#define CASSETTE_H + +#include +#include + +#include "screenimage.h" + +class Cassette +{ +private: + unsigned int t; + unsigned int prevT; + + unsigned int markT; + bool positive; + + std::vector half_cycles; + + std::string fileName; + std::string filePath; + bool writable; + bool loaded; + unsigned int pos; + bool modified; + + ScreenImage& gui; + + void checkForWriteProtection(); + + unsigned char getHalfCycleTime(); // in 10-microsecond units + +public: + Cassette(ScreenImage& gui); + ~Cassette(); + + void tick(); + void output(); + bool input(); + void rewind(); + bool newFile(const std::string& filePath); + bool load(const std::string& filePath); + std::string getFileName() + { + return this->fileName; + } + + bool isLoaded() + { + return this->loaded; + } + + void save(); + void unload(); + bool isWriteProtected() + { + return !this->writable; + } + + bool isModified() + { + return this->modified; + } +}; + +#endif diff --git a/src/clipboardhandler.cpp b/src/clipboardhandler.cpp new file mode 100644 index 0000000..2d73927 --- /dev/null +++ b/src/clipboardhandler.cpp @@ -0,0 +1,230 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "clipboardhandler.h" + +#include +#include +#include + +// taken from: http://www.libsdl.org/projects/scrap (original author: Sam Lantinga) + + +/* Determine what type of clipboard we are using */ +#if defined(__WIN32__) || defined(__CYGWIN__) +#define WIN_SCRAP +#elif defined(__unix__) +#define X11_SCRAP +#else +#error Unknown window manager for clipboard handling +#endif /* scrap type */ + + + +/* System dependent variables */ +#if defined(X11_SCRAP) +static Display *SDL_Display; +static Window SDL_Window; +static void (*Lock_Display)(); +static void (*Unlock_Display)(); +#elif defined(WIN_SCRAP) +static HWND SDL_Window; +#endif /* scrap type */ +static bool initialized(false); +static bool have(false); + + +#ifdef X11_SCRAP +static int clipboard_filter(const SDL_Event *event) +{ + /* Post all non-window manager specific events */ + if ( event->type != SDL_SYSWMEVENT ) { + return(1); + } + + /* Handle window-manager specific clipboard events */ + switch (event->syswm.msg->event.xevent.type) { + /* Copy the selection from XA_CUT_BUFFER0 to the requested property */ + case SelectionRequest: { + XSelectionRequestEvent *req; + XEvent sevent; + int seln_format; + unsigned long nbytes; + unsigned long overflow; + unsigned char *seln_data; + + req = &event->syswm.msg->event.xevent.xselectionrequest; + sevent.xselection.type = SelectionNotify; + sevent.xselection.display = req->display; + sevent.xselection.selection = req->selection; + sevent.xselection.target = None; + sevent.xselection.property = None; + sevent.xselection.requestor = req->requestor; + sevent.xselection.time = req->time; + if ( XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER0, 0, INT_MAX/4, False, req->target, + &sevent.xselection.target, &seln_format, + &nbytes, &overflow, &seln_data) == Success ) + { + if ( sevent.xselection.target == req->target ) + { + if ( sevent.xselection.target == XA_STRING ) + { + if ( seln_data[nbytes-1] == '\0' ) + --nbytes; + } + XChangeProperty(SDL_Display, req->requestor, req->property, + sevent.xselection.target, seln_format, PropModeReplace, + seln_data, nbytes); + sevent.xselection.property = req->property; + } + XFree(seln_data); + } + XSendEvent(SDL_Display,req->requestor,False,0,&sevent); + XSync(SDL_Display, False); + } + break; + } + + /* Post the event for X11 clipboard reading above */ + return(1); +} +#endif /* X11_SCRAP */ + + + + +ClipboardHandler::ClipboardHandler() +{ + if (initialized) + return; + initialized = true; + + SDL_SetError("SDL is not running on known window manager"); + + /* Grab the window manager specific information */ + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (SDL_GetWMInfo(&info)) + { + /* Save the information for later use */ +#if defined(X11_SCRAP) + if (info.subsystem == SDL_SYSWM_X11) + { + SDL_Display = info.info.x11.display; + SDL_Window = info.info.x11.window; + Lock_Display = info.info.x11.lock_func; + Unlock_Display = info.info.x11.unlock_func; + + /* Enable the special window hook events */ + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_SetEventFilter(clipboard_filter); + + have = true; + } + else + { + SDL_SetError("SDL is not running on X11"); + } +#elif defined(WIN_SCRAP) + SDL_Window = info.window; + have = true; +#endif /* scrap type */ + } +} + + +ClipboardHandler::~ClipboardHandler() +{ +} + + +std::string ClipboardHandler::getText() +{ + std::string ret; + if (!have) + return ret; + +#if defined(X11_SCRAP) + const Atom format(XA_STRING); + + Atom selection; + Lock_Display(); + Window owner = XGetSelectionOwner(SDL_Display, XA_PRIMARY); + Unlock_Display(); + if (owner == None || owner == SDL_Window) + { + owner = DefaultRootWindow(SDL_Display); + selection = XA_CUT_BUFFER0; + } + else + { + int selection_response = 0; + SDL_Event event; + + owner = SDL_Window; + Lock_Display(); + selection = XInternAtom(SDL_Display, "SDL_SELECTION", False); + XConvertSelection(SDL_Display, XA_PRIMARY, format, selection, owner, CurrentTime); + Unlock_Display(); + while (!selection_response) + { + SDL_WaitEvent(&event); + if (event.type == SDL_SYSWMEVENT) + { + XEvent xevent = event.syswm.msg->event.xevent; + + if (xevent.type == SelectionNotify && xevent.xselection.requestor == owner) + selection_response = 1; + } + } + } + + + + Lock_Display(); + Atom seln_type; + int seln_format; + unsigned long nbytes; + unsigned long overflow; + char *src; + if (XGetWindowProperty(SDL_Display, owner, selection, 0, INT_MAX/4, False, format, &seln_type, &seln_format, &nbytes, &overflow, (unsigned char **)&src) == Success) + { + if (seln_type == format) + { + ret.assign(src,nbytes); + } + XFree(src); + } + Unlock_Display(); +#elif defined(WIN_SCRAP) + const UINT format(CF_TEXT); + if (IsClipboardFormatAvailable(format) && OpenClipboard(SDL_Window)) + { + const HANDLE hMem = GetClipboardData(format); + if (hMem) + { + char *src = (char*)GlobalLock(hMem); + ret.assign(src); + GlobalUnlock(hMem); + } + CloseClipboard(); + } +#endif /* scrap type */ + + return ret; +} diff --git a/src/clipboardhandler.h b/src/clipboardhandler.h new file mode 100644 index 0000000..5764be3 --- /dev/null +++ b/src/clipboardhandler.h @@ -0,0 +1,33 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CLIPBOARDHANDLER_H +#define CLIPBOARDHANDLER_H + +#include + + +class ClipboardHandler +{ +public: + ClipboardHandler(); + ~ClipboardHandler(); + + std::string getText(); +}; + +#endif diff --git a/src/clockcard.cpp b/src/clockcard.cpp new file mode 100644 index 0000000..699016d --- /dev/null +++ b/src/clockcard.cpp @@ -0,0 +1,70 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "clockcard.h" +#include + +ClockCard::ClockCard(): + latch(0), + pos(0) +{ +} + + +ClockCard::~ClockCard() +{ +} + + + +unsigned char ClockCard::io(const unsigned short address, const unsigned char data, const bool writing) +{ + const int sw = address & 0x0F; + if (sw == 0) + { + if (!(this->latch & 0x80)) + { + if (this->pos == 0) + { + getTime(); + } + char c = this->time[this->pos]; + this->latch = (unsigned char)(c | 0x80); + ++this->pos; + if (this->pos >= this->timelen) + { + this->pos = 0; + } + } + } + else if (sw == 1) + { + this->latch &= 0x7F; + } + return this->latch; +} + +#define TIMEFORMAT "%m,0%w,%d,%H,%M,%S,000,%Y,%Z,D\r" + +void ClockCard::getTime() +{ + time_t now; + ::time(&now); + struct tm* nowtm = ::localtime(&now); + this->timelen = ::strftime(this->time,sizeof(this->time),TIMEFORMAT,nowtm); + this->time[this->timelen-2] = nowtm->tm_isdst>0 ? '1' : '0'; +} diff --git a/src/clockcard.h b/src/clockcard.h new file mode 100644 index 0000000..7e16c0a --- /dev/null +++ b/src/clockcard.h @@ -0,0 +1,42 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CLOCKCARD_H +#define CLOCKCARD_H + +#include "card.h" +#include + +class ClockCard : public Card +{ +private: + unsigned char latch; + unsigned int pos; + char time[64]; + size_t timelen; + + void getTime(); + +public: + ClockCard(); + ~ClockCard(); + + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + virtual std::string getName() { return "clock"; } +}; + +#endif diff --git a/src/configep2.cpp b/src/configep2.cpp new file mode 100644 index 0000000..e8a539f --- /dev/null +++ b/src/configep2.cpp @@ -0,0 +1,425 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "configep2.h" + +#include "memory.h" +#include "slots.h" +#include "diskcontroller.h" +#include "languagecard.h" +#include "firmwarecard.h" +#include "standardout.h" +#include "standardin.h" +#include "clockcard.h" +#include "cassette.h" + +#include +#include +#include +#include +#include +#include + +unsigned char Config::disk_mask(0); + +Config::Config(const std::string& file_path): + file_path(file_path) +{ +} + + +Config::~Config() +{ +} + +static void strip_comment(std::string& str) +{ + const size_t comment = str.find('#'); + if (comment < std::string::npos) + { + str.erase(comment); + } +} + +static void trim(std::string& str) +{ + { + const size_t p = str.find_first_not_of(" \t"); + if (p < std::string::npos) + { + str.erase(0,p); + } + } + { + const size_t p = str.find_last_not_of(" \t"); + if (p+1 < std::string::npos) + { + str.erase(p+1); + } + } +} + +void Config::parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette) +{ + std::ifstream* pConfig; + + std::string path(this->file_path); + + if (!path.empty()) + { + pConfig = new std::ifstream(path.c_str()); + if (!pConfig->is_open()) + { + std::stringstream ss; + ss << "Cannot open config file " << this->file_path.c_str(); + throw std::runtime_error(ss.str()); + } + } + if (path.empty()) + { + /* + On Windows, the default directory will be + C:\Program Files\Epple2 if they start the + program from the Start Menu; therefore + etc/epple2/epple2.conf would be + C:\Program Files\epple2\etc\epple2\epple2.conf + On Linux... the current directory could be + anything, so this probably won't find it (unless + the current directory is /). + */ + path = "etc/epple2/epple2.conf"; + pConfig = new std::ifstream(path.c_str()); + if (!pConfig->is_open()) + path.clear(); + } + if (path.empty()) + { + /* + This is primarily for Linux. If configured for + a PREFIX of "/usr/local", then this would be + /usr/local/etc/epple2/epple2.conf + */ + path = ETCDIR "/epple2/epple2.conf"; + pConfig = new std::ifstream(path.c_str()); + if (!pConfig->is_open()) + path.clear(); + } + if (path.empty()) + { + /* + Last effort to find it (most likely will + only work on Linux). + */ + path = "/etc/epple2/epple2.conf"; + pConfig = new std::ifstream(path.c_str()); + if (!pConfig->is_open()) + path.clear(); + } + if (path.empty()) + { + std::cerr << "Cannot open config file /etc/epple2/epple2.conf" << std::endl; + return; + } + + std::string line; + std::getline(*pConfig,line); + while (!pConfig->eof()) + { + strip_comment(line); + trim(line); + if (!line.empty()) + { + parseLine(line,ram,rom,slts,revision,gui,cassette); + } + std::getline(*pConfig,line); + } + pConfig->close(); + delete pConfig; + + // TODO: make sure there is no more than ONE stdin and/or ONE stdout card +} +void Config::parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette) +{ + try + { + tryParseLine(line,ram,rom,slts,revision,gui,cassette); + } + catch (const ConfigException& err) + { + std::cerr << err.msg.c_str() << std::endl; + } +} + +void Config::tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette) +{ + std::istringstream tok(line); + + std::string cmd; + tok >> cmd; + if (cmd == "slot") + { + int slot; + std::string sCardType; + tok >> slot >> sCardType; + + insertCard(sCardType,slot,slts,gui); + } + else if (cmd == "import") + { + std::string sm; + tok >> sm; + + int slot(-1); + if (sm == "slot") + { + tok >> slot; + } + else if (sm != "motherboard") + { + throw ConfigException("error at \""+sm+"\"; expected \"slot #\" or \"motherboard\""); + } + + std::string romtype; + tok >> romtype; + + unsigned short base(0); + tok >> std::hex >> base; + + std::string file; + std::getline(tok,file); + trim(file); + std::ifstream memfile(file.c_str(),std::ios::binary); + if (!memfile.is_open()) + { + throw ConfigException("cannot open file "+file); + } + + if (slot < 0) // motherboard + { + if (romtype == "rom") + { + rom.load(base,memfile); + } + else if (romtype == "ram") + { + ram.load(base,memfile); + } + else + { + throw ConfigException("error at \""+romtype+"\"; expected rom or ram"); + } + } + else + { + if (8 <= slot) + { + throw ConfigException("invalid slot number"); + } + Card* card = slts.get(slot); + if (romtype == "rom") + card->loadRom(base,memfile); + else if (romtype == "rom7") + card->loadSeventhRom(base,memfile); + else if (romtype == "rombank") + card->loadBankRom(base,memfile); + else + throw ConfigException("error at \""+romtype+"\"; expected rom, rom7, or rombank"); + } + memfile.close(); + } + else if (cmd == "load" || cmd == "save" || cmd == "unload") + { + std::string slotk; + tok >> slotk; + if (slotk != "slot") + { + throw ConfigException("error at \""+slotk+"\"; expected \"slot\""); + } + + int slot(-1); + tok >> slot; + + std::string drivek; + tok >> drivek; + if (drivek != "drive") + { + throw ConfigException("error at \""+drivek+"\"; expected \"drive\""); + } + + int drive(-1); + tok >> drive; + + if (cmd == "load") + { + std::string fnib; + std::getline(tok,fnib); + trim(fnib); + loadDisk(slts,slot,drive,fnib); + } + else if (cmd == "unload") + { + unloadDisk(slts,slot,drive); + } + else if (cmd == "save") + { + saveDisk(slts,slot,drive); + } + } + else if (cmd == "revision") + { + tok >> std::hex >> revision; + } + else if (cmd == "cassette") + { + std::string cas; + tok >> cas; + + if (cas == "rewind") + { + cassette.rewind(); + } + else if (cas == "new") + { + std::string fcas; + std::getline(tok,fcas); + trim(fcas); + cassette.newFile(fcas); + } + else if (cas == "load") + { + std::string fcas; + std::getline(tok,fcas); + trim(fcas); + cassette.load(fcas); + } + else if (cas == "unload") + { + cassette.unload(); + } + else if (cas == "save") + { + cassette.save(); + } + else + { + throw ConfigException("error: unknown cassette command: "+cas); + } + } + else + { + throw ConfigException("Invalid command: "+cmd); + } +} + +void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib) +{ + if (drive < 1 || 2 < drive) + { + throw ConfigException("Invalid drive; must be 1 or 2"); + } + + // TODO if file doesn't exist, name still gets displayed, and there's no error message + Card* card = slts.get(slot); + if (!(disk_mask & (1 << slot))) + { + std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl; + return; + } + + DiskController* controller = (DiskController*)card; + controller->loadDisk(drive-1,fnib); +} + +void Config::unloadDisk(Slots& slts, int slot, int drive) +{ + if (drive < 1 || 2 < drive) + { + throw ConfigException("Invalid drive; must be 1 or 2"); + } + + Card* card = slts.get(slot); + if (!(disk_mask & (1 << slot))) + { + std::cerr << "Slot " << slot << " doesn't have a disk controller card" << std::endl; + return; + } + + DiskController* controller = (DiskController*)card; + controller->unloadDisk(drive-1); +} + +void Config::saveDisk(Slots& slts, int slot, int drive) +{ + if (drive < 1 || 2 < drive) + { + throw ConfigException("Invalid drive; must be 1 or 2"); + } + Card* card = slts.get(slot); + DiskController* controller = (DiskController*)card; + controller->saveDisk(drive-1); +} + +void Config::insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui) +{ + if (slot < 0 || 8 <= slot) + { + throw ConfigException("Invalid slot number"); + } + + Card* card; + + disk_mask &= ~(1 << slot); + + if (cardType == "language") + { + card = new LanguageCard(gui,slot); + } + else if (cardType == "firmware") + { + card = new FirmwareCard(gui,slot); + } + else if (cardType == "disk") + { + card = new DiskController(gui,slot); + disk_mask |= (1 << slot); + } + else if (cardType == "clock") + { + card = new ClockCard(); + } + else if (cardType == "stdout") + { + card = new StandardOut(); + } + else if (cardType == "stdin") + { + card = new StandardIn(); + } + else if (cardType == "empty") + { + card = 0; + } + else + { + throw ConfigException("Invalid card type: "+cardType); + } + + if (card) + slts.set(slot,card); + else + slts.remove(slot); +} diff --git a/src/configep2.h b/src/configep2.h new file mode 100644 index 0000000..287fd89 --- /dev/null +++ b/src/configep2.h @@ -0,0 +1,54 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CONFIG_H +#define CONFIG_H + +#include +class Memory; +class Slots; +class ScreenImage; +class Cassette; + +class ConfigException +{ +public: + const std::string msg; + ConfigException(const std::string& msg) : msg(msg) {} +}; + +class Config +{ +private: + const std::string& file_path; + static unsigned char disk_mask; + + static void loadDisk(Slots& slts, int slot, int drive, const std::string& fnib); + static void unloadDisk(Slots& slts, int slot, int drive); + static void saveDisk(Slots& slts, int slot, int drive); + static void insertCard(const std::string& cardType, int slot, Slots& slts, ScreenImage& gui); + static void tryParseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette); + +public: + Config(const std::string& file_path); + ~Config(); + + void parse(Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette); + static void parseLine(const std::string& line, Memory& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, Cassette& cassette); +}; + +#endif diff --git a/src/cpu.cpp b/src/cpu.cpp new file mode 100644 index 0000000..4216a1e --- /dev/null +++ b/src/cpu.cpp @@ -0,0 +1,2118 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + + Portions Copyright (C) 2000, by Verhille Arnaud, GPLv2 license. (See ADC, SBC below.) +*/ +#include "cpu.h" +#include "addressbus.h" + + + +CPU::CPU(AddressBus& addressBus): + addressBus(addressBus) +{ +} + +CPU::~CPU() +{ +} + +void CPU::powerOn() +{ + this->started = false; + this->pendingReset = false; + this->pendingIRQ = false; + this->pendingNMI = false; + this->p = PMASK_M; +} + +void CPU::reset() +{ + this->started = true; + this->pendingReset = true; + this->t = 0; +} + +void CPU::IRQ() +{ + this->pendingIRQ = true; +} + +void CPU::NMI() +{ + this->pendingNMI = true; +} + +void CPU::tick() +{ + if (!this->started) + { + return; + } + if (!this->t) + { + firstCycle(); + } + else + { + subsequentCycle(); + } + ++this->t; +} + +void CPU::firstCycle() +{ + const bool interrupt = this->pendingNMI || this->pendingReset || (!(this->p & PMASK_I) && this->pendingIRQ); + + if (interrupt) + { + this->pc = getInterruptAddress(); + } + + this->address = this->pc++; + + read(); + + if (interrupt) + { + this->opcode = getInterruptPseudoOpCode(); + } + else + { + this->opcode = this->data; + } +} + +int CPU::getInterruptAddress() +{ + if (this->pendingNMI) + { + return NMI_VECTOR-2; + } + if (this->pendingReset) + { + return RESET_VECTOR-2; + } + if (!(this->p & PMASK_I) && this->pendingIRQ) + { + return IRQ_VECTOR-2; + } + return 0; // can't happen +} + +int CPU::getInterruptPseudoOpCode() +{ + if (this->pendingNMI) + { + return 0x100; + } + if (this->pendingReset) + { + return 0x101; + } + if (!(this->p & PMASK_I) && this->pendingIRQ) + { + return 0x102; + } + return 0; // can't happen +} + +void (CPU::*(CPU::addr[]))() = +{ +&CPU::addr_MISC_BREAK, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_MISC_PUSH, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_MISC_JSR, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_MISC_PULL, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_MISC_RTI, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_MISC_PUSH, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_JMP_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_RTS, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_MISC_PULL, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_JMP_INDIRECT, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_ZERO_PAGE, +&CPU::addr_STORE_ZERO_PAGE, +&CPU::addr_STORE_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_ABSOLUTE, +&CPU::addr_STORE_ABSOLUTE, +&CPU::addr_STORE_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_STORE_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_ZERO_PAGE_XY, +&CPU::addr_STORE_ZERO_PAGE_XY, +&CPU::addr_STORE_ZERO_PAGE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_STORE_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_INTERNAL_INDIRECT_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_INTERNAL_ZERO_PAGE, +&CPU::addr_RMW_ZERO_PAGE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_IMMEDIATE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_INTERNAL_ABSOLUTE, +&CPU::addr_RMW_ABSOLUTE, +&CPU::addr_SINGLE, +&CPU::addr_BRANCH, +&CPU::addr_INTERNAL_INDIRECT_Y, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ZERO_PAGE_XY, +&CPU::addr_RMW_ZERO_PAGE_X, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_SINGLE, +&CPU::addr_INTERNAL_ABSOLUTE_XY, +&CPU::addr_RMW_ABSOLUTE_X, +&CPU::addr_SINGLE, +&CPU::addr_NMI, +&CPU::addr_RESET, +&CPU::addr_IRQ, +}; + +void (CPU::*(CPU::exec[]))() = +{ +&CPU::BRK, +&CPU::ORA, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::ORA, +&CPU::ASL, +&CPU::Unoff, +&CPU::PHP, +&CPU::ORA, +&CPU::ASL_A, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::ORA, +&CPU::ASL, +&CPU::Unoff, +&CPU::BPL, +&CPU::ORA, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::ORA, +&CPU::ASL, +&CPU::Unoff, +&CPU::CLC, +&CPU::ORA, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::ORA, +&CPU::ASL, +&CPU::Unoff, +&CPU::JSR, +&CPU::AND, +&CPU::Hang, +&CPU::Unoff, +&CPU::BIT, +&CPU::AND, +&CPU::ROL, +&CPU::Unoff, +&CPU::PLP, +&CPU::AND, +&CPU::ROL_A, +&CPU::Unoff, +&CPU::BIT, +&CPU::AND, +&CPU::ROL, +&CPU::Unoff, +&CPU::BMI, +&CPU::AND, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::AND, +&CPU::ROL, +&CPU::Unoff, +&CPU::SEC, +&CPU::AND, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::AND, +&CPU::ROL, +&CPU::Unoff, +&CPU::RTI, +&CPU::EOR, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::EOR, +&CPU::LSR, +&CPU::Unoff, +&CPU::PHA, +&CPU::EOR, +&CPU::LSR_A, +&CPU::Unoff, +&CPU::JMP, +&CPU::EOR, +&CPU::LSR, +&CPU::Unoff, +&CPU::BVC, +&CPU::EOR, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::EOR, +&CPU::LSR, +&CPU::Unoff, +&CPU::CLI, +&CPU::EOR, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::EOR, +&CPU::LSR, +&CPU::Unoff, +&CPU::RTS, +&CPU::ADC, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::ADC, +&CPU::ROR, +&CPU::Unoff, +&CPU::PLA, +&CPU::ADC, +&CPU::ROR_A, +&CPU::Unoff, +&CPU::JMP, +&CPU::ADC, +&CPU::ROR, +&CPU::Unoff, +&CPU::BVS, +&CPU::ADC, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::ADC, +&CPU::ROR, +&CPU::Unoff, +&CPU::SEI, +&CPU::ADC, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::ADC, +&CPU::ROR, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::STA, +&CPU::Unoff2, +&CPU::Unoff, +&CPU::STY, +&CPU::STA, +&CPU::STX, +&CPU::Unoff, +&CPU::DEY, +&CPU::Unoff2, +&CPU::TXA, +&CPU::Unoff, +&CPU::STY, +&CPU::STA, +&CPU::STX, +&CPU::Unoff, +&CPU::BCC, +&CPU::STA, +&CPU::Hang, +&CPU::Unoff, +&CPU::STY, +&CPU::STA, +&CPU::STX, +&CPU::Unoff, +&CPU::TYA, +&CPU::STA, +&CPU::TXS, +&CPU::Unoff, +&CPU::Unoff, +&CPU::STA, +&CPU::Unoff, +&CPU::Unoff, +&CPU::LDY, +&CPU::LDA, +&CPU::LDX, +&CPU::Unoff, +&CPU::LDY, +&CPU::LDA, +&CPU::LDX, +&CPU::Unoff, +&CPU::TAY, +&CPU::LDA, +&CPU::TAX, +&CPU::Unoff, +&CPU::LDY, +&CPU::LDA, +&CPU::LDX, +&CPU::Unoff, +&CPU::BCS, +&CPU::LDA, +&CPU::Hang, +&CPU::Unoff, +&CPU::LDY, +&CPU::LDA, +&CPU::LDX, +&CPU::Unoff, +&CPU::CLV, +&CPU::LDA, +&CPU::TSX, +&CPU::Unoff, +&CPU::LDY, +&CPU::LDA, +&CPU::LDX, +&CPU::Unoff, +&CPU::CPY, +&CPU::CMP, +&CPU::Unoff2, +&CPU::Unoff, +&CPU::CPY, +&CPU::CMP, +&CPU::DEC, +&CPU::Unoff, +&CPU::INY, +&CPU::CMP, +&CPU::DEX, +&CPU::Unoff, +&CPU::CPY, +&CPU::CMP, +&CPU::DEC, +&CPU::Unoff, +&CPU::BNE, +&CPU::CMP, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::CMP, +&CPU::DEC, +&CPU::Unoff, +&CPU::CLD, +&CPU::CMP, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::CMP, +&CPU::DEC, +&CPU::Unoff, +&CPU::CPX, +&CPU::SBC, +&CPU::Unoff2, +&CPU::Unoff, +&CPU::CPX, +&CPU::SBC, +&CPU::INC, +&CPU::Unoff, +&CPU::INX, +&CPU::SBC, +&CPU::NOP, +&CPU::Unoff, +&CPU::CPX, +&CPU::SBC, +&CPU::INC, +&CPU::Unoff, +&CPU::BEQ, +&CPU::SBC, +&CPU::Hang, +&CPU::Unoff, +&CPU::Unoff2, +&CPU::SBC, +&CPU::INC, +&CPU::Unoff, +&CPU::SED, +&CPU::SBC, +&CPU::Unoff1, +&CPU::Unoff, +&CPU::Unoff3, +&CPU::SBC, +&CPU::INC, +&CPU::Unoff, +}; + +void CPU::subsequentCycle() +{ + (this->*this->addr[this->opcode])(); +} + + +void CPU::addr_SINGLE() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_IMMEDIATE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_ZERO_PAGE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + adh = 0; + address = ad(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_ABSOLUTE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + address = pc++; + read(); + adh = data; + break; + case 3: + address = ad(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_INDIRECT_X() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + address = bal; + read(); // discard + break; + case 3: + address += x; + address &= 0xFF; + read(); + adl = data; + break; + case 4: + ++address; + address &= 0xFF; + read(); + adh = data; + break; + case 5: + address = ad(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_ABSOLUTE_XY() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + address = pc++; + read(); + bah = data; + break; + case 3: + idx = getIndex(); + wc = ((unsigned short)bal + (unsigned short)idx) >= 0x100; + bal += idx; + address = ba(); + read(); + if (!wc) + { + execute(); + done(); + } + break; + case 4: + ++bah; + address = ba(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_ZERO_PAGE_XY() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + bah = 0; + address = ba(); + read(); // discard + break; + case 3: + idx = getIndex(); + bal += idx; // doesn't leave page zero + address = ba(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_INTERNAL_INDIRECT_Y() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + ial = data; + break; + case 2: + address = ial; + read(); + bal = data; + break; + case 3: + ++address; + address &= 0xFF; // doesn't leave page zero + read(); + bah = data; + break; + case 4: + wc = ((unsigned short)bal + (unsigned short)y) >= 0x100; + bal += y; + address = ba(); + read(); + if (!wc) + { + execute(); + done(); + } + break; + case 5: + ++bah; + address = ba(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_STORE_ZERO_PAGE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + execute(); + break; + case 2: + adh = 0; + address = ad(); + write(); + done(); + break; + } +} + +void CPU::addr_STORE_ABSOLUTE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + address = pc++; + read(); + adh = data; + execute(); + break; + case 3: + address = ad(); + write(); + done(); + break; + } +} + +void CPU::addr_STORE_INDIRECT_X() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + address = bal; + address &= 0xFF; + read(); // discard + break; + case 3: + address += x; + address &= 0xFF; + read(); + adl = data; + break; + case 4: + ++address; + address &= 0xFF; + read(); + adh = data; + execute(); + break; + case 5: + address = ad(); + write(); + done(); + break; + } +} + +void CPU::addr_STORE_ABSOLUTE_XY() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + address = pc++; + read(); + bah = data; + break; + case 3: + idx = getIndex(); + address = ba(); + address += idx; + read(); // discard (assume this is the right address, manual is ambiguous) + execute(); + break; + case 4: + write(); + done(); + break; + } +} + +void CPU::addr_STORE_ZERO_PAGE_XY() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + bah = 0; + address = ba(); + read(); // discard + execute(); + break; + case 3: + idx = getIndex(); + bal += idx; // doesn't leave page zero + address = ba(); + write(); + done(); + break; + } +} + +void CPU::addr_STORE_INDIRECT_Y() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + ial = data; + break; + case 2: + address = ial; + read(); + bal = data; + break; + case 3: + ++address; + address &= 0xFF; + read(); + bah = data; + break; + case 4: + address = ba(); + address += y; + read(); // discard + execute(); + break; + case 5: + write(); + done(); + break; + } +} + +void CPU::addr_RMW_ZERO_PAGE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + adh = 0; + address = ad(); + read(); + break; + case 3: + write(); + execute(); + break; + case 4: + write(); + done(); + break; + } +} + +void CPU::addr_RMW_ABSOLUTE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + address = pc++; + read(); + adh = data; + break; + case 3: + address = ad(); + read(); + break; + case 4: + write(); + execute(); + break; + case 5: + write(); + done(); + break; + } +} + +void CPU::addr_RMW_ZERO_PAGE_X() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + bah = 0; + address = ba(); + read(); // discard + break; + case 3: + bal += x; // doesn't leave page zero + address = ba(); + read(); + break; + case 4: + write(); + execute(); + break; + case 5: + write(); + done(); + break; + } +} + +void CPU::addr_RMW_ABSOLUTE_X() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + bal = data; + break; + case 2: + address = pc++; + read(); + bah = data; + break; + case 3: + address = ba(); + address += x; + read(); // discard + break; + case 4: + read(); + break; + case 5: + write(); + execute(); + break; + case 6: + write(); + done(); + break; + } +} + +void CPU::addr_MISC_PUSH() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + execute(); + break; + case 2: + address = push(); + write(); + done(); + break; + } +} + +void CPU::addr_MISC_PULL() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = sp(); + read(); // discard + break; + case 3: + address = pull(); + read(); + execute(); + done(); + break; + } +} + +void CPU::addr_MISC_JSR() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + address = push(); + read(); // discard + break; + case 3: + data = pch(); + write(); + address = push(); + break; + case 4: + data = pcl(); + write(); + break; + case 5: + address = pc; + read(); + adh = data; + pc = ad(); + done(); + break; + } +} + +void CPU::addr_MISC_BREAK() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = push(); + data = pch(); + write(); + break; + case 3: + address = push(); + data = pcl(); + write(); + break; + case 4: + address = push(); + p |= PMASK_B; + data = p; + write(); + break; + case 5: + address = IRQ_VECTOR; + read(); + adl = data; + break; + case 6: + ++address; + read(); + adh = data; + pc = ad(); + done(); + break; + } +} + +void CPU::addr_MISC_RTI() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = sp(); + read(); // discard + break; + case 3: + address = pull(); + read(); + p = data; p |= PMASK_M; + break; + case 4: + address = pull(); + read(); + adl = data; + break; + case 5: + address = pull(); + read(); + adh = data; + pc = ad(); + done(); + break; + } +} + +void CPU::addr_JMP_ABSOLUTE() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + adl = data; + break; + case 2: + address = pc; + read(); + adh = data; + pc = ad(); + done(); + break; + } +} + +void CPU::addr_JMP_INDIRECT() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); + ial = data; + break; + case 2: + address = pc; + read(); + iah = data; + break; + case 3: + address = ia(); + read(); + adl = data; + break; + case 4: + /* Interactive [newsletter] (Rockwell Intl., 1980), Issue 2, + currently available here: + http://www.6502.org/documents/publications/interactive/aim_interactive_2.pdf + on page 12, documents a bug where JMP absolute does NOT + leave the page. This causes problems for + example for JMP ($08FF), where high byte + is read from $800 instead of $900. + */ + ++ial; // emulate the bug here (don't touch iah) + address = ia(); + read(); + adh = data; + pc = ad(); + done(); + break; + } +} + +void CPU::addr_RTS() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = sp(); + read(); // discard + break; + case 3: + address = pull(); + read(); + adl = data; + break; + case 4: + address = pull(); + read(); + adh = data; + break; + case 5: + pc = ad(); + address = pc; + read(); // discard + ++pc; + done(); + break; + } +} + +void CPU::addr_BRANCH() +{ + signed short lo; + switch (this->t) + { + case 1: + address = pc++; + read(); + offset = (signed char)data; + execute(); + if (!branch) + { + done(); + } + break; + case 2: + lo = pcl()+offset; + sc = 0; + if (lo < 0) + { + lo += 0x100; + sc = -1; + } + else if (lo >= 0x100) + { + lo -= 0x100; + sc = 1; + } + pc = (pc & 0xFF00) | lo; + address = pc; + read(); + if (sc == 0) + { + done(); + } + break; + case 3: + unsigned short hi = pch() + sc; + pc = (hi << 8) | pcl(); + read(); + done(); + break; + } +} + +void CPU::addr_NMI() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = push(); + data = pch(); + write(); + break; + case 3: + address = push(); + data = pcl(); + write(); + break; + case 4: + address = push(); + p |= PMASK_I; + p &= ~PMASK_B; // ??? + data = p; + write(); + break; + case 5: + ++address; + read(); + adl = data; + break; + case 6: + ++address; + read(); + adh = data; + pc = ad(); + pendingNMI = false; + done(); + break; + } +} + +void CPU::addr_RESET() +{ + switch (this->t) + { + case 1: + address = pc++; + read(); // discard + break; + case 2: + s = 0xFF; // real CPU doesn't do this ??? + address = push(); + data = pch(); + read(); // discard + break; + case 3: + address = push(); + data = pcl(); + read(); // discard + break; + case 4: + address = push(); + p |= PMASK_I; + data = p; + read(); // discard + break; + case 5: + address = pc++; + read(); + adl = data; + break; + case 6: + address = pc; + read(); + adh = data; + pc = ad(); + pendingReset = false; + done(); + break; + } +} + +void CPU::addr_IRQ() +{ + switch (this->t) + { + case 1: + address = pc; + read(); // discard + break; + case 2: + address = push(); + data = pch(); + write(); + break; + case 3: + address = push(); + data = pcl(); // ??? + write(); + break; + case 4: + address = push(); + p |= PMASK_I; + p &= ~PMASK_B; // ??? + data = p; + write(); + break; + case 5: + address = pc++; + read(); + adl = data; + break; + case 6: + address = pc; + read(); + adh = data; + pc = ad(); + pendingIRQ = false; + done(); + break; + } +} + +void CPU::read() +{ + this->data = this->addressBus.read(this->address); +} + +void CPU::write() +{ + this->addressBus.write(this->address,this->data); +} + +void CPU::execute() +{ + (this->*this->exec[this->opcode])(); +} + +void CPU::done() +{ + this->t = -1; +} + + +unsigned char CPU::pch() +{ + return (unsigned char)(this->pc >> 8); +} + +unsigned char CPU::pcl() +{ + return (unsigned char)(this->pc); +} + +unsigned short CPU::sp() +{ + return 0x100+this->s; +} + +unsigned short CPU::push() +{ + const unsigned short psp = sp(); + --this->s; + return psp; +} + +unsigned short CPU::pull() +{ + ++this->s; + return sp(); +} + +unsigned char CPU::getIndex() +{ + // opcode: aaabbbcc + const int aaa = (this->opcode & 0xE0) >> 5; + int bbb = (this->opcode & 0x1C) >> 2; + int cc = (this->opcode & 0x03); + if (bbb == 0) + { + return this->x; + } + if (bbb == 4 || bbb == 6) + { + return this->y; + } + if (bbb == 5 || bbb == 7) + { + if (cc == 2 && (aaa == 4 || aaa == 5)) + { + return this->y; + } + return this->x; + } + return 0; +} + +unsigned short CPU::ad() +{ + return combine(this->adl,this->adh); +} + +unsigned short CPU::ia() +{ + return combine(this->ial,this->iah); +} + +unsigned short CPU::ba() +{ + return combine(this->bal,this->bah); +} + +unsigned short CPU::combine(const unsigned char lo, const unsigned char hi) +{ + return hi << 8 | lo; +} + +void CPU::setStatusRegisterNZ(const unsigned char val) +{ + setP(PMASK_N,val & 0x80); + setP(PMASK_Z,!val); +} + +void CPU::LDA() +{ + this->a = this->data; + setStatusRegisterNZ(this->a); +} + +void CPU::LDX() +{ + this->x = this->data; + setStatusRegisterNZ(this->x); +} + +void CPU::LDY() +{ + this->y = this->data; + setStatusRegisterNZ(this->y); +} + +void CPU::STA() +{ + this->data = this->a; +} + +void CPU::STX() +{ + this->data = this->x; +} + +void CPU::STY() +{ + this->data = this->y; +} + +void CPU::compare(const unsigned char r) +{ + const signed short tmp = r - this->data; + setP(PMASK_C,0 <= tmp && tmp < 0x100); + setStatusRegisterNZ((const signed char)tmp); +} + +void CPU::CMP() +{ + compare(this->a); +} + +void CPU::CPX() +{ + compare(this->x); +} + +void CPU::CPY() +{ + compare(this->y); +} + +void CPU::AND() +{ + this->a &= this->data; + setStatusRegisterNZ(this->a); +} + +void CPU::ORA() +{ + this->a |= this->data; + setStatusRegisterNZ(this->a); +} + +void CPU::EOR() +{ + this->a ^= this->data; + setStatusRegisterNZ(this->a); +} + + + + + + +void CPU::ASL() +{ + this->data = shiftLeft(this->data); +} + +void CPU::ASL_A() +{ + this->a = shiftLeft(this->a); +} + +void CPU::LSR() +{ + this->data = shiftRight(this->data); +} + +void CPU::LSR_A() +{ + this->a = shiftRight(this->a); +} + +void CPU::ROL() +{ + this->data = rotateLeft(this->data); +} + +void CPU::ROL_A() +{ + this->a = rotateLeft(this->a); +} + +void CPU::ROR() +{ + this->data = rotateRight(this->data); +} + +void CPU::ROR_A() +{ + this->a = rotateRight(this->a); +} + +unsigned char CPU::shiftLeft(unsigned char byt) +{ + setP(PMASK_C,byt & 0x80); + byt <<= 1; + setStatusRegisterNZ(byt); + return byt; +} + +unsigned char CPU::shiftRight(unsigned char byt) +{ + setP(PMASK_C,byt & 0x01); + byt >>= 1; + setStatusRegisterNZ(byt); + return byt; +} + +unsigned char CPU::rotateLeft(unsigned char byt) +{ + const bool newCarry = (byt & 0x80); + + byt <<= 1; + + if (this->p & PMASK_C) + { + byt |= 0x01; + } + + setP(PMASK_C,newCarry); + setStatusRegisterNZ(byt); + + return byt; +} + +unsigned char CPU::rotateRight(unsigned char byt) +{ + const bool newCarry = (byt & 0x01); + + byt >>= 1; + + if (this->p & PMASK_C) + { + byt |= 0x80; + } + + setP(PMASK_C,newCarry); + setStatusRegisterNZ(byt); + + return byt; +} + + + + + + +void CPU::ADC() +{ + /* + This method based on ADC from the POM1 Apple 1 emulator. + Copyright (C) 2000, by Verhille Arnaud, GPLv2 license. + */ + int Op1 = this->a; + int Op2 = this->data; + if (this->p & PMASK_D) + { + setP(PMASK_Z,!(Op1 + Op2 + !!(this->p & PMASK_C) & 0xff)); + int tmp = (Op1 & 0xf) + (Op2 & 0xf) + !!(this->p & PMASK_C); + tmp = tmp >= 10 ? tmp + 6 : tmp; + this->a = tmp; + tmp = (Op1 & 0xf0) + (Op2 & 0xf0) + (tmp & 0xf0); + setP(PMASK_N,tmp < 0); + setP(PMASK_V,((Op1 ^ tmp) & ~(Op1 ^ Op2) & 0x80)); + tmp = this->a & 0xf | (tmp >= 160 ? tmp + 96 : tmp); + setP(PMASK_C,tmp >= 0x100); + this->a = tmp & 0xff; + } + else + { + int tmp = Op1 + Op2 + !!(this->p & PMASK_C); + this->a = tmp & 0xFF; + setP(PMASK_V,((Op1 ^ this->a) & ~(Op1 ^ Op2) & 0x80)); + setP(PMASK_C,tmp >= 0x100); + setStatusRegisterNZ(this->a); + } +} + +void CPU::SBC() +{ + /* + This method based on SBC from the POM1 Apple 1 emulator. + Copyright (C) 2000, by Verhille Arnaud, GPLv2 license. + */ + int Op1 = this->a; + int Op2 = this->data; + if (this->p & PMASK_D) + { + int tmp = (Op1 & 0xf) - (Op2 & 0xf) - !(this->p & PMASK_C); + tmp = (tmp & 0x10) != 0 ? tmp - 6 : tmp; + this->a = tmp; + tmp = (Op1 & 0xf0) - (Op2 & 0xf0) - (this->a & 0x10); + this->a = this->a & 0xf | ((tmp & 0x100) != 0 ? tmp - 96 : tmp); + tmp = Op1 - Op2 - !(this->p & PMASK_C); + setP(PMASK_C,0 <= tmp && tmp < 0x100); + setStatusRegisterNZ(tmp); + } + else + { + int tmp = Op1 - Op2 - !(this->p & PMASK_C); + this->a = tmp & 0xff; + setP(PMASK_V,((Op1 ^ Op2) & (Op1 ^ this->a) & 0x80)); + setP(PMASK_C,0 <= tmp && tmp < 0x100); + setStatusRegisterNZ(this->a); + } +} + + + + +void CPU::INC() +{ + ++this->data; + setStatusRegisterNZ(this->data); +} + +void CPU::DEC() +{ + --this->data; + setStatusRegisterNZ(this->data); +} + +void CPU::INX() +{ + ++this->x; + setStatusRegisterNZ(this->x); +} + +void CPU::INY() +{ + ++this->y; + setStatusRegisterNZ(this->y); +} + +void CPU::DEX() +{ + --this->x; + setStatusRegisterNZ(this->x); +} + +void CPU::DEY() +{ + --this->y; + setStatusRegisterNZ(this->y); +} + +void CPU::setP(const unsigned char mask, const unsigned char val) +{ + if (val) + this->p |= mask; + else + this->p &= ~mask; +} + +void CPU::BIT() +{ + setP(PMASK_V,this->data & 0x40); + setP(PMASK_N,this->data & 0x80); + setP(PMASK_Z,!(this->data & this->a)); +} + +void CPU::PHA() +{ + this->data = this->a; +} + +void CPU::PHP() +{ + this->data = p; +} + +void CPU::PLA() +{ + this->a = this->data; + setStatusRegisterNZ(this->a); +} + +void CPU::PLP() +{ + this->p = this->data; + this->p |= PMASK_M; +} + +void CPU::BRK() +{ +} + +void CPU::RTI() +{ +} + +void CPU::JMP() +{ +} + +void CPU::RTS() +{ +} + +void CPU::JSR() +{ +} + +void CPU::BNE() +{ + this->branch = !(this->p & PMASK_Z); +} + +void CPU::BEQ() +{ + this->branch = this->p & PMASK_Z; +} + +void CPU::BVC() +{ + this->branch = !(this->p & PMASK_V); +} + +void CPU::BVS() +{ + this->branch = this->p & PMASK_V; +} + +void CPU::BCC() +{ + this->branch = !(this->p & PMASK_C); +} + +void CPU::BCS() +{ + this->branch = this->p & PMASK_C; +} + +void CPU::BPL() +{ + this->branch = !(this->p & PMASK_N); +} + +void CPU::BMI() +{ + this->branch = this->p & PMASK_N; +} + +void CPU::TAX() +{ + this->x = this->a; + setStatusRegisterNZ(this->x); +} + +void CPU::TXA() +{ + this->a = this->x; + setStatusRegisterNZ(this->a); +} + +void CPU::TAY() +{ + this->y = this->a; + setStatusRegisterNZ(this->y); +} + +void CPU::TYA() +{ + this->a = this->y; + setStatusRegisterNZ(this->a); +} + +void CPU::TXS() +{ + this->s = this->x; + // make sure this doesn't affect status register + // it doesn't +} + +void CPU::TSX() +{ + this->x = this->s; + setStatusRegisterNZ(this->x); + // make sure this does affect status register + // it does +} + +void CPU::CLC() +{ + this->p &= ~PMASK_C; +} + +void CPU::SEC() +{ + this->p |= PMASK_C; +} + +void CPU::CLI() +{ + this->p &= ~PMASK_I; +} + +void CPU::SEI() +{ + this->p |= PMASK_I; +} + +void CPU::CLV() +{ + this->p &= ~PMASK_V; +} + +void CPU::CLD() +{ + this->p &= ~PMASK_D; +} + +void CPU::SED() +{ + this->p |= PMASK_D; +} + +void CPU::NOP() +{ +} + +// TODO implement 6502 undocumented instructions +// See: +// "The 6502/65C02/65C816 Instruction Set Decoded," by Neil Parker, http://axis.llx.com/~nparker/a2/opcodes.html +// "Extra Instructions Of The 65XX Series CPU," by Adam Vardy, http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes +void CPU::Unoff() +{ +} + +void CPU::Unoff1() +{ +} + +void CPU::Unoff2() +{ + this->pc++; +} + +void CPU::Unoff3() +{ + this->pc += 2; +} + +void CPU::Hang() +{ + this->pc--; +} diff --git a/src/cpu.h b/src/cpu.h new file mode 100644 index 0000000..fd60367 --- /dev/null +++ b/src/cpu.h @@ -0,0 +1,217 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef CPU_H +#define CPU_H + +class AddressBus; + +class CPU +{ +private: + enum { MEMORY_LIM = 1 << 0x10 }; + enum { IRQ_VECTOR = MEMORY_LIM-2 }; // or BRK + enum { RESET_VECTOR = IRQ_VECTOR-2 }; // or power-on + enum { NMI_VECTOR = RESET_VECTOR-2 }; + + unsigned char adl; + unsigned char adh; + unsigned char bal; + unsigned char bah; + unsigned char ial; + unsigned char iah; + unsigned char idx; + signed char offset; + bool branch; + signed char sc; + bool wc; + + bool pendingIRQ; + bool pendingNMI; + bool pendingReset; + + bool started; + + unsigned char a; + unsigned char x; + unsigned char y; + + unsigned char s; + + //p = NVMBDIZC + enum { PMASK_C = 1<<0 }; + enum { PMASK_Z = 1<<1 }; + enum { PMASK_I = 1<<2 }; + enum { PMASK_D = 1<<3 }; + enum { PMASK_B = 1<<4 }; + enum { PMASK_M = 1<<5 }; + enum { PMASK_V = 1<<6 }; + enum { PMASK_N = 1<<7 }; + unsigned char p; + + unsigned short pc; + + AddressBus& addressBus; + + unsigned short address; + unsigned char data; + + unsigned short opcode; + + signed char t; + + + static void (CPU::*addr[])(); + static void (CPU::*exec[])(); + + void firstCycle(); + int getInterruptAddress(); + int getInterruptPseudoOpCode(); + void subsequentCycle(); + + void read(); + void write(); + void execute(); + void done(); + + unsigned char pch(); + unsigned char pcl(); + unsigned short sp(); + unsigned short push(); + unsigned short pull(); + unsigned char getIndex(); + unsigned short ad(); + unsigned short ia(); + unsigned short ba(); + unsigned short combine(const unsigned char lo, const unsigned char hi); + void setP(const unsigned char mask, const unsigned char val); + void setStatusRegisterNZ(const unsigned char val); + unsigned char shiftLeft(unsigned char byt); + unsigned char shiftRight(unsigned char byt); + unsigned char rotateLeft(unsigned char byt); + unsigned char rotateRight(unsigned char byt); + void compare(const unsigned char r); + + void addr_SINGLE(); + void addr_INTERNAL_IMMEDIATE(); + void addr_INTERNAL_ZERO_PAGE(); + void addr_INTERNAL_ABSOLUTE(); + void addr_INTERNAL_INDIRECT_X(); + void addr_INTERNAL_ABSOLUTE_XY(); + void addr_INTERNAL_ZERO_PAGE_XY(); + void addr_INTERNAL_INDIRECT_Y(); + void addr_STORE_ZERO_PAGE(); + void addr_STORE_ABSOLUTE(); + void addr_STORE_INDIRECT_X(); + void addr_STORE_ABSOLUTE_XY(); + void addr_STORE_ZERO_PAGE_XY(); + void addr_STORE_INDIRECT_Y(); + void addr_RMW_ZERO_PAGE(); + void addr_RMW_ABSOLUTE(); + void addr_RMW_ZERO_PAGE_X(); + void addr_RMW_ABSOLUTE_X(); + void addr_MISC_PUSH(); + void addr_MISC_PULL(); + void addr_MISC_JSR(); + void addr_MISC_BREAK(); + void addr_MISC_RTI(); + void addr_JMP_ABSOLUTE(); + void addr_JMP_INDIRECT(); + void addr_RTS(); + void addr_BRANCH(); + void addr_NMI(); + void addr_RESET(); + void addr_IRQ(); + + void LDA(); + void LDX(); + void LDY(); + void STA(); + void STX(); + void STY(); + void CMP(); + void CPX(); + void CPY(); + void AND(); + void ORA(); + void EOR(); + void ASL(); + void ASL_A(); + void LSR(); + void LSR_A(); + void ROL(); + void ROL_A(); + void ROR(); + void ROR_A(); + void ADC(); + void SBC(); + void INC(); + void DEC(); + void INX(); + void INY(); + void DEX(); + void DEY(); + void BIT(); + void PHA(); + void PHP(); + void PLA(); + void PLP(); + void BRK(); + void RTI(); + void JMP(); + void RTS(); + void JSR(); + void BNE(); + void BEQ(); + void BVC(); + void BVS(); + void BCC(); + void BCS(); + void BPL(); + void BMI(); + void TAX(); + void TXA(); + void TAY(); + void TYA(); + void TXS(); + void TSX(); + void CLC(); + void SEC(); + void CLI(); + void SEI(); + void CLV(); + void CLD(); + void SED(); + void NOP(); + void Unoff(); + void Unoff1(); + void Unoff2(); + void Unoff3(); + void Hang(); + +public: + CPU(AddressBus& addressBus); + ~CPU(); + + void powerOn(); + void reset(); + void IRQ(); + void NMI(); + void tick(); +}; + +#endif diff --git a/src/diskbytes.cpp b/src/diskbytes.cpp new file mode 100644 index 0000000..fd2fb75 --- /dev/null +++ b/src/diskbytes.cpp @@ -0,0 +1,131 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "diskbytes.h" + +#include +#include +#include +#include +#include + +DiskBytes::DiskBytes() +{ + unload(); +} + +DiskBytes::~DiskBytes() +{ +} + +bool DiskBytes::load(const std::string& filePath) +{ + + +// TODO better I/O error handling during disk loading and saving + std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in); + if (!in.is_open()) + { + return false; + } + if (isLoaded()) + { + unload(); + } + for (int t(0); t < TRACKS_PER_DISK; ++t) + { + this->bytes[t].resize(BYTES_PER_TRACK); + in.read((char*)&this->bytes[t][0],BYTES_PER_TRACK); + } + in.close(); + + this->filePath = filePath; + + checkForWriteProtection(); + + this->loaded = true; + this->modified = false; + + return true; +} + +void DiskBytes::checkForWriteProtection() +{ + std::ofstream outf(filePath.c_str(),std::ios::binary|std::ios::app); + this->writable = outf.is_open(); + outf.close(); +} + +void DiskBytes::save() +{ + if (isWriteProtected() || !isLoaded()) + { + return; + } + std::ofstream out(filePath.c_str(),std::ios::binary); + for (int t(0); t < TRACKS_PER_DISK; ++t) + out.write((char*)&this->bytes[t][0],BYTES_PER_TRACK); + out.flush(); + out.close(); + + this->modified = false; +} + +void DiskBytes::unload() +{ + this->byt = 0; + this->writable = true; + this->loaded = false; + this->filePath = ""; + this->modified = false; +} + +unsigned char DiskBytes::get(const int track) +{ + if (!isLoaded()) + { + return 0xFF; + } + const unsigned char ret = this->bytes[track][this->byt]; + nextByte(); + return ret; +} + +void DiskBytes::put(const unsigned char track, const unsigned char value) +{ + if (TRACKS_PER_DISK <= track) + { + throw 0; + } + if (isWriteProtected() || !isLoaded()) + { + return; + } + this->bytes[track][this->byt] = value; + this->modified = true; + nextByte(); +} + +void inline DiskBytes::nextByte() +{ + // emulates circular disk track + ++this->byt; + if (this->byt >= BYTES_PER_TRACK) + { + this->byt = 0; + } +} diff --git a/src/diskbytes.h b/src/diskbytes.h new file mode 100644 index 0000000..81dee36 --- /dev/null +++ b/src/diskbytes.h @@ -0,0 +1,72 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef DISKBYTES_H +#define DISKBYTES_H + +#include +#include + +class DiskBytes +{ +private: + enum { TRACKS_PER_DISK = 0x23 }; + enum { BYTES_PER_TRACK = 0x1A00 }; + + std::vector bytes[TRACKS_PER_DISK]; + + std::string fileName; + std::string filePath; + bool writable; + bool loaded; + unsigned int byt; // represents rotational position of disk + bool modified; + + void nextByte(); + void checkForWriteProtection(); + +public: + DiskBytes(); + ~DiskBytes(); + + bool load(const std::string& filePath); + std::string getFileName() + { + return this->fileName; + } + + bool isLoaded() + { + return this->loaded; + } + + void save(); + void unload(); + unsigned char get(const int track); + void put(const unsigned char track, const unsigned char value); + bool isWriteProtected() + { + return !this->writable; + } + + bool isModified() + { + return this->modified; + } +}; + +#endif diff --git a/src/diskcontroller.cpp b/src/diskcontroller.cpp new file mode 100644 index 0000000..db2bc2e --- /dev/null +++ b/src/diskcontroller.cpp @@ -0,0 +1,82 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "diskcontroller.h" + +DiskController::DiskController(ScreenImage& gui, int slot): + gui(gui), + slot(slot), + drive1(diskBytes1,arm1), + drive2(diskBytes2,arm2), + currentDrive(&this->drive1) +{ +} + +DiskController::~DiskController() +{ +} + +unsigned char DiskController::io(const unsigned short addr, const unsigned char d, const bool writing) +{ + unsigned char data(d); + const unsigned char q = (addr & 0x000E) >> 1; + const bool on = (addr & 0x0001); + + switch (q) + { + case 0: + case 1: + case 2: + case 3: + this->currentDrive->setMagnet(q,on); + this->gui.setTrack(this->slot,getCurrentDriveNumber(),getTrack()); + break; + case 4: + this->motorOn = on; + this->gui.setIO(this->slot,getCurrentDriveNumber(),on); + break; + case 5: + this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber()); + this->currentDrive = (on ? &this->drive2 : &this->drive1); + this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),this->motorOn); + break; + case 6: + if (on && this->write && writing) + { + set(data); + this->gui.setIO(this->slot,getCurrentDriveNumber(),this->motorOn); + this->gui.setDirty(this->slot,getCurrentDriveNumber(),true); + } + else if (!(on || this->write)) + { + data = get(); + } + break; + case 7: + this->write = on; + if (this->currentDrive->isWriteProtected()) + { + data |= 0x80; + } + else + { + data &= 0x7F; + } + break; + } + return data; +} diff --git a/src/diskcontroller.h b/src/diskcontroller.h new file mode 100644 index 0000000..264f0ed --- /dev/null +++ b/src/diskcontroller.h @@ -0,0 +1,166 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "card.h" +#include "drive.h" +#include "screenimage.h" +#include +#include + +class DiskController : public Card +{ +private: + ScreenImage& gui; + int slot; + DiskBytes diskBytes1; + StepperMotor arm1; + Drive drive1; + + DiskBytes diskBytes2; + StepperMotor arm2; + Drive drive2; + + Drive* currentDrive; + + bool write; + bool motorOn; + + + // TODO for a rev. 0 motherboard, the disk controller will auto reset the CPU + + void set(unsigned char data) + { + if (!this->motorOn) + { + return; + } + this->currentDrive->set(data); + } + + unsigned char get() const + { + if (!this->motorOn) + { + return 0xFF; + } + return this->currentDrive->get(); + } + + Drive& getDrive(const unsigned char drive) + { + return (drive == 0) ? this->drive1 : this->drive2; + } + + Drive& getOtherDrive() + { + return (this->currentDrive == &this->drive1) ? this->drive2 : this->drive1; + } + + + +public: + DiskController(ScreenImage& gui, int slot); + ~DiskController(); + + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + + void reset() + { + this->gui.setIO(this->slot,getCurrentDriveNumber(),false); + this->gui.clearCurrentDrive(this->slot,getCurrentDriveNumber()); + + this->currentDrive = &this->drive1; + this->motorOn = false; + + this->gui.setCurrentDrive(this->slot,getCurrentDriveNumber(),getTrack(),false); + } + + void loadDisk(unsigned char drive, const std::string& fnib) + { + if (!this->getDrive(drive).loadDisk(fnib)) + { + return; + } + this->gui.setDiskFile(this->slot,drive,fnib); + this->gui.setDirty(this->slot,getCurrentDriveNumber(),false); + } + + void unloadDisk(unsigned char drive) + { + this->getDrive(drive).unloadDisk(); + this->gui.setDiskFile(this->slot,drive,""); + this->gui.setDirty(this->slot,getCurrentDriveNumber(),false); + } + + void saveDisk(unsigned char drive) + { + this->getDrive(drive).saveDisk(); + this->gui.setDirty(this->slot,getCurrentDriveNumber(),false); + } + + bool isMotorOn() + { + return this->motorOn; + } + + const DiskBytes& getDiskBytes(unsigned char disk) + { + return this->getDrive(disk).getDiskBytes(); + } + + unsigned char getTrack() + { + return this->currentDrive->getTrack(); + } + + bool isWriting() + { + return this->write; + } + + bool isModified() + { + return this->currentDrive->isModified(); + } + + bool isModifiedOther() + { + return getOtherDrive().isModified(); + } + + bool isWriteProtected() + { + return this->currentDrive->isWriteProtected(); + } + + bool isDirty() + { + return isModified() || isModifiedOther(); + } + + unsigned char getCurrentDriveNumber() + { + return this->currentDrive == &this->drive1 ? 0 : 1; + } + + unsigned char getOtherDriveNumber() + { + return 1-getCurrentDriveNumber(); + } + + virtual std::string getName() { return "disk][ drive 1 drive 2 "; } +}; diff --git a/src/drive.cpp b/src/drive.cpp new file mode 100644 index 0000000..a71d940 --- /dev/null +++ b/src/drive.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "drive.h" diff --git a/src/drive.h b/src/drive.h new file mode 100644 index 0000000..9342846 --- /dev/null +++ b/src/drive.h @@ -0,0 +1,104 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef DRIVE_H +#define DRIVE_H + +#include + +#include "diskbytes.h" +#include "steppermotor.h" + +class Drive +{ +private: + enum { TRACKS_PER_DISK = 0x23 }; + + DiskBytes& disk; + StepperMotor& arm; + +public: + Drive(DiskBytes& disk, StepperMotor& arm): + disk(disk), + arm(arm) + { + } + + ~Drive() {} + + bool loadDisk(const std::string& fnib) + { + return this->disk.load(fnib); + } + + void unloadDisk() + { + this->disk.unload(); + } + bool isLoaded() + { + return this->disk.isLoaded(); + } + + void saveDisk() + { + this->disk.save(); + } + + bool isWriteProtected() const + { + return this->disk.isWriteProtected(); + } + + bool isModified() const + { + return this->disk.isModified(); + } + + + + void setMagnet(unsigned char q, bool on) + { + this->arm.setMagnet(q,on); + } + + int getTrack() const + { + return this->arm.getTrack(); + } + + + + unsigned char get() const + { + return this->disk.get(this->arm.getTrack()); + } + + void set(unsigned char value) + { + this->disk.put(this->arm.getTrack(),value); + } + + + + const DiskBytes& getDiskBytes() + { + return this->disk; + } +}; + +#endif diff --git a/src/e2const.h b/src/e2const.h new file mode 100644 index 0000000..e35c655 --- /dev/null +++ b/src/e2const.h @@ -0,0 +1,169 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef E2CONST_H +#define E2CONST_H + +class E2Const +{ +public: + /* + The NTSC standard defines the field rate as 60 fields per second. The number 60 + is based on the USA AC current frequency of 60 Hz. This, in turn, was based on the + clock standard (60 seconds per minute and 60 minutes per hour). + */ + static const int NTSC_FIELD_HZ = 60; + + /* + The NTSC standard defines 525 lines per frame, which was chosen to be a multiple + of a small number of standard tubes at the time, to produce a rate between RCA's + recommended 441 (used by NBC) and Philco's suggested 600-800 lines. + */ + static const int NTSC_LINES_PER_FRAME = 3*5*5*7; + + /* + When color was added to the NTSC signal, studies by General Electric showed that + minimum interference was achieved using a subcarrier frequency 455 times the field + rate, which can also be obtained using standard tubes. + */ + static const int NTSC_COLOR_MULTIPLE = 5*7*13; + + /* + Adding color to NTSC also required slowing down the frame rate, by dropping one + field after every 1000. + */ + static const int NTSC_COLOR_DROP_FIELD = 1000; + + /* + Calculate the color sub-channel rate, times 4. + This will be the (approximate) Hz of the "14M" + crystal oscillator in the Apple ][. + 14318181.818181818... Hz rounds to 14318182 Hz + U.A.II, p.3-2 + */ + static const int CRYSTAL_HZ = (int)(1.0F*NTSC_FIELD_HZ * NTSC_LINES_PER_FRAME * NTSC_COLOR_MULTIPLE * NTSC_COLOR_DROP_FIELD / (NTSC_COLOR_DROP_FIELD+1)); + + /* + U.A.II, p. 3-3 + Normal 6502 cycle == 14 crystal periods + Long 6502 cycle == 16 crystal periods + */ + static const int CRYSTAL_CYCLES_PER_CPU_CYCLE = 14; + static const int EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE = 2; + + /* + 65 bytes per row (64 normal CPU cycles plus one long CPU cycle) + */ + static const int BYTES_PER_ROW = (int)((NTSC_COLOR_DROP_FIELD+1)*1.0F*CRYSTAL_HZ/(NTSC_FIELD_HZ/2*NTSC_COLOR_DROP_FIELD*NTSC_LINES_PER_FRAME*CRYSTAL_CYCLES_PER_CPU_CYCLE)); + static const int HORIZ_CYCLES = BYTES_PER_ROW; + + /* + U.A.II, p. 3-2, "composite frequency... 1.0205 MHz" + Actually 1020484 Hz. + */ + static const int AVG_CPU_HZ = (int)((1.0F*CRYSTAL_HZ*HORIZ_CYCLES)/(CRYSTAL_CYCLES_PER_CPU_CYCLE*HORIZ_CYCLES+EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE)); + + /* + A normal NTSC field is 262.5 lines (half of a full frame's 525 lines). + The Apple rounds this down to 262 lines. + */ + static const int NTSC_WHOLE_LINES_PER_FIELD = NTSC_LINES_PER_FRAME/2; + + static const int BYTES_PER_FIELD = BYTES_PER_ROW*NTSC_WHOLE_LINES_PER_FIELD; + + + + + + + // exactly 1 million + static const int MEGA = 1000000; + + static const int VISIBLE_BITS_PER_BYTE = 7; + static const int VISIBLE_LINES_PER_CHARACTER = 8; + + /* + * 1000+1 seconds 2 fields 1 frame 1000000 microseconds 63 50 + * total horizontal line period = -------------- * -------- * ------------- * -------------------- = ( -- + -- ) microseconds per line + * 60*1000 fields 1 frame 3*5*5*7 lines 1 second 90 + * + * 10 81 + * horizontal blanking period = (1.5+4.7+.6+2.5+1.6) = 10.9 microseconds per line = ( -- + -- ) microseconds per line + * 90 + * + * visible line period = total horizontal line period minus horizontal blanking period = + * + * 52 59 + * -- + -- microseconds per line + * 90 + * + * + * To avoid the over-scan area, the Apple ][ uses only the middle 75% of the visible line, or 4739/120 microseconds + * + * Apple ][ uses half the clock rate, or 315/44 MHz, to oscillate the video signal. + * + * The result is 315/44 MHz * 4739/120 microseconds/line, rounded down, = 282 full pixel spots across the screen. + * The Apple ][ displays 7 bits per byte hi-res or lo-res, (or 7 pixel-wide characters for text mode), so that + * gives 282/7, which rounds down to 40 bytes per line. + */ + static const int VISIBLE_BYTES_PER_ROW = (int)((((1.0F*(NTSC_COLOR_DROP_FIELD+1)/(NTSC_FIELD_HZ*NTSC_COLOR_DROP_FIELD)*2/NTSC_LINES_PER_FRAME*MEGA)-(1.5+4.7+.6+2.5+1.6)) * 3/4) * (CRYSTAL_HZ/2)) / MEGA / VISIBLE_BITS_PER_BYTE; + + /* + * NTSC total lines per frame (525) minus unusable lines (19 plus 20) = 486 usable lines + * To avoid the over-scan area, use the middle 80% of the vertical lines, giving 388 (rounded down) clearly visible lines + * Apple ][ uses only half the vertical resolution because it doesn't interlace, giving 194. + * Text characters are 8 pixels tall, so 194/8 rounded down gives 24 text lines. + * Multiply by 8 to give 192 lines total. + */ + static const int VISIBLE_ROWS_PER_FIELD = (NTSC_LINES_PER_FRAME-(20+19)) * 8/10 / 2 /VISIBLE_LINES_PER_CHARACTER*VISIBLE_LINES_PER_CHARACTER; + + static const int BLANKED_BYTES_PER_ROW = BYTES_PER_ROW-VISIBLE_BYTES_PER_ROW; + static const int VISIBLE_BYTES_PER_FIELD = BYTES_PER_ROW*VISIBLE_ROWS_PER_FIELD; + static const int SCANNABLE_ROWS = 0x100; + static const int SCANNABLE_BYTES = SCANNABLE_ROWS*BYTES_PER_ROW; + static const int RESET_ROWS = NTSC_WHOLE_LINES_PER_FIELD-SCANNABLE_ROWS; + static const int RESET_BYTES = RESET_ROWS*BYTES_PER_ROW; + + + + static const int MIXED_TEXT_LINES = 4; + static const int ROWS_PER_TEXT_LINE = 8; + static const int MIXED_TEXT_CYCLE = (VISIBLE_ROWS_PER_FIELD-MIXED_TEXT_LINES*ROWS_PER_TEXT_LINE)*BYTES_PER_ROW; + + + static int test() + { + if (NTSC_FIELD_HZ!=60) return NTSC_FIELD_HZ; + if (NTSC_LINES_PER_FRAME!=525) return NTSC_LINES_PER_FRAME; + if (NTSC_COLOR_MULTIPLE!=455) return NTSC_COLOR_MULTIPLE; + if (NTSC_COLOR_DROP_FIELD!=1000) return NTSC_COLOR_DROP_FIELD; + if (CRYSTAL_HZ!=14318182) return CRYSTAL_HZ; + if (BYTES_PER_ROW!=65) return BYTES_PER_ROW; + if (AVG_CPU_HZ!=1020484) return AVG_CPU_HZ; + if (BYTES_PER_FIELD!=17030) return BYTES_PER_FIELD; + if (VISIBLE_BYTES_PER_ROW!=40) return VISIBLE_BYTES_PER_ROW; + if (VISIBLE_ROWS_PER_FIELD!=192) return VISIBLE_ROWS_PER_FIELD; + if (RESET_BYTES!=390) return RESET_BYTES; + if (BLANKED_BYTES_PER_ROW!=25) return BLANKED_BYTES_PER_ROW; + if (VISIBLE_BYTES_PER_FIELD!=12480) return VISIBLE_BYTES_PER_FIELD; + if (SCANNABLE_BYTES!=16640) return SCANNABLE_BYTES; + return -1; + } +}; + + +#endif diff --git a/src/emptyslot.cpp b/src/emptyslot.cpp new file mode 100644 index 0000000..2ca8e4f --- /dev/null +++ b/src/emptyslot.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "emptyslot.h" diff --git a/src/emptyslot.h b/src/emptyslot.h new file mode 100644 index 0000000..59c1975 --- /dev/null +++ b/src/emptyslot.h @@ -0,0 +1,36 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef EMPTYSLOT_H +#define EMPTYSLOT_H + +#include "card.h" + +class EmptySlot : public Card +{ +public: + EmptySlot() {} + virtual ~EmptySlot() {} + + virtual std::string getName() { return "empty"; } + + // empty slots have no ROMs, so just return data (for floating bus emulation) + virtual unsigned char readRom(const unsigned short address, const unsigned char data) { return data; } + virtual void readSeventhRom(const unsigned short address, unsigned char* const pb) { } +}; + +#endif diff --git a/src/emulator.cpp b/src/emulator.cpp new file mode 100644 index 0000000..c1205c4 --- /dev/null +++ b/src/emulator.cpp @@ -0,0 +1,461 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "emulator.h" +#include "configep2.h" +#include "e2const.h" + +#include + +#include + +Emulator::Emulator(): + display(screenImage), + videoStatic(display), + apple2(keypresses,paddleButtonStates,display,fhyper,buffered,screenImage), + timable(0), // No ticked object (NULL pointer) + quit(false), + repeat(false), + keysDown(0), + command(false), + pendingCommandExit(false) +{ +} + + +Emulator::~Emulator() +{ +} + +void Emulator::toggleComputerPower() +{ + if (this->timable==&this->videoStatic) + powerOnComputer(); + else + powerOffComputer(); +} + +void Emulator::powerOnComputer() +{ + this->apple2.powerOn(); + this->screenImage.drawPower(true); + this->display.setNoise(false); + + // The apple2 becomes the ticked object + this->timable = &this->apple2; +} + +void Emulator::powerOffComputer() +{ + // TODO Need to ask user if OK to lose any unsaved changes to disks + this->apple2.powerOff(); + this->screenImage.drawPower(false); + this->display.setNoise(true); + this->videoStatic.powerOn(); + + // The video static becomes the ticked object + this->timable = &this->videoStatic; +} + +void Emulator::config(Config& cfg) +{ + cfg.parse(this->apple2.ram,this->apple2.rom,this->apple2.slts,this->apple2.revision,this->screenImage,this->apple2.cassette); +} + +void Emulator::init() +{ + powerOffComputer(); + this->display.setType(AnalogTV::MONITOR_COLOR); + this->display.powerOn(true); +} + + // How many emulation ticks between asking SDL if there is any new input + // from the user or other GUI events. + // This is also how often we shall update the estimate of the emulator's + // actual speed performance + // When the CPU is the object being ticked (each tick is a CPU cycle), then + // this is 20.04378892 Hz in emulated seconds time +#define CHECK_EVERY_CYCLE 51024 +#define CHECK_CYCLES_K 51024000 +#define EXPECTED_MS 50 + +// U.A.2 p. 7-13: REPT key repeats at 10Hz. +static const int CYCLES_PER_REPT(E2Const::AVG_CPU_HZ/10); + + + // The core of this Apple +int Emulator::run() +{ + int skip = CHECK_EVERY_CYCLE; + Uint32 prev_ms = SDL_GetTicks(); + // While the user still wants to run this emulation... + while (!this->quit) + { + // (Obligatory protection against NULL object pointer) + if (this->timable) + { + this->timable->tick(); + // If the Apple ][ keyboard repeat is on (the REPT key is + // down)... + if (this->repeat) + { + // Count our way down to when the timer for the REPT key + // fires off: 10Hz in terms of how many CPU cycles have gone + // by + --this->rept; + // If it's time for the REPT key timer to fire (at long + // last)... + if (this->rept <= 0) + { + // ...reload the timer for the next firing 1/10 second from + // now ( *reset* the timer ) + this->rept = CYCLES_PER_REPT; + // If any other keys are actually being held down... + if (this->keysDown > 0) + { + // ...REPEAT the most recent one that was pressed + this->keypresses.push(this->lastKeyDown); + } + } + } + } + + // People who have too many press releases should be referred to as + // keyboards + + --skip; + // If skip has been decremented to zero... + if (!skip) + { + // ...then it's time to drain away any piled-up user interaction + // events that SDL has stored up for us + // Reload the skip quantity + skip = CHECK_EVERY_CYCLE; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + // If SDL is going away... + case SDL_QUIT: + this->quit = true; + break; + case SDL_KEYDOWN: + // If we're collecting a command line for changing any + // of the configurables of the emulator... + if (this->command) + cmdKey(event.key); + else + // ...else we're collecting keypresses for the keyboard + // emulation (and thus the Apple ][ emulation itself) + dispatchKeypress(event.key); + break; + case SDL_KEYUP: + // If we're collecting a command line for changing any + // of the configurables of the emulator... + if (this->command) + { + if (this->pendingCommandExit) + { + this->command = false; + this->pendingCommandExit = false; + } + } + else + { + // ...else we're collecting keypresses for the keyboard + // emulation (and thus the Apple ][ emulation itself) + dispatchKeyUp(event.key); + } + break; + } + } + // If we're trying to run as slow as a real Apple ][... + if (!this->fhyper.isHyper()) + { + const int delta_ms = EXPECTED_MS-(SDL_GetTicks()-prev_ms); + if (0 < delta_ms && delta_ms <= EXPECTED_MS) + { + SDL_Delay(delta_ms); + } + + } + // Display the current estimate of the emulator's actual speed + // performance + this->screenImage.displayHz(CHECK_CYCLES_K/(SDL_GetTicks()-prev_ms)); + prev_ms = SDL_GetTicks(); + } + } + return 0; +} + +void Emulator::dispatchKeyUp(const SDL_KeyboardEvent& keyEvent) +{ + unsigned char key = (unsigned char)(keyEvent.keysym.unicode & 0x7F); + SDLKey sym = keyEvent.keysym.sym; + SDLMod mod = keyEvent.keysym.mod; + unsigned char scancode = keyEvent.keysym.scancode; +// printf("key UP: %d sym: %d mod: %04X scn: %d\n",key,sym,mod,scancode); + + if ((sym < 0x7F || sym == SDLK_LEFT || sym == SDLK_RIGHT) && + !(sym == SDLK_TAB || sym == SDLK_BACKQUOTE || sym == '[' || sym == '\\' || sym == SDLK_DELETE) && + !(sym == ']' && mod&KMOD_SHIFT)) + { + --this->keysDown; + } + // ...else if this is the emulated REPT key on the Apple keyboard... + else if (sym == SDLK_F10) + { + // ...stop repeating. The key has been released + this->repeat = false; + this->rept = 0; + } +} + + // Take real-world keystrokes from SDL and filter them to emulate the + // Apple ][ or Apple ][ plus keyboard +void Emulator::dispatchKeypress(const SDL_KeyboardEvent& keyEvent) +{ + unsigned char key = (unsigned char)(keyEvent.keysym.unicode & 0x7F); + SDLKey sym = keyEvent.keysym.sym; + SDLMod mod = keyEvent.keysym.mod; + unsigned char scancode = keyEvent.keysym.scancode; + +// printf("key DN: %d sym: %d mod: %04X scn: %d\n",key,sym,mod,scancode); + + if ((sym < 0x7F || sym == SDLK_LEFT || sym == SDLK_RIGHT) && + !(sym == SDLK_TAB || sym == SDLK_BACKQUOTE || sym == '[' || sym == '\\' || sym == SDLK_DELETE) && + !(sym == ']' && mod&KMOD_SHIFT)) + { + ++this->keysDown; + } + + if (sym == SDLK_LEFT) + { + key = 8; + } + else if (sym == SDLK_RIGHT) + { + key = 21; + } + else if (sym == SDLK_PAUSE) + { + this->apple2.reset(); + return; + } + else if (sym == SDLK_INSERT) + { + // Feed input from the clipboard to the Apple keyboard + std::string s = this->clip.getText(); + for (unsigned int i = 0; i < s.length(); ++i) + { + key = s[i]; + // TODO fix pasting line-endings + if (key == '\n') + key = '\r'; + if ('a' <= key && key <= 'z') + { + key -= 32; + } + this->keypresses.push(key); + } + return; + } + // ...else if this is the emulated REPT key on the Apple keyboard... + else if (sym == SDLK_F10) + { + // ...start auto-repeat + this->repeat = true; + this->rept = CYCLES_PER_REPT; + return; + } + // ...else if the user wants to run at full speed instead of emulating + // the Apple's speed... + else if (sym == SDLK_F11) + { + this->fhyper.toggleHyper(); + this->screenImage.toggleHyperLabel(); + return; + } + else if (sym == SDLK_F12) + { + this->buffered.toggleBuffered(); + this->screenImage.toggleKdbBufferLabel(); + return; + } + // ...else if the user has hit the rocker switch on the back of the Apple... + else if (sym == SDLK_F1) + { + toggleComputerPower(); + return; + } + // ...else if the user wants to look at a different video display medium... + else if (sym == SDLK_F2) + { + this->display.cycleType(); + this->screenImage.cycleDisplayLabel(); + return; + } + // ...else if the user wants to switch to/from full screen and an + // individual application window... + else if (sym == SDLK_F3) + { + this->screenImage.toggleFullScreen(); + this->screenImage.drawPower(this->timable==&this->apple2); + return; + } + // ...else if the user wants to switch between the interlaced extension + // of the display and the non-interlaced historically correct display... + else if (sym == SDLK_F4) + { + this->display.toggleBleedDown(); + this->screenImage.toggleFillLinesLabel(); + return; + } + // ...else initiate command line entry at the bottom of the emulator window + else if (sym == SDLK_F5) + { + this->command = true; + this->screenImage.enterCommandMode(); + } + // ...else exit the entire emulation + else if (sym == SDLK_END) + { + this->quit = true; + return; + } + // ...else save a screen shot + else if (sym == SDLK_PRINT) + { + this->screenImage.saveBMP(); + } + + // The unmodified Apple ][ hardware keyboard only generates upper-case + if ('a' <= key && key <= 'z') + { + key -= 32; + } + + if ((mod&KMOD_SHIFT) && (mod&KMOD_CTRL) && sym == '2') + { + // Ctrl-Shift-2 == Ctrl-@ == NUL == ASCII: 0 + key = 0; + } + else if ((mod&KMOD_SHIFT) && (mod&KMOD_CTRL) && sym == ' ') + { + // Ctrl-Shift-Space is the same as Space + key = ' '; + } + else if ((mod&KMOD_CTRL) && !(mod&KMOD_SHIFT) && SDLK_KP0 <= sym && sym <= SDLK_KP9) + { + // Control-only numeric keypad keys are converted to regular digit keys + key = sym-SDLK_KP0+'0'; + } + else if ((mod&KMOD_CTRL) && !(mod&KMOD_SHIFT) && (('0' <= sym && sym <= '9') || sym == '/' || sym == ' ')) + { + // Control-only upon 0-9, / and space leaves them unchanged, the + // same as unmodified + key = sym; + } + else if (sym == ']') + { + if (mod&KMOD_SHIFT) + { + // ignore '}' (shift ']') + return; + } + if (mod&KMOD_CTRL) + { + // Ctrl-] == ASCII: $1D + key = 29; + } + } + // ...else if this is one of the keys that can't be typed on an Apple ][ + // keyboard... + else if (key == 0 || sym == SDLK_TAB || sym == SDLK_BACKQUOTE || sym == '[' || sym == '\\' || sym == SDLK_DELETE) + { + return; + } + + +// printf(" sending to apple as ascii------------------------------>%02X (%02X)\n",key,key|0x80); + this->keypresses.push(key); + this->lastKeyDown = key; +} + + // Collect and edit a command line typed at the bottom of the emulator + // window +void Emulator::cmdKey(const SDL_KeyboardEvent& keyEvent) +{ + unsigned char key = (unsigned char)(keyEvent.keysym.unicode & 0x7F); + SDLKey sym = keyEvent.keysym.sym; + if (sym == SDLK_RETURN) + { + processCommand(); + } + else if (sym == SDLK_ESCAPE) + { + cmdline.erase(cmdline.begin(),cmdline.end()); + processCommand(); + } + else if (sym == SDLK_BACKSPACE) + { + if (cmdline.length()) + { + cmdline.erase(cmdline.end()-1); + this->screenImage.backspaceCommand(); + } + } + else if (sym == SDLK_INSERT) + { + std::string s = this->clip.getText(); + for (unsigned int i = 0; i < s.length(); ++i) + { + key = s[i]; + if (key == '\n' || key == '\r') + { + processCommand(); + break; + } + else + { + cmdline += key; + this->screenImage.addkeyCommand(key); + } + } + } + else if (key) + { + cmdline += key; + this->screenImage.addkeyCommand(key); + } +} + + // Process a command line typed at the bottom of the emulator window +void Emulator::processCommand() +{ + this->screenImage.exitCommandMode(); + this->pendingCommandExit = true; + + if (cmdline.empty()) + { + return; + } + + Config::parseLine(cmdline,this->apple2.ram,this->apple2.rom,this->apple2.slts,this->apple2.revision,this->screenImage,this->apple2.cassette); + cmdline.erase(cmdline.begin(),cmdline.end()); +} diff --git a/src/emulator.h b/src/emulator.h new file mode 100644 index 0000000..ed66c97 --- /dev/null +++ b/src/emulator.h @@ -0,0 +1,80 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef EMULATOR_H +#define EMULATOR_H + +#include "keyboard.h" +#include "paddlebuttonstates.h" +#include "apple2.h" +#include "videostaticgenerator.h" +#include "screenimage.h" +#include "analogtv.h" +#include "keyboardbuffermode.h" +#include "hypermode.h" +#include "clipboardhandler.h" + +class Timable; +class Config; +struct SDL_KeyboardEvent; + +class Emulator +{ + PaddleButtonStates paddleButtonStates; + KeypressQueue keypresses; + + HyperMode fhyper; + KeyboardBufferMode buffered; + ScreenImage screenImage; + AnalogTV display; + VideoStaticGenerator videoStatic; + Apple2 apple2; + ClipboardHandler clip; + + Timable* timable; + + bool quit; + bool repeat; + int keysDown; + int rept; + unsigned char lastKeyDown; + bool command; + bool pendingCommandExit; + std::string cmdline; + + void dispatchKeypress(const SDL_KeyboardEvent& keyEvent); + void dispatchKeyUp(const SDL_KeyboardEvent& keyEvent); + void cmdKey(const SDL_KeyboardEvent& keyEvent); + void processCommand(); + +public: + Emulator(); + virtual ~Emulator(); + + void config(Config& cfg); + + virtual void init(); + + void powerOnComputer(); + void powerOffComputer(); + void toggleComputerPower(); + void cycleDisplayType(); + + virtual int run(); +}; + +#endif diff --git a/src/firmwarecard.cpp b/src/firmwarecard.cpp new file mode 100644 index 0000000..6ced79d --- /dev/null +++ b/src/firmwarecard.cpp @@ -0,0 +1,60 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "firmwarecard.h" +#include "memory.h" + +FirmwareCard::FirmwareCard(ScreenImage& gui, int slot): + gui(gui), + slot(slot), + inhibitBankRom(false), + inhibitF8Rom(false), + inhibit(false), + bankRom(0x10000-0xD000) +{ +} + + +FirmwareCard::~FirmwareCard() +{ +} + + + + + + +void FirmwareCard::ioBankRom(const unsigned short addr, unsigned char* const pb, const bool) +{ + this->inhibit = false; + if (addr < 0x2800) + { + if (this->inhibitBankRom) + { + *pb = this->bankRom.read(addr); + this->inhibit = true; + } + } + else if (0x2800 <= addr && addr < 0x3000) + { + if (this->inhibitF8Rom) + { + *pb = this->bankRom.read(addr); + this->inhibit = true; + } + } +} diff --git a/src/firmwarecard.h b/src/firmwarecard.h new file mode 100644 index 0000000..9503932 --- /dev/null +++ b/src/firmwarecard.h @@ -0,0 +1,73 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef FIRMWARECARD_H +#define FIRMWARECARD_H + +#include "card.h" +#include "memory.h" +#include "screenimage.h" + +class FirmwareCard : public Card +{ +private: + ScreenImage& gui; + int slot; + bool inhibitBankRom; + bool inhibitF8Rom; + bool inhibit; + Memory bankRom; + +public: + FirmwareCard(ScreenImage& gui, int slot); + ~FirmwareCard(); + + virtual void ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write); + + virtual void reset() + { + this->inhibitBankRom = false; + this->inhibitF8Rom = false; + this->gui.setFirmCard(this->slot,this->inhibitBankRom,this->inhibitF8Rom); + } + + + + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing) + { + this->inhibitBankRom = !(address & 1); + this->inhibitF8Rom = (address & 2); + this->gui.setFirmCard(this->slot,this->inhibitBankRom,this->inhibitF8Rom); + return data; + } + + virtual void loadBankRom(const unsigned short base, std::istream& in) + { + this->bankRom.load(base,in); + } + + + + virtual bool inhibitMotherboardRom() + { + return this->inhibit; + } + + virtual std::string getName() { return "firmware "; } +}; + +#endif diff --git a/src/font3x5.h b/src/font3x5.h new file mode 100644 index 0000000..e2bc01b --- /dev/null +++ b/src/font3x5.h @@ -0,0 +1,693 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#define FONTW 4 +#define FONTH 6 + +static const char* font3x5 = +"----" +"----" +"----" +"----" +"----" +"----" + +"-@--" +"-@--" +"-@--" +"----" +"-@--" +"----" + +"@-@-" +"@-@-" +"----" +"----" +"----" +"----" + +"-@-@" +"@@@@" +"@-@-" +"@@@@" +"@-@-" +"----" + +"-@@@" +"@@--" +"-@@-" +"-@-@" +"@@@-" +"-@--" + +"@-@-" +"--@-" +"-@--" +"@---" +"@-@-" +"----" + +"-@--" +"@-@-" +"-@--" +"@-@-" +"@@-@" +"----" + +"-@--" +"-@--" +"----" +"----" +"----" +"----" + +"--@-" +"-@--" +"-@--" +"-@--" +"--@-" +"----" + +"@---" +"-@--" +"-@--" +"-@--" +"@---" +"----" + +"@--@" +"-@@-" +"@@@@" +"-@@-" +"@--@" +"----" + +"----" +"-@--" +"@@@-" +"-@--" +"----" +"----" + +"----" +"----" +"----" +"----" +"-@--" +"@---" + +"----" +"----" +"@@@-" +"----" +"----" +"----" + +"----" +"----" +"----" +"----" +"-@--" +"----" + +"--@-" +"--@-" +"-@--" +"@---" +"@---" +"----" + +"-@--" +"@-@-" +"@@@-" +"@-@-" +"-@--" +"----" + +"-@--" +"@@--" +"-@--" +"-@--" +"-@--" +"----" + +"@@--" +"--@-" +"-@--" +"@---" +"@@@-" +"----" + +"@@--" +"--@-" +"@@--" +"--@-" +"@@--" +"----" + +"@-@-" +"@-@-" +"@@@-" +"--@-" +"--@-" +"----" + +"@@@-" +"@---" +"@@--" +"--@-" +"@@--" +"----" + +"-@@-" +"@---" +"@@--" +"@-@-" +"-@--" +"----" + +"@@@-" +"--@-" +"-@--" +"-@--" +"-@--" +"----" + +"-@--" +"@-@-" +"-@--" +"@-@-" +"-@--" +"----" + +"-@--" +"@-@-" +"-@@-" +"--@-" +"@@--" +"----" + +"----" +"----" +"-@--" +"----" +"-@--" +"----" + +"----" +"----" +"-@--" +"----" +"-@--" +"@---" + +"--@-" +"-@--" +"@---" +"-@--" +"--@-" +"----" + +"----" +"@@@-" +"----" +"@@@-" +"----" +"----" + +"@---" +"-@--" +"--@-" +"-@--" +"@---" +"----" + +"@@--" +"--@-" +"-@--" +"----" +"-@--" +"----" + +"-@@-" +"@-@@" +"@-@@" +"@---" +"-@@-" +"----" + +"-@--" +"@-@-" +"@@@-" +"@-@-" +"@-@-" +"----" + +"@@--" +"@-@-" +"@@--" +"@-@-" +"@@--" +"----" + +"-@@-" +"@---" +"@---" +"@---" +"-@@-" +"----" + +"@@--" +"@-@-" +"@-@-" +"@-@-" +"@@--" +"----" + +"@@@-" +"@---" +"@@@-" +"@---" +"@@@-" +"----" + +"@@@-" +"@---" +"@@--" +"@---" +"@---" +"----" + +"-@@-" +"@---" +"@-@-" +"@-@-" +"-@@-" +"----" + +"@-@-" +"@-@-" +"@@@-" +"@-@-" +"@-@-" +"----" + +"-@--" +"-@--" +"-@--" +"-@--" +"-@--" +"----" + +"--@-" +"--@-" +"--@-" +"@-@-" +"-@--" +"----" + +"@-@-" +"@-@-" +"@@--" +"@-@-" +"@-@-" +"----" + +"@---" +"@---" +"@---" +"@---" +"@@@-" +"----" + +"@-@-" +"@@@-" +"@@@-" +"@-@-" +"@-@-" +"----" + +"@@--" +"@-@-" +"@-@-" +"@-@-" +"@-@-" +"----" + +"-@--" +"@-@-" +"@-@-" +"@-@-" +"-@--" +"----" + +"@@--" +"@-@-" +"@@--" +"@---" +"@---" +"----" + +"-@--" +"@-@-" +"@-@-" +"@@@-" +"-@@-" +"----" + +"@@--" +"@-@-" +"@@--" +"@-@-" +"@-@-" +"----" + +"-@@-" +"@---" +"-@--" +"--@-" +"@@--" +"----" + +"@@@-" +"-@--" +"-@--" +"-@--" +"-@--" +"----" + +"@-@-" +"@-@-" +"@-@-" +"@-@-" +"@@@-" +"----" + +"@-@-" +"@-@-" +"@-@-" +"@-@-" +"-@--" +"----" + +"@-@-" +"@-@-" +"@@@-" +"@@@-" +"@-@-" +"----" + +"@-@-" +"@-@-" +"-@--" +"@-@-" +"@-@-" +"----" + +"@-@-" +"@-@-" +"-@--" +"-@--" +"-@--" +"----" + +"@@@-" +"--@-" +"-@--" +"@---" +"@@@-" +"----" + +"-@@-" +"-@--" +"-@--" +"-@--" +"-@@-" +"----" + +"@---" +"@---" +"-@--" +"--@-" +"--@-" +"----" + +"@@--" +"-@--" +"-@--" +"-@--" +"@@--" +"----" + +"-@--" +"@-@-" +"----" +"----" +"----" +"----" + +"----" +"----" +"----" +"----" +"@@@-" +"----" + +"-@--" +"--@-" +"----" +"----" +"----" +"----" + +"----" +"----" +"-@@-" +"@-@-" +"-@@-" +"----" + +"@---" +"@---" +"@@--" +"@-@-" +"@@--" +"----" + +"----" +"----" +"-@@-" +"@---" +"-@@-" +"----" + +"--@-" +"--@-" +"-@@-" +"@-@-" +"-@@-" +"----" + +"----" +"----" +"@@@-" +"@@--" +"-@@-" +"----" + +"--@-" +"-@--" +"@@@-" +"-@--" +"-@--" +"----" + +"----" +"----" +"-@@-" +"@-@-" +"-@@-" +"@@--" + +"@---" +"@---" +"@@--" +"@-@-" +"@-@-" +"----" + +"-@--" +"----" +"-@--" +"-@--" +"-@--" +"----" + +"-@--" +"----" +"-@--" +"-@--" +"-@--" +"@---" + +"@---" +"@---" +"@-@-" +"@@--" +"@-@-" +"----" + +"-@--" +"-@--" +"-@--" +"-@--" +"-@--" +"----" + +"----" +"----" +"@@@-" +"@@@-" +"@-@-" +"----" + +"----" +"----" +"@@--" +"@-@-" +"@-@-" +"----" + +"----" +"----" +"-@--" +"@-@-" +"-@--" +"----" + +"----" +"----" +"@@--" +"@-@-" +"@@--" +"@---" + +"----" +"----" +"-@@-" +"@-@-" +"-@@-" +"--@-" + +"----" +"----" +"@@@-" +"@---" +"@---" +"----" + +"----" +"----" +"-@@-" +"-@--" +"@@--" +"----" + +"----" +"-@--" +"@@@-" +"-@--" +"-@--" +"----" + +"----" +"----" +"@-@-" +"@-@-" +"-@@-" +"----" + +"----" +"----" +"@-@-" +"@-@-" +"-@--" +"----" + +"----" +"----" +"@-@-" +"@@@-" +"@@@-" +"----" + +"----" +"----" +"@-@-" +"-@--" +"@-@-" +"----" + +"----" +"----" +"@-@-" +"@-@-" +"-@--" +"@---" + +"----" +"----" +"@@--" +"-@--" +"-@@-" +"----" + +"--@-" +"-@--" +"@@--" +"-@--" +"--@-" +"----" + +"-@--" +"-@--" +"-@--" +"-@--" +"-@--" +"-@--" + +"@---" +"-@--" +"-@@-" +"-@--" +"@---" +"----" + +"-@-@" +"@-@-" +"----" +"----" +"----" +"----" + +"@@@@" +"@@@@" +"@@@@" +"@@@@" +"@@@@" +"@@@@" +; diff --git a/src/gui.cpp b/src/gui.cpp new file mode 100644 index 0000000..a38e51f --- /dev/null +++ b/src/gui.cpp @@ -0,0 +1,50 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gui.h" +#include + + // Create, initialize, and cable together the UI objects to serve this + // program +GUI::GUI() +{ + const int result = SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO); + + if (result != 0) + { + throw GUI::NotInitException(); + } + + SDL_EnableUNICODE(1); + SDL_ShowCursor(0); + SDL_EnableKeyRepeat(0,0); +} + +GUI::~GUI() +{ + SDL_Quit(); +} + +GUI::NotInitException::NotInitException() : + runtime_error("Unable to initialize SDL") +{ + SDL_GetError(); +} diff --git a/src/gui.h b/src/gui.h new file mode 100644 index 0000000..ffd8ffe --- /dev/null +++ b/src/gui.h @@ -0,0 +1,38 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef GUI_H +#define GUI_H + +#include + +class GUI +{ +public: + GUI(); + ~GUI(); + + class NotInitException : public std::runtime_error + { + public: + NotInitException(); + virtual ~NotInitException() throw () {} + }; + +}; + +#endif diff --git a/src/hypermode.cpp b/src/hypermode.cpp new file mode 100644 index 0000000..24b5de0 --- /dev/null +++ b/src/hypermode.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "hypermode.h" diff --git a/src/hypermode.h b/src/hypermode.h new file mode 100644 index 0000000..905ef54 --- /dev/null +++ b/src/hypermode.h @@ -0,0 +1,45 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef HYPERMODE_H +#define HYPERMODE_H + +class HyperMode +{ +private: + bool fhyper; + +public: + HyperMode(): fhyper(false) { } + ~HyperMode() { } + bool isHyper() + { + return this->fhyper; + } + + void setHyper(bool isHyper) + { + this->fhyper = isHyper; + } + + void toggleHyper() + { + this->fhyper = !this->fhyper; + } +}; + +#endif diff --git a/src/keyboard.cpp b/src/keyboard.cpp new file mode 100644 index 0000000..67c97a5 --- /dev/null +++ b/src/keyboard.cpp @@ -0,0 +1,73 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "keyboard.h" +#include "hypermode.h" +#include "keyboardbuffermode.h" + +Keyboard::Keyboard(KeypressQueue& q, HyperMode& fhyper, KeyboardBufferMode& buffered): + keys(q), + fhyper(fhyper), + buffered(buffered), + cGet(0) +{ +} + +void Keyboard::clear() +{ + this->latch &= 0x7F; +} + +unsigned char Keyboard::get() +{ + waitIfTooFast(); + if (!this->buffered.isBuffered() || !(this->latch & 0x80)) + { + if (!this->keys.empty()) + { + this->latch = this->keys.front() | 0x80; + this->keys.pop(); + } + } + return this->latch; +} + +void Keyboard::waitIfTooFast() +{ + if (this->fhyper.isHyper()) + { + return; + } + + ++this->cGet; + if (!this->cGet) + { + if (SDL_GetTicks() - this->lastGet <= 1000) + { + /* + * Check every 256 gets to see if they are + * happening too fast (within one second). + * If so, it means we are probably just + * looping waiting for a keypress, so + * wait a millisecond (or so) just to + * prevent us from using 100% of CPU time. + */ + SDL_Delay(1); + } + } + this->lastGet = SDL_GetTicks(); +} diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..014ca3c --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,48 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include +#include + +typedef std::queue KeypressQueue; + +class HyperMode; +class KeyboardBufferMode; + +class Keyboard +{ +private: + KeypressQueue& keys; + HyperMode& fhyper; + KeyboardBufferMode& buffered; + + unsigned char latch; + unsigned char cGet; + Uint32 lastGet; + + void waitIfTooFast(); + +public: + Keyboard(KeypressQueue& q, HyperMode& fhyper, KeyboardBufferMode& buffered); + void clear(); + unsigned char get(); +}; + +#endif diff --git a/src/keyboardbuffermode.cpp b/src/keyboardbuffermode.cpp new file mode 100644 index 0000000..aa47fe3 --- /dev/null +++ b/src/keyboardbuffermode.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "keyboardbuffermode.h" diff --git a/src/keyboardbuffermode.h b/src/keyboardbuffermode.h new file mode 100644 index 0000000..4354a37 --- /dev/null +++ b/src/keyboardbuffermode.h @@ -0,0 +1,46 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef KEYBOARDBUFFERMODE_H +#define KEYBOARDBUFFERMODE_H + +class KeyboardBufferMode +{ +private: + bool buffered; + +public: + KeyboardBufferMode(): buffered(true) { } + ~KeyboardBufferMode() { } + + bool isBuffered() + { + return this->buffered; + } + + void setBuffered(bool buffered) + { + this->buffered = buffered; + } + + void toggleBuffered() + { + this->buffered = !this->buffered; + } +}; + +#endif diff --git a/src/languagecard.cpp b/src/languagecard.cpp new file mode 100644 index 0000000..6391d9d --- /dev/null +++ b/src/languagecard.cpp @@ -0,0 +1,95 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "languagecard.h" +#include "screenimage.h" + +LanguageCard::LanguageCard(ScreenImage& gui, int slot): + gui(gui), + slot(slot), + inhibit(false), + ramTop(0x10000-0xE000), + bank(1), + readEnable(false), + writeEnable(true), + writeCount(0) +{ + this->ramBank.push_back(new Memory(0xE000-0xD000)); + this->ramBank.push_back(new Memory(0xE000-0xD000)); +} + +LanguageCard::~LanguageCard() +{ +} + + +unsigned char LanguageCard::io(const unsigned short address, const unsigned char data, const bool writing) +{ + if ((address & 1) && !writing) + { + ++this->writeCount; + } + else + { + this->writeCount = 0; + } + if (this->writeCount > 1) + { + this->writeEnable = true; + } + if (!(address & 1)) + { + this->writeEnable = false; + } + + const int r = address & 3; + this->readEnable = (r==0 || r==3); + + this->bank = !(address & 8); + + this->gui.setLangCard(this->slot,this->readEnable,this->writeEnable,this->bank); + + return data; +} + +void LanguageCard::ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write) +{ + this->inhibit = false; + if (this->readEnable && !write) + { + if (addr < 0x1000) + { + *pb = this->ramBank[this->bank]->read(addr); + } + else + { + *pb = this->ramTop.read(addr-0x1000); + } + this->inhibit = true; + } + else if (this->writeEnable && write) + { + if (addr < 0x1000) + { + this->ramBank[this->bank]->write(addr,*pb); + } + else + { + this->ramTop.write(addr-0x1000,*pb); + } + } +} diff --git a/src/languagecard.h b/src/languagecard.h new file mode 100644 index 0000000..64d7013 --- /dev/null +++ b/src/languagecard.h @@ -0,0 +1,51 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef LANGUAGECARD_H +#define LANGUAGECARD_H + +#include "card.h" +#include "memory.h" +#include + +class ScreenImage; + +class LanguageCard : public Card +{ +private: + ScreenImage& gui; + int slot; + bool inhibit; + std::vector ramBank; + Memory ramTop; + unsigned char bank; + bool readEnable; + bool writeEnable; + unsigned char writeCount; + +public: + LanguageCard(ScreenImage& gui, int slot); + ~LanguageCard(); + + virtual void reset() { /* does nothing */ } + virtual bool inhibitMotherboardRom() { return this->inhibit; } + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + virtual void ioBankRom(const unsigned short addr, unsigned char* const pb, const bool write); + virtual std::string getName() { return "language W B2"; } +}; + +#endif diff --git a/src/lowpass_1_5_mhz.cpp b/src/lowpass_1_5_mhz.cpp new file mode 100644 index 0000000..73f95a0 --- /dev/null +++ b/src/lowpass_1_5_mhz.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "lowpass_1_5_mhz.h" diff --git a/src/lowpass_1_5_mhz.h b/src/lowpass_1_5_mhz.h new file mode 100644 index 0000000..91b59b6 --- /dev/null +++ b/src/lowpass_1_5_mhz.h @@ -0,0 +1,51 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef LOWPASS_1_5_MHZ_H +#define LOWPASS_1_5_MHZ_H + +/* + Generated by the utility at http://www-users.cs.york.ac.uk/~fisher/mkfilter + then hand modified by Chris Mosher. +*/ +class Lowpass_1_5_MHz +{ +public: + Lowpass_1_5_MHz() + { + x[0] = x[1] = x[2] = 0; + y[0] = y[1] = y[2] = y[3] = y[4] = 0; + } + ~Lowpass_1_5_MHz() { } + + int x[3]; + int y[5]; + + int next(const int v) + { + x[0] = x[1]; x[1] = x[2]; + x[2] = v >> 3; + + y[0] = y[1]; y[1] = y[2]; y[2] = y[3]; y[3] = y[4]; + y[4] = x[0]+x[2]+(y[1]>>2)-y[2]+y[3]+(y[3]>>1); + + return y[4]; + } + +}; + +#endif diff --git a/src/lowpass_3_58_mhz.cpp b/src/lowpass_3_58_mhz.cpp new file mode 100644 index 0000000..d55ed06 --- /dev/null +++ b/src/lowpass_3_58_mhz.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "lowpass_3_58_mhz.h" diff --git a/src/lowpass_3_58_mhz.h b/src/lowpass_3_58_mhz.h new file mode 100644 index 0000000..84e80e6 --- /dev/null +++ b/src/lowpass_3_58_mhz.h @@ -0,0 +1,50 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef LOWPASS_3_58_MHZ_H +#define LOWPASS_3_58_MHZ_H + +/* + Generated by the utility at http://www-users.cs.york.ac.uk/~fisher/mkfilter + then hand modified by Chris Mosher. +*/ +class Lowpass_3_58_MHz +{ + int x[5]; + int y[5]; + +public: + Lowpass_3_58_MHz() + { + x[0] = x[1] = x[2] = x[3] = x[4] = 0; + y[0] = y[1] = y[2] = y[3] = y[4] = 0; + } + ~Lowpass_3_58_MHz() { } + + int next(const int v) + { + x[0] = x[1]; x[1] = x[2]; x[2] = x[3]; x[3] = x[4]; + x[4] = v/6; + + y[0] = y[1]; y[1] = y[2]; y[2] = y[3]; y[3] = y[4]; + y[4] = x[0]+x[4]+((x[1]+x[2]+x[3])<<1)-(y[3]>>2); + + return y[4]; + } +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..835ca8e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,68 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "emulator.h" +#include "configep2.h" +#include "gui.h" +#include "e2const.h" + +#include +#include +#include + +#include + +static int run(const std::string& config_file) +{ + GUI gui; + + std::auto_ptr emu(new Emulator()); + + Config cfg(config_file); + emu->config(cfg); + + emu->init(); + + return emu->run(); +} + +int main(int argc, char* argv[]) +{ + if (argc > 2) + { + throw std::runtime_error("usage: epple2 [config-file]" ); + } + + int x = E2Const::test(); + if (x != -1) + { + std::cerr << x << std::endl; + throw std::runtime_error("bad constant in e2const.h" ); + } + + std::string config_file; + if (argc > 1) + { + config_file = argv[1]; + } + + return run(config_file); +} diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..1c1609e --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,50 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "memory.h" +#include +#include +#include +#include "raminitializer.h" + +const int Memory::CLEAR_VALUE(0); + +Memory::Memory(const size_t n): + bytes(n) +{ +} + +void Memory::clear() +{ + std::fill(this->bytes.begin(),this->bytes.end(),CLEAR_VALUE); +} + +void Memory::powerOn() +{ + RAMInitializer initRam(*this); + initRam.init(); +} + +void Memory::powerOff() +{ + clear(); +} + +void Memory::load(const unsigned short base, std::istream& in) +{ + in.read((char*)&this->bytes[base],this->bytes.size()-base); +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..f9228ca --- /dev/null +++ b/src/memory.h @@ -0,0 +1,53 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef MEMORY_H +#define MEMORY_H + +#include +#include + +class Memory +{ +private: + std::vector bytes; + static const int CLEAR_VALUE; + +public: + Memory(const size_t n); + size_t size() const + { + return this->bytes.size(); + } + + unsigned char read(const unsigned short address) const + { + return this->bytes[address]; + } + + void write(const unsigned short address, const unsigned char data) + { + this->bytes[address] = data; + } + + void clear(); + void powerOn(); + void powerOff(); + void load(const unsigned short base, std::istream& in); +}; + +#endif diff --git a/src/paddlebuttonstates.cpp b/src/paddlebuttonstates.cpp new file mode 100644 index 0000000..9528a5f --- /dev/null +++ b/src/paddlebuttonstates.cpp @@ -0,0 +1,44 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "paddlebuttonstates.h" + +#include + +const int PaddleButtonStates::PADDLE_COUNT(3); + +PaddleButtonStates::PaddleButtonStates() +{ +} + +PaddleButtonStates::~PaddleButtonStates() +{ +} + +bool PaddleButtonStates::isDown(const int paddle) +{ + if (paddle < 0 || PADDLE_COUNT <= paddle) + { + return false; + } + unsigned char btn = SDL_GetMouseState(0,0); + if (paddle==0) + return btn&SDL_BUTTON_LMASK; + if (paddle==1) + return btn&SDL_BUTTON_RMASK; + return btn&SDL_BUTTON_MMASK; +} diff --git a/src/paddlebuttonstates.h b/src/paddlebuttonstates.h new file mode 100644 index 0000000..42588fb --- /dev/null +++ b/src/paddlebuttonstates.h @@ -0,0 +1,33 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef PADDLEBUTTONSTATES_H +#define PADDLEBUTTONSTATES_H + +#include + +class PaddleButtonStates +{ + static const int PADDLE_COUNT; + +public: + PaddleButtonStates(); + ~PaddleButtonStates(); + bool isDown(const int paddle); +}; + +#endif diff --git a/src/paddles.cpp b/src/paddles.cpp new file mode 100644 index 0000000..e1655fa --- /dev/null +++ b/src/paddles.cpp @@ -0,0 +1,93 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "e2const.h" +#include "paddles.h" +#include +#include +#include + + +Paddles::Paddles(): + rTick(PADDLE_COUNT) +{ +} + + +Paddles::~Paddles() +{ +} + + +void Paddles::tick() +{ + for (int paddle = 0; paddle < PADDLE_COUNT; ++paddle) + { + if (this->rTick[paddle] > 0) + --this->rTick[paddle]; + } +} + +void Paddles::startTimers() +{ + try + { + tryStartPaddleTimers(); + } + catch (...) + { + std::cerr << "Warning: cannot start paddle timers; mouse will not function as paddles." << std::endl; + } +} + +void Paddles::tryStartPaddleTimers() +{ + int x, y; + SDL_GetMouseState(&x,&y); + + double pMin = 0; + double pMax = 500; + x = (int)((x-pMin)/(pMax-pMin)*PADDLE_CYCLES+.5); + y = (int)((y-pMin)/(pMax-pMin)*PADDLE_CYCLES+.5); + + if (isTimedOut(0)) + this->rTick[0] = x; + if (isTimedOut(1)) + this->rTick[1] = y; + + /* + Here we emulate having 4700 ohm across pins 7 and 1 + of the game controller, and a 47Kohm resistor acros + pins 11 and 1, to give cheap real-time clocks at + paddles 2 and 3. Paddle 2 is the 100 microsecond reference, + and paddle 3 is the 1 millisecond reference. This is + described in U.A.2, p. 7-33. + */ + if (isTimedOut(2)) + this->rTick[2] = E2Const::AVG_CPU_HZ/10000; // was 90, but why? + if (isTimedOut(3)) + this->rTick[3] = E2Const::AVG_CPU_HZ/1000; +} + +bool Paddles::isTimedOut(const int paddle) +{ + if (paddle < 0 || PADDLE_COUNT <= paddle) + { + return false; + } + return this->rTick[paddle] <= 0; +} diff --git a/src/paddles.h b/src/paddles.h new file mode 100644 index 0000000..eee5279 --- /dev/null +++ b/src/paddles.h @@ -0,0 +1,41 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef PADDLES_H +#define PADDLES_H + +#include + +class Paddles +{ +private: + std::vector rTick; + + enum { PADDLE_COUNT = 4 }; + enum { PADDLE_CYCLES = 2805 }; // TODO: document where PADDLE_CYCLES==2805 came from + + void tryStartPaddleTimers(); + +public: + Paddles(); + ~Paddles(); + void tick(); + void startTimers(); + bool isTimedOut(const int paddle); +}; + +#endif diff --git a/src/picturegenerator.cpp b/src/picturegenerator.cpp new file mode 100644 index 0000000..eda0c5d --- /dev/null +++ b/src/picturegenerator.cpp @@ -0,0 +1,327 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "picturegenerator.h" +#include "analogtv.h" +#include "videomode.h" +#include "applentsc.h" +#include "e2const.h" + +PictureGenerator::PictureGenerator(AnalogTV& display, VideoMode& mode, const int& revision): + display(display), mode(mode), itestsig(testsig), itestsiglim(testsig+AppleNTSC::SIGNAL_LEN), + VISIBLE_X_OFFSET(E2Const::BYTES_PER_ROW-E2Const::VISIBLE_BYTES_PER_ROW), + revision(revision) +{ +} + + +PictureGenerator::~PictureGenerator() +{ +} + + +void PictureGenerator::powerOn() +{ + this->hpos = 0; + this->line = 0; + this->display.signal = this->testsig; + this->itestsig = this->testsig; +} + +void inline PictureGenerator::shiftLoRes() +{ + /* + * For byte ABCDEFGH in register, perform + * the following 4-bit end-around shifts: + * + * +---<----+ +---<----+ + * | | | | + * +->ABCD->+ +->EFGH->+ + * + * Therefore: + * + * ABCDEFGH --> DABCHEFG + */ + + unsigned char rot_bits = this->latchGraphics & 0x11; + // 000D000H + rot_bits <<= 3; + // D000H000 + + this->latchGraphics &= 0xEE; + // ABC0EFG0 + this->latchGraphics >>= 1; + // 0ABC0EFG + this->latchGraphics |= rot_bits; + // DABCHEFG +} + +void inline PictureGenerator::shiftHiRes() +{ + /* + * For byte ABCDEFGH in register, perform + * the following shift: + * + * +---<----+ + * | | + * +->ABCD->+--->EFGH-> + * + * Therefore: + * + * ABCDEFGH --> DABCDEFG + */ + + unsigned char rot_bits = this->latchGraphics & 0x10; + // 000D0000 + rot_bits <<= 3; + // D0000000 + + this->latchGraphics >>= 1; + // 0ABCDEFG + this->latchGraphics |= rot_bits; + // DABCDEFG +} + +void inline PictureGenerator::shiftText() +{ + this->latchText >>= 1; +} + +bool inline PictureGenerator::getTextBit() +{ + return this->latchText & 1; +} + +bool inline PictureGenerator::getHiResBit() +{ + return this->latchGraphics & 1; +} + +bool inline PictureGenerator::getLoResBit(const bool odd, const bool vc) +{ + const int nibble = (this->latchGraphics >> (vc ? 4 : 0)) & 0x0F; + return (nibble >> (odd ? 2 : 0)) & 1; +} + +void inline PictureGenerator::loadGraphics(const unsigned char value) +{ + this->latchGraphics = value; + this->d7 = this->latchGraphics & 0x80; +} + +void inline PictureGenerator::loadText(const int value) +{ + this->latchText = value; +} + +// TODO can we hand-optimize the main picture generator algorithm any more? +// Note that the innermost loop (which calls writeVideoSignal) has to execute +// at 14MHz, in order to maintain authentic Apple ][ speed. +void PictureGenerator::tick(const int t, const unsigned char rowToPlot) +{ + const bool isText(this->mode.isDisplayingText(t)); + const bool isHiRes(this->mode.isHiRes()); + + signed char* is = this->itestsig; + + if (isText) + loadText(rowToPlot); + else + loadGraphics(rowToPlot); + + if (t==0) + { + this->line = 0; + } + + int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE; + if (this->hpos == E2Const::HORIZ_CYCLES-1) + { + cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE; + } + + // hi-res half-pixel shift: + const bool shift = !isText && isHiRes && this->d7 && this->line < E2Const::VISIBLE_ROWS_PER_FIELD && !(this->hpos < VISIBLE_X_OFFSET) && this->revision > 0; + const bool showLastHiRes = shift && this->lasthires; + + int xtra(0); + if (shift) + { + --cycles; + ++xtra; + } + const int firstBlankedCycle(E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE-xtra); + + int hcycle(this->hpos*E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE); + const bool lineVis(this->line < E2Const::VISIBLE_ROWS_PER_FIELD); + const bool hVis(this->hpos >= VISIBLE_X_OFFSET); + for (int cycle(0); cycle < cycles-1; ++cycle) + { + const bool bit = shiftLatch(t,cycle,isText,isHiRes); + is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is); + ++hcycle; + } + // optimization: pull the last iteration of the loop out, so we don't getHiResBit every time + { + this->lasthires = getHiResBit(); // save it for the next plotted byte, just in case we need it + const int cycle = cycles-1; + const bool bit = shiftLatch(t,cycle,isText,isHiRes); + is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is); + } + + this->itestsig = is; + + ++this->hpos; + if (this->hpos >= E2Const::HORIZ_CYCLES) + { + this->hpos = 0; + ++this->line; + if (this->itestsig >= this->itestsiglim) + { + this->itestsig = this->testsig; + this->display.drawCurrent(); + } + } +} + +bool inline PictureGenerator::shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes) +{ + bool bit; + if (isText) + { + bit = getTextBit(); + if (cycle & 1) // @ 7MHz + { + shiftText(); + } + } + else if (isHiRes) + { + bit = getHiResBit(); + if (cycle & 1) // @ 7MHz + { + shiftHiRes(); + } + } + else // LO-RES + { + const int y = t / E2Const::BYTES_PER_ROW; + bit = getLoResBit((t & 1) == (this->line & 1), y & 4); + shiftLoRes(); + } + return bit; +} + +inline signed char* PictureGenerator::writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is) +{ + if (shift && !cycle) + { + *is++ = showLastHiRes ? AppleNTSC::WHITE_LEVEL : AppleNTSC::BLANK_LEVEL; + } + + signed char sig; + if (lineVis) + { + if (hVis) + { + if (bit && cycle < firstBlankedCycle) + { + sig = AppleNTSC::WHITE_LEVEL; + } + else + { + sig = AppleNTSC::BLANK_LEVEL; + } + } + else + { + sig = hbl(hcycle); + } + } + else + { + sig = vbl(hcycle); + } + *is++ = sig; + return is; +} + +// TODO Just to be extremely accurate, fix picture signal values during HBL and VBL +// (note that they vary by motherboard revision... there is a whole section in U.A.2) +signed char inline PictureGenerator::vbl(const int hcycle) +{ + signed char sig; + if (224 <= this->line && this->line < 240) // VSYNC // TODO symbolize constants + { + sig = AppleNTSC::SYNC_LEVEL; + } + else + { + if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) + { + sig = AppleNTSC::SYNC_LEVEL; + } + else + { + sig = AppleNTSC::BLANK_LEVEL; + } + } + return sig; +} + + +// Set up the color burst signal. +// TODO confirm that color burst signal phase is correct +// Note, I believe this is the correct phase. The only other +// possible configuration would be +10,-10,-10,+10. Can anyone confirm +// that 0,-20,0,+20 gives the correct phase? By eye, it seems +// +10,-10,-10,+10 makes brown look too green, but 0,-20,0,+20 +// makes hi-res blue look too light. +// Note that this color burst signal only affects "old TV" mode; +// the other color modes use A2ColorsObserved. +const signed char PictureGenerator::lutCB[] = +{ + 0, + -AppleNTSC::CB_LEVEL, + 0, + +AppleNTSC::CB_LEVEL, +}; + +signed char inline PictureGenerator::hbl(const int hcycle) +{ + signed char cb; + if (AppleNTSC::CB_START <= hcycle && hcycle < AppleNTSC::CB_END) + { + if (this->mode.isText() && this->revision > 0) + { + cb = AppleNTSC::BLANK_LEVEL; + } + else + { + cb = lutCB[(hcycle-AppleNTSC::CB_START)%4]; + } + } + else if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) + { + cb = AppleNTSC::SYNC_LEVEL; + } + else + { + cb = AppleNTSC::BLANK_LEVEL; + } + return cb; +} diff --git a/src/picturegenerator.h b/src/picturegenerator.h new file mode 100644 index 0000000..04f92e4 --- /dev/null +++ b/src/picturegenerator.h @@ -0,0 +1,68 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef PICTUREGENERATOR_H +#define PICTUREGENERATOR_H +#include "applentsc.h" +class AnalogTV; +class VideoMode; + +class PictureGenerator +{ +private: + AnalogTV& display; + VideoMode& mode; + + unsigned char latchGraphics; + bool d7; + unsigned char latchText; + unsigned int hpos; + unsigned int line; + bool lasthires; + static const signed char lutCB[]; + + signed char testsig[AppleNTSC::SIGNAL_LEN]; + signed char* itestsig; + signed char* itestsiglim; + + void shiftLoRes(); + void shiftHiRes(); + void shiftText(); + bool getTextBit(); + bool getHiResBit(); + bool getLoResBit(const bool odd, const bool vc); + void loadGraphics(const unsigned char value); + void loadText(const int value); + bool shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes); + signed char* writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is); + signed char vbl(const int hcycle); + signed char hbl(const int hcycle); + + const unsigned int VISIBLE_X_OFFSET; + + const int& revision; + +public: + + PictureGenerator(AnalogTV& display, VideoMode& mode, const int& revision); + ~PictureGenerator(); + + void powerOn(); + void tick(const int t, const unsigned char c); +}; + +#endif diff --git a/src/powerupreset.cpp b/src/powerupreset.cpp new file mode 100644 index 0000000..6a65451 --- /dev/null +++ b/src/powerupreset.cpp @@ -0,0 +1,48 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "powerupreset.h" +#include "apple2.h" +#include "e2const.h" + +PowerUpReset::PowerUpReset(Apple2& apple): + apple(apple) +{ +} + + +PowerUpReset::~PowerUpReset() +{ +} + + +void PowerUpReset::tick() +{ + if (this->pendingTicks > 0) + { + --this->pendingTicks; + if (this->pendingTicks == 0) + { + this->apple.reset(); + } + } +} + +void PowerUpReset::powerOn() +{ + this->pendingTicks = (int)(E2Const::AVG_CPU_HZ*.3); // U.A.II, p. 7-15 +} diff --git a/src/powerupreset.h b/src/powerupreset.h new file mode 100644 index 0000000..f73c100 --- /dev/null +++ b/src/powerupreset.h @@ -0,0 +1,36 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef POWERUPRESET_H +#define POWERUPRESET_H + +class Apple2; + +class PowerUpReset +{ +private: + Apple2& apple; + int pendingTicks; + +public: + PowerUpReset(Apple2& apple); + ~PowerUpReset(); + void powerOn(); + void tick(); +}; + +#endif diff --git a/src/raminitializer.cpp b/src/raminitializer.cpp new file mode 100644 index 0000000..d2ca710 --- /dev/null +++ b/src/raminitializer.cpp @@ -0,0 +1,137 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "raminitializer.h" +#include "memory.h" +#include +#include + +RAMInitializer::RAMInitializer(Memory& mem): + ram(mem) +{ + srand(time(0)); +} + +void RAMInitializer::init() +{ + this->ram.clear(); + + int b(0); + // TODO make the types of RAM chips configurable + putBytesUntilFull(b++,1); + putBytesUntilFull(b++,2); + putBytesUntilFull(b++,1); + putBytesUntilFull(b++,2); + putBytesUntilFull(b++,1); + putBytesUntilFull(b++,2); + putBytesUntilFull(b++,2); + putBytesUntilFull(b++,1); +}; + + +void RAMInitializer::putBytesUntilFull(int bit, int pat) +{ + this->nextinit = 0; + try + { + while (true) + { + if (pat==1) + ramPattern1(bit); + else + ramPattern2(bit); + } + } + catch (const done&) + { + // done filling this bit in RAM + } +} + +void RAMInitializer::ramPattern1(const int bit) throw (done) +{ + for (int k = 0; k < 2; ++k) + { + for (int j = 0; j < 8; ++j) + { + int i; + for (i = 0; i < 0x10; ++i) + { + putn(4,false,bit); + putn(2,true,bit); + putn(2,false,bit); + } + for (i = 0; i < 0x40; ++i) + { + putn(2,true,bit); + putn(2,false,bit); + } + for (i = 0; i < 0x08; ++i) + { + putn(2,true,bit); + putn(1,false,bit); + putn(3,true,bit); + putn(2,false,bit); + putn(2,true,bit); + putn(2,false,bit); + putn(2,true,bit); + putn(2,false,bit); + } + } + for (int i = 0; i < 0x400; ++i) + { + putn(2,true,bit); + putn(2,false,bit); + } + } +} + +void RAMInitializer::ramPattern2(const int bit) throw (done) +{ + for (int i = 0; i < 0x40; ++i) + { + putn(0x80,true,bit); + putn(0x80,false,bit); + } +} + + + +void RAMInitializer::putn(const int c, bool on, const int bit) throw (done) +{ + if (((rand() >> 9) & 0x1F) == 5) + on = !on; + const unsigned char mask(1 << bit); + for (int i = 0; i < c; ++i) + { + if (this->nextinit >= this->ram.size()) + { + throw done(); + } + unsigned char b = this->ram.read(this->nextinit); + if (on) + { + b |= mask; + } + else + { + b &= ~mask; + } + this->ram.write(this->nextinit,b); + ++this->nextinit; + } +} diff --git a/src/raminitializer.h b/src/raminitializer.h new file mode 100644 index 0000000..4a989e1 --- /dev/null +++ b/src/raminitializer.h @@ -0,0 +1,41 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef RAMINITIALIZER_H +#define RAMINITIALIZER_H + +class Memory; + +class RAMInitializer +{ +private: + class done {}; + + Memory& ram; + unsigned short nextinit; + + void putBytesUntilFull(int bit, int pat); + void ramPattern1(const int bit) throw (done); + void ramPattern2(const int bit) throw (done); + void putn(const int c, bool on, const int bit) throw (done); + +public: + RAMInitializer(Memory& mem); + void init(); +}; + +#endif diff --git a/src/screenimage.cpp b/src/screenimage.cpp new file mode 100644 index 0000000..f0dd6e1 --- /dev/null +++ b/src/screenimage.cpp @@ -0,0 +1,561 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "screenimage.h" +#include "e2const.h" +#include "applentsc.h" +#include "card.h" +#include "util.h" +#include +#include +#include + +static const char* power = +" @@@@ @@@ @ @ @ @@@@@ @@@@ " +" @ @ @ @ @ @ @ @ @ @ @" +" @ @ @ @ @ @ @ @ @ @ @" +" @@@@ @ @ @ @ @ @ @@@@@ @@@@ " +" @ @ @ @ @ @ @ @ @ @ " +" @ @ @ @ @ @ @ @ @ @ " +" @ @@@ @ @ @@@@@ @ @"; + +#define POWERD 56 +#define LABEL_Y 24 +#define ON_CLR 0xF0D050 +#define OFF_CLR 0x807870 +#define SCRW 640 +#define SCRH 480 + +static const int HEIGHT = E2Const::VISIBLE_ROWS_PER_FIELD*2; +static const int WIDTH = AppleNTSC::H-AppleNTSC::PIC_START-2; + +#include "font3x5.h" + +class ScreenException { }; + +ScreenImage::ScreenImage(): + fullscreen(false), + hyper(false), + buffer(true), + fillLines(false), + display(AnalogTV::MONITOR_COLOR), + slotnames(8), + cassettename(32,' ') +{ + createScreen(); +} + +void ScreenImage::toggleFullScreen() +{ + this->fullscreen = !this->fullscreen; + createScreen(); +} + +void ScreenImage::createScreen() +{ + this->screen = SDL_SetVideoMode(SCRW,SCRH,32,SDL_HWSURFACE|SDL_HWPALETTE|(this->fullscreen?SDL_FULLSCREEN:0)); + if (this->screen == NULL) + { + printf("Unable to set video mode: %s\n",SDL_GetError()); + throw ScreenException(); + }; + if (this->screen->pitch/4 != SCRW) + { + printf("Cannot set video screen pitch to 640*4 pixels (gets set to %d)\n",this->screen->pitch); + throw ScreenException(); + }; + drawLabels(); + notifyObservers(); +} + +void ScreenImage::drawLabels() +{ + drawText("EPPLE ][",0,141); + drawSlots(); + drawCassette(); + drawFnKeys(); +} + +void ScreenImage::drawSlots() +{ + int r(65); + int c(17); + drawText("SLOTS:",r++,c); + for (int slot(0); slot < 8; ++slot) + { + drawSlot(slot,r++,c); + } +} + +void ScreenImage::drawSlot(int slot, int r, int c) +{ + drawChar('0'+slot,r,c++); + drawChar(':',r,c++); + drawChar(' ',r,c++); + drawText(this->slotnames[slot],r,c); + const int len = this->slotnames[slot].length(); + if (len < 100) + { + drawText(std::string(100-len,' '),r,c+len); + } +} + +void ScreenImage::drawCassette() +{ + int r(65); + int c(85); + drawText("CASSETTE:",r,c); + c += 9; + drawText(this->cassettename,r,c); + const int len = this->cassettename.length(); + if (len < 40) + { + drawText(std::string(40-len,' '),r,c+len); + } +} + +static const char* displays[] = +{ + "COLOR MONITOR ", + "WHITE MONITOR ", + "GREEN MONITOR ", + "ORANGE MONITOR", + "OLD COLOR TV ", + "OLD B & W TV ", + "NEW COLOR TV ", + "NEW B & W TV ", +}; + +void ScreenImage::drawFnKeys() +{ + int r(76); + int c(1); + drawText( +" FULLSCRN SCAN-LINES KEYBOARD",r++,c); + drawText( +" XXXXXXXXXXXXXX WINDOW FILL-LINES CMD REPT HYPER BUFFER RESET PASTE SAVE BMP QUIT!",r++,c); + drawText( +" F1 F2 F3 F4 F5 F10 F11 F12 Break Insert PrintScreen End",r++,c); + + if (this->fullscreen) + invertText(76,32,42); // FULLSCRN + else + invertText(77,32,40); // WINDOW + + if (this->fillLines) + invertText(77,43,55); // FILL-LINES + else + invertText(76,43,55); // SCAN-LINES + + if (this->hyper) + invertText(77,67,74); // HYPER + + if (this->buffer) + invertText(77,75,83); // BUFFER + + drawDisplayLabel(); +} + +void ScreenImage::toggleFillLinesLabel() +{ + this->fillLines = !this->fillLines; + invertText(76,43,55); // SCAN-LINES + invertText(77,43,55); // FILL-LINES +} + +void ScreenImage::drawDisplayLabel() +{ + const char* label = displays[(int)(this->display)]; + drawText(label,77,17); +} + +void ScreenImage::cycleDisplayLabel() +{ + this->display = (AnalogTV::DisplayType)((((int)this->display)+1)%AnalogTV::NUM_DISPLAY_TYPES); + drawDisplayLabel(); +} + +void ScreenImage::toggleHyperLabel() +{ + this->hyper = !this->hyper; + invertText(77,67,74); // HYPER +} + +void ScreenImage::toggleKdbBufferLabel() +{ + this->buffer = !this->buffer; + invertText(77,75,83); // BUFFER +} + +void ScreenImage::invertText(int row, int begincol, int endcol) +{ + unsigned int* pn = (unsigned int*)this->screen->pixels; + pn += row*FONTH*SCRW+begincol*FONTW; + const int dc = (endcol-begincol)*FONTW; + for (int ir = 0; ir < FONTH; ++ir) + { + for (int ic = 0; ic < dc; ++ic) + { + *pn = ~*pn; + ++pn; + } + pn -= dc; + pn += SCRW; + } +} + +void ScreenImage::drawText(const std::string& text, int row, int col, int color, int bgcolor) +{ + for (std::string::const_iterator i = text.begin(); i != text.end(); ++i) + { + char c = (*i) & 0x7F; + if (0 <= c && c < 0x20) + c += 0x40; + drawChar(c,row,col++,color,bgcolor); + } +} + +void ScreenImage::drawChar(const char ch, int row, int col, int color, int bgcolor) +{ + if (!(0 <= row && row < SCRH/6 && 0 <= col && col < SCRW/4)) + { + printf("bad text plotting (r %d, c %d): \"%c\"\n",row,col,ch); + } + unsigned int* pn = (unsigned int*)this->screen->pixels; + pn += row*FONTH*SCRW+col*FONTW; + + const char* pt = font3x5+FONTH*FONTW*(ch-0x20); + for (int r = 0; r < FONTH; ++r) + { + for (int c = 0; c < FONTW; ++c) + { + *pn++ = *pt++ == '@' ? color : bgcolor; + } + pn -= FONTW; + pn += SCRW; + } + SDL_UpdateRect(this->screen,col*FONTW,row*FONTH,(col+1)*FONTW,(row+1)*FONTH); +} + +void ScreenImage::displayHz(int hz) +{ + char s[20]; + sprintf(s,"%4.2f MHz ",hz/1e6); + drawText(s,3,141); +} + +void ScreenImage::drawPower(bool on) +{ + unsigned int* pn = (unsigned int*)this->screen->pixels; + pn += (HEIGHT+5)*(this->screen->pitch/4)+5; + for (int r = 0; r < POWERD; ++r) + { + if (r < LABEL_Y || LABEL_Y+7 <= r) + { + for (int c = 0; c < POWERD; ++c) + { + *pn++ = on ? ON_CLR : OFF_CLR; + } + } + else + { + { + for (int c = 0; c < 8; ++c) + { + *pn++ = on ? ON_CLR : OFF_CLR; + } + } + for (const char* ppow = power+(r-(LABEL_Y))*40; ppow < power+(r-(LABEL_Y-1))*40; ++ppow) + { + if (*ppow == '@') + *pn++ = 0; + else + *pn++ = on ? ON_CLR : OFF_CLR; + } + { + for (int c = 0; c < 8; ++c) + { + *pn++ = on ? ON_CLR : OFF_CLR; + } + } + } + pn -= POWERD; + pn += this->screen->pitch/4; + } + SDL_UpdateRect(this->screen,0,HEIGHT,POWERD,HEIGHT+POWERD); +} + +ScreenImage::~ScreenImage() +{ +} + +void ScreenImage::notifyObservers() +{ + SDL_UpdateRect(this->screen,0,0,SCRW,SCRH); +} + +void ScreenImage::setElem(unsigned int i, const unsigned int val) +{ + unsigned int* pn = (unsigned int*)this->screen->pixels; + i += (i/WIDTH)*(SCRW-WIDTH); + pn += i; + *pn = val; +} + +void ScreenImage::blank() +{ + for (int r = 0; r < HEIGHT; ++r) + { + memset((char*)(this->screen->pixels)+r*SCRW*4,0,WIDTH*4); + } +} + +void ScreenImage::enterCommandMode() +{ + int r(76); + int c(1); + unsigned int* pn = (unsigned int*)this->screen->pixels; + pn += r*FONTH*SCRW+c*FONTW; + + memset((char*)pn,0,SCRW*4*FONTH*3); + + drawText("command: ",78,1); + this->cmdpos = 9; + + notifyObservers(); +} + +void ScreenImage::exitCommandMode() +{ + drawFnKeys(); + notifyObservers(); +} + +void ScreenImage::addkeyCommand(unsigned char key) +{ + ++this->cmdpos; + drawChar((char)key,78,this->cmdpos); +} + +void ScreenImage::backspaceCommand() +{ + drawChar(' ',78,this->cmdpos); + --this->cmdpos; +} + +void ScreenImage::updateSlotName(const int slot, Card* card) +{ + int r(66+slot); + int c(20); + const std::string& name = card->getName(); + this->slotnames[slot] = name; + drawText(std::string(80,' '),r,c); + drawText(name,r,c); +} + +void ScreenImage::removeCard(const int slot, Card* card /* empty */) +{ + updateSlotName(slot,card); +} + +/* +1 2 3 4 5 6 7 8 +789012345678901234567890123456789012345678901234567890123456789012345 +6: disk][ drive 1M*filename.nib T$FF drive 2M*filename.nib T$FF +*/ +void ScreenImage::setDiskFile(int slot, int drive, const std::string& filepath) +{ + std::string f = truncateFilePath(filepath); + int r(66+slot); + int c(37+32*drive); + drawText(f,r,c); + + const int dlen = 12 - f.length(); + if (dlen > 0) + { + std::string d(dlen,' '); + drawText(d,r,c+f.length()); + } + + this->slotnames[slot].replace(c-20,12,12,' '); + this->slotnames[slot].replace(c-20,f.length(),f); +} + +std::string ScreenImage::truncateFilePath(const std::string& filepath) +{ + std::string f(filepath); + size_t slash = f.find_last_of("/\\"); + if (slash != std::string::npos) + { + f = f.substr(slash+1); + } + if (f.length() > 12) + { + f = f.substr(0,12); + } + return f; +} + +void ScreenImage::clearCurrentDrive(int slot, int drive) +{ + int r(66+slot); + int c(35+32*drive); + drawChar(' ',r,c); + this->slotnames[slot][c-20] = ' '; + c += 15; + drawText(" ",r,c); + this->slotnames[slot].replace(c-20,4,4,' '); +} + +void ScreenImage::setCurrentDrive(int slot, int drive, int track, bool on) +{ + int r(66+slot); + int c(35+32*drive); + drawChar(' ',r,c,0xFFFFFF,on?0xFF0000:0); + c += 15; + drawChar('T',r,c); + this->slotnames[slot][c-20] = 'T'; + ++c; + drawChar('$',r,c); + this->slotnames[slot][c-20] = '$'; + ++c; + char nibh = Util::hexDigit((((unsigned char)track) >> 4) & 0xF); + drawChar(nibh,r,c); + this->slotnames[slot][c-20] = nibh; + ++c; + char nibl = Util::hexDigit((unsigned char)track & 0xF); + drawChar(nibl,r,c); + this->slotnames[slot][c-20] = nibl; +} + +void ScreenImage::setTrack(int slot, int drive, int track) +{ + int r(66+slot); + int c(52+32*drive); + char nibh = Util::hexDigit((((unsigned char)track) >> 4) & 0xF); + drawChar(nibh,r,c); + this->slotnames[slot][c-20] = nibh; + ++c; + char nibl = Util::hexDigit((unsigned char)track & 0xF); + drawChar(nibl,r,c); + this->slotnames[slot][c-20] = nibl; +} + +void ScreenImage::setIO(int slot, int drive, bool on) +{ + int r(66+slot); + int c(35+32*drive); + drawChar(' ',r,c,0xFFFFFF,on?0xFF0000:0); +} + +void ScreenImage::setDirty(int slot, int drive, bool dirty) +{ + int r(66+slot); + int c(36+32*drive); + drawChar(dirty?'*':' ',r,c); + this->slotnames[slot][c-20] = dirty?'*':' '; +} + +void ScreenImage::setCassetteFile(const std::string& filepath) +{ + std::string f = truncateFilePath(filepath); + int r(65); + int c(85+11); + drawText(f,r,c); + + const int dlen = 12 - f.length(); + if (dlen > 0) + { + std::string d(dlen,' '); + drawText(d,r,c+f.length()); + } + + this->cassettename.replace(c-94,12,12,' '); + this->cassettename.replace(c-94,f.length(),f); +} + +void ScreenImage::setCassetteDirty(bool dirty) +{ + int r(65); + int c(85+10); + drawChar(dirty?'*':' ',r,c); + this->cassettename[c-94] = dirty?'*':' '; +} + +void ScreenImage::setCassettePos(int pos, int siz) +{ + int r(65); + int c(110); + std::ostringstream os; + os << pos << '/' << siz << " "; + drawText(os.str(),r,c); +} + +/* +1 2 3 4 5 6 7 8 +789012345678901234567890123456789012345678901234567890123456789012345678 +0: language RW B2 +*/ +void ScreenImage::setLangCard(int slot, bool readEnable, bool writeEnable, int bank) +{ + int r(66+slot); + int c(29); + drawChar(readEnable?'R':' ',r,c); + this->slotnames[slot][c-20] = readEnable?'R':' '; + ++c; + drawChar(writeEnable?'W':' ',r,c); + this->slotnames[slot][c-20] = writeEnable?'W':' '; + ++c; + ++c; + drawChar('B',r,c); + this->slotnames[slot][c-20] = 'B'; + ++c; + drawChar(bank==0?'1':'2',r,c); + this->slotnames[slot][c-20] = bank==0?'1':'2'; +} + +/* +1 2 3 4 5 6 7 8 +789012345678901234567890123456789012345678901234567890123456789012345678 +0: firmware D F8 +*/ +void ScreenImage::setFirmCard(int slot, bool bank, bool F8) +{ + int r(66+slot); + int c(29); + drawChar(bank?'D':' ',r,c); + this->slotnames[slot][c-20] = bank?'D':' '; + ++c; + ++c; + drawChar(F8?'F':' ',r,c); + this->slotnames[slot][c-20] = F8?'F':' '; + ++c; + drawChar(F8?'8':' ',r,c); + this->slotnames[slot][c-20] = F8?'8':' '; +} + +#define TIMEFORMAT "ep2_%Y%m%d%H%M%S.bmp" + +void ScreenImage::saveBMP() +{ + time_t now; + ::time(&now); + struct tm* nowtm = ::localtime(&now); + char time[64]; + ::strftime(time,sizeof(time),TIMEFORMAT,nowtm); + SDL_SaveBMP(this->screen,time); +} diff --git a/src/screenimage.h b/src/screenimage.h new file mode 100644 index 0000000..2423c77 --- /dev/null +++ b/src/screenimage.h @@ -0,0 +1,94 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef SCREENIMAGE_H +#define SCREENIMAGE_H + +#include "analogtv.h" +#include +#include + +class Card; +struct SDL_Surface; + +class ScreenImage +{ +private: + SDL_Surface* screen; + bool fullscreen; + bool hyper; + bool buffer; + bool fillLines; + AnalogTV::DisplayType display; + unsigned int cmdpos; + void createScreen(); + std::vector slotnames; + std::string cassettename; + + static std::string truncateFilePath(const std::string& filepath); + +// TODO some of these methods should be private +public: + ScreenImage(); + ~ScreenImage(); + + void toggleFullScreen(); + void drawPower(bool on); + void notifyObservers(); + void setElem(const unsigned int i, const unsigned int val); + void blank(); + void drawText(const std::string& text, int row, int col, int color = 0xFFFFFF, int bgcolor = 0); + void drawChar(const char ch, int row, int col, int color = 0xFFFFFF, int bgcolor = 0); + void drawLabels(); + void drawSlots(); + void drawSlot(int slot, int r, int c); + void drawCassette(); + void drawFnKeys(); + void toggleHyperLabel(); + void toggleKdbBufferLabel(); + void cycleDisplayLabel(); + void displayHz(int hz); + void toggleFillLinesLabel(); + void invertText(int row, int begincol, int endcol); + void drawDisplayLabel(); + void updateSlotName(const int slot, Card* card); + void removeCard(const int slot, Card* card /* empty */); + + void enterCommandMode(); + void exitCommandMode(); + void addkeyCommand(unsigned char key); + void backspaceCommand(); + + void setDiskFile(int slot, int drive, const std::string& filename); + + void clearCurrentDrive(int slt, int drv); + void setCurrentDrive(int slt, int drv, int track, bool on); + void setTrack(int slot, int drive, int track); + void setIO(int slot, int drive, bool on); + void setDirty(int slot, int drive, bool dirty); + + void setCassetteFile(const std::string& filepath); + void setCassetteDirty(bool dirty); + void setCassettePos(int pos, int siz); + + void setLangCard(int slot, bool readEnable, bool writeEnable, int bank); + void setFirmCard(int slot, bool bank, bool F8); + + void saveBMP(); +}; + +#endif diff --git a/src/slots.cpp b/src/slots.cpp new file mode 100644 index 0000000..54068c1 --- /dev/null +++ b/src/slots.cpp @@ -0,0 +1,151 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "slots.h" +#include "screenimage.h" +#include + +Slots::Slots(ScreenImage& gui): + gui(gui), + empty(), + cards(8,&this->empty) +{ + forceGuiUpdate(); +} + +Slots::~Slots() +{ +} + +unsigned char Slots::io(const int islot, const int iswch, const unsigned char b, const bool writing) +{ + return this->cards[islot]->io(iswch,b,writing); +} + +struct Slots_Card_reset +{ + void operator() (Card* p) { p->reset(); } +}; + +void Slots::reset() +{ + std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_reset()); +} + +unsigned char Slots::readRom(const int islot, const unsigned short addr, const unsigned char data) +{ + return this->cards[islot]->readRom(addr,data); +} + +struct Slots_Card_readSeventhRom +{ + const unsigned short addr; + unsigned char* b; + Slots_Card_readSeventhRom(const unsigned short addr, unsigned char* b):addr(addr),b(b){} + void operator() (Card* p) { p->readSeventhRom(this->addr,this->b); } +}; + +unsigned char Slots::readSeventhRom(const unsigned short addr, const unsigned char data) +{ + unsigned char b(data); + std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_readSeventhRom(addr,&b)); + return b; +} + +struct Slots_Card_ioBankRom +{ + const unsigned short addr; + unsigned char* b; + const bool write; + Slots_Card_ioBankRom(const unsigned short addr, unsigned char* b, const bool write):addr(addr),b(b),write(write){} + void operator() (Card* p) { p->ioBankRom(this->addr,this->b,this->write); } +}; + +unsigned char Slots::ioBankRom(const unsigned short addr, const unsigned char data, const bool write) +{ + unsigned char b(data); + std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_ioBankRom(addr,&b,write)); + return b; +} + +struct Slots_Card_inhibitMotherboardRom +{ + bool inhibit; + Slots_Card_inhibitMotherboardRom():inhibit(false) { } + void operator() (Card* p) { if (p->inhibitMotherboardRom()) { inhibit = true; }} +}; + +bool Slots::inhibitMotherboardRom() +{ + return std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_inhibitMotherboardRom()).inhibit; +} + +void Slots::set(const int slot, Card* card) +{ + remove(slot); + this->cards[slot] = card; + this->gui.updateSlotName(slot,this->cards[slot]); +} + +void Slots::remove(const int slot) +{ + if (this->cards[slot] != &this->empty) + { + delete this->cards[slot]; + this->cards[slot] = &this->empty; + this->gui.removeCard(slot,this->cards[slot]); + } +} + +Card* Slots::get(const int slot) +{ + return this->cards[slot]; +} + + +void Slots::forceGuiUpdate() +{ + for (int slot(0); slot < 8; ++slot) + this->gui.updateSlotName(slot,this->cards[slot]); +} + +/* +struct isAnyDiskDriveMotorOnCard +{ + bool on; + isAnyDiskDriveMotorOnCard():on(false) {} + void operator() (Card* p) { if (p->isMotorOn()) on = true; } +}; + +bool isAnyDiskDriveMotorOn() +{ + isAnyDiskDriveMotorOnCard on = isAnyDiskDriveMotorOnCard(); + std::for_each(this->cards.begin(),this->cards.end(),inh); + return on.inhibit; +} +*/ +struct Slots_Card_isDirty +{ + bool dirty; + Slots_Card_isDirty():dirty(false) {} + void operator() (Card* p) { if (p->isDirty()) dirty = true; } +}; + +bool Slots::isDirty() +{ + return std::for_each(this->cards.begin(),this->cards.end(),Slots_Card_isDirty()).dirty; +} diff --git a/src/slots.h b/src/slots.h new file mode 100644 index 0000000..726764a --- /dev/null +++ b/src/slots.h @@ -0,0 +1,51 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef SLOTS_H +#define SLOTS_H + +#include +#include "card.h" +#include "emptyslot.h" + +class ScreenImage; + +class Slots +{ +private: + ScreenImage& gui; + EmptySlot empty; + std::vector cards; + +public: + Slots(ScreenImage& gui); + ~Slots(); + + unsigned char io(const int islot, const int iswch, const unsigned char b, const bool writing); + void reset(); + unsigned char readRom(const int islot, const unsigned short addr, const unsigned char data); + unsigned char readSeventhRom(const unsigned short addr, const unsigned char data); + unsigned char ioBankRom(const unsigned short addr, const unsigned char data, const bool write); + bool inhibitMotherboardRom(); + void set(const int slot, Card* card); + Card* get(const int slot); + void remove(const int slot); + bool isDirty(); + void forceGuiUpdate(); +}; + +#endif diff --git a/src/speakerclicker.cpp b/src/speakerclicker.cpp new file mode 100644 index 0000000..aa0dc64 --- /dev/null +++ b/src/speakerclicker.cpp @@ -0,0 +1,179 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "speakerclicker.h" +#include +#include +#include +#include + +std::deque locbuf; +volatile bool done; +SDL_cond* bufchg; +SDL_mutex* buflck; + +// TODO figure out why sound is so choppy, and fix it +void fillbuf(void *userdata, Uint8 *stream, int len) +{ + int tot(0); + while (len && !done) + { + // wait for locbuf to have some data + // (it gets filled up by tick() method) + SDL_LockMutex(buflck); + while (locbuf.empty() && !done) + { + SDL_CondWait(bufchg,buflck); + } + if (done) + { + SDL_UnlockMutex(buflck); + return; + } + while (locbuf.size() > 0 && len > 0) + { + int v = locbuf.front(); + *stream++ = v; + + if (v < 0) v = -v; + tot += v; + + --len; + locbuf.pop_front(); + } + if (tot <= 0) + { + if (locbuf.size() >= 1024) + locbuf.clear(); + } + else + { + if (locbuf.size() >= 65536); + locbuf.clear(); + } + SDL_UnlockMutex(buflck); + } +} + +#define TICKS_PER_SAMPLE 47 +#define AMP 126 + +SpeakerClicker::SpeakerClicker(): + clicked(false), + t(TICKS_PER_SAMPLE) +{ + done = false; + + SDL_AudioSpec audio; + audio.freq = 22050; // samples per second + audio.format = AUDIO_S8; // 8 bits (1 byte) per sample + audio.channels = 1; // mono + audio.silence = 0; + audio.samples = 4096; + audio.callback = fillbuf; + audio.userdata = 0; + SDL_AudioSpec obtained; + obtained.callback = 0; + obtained.userdata = 0; + if (SDL_OpenAudio(&audio,&obtained) != 0) + { + done = true; + std::cerr << "Unable to initialize audio: " << SDL_GetError() << std::endl; + } + else + { + bufchg = SDL_CreateCond(); + buflck = SDL_CreateMutex(); + + std::cout << "initialized audio: " << std::endl; + std::cout << "freq: " << obtained.freq << std::endl; + std::cout << "format: " << (obtained.format==AUDIO_S8?"8-bit":"other") << std::endl; + std::cout << "channels: " << (int)obtained.channels << std::endl; + std::cout << "silence: " << (int)obtained.silence << std::endl; + std::cout << "samples: " << obtained.samples << std::endl; + std::cout << "size: " << obtained.size << std::endl; + SDL_PauseAudio(0); + } +} + + +SpeakerClicker::~SpeakerClicker() +{ + if (done) + { + return; + } + done = true; + SDL_LockMutex(buflck); + SDL_CondSignal(bufchg); + SDL_UnlockMutex(buflck); + SDL_CloseAudio(); + SDL_DestroyCond(bufchg); + SDL_DestroyMutex(buflck); +} + +/* +void SpeakerClicker::tick() +{ + --this->t; + if (this->t <= 0) + { + this->t = TICKS_PER_SAMPLE; + if (this->clicked) + { + this->positive = !this->positive; + this->clicked = false; + } + locbuf.push_back(this->positive ? AMP : -AMP); + locbuf.push_back(this->positive ? AMP : -AMP); + } +} +*/ + +void SpeakerClicker::tick() +{ + if (done) + { + return; + } + + --this->t; + if (this->t <= 0) + { + this->t = TICKS_PER_SAMPLE; + int amp; + if (this->clicked) + { + amp = this->positive ? AMP : -AMP; + this->positive = !this->positive; + this->clicked = false; + } + else + { + amp = 0; + } + SDL_LockMutex(buflck); + locbuf.push_back(amp); + SDL_CondSignal(bufchg); + SDL_UnlockMutex(buflck); + } +} + +void SpeakerClicker::click() +{ + this->clicked = true; +} diff --git a/src/speakerclicker.h b/src/speakerclicker.h new file mode 100644 index 0000000..e977e31 --- /dev/null +++ b/src/speakerclicker.h @@ -0,0 +1,34 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef SPEAKERCLICKER_H +#define SPEAKERCLICKER_H + +class SpeakerClicker +{ +private: + bool clicked; + int t; + bool positive; +public: + SpeakerClicker(); + ~SpeakerClicker(); + void tick(); + void click(); +}; + +#endif diff --git a/src/standardin.cpp b/src/standardin.cpp new file mode 100644 index 0000000..82c6f64 --- /dev/null +++ b/src/standardin.cpp @@ -0,0 +1,65 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "standardin.h" + +StandardIn::StandardIn(): + latch(0), + gotEOF(false) +{ +} + + +StandardIn::~StandardIn() +{ +} + + + + +unsigned char StandardIn::io(const unsigned short address, const unsigned char data, const bool writing) +{ + int sw = address & 0x0F; + if (sw == 0) + { + if (!(this->latch & 0x80)) + { + if (this->gotEOF) + { + this->latch = 0xFF; + } + else + { + if (!this->producer.getKeys().empty()) + { + this->latch = this->producer.getKeys().front(); + this->producer.getKeys().pop(); + if (this->latch == 0xFF) + { + this->gotEOF = true; + } + this->latch |= 0x80; + } + } + } + } + else if (sw == 1) + { + this->latch &= 0x7F; + } + return this->latch; +} diff --git a/src/standardin.h b/src/standardin.h new file mode 100644 index 0000000..236e022 --- /dev/null +++ b/src/standardin.h @@ -0,0 +1,39 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef STANDARDIN_H +#define STANDARDIN_H + +#include "card.h" +#include "standardinproducer.h" + +class StandardIn : public Card +{ +private: + StandardInProducer producer; + unsigned char latch; + bool gotEOF; + +public: + StandardIn(); + ~StandardIn(); + + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + virtual std::string getName() { return "standard input"; } +}; + +#endif diff --git a/src/standardinproducer.cpp b/src/standardinproducer.cpp new file mode 100644 index 0000000..6d379f9 --- /dev/null +++ b/src/standardinproducer.cpp @@ -0,0 +1,100 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "standardinproducer.h" +#include +#include +#include + +#define CR '\r' +#define LF '\n' + +enum state_t { START, GOT_CR, GOT_LF, GOT_EOF }; + + +static int readInput(void *voidkeys) +{ + KeypressQueue* keys = (KeypressQueue*)voidkeys; + + /* + * Continuously read characters from standard in + * and put them onto the queue. + * Stop when we hit EOF (placing EOF onto the queue). + * Translate LF to CR. + * If we hit a CR immediately after a LF, drop it. + * If we hit a LF immediately after a CR, drop it. + */ + enum state_t state = START; + + while (state != GOT_EOF) + { + char c = std::cin.get(); + c &= 0x7F; + if (!std::cin.good()) + { + state = GOT_EOF; + keys->push(0xFF); + } + else + { + if (state == START) + { + if (c == CR) + { + state = GOT_CR; + } + else if (c == LF) + { + state = GOT_LF; + c = CR; + } + keys->push(c); + } + else if (state == GOT_CR) + { + if (c != LF) + { + keys->push(c); + } + state = START; + } + else if (state == GOT_LF) + { + if (c != CR) + { + if (c == LF) + { + c = CR; + } + keys->push(c); + } + state = START; + } + } + } + + return 0; +} + +StandardInProducer::StandardInProducer() +{ + SDL_CreateThread(readInput,&this->keys); +} + +StandardInProducer::~StandardInProducer() +{ +} diff --git a/src/standardinproducer.h b/src/standardinproducer.h new file mode 100644 index 0000000..5626c07 --- /dev/null +++ b/src/standardinproducer.h @@ -0,0 +1,35 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef STANDARDINPRODUCER_H +#define STANDARDINPRODUCER_H + +#include "keyboard.h" + +class StandardInProducer +{ +private: + KeypressQueue keys; + +public: + StandardInProducer(); + ~StandardInProducer(); + + KeypressQueue& getKeys() { return this->keys; } +}; + +#endif diff --git a/src/standardout.cpp b/src/standardout.cpp new file mode 100644 index 0000000..41dcad5 --- /dev/null +++ b/src/standardout.cpp @@ -0,0 +1,50 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "standardout.h" +#include + +StandardOut::StandardOut() +{ +} + + +StandardOut::~StandardOut() +{ +} + + +unsigned char StandardOut::io(const unsigned short address, const unsigned char data, const bool writing) +{ + if (!writing) + { + return data; + } + + const char c = (char)(data&0x7F); + if (c == '\r') + { + std::cout << std::endl; + } + else + { + std::cout << c; + } + std::cout << std::flush; + + return data; +} diff --git a/src/standardout.h b/src/standardout.h new file mode 100644 index 0000000..a1b7434 --- /dev/null +++ b/src/standardout.h @@ -0,0 +1,34 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef STANDARDOUT_H +#define STANDARDOUT_H + +#include "card.h" + +class StandardOut : public Card +{ + +public: + StandardOut(); + ~StandardOut(); + + virtual unsigned char io(const unsigned short address, const unsigned char data, const bool writing); + virtual std::string getName() { return "standard output"; } +}; + +#endif diff --git a/src/steppermotor.cpp b/src/steppermotor.cpp new file mode 100644 index 0000000..a4008f8 --- /dev/null +++ b/src/steppermotor.cpp @@ -0,0 +1,117 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +/** + * Emulates the arm stepper motor in the Disk ][. + * This implementation differs from the actual Disk ][ in + * that it rounds half- and quarter-tracks down to the + * next whole track. Also, this emulator moves the arm + * instantaneously, whereas the Disk ][ arm would actually + * take some time to reach its new position (this would + * cause a difference if the state of the magnets changed + * during this interval). + * + * @author Chris Mosher + */ +/* +mags ps magval +3210 +---- -- ------ +0001 0 1 +0011 1 3 +0010 2 2 +0110 3 6 +0100 4 4 +1100 5 C +1000 6 8 +1001 7 9 + +(strange, but defined) +1011 0 B +0111 2 7 +1110 4 E +1101 6 D + +(undefined, i.e., no movement) +0000 ? 0 +0101 ? 5 +1010 ? A +1111 ? F +*/ +#include "steppermotor.h" +#include "util.h" + +StepperMotor::StepperMotor(): + quarterTrack(QTRACKS >> 1), // start in the middle of the disk... just for fun +// TODO if we want to be extremely accurate, we should save each arm's position on shutdown and restore on startup +// (because in the real-life Apple ][, the arm stays in the same position when powered off). + pos(0), + mags(0) +{ +} + +StepperMotor::~StepperMotor() +{ +} + +signed char StepperMotor::mapMagPos[] = {-1,0,2,1,4,-1,3,2,6,7,-1,0,5,6,4,-1}; + +void StepperMotor::setMagnet(const unsigned char magnet, const bool on) +{ + const unsigned char mask = 1 << magnet; + if (on) + { + this->mags |= mask; + } + else + { + this->mags &= ~mask; + } + + const char newPos = mapMagPos[this->mags]; + char d; + if (newPos >= 0) + { + d = calcDeltaPos(this->pos,newPos); + this->pos = newPos; + + this->quarterTrack += d; + if (this->quarterTrack < 0) + this->quarterTrack = 0; + else if (this->quarterTrack > QTRACKS) + this->quarterTrack = QTRACKS; + } +/* + std::cout << " ARM: magnet " << (unsigned int)magnet << " " << (on ? "on " : "off" ); + std::cout << " [" << + ((mags&1)?"*":".") << + ((mags&2)?"*":".") << + ((mags&4)?"*":".") << + ((mags&8)?"*":".") << + "]"; + if (d != 0) + { + std::cout << " track " << std::hex << (unsigned int)(this->quarterTrack >> 2); + int fract = this->quarterTrack & 3; + if (fract != 0) + { + std::cout << (fract == 1 ? " +.25" : fract == 2 ? " +.5" : " +.75"); + } + } + std::cout << std::endl; +*/ +} diff --git a/src/steppermotor.h b/src/steppermotor.h new file mode 100644 index 0000000..06888d7 --- /dev/null +++ b/src/steppermotor.h @@ -0,0 +1,64 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef STEPPERMOTOR_H +#define STEPPERMOTOR_H + +class StepperMotor +{ +private: + enum { TRACKS_PER_DISK = 0x23 }; + enum { QTRACKS = TRACKS_PER_DISK << 2 }; + + signed short quarterTrack; + signed char pos; // 0 - 7 + unsigned char mags; + + static signed char mapMagPos[]; + + static signed char calcDeltaPos(const unsigned char cur, const signed char next) + { + signed char d = next-cur; // -7 to +7 + + if (d==4 || d==-4) + { + d = 0; + } + else if (d>4) + { + d -= 8; + } + else if (d<-4) + { + d += 8; + } + + return d; + } + +public: + StepperMotor(); + ~StepperMotor(); + + void setMagnet(const unsigned char magnet, const bool on); + unsigned char getTrack() + { + return ((unsigned short)(this->quarterTrack)) >> 2; + } +}; + +#endif diff --git a/src/textcharacterimages.h b/src/textcharacterimages.h new file mode 100644 index 0000000..0c5c701 --- /dev/null +++ b/src/textcharacterimages.h @@ -0,0 +1,580 @@ +/* + The string in this file represents the character images from + General Instrument's 2513 character generator ROM chip. + This is the chip that was in the original Apple ][ and ][+ + (motherboard revisions 0 thru 6). +*/ +"-@@@-" +"@---@" +"@-@-@" +"@-@@@" +"@-@@-" +"@----" +"-@@@@" + + +"--@--" +"-@-@-" +"@---@" +"@---@" +"@@@@@" +"@---@" +"@---@" + + +"@@@@-" +"@---@" +"@---@" +"@@@@-" +"@---@" +"@---@" +"@@@@-" + + +"-@@@-" +"@---@" +"@----" +"@----" +"@----" +"@---@" +"-@@@-" + + +"@@@@-" +"@---@" +"@---@" +"@---@" +"@---@" +"@---@" +"@@@@-" + + +"@@@@@" +"@----" +"@----" +"@@@@-" +"@----" +"@----" +"@@@@@" + + +"@@@@@" +"@----" +"@----" +"@@@@-" +"@----" +"@----" +"@----" + + +"-@@@@" +"@----" +"@----" +"@----" +"@--@@" +"@---@" +"-@@@@" + + +"@---@" +"@---@" +"@---@" +"@@@@@" +"@---@" +"@---@" +"@---@" + + +"-@@@-" +"--@--" +"--@--" +"--@--" +"--@--" +"--@--" +"-@@@-" + + +"----@" +"----@" +"----@" +"----@" +"----@" +"@---@" +"-@@@-" + + +"@---@" +"@--@-" +"@-@--" +"@@---" +"@-@--" +"@--@-" +"@---@" + + +"@----" +"@----" +"@----" +"@----" +"@----" +"@----" +"@@@@@" + + +"@---@" +"@@-@@" +"@-@-@" +"@-@-@" +"@---@" +"@---@" +"@---@" + + +"@---@" +"@---@" +"@@--@" +"@-@-@" +"@--@@" +"@---@" +"@---@" + + +"-@@@-" +"@---@" +"@---@" +"@---@" +"@---@" +"@---@" +"-@@@-" + + +"@@@@-" +"@---@" +"@---@" +"@@@@-" +"@----" +"@----" +"@----" + + +"-@@@-" +"@---@" +"@---@" +"@---@" +"@-@-@" +"@--@-" +"-@@-@" + + +"@@@@-" +"@---@" +"@---@" +"@@@@-" +"@-@--" +"@--@-" +"@---@" + + +"-@@@-" +"@---@" +"@----" +"-@@@-" +"----@" +"@---@" +"-@@@-" + + +"@@@@@" +"--@--" +"--@--" +"--@--" +"--@--" +"--@--" +"--@--" + + +"@---@" +"@---@" +"@---@" +"@---@" +"@---@" +"@---@" +"-@@@-" + + +"@---@" +"@---@" +"@---@" +"@---@" +"@---@" +"-@-@-" +"--@--" + + +"@---@" +"@---@" +"@---@" +"@-@-@" +"@-@-@" +"@@-@@" +"@---@" + + +"@---@" +"@---@" +"-@-@-" +"--@--" +"-@-@-" +"@---@" +"@---@" + + +"@---@" +"@---@" +"-@-@-" +"--@--" +"--@--" +"--@--" +"--@--" + + +"@@@@@" +"----@" +"---@-" +"--@--" +"-@---" +"@----" +"@@@@@" + + +"@@@@@" +"@@---" +"@@---" +"@@---" +"@@---" +"@@---" +"@@@@@" + + +"-----" +"@----" +"-@---" +"--@--" +"---@-" +"----@" +"-----" + + +"@@@@@" +"---@@" +"---@@" +"---@@" +"---@@" +"---@@" +"@@@@@" + + +"-----" +"-----" +"--@--" +"-@-@-" +"@---@" +"-----" +"-----" + + +"-----" +"-----" +"-----" +"-----" +"-----" +"-----" +"@@@@@" + + +"-----" +"-----" +"-----" +"-----" +"-----" +"-----" +"-----" + + +"--@--" +"--@--" +"--@--" +"--@--" +"--@--" +"-----" +"--@--" + + +"-@-@-" +"-@-@-" +"-@-@-" +"-----" +"-----" +"-----" +"-----" + + +"-@-@-" +"-@-@-" +"@@@@@" +"-@-@-" +"@@@@@" +"-@-@-" +"-@-@-" + + +"--@--" +"-@@@@" +"@-@--" +"-@@@-" +"--@-@" +"@@@@-" +"--@--" + + +"@@---" +"@@--@" +"---@-" +"--@--" +"-@---" +"@--@@" +"---@@" + + +"-@---" +"@-@--" +"@-@--" +"-@---" +"@-@-@" +"@--@-" +"-@@-@" + + +"--@--" +"--@--" +"--@--" +"-----" +"-----" +"-----" +"-----" + + +"--@--" +"-@---" +"@----" +"@----" +"@----" +"-@---" +"--@--" + + +"--@--" +"---@-" +"----@" +"----@" +"----@" +"---@-" +"--@--" + + +"--@--" +"@-@-@" +"-@@@-" +"--@--" +"-@@@-" +"@-@-@" +"--@--" + + +"-----" +"--@--" +"--@--" +"@@@@@" +"--@--" +"--@--" +"-----" + + +"-----" +"-----" +"-----" +"-----" +"--@--" +"--@--" +"-@---" + + +"-----" +"-----" +"-----" +"@@@@@" +"-----" +"-----" +"-----" + + +"-----" +"-----" +"-----" +"-----" +"-----" +"-----" +"--@--" + + +"-----" +"----@" +"---@-" +"--@--" +"-@---" +"@----" +"-----" + + +"-@@@-" +"@---@" +"@--@@" +"@-@-@" +"@@--@" +"@---@" +"-@@@-" + + +"--@--" +"-@@--" +"--@--" +"--@--" +"--@--" +"--@--" +"-@@@-" + + +"-@@@-" +"@---@" +"----@" +"--@@-" +"-@---" +"@----" +"@@@@@" + + +"@@@@@" +"----@" +"---@-" +"--@@-" +"----@" +"@---@" +"-@@@-" + + +"---@-" +"--@@-" +"-@-@-" +"@--@-" +"@@@@@" +"---@-" +"---@-" + + +"@@@@@" +"@----" +"@@@@-" +"----@" +"----@" +"@---@" +"-@@@-" + + +"--@@@" +"-@---" +"@----" +"@@@@-" +"@---@" +"@---@" +"-@@@-" + + +"@@@@@" +"----@" +"---@-" +"--@--" +"-@---" +"-@---" +"-@---" + + +"-@@@-" +"@---@" +"@---@" +"-@@@-" +"@---@" +"@---@" +"-@@@-" + + +"-@@@-" +"@---@" +"@---@" +"-@@@@" +"----@" +"---@-" +"@@@--" + + +"-----" +"-----" +"--@--" +"-----" +"--@--" +"-----" +"-----" + + +"-----" +"-----" +"--@--" +"-----" +"--@--" +"--@--" +"-@---" + + +"---@-" +"--@--" +"-@---" +"@----" +"-@---" +"--@--" +"---@-" + + +"-----" +"-----" +"@@@@@" +"-----" +"@@@@@" +"-----" +"-----" + + +"-@---" +"--@--" +"---@-" +"----@" +"---@-" +"--@--" +"-@---" + + +"-@@@-" +"@---@" +"---@-" +"--@--" +"--@--" +"-----" +"--@--" diff --git a/src/textcharacters.cpp b/src/textcharacters.cpp new file mode 100644 index 0000000..5f168f0 --- /dev/null +++ b/src/textcharacters.cpp @@ -0,0 +1,45 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "textcharacters.h" + +TextCharacters::TextCharacters(): + rows(0x40*8) +{ + int r(0); + + const char *pi = +#include "textcharacterimages.h" + ; + + for (int ch(0); ch < 0x40; ++ch) + { + + rows[r] = 0; + ++r; + for (int ln(1); ln < 8; ++ln) + { + for (int bt(0); bt < 5; ++bt) + { + rows[r] >>= 1; + if (*pi++=='@') + rows[r] |= 0x20; + } + ++r; + } + } +} diff --git a/src/textcharacters.h b/src/textcharacters.h new file mode 100644 index 0000000..96669e9 --- /dev/null +++ b/src/textcharacters.h @@ -0,0 +1,37 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef TEXTCHARACTERS_H +#define TEXTCHARACTERS_H + +#include + +class TextCharacters +{ +private: + std::vector rows; + +public: + TextCharacters(); + ~TextCharacters() {} + unsigned char get(unsigned int iRow) + { + return this->rows[iRow]; + } +}; + +#endif diff --git a/src/timable.cpp b/src/timable.cpp new file mode 100644 index 0000000..97a25cd --- /dev/null +++ b/src/timable.cpp @@ -0,0 +1,18 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "timable.h" diff --git a/src/timable.h b/src/timable.h new file mode 100644 index 0000000..26a7a5a --- /dev/null +++ b/src/timable.h @@ -0,0 +1,30 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef TIMABLE_H +#define TIMABLE_H + +class Timable +{ +public: + Timable() {} + virtual ~Timable() {} + + virtual void tick() = 0; +}; + +#endif diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..2986a02 --- /dev/null +++ b/src/util.h @@ -0,0 +1,64 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UTIL_H +#define UTIL_H + +class Util +{ +public: + static int divideRoundUp(const int num, const int denom) + { + return (num+denom-1)/denom; + } + + static int divideRound(const int dividend, const int divisor) + { + return (dividend+divisor/2)/divisor; + } + + template static T mod(T x, const T m) + { + x %= m; + if (x < 0) + { + x += m; + } + return x; + } + + template static void constrain(const T& min, T& x, const T& lim) + { + if (x < min) + { + x = min; + } + else if (lim <= x) + { + x = lim-1; + } + } + + // 0x0 <= nib <= 0xF + // '0' <= ret <= 'F' + static char hexDigit(unsigned char nib) + { + return nib < 10 ? '0'+nib : 'A'+nib-10; + } +}; + +#endif diff --git a/src/video.cpp b/src/video.cpp new file mode 100644 index 0000000..5a47a40 --- /dev/null +++ b/src/video.cpp @@ -0,0 +1,125 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "video.h" +#include "e2const.h" +#include "videoaddressing.h" +#include "videomode.h" +#include "addressbus.h" +#include "picturegenerator.h" +#include "textcharacters.h" + + +static const int FLASH_HALF_PERIOD = E2Const::AVG_CPU_HZ/4; // 2 Hz period = 4 Hz half-period + + +Video::Video(VideoMode& mode, AddressBus& addressBus, PictureGenerator& picgen, TextCharacters& textRows): + mode(mode), addressBus(addressBus), picgen(picgen), textRows(textRows) +{ + VideoAddressing::buildLUT(TEXT_BASE_1,TEXT_LEN,lutTEXT[0]); + VideoAddressing::buildLUT(TEXT_BASE_2,TEXT_LEN,lutTEXT[1]); + VideoAddressing::buildLUT(HRES_BASE_1,HRES_LEN,lutHRES[0]); + VideoAddressing::buildLUT(HRES_BASE_2,HRES_LEN,lutHRES[1]); +} + +Video::~Video() +{ +} + +void Video::powerOn() +{ + this->t = 0; + this->flash = false; + this->cflash = 0; +} + +void Video::tick() +{ + const unsigned char data = getDataByte(); + const unsigned char rowToPlot = getRowToPlot(data); + + this->picgen.tick(this->t,rowToPlot); + + updateFlash(); + + ++this->t; + + if (this->t >= E2Const::BYTES_PER_FIELD) + { + this->t = 0; + } +} + +void Video::updateFlash() +{ + ++this->cflash; + if (this->cflash >= FLASH_HALF_PERIOD) + { + this->flash = !this->flash; + this->cflash = 0; + } +} + +unsigned char Video::getDataByte() +{ + // TODO should fix the mixed-mode scanning during VBL (see U.A.][, p. 5-13) + + std::vector& lut = + (this->mode.isDisplayingText(this->t) || !this->mode.isHiRes()) + ? + this->lutTEXT[this->mode.getPage()] + : + this->lutHRES[this->mode.getPage()]; + + return this->addressBus.read(lut[this->t]); +} + +unsigned char Video::getRowToPlot(unsigned char d) +{ + if (this->mode.isDisplayingText(this->t)) + { + const bool inverse = inverseChar(d); + const int y = this->t / E2Const::BYTES_PER_ROW; + d = this->textRows.get(((d & 0x3F) << 3) | (y & 0x07)); + if (inverse) + { + d = ~d & 0x7F; + } + } + return d; +} + +bool Video::inverseChar(const unsigned char d) +{ + bool inverse; + + const int cs = (d >> 6) & 3; + if (cs == 0) + { + inverse = true; + } + else if (cs == 1) + { + inverse = this->flash; + } + else + { + inverse = false; + } + + return inverse; +} diff --git a/src/video.h b/src/video.h new file mode 100644 index 0000000..9380090 --- /dev/null +++ b/src/video.h @@ -0,0 +1,65 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef VIDEO_H_ +#define VIDEO_H_ + +#include + +class VideoMode; +class AddressBus; +class PictureGenerator; +class TextCharacters; + +class Video +{ +private: + enum { TEXT_BASE_1 = 0x0400 }; + enum { TEXT_BASE_2 = 0x0800 }; + enum { TEXT_LEN = 0x0400 }; + + enum { HRES_BASE_1 = 0x2000 }; + enum { HRES_BASE_2 = 0x4000 }; + enum { HRES_LEN = 0x2000 }; + + std::vector lutTEXT[2]; // [0] is page 1, [1] is page 2 + std::vector lutHRES[2]; // [0] is page 1, [1] is page 2 + + VideoMode& mode; + AddressBus& addressBus; + PictureGenerator& picgen; + + TextCharacters& textRows; + + unsigned int t; + + bool flash; + int cflash; + + void updateFlash(); + unsigned char getDataByte(); + unsigned char getRowToPlot(unsigned char d); + bool inverseChar(const unsigned char d); + +public: + Video(VideoMode& mode, AddressBus& addressBus, PictureGenerator& picgen, TextCharacters& textRows); + ~Video(); + void powerOn(); + void tick(); +}; + +#endif /*VIDEO_H_*/ diff --git a/src/videoaddressing.cpp b/src/videoaddressing.cpp new file mode 100644 index 0000000..a140fac --- /dev/null +++ b/src/videoaddressing.cpp @@ -0,0 +1,85 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "videoaddressing.h" +#include "e2const.h" + +VideoAddressing::VideoAddressing() +{ +} + +static int calc(const unsigned int t) +{ + int c = t % E2Const::VISIBLE_BYTES_PER_FIELD; + if (t >= E2Const::SCANNABLE_BYTES) + { + c -= E2Const::RESET_BYTES; + } + + int n = c / E2Const::BYTES_PER_ROW; + const int s = (n >> 6); + n -= s << 6; + const int q = (n >> 3); + n -= q << 3; + const int base = (n<<10) + (q<<7) + E2Const::VISIBLE_BYTES_PER_ROW*s; + + const int half_page = base & 0xFF80; + + int a = base+(c%E2Const::BYTES_PER_ROW)-E2Const::BLANKED_BYTES_PER_ROW; + if (a < half_page) + { + a += 0x80; + } + return a; +} + +void VideoAddressing::buildLUT(const unsigned short base, const unsigned short len, std::vector& lut) +{ + lut.resize(E2Const::BYTES_PER_FIELD); + for (unsigned int t = 0; t < E2Const::BYTES_PER_FIELD; ++t) + { + unsigned int off = (calc(t) % len); + + const unsigned int col = t % E2Const::BYTES_PER_ROW; + const unsigned int row = t / E2Const::BYTES_PER_ROW; + + if (col < E2Const::BLANKED_BYTES_PER_ROW) + { + // HBL + if (base < 0x1000) + { + off += 0x1000; + } + if (col == 0) + { + ++off; + } + } + + if (row >= E2Const::VISIBLE_ROWS_PER_FIELD) + { + // VBL + const int base2 = off & 0xFF80; + off &= 0x7F; + off -= 8; + off &= 0x7F; + off += base2; + } + + lut[t] = base + off; + } +} diff --git a/src/videoaddressing.h b/src/videoaddressing.h new file mode 100644 index 0000000..0586d7e --- /dev/null +++ b/src/videoaddressing.h @@ -0,0 +1,30 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef VIDEOADDRESSING_H +#define VIDEOADDRESSING_H + +#include + +class VideoAddressing +{ +public: + VideoAddressing(); + static void buildLUT(const unsigned short base, const unsigned short len, std::vector& lut); +}; + +#endif diff --git a/src/videomode.cpp b/src/videomode.cpp new file mode 100644 index 0000000..3c7a3d8 --- /dev/null +++ b/src/videomode.cpp @@ -0,0 +1,48 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "videomode.h" + +VideoMode::VideoMode() +{ +} + +unsigned char VideoMode::io(const unsigned short addr, const unsigned char b) +{ + const int sw = (addr & 0xE) >> 1; + const bool on = addr & 0x1; + switch (sw) + { + case 0: + this->swText = on; break; + case 1: + this->swMixed = on; break; + case 2: + this->swPage2 = on ? 1 : 0; break; + case 3: + this->swHiRes = on; break; + } + return b; +} + +void VideoMode::powerOn() +{ + this->swText = false; + this->swMixed = false; + this->swPage2 = 0; + this->swHiRes = false; +} diff --git a/src/videomode.h b/src/videomode.h new file mode 100644 index 0000000..b929ae5 --- /dev/null +++ b/src/videomode.h @@ -0,0 +1,44 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef VIDEOMODE_H +#define VIDEOMODE_H + +#include "videoaddressing.h" +#include "e2const.h" + +class VideoMode +{ +private: + bool swText; + bool swMixed; + int swPage2; + bool swHiRes; + +public: + VideoMode(); + unsigned char io(const unsigned short addr, const unsigned char b); + void powerOn(); + + bool isText() const { return this->swText; } + bool isHiRes() const { return this->swHiRes; } + bool isMixed() const { return this->swMixed; } + int getPage() const { return this->swPage2; } + bool isDisplayingText(const int atTickInField) const { return this->swText || (this->swMixed && atTickInField >= E2Const::MIXED_TEXT_CYCLE); } +}; + +#endif diff --git a/src/videostaticgenerator.cpp b/src/videostaticgenerator.cpp new file mode 100644 index 0000000..b12484c --- /dev/null +++ b/src/videostaticgenerator.cpp @@ -0,0 +1,70 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "videostaticgenerator.h" +#include "analogtv.h" +#include "e2const.h" +#include +#include + +VideoStaticGenerator::VideoStaticGenerator(AnalogTV& display): + display(display), + isig(sig), + isiglim(sig+AppleNTSC::SIGNAL_LEN), + hpos(0) +{ + this->display.signal = sig; + srand(time(0)); +} + + +VideoStaticGenerator::~VideoStaticGenerator() +{ +} + + +void VideoStaticGenerator::tick() +{ + signed char* is = this->isig; + unsigned int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE; + if (this->hpos == E2Const::HORIZ_CYCLES-1) + { + cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE; + } + for (unsigned int i = 0; i < cycles; ++i) + { + *is++ = (rand()>>7&0x7F)-27; + } + this->isig = is; + ++this->hpos; + if (this->hpos >= E2Const::HORIZ_CYCLES) + { + this->hpos = 0; + if (isig >= isiglim) + { + isig = sig; + this->display.drawCurrent(); + } + } +} + +void VideoStaticGenerator::powerOn() +{ + this->hpos = 0; + this->display.signal = sig; + isig = sig; +} diff --git a/src/videostaticgenerator.h b/src/videostaticgenerator.h new file mode 100644 index 0000000..648cdeb --- /dev/null +++ b/src/videostaticgenerator.h @@ -0,0 +1,42 @@ +/* + epple2 + Copyright (C) 2008 by Chris Mosher + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef VIDEOSTATICGENERATOR_H +#define VIDEOSTATICGENERATOR_H + +class AnalogTV; +#include "timable.h" +#include "applentsc.h" + +class VideoStaticGenerator : public Timable +{ +private: + AnalogTV& display; + signed char sig[AppleNTSC::SIGNAL_LEN]; + signed char* isig; + signed char* isiglim; + unsigned int hpos; + +public: + VideoStaticGenerator(AnalogTV& display); + ~VideoStaticGenerator(); + + virtual void tick(); + void powerOn(); +}; + +#endif