From 44f39fe332559cc5fafe3aa0b6be26ae202d21f2 Mon Sep 17 00:00:00 2001 From: Ed McCardell Date: Tue, 13 Mar 2012 20:43:29 -0400 Subject: [PATCH] Initial commit --- COPYING | 339 +++++++++++ README | 51 ++ src/Makefile | 23 + src/d6502/base.d | 140 +++++ src/d6502/cmos.d | 238 ++++++++ src/d6502/cpu.d | 758 +++++++++++++++++++++++ src/d6502/nmosbase.d | 67 ++ src/d6502/nmosundoc.d | 351 +++++++++++ src/data/charset_lower | 89 +++ src/data/charset_mouse | 91 +++ src/data/charset_symbol_ii | 89 +++ src/data/charset_symbol_iie | 89 +++ src/data/charset_upper_ii | 90 +++ src/data/charset_upper_iie | 90 +++ src/data/hires80_test | Bin 0 -> 16384 bytes src/data/hires_test | Bin 0 -> 8192 bytes src/data/laz_scheme | 442 ++++++++++++++ src/data/laz_sharp | 37 ++ src/data/lores_test | 130 ++++ src/data/palette_5_6_5 | 65 ++ src/device/annunciator.d | 111 ++++ src/device/base.d | 203 +++++++ src/device/keyboard.d | 412 +++++++++++++ src/device/paddle.d | 161 +++++ src/device/pushbutton.d | 67 ++ src/device/speaker.d | 138 +++++ src/host.d | 160 +++++ src/iomem.d | 310 ++++++++++ src/ioummu.d | 407 +++++++++++++ src/memory.d | 414 +++++++++++++ src/peripheral/base.d | 59 ++ src/peripheral/diskii.d | 1144 +++++++++++++++++++++++++++++++++++ src/peripheral/langcard.d | 430 +++++++++++++ src/peripheral/saturn128.d | 171 ++++++ src/sndfile.d | 653 ++++++++++++++++++++ src/system/base.d | 305 ++++++++++ src/system/io.d | 158 +++++ src/system/peripheral.d | 87 +++ src/system/video.d | 124 ++++ src/timer.d | 244 ++++++++ src/twoapple.d | 115 ++++ src/ui/inputevents.d | 281 +++++++++ src/ui/mainwindow.d | 454 ++++++++++++++ src/ui/monitor.d | 93 +++ src/ui/sound.d | 209 +++++++ src/ui/textinput.d | 79 +++ src/video/base.d | 98 +++ src/video/laz_engine.d | 214 +++++++ src/video/offsets.d | 171 ++++++ src/video/patterns.d | 485 +++++++++++++++ src/video/scanner.d | 295 +++++++++ src/video/signal.d | 207 +++++++ 52 files changed, 11638 insertions(+) create mode 100644 COPYING create mode 100644 README create mode 100644 src/Makefile create mode 100644 src/d6502/base.d create mode 100644 src/d6502/cmos.d create mode 100644 src/d6502/cpu.d create mode 100644 src/d6502/nmosbase.d create mode 100644 src/d6502/nmosundoc.d create mode 100644 src/data/charset_lower create mode 100644 src/data/charset_mouse create mode 100644 src/data/charset_symbol_ii create mode 100644 src/data/charset_symbol_iie create mode 100644 src/data/charset_upper_ii create mode 100644 src/data/charset_upper_iie create mode 100644 src/data/hires80_test create mode 100644 src/data/hires_test create mode 100644 src/data/laz_scheme create mode 100644 src/data/laz_sharp create mode 100644 src/data/lores_test create mode 100644 src/data/palette_5_6_5 create mode 100644 src/device/annunciator.d create mode 100644 src/device/base.d create mode 100644 src/device/keyboard.d create mode 100644 src/device/paddle.d create mode 100644 src/device/pushbutton.d create mode 100644 src/device/speaker.d create mode 100644 src/host.d create mode 100644 src/iomem.d create mode 100644 src/ioummu.d create mode 100644 src/memory.d create mode 100644 src/peripheral/base.d create mode 100644 src/peripheral/diskii.d create mode 100644 src/peripheral/langcard.d create mode 100644 src/peripheral/saturn128.d create mode 100644 src/sndfile.d create mode 100644 src/system/base.d create mode 100644 src/system/io.d create mode 100644 src/system/peripheral.d create mode 100644 src/system/video.d create mode 100644 src/timer.d create mode 100644 src/twoapple.d create mode 100644 src/ui/inputevents.d create mode 100644 src/ui/mainwindow.d create mode 100644 src/ui/monitor.d create mode 100644 src/ui/sound.d create mode 100644 src/ui/textinput.d create mode 100644 src/video/base.d create mode 100644 src/video/laz_engine.d create mode 100644 src/video/offsets.d create mode 100644 src/video/patterns.d create mode 100644 src/video/scanner.d create mode 100644 src/video/signal.d diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/README b/README new file mode 100644 index 0000000..8f3f030 --- /dev/null +++ b/README @@ -0,0 +1,51 @@ +=Twoapple Apple II Emulator= + +http://twoapple.googlecode.com + +==About== + +Twoapple is an Apple II/IIe emulator written in the D programming language. + +==Usage== + +When started with no command-line options, Twoapple will run in II+ mode; use +the "--iie" command-line option to run in IIe mode. + +To select the ROM to use, choose a suitable file in the dialog which opens at +program startup. (Currently, there are no checks for ROM file validity beyond a +simple file size test). + +The user interface is fairly simple at the moment: F12 acts as reset, and +Pause/Break allows you to pause and restart the emulation (there is also a +toolbar button for this). The "Boot" toolbar button acts as a power on reset, +and the "Mono" toolbar button switches back and forth between color and +monochrome display. + +Opening, saving, and ejecting disk images can be done using the drive menus at +the bottom of the window. The "WP" checkbox acts like a write-protect switch +for the drive, rather than for individual images. (If a read-only image file is +loaded, the write-protect status is set for the drive, and cannot be changed +until another image is loaded.) + +==Building from source== + +To build twoapple, you will first need to install the Digital Mars D compiler +(DMD) the D language bindings to GTK+ (gtkD), and the D language bindings to +SDL (Derelict). + +* DMD: http://www.digitalmars.com/d/1.0/changelog.html +* gtkD: http://www.dsource.org/projects/dui + * depends on GTK+2.0 (http://www.gtk.org) and GtkGLExt + (http://gtkglext.sourceforge.net). + * this patch (http://twoapple.googlecode.com/files/gtkD-pre3-ref-fix.patch) + is also needed (from the gtkD directory, + `patch -p1 < gtkD-pre3-ref-fix.patch`). +* Derelict: http://www.dsource.org/projects/derelict + * depends on SDL (http://www.sdl.org). + * follow the build instructions to build the static libraries. +* Twoapple: http://code.google.com/p/twoapple/downloads/list + * (or via svn: + `svn checkout http://twoapple.googlecode.com/svn/trunk twoapple`). + * to build: `cd src; make DMDROOT=/path/to/dmd GTKD=/path/to/gtkD + DERELICT=/path/to/derelict` + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d4a195c --- /dev/null +++ b/src/Makefile @@ -0,0 +1,23 @@ +DMD_OPTS = -c -version=CycleAccuracy -op -Jdata -I$(GTKD)/src -I$(GTKD)/srcgl \ + -I$(DERELICT)/DerelictSDL -I$(DERELICT)/DerelictUtil + +GCC_OPTS = -m32 -lpthread -lm -lGL -ldl \ + -L$(DMDROOT)/dmd/lib -lphobos \ + -L$(GTKD) -lgtkd -lgtkdgl \ + -L$(DERELICT)/lib -lDerelictSDL -lDerelictUtil + +ALL_SRC = $(shell find -name "*.d") +ALL_OBJS = $(ALL_SRC:%.d=%.o) + +all: ${ALL_OBJS} + gcc ${ALL_OBJS} -o twoapple ${GCC_OPTS} + +clean: + rm -rf twoapple ${ALL_OBJS} + +twoapple.o: twoapple.d + dmd $(DFLAGS) -inline -O $(DMD_OPTS) $< + +%.o: %.d + dmd $(DFLAGS) -inline -release -O $(DMD_OPTS) $< + diff --git a/src/d6502/base.d b/src/d6502/base.d new file mode 100644 index 0000000..9adf295 --- /dev/null +++ b/src/d6502/base.d @@ -0,0 +1,140 @@ +/+ + + d6502/base.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module d6502.base; + +string hexByte(int decByte) +{ + int highNybble = (decByte & 0xF0) >> 4; + int lowNybble = decByte & 0x0F; + + string digits = "0123456789ABCDEF"; + + return digits[highNybble..(highNybble + 1)] ~ + digits[lowNybble..(lowNybble + 1)]; +} + +final class StatusRegister +{ + bool carry, decimal, interrupt, overflow; + ubyte zero_, negative_; + + ubyte toByte() + { + return (carry ? 0x01 : 0) | + ((zero_ == 0) ? 0x02 : 0) | + (interrupt ? 0x04 : 0) | + (decimal ? 0x08 : 0) | + 0x30 | // break and reserved both set + (overflow ? 0x40 : 0) | + (negative_ & 0x80); + } + + void fromByte(ubyte val) + { + carry = ((val & 0x01) != 0); + zero_ = ((val & 0x02) ? 0 : 1); + interrupt = ((val & 0x04) != 0); + decimal = ((val & 0x08) != 0); + overflow = ((val & 0x40) != 0); + negative_ = val; + } +} + +class CpuBase +{ + static string AbstractOpcodes() + { + string abstractOpcodes; + for (int op = 0; op < 256; ++op) + abstractOpcodes ~= "abstract void opcode" ~ hexByte(op) ~ "();\n"; + return abstractOpcodes; + } + + mixin(AbstractOpcodes()); + + ushort programCounter; + ubyte accumulator, xIndex, yIndex, stackPointer; + final StatusRegister flag; + + bool signalActive, irqActive, resetActive, nmiActive, nmiArmed; + + ushort opcodePC; + ubyte opcode, operand1, operand2; + + final ubyte[] save() + { + ubyte[] data = new ubyte[12]; + data[0] = programCounter & 0xFF; + data[1] = programCounter >> 8; + data[2] = accumulator; + data[3] = xIndex; + data[4] = yIndex; + data[5] = stackPointer; + data[6] = flag.toByte(); + data[7] = (signalActive ? 1 : 0); + data[8] = (irqActive ? 1 : 0); + data[9] = (resetActive ? 1 : 0); + data[10] = (nmiActive ? 1 : 0); + data[11] = (nmiArmed ? 1 : 0); + return data; + } + + final void restore(ubyte[] data) + { + assert (data.length >= 12); + + programCounter = data[0] | (data[1] << 8); + accumulator = data[2]; + xIndex = data[3]; + yIndex = data[4]; + stackPointer = data[5]; + flag.fromByte(data[6]); + signalActive = ((data[7] == 0) ? false : true); + irqActive = ((data[8] == 0) ? false : true); + resetActive = ((data[9] == 0) ? false : true); + nmiActive = ((data[10] == 0) ? false : true); + nmiArmed = ((data[11] == 0) ? false : true); + } + + final void reboot() + { + restore([0, 0, 0, 0, 0, 0xFF, 0, 0, 0, 0, 0, 1]); + } + + ubyte delegate(ushort addr) memoryRead; + void delegate(ushort addr, ubyte val) memoryWrite; + debug(disassemble) + { + string delegate(ushort addr) memoryName; + } + version(CycleAccuracy) void delegate() tick; + version(CumulativeCycles) void delegate(int cycles) ticks; + + abstract void run(bool continuous); + abstract void stop(); + version(CycleAccuracy) abstract bool checkFinalCycle(); + abstract void resetLow(); + abstract void nmiLow(bool signalLow); + abstract void irqLow(bool signalLow); +} + diff --git a/src/d6502/cmos.d b/src/d6502/cmos.d new file mode 100644 index 0000000..4aaad7f --- /dev/null +++ b/src/d6502/cmos.d @@ -0,0 +1,238 @@ +/+ + + d6502/cmos.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import d6502.base; +import d6502.cpu; + +class Cmos : Cpu +{ + this() + { + super(); + spuriousAddress = &programCounter; + } + + final override void do_IRQ_or_NMI(ushort vector) + { + super.do_IRQ_or_NMI(vector); + flag.decimal = false; + } + + final override void doReset() + { + super.doReset(); + flag.decimal = false; + } + + final override void dec_addWithCarry(ubyte val) + { + super.dec_addWithCarry(val); + peek(programCounter); + flag.zero_ = flag.negative_ = accumulator; + } + + final override void dec_subWithCarry(ubyte val) + { + super.dec_subWithCarry(val); + peek(programCounter); + flag.zero_ = flag.negative_ = accumulator; + } + + final void addrZeropageI() + { + ubyte vector = readByteOperand(); + primaryAddress = readWord(vector, cast(ubyte)(vector + 1)); + } + + final void addrNone() + { + version(CumulativeCycles) ticks(totalCycles); + } + + final ubyte testSet(ubyte val) + { + flag.zero_ = val & accumulator; + return val | accumulator; + } + + final ubyte testReset(ubyte val) + { + flag.zero_ = val & accumulator; + return val & ~accumulator; + } + + static string RMW(string action) + { + return "peek(primaryAddress);\n" ~ + "writeFinal(primaryAddress, (flag.zero_ = flag.negative_ = " ~ + action ~ "(readVal = read(primaryAddress))));\n"; + } + + static string TestModify(string action) + { + return "peek(primaryAddress);\n" ~ + "writeFinal(primaryAddress, " ~ + action ~ "(readVal = read(primaryAddress)));\n"; + } + + static string ReadNOP() + { + return "readVal = readFinal(primaryAddress);\n"; + } + + static string ManualAddress(string name, int[] opcodes, + string mode) + { + string modes = "[[\"" ~ name ~ "\", \"NA\"], \n"; + for (int op = 0; op < opcodes.length; ++op) + { + int opcode = opcodes[op]; + modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"" ~ mode ~ "\"]"; + if (op != (opcodes.length - 1)) modes ~= ", "; + modes ~= "\n"; + } + return modes ~ "]\n"; + } + + mixin(Opcode(mixin(Type2Address( + "ASL", "Read", [0x06, 0x0E, 0x16, 0x1E])), + RMW("shiftLeft"))); + mixin(Opcode(mixin(Type2Address( + "LSR", "Read", [0x46, 0x4E, 0x56, 0x5E])), + RMW("shiftRight"))); + mixin(Opcode(mixin(Type2Address( + "ROL", "Read", [0x26, 0x2E, 0x36, 0x3E])), + RMW("rotateLeft"))); + mixin(Opcode(mixin(Type2Address( + "ROR", "Read", [0x66, 0x6E, 0x76, 0x7E])), + RMW("rotateRight"))); + mixin(Opcode(mixin(Type2Address( + "INC", "Read", [0xE6, 0xEE, 0xF6, 0xFE])), + RMW("increment"))); + mixin(Opcode(mixin(Type2Address( + "DEC", "Read", [0xC6, 0xCE, 0xD6, 0xDE])), + RMW("decrement"))); + + mixin(Opcode(mixin(Type2Address( + "BIT", "Read", [0x34, 0x3C])), + BitTest())); + mixin(Opcode([["ORA", "Read"], ["12", "ZeropageI()"]], + Read("accumulator |="))); + mixin(Opcode([["AND", "Read"], ["32", "ZeropageI()"]], + Read("accumulator &="))); + mixin(Opcode([["EOR", "Read"], ["52", "ZeropageI()"]], + Read("accumulator ^="))); + mixin(Opcode([["LDA", "Read"], ["B2", "ZeropageI()"]], + Read("accumulator ="))); + mixin(Opcode([["CMP", "Read"], ["D2", "ZeropageI()"]], + Compare("accumulator"))); + mixin(Opcode([["ADC", "Read"], ["72", "ZeropageI()"]], + Decimal("addWithCarry"))); + mixin(Opcode([["SBC", "Read"], ["F2", "ZeropageI()"]], + Decimal("subWithCarry"))); + mixin(Opcode([["STA", "Write"], ["92", "ZeropageI()"]], + Write("accumulator"))); + + mixin(RegisterOpcode("DEA", "3A", "accumulator -= 1")); + mixin(RegisterOpcode("INA", "1A", "accumulator += 1")); + mixin(SimpleOpcode("PHX", "DA", "push(xIndex)")); + mixin(SimpleOpcode("PHY", "5A", "push(yIndex)")); + mixin(RegisterOpcode("PLX", "FA", "xIndex = pull()")); + mixin(RegisterOpcode("PLY", "7A", "yIndex = pull()")); + mixin(BranchOpcode("BRA", "80", "true")); + + mixin(Opcode([["TRB", "Read"], + ["14", "Zeropage()"], ["1C", "Absolute"]], + TestModify("testReset"))); + mixin(Opcode(mixin(Type2Address( + "TSB", "Read", [0x04, 0x0C])), + TestModify("testSet"))); + mixin(Opcode([["STZ", "Write"], + ["64", "Zeropage()"], ["74", "ZeropageX()"], + ["9C", "Absolute()"], ["9E", "AbsoluteX(true)"]], + Write("0"))); + + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x02, 0x22, 0x42, 0x62, 0x82, 0xC2, 0xE2], + "Immediate")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x44], + "Zeropage()")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x54, 0xD4, 0xF4], + "ZeropageX()")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0xDC, 0xFC], + "AbsoluteX(false)")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, + 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, 0x07, 0x17, 0x27, 0x37, + 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7, + 0xE7, 0xF7, 0x0B, 0x1B, 0x2B, 0x3B, 0x4B, 0x5B, 0x6B, 0x7B, + 0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB, 0x0F, 0x1F, + 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F, 0x8F, 0x9F, 0xAF, 0xBF, + 0xCF, 0xDF, 0xEF, 0xFF], "None()")), + "")); + + /* NOP8 */ + void opcode5C() + { + readByteOperand(); + peek(programCounter); + peek(0xFF00 | operand1); + peek(0xFFFF); + peek(0xFFFF); + peek(0xFFFF); + peek(0xFFFF); + } + + /* JMP ($$$$) */ + override void opcode6C() + { + ushort vector = readWordOperand(); + peek(programCounter); + programCounter = readWord(vector, vector + 1); + version(CumulativeCycles) ticks(totalCycles); + } + + /* JMP ($$$$,X) */ + void opcode7C() + { + baseAddress = readWordOperand(); + peek(programCounter); + ushort vector = baseAddress + xIndex; + programCounter = readWord(vector, vector + 1); + version(CumulativeCycles) ticks(totalCycles); + } + + /* BIT #$$ */ + void opcode89() + { + readVal = operand1 = readFinal(programCounter++); + flag.zero_ = accumulator & readVal; + } +} + diff --git a/src/d6502/cpu.d b/src/d6502/cpu.d new file mode 100644 index 0000000..56ca39f --- /dev/null +++ b/src/d6502/cpu.d @@ -0,0 +1,758 @@ +/+ + + d6502/cpu.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import d6502.base; + +class Cpu : CpuBase +{ + static string InitOpcodes() + { + string initCalls; + for (int op = 0; op < 256; ++op) + { + initCalls ~= "opcodes[0x" ~ hexByte(op) ~ "] = &opcode" ~ + hexByte(op) ~ ";\n"; + } + return initCalls; + } + + this() + { + mixin(InitOpcodes()); + flag = new StatusRegister(); + } + + const ushort STACK_BASE = 0x0100; + const ushort NMI_VECTOR = 0xFFFA; + const ushort RESET_VECTOR = 0xFFFC; + const ushort IRQ_VECTOR = 0xFFFE; + + void delegate()[256] opcodes; + bool continueExecution; + version(CycleAccuracy) bool finalCycle; + version(CumulativeCycles) int totalCycles; + + debug(disassemble) + { + import hacking.debugger; + import std.stdio; + } + final override void run(bool continuous) + { + assert ((memoryRead !is null) && (memoryWrite !is null)); + version(CycleAccuracy) assert (tick !is null); + + continueExecution = continuous; + do + { + if (signalActive) handleSignals(); + + opcodePC = programCounter; + opcode = read(programCounter++); + version(CycleAccuracy) finalCycle = false; + version(CumulativeCycles) totalCycles = 0; + + /+ TODO: call sync delegate +/ + + opcodes[opcode](); + debug(disassemble) + { + writefln(Debugger.disassemble(this, cmosMap) + ~ Debugger.displayRegisters(this)); + } + } while (continueExecution); + } + + final override void stop() + { + continueExecution = false; + } + + version(CycleAccuracy) + { + final override bool checkFinalCycle() + { + return finalCycle; + } + } + + final override void resetLow() + { + resetActive = signalActive = true; + } + + final override void nmiLow(bool signalLow) + { + nmiActive = signalLow; + if (!signalLow) nmiArmed = true; + signalActive = testSignals(); + } + + final override void irqLow(bool signalLow) + { + irqActive = signalLow; + signalActive = testSignals(); + } + + final bool testSignals() + { + return (resetActive || nmiActive || irqActive); + } + + final void handleSignals() + { + bool checkNMI() + { + if (nmiActive && nmiArmed) + { + nmiArmed = false; + return true; + } + return false; + } + + if (resetActive) doReset(); + else if (checkNMI()) do_IRQ_or_NMI(NMI_VECTOR); + else if ((!flag.interrupt) && irqActive) do_IRQ_or_NMI(IRQ_VECTOR); + } + + void do_IRQ_or_NMI(ushort vector) + { + doInterrupt(vector, (flag.toByte() & ~0x10)); + } + + final void doInterrupt(ushort vector, ubyte statusByte) + { + pushWord(programCounter); + push(statusByte); + flag.interrupt = true; + programCounter = readWord(vector, vector + 1); + version(CumulativeCycles) ticks(totalCycles); + } + + void doReset() + { + version(CycleAccuracy) + { + tick(); tick(); + } + version(CumulativeCycles) + { + totalCycles += 2; + } + + peek(STACK_BASE + stackPointer); + --stackPointer; + peek(STACK_BASE + stackPointer); + --stackPointer; + peek(STACK_BASE + stackPointer); + --stackPointer; + + flag.interrupt = true; + resetActive = false; + signalActive = testSignals(); + + programCounter = readWord(RESET_VECTOR, RESET_VECTOR + 1); + version(CumulativeCycles) ticks(totalCycles); + } + + final ubyte read(ushort addr) + { + version(CycleAccuracy) tick(); + version(CumulativeCycles) ++totalCycles; + return memoryRead(addr); + } + + final void write(ushort addr, ubyte val) + { + version(CycleAccuracy) tick(); + version(CumulativeCycles) ++totalCycles; + memoryWrite(addr, val); + } + + final void peek(ushort addr) + { + version(CycleAccuracy) tick(); + version(CumulativeCycles) ++totalCycles; + version(StrictMemoryAccess) memoryRead(addr); + } + + final void poke(ushort addr, ubyte val) + { + version(CycleAccuracy) tick(); + version(CumulativeCycles) ++totalCycles; + version(StrictMemoryAccess) memoryWrite(addr, val); + } + + final ubyte readFinal(ushort addr) + { + version(CycleAccuracy) + { + finalCycle = true; + tick(); + } + version(CumulativeCycles) ticks(++totalCycles); + return memoryRead(addr); + } + + final void writeFinal(ushort addr, ubyte val) + { + version(CycleAccuracy) + { + finalCycle = true; + tick(); + } + version(CumulativeCycles) ticks(++totalCycles); + memoryWrite(addr, val); + } + + final ushort readWord(ushort addrLo, ushort addrHi) + { + ushort word = read(addrLo); + return word | (read(addrHi) << 8); + } + + final void push(ubyte val) + { + write((STACK_BASE + stackPointer), val); + --stackPointer; + /+ TODO: call stack overflow delegate +/ + } + + final void pushWord(ushort val) + { + push(val >> 8); + push(val & 0xFF); + } + + final ubyte readStack() + { + ++stackPointer; + /+ TODO: call stack underflow delegate +/ + return read(STACK_BASE + stackPointer); + } + + final ubyte pull() + { + peek(STACK_BASE + stackPointer); + return readStack(); + } + + final ushort pullWord() + { + ushort word = pull(); + return word | (readStack() << 8); + } + + final ubyte readByteOperand() + { + return (operand1 = read(programCounter++)); + } + + final ushort readWordOperand() + { + operand1 = read(programCounter++); + operand2 = read(programCounter++); + return (operand1 | (operand2 << 8)); + } + + ushort* spuriousAddress; + ushort badAddress, baseAddress, primaryAddress; + ubyte readVal, writeVal; + + final ushort tryShortcut(bool noShortcut, ushort goodAddress) + { + badAddress = (baseAddress & 0xFF00) | cast(ubyte)goodAddress; + if (noShortcut || (badAddress != goodAddress)) peek(*spuriousAddress); + return goodAddress; + } + + final void addrRelative(byte offset) + { + peek(programCounter); + baseAddress = programCounter; + programCounter = tryShortcut(false, programCounter + offset); + } + + final void addrZeropage() + { + primaryAddress = readByteOperand(); + } + + final void addrAbsolute() + { + primaryAddress = readWordOperand(); + } + + final void addrZeropageX() + { + baseAddress = badAddress = readByteOperand(); + peek(*spuriousAddress); + primaryAddress = cast(ubyte)(baseAddress + xIndex); + } + + final void addrZeropageY() + { + baseAddress = badAddress = readByteOperand(); + peek(*spuriousAddress); + primaryAddress = cast(ubyte)(baseAddress + yIndex); + } + + final void addrIndirectX() + { + baseAddress = badAddress = readByteOperand(); + peek(*spuriousAddress); + ushort vector = cast(ubyte)(baseAddress + xIndex); + primaryAddress = readWord(vector, cast(ubyte)(vector + 1)); + } + + final void addrAbsoluteX(bool write) + { + baseAddress = readWordOperand(); + primaryAddress = tryShortcut(write, baseAddress + xIndex); + } + + final void addrAbsoluteY(bool write) + { + baseAddress = readWordOperand(); + primaryAddress = tryShortcut(write, baseAddress + yIndex); + } + + final void addrIndirectY(bool write) + { + ubyte vector = readByteOperand(); + baseAddress = readWord(vector, cast(ubyte)(vector + 1)); + primaryAddress = tryShortcut(write, baseAddress + yIndex); + } + + void dec_addWithCarry(ubyte val) + { + uint bcdSum = (accumulator & 0x0F) + (val & 0x0F) + flag.carry; + + if (bcdSum >= 10) + bcdSum = (bcdSum - 10) | 0x10; + bcdSum += (accumulator & 0xF0) + (val & 0xF0); + + flag.negative_ = bcdSum; + flag.overflow = + (!((accumulator ^ val) & 0x80)) && ((val ^ bcdSum) & 0x80); + + if (bcdSum > 0x9f) + bcdSum += 0x60; + + flag.zero_ = accumulator + val + (flag.carry ? 1 : 0); + flag.carry = (bcdSum > 0xFF); + + accumulator = bcdSum; + } + + void dec_subWithCarry(ubyte val) + { + uint diff = accumulator - val - (flag.carry ? 0 : 1); + + flag.overflow = + ((accumulator ^ diff) & 0x80) && + ((accumulator ^ val) & 0x80); + + uint al = (accumulator & 0x0F) - (val & 0x0F) - + (flag.carry ? 0 : 1); + uint ah = (accumulator >> 4) - (val >> 4); + if (al & 0x10) + { + al -= 6; + ah--; + } + if (ah & 0x10) + ah -= 6; + + flag.carry = (diff < 0x100); + flag.zero_ = flag.negative_ = diff; + + accumulator = (ah << 4) + (al & 0x0F); + } + + final void hex_addWithCarry(ubyte val) + { + uint sum = accumulator + val + flag.carry; + + flag.overflow = + (!((accumulator ^ val) & 0x80)) && ((val ^ sum) & 0x80); + flag.carry = (sum > 0xFF); + + flag.zero_ = flag.negative_ = (accumulator = sum); + } + + final void hex_subWithCarry(ubyte val) + { + uint diff = accumulator - val - (flag.carry ? 0 : 1); + + flag.overflow = + ((accumulator ^ diff) & 0x80) && + ((accumulator ^ val) & 0x80); + flag.carry = (diff < 0x100); + + flag.zero_ = flag.negative_ = (accumulator = diff); + } + + final ubyte compare(ubyte reg, ubyte val) + { + flag.carry = (reg >= val); + return reg - val; + } + + final void bitTest(ubyte val) + { + flag.negative_ = val; + flag.zero_ = accumulator & val; + flag.overflow = ((val & 0x40) != 0); + } + + final ubyte shiftLeft(ubyte val) + { + flag.carry = (val > 0x7F); + return val << 1; + } + + final ubyte rotateLeft(ubyte val) + { + bool oldCarry = flag.carry; + flag.carry = (val > 0x7F); + val = (val << 1 | (oldCarry ? 1 : 0)); + return val; + } + + final ubyte shiftRight(ubyte val) + { + flag.carry = ((val & 0x01) != 0); + return val >> 1; + } + + final ubyte rotateRight(ubyte val) + { + bool oldCarry = flag.carry; + flag.carry = ((val & 0x01) != 0); + val = (val >> 1 | (oldCarry ? 0x80 : 0)); + return val; + } + + final ubyte increment(ubyte val) + { + return val + 1; + } + + final ubyte decrement(ubyte val) + { + return val - 1; + } + + static string SimpleOpcode(string name, string opcode, string action) + { + string code = "peek(programCounter);\n"; + version(CumulativeCycles) code ~= "ticks(totalCycles);\n"; + code ~= (action == "") ? "" : (action ~ ";"); + return "override void opcode" ~ opcode ~ "()\n{\n" ~ code ~ "\n}\n"; + } + + static string UpdateNZ(string action) + { + return "flag.zero_ = flag.negative_ = (" ~ action ~ ");" ~ "\n"; + } + + static string RegisterOpcode(string name, string opcode, string action) + { + string code = "peek(programCounter);\n"; + version(CumulativeCycles) code ~= "ticks(totalCycles);\n"; + return "override void opcode" ~ opcode ~ "()\n{\n" ~ + code ~ UpdateNZ(action) ~ "}\n"; + } + + static string BranchOpcode(string name, string opcode, string action) + { + string code = "readByteOperand();\n" ~ + "if (" ~ action ~ ") addrRelative(cast(byte)operand1);\n"; + version(CumulativeCycles) code ~= "ticks(totalCycles);\n"; + return "override void opcode" ~ opcode ~ "()\n{\n" ~ code ~ "}\n"; + } + + static string Type1Address(string name, string rw, int[] opcodes) + { + string type = (rw == "Write") ? "true" : "false"; + string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n"; + for (int op = 0; op < opcodes.length; ++op) + { + int opcode = opcodes[op]; + modes ~= "[\"" ~ hexByte(opcode) ~ "\", \""; + switch ((opcode & 0b00011100) >> 2) + { + case 0: + modes ~= "IndirectX()"; + break; + case 1: + modes ~= "Zeropage()"; + break; + case 2: + modes ~= "Immediate"; + break; + case 3: + modes ~= "Absolute()"; + break; + case 4: + modes ~= "IndirectY("~ type ~ ")"; + break; + case 5: + modes ~= "ZeropageX()"; + break; + case 6: + modes ~= "AbsoluteY(" ~ type ~ ")"; + break; + case 7: + modes ~= "AbsoluteX(" ~ type ~ ")"; + break; + } + modes ~= "\"]"; + if (op != (opcodes.length - 1)) modes ~= ", "; + modes ~= "\n"; + } + return modes ~ "]\n"; + } + + static string Type2Address(string name, string rw, int[] opcodes) + { + string type = (rw == "Write") ? "true" : "false"; + string index = (name[2] == 'X') ? "Y" : "X"; + string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n"; + for (int op = 0; op < opcodes.length; ++op) + { + int opcode = opcodes[op]; + modes ~= "[\"" ~ hexByte(opcode) ~ "\", \""; + switch ((opcode & 0b00011100) >> 2) + { + case 0: + modes ~= "Immediate"; + break; + case 1: + modes ~= "Zeropage()"; + break; + case 3: + modes ~= "Absolute()"; + break; + case 5: + modes ~= "Zeropage" ~ index ~ "()"; + break; + case 7: + modes ~= "Absolute" ~ index ~ "(" ~ type ~ ")"; + break; + } + modes ~= "\"]"; + if (op != (opcodes.length - 1)) modes ~= ", "; + modes ~= "\n"; + } + return modes ~ "]\n"; + } + + static string Opcode(string[][] details, string action) + { + string methods; + for (int op = 1; op < details.length; ++op) + { + methods ~= "override void opcode" ~ details[op][0] ~ "()\n{\n"; + if (details[op][1] == "Immediate") + { + methods ~= "primaryAddress = programCounter++;\n" ~ + action ~ "operand1 = readVal;\n"; + } + else + { + methods ~= "addr" ~ details[op][1] ~ ";\n" ~ action; + } + methods ~= "}\n"; + } + return methods; + } + + static string Read(string action) + { + return UpdateNZ(action ~ " (readVal = readFinal(primaryAddress))"); + } + + static string Decimal(string action) + { + string code = action ~ "(readVal = readFinal(primaryAddress));\n"; + return "if (flag.decimal) dec_" ~ code ~ + "else hex_" ~ code; + } + + static string Compare(string action) + { + return UpdateNZ("compare(" ~ action ~ + ", (readVal = readFinal(primaryAddress)))"); + } + + static string Write(string action) + { + return "writeFinal(primaryAddress, " ~ action ~ ");\n"; + } + + static string BitTest() + { + return "bitTest(readVal = readFinal(primaryAddress));\n"; + } + + mixin(SimpleOpcode("CLC", "18", "flag.carry = false")); + mixin(SimpleOpcode("SEC", "38", "flag.carry = true")); + mixin(SimpleOpcode("CLI", "58", "flag.interrupt = false")); + mixin(SimpleOpcode("SEI", "78", "flag.interrupt = true")); + mixin(SimpleOpcode("CLV", "B8", "flag.overflow = false")); + mixin(SimpleOpcode("CLD", "D8", "flag.decimal = false")); + mixin(SimpleOpcode("SED", "F8", "flag.decimal = true")); + + mixin(SimpleOpcode("NOP", "EA", "")); + + mixin(SimpleOpcode("PHP", "08", "push(flag.toByte())")); + mixin(SimpleOpcode("PLP", "28", "flag.fromByte(pull())")); + mixin(SimpleOpcode("PHA", "48", "push(accumulator)")); + mixin(SimpleOpcode("TXS", "9A", "stackPointer = xIndex")); + + mixin(RegisterOpcode("PLA", "68", "accumulator = pull()")); + mixin(RegisterOpcode("TSX", "BA", "xIndex = stackPointer")); + + mixin(RegisterOpcode("TAX", "AA", "xIndex = accumulator")); + mixin(RegisterOpcode("TXA", "8A", "accumulator = xIndex")); + mixin(RegisterOpcode("DEX", "CA", "xIndex -= 1")); + mixin(RegisterOpcode("INX", "E8", "xIndex += 1")); + mixin(RegisterOpcode("TAY", "A8", "yIndex = accumulator")); + mixin(RegisterOpcode("TYA", "98", "accumulator = yIndex")); + mixin(RegisterOpcode("DEY", "88", "yIndex -= 1")); + mixin(RegisterOpcode("INY", "C8", "yIndex += 1")); + + mixin(BranchOpcode("BPL", "10", "flag.negative_ < 0x80")); + mixin(BranchOpcode("BMI", "30", "flag.negative_ > 0x7F")); + mixin(BranchOpcode("BVC", "50", "!flag.overflow")); + mixin(BranchOpcode("BVS", "70", "flag.overflow")); + mixin(BranchOpcode("BCC", "90", "!flag.carry")); + mixin(BranchOpcode("BCS", "B0", "flag.carry")); + mixin(BranchOpcode("BNE", "D0", "flag.zero_ != 0")); + mixin(BranchOpcode("BEQ", "F0", "flag.zero_ == 0")); + + mixin(RegisterOpcode("ASL A", "0A", + "accumulator = shiftLeft(accumulator)")); + mixin(RegisterOpcode("ROL A", "2A", + "accumulator = rotateLeft(accumulator)")); + mixin(RegisterOpcode("LSR A", "4A", + "accumulator = shiftRight(accumulator)")); + mixin(RegisterOpcode("ROR A", "6A", + "accumulator = rotateRight(accumulator)")); + + mixin(Opcode(mixin(Type1Address( + "LDA", "Read", [0xA1, 0xA5, 0xA9, 0xAD, 0xB1, 0xB5, 0xB9, 0xBD])), + Read("accumulator ="))); + mixin(Opcode(mixin(Type1Address( + "ORA", "Read", [0x01, 0x05, 0x09, 0x0D, 0x11, 0x15, 0x19, 0x1D])), + Read("accumulator |="))); + mixin(Opcode(mixin(Type1Address( + "AND", "Read", [0x21, 0x25, 0x29, 0x2D, 0x31, 0x35, 0x39, 0x3D])), + Read("accumulator &="))); + mixin(Opcode(mixin(Type1Address( + "EOR", "Read", [0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D])), + Read("accumulator ^="))); + mixin(Opcode(mixin(Type1Address( + "ADC", "Read", [0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D])), + Decimal("addWithCarry"))); + mixin(Opcode(mixin(Type1Address( + "SBC", "Read", [0xE1, 0xE5, 0xE9, 0xED, 0xF1, 0xF5, 0xF9, 0xFD])), + Decimal("subWithCarry"))); + mixin(Opcode(mixin(Type1Address( + "CMP", "Read", [0xC1, 0xC5, 0xC9, 0xCD, 0xD1, 0xD5, 0xD9, 0xDD])), + Compare("accumulator"))); + mixin(Opcode(mixin(Type1Address( + "STA", "Write", [0x81, 0x85, 0x8D, 0x91, 0x95, 0x99, 0x9D])), + Write("accumulator"))); + + mixin(Opcode(mixin(Type2Address( + "LDX", "Read", [0xA2, 0xA6, 0xAE, 0xB6, 0xBE])), + Read("xIndex ="))); + mixin(Opcode(mixin(Type2Address( + "LDY", "Read", [0xA0, 0xA4, 0xAC, 0xB4, 0xBC])), + Read("yIndex ="))); + mixin(Opcode(mixin(Type2Address( + "CPX", "Read", [0xE0, 0xE4, 0xEC])), + Compare("xIndex"))); + mixin(Opcode(mixin(Type2Address( + "CPY", "Read", [0xC0, 0xC4, 0xCC])), + Compare("yIndex"))); + mixin(Opcode(mixin(Type2Address( + "STX", "Write", [0x86, 0x8E, 0x96])), + Write("xIndex"))); + mixin(Opcode(mixin(Type2Address( + "STY", "Write", [0x84, 0x8C, 0x94])), + Write("yIndex"))); + mixin(Opcode(mixin(Type2Address( + "BIT", "Read", [0x24, 0x2C])), + BitTest())); + + /* BRK */ + final override void opcode00() + { + peek(programCounter); + ++programCounter; + doInterrupt(IRQ_VECTOR, flag.toByte()); + } + + /* JSR */ + final override void opcode20() + { + ushort finalAddress = (operand1 = read(programCounter++)); + + peek(STACK_BASE + stackPointer); + pushWord(programCounter); + + finalAddress |= ((operand2 = read(programCounter)) << 8); + version(CumulativeCycles) ticks(totalCycles); + programCounter = finalAddress; + } + + /* RTI */ + final override void opcode40() + { + peek(programCounter); + flag.fromByte(pull()); + programCounter = pullWord(); + version(CumulativeCycles) ticks(totalCycles); + } + + /* JMP $$$$ */ + final override void opcode4C() + { + programCounter = readWordOperand(); + version(CumulativeCycles) ticks(totalCycles); + } + + /* RTS */ + final override void opcode60() + { + peek(programCounter); + programCounter = pullWord(); + peek(programCounter); + version(CumulativeCycles) ticks(totalCycles); + ++programCounter; + } +} diff --git a/src/d6502/nmosbase.d b/src/d6502/nmosbase.d new file mode 100644 index 0000000..df1749f --- /dev/null +++ b/src/d6502/nmosbase.d @@ -0,0 +1,67 @@ +/+ + + d6502/nmosbase.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import d6502.cpu; + +class NmosBase : Cpu +{ + this() + { + super(); + spuriousAddress = &badAddress; + } + + static string RMW(string action) + { + return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~ + "writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~ + action ~ "(readVal));\n"; + } + + mixin(Opcode(mixin(Type2Address( + "ASL", "Write", [0x06, 0x0E, 0x16, 0x1E])), + RMW("shiftLeft"))); + mixin(Opcode(mixin(Type2Address( + "LSR", "Write", [0x46, 0x4E, 0x56, 0x5E])), + RMW("shiftRight"))); + mixin(Opcode(mixin(Type2Address( + "ROL", "Write", [0x26, 0x2E, 0x36, 0x3E])), + RMW("rotateLeft"))); + mixin(Opcode(mixin(Type2Address( + "ROR", "Write", [0x66, 0x6E, 0x76, 0x7E])), + RMW("rotateRight"))); + mixin(Opcode(mixin(Type2Address( + "INC", "Write", [0xE6, 0xEE, 0xF6, 0xFE])), + RMW("increment"))); + mixin(Opcode(mixin(Type2Address( + "DEC", "Write", [0xC6, 0xCE, 0xD6, 0xDE])), + RMW("decrement"))); + + /* JMP ($$$$) */ + override void opcode6C() + { + ushort vector = readWordOperand(); + programCounter = readWord(vector, + (vector & 0xFF00) | cast(ubyte)(vector + 1)); + version(CumulativeCycles) ticks(totalCycles); + } +} diff --git a/src/d6502/nmosundoc.d b/src/d6502/nmosundoc.d new file mode 100644 index 0000000..631e4d8 --- /dev/null +++ b/src/d6502/nmosundoc.d @@ -0,0 +1,351 @@ +/+ + + d6502/nmosundoc.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import d6502.base; +import d6502.nmosbase; + +class NmosUndoc : NmosBase +{ + this() + { + super(); + } + + final void addrHalt() {} + final void addrImplied() + { + peek(programCounter); + version(CumulativeCycles) ticks(totalCycles); + } + + final void strange(ubyte val) + { + version(Commodore64) + { + ubyte hiAddr = ((primaryAddress >> 8) + 1); + val = val & hiAddr; + ushort addr = (badAddress == primaryAddress) ? primaryAddress : + ((val << 8) | (primaryAddress & 0xFF)); + writeFinal(addr, val); + } + else + { + ubyte hiAddr = ((baseAddress >> 8) + 1); + writeFinal(primaryAddress, val & hiAddr); + } + } + + static string UndocAddress(string name, string rw, int[] opcodes) + { + string type = (rw == "Write") ? "true" : "false"; + string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n"; + for (int op = 0; op < opcodes.length; ++op) + { + int opcode = opcodes[op]; + modes ~= "[\"" ~ hexByte(opcode) ~ "\", \""; + switch ((opcode & 0b00011100) >> 2) + { + case 0: + modes ~= "IndirectX()"; + break; + case 1: + modes ~= "Zeropage()"; + break; + case 3: + modes ~= "Absolute()"; + break; + case 4: + modes ~= "IndirectY("~ type ~ ")"; + break; + case 5: + modes ~= "ZeropageX()"; + break; + case 7: + modes ~= "AbsoluteY(" ~ type ~ ")"; + break; + } + modes ~= "\"]"; + if (op != (opcodes.length - 1)) modes ~= ", "; + modes ~= "\n"; + } + return modes ~ "]\n"; + } + + static string ManualAddress(string name, int[] opcodes, + string mode) + { + string modes = "[[\"" ~ name ~ "\", \"NA\"], \n"; + for (int op = 0; op < opcodes.length; ++op) + { + int opcode = opcodes[op]; + modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"" ~ mode ~ "\"]"; + if (op != (opcodes.length - 1)) modes ~= ", "; + modes ~= "\n"; + } + return modes ~ "]\n"; + } + + static string Halt() + { + /+ TODO: have this do something useful +/ + return "\n"; + } + + static string ReadNOP() + { + return "readVal = readFinal(primaryAddress);\n"; + } + + static string RMW_Read(string action1, string action2) + { + return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~ + "writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~ + "(writeVal = " ~ action1 ~ "(readVal)));\n" ~ + action2 ~ " writeVal;\n"; + } + + static string RMW_Compare(string action1, string action2) + { + return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~ + "writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~ + "(writeVal = " ~ action1 ~ "(readVal)));\n" ~ + "compare(" ~ action2 ~ ", writeVal);\n"; + } + + static string RMW_Decimal(string action1, string action2) + { + return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~ + "writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~ + "(writeVal = " ~ action1 ~ "(readVal)));\n" ~ + "if (flag.decimal) dec_" ~ action2 ~ "(writeVal);\n" ~ + "else hex_" ~ action2 ~ "(writeVal);\n"; + } + + mixin(Opcode(mixin(ManualAddress( + "HLT", [0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, 0x92, 0xB2, + 0xD2, 0xF2], "Halt()")), + Halt())); + + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x1A, 0x3A, 0x5A, 0x7A, 0xDA, 0xFA], "Implied()")), + "")); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x0C, 0x1C, 0x3C, 0x5C, 0x7C, 0xDC, 0xFC], + "AbsoluteX(false)")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x80, 0x82, 0x89, 0xC2, 0xE2], + "Immediate")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x04, 0x44, 0x64], + "Zeropage()")), + ReadNOP())); + mixin(Opcode(mixin(ManualAddress( + "NOP", [0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4], + "ZeropageX()")), + ReadNOP())); + + mixin(Opcode(mixin(UndocAddress( + "LAX", "Read", [0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF])), + Read("accumulator = xIndex ="))); + mixin(Opcode(mixin(UndocAddress( + "SAX", "Write", [0x83, 0x87, 0x8F, 0x97])), + Write("accumulator & xIndex"))); + + mixin(Opcode(mixin(Type1Address( + "ASO", "Write", [0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F])), + RMW_Read("shiftLeft", "accumulator |="))); + mixin(Opcode(mixin(Type1Address( + "RLA", "Write", [0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F])), + RMW_Read("rotateLeft", "accumulator &="))); + mixin(Opcode(mixin(Type1Address( + "LSE", "Write", [0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F])), + RMW_Read("shiftRight", "accumulator ^="))); + mixin(Opcode(mixin(Type1Address( + "DCM", "Write", [0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF])), + RMW_Compare("decrement", "accumulator"))); + mixin(Opcode(mixin(Type1Address( + "RRA", "Write", [0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F])), + RMW_Decimal("rotateRight", "addWithCarry"))); + mixin(Opcode(mixin(Type1Address( + "INS", "Write", [0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF])), + RMW_Decimal("increment", "subWithCarry"))); + + /* ANC #$$ */ + override void opcode0B() + { + readVal = operand1 = readFinal(programCounter); + flag.zero_ = flag.negative_ = (accumulator = readVal); + flag.carry = (flag.negative_ > 0x7F); + } + + /* ANC #$$ */ + override void opcode2B() + { + readVal = operand1 = readFinal(programCounter); + flag.zero_ = flag.negative_ = (accumulator = readVal); + flag.carry = (flag.negative_ > 0x7F); + } + + /* ALR #$$ */ + override void opcode4B() + { + readVal = operand1 = readFinal(programCounter); + flag.zero_ = flag.negative_ = + (accumulator = shiftRight(accumulator & readVal)); + } + + /* ARR #$$ */ + override void opcode6B() + { + readVal = operand1 = readFinal(programCounter); + ubyte val = readVal & accumulator; + if (flag.decimal) { + ubyte temp = (val >> 1) + (flag.carry ? 0x80 : 0); + flag.zero_ = flag.negative_ = temp; + flag.overflow = (((temp ^ val) & 0x40) != 0); + if ((readVal & 0x0F) + (val & 0x01) > 5) + temp = (temp & 0xF0) + ((temp + 0x6) & 0x0F); + if (val + (val & 0x10) >= 0x60) + { + temp += 0x60; + flag.carry = true; + } + else + flag.carry = false; + accumulator = temp; + } + else { + accumulator = (val >> 1) + (flag.carry ? 0x80 : 0); + flag.zero_ = flag.negative_ = accumulator; + val >>= 7; + flag.carry = (val != 0); + flag.overflow = ((val ^ ((accumulator >> 5) & 1)) != 0); + } + } + + /* ANE #$$ */ + override void opcode8B() + { + // unstable + readVal = operand1 = readFinal(programCounter); + + version(Atari8Bit) + { + flag.zero_ = flag.negative_ = + (accumulator & xIndex & readVal); + accumulator &= xIndex & (operand1 | 0xEF); + } + else + { + flag.zero_ = flag.negative_ = + (accumulator &= (xIndex & readVal)); + } + } + + /* SHA ($$),Y */ + void opcode93() + { + addrIndirectY(true); + strange(accumulator & xIndex); + } + + /* SHA $$$$,Y */ + void opcode9F() + { + addrAbsoluteY(true); + strange(accumulator & xIndex); + } + + /* SHS $$$$,Y */ + void opcode9B() + { + addrAbsoluteY(true); + strange(stackPointer = (accumulator & xIndex)); + } + + /* SHY $$$$,X */ + void opcode9C() + { + addrAbsoluteX(true); + strange(yIndex); + } + + /* SHX $$$$,Y */ + void opcode9E() + { + addrAbsoluteY(true); + strange(xIndex); + } + + /* LAX #$$ */ + override void opcodeAB() + { + readVal = operand1 = readFinal(programCounter); + + version(Commodore128) + { + // not unstable? + flag.zero_ = flag.negative_ = + (accumulator = readVal); + } + else + { + // unstable + version(Commodore64) + { + accumulator |= 0xEE; + } + flag.zero_ = flag.negative_ = + (accumulator &= readVal); + } + xIndex = accumulator; + } + + /* LAS $$$$,Y */ + override void opcodeBB() + { + addrAbsoluteY(false); + readVal = readFinal(primaryAddress); + + flag.zero_ = flag.negative_ = + (xIndex = accumulator = (stackPointer & readVal)); + } + + /* SBX #$$ */ + override void opcodeCB() + { + readVal = operand1 = readFinal(programCounter); + xIndex &= accumulator; + flag.zero_ = flag.negative_ = compare(xIndex, readVal); + } + + /* SBC #$$ */ + override void opcodeEB() + { + readVal = operand1 = readFinal(programCounter); + if (flag.decimal) dec_subWithCarry(readVal); + else hex_subWithCarry(readVal); + } +} + diff --git a/src/data/charset_lower b/src/data/charset_lower new file mode 100644 index 0000000..7fde291 --- /dev/null +++ b/src/data/charset_lower @@ -0,0 +1,89 @@ +[ +// ` a b c + + 0b0010000, 0b0000000, 0b0100000, 0b0000000, + 0b0001000, 0b0000000, 0b0100000, 0b0000000, + 0b0000100, 0b0011100, 0b0111100, 0b0011110, + 0b0000000, 0b0000010, 0b0100010, 0b0100000, + 0b0000000, 0b0011110, 0b0100010, 0b0100000, + 0b0000000, 0b0100010, 0b0100010, 0b0100000, + 0b0000000, 0b0011110, 0b0111100, 0b0011110, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// d e f g + + 0b0000010, 0b0000000, 0b0001100, 0b0000000, + 0b0000010, 0b0000000, 0b0010010, 0b0000000, + 0b0011110, 0b0011100, 0b0010000, 0b0011100, + 0b0100010, 0b0100010, 0b0111100, 0b0100010, + 0b0100010, 0b0111110, 0b0010000, 0b0100010, + 0b0100010, 0b0100000, 0b0010000, 0b0011110, + 0b0011110, 0b0011110, 0b0010000, 0b0000010, + 0b0000000, 0b0000000, 0b0000000, 0b0011100, + +// h i j k + + 0b0100000, 0b0001000, 0b0000100, 0b0100000, + 0b0100000, 0b0000000, 0b0000000, 0b0100000, + 0b0111100, 0b0011000, 0b0001100, 0b0100010, + 0b0100010, 0b0001000, 0b0000100, 0b0100100, + 0b0100010, 0b0001000, 0b0000100, 0b0111000, + 0b0100010, 0b0001000, 0b0000100, 0b0100100, + 0b0100010, 0b0011100, 0b0100100, 0b0100010, + 0b0000000, 0b0000000, 0b0011000, 0b0000000, + +// l m n o + + 0b0011000, 0b0000000, 0b0000000, 0b0000000, + 0b0001000, 0b0000000, 0b0000000, 0b0000000, + 0b0001000, 0b0110110, 0b0111100, 0b0011100, + 0b0001000, 0b0101010, 0b0100010, 0b0100010, + 0b0001000, 0b0101010, 0b0100010, 0b0100010, + 0b0001000, 0b0101010, 0b0100010, 0b0100010, + 0b0011100, 0b0100010, 0b0100010, 0b0011100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// p q r s + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0111100, 0b0011110, 0b0101110, 0b0011110, + 0b0100010, 0b0100010, 0b0110000, 0b0100000, + 0b0100010, 0b0100010, 0b0100000, 0b0011100, + 0b0111100, 0b0011110, 0b0100000, 0b0000010, + 0b0100000, 0b0000010, 0b0100000, 0b0111100, + 0b0100000, 0b0000010, 0b0000000, 0b0000000, + +// t u v w + + 0b0010000, 0b0000000, 0b0000000, 0b0000000, + 0b0010000, 0b0000000, 0b0000000, 0b0000000, + 0b0111100, 0b0100010, 0b0100010, 0b0100010, + 0b0010000, 0b0100010, 0b0100010, 0b0100010, + 0b0010000, 0b0100010, 0b0100010, 0b0101010, + 0b0010010, 0b0100110, 0b0010100, 0b0101010, + 0b0001100, 0b0011010, 0b0001000, 0b0110110, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// x y z { + + 0b0000000, 0b0000000, 0b0000000, 0b0001110, + 0b0000000, 0b0000000, 0b0000000, 0b0011000, + 0b0100010, 0b0100010, 0b0111110, 0b0011000, + 0b0010100, 0b0100010, 0b0000100, 0b0110000, + 0b0001000, 0b0100010, 0b0001000, 0b0011000, + 0b0010100, 0b0011110, 0b0010000, 0b0011000, + 0b0100010, 0b0000010, 0b0111110, 0b0001110, + 0b0000000, 0b0011100, 0b0000000, 0b0000000, + +// | } ~ (checker) + + 0b0001000, 0b0111000, 0b0011010, 0b0000000, + 0b0001000, 0b0001100, 0b0101100, 0b0101010, + 0b0001000, 0b0001100, 0b0000000, 0b0010100, + 0b0001000, 0b0000110, 0b0000000, 0b0101010, + 0b0001000, 0b0001100, 0b0000000, 0b0010100, + 0b0001000, 0b0001100, 0b0000000, 0b0101010, + 0b0001000, 0b0111000, 0b0000000, 0b0000000, + 0b0001000, 0b0000000, 0b0000000, 0b0000000 +]; diff --git a/src/data/charset_mouse b/src/data/charset_mouse new file mode 100644 index 0000000..c95e53b --- /dev/null +++ b/src/data/charset_mouse @@ -0,0 +1,91 @@ +[ +// derived from http://en.wikipedia.org/wiki/Image:Mousetext-original.png + +// closed apple open apple pointer hourglass + + 0b0000100, 0b0000100, 0b0000000, 0b1111111, + 0b0001000, 0b0001000, 0b0000000, 0b0100010, + 0b0110110, 0b0110110, 0b0100000, 0b0010100, + 0b1111111, 0b1000001, 0b0110000, 0b0001000, + 0b1111110, 0b1000010, 0b0111000, 0b0001000, + 0b1111110, 0b1000010, 0b0111100, 0b0010100, + 0b0111111, 0b0101001, 0b0110110, 0b0101010, + 0b0110110, 0b0110110, 0b0100001, 0b1111111, + +// check inv. check left man right man + + 0b0000000, 0b1111111, 0b0000111, 0b0000000, + 0b0000001, 0b1111110, 0b0000011, 0b0001100, + 0b0000010, 0b1111101, 0b0111111, 0b1110000, + 0b1000100, 0b0011011, 0b1000110, 0b0000000, + 0b0101000, 0b1010111, 0b1001111, 0b1110000, + 0b0010000, 0b1101111, 0b0000110, 0b0011000, + 0b0010000, 0b1101111, 0b1111110, 0b0001000, + 0b0000000, 0b1111111, 0b0100000, 0b0000111, + +// left arrow ellipsis down arrow up arrow + + 0b0001000, 0b0000000, 0b0001000, 0b0001000, + 0b0010000, 0b0000000, 0b0001000, 0b0011100, + 0b0100000, 0b0000000, 0b0001000, 0b0101010, + 0b1111111, 0b0000000, 0b0001000, 0b1001001, + 0b0100000, 0b0000000, 0b1001001, 0b0001000, + 0b0010000, 0b0000000, 0b0101010, 0b0001000, + 0b0001000, 0b0000000, 0b0011100, 0b0001000, + 0b0000000, 0b0101010, 0b0001000, 0b0001000, + +// top line enter block left arr. 2 + + 0b1111111, 0b0000001, 0b1111110, 0b1100100, + 0b0000000, 0b0000001, 0b1111110, 0b0001100, + 0b0000000, 0b0000001, 0b1111110, 0b0011100, + 0b0000000, 0b0010001, 0b1111110, 0b0111111, + 0b0000000, 0b0110001, 0b1111110, 0b0011100, + 0b0000000, 0b1111111, 0b1111110, 0b0001100, + 0b0000000, 0b0110000, 0b1111110, 0b0000100, + 0b0000000, 0b0010000, 0b1111110, 0b1111011, + +// right arr. 2 down arr. 2 up arr. 2 mid line + + 0b0010011, 0b0000001, 0b0000001, 0b0000000, + 0b0011000, 0b0001001, 0b0001001, 0b0000000, + 0b0011100, 0b0001000, 0b0011100, 0b0000000, + 0b1111110, 0b1111111, 0b0111110, 0b1111111, + 0b0011100, 0b0111110, 0b1111111, 0b0000000, + 0b0011000, 0b0011100, 0b0001000, 0b0000000, + 0b0010000, 0b0001001, 0b0001001, 0b0000000, + 0b1101111, 0b0000001, 0b0000001, 0b0000000, + +// left/bot line right arrow checker 1 checker 2 + + 0b1000000, 0b0001000, 0b0101010, 0b1010101, + 0b1000000, 0b0000100, 0b1010101, 0b0101010, + 0b1000000, 0b0000010, 0b0101010, 0b1010101, + 0b1000000, 0b1111111, 0b1010101, 0b0101010, + 0b1000000, 0b0000010, 0b0101010, 0b1010101, + 0b1000000, 0b0000100, 0b1010101, 0b0101010, + 0b1000000, 0b0001000, 0b0101010, 0b1010101, + 0b1111111, 0b0000000, 0b1010101, 0b0101010, + +// left folder right folder right line diamond + + 0b0000000, 0b0000000, 0b0000001, 0b0001000, + 0b0111110, 0b0000000, 0b0000001, 0b0011100, + 0b1000001, 0b1111110, 0b0000001, 0b0111110, + 0b1000000, 0b0000001, 0b0000001, 0b1111111, + 0b1000000, 0b0000001, 0b0000001, 0b0111110, + 0b1000000, 0b0000001, 0b0000001, 0b0011100, + 0b1111111, 0b1111111, 0b0000001, 0b0001000, + 0b0000000, 0b0000000, 0b0000001, 0b0000000, + +// top/bot line four corners close left line + + 0b1111111, 0b0010100, 0b1111111, 0b1000000, + 0b0000000, 0b0010100, 0b0000001, 0b1000000, + 0b0000000, 0b1110111, 0b0000001, 0b1000000, + 0b0000000, 0b0000000, 0b0011001, 0b1000000, + 0b0000000, 0b1110111, 0b0011001, 0b1000000, + 0b0000000, 0b0010100, 0b0000001, 0b1000000, + 0b0000000, 0b0010100, 0b0000001, 0b1000000, + 0b1111111, 0b0000000, 0b1111111, 0b1000000 +]; diff --git a/src/data/charset_symbol_ii b/src/data/charset_symbol_ii new file mode 100644 index 0000000..5f67005 --- /dev/null +++ b/src/data/charset_symbol_ii @@ -0,0 +1,89 @@ +[ +// space ! " # + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0001000, 0b0010100, 0b0010100, + 0b0000000, 0b0001000, 0b0010100, 0b0010100, + 0b0000000, 0b0001000, 0b0010100, 0b0111110, + 0b0000000, 0b0001000, 0b0000000, 0b0010100, + 0b0000000, 0b0001000, 0b0000000, 0b0111110, + 0b0000000, 0b0000000, 0b0000000, 0b0010100, + 0b0000000, 0b0001000, 0b0000000, 0b0010100, + +// $ % & ' + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0001000, 0b0110000, 0b0010000, 0b0001000, + 0b0011110, 0b0110010, 0b0101000, 0b0001000, + 0b0101000, 0b0000100, 0b0101000, 0b0001000, + 0b0011100, 0b0001000, 0b0010000, 0b0000000, + 0b0001010, 0b0010000, 0b0101010, 0b0000000, + 0b0111100, 0b0100110, 0b0100100, 0b0000000, + 0b0001000, 0b0000110, 0b0011010, 0b0000000, + +// ( ) * + + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0001000, 0b0001000, 0b0001000, 0b0000000, + 0b0010000, 0b0000100, 0b0101010, 0b0001000, + 0b0100000, 0b0000010, 0b0011100, 0b0001000, + 0b0100000, 0b0000010, 0b0001000, 0b0111110, + 0b0100000, 0b0000010, 0b0011100, 0b0001000, + 0b0010000, 0b0000100, 0b0101010, 0b0001000, + 0b0001000, 0b0001000, 0b0001000, 0b0000000, + +// 0, - . / + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000010, + 0b0000000, 0b0000000, 0b0000000, 0b0000100, + 0b0000000, 0b0111110, 0b0000000, 0b0001000, + 0b0001000, 0b0000000, 0b0000000, 0b0010000, + 0b0001000, 0b0000000, 0b0000000, 0b0100000, + 0b0010000, 0b0000000, 0b0001000, 0b0000000, + +// 0 1 2 3 + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0011100, 0b0001000, 0b0011100, 0b0111110, + 0b0100010, 0b0011000, 0b0100010, 0b0000010, + 0b0100110, 0b0001000, 0b0000010, 0b0000100, + 0b0101010, 0b0001000, 0b0001100, 0b0001100, + 0b0110010, 0b0001000, 0b0010000, 0b0000010, + 0b0100010, 0b0001000, 0b0100000, 0b0100010, + 0b0011100, 0b0011100, 0b0111110, 0b0011100, + +// 4 5 6 7 + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000100, 0b0111110, 0b0001110, 0b0111110, + 0b0001100, 0b0100000, 0b0010000, 0b0000010, + 0b0010100, 0b0111100, 0b0100000, 0b0000100, + 0b0100100, 0b0000010, 0b0111100, 0b0001000, + 0b0111110, 0b0000010, 0b0100010, 0b0010000, + 0b0000100, 0b0100010, 0b0100010, 0b0010000, + 0b0000100, 0b0011100, 0b0011100, 0b0010000, + +// 8 9 : ; + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0011100, 0b0011100, 0b0000000, 0b0000000, + 0b0100010, 0b0100010, 0b0000000, 0b0000000, + 0b0100010, 0b0100010, 0b0001000, 0b0001000, + 0b0011100, 0b0011110, 0b0000000, 0b0000000, + 0b0100010, 0b0000010, 0b0001000, 0b0001000, + 0b0100010, 0b0000100, 0b0000000, 0b0001000, + 0b0011100, 0b0111000, 0b0000000, 0b0010000, + +// < = > ? + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000100, 0b0000000, 0b0010000, 0b0011100, + 0b0001000, 0b0000000, 0b0001000, 0b0100010, + 0b0010000, 0b0111110, 0b0000100, 0b0000100, + 0b0100000, 0b0000000, 0b0000010, 0b0001000, + 0b0010000, 0b0111110, 0b0000100, 0b0001000, + 0b0001000, 0b0000000, 0b0001000, 0b0000000, + 0b0000100, 0b0000000, 0b0010000, 0b0001000 +]; diff --git a/src/data/charset_symbol_iie b/src/data/charset_symbol_iie new file mode 100644 index 0000000..e1a39b7 --- /dev/null +++ b/src/data/charset_symbol_iie @@ -0,0 +1,89 @@ +[ +// space ! " # + + 0b0000000, 0b0001000, 0b0010100, 0b0010100, + 0b0000000, 0b0001000, 0b0010100, 0b0010100, + 0b0000000, 0b0001000, 0b0010100, 0b0111110, + 0b0000000, 0b0001000, 0b0000000, 0b0010100, + 0b0000000, 0b0001000, 0b0000000, 0b0111110, + 0b0000000, 0b0000000, 0b0000000, 0b0010100, + 0b0000000, 0b0001000, 0b0000000, 0b0010100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// $ % & ' + + 0b0001000, 0b0110000, 0b0010000, 0b0001000, + 0b0011110, 0b0110010, 0b0101000, 0b0001000, + 0b0101000, 0b0000100, 0b0101000, 0b0001000, + 0b0011100, 0b0001000, 0b0010000, 0b0000000, + 0b0001010, 0b0010000, 0b0101010, 0b0000000, + 0b0111100, 0b0100110, 0b0100100, 0b0000000, + 0b0001000, 0b0000110, 0b0011010, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// ( ) * + + + 0b0001000, 0b0001000, 0b0001000, 0b0000000, + 0b0010000, 0b0000100, 0b0101010, 0b0001000, + 0b0100000, 0b0000010, 0b0011100, 0b0001000, + 0b0100000, 0b0000010, 0b0001000, 0b0111110, + 0b0100000, 0b0000010, 0b0011100, 0b0001000, + 0b0010000, 0b0000100, 0b0101010, 0b0001000, + 0b0001000, 0b0001000, 0b0001000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// 0, - . / + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000010, + 0b0000000, 0b0000000, 0b0000000, 0b0000100, + 0b0000000, 0b0111110, 0b0000000, 0b0001000, + 0b0001000, 0b0000000, 0b0000000, 0b0010000, + 0b0001000, 0b0000000, 0b0000000, 0b0100000, + 0b0010000, 0b0000000, 0b0001000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// 0 1 2 3 + + 0b0011100, 0b0001000, 0b0011100, 0b0111110, + 0b0100010, 0b0011000, 0b0100010, 0b0000010, + 0b0100110, 0b0001000, 0b0000010, 0b0000100, + 0b0101010, 0b0001000, 0b0001100, 0b0001100, + 0b0110010, 0b0001000, 0b0010000, 0b0000010, + 0b0100010, 0b0001000, 0b0100000, 0b0100010, + 0b0011100, 0b0011100, 0b0111110, 0b0011100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// 4 5 6 7 + + 0b0000100, 0b0111110, 0b0001110, 0b0111110, + 0b0001100, 0b0100000, 0b0010000, 0b0000010, + 0b0010100, 0b0111100, 0b0100000, 0b0000100, + 0b0100100, 0b0000010, 0b0111100, 0b0001000, + 0b0111110, 0b0000010, 0b0100010, 0b0010000, + 0b0000100, 0b0100010, 0b0100010, 0b0010000, + 0b0000100, 0b0011100, 0b0011100, 0b0010000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// 8 9 : ; + + 0b0011100, 0b0011100, 0b0000000, 0b0000000, + 0b0100010, 0b0100010, 0b0000000, 0b0000000, + 0b0100010, 0b0100010, 0b0001000, 0b0001000, + 0b0011100, 0b0011110, 0b0000000, 0b0000000, + 0b0100010, 0b0000010, 0b0001000, 0b0001000, + 0b0100010, 0b0000100, 0b0000000, 0b0001000, + 0b0011100, 0b0111000, 0b0000000, 0b0010000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// < = > ? + + 0b0000100, 0b0000000, 0b0010000, 0b0011100, + 0b0001000, 0b0000000, 0b0001000, 0b0100010, + 0b0010000, 0b0111110, 0b0000100, 0b0000100, + 0b0100000, 0b0000000, 0b0000010, 0b0001000, + 0b0010000, 0b0111110, 0b0000100, 0b0001000, + 0b0001000, 0b0000000, 0b0001000, 0b0000000, + 0b0000100, 0b0000000, 0b0010000, 0b0001000, + 0b0000000, 0b0000000, 0b0000000, 0b0000000 +]; diff --git a/src/data/charset_upper_ii b/src/data/charset_upper_ii new file mode 100644 index 0000000..6f23536 --- /dev/null +++ b/src/data/charset_upper_ii @@ -0,0 +1,90 @@ +[ +// @ A B C + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0011100, 0b0001000, 0b0111100, 0b0011100, + 0b0100010, 0b0010100, 0b0100010, 0b0100010, + 0b0101010, 0b0100010, 0b0100010, 0b0100000, + 0b0101110, 0b0100010, 0b0111100, 0b0100000, + 0b0101100, 0b0111110, 0b0100010, 0b0100000, + 0b0100000, 0b0100010, 0b0100010, 0b0100010, + 0b0011110, 0b0100010, 0b0111100, 0b0011100, + +// D E F G + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0111100, 0b0111110, 0b0111110, 0b0011110, + 0b0100010, 0b0100000, 0b0100000, 0b0100000, + 0b0100010, 0b0100000, 0b0100000, 0b0100000, + 0b0100010, 0b0111100, 0b0111100, 0b0100000, + 0b0100010, 0b0100000, 0b0100000, 0b0100110, + 0b0100010, 0b0100000, 0b0100000, 0b0100010, + 0b0111100, 0b0111110, 0b0100000, 0b0011110, + +// H I J K + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0100010, 0b0011100, 0b0000010, 0b0100010, + 0b0100010, 0b0001000, 0b0000010, 0b0100100, + 0b0100010, 0b0001000, 0b0000010, 0b0101000, + 0b0111110, 0b0001000, 0b0000010, 0b0110000, + 0b0100010, 0b0001000, 0b0000010, 0b0101000, + 0b0100010, 0b0001000, 0b0100010, 0b0100100, + 0b0100010, 0b0011100, 0b0011100, 0b0100010, + +// L M N O + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0100000, 0b0100010, 0b0100010, 0b0011100, + 0b0100000, 0b0110110, 0b0100010, 0b0100010, + 0b0100000, 0b0101010, 0b0110010, 0b0100010, + 0b0100000, 0b0101010, 0b0101010, 0b0100010, + 0b0100000, 0b0100010, 0b0100110, 0b0100010, + 0b0100000, 0b0100010, 0b0100010, 0b0100010, + 0b0111110, 0b0100010, 0b0100010, 0b0011100, + +// P Q R S + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0111100, 0b0011100, 0b0111100, 0b0011100, + 0b0100010, 0b0100010, 0b0100010, 0b0100010, + 0b0100010, 0b0100010, 0b0100010, 0b0100000, + 0b0111100, 0b0100010, 0b0111100, 0b0011100, + 0b0100000, 0b0101010, 0b0101000, 0b0000010, + 0b0100000, 0b0100100, 0b0100100, 0b0100010, + 0b0100000, 0b0011010, 0b0100010, 0b0011100, + +// T U V W + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0111110, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0101010, + 0b0001000, 0b0100010, 0b0100010, 0b0101010, + 0b0001000, 0b0100010, 0b0010100, 0b0110110, + 0b0001000, 0b0011100, 0b0001000, 0b0100010, + +// X Y Z [ + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0100010, 0b0100010, 0b0111110, 0b0111110, + 0b0100010, 0b0100010, 0b0000010, 0b0110000, + 0b0010100, 0b0010100, 0b0000100, 0b0110000, + 0b0001000, 0b0001000, 0b0001000, 0b0110000, + 0b0010100, 0b0001000, 0b0010000, 0b0110000, + 0b0100010, 0b0001000, 0b0100000, 0b0110000, + 0b0100010, 0b0001000, 0b0111110, 0b0111110, + +// \ ] ^ _ + + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + 0b0000000, 0b0111110, 0b0000000, 0b0000000, + 0b0100000, 0b0000110, 0b0000000, 0b0000000, + 0b0010000, 0b0000110, 0b0001000, 0b0000000, + 0b0001000, 0b0000110, 0b0010100, 0b0000000, + 0b0000100, 0b0000110, 0b0100010, 0b0000000, + 0b0000010, 0b0000110, 0b0000000, 0b0000000, + 0b0000000, 0b0111110, 0b0000000, 0b0111110 +]; + diff --git a/src/data/charset_upper_iie b/src/data/charset_upper_iie new file mode 100644 index 0000000..442a7fa --- /dev/null +++ b/src/data/charset_upper_iie @@ -0,0 +1,90 @@ +[ +// @ A B C + + 0b0011100, 0b0001000, 0b0111100, 0b0011100, + 0b0100010, 0b0010100, 0b0100010, 0b0100010, + 0b0101010, 0b0100010, 0b0100010, 0b0100000, + 0b0101110, 0b0100010, 0b0111100, 0b0100000, + 0b0101100, 0b0111110, 0b0100010, 0b0100000, + 0b0100000, 0b0100010, 0b0100010, 0b0100010, + 0b0011110, 0b0100010, 0b0111100, 0b0011100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// D E F G + + 0b0111100, 0b0111110, 0b0111110, 0b0011110, + 0b0100010, 0b0100000, 0b0100000, 0b0100000, + 0b0100010, 0b0100000, 0b0100000, 0b0100000, + 0b0100010, 0b0111100, 0b0111100, 0b0100000, + 0b0100010, 0b0100000, 0b0100000, 0b0100110, + 0b0100010, 0b0100000, 0b0100000, 0b0100010, + 0b0111100, 0b0111110, 0b0100000, 0b0011110, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// H I J K + + 0b0100010, 0b0011100, 0b0000010, 0b0100010, + 0b0100010, 0b0001000, 0b0000010, 0b0100100, + 0b0100010, 0b0001000, 0b0000010, 0b0101000, + 0b0111110, 0b0001000, 0b0000010, 0b0110000, + 0b0100010, 0b0001000, 0b0000010, 0b0101000, + 0b0100010, 0b0001000, 0b0100010, 0b0100100, + 0b0100010, 0b0011100, 0b0011100, 0b0100010, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// L M N O + + 0b0100000, 0b0100010, 0b0100010, 0b0011100, + 0b0100000, 0b0110110, 0b0100010, 0b0100010, + 0b0100000, 0b0101010, 0b0110010, 0b0100010, + 0b0100000, 0b0101010, 0b0101010, 0b0100010, + 0b0100000, 0b0100010, 0b0100110, 0b0100010, + 0b0100000, 0b0100010, 0b0100010, 0b0100010, + 0b0111110, 0b0100010, 0b0100010, 0b0011100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// P Q R S + + 0b0111100, 0b0011100, 0b0111100, 0b0011100, + 0b0100010, 0b0100010, 0b0100010, 0b0100010, + 0b0100010, 0b0100010, 0b0100010, 0b0100000, + 0b0111100, 0b0100010, 0b0111100, 0b0011100, + 0b0100000, 0b0101010, 0b0101000, 0b0000010, + 0b0100000, 0b0100100, 0b0100100, 0b0100010, + 0b0100000, 0b0011010, 0b0100010, 0b0011100, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// T U V W + + 0b0111110, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0100010, + 0b0001000, 0b0100010, 0b0100010, 0b0101010, + 0b0001000, 0b0100010, 0b0100010, 0b0101010, + 0b0001000, 0b0100010, 0b0010100, 0b0110110, + 0b0001000, 0b0011100, 0b0001000, 0b0100010, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// X Y Z [ + + 0b0100010, 0b0100010, 0b0111110, 0b0111110, + 0b0100010, 0b0100010, 0b0000010, 0b0110000, + 0b0010100, 0b0010100, 0b0000100, 0b0110000, + 0b0001000, 0b0001000, 0b0001000, 0b0110000, + 0b0010100, 0b0001000, 0b0010000, 0b0110000, + 0b0100010, 0b0001000, 0b0100000, 0b0110000, + 0b0100010, 0b0001000, 0b0111110, 0b0111110, + 0b0000000, 0b0000000, 0b0000000, 0b0000000, + +// \ ] ^ _ + + 0b0000000, 0b0111110, 0b0000000, 0b0000000, + 0b0100000, 0b0000110, 0b0000000, 0b0000000, + 0b0010000, 0b0000110, 0b0001000, 0b0000000, + 0b0001000, 0b0000110, 0b0010100, 0b0000000, + 0b0000100, 0b0000110, 0b0100010, 0b0000000, + 0b0000010, 0b0000110, 0b0000000, 0b0000000, + 0b0000000, 0b0111110, 0b0000000, 0b0000000, + 0b0000000, 0b0000000, 0b0000000, 0b1111111 +]; + diff --git a/src/data/hires80_test b/src/data/hires80_test new file mode 100644 index 0000000000000000000000000000000000000000..3e156424064bfb3fa8fc5943e1194cc4f1905ba3 GIT binary patch literal 16384 zcmcgzZI9c=5ia1km0Kx@;-(d?Gwe!$i(U+;5@9t5eK^Eb1i7G`FL5oQRu4F-YoL+O zKo5v;jn4bm`^@YMcS$ZORzOUgxFn^0=Ixo8T_)qJ@xh0PMD)7RQ%B%eaS|u%#i%t} z^k==su-U^kAA2d|lN3hnYWrmdXzLvsC|&lIN6Coe9OUyzziOOITvmFm}x2>N|V+ zo%^g5-_uCV95;uJIO)xJj%J_ExE|t;gd9&kapx`^@6X}?^eDi8-z&g1)?G*OtUntz zf!ORPWdA;*YuJnvU#}D)JMzDm!H0JR<}_B!pW{Sy`ioJc4@5?v4I9HbVaVGoQs(+? z%-_3qw9m#r(3elhpNl-Qwto`0#C-JqaMpv?J=lJv=ObPf9oMT};eGYpjQ$0d&q?x( z`2Ph-Nyq!#bDo1{Unim^nxn=L3|t!-EdT#s{2##o|J>8)kf4nJ@nX~=EA`p(3bFC{ zPEU>Kv$p<`_#(yDqdNTG$KRbBTU6qM`SkBYYU4{}-b-V9_YD3GqbD|UQw_7D>%DuY zuKsb-N6f{VbGVuz7c_cFZz*T+1wAW2_Eq@eZYZY63hPBoh=_i~expyhfT{~{sG zwOrR_`49Xj{-yTLj^bv#9Cb#_J(U9>0nZ%bpOxzEj-$}^ekT5iSu5s$Py)=>LS0=# zf<`}~JgDbqURf-27iX{^^>Vi=dX~XIg#A}z%8}6@OzZ_R6{AIK9LST+;ml;I*vP3F zSvUU1SKxnRXV7sj<$sg-j~Lb621#|Ncc*u4?Nb{+LVP;tJg&w6%KGPeA;bRhRquOi zd0z_f|MGS36BDn+-ln@%{4+BYpAr9X|UlTkJQ6Q3t}`=kB_Vu~ah zq4rWeLG;ghZ+qZBx&ZD&{JLJEKg(^NXYdi(#J`d218cbo3G8H68!Xl1#${@Yy7)gt z!s$J<73%OmIj>Rw$5+oY9dnEw&@&+H#^-GJ)eU&@N6gZz%VJisUpus_zJ3WQk$o()OkH&< z9oZmM_lkAot0VXi4`rFxKplb%W`(M(X7#n2>N|V6cRRqk`uBbt$N;qd=GtL9!Ys17 z-k*Cssf!OSkWImOc(kv&-~<@}IYFWl$KXQM;qxSiU$OpPF9g*SNPyZ$I{^4pdrOD% z-?e_O{%QVQ2AKfdMpqK>mx?Xem0}d%yx3 z$?`vsTz1F94{GgY0+Q0%fAgN%85pxUio|Q`(VTT5JOiZ0lfZB*P?Vkc*t`7EBGHrqNVB{vJR>r#@v4#UjM83&+982$F5Ilj9sk` z?J7~;K_;X_Yg)yB^Sx{JechLP@dsx8U9T=fD-g`m6s>i_5+LuI>|@u}LAx7#|Loxh zwf3w)QXa^E(GhdrueDY}{PphkzP9zaHvTg=_h>d0$gQ<(s{Kb-=ks3tPnCP=Ze4un z?lVtcjh|g~U?tm&xQO0(f9-jwF1}O$QGwF~^%vEsk8a$)X>p#W-$PbBuKv%_O;O!N z|I784`fsj+@}-MXGA_l(`U)2H;?46eML%_vR8@MYE06)ZJA@`LN-!N;m^`Vz?b{#d z5#ARCceR=S=s+$Pc;kS62fOfzVc?u;3kpL4ZYDem#`3;p~N$;_LTs z78{Wm(J^3_ND!Qx^DDLM7TD0#>egd zty_pcaF5;q(9S;9bIJT$QevX$*n{BZvP^t8GJqJV5CB4mJwvtY_B#eZ}a zX>D`u92y(_=?%ew2XEg^+UnQ_enq-$qoljV_cr*WYd)}}jdNF=sq6aq^BAVNZ}2DW zVyV^aNjGV4qDgyd&?7NvNAFJ5Q~IopA17M#RDucF;MSKTMaYcgJJR1CrT7<;tt(|Z zcM?0et|anXSmN7({^r9JAv1a;EjB$V#V_zJp^In|P4OpqJmvW&@|>l~pm|vQ?8g5_ z9{Gj44;@aCVY;l0_*QqBARxmgY5w59Mv0xD&-@MdFEq-E#^A~ z=ZyY^m>C;zDq$&0BqbTPgK1aX-;jk;-zff524HFG<0q7c30WT*>9huF_U&NXrAT29 zDeb>`chWvF=sEva>W`N!7THKpkN+PJ=RdvuqkoQ6$)7B$F9-NPN9<~Ag!wN^8a6xki|4;#x1%Ja&`WuG@y`3LsROJOtMcJP96AcfX{qnIG&K>57lvy6YM zo#MaAqIo%1B2J=BH=*nUT*Ni~k-pN`t3V3H!yEsJ7yl`<{!wgw=6?)cb%_05Wlj0- zTmMW%s+I6@&6S5=pl^X3B;}Vj3Io5P`H`#R1pHH(N+U}76^%?zs*j(HU!7TWSie9* z$fVG=vFK8j*g7`BPmZYn^Y|$y$Q)#YTVD>Xt%Gat9M1pMF2nxaYI2&vHnM>Kr=MGE zl zy_$bYicL?BZS1aviTeSE58e*XdVE#ps?E~YyMs|M(+x$}VT zxY8Ccc=ls*kTbBzQm_u65F} z!kBgmAJL+Gq&~3%NU!VSCx`LBgnu7zQuO-$$K;;TJ97sApMrrnR*0T3{>?|zC$bgl z>M!6+@K0F~b|v=F)dcgO4BPpY^|zsjmJOGb;u7uU18#uD<4UB`f$>!acIE zxTmry@eZ|!e9N_XBQNq;MRzLnM|8C(ftVuvZL}rMtLR7lg?HPpSTCQ~K`WHzAH#pz z;VwQzl*H4Bca>QCjp<}7`G2JOzZRo&U)v=KwyO8w+{?P^zB;IP zkuPx1#SdZn_sK?oQ@a!-Yammx99`_cJ=nlkx?U(heRE9zy^9l7<+Pow1h>AFgb$0y z$zTh7&noyydRYu4A+>r;o4ZXeUU*G?z z^j!U^|0OHZ6=eWg|EE?h8CCTE;6OXv{ZjpZ%KTUBf5A17qpOmCTBb*3`HvV#_AS_f z)ZWD`SbuM!HTM9JMbBGYf6deMZ2Y7ZE;tUg5peDt$zN=^Iv(Zfd9bgVzuUWAF$SEC!gxvOES3j582$k zpTsW;_=kx7=`_ZF;bn3_nE#nLsf+Kh-*_HpM1uSm@seb7?i(w+XTMI?#mDX!#~m?` z4!WZJPW%5ZN_bqxzugLZOTn7L@z47?lzrF|!~mu7!TdLQQc39R4|KKuHh#>Vc~wjA zAV_c6-PRT<`TvUv>#d)Z>^~6tQypEQ4g|BHrU|_Ag!N&Csqwayr_Cn{;VP;mF<@&$JAfce{CW5yc(02Zhd(*9^ODCh|&~YL^Ar|T$k!^cb(I@ zb$l?c>D3^~a{d$T8vWIDt|j~zH?B0L__^rA{vY#B%|JDXYA;pciTslIM{+sZxk!5a z=(znqynRB?PpeJ&ESq}#&*TZ;{yCvX>qT$4Wh>Zly|9IrYFjmq_4mX4bQV8OLJ> z%$Tgq!z{*KqI8Jcty4Mh*4{csahq%{zaa96mOrlZq8ino zG+({ZzVnIrzr_0%$CTpj()Rll;NH9bwc(NN_6s?3p%4FuPSn^_bFTHbt@Qs{C|xq) zW9PF(icCSay6IAe#)yL8!To`(9dWH+>l~V|TJh)8`DuGwGC}W?GO&MUsA#d#PdayT z7mTjV3}lCBf91vB|G1|AF0ERMSOjuBL8Z@#%z#h951Q$JuQ~q&RjNnz-qot! zyHf4ZxA@5BXS!|6^S=oKu}wb|P+SnnOzIBKg8UhxC4-j1A1@rKU+OQ$=f7UP)D#E1 z`%EQs4%uVtLMRH9ojGqAIU`({nPx^x>>$(M7L zz$FFzU%LeScT&)4)7%$q6%^E3!X<=^I+h$}#M~(loFKk2vbJUy0!3UOG={ak=!k#F_F^0d) z)QG2Fy%*kvpN$cqEvfPEd^>Pa?=W5HhCj=KZy_qsu?AcvSPUHd`2n~9mX5Jt?1u${ z2QzEHKY~M5TNo_=jQYlBkNy;$$(c4qYCn+^ur2Wow!GGIc;9r0#&?Zem2eI^wTQUV{fd4}0%G9E( z4D;Rqgsf3~!Y%OEH2%G^V|RrA*WhcMV$Gt{YaaBxp!8W_rTi2rk^+9}k=!qdhPTQ; zj%#ihOqH48xW;zv$*$aWk^gziDF2r&iE`n7dHlQTUl{+WL8AO`A^%(AVgBp)n&W@_ zUxEKqgJhh7#SG&kvZWAR(qO-G8{*myw(OjvOsVxBD?{9L%0~%EzC8icge0QH8}0j0 z0@NtXA?>wVvb`Y!BwvKCH1_R>A^uC9Y22#MyA51usb3)4_VJdYcK&kF71R%F&!uir zCuBhWzYP4dbbR=NzswY$e;k=Me-fU5%NjiS0##7#sooLZ=8$99<%}&0u7&!CM(?cT zr@Fl}3kx?MJ^vQ`8{=fN{J%rYBLWPP`SrC+*JRN2vb* z8C|D-wQ>?wtT`c4>;fUhr`-bon}=K>^z&DD?*3&>v>h7%$bXFg_(s-ZH{JZUnyQr52+4|33Op zTTOfE(F4+-e5C$cZ=wITT&JSGaVgnPP0N;+oHaZp1lN^_YaH1?;!LGP?>{>_LcfXXem3Kt*w}Fd2=PDiH{c)je-3>$tIl<` zLS5Z2VC@?I!I0~}!KnT_=y))hK>zt~GUZxkHobsv2*|1_KK~Z@)A(;)g9HDi)0raU zANBtsr^hN+Y*q5jQRM&AcUJSZ?PX@K-Z=hL{A0U3jDw#3;~Q`syPWs%O2jz?^RE!jWP^0C+Vk_laD z1BAF1u)hubf1*)?_3()=XBLf#A-@0@2r1^@!v8zq)|-cKB)dGjH^+dFF;_VP5TLHypY6{TqyBrFR!ex= z#eshvgE{|$mTm9cobiVb1pZ4m@>UU#{FR6NKO{7Yv|zFRt@5w__U0cc zTZkzCZLi^9_x~f9f3g2NuFg|SVgA21yIKBkg?}6Phu8t#KZ~Ij9iTk@3w!IiruKhN z%?yl+ZCd|hbI>07(L)sk@?G73eqTKd{NLjIe^NikM|6~XJHizzab4uj=b|^^^>reJ zU@qp^Pr7VaR;7_u1K!=gvm%7$I!C{0+zm`%j(!Z40rgUVkF{ z{~|`uMwYcZ9RWi8W3FDS{}BZR=YK8LM*NqJLFTEBk8_nom1#XEMT}l`K3N#Z&!(@2 zf6QOnlK+Pl?xr%v8ie@It_30f-Omk$7Da^f4NLXVf9yu3Oba^DjFW}KrLYBF)` zq?lJ0`O;E`ADr(sGwe3wn17sZI{$uV{GHMvshm0}V zAk#w`;&HZkH(@e6gY?BjvrV^X+ED0-P^S)Om(c&ef82*fY83Nok^d9XA8c~Hy>j%vq1d(E`!prYs)b^Py*nZ5nEGvyi}`A#2O zP=2`l{ndL{bC|26vqSz5=bDZEKW*xNYy~wvS{gVr`?UFgPlU0@%E|u!5_8I}3Biul znc}{WXe69}_#B%xCOCM76#HL;uW$COh5tvfDT(|ihH&%&nF2+?-w$AQ9r(li782|qHuhL>a`u{E0|GVJr)R&@^nm?5mBnno3`j{Ec zH81pI+Gd3K-_H7%Jz+3^)E~IRI5O-%(@Qs|Kj8kqQt$t3@M~`i2TYv%af}_h8i=8Z z#S8Q7$z}Zh28$hWFn?E;nhNKdwYoMxiTp37Q2!SVjeh~%KS2GpjBbqkPh&!r@IxhH zRlWa@{kmyy-O=$*jun#i-__|)Vg84D%fOYcYo#F)xQ~eak4MopV*Ib=7t#xS=6IH< IRuhT;1Ifn$&j0`b literal 0 HcmV?d00001 diff --git a/src/data/laz_scheme b/src/data/laz_scheme new file mode 100644 index 0000000..89610f8 --- /dev/null +++ b/src/data/laz_scheme @@ -0,0 +1,442 @@ +[ + // Ellsworth + [ + // Ellsworth:A2_ColorSet_DARK_BLUE + [ + Color.Black, + Color.Brown, + Color.DarkGreen, + Color.Green, + Color.DarkBlue, + Color.LightGray, + Color.MediumBlue, + Color.Aqua, + Color.Red, + Color.Orange, + Color.DarkGray, + Color.Yellow, + Color.Purple, + Color.Pink, + Color.LightBlue, + Color.White, + Color.Brown, + Color.Brown, + Color.Green, + Color.Green, + Color.LightGray, + Color.LightGray, + Color.Aqua, + Color.Aqua, + Color.Orange, + Color.Orange, + Color.Yellow, + Color.Yellow, + Color.Pink, + Color.Pink, + Color.White, + Color.White + ], + + // Ellsworth:A2_ColorSet_DARK_GREEN + [ + Color.Black, + Color.Red, + Color.Brown, + Color.Orange, + Color.DarkGreen, + Color.DarkGray, + Color.Green, + Color.Yellow, + Color.DarkBlue, + Color.Purple, + Color.LightGray, + Color.Pink, + Color.MediumBlue, + Color.LightBlue, + Color.Aqua, + Color.White, + Color.Red, + Color.Red, + Color.Orange, + Color.Orange, + Color.DarkGray, + Color.DarkGray, + Color.Yellow, + Color.Yellow, + Color.Purple, + Color.Purple, + Color.Pink, + Color.Pink, + Color.LightBlue, + Color.LightBlue, + Color.White, + Color.White + ], + + // Ellsworth:A2_ColorSet_BROWN + [ + Color.Black, + Color.DarkBlue, + Color.Red, + Color.Purple, + Color.Brown, + Color.LightGray, + Color.Orange, + Color.Pink, + Color.DarkGreen, + Color.MediumBlue, + Color.DarkGray, + Color.LightBlue, + Color.Green, + Color.Aqua, + Color.Yellow, + Color.White, + Color.DarkBlue, + Color.DarkBlue, + Color.Purple, + Color.Purple, + Color.LightGray, + Color.LightGray, + Color.Pink, + Color.Pink, + Color.MediumBlue, + Color.MediumBlue, + Color.LightBlue, + Color.LightBlue, + Color.Aqua, + Color.Aqua, + Color.White, + Color.White + ], + + // Ellsworth:A2_ColorSet_RED + [ + Color.Black, + Color.DarkGreen, + Color.DarkBlue, + Color.MediumBlue, + Color.Red, + Color.DarkGray, + Color.Purple, + Color.LightBlue, + Color.Brown, + Color.Green, + Color.LightGray, + Color.Aqua, + Color.Orange, + Color.Yellow, + Color.Pink, + Color.White, + Color.DarkGreen, + Color.DarkGreen, + Color.MediumBlue, + Color.MediumBlue, + Color.DarkGray, + Color.DarkGray, + Color.LightBlue, + Color.LightBlue, + Color.Green, + Color.Green, + Color.Aqua, + Color.Aqua, + Color.Yellow, + Color.Yellow, + Color.White, + Color.White + ] + ], + + // DoubleColor + [ + // DoubleColor:A2_ColorSet_DARK_BLUE + [ + Color.Black, + Color.Black, + Color.DarkGreen + 0x10, + Color.Green + 0x10, + Color.DarkBlue, + Color.LightGray, + Color.MediumBlue, + Color.Aqua, + Color.Red + 0x10, + Color.Orange + 0x10, + Color.DarkGray, + Color.Yellow + 0x10, + Color.Purple, + Color.Pink, + Color.LightBlue, + Color.White, + Color.Black, + Color.Brown + 0x10, + Color.Green + 0x10, + Color.Green + 0x10, + Color.LightGray, + Color.LightGray, + Color.Aqua, + Color.Aqua, + Color.Orange + 0x10, + Color.Orange + 0x10, + Color.Yellow + 0x10, + Color.Yellow + 0x10, + Color.Pink, + Color.Pink, + Color.White, + Color.White + ], + + // DoubleColor:A2_ColorSet_DARK_GREEN + [ + Color.Black, + Color.Black, + Color.Brown + 0x10, + Color.Orange + 0x10, + Color.DarkGreen, + Color.DarkGray, + Color.Green, + Color.Yellow, + Color.DarkBlue + 0x10, + Color.Purple + 0x10, + Color.LightGray, + Color.Pink + 0x10, + Color.MediumBlue, + Color.LightBlue, + Color.Aqua, + Color.White, + Color.Black, + Color.Red + 0x10, + Color.Orange + 0x10, + Color.Orange + 0x10, + Color.DarkGray, + Color.DarkGray, + Color.Yellow, + Color.Yellow, + Color.Purple + 0x10, + Color.Purple + 0x10, + Color.Pink + 0x10, + Color.Pink + 0x10, + Color.LightBlue, + Color.LightBlue, + Color.White, + Color.White + ], + + // DoubleColor:A2_ColorSet_BROWN + [ + Color.Black, + Color.Black, + Color.Red + 0x10, + Color.Purple + 0x10, + Color.Brown, + Color.LightGray, + Color.Orange, + Color.Pink, + Color.DarkGreen + 0x10, + Color.MediumBlue + 0x10, + Color.DarkGray, + Color.LightBlue + 0x10, + Color.Green, + Color.Aqua, + Color.Yellow, + Color.White, + Color.Black, + Color.DarkBlue + 0x10, + Color.Purple + 0x10, + Color.Purple + 0x10, + Color.LightGray, + Color.LightGray, + Color.Pink, + Color.Pink, + Color.MediumBlue + 0x10, + Color.MediumBlue + 0x10, + Color.LightBlue + 0x10, + Color.LightBlue + 0x10, + Color.Aqua, + Color.Aqua, + Color.White, + Color.White + ], + + // DoubleColor:A2_ColorSet_RED + [ + Color.Black, + Color.Black, + Color.DarkBlue + 0x10, + Color.MediumBlue + 0x10, + Color.Red, + Color.DarkGray, + Color.Purple, + Color.LightBlue, + Color.Brown + 0x10, + Color.Green + 0x10, + Color.LightGray, + Color.Aqua + 0x10, + Color.Orange, + Color.Yellow, + Color.Pink, + Color.White, + Color.Black, + Color.DarkGreen + 0x10, + Color.MediumBlue + 0x10, + Color.MediumBlue + 0x10, + Color.DarkGray, + Color.DarkGray, + Color.LightBlue, + Color.LightBlue, + Color.Green + 0x10, + Color.Green + 0x10, + Color.Aqua + 0x10, + Color.Aqua + 0x10, + Color.Yellow, + Color.Yellow, + Color.White, + Color.White + ] + ], + + // Sharp + [ + // Sharp:A2_ColorSet_DARK_BLUE + [ + Color.Black, + Color.Black, + Color.DarkGreen + 0x80, + Color.Green + 0x80, + Color.DarkBlue, + Color.LightGray, + Color.MediumBlue, + Color.Aqua + 0x80, + Color.Red + 0x80, + Color.Orange, + Color.DarkGray, + Color.Yellow, + Color.Purple, + Color.Pink, + Color.LightBlue, + Color.White, + Color.Black, + Color.Brown + 0x80, + Color.Green, + Color.Green, + Color.LightGray, + Color.LightGray, + Color.Aqua, + Color.Aqua, + Color.Orange + 0x80, + Color.Orange, + Color.Yellow, + Color.Yellow, + Color.Pink + 0x80, + Color.Pink, + Color.White, + Color.White + ], + + // Sharp:A2_ColorSet_DARK_GREEN + [ + Color.Black, + Color.Black, + Color.Brown + 0x80, + Color.Orange + 0x80, + Color.DarkGreen, + Color.DarkGray, + Color.Green, + Color.Yellow + 0x80, + Color.DarkBlue + 0x80, + Color.Purple, + Color.LightGray, + Color.Pink, + Color.MediumBlue, + Color.LightBlue, + Color.Aqua, + Color.White, + Color.Black, + Color.Red + 0x80, + Color.Orange, + Color.Orange, + Color.DarkGray, + Color.DarkGray, + Color.Yellow, + Color.Yellow, + Color.Purple + 0x80, + Color.Purple, + Color.Pink, + Color.Pink, + Color.LightBlue + 0x80, + Color.LightBlue, + Color.White, + Color.White + ], + + // Sharp:A2_ColorSet_BROWN + [ + Color.Black, + Color.Black, + Color.Red + 0x80, + Color.Purple + 0x80, + Color.Brown, + Color.LightGray, + Color.Orange, + Color.Pink + 0x80, + Color.DarkGreen + 0x80, + Color.MediumBlue, + Color.DarkGray, + Color.LightBlue, + Color.Green, + Color.Aqua, + Color.Yellow, + Color.White, + Color.Black, + Color.DarkBlue + 0x80, + Color.Purple, + Color.Purple, + Color.LightGray, + Color.LightGray, + Color.Pink, + Color.Pink, + Color.MediumBlue + 0x80, + Color.MediumBlue, + Color.LightBlue, + Color.LightBlue, + Color.Aqua + 0x80, + Color.Aqua, + Color.White, + Color.White + ], + + // Sharp:A2_ColorSet_RED + [ + Color.Black, + Color.Black, + Color.DarkBlue + 0x80, + Color.MediumBlue + 0x80, + Color.Red, + Color.DarkGray, + Color.Purple, + Color.LightBlue + 0x80, + Color.Brown + 0x80, + Color.Green, + Color.LightGray, + Color.Aqua, + Color.Orange, + Color.Yellow, + Color.Pink, + Color.White, + Color.Black, + Color.DarkGreen + 0x80, + Color.MediumBlue, + Color.MediumBlue, + Color.DarkGray, + Color.DarkGray, + Color.LightBlue, + Color.LightBlue, + Color.Green + 0x80, + Color.Green, + Color.Aqua, + Color.Aqua, + Color.Yellow + 0x80, + Color.Yellow, + Color.White, + Color.White + ] + ] +]; diff --git a/src/data/laz_sharp b/src/data/laz_sharp new file mode 100644 index 0000000..7b6a831 --- /dev/null +++ b/src/data/laz_sharp @@ -0,0 +1,37 @@ +[ + // prevBit 0 + [ + // nextBit 0 + [ + -1, -1, 0, 0, -1, -1, -1, 99, + 0, -1, -1, -1, -1, -1, -1, -1, + -1, 99, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, 99, -1, -1, -1 + ], + // nextBit 1 + [ + -1, -1, 0, 0, -1, -1, -1, 15, + 99, -1, -1, -1, -1, -1, -1, -1, + -1, 99, -1, -1, -1, -1, -1, -1, + 99, -1, -1, -1, 99, -1, -1, -1 + ] + ], + + // prevBit 1 + [ + //nextBit 0 + [ + -1, -1, 99, 99, -1, -1, -1, 99, + 0, -1, -1, -1, -1, -1, -1, -1, + -1, 99, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, 15, -1, -1, -1 + ], + // nextBit 1 + [ + -1, -1, 99, 99, -1, -1, -1, 15, + 99, -1, -1, -1, -1, -1, -1, -1, + -1, 0, -1, -1, -1, -1, -1, -1, + 0, -1, -1, -1, 15, -1, -1, -1 + ] + ] +]; diff --git a/src/data/lores_test b/src/data/lores_test new file mode 100644 index 0000000..eaab43f --- /dev/null +++ b/src/data/lores_test @@ -0,0 +1,130 @@ +[ + 0x00, 0x00, 0x55, 0x05, 0x05, 0x15, 0x15, 0x25, + 0x25, 0x35, 0x35, 0x45, 0x45, 0x55, 0x55, 0x65, + 0x65, 0x75, 0x75, 0x85, 0x85, 0x95, 0x95, 0xa5, + 0xa5, 0xb5, 0xb5, 0xc5, 0xc5, 0xd5, 0xd5, 0xe5, + 0xe5, 0xf5, 0xf5, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x38, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x50, 0x50, 0x51, 0x51, 0x52, + 0x52, 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x56, + 0x56, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5a, + 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5d, 0x5d, 0x5e, + 0x5e, 0x5f, 0x5f, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0xa0, 0xa0, 0xa0, 0xc2, 0xa0, 0xcd, 0xa0, 0xc4, + 0xa0, 0xd0, 0xa0, 0xc4, 0xa0, 0xc7, 0xa0, 0xcd, + 0xa0, 0xcc, 0xa0, 0xc2, 0xa0, 0xcf, 0xa0, 0xc7, + 0xa0, 0xd0, 0xa0, 0xcc, 0xa0, 0xd9, 0xa0, 0xc1, + 0xa0, 0xd7, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0xa0, 0xa0, 0xa0, 0xcc, 0xa0, 0xc7, 0xa0, 0xc2, + 0xa0, 0xd5, 0xa0, 0xc7, 0xa0, 0xd2, 0xa0, 0xc2, + 0xa0, 0xc2, 0xa0, 0xd2, 0xa0, 0xd2, 0xa0, 0xd2, + 0xa0, 0xc9, 0xa0, 0xc7, 0xa0, 0xc5, 0xa0, 0xd1, + 0xa0, 0xc9, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0xa0, 0xa0, 0xa0, 0xc1, 0xa0, 0xd4, 0xa0, 0xcc, + 0xa0, 0xd2, 0xa0, 0xd2, 0xa0, 0xc5, 0xa0, 0xcc, + 0xa0, 0xcc, 0xa0, 0xd7, 0xa0, 0xce, 0xa0, 0xc5, + 0xa0, 0xce, 0xa0, 0xd2, 0xa0, 0xcc, 0xa0, 0xd5, + 0xa0, 0xd4, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x00, 0x11, 0x11, 0x22, + 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, + 0x66, 0x77, 0x77, 0x88, 0x88, 0x99, 0x99, 0xaa, + 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, + 0xee, 0xff, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, + 0xa0, 0xa0, 0xa0, 0xcb, 0xa0, 0xc1, 0xa0, 0xd5, + 0xa0, 0xd0, 0xa0, 0xce, 0xa0, 0xd9, 0xa0, 0xd5, + 0xa0, 0xd5, 0xa0, 0xce, 0xa0, 0xc7, 0xa0, 0xd9, + 0xa0, 0xcb, 0xa0, 0xce, 0xa0, 0xcf, 0xa0, 0xc1, + 0xa0, 0xc5, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x00 +]; diff --git a/src/data/palette_5_6_5 b/src/data/palette_5_6_5 new file mode 100644 index 0000000..c444f43 --- /dev/null +++ b/src/data/palette_5_6_5 @@ -0,0 +1,65 @@ +[ + // Ellsworth + [ + 0x0000, 0x7908, 0x3A60, 0xBB68, 0x02E7, 0x7BEF, 0x3D47, 0xBE4F, + 0x418F, 0xBA97, 0x7BEF, 0xFCF7, 0x4477, 0xBD7F, 0x7ED7, 0xFFDF, + 0x0000, 0x3864, 0x1920, 0x59A4, 0x0163, 0x39E7, 0x1AA3, 0x6BA5, + 0x18A7, 0x612C, 0x39E7, 0xC808, 0x222B, 0x201A, 0x242C, 0x7BEF + ], + + // DeBabelizer + [ + 0x0000, 0xF800, 0x7A40, 0xFAA0, 0x0320, 0x4A69, 0x07E0, 0xFFE0, + 0x000E, 0xF81A, 0xA534, 0xFBEC, 0x001C, 0x3CFF, 0x37F4, 0xFFFF, + 0x0000, 0x7800, 0x3900, 0x7940, 0x0180, 0x2124, 0x03E0, 0x7BE0, + 0x0007, 0x780D, 0x528A, 0xB0E0, 0x000E, 0x0273, 0x04CA, 0x7BEF + ], + + // Laz's TV + [ + 0x0000, 0xB807, 0x7BE0, 0xFBE0, 0x3BEF, 0x7BEF, 0x3FE0, 0xBFE7, + 0x381F, 0xF9FF, 0x7BEF, 0xFBEF, 0x3BFF, 0xBBFF, 0x7DF7, 0xFFFF, + 0x0000, 0x5844, 0x3A01, 0x81E1, 0x1A08, 0x4208, 0x1C01, 0x5C03, + 0x184F, 0x80F0, 0x4208, 0x8208, 0x1A10, 0x5A0F, 0x3AEB, 0x8410 + ], + + // Bernie + [ + 0x0000, 0xC806, 0x9B00, 0xFB00, 0x0306, 0x528A, 0x06E0, 0xFFE0, + 0x0013, 0xC999, 0xAD75, 0xFCF3, 0x319F, 0x64FF, 0x37F3, 0xFFFF, + 0x0000, 0x5801, 0x4120, 0x7900, 0x0142, 0x18E3, 0x0320, 0x63A0, + 0x1008, 0x600B, 0x4228, 0x71C7, 0x200E, 0x29AE, 0x03A6, 0x6B6D + ], + + // Gus + [ + 0x0000, 0xE861, 0x6302, 0xFB01, 0x1562, 0x528A, 0x17C2, 0xFFC3, + 0x1051, 0xF89F, 0xCE79, 0xFE7F, 0x107F, 0x667F, 0x3673, 0xFFFF, + 0x0000, 0x7821, 0x3181, 0x7981, 0x12A0, 0x2945, 0x1401, 0x7BE2, + 0x0828, 0x804F, 0x632C, 0x7B2F, 0x0850, 0x2B30, 0x1B29, 0x8410 + ], + + // Canon DV + [ + 0x0000, 0x8040, 0x1261, 0xA221, 0x0A68, 0x4A69, 0x1562, 0x6DC1, + 0x287A, 0xC07D, 0x4A69, 0xF26B, 0x129F, 0x929F, 0x15D4, 0xFFFF, + 0x0000, 0x4020, 0x0940, 0x5121, 0x0924, 0x2124, 0x12A0, 0x32C1, + 0x184D, 0x606E, 0x2124, 0x7925, 0x094F, 0x4950, 0x0AEA, 0x8410 + ], + + // IREZ CapSure + [ + 0x0000, 0xA105, 0x2B62, 0xDC21, 0x138D, 0x8430, 0x1F02, 0xB7A1, + 0x58FE, 0xF93F, 0x8C51, 0xFCB4, 0x2C3F, 0xE4BF, 0x57DD, 0xFFFF, + 0x0000, 0x5082, 0x11A1, 0x6A21, 0x11C7, 0x4208, 0x0B82, 0x53C1, + 0x288F, 0x80AF, 0x4228, 0x7A4A, 0x1A30, 0x7270, 0x2BEF, 0x8410 + ], + + // IIGS + [ + 0x0000, 0xD806, 0x8AA0, 0xFB20, 0x03A4, 0x52AA, 0x16E0, 0xFFE0, + 0x0013, 0xD91B, 0xAD55, 0xFCD1, 0x211F, 0x655F, 0x47F3, 0xFFFF, + 0x0000, 0x6843, 0x4141, 0x8181, 0x01E2, 0x2945, 0x0B62, 0x7BE2, + 0x0009, 0x688D, 0x52AA, 0x7A68, 0x1090, 0x2AB0, 0x1C09, 0x8410 + ] +]; diff --git a/src/device/annunciator.d b/src/device/annunciator.d new file mode 100644 index 0000000..30e778c --- /dev/null +++ b/src/device/annunciator.d @@ -0,0 +1,111 @@ +/+ + + device/annunciator.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.annunciator; + +import device.base; +import memory; + +class Annunciator +{ + bool[4] ann; + + void reboot() + { + ann[0..4] = false; + } + + void activate(int index) + { + if (!ann[index]) + { + ann[index] = true; + } + } + + void deactivate(int index) + { + if (ann[index]) + { + ann[index] = false; + } + } + + void ann_0_On() + { + activate(0); + } + + void ann_1_On() + { + activate(1); + } + + void ann_2_On() + { + activate(2); + } + + void ann_3_On() + { + activate(3); + } + + void ann_0_Off() + { + deactivate(0); + } + + void ann_1_Off() + { + deactivate(1); + } + + void ann_2_Off() + { + deactivate(2); + } + + void ann_3_Off() + { + deactivate(3); + } + + mixin(EmptyInitSwitches()); +} + +class Annunciator_II : Annunciator +{ + mixin(InitSwitches("super", [ + mixin(MakeSwitch([0xC058], "R0W", "ann_0_Off")), + mixin(MakeSwitch([0xC059], "R0W", "ann_0_On")), + mixin(MakeSwitch([0xC05A], "R0W", "ann_1_Off")), + mixin(MakeSwitch([0xC05B], "R0W", "ann_1_On")), + mixin(MakeSwitch([0xC05C], "R0W", "ann_2_Off")), + mixin(MakeSwitch([0xC05D], "R0W", "ann_2_On")), + mixin(MakeSwitch([0xC05E], "R0W", "ann_3_Off")), + mixin(MakeSwitch([0xC05F], "R0W", "ann_3_On")) + ])); +} + +// NOTE: IIe uses Annunciator (the switches are handled by the IOU) +// NOTE: IIc has no annunciators diff --git a/src/device/base.d b/src/device/base.d new file mode 100644 index 0000000..acb8c04 --- /dev/null +++ b/src/device/base.d @@ -0,0 +1,203 @@ +/+ + + device/base.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.base; + +string hex2Digits(int decimalByte) +{ + int highNibble = (decimalByte & 0xF0) >> 4; + int lowNibble = decimalByte & 0x0F; + + string digits = "0123456789ABCDEF"; + + return digits[highNibble..(highNibble + 1)] ~ + digits[lowNibble..(lowNibble + 1)]; +} + +string hex4Digits(int decimalWord) +{ + return hex2Digits(decimalWord >> 8) ~ + hex2Digits(decimalWord & 0xFF); +} + +string InitSwitches(string superCall, string[][] switches) +{ + string initFunc = + "void initSwitches(SoftSwitchPage switches, " ~ + "int slot = -1)\n{\n"; + + if (superCall == "super") + { + initFunc ~= "super.initSwitches(switches, slot);\n"; + } + + initFunc ~= + "ubyte offset = (slot == -1) ? 0 : slot * 16;\n"; + + + string switchFuncs; + for(int sw = 0; sw < switches.length; ++sw) + { + initFunc ~= switches[sw][0] ~ "\n"; + switchFuncs ~= switches[sw][1] ~ "\n"; + } + + initFunc ~= "}\n"; + return initFunc ~ "\n" ~ switchFuncs; +} + +/+ +Read: + ubyte wrapper(ushort addr) { wrapped(); return 0; } + ubyte wrapper(ushort addr) { return wrapped(); } + ubyte wrapped(ushort addr) +Write: + void wrapper(ushort addr, ubyte val) { wrapped(); } + void wrapper(ushort addr, ubyte val) { wrapped(val); } + void wrapper(ushort addr, ubyte val) { wrapped(addr); } + void wrapped(ushort addr, ubyte val) + +Wrapped: + same called for r/w: + void func() void func(addr) + void func(rw) void func(rw, addr) void func(rw, addr, val) + ubyte func() + ubyte func(rw) ubyte func(rw, addr) ubyte func(rw, addr, val) + only read: + void func() void func(addr) ubyte(func) + only write: + void func() void func(addr) + RETURN VAL: (only applies to read) "0" = void, " "/"7" = ubyte + WRAP STYLE: + "R? " (read only) could be: ()/(addr) (+ rw) + " W?" (write only) could be: ()/(addr)/(val)/(addr, val) (+ rw) + "R?W?" (both) could be: ()/(addr) (+ rw) + OR: (rw, val)/(rw, addr, val) ++/ + +string MakeSwitch(int[] addrs, string type, string wrapped) +{ + string initCalls = ""; + string funcBody; + + string readType, writeType; + if (type[0] == 'R') + { + if (type.length == 1) readType = "R"; + else if (type[1] == 'W') + { + readType = "R"; + writeType = "W"; + } + else + { + readType = type[0..2]; + if (type.length > 2) writeType = "W"; + } + } + else writeType = "W"; + + string wrapper, realWrapped; + string rSwitch, wSwitch, args; + + realWrapped = wrapped; + if (wrapped[length - 1] == ')') + { + for (int pos = 0; pos < wrapped.length; ++pos) + { + if (wrapped[pos] == '(') + { + args = wrapped[(pos + 1) .. (length - 1)]; + realWrapped = wrapped[0 .. pos]; + break; + } + } + } + wrapper = "wrapper_" ~ realWrapped; + + if (readType == "R") + { + rSwitch = "setRSwitch"; + funcBody = MakeRSwitch(wrapper, realWrapped, args); + } + else if (readType == "R7") + { + rSwitch = "setR7Switch"; + funcBody = MakeRSwitch(wrapper, realWrapped, args); + } + else if (readType == "R0") + { + rSwitch = "setR0Switch"; + funcBody = MakeR0Switch(wrapper, realWrapped, args); + } + + if (writeType == "W") + { + wSwitch = "setWSwitch"; + funcBody ~= MakeWSwitch(wrapper, realWrapped, args); + } + + for (int ad = 0; ad < addrs.length; ++ad) + { + string addrStr = "(0x" ~ hex4Digits(addrs[ad]) ~ " + offset)"; + if (rSwitch != "") + initCalls ~= "switches." ~ rSwitch ~ "(" ~ addrStr ~ ", &" ~ + wrapper ~ ");\n"; + if (wSwitch != "") + initCalls ~= "switches." ~ wSwitch ~ "(" ~ addrStr ~ ", &" ~ + wrapper ~ ");\n"; + } + + return "[\"" ~ initCalls ~ "\", \"" ~ funcBody ~ "\"]"; +} + +string MakeR0Switch(string wrapper, string wrapped, string args) +{ + return "ubyte " ~ wrapper ~ "(ushort addr)\n" ~ + "{\n" ~ wrapped ~ "(" ~ args ~ ");\n" ~ + "return 0;\n}\n"; +} + +string MakeRSwitch(string wrapper, string wrapped, string args) +{ + return "ubyte " ~ wrapper ~ "(ushort addr)\n" ~ + "{\n" ~ "return " ~ wrapped ~ "(" ~ args ~ ");\n}\n"; +} + +string MakeWSwitch(string wrapper, string wrapped, string args) +{ + return "void " ~ wrapper ~ "(ushort addr, ubyte val)\n" ~ + "{\n" ~ wrapped ~ "(" ~ args ~ ");\n}\n"; +} + +string AbstractInitSwitches() +{ + return + "abstract void initSwitches(SoftSwitchPage switches, " ~ + "int slot = -1);\n"; +} + +string EmptyInitSwitches() +{ + return + "void initSwitches(SoftSwitchPage switches, int slot = -1) {}\n"; +} diff --git a/src/device/keyboard.d b/src/device/keyboard.d new file mode 100644 index 0000000..9b58a23 --- /dev/null +++ b/src/device/keyboard.d @@ -0,0 +1,412 @@ +/+ + + device/keyboard.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.keyboard; + +import device.base; +import memory; +import device.pushbutton; + +const int KEY_BKSP = 65288; // Backspace (DELETE) +const int KEY_TAB = 65289; // Tab +const int KEY_RETURN = 65293; // Enter +const int KEY_ESCAPE = 65307; // ESC +const int KEY_LEFT = 65361; // left arrow +const int KEY_UP = 65362; // up arrow +const int KEY_RIGHT = 65363; // right arrow +const int KEY_DOWN = 65364; // down arrow +const int KEY_LSHIFT = 65505; // left shift +const int KEY_RSHIFT = 65506; // right sight +const int KEY_LOS = 65515; // left "windows" (open apple) +const int KEY_ROS = 65516; // right "windows" (closed apple) + +const int KEY_LOWER_MIN = 97; +const int KEY_LOWER_MAX = 122; +const int KEY_UPPER_MIN = 65; +const int KEY_UPPER_MAX = 90; + +// !"#$%&'()*+,-./0123456789:;<=>?@ +const int KEY_SYMBOL_MIN = 32; +const int KEY_SYMBOL_MAX = 64; +const int KEY_SYMBOL2_MAX = 126; + +const int KEY_BRACKETRIGHT = 93; +const int KEY_CIRCUM = 94; + +import std.stdio; + +class Keyboard +{ + bool keyStrobe; + bool anyKeyDown; + ubyte latch; + bool[int] keysDown; + Pushbuttons buttons; + + class RingBuffer + { + int[] values; + int nextRead, nextWrite; + + this(int len) + { + values.length = len; + clear(); + } + + void clear() + { + nextRead = nextWrite = 0; + } + + bool canRead() + { + return (nextRead != nextWrite); + } + + int read() + { + // assert(canRead()); XXX + int val = values[nextRead]; + nextRead = (nextRead + 1) % values.length; + return val; + } + + void write(int val) + { + int next = (nextWrite + 1) % values.length; + if (next != nextRead) + { + values[nextWrite] = val; + nextWrite = next; + } + else + { + // XXX Need to clean up press buffer + // else emulator misbehaves + writefln("Press buffer full"); + } + } + } + + RingBuffer presses, releases; + + this() + { + // XXX constants + presses = new RingBuffer(20); + releases = new RingBuffer(10); + } + + void reboot() + { + latch = 0; + keyStrobe = anyKeyDown = false; + keysDown = keysDown.init; + presses.clear(); + releases.clear(); + } + + abstract int appleASCII(int keyval, bool ctrl); + abstract bool handleSpecialKey(int keyval, bool keyDown); + + bool handlePress(int keyval, bool ctrl, int keycode) + { + int ascii = appleASCII(keyval, ctrl); + if (ascii < 0) + { + if (handleSpecialKey(keyval, true)) return true; + return false; + } + recordKeyPress(ascii, keycode); + return true; + } + + bool handleRelease(int keyval, bool ctrl, int keycode) + { + int ascii = appleASCII(keyval, ctrl); + if (ascii < 0) + { + if (handleSpecialKey(keyval, false)) return true; + return false; + } + recordKeyRelease(keycode); + return true; + } + + void processPresses() + { + if (!presses.canRead()) return; + + anyKeyDown = true; + keyStrobe = true; + + // assert latch < 0x80; XXX + latch = presses.read(); + keysDown[presses.read()] = true; + } + + void processReleases() + { + if (!releases.canRead()) return; + + int code = releases.read(); + if (code in keysDown) { + keysDown.remove(code); + if (keysDown.length == 0) + { + anyKeyDown = false; + } + } + } + + void recordKeyPress(int ascii, int code) + { + presses.write(ascii); + presses.write(code); + } + + void recordKeyRelease(int code) + { + releases.write(code); + } + + ubyte delegate() onReadLatch; + void delegate() onClearStrobe; + + ubyte readLatch() + { + if (onReadLatch !is null) + { + keyStrobe = true; + return onReadLatch() | 0x80; + } + + if (keyStrobe) + { + return latch | 0x80; + } + else + { + return latch; + } + } + + ubyte peekLatch() + { + return latch; + } + + void clearKeystrobe() + { + if (keyStrobe) + { + keyStrobe = false; + if (onClearStrobe !is null) + onClearStrobe(); + } + } + + mixin(AbstractInitSwitches()); +} + +class Keyboard_II : Keyboard +{ + int appleASCII(int keyval, bool ctrl) + { + int ascii = -1; + if (keyval > 65000) + { + switch (keyval) + { + case KEY_RETURN: + ascii = 0x0D; + break; + case KEY_ESCAPE: + ascii = 0x1B; + break; + case KEY_LEFT: + ascii = 0x08; + break; + case KEY_RIGHT: + ascii = 0x15; + break; + default: + break; + } + } + else + { + if (keyval >= KEY_SYMBOL_MIN) + { + if (keyval < KEY_SYMBOL_MAX) + { + ascii = keyval; + } + else if (keyval == KEY_SYMBOL_MAX) + { + if (ctrl) + ascii = 0; + else + ascii = keyval; + } + else if (keyval >= KEY_UPPER_MIN) + { + if (keyval <= KEY_UPPER_MAX) + { + if (ctrl) + ascii = keyval - KEY_UPPER_MIN + 1; + else + ascii = keyval; + } + else if (keyval == KEY_BRACKETRIGHT || + keyval == KEY_CIRCUM) + { + ascii = keyval; + } + else if (keyval >= KEY_LOWER_MIN) + { + if (keyval <= KEY_LOWER_MAX) + { + if (ctrl) + ascii = keyval - KEY_LOWER_MIN + 1; + // XXX + // else if lowercase-mod + else + ascii = keyval - 32; + } + } + } + } + } + return ascii; + } + + bool handleSpecialKey(int keyval, bool keyDown) + { + // XXX check for shift key mod + return false; + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch( + [0xC000, 0xC001, 0xC002, 0xC003, 0xC004, 0xC005, 0xC006, 0xC007, + 0xC008, 0xC009, 0xC00A, 0xC00B, 0xC00C, 0xC00D, 0xC00E, 0xC00F], + "R", "readLatch")), + mixin(MakeSwitch( + [0xC010, 0xC011, 0xC012, 0xC013, 0xC014, 0xC015, 0xC016, 0xC017, + 0xC018, 0xC019, 0xC01A, 0xC01B, 0xC01C, 0xC01D, 0xC01E, 0xC01F], + "R0W", "clearKeystrobe")) + ])); +} + +class Keyboard_IIe : Keyboard +{ + int appleASCII(int keyval, bool ctrl) + { + int ascii = -1; + if (keyval > 65000) + { + switch (keyval) + { + case KEY_RETURN: + ascii = 0x0D; + break; + case KEY_ESCAPE: + ascii = 0x1B; + break; + case KEY_LEFT: + ascii = 0x08; + break; + case KEY_RIGHT: + ascii = 0x15; + break; + case KEY_UP: + ascii = 0x0B; + break; + case KEY_DOWN: + ascii = 0x0A; + break; + case KEY_BKSP: + ascii = 0x7F; + break; + case KEY_TAB: + ascii = 0x09; + break; + default: + break; + } + } + else if ((keyval >= KEY_SYMBOL_MIN) && (keyval <= KEY_SYMBOL2_MAX)) + { + if (ctrl) + { + if ((keyval >= KEY_UPPER_MIN) && (keyval <= KEY_UPPER_MAX)) + ascii = keyval - KEY_UPPER_MIN + 1; + else if ((keyval >= KEY_LOWER_MIN) && + (keyval <= KEY_LOWER_MAX)) + ascii = keyval - KEY_LOWER_MIN + 1; + else + ascii = keyval; + } + else + { + ascii = keyval; + } + } + return ascii; + } + + bool handleSpecialKey(int keyval, bool keyDown) + { + if (keyval == KEY_LOS) + { + buttons.update(0, keyDown); + return true; + } + else if (keyval == KEY_ROS) + { + buttons.update(1, keyDown); + return true; + } + return false; + } + + ubyte readAKD() + { + clearKeystrobe(); + return latch | (anyKeyDown ? 0x80 : 0x00); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch( + [0xC000, 0xC001, 0xC002, 0xC003, 0xC004, 0xC005, 0xC006, 0xC007, + 0xC008, 0xC009, 0xC00A, 0xC00B, 0xC00C, 0xC00D, 0xC00E, 0xC00F], + "R", "readLatch")), + mixin(MakeSwitch([0xC010], "R", "readAKD")), + mixin(MakeSwitch( + [0xC010, 0xC011, 0xC012, 0xC013, 0xC014, 0xC015, 0xC016, 0xC017, + 0xC018, 0xC019, 0xC01A, 0xC01B, 0xC01C, 0xC01D, 0xC01E, 0xC01F], + "W", "clearKeystrobe")) + ])); +} + diff --git a/src/device/paddle.d b/src/device/paddle.d new file mode 100644 index 0000000..ff93c3f --- /dev/null +++ b/src/device/paddle.d @@ -0,0 +1,161 @@ +/+ + + device/paddle.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.paddle; + +import device.base; +import timer; +import memory; + +class Paddles +{ + Timer timer; + + int numPaddles; + int[4] onTrigger; + bool[4] stillTiming; + ubyte[4] switchVal; + + this() + { + numPaddles = 4; + } + + void reboot() + { + onTrigger[0..4] = 1380; // XXX only if no real joystick ?? + stillTiming[0..4] = false; + switchVal[0..4] = 0; + } + + // XXX II: C07X | IIe Orig: C07X | IIe Enh: C070-C07E + // (IOU gets C07F) + // IIc: as IIe Enh, plus VBLINT? + + mixin(InitSwitches("", [ + mixin(MakeSwitch( + [0xC070, 0xC071, 0xC072, 0xC073, 0xC074, 0xC075, 0xC076, 0xC077, + 0xC078, 0xC079, 0xC07A, 0xC07B, 0xC07C, 0xC07D, 0xC07E], + "R0W", "trigger")), + mixin(MakeSwitch([0xC064, 0xC06C], "R7", "check_pdl_0")), + mixin(MakeSwitch([0xC065, 0xC06D], "R7", "check_pdl_1")), + mixin(MakeSwitch([0xC066, 0xC06E], "R7", "check_pdl_2")), + mixin(MakeSwitch([0xC067, 0xC06F], "R7", "check_pdl_3")) + ])); + + void update(int pdl, int value) + { + onTrigger[pdl] = value ? value : 1; + } + + ubyte check_pdl_0() + { + return switchVal[0]; + } + + ubyte check_pdl_1() + { + return switchVal[1]; + } + ubyte check_pdl_2() + { + return switchVal[2]; + } + ubyte check_pdl_3() + { + return switchVal[3]; + } + + void trigger() + { + for (int i = 0; i < numPaddles; ++i) + { + if (stillTiming[i]) return; + } + for (int i = 0; i < numPaddles; ++i) + { + if (onTrigger[i] == 0) onTrigger[i] = 1; + switchVal[i] = 0x80; + } + timer.new Counter(onTrigger[0], &pdl_0_expired); + timer.new Counter(onTrigger[1], &pdl_1_expired); + if (numPaddles > 2) + { + timer.new Counter(onTrigger[2], &pdl_2_expired); + timer.new Counter(onTrigger[3], &pdl_3_expired); + } + } + + bool pdl_0_expired() + { + stillTiming[0] = false; + switchVal[0] = 0x00; + return false; + } + + bool pdl_1_expired() + { + stillTiming[1] = false; + switchVal[1] = 0x00; + return false; + } + + bool pdl_2_expired() + { + stillTiming[2] = false; + switchVal[2] = 0x00; + return false; + } + + bool pdl_3_expired() + { + stillTiming[3] = false; + switchVal[3] = 0x00; + return false; + } +} + +// NOTE: IIe Unenhanced uses Paddles_II; IIe enhanced uses Paddles +// (because C07F is handled first by IOU) + +class Paddles_II : Paddles +{ + mixin(InitSwitches("super", [ + mixin(MakeSwitch([0xC07F], "R0W", "trigger")) + ])); +} + +// XXX IIc: 0xC07X resets VBLINT along with triggering timers, +// C07E/C07F as IIe enhanced, maybe more? (and there +// are only two paddles, so no C066/C067/C06E/C06F) + +class Paddles_IIc : Paddles +{ + // XXX add the switches + mixin(EmptyInitSwitches()); + + this() + { + super(); + numPaddles = 2; + } +} diff --git a/src/device/pushbutton.d b/src/device/pushbutton.d new file mode 100644 index 0000000..b2df365 --- /dev/null +++ b/src/device/pushbutton.d @@ -0,0 +1,67 @@ +/+ + + device/pushbutton.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.pushbutton; + +import device.base; +import memory; + +class Pushbuttons +{ + bool buttons[3]; + + void reboot() + { + buttons[0..3] = false; + } + + void update(int btn, bool isDown) + { + buttons[btn] = isDown; + } + + ubyte read(int btn) + { + return (buttons[btn] ? 0x80 : 0); + } + + ubyte readPB0() + { + return read(0); + } + + ubyte readPB1() + { + return read(1); + } + + ubyte readPB2() + { + return read(2); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC061, 0xC069], "R7", "readPB0")), + mixin(MakeSwitch([0xC062, 0xC06A], "R7", "readPB1")), + mixin(MakeSwitch([0xC063, 0xC06B], "R7", "readPB2")) + ])); +} diff --git a/src/device/speaker.d b/src/device/speaker.d new file mode 100644 index 0000000..0920b2c --- /dev/null +++ b/src/device/speaker.d @@ -0,0 +1,138 @@ +/+ + + device/speaker.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module device.speaker; + +import std.c.string; + +import device.base; +import memory; +import timer; + +class Speaker +{ + bool muted; + Timer.Cycle cycle; + + int sampleTicks; + ushort sample; + + uint lastToggleTick; + bool toggled; + + short[] mainBuffer; + short[] extraBuffer; + uint mainIndex, extraIndex; + + this() + { + sample = 0x8000; + mainBuffer.length = 8192; + } + + void setTiming(uint hertz, int sampleFreq, Timer.Cycle deviceCycle) + { + extraBuffer.length = sampleTicks = (hertz / sampleFreq); + cycle = deviceCycle; + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC030], "R0W", "toggleSpeaker")) + ])); + + uint processExtraBuffer(uint elapsed) + { + uint newElapsed = elapsed; + + if (extraIndex != 0) + { + for (; extraIndex < extraBuffer.length; ++extraIndex) + { + if (newElapsed == 0) break; + extraBuffer[extraIndex] = sample; + --newElapsed; + } + if (extraIndex == extraBuffer.length) + { + extraIndex = 0; + long sampleMean = 0; + for (uint i = 0; i < extraBuffer.length; ++i) + sampleMean += cast(long)extraBuffer[i]; + sampleMean /= cast(long)extraBuffer.length; + + if (mainIndex < (mainBuffer.length - 1)) + { + mainBuffer[mainIndex++] = + (muted ? 0 : cast(short)sampleMean); + } + } + } + + return newElapsed; + } + + void updateExtraBuffer(uint extraTicks) + { + if (extraTicks == 0) return; + + for (extraIndex = 0; extraIndex < extraTicks; ++extraIndex) + { + extraBuffer[extraIndex] = + (muted ? 0 : sample); + } + } + + void update() + { + uint elapsedSinceToggle = cycle.currentVal() - lastToggleTick; + lastToggleTick = cycle.currentVal(); + elapsedSinceToggle = processExtraBuffer(elapsedSinceToggle); + + uint samples = elapsedSinceToggle / sampleTicks; + uint extraTicks = elapsedSinceToggle % sampleTicks; + + // update main buffer + for (; mainIndex < (mainBuffer.length - 1); ++mainIndex) + { + if (samples == 0) break; + mainBuffer[mainIndex] = + (muted ? 0 : sample); + --samples; + } + + updateExtraBuffer(extraTicks); + } + + void toggleSpeaker() + { + toggled = true; + update(); + sample = ~sample; + } + + void clearBuffer() + { + mainIndex = 0; + lastToggleTick = 0; + toggled = false; + } +} diff --git a/src/host.d b/src/host.d new file mode 100644 index 0000000..4653889 --- /dev/null +++ b/src/host.d @@ -0,0 +1,160 @@ +/+ + + host.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module host; + +import std.c.time; +import std.c.linux.linux; +import std.stdio; + +import derelict.sdl.sdl; +import derelict.util.exception; + +static this() +{ + delay = new Delay(); + + try + { + DerelictSDL.load(); + writefln("Loaded lib = %s", DerelictSDL.libName); + } + catch (DerelictException e) + { + writefln("%s", e.msg); + return; + } + if (SDL_Init(0) == -1) + { + writefln("%s", std.string.toString(SDL_GetError())); + return; + } + SDL = true; +} + +static ~this() +{ + if (SDL) SDL_Quit(); +} + +bool SDL; + +class Delay +{ + timeval timeNow, timeShould, timeDiff, timeCheck; + uint checkCycles; + static float speedCorrection = 1000000.0 / 1020478.0; + bool delegate() soundCardActive; + bool delegate() soundCardHasEnoughData; + + void sleep() + { + if (soundCardActive()) + sleepAudio(); + else + sleepNoAudio(); + } + + void sleepAudio() + { + while (soundCardHasEnoughData()) + { + usleep(1000); + } + } + + void nap() + { + usleep(1000); + } + + void sleepNoAudio() + { + if (timeShould.tv_usec >= 990_000) + { + ++timeShould.tv_sec; + timeShould.tv_usec -= 1_000_000; + } + timeShould.tv_usec += 10_000; + + gettimeofday(&timeNow, null); + + // Assume that tv_sec = 0; + if (timeCompare(&timeShould, &timeNow, &timeDiff)) + usleep(timeDiff.tv_usec); + } + + void reset() + { + gettimeofday(&timeShould, null); + resetSpeedCheck(); + } + + void resetSpeedCheck() + { + gettimeofday(&timeCheck, null); + checkCycles = 0; + } + + float checkSpeedPercent(uint cycles) + { + checkCycles += cycles; + if (checkCycles > 1020478) // do calculation at least once per "second" + { + gettimeofday(&timeNow, null); + timeCompare(&timeNow, &timeCheck, &timeDiff); + uint elapsed = (timeDiff.tv_sec * 1000000) + timeDiff.tv_usec; + if (elapsed >= 1000000) + { + float percent = cast(float)checkCycles / cast(float)elapsed; + resetSpeedCheck(); + return percent * speedCorrection; + } + } + return -1.0; + } +} + +bool timeCompare(timeval* later, timeval* earlier, timeval* diff) +{ + if (later.tv_usec < earlier.tv_usec) { + int nsec = (earlier.tv_usec - later.tv_usec) / 1000000 + 1; + earlier.tv_usec -= 1000000 * nsec; + earlier.tv_sec += nsec; + } + if (later.tv_usec - earlier.tv_usec > 1000000) { + int nsec = (later.tv_usec - earlier.tv_usec) / 1000000; + earlier.tv_usec += 1000000 * nsec; + earlier.tv_sec -= nsec; + } + + if (later.tv_sec >= earlier.tv_sec) + { + diff.tv_sec = later.tv_sec - earlier.tv_sec; + diff.tv_usec = later.tv_usec - earlier.tv_usec; + return true; + } + else + return false; +} + +Delay delay; diff --git a/src/iomem.d b/src/iomem.d new file mode 100644 index 0000000..818443a --- /dev/null +++ b/src/iomem.d @@ -0,0 +1,310 @@ +/+ + + iomem.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import memory; +import timer; + +import system.peripheral; +import peripheral.base; +import device.base; + +import std.string; + +class IOMem +{ + // NOTE: It is implied that a peripheral slot with + // an I/O STROBE ROM' always responds to I/O STROBE'. + class IOSelectMem : Rom + { + int slotNum; + + this(int slotNum_, ubyte[] rom) + { + super(0xC000 + (slotNum_ * 0x100), 0x0100, rom); + slotNum = slotNum_; + } + + ubyte read(ushort addr) + { + activateStrobeMem(slotNum); + + return data[addr - baseAddress]; + } + + void write(ushort addr, ubyte val) + { + activateStrobeMem(slotNum); + } + } + + // NOTE: It is implied that all peripherals obey the + // $CFFF I/O STROBE' protocol. + class IOStrobeMem : Rom + { + int slotNum; // XXX not just num, but a reference to the + // rom object for that slot (for debug + // display). + + this(int slotNum_, ubyte[] rom) + { + super(0xC800, 0x0800, rom); + slotNum = slotNum_; + } + + ubyte read(ushort addr) + { + if (addr == 0xCFFF) deactivateStrobeMem(); + + return data[addr - 0xC800]; + } + + void write(ushort addr) + { + if (addr == 0xCFFF) deactivateStrobeMem(); + } + } + + int strobeSlotNum; + IOSelectMem[8] selectMem; + IOStrobeMem[8] strobeMem; + AddressDecoder decoder; + + void initialize(AddressDecoder decoder_, SoftSwitchPage switches, + Timer timer, Peripherals peripherals) + { + decoder = decoder_; + Peripheral card; + for (int slot = 0; slot <=7; ++slot) + { + card = peripherals.cards[slot]; + if (slot > 0) + { + if ((card is null) || (card.ioSelectROM is null)) + { + installEmptySelect(slot); + } + else + { + selectMem[slot] = new IOSelectMem(slot, card.ioSelectROM); + selectMem[slot].debugName = + "Slot " ~ std.string.toString(slot) ~ " ROM"; + decoder.install(selectMem[slot]); + if (card.ioStrobeROM !is null) + { + strobeMem[slot] = new IOStrobeMem(slot, + card.ioStrobeROM); + strobeMem[slot].debugName = selectMem[slot].debugName; + } + } + } + if (card !is null) card.plugIn(slot, switches, timer); + } + } + + void reboot() + { + deactivateStrobeMem(); + } + + void installEmptySelect(int slotNum) + { + // NOTE: using these read/write delegates implies that + // a slot without an I/O SELECT' ROM _cannot_ have an + // I/O STROBE' ROM. + decoder.installNull(0xC000 + (slotNum * 0x100), 0x100); + } + + void activateStrobeMem(int slotNum) + { + // Do nothing if the I/O STROBE' memory for + // the given slotNum is already active. + if (strobeSlotNum == slotNum) return; + + if (strobeMem[slotNum] is null) + { + deactivateStrobeMem(); + } + else + { + decoder.install(strobeMem[slotNum]); + strobeSlotNum = slotNum; + } + } + + void deactivateStrobeMem() + { + decoder.installNull(0xC800, 0x0800); + strobeSlotNum = 0; + } + +} + +class IOMem_IIe : IOMem +{ + class IntC3ROM : Rom + { + this(ubyte[] rom) + { + super(0xC300, 0x0100, rom); + } + + ubyte read(ushort addr) + { + activateIntStrobeMem(); + return data[addr - 0xC300]; + } + + void write(ushort addr, ubyte val) + { + activateIntStrobeMem(); + } + } + + bool intC8ROM, slotC3ROM, intCXROM; + IOStrobeMem intStrobeMem; + IntC3ROM intC3ROM; + Rom c100c2ff, c400c7ff; + + ubyte delegate() kbdLatch; + + void reboot() + { + super.reboot(); + resetIntCXROM(); + resetSlotC3ROM(); + } + + void reset() + { + deactivateStrobeMem(); + resetIntCXROM(); + resetSlotC3ROM(); + } + + void setSlotC3ROM() + { + if (slotC3ROM) return; + slotC3ROM = true; + + // $C3XX cannot be configured for slot response if + // INTCXROM is set. + if (intCXROM) return; + + if (selectMem[3] !is null) + { + decoder.install(selectMem[3]); + } + else + { + installEmptySelect(3); + } + } + + void resetSlotC3ROM() + { + slotC3ROM = false; + decoder.install(intC3ROM); + } + + void setIntCXROM() + { + if (intCXROM) return; + intCXROM = true; + decoder.install(c100c2ff); + decoder.install(intC3ROM); + decoder.install(c400c7ff); + decoder.install(intStrobeMem); + } + + void resetIntCXROM() + { + intCXROM = false; + for (int s = 1; s <= 7; ++s) + { + if (selectMem[s] !is null) + { + if ((s != 3) || (slotC3ROM)) decoder.install(selectMem[s]); + } + else + { + if ((s != 3) || (slotC3ROM)) installEmptySelect(s); + } + } + if (!intC8ROM) deactivateStrobeMem(); + } + + void activateIntStrobeMem() + { + if (intC8ROM) return; + + decoder.install(intStrobeMem); + strobeSlotNum = -1; // XXX hack (-1 represents internal?) + intC8ROM = true; + } + + void activateStrobeMem(int slotNum) + { + if (intC8ROM) return; + + super.activateStrobeMem(slotNum); + } + + void deactivateStrobeMem() + { + intC8ROM = false; + super.deactivateStrobeMem(); + } + + ubyte readIntCXROM() + { + return kbdLatch() | (intCXROM ? 0x80 : 0x00); + } + + ubyte readSlotC3ROM() + { + return kbdLatch() | (slotC3ROM ? 0x80 : 0x00); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC006], "W", "resetIntCXROM")), + mixin(MakeSwitch([0xC007], "W", "setIntCXROM")), + mixin(MakeSwitch([0xC00A], "W", "resetSlotC3ROM")), + mixin(MakeSwitch([0xC00B], "W", "setSlotC3ROM")), + mixin(MakeSwitch([0xC015], "R", "readIntCXROM")), + mixin(MakeSwitch([0xC017], "R", "readSlotC3ROM")) + ])); + + void setRom(ubyte[] romDump) + { + int c100 = romDump.length - 16128; + c100c2ff = new Rom(0xC100, 0x0200, romDump[c100 .. (c100 + 0x0200)]); + intC3ROM = new IntC3ROM(romDump[(c100 + 0x0200) .. (c100 + 0x0300)]); + c400c7ff = new Rom(0xC400, 0x0400, + romDump[(c100 + 0x0300) .. (c100 + 0x0700)]); + // XXX not slot, but ref to intc3rom + intStrobeMem = new IOStrobeMem(3, + romDump[(c100 + 0x0700) .. (c100 + 0x0F00)]); + c100c2ff.debugName = intC3ROM.debugName = c400c7ff.debugName = + intStrobeMem.debugName = "Internal ROM"; + } +} + diff --git a/src/ioummu.d b/src/ioummu.d new file mode 100644 index 0000000..2adfa05 --- /dev/null +++ b/src/ioummu.d @@ -0,0 +1,407 @@ +/+ + + ioummu.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import memory; +import device.base; +import system.io; // XXX XXX too many dependencies? +import video.base; +import peripheral.langcard; +import iomem; + +struct MemMap +{ + Memory X0000_01FF; + Memory X0200_03FF; + Memory X0400_07FF; + Memory X0800_1FFF; + Memory X2000_3FFF; + Memory X4000_BFFF; + + void reboot() + { + X0000_01FF.reboot(); + X0200_03FF.reboot(); + X0400_07FF.reboot(); + X0800_1FFF.reboot(); + X2000_3FFF.reboot(); + X4000_BFFF.reboot(); + } +} + +class IOU +{ + IO io_; + SignalBase signal; + bool dHGR; + + this(IO ioDevices, SignalBase sig) + { + io_ = ioDevices; + signal = sig; + } + + void resetAn0() + { + io_.annun.ann_0_Off(); + } + + void resetAn1() + { + io_.annun.ann_1_Off(); + } + + void resetAn2() + { + io_.annun.ann_2_Off(); + } + + void resetAn3() + { + io_.annun.ann_3_Off(); + dHGR = true; + signal.dHGRChange(true); + } + + void setAn0() + { + io_.annun.ann_0_On(); + } + + void setAn1() + { + io_.annun.ann_1_On(); + } + + void setAn2() + { + io_.annun.ann_2_On(); + } + + void setAn3() + { + io_.annun.ann_3_On(); + dHGR = false; + signal.dHGRChange(false); + } + + ubyte readDHGR() + { + io_.paddles.trigger(); + return (dHGR ? 0x80 : 0x00); + } + + void triggerTimers() + { + io_.paddles.trigger(); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC058], "R0W", "resetAn0")), + mixin(MakeSwitch([0xC059], "R0W", "setAn0")), + mixin(MakeSwitch([0xC05A], "R0W", "resetAn1")), + mixin(MakeSwitch([0xC05B], "R0W", "setAn1")), + mixin(MakeSwitch([0xC05C], "R0W", "resetAn2")), + mixin(MakeSwitch([0xC05D], "R0W", "setAn2")), + mixin(MakeSwitch([0xC05E], "R0W", "resetAn3")), + mixin(MakeSwitch([0xC05F], "R0W", "setAn3")), + // XXX next two only for enhanced: + mixin(MakeSwitch([0xC07F], "R7", "readDHGR")), + mixin(MakeSwitch([0xC07F], "W", "triggerTimers")) + ])); +} + +class MMU +{ + LanguageCard_IIe himemManager; + AddressDecoder decoder; + AuxiliaryCard auxCard; + IOMem_IIe ioMem; + ScannerBase scanner; + ubyte delegate() kbdLatch; + + MemMap[2] mem; + + this() + { + himemManager = new LanguageCard_IIe(); + } + + void reboot() + { + himemManager.reboot(); + mem[0].reboot(); + } + + void reset() + { + himemManager.reset(); + ioMem.reset(); + } + + void init(SoftSwitchPage switches, AuxiliaryCard auxCard_, + AddressDecoder addrDecode, DataMem mainRam, DataMem mainRom) + { + auxCard = auxCard_; + decoder = addrDecode; + + himemManager.auxslot = auxCard.himem; + himemManager.initSwitches(switches); + himemManager.init(decoder, &mainRom.read, + &mainRom.write); + + initMem(mainRam); + + initSwitches(switches); + ioMem.initSwitches(switches); + } + + void initMem(DataMem mainRam) + { + mem[0].X0000_01FF = new SliceMem(0x0000, 0x0200, mainRam); + mem[0].X0200_03FF = new SliceMem(0x0200, 0x0200, mainRam); + mem[0].X0400_07FF = new SliceMem(0x0400, 0x0400, mainRam); + mem[0].X0800_1FFF = new SliceMem(0x0800, 0x1800, mainRam); + mem[0].X2000_3FFF = new SliceMem(0x2000, 0x2000, mainRam); + mem[0].X4000_BFFF = new SliceMem(0x4000, 0x8000, mainRam); + mem[1] = auxCard.mem; + } + + void initIO(ScannerBase scn, ubyte delegate() peekLatch) + { + kbdLatch = peekLatch; + scanner = scn; + ioMem.kbdLatch = kbdLatch; + himemManager.kbdLatch = kbdLatch; + } + + bool switchPage2, switchHires, switch80Store; + bool ramRd, ramWrt, altZP; + + void mapVidMem(bool rd, bool wrt) + { + decoder.installRead(mem[cast(int)rd].X0400_07FF); + decoder.installWrite(mem[cast(int)wrt].X0400_07FF); + if (switchHires) + { + mapHiresVidMem(rd, wrt); + } + } + + void mapHiresVidMem(bool rd, bool wrt) + { + decoder.installRead(mem[cast(int)rd].X2000_3FFF); + decoder.installWrite(mem[cast(int)wrt].X2000_3FFF); + } + + void resetRamRd() + { + ramRd = false; + changeRamSwitch(ramRd, true, false); + } + + void setRamRd() + { + ramRd = true; + changeRamSwitch(ramRd, true, false); + } + + void resetRamWrt() + { + ramWrt = false; + changeRamSwitch(ramWrt, false, true); + } + + void setRamWrt() + { + ramWrt = true; + changeRamSwitch(ramWrt, false, true); + } + + void changeRamSwitch(bool sw, bool rd, bool wrt) + { + int bank = cast(int)sw; + decoder.install(mem[bank].X0200_03FF, rd, wrt); + if (!switch80Store) + decoder.install(mem[bank].X0400_07FF, rd, wrt); + decoder.install(mem[bank].X0800_1FFF, rd, wrt); + if ((!switch80Store) || (!switchHires)) + decoder.install(mem[bank].X2000_3FFF, rd, wrt); + decoder.install(mem[bank].X4000_BFFF, rd, wrt); + } + + void resetAltZP() + { + altZP = false; + himemManager.enableAuxSlot(false); + decoder.install(mem[0].X0000_01FF); + } + + void setAltZP() + { + altZP = true; + himemManager.enableAuxSlot(true); + decoder.install(mem[1].X0000_01FF); + } + + void reset80Store() + { + if (switchPage2) + scanner.page2SwitchOn(); + else + scanner.page2SwitchOff(); + switch80Store = false; + mapVidMem(ramRd, ramWrt); + } + + void set80Store() + { + scanner.page2SwitchOff(); + switch80Store = true; + mapVidMem(switchPage2, switchPage2); + } + + void resetPage2() + { + if (!switch80Store) scanner.page2SwitchOff(); + switchPage2 = false; + if (switch80Store) mapVidMem(switchPage2, switchPage2); + } + + void setPage2() + { + if (!switch80Store) scanner.page2SwitchOn(); + switchPage2 = true; + if (switch80Store) mapVidMem(switchPage2, switchPage2); + } + + void resetHires() + { + scanner.hiresSwitchOff(); + switchHires = false; + if (switch80Store) mapHiresVidMem(ramRd, ramWrt); + } + + void setHires() + { + scanner.hiresSwitchOn(); + switchHires = true; + if (switch80Store) mapHiresVidMem(switchPage2, switchPage2); + } + + ubyte readKbdSwitch(bool sw) + { + return kbdLatch() | (sw ? 0x80 : 0x00); + } + + ubyte readRamRd() + { + return readKbdSwitch(ramRd); + } + + ubyte readRamWrt() + { + return readKbdSwitch(ramWrt); + } + + ubyte readAltZP() + { + return readKbdSwitch(altZP); + } + + ubyte read80Store() + { + return readKbdSwitch(switch80Store); + } + + ubyte readPage2() + { + return readKbdSwitch(switchPage2); + } + + ubyte readHires() + { + return readKbdSwitch(switchHires); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC000], "W", "reset80Store")), + mixin(MakeSwitch([0xC001], "W", "set80Store")), + mixin(MakeSwitch([0xC002], "W", "resetRamRd")), + mixin(MakeSwitch([0xC003], "W", "setRamRd")), + mixin(MakeSwitch([0xC004], "W", "resetRamWrt")), + mixin(MakeSwitch([0xC005], "W", "setRamWrt")), + mixin(MakeSwitch([0xC008], "W", "resetAltZP")), + mixin(MakeSwitch([0xC009], "W", "setAltZP")), + mixin(MakeSwitch([0xC013], "R", "readRamRd")), + mixin(MakeSwitch([0xC014], "R", "readRamWrt")), + mixin(MakeSwitch([0xC016], "R", "readAltZP")), + mixin(MakeSwitch([0xC018], "R", "read80Store")), + mixin(MakeSwitch([0xC01C], "R", "readPage2")), + mixin(MakeSwitch([0xC01D], "R", "readHires")), + mixin(MakeSwitch([0xC054], "R0W", "resetPage2")), + mixin(MakeSwitch([0xC055], "R0W", "setPage2")), + mixin(MakeSwitch([0xC056], "R0W", "resetHires")), + mixin(MakeSwitch([0xC057], "R0W", "setHires")) + ])); +} + +class AuxiliaryCard +{ + HighRam_IIe himem; + MemMap mem; + VideoPages vidPages; + abstract void reboot(); + abstract void reset(); +} + +class Extended80ColumnCard : AuxiliaryCard +{ + PrimaryMem auxRam; + + this() + { + auxRam = new PrimaryMem(0x0000, 0xC000); + auxRam.debugName = "Aux RAM"; + himem = new HighRam_IIe(false, 1); + himem.setDebugName("Aux high RAM"); + vidPages.lores1 = new SliceMem(0x0400, 0x0400, auxRam); + vidPages.lores2 = new SliceMem(0x0800, 0x0400, auxRam); + vidPages.hires1 = new SliceMem(0x2000, 0x2000, auxRam); + vidPages.hires2 = new SliceMem(0x4000, 0x2000, auxRam); + mem.X0000_01FF = new SliceMem(0x0000, 0x0200, auxRam); + mem.X0200_03FF = new SliceMem(0x0200, 0x0200, auxRam); + mem.X0400_07FF = new SliceMem(0x0400, 0x0400, auxRam); + mem.X0800_1FFF = new SliceMem(0x0800, 0x1800, auxRam); + mem.X2000_3FFF = new SliceMem(0x2000, 0x2000, auxRam); + mem.X4000_BFFF = new SliceMem(0x4000, 0x8000, auxRam); + } + + void reboot() + { + auxRam.reboot(); + vidPages.reboot(); + mem.reboot(); + } + + void reset() {} +} + diff --git a/src/memory.d b/src/memory.d new file mode 100644 index 0000000..cc88515 --- /dev/null +++ b/src/memory.d @@ -0,0 +1,414 @@ +/+ + + memory.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import std.string; + +class Memory +{ + final ushort baseAddress; + final uint blockSize; + string debugName; + + this(ushort baseAddr, uint size) + { + assert(baseAddr + size <= 0x10000, + "Memory block larger than 64K"); + assert((baseAddr % 0x0100) == 0, + "Memory block does not start on page boundary"); + assert((size % 0x0100) == 0, + "Memory block does not end on page boundary"); + baseAddress = baseAddr; + blockSize = size; + } + + abstract ubyte read(ushort addr); + abstract void write(ushort addr, ubyte val); + void reboot() {} +} + +class ZeroMem : Memory +{ + this(ushort baseAddr, uint size) + { + super(baseAddr, size); + } + + ubyte read(ushort addr) { return 0; } + void write(ushort addr, ubyte val) {} +} + +class DataMem : Memory +{ + ubyte* data; + ubyte data_[]; + + this(ushort baseAddr, uint size) + { + super(baseAddr, size); + } + + ubyte read(ushort addr) + { + return data[addr - baseAddress]; + } + + void write(ushort addr, ubyte val) + { + data[addr - baseAddress] = val; + } +} + +class PrimaryMem : DataMem +{ + this(ushort baseAddr, uint size) + { + super(baseAddr, size); + } + + void reboot() + { + data_ = new ubyte[blockSize]; + data = data_.ptr; + } +} + +class Rom : DataMem +{ + this(ushort baseAddr, uint size, ubyte[] rom) + { + super(baseAddr, size); + data_ = rom; + data = data_.ptr; + } + + void write(ushort addr, ubyte val) {} +} + +class SliceMem : DataMem +{ + DataMem otherMem; + + this(ushort baseAddr, uint size, DataMem other) + { + super(baseAddr, size); + otherMem = other; + debugName = otherMem.debugName; + } + + void reboot() + { + int otherStart = baseAddress - otherMem.baseAddress; + int otherEnd = otherStart + blockSize; + assert((otherStart >= 0) && (otherEnd <= otherMem.blockSize), + "Memory slice out of range"); + data_ = otherMem.data_[otherStart..otherEnd]; + data = data_.ptr; + } +} + +class BankMem : DataMem +{ + ubyte[][] banks; + string[] debugNames; + + this(ushort baseAddr, uint size, uint numBanks) + { + super(baseAddr, size); + banks.length = numBanks; + debugNames.length = numBanks; + } + + void setDebugNames(string name) + { + if (debugNames.length > 1) + { + for (int n = 0; n < debugNames.length; ++n) + { + debugNames[n] = name ~ " bank " ~ std.string.toString(n); + } + } + else + { + debugNames[0] = name; + } + } + + void reboot() + { + for (int b = 0; b < banks.length; ++b) + { + banks[b] = new ubyte[blockSize]; + } + setBank(0); + } + + void setBank(int bankNum) + { + data_ = banks[bankNum]; + data = data_.ptr; + debugName = debugNames[bankNum]; + } +} + +class SubBankMem : DataMem +{ + ubyte[][][] banks; + string[][] debugNames; + int primaryBank, subBank; + int numBanks, numSubBanks; + + this(ushort baseAddr, uint size, uint numBanks_, uint numSubBanks_) + { + super(baseAddr, size); + banks.length = numBanks = numBanks_; + debugNames.length = numBanks; + numSubBanks = numSubBanks_; + for (int b = 0; b < numBanks; ++b) + { + banks[b].length = numSubBanks_; + debugNames[b].length = numSubBanks_; + } + } + + void setDebugNames(string[] names) + { + for (int b = 0; b < numBanks; ++b) + { + for (int n = 0; n < numSubBanks; ++n) + { + debugNames[b][n] = names[b] ~ " bank " ~ + std.string.toString(n); + } + } + } + + void reboot() + { + for (int b = 0; b < banks.length; ++b) + { + for (int s = 0; s < banks[b].length; ++s) + { + banks[b][s] = new ubyte[blockSize]; + } + } + primaryBank = subBank = 0; + setSubBank(0); + setPrimaryBank(0); + } + + void setPrimaryBank(uint bank) + { + primaryBank = bank; + data_ = banks[bank][subBank]; + data = data_.ptr; + debugName = debugNames[bank][subBank]; + } + + void setSubBank(uint bank) + { + subBank = bank; + data_ = banks[primaryBank][bank]; + data = data_.ptr; + debugName = debugNames[primaryBank][bank]; + } +} + +alias ubyte delegate(ushort) ReadFunc; +alias void delegate(ushort, ubyte) WriteFunc; + +class AddressDecoder +{ + ReadFunc readPages[256]; + WriteFunc writePages[256]; + Memory readResponders[256]; + Memory writeResponders[256]; + + void nullWrite(ushort addr, ubyte val) {} + + public: + + ubyte delegate(ushort) nullRead; + + void installSwitches(SoftSwitchPage switches) + { + readPages[0xC0] = &switches.read; + writePages[0xC0] = &switches.write; + } + + ubyte read(ushort addr) + { + return readPages[addr >> 8](addr); + } + + void write(ushort addr, ubyte val) + { + writePages[addr >> 8](addr, val); + } + + // XXX address read only/write only code + void install(Memory block, bool forRead = true, bool forWrite = true) + { + uint base = block.baseAddress >> 8; + uint size = block.blockSize >> 8; + for (uint pg = base; pg < base + size; ++pg) + { + if (pg == 0xC0) continue; + + if (forRead) + { + readPages[pg] = &block.read; + readResponders[pg] = block; + } + if (forWrite) + { + writePages[pg] = &block.write; + writeResponders[pg] = block; + } + } + } + + void installNull(uint baseAddress, uint blockSize, bool forRead = true, + bool forWrite = true) + { + uint base = baseAddress >> 8; + uint size = blockSize >> 8; + for (uint pg = base; pg < base + size; ++pg) + { + if (pg == 0xC0) continue; + if (forRead) + { + readPages[pg] = nullRead; + readResponders[pg] = null; + } + if (forWrite) + { + writePages[pg] = &nullWrite; + writeResponders[pg] = null; + } + } + } + + void installRead(Memory block) + { + install(block, true, false); + } + + void installWrite(Memory block) + { + install(block, false, true); + } + + string memoryReadName(ushort addr) + { + int page = addr >> 8; + if (readResponders[page] is null) return null; + return readResponders[page].debugName; + } + + Memory readResponse(int page) + { + return readResponders[page]; + } + + Memory writeResponse(int page) + { + return writeResponders[page]; + } +} + +class SoftSwitchPage : Memory +{ + private: + + ReadFunc[256] readSwitches; + ubyte[256] bitsReturned; + WriteFunc[256] writeSwitches; + + ubyte nullRead(ushort addr) { return 0; } + void nullWrite(ushort addr, ubyte val) {} + + public: + + ReadFunc floatingBus; + + this() + { + super(0xC000, 0x0100); + for (int addr = 0xC000; addr < 0xC100; ++addr) + { + writeSwitches[addr & 0xFF] = &nullWrite; + } + } + + void setFloatingBus(ReadFunc floatingBus_) + { + floatingBus = floatingBus_; + for (int addr = 0xC000; addr < 0xC100; ++addr) + { + if (readSwitches[addr & 0xFF] is null) + readSwitches[addr & 0xFF] = floatingBus; + } + } + + void setReadSwitch(ushort addr, ReadFunc read_, ubyte bitsReturned_) + { + readSwitches[addr - 0xC000] = read_; + bitsReturned[addr - 0xC000] = bitsReturned_; + } + + void setR0Switch(ushort addr, ReadFunc read_) + { + setReadSwitch(addr, read_, 0); + } + + void setR7Switch(ushort addr, ReadFunc read_) + { + setReadSwitch(addr, read_, 0x80); + } + + void setRSwitch(ushort addr, ReadFunc read_) + { + setReadSwitch(addr, read_, 0xFF); + } + + void setWSwitch(ushort addr, WriteFunc write_) + { + writeSwitches[addr - 0xC000] = write_; + } + + final ubyte read(ushort addr) + { + ubyte ret = readSwitches[addr - 0xC000](addr); + ubyte mask = bitsReturned[addr - 0xC000]; + if (mask < 0xFF) + { + ret = (ret & mask) | (floatingBus(addr) & (mask ^ 0xFF)); + } + return ret; + } + + final void write(ushort addr, ubyte val) + { + writeSwitches[addr - 0xC000](addr, val); + } +} + diff --git a/src/peripheral/base.d b/src/peripheral/base.d new file mode 100644 index 0000000..cece146 --- /dev/null +++ b/src/peripheral/base.d @@ -0,0 +1,59 @@ +/+ + + peripheral/base.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module peripheral.base; + +import memory; +import device.base; +import timer; + +import gtk.Box; +import gtk.VBox; + +VBox peripheralStatus; + +class Peripheral +{ + Box statusBox; + + ubyte[] ioSelectROM; + ubyte[] ioStrobeROM; + + void plugIn(int slotNum, SoftSwitchPage switches, Timer timer) + { + initSwitches(switches, slotNum); + initTimer(timer); + + if (statusBox !is null) + { + peripheralStatus.packStart(statusBox, false, false, 0); + } + } + + void reset() {} + void reboot() {} + void updateStatus() {} + void initTimer(Timer timer) {} + + mixin(EmptyInitSwitches()); +} + diff --git a/src/peripheral/diskii.d b/src/peripheral/diskii.d new file mode 100644 index 0000000..15a9165 --- /dev/null +++ b/src/peripheral/diskii.d @@ -0,0 +1,1144 @@ +/+ + + peripheral/diskii.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module peripheral.diskii; + +import peripheral.base; +import device.base; +import memory; +import timer; + +import ui.mainwindow; +import ui.sound; + +import std.stream; + +ubyte[256] controllerRom = [ + 0xa2, 0x20, 0xa0, 0x00, 0xa2, 0x03, 0x86, 0x3c, + 0x8a, 0x0a, 0x24, 0x3c, 0xf0, 0x10, 0x05, 0x3c, + 0x49, 0xff, 0x29, 0x7e, 0xb0, 0x08, 0x4a, 0xd0, + 0xfb, 0x98, 0x9d, 0x56, 0x03, 0xc8, 0xe8, 0x10, + 0xe5, 0x20, 0x58, 0xff, 0xba, 0xbd, 0x00, 0x01, + 0x0a, 0x0a, 0x0a, 0x0a, 0x85, 0x2b, 0xaa, 0xbd, + 0x8e, 0xc0, 0xbd, 0x8c, 0xc0, 0xbd, 0x8a, 0xc0, + 0xbd, 0x89, 0xc0, 0xa0, 0x50, 0xbd, 0x80, 0xc0, + 0x98, 0x29, 0x03, 0x0a, 0x05, 0x2b, 0xaa, 0xbd, + 0x81, 0xc0, 0xa9, 0x56, 0x20, 0xa8, 0xfc, 0x88, + 0x10, 0xeb, 0x85, 0x26, 0x85, 0x3d, 0x85, 0x41, + 0xa9, 0x08, 0x85, 0x27, 0x18, 0x08, 0xbd, 0x8c, + 0xc0, 0x10, 0xfb, 0x49, 0xd5, 0xd0, 0xf7, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0xaa, 0xd0, 0xf3, + 0xea, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0xc9, 0x96, + 0xf0, 0x09, 0x28, 0x90, 0xdf, 0x49, 0xad, 0xf0, + 0x25, 0xd0, 0xd9, 0xa0, 0x03, 0x85, 0x40, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0x2a, 0x85, 0x3c, 0xbd, + 0x8c, 0xc0, 0x10, 0xfb, 0x25, 0x3c, 0x88, 0xd0, + 0xec, 0x28, 0xc5, 0x3d, 0xd0, 0xbe, 0xa5, 0x40, + 0xc5, 0x41, 0xd0, 0xb8, 0xb0, 0xb7, 0xa0, 0x56, + 0x84, 0x3c, 0xbc, 0x8c, 0xc0, 0x10, 0xfb, 0x59, + 0xd6, 0x02, 0xa4, 0x3c, 0x88, 0x99, 0x00, 0x03, + 0xd0, 0xee, 0x84, 0x3c, 0xbc, 0x8c, 0xc0, 0x10, + 0xfb, 0x59, 0xd6, 0x02, 0xa4, 0x3c, 0x91, 0x26, + 0xc8, 0xd0, 0xef, 0xbc, 0x8c, 0xc0, 0x10, 0xfb, + 0x59, 0xd6, 0x02, 0xd0, 0x87, 0xa0, 0x00, 0xa2, + 0x56, 0xca, 0x30, 0xfb, 0xb1, 0x26, 0x5e, 0x00, + 0x03, 0x2a, 0x5e, 0x00, 0x03, 0x2a, 0x91, 0x26, + 0xc8, 0xd0, 0xee, 0xe6, 0x27, 0xe6, 0x3d, 0xa5, + 0x3d, 0xcd, 0x00, 0x08, 0xa6, 0x2b, 0x90, 0xdb, + 0x4c, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 +]; + +class StopTimer +{ + Timer timer; + Timer.Counter stopCounter; + void delegate() notifyExpired; + + void startCountdown() + { + if (stopCounter is null) + stopCounter = timer.new Counter(1_020_484, &expire); + } + + void stopCountdown() + { + if (stopCounter !is null) + { + stopCounter.discard(); + stopCounter = null; + } + } + + bool expire() + { + notifyExpired(); + stopCounter = null; + return false; + } +} + +class Controller : Peripheral +{ + import gtk.VBox; + + Drive[2] drives; + + int activeDrive; + bool writeMode; + bool loadRegister; + bool delegate() checkFinalCycle; + ubyte dataLatch; + bool isOn; + StopTimer drivesOffDelay; + + this() + { + ioSelectROM = controllerRom; + statusBox = new VBox(false, 3); + drives[0] = new Drive(1); + statusBox.packStart(drives[0].status.display, false, false, 0); + drives[1] = new Drive(2); + statusBox.packStart(drives[1].status.display, false, false, 0); + } + + void reset() + { + drives[0].deactivate(); + drives[1].deactivate(); + activeDrive = 0; + } + + void initTimer(Timer timer) + { + drivesOffDelay = new StopTimer(); + drivesOffDelay.timer = timer; + drivesOffDelay.notifyExpired = &doDrivesOff; + } + + void doDrivesOff() + { + drives[activeDrive].deactivate(); + isOn = false; + } + + void changeActiveDrive(int newActive) + { + drives[activeDrive].deactivate(); + if (isOn) drives[newActive].activate(); + activeDrive = newActive; + } + + ubyte checkWriteProtect() + { + if (drives[activeDrive].writeProtected || + drives[activeDrive].magnets[1]) + return 0x80; + else + return 0x00; + } + + ubyte phaseOff(ushort addr) + { + int phase = (addr >> 1) & 3; + drives[activeDrive].setPhase(phase, false); + return 0xFF; + } + + void phaseOn(ushort addr) + { + int phase = (addr >> 1) & 3; + drives[activeDrive].setPhase(phase, true); + } + + ubyte drive_0_select() + { + if (activeDrive != 0) changeActiveDrive(0); + return 0xFF; + } + + void drive_1_select() + { + if (activeDrive != 1) changeActiveDrive(1); + } + + ubyte drivesOff() + { + drivesOffDelay.startCountdown(); + return 0xFF; + } + + void drivesOn() + { + isOn = true; + drivesOffDelay.stopCountdown(); + drives[activeDrive].activate(); + } + + void readQ6H() + { + loadRegister = true; + } + + ubyte Q6L() + { + loadRegister = false; + if (isOn && checkFinalCycle()) + { + if (writeMode) + { + drives[activeDrive].write(dataLatch); + return dataLatch; + } + else + { + return drives[activeDrive].read(); + } + } + return 0x00; + } + + void writeQ6H(ubyte val) + { + loadRegister = true; + dataLatch = val; + } + + ubyte Q7L() + { + writeMode = false; + if (loadRegister) return checkWriteProtect(); + else return 0xFF; + } + + void readQ7H() + { + writeMode = true; + } + + void writeQ7H(ubyte val) + { + writeMode = true; + dataLatch = val; + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC080, 0xC082, 0xC084, 0xC086], + "RW", "phaseOff(addr)")), + mixin(MakeSwitch([0xC081, 0xC083, 0xC085, 0xC087], + "R0W", "phaseOn(addr)")), + mixin(MakeSwitch([0xC088], "RW", "drivesOff")), + mixin(MakeSwitch([0xC089], "R0W", "drivesOn")), + mixin(MakeSwitch([0xC08A], "RW", "drive_0_select")), + mixin(MakeSwitch([0xC08B], "R0W", "drive_1_select")), + mixin(MakeSwitch([0xC08C], "RW", "Q6L")), + mixin(MakeSwitch([0xC08D], "R0", "readQ6H")), + mixin(MakeSwitch([0xC08D], "W", "writeQ6H(val)")), + mixin(MakeSwitch([0xC08E], "RW", "Q7L")), + mixin(MakeSwitch([0xC08F], "R0", "readQ7H")), + mixin(MakeSwitch([0xC08F], "W", "writeQ7H(val)")) + ])); +} + +class DriveStatus +{ + import gtk.HBox; + import gtk.MenuBar; + import gtk.MenuItem; + import gtk.ProgressBar; + import gtk.CheckButton; + import gtk.ToggleButton; + import gtk.Label; + import gtkc.pangotypes; + + import std.string; + + Drive drive; + HBox display; + ProgressBar activity; + string statusClean, statusDirty; + CheckButton wpButton; + Label imgName; + MenuItem openItem; + MenuItem saveItem; + MenuItem ejectItem; + + this(Drive drive_, int driveNum) + { + drive = drive_; + + statusClean = "Drive " ~ std.string.toString(driveNum); + statusDirty = "( " ~ statusClean ~ " )"; + activity = new ProgressBar(); + activity.setText(statusClean); + + auto menuBar = new MenuBar(); + auto menu = menuBar.append("Image"); + openItem = new MenuItem(&onOpenImage, "Open", "img.open", false); + saveItem = new MenuItem(&onSaveImage, "Save", "img.save", false); + ejectItem = new MenuItem(&onEjectImage, "Eject", "img.eject", false); + menu.append(openItem); + menu.append(saveItem); + menu.append(ejectItem); + saveItem.setSensitive(false); + ejectItem.setSensitive(false); + + wpButton = new CheckButton("WP", false); + wpButton.addOnToggled(&onWPToggle); + wpButton.setSensitive(false); + + imgName = new Label("No disk"); + imgName.setMaxWidthChars(35); + imgName.setEllipsize(PangoEllipsizeMode.START); + + display = new HBox(false, 3); + display.packStart(activity, false, false, 0); + display.packStart(wpButton, false, false, 0); + display.packStart(imgName, true, true, 0); + display.packStart(menuBar, false, false, 0); + } + + void setActive(bool isActive) + { + soundCard.speedyMode = isActive; + activity.setFraction(isActive ? 1.0 : 0.0); + } + + void setDirty(bool isDirty) + { + if (isDirty) + activity.setText(statusDirty); + else + activity.setText(statusClean); + saveItem.setSensitive(isDirty); + } + + void onWPToggle(ToggleButton b) + { + drive.writeProtected = (b.getActive() != 0); + } + + void onOpenImage(MenuItem m) + { + TwoappleFile file = TwoappleFilePicker.open("image file", + &ExternalImage.isValidImage); + if (file is null) return; + if (!drive.loadImage(file)) return; + + if (!file.canWrite()) + { + wpButton.setActive(true); + wpButton.setSensitive(false); + } + else + wpButton.setSensitive(true); + + setDirty(false); + ejectItem.setSensitive(true); + imgName.setText(file.fileName); + } + + void onEjectImage(MenuItem m) + { + if (!drive.ejectImage()) return; + setDirty(false); + wpButton.setActive(false); + wpButton.setSensitive(true); + ejectItem.setSensitive(false); + imgName.setText("No disk"); + } + + void onSaveImage(MenuItem m) + { + if (!drive.saveImage()) return; + setDirty(false); + } +} + +class Drive +{ + InternalImage imgData; + ExternalImage imgFile; + DriveStatus status; + bool writeProtected; + int headPos, maxHeadPos, track; + bool[4] magnets; + + this(int driveNum) + { + maxHeadPos = (InternalImage.NUM_TRACKS - 1) * 4; + imgData = new NonImage(); + status = new DriveStatus(this, driveNum); + } + + void setPhase(int phase, bool newState) + { + //if (magnets[phase] == newState) return; + magnets[phase] = newState; + + // Find the number of active magnets and their + // distances from any cog. + int totalOn = 0, delta; + for (int p = 0; p < 4; ++p) + { + if (magnets[p]) + { + ++totalOn; + delta = (p * 2) - (headPos % 8); + } + } + + // Do not move the head if more than one magnet + // is on (which precludes quarter-tracking). + if (totalOn != 1) return; + + // Do not move the head if the active magnet is + // equidistant from the two nearest cogs, or if + // it is already in line with a cog. + if ((delta == -4) || (delta == 4) || (delta == 0)) return; + + // Pull the nearest cog to the magnet. + if (delta > 4) delta -= 8; + else if (delta < -4) delta += 8; + headPos += delta; + + if (headPos < 0) headPos = 0; // make a noise? + //else if (headPos > 136) headPos = 136; + else if (headPos > maxHeadPos) headPos = maxHeadPos; + track = (headPos & -4) / 4; + } + + void activate() + { + status.setActive(true); + } + + void deactivate() + { + status.setActive(false); + magnets[0..4] = false; + } + + ubyte read() + { + return imgData.peek(track); + } + + void write(ubyte val) + { + if (writeProtected) return; + bool wasDirty = imgData.isDirty; + imgData.poke(track, val); + if (imgData.isDirty && !wasDirty) status.setDirty(true); + } + + bool ejectImage() + { + if (imgData.isDirty) + { + int response = TwoappleDialog.run("Warning", + "Save image currently in drive?", + ["Save", "Don't save", "Cancel"]); + if (response == 2) return false; + if (response == 0) + { + if (!saveImage(true)) return false; + } + } + + imgData = new NonImage(); + imgFile = null; + + return true; + } + + bool loadImage(TwoappleFile file) + { + if (imgData.isDirty) + { + int response = TwoappleDialog.run("Warning", + "Save image currently in drive?", + ["Save", "Don't save", "Cancel"]); + if (response == 2) return false; + if (response == 0) + { + if (!saveImage(true)) return false; + } + } + + imgFile = ExternalImage.loadImage(file); + assert(imgFile !is null); + imgData = imgFile.imgData; + + return true; + } + + bool saveImage(bool indirect = false) + { + bool retry, success; + + void chooseAction(string msg, void delegate() firstAction = null, + string firstButton = null) + { + bool chooseAgain; + void delegate()[] actions; + string[] buttons; + + void delegate() saveAsNib = + { + TwoappleFile file = TwoappleFilePicker.saveAs("Image file", + imgFile.imgFile.folder(), + imgFile.imgFile.baseName() ~ ".nib"); + if (file is null) chooseAgain = true; + else + { + imgFile = new NIBImage(file, imgData); + retry = true; + } + }; + void delegate() noSave = { success = true; }; + void delegate() cancel = {}; + + actions.length = buttons.length = 2 + + (indirect ? 1 : 0) + + ((firstAction !is null) ? 1 : 0); + actions[length - 1] = cancel; + buttons[length - 1] = "Cancel"; + if (indirect) + { + actions[length - 2] = noSave; + buttons[length - 2] = "Don't save"; + actions[length - 3] = saveAsNib; + buttons[length - 3] = "Save as NIB"; + } + else + { + actions[length - 2] = saveAsNib; + buttons[length - 2] = "Save as NIB"; + } + if (firstAction !is null) + { + actions[0] = firstAction; + buttons[0] = firstButton; + } + + do + { + int action = TwoappleDialog.run("Warning", msg, buttons); + actions[action](); + } while (chooseAgain); + } + + do + { + success = retry = false; + try + { + imgFile.save(); + imgData.isDirty = false; + success = true; + } + catch (DSKImage.VolumeException e) + { + chooseAction("Disk volume is not 254", + { e.ignoreVolume(); retry = true; }, + "Ignore and save"); + } + catch (DSKImage.DSKException e) + { + chooseAction("Image cannot be saved in DSK format"); + } + catch (Exception e) + { + TwoappleError.show(e.msg); + } + } while (retry); + + return success; + } +} + +class InternalImage +{ + bool isDirty; + static const NUM_TRACKS = 35; + static const TRACK_LENGTH = 6656; // XXX No DOS 3.2 + abstract ubyte peek(uint track); + abstract void poke(uint track, ubyte val); +} + +class NonImage : InternalImage +{ + ubyte peek(uint track) { return 0; } + void poke(uint track, ubyte val) {} +} + +class RealImage : InternalImage +{ + ubyte[][] trackData; + uint currentPos; + + this() + { + trackData = new ubyte[][NUM_TRACKS]; + for (uint t = 0; t < NUM_TRACKS; ++t) + trackData[t] = new ubyte[TRACK_LENGTH]; + } + + ubyte peek(uint track) + { + currentPos %= TRACK_LENGTH; + return trackData[track][currentPos++]; + } + + void poke(uint track, ubyte val) + { + currentPos %= TRACK_LENGTH; + trackData[track][currentPos++] = val; + isDirty = true; + } +} + +class ExternalImage +{ + TwoappleFile imgFile; + RealImage imgData; + static const string invalidMessage = "Invalid image file"; + + class ReadOnlyException : Exception + { + this() { super("File is read-only"); } + } + + static string isValidImage(TwoappleFile checkFile) + { + uint fsize = checkFile.fileSize(); + return (isDSKImage(fsize) || isNIBImage(fsize)) ? + null : invalidMessage; + } + + static ExternalImage loadImage(TwoappleFile checkFile) + { + uint fsize = checkFile.fileSize(); + if (isDSKImage(fsize)) return new DSKImage(checkFile); + if (isNIBImage(fsize)) return new NIBImage(checkFile); + return null; + } + + static bool isDSKImage(uint fsize) + { + return (fsize == 143488) || + ((fsize >= 143358) && (fsize <= 143363)); + } + + static bool isNIBImage(uint fsize) + { + return (fsize == 232960); + } + + this(TwoappleFile checkFile) + { + imgFile = checkFile; + imgData = new RealImage(); + load(); + } + + this(TwoappleFile checkFile, InternalImage data) + { + imgFile = checkFile; + imgData = cast(RealImage)data; + } + + abstract void load(); + abstract void writeOut(File stream); + + void save() + { + //if (!imgFile.canWrite()) throw new ReadOnlyException(); + File stream = new File(imgFile.fileName, FileMode.Out); + scope(exit) + { + if (stream !is null) stream.close(); + } + writeOut(stream); + } +} + +class DSKImage : ExternalImage +{ + static int[16] dosOrder = [ + 0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04, + 0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F]; + + static int[16] prodosOrder = [ + 0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B, + 0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F]; + + static ubyte[3] DATA_PROLOGUE = [0xD5, 0xAA, 0xAD]; + static ubyte[3] ADDR_PROLOGUE = [0xD5, 0xAA, 0x96]; + static ubyte[3] EPILOGUE = [0xDE, 0xAA, 0xEB]; + + static ubyte[0x40] diskByte = [ + 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, + 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3, + 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, + 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3, + 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, + 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC, + 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, + 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + ]; + + static int[0x70] memByte; + + static const NUM_SECTORS = 16; + static const SECTOR_LENGTH = 256; + static const DATA_FIELD_LENGTH = 342; + + static this() + { + memByte[0..0x70] = -1; + for (int i = 0; i < 0x40; ++i) + { + memByte[diskByte[i] - 0x96] = i; + } + } + + class VolumeException : Exception + { + DSKImage img; + + this() + { + super(""); + img = this.outer; + } + + void ignoreVolume() + { + img.preserveVolume = false; + } + } + + class DSKException : Exception + { + this() { super(""); } + } + + uint dataOffset; + ubyte[] mbHeader; + bool isProdos; + ubyte volume; + bool preserveVolume; + + this(TwoappleFile checkFile) + { + super(checkFile); + volume = 0xFE; + preserveVolume = true; + } + + this(TwoappleFile checkFile, InternalImage data) + { + super(checkFile, data); + } + + void checkMacBinary(File stream) + { + if (imgFile.fileSize() == 143488) + { + dataOffset = 128; + mbHeader = new ubyte[128]; + stream.read(mbHeader); + } + } + + bool prodosOrderProdos(File stream) + { + ubyte checkLo, checkHi; + ushort check1, check2, match1, match2; + for (int s = 2; s <= 5; ++s) + { + stream.seekSet(s * 512 + dataOffset); + stream.read(checkLo); stream.read(checkHi); + check1 = (checkHi << 8) | checkLo; + stream.read(checkLo); stream.read(checkHi); + check2 = (checkHi << 8) | checkLo; + match1 = ((s == 2) ? 0 : (s - 1)); + match2 = ((s == 5) ? 0 : (s + 1)); + if ((check1 != match1) || (check2 != match2)) return false; + } + return true; + } + + bool prodosOrderDos(File stream) + { + ubyte check; + for (int s = 5; s <= 13; ++s) + { + stream.seekSet(dataOffset + 0x11002 + (s * 256)); + stream.read(check); + if (check != (14 - s)) return false; + } + return true; + } + + void loadTrack(File stream, int track) + { + uint offset; + ubyte[] trackData = imgData.trackData[track]; + + void loadBytes(ubyte[] data) + { + uint len = data.length; + trackData[offset..offset+len] = data; + offset += len; + } + + void loadByte(ubyte data, uint len = 1) + { + trackData[offset..offset+len] = data; + offset += len; + } + + void loadAddrField(ubyte sector) + { + void encode44(ubyte val) + { + loadByte((((val >> 1) & 0x55) | 0xAA)); + loadByte(((val & 0x55) | 0xAA)); + } + + loadBytes(ADDR_PROLOGUE[0..3]); + + encode44(0xFE); // volume + encode44(track); + encode44(sector); + encode44(0xFE ^ track ^ sector); // check byte + + loadBytes(EPILOGUE[0..2]); + } + + void loadDataField(ubyte sector) + { + loadBytes(DATA_PROLOGUE[0..3]); + loadBytes(loadSector(stream, track, sector)); + loadBytes(EPILOGUE[0..3]); + } + + loadByte(0xFF, 48); // Load gap 1 + + for (ubyte sector = 0; sector < NUM_SECTORS; ++sector) + { + loadAddrField(sector); + loadByte(0xFF, 6); // Load gap 2 + loadDataField(sector); + loadByte(0xFF, 45); // Load gap 3 + } + } + + ubyte[] loadSector(File stream, int track, int sector) + { + ubyte[] sectorData = new ubyte[SECTOR_LENGTH]; + ubyte[] trackData = new ubyte[DATA_FIELD_LENGTH + 1]; + + stream.seekSet(dataOffset + (track * (NUM_SECTORS * SECTOR_LENGTH)) + + (isProdos ? (prodosOrder[sector] * SECTOR_LENGTH) : + (dosOrder[sector] * SECTOR_LENGTH))); + stream.read(sectorData); + + int x = 0x55; + ubyte y = 2; + uint val; + + // Translate 256 bytes of data into 342 6-bit index values + + while(true) + { + --y; + val = sectorData[y]; + + // index values 0 through 85 are composed of a combination + // of the two least significant bits from each data value + // index 85 from data 85, 171, 1 + // index 84 from data 84, 170, 0 + // index 83 from data 83, 169, 255 + // ... + // index 0 from data 0, 86, 172 + + trackData[x] = + (trackData[x] << 2) | + (((val & 0x01) << 1) | ((val & 0x02) >> 1)); + + // index values 86 through 341 are composed of the six + // most significant bits of data values 0 through 255 + + trackData[y + 0x56] = (val >> 2); + + --x; + if (x >= 0x00) continue; + x = 0x55; + if (y == 0) break; + } + + // Translate the 342 index values into 343 disk bytes + // (where the 343rd disk byte is a check byte) + + ubyte lastByte = 0, indexByte; + for (int i = 0; i < DATA_FIELD_LENGTH; ++i) + { + indexByte = trackData[i] & 0x3F; + trackData[i] = diskByte[lastByte ^ indexByte]; + lastByte = indexByte; + } + trackData[DATA_FIELD_LENGTH] = diskByte[lastByte]; + + return trackData; + } + + void load() + { + File stream = new File(imgFile.fileName); + + checkMacBinary(stream); + isProdos = (prodosOrderDos(stream) || prodosOrderProdos(stream)); + stream.seekSet(dataOffset); + + for (int t = 0; t < InternalImage.NUM_TRACKS; ++t) + loadTrack(stream, t); + stream.close(); + } + + bool writeTrack(int track, ubyte[] saveData) + { + uint offset; + ubyte sector; + int expectedSector = -1; + bool sectorsWritten[] = new bool[NUM_SECTORS]; + bool sectorsSeen[] = new bool[NUM_SECTORS]; + ubyte sectorData[] = new ubyte[DATA_FIELD_LENGTH]; + + ubyte[] trackData = imgData.trackData[track]; + + ubyte nextData(uint delta) + { + return trackData[(offset + delta) % InternalImage.TRACK_LENGTH]; + } + + bool dataMatches(uint delta, ubyte[] data) + { + for (int b = 0; b < data.length; ++b) + { + if (nextData(delta + b) != data[b]) + return false; + } + return true; + } + + bool findAddressField() + { + ubyte decode44(ubyte first, ubyte second) + { + return ((first & 0x55) << 1) | (second & 0x55); + } + + bool testAddressField() + { + ubyte check, storedVolume, storedTrack; + + storedVolume = decode44(nextData(3), nextData(4)); + if (storedVolume != 0xFE) volume = storedVolume; + + storedTrack = decode44(nextData(5), nextData(6)); + sector = decode44(nextData(7), nextData(8)); + check = decode44(nextData(9), nextData(10)); + + if (expectedSector == -1) + expectedSector = sector; + else + expectedSector = (expectedSector + 1) % NUM_SECTORS; + + offset += 13; + + return (storedTrack == track) && (sector < NUM_SECTORS) && + (expectedSector == sector) && + (check == (storedVolume ^ storedTrack ^ sector)) && + (!sectorsSeen[sector]); + } + + while (offset < InternalImage.TRACK_LENGTH) + { + if (dataMatches(0, ADDR_PROLOGUE) && + dataMatches(11, EPILOGUE[0..2])) + return testAddressField(); + else + ++offset; + } + return false; + } + + bool findDataField() + { + offset %= InternalImage.TRACK_LENGTH; + while (offset < InternalImage.TRACK_LENGTH) + { + if (dataMatches(0, DATA_PROLOGUE) && + dataMatches(346, EPILOGUE)) + { + offset += 3; + sectorsSeen[sector] = true; + return true; + } + else + ++offset; + } + return false; + } + + void writeDataBlock() + { + uint sectorFirst = (InternalImage.TRACK_LENGTH - offset); + if (sectorFirst > DATA_FIELD_LENGTH) + sectorFirst = DATA_FIELD_LENGTH; + uint sectorSecond = DATA_FIELD_LENGTH - sectorFirst; + + sectorData[0..sectorFirst] = trackData[offset..offset+sectorFirst]; + if (sectorSecond) + { + sectorData[sectorFirst..sectorFirst+sectorSecond] = + trackData[0..sectorSecond]; + } + + sectorsWritten[sector] = + writeSector(saveData, sectorData, sector); + } + + while (true) + { + if (!findAddressField()) break; + if (!findDataField()) break; + writeDataBlock(); + } + + for (int sect = 0; sect < NUM_SECTORS; ++sect) + { + if (!sectorsWritten[sect]) return false; + } + + return true; + } + + bool writeSector(ubyte[] saveData, ubyte[] sectorData, ubyte sector) + { + uint dskOffset = (isProdos ? (prodosOrder[sector] * SECTOR_LENGTH) : + (dosOrder[sector] * SECTOR_LENGTH)); + ubyte[] dskData = saveData[dskOffset..dskOffset+SECTOR_LENGTH]; + + // Translate the 342 disk bytes into 6-bit index values + + ubyte[] indexData = new ubyte[DATA_FIELD_LENGTH]; + + int lastByte = 0; + ubyte nibByte; + for (int i = 0; i < 0x156; ++i) + { + if (sectorData[i] < 0x96) return false; + nibByte = memByte[sectorData[i] - 0x96]; + if (nibByte == -1) return false; + indexData[i] = lastByte ^ nibByte; + lastByte = indexData[i]; + } + + // TODO: verify the checksum + // Translate the 342 index values into 256 bytes + + ubyte y = 0; + uint x = 0; + while(true) + { + // The lower two bits of each byte are taken from + // a pair of bits from index values 0 through 85 + + dskData[y] = + ((indexData[x] & 0x01) << 1) | ((indexData[x] & 0x02) >> 1); + indexData[x] >>= 2; + + // The upper six bits of bytes 0 through 255 are taken + // from the lower six bits of index values 86 through 342. + + dskData[y] |= (indexData[y + 0x56] << 2); + ++y; + if (y == 0) break; + ++x; + if (x == 0x56) x = 0; + } + + return true; + } + + void writeOut(File stream) + { + ubyte[][] saveData; + saveData = new ubyte[][InternalImage.NUM_TRACKS]; + + for (int t = 0; t < InternalImage.NUM_TRACKS; ++t) + { + saveData[t] = new ubyte[NUM_SECTORS * SECTOR_LENGTH]; + if (!(writeTrack(t, saveData[t]))) + throw new DSKException(); + } + if ((volume != 0xFE) && preserveVolume) + throw new VolumeException(); + + if (mbHeader.length != 0) stream.write(mbHeader); + for (int t = 0; t < InternalImage.NUM_TRACKS; ++t) + stream.write(saveData[t]); + } +} + +class NIBImage : ExternalImage +{ + this(TwoappleFile checkFile) { super(checkFile); } + + this(TwoappleFile checkFile, InternalImage data) + { + super(checkFile, data); + } + + void load() + { + File stream = new File(imgFile.fileName); + for (int t = 0; t < InternalImage.NUM_TRACKS; ++t) + { + stream.read(imgData.trackData[t]); + } + stream.close(); + } + + void writeOut(File stream) + { + for (int t = 0; t < InternalImage.NUM_TRACKS; ++t) + { + stream.write(imgData.trackData[t]); + } + } +} + diff --git a/src/peripheral/langcard.d b/src/peripheral/langcard.d new file mode 100644 index 0000000..adebcab --- /dev/null +++ b/src/peripheral/langcard.d @@ -0,0 +1,430 @@ +/+ + + peripheral/langcard.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module peripheral.langcard; + +import peripheral.base; +import device.base; +import memory; + +private: + +string MakeRAMSwitches(bool isIIe) +{ + string ramSwitches; + for (int sw = 0xC080; sw <= 0xC08F; ++sw) + { + ramSwitches ~= "void read" ~ hex4Digits(sw) ~ "()\n{\n" ~ + MakeRAMSwitch(sw, false, isIIe) ~ "}\n"; + ramSwitches ~= "void write" ~ hex4Digits(sw) ~ "()\n{\n" ~ + MakeRAMSwitch(sw, true, isIIe) ~ "}\n"; + } + return ramSwitches; +} + +string MakeRAMSwitch(int sw, bool write, bool isIIe) +{ + string ramSwitch; + + ramSwitch ~= CallRAMFunction(isIIe, "setBank(" ~ + hex4Digits((sw & 0b1000) >> 3 ) ~ ")") ~ "\n"; + + if (((sw & 0b11) == 0b11) || ((sw & 0b11) == 0b00)) + ramSwitch ~= CallRAMFunction(isIIe, "enableRead()") ~ "\n"; + else + ramSwitch ~= CallRAMFunction(isIIe, "disableRead()") ~ "\n"; + + if ((sw & 0b1) == 0b0) + ramSwitch ~= CallRAMFunction(isIIe, "clearPreWrite()") ~ "\n" ~ + CallRAMFunction(isIIe, "disableWrite()") ~ "\n"; + else + { + if (write) + ramSwitch ~= CallRAMFunction(isIIe, "clearPreWrite()") ~ "\n"; + else + ramSwitch ~= CallRAMFunction(isIIe, "checkEnableWrite()") ~ "\n"; + } + + return ramSwitch; +} + +string CallRAMFunction(bool isIIe, string func) +{ + if (!isIIe) + { + return "implementation." ~ func ~ ";"; + } + else + { + return "onboard." ~ func ~ ";\n" ~ + "if (auxslot !is null) auxslot." ~ func ~ ";"; + } +} + +static const string[][] langcardSwitches = [ + mixin(MakeSwitch([0xC080], "R0", "readC080")), + mixin(MakeSwitch([0xC081], "R0", "readC081")), + mixin(MakeSwitch([0xC082], "R0", "readC082")), + mixin(MakeSwitch([0xC083], "R0", "readC083")), + mixin(MakeSwitch([0xC084], "R0", "readC084")), + mixin(MakeSwitch([0xC085], "R0", "readC085")), + mixin(MakeSwitch([0xC086], "R0", "readC086")), + mixin(MakeSwitch([0xC087], "R0", "readC087")), + mixin(MakeSwitch([0xC088], "R0", "readC088")), + mixin(MakeSwitch([0xC089], "R0", "readC089")), + mixin(MakeSwitch([0xC08A], "R0", "readC08A")), + mixin(MakeSwitch([0xC08B], "R0", "readC08B")), + mixin(MakeSwitch([0xC08C], "R0", "readC08C")), + mixin(MakeSwitch([0xC08D], "R0", "readC08D")), + mixin(MakeSwitch([0xC08E], "R0", "readC08E")), + mixin(MakeSwitch([0xC08F], "R0", "readC08F")), + mixin(MakeSwitch([0xC080], "W", "writeC080")), + mixin(MakeSwitch([0xC081], "W", "writeC081")), + mixin(MakeSwitch([0xC082], "W", "writeC082")), + mixin(MakeSwitch([0xC083], "W", "writeC083")), + mixin(MakeSwitch([0xC084], "W", "writeC084")), + mixin(MakeSwitch([0xC085], "W", "writeC085")), + mixin(MakeSwitch([0xC086], "W", "writeC086")), + mixin(MakeSwitch([0xC087], "W", "writeC087")), + mixin(MakeSwitch([0xC088], "W", "writeC088")), + mixin(MakeSwitch([0xC089], "W", "writeC089")), + mixin(MakeSwitch([0xC08A], "W", "writeC08A")), + mixin(MakeSwitch([0xC08B], "W", "writeC08B")), + mixin(MakeSwitch([0xC08C], "W", "writeC08C")), + mixin(MakeSwitch([0xC08D], "W", "writeC08D")), + mixin(MakeSwitch([0xC08E], "W", "writeC08E")), + mixin(MakeSwitch([0xC08F], "W", "writeC08F")) +]; + +public: + +class HighRam +{ + AddressDecoder decoder; + + ReadFunc origRead; + WriteFunc origWrite; + + Memory lowerChunk, upperChunk; + + bool preWrite; + bool readEn, writeEn; + bool enabled; + int current4KBank; + + this() + { + initMemory(); + } + + abstract void initMemory(); + + void init(AddressDecoder addrDecode, ReadFunc read, WriteFunc write) + { + decoder = addrDecode; + origRead = read; + origWrite = write; + } + + void reboot() + { + preWrite = false; + lowerChunk.reboot(); + upperChunk.reboot(); + current4KBank = 0; + forceReadDisable(); + forceWriteEnable(); + } + + void clearPreWrite() + { + preWrite = false; + } + + void forceReadDisable() + { + readEn = true; + disableRead(); + } + + void forceReadEnable() + { + readEn = false; + enableRead(); + } + + void forceWriteEnable() + { + writeEn = false; + enableWrite(); + } + + void enableRead() + { + if ((!readEn) && enabled) + { + decoder.installRead(lowerChunk); + decoder.installRead(upperChunk); + } + readEn = true; + } + + void disableRead() + { + if (readEn && enabled) + { + decoder.readPages[0xD0..0x100] = origRead; + } + readEn = false; + } + + void checkEnableWrite() + { + if (preWrite) enableWrite(); + preWrite = true; + } + + void enableWrite() + { + if ((!writeEn) && enabled) + { + decoder.installWrite(lowerChunk); + decoder.installWrite(upperChunk); + } + writeEn = true; + } + + void disableWrite() + { + if (writeEn && enabled) + { + decoder.writePages[0xD0..0x100] = origWrite; + } + writeEn = false; + } + + abstract void setLowerBank(int bank); + + void setBank(int bank) + { + setLowerBank(bank); + current4KBank = bank; + } +} + +class HighRam_II : HighRam +{ + PrimaryMem e000ffff; + BankMem d000dfff; + + this() + { + super(); + enabled = true; + } + + void initMemory() + { + d000dfff = new BankMem(0xD000, 0x1000, 2); + e000ffff = new PrimaryMem(0xE000, 0x2000); + lowerChunk = d000dfff; + upperChunk = e000ffff; + } + + void setDebugName(string name) + { + e000ffff.debugName = name; + d000dfff.setDebugNames(name); + } + + void setLowerBank(int bank) + { + d000dfff.setBank(bank); + } +} + +class HighRam_IIe : HighRam +{ + bool isOnboard; + int numBanks; + SubBankMem d000dfff; + BankMem e000ffff; + + this(bool isOnboard_, int banks) + { + numBanks = banks; + super(); + isOnboard = isOnboard_; + } + + void initMemory() + { + d000dfff = new SubBankMem(0xD000, 0x1000, numBanks, 2); + e000ffff = new BankMem(0xE000, 0x2000, numBanks); + lowerChunk = d000dfff; + upperChunk = e000ffff; + } + + void setLowerBank(int bank) + { + d000dfff.setSubBank(bank); + } + + void resetBanks() + { + e000ffff.setBank(0); + d000dfff.setPrimaryBank(0); + d000dfff.setSubBank(0); + } + + void enable(bool newState) + { + if (enabled == newState) return; + enabled = newState; + if (newState) + { + if (writeEn) forceWriteEnable(); + if (readEn) forceReadEnable(); + } + } + + void setDebugName(string name) + { + string[] names; + names.length = numBanks; + if (numBanks > 1) + { + for (int b = 0; b < numBanks; ++b) + names[b] = name ~ " " ~ std.string.toString(b); + } + else + { + names[0] = name; + } + e000ffff.setDebugNames(name); + d000dfff.setDebugNames(names); + } + + void reboot() + { + enabled = isOnboard; + super.reboot(); + } + + void reset() + { + enabled = isOnboard; + preWrite = false; + resetBanks(); + current4KBank = 0; + disableRead(); + enableWrite(); + } +} + +class HighRam_Null : HighRam_IIe +{ + this() + { + super(false, 0); + } + + void initMemory() + { + lowerChunk = new ZeroMem(0xD000, 0x1000); + upperChunk = new ZeroMem(0xE000, 0x2000); + } + + void setLowerBank(int bank) {} + void resetBanks() {} +} + +class LanguageCard : Peripheral +{ + HighRam_II implementation; + + mixin(MakeRAMSwitches(false)); + mixin(InitSwitches("", langcardSwitches)); + + this() + { + implementation = new HighRam_II(); + implementation.setDebugName("Language card"); + } + + void reboot() + { + implementation.reboot(); + } +} + +class LanguageCard_IIe +{ + HighRam_IIe onboard, auxslot; + ubyte delegate() kbdLatch; + + mixin(MakeRAMSwitches(true)); + mixin(InitSwitches("", langcardSwitches ~ [ + mixin(MakeSwitch([0xC011], "R", "readBank2Switch")), + mixin(MakeSwitch([0xC012], "R", "readReadSwitch")) + ])); + + this() + { + onboard = new HighRam_IIe(true, 1); + onboard.setDebugName("High RAM"); + } + + void init(AddressDecoder addrDecode, ReadFunc read, WriteFunc write) + { + onboard.init(addrDecode, read, write); + auxslot.init(addrDecode, read, write); + } + + void reboot() + { + onboard.reboot(); + auxslot.reboot(); + } + + void reset() + { + onboard.reset(); + auxslot.reset(); + } + + void enableAuxSlot(bool isAuxSlot) + { + onboard.enable(!isAuxSlot); + auxslot.enable(isAuxSlot); + } + + ubyte readBank2Switch() + { + return kbdLatch() | ((onboard.current4KBank == 0) ? 0x80 : 0x00); + } + + ubyte readReadSwitch() + { + return kbdLatch() | (onboard.readEn ? 0x80 : 0x00); + } +} diff --git a/src/peripheral/saturn128.d b/src/peripheral/saturn128.d new file mode 100644 index 0000000..cfaf928 --- /dev/null +++ b/src/peripheral/saturn128.d @@ -0,0 +1,171 @@ +/+ + + peripheral/saturn128.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module peripheral.saturn128; + +import device.base; +import peripheral.base; +import memory; + +string MakeSaturnSwitches() +{ + string saturnSwitches; + for (int sw = 0xC080; sw <= 0xC08F; ++sw) + { + saturnSwitches ~= "void access" ~ hex4Digits(sw) ~ "()\n{\n"; + if (sw & 0b00000100) + saturnSwitches ~= MakeBankSwitch(sw); + else + saturnSwitches ~= MakeModeSwitch(sw); + saturnSwitches ~= "}\n"; + } + return saturnSwitches; +} + +string MakeModeSwitch(int sw) +{ + string modeSwitch; + modeSwitch ~= "set4KBank(" ~ hex2Digits((sw & 0b1000) >> 3) ~ ");\n"; + modeSwitch ~= (sw & 0b1) ? "tryWriteEnable();\n" : "writeProtect();\n"; + modeSwitch ~= (((sw & 0b11) == 0b00) || ((sw & 0b11) == 0b11)) ? + "readRAM();\n" : "readROM();\n"; + return modeSwitch; +} + +string MakeBankSwitch(int sw) +{ + int bank = (sw & 0b11) | ((sw & 0b1000) >> 1); + return "set16KBank(" ~ hex2Digits(bank) ~ ");\n"; +} + +class Saturn128 : Peripheral +{ + AddressDecoder decoder; + + ReadFunc origRead; + WriteFunc origWrite; + bool preWrite; + bool readEn, writeEn; + BankMem e000ffff; + SubBankMem d000dfff; + + this() + { + e000ffff = new BankMem(0xE000, 0x2000, 16); + d000dfff = new SubBankMem(0xD000, 0x1000, 16, 2); + } + + void init(AddressDecoder addrDecode, ReadFunc read, WriteFunc write) + { + decoder = addrDecode; + origRead = read; + origWrite = write; + } + + void reboot() + { + preWrite = false; + e000ffff.reboot(); + d000dfff.reboot(); + readEn = true; + readROM(); + writeEn = true; + writeProtect(); + } + + void writeProtect() + { + if (writeEn) + { + decoder.writePages[0xD0..0x100] = origWrite; + } + preWrite = false; + writeEn = false; + } + + void tryWriteEnable() + { + if (preWrite) writeEnable(); + preWrite = true; + } + + void writeEnable() + { + if (!writeEn) + { + decoder.installWrite(e000ffff); + decoder.installWrite(d000dfff); + } + writeEn = true; + } + + void readRAM() + { + if (!readEn) + { + decoder.installRead(e000ffff); + decoder.installRead(d000dfff); + } + readEn = true; + } + + void readROM() + { + if (readEn) + { + decoder.readPages[0xD0..0x100] = origRead; + } + readEn = false; + } + + void set16KBank(int bank) + { + e000ffff.setBank(bank); + d000dfff.setPrimaryBank(bank); + } + + void set4KBank(int bank) + { + d000dfff.setSubBank(bank); + } + + mixin(MakeSaturnSwitches()); + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC080], "R0W", "accessC080")), + mixin(MakeSwitch([0xC081], "R0W", "accessC081")), + mixin(MakeSwitch([0xC082], "R0W", "accessC082")), + mixin(MakeSwitch([0xC083], "R0W", "accessC083")), + mixin(MakeSwitch([0xC084], "R0W", "accessC084")), + mixin(MakeSwitch([0xC085], "R0W", "accessC085")), + mixin(MakeSwitch([0xC086], "R0W", "accessC086")), + mixin(MakeSwitch([0xC087], "R0W", "accessC087")), + mixin(MakeSwitch([0xC088], "R0W", "accessC088")), + mixin(MakeSwitch([0xC089], "R0W", "accessC089")), + mixin(MakeSwitch([0xC08A], "R0W", "accessC08A")), + mixin(MakeSwitch([0xC08B], "R0W", "accessC08B")), + mixin(MakeSwitch([0xC08C], "R0W", "accessC08C")), + mixin(MakeSwitch([0xC08D], "R0W", "accessC08D")), + mixin(MakeSwitch([0xC08E], "R0W", "accessC08E")), + mixin(MakeSwitch([0xC08F], "R0W", "accessC08F")) + ])); +} diff --git a/src/sndfile.d b/src/sndfile.d new file mode 100644 index 0000000..9e2b0fc --- /dev/null +++ b/src/sndfile.d @@ -0,0 +1,653 @@ +module sndfile; + +import derelict.util.loader; + +/+ + + The following file types can be read and written. + + A file type would consist of a major type (ie SF_FORMAT_WAV) bitwise + + ORed with a minor type (ie SF_FORMAT_PCM). SF_FORMAT_TYPEMASK and + + SF_FORMAT_SUBMASK can be used to separate the major and minor file + + types. + +/ + +enum +{ + // Major formats. + SF_FORMAT_WAV = 0x010000, /+ Microsoft WAV format + (little endian default). +/ + SF_FORMAT_AIFF = 0x020000, // Apple/SGI AIFF format (big endian). + SF_FORMAT_AU = 0x030000, // Sun/NeXT AU format (big endian). + SF_FORMAT_RAW = 0x040000, // RAW PCM data. + SF_FORMAT_PAF = 0x050000, // Ensoniq PARIS file format. + SF_FORMAT_SVX = 0x060000, // Amiga IFF / SVX8 / SV16 format. + SF_FORMAT_NIST = 0x070000, // Sphere NIST format. + SF_FORMAT_VOC = 0x080000, // VOC files. + SF_FORMAT_IRCAM = 0x0A0000, // Berkeley/IRCAM/CARL + SF_FORMAT_W64 = 0x0B0000, // Sonic Foundry's 64 bit RIFF/WAV + SF_FORMAT_MAT4 = 0x0C0000, // Matlab (tm) V4.2 / GNU Octave 2.0 + SF_FORMAT_MAT5 = 0x0D0000, // Matlab (tm) V5.0 / GNU Octave 2.1 + SF_FORMAT_PVF = 0x0E0000, // Portable Voice Format + SF_FORMAT_XI = 0x0F0000, // Fasttracker 2 Extended Instrument + SF_FORMAT_HTK = 0x100000, // HMM Tool Kit format + SF_FORMAT_SDS = 0x110000, // Midi Sample Dump Standard + SF_FORMAT_AVR = 0x120000, // Audio Visual Research + SF_FORMAT_WAVEX = 0x130000, // MS WAVE with WAVEFORMATEX + SF_FORMAT_SD2 = 0x160000, // Sound Designer 2 + SF_FORMAT_FLAC = 0x170000, // FLAC lossless file format + SF_FORMAT_CAF = 0x180000, // Core Audio File format + + // Subtypes from here on. + + SF_FORMAT_PCM_S8 = 0x0001, // Signed 8 bit data + SF_FORMAT_PCM_16 = 0x0002, // Signed 16 bit data + SF_FORMAT_PCM_24 = 0x0003, // Signed 24 bit data + SF_FORMAT_PCM_32 = 0x0004, // Signed 32 bit data + + SF_FORMAT_PCM_U8 = 0x0005, /+ Unsigned 8 bit data + (WAV and RAW only) +/ + + SF_FORMAT_FLOAT = 0x0006, // 32 bit float data + SF_FORMAT_DOUBLE = 0x0007, // 64 bit float data + + SF_FORMAT_ULAW = 0x0010, // U-Law encoded. + SF_FORMAT_ALAW = 0x0011, // A-Law encoded. + SF_FORMAT_IMA_ADPCM = 0x0012, // IMA ADPCM. + SF_FORMAT_MS_ADPCM = 0x0013, // Microsoft ADPCM. + + SF_FORMAT_GSM610 = 0x0020, // GSM 6.10 encoding. + SF_FORMAT_VOX_ADPCM = 0x0021, // OKI / Dialogix ADPCM + + SF_FORMAT_G721_32 = 0x0030, // 32kbs G721 ADPCM encoding. + SF_FORMAT_G723_24 = 0x0031, // 24kbs G723 ADPCM encoding. + SF_FORMAT_G723_40 = 0x0032, // 40kbs G723 ADPCM encoding. + + SF_FORMAT_DWVW_12 = 0x0040, /+ 12 bit Delta Width Variable Word + encoding. +/ + SF_FORMAT_DWVW_16 = 0x0041, /+ 16 bit Delta Width Variable Word + encoding. +/ + SF_FORMAT_DWVW_24 = 0x0042, /+ 24 bit Delta Width Variable Word + encoding. +/ + SF_FORMAT_DWVW_N = 0x0043, /+ N bit Delta Width Variable Word + encoding. +/ + + SF_FORMAT_DPCM_8 = 0x0050, // 8 bit differential PCM (XI only) + SF_FORMAT_DPCM_16 = 0x0051, // 16 bit differential PCM (XI only) + + // Endian-ness options. + + SF_ENDIAN_FILE = 0x00000000, // Default file endian-ness. + SF_ENDIAN_LITTLE = 0x10000000, // Force little endian-ness. + SF_ENDIAN_BIG = 0x20000000, // Force big endian-ness. + SF_ENDIAN_CPU = 0x30000000, // Force CPU endian-ness. + + SF_FORMAT_SUBMASK = 0x0000FFFF, + SF_FORMAT_TYPEMASK = 0x0FFF0000, + SF_FORMAT_ENDMASK = 0x30000000 +} + +/+ + + The following are the valid command numbers for the sf_command() + + interface. The use of these commands is documented in the file + + command.html in the doc directory of the source code distribution. + +/ + +enum +{ + SFC_GET_LIB_VERSION = 0x1000, + SFC_GET_LOG_INFO = 0x1001, + + SFC_GET_NORM_DOUBLE = 0x1010, + SFC_GET_NORM_FLOAT = 0x1011, + SFC_SET_NORM_DOUBLE = 0x1012, + SFC_SET_NORM_FLOAT = 0x1013, + SFC_SET_SCALE_FLOAT_INT_READ = 0x1014, + + SFC_GET_SIMPLE_FORMAT_COUNT = 0x1020, + SFC_GET_SIMPLE_FORMAT = 0x1021, + + SFC_GET_FORMAT_INFO = 0x1028, + + SFC_GET_FORMAT_MAJOR_COUNT = 0x1030, + SFC_GET_FORMAT_MAJOR = 0x1031, + SFC_GET_FORMAT_SUBTYPE_COUNT = 0x1032, + SFC_GET_FORMAT_SUBTYPE = 0x1033, + + SFC_CALC_SIGNAL_MAX = 0x1040, + SFC_CALC_NORM_SIGNAL_MAX = 0x1041, + SFC_CALC_MAX_ALL_CHANNELS = 0x1042, + SFC_CALC_NORM_MAX_ALL_CHANNELS = 0x1043, + SFC_GET_SIGNAL_MAX = 0x1044, + SFC_GET_MAX_ALL_CHANNELS = 0x1045, + + SFC_SET_ADD_PEAK_CHUNK = 0x1050, + + SFC_UPDATE_HEADER_NOW = 0x1060, + SFC_SET_UPDATE_HEADER_AUTO = 0x1061, + + SFC_FILE_TRUNCATE = 0x1080, + + SFC_SET_RAW_START_OFFSET = 0x1090, + + SFC_SET_DITHER_ON_WRITE = 0x10A0, + SFC_SET_DITHER_ON_READ = 0x10A1, + + SFC_GET_DITHER_INFO_COUNT = 0x10A2, + SFC_GET_DITHER_INFO = 0x10A3, + + SFC_GET_EMBED_FILE_INFO = 0x10B0, + + SFC_SET_CLIPPING = 0x10C0, + SFC_GET_CLIPPING = 0x10C1, + + SFC_GET_INSTRUMENT = 0x10D0, + SFC_SET_INSTRUMENT = 0x10D1, + + SFC_GET_LOOP_INFO = 0x10E0, + + SFC_GET_BROADCAST_INFO = 0x10F0, + SFC_SET_BROADCAST_INFO = 0x10F1, + + // Following commands for testing only. + SFC_TEST_IEEE_FLOAT_REPLACE = 0x6001, + + /+ + + SFC_SET_ADD_* values are deprecated and will disappear at some + + time in the future. They are guaranteed to be here up to and + + including version 1.0.8 to avoid breakage of existng software. + + They currently do nothing and will continue to do nothing. + +/ + SFC_SET_ADD_DITHER_ON_WRITE = 0x1070, + SFC_SET_ADD_DITHER_ON_READ = 0x1071 +} + + +/+ + + String types that can be set and read from files. Not all file types + + support this and even the file types which support one, may not support + + all string types. + +/ + +enum +{ + SF_STR_TITLE = 0x01, + SF_STR_COPYRIGHT = 0x02, + SF_STR_SOFTWARE = 0x03, + SF_STR_ARTIST = 0x04, + SF_STR_COMMENT = 0x05, + SF_STR_DATE = 0x06 +} + +/+ + + Use the following as the start and end index when doing metadata + + transcoding. + +/ + +alias SF_STR_TITLE SF_STR_FIRST; +alias SF_STR_DATE SF_STR_LAST; + +enum +{ + // True and false + SF_FALSE = 0, + SF_TRUE = 1, + + // Modes for opening files. + SFM_READ = 0x10, + SFM_WRITE = 0x20, + SFM_RDWR = 0x30 +} + +/+ + + Public error values. These are guaranteed to remain unchanged for the + + duration of the library major version number. There are also a large number + + of private error numbers which are internal to the library which can change + + at any time. + +/ + +enum +{ + SF_ERR_NO_ERROR = 0, + SF_ERR_UNRECOGNISED_FORMAT = 1, + SF_ERR_SYSTEM = 2, + SF_ERR_MALFORMED_FILE = 3, + SF_ERR_UNSUPPORTED_ENCODING = 4 +} + + +// A SNDFILE* pointer can be passed around much like stdio.h's FILE* pointer. +struct SNDFILE_tag {} +alias SNDFILE_tag SNDFILE; + +alias long sf_count_t; + +enum : long +{ + SF_COUNT_MAX = 0x7FFFFFFFFFFFFFFFL +} + +/+ + + A pointer to a SF_INFO structure is passed to sf_open_read() and filled in. + + On write, the SF_INFO structure is filled in by the user and passed into + + sf_open_write(). + +/ + +struct SF_INFO +{ + sf_count_t frames; /+ Used to be called samples. + Changed to avoid confusion. +/ + int samplerate; + int channels; + int format; + int sections; + int seekable; +} + +/+ + + The SF_FORMAT_INFO struct is used to retrieve information about the sound + + file formats libsndfile supports using the sf_command() interface. + + + + Using this interface will allow applications to support new file formats + + and encoding types when libsndfile is upgraded, without requiring + + re-compilation of the application. + + + + Please consult the libsndfile documentation (particularly the information + + on the sf_command() interface) for examples of its use. + +/ + +struct SF_FORMAT_INFO +{ + int format; + char* name; + char* extension; +} + +/+ + + Enums and typedefs for adding dither on read and write. + + See the html documentation for sf_command(), SFC_SET_DITHER_ON_WRITE + + and SFC_SET_DITHER_ON_READ. + +/ + +enum +{ + SFD_DEFAULT_LEVEL = 0, + SFD_CUSTOM_LEVEL = 0x40000000, + + SFD_NO_DITHER = 500, + SFD_WHITE = 501, + SFD_TRIANGULAR_PDF = 502 +} + +struct SF_DITHER_INFO +{ + int type; + double level; + char* name; +} + +/+ + + Struct used to retrieve information about a file embedded within a + + larger file. See SFC_GET_EMBED_FILE_INFO. + +/ + +struct SF_EMBED_FILE_INFO +{ + sf_count_t offset; + sf_count_t length; +} + + +// Structs used to retrieve music sample information from a file. + +enum +{ + // The loop mode field in SF_INSTRUMENT will be one of the following. + SF_LOOP_NONE = 800, + SF_LOOP_FORWARD, + SF_LOOP_BACKWARD, + SF_LOOP_ALTERNATING +} + +struct SF_INSTRUMENT +{ + int gain; + byte basenote, detune; + byte velocity_lo, velocity_hi; + byte key_lo, key_hi; + int loop_count; + + struct __loops + { int mode; + uint start; + uint end; + uint count; + } + + __loops[16] loops; +} + + +// Struct used to retrieve loop information from a file. +struct SF_LOOP_INFO +{ + short time_sig_num; // any positive integer > 0 + short time_sig_den; // any positive power of 2 > 0 + int loop_mode; // see SF_LOOP enum + + int num_beats; /+ this is NOT the amount of quarter notes !!! + a full bar of 4/4 is 4 beats + a full bar of 7/8 is 7 beats +/ + + float bpm; /+ suggestion, as it can be calculated using other + fields (file's length, file's sampleRate and + our time_sig_den). bpms are always the amount + of _quarter notes_ per minute +/ + + int root_key; // MIDI note, or -1 for None + int[6] future; +} + + +/+ + + Struct used to retrieve broadcast (EBU) information from a file. + + Strongly (!) based on EBU "bext" chunk format used in Broadcast WAVE. + +/ +struct SF_BROADCAST_INFO +{ + byte[256] description; + byte[32] originator; + byte[32] originator_reference; + byte[10] origination_date; + byte[8] origination_time; + int time_reference_low; + int time_reference_high; + short version_; // XXX changed from version + byte[64] umid; + byte[190] reserved; + uint coding_history_size ; + byte[256] coding_history; +} + + +extern(C) alias sf_count_t function(void*) sf_vio_get_filelen; +extern(C) alias sf_count_t function(sf_count_t, int, void*) sf_vio_seek; +extern(C) alias sf_count_t function(void*, sf_count_t, void*) sf_vio_read; +extern(C) alias sf_count_t function(void*, sf_count_t, void*) sf_vio_write; +extern(C) alias sf_count_t function(void*) sf_vio_tell; + +struct SF_VIRTUAL_IO +{ + sf_vio_get_filelen get_filelen; + sf_vio_seek seek; + sf_vio_read read; + sf_vio_write write; + sf_vio_tell tell; +} + +private void loadsndfile(SharedLib lib) +{ + bindFunc(sf_open)("sf_open", lib); + bindFunc(sf_open_fd)("sf_open_fd", lib); + bindFunc(sf_open_virtual)("sf_open_virtual", lib); + bindFunc(sf_error)("sf_error", lib); + bindFunc(sf_strerror)("sf_strerror", lib); + bindFunc(sf_error_number)("sf_error_number", lib); + bindFunc(sf_perror)("sf_perror", lib); + bindFunc(sf_error_str)("sf_error_str", lib); + bindFunc(sf_command)("sf_command", lib); + bindFunc(sf_format_check)("sf_format_check", lib); + bindFunc(sf_seek)("sf_seek", lib); + bindFunc(sf_set_string)("sf_set_string", lib); + bindFunc(sf_get_string)("sf_get_string", lib); + bindFunc(sf_read_raw)("sf_read_raw", lib); + bindFunc(sf_write_raw)("sf_write_raw", lib); + bindFunc(sf_readf_short)("sf_readf_short", lib); + bindFunc(sf_writef_short)("sf_writef_short", lib); + bindFunc(sf_readf_int)("sf_readf_int", lib); + bindFunc(sf_writef_int)("sf_writef_int", lib); + bindFunc(sf_readf_float)("sf_readf_float", lib); + bindFunc(sf_writef_float)("sf_writef_float", lib); + bindFunc(sf_readf_double)("sf_readf_double", lib); + bindFunc(sf_writef_double)("sf_writef_double", lib); + bindFunc(sf_read_short)("sf_read_short", lib); + bindFunc(sf_write_short)("sf_write_short", lib); + bindFunc(sf_read_int)("sf_read_int", lib); + bindFunc(sf_write_int)("sf_write_int", lib); + bindFunc(sf_read_float)("sf_read_float", lib); + bindFunc(sf_write_float)("sf_write_float", lib); + bindFunc(sf_read_double)("sf_read_double", lib); + bindFunc(sf_write_double)("sf_write_double", lib); + bindFunc(sf_close)("sf_close", lib); + bindFunc(sf_write_sync)("sf_write_sync", lib); +} + +GenericLoader libsndfile; +static this() +{ + libsndfile.setup( + "libsndfile.dll", + "libsndfile.so, libsndfile.so.1", + "", + &loadsndfile); +} + +extern(C): + +/+ + + Open the specified file for read, write or both. On error, this will + + return a NULL pointer. To find the error number, pass a NULL SNDFILE + + to sf_perror() or sf_error_str(). + + All calls to sf_open() should be matched with a call to sf_close(). + +/ + +typedef SNDFILE* function(char* path, int mode, SF_INFO* sfinfo) pfsf_open; +pfsf_open sf_open; + +/+ + + Use the existing file descriptor to create a SNDFILE object. If close_desc + + is TRUE, the file descriptor will be closed when sf_close() is called. If + + it is FALSE, the descritor will not be closed. + + When passed a descriptor like this, the library will assume that the start + + of file header is at the current file offset. This allows sound files within + + larger container files to be read and/or written. + + On error, this will return a NULL pointer. To find the error number, pass a + + NULL SNDFILE to sf_perror() or sf_error_str(). + + All calls to sf_open_fd() should be matched with a call to sf_close(). + +/ + +typedef SNDFILE* function(int fd, int mode, SF_INFO* sfinfo, int close_desc) + pfsf_open_fd; +typedef SNDFILE* function(SF_VIRTUAL_IO* sfvirtual, int mode, SF_INFO* sfinfo, + void* user_data) pfsf_open_virtual; + +pfsf_open_fd sf_open_fd; +pfsf_open_virtual sf_open_virtual; + +/+ + + sf_error() returns a error number which can be translated to a text + + string using sf_error_number(). + +/ + +typedef int function(SNDFILE* sndfile) pfsf_error; +pfsf_error sf_error; + +/+ + + sf_strerror() returns to the caller a pointer to the current error message + + for the given SNDFILE. + +/ + +typedef char* function(SNDFILE* sndfile) pfsf_strerror; +pfsf_strerror sf_strerror; + +/+ + + sf_error_number() allows the retrieval of the error string for each + + internal error number. + +/ + +typedef char* function(int errnum) pfsf_error_number; +pfsf_error_number sf_error_number; + +/+ + + The following three error functions are deprecated but they will remain in + + the library for the forseeable future. The function sf_strerror() should be + + used in their place. + +/ + +typedef int function(SNDFILE* sndfile) pfsf_perror; +typedef int function(SNDFILE* sndfile, char* str, size_t len) pfsf_error_str; + +pfsf_perror sf_perror; +pfsf_error_str sf_error_str; + +/+ + + Return TRUE if fields of the SF_INFO struct are a valid combination of + + values. + +/ + +typedef int function(SNDFILE* sndfile, int command, void* data, int datasize) + pfsf_command; +pfsf_command sf_command; + +/+ + + Return TRUE if fields of the SF_INFO struct are a valid combination of + + values. + +/ + +typedef int function(SF_INFO* info) pfsf_format_check; +pfsf_format_check sf_format_check; + +/+ + + Seek within the waveform data chunk of the SNDFILE. sf_seek() uses + + the same values for whence (SEEK_SET, SEEK_CUR and SEEK_END) as + + stdio.h function fseek(). + + An offset of zero with whence set to SEEK_SET will position the + + read / write pointer to the first data sample. + + On success sf_seek returns the current position in (multi-channel) + + samples from the start of the file. + + Please see the libsndfile documentation for moving the read pointer + + separately from the write pointer on files open in mode SFM_RDWR. + + On error all of these functions return -1. + +/ + +typedef sf_count_t function(SNDFILE* sndfile, sf_count_t frames, int whence) + pfsf_seek; +pfsf_seek sf_seek; + +/+ + + Functions for retrieving and setting string data within sound files. + + Not all file types support this features; AIFF and WAV do. For both + + functions, the str_type parameter must be one of the SF_STR_* values + + defined above. + + On error, sf_set_string() returns non-zero while sf_get_string() + + returns NULL. + +/ + +typedef int function(SNDFILE* sndfile, int str_type, char* str) + pfsf_set_string; +typedef char* function(SNDFILE* sndfile, int str_type) pfsf_get_string; + +pfsf_set_string sf_set_string; +pfsf_get_string sf_get_string; + +// Functions for reading/writing the waveform data of a sound file. + +typedef sf_count_t function(SNDFILE* sndfile, void* ptr, sf_count_t bytes) + pfsf_read_raw; +typedef sf_count_t function(SNDFILE* sndfile, void* ptr, sf_count_t bytes) + pfsf_write_raw; + +pfsf_read_raw sf_read_raw; +pfsf_write_raw sf_write_raw; + +/+ + + Functions for reading and writing the data chunk in terms of frames. + + The number of items actually read/written = frames * number of channels. + + sf_xxxx_raw read/writes the raw data bytes from/to the file + + sf_xxxx_short passes data in the native short format + + sf_xxxx_int passes data in the native int format + + sf_xxxx_float passes data in the native float format + + sf_xxxx_double passes data in the native double format + + All of these read/write function return number of frames read/written. + +/ + +typedef sf_count_t function(SNDFILE* sndfile, short* ptr, sf_count_t frames) + pfsf_readf_short; +typedef sf_count_t function(SNDFILE* sndfile, short* ptr, sf_count_t frames) + pfsf_writef_short; + +typedef sf_count_t function(SNDFILE* sndfile, int* ptr, sf_count_t frames) + pfsf_readf_int; +typedef sf_count_t function(SNDFILE* sndfile, int* ptr, sf_count_t frames) + pfsf_writef_int; + +typedef sf_count_t function(SNDFILE* sndfile, float* ptr, sf_count_t frames) + pfsf_readf_float; +typedef sf_count_t function(SNDFILE* sndfile, float* ptr, sf_count_t frames) + pfsf_writef_float; + +typedef sf_count_t function(SNDFILE* sndfile, double* ptr, sf_count_t frames) + pfsf_readf_double; +typedef sf_count_t function(SNDFILE* sndfile, double* ptr, sf_count_t frames) + pfsf_writef_double; + +pfsf_readf_short sf_readf_short; +pfsf_writef_short sf_writef_short; +pfsf_readf_int sf_readf_int; +pfsf_writef_int sf_writef_int; +pfsf_readf_float sf_readf_float; +pfsf_writef_float sf_writef_float; +pfsf_readf_double sf_readf_double; +pfsf_writef_double sf_writef_double; + +/+ + + Functions for reading and writing the data chunk in terms of items. + + Otherwise similar to above. + + All of these read/write function return number of items read/written. + +/ + +typedef sf_count_t function(SNDFILE* sndfile, short* ptr, sf_count_t items) + pfsf_read_short; +typedef sf_count_t function(SNDFILE* sndfile, short* ptr, sf_count_t items) + pfsf_write_short; + +typedef sf_count_t function(SNDFILE* sndfile, int* ptr, sf_count_t items) + pfsf_read_int; +typedef sf_count_t function(SNDFILE* sndfile, int* ptr, sf_count_t items) + pfsf_write_int; + +typedef sf_count_t function(SNDFILE* sndfile, float* ptr, sf_count_t items) + pfsf_read_float; +typedef sf_count_t function(SNDFILE* sndfile, float* ptr, sf_count_t items) + pfsf_write_float; + +typedef sf_count_t function(SNDFILE* sndfile, double* ptr, sf_count_t items) + pfsf_read_double; +typedef sf_count_t function(SNDFILE* sndfile, double* ptr, sf_count_t items) + pfsf_write_double; + +pfsf_read_short sf_read_short; +pfsf_write_short sf_write_short; +pfsf_read_int sf_read_int; +pfsf_write_int sf_write_int; +pfsf_read_float sf_read_float; +pfsf_write_float sf_write_float; +pfsf_read_double sf_read_double; +pfsf_write_double sf_write_double; + +/+ + + Close the SNDFILE and clean up all memory allocations associated with this + + file. + + Returns 0 on success, or an error number. + +/ + +typedef int function(SNDFILE* sndfile) pfsf_close; +pfsf_close sf_close; + +/+ + + If the file is opened SFM_WRITE or SFM_RDWR, call fsync() on the file + + to force the writing of data to disk. If the file is opened SFM_READ + + no action is taken. + +/ + +typedef void function(SNDFILE* sndfile) pfsf_write_sync; +pfsf_write_sync sf_write_sync; + diff --git a/src/system/base.d b/src/system/base.d new file mode 100644 index 0000000..bdc3b7c --- /dev/null +++ b/src/system/base.d @@ -0,0 +1,305 @@ +/+ + + system/base.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module system.base; + +import timer; +import memory; +import d6502.base; + +import ui.sound; +import ui.inputevents; + +import system.io; +import system.video; +import iomem; +import video.base; +import system.peripheral; + +class System +{ + Timer timer; + Timer.Cycle deviceCycle; + AddressDecoder decoder; + SoftSwitchPage switches; + CpuBase cpu; + IOMem ioMem; + Peripherals peripherals; + + class Memory + { + PrimaryMem mainRam; + Rom mainRom; + VideoPages vidPages; + + this(ubyte[] romDump) + { + mainRam = new PrimaryMem(0x0000, 0xC000); + mainRam.debugName = "RAM"; + mainRom = new Rom(0xD000, 0x3000, + romDump[(length-12288)..length]); + mainRom.debugName = "ROM"; + vidPages.lores1 = new SliceMem(0x0400, 0x0400, mainRam); + vidPages.lores2 = new SliceMem(0x0800, 0x0400, mainRam); + vidPages.hires1 = new SliceMem(0x2000, 0x2000, mainRam); + vidPages.hires2 = new SliceMem(0x4000, 0x2000, mainRam); + } + + void reboot() + { + mainRam.reboot(); + vidPages.reboot(); + } + } + + Video video_; + Memory memory_; + IO io_; + + abstract IO newIO(); + abstract CpuBase newCpu(); + abstract Video newVideo(ubyte[] vidRom); + abstract IOMem newIOMem(); + abstract Peripherals newPeripherals(); + + this(ubyte[] romDump) + { + initTimer(); + initMemory(romDump); + initCpu(); + initIO(null); // XXX where is vidRom passed in? + decoder.nullRead = &video_.scanner.floatingBus; + + peripherals = newPeripherals(); + peripherals.install(cpu, decoder, memory_.mainRom); + ioMem.initialize(decoder, switches, timer, peripherals); + + input.onReset = &reset; + switches.setFloatingBus(&video_.scanner.floatingBus); + } + + void reboot() + { + cpu.reboot(); + deviceCycle.restart(); + memory_.reboot(); + ioMem.reboot(); + io_.reboot(); + peripherals.reboot(); + video_.reboot(); + } + + void initTimer() + { + // XXX constants? variables? + timer = new Timer(10_205, 1_020_484); + deviceCycle = + timer.startCycle(timer.primaryCounter.startLength * 2); + } + + void initMemory(ubyte[] romDump) + { + memory_ = new Memory(romDump); + decoder = new AddressDecoder(); + switches = new SoftSwitchPage(); + decoder.installSwitches(switches); + decoder.install(memory_.mainRam); + decoder.install(memory_.mainRom); + ioMem = newIOMem(); + } + + void initCpu() + { + cpu = newCpu(); + debug(disassemble) cpu.memoryName = &decoder.memoryReadName; + cpu.tick = &timer.tick; + timer.onPrimaryStop(&primaryStop); + cpu.memoryRead = &decoder.read; + cpu.memoryWrite = &decoder.write; + } + + void initIO(ubyte[] vidRom) + { + io_ = newIO(); + video_ = newVideo(vidRom); + } + + bool primaryStop() + { + cpu.stop(); + return true; + } + + void reset() + { + peripherals.reset(); + cpu.resetLow(); + } + + uint checkpoint() + { + return timer.primaryCounter.currentLength; + } + + uint sinceCheckpoint(uint cp) + { + uint currentLength = timer.primaryCounter.currentLength; + return ((currentLength == timer.primaryCounter.startLength) ? + cp : (cp - currentLength)); + } + + void execute() + { + cpu.run(true); + + soundCard.process(); + soundCard.start(); + + video_.signal.update(); + deviceCycle.restart(); + // XXX peripherals get notification + } +} + +class II : System +{ + import d6502.nmosundoc : NmosUndoc; + + int revision; + + this(ubyte[] romDump) + { + // XXX FIXME XXX + revision = int.max; + super(romDump); + } + + CpuBase newCpu() + { + return new NmosUndoc(); + } + + IO newIO() + { + return new IO_II(switches, timer, deviceCycle); + } + + Video newVideo(ubyte[] vidRom) + { + return new Video_II(switches, memory_.vidPages, timer, vidRom, + &io_.kbd.peekLatch, decoder); + } + + IOMem newIOMem() + { + return new IOMem(); + } + + Peripherals newPeripherals() + { + return new Peripherals_II(); + } +} + +import ioummu; + +class IIe : System +{ + import d6502.cmos : Cmos; + + AuxiliaryCard auxCard; + MMU mmu; + IOU iou; + + this(ubyte[] romDump) + { + // XXX if different or no aux card? + //auxMemory = new Memory(); + super(romDump); + } + + void reboot() + { + super.reboot(); + auxCard.reboot(); + mmu.reboot(); + } + + void reset() + { + auxCard.reset(); + mmu.reset(); + super.reset(); + } + + CpuBase newCpu() + { + // XXX this is enhanced + return new Cmos(); + } + + IO newIO() + { + return new IO_IIe(switches, timer, deviceCycle); + } + + Video newVideo(ubyte[] vidRom) + { + return new Video_IIe(switches, memory_.vidPages, timer, vidRom, + &io_.kbd.peekLatch, auxCard.vidPages); + } + + IOMem newIOMem() + { + return mmu.ioMem; + } + + Peripherals newPeripherals() + { + return new Peripherals_IIe(); + } + + void initMemory(ubyte[] romDump) + { + mmu = new MMU(); + mmu.ioMem = new IOMem_IIe(); + mmu.ioMem.setRom(romDump); + + super.initMemory(romDump); + + // XXX XXX XXX + // allow for different card from config + auxCard = new Extended80ColumnCard(); + + mmu.init(switches, auxCard, decoder, memory_.mainRam, + memory_.mainRom); + } + + void initIO(ubyte[] vidRom) + { + super.initIO(vidRom); + iou = new IOU(io_, video_.signal); + iou.initSwitches(switches); + mmu.initIO(video_.scanner, &io_.kbd.peekLatch); + } +} + diff --git a/src/system/io.d b/src/system/io.d new file mode 100644 index 0000000..f8802c9 --- /dev/null +++ b/src/system/io.d @@ -0,0 +1,158 @@ +/+ + + system/io.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module system.io; + +import memory; +import timer; + +import ui.inputevents; +import ui.sound; + +import device.annunciator; +import device.pushbutton; +import device.speaker; +import device.keyboard; +import device.paddle; + +class IO +{ + Annunciator annun; + Paddles paddles; + Pushbuttons buttons; + Speaker spkr; + Keyboard kbd; + + this(SoftSwitchPage switches, Timer timer, Timer.Cycle deviceCycle) + { + makeSpeaker(switches, timer, deviceCycle); + makeButtons(switches); + makeKeyboard(switches); + makePaddles(switches, timer); + makeAnnunciators(switches); + } + + void makeSpeaker(SoftSwitchPage switches, Timer timer, + Timer.Cycle deviceCycle) + { + if (soundCard.isActive) + { + spkr = new Speaker(); + spkr.initSwitches(switches); + // XXX reference to SoundCardYes is bad? + spkr.setTiming(timer.hertz, SoundCardYes.sampleFreq, deviceCycle); + soundCard.installSpeaker(spkr); + } + } + + void makeButtons(SoftSwitchPage switches) + { + buttons = new Pushbuttons(); + buttons.initSwitches(switches); + input.installButtons(buttons); + } + + void makeKeyboard(SoftSwitchPage switches) + { + kbd = newKeyboard(); + kbd.initSwitches(switches); + kbd.buttons = buttons; + input.installKeyboard(kbd); + } + + void makePaddles(SoftSwitchPage switches, Timer timer) + { + paddles = newPaddles(); + paddles.initSwitches(switches); + paddles.timer = timer; + input.installPaddles(paddles); + } + + void makeAnnunciators(SoftSwitchPage switches) + { + annun = newAnnunciators(); + annun.initSwitches(switches); + } + + void reboot() + { + annun.reboot(); + buttons.reboot(); + kbd.reboot(); + paddles.reboot(); + } + + abstract Keyboard newKeyboard(); + abstract Annunciator newAnnunciators(); + abstract Paddles newPaddles(); +} + +class IO_II : IO +{ + this(SoftSwitchPage switches, Timer timer, Timer.Cycle deviceCycle) + { + super(switches, timer, deviceCycle); + } + + Keyboard newKeyboard() + { + return new Keyboard_II(); + } + + Annunciator newAnnunciators() + { + return new Annunciator_II(); + } + + Paddles newPaddles() + { + return new Paddles_II(); + } +} + +class IO_IIe : IO +{ + this(SoftSwitchPage switches, Timer timer, Timer.Cycle deviceCycle) + { + super(switches, timer, deviceCycle); + } + + Keyboard newKeyboard() + { + return new Keyboard_IIe(); + } + + Annunciator newAnnunciators() + { + // NOTE: C058-C05F are handled by IOU + return new Annunciator(); + } + + Paddles newPaddles() + { + // XXX if unenhanced: + //return new Paddles_II(); + // XXX else: + return new Paddles(); + } +} + diff --git a/src/system/peripheral.d b/src/system/peripheral.d new file mode 100644 index 0000000..d63b174 --- /dev/null +++ b/src/system/peripheral.d @@ -0,0 +1,87 @@ +/+ + + system/peripheral.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module system.peripheral; + +import memory; +import d6502.base; + +import peripheral.base; +import peripheral.diskii; +import peripheral.langcard; +import peripheral.saturn128; + +class Peripherals +{ + Peripheral[8] cards; + + abstract void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom); + + void reboot() + { + for (int p = 0; p < 8; ++p) + { + if (cards[p] !is null) + cards[p].reboot(); + } + } + + void reset() + { + for (int s = 0; s < 8; ++s) + { + if (cards[s] !is null) + cards[s].reset(); + } + } +} + +class Peripherals_II : Peripherals +{ + void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom) + { + auto diskController = new Controller(); + diskController.checkFinalCycle = &cpu.checkFinalCycle; + cards[6] = diskController; // XXX + + auto langCard = new LanguageCard(); + langCard.implementation.init(decoder, &mainRom.read, &mainRom.write); + cards[0] = langCard; + + /+ + auto saturn = new Saturn128(); + saturn.init(decoder, &mainRom.read, &mainRom.write); + cards[0] = saturn; + +/ + } +} + +class Peripherals_IIe : Peripherals +{ + void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom) + { + auto diskController = new Controller(); + diskController.checkFinalCycle = &cpu.checkFinalCycle; + cards[6] = diskController; // XXX + } +} + diff --git a/src/system/video.d b/src/system/video.d new file mode 100644 index 0000000..7742ecf --- /dev/null +++ b/src/system/video.d @@ -0,0 +1,124 @@ +/+ + + system/video.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module system.video; + +import memory; +import timer; + +import ui.monitor; + +import video.base; +import video.scanner; +import video.signal; + +class Video +{ + Scanner scanner; + Signal signal; + Screen screen; + + this(SoftSwitchPage switches, VideoPages vidPages, Timer timer, + ubyte[] vidRom, ubyte delegate() peekLatch) + { + signal = newSignal(); + scanner = newScanner(); + + screen = monitor.screen; + screen.scanner = scanner; + + scanner.signal = signal; + scanner.drawFrame = &monitor.glDrawFrame; + scanner.kbdLatch = peekLatch; + scanner.init(timer); + scanner.installMemory(vidPages.lores1, vidPages.lores2, + vidPages.hires1, vidPages.hires2); + scanner.initSwitches(switches); + + signal.scanner = scanner; + signal.screen = screen; + signal.kbdLatch = peekLatch; + signal.init(timer, vidRom); + signal.initSwitches(switches); + } + + void reboot() + { + signal.reboot(); + scanner.reboot(); + } + + void forceFrame() + { + scanner.forceFrame(); + monitor.glDrawFrame(); + } + + abstract Scanner newScanner(); + abstract Signal newSignal(); +} + +class Video_II : Video +{ + Scanner_II scanner40; + + this(SoftSwitchPage switches, VideoPages vidPages, Timer timer, + ubyte[] vidRom, ubyte delegate() peekLatch, + AddressDecoder decoder) + { + super(switches, vidPages, timer, vidRom, peekLatch); + scanner40.decoder = decoder; + } + + Scanner newScanner() + { + scanner40 = new Scanner_II(); + return scanner40; + } + + Signal newSignal() + { + return new Signal_II(); + } +} + +class Video_IIe : Video +{ + this(SoftSwitchPage switches, VideoPages vidPages, Timer timer, + ubyte[] vidRom, ubyte delegate() peekLatch, VideoPages auxPages) + { + super(switches, vidPages, timer, vidRom, peekLatch); + scanner.installAuxMemory(auxPages.lores1, auxPages.lores2, + auxPages.hires1, auxPages.hires2); + } + + Scanner newScanner() + { + return new Scanner_IIe(); + } + + Signal newSignal() + { + return new Signal_IIe(); + } +} + diff --git a/src/timer.d b/src/timer.d new file mode 100644 index 0000000..8d072db --- /dev/null +++ b/src/timer.d @@ -0,0 +1,244 @@ +/+ + + timer.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module timer; + +class Timer +{ + class Cycle + { + int delta; + uint rollOver; + + this(uint maxVal) + { + rollOver = maxVal; + restart(); + } + + void restart() + { + delta = 0 - currentCounter.elapsed(); + } + + uint currentVal() + { + return (currentCounter.elapsed() + delta) % rollOver; + } + + void update() + { + delta = currentVal(); + } + } + + class Counter + { + bool delegate() expiry; + uint startLength, currentLength; + int ticks; + bool shouldContinue; + + this(uint start) + { + shouldContinue = true; + startLength = currentLength = ticks = start; + addCounter(this); + } + + this(uint start, bool delegate() expiration) + { + this(start); + initCounter(this); + expiry = expiration; + } + + final uint elapsed() + { + return currentLength - ticks; + } + + final void tick() + { + --ticks; + if (ticks == 0) + { + reset(); + } + } + + final void forceExpire() + { + ticks = 1; + tick(); + } + + final void discard() + { + expiry = &nullExpiry; + forceExpire(); + } + + private final void resume() + { + currentLength = ticks; + } + + private final bool expire() + { + ticks = currentLength = startLength; + return expiry(); + } + + private bool nullExpiry() { return false; } + } + + class DelayedCounter : Counter + { + uint realStart; + bool delegate() realExpiry; + + this(uint start, bool delegate() expiration, uint delay) + { + realStart = start; + realExpiry = expiration; + super(delay, &becomeReal); + } + + private bool becomeReal() + { + ticks = currentLength = startLength = realStart; + expiry = realExpiry; + bool retval = expiry(); + initCounter(this); + return retval; + } + } + + Cycle[] cycles; + Counter[] counters; + Counter primaryCounter, currentCounter; + uint hertz; + + this(uint primaryStart, uint hz) + { + hertz = hz; + cycles.length = 10; + counters.length = 10; + cycles.length = 0; + counters.length = 0; + currentCounter = primaryCounter = new Counter(primaryStart); + } + + final void onPrimaryStop(bool delegate() expiration) + { + primaryCounter.expiry = expiration; + } + + Cycle startCycle(uint maxVal) + { + cycles.length = cycles.length + 1; + cycles[length-1] = new Cycle(maxVal); + return cycles[length-1]; + } + + void tick() + { + currentCounter.tick(); + } + + private void deleteCounters() + { + int numCounters = counters.length; + int lastCounter; +main: for (int counter = 0; counter < counters.length; ++counter) + { + lastCounter = counter; + while (!counters[counter].shouldContinue) + { + numCounters--; + if (++counter >= counters.length) break main; + } + currentCounter = counters[lastCounter] = counters[counter]; + } + if (numCounters < counters.length) + { + counters.length = numCounters; + } + } + + private void addCounter(Counter newCounter) + { + counters.length = counters.length + 1; + counters[length-1] = newCounter; + } + + private void initCounter(Counter newCounter) + { + if (newCounter.ticks < currentCounter.ticks) + { + reset(newCounter); + } + else + { + newCounter.ticks += currentCounter.elapsed(); + } + } + + private void reset(Counter newCounter = null) + { + // update cycle counts + for (int cycle = 0; cycle < cycles.length; ++cycle) + { + cycles[cycle].update(); + } + + // update counter counts + for (int counter = 0; counter < counters.length; ++counter) + { + if (counters[counter] !is currentCounter && + counters[counter] !is newCounter) + counters[counter].ticks -= currentCounter.elapsed(); + } + + // check for expired counters + for (int counter = 0; counter < counters.length; ++counter) + { + if (counters[counter].ticks <= 0) + counters[counter].shouldContinue = counters[counter].expire(); + else + counters[counter].resume(); + } + + //delete counters that should be deleted + deleteCounters(); + + // set current counter + for (int counter = 0; counter < counters.length; ++counter) + { + if (counters[counter].ticks < currentCounter.ticks) + currentCounter = counters[counter]; + } + } + +} + diff --git a/src/twoapple.d b/src/twoapple.d new file mode 100644 index 0000000..f0332dc --- /dev/null +++ b/src/twoapple.d @@ -0,0 +1,115 @@ +//@ ////////////////////////////////////////////////////////////////////////// +//@ Copyright: 2007 Gerald Stocker +//@ +//@ This file is part of Twoapple. +//@ +//@ Twoapple 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 2 of the License, or +//@ (at your option) any later version. +//@ +//@ Twoapple 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 Twoapple; if not, write to the Free Software +//@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +//@ ////////////////////////////////////////////////////////////////////////// + +import std.stdio; + +import d6502.base; +import timer; +import memory; +import system.base; +import ui.monitor; +import ui.sound; +import ui.inputevents; + +import gthread.Thread; +import gtk.GtkD; +import glgdk.GLdInit; +import gtkc.gdktypes; +import gtkc.gtktypes; + +import peripheral.base; +import peripheral.diskii; +import peripheral.langcard; +import peripheral.saturn128; + +class TestSystem : II +{ + this(ubyte[] romDump) + { + super(romDump); + } + + void setRom(ubyte[] rom_data) + { + uint rom_len = rom_data.length; + memory_.mainRom.data_[0..12288] = rom_data[(rom_len - 12288)..rom_len]; + } +} + +import std.file; +import std.string; +import device.speaker; +import ui.mainwindow; +import video.laz_engine; + +// XXX Only needed for initializing peripheralStatus +import gtk.VBox; +import peripheral.base; + +void main(string[] args) +{ + // Init GTK + Thread.init(null); + GtkD.init(args); + GLdInit.init(null, null); + + // open config + + input = new Input(); + monitor = new Monitor(); + auto screen = new LazEngine(); + monitor.installScreen(screen); + + // XXX should make it so this can happen after system? + peripheralStatus = new VBox(false, 3); + + appWindow = new TwoappleMainWindow(); + + // Get ROM file + TwoappleFile romFile = TwoappleFilePicker.open("ROM file", &checkRomFile); + if (romFile is null) return; + + System sys; + if ((args.length > 1) && (args[1] == "--iie")) + sys = new IIe(cast(ubyte[])std.file.read(romFile.fileName)); + else + sys = new II(cast(ubyte[])std.file.read(romFile.fileName)); + appWindow.initSystem(sys); + // XXX hack + appWindow.configChanged = true; + debug(disassemble) + { + input.pauseButton.setActive(true); + } + appWindow.mainLoop(); + + // save config +} + +string checkRomFile(TwoappleFile checkFile) +{ + ulong fsize = checkFile.fileSize(); + // XXX allow 12288 for II/II+ ? + if (fsize >= 20480 && fsize <= 32768) + return null; + else + return "Invalid ROM file"; +} + diff --git a/src/ui/inputevents.d b/src/ui/inputevents.d new file mode 100644 index 0000000..be8f6a9 --- /dev/null +++ b/src/ui/inputevents.d @@ -0,0 +1,281 @@ +/+ + + ui/inputvents.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module ui.inputevents; + +import std.stdio; + +import gtkglc.glgdktypes; +import gtk.Widget; +import gtkc.gtktypes; +import gtk.Image; +import gtk.ToggleToolButton; +import gtk.GtkD; +import gtk.Timeout; + +import derelict.sdl.sdl; + +import device.keyboard; +import device.paddle; +import device.pushbutton; + +import ui.textinput; + +static import host; + +static this() +{ + if (host.SDL) + { + if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) == -1) + { + writefln("%s", std.string.toString(SDL_GetError())); + return; + } + + int numJoysticks = SDL_NumJoysticks(); + if (numJoysticks == 0) return; + + joysticks.length = numJoysticks; + SDL_JoystickEventState(SDL_ENABLE); + + SDL_Joystick* device; + for (int i = 0; i < numJoysticks; ++i) + { + device = SDL_JoystickOpen(i); + if (device is null) + { + writefln("%s", std.string.toString(SDL_GetError())); + continue; + } + writefln("Joystick %s: %s", i, + std.string.toString(SDL_JoystickName(i))); + + joysticks[i] = new Joystick(device, + SDL_JoystickNumAxes(device), + SDL_JoystickNumButtons(device)); + } + if (device is null) + joysticks.length = 0; + } +} + +Input input; + +private: + +Joystick[] joysticks; + +class Joystick +{ + bool isActive; + + int deviceNum; + SDL_Joystick* device; + + int[] pdlForAxis; + int[] pbForButton; + + this(SDL_Joystick* dev, int numAxes, int numButtons) + { + device = dev; + pdlForAxis.length = numAxes; + pbForButton.length = numButtons; + for (int i = 0; i < numAxes; ++i) + pdlForAxis[i] = -1; + for (int i = 0; i < numButtons; ++i) + pbForButton[i] = -1; + } +} + +class Input +{ + SDL_Event sEvent; + Timeout timeout; + + Keyboard kbd; + Pushbuttons buttons; + Paddles paddles; + TextInput textIn; + ToggleToolButton pauseButton; + void delegate() onReset; + + this() + { + pauseButton = new ToggleToolButton("gtk-media-pause"); + // XXX read from config / allow changing + for (int i = 0; i < joysticks.length; ++i) + { + // By default, ignore all but the first two joysticks + if (i > 1) break; + + joysticks[i].pdlForAxis[0] = i * 2; + joysticks[i].pdlForAxis[1] = i * 2 + 1; + joysticks[i].pbForButton[0] = i * 2; + joysticks[i].pbForButton[1] = 1; + + // XXX hackish way of setting "pause" button + if (joysticks[i].pbForButton.length > 2) + joysticks[i].pbForButton[length - 1] = -2; + } + } + + void installButtons(Pushbuttons btns) + { + buttons = btns; + } + + void installKeyboard(Keyboard keyboard) + { + kbd = keyboard; + } + + void installPaddles(Paddles pdl) + { + paddles = pdl; + } + + gboolean onKeyPress(GdkEventKey* evt, Widget w) + { + static const int KEY_PAUSE = 65299; // Pause/Break + static const int KEY_ESCAPE = 65307; + + // Let someone else handle ALT- presses + if ((evt.state & GdkModifierType.MOD1_MASK) != 0) return false; + + if (textIn !is null) + { + if (evt.keyval == KEY_ESCAPE) + { + stopTextInput(); + return true; + } + else + return false; + } + + if (evt.keyval == KEY_PAUSE) + { + pauseButton.setActive(!(pauseButton.getActive())); + return true; + } + + if (pauseButton.getActive()) return false; + + bool ctrl = ((evt.state & GdkModifierType.CONTROL_MASK) != 0); + return kbd.handlePress(evt.keyval, ctrl, evt.hardwareKeycode); + } + + gboolean onKeyRelease(GdkEventKey* evt, Widget w) + { + static const int KEY_RESET = 65481; // F12 + + // Let someone else handle ALT- releases + if ((evt.state & GdkModifierType.MOD1_MASK) != 0) return false; + + if (evt.keyval == KEY_RESET) + { + onReset(); + if (pauseButton.getActive()) + { + pauseButton.setActive(false); + } + return true; + } + + if (pauseButton.getActive()) return false; + + bool ctrl = ((evt.state & GdkModifierType.CONTROL_MASK) != 0); + return kbd.handleRelease(evt.keyval, ctrl, evt.hardwareKeycode); + } + + void onJoystickEvent() + { + static const float scale = 65535.0 / 2760.0; + static const int shift = 32768; + bool buttonDown = false; + switch(sEvent.type) + { + case SDL_JOYAXISMOTION: + Joystick joy = joysticks[sEvent.jaxis.which]; + int axis = sEvent.jaxis.axis; + int pdl = joy.pdlForAxis[axis]; + if (pdl != -1) + { + int value = + cast(int)(((sEvent.jaxis.value) + shift) / scale); + paddles.update(pdl, value); + } + break; + case SDL_JOYBUTTONDOWN: + buttonDown = true; + case SDL_JOYBUTTONUP: + Joystick joy = joysticks[sEvent.jbutton.which]; + int pb = joy.pbForButton[sEvent.jbutton.button]; + if ((pb == -2) && buttonDown) + pauseButton.setActive(!pauseButton.getActive()); + else if (pb >= 0) + { + buttons.update(pb, buttonDown); + } + break; + default: + break; + } + } + + void startTextInput(string filename) + { + textIn = new TextInput(filename, &stopTextInput); + kbd.onReadLatch = &textIn.read; + kbd.onClearStrobe = &textIn.advancePos; + } + + void stopTextInput() + { + kbd.onReadLatch = null; + kbd.onClearStrobe = null; + delete textIn; + } + + void processEvents() + { + kbd.processPresses(); + kbd.processReleases(); + + while(GtkD.eventsPending()) + { + GtkD.mainIteration(); + } + processJoystickEvents(); + } + + bool processJoystickEvents() + { + while (SDL_PollEvent(&sEvent)) + { + onJoystickEvent(); + } + return true; + } +} + diff --git a/src/ui/mainwindow.d b/src/ui/mainwindow.d new file mode 100644 index 0000000..036749e --- /dev/null +++ b/src/ui/mainwindow.d @@ -0,0 +1,454 @@ +/+ + + ui/mainwindow.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module ui.mainwindow; + +import std.string; + +import gtk.MainWindow; +import gtkc.gtktypes; + +import ui.inputevents; +import ui.sound; +import ui.monitor; +import host; +import system.base; + +TwoappleMainWindow appWindow; + +class TwoappleMainWindow : MainWindow +{ + import gtk.MenuBar; + import gtk.Widget; + import gdk.Event; + import gtk.Toolbar; + import gtk.ToggleToolButton; + import gtk.SeparatorToolItem; + import gtk.ToolButton; + import gtk.SeparatorToolItem; + import gtk.MenuItem; + import gtk.AccelGroup; + import gtk.VBox; + import gtk.Toolbar; + import gtk.Label; + import gtk.ToolItem; + import gtk.Alignment; + import peripheral.base; + + System system; + Label speedLabel; + ToolItem speedItem; + + // XXX FIXME XXX + // the system and its ui components need to be separated + // (so system can switch from ii to iie or whatever, + // without affecting listeners/pausers/etc. that are connected to + // the mainwindow) + this() + { + super("Twoapple"); + + addOnKeyPress(&input.onKeyPress); + addOnKeyRelease(&input.onKeyRelease); + + auto mainBox = new VBox(false, 3); + + auto menuBar = new MenuBar(); + auto menu = menuBar.append("_Menu"); + auto item = new MenuItem(&onMenuActivate, "_Read text file", + "menu.read", true, null, 'r'); + menu.append(item); + + auto toolBar = new Toolbar(); + toolBar.insert(input.pauseButton, -1); + auto bootButton = new ToolButton(null, "Boot"); + toolBar.insert(bootButton, -1); + bootButton.addOnClicked(&onBootClicked); + toolBar.insert(new SeparatorToolItem()); + auto monoButton = new ToggleToolButton(); + monoButton.setLabel("Mono"); + monoButton.addOnToggled(&onMonoToggle); + toolBar.insert(monoButton, -1); + auto toolSpace = new SeparatorToolItem(); + toolSpace.setDraw(false); + toolSpace.setExpand(true); + toolBar.insert(toolSpace, -1); + speedLabel = new Label(" 0%"); + speedItem = new ToolItem(); + speedItem.add(speedLabel); + toolBar.insert(speedItem, -1); + toolBar.setStyle(GtkToolbarStyle.TEXT); + + mainBox.packStart(menuBar, false, false, 0); + mainBox.packStart(toolBar, false, false, 0); + mainBox.packStart(monitor, false, false, 0); + mainBox.packStart(peripheralStatus, false, false, 0); + + add(mainBox); + setResizable(0); + showAll(); + } + + void onBootClicked(ToolButton b) + { + reboot = true; + } + + void onMonoToggle(ToggleToolButton b) + { + monitor.screen.isColor = (!b.getActive()); + if (input.pauseButton.getActive()) + { + system.video_.forceFrame(); + } + } + + void initSystem(System sys) + { + showAll(); + system = sys; + } + + void onMenuActivate(MenuItem menuItem) + { + TwoappleFile checkFile = + TwoappleFilePicker.open("text file"); + if (checkFile !is null) + input.startTextInput(checkFile.fileName); + } + + int windowDelete(Event event, Widget widget) + { + stayOpen = false; + soundCard.pause(); + return super.windowDelete(event, widget); + } + + bool configChanged; + bool runOnce; + bool stayOpen; + bool reboot; + + import std.stream; + void mainLoop() + { + bool shouldRun; + uint willElapse, didElapse; + float speedPercent; + char[] speedString = new char[10]; + stayOpen = true; + do + { + if (configChanged) + { + // apply config + configChanged = false; + debug(disassemble) + { + reboot = false; + } + else + { + reboot = true; + } + } + + if (reboot) + { + system.reboot(); + system.reset(); + input.pauseButton.setSensitive(true); + input.pauseButton.setActive(false); + host.delay.reset(); + reboot = false; + } + + if (input.pauseButton.getActive()) + shouldRun = runOnce; + else + shouldRun = true; + + if (shouldRun) + { + willElapse = system.checkpoint(); + system.execute(); + didElapse = system.sinceCheckpoint(willElapse); + input.processEvents(); + // XXX do something about typeahead? + if (!runOnce) + { + host.delay.sleep(); + speedPercent = host.delay.checkSpeedPercent(didElapse); + if (speedPercent > -1.0) + { + speedString = std.string.sformat(speedString, + "% 5d%%", cast(int)(speedPercent * 100)); + speedLabel.setText(speedString); + } + } + runOnce = false; + } + else + { + input.processEvents(); + // XXX time says "Paused" or something + if (!(input.pauseButton.getActive())) + host.delay.reset(); + else + host.delay.nap(); + } + } + while (stayOpen) + } +} + +class TwoappleFile +{ + import std.c.linux.linux; + import std.file; + import std.path; + + string fileName; + char* fileNameZ; + + this(string fname) + { + assert(isabs(fname) != 0); + fileName = fname; + fileNameZ = std.string.toStringz(fname); + } + + string folder() + { + return getDirName(fileName); + } + + string baseName() + { + string base = getName(fileName); + if (base is null) return getBaseName(fileName); + else return getBaseName(base); + } + + bool canRead() + { + return !(access(fileNameZ, 4)); + } + + bool canWrite() + { + return !(access(fileNameZ, 2)); + } + + bool canCreate() + { + return !(access(std.string.toStringz(folder()), 2)); + } + + bool exists() + { + return (std.file.exists(fileName) != 0); + } + + uint fileSize() + { + return getSize(fileName); + } +} + +class TwoappleError +{ + import gtk.Window; + import gtk.MessageDialog; + + static void show(Window parent, string msg) + { + scope md = new MessageDialog(parent, GtkDialogFlags.MODAL, + MessageType.ERROR, ButtonsType.CLOSE, msg); + md.setTitle("Error"); + md.run(); + md.hide(); + md.destroy(); + } + + static void show(string msg) + { + soundCard.pause(); + show(appWindow, msg); + host.delay.reset(); + soundCard.resume(); + } +} + +class TwoappleFilePicker +{ + import std.stdio; + import gtk.FileChooserDialog; + + static string[string] lastFolder; + + static TwoappleFile saveAs(string type, string folder, string name) + { + soundCard.pause(); + + scope fcd = new FileChooserDialog("Save " ~ type, appWindow, + FileChooserAction.SAVE); + + scope chooser = fcd.getFileChooser(); + chooser.setCurrentFolder(folder); + chooser.setCurrentName(name); + + TwoappleFile file; + while (true) + { + if (fcd.run() != ResponseType.GTK_RESPONSE_OK) break; + file = new TwoappleFile(chooser.getFilename()); + if (file.exists()) + { + if (!file.canWrite()) + { + TwoappleError.show(fcd, "File is read-only"); + file = null; + } + } + else + { + if (!file.canCreate()) + { + TwoappleError.show(fcd, "Directory is read-only"); + file = null; + } + } + if (file !is null) break; + } + + fcd.hide(); + fcd.destroy(); + + host.delay.reset(); + soundCard.resume(); + + return file; + } + + static TwoappleFile open(string type, string delegate(TwoappleFile) dg) + { + soundCard.pause(); + + scope fcd = new FileChooserDialog("Open " ~ type, appWindow, + FileChooserAction.OPEN); + scope chooser = fcd.getFileChooser(); + + if (type in lastFolder) + chooser.setCurrentFolder(lastFolder[type]); + + TwoappleFile file; + while(true) + { + if (fcd.run() != ResponseType.GTK_RESPONSE_OK) break; + file = new TwoappleFile(chooser.getFilename()); + if (!file.canRead()) + { + TwoappleError.show(fcd, "File cannot be read"); + file = null; + } + else + { + string msg = dg(file); + if (msg !is null) + { + TwoappleError.show(fcd, msg); + file = null; + } + } + if (file !is null) + { + lastFolder[type] = file.folder(); + break; + } + } + + fcd.hide(); + fcd.destroy(); + + host.delay.reset(); + soundCard.resume(); + + return file; + } + + static TwoappleFile open(string type, string function(TwoappleFile) func) + { + return open(type, + delegate string(TwoappleFile checkFile) + { return func(checkFile); }); + } + + static TwoappleFile open(string type) + { + return open(type, + delegate string(TwoappleFile checkFile) + { return null; }); + } +} + +class TwoappleDialog +{ + import gtk.MessageDialog; + + static int run(string title, string msg, string[] buttonText, + bool hasDefault = true) + { + soundCard.pause(); + + scope md = new MessageDialog(appWindow, GtkDialogFlags.MODAL, + MessageType.WARNING, ButtonsType.NONE, msg); + md.setTitle(title); + + int response; + for (int b = 0; b < buttonText.length; ++b) + { + if ((b == 0) && hasDefault) + response = ResponseType.GTK_RESPONSE_OK; + else if (b == (buttonText.length - 1)) + response = ResponseType.GTK_RESPONSE_CANCEL; + else + response = b; + md.addButton(buttonText[b], response); + } + + response = md.run(); + md.hide(); + md.destroy(); + + if (response == ResponseType.GTK_RESPONSE_OK) + response = 0; + else if ((response == ResponseType.GTK_RESPONSE_CANCEL) || + (response == ResponseType.GTK_RESPONSE_DELETE_EVENT)) + response = buttonText.length - 1; + + host.delay.reset(); + soundCard.resume(); + + return response; + } +} diff --git a/src/ui/monitor.d b/src/ui/monitor.d new file mode 100644 index 0000000..abe8188 --- /dev/null +++ b/src/ui/monitor.d @@ -0,0 +1,93 @@ +/+ + + ui/monitor.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +private import gtk.DrawingArea; +private import glgtk.GLCapability; +private import glgdk.GLDrawable; +private import glgdk.GLConfig; +private import glgdk.GLContext; +private import gtkglc.glgdktypes; +private import gtk.Widget; +private import gtk.ToggleToolButton; + +import video.base; + +Monitor monitor; + +class Monitor : DrawingArea +{ + mixin GLCapability; + + Screen screen; + + this() + { + setGLCapability(new GLConfig( + GLConfigMode.MODE_RGB | GLConfigMode.MODE_DOUBLE, + GLConfigMode.MODE_RGB)); + } + + void installScreen(Screen screen_) + { + screen = screen_; + setSizeRequest(screen.width, screen.height * 2); + } + + bool initGL() + { + resizeGL(null); + + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_FOG); + glDisable(GL_LIGHTING); + glDisable(GL_LOGIC_OP); + glDisable(GL_STENCIL_TEST); + glDisable(GL_TEXTURE_1D); + glDisable(GL_TEXTURE_2D); + + glPixelZoom(1.0, -2.0); + + return true; + } + + bool drawGL(GdkEventExpose* event = null) + { + glRasterPos2i(-1, 1); + glDrawPixels(screen.width, screen.height, GL_RGB, + GL_UNSIGNED_SHORT_5_6_5, screen.data); + return true; + } + + bool resizeGL(GdkEventConfigure* event = null) + { + glViewport(0, 0, screen.width, screen.height * 2); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + return true; + } +} + diff --git a/src/ui/sound.d b/src/ui/sound.d new file mode 100644 index 0000000..419f4d9 --- /dev/null +++ b/src/ui/sound.d @@ -0,0 +1,209 @@ +/+ + + ui/sound.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module ui.sound; + +import std.stdio; +import std.c.string; + +import derelict.sdl.sdl; +import derelict.util.exception; + +import timer; +import device.speaker; +import ui.inputevents; + +static import host; + +static this() +{ + soundCard = new SoundCardNo(); + if (host.SDL) + { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) + { + writefln("%s", std.string.toString(SDL_GetError())); + return; + } + auto checkCard = new SoundCardYes(); + if (checkCard.isActive) + soundCard = checkCard; + } + host.delay.soundCardActive = &soundCard.getIsActive; + host.delay.soundCardHasEnoughData = &soundCard.hasEnoughData; +} + +SoundCard soundCard; + +private: + +class SoundCard +{ + bool isActive; + + // XXX hack for fast mode testing + bool speedyMode; + + bool getIsActive() + { + return isActive; + } + + abstract void installSpeaker(Speaker spkr); + abstract void start(); + abstract void pause(); + abstract void resume(); + abstract void process(); + abstract bool hasEnoughData(); +} + +class SoundCardNo : SoundCard +{ + this() + { + isActive = false; + } + + void installSpeaker(Speaker spkr) {} + void start() {} + void pause() {} + void resume() {} + void process() {} + bool hasEnoughData() { return true; } // XXX Throw exception? +} + +class SoundCardYes : SoundCard +{ + SDL_AudioSpec audioRequest; + short[] xferBuffer; + short[] zeroBuffer; + uint xferWriteIndex, xferReadIndex; + Timer.Cycle soundCycle; + Speaker speaker; + + static const int samplesPerCallback = 1024; + static const int sampleFreq = 44100; + + this() + { + audioRequest.freq = sampleFreq; + audioRequest.format = AUDIO_S16SYS; + audioRequest.channels = 1; + audioRequest.samples = samplesPerCallback; + audioRequest.callback = &audioCallback; + audioRequest.userdata = cast(void*)this; + + if (SDL_OpenAudio(&audioRequest, null) == -1) + { + writefln("%s", std.string.toString(SDL_GetError())); + return; + } + + xferBuffer.length = 8192; + zeroBuffer.length = samplesPerCallback; + isActive = true; + } + + void installSpeaker(Speaker spkr) + { + speaker = spkr; + } + + void start() + { + SDL_PauseAudio(0); + } + + void pause() + { + SDL_PauseAudio(1); + SDL_LockAudio(); + } + + void resume() + { + SDL_UnlockAudio(); + SDL_PauseAudio(0); + } + + void process() + { + speaker.update(); + + if (!speedyMode) // XXX + { + SDL_LockAudio(); + + int inLength = speaker.mainIndex; + short* inBuffer = speaker.mainBuffer.ptr; + + // truncate xfer buffer if it would overflow + if (xferWriteIndex + inLength > xferBuffer.length) + { + memmove(xferBuffer.ptr, xferBuffer.ptr + xferReadIndex, + (xferBuffer.length - xferReadIndex) * 2); + xferWriteIndex -= xferReadIndex; + xferReadIndex = 0; + } + + memcpy(xferBuffer.ptr + xferWriteIndex, inBuffer, + inLength * 2); + xferWriteIndex += inLength; + + SDL_UnlockAudio(); + } + finishProcessing(); + } + + void finishProcessing() + { + speaker.clearBuffer(); + } + + bool hasEnoughData() + { + if (speedyMode) return false; // XXX + SDL_LockAudio(); + uint bufLen = xferWriteIndex - xferReadIndex; + SDL_UnlockAudio(); + return bufLen > samplesPerCallback; + } + + void fillAudio(Uint8* stream, int len) + { + int available = (xferWriteIndex - xferReadIndex) * 2; + int readLen = (available > len) ? len : available; + memcpy(stream, xferBuffer.ptr + xferReadIndex, readLen); + if (input.pauseButton.getActive()) + { + if (len > readLen) + memcpy(stream + readLen, zeroBuffer.ptr, (len - readLen)); + } + xferReadIndex += (readLen / 2); + } +} + +extern(C) void audioCallback(void* userdata, Uint8* stream, int len) +{ + (cast(SoundCardYes)userdata).fillAudio(stream, len); +} + diff --git a/src/ui/textinput.d b/src/ui/textinput.d new file mode 100644 index 0000000..2270e25 --- /dev/null +++ b/src/ui/textinput.d @@ -0,0 +1,79 @@ +/+ + + ui/textinput.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module ui.textinput; + +import std.stream; + +import gtkglc.glgdktypes; +import gtk.Widget; + +class TextInput +{ + File file; + int pos; + string line; + void delegate() onFinish; + bool eol; + + this(string filename, void delegate() finished) + { + onFinish = finished; + file = new File(filename); + } + + ~this() + { + delete file; + } + + bool getLine() + { + if (file.eof()) + { + onFinish(); + return false; + } + line = file.readLine() ~ x"0D"; + pos = 0; + return true; + } + + ubyte read() + { + if (line is null) + { + if (!getLine()) + return 0; + } + return cast(ubyte)line[pos]; + } + + void advancePos() + { + if (line is null) return; + if (++pos >= line.length) + { + getLine(); + } + } +} diff --git a/src/video/base.d b/src/video/base.d new file mode 100644 index 0000000..db24f32 --- /dev/null +++ b/src/video/base.d @@ -0,0 +1,98 @@ +/+ + + video/base.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +module video.base; + +import memory; + +enum Mode +{ + TEXT, + LORES, + HIRES +} + +class ScannerBase +{ + abstract ubyte* getData(uint vidClock); + abstract ubyte* getData(uint line, uint col); + abstract ubyte* getData80(uint vidClock); + abstract ubyte* getData80(uint line, uint col); + abstract bool shouldUpdate(); + abstract uint currentLine(); + abstract uint currentCol(); + abstract Mode getMode(); + abstract bool checkColorBurst(); + abstract ubyte floatingBus(ushort addr); + abstract void page2SwitchOn(); + abstract void page2SwitchOff(); + abstract void hiresSwitchOn(); + abstract void hiresSwitchOff(); +} + +class PatternGenerator +{ + ScannerBase scanner; + abstract void initPatterns(); + abstract void initPatterns(ubyte[] rom); + abstract void update(ubyte* signal, int scanLine, int startCol, + uint len); + abstract void update80(ubyte* signal, int scanLine, int startCol, + uint len); +} + +class SignalBase +{ + abstract void update(); + abstract void update(Mode mode, bool col80, bool colorBurst, + int startLine, int stopLine, int startCol, int stopCol); + abstract void dHGRChange(bool newdHGR); +} + +class Screen +{ + ushort* data; + int width, height; + ScannerBase scanner; + + bool isColor; + + abstract void draw(bool col80, bool colorBurst, ubyte* bitmap, + int scanLine, int scanStart, int scanEnd); +} + +struct VideoPages +{ + DataMem lores1; + DataMem lores2; + DataMem hires1; + DataMem hires2; + + void reboot() + { + lores1.reboot(); + lores2.reboot(); + hires1.reboot(); + hires2.reboot(); + } +} + diff --git a/src/video/laz_engine.d b/src/video/laz_engine.d new file mode 100644 index 0000000..922d7d7 --- /dev/null +++ b/src/video/laz_engine.d @@ -0,0 +1,214 @@ +/+ + + video/laz_engine.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import video.base; + +private: + +template Tuple(E...) { alias E Tuple; } + +enum Color +{ + Black, + Red, + Brown, + Orange, + DarkGreen, + DarkGray, + Green, + Yellow, + DarkBlue, + Purple, + LightGray, + Pink, + MediumBlue, + LightBlue, + Aqua, + White +} + +public: + +class LazEngine : Screen +{ + ushort data_[562 * 192]; + + ushort[1][3] monoPalettes = [ + [0x03E0], // green + [0xFAA3], // amber + [0xFFFF] // white + ]; + ushort* monoPalette; + + mixin("static final ushort[32][8] colorPalettes = " + ~ import("palette_5_6_5")); + ushort* colorPalette; + + mixin("static final uint[32][4][3] schemes = " + ~ import("laz_scheme")); + uint[32]* scheme; + + mixin("static final uint[32][2][2] sharp_table = " + ~ import("laz_sharp")); + + this() + { + width = 562; + height = 192; + data = data_.ptr; + monoPalette = monoPalettes[0].ptr; + colorPalette = colorPalettes[0].ptr; + scheme = schemes[0].ptr; + isColor = true; + } + + void monochrome(ubyte* bitmap, int scanLine, int scanStart, int scanEnd, + ushort color_5_6_5) + { + uint dataPtr = (scanLine * width) + scanStart * 14; + uint bitmapPtr = 0; + uint bits, bitPos; + for (int scan = scanStart; scan <= scanEnd; ++scan) + { + foreach(mapByte; Tuple!(0, 1)) + { + bits = bitmap[bitmapPtr++]; + bitPos = 0b01000000; + foreach (mapBit; Tuple!(0, 1, 2, 3, 4, 5, 6)) + { + if (bits & bitPos) + data[dataPtr++] = color_5_6_5; + else + data[dataPtr++] = 0; + bitPos >>= 1; + } + } + } + } + + void draw(bool col80, bool colorBurst, ubyte* bitmap, int scanLine, + int scanStart, int scanEnd) + { + if (!isColor || !colorBurst) + { + monochrome(((scanStart == 0) ? bitmap : bitmap + 1), + scanLine, scanStart, scanEnd, + (isColor ? colorPalette[15] : monoPalette[0])); + } + else + { + polychrome(col80, bitmap, scanLine, scanStart, scanEnd); + } + } + + void polychrome(bool col80, ubyte* bitmap, int scanLine, int scanStart, + int scanEnd) + { + uint colStart = scanStart * 14; + uint colEnd = scanEnd * 14 + 13; + uint colCorrection = (col80 ? 0 : 3); + bool finishCols; + + uint bitmapPtr = 0; + uint dataPtr = (scanLine * width) + colStart; + + uint colorIndex, paletteIndex; + uint prevBit, nextBit; + uint nextBitTest = 0b00010000; + uint nextBitShift = 4; + + ubyte bits = bitmap[bitmapPtr]; + + if (scanStart == 0) + { + colorIndex = (bits >> 5) & 0b11; + } + else + { + colorIndex = (bits << 2) & 0b11111; + bits = bitmap[++bitmapPtr]; + colorIndex |= (bits >> 5 & 0b11); + } + nextBit = (bits & nextBitTest) >> nextBitShift; + + if (colEnd > 556) + { + finishCols = true; + colEnd = 556; + } + colEnd += colCorrection; + + for (uint col = colStart + colCorrection; col <= colEnd; ++col) + { + nextBitTest >>= 1; + --nextBitShift; + if (nextBitTest == 0) + { + nextBitTest = 0b01000000; + nextBitShift = 6; + bits = bitmap[++bitmapPtr]; + } + prevBit = colorIndex >> 4; + colorIndex = ((colorIndex << 1) | nextBit) & 0b11111; + nextBit = (bits & nextBitTest) >> nextBitShift; + paletteIndex = scheme[col & 0b11][colorIndex]; + if (paletteIndex > 0x80) + { + switch (sharp_table[prevBit][nextBit][colorIndex]) + { + case 0: + paletteIndex = Color.Black; + case 15: + paletteIndex = Color.White; + case 99: + paletteIndex &= 0x7F; + } + } + data[dataPtr++] = colorPalette[paletteIndex]; + } + + if (finishCols) + { + for (uint col = 557 + colCorrection; col <= 561 + colCorrection; + ++col) + { + prevBit = colorIndex >> 4; + colorIndex = (colorIndex << 1) & 0b11111; + paletteIndex = scheme[col & 0b11][colorIndex]; + if (paletteIndex > 0x80) + { + switch (sharp_table[prevBit][nextBit][colorIndex]) + { + case 0: + paletteIndex = Color.Black; + case 15: + paletteIndex = Color.White; + case 99: + paletteIndex &= 0x7F; + } + } + data[dataPtr++] = colorPalette[paletteIndex]; + } + } + } +} + diff --git a/src/video/offsets.d b/src/video/offsets.d new file mode 100644 index 0000000..1c9d7c8 --- /dev/null +++ b/src/video/offsets.d @@ -0,0 +1,171 @@ +/+ + + video/offsets.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import video.base : Mode; + +static this() +{ + hCounter = new HorizontalCounter(); + vCounter = new VerticalCounter(); + + for (int scanLine = 0; scanLine < 192; ++scanLine) + { + firstDisplayedOffset[Mode.HIRES][scanLine] = + screenOffset(&vCounter.states[scanLine], &hCounter.states[25], + Mode.HIRES); + firstDisplayedOffset[Mode.LORES][scanLine] = + screenOffset(&vCounter.states[scanLine], &hCounter.states[25], + Mode.LORES); + firstDisplayedOffset[Mode.TEXT][scanLine] = + screenOffset(&vCounter.states[scanLine], &hCounter.states[25], + Mode.TEXT); + } +} + +ushort scanOffset(uint vidClock, Mode mode) +{ + if (((vidClock % 65) == 25) && (vidClock / 65) < 192) + return firstDisplayedOffset[mode][vidClock / 65]; + else + return screenOffset(&vCounter.states[vidClock / 65], + &hCounter.states[vidClock % 65], mode); +} + +ushort scanOffset(uint line, uint col, Mode mode) +{ + if ((col == 25) && (line < 192)) + return firstDisplayedOffset[mode][line]; + else + return screenOffset(&vCounter.states[line], &hCounter.states[col], + mode); +} + +private: + +HorizontalCounter hCounter; +VerticalCounter vCounter; +ushort[192][3] firstDisplayedOffset; + +class HorizontalCounter +{ + State[65] states; + + this() + { + uint counter = 0b0000000; + int number = 0; + while(counter <= 0b1111111) + { + states[number++] = new State(counter); + if (counter == 0b0000000) + counter = 0b1000000; + else + ++counter; + } + } + + enum + { + HP = 0b1000000, + H5 = 0b0100000, + H4 = 0b0010000, + H3 = 0b0001000, + H2 = 0b0000100, + H1 = 0b0000010, + H0 = 0b0000001 + } + + class State + { + int a6_a5_a4_a3; + int a2_a1_a0; + + this(int counter) + { + a6_a5_a4_a3 = counter & (H5 | H4 | H3); + a2_a1_a0 = counter & (H2 | H1 | H0); + } + } +} + +class VerticalCounter +{ + State[] states; + + this() + { + uint counter = 0b011111010; // XXX PAL: 0b011001000 + states.length = 512 - counter; + int scanline = 256; + while (counter <= 0b111111111) + { + states[scanline++] = new State(counter++); + if (counter == 0b100000000) scanline = 0; + } + } + + enum + { + V5 = 0b100000000, + V4 = 0b010000000, + V3 = 0b001000000, + V2 = 0b000100000, + V1 = 0b000010000, + V0 = 0b000001000, + VC = 0b000000100, + VB = 0b000000010, + VA = 0b000000001 + } + + class State + { + int a12_a11_a10; + int a9_a8_a7; + int a6_a5_a4_a3; + + this(int counter) + { + a12_a11_a10 = (counter & (VC | VB | VA)) << 10; + a9_a8_a7 = (counter & (V2 | V1 | V0)) << 4; + a6_a5_a4_a3 = ((counter & (V4 | V3)) >> 1) | + ((counter & (V4 | V3)) >> 3); + } + } +} + +alias VerticalCounter.State vState; +alias HorizontalCounter.State hState; + +ushort screenOffset(vState* vSt, hState* hSt, Mode mode) +{ + ushort sum_a6_a5_a4_a3 = + (vSt.a6_a5_a4_a3 + hSt.a6_a5_a4_a3 + 0b1101000) & 0b1111000; + ushort offset = vSt.a9_a8_a7 | sum_a6_a5_a4_a3 | hSt.a2_a1_a0; + + if (mode == Mode.HIRES) + { + offset |= vSt.a12_a11_a10; + } + + return offset; +} + diff --git a/src/video/patterns.d b/src/video/patterns.d new file mode 100644 index 0000000..7b42454 --- /dev/null +++ b/src/video/patterns.d @@ -0,0 +1,485 @@ +/+ + + video/patterns.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import video.base; +import device.base; +import memory; +import timer; + +static this() +{ + initDoubleBits(); + initReverseBits(); +} + +private: + +ubyte[2][128] doubleBits; +ubyte[128] reverseBits; + +void initDoubleBits() +{ + for (int bits = 0; bits < 128; ++bits) + { + for (int newBit = 13; newBit >= 0; --newBit) + { + int index = 1 - (newBit / 7); + doubleBits[bits][index] <<= 1; + if (bits & (1 << (newBit / 2))) + doubleBits[bits][index] |= 1; + } + } +} + +void initReverseBits() +{ + for (int bits = 0; bits < 128; ++bits) + { + for (int bitPos = 0; bitPos < 7 ; ++bitPos) + { + reverseBits[bits] <<= 1; + if ((bits & (1 << bitPos)) != 0) + reverseBits[bits] |= 1; + } + } +} + +final ubyte INVERSE = 0b01111111; + +public: + +class TextPatternGenerator : PatternGenerator +{ + ubyte[2][8][256] flashOffPatterns; + ubyte[2][8][256] flashOnPatterns; + ubyte[2][8][256] flashNullPatterns; + ubyte[8][256] flashOffPatterns80; + ubyte[8][256] flashOnPatterns80; + ubyte[8][256] flashNullPatterns80; + + ubyte[2][8][256]* patterns; + ubyte[8][256]* patterns80; + + bool flash, altCharset; + + SignalBase signal; + ScannerBase scanner; + ubyte delegate() kbdLatch; + + this() + { + patterns = &flashOffPatterns; + patterns80 = &flashOffPatterns80; + } + + bool toggleFlash() + { + flash = !flash; + if (!altCharset) + { + if (scanner.getMode() == Mode.TEXT) + { + signal.update(); + } + applyFlash(); + } + return true; + } + + void altCharsetChange(bool newState) + { + if (altCharset == newState) return; + if (scanner.getMode() == Mode.TEXT) + { + signal.update(); + } + altCharset = newState; + if (newState) + { + patterns = &flashNullPatterns; + patterns80 = &flashNullPatterns80; + } + else applyFlash(); + } + + void applyFlash() + { + patterns = (flash ? &flashOnPatterns : &flashOffPatterns); + patterns80 = (flash ? &flashOnPatterns80 : &flashOffPatterns80); + } + + void update(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + uint dotLine = scanLine % 8; + uint memByte; + ubyte* data = scanner.getData(scanLine, startCol + 25); + + for (uint offset = 0; offset < len; ++offset) + { + memByte = data[offset]; + signal[screenPos++] = (*patterns)[memByte][dotLine][0]; + signal[screenPos++] = (*patterns)[memByte][dotLine][1]; + } + } + + void update80(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + uint dotLine = scanLine % 8; + ubyte* data = scanner.getData(scanLine, startCol + 25); + ubyte* auxData = scanner.getData80(scanLine, startCol + 25); + + for (uint offset = 0; offset < len; ++offset) + { + signal[screenPos++] = (*patterns80)[auxData[offset]][dotLine]; + signal[screenPos++] = (*patterns80)[data[offset]][dotLine]; + } + } + + void reboot() + { + altCharsetChange(false); + } + + mixin(EmptyInitSwitches()); + + abstract void init(Timer timer); +} + +class TextPatternGenerator_II : TextPatternGenerator +{ + mixin("static final ubyte[256] charsetUpper_II = " + ~ import("charset_upper_ii")); + mixin("static final ubyte[256] charsetSymbol_II = " + ~ import("charset_symbol_ii")); + + void initPatterns() + { + ubyte[][] segments = new ubyte[][8]; + segments[0] = segments[2] = segments[4] = segments[6] = + charsetUpper_II; + segments[1] = segments[3] = segments[5] = segments[7] = + charsetSymbol_II; + + ubyte flashOff, flashOn; + for (int seg = 0; seg < 8; ++seg) + { + foreach(index, pattern; segments[seg]) + { + uint ascii = (((index / 32) * 4) + (index % 4)) + (seg * 32); + uint dotLine = (index / 4) % 8; + switch (seg) + { + case 0, 1: + flashOff = flashOn = (pattern ^ INVERSE); + break; + case 2, 3: + flashOff = pattern; + flashOn = (pattern ^ INVERSE); + break; + default: + flashOff = flashOn = pattern; + break; + } + flashOffPatterns[ascii][dotLine][0] = doubleBits[flashOff][0]; + flashOffPatterns[ascii][dotLine][1] = doubleBits[flashOff][1]; + flashOnPatterns[ascii][dotLine][0] = doubleBits[flashOn][0]; + flashOnPatterns[ascii][dotLine][1] = doubleBits[flashOn][1]; + } + } + } + + void initPatterns(ubyte[] rom) + { + /+ TODO: Initialize patterns from character generator rom +/ + if (rom is null) initPatterns(); + else initPatterns(); + } + + // XXX XXX INIT + void init(Timer timer) + { + timer.new Counter(timer.hertz / 2, &toggleFlash); + } +} + +class TextPatternGenerator_IIe : TextPatternGenerator +{ + mixin("static final ubyte[256] charsetUpper_IIe = " + ~ import("charset_upper_iie")); + mixin("static final ubyte[256] charsetSymbol_IIe = " + ~ import("charset_symbol_iie")); + mixin("static final ubyte[256] charsetLower = " + ~ import("charset_lower")); + mixin("static final ubyte[256] charsetMouse = " + ~ import("charset_mouse")); + + void initPatterns() + { + ubyte[][] segments = new ubyte[][8]; + segments[0] = segments[4] = segments[6] = charsetUpper_IIe; + segments[1] = segments[5] = charsetSymbol_IIe; + segments[2] = charsetMouse; // unenhanced: charsetUpper + segments[3] = segments[7] = charsetLower; + + ubyte flashNull; + for (int seg = 0; seg < 8; ++seg) + { + foreach(index, pattern; segments[seg]) + { + uint ascii = (((index / 32) * 4) + (index % 4)) + (seg * 32); + uint dotLine = (index / 4) % 8; + switch (seg) + { + case 0, 1, 3: + flashNull = pattern ^ INVERSE; + break; + case 2: + flashNull = pattern; // unenhanced: pattern ^ INVERSE + break; + default: + flashNull = pattern; + break; + } + flashNullPatterns[ascii][dotLine][0] = + doubleBits[flashNull][0]; + flashNullPatterns[ascii][dotLine][1] = + doubleBits[flashNull][1]; + flashNullPatterns80[ascii][dotLine] = flashNull; + } + } + + ubyte flashOn, flashOff; + for (uint ascii = 0; ascii < 256; ++ascii) + { + for (uint dotLine = 0; dotLine < 8; ++dotLine) + { + if ((ascii >= 64) && (ascii < 128)) + { + flashOn = flashNullPatterns80[ascii - 64][dotLine]; + flashOff = flashNullPatterns80[ascii + 64][dotLine]; + } + else + { + flashOn = flashOff = flashNullPatterns80[ascii][dotLine]; + } + flashOffPatterns[ascii][dotLine][0] = doubleBits[flashOff][0]; + flashOffPatterns[ascii][dotLine][1] = doubleBits[flashOff][1]; + flashOffPatterns80[ascii][dotLine] = flashOff; + flashOnPatterns[ascii][dotLine][0] = doubleBits[flashOn][0]; + flashOnPatterns[ascii][dotLine][1] = doubleBits[flashOn][1]; + flashOnPatterns80[ascii][dotLine] = flashOn; + } + } + } + + void initPatterns(ubyte[] rom) + { + /+ TODO: Initialize patterns from character generator rom +/ + if (rom is null) initPatterns(); + else initPatterns(); + } + + // XXX XXX INIT + void init(Timer timer) + { + timer.new Counter(32 * 262 * 65, &toggleFlash); // XXX PAL + } + + void altCharsetOn() + { + altCharsetChange(true); + } + + void altCharsetOff() + { + altCharsetChange(false); + } + + ubyte readAltCharset() + { + return kbdLatch() | (altCharset ? 0x80 : 0x00); + } + + // XXX XXX INIT + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC00E], "W", "altCharsetOff")), + mixin(MakeSwitch([0xC00F], "W", "altCharsetOn")), + mixin(MakeSwitch([0xC01E], "R", "readAltCharset")) + ])); +} + +class LoresPatternGenerator : PatternGenerator +{ + ubyte[2][2][16] patterns; + + void initPatterns() + { + int[16] dhrBits4 = [ + 0x0, 0x8, 0x4, 0xC, 0x2, 0x5, 0x6, 0xE, + 0x1, 0x9, 0xA, 0xD, 0x3, 0xB, 0x7, 0xF]; + for (int bits4 = 0; bits4 < 16; ++bits4) + { + ubyte bits8 = (dhrBits4[bits4] << 4) | dhrBits4[bits4]; + ushort bits16 = (bits8 << 8) | bits8; + uint bits32 = (bits16 << 16) | bits16; + patterns[bits4][0][0] = bits32 >> 25; + patterns[bits4][0][1] = (bits32 >> 18) & 0b01111111; + patterns[bits4][1][0] = (bits32 >> 11) & 0b01111111; + patterns[bits4][1][1] = (bits32 >> 4) & 0b01111111; + } + } + + void initPatterns(ubyte[] rom) + { + /+ TODO: Initialize patterns from IIe video ROM +/ + if (rom is null) initPatterns(); + else initPatterns(); + } + + void update(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + uint col = startCol; + int shiftAmt = (((scanLine % 8) / 4) == 0) ? 0 : 4; + ubyte* data = scanner.getData(scanLine, startCol + 25); + uint memNybble; + + for (uint offset = 0; offset < len; ++offset) + { + memNybble = (data[offset] >> shiftAmt) & 0x0F; + signal[screenPos++] = patterns[memNybble][col & 1][0]; + signal[screenPos++] = patterns[memNybble][col++ & 1][1]; + } + } + + void update80(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + uint col = startCol; + int shiftAmt = (((scanLine % 8) / 4) == 0) ? 0 : 4; + ubyte* data = scanner.getData(scanLine, startCol + 25); + ubyte* auxData = scanner.getData80(scanLine, startCol + 25); + uint memNybble; + + for (uint offset = 0; offset < len; ++offset) + { + memNybble = (auxData[offset] >> shiftAmt) & 0x0F; + signal[screenPos++] = patterns[memNybble][col & 1][0]; + + memNybble = (data[offset] >> shiftAmt) & 0x0F; + signal[screenPos++] = patterns[memNybble][col++ & 1][0]; + } + } +} + +class HiresPatternGenerator : PatternGenerator +{ + ubyte[2][256] patterns; + ubyte[256] patterns80; + + ubyte prevByte; + + void initPatterns() + { + for (int bits = 0; bits < 256; ++bits) + { + ubyte leftPattern = doubleBits[reverseBits[bits & 0x7F]][0]; + ubyte rightPattern = doubleBits[reverseBits[bits & 0x7F]][1]; + if (bits > 0x7F) + { + patterns[bits][0] = leftPattern >> 1; + patterns[bits][1] = (rightPattern >> 1) | + ((leftPattern & 1) << 6); + } + else + { + patterns[bits][0] = leftPattern; + patterns[bits][1] = rightPattern; + } + + patterns80[bits] = reverseBits[bits & 0x7F] | (bits & 0x80); + } + } + + void initPatterns(ubyte[] rom) + { + /+ TODO: Initialize patterns from IIe video ROM +/ + if (rom is null) initPatterns(); + else initPatterns(); + } + + void update(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + uint vidClock = scanLine * 65 + startCol + 25; + ubyte* data = scanner.getData(vidClock); + uint memByte; + + if (startCol == 0) + prevByte = *(scanner.getData(vidClock - 1)) & 0b01000000; + + for (uint offset = 0; offset < len; ++offset) + { + memByte = data[offset]; + + if (memByte > 0x7F) + signal[screenPos++] = patterns[memByte][0] | prevByte; + else + signal[screenPos++] = patterns[memByte][0]; + prevByte = memByte & 0b01000000; + signal[screenPos++] = patterns[memByte][1]; + } + } + + void update80(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + ubyte* data = scanner.getData(scanLine, startCol + 25); + ubyte* auxData = scanner.getData80(scanLine, startCol + 25); + + for (int offset = 0; offset < len; ++offset) + { + signal[screenPos++] = patterns80[auxData[offset]]; + signal[screenPos++] = patterns80[data[offset]]; + } + } +} + +class HiresPatternGenerator_Revision0 : HiresPatternGenerator +{ + void update(ubyte* signal, int scanLine, int startCol, uint len) + { + uint screenPos = 0; + ubyte* data = scanner.getData(scanLine, startCol); + uint memByte; + + for (int offset = 0; offset < len; ++offset) + { + memByte = data[offset] & 0x7F; + signal[screenPos++] = patterns[memByte][0]; + signal[screenPos++] = patterns[memByte][1]; + } + } +} + diff --git a/src/video/scanner.d b/src/video/scanner.d new file mode 100644 index 0000000..1e5abd4 --- /dev/null +++ b/src/video/scanner.d @@ -0,0 +1,295 @@ +/+ + + video/scanner.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import video.base; +import video.offsets; +import memory; +import timer; +import device.base; + +class Scanner : ScannerBase +{ + uint page; + bool graphicsTime, textSwitch, mixedSwitch, hiresSwitch, oldTextSwitch; + Mode mode; + + Timer.Cycle vidCycle; + uint frameSkip, frameCount; + + DataMem[2][3] displayMem; + DataMem[2][3] auxDisplayMem; + + SignalBase signal; + ubyte delegate() kbdLatch; + bool delegate() drawFrame; + + void init(Timer timer) + { + int frameLen = 262 * 65; // XXX PAL: 312 * 65 + vidCycle = timer.startCycle(frameLen); + graphicsTime = true; + + timer.new Counter(frameLen, &graphicsTimeOn); + timer.new DelayedCounter(frameLen, &frameComplete, frameLen - 1); + timer.new DelayedCounter(frameLen, &graphicsTimeOff, 160 * 65); + timer.new DelayedCounter(frameLen, &graphicsTimeOn, 192 * 65); + timer.new DelayedCounter(frameLen, &graphicsTimeOff, 224 * 65); + } + + void forceFrame() + { + } + + void reboot() + { + page2SwitchOff(); + hiresSwitchOff(); + mixedSwitchOff(); + textSwitchOn(); + } + + void installMemory(DataMem loresPage1, DataMem loresPage2, + DataMem hiresPage1, DataMem hiresPage2) + { + displayMem[Mode.TEXT][0] = loresPage1; + displayMem[Mode.TEXT][1] = loresPage2; + displayMem[Mode.LORES][0] = loresPage1; + displayMem[Mode.LORES][1] = loresPage2; + displayMem[Mode.HIRES][0] = hiresPage1; + displayMem[Mode.HIRES][1] = hiresPage2; + } + + void installAuxMemory(DataMem loresPage1, DataMem loresPage2, + DataMem hiresPage1, DataMem hiresPage2) + { + auxDisplayMem[Mode.TEXT][0] = loresPage1; + auxDisplayMem[Mode.TEXT][1] = loresPage2; + auxDisplayMem[Mode.LORES][0] = loresPage1; + auxDisplayMem[Mode.LORES][1] = loresPage2; + auxDisplayMem[Mode.HIRES][0] = hiresPage1; + auxDisplayMem[Mode.HIRES][1] = hiresPage2; + } + + bool graphicsTimeOn() + { + changeMode({graphicsTime = true;}); + return true; + } + + bool graphicsTimeOff() + { + changeMode({graphicsTime = false;}); + return true; + } + + bool frameComplete() + { + signal.update(); + if (shouldUpdate()) drawFrame(); + frameCount = ((frameCount + 1) % (frameSkip + 1)); + return true; + } + + bool shouldUpdate() + { + return ((frameCount % (frameSkip + 1)) == 0); + } + + void changeMode(void delegate() change) + { + oldTextSwitch = textSwitch; + change(); + Mode newMode = changedMode(); + if (newMode != mode) + { + signal.update(); + } + mode = newMode; + oldTextSwitch = textSwitch; + } + + Mode changedMode() + { + if (textSwitch) return Mode.TEXT; + if (mixedSwitch && !graphicsTime) return Mode.TEXT; + + if (hiresSwitch) return Mode.HIRES; + else return Mode.LORES; + } + + void textSwitchOff() + { + changeMode({textSwitch = false;}); + } + + void textSwitchOn() + { + changeMode({textSwitch = true;}); + } + + void mixedSwitchOff() + { + changeMode({mixedSwitch = false;}); + } + + void mixedSwitchOn() + { + changeMode({mixedSwitch = true;}); + } + + void hiresSwitchOff() + { + changeMode({hiresSwitch = false;}); + } + + void hiresSwitchOn() + { + changeMode({hiresSwitch = true;}); + } + + void page2SwitchOff() + { + if (page == 1) + { + signal.update(); + page = 0; + } + } + + void page2SwitchOn() + { + if (page == 0) + { + signal.update(); + page = 1; + } + } + + Mode getMode() + { + return mode; + } + + bool checkColorBurst() + { + return !oldTextSwitch; + /+ TODO: For "pretty" mixed mode, return (mode != Mode.TEXT) +/ + } + + uint currentLine() + { + return vidCycle.currentVal() / 65; + } + + uint currentCol() + { + return vidCycle.currentVal() % 65; + } + + ubyte* getData(uint vidClock) + { + return displayMem[mode][page].data + scanOffset(vidClock, mode); + } + + ubyte* getData(uint line, uint col) + { + return displayMem[mode][page].data + scanOffset(line, col, mode); + } + + ubyte* getData80(uint vidClock) + { + return auxDisplayMem[mode][page].data + scanOffset(vidClock, mode); + } + + ubyte* getData80(uint line, uint col) + { + return auxDisplayMem[mode][page].data + scanOffset(line, col, mode); + } + + mixin(InitSwitches("", [ + mixin(MakeSwitch([0xC050], "R0W", "textSwitchOff")), + mixin(MakeSwitch([0xC051], "R0W", "textSwitchOn")), + mixin(MakeSwitch([0xC052], "R0W", "mixedSwitchOff")), + mixin(MakeSwitch([0xC053], "R0W", "mixedSwitchOn")) + ])); +} + +class Scanner_II : Scanner +{ + import memory: AddressDecoder; + + AddressDecoder decoder; + + ubyte floatingBus(ushort addr) + { + uint clock = vidCycle.currentVal(); + if (((clock % 65) < 25) && (mode != Mode.HIRES)) + return decoder.read(0x1400 + (page * 0x400) + + scanOffset(clock, mode)); + else + return displayMem[mode][page].data[scanOffset(clock, mode)]; + } + + mixin(InitSwitches("super", [ + mixin(MakeSwitch([0xC054], "R0W", "page2SwitchOff")), + mixin(MakeSwitch([0xC055], "R0W", "page2SwitchOn")), + mixin(MakeSwitch([0xC056], "R0W", "hiresSwitchOff")), + mixin(MakeSwitch([0xC057], "R0W", "hiresSwitchOn")) + ])); +} + +class Scanner_IIe : Scanner +{ + ubyte floatingBus(ushort addr) + { + return displayMem[mode][page].data[scanOffset(vidCycle.currentVal(), + mode)]; + // equivalent to getData()[0]; + } + + ubyte readText() + { + return kbdLatch() | (textSwitch ? 0x80 : 0x00); + } + + ubyte readMixed() + { + return kbdLatch() | (mixedSwitch ? 0x80 : 0x00); + } + + bool readVBL() + { + return (vidCycle.currentVal() >= (192 * 65)); + } + + ubyte readLowVBL() + { + return kbdLatch() | ((!readVBL()) ? 0x80 : 0x00); + } + + mixin(InitSwitches("super", [ + mixin(MakeSwitch([0xC019], "R", "readLowVBL")), + mixin(MakeSwitch([0xC01A], "R", "readText")), + mixin(MakeSwitch([0xC01B], "R", "readMixed")) + ])); +} + diff --git a/src/video/signal.d b/src/video/signal.d new file mode 100644 index 0000000..4e060c6 --- /dev/null +++ b/src/video/signal.d @@ -0,0 +1,207 @@ +/+ + + video/signal.d + + + + Copyright: 2007 Gerald Stocker + + + + This file is part of Twoapple. + + + + Twoapple 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 2 of the License, or + + (at your option) any later version. + + + + Twoapple 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 Twoapple; if not, write to the Free Software + + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +/ + +import video.base; +import video.patterns; +import device.base; +import memory; +import timer; + +class Signal : SignalBase +{ + ubyte[560 * 192] bitmap; + int nextLine, nextCol; + bool col80Switch, dHGR; + + Screen screen; + ScannerBase scanner; + ubyte delegate() kbdLatch; + + PatternGenerator loresPattern, hiresPattern; + TextPatternGenerator textPattern; + + void init(Timer timer, ubyte[] rom) + { + assert(textPattern !is null); + + textPattern.signal = this; + textPattern.scanner = scanner; + textPattern.init(timer); + textPattern.initPatterns(rom); + loresPattern = new LoresPatternGenerator(); + hiresPattern = new HiresPatternGenerator(); + loresPattern.scanner = scanner; + hiresPattern.scanner = scanner; + } + + void reboot() + { + textPattern.reboot(); + } + + void update() + { + if (!scanner.shouldUpdate()) return; + + int stopLine = scanner.currentLine(); + int stopCol = scanner.currentCol(); + + Mode mode = scanner.getMode(); + bool col80 = + ((mode == Mode.TEXT) ? col80Switch : (col80Switch && dHGR)); + bool colorBurst = scanner.checkColorBurst(); + + if (nextLine < 192) + { + update(mode, col80, colorBurst, nextLine, nextCol, stopLine, + stopCol); + } + + if (stopCol == 64) + { + nextLine = (stopLine + 1) % 262; // XXX PAL: 312 + nextCol = 25; + } + else + { + nextLine = stopLine; + nextCol = stopCol + 1; + } + } + + void update(Mode mode, bool col80, bool colorBurst, int startLine, + int startCol, int stopLine, int stopCol) + { + PatternGenerator pattern; + switch (mode) + { + case Mode.TEXT: + pattern = textPattern; + break; + case Mode.LORES: + pattern = loresPattern; + break; + case Mode.HIRES: + pattern = hiresPattern; + break; + } + + int lastLine = (stopLine < 192) ? stopLine : 191; + int scanStart = (startCol < 25) ? 0 : startCol - 25; + int scanEnd = 39; + ubyte* bitmapPtr; + + for (int scanLine = startLine; scanLine <= lastLine; ++scanLine) + { + if (scanLine == stopLine) + { + if (stopCol < 25) break; + scanEnd = stopCol - 25; + } + + bitmapPtr = bitmap.ptr + ((scanLine * 80) + (scanStart * 2)); + + if (col80) + pattern.update80(bitmapPtr, scanLine, scanStart, + (scanEnd - scanStart) + 1); + else + pattern.update(bitmapPtr, scanLine, scanStart, + (scanEnd - scanStart) + 1); + + screen.draw(col80, colorBurst, + scanStart == 0 ? bitmapPtr: bitmapPtr - 1, + scanLine, scanStart, scanEnd); + + scanStart = 0; + } + } + + void col80SwitchChange(bool newCol80Switch) + { + if (col80Switch == newCol80Switch) return; + update(); + col80Switch = newCol80Switch; + } + + void dHGRChange(bool newdHGR) + { + if (dHGR == newdHGR) return; + update(); + dHGR = newdHGR; + } + + void initSwitches(SoftSwitchPage switches, int slot = -1) + { + textPattern.initSwitches(switches, slot); + } +} + +class Signal_II : Signal +{ + void init(Timer timer, ubyte[] rom) + { + textPattern = new TextPatternGenerator_II(); + super.init(timer, rom); + loresPattern.initPatterns(); + hiresPattern.initPatterns(); + } +} + +class Signal_IIe : Signal +{ + void init(Timer timer, ubyte[] rom) + { + textPattern = new TextPatternGenerator_IIe(); + textPattern.kbdLatch = kbdLatch; + super.init(timer, rom); + loresPattern.initPatterns(rom); + hiresPattern.initPatterns(rom); + } + + void reboot() + { + super.reboot(); + col80SwitchChange(false); + } + + void col80SwitchOff() + { + col80SwitchChange(false); + } + + void col80SwitchOn() + { + col80SwitchChange(true); + } + + ubyte readCol80Switch() + { + return kbdLatch() | (col80Switch ? 0x80 : 0x00); + } + + mixin(InitSwitches("super", [ + mixin(MakeSwitch([0xC00C], "W", "col80SwitchOff")), + mixin(MakeSwitch([0xC00D], "W", "col80SwitchOn")), + mixin(MakeSwitch([0xC01F], "R", "readCol80Switch")) + ])); +} +