Repository: digarok/gsplus Branch: main Commit: 47389939d652 Files: 178 Total size: 2.5 MB Directory structure: gitextract_v8qf17it/ ├── .gitignore ├── CLAUDE.md ├── LICENSE ├── README.md ├── TODO.md ├── gsplus/ │ ├── lib/ │ │ ├── 2mg.icns │ │ ├── 525.icns │ │ ├── MainMenu.nib │ │ ├── kegsicon.icns │ │ └── make_mac_icon │ └── src/ │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Kegs-Bridging-Header.h │ ├── MainView.swift │ ├── Makefile │ ├── adb.c │ ├── applesingle.c │ ├── clock.c │ ├── comp_swift │ ├── compile_time.c │ ├── config.c │ ├── config.h │ ├── cp_gsplus_libs │ ├── debugger.c │ ├── defc.h │ ├── defcomm.h │ ├── defs_instr.h │ ├── dependency │ ├── disas.h │ ├── doc.c │ ├── dyna_filt.c │ ├── dyna_type.c │ ├── dyna_validate.c │ ├── dynapro.c │ ├── engine.h │ ├── engine_c.c │ ├── instable.h │ ├── iwm.c │ ├── iwm.h │ ├── joystick_driver.c │ ├── kegs.icns │ ├── kegsfont.h │ ├── kegswin.sln │ ├── kegswin.vcxproj │ ├── ldvars │ ├── macsnd_driver.c │ ├── mockingboard.c │ ├── moremem.c │ ├── op_routs.h │ ├── paddles.c │ ├── protos.h │ ├── protos_base.h │ ├── protos_macdriver.h │ ├── protos_macsnd_driver.h │ ├── protos_pulseaudio_driver.h │ ├── protos_windriver.h │ ├── protos_xdriver.h │ ├── pulseaudio_driver.c │ ├── scc.c │ ├── scc.h │ ├── scc_socket_driver.c │ ├── scc_unixdriver.c │ ├── scc_windriver.c │ ├── sim65816.c │ ├── size_c.h │ ├── smartport.c │ ├── sound.c │ ├── sound.h │ ├── sound_driver.c │ ├── style_check │ ├── undeflate.c │ ├── unshk.c │ ├── vars │ ├── vars_mac │ ├── vars_mac_x │ ├── vars_x86linux │ ├── video.c │ ├── voc.c │ ├── win32snd_driver.c │ ├── win_dirent.h │ ├── windriver.c │ ├── woz.c │ └── xdriver.c └── upstream/ ├── KEGS.version └── kegs/ ├── config.kegs ├── doc/ │ ├── CHANGES.txt │ ├── COPYING.txt │ ├── INTERNALS.iwm.txt │ ├── INTERNALS.overview.txt │ ├── INTERNALS.video.txt │ ├── INTERNALS.xdriver.txt │ ├── README.ROM.files.txt │ ├── README.a2.compatibility.txt │ ├── README.compile.txt │ ├── README.dynapro.txt │ ├── README.kegs.txt │ ├── README.linux.partitions.txt │ ├── README.mac.txt │ ├── README.serial.ports.txt │ └── README.win32.txt ├── lib/ │ ├── 2mg.icns │ ├── 525.icns │ ├── MainMenu.nib │ ├── kegsicon.icns │ └── make_mac_icon ├── src/ │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Kegs-Bridging-Header.h │ ├── MainView.swift │ ├── Makefile │ ├── adb.c │ ├── applesingle.c │ ├── clock.c │ ├── comp_swift │ ├── compile_time.c │ ├── config.c │ ├── config.h │ ├── cp_kegs_libs │ ├── debugger.c │ ├── defc.h │ ├── defcomm.h │ ├── defs_instr.h │ ├── dependency │ ├── disas.h │ ├── doc.c │ ├── dyna_filt.c │ ├── dyna_type.c │ ├── dyna_validate.c │ ├── dynapro.c │ ├── engine.h │ ├── engine_c.c │ ├── instable.h │ ├── iwm.c │ ├── iwm.h │ ├── joystick_driver.c │ ├── kegsfont.h │ ├── kegswin.sln │ ├── kegswin.vcxproj │ ├── ldvars │ ├── macsnd_driver.c │ ├── mockingboard.c │ ├── moremem.c │ ├── op_routs.h │ ├── paddles.c │ ├── protos.h │ ├── protos_base.h │ ├── protos_macdriver.h │ ├── protos_macsnd_driver.h │ ├── protos_pulseaudio_driver.h │ ├── protos_windriver.h │ ├── protos_xdriver.h │ ├── pulseaudio_driver.c │ ├── scc.c │ ├── scc.h │ ├── scc_socket_driver.c │ ├── scc_unixdriver.c │ ├── scc_windriver.c │ ├── sim65816.c │ ├── size_c.h │ ├── smartport.c │ ├── sound.c │ ├── sound.h │ ├── sound_driver.c │ ├── style_check │ ├── undeflate.c │ ├── unshk.c │ ├── vars │ ├── vars_mac │ ├── vars_mac_x │ ├── vars_x86linux │ ├── video.c │ ├── voc.c │ ├── win32snd_driver.c │ ├── win_dirent.h │ ├── windriver.c │ ├── woz.c │ └── xdriver.c └── xkegs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.o ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview GSplus is a cross-platform Apple IIgs emulator based on KEGS (Kent's Emulated GS) by Kent Dickey. It emulates the 65816 CPU, all Apple IIgs graphics/sound modes, disk controllers, serial ports, and more. Licensed under GPLv3. ## Repository Structure - `gsplus/src/` — Active source code and build files (this is where you build) - `gsplus/lib/` — Icons, NIB files, and asset resources - `upstream/kegs/` — Upstream KEGS tracked separately, merged to main when updated - `upstream/kegs/doc/` — Comprehensive documentation (architecture internals, platform setup, compatibility) The `upstream` branch tracks KEGS releases; `main` is the primary development branch. ## Build Commands ### macOS (default target) ```bash cd gsplus/src make -j 20 ``` Produces `gsplus/KEGSMAC.app`. Requires Xcode with command-line tools installed. ### Linux (X11) ```bash cd gsplus/src rm vars; ln -s vars_x86linux vars make -j 20 ``` Produces `xkegs`. Requires `libX11-devel`, `libXext-devel`, `pulseaudio-libs-devel`. ### Windows Open `gsplus/src/kegswin.vcxproj` in Visual Studio Community Edition and press F7. ### Clean ```bash cd gsplus/src make clean ``` ## Architecture ### CPU & Core Loop - `sim65816.c` — Main simulation loop, event scheduling, interrupt handling - `engine_c.c` + `engine.h` — 65816 CPU instruction emulation (macro-heavy for performance) - `defs_instr.h`, `instable.h`, `op_routs.h` — Instruction definitions, opcode table, operation macros ### Memory - `moremem.c` — Memory management, page table fixup, I/O register reads/writes ($C000-$C0FF area) - Page-table-based MMU for dynamic address mapping ### Video - `video.c` — All Apple IIgs/II graphics mode rendering (text, lores, hires, super hires) ### Audio - `sound.c` — Sound generation (mixing, output buffering) - `doc.c` — Ensoniq DOC 32-voice synthesizer emulation - `mockingboard.c` — Mockingboard A card (6522 VIA + AY-8913) ### Disk I/O - `iwm.c` — IWM disk controller (5.25" and 3.5" drives, nibble-level accuracy) - `smartport.c` — SmartPort controller for hard drive images - `dynapro.c` — Host directory mounting as virtual ProDOS volumes - `woz.c` — WOZ disk image format support - `unshk.c`, `undeflate.c`, `applesingle.c` — Archive/compression format support ### Input - `adb.c` — Apple Desktop Bus (keyboard, mouse) - `scc.c` + `scc_socket_driver.c` — Serial Communications Controller with TCP/IP modem emulation - `paddles.c`, `joystick_driver.c` — Game input ### Configuration & Debug - `config.c` — Runtime configuration UI, disk mounting, settings persistence (`config.kegs`) - `debugger.c` — Built-in 65816 debugger/monitor ### Platform Drivers The emulator core is platform-independent. Platform-specific code is isolated in driver files: | Component | macOS | Linux | Windows | |-----------|-------|-------|---------| | Display | `AppDelegate.swift` + `MainView.swift` | `xdriver.c` | `windriver.c` | | Audio | `macsnd_driver.c` (CoreAudio) | `pulseaudio_driver.c` | `win32snd_driver.c` | | Serial | `scc_unixdriver.c` | `scc_unixdriver.c` | `scc_windriver.c` | ### Key Headers - `defc.h` — Global defines, structs, macros (included by nearly every .c file) - `defcomm.h` — Shared defines for C and assembly - `protos_base.h` — Function prototypes for all modules ## Build System Details The Makefile includes `vars` (platform config) and `ldvars` (object file list). To change platforms, symlink the appropriate vars file (`vars_mac`, `vars_x86linux`, etc.) to `vars`. Swift files are compiled via the `comp_swift` wrapper script. The `dependency` file contains header dependency rules. Compiler flags are set in `vars`: `-Wall -O2 -DMAC` for macOS. The `-DMAC` define selects macOS-specific code paths throughout the codebase. ## Runtime Requirements The emulator needs an Apple IIgs ROM file to run. Demo disk images (`NUCLEUS03.gz`, `XMAS_DEMO.gz`) are included in `upstream/kegs/`. Configuration is stored in `config.kegs`. ## No Test Suite There is no automated test infrastructure. Testing is done manually by running Apple IIgs software and ROM self-tests. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # gsplus Cross-platform Apple IIgs emulator and tools based on KEGS ### About Branches - KEGS latest is tracked in `./upstream` directory - There is an upstream branch that is updated whenever there are new version and merged to main. - This makes it easy to track kegs changes somewhat independently of the gsplus work. ================================================ FILE: TODO.md ================================================ # TODO ## Backlog - [ ] Remove `#ifdef INCLUDE_RCSID_C` / `#endif` guards from header files and `#define`/`#undef INCLUDE_RCSID_C` from `sim65816.c` and `sound.c` in `gsplus/src/`. The `const char rcsid_` lines were already removed; these are the leftover scaffolding. - [ ] Update Menubar titles for X/Win to match.. Mac build already updated to "GS+" ================================================ FILE: gsplus/lib/make_mac_icon ================================================ #!/usr/bin/perl -w # Based on https://gist.github.com/ansarizafar/6fa64f44aa933794c4d6638eec32b9aa # and https://github.com/retifrav/generate-iconset # We need to create a directory of the icon in several scaled sizes, # (the Mac command "sips" can do this), then run iconutil to form the # .icns file. # kegsicon.png created by Alex Lee my $icondir; my $img_file = ""; my $ext = ".png"; my $scale; my $sz; my $pixels; my $scale_str; if($#ARGV == 0) { $img_file = shift; if($img_file =~ /^.*\.(^\.*)$/) { $ext = $1; print "Set ext to $ext\n"; } } else { die "Usage: $0 image_file.jpg/.png" } $icondir = "./icon.iconset"; # Must have .iconset extension if(-d $icondir) { `rm -rf $icondir`; } `mkdir $icondir`; for($scale = 1; $scale <= 2; $scale++) { for($sz = 16; $sz <= 512; $sz = $sz * 2) { if($sz == 64) { next; } $pixels = $sz * $scale; $scale_str = ""; if($scale == 2) { $scale_str = '@2x'; } @cmd = ("sips", "-z", $pixels, $pixels, $img_file, "--matchTo", "/System/Library/ColorSync/Profiles/sRGB\\ Profile.icc", "--out", $icondir . "/" . "icon_" . $sz . "x" . $sz . $scale_str . $ext); print "cmd: @cmd\n"; `@cmd`; } } print "Calling: iconutil -o kegs.icns -c icns $icondir"; `iconutil -o kegs.icns -c icns $icondir`; `rm -rf $icondir`; ================================================ FILE: gsplus/src/AppDelegate.swift ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ import Cocoa let Context_draw = false // Default: use safe draw function. // If true, use NSGraphicsContext.current.data to try to write // directly to screen memory (in a different ARGB format) class Window_info { var x_win : NSWindow? = nil var mac_view : MainView? = nil var kimage_ptr : UnsafeMutablePointer! = nil var title : String = "" var app_delegate : AppDelegate! = nil var mac_a2_height : Int = 0 // init(_ new_is_main: Bool) { // is_main = new_is_main // } func set_kimage(_ kimage_ptr : UnsafeMutablePointer!, title: String, delegate: AppDelegate!) { self.kimage_ptr = kimage_ptr self.title = title self.app_delegate = delegate } func create_window() { let x_xpos = Int(video_get_x_xpos(kimage_ptr)) let x_ypos = Int(video_get_x_ypos(kimage_ptr)) let width = Int(video_get_x_width(kimage_ptr)) let height = Int(video_get_x_height(kimage_ptr)) let windowRect = NSRect(x: x_xpos, y: x_ypos, width: width, height: height) var window : NSWindow! var view : MainView! let style : NSWindow.StyleMask = [.titled, .closable, .resizable] window = NSWindow(contentRect: windowRect, styleMask: style, backing: .buffered, defer: false) let viewRect = NSRect(x: 0, y: 0, width: windowRect.size.width, height: windowRect.size.height) print("About to init MainView"); view = MainView(frame: viewRect) print("About to set kimage_ptr"); view.kimage_ptr = kimage_ptr; print("About to call initialize"); view.initialize() view.closed = false window.delegate = app_delegate window.contentView = view window.makeKeyAndOrderFront(app_delegate) window.acceptsMouseMovedEvents = true window.title = title window.showsToolbarButton = true window.contentAspectRatio = NSSize(width: width, height: height) video_set_active(kimage_ptr, Int32(1)) video_update_scale(kimage_ptr, Int32(width), Int32(height), Int32(1)) x_win = window mac_view = view mac_a2_height = height; window.makeKey() } func update() { // Decide if window should be opened/closed (if it's the // debugger window), and call mac_update_display() to update let new_height = Int(video_get_a2_height(kimage_ptr)) let a2_active = video_get_active(kimage_ptr) if let view = mac_view { if(new_height != mac_a2_height) { mac_resize_window() } if(a2_active == 0 && !view.closed) { print("a2_active 0 on \(title), calling close") x_win!.orderOut(x_win) view.closed = true } else if(a2_active != 0 && view.closed) { print("Opening closed window \(title)") view.closed = false x_win!.orderFront(x_win) x_win!.makeKey() // Move to front } else if(a2_active != 0) { view.mac_update_display() } if((a2_active != 0) && !view.closed) { if(adb_get_copy_requested() != 0) { view.do_copy_text(view); } } } else { if(a2_active != 0) { print("Opening window \(title)") create_window() } } } func mac_resize_window() { let a2_height = Int(video_get_a2_height(kimage_ptr)) let a2_width = Int(video_get_a2_width(kimage_ptr)) let ratio = CGFloat(a2_height) / CGFloat(a2_width) let cur_width = x_win!.frame.size.width let new_height = cur_width * ratio // CGFloat var newframe = x_win!.frame // NSRect mac_a2_height = a2_height newframe.size.height = new_height x_win!.contentAspectRatio = NSSize(width: a2_width, height: a2_height) x_win!.setFrame(newframe, display: true, animate: true) mac_view!.initialize() // Must call initialize for the case where the // status lines were enabled, we need more lines // print("Call video_update_scale from mac_resize_window\n"); video_update_scale(kimage_ptr, Int32(x_win!.frame.width), Int32(x_win!.frame.height), 0) video_update_xpos_ypos(kimage_ptr, Int32(x_win!.frame.origin.x), Int32(x_win!.frame.origin.y)) //print("Did mac_resize window to \(a2_width), \(a2_height)" + // " frame:\(x_win!.frame.width), " + // "\(x_win!.frame.height)" + // " ratio:\(ratio)\n") } func update_window_size(width: Int, height: Int) { // print("Call video_update_scale from update_window_size\n"); video_update_scale(kimage_ptr, Int32(width), Int32(height), 0); } } @main class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { static func main() { let delegate = AppDelegate() NSApplication.shared.delegate = delegate _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) } var mainwin_info = Window_info(); var debugwin_info = Window_info(); func find_win_info(_ window: NSWindow) -> Window_info { if(mainwin_info.x_win == window) { return mainwin_info } return debugwin_info } @objc func do_about(_:AnyObject) { print("About") if let ver_str = Bundle.main.infoDictionary?[ "CFBundleShortVersionString"] as? String { NSApplication.shared.orderFrontStandardAboutPanel( options: [.applicationVersion: ver_str, .version: "", .applicationName: "GS+"]) } } func applicationDidFinishLaunching(_ aNotification: Notification) { // This is your first real entry point into the app print("start!") set_menu_for_kegs() NSApp.activate(ignoringOtherApps: true) // Bring window to front main_init() } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } func applicationShouldTerminateAfterLastWindowClosed( _ theApplication: NSApplication) -> Bool { // Application will close if main window is closed return true } func windowDidBecomeKey(_ notification: Notification) { if let w = notification.object as? NSWindow { if(w == mainwin_info.x_win) { adb_mainwin_focus(Int32(1)); //print("Main window became KEY") } } //print("DidbecomeKey") // If window focus is changing, turn off key repeat adb_kbd_repeat_off() } func windowDidResignKey(_ notification: Notification) { //print("DidResignKey") adb_kbd_repeat_off() adb_mainwin_focus(Int32(0)) CGDisplayShowCursor(CGMainDisplayID()) } func windowDidMove(_ notification: Notification) { //print("DidMove") if let w = notification.object as? NSWindow { let win_info = find_win_info(w) video_update_xpos_ypos(win_info.kimage_ptr, Int32(w.frame.origin.x), Int32(w.frame.origin.y)) } } func windowWillResize(_ window: NSWindow, to frameSize: NSSize) -> NSSize { // print("WILL RESIZE app \(frameSize)") let width = Int(frameSize.width) let height = Int(frameSize.height) let win_info = find_win_info(window) win_info.update_window_size(width: width, height: height) return frameSize } func windowShouldClose(_ window: NSWindow) -> Bool { print("windowShouldClose") let win_info = find_win_info(window) if(mainwin_info.x_win == window) { // User has clicked the close box on the main emulator // window. Just exit the app NSApp.terminate(window) return true // Let main window close } else { video_set_active(win_info.kimage_ptr, Int32(0)) win_info.update() return false } } func set_menu_for_kegs() { let appname = "Kegs" let menu = NSMenu(title: "MainMenu") NSApp.mainMenu = menu print("Installing my menu now") // Add "Kegs" menu let kegs = NSMenu(title: appname) kegs.addItem(withTitle: "About \(appname)", action: #selector(AppDelegate.do_about(_:)), keyEquivalent: "") kegs.addItem(NSMenuItem.separator()) let quit_item = NSMenuItem(title: "Quit \(appname)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") quit_item.keyEquivalentModifierMask = [.option, .command ] kegs.addItem(quit_item) let kegs_item = NSMenuItem() kegs_item.title = appname kegs_item.submenu = kegs menu.addItem(kegs_item) // Add "Edit" menu let edit = NSMenu(title: "Edit") edit.addItem(withTitle: "Copy Text Screen", action: #selector(MainView.do_copy_text(_:)), keyEquivalent: "") edit.addItem(NSMenuItem.separator()) edit.addItem(withTitle: "Paste", action: #selector(MainView.do_paste(_:)), keyEquivalent: "") let edit_item = NSMenuItem() edit_item.title = "Edit" edit_item.submenu = edit menu.addItem(edit_item) // Add "Config" menu let config = NSMenu(title: "Config") config.addItem(withTitle: "Configuration F4", action: #selector(MainView.do_config(_:)), keyEquivalent: "") let config_item = NSMenuItem() config_item.title = "Config" config_item.submenu = config menu.addItem(config_item) show_menu(menu, depth: 0) } func show_menu(_ menu: NSMenu, depth: Int) { if(depth >= -10) { return // HACK: remove to see debug output! } print("menu at depth \(depth): \(menu.title)") if(depth > 5) { // Prevent infinite recursion return } for menuit in menu.items { print("menuit: depth:\(depth) \(menuit.title)") print(" keyeq:\(menuit.keyEquivalent)") print(" modifiers:\(menuit.keyEquivalentModifierMask)") print(" isSeparator:\(menuit.isSeparatorItem)") if let sub = menuit.submenu { show_menu(sub, depth: depth+1) } } } var mainWindow : NSWindow! var mainwin_view : MainView! func main_init() { var kimage_ptr : UnsafeMutablePointer! var rect = NSRect.zero let argc = CommandLine.argc let argv = CommandLine.unsafeArgv parse_argv(argc, argv, 3); rect.size.width = 2560 rect.size.height = 1440 if let screen = NSScreen.main { rect = screen.frame } kegs_init(24, Int32(rect.size.width), Int32(rect.size.height), Int32(1)) if(Context_draw) { video_set_blue_mask(UInt32(0x0000ff)) video_set_green_mask(UInt32(0x00ff00)) video_set_red_mask(UInt32(0xff0000)) } else { video_set_red_mask(UInt32(0x0000ff)) video_set_green_mask(UInt32(0x00ff00)) video_set_blue_mask(UInt32(0xff0000)) } video_set_palette() kimage_ptr = video_get_kimage(Int32(0)) mainwin_info.set_kimage(kimage_ptr, title: "GS+", delegate: self) kimage_ptr = video_get_kimage(Int32(1)) debugwin_info.set_kimage(kimage_ptr, title: "Debugger", delegate: self) mainwin_info.create_window() NSApp.activate(ignoringOtherApps: true) main_run_loop() } func main_run_loop() { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) { self.main_run_loop() } let ret = run_16ms() if(ret != 0) { exit(ret); } mainwin_info.update() debugwin_info.update() } } ================================================ FILE: gsplus/src/Info.plist ================================================ BuildMachineOSBuild 18G103 CFBundleDevelopmentRegion en CFBundleExecutable KEGSMAC CFBundleIconFile kegs.icns CFBundleIdentifier com.provalid.Kegs CFBundleInfoDictionaryVersion 6.0 CFBundleName Kegs CFBundlePackageType APPL CFBundleShortVersionString 1.38 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 11A1027 DTPlatformVersion GM DTSDKBuild 19A547 DTSDKName macosx10.15 DTXcode 1110 DTXcodeBuild 11A1027 LSMinimumSystemVersion 10.13 NSHumanReadableCopyright Copyright © 2025 Kent Dickey. All rights reserved. NSHighResolutionCapable NSPrincipalClass NSApplication ================================================ FILE: gsplus/src/Kegs-Bridging-Header.h ================================================ // $KmKId: Kegs-Bridging-Header.h,v 1.1 2019-10-14 22:33:09+00 kentd Exp $ // Use this file to import your target's public headers that you would like to expose to Swift. // #import "defc.h" ================================================ FILE: gsplus/src/MainView.swift ================================================ // $KmKId: MainView.swift,v 1.42 2024-09-15 13:55:35+00 kentd Exp $ // Copyright 2019-2024 by Kent Dickey // This code is covered by the GNU GPL v3 // See the file COPYING.txt or https://www.gnu.org/licenses/ // import Cocoa import CoreGraphics import AudioToolbox class MainView: NSView { var bitmapContext : CGContext! var bitmapData : UnsafeMutableRawPointer! var rawData : UnsafeMutablePointer! var current_flags : UInt = 0 var mouse_moved : Bool = false var mac_warp_pointer : Int32 = 0 var mac_hide_pointer : Int32 = 0 var kimage_ptr : UnsafeMutablePointer! var closed : Bool = false var pixels_per_line = 640 var max_height = 600 let is_cmd = UInt(NSEvent.ModifierFlags.command.rawValue) let is_control = UInt(NSEvent.ModifierFlags.control.rawValue) let is_shift = NSEvent.ModifierFlags.shift.rawValue let is_capslock = NSEvent.ModifierFlags.capsLock.rawValue let is_option = NSEvent.ModifierFlags.option.rawValue override init(frame frameRect: NSRect) { super.init(frame: frameRect) } required init?(coder: NSCoder) { super.init(coder: coder) } func windowDidResize(_ notification: Notification) { print("DID RESIZE") } override func performKeyEquivalent(with event: NSEvent) -> Bool { let keycode = event.keyCode let is_repeat = event.isARepeat let unicode_key = get_unicode_key_from_event(event) // print(".performKeyEquiv keycode: \(keycode), is_repeat: " + // "\(is_repeat)") if(((current_flags & is_cmd) == 0) || is_repeat) { // If CMD isn't being held down, just ignore this return false } // Otherwise, manually do down, then up, for this key adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 0); adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 1); return true } override var acceptsFirstResponder: Bool { return true } func get_unicode_key_from_event(_ event: NSEvent) -> UInt32 { var unicode_key = UInt32(0); if let str = event.charactersIgnoringModifiers { //print(" keydown unmod str: \(str), " + // "code:\(event.keyCode)") let arr_chars = Array(str.unicodeScalars) //print(" arr_chars: \(arr_chars)") if(arr_chars.count == 1) { unicode_key = UInt32(arr_chars[0]); //print("key1:\(unicode_key)") } } return unicode_key } override func keyDown(with event: NSEvent) { let keycode = event.keyCode let is_repeat = event.isARepeat; // print(".keyDown code: \(keycode), repeat: \(is_repeat)") //if let str = event.characters { // print(" keydown str: \(str), code:\(keycode)") //} let unicode_key = get_unicode_key_from_event(event) if(is_repeat) { // If we do CMD-E, then we never get a down for the E, // but we will get repeat events for that E. Let's // ignore those return } adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 0); } override func keyUp(with event: NSEvent) { let keycode = event.keyCode // let is_repeat = event.isARepeat; // print(".keyUp code: \(keycode), repeat: \(is_repeat)") let unicode_key = get_unicode_key_from_event(event) adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 1); } override func flagsChanged(with event: NSEvent) { let flags = event.modifierFlags.rawValue & (is_cmd | is_control | is_shift | is_capslock | is_option) var c025_val = UInt32(0); if((flags & is_shift) != 0) { c025_val |= 1; } if((flags & is_control) != 0) { c025_val |= 2; } if((flags & is_capslock) != 0) { c025_val |= 4; } if((flags & is_option) != 0) { c025_val |= 0x40; } if((flags & is_cmd) != 0) { c025_val |= 0x80; } adb_update_c025_mask(kimage_ptr, c025_val, UInt32(0xc7)); current_flags = flags //print("flagsChanged: \(flags) and keycode: \(event.keyCode)") } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { // This is to let the first click when changing to this window // through to the app, I probably don't want this. return false } override func mouseMoved(with event: NSEvent) { //let type = event.eventNumber //print(" event type: \(type)") mac_update_mouse(event: event, buttons_state:0, buttons_valid:0) } override func mouseDragged(with event: NSEvent) { mac_update_mouse(event: event, buttons_state: 0, buttons_valid: 0) } override func otherMouseDown(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:2, buttons_valid:2) } override func otherMouseUp(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:0, buttons_valid:2) } override func mouseEntered(with event: NSEvent) { print("mouse entered") } override func rightMouseUp(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:0, buttons_valid:4) } override func rightMouseDown(with event: NSEvent) { print("Right mouse down") mac_update_mouse(event: event, buttons_state:4, buttons_valid:4) } override func mouseUp(with event: NSEvent) { // print("Mouse up \(event.locationInWindow.x)," + // "\(event.locationInWindow.y)") mac_update_mouse(event: event, buttons_state:0, buttons_valid:1) } override func mouseDown(with event: NSEvent) { //print("Mouse down \(event.locationInWindow.x)," + // "\(event.locationInWindow.y)") mac_update_mouse(event: event, buttons_state:1, buttons_valid:1) } func mac_update_mouse(event: NSEvent, buttons_state: Int, buttons_valid: Int) { var warp = Int32(0) var x_width = 0 var y_height = 0 var x = Int32(0) var y = Int32(0) var do_delta = Int(0) let hide = adb_get_hide_warp_info(kimage_ptr, &warp) if(warp != mac_warp_pointer) { mouse_moved = true } mac_warp_pointer = warp if(mac_hide_pointer != hide) { mac_hide_pointer(hide: hide) } mac_hide_pointer = hide let location = event.locationInWindow if(!Context_draw) { // We're letting the Mac scale the window for us, // so video_scale_mouse*() doesn't know the scale // factor, so pass it in x_width = Int(bounds.size.width); y_height = Int(bounds.size.height); } let raw_x = location.x let raw_y = bounds.size.height - 1 - location.y // raw_y is 0 at the top of the window now x = video_scale_mouse_x(kimage_ptr, Int32(raw_x), Int32(x_width)) y = video_scale_mouse_y(kimage_ptr, Int32(raw_y), Int32(y_height)) do_delta = 0; if(mac_warp_pointer != 0) { do_delta |= 0x1000; // x,y are deltas x = Int32(event.deltaX) y = Int32(event.deltaY) } let ret = adb_update_mouse(kimage_ptr, x, y, Int32(buttons_state), Int32(buttons_valid | do_delta)) if(ret != 0) { mouse_moved = true } guard let win = window else { return // No valid window } if(mouse_moved) { var rect1 = NSRect.zero // If warp, warp cursor to middle of window. Moving // the cursor requires absolute screen coordinates, // where y=0 is the top of the screen. We must convert // window coords (where y=0 is the bottom of the win). mouse_moved = false let warp_x = CGFloat(video_unscale_mouse_x(kimage_ptr, Int32(A2_WINDOW_WIDTH/2), Int32(x_width))) let warp_y = CGFloat(video_unscale_mouse_y(kimage_ptr, Int32(A2_WINDOW_HEIGHT/2), Int32(y_height))) let scr_height = CGDisplayPixelsHigh(CGMainDisplayID()); rect1.origin.x = CGFloat(warp_x) rect1.origin.y = bounds.size.height - 1 - CGFloat(warp_y) rect1.size.width = 1; rect1.size.height = 0; let screen_rect = win.convertToScreen(rect1) let screen_rect_y = CGFloat(scr_height) - screen_rect.origin.y let cg_loc = CGPoint(x: screen_rect.origin.x, y: CGFloat(screen_rect_y)) //print("scr_rect:\(screen_rect)") if(warp != 0) { // Warp to middle of the window CGDisplayMoveCursorToPoint(CGMainDisplayID(), cg_loc); } } } func mac_hide_pointer(hide: Int32) { // print("Hide called: \(hide)") if(hide != 0) { CGDisplayHideCursor(CGMainDisplayID()) } else { CGDisplayShowCursor(CGMainDisplayID()) } } func initialize() { //let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) print("Initialize view called") // Get width,height from video.c to handle toggling status lines let width = Int(video_get_a2_width(kimage_ptr)) let height = Int(video_get_a2_height(kimage_ptr)) //if let screen = NSScreen.main { // let rect = screen.frame // width = Int(rect.size.width) // height = Int(rect.size.height) //} pixels_per_line = width max_height = height //print("pixels_per_line: \(pixels_per_line), " + // "max_height: \(max_height)") let color_space = CGDisplayCopyColorSpace(CGMainDisplayID()) //let colorSpace = CGColorSpaceCreateDeviceRGB() bitmapContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: color_space, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) //CGImageAlphaInfo.noneSkipLast.rawValue bitmapData = bitmapContext.data! // Set the intial value of the data to black (0) bitmapData.initializeMemory(as: UInt32.self, repeating: 0, count: height * width) rawData = bitmapData.bindMemory(to: UInt32.self, capacity: height * width) //print("Calling video_update_scale from MainViewinitialize()") let x_width = Int(video_get_x_width(kimage_ptr)) let x_height = Int(video_get_x_height(kimage_ptr)) video_update_scale(kimage_ptr, Int32(x_width), Int32(x_height), Int32(1)) if(Context_draw) { video_set_alpha_mask(UInt32(0xff000000)) // Set video.c alpha mask, since 0 means transparent } } override func draw(_ dirtyRect: NSRect) { var rect : Change_rect //super.draw(dirtyRect) // Documentation says super.draw not needed... // Draw the current image buffer to the screen let context = NSGraphicsContext.current!.cgContext if(!Context_draw) { // Safe, simple drawing let image = bitmapContext.makeImage()! context.draw(image, in: bounds) // The above call does the actual copy of // data to the screen, and can take a while //print("Draw, bounds:\(bounds), dirtyr:\(dirtyRect)") } else { // Unsafe, more direct drawing by peeking into // NSGraphicsContext.current.data if let data = context.data { rect = Change_rect(x:0, y:0, width:Int32(context.width), height:Int32(context.height)); video_update_scale(kimage_ptr, Int32(context.width), Int32(context.height), Int32(0)) video_out_data_scaled(data, kimage_ptr, Int32(context.bytesPerRow/4), &rect); video_out_done(kimage_ptr); print("Did out_data_scaled, rect:\(rect)"); } } } @objc func do_config(_ : AnyObject) { // Create a "virtual" F4 press //print("do_config") // Create a keydown for the F4 key (keycode:0x76) adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 0); // and create a keyup for the F4 key (keycode:0x76) adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 1); } @objc func do_copy_text(_ : AnyObject) { // print("do_copy"); //let text_buf_cstr = UnsafeMutablePointer.allocate( // capacity: 2100); if let cstr = cfg_text_screen_str() { let str = String(cString: cstr); NSPasteboard.general.clearContents(); NSPasteboard.general.setString(str, forType: NSPasteboard.PasteboardType.string); } } @objc func do_paste(_ : AnyObject) { // print("do_paste") let general = NSPasteboard.general; guard let str = general.string(forType: .string) else { print("Cannot paste, nothing in clipboard"); return } //print("str: \(str)") for raw_c in str.utf8 { let c = UInt32(raw_c) let ret = adb_paste_add_buf(c) if(ret != 0) { print("Paste too large!") return; } } } func mac_update_display() { var valid : Int32 var rect : Change_rect var dirty_rect = NSRect.zero if(Context_draw) { // We just need to know if there are any changes, // don't actually do the copies now valid = video_out_query(kimage_ptr); if(valid != 0) { self.setNeedsDisplay(bounds) print("Needs display"); } return; } // Otherwise, update rawData in our Bitmap now rect = Change_rect(x:0, y:0, width:0, height:0) for i in 0.. ../GSplus.app/Contents/PkgInfo cp -f Info.plist ../GSplus.app/Contents/ $(PROJROOT)/lib/make_mac_icon $(PROJROOT)/lib/kegsicon.png cp -f kegs.icns ../GSplus.app/Contents/Resources/ touch '../GSplus.app/Icon?' #cp -f $(PROJROOT)/lib/2mg.icns ../GSplus.app/Contents/Resources/ #cp -f $(PROJROOT)/lib/525.icns ../GSplus.app/Contents/Resources/ # Linux for X builds: gsplus-x: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ -lX11 -lXext mv gsplus .. # Cygwin for X builds: gsplus.exe: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ -lXext -lX11 -lm mv gsplus.exe .. # Mingw32 (native windows) builds: (broken, doesn't work currently) gspluswin.exe: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(EXTRA_LIBS) \ -lwinmm -lgdi32 -ldsound -lcomctl32 -lws2_32 mv $(NAME)$(SUFFIX) .. .s.o: $(AS) -c $(OPTS) -I. $*.s .c.o: $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.c .m.o: $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.m AppDelegate.o: AppDelegate.swift sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ MainView.swift -o $*.o MainView.o: MainView.swift sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ AppDelegate.swift -o $*.o win32.o: win32.rc windres -o win32.o win32.rc .c.proto: $(KMKROOT)/bin/kmkproto $(XOPTS) $*.c > $*.proto echo >> $*.proto .m.proto: $(KMKROOT)/bin/kmkproto $(XOPTS) $*.m > $*.proto echo >> $*.proto $(PROTO_OUT): $(PROTO_FILE_LIST) ldvars proto_vars $(KMKROOT)/bin/kmkproto_head $(PROTO_OUT) tmp_protos.h cat /dev/null $(PROTO_FILE_LIST) >> tmp_protos.h rm -f $(PROTO_OUT) mv tmp_protos.h $(PROTO_OUT) kmk_cp_if_diff $(PROTO_OUT) .. rm -f *.proto compile_time.o: $(OBJECTS) $(OBJECTS1) include dependency ================================================ FILE: gsplus/src/adb.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* adb_mode bit 3 and bit 2 (faster repeats for arrows and space/del) not done*/ #include "defc.h" extern int Verbose; extern word32 g_vbl_count; extern int g_num_lines_prev_superhires640; extern int g_num_lines_prev_superhires; extern int g_rom_version; extern int g_fast_disk_emul_en; extern int g_limit_speed; extern int g_irq_pending; extern int g_swap_paddles; extern int g_invert_paddles; extern int g_joystick_type; extern int g_config_control_panel; extern int g_status_enable; extern dword64 g_cur_dfcyc; extern byte *g_slow_memory_ptr; extern byte *g_memory_ptr; extern word32 g_mem_size_total; extern Kimage g_mainwin_kimage; extern Kimage g_debugwin_kimage; enum { ADB_IDLE = 0, ADB_IN_CMD, ADB_SENDING_DATA, }; #define ADB_C027_MOUSE_DATA 0x80 #define ADB_C027_MOUSE_INT 0x40 #define ADB_C027_DATA_VALID 0x20 #define ADB_C027_DATA_INT 0x10 #define ADB_C027_KBD_VALID 0x08 #define ADB_C027_KBD_INT 0x04 #define ADB_C027_MOUSE_COORD 0x02 #define ADB_C027_CMD_FULL 0x01 #define ADB_C027_NEG_MASK ( ~ ( \ ADB_C027_MOUSE_DATA | ADB_C027_DATA_VALID | \ ADB_C027_KBD_VALID | ADB_C027_MOUSE_COORD | \ ADB_C027_CMD_FULL)) int halt_on_all_c027 = 0; word32 g_adb_repeat_delay = 45; word32 g_adb_repeat_rate = 3; word32 g_adb_repeat_info = 0x23; word32 g_adb_char_set = 0x0; word32 g_adb_layout_lang = 0x0; word32 g_adb_interrupt_byte = 0; int g_adb_state = ADB_IDLE; word32 g_adb_cmd = (word32)-1; int g_adb_cmd_len = 0; int g_adb_cmd_so_far = 0; word32 g_adb_cmd_data[16]; #define MAX_ADB_DATA_PEND 16 word32 g_adb_data[MAX_ADB_DATA_PEND]; int g_adb_data_pending = 0; word32 g_c027_val = 0; word32 g_c025_val = 0; byte adb_memory[256]; word32 g_adb_mode = 0; /* mode set via set_modes, clear_modes */ int g_warp_pointer = 0; int g_hide_pointer = 0; int g_unhide_pointer = 0; int g_adb_copy_requested = 0; int g_mouse_a2_x = 0; int g_mouse_a2_y = 0; int g_mouse_a2_button = 0; int g_mouse_fifo_pos = 0; int g_mouse_raw_x = 0; int g_mouse_raw_y = 0; #define ADB_MOUSE_FIFO 8 STRUCT(Mouse_fifo) { dword64 dfcyc; int x; int y; int buttons; }; Mouse_fifo g_mouse_fifo[ADB_MOUSE_FIFO] = { { 0, 0, 0, 0 } }; int g_adb_mouse_valid_data = 0; int g_adb_mouse_coord = 0; #define MAX_KBD_BUF 8 #define MAX_KBD_PASTE_BUF 32768 int g_adb_mainwin_has_focus = 1; #if defined(__linux__) || defined(_WIN32) int g_adb_swap_command_option = 1; // Default to swap on Linux/Win #else int g_adb_swap_command_option = 0; #endif int g_key_down = 0; int g_hard_key_down = 0; int g_a2code_down = 0; int g_kbd_read_no_update = 0; int g_kbd_chars_buffered = 0; int g_kbd_buf[MAX_KBD_BUF]; int g_kbd_paste_rd_pos = 0; int g_kbd_paste_wr_pos = 0; byte g_kbd_paste_buf[MAX_KBD_PASTE_BUF]; word32 g_kbd_paste_last_key = 0; word32 g_adb_repeat_vbl = 0; int g_kbd_dev_addr = 2; /* ADB physical kbd addr */ int g_mouse_dev_addr = 3; /* ADB physical mouse addr */ int g_kbd_ctl_addr = 2; /* ADB microcontroller's kbd addr */ int g_mouse_ctl_addr = 3; /* ADB ucontroller's mouse addr*/ /* above are ucontroller's VIEW of where mouse/kbd */ /* are...if they are moved, mouse/keyboard funcs */ /* should stop (c025, c000, c024, etc). */ word32 g_virtual_key_up[4]; /* bitmask of all possible 128 a2codes */ /* indicates which keys are up=1 by bit */ int g_rawa2_to_a2code[128]; int g_keypad_key_is_down[10] = { 0 };/* List from 0-9 of which keypad */ /* keys are currently pressed */ #define SHIFT_DOWN ( (g_c025_val & 0x01) ) #define CTRL_DOWN ( (g_c025_val & 0x02) ) #define CAPS_LOCK_DOWN ( (g_c025_val & 0x04) ) #define OPTION_DOWN ( (g_c025_val & 0x40) ) #define CMD_DOWN ( (g_c025_val & 0x80) ) #define MAX_ADB_KBD_REG3 16 int g_kbd_reg0_pos = 0; int g_kbd_reg0_data[MAX_ADB_KBD_REG3]; int g_kbd_reg3_16bit = 0x602; /* also set in adb_reset()! */ int g_adb_init = 0; /* Format: a2code, ascii if no shift, ascii if shift, ascii if ctl */ const int g_a2_key_to_ascii[][4] = { { 0x00, 'a', 'A', 0x01 }, { 0x01, 's', 'S', 0x13 }, { 0x02, 'd', 'D', 0x04 }, { 0x03, 'f', 'F', 0x06 }, { 0x04, 'h', 'H', 0x08 }, { 0x05, 'g', 'G', 0x07 }, { 0x06, 'z', 'Z', 0x1a }, { 0x07, 'x', 'X', 0x18 }, { 0x08, 'c', 'C', 0x03 }, { 0x09, 'v', 'V', 0x16 }, { 0x0a, -1, -1, -1 }, { 0x0b, 'b', 'B', 0x02 }, { 0x0c, 'q', 'Q', 0x11 }, { 0x0d, 'w', 'W', 0x17 }, { 0x0e, 'e', 'E', 0x05 }, { 0x0f, 'r', 'R', 0x12 }, { 0x10, 'y', 'Y', 0x19 }, { 0x11, 't', 'T', 0x14 }, { 0x12, '1', '!', -1 }, { 0x13, '2', '@', 0x00 }, { 0x14, '3', '#', -1 }, { 0x15, '4', '$', -1 }, { 0x16, '6', '^', 0x1e }, { 0x17, '5', '%', -1 }, { 0x18, '=', '+', -1 }, { 0x19, '9', '(', -1 }, { 0x1a, '7', '&', -1 }, { 0x1b, '-', '_', 0x1f }, { 0x1c, '8', '*', -1 }, { 0x1d, '0', ')', -1 }, { 0x1e, ']', '}', 0x1d }, { 0x1f, 'o', 'O', 0x0f }, { 0x20, 'u', 'U', 0x15 }, { 0x21, '[', '{', 0x1b }, { 0x22, 'i', 'I', 0x09 }, { 0x23, 'p', 'P', 0x10 }, { 0x24, 0x0d, 0x0d, -1 }, /* return */ { 0x25, 'l', 'L', 0x0c }, { 0x26, 'j', 'J', 0x0a }, { 0x27, 0x27, '"', -1 }, /* single quote */ { 0x28, 'k', 'K', 0x0b }, { 0x29, ';', ':', -1 }, { 0x2a, 0x5c, '|', 0x1c }, /* \, | */ { 0x2b, ',', '<', -1 }, { 0x2c, '/', '?', 0x7f }, { 0x2d, 'n', 'N', 0x0e }, { 0x2e, 'm', 'M', 0x0d }, { 0x2f, '.', '>', -1 }, { 0x30, 0x09, 0x09, -1 }, /* tab */ { 0x31, ' ', ' ', -1 }, { 0x32, '`', '~', -1 }, { 0x33, 0x7f, 0x7f, -1 }, /* Delete */ { 0x34, -1, -1, -1 }, { 0x35, 0x1b, 0x1b, -1 }, /* Esc */ { 0x36, 0x0200, 0x0200, -1 }, /* control */ { 0x37, 0x8000, 0x8000, -1 }, /* Command */ { 0x38, 0x0100, 0x0100, -1 }, /* shift */ { 0x39, 0x0400, 0x0400, -1 }, /* caps lock */ { 0x3a, 0x4000, 0x4000, -1 }, /* Option */ { 0x3b, 0x08, 0x08, -1 }, /* left */ { 0x3c, 0x15, 0x15, -1 }, /* right */ { 0x3d, 0x0a, 0x0a, -1 }, /* down */ { 0x3e, 0x0b, 0x0b, -1 }, /* up arrow */ { 0x3f, -1, -1, -1 }, { 0x40, -1, -1, -1 }, { 0x41, 0x102e, 0x102c, -1 }, /* keypad . */ { 0x42, -1, -1, -1 }, { 0x43, 0x102a, 0x102a, -1 }, /* keypad * */ { 0x44, -1, -1, -1 }, { 0x45, 0x102b, 0x102b, -1 }, /* keypad + */ { 0x46, -1, -1, -1 }, { 0x47, 0x1018, 0x1018, -1 }, /* keypad Clear */ { 0x48, -1, -1, -1 }, { 0x49, -1, -1, -1 }, { 0x4a, -1, -1, -1 }, { 0x4b, 0x102f, 0x102f, -1 }, /* keypad / */ { 0x4c, 0x100d, 0x100d, -1 }, /* keypad enter */ { 0x4d, -1, -1, -1 }, { 0x4e, 0x102d, 0x102d, -1 }, /* keypad - */ { 0x4f, -1, -1, -1 }, { 0x50, -1, -1, -1 }, { 0x51, 0x103d, 0x103d, -1 }, /* keypad = */ { 0x52, 0x1030, 0x1030, -1 }, /* keypad 0 */ { 0x53, 0x1031, 0x1031, -1 }, /* keypad 1 */ { 0x54, 0x1032, 0x1032, -1 }, /* keypad 2 */ { 0x55, 0x1033, 0x1033, -1 }, /* keypad 3 */ { 0x56, 0x1034, 0x1034, -1 }, /* keypad 4 */ { 0x57, 0x1035, 0x1035, -1 }, /* keypad 5 */ { 0x58, 0x1036, 0x1036, -1 }, /* keypad 6 */ { 0x59, 0x1037, 0x1037, -1 }, /* keypad 7 */ { 0x5a, 'a', 'A', 0x01 }, /* probably not necessary */ { 0x5b, 0x1038, 0x1038, -1 }, /* keypad 8 */ { 0x5c, 0x1039, 0x1039, -1 }, /* keypad 9 */ { 0x5d, -1, -1, -1 }, { 0x5e, -1, -1, -1 }, { 0x5f, -1, -1, -1 }, { 0x60, 0x8005, 0x1060, -1 }, /* F5 */ { 0x61, 0x8006, 0x1061, -1 }, /* F6 */ { 0x62, 0x8007, 0x1062, -1 }, /* F7 */ { 0x63, 0x8003, 0x1063, -1 }, /* F3 */ { 0x64, 0x8008, 0x1064, -1 }, /* F8 */ { 0x65, 0x8009, 0x1065, -1 }, /* F9 */ { 0x66, -1, -1, -1 }, { 0x67, 0x800b, 0x1067, -1 }, /* F11 */ { 0x68, -1, -1, -1 }, { 0x69, 0x800d, 0x1069, -1 }, /* F13 */ { 0x6a, -1, -1, -1 }, { 0x6b, 0x800e, 0x106b, -1 }, /* F14 */ { 0x6c, -1, -1, -1 }, { 0x6d, 0x800a, 0x106d, -1 }, /* F10 */ { 0x6e, 0x4000, 0x4000, -1 }, /* windows key alias to option */ { 0x6f, 0x800c, 0x106f, -1 }, /* F12 */ { 0x70, -1, -1, -1 }, { 0x71, 0x800f, 0x1071, -1 }, /* F15 */ { 0x72, 0x1072, 0x1072, -1 }, /* Help, insert */ { 0x73, 0x1073, 0x1073, -1 }, /* Home */ { 0x74, 0x1074, 0x1074, -1 }, /* Page up */ { 0x75, 0x1075, 0x1075, -1 }, /* keypad delete */ { 0x76, 0x8004, 0x1076, -1 }, /* F4 */ { 0x77, 0x1077, 0x1077, -1 }, /* keypad end */ { 0x78, 0x8002, 0x1078, -1 }, /* F2 */ { 0x79, 0x1079, 0x1079, -1 }, /* keypad page down */ { 0x7a, 0x8001, 0x107a, -1 }, /* F1 */ { 0x7b, 0x08, 0x08, -1 }, /* left */ /* remapped to 0x3b */ { 0x7c, 0x15, 0x15, -1 }, /* right */ /* remapped to 0x3c */ { 0x7d, 0x0a, 0x0a, -1 }, /* down */ /* remapped to 0x3d */ { 0x7e, 0x0b, 0x0b, -1 }, /* up arrow */ /* remapped to 0x3e */ { 0x7f, -1, -1, -1 }, /* Reset */ }; int adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr) { if((kimage_ptr == &g_mainwin_kimage) && g_adb_mainwin_has_focus) { *warpptr = g_warp_pointer; return g_hide_pointer; } *warpptr = 0; return 0; } int adb_get_copy_requested() { int ret; ret = g_adb_copy_requested; g_adb_copy_requested = 0; return ret; } void adb_nonmain_check() { // Debug window active. Undo F8 pointer warping g_warp_pointer = 0; g_hide_pointer = 0; } void adb_init() { int keycode; int i; if(g_adb_init) { halt_printf("g_adb_init = %d!\n", g_adb_init); } g_adb_init = 1; for(i = 0; i < 128; i++) { keycode = g_a2_key_to_ascii[i][0]; if(keycode != i) { printf("ADB keycode lost/skipped: i=%x: keycode=%x\n", i, keycode); my_exit(1); } g_rawa2_to_a2code[i] = -1; } g_c025_val = 0; for(i = 0; i < 4; i++) { g_virtual_key_up[i] = -1; } for(i = 0; i < 10; i++) { g_keypad_key_is_down[i] = 0; } } void adb_reset() { g_c027_val = 0; g_key_down = 0; g_kbd_paste_rd_pos = 0; g_kbd_paste_wr_pos = 0; g_kbd_chars_buffered = 0; g_kbd_dev_addr = 2; g_mouse_dev_addr = 3; g_kbd_ctl_addr = 2; g_mouse_ctl_addr = 3; adb_clear_data_int(); adb_clear_mouse_int(); adb_clear_kbd_srq(); g_adb_data_pending = 0; g_adb_interrupt_byte = 0; g_adb_state = ADB_IDLE; g_adb_mouse_coord = 0; g_adb_mouse_valid_data = 0; g_kbd_reg0_pos = 0; g_kbd_reg3_16bit = 0x602; } #define LEN_ADB_LOG 16 STRUCT(Adb_log) { word32 addr; int val; int state; }; Adb_log g_adb_log[LEN_ADB_LOG]; int g_adb_log_pos = 0; void adb_log(word32 addr, int val) { int pos; pos = g_adb_log_pos; g_adb_log[pos].addr = addr; g_adb_log[pos].val = val; g_adb_log[pos].state = g_adb_state; pos++; if(pos >= LEN_ADB_LOG) { pos = 0; } g_adb_log_pos = pos; } void show_adb_log(void) { int pos; int i; pos = g_adb_log_pos; printf("ADB log pos: %d\n", pos); for(i = 0; i < LEN_ADB_LOG; i++) { pos--; if(pos < 0) { pos = LEN_ADB_LOG - 1; } printf("%d:%d: addr:%04x = %02x, st:%d\n", i, pos, g_adb_log[pos].addr, g_adb_log[pos].val, g_adb_log[pos].state); } printf("kbd: dev: %x, ctl: %x; mouse: dev: %x, ctl: %x\n", g_kbd_dev_addr, g_kbd_ctl_addr, g_mouse_dev_addr, g_mouse_ctl_addr); printf("g_adb_state: %d, g_adb_interrupt_byte: %02x\n", g_adb_state, g_adb_interrupt_byte); } void adb_error(void) { halt_printf("Adb Error\n"); show_adb_log(); } void adb_add_kbd_srq() { if(g_kbd_reg3_16bit & 0x200) { /* generate SRQ */ g_adb_interrupt_byte |= 0x08; add_irq(IRQ_PENDING_ADB_KBD_SRQ); } else { printf("Got keycode but no kbd SRQ!\n"); } } void adb_clear_kbd_srq() { remove_irq(IRQ_PENDING_ADB_KBD_SRQ); /* kbd SRQ's are the only ones to handle now, so just clean it out */ g_adb_interrupt_byte &= (~(0x08)); } void adb_add_data_int() { if(g_c027_val & ADB_C027_DATA_INT) { add_irq(IRQ_PENDING_ADB_DATA); } } void adb_add_mouse_int() { if(g_c027_val & ADB_C027_MOUSE_INT) { add_irq(IRQ_PENDING_ADB_MOUSE); } } void adb_clear_data_int() { remove_irq(IRQ_PENDING_ADB_DATA); } void adb_clear_mouse_int() { remove_irq(IRQ_PENDING_ADB_MOUSE); } void adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2) { word32 val; int shift_amount; int i; if((num_bytes >= 12) || (num_bytes >= MAX_ADB_DATA_PEND)) { halt_printf("adb_send_bytes: %d is too many!\n", num_bytes); } g_adb_state = ADB_SENDING_DATA; g_adb_data_pending = num_bytes; adb_add_data_int(); for(i = 0; i < num_bytes; i++) { if(i < 4) { val = val0; } else if(i < 8) { val = val1; } else { val = val2; } shift_amount = 8*(3 - i); g_adb_data[i] = (val >> shift_amount) & 0xff; adb_printf("adb_send_bytes[%d] = %02x\n", i, g_adb_data[i]); } } void adb_send_1byte(word32 val) { if(g_adb_data_pending != 0) { halt_printf("g_adb_data_pending: %d\n", g_adb_data_pending); } adb_send_bytes(1, val << 24, 0, 0); } void adb_response_packet(int num_bytes, word32 val) { if(g_adb_data_pending != 0) { halt_printf("adb_response_packet, but pending: %d\n", g_adb_data_pending); } g_adb_state = ADB_IDLE; g_adb_data_pending = num_bytes; g_adb_data[0] = val & 0xff; g_adb_data[1] = (val >> 8) & 0xff; g_adb_data[2] = (val >> 16) & 0xff; g_adb_data[3] = (val >> 24) & 0xff; if(num_bytes) { g_adb_interrupt_byte |= 0x80 + num_bytes - 1; } else { g_adb_interrupt_byte |= 0x80; } adb_printf("adb_response packet: %d: %08x\n", num_bytes, val); adb_add_data_int(); } void adb_kbd_reg0_data(int a2code, int is_up) { if(g_kbd_reg0_pos >= MAX_ADB_KBD_REG3) { /* too many keys, toss */ halt_printf("Had to toss key: %02x, %d\n", a2code, is_up); return; } g_kbd_reg0_data[g_kbd_reg0_pos] = a2code + (is_up << 7); adb_printf("g_kbd_reg0_data[%d] = %02x\n", g_kbd_reg0_pos, g_kbd_reg0_data[g_kbd_reg0_pos]); g_kbd_reg0_pos++; adb_add_kbd_srq(); } void adb_kbd_talk_reg0() { word32 val0, val1, reg; int num_bytes, num; int i; num = 0; val0 = g_kbd_reg0_data[0]; val1 = g_kbd_reg0_data[1]; num_bytes = 0; if(g_kbd_reg0_pos > 0) { num_bytes = 2; num = 1; if((val0 & 0x7f) == 0x7f) { /* reset */ val1 = val0; } else if(g_kbd_reg0_pos > 1) { num = 2; if((val1 & 0x7f) == 0x7f) { /* If first byte some other key, don't */ /* put RESET next! */ num = 1; val1 = 0xff; } } else { val1 = 0xff; } } if(num) { for(i = num; i < g_kbd_reg0_pos; i++) { g_kbd_reg0_data[i-1] = g_kbd_reg0_data[i]; } g_kbd_reg0_pos -= num; } reg = (val0 << 8) + val1; adb_printf("adb_kbd_talk0: %04x\n", reg); adb_response_packet(num_bytes, reg); if(g_kbd_reg0_pos == 0) { adb_clear_kbd_srq(); } } void adb_set_config(word32 val0, word32 val1, word32 val2) { int new_mouse; int new_kbd; int tmp1; new_mouse = val0 >> 4; new_kbd = val0 & 0xf; if(new_mouse != g_mouse_ctl_addr) { printf("ADB config: mouse from %x to %x!\n", g_mouse_ctl_addr, new_mouse); adb_error(); g_mouse_ctl_addr = new_mouse; } if(new_kbd != g_kbd_ctl_addr) { printf("ADB config: kbd from %x to %x!\n", g_kbd_ctl_addr, new_kbd); adb_error(); g_kbd_ctl_addr = new_kbd; } if(val1) { // Do nothing } tmp1 = val2 >> 4; if(tmp1 == 4) { g_adb_repeat_delay = 0; } else if(tmp1 < 4) { g_adb_repeat_delay = (tmp1 + 1) * 15; } else { halt_printf("Bad ADB repeat delay: %02x\n", tmp1); } tmp1 = val2 & 0xf; if(g_rom_version >= 3) { tmp1 = 9 - tmp1; } switch(tmp1) { case 0: g_adb_repeat_rate = 1; break; case 1: g_adb_repeat_rate = 2; break; case 2: g_adb_repeat_rate = 3; break; case 3: g_adb_repeat_rate = 3; break; case 4: g_adb_repeat_rate = 4; break; case 5: g_adb_repeat_rate = 5; break; case 6: g_adb_repeat_rate = 7; break; case 7: g_adb_repeat_rate = 15; break; case 8: /* I don't know what this should be, ROM 03 uses it */ g_adb_repeat_rate = 30; break; case 9: /* I don't know what this should be, ROM 03 uses it */ g_adb_repeat_rate = 60; break; default: halt_printf("Bad repeat rate: %02x\n", tmp1); } } void adb_set_new_mode(word32 val) { if(val & 0x03) { printf("Disabling keyboard/mouse:%02x!\n", val); } if(val & 0xa2) { halt_printf("ADB set mode: %02x!\n", val); adb_error(); } g_adb_mode = val; } int adb_read_c026() { word32 ret; int i; ret = 0; switch(g_adb_state) { case ADB_IDLE: ret = g_adb_interrupt_byte; g_adb_interrupt_byte = 0; if(g_irq_pending & IRQ_PENDING_ADB_KBD_SRQ) { g_adb_interrupt_byte |= 0x08; } if(g_adb_data_pending == 0) { if(ret & 0x80) { halt_printf("read_c026: ret:%02x, pend:%d\n", ret, g_adb_data_pending); } adb_clear_data_int(); } if(g_adb_data_pending) { if(g_adb_state != ADB_IN_CMD) { g_adb_state = ADB_SENDING_DATA; } } break; case ADB_IN_CMD: ret = 0; break; case ADB_SENDING_DATA: ret = g_adb_data[0]; for(i = 1; i < g_adb_data_pending; i++) { g_adb_data[i-1] = g_adb_data[i]; } g_adb_data_pending--; if(g_adb_data_pending <= 0) { g_adb_data_pending = 0; g_adb_state = ADB_IDLE; adb_clear_data_int(); } break; default: halt_printf("Bad ADB state: %d!\n", g_adb_state); adb_clear_data_int(); break; } adb_printf("Reading c026. Returning %02x, st: %02x, pend: %d\n", ret, g_adb_state, g_adb_data_pending); adb_log(0xc026, ret); return (ret & 0xff); } void adb_write_c026(int val) { word32 tmp; int dev; adb_printf("Writing c026 with %02x\n", val); adb_log(0x1c026, val); switch(g_adb_state) { case ADB_IDLE: g_adb_cmd = val; g_adb_cmd_so_far = 0; g_adb_cmd_len = 0; dev = val & 0xf; switch(val) { case 0x01: /* Abort */ adb_printf("Performing adb abort\n"); /* adb_abort() */ break; case 0x03: /* Flush keyboard buffer */ adb_printf("Flushing adb keyboard buffer\n"); /* Do nothing */ break; case 0x04: /* Set modes */ adb_printf("ADB set modes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x05: /* Clear modes */ adb_printf("ADB clear modes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x06: /* Set config */ adb_printf("ADB set config\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 3; break; case 0x07: /* Sync */ adb_printf("Performing sync cmd!\n"); g_adb_state = ADB_IN_CMD; if(g_rom_version == 1) { g_adb_cmd_len = 4; } else { g_adb_cmd_len = 8; } break; case 0x08: /* Write mem */ adb_printf("Starting write_mem cmd\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0x09: /* Read mem */ adb_printf("Performing read_mem cmd!\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0x0a: /* Read modes byte */ printf("Performing read_modes cmd!\n"); /* set_halt(1); */ adb_send_1byte(g_adb_mode); break; case 0x0b: /* Read config bytes */ printf("Performing read_configs cmd!\n"); tmp = (g_mouse_ctl_addr << 20) + (g_kbd_ctl_addr << 16) + (g_adb_char_set << 12) + (g_adb_layout_lang << 8) + (g_adb_repeat_info << 0); tmp = (0x82U << 24) + tmp; adb_send_bytes(4, tmp, 0, 0); break; case 0x0d: /* Get Version */ adb_printf("Performing get_version cmd!\n"); val = 0; if(g_rom_version == 1) { /* ROM 01 = revision 5 */ val = 5; } else { /* ROM 03 checks for rev >= 6 */ val = 6; } adb_send_1byte(val); break; case 0x0e: /* Read avail char sets */ adb_printf("Performing read avail char sets cmd!\n"); adb_send_bytes(2, /* just 2 bytes */ 0x08000000, /* number of ch sets=0x8 */ 0, 0); /* set_halt(1); */ break; case 0x0f: /* Read avail kbd layouts */ adb_printf("Performing read avail kbd layouts cmd!\n"); adb_send_bytes(0x2, /* number of kbd layouts=0xa */ 0x0a000000, 0, 0); /* set_halt(1); */ break; case 0x10: /* Reset */ printf("ADB reset, cmd 0x10\n"); do_reset(); break; case 0x11: /* Send ADB keycodes */ adb_printf("Sending ADB keycodes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x12: /* ADB cmd 12: ROM 03 only! */ if(g_rom_version >= 3) { g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; } else { printf("ADB cmd 12, but not ROM 3!\n"); adb_error(); } break; case 0x13: /* ADB cmd 13: ROM 03 only! */ if(g_rom_version >= 3) { g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; } else { printf("ADB cmd 13, but not ROM 3!\n"); adb_error(); } break; case 0x73: /* Disable SRQ device 3: mouse */ adb_printf("Disabling Mouse SRQ's (device 3)\n"); /* HACK HACK...should deal with SRQs on mouse */ break; case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: /* Listen dev x reg 3 */ adb_printf("Sending data to dev %x reg 3\n", dev); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: /* Talk dev x reg 0 */ adb_printf("Performing talk dev %x reg 0\n", dev); if(dev == g_kbd_dev_addr) { adb_kbd_talk_reg0(); } else { printf("Unknown talk dev %x reg 0!\n", dev); /* send no data, on SRQ, system polls devs */ /* so we don't want to send anything */ adb_error(); } break; case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: /* Talk dev x reg 3 */ adb_printf("Performing talk dev %x reg 3\n", dev); if(dev == g_kbd_dev_addr) { adb_response_packet(2, g_kbd_reg3_16bit); } else { printf("Performing talk dev %x reg 3!!\n", dev); adb_error(); } break; default: halt_printf("ADB ucontroller cmd %02x unknown!\n", val); /* The Gog's says ACS Demo 2 has a bug and writes to */ /* c026 */ break; } break; case ADB_IN_CMD: adb_printf("Setting byte %d of cmd %02x to %02x\n", g_adb_cmd_so_far, g_adb_cmd, val); g_adb_cmd_data[g_adb_cmd_so_far] = val; g_adb_cmd_so_far++; if(g_adb_cmd_so_far >= g_adb_cmd_len) { adb_printf("Finished cmd %02x\n", g_adb_cmd); do_adb_cmd(); } break; default: printf("adb_state: %02x is unknown! Setting it to ADB_IDLE\n", g_adb_state); g_adb_state = ADB_IDLE; adb_error(); halt_on_all_c027 = 1; break; } return; } void do_adb_cmd() { word32 val; int dev, new_kbd, addr; dev = g_adb_cmd & 0xf; g_adb_state = ADB_IDLE; switch(g_adb_cmd) { case 0x04: /* Set modes */ adb_printf("Performing ADB set mode: OR'ing in %02x\n", g_adb_cmd_data[0]); val = g_adb_cmd_data[0] | g_adb_mode; adb_set_new_mode(val); break; case 0x05: /* clear modes */ adb_printf("Performing ADB clear mode: AND'ing in ~%02x\n", g_adb_cmd_data[0]); val = g_adb_cmd_data[0]; val = g_adb_mode & (~val); adb_set_new_mode(val); break; case 0x06: /* Set config */ adb_printf("Set ADB config to %02x %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1],g_adb_cmd_data[2]); adb_set_config(g_adb_cmd_data[0], g_adb_cmd_data[1], g_adb_cmd_data[2]); break; case 0x07: /* SYNC */ adb_printf("Performing ADB SYNC\n"); adb_printf("data: %02x %02x %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1], g_adb_cmd_data[2], g_adb_cmd_data[3]); adb_set_new_mode(g_adb_cmd_data[0]); adb_set_config(g_adb_cmd_data[1], g_adb_cmd_data[2], g_adb_cmd_data[3]); if(g_rom_version >= 3) { adb_printf(" and cmd12:%02x %02x cmd13:%02x %02x\n", g_adb_cmd_data[4], g_adb_cmd_data[5], g_adb_cmd_data[6], g_adb_cmd_data[7]); } break; case 0x08: /* Write mem */ addr = g_adb_cmd_data[0]; val = g_adb_cmd_data[1]; write_adb_ram(addr, val); break; case 0x09: /* Read mem */ addr = (g_adb_cmd_data[1] << 8) + g_adb_cmd_data[0]; adb_printf("Performing mem read to addr %04x\n", addr); adb_send_1byte(read_adb_ram(addr)); break; case 0x11: /* Send ADB keycodes */ val = g_adb_cmd_data[0]; adb_printf("Performing send ADB keycodes: %02x\n", val); adb_virtual_key_update(val & 0x7f, val >> 7); break; case 0x12: /* ADB cmd12 */ adb_printf("Performing ADB cmd 12\n"); adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); break; case 0x13: /* ADB cmd13 */ adb_printf("Performing ADB cmd 13\n"); adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); break; case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: /* Listen dev x reg 3 */ if(dev == g_kbd_dev_addr) { if(g_adb_cmd_data[1] == 0xfe) { /* change keyboard addr? */ new_kbd = g_adb_cmd_data[0] & 0xf; if(new_kbd != dev) { printf("Moving kbd to dev %x!\n", new_kbd); adb_error(); } g_kbd_dev_addr = new_kbd; } else if(g_adb_cmd_data[1] != 1) { /* see what new device handler id is */ printf("KBD listen to dev %x reg 3: 1:%02x\n", dev, g_adb_cmd_data[1]); adb_error(); } if(g_adb_cmd_data[0] != (word32)g_kbd_dev_addr) { /* see if app is trying to change addr */ printf("KBD listen to dev %x reg 3: 0:%02x!\n", dev, g_adb_cmd_data[0]); adb_error(); } g_kbd_reg3_16bit = ((g_adb_cmd_data[0] & 0xf) << 12) + (g_kbd_reg3_16bit & 0x0fff); } else if(dev == g_mouse_dev_addr) { if(g_adb_cmd_data[0] != (word32)dev) { /* see if app is trying to change mouse addr */ printf("MOUS listen to dev %x reg3: 0:%02x!\n", dev, g_adb_cmd_data[0]); adb_error(); } if(g_adb_cmd_data[1] != 1 && g_adb_cmd_data[1] != 2) { /* see what new device handler id is */ printf("MOUS listen to dev %x reg 3: 1:%02x\n", dev, g_adb_cmd_data[1]); adb_error(); } } else { printf("Listen cmd to dev %x reg3????\n", dev); printf("data0: %02x, data1: %02x ????\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); adb_error(); } break; default: printf("Doing adb_cmd %02x: UNKNOWN!\n", g_adb_cmd); break; } } int adb_read_c027() { word32 ret; if(halt_on_all_c027) { halt_printf("halting on all c027 reads!\n"); } if(g_c027_val & (~ADB_C027_NEG_MASK)) { halt_printf("read_c027: g_c027_val: %02x\n", g_c027_val); } ret = (g_c027_val & ADB_C027_NEG_MASK); if(g_adb_mouse_valid_data) { ret |= ADB_C027_MOUSE_DATA; } if(g_adb_interrupt_byte != 0) { ret |= ADB_C027_DATA_VALID; } else if(g_adb_data_pending > 0) { if((g_adb_state != ADB_IN_CMD)) { ret |= ADB_C027_DATA_VALID; } } if(g_adb_mouse_coord) { ret |= ADB_C027_MOUSE_COORD; } #if 0 adb_printf("Read c027: %02x, int_byte: %02x, d_pend: %d\n", ret, g_adb_interrupt_byte, g_adb_data_pending); #endif #if 0 adb_log(0xc027, ret); #endif return ret; } void adb_write_c027(int val) { word32 old_val; word32 new_int; word32 old_int; adb_printf("Writing c027 with %02x\n", val); adb_log(0x1c027, val); old_val = g_c027_val; g_c027_val = (val & ADB_C027_NEG_MASK); new_int = g_c027_val & ADB_C027_MOUSE_INT; old_int = old_val & ADB_C027_MOUSE_INT; if(!new_int && old_int) { adb_clear_mouse_int(); } new_int = g_c027_val & ADB_C027_DATA_INT; old_int = old_val & ADB_C027_DATA_INT; if(!new_int && old_int) { /* ints were on, now off */ adb_clear_data_int(); } if(g_c027_val & ADB_C027_KBD_INT) { halt_printf("Can't support kbd interrupts!\n"); } return; } int read_adb_ram(word32 addr) { int val; adb_printf("Reading adb ram addr: %02x\n", addr); if(addr >= 0x100) { if(addr >= 0x1000 && addr < 0x2000) { /* ROM self-test checksum */ if(addr == 0x1400) { val = 0x72; } else if(addr == 0x1401) { val = 0xf7; } else { val = 0; } } else { printf("adb ram addr out of range: %04x!\n", addr); val = 0; } } else { val = adb_memory[addr]; if((addr == 0xb) && (g_rom_version == 1)) { // read special key state byte for Out of This World val = (g_c025_val >> 1) & 0x43; val |= (g_c025_val << 2) & 0x4; val |= (g_c025_val >> 2) & 0x10; } if((addr == 0xc) && (g_rom_version >= 3)) { // read special key state byte for Out of This World val = g_c025_val & 0xc7; printf("val is %02x\n", val); } } adb_printf("adb_ram returning %02x\n", val); return val; } void write_adb_ram(word32 addr, int val) { adb_printf("Writing adb_ram addr: %02x: %02x\n", addr, val); if(addr >= 0x100) { printf("write adb_ram addr: %02x: %02x!\n", addr, val); adb_error(); } else { adb_memory[addr] = val; } } int adb_get_keypad_xy(int get_y) { int x, y, key, num_keys; int i, j; key = 1; num_keys = 0; x = 0; y = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) { if(g_keypad_key_is_down[key]) { num_keys++; x = x + (j - 1)*32768; y = y + (1 - i)*32768; } key++; } } if(num_keys == 0) { num_keys = 1; } adb_printf("get_xy=%d, num_keys: %d, x:%d, y:%d\n", get_y, num_keys, x, y); if(get_y) { return y / num_keys; } else { return x / num_keys; } } // g_mouse_raw_x/y: Current position (in A2 coordinates) of mouse on host screen // g_mouse_fifo[0].x/y: Current position (in A2 coords) of where we "want" // mouse on the A2 screen. // g_mouse_a2_x/y: last x,y returned through $c024 to software. // So, reading $c024 return g_mouse_fifo[].x - g_mouse_a2_x. // And, in simple cases, host mouse movement just sets g_mouse_fifo[0].x=raw_x int adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, int buttons_valid) { dword64 dfcyc; int button1_changed, mouse_moved, unhide, pos; int i; if(kimage_ptr != &g_mainwin_kimage) { adb_nonmain_check(); } dfcyc = g_cur_dfcyc; unhide = (g_adb_mainwin_has_focus == 0); if((buttons_valid >= 0) && (buttons_valid & 0x1000)) { // x, y are really deltas buttons_valid &= 0xfff; x = g_mouse_raw_x + x; y = g_mouse_raw_y + y; g_mouse_raw_x = x; g_mouse_raw_y = y; } else { g_mouse_raw_x = x; g_mouse_raw_y = y; // Clamp mouse to 0-639, 0-399 to make GSOS work nicely if(x < 0) { x = 0; unhide = 1; } if(x >= 640) { x = 639; unhide = 1; } if(y < 0) { y = 0; unhide = 1; } if(y >= 400) { y = 399; unhide = 1; } } g_unhide_pointer = unhide && !g_warp_pointer; if(kimage_ptr != &g_mainwin_kimage) { // In debugger window...just get out return 0; } if(!g_warp_pointer) { if(g_hide_pointer && g_unhide_pointer) { /* cursor has left a2 window, show it */ g_hide_pointer = 0; } if((g_num_lines_prev_superhires == 200) && (g_num_lines_prev_superhires640 == 0) && ((g_slow_memory_ptr[0x19d00] & 0x80) == 0)) { // In 320-mode superhires, cut mouse range in half x = x >> 1; } y = y >> 1; } mouse_compress_fifo(dfcyc); #if 0 printf("Update Mouse called with buttons:%d x,y:%d,%d, fifo:%d,%d, " " a2: %d,%d\n", buttons_valid, x, y, g_mouse_fifo[0].x, g_mouse_fifo[0].y, g_mouse_a2_x, g_mouse_a2_y); #endif if((buttons_valid < 0) && g_warp_pointer) { /* Warping the pointer causes it to jump here...this is not */ /* real motion, just update info and get out */ g_mouse_a2_x += (x - g_mouse_fifo[0].x); g_mouse_a2_y += (y - g_mouse_fifo[0].y); g_mouse_fifo[0].x = x; g_mouse_fifo[0].y = y; return 0; } #if 0 printf("...real move, new x: %d, %d, a2:%d,%d\n", g_mouse_fifo[0].x, g_mouse_fifo[0].y, g_mouse_a2_x, g_mouse_a2_y); #endif mouse_moved = (g_mouse_fifo[0].x != x) || (g_mouse_fifo[0].y != y); g_mouse_fifo[0].x = x; g_mouse_fifo[0].y = y; g_mouse_fifo[0].dfcyc = dfcyc; button1_changed = (buttons_valid & 1) && ((button_states & 1) != (g_mouse_fifo[0].buttons & 1)); if((button_states & 4) && !(g_mouse_fifo[0].buttons & 4) && (buttons_valid & 4)) { /* right button pressed */ adb_increment_speed(); } if((button_states & 2) && !(g_mouse_fifo[0].buttons & 2) && (buttons_valid & 2)) { /* middle button pressed */ halt2_printf("Middle button pressed\n"); } pos = g_mouse_fifo_pos; if((pos < (ADB_MOUSE_FIFO - 2)) && button1_changed) { /* copy delta to overflow, set overflow */ /* overflow ensures the mouse button state is precise at */ /* button up/down times. Using a mouse event list where */ /* deltas accumulate until a button change would work, too */ for(i = pos; i >= 0; i--) { g_mouse_fifo[i + 1] = g_mouse_fifo[i]; /* copy struct*/ } g_mouse_fifo_pos = pos + 1; } g_mouse_fifo[0].buttons = (button_states & buttons_valid) | (g_mouse_fifo[0].buttons & ~buttons_valid); if(mouse_moved || button1_changed) { if( (g_mouse_ctl_addr == g_mouse_dev_addr) && ((g_adb_mode & 0x2) == 0)) { g_adb_mouse_valid_data = 1; adb_add_mouse_int(); } } return mouse_moved; } int mouse_read_c024(dword64 dfcyc) { word32 ret, tool_start; int em_active, target_x, target_y, delta_x, delta_y, a2_x, a2_y; int mouse_button, clamped, pos; if(((g_adb_mode & 0x2) != 0) || (g_mouse_dev_addr != g_mouse_ctl_addr)){ /* mouse is off, return 0, or mouse is not autopoll */ g_adb_mouse_valid_data = 0; adb_clear_mouse_int(); return 0; } mouse_compress_fifo(dfcyc); pos = g_mouse_fifo_pos; target_x = g_mouse_fifo[pos].x; target_y = g_mouse_fifo[pos].y; mouse_button = (g_mouse_fifo[pos].buttons & 1); delta_x = target_x - g_mouse_a2_x; delta_y = target_y - g_mouse_a2_y; clamped = 0; if(delta_x > 0x3f) { delta_x = 0x3f; clamped = 1; } else if(delta_x < -0x3f) { delta_x = -0x3f; clamped = 1; } if(delta_y > 0x3f) { delta_y = 0x3f; clamped = 1; } else if(delta_y < -0x3f) { delta_y = -0x3f; clamped = 1; } if(pos > 0) { /* peek into next entry's button info if we are not clamped */ /* and we're returning the y-coord */ if(!clamped && g_adb_mouse_coord) { mouse_button = g_mouse_fifo[pos - 1].buttons & 1; } } if(g_adb_mouse_coord) { /* y coord */ delta_x = 0; /* clear unneeded x delta */ } else { delta_y = 0; /* clear unneeded y delta */ } adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x100e9], g_slow_memory_ptr[0x100ea], g_slow_memory_ptr[0x100eb], g_slow_memory_ptr[0x100ec]); adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); /* Update event manager internal state */ tool_start = (g_slow_memory_ptr[0x103ca] << 16) + (g_slow_memory_ptr[0x103c9] << 8) + g_slow_memory_ptr[0x103c8]; em_active = 0; if((tool_start >= 0x20000) && (tool_start < (g_mem_size_total - 28)) ) { /* seems to be valid ptr to addr of mem space for tools */ /* see if event manager appears to be active */ em_active = g_memory_ptr[tool_start + 6*4] + (g_memory_ptr[tool_start + 6*4 + 1] << 8); if(g_warp_pointer) { em_active = 0; } } //em_active = 0; // HACK! a2_x = g_mouse_a2_x; a2_y = g_mouse_a2_y; if(em_active) { if((!g_hide_pointer) && (g_num_lines_prev_superhires == 200) && !g_unhide_pointer) { /* if super-hires and forcing tracking, then hide */ g_hide_pointer = 1; } if(g_adb_mouse_coord == 0) { /* update x coord values */ g_slow_memory_ptr[0x47c] = a2_x & 0xff; g_slow_memory_ptr[0x57c] = a2_x >> 8; g_memory_ptr[0x47c] = a2_x & 0xff; g_memory_ptr[0x57c] = a2_x >> 8; g_slow_memory_ptr[0x10190] = a2_x & 0xff; g_slow_memory_ptr[0x10192] = a2_x >> 8; } else { g_slow_memory_ptr[0x4fc] = a2_y & 0xff; g_slow_memory_ptr[0x5fc] = a2_y >> 8; g_memory_ptr[0x4fc] = a2_y & 0xff; g_memory_ptr[0x5fc] = a2_y >> 8; g_slow_memory_ptr[0x10191] = a2_y & 0xff; g_slow_memory_ptr[0x10193] = a2_y >> 8; } } else { if(g_hide_pointer && !g_warp_pointer) { g_hide_pointer = 0; } } ret = ((!mouse_button) << 7) + ((delta_x | delta_y) & 0x7f); if(g_adb_mouse_coord) { g_mouse_a2_button = mouse_button; /* y coord has button*/ } else { ret |= 0x80; /* mouse button not down on x coord rd */ } a2_x += delta_x; a2_y += delta_y; g_mouse_a2_x = a2_x; g_mouse_a2_y = a2_y; if(g_mouse_fifo_pos) { if((target_x == a2_x) && (target_y == a2_y) && (g_mouse_a2_button == mouse_button)) { g_mouse_fifo_pos--; } } adb_printf("Rd c024, mouse is_y:%d, %02x, vbl:%08x, dfcyc:%016llx, em:" "%d\n", g_adb_mouse_coord, ret, g_vbl_count, dfcyc, em_active); adb_printf("...mouse targ_x:%d,%d delta_x,y:%d,%d fifo:%d, a2:%d,%d\n", target_x, target_y, delta_x, delta_y, g_mouse_fifo_pos, a2_x, a2_y); adb_printf(" post a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); if((g_mouse_fifo_pos == 0) && (g_mouse_fifo[0].x == a2_x) && (g_mouse_fifo[0].y == a2_y) && ((g_mouse_fifo[0].buttons & 1) == g_mouse_a2_button)) { g_adb_mouse_valid_data = 0; adb_clear_mouse_int(); } g_adb_mouse_coord = !g_adb_mouse_coord; return ret; } void mouse_compress_fifo(dword64 dfcyc) { dword64 ddelta; int pos; /* The mouse fifo exists so that fast button changes don't get lost */ /* if the emulator lags behind the mouse events */ /* But the FIFO means really old mouse events are saved if */ /* the emulated code isn't looking at the mouse registers */ /* This routine compresses all mouse events > 0.5 seconds old */ ddelta = (500LL*1000) << 16; for(pos = g_mouse_fifo_pos; pos >= 1; pos--) { if((g_mouse_fifo[pos].dfcyc + ddelta) < dfcyc) { /* Remove this entry */ adb_printf("Old mouse FIFO pos %d removed\n", pos); g_mouse_fifo_pos = pos - 1; continue; } /* Else, stop searching the FIFO */ break; } } void adb_paste_update_state() { int rd_pos, wr_pos; rd_pos = g_kbd_paste_rd_pos; wr_pos = g_kbd_paste_wr_pos; if(rd_pos >= wr_pos) { g_kbd_paste_rd_pos = 0; g_kbd_paste_wr_pos = 0; return; } if(g_kbd_chars_buffered == 0) { g_kbd_buf[0] = g_kbd_paste_buf[rd_pos]; g_kbd_paste_rd_pos = rd_pos + 1; g_kbd_chars_buffered = 1; } } int adb_paste_add_buf(word32 key) { word32 last_key; int pos; // Applesoft reads $C000 to check for ctrl-C after each statement. // So if we dropped all chars into g_kbd_buf[], we could end up // losing chars due to multiple reads of $C000 without writes to $C010 // causing g_kbd_read_no_update to toss a paste char. // Instead, have a separate buffer, and when g_kbd_chars_buffered==0, // copy one paste char to g_kbd_buf[0]. This also solves a problem // where Applesoft is doing: 10 GOTO 10 and it needs to see a Ctrl-C // to stop--but a paste buffer is in the way. // But, now pressing keys while a paste is pending causes those keys // to take priority during the paste. last_key = g_kbd_paste_last_key; g_kbd_paste_last_key = key; if(key == 10) { // \n, newline on Unix key = 13; // \r, return if(last_key == 13) { key = 0; // CR, then LF--eat the LF } } if((key == 0) || (key >= 0x80)) { return 0; // Just skip these keys } pos = g_kbd_paste_wr_pos; if(pos >= MAX_KBD_PASTE_BUF) { return 1; } g_kbd_paste_buf[pos] = key | 0x80; g_kbd_paste_wr_pos = pos + 1; adb_paste_update_state(); return 0; } void adb_key_event(int a2code, int is_up) { word32 special, vbl_count; int key, hard_key, pos, tmp_ascii, ascii; if(is_up) { adb_printf("adb_key_event, key:%02x, is up, g_key_down: %02x\n", a2code, g_key_down); } if(a2code < 0 || a2code > 0x7f) { halt_printf("add_key_event: a2code: %04x!\n", a2code); return; } if(!is_up && a2code == 0x35) { /* ESC pressed, see if ctrl & cmd key down */ if(CTRL_DOWN && CMD_DOWN) { /* Desk mgr int */ printf("Desk mgr int!\n"); g_adb_interrupt_byte |= 0x20; adb_add_data_int(); } } /* convert key to ascii, if possible */ hard_key = 0; if(g_a2_key_to_ascii[a2code][1] & 0xef00) { /* special key */ } else { /* we have ascii */ hard_key = 1; } pos = 1; ascii = g_a2_key_to_ascii[a2code][1]; if(CAPS_LOCK_DOWN && (ascii >= 'a') && (ascii <= 'z')) { pos = 2; if(SHIFT_DOWN && (g_adb_mode & 0x40)) { /* xor shift mode--capslock and shift == lowercase */ pos = 1; } } else if(SHIFT_DOWN) { pos = 2; } ascii = g_a2_key_to_ascii[a2code][pos]; if(CTRL_DOWN) { tmp_ascii = g_a2_key_to_ascii[a2code][3]; if(tmp_ascii >= 0) { ascii = tmp_ascii; } } key = (ascii & 0x7f) + 0x80; special = (ascii >> 8) & 0xff; if(ascii < 0) { printf("ascii1: %d, a2code: %02x, pos: %d\n", ascii,a2code,pos); ascii = 0; special = 0; } if(!is_up) { if(hard_key) { g_kbd_buf[g_kbd_chars_buffered] = key; g_kbd_chars_buffered++; if(g_kbd_chars_buffered >= MAX_KBD_BUF) { g_kbd_chars_buffered = MAX_KBD_BUF - 1; } g_key_down = 1; g_a2code_down = a2code; /* first key down, set up autorepeat */ vbl_count = g_vbl_count; g_adb_repeat_vbl = vbl_count + g_adb_repeat_delay; if(g_adb_repeat_delay == 0) { g_key_down = 0; } g_hard_key_down = 1; } g_c025_val = g_c025_val | special; adb_printf("new c025_or: %02x\n", g_c025_val); } else { if(hard_key && (a2code == g_a2code_down)) { g_hard_key_down = 0; /* Turn off repeat */ g_key_down = 0; } g_c025_val = g_c025_val & (~ special); adb_printf("new c025_and: %02x\n", g_c025_val); } if(g_key_down) { g_c025_val = g_c025_val & (~0x20); } else { /* If no hard key down, set update mod latch */ g_c025_val = g_c025_val | 0x20; } } word32 adb_read_c000() { word32 vbl_count; if( ((g_kbd_buf[0] & 0x80) == 0) && (g_key_down == 0)) { /* nothing happening, just get out */ return g_kbd_buf[0]; } if(g_kbd_buf[0] & 0x80) { /* got one */ if((g_kbd_read_no_update++ > 5) && (g_kbd_chars_buffered > 1)) { /* read 5 times, keys pending, let's move it along */ printf("Read %02x %d times, tossing\n", g_kbd_buf[0], g_kbd_read_no_update); adb_access_c010(); } } else { vbl_count = g_vbl_count; if(g_key_down && (vbl_count >= g_adb_repeat_vbl)) { /* repeat the g_key_down */ g_c025_val |= 0x8; adb_key_event(g_a2code_down, 0); g_adb_repeat_vbl = vbl_count + g_adb_repeat_rate; } } return g_kbd_buf[0]; } word32 adb_access_c010() { int tmp; int i; g_kbd_read_no_update = 0; tmp = g_kbd_buf[0] & 0x7f; g_kbd_buf[0] = tmp; tmp = tmp | (g_hard_key_down << 7); if(g_kbd_chars_buffered) { for(i = 1; i < g_kbd_chars_buffered; i++) { g_kbd_buf[i - 1] = g_kbd_buf[i]; } g_kbd_chars_buffered--; if(g_kbd_chars_buffered == 0) { adb_paste_update_state(); } } g_c025_val = g_c025_val & (~ (0x08)); return tmp; } word32 adb_read_c025() { return g_c025_val; } int adb_is_cmd_key_down() { return CMD_DOWN; } int adb_is_option_key_down() { return OPTION_DOWN; } void adb_increment_speed() { const char *str; g_limit_speed++; if(g_limit_speed > 3) { g_limit_speed = 0; } str = ""; switch(g_limit_speed) { case 0: str = "...as fast as possible!"; break; case 1: str = "...1.024MHz!"; break; case 2: str = "...2.8MHz!"; break; case 3: str = "...8.0MHz!"; break; } printf("Toggling g_limit_speed to %d%s\n", g_limit_speed, str); } void adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask) { // Called by *driver.c host drivers to handle focus changes and // capslock state (so if capslock is on, we leave the window, release // capslock, then reenter the window, we update things properly). if(kimage_ptr == &g_mainwin_kimage) { g_c025_val = (g_c025_val & (~mask)) | new_c025_val; } else { kimage_ptr->c025_val = (kimage_ptr->c025_val & (~mask)) | new_c025_val; } } int adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr) { int i; switch(unicode_c) { case 0x00a3: // British pound unicode_c = '#'; break; case 0x00e0: // a with left accent unicode_c = '@'; break; case 0x00b0: // degrees (French) case 0x00c4: // A with umlaut (German, Swedish) case 0x00a1: // ! upside down (Spanish) case 0x00c6: // AE (Danish) unicode_c = '['; break; case 0x00e7: // c with tail (French/Italian) case 0x00d1: // N with ~ (Spanish) case 0x00d6: // O with umlaut (German, Swedish) case 0x00d8: // O with slash (Danish) unicode_c = '\\'; break; case 0x00a7: // ss like thing (French) case 0x00dc: // U with umlaut case 0x00bf: // ? upside down (Spanish) case 0x00c5: // A with circle (Danish) //case 0x00e9: // e with right accent (Italian) unicode_c = ']'; break; //case 0x0000: // u with left accent (Italian) // unicode_c = '`'; // break; case 0x00e4: // a with umlaut (german) case 0x00e9: // e with accent (french) case 0x0000: // ae (Danish) unicode_c = '{'; break; case 0x00f6: // o with umlaut (German/Swedish) case 0x00f9: // u with left accent (French) case 0x00f8: // o with slash (Danish) case 0x00f1: // n with ~ (Spanish) case 0x00f2: // o with ` (Italian) unicode_c = '|'; break; case 0x00e8: // e with ` (French, Italian) case 0x00fc: // u with umlaut (German) case 0x00e5: // a with circle (Danish/Swedish) unicode_c = '}'; break; case 0x00a8: // two high dots (French) case 0x00ec: // i with ` (Italian) case 0x00df: // german B thing (German) unicode_c = '~'; break; } if(unicode_c > 0x7f) { return a2code; // Use a2code instead } if((g_a2_key_to_ascii[a2code][1] & 0xf000) == 0x1000) { // Keypad // Don't remap keypad keys, we need them for Keypad Joystick if((unicode_c >= '0') && (unicode_c <= '9')) { return a2code; } } for(i = 0; i < 128; i++) { if(g_a2_key_to_ascii[i][1] == unicode_c) { // Not-shifted *shift_down_ptr = 0; return g_a2_key_to_ascii[i][0]; } if(g_a2_key_to_ascii[i][2] == unicode_c) { // Shifted *shift_down_ptr = 1; return g_a2_key_to_ascii[i][0]; } } return a2code; // Not found, use default a2code } void adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, int is_up) { word32 restore_c025_val, restorek_c025_val; int special, ascii_and_type, ascii, new_shift, a2code, other_a2code; /* this routine called by xdriver to pass raw codes--handle */ /* ucontroller and ADB bus protocol issues here */ /* if autopoll on, pass it on through to c025,c000 regs */ /* else only put it in kbd reg 3, and pull SRQ if needed */ adb_printf("adb_phys_key_update: %02x, %d\n", raw_a2code, is_up); if((raw_a2code < 0) || (raw_a2code > 0x7f)) { halt_printf("raw_a2code: %04x!\n", raw_a2code); return; } a2code = raw_a2code; restore_c025_val = 0; restorek_c025_val = 0; if(unicode_c > 0) { // To enable international keyboards, ignore a2code, look up // what U.S. keycode would be and return that new_shift = g_c025_val & 1; a2code = adb_ascii_to_a2code(unicode_c, a2code, &new_shift); if(a2code && ((g_c025_val & 1) != new_shift)) { restore_c025_val = g_c025_val | 0x100; restorek_c025_val = kimage_ptr->c025_val; g_c025_val = (g_c025_val & -2) | new_shift; kimage_ptr->c025_val = (kimage_ptr->c025_val & -2) | new_shift; } if(!is_up) { g_rawa2_to_a2code[raw_a2code & 0x7f] = a2code; } } /* Remap 0x7b-0x7e to 0x3b-0x3e (arrow keys on new mac keyboards) */ if((a2code >= 0x7b) && (a2code <= 0x7e)) { a2code = a2code - 0x40; } if(g_adb_swap_command_option) { if(a2code == 0x37) { // Command? a2code = 0x3a; // -> Option } else if(a2code == 0x3a) { // Option? a2code = 0x37; // -> Command } } /* Now check for special keys (function keys, etc) */ ascii_and_type = g_a2_key_to_ascii[a2code][1]; special = 0; if((ascii_and_type & 0xf000) == 0x8000) { /* special function key */ special = ascii_and_type & 0xff; switch(special) { case 0x01: /* F1 - remap to cmd */ a2code = 0x37; special = 0; break; case 0x02: /* F2 - remap to option */ a2code = 0x3a; special = 0; break; case 0x03: /* F3 - remap to escape for OS/2 */ a2code = 0x35; special = 0; break; case 0x0c: /* F12 - remap to reset */ a2code = 0x7f; special = 0; break; default: break; } } /* Only process reset requests here */ if((is_up == 0) && (a2code == 0x7f) && CTRL_DOWN) { /* Reset pressed! */ printf("Reset pressed since CTRL_DOWN: %d\n", CTRL_DOWN); do_reset(); return; } if(special && !is_up) { switch(special) { case 0x04: /* F4 - Emulator config panel */ cfg_toggle_config_panel(); break; case 0x05: /* F5 - Force Refresh */ g_status_enable = !g_status_enable; // video_update() will call video_update_status_enable() break; case 0x06: /* F6 - emulator speed */ if(SHIFT_DOWN) { halt2_printf("Shift-F6 pressed\n"); } else { adb_increment_speed(); } break; case 0x07: /* F7 - toggle debugger window, SHIFT:fast disk */ if(SHIFT_DOWN) { g_fast_disk_emul_en = !g_fast_disk_emul_en; iwm_update_fast_disk_emul(g_fast_disk_emul_en); printf("g_fast_disk_emul_en is now %d\n", g_fast_disk_emul_en); } else { video_set_active(&g_debugwin_kimage, !g_debugwin_kimage.active); printf("Toggled debugger window to:%d\n", g_debugwin_kimage.active); } break; case 0x08: /* F8 - warp pointer */ g_warp_pointer = !g_warp_pointer; g_hide_pointer = g_warp_pointer; printf("New warp:%d, new hide:%d\n", g_warp_pointer, g_hide_pointer); break; case 0x09: /* F9 - swap paddles */ if(CTRL_DOWN) { g_adb_copy_requested = 1; } else if(SHIFT_DOWN) { g_swap_paddles = !g_swap_paddles; printf("Swap paddles is now: %d\n", g_swap_paddles); } else { g_invert_paddles = !g_invert_paddles; printf("Invert paddles is now: %d\n", g_invert_paddles); } break; case 0x0a: /* F10 - nothing */ break; case 0x0b: /* F11 - full screen */ break; } return; } if(kimage_ptr == &g_debugwin_kimage) { debugger_key_event(kimage_ptr, a2code, is_up); if(restore_c025_val) { g_c025_val = restore_c025_val & 0xff; // Restore shift kimage_ptr->c025_val = restorek_c025_val; } return; } /* Handle Keypad Joystick here partly...if keypad key pressed */ /* while in Keypad Joystick mode, do not pass it on as a key press */ if((ascii_and_type & 0xff00) == 0x1000) { /* Keep track of keypad number keys being up or down even */ /* if joystick mode isn't keypad. This avoid funny cases */ /* if joystick mode is changed while a key is pressed */ ascii = ascii_and_type & 0xff; if(ascii > 0x30 && ascii <= 0x39) { g_keypad_key_is_down[ascii - 0x30] = !is_up; } if(g_joystick_type == 0) { /* If Joystick type is keypad, then do not let these */ /* keypress pass on further, except for cmd/opt */ if(ascii == 0x30) { /* remap '0' to cmd */ a2code = 0x37; } else if(ascii == 0x2e || ascii == 0x2c) { /* remap '.' and ',' to option */ a2code = 0x3a; } else { /* Just ignore it in this mode */ return; } } } adb_maybe_virtual_key_update(a2code, is_up); other_a2code = g_rawa2_to_a2code[raw_a2code & 0x7f]; if((other_a2code >= 0) && is_up) { adb_maybe_virtual_key_update(other_a2code, is_up); g_rawa2_to_a2code[raw_a2code & 0x7f] = -1; } if(restore_c025_val) { g_c025_val = restore_c025_val & 0xff; // Restore shift } } void adb_maybe_virtual_key_update(int a2code, int is_up) { int autopoll; autopoll = 1; if(g_adb_mode & 1) { /* autopoll is explicitly off */ autopoll = 0; } if(g_kbd_dev_addr != g_kbd_ctl_addr) { /* autopoll is off because ucontroller doesn't know kbd moved */ autopoll = 0; } if(g_config_control_panel) { /* always do autopoll */ autopoll = 1; } if(is_up) { if(!autopoll) { /* no auto keys, generate SRQ! */ adb_kbd_reg0_data(a2code, is_up); } else { adb_virtual_key_update(a2code, is_up); } } else { if(!autopoll) { /* no auto keys, generate SRQ! */ adb_kbd_reg0_data(a2code, is_up); } else { /* was up, now down */ adb_virtual_key_update(a2code, is_up); } } } void adb_virtual_key_update(int a2code, int is_up) { word32 mask; int bitpos; int i; adb_printf("Virtual handle a2code: %02x, is_up: %d\n", a2code, is_up); if(a2code < 0 || a2code > 0x7f) { halt_printf("a2code: %04x!\n", a2code); return; } i = (a2code >> 5) & 3; bitpos = a2code & 0x1f; mask = (1 << bitpos); if(is_up) { if(g_virtual_key_up[i] & mask) { /* already up, do nothing */ } else { g_virtual_key_up[i] |= mask; adb_key_event(a2code, is_up); } } else { if(g_virtual_key_up[i] & mask) { g_virtual_key_up[i] &= (~mask); adb_key_event(a2code, is_up); } } } #if 0 void adb_all_keys_up() { word32 mask; int i, j; for(i = 0; i < 4; i++) { for(j = 0; j < 32; j++) { mask = 1 << j; if((g_virtual_key_up[i] & mask) == 0) { /* create key-up event */ adb_physical_key_update(i*32 + j, 1); } } } } #endif void adb_kbd_repeat_off() { g_key_down = 0; } void adb_mainwin_focus(int has_focus) { g_adb_mainwin_has_focus = has_focus; // printf("g_adb_mainwin_has_focus=%d\n", g_adb_mainwin_has_focus); if(!has_focus) { adb_nonmain_check(); } } ================================================ FILE: gsplus/src/applesingle.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // From Wikipedia AppleSingle_and_AppleDouble_formats): // https://web.archive.org/web/20180311140826/http://kaiser-edv.de/ // documents/AppleSingle_AppleDouble.pdf // All fields in an Applesingle file are in big-endian format // ProDOS forked files are described in Technote tn-pdos-025. #include "defc.h" word32 applesingle_get_be32(const byte *bptr) { return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; } word32 applesingle_get_be16(const byte *bptr) { return (bptr[0] << 8) | bptr[1]; } void applesingle_set_be32(byte *bptr, word32 val) { bptr[3] = val; bptr[2] = val >> 8; bptr[1] = val >> 16; bptr[0] = val >> 24; } void applesingle_set_be16(byte *bptr, word32 val) { bptr[1] = val; bptr[0] = val >> 8; } word32 applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data) { byte *fptr, *bptr; word32 data_size, resource_size, rounded_data_size, num_entries; word32 rounded_resource_size, hdr_size, max_size, hdr_pos, data_pos; word32 block, key_block, ret, good, has_finder_info, offset; int level; int i, j; #if 0 printf("applesingle_map_from_prodos: %s do_file_data:%d\n", fileptr->unix_path, do_file_data); #endif // First, handle mini directory describing the forks key_block = fileptr->key_block; ret = dynapro_map_one_file_block(dsk, fileptr, key_block, 1U << 30, 0); if(ret == 0) { printf(" dynapro_map_one_file_block ret 0, applesingle done\n"); return 0; } bptr = &(dsk->raw_data[key_block << 9]); data_size = dynapro_get_word24(&bptr[5]); resource_size = dynapro_get_word24(&bptr[0x100 + 5]); has_finder_info = bptr[9] | bptr[27]; num_entries = 1; // ProDOS info always if(has_finder_info) { num_entries++; } rounded_data_size = data_size; if(data_size) { rounded_data_size = (data_size + 0x200) & -0x200; num_entries++; } rounded_resource_size = resource_size; if(resource_size) { rounded_resource_size = (resource_size + 0x200) & -0x200; num_entries++; } hdr_size = 256; max_size = hdr_size + rounded_resource_size + rounded_data_size; fileptr->buffer_ptr = 0; fptr = 0; if(do_file_data) { fptr = calloc(1, max_size + 0x200); #if 0 printf(" fptr:%p, max_size:%08x, res:%08x, data:%08x\n", fptr, max_size, rounded_resource_size, rounded_data_size); #endif } // From now on, errors cannot return without free'ing fptr good = 1; if(resource_size) { block = dynapro_get_word16(&bptr[0x100 + 1]); level = bptr[0x100]; if(fptr) { fileptr->buffer_ptr = fptr + 256; } ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, resource_size); if(ret == 0) { good = 0; } } if(data_size) { block = dynapro_get_word16(&bptr[1]); level = bptr[0]; if(fptr) { fileptr->buffer_ptr = fptr + 256 + rounded_resource_size; } ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, data_size); if(ret == 0) { good = 0; } } fileptr->buffer_ptr = 0; // Now prepare the header if(fptr) { applesingle_set_be32(&fptr[0], 0x00051600); // Magic applesingle_set_be32(&fptr[4], 0x00020000); // Version applesingle_set_be16(&fptr[24], num_entries); // Version hdr_pos = 26; data_pos = 192; // First do ProDOS entry applesingle_set_be32(&fptr[hdr_pos + 0], 11); // ProDOS Info applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); applesingle_set_be32(&fptr[hdr_pos + 8], 8); applesingle_set_be16(&fptr[data_pos + 0], 0xc3); applesingle_set_be16(&fptr[data_pos + 2], fileptr->file_type); applesingle_set_be32(&fptr[data_pos + 4], fileptr->aux_type); hdr_pos += 12; data_pos += 8; // Then do FinderInfo if(has_finder_info) { applesingle_set_be32(&fptr[hdr_pos + 0], 9); //Finder applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); applesingle_set_be32(&fptr[hdr_pos + 8], 32); for(i = 0; i < 2; i++) { offset = bptr[9 + 18*i]; if(!offset) { continue; // skip it } offset = ((offset - 1) & 1) * 8; for(j = 0; j < 9; j++) { fptr[data_pos + offset + j] = bptr[10 + 18*i + j]; } } hdr_pos += 12; data_pos += 32; } if(data_pos >= 256) { printf("data_pos:%08x is too big\n", data_pos); good = 0; } // First, do data fork if(data_size) { applesingle_set_be32(&fptr[hdr_pos + 0], 1); // Data applesingle_set_be32(&fptr[hdr_pos + 4], 256 + rounded_resource_size); applesingle_set_be32(&fptr[hdr_pos + 8], data_size); hdr_pos += 12; } // Then do resource fork if(resource_size) { applesingle_set_be32(&fptr[hdr_pos + 0], 2); // Rsrc applesingle_set_be32(&fptr[hdr_pos + 4], 256); applesingle_set_be32(&fptr[hdr_pos + 8], resource_size); hdr_pos += 12; } if(hdr_pos > 192) { printf("hdr:%08x stomped on data\n", hdr_pos); good = 0; } if(good) { ret = dynapro_write_to_unix_file(fileptr->unix_path, fptr, 256 + rounded_resource_size + data_size); if(ret == 0) { good = 0; } } free(fptr); } // printf("applesingle_map_from_prodos done, good:%d\n", good); return good; } word32 applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, dword64 dsize) { byte *bptr, *tptr; word32 key_block, blocks_used, entry_id, blocks_out, offset, length; word32 magic, version, hdr_pos, did_fork; int num_entries; int i; // Return 0 if anything is wrong with the .applesingle file // Otherwise, return (blocks_used << 16) | (key_block & 0xffff) #if 0 printf("applesingle_from_unix %s, size:%08llx\n", fileptr->unix_path, dsize); #endif key_block = fileptr->key_block; bptr = &(dsk->raw_data[key_block << 9]); if(dsize < 50) { printf("Applesingle is too small\n"); return 0; } magic = applesingle_get_be32(&fptr[0]); version = applesingle_get_be32(&fptr[4]); num_entries = applesingle_get_be16(&fptr[24]); if((magic != 0x00051600) || (version < 0x00020000)) { printf("Bad Applesingle magic number is: %08x, vers:%08x\n", magic, version); return 0; } hdr_pos = 26; blocks_used = 1; did_fork = 0; // printf(" num_entries:%d\n", num_entries); for(i = 0; i < num_entries; i++) { if((hdr_pos + 24) > dsize) { printf("Applesingle header exceeds file size i:%d of " "%d, hdr_pos:%04x dsize:%08llx\n", i, num_entries, hdr_pos, dsize); return 0; } entry_id = applesingle_get_be32(&fptr[hdr_pos + 0]); offset = applesingle_get_be32(&fptr[hdr_pos + 4]); length = applesingle_get_be32(&fptr[hdr_pos + 8]); #if 0 printf(" header[%d] at +%04x: id:%d, offset:%08x, len:%08x\n", i, hdr_pos, entry_id, offset, length); #endif if((offset + length) > dsize) { printf("Applesingle entry_id:%d exceeds file size\n", entry_id); return 0; } switch(entry_id) { case 1: // Data fork case 2: // Resource fork tptr = bptr; if(entry_id == 2) { // Resource fork tptr += 0x100; } #if 0 printf(" for entry_id %d, offset:%08x, length:%08x, " "fptr:%p\n", entry_id, offset, length, fptr); #endif if(did_fork & (1 << entry_id)) { printf("fork %d repeated!\n", entry_id); return 0; } did_fork |= (1 << entry_id); blocks_out = applesingle_make_prodos_fork(dsk, fptr + offset, tptr, length); if(blocks_out == 0) { return 0; } blocks_used += (blocks_out >> 16); break; case 9: // Finder Info if(length < 32) { printf("Invalid Finder info, len:%d\n", length); } bptr[8] = 0x12; bptr[8 + 18] = 0x12; bptr[9] = 1; bptr[9 + 18] = 2; for(i = 0; i < 16; i++) { bptr[10 + i] = fptr[offset + i]; bptr[10 + 18 + i] = fptr[offset + 16 + i]; } break; case 11: // ProDOS File Info fileptr->file_type = fptr[offset + 3]; fileptr->aux_type = (fptr[offset + 6] << 8) | fptr[offset + 7]; break; default: break; // Ignore it } hdr_pos += 12; } for(i = 1; i < 3; i++) { if((did_fork >> i) & 1) { continue; } // Create one block for this fork even though it's length==0 // i==1: no data fork; i==2: no resource fork printf(" Doing dummy fork, i:%d, fptr:%p\n", i, fptr); blocks_out = applesingle_make_prodos_fork(dsk, fptr, bptr + ((i & 2) * 0x80), 0); if(blocks_out == 0) { return blocks_out; } blocks_used += (blocks_out >> 16); } fileptr->eof = 0x200; return (blocks_used << 16) | key_block; } word32 applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length) { word32 block, blocks_out, storage_type; #if 0 printf("applesingle_make_prodos_fork: fptr:%p, tptr:%p, length:%08x\n", fptr, tptr, length); #endif // Handle creating either a resource or data fork block = dynapro_find_free_block(dsk); if(block == 0) { return 0; } blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, block, length); // printf(" dynapro_fork_from_unix ret: %08x, storage:%02x\n", // blocks_out, storage_type); tptr[0] = storage_type >> 4; tptr[1] = blocks_out & 0xff; // key_block lo tptr[2] = (blocks_out >> 8) & 0xff; // key_block hi tptr[3] = (blocks_out >> 16) & 0xff; // blocks_used lo tptr[4] = (blocks_out >> 24) & 0xff; // blocks_used hi tptr[5] = length & 0xff; // eof lo tptr[6] = (length >> 8) & 0xff; // eof mid tptr[7] = (length >> 16) & 0xff; // eof hi return blocks_out; } ================================================ FILE: gsplus/src/clock.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" #include #ifdef _WIN32 # include # include #else # include #endif extern int Verbose; extern word32 g_vbl_count; extern int g_rom_version; extern int g_config_kegs_update_needed; #define CLK_IDLE 1 #define CLK_TIME 2 #define CLK_INTERNAL 3 #define CLK_BRAM1 4 #define CLK_BRAM2 5 int g_clk_mode = CLK_IDLE; int g_clk_read = 0; int g_clk_reg1 = 0; extern word32 g_c033_data; extern word32 g_c034_val; byte g_bram[2][256]; byte *g_bram_ptr = &(g_bram[0][0]); word32 g_clk_cur_time = 0xa0000000; int g_clk_next_vbl_update = 0; double get_dtime() { #ifdef _WIN32 FILETIME filetime; dword64 dlow, dhigh; #else struct timeval tp1; double dsec; double dusec; #endif double dtime; /* Routine used to return actual system time as a double */ /* No routine cares about the absolute value, only deltas--maybe */ /* take advantage of that in future to increase usec accuracy */ #ifdef _WIN32 //dtime = timeGetTime() / 1000.0; GetSystemTimePreciseAsFileTime(&filetime); dlow = filetime.dwLowDateTime; dhigh = filetime.dwHighDateTime; dlow = (dhigh << 32) | dlow; dtime = (double)dlow; dtime = dtime / (1000*1000*10.0); // FILETIME is in 100ns incs #else # ifdef SOLARIS gettimeofday(&tp1, (void *)0); # else gettimeofday(&tp1, (struct timezone *)0); # endif dsec = (double)tp1.tv_sec; dusec = (double)tp1.tv_usec; dtime = dsec + (dusec / (1000.0 * 1000.0)); #endif return dtime; } int micro_sleep(double dtime) { #ifndef _WIN32 struct timeval Timer; int ret; #endif if(dtime <= 0.0) { return 0; } if(dtime >= 1.0) { halt_printf("micro_sleep called with %f!!\n", dtime); return -1; } #if 0 printf("usleep: %f\n", dtime); #endif #ifdef _WIN32 Sleep((word32)(dtime * 1000)); #else Timer.tv_sec = 0; Timer.tv_usec = (dtime * 1000000.0); if( (ret = select(0, 0, 0, 0, &Timer)) < 0) { fprintf(stderr, "micro_sleep (select) ret: %d, errno: %d\n", ret, errno); return -1; } #endif return 0; } void clk_bram_zero() { int i, j; /* zero out all bram */ for(i = 0; i < 2; i++) { for(j = 0; j < 256; j++) { g_bram[i][j] = 0; } } g_bram_ptr = &(g_bram[0][0]); } void clk_bram_set(int bram_num, int offset, int val) { if((bram_num < 0) || (bram_num > 2)) { printf("bram_num %d out of range\n", bram_num); return; } if((offset < 0) || (offset > 0x100)) { printf("bram offset %05x out of range\n", offset); return; } g_bram[bram_num][offset] = val; } void clk_setup_bram_version() { if(g_rom_version < 3) { g_bram_ptr = (&g_bram[0][0]); // ROM 01 } else { g_bram_ptr = (&g_bram[1][0]); // ROM 03 } } void clk_write_bram(FILE *fconf) { int i, j, k; for(i = 0; i < 2; i++) { fprintf(fconf, "\n"); for(j = 0; j < 256; j += 16) { fprintf(fconf, "bram%d[%02x] =", 2*i + 1, j); for(k = 0; k < 16; k++) { fprintf(fconf, " %02x", g_bram[i][j+k]); } fprintf(fconf, "\n"); } } } void update_cur_time() { struct tm *tm_ptr; time_t cur_time, secs, secs2; cur_time = time(0); /* Figure out the timezone (effectively) by diffing two times. */ /* this is probably not right for a few hours around daylight savings*/ /* time transition */ secs2 = mktime(gmtime(&cur_time)); tm_ptr = localtime(&cur_time); secs = mktime(tm_ptr); secs2 = secs2 - secs; // this is the timezone offset #ifdef MAC /* Mac OS X's mktime function modifies the tm_ptr passed in for */ /* the CDT timezone and breaks this algorithm. So on a Mac, we */ /* will use the tm_ptr->gmtoff member to correct the time */ secs = secs + tm_ptr->tm_gmtoff; #else secs = cur_time - secs2; if(tm_ptr->tm_isdst) { /* adjust for daylight savings time */ secs += 3600; } #endif /* add in secs to make date based on Apple Jan 1, 1904 instead of */ /* Unix's Jan 1, 1970 */ /* So add in 66 years and 17 leap year days (1904 is a leap year) */ secs += ((66*365) + 17) * (24*3600); g_clk_cur_time = (word32)secs; clk_printf("Update g_clk_cur_time to %08x\n", g_clk_cur_time); g_clk_next_vbl_update = g_vbl_count + 5; } /* clock_update called by sim65816 every VBL */ void clock_update() { /* Nothing to do */ } void clock_update_if_needed() { int diff; diff = g_clk_next_vbl_update - g_vbl_count; if(diff < 0 || diff > 60) { /* Been a while, re-read the clock */ update_cur_time(); } } void clock_write_c034(word32 val) { g_c034_val = val & 0x7f; if((val & 0x80) != 0) { if((val & 0x20) == 0) { printf("c034 write not last = 1\n"); /* set_halt(1); */ } do_clock_data(); } } void do_clock_data() { word32 mask, read, op; clk_printf("In do_clock_data, g_clk_mode: %02x\n", g_clk_mode); read = g_c034_val & 0x40; switch(g_clk_mode) { case CLK_IDLE: g_clk_read = (g_c033_data >> 7) & 1; g_clk_reg1 = (g_c033_data >> 2) & 3; op = (g_c033_data >> 4) & 7; if(!read) { /* write */ switch(op) { case 0x0: /* Read/write seconds register */ g_clk_mode = CLK_TIME; clock_update_if_needed(); break; case 0x3: /* internal registers */ g_clk_mode = CLK_INTERNAL; if(g_clk_reg1 & 0x2) { /* extend BRAM read */ g_clk_mode = CLK_BRAM2; g_clk_reg1 = (g_c033_data & 7) << 5; } break; case 0x2: /* read/write ram 0x10-0x13 */ g_clk_mode = CLK_BRAM1; g_clk_reg1 += 0x10; break; case 0x4: /* read/write ram 0x00-0x0f */ case 0x5: case 0x6: case 0x7: g_clk_mode = CLK_BRAM1; g_clk_reg1 = (g_c033_data >> 2) & 0xf; break; default: halt_printf("Bad c033_data in CLK_IDLE: %02x\n", g_c033_data); } } else { printf("clk read from IDLE mode!\n"); /* set_halt(1); */ g_clk_mode = CLK_IDLE; } break; case CLK_BRAM2: if(!read) { /* get more bits of bram addr */ if((g_c033_data & 0x83) == 0x00) { /* more address bits */ g_clk_reg1 |= ((g_c033_data >> 2) & 0x1f); g_clk_mode = CLK_BRAM1; } else { halt_printf("CLK_BRAM2: c033_data: %02x!\n", g_c033_data); g_clk_mode = CLK_IDLE; } } else { halt_printf("CLK_BRAM2: clock read!\n"); g_clk_mode = CLK_IDLE; } break; case CLK_BRAM1: /* access battery ram addr g_clk_reg1 */ if(read) { if(g_clk_read) { /* Yup, read */ g_c033_data = g_bram_ptr[g_clk_reg1]; clk_printf("Reading BRAM loc %02x: %02x\n", g_clk_reg1, g_c033_data); } else { halt_printf("CLK_BRAM1: said wr, now read\n"); } } else { if(g_clk_read) { halt_printf("CLK_BRAM1: said rd, now write\n"); } else { /* Yup, write */ clk_printf("Writing BRAM loc %02x with %02x\n", g_clk_reg1, g_c033_data); g_bram_ptr[g_clk_reg1] = g_c033_data; g_config_kegs_update_needed = 1; } } g_clk_mode = CLK_IDLE; break; case CLK_TIME: if(read) { if(g_clk_read == 0) { halt_printf("Reading time, but in set mode!\n"); } g_c033_data = (g_clk_cur_time >> (g_clk_reg1 * 8)) & 0xff; clk_printf("Returning time byte %d: %02x\n", g_clk_reg1, g_c033_data); } else { /* Write */ if(g_clk_read) { halt_printf("Write time, but in read mode!\n"); } clk_printf("Writing TIME loc %d with %02x\n", g_clk_reg1, g_c033_data); mask = 0xff << (8 * g_clk_reg1); g_clk_cur_time = (g_clk_cur_time & (~mask)) | ((g_c033_data & 0xff) << (8 * g_clk_reg1)); } g_clk_mode = CLK_IDLE; break; case CLK_INTERNAL: if(read) { printf("Attempting to read internal reg %02x!\n", g_clk_reg1); } else { switch(g_clk_reg1) { case 0x0: /* test register */ if(g_c033_data & 0xc0) { printf("Writing test reg: %02x!\n", g_c033_data); /* set_halt(1); */ } break; case 0x1: /* write protect reg */ clk_printf("Writing clk wr_protect: %02x\n", g_c033_data); if(g_c033_data & 0x80) { printf("Stop, wr clk wr_prot: %02x\n", g_c033_data); /* set_halt(1); */ } break; default: halt_printf("Writing int reg: %02x with %02x\n", g_clk_reg1, g_c033_data); } } g_clk_mode = CLK_IDLE; break; default: halt_printf("clk mode: %d unknown!\n", g_clk_mode); g_clk_mode = CLK_IDLE; break; } } ================================================ FILE: gsplus/src/comp_swift ================================================ #!/bin/bash # $KmKId: comp_swift,v 1.2 2020-12-11 22:58:32+00 kentd Exp $ echo "args are: " "$@" /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c \ -enable-objc-interop \ -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \ -swift-version 4 -Onone \ -serialize-debugging-options \ -import-objc-header Kegs-Bridging-Header.h \ -module-name Kegs \ "$@" ================================================ FILE: gsplus/src/compile_time.c ================================================ char g_compile_time[] = "Compiled: " __DATE__ " " __TIME__ ; ================================================ FILE: gsplus/src/config.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // g_cfg_slotdrive: 0: not doing file selection at all // 1-0x7ff: doing file selection for given slot/drive // 0xfff: doing file selection for ROM or charrom #include "defc.h" #include #include "config.h" #ifdef _WIN32 # include "win_dirent.h" #else # include #endif extern int Verbose; extern word32 g_vbl_count; extern int g_track_bytes_35[]; extern int g_c031_disk35; extern int g_cur_a2_stat; extern byte *g_slow_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern double g_cur_dcycs; extern int g_rom_version; extern word32 g_adb_repeat_vbl; extern int g_adb_swap_command_option; extern int g_limit_speed; extern int g_zip_speed_mhz; extern int g_force_depth; int g_serial_cfg[2] = { 0, 1 }; // Slot 1=0=Real serial (printer?) // Slot 2=1=Virt Modem int g_serial_mask[2] = { 0, 0 }; char *g_serial_remote_ip[2] = { "", "" }; // cfg_init_menus will malloc() int g_serial_remote_port[2] = { 9100, 9100 }; char *g_serial_device[2] = { "/dev/tty.USB.0", "/dev/tty.USB.1" }; // cfg_init_menus() will malloc() the above int g_serial_win_device[2] = { 0, 0 }; // Disabled int g_serial_modem_response_code = 10; // 10 - 2400 int g_serial_modem_allow_incoming = 0; // 1 for BBS'es int g_serial_modem_init_telnet = 1; // 1 for BBS'es extern word32 g_mem_size_base; extern word32 g_mem_size_exp; extern int g_video_line_update_interval; extern int g_user_halt_bad; extern int g_joystick_type; extern int g_joystick_scale_factor_x; extern int g_joystick_scale_factor_y; extern int g_joystick_trim_amount_x; extern int g_joystick_trim_amount_y; extern int g_swap_paddles; extern int g_invert_paddles; extern int g_voc_enable; extern int g_status_enable; extern int g_mainwin_width; extern int g_mainwin_height; extern int g_mainwin_xpos; extern int g_mainwin_ypos; extern int g_screen_index[]; extern word32 g_full_refresh_needed; extern word32 g_a2_screen_buffer_changed; extern int g_key_down; extern const char g_kegs_version_str[]; int g_config_control_panel = 0; char g_config_kegs_name[1024] = { 0 }; char g_cfg_cwd_str[CFG_PATH_MAX] = { 0 }; int g_config_kegs_auto_update = 1; int g_config_kegs_update_needed = 0; int g_cfg_newdisk_select = 0; int g_cfg_newdisk_blocks = 0; int g_cfg_newdisk_blocks_default = 140*2; int g_cfg_newdisk_type = 1; int g_cfg_newdisk_type_default = 1; word32 g_cfg_newdisk_slotdrive = 0; const char *g_config_kegs_name_list[] = { "config.kegs", "kegs_conf", ".config.kegs", 0 }; int g_highest_smartport_unit = -1; int g_reparse_delay = 0; int g_user_page2_shadow = 1; char g_cfg_printf_buf[CFG_PRINTF_BUFSIZE]; char g_config_kegs_buf[CONF_BUF_LEN]; #define CFG_ERR_BUFSIZE 80 #define CFG_ERR_MAX 5 int g_cfg_err_pos = 0; char g_cfg_err_bufs[CFG_ERR_MAX][CFG_ERR_BUFSIZE]; int g_cfg_curs_x = 0; int g_cfg_curs_y = 0; int g_cfg_curs_inv = 0; int g_cfg_curs_mousetext = 0; int g_cfg_screen_changed = 0; byte g_cfg_screen[24][80]; #if defined(MAC) || defined(_WIN32) int g_cfg_ignorecase = 1; // Ignore case in filenames #else int g_cfg_ignorecase = 0; #endif #define CFG_MAX_OPTS 34 #define CFG_OPT_MAXSTR 100 int g_cfg_opts_vals[CFG_MAX_OPTS]; char g_cfg_opts_str[CFG_PATH_MAX]; char g_cfg_opt_buf[CFG_OPT_MAXSTR]; char g_cfg_edit_buf[CFG_OPT_MAXSTR]; char *g_cfg_rom_path = "ROM"; // config_init_menus will malloc char *g_cfg_charrom_path = "Undefined"; // config_init_menus will malloc int g_cfg_charrom_pos = 0; char *g_cfg_file_def_name = "Undefined"; char **g_cfg_file_strptr = 0; int g_cfg_file_min_size = 1024; int g_cfg_file_max_size = 2047*1024*1024; int g_cfg_edit_type = 0; void *g_cfg_edit_ptr = 0; #define MAX_PARTITION_BLK_SIZE 65536 char *g_argv0_path = "."; const char *g_kegs_default_paths[] = { "", "./", "${HOME}/", "${HOME}/Library/KEGS/", "${0}/../", "${0}/", "${0}/Contents/Resources/", 0 }; extern Cfg_menu g_cfg_main_menu[]; #define KNMP(a) &a, #a, 0 #define KNM(a) &a, #a Cfg_menu g_cfg_disk_menu[] = { { "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { "s5d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x5000 }, { "s5d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x5010 }, { "", 0, 0, 0, 0 }, { "s6d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x6000 }, { "s6d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x6010 }, { "", 0, 0, 0, 0 }, { "s7d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x7000 }, { "s7d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x7010 }, { "s7d3 = ", 0, 0, 0, CFGTYPE_DISK + 0x7020 }, { "s7d4 = ", 0, 0, 0, CFGTYPE_DISK + 0x7030 }, { "s7d5 = ", 0, 0, 0, CFGTYPE_DISK + 0x7040 }, { "s7d6 = ", 0, 0, 0, CFGTYPE_DISK + 0x7050 }, { "s7d7 = ", 0, 0, 0, CFGTYPE_DISK + 0x7060 }, { "s7d8 = ", 0, 0, 0, CFGTYPE_DISK + 0x7070 }, { "s7d9 = ", 0, 0, 0, CFGTYPE_DISK + 0x7080 }, { "s7d10= ", 0, 0, 0, CFGTYPE_DISK + 0x7090 }, { "s7d11= ", 0, 0, 0, CFGTYPE_DISK + 0x70a0 }, { "s7d12= ", 0, 0, 0, CFGTYPE_DISK + 0x70b0 }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot6_menu[] = { { "New 5.25\" disk image Configuration", g_cfg_newslot6_menu, 0, 0, CFGTYPE_MENU }, { "size,280,140KB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS/DOS 3.3,2,WOZ image,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot5_menu[] = { { "New 3.5\" disk image Configuration", g_cfg_newslot5_menu, 0, 0,CFGTYPE_MENU}, { "size,1600,800KB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS,2,WOZ image,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot7_menu[] = { { "New Smartport disk image Configuration", g_cfg_newslot7_menu, 0, 0, CFGTYPE_MENU}, { "size,1600,800KB,3200,1600KB,16384,8MB,32768,16MB,65535,32MB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_joystick_menu[] = { { "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, { "Joystick Emulation,0,Keypad Joystick,1,Mouse Joystick,2,Native Joystick 1," "3,Native Joystick 2", KNMP(g_joystick_type), CFGTYPE_INT }, { "Joystick Scale X,0x100,Standard,0x119,+10%,0x133,+20%," "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", KNMP(g_joystick_scale_factor_x), CFGTYPE_INT }, { "Joystick Scale Y,0x100,Standard,0x119,+10%,0x133,+20%," "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", KNMP(g_joystick_scale_factor_y), CFGTYPE_INT }, { "Joystick Trim X", KNMP(g_joystick_trim_amount_x), CFGTYPE_INT }, { "Joystick Trim Y", KNMP(g_joystick_trim_amount_y), CFGTYPE_INT }, { "Swap Joystick X and Y,0,Normal operation,1,Paddle 1 and Paddle 0 swapped", KNMP(g_swap_paddles), CFGTYPE_INT }, { "Invert Joystick,0,Normal operation,1,Left becomes right and up becomes down", KNMP(g_invert_paddles), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_rom_menu[] = { { "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, { "ROM File", KNMP(g_cfg_rom_path), CFGTYPE_FILE }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_charrom_menu[] = { { "Character ROM File Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, { "Character ROM File", KNMP(g_cfg_charrom_path), CFGTYPE_FILE }, { "Character Set,0,0x00 US Enhanced,1,0x01 US Un-enhanced," "2,0x02 Clinton Turner V1 Enhanced,3,0x03 ReActiveMicro Enhanced," "4,0x04 Dan Paymar Enhanced,5,0x05 Blippo Black Enhanced," "6,0x06 Byte Enhanced,7,0x07 Colossal Enhanced," "8,0x08 Count Enhanced,9,0x09 Flow Enhanced," "10,0x0a Gothic Enhanced,11,0x0b Outline Enhanced," "12,0x0c Pigfont Enhanced,13,0x0d Pinocchio Enhanced," "14,0x0e Slant Enhanced,15,0x0f Stop Enhanced," "16,0x10 Euro Un-Enhanced,17,0x11 Euro Enhanced," "18,0x12 Clinton Turner V2 Enhanced,19,0x13 Improved German Enhanced," "20,0x14 Improved German Un-Enhanced,21,0x15 Franch Canadian Enhanced," "22,0x16 French Canadian Un-Enhanced,23,0x17 Hebrew Enhanced," "24,0x18 Hebrew Un-Enhanced,25,0x19 Apple II+ Enhanced," "26,0x1a Apple II+ Un-Enhanced,27,0x1b Katakana Enhanced," "28,0x1c Cyrillic Enhanced,29,0x1d Greek Enhanced," "30,0x1e Esperanto Enhanced,31,0x1f Videx Enhanced", KNMP(g_cfg_charrom_pos), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_serial_menu[] = { { "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, { "Slot 1 (port 0) settings", 0, 0, 0, 0 }, { " Main setting ,0,Use Real Device below,1,Use a virtual modem," "2,Use Remote IP below,3,Use incoming port 6501", KNMP(g_serial_cfg[0]), CFGTYPE_INT }, { " Status ", (void *)cfg_get_serial0_status, 0, 0, CFGTYPE_FUNC }, { " Real Device ", KNMP(g_serial_device[0]), CFGTYPE_FILE }, { " Windows Device,0,Disabled,1,COM1,2,COM2,3,COM3,4,COM4", KNMP(g_serial_win_device[0]), CFGTYPE_INT }, { " Remote IP ", KNMP(g_serial_remote_ip[0]), CFGTYPE_STR }, { " Remote Port ", KNMP(g_serial_remote_port[0]), CFGTYPE_INT }, { " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", KNMP(g_serial_mask[0]), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Slot 2 (port 1) settings", 0, 0, 0, 0, }, { " Main setting ,0,Use Real Device below,1,Use a virtual modem," "2,Use Remote IP below,3,Use incoming port 6502", KNMP(g_serial_cfg[1]), CFGTYPE_INT }, { " Status ", (void *)cfg_get_serial1_status, 0, 0, CFGTYPE_FUNC }, { " Real Device ", KNMP(g_serial_device[1]), CFGTYPE_FILE }, { " Windows Device,1,COM1,2,COM2,3,COM3,4,COM4", KNMP(g_serial_win_device[1]), CFGTYPE_INT }, { " Remote IP ", KNMP(g_serial_remote_ip[1]), CFGTYPE_STR }, { " Remote Port ", KNMP(g_serial_remote_port[1]), CFGTYPE_INT }, { " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", KNMP(g_serial_mask[1]), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_modem_menu[] = { { "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, { "Modem Speed Response Code ,5,5 - CONNECT 1200,10,10 - CONNECT 2400," "12,12 - CONNECT 9600 (HAYES/Warp6)," "13,13 - CONNECT 9600 (USR/HST)," "14,14 - CONNECT 19200 (HAYES/Warp6)," "28,28 - CONNECT 38400 (HAYES/Warp6)", KNMP(g_serial_modem_response_code), CFGTYPE_INT }, { "Allow Modem incoming on 6501/6502 ,0,Outgoing only," "1,Incoming and outgoing (BBS)", KNMP(g_serial_modem_allow_incoming), CFGTYPE_INT }, { "Send Telnet Escape codes ,0,Disable Telnet,1,Send Telnet codes", KNMP(g_serial_modem_init_telnet), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_video_menu[] = { { "Force X-windows display depth", KNMP(g_force_depth), CFGTYPE_INT }, { "Enable VOC,0,Disabled,1,Enabled", KNMP(g_voc_enable), CFGTYPE_INT }, { "Default Main Window width", KNMP(g_mainwin_width), CFGTYPE_INT }, { "Default Main Window height", KNMP(g_mainwin_height), CFGTYPE_INT }, { "Main Window X position", KNMP(g_mainwin_xpos), CFGTYPE_INT }, { "Main Window Y position", KNMP(g_mainwin_ypos), CFGTYPE_INT }, { "3200 Color Enable,0,Auto (Full if fast enough),1,Full (Update every line)," "8,Off (Update video every 8 lines)", KNMP(g_video_line_update_interval), CFGTYPE_INT }, { "Dump text screen to file", (void *)cfg_text_screen_dump, 0, 0, CFGTYPE_FUNC}, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_main_menu[] = { { "KEGS Configuration", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, { "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, { "Character ROM Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, { "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, { "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, { "Video Settings", g_cfg_video_menu, 0, 0, CFGTYPE_MENU }, { "Auto-update config.kegs,0,Manual,1,Immediately", KNMP(g_config_kegs_auto_update), CFGTYPE_INT }, { "Speed,0,Unlimited,1,1.0MHz,2,2.8MHz,3,8.0MHz (Zip)", KNMP(g_limit_speed), CFGTYPE_INT }, { "ZipGS Speed,8,8MHz,16,16MHz,32,32MHz,64,64MHz,128,128MHz", KNMP(g_zip_speed_mhz), CFGTYPE_INT }, { "Expansion Mem Size,0,0MB,0x100000,1MB,0x200000,2MB,0x300000,3MB," "0x400000,4MB,0x600000,6MB,0x800000,8MB,0xa00000,10MB,0xc00000,12MB," "0xe00000,14MB", KNMP(g_mem_size_exp), CFGTYPE_INT }, { "Show Status lines,0,Disabled,1,Enabled", KNMP(g_status_enable), CFGTYPE_INT}, { "Code Red Halts,0,Do not stop on bad accesses,1,Enter debugger on bad " "accesses", KNMP(g_user_halt_bad), CFGTYPE_INT }, { "Enable Text Page 2 Shadow,0,Disabled on ROM 01 (matches real hardware)," "1,Enabled on ROM 01 and 03", KNMP(g_user_page2_shadow), CFGTYPE_INT }, { "Swap Command/Option keys,0,Disabled,1,Swapped", KNMP(g_adb_swap_command_option), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Save changes to config.kegs", (void *)config_write_config_kegs_file, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Exit Config (or press F4)", (void *)cfg_exit, 0, 0, CFGTYPE_FUNC }, { 0, 0, 0, 0, 0 }, }; #define CFG_MAX_DEFVALS 128 Cfg_defval g_cfg_defvals[CFG_MAX_DEFVALS]; int g_cfg_defval_index = 0; word32 g_cfg_slotdrive = 0; int g_cfg_select_partition = -1; char g_cfg_tmp_path[CFG_PATH_MAX]; char g_cfg_file_path[CFG_PATH_MAX]; char g_cfg_file_cachedpath[CFG_PATH_MAX]; char g_cfg_file_cachedreal[CFG_PATH_MAX]; char g_cfg_file_curpath[CFG_PATH_MAX]; char g_cfg_file_shortened[CFG_PATH_MAX]; char g_cfg_file_match[CFG_PATH_MAX]; char g_cfg_part_path[CFG_PATH_MAX]; int g_cfg_partition_is_zip = 0; Cfg_listhdr g_cfg_dirlist = { 0 }; Cfg_listhdr g_cfg_partitionlist = { 0 }; int g_cfg_file_pathfield = 0; const char *g_kegs_rom_names[] = { "ROM", "ROM", "ROM.01", "ROM.03", "APPLE2GS.ROM", "APPLE2GS.ROM2", "xgs.rom", "XGS.ROM", "Rom03gd", "342-0077-b", // MAME ROM.01 0 }; /* First entry is special--it will be overwritten by g_cfg_rom_path */ const char *g_kegs_c1rom_names[] = { 0 }; const char *g_kegs_c2rom_names[] = { 0 }; const char *g_kegs_c3rom_names[] = { 0 }; const char *g_kegs_c4rom_names[] = { 0 }; const char *g_kegs_c5rom_names[] = { 0 }; const char *g_kegs_c6rom_names[] = { "c600.rom", "controller.rom", "disk.rom", "DISK.ROM", "diskII.prom", 0 }; const char *g_kegs_c7rom_names[] = { 0 }; const char **g_kegs_rom_card_list[8] = { 0, g_kegs_c1rom_names, g_kegs_c2rom_names, g_kegs_c3rom_names, g_kegs_c4rom_names, g_kegs_c5rom_names, g_kegs_c6rom_names, g_kegs_c7rom_names }; byte g_rom_c600_rom01_diffs[256] = { 0x00, 0x00, 0x00, 0x00, 0xc6, 0x00, 0xe2, 0x00, 0xd0, 0x50, 0x0f, 0x77, 0x5b, 0xb9, 0xc3, 0xb1, 0xb1, 0xf8, 0xcb, 0x4e, 0xb8, 0x60, 0xc7, 0x2e, 0xfc, 0xe0, 0xbf, 0x1f, 0x66, 0x37, 0x4a, 0x70, 0x55, 0x2c, 0x3c, 0xfc, 0xc2, 0xa5, 0x08, 0x29, 0xac, 0x21, 0xcc, 0x09, 0x55, 0x03, 0x17, 0x35, 0x4e, 0xe2, 0x0c, 0xe9, 0x3f, 0x9d, 0xc2, 0x06, 0x18, 0x88, 0x0d, 0x58, 0x57, 0x6d, 0x83, 0x8c, 0x22, 0xd3, 0x4f, 0x0a, 0xe5, 0xb7, 0x9f, 0x7d, 0x2c, 0x3e, 0xae, 0x7f, 0x24, 0x78, 0xfd, 0xd0, 0xb5, 0xd6, 0xe5, 0x26, 0x85, 0x3d, 0x8d, 0xc9, 0x79, 0x0c, 0x75, 0xec, 0x98, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x6c, 0x44, 0xce, 0x4c, 0x01, 0x08, 0x00, 0x00 }; byte g_rom_c700[256] = { //0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x3c, // For Apple //e 0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x00, //^^= LDX #$20; LDY #$00, LDX #$03 CMP #$3c 0x80, 0x0c, 0x18, 0xb8, 0x70, 0x38, 0xb8, 0x42, //^^= BRA $c716; CLC; CLV; BVS $c746 (SEC); CLV; WDM $c7,$00 0xc7, 0x00, 0x60, 0x00, 0x00, 0xea, 0xe2, 0x41, //^^= ...; RTS..............; NOP; SEP #$41 0x70, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //^^= BVS $c70f 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // So does WDM $c7,$00 with psr.v=1 for $c700; v=0,c=0 for $c70a, // and v=0,c=1 for $c70d 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xbf, 0x0a }; Cfg_menu *g_menuptr = 0; int g_menu_line = 1; int g_menu_inc = 1; int g_menu_max_line = 1; int g_menu_redraw_needed = 1; #define MAX_CFG_ARGV_OVERRIDES 64 int g_cfg_argv_num_overrides = 0; char *g_cfg_argv_overrides[MAX_CFG_ARGV_OVERRIDES]; int config_add_argv_override(const char *str1, const char *str2) { const char *equal_ptr; char *str; int ret, pos, len; // Handle things like "rom=rompath" and "rom", "rompath" // Look through str1, see if there is '=', if so ignore str2 equal_ptr = strchr(str1, '='); ret = 1; if(equal_ptr) { // str1 has '=' in it ret = 0; // Don't eat up str2 argument str = kegs_malloc_str(str1); } else { // We need to form a new string of str1, =, str2 len = (int)(strlen(str1) + strlen(str2) + 2); str = malloc(len); cfg_strncpy(str, str1, len); cfg_strlcat(str, "=", len); cfg_strlcat(str, str2, len); } pos = g_cfg_argv_num_overrides++; if(pos >= MAX_CFG_ARGV_OVERRIDES) { g_cfg_argv_num_overrides = MAX_CFG_ARGV_OVERRIDES; fatal_printf("MAX_CFG_ARGV_OVERRIDES overflow\n"); my_exit(5); return ret; } g_cfg_argv_overrides[pos] = str; printf("Added config override %d, %s\n", pos, str); return ret; } void config_set_config_kegs_name(const char *str1) { int maxlen; // Command line override "-cfg cfg_file" g_config_kegs_name[0] = 0; maxlen = (int)sizeof(g_config_kegs_name); cfg_strncpy(&g_config_kegs_name[0], str1, maxlen); } void config_init_menus(Cfg_menu *menuptr) { void *voidptr; const char *name_str; Cfg_defval *defptr; char **str_ptr; char *str; int type, pos, val; if(menuptr[0].defptr != 0) { return; } menuptr[0].defptr = (void *)1; pos = 0; while(pos < 100) { type = menuptr->cfgtype; voidptr = menuptr->ptr; name_str = menuptr->name_str; if(menuptr->str == 0) { break; } if(name_str != 0) { defptr = &(g_cfg_defvals[g_cfg_defval_index++]); if(g_cfg_defval_index >= CFG_MAX_DEFVALS) { fatal_printf("CFG_MAX_DEFVAL overflow\n"); my_exit(5); return; } defptr->menuptr = menuptr; defptr->intval = 0; defptr->strval = 0; switch(type) { case CFGTYPE_INT: val = *((int *)voidptr); defptr->intval = val; menuptr->defptr = &(defptr->intval); break; case CFGTYPE_FILE: case CFGTYPE_STR: str_ptr = (char **)menuptr->ptr; str = *str_ptr; // We need to malloc this string since all // string values must be dynamically alloced defptr->strval = str; // this can have a copy *str_ptr = kegs_malloc_str(str); menuptr->defptr = &(defptr->strval); break; default: fatal_printf("name_str is %p = %s, but type: " "%d\n", name_str, name_str, type); my_exit(5); return; } } if(type == CFGTYPE_MENU) { config_init_menus((Cfg_menu *)voidptr); } pos++; menuptr++; } } void config_init() { config_init_menus(g_cfg_main_menu); // Find the config.kegs file if(g_config_kegs_name[0] == 0) { cfg_find_config_kegs_file(); } config_parse_config_kegs_file(); } void cfg_find_config_kegs_file() { const char **path_ptr; int maxlen, fd; g_config_kegs_name[0] = 0; maxlen = sizeof(g_config_kegs_name); fd = 0; if(!config_setup_kegs_file(&g_config_kegs_name[0], maxlen, &g_config_kegs_name_list[0])) { // Try to create config.kegs fd = -1; path_ptr = &g_kegs_default_paths[0]; while(*path_ptr) { config_expand_path(&g_config_kegs_name[0], *path_ptr, maxlen); cfg_strlcat(&g_config_kegs_name[0], "config.kegs", maxlen); printf("Trying to create %s\n", &g_config_kegs_name[0]); fd = open(&g_config_kegs_name[0], O_CREAT | O_TRUNC | O_WRONLY, 0x1b6); close(fd); if(fd >= 0) { break; } path_ptr++; } } if(fd < 0) { fatal_printf("Could not create config.kegs!\n"); my_exit(2); } } int config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr) { struct stat stat_buf; const char **path_ptr, **cur_name_ptr; mode_t fmt; int ret, len; outname[0] = 0; path_ptr = &g_kegs_default_paths[0]; // Array of strings while(*path_ptr) { len = config_expand_path(outname, *path_ptr, maxlen); if(len != (int)strlen(outname)) { printf("config_expand_path ret %d, but strlen:%d!\n", len, (int)strlen(outname)); } cur_name_ptr = name_ptr; while(*cur_name_ptr && (len < maxlen)) { outname[len] = 0; cfg_strlcat(outname, *cur_name_ptr, maxlen); // printf("Doing stat on %s\n", outname); ret = cfg_stat(outname, &stat_buf, 0); if(ret == 0) { fmt = stat_buf.st_mode & S_IFMT; if(fmt != S_IFDIR) { /* got it! */ return 1; } } cur_name_ptr++; } path_ptr++; } return 0; } int config_expand_path(char *out_ptr, const char *in_ptr, int maxlen) { char name_buf[256]; char *tmp_ptr; int name_len, in_char, state, pos; out_ptr[0] = 0; pos = 0; name_len = 0; state = 0; /* See if in_ptr has ${} notation, replace with getenv or argv0 */ while(pos < (maxlen - 1)) { in_char = *in_ptr++; out_ptr[pos++] = in_char; out_ptr[pos] = 0; if(in_char == 0) { return pos - 1; } if(state == 0) { /* No $ seen yet, look for it */ if(in_char == '$') { state = 1; } } else if(state == 1) { /* See if next char is '{' (dummy }) */ if(in_char == '{') { /* add dummy } */ state = 2; name_len = 0; pos -= 2; out_ptr[pos] = 0; } else { state = 0; } } else if(state == 2) { /* fill name_buf ... dummy '{' */ pos--; out_ptr[pos] = 0; if(in_char == '}') { name_buf[name_len] = 0; /* got token, now look it up */ tmp_ptr = ""; if(!strncmp("0", name_buf, 128)) { /* Replace ${0} with g_argv0_path */ tmp_ptr = g_argv0_path; } else { tmp_ptr = getenv(name_buf); if(tmp_ptr == 0) { tmp_ptr = ""; } } pos = cfg_strlcat(out_ptr, tmp_ptr, maxlen); state = 0; } else { name_buf[name_len++] = in_char; if(name_len >= 250) { name_len--; } } } } return pos; } char * cfg_exit(int get_status) { /* printf("In cfg exit\n"); */ if(get_status) { return 0; } cfg_toggle_config_panel(); return 0; } void cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap) { char *bufptr; int pos, len, bufsize; pos = g_cfg_err_pos; if(pos >= CFG_ERR_MAX) { pos = CFG_ERR_MAX - 1; } bufptr = &g_cfg_err_bufs[pos][0]; len = 0; bufsize = CFG_ERR_BUFSIZE; if(pre_str && *pre_str) { cfg_strncpy(bufptr, pre_str, CFG_ERR_BUFSIZE); cfg_strlcat(bufptr, " error: ", CFG_ERR_BUFSIZE); len = (int)strlen(bufptr); bufsize = CFG_ERR_BUFSIZE - len; } if(bufsize > 0) { vsnprintf(&bufptr[len], bufsize, fmt, ap); } fputs(bufptr, stderr); g_cfg_err_pos = pos + 1; } void cfg_err_printf(const char *pre_str, const char *fmt, ...) { va_list ap; va_start(ap, fmt); cfg_err_vprintf(pre_str, fmt, ap); va_end(ap); } void cfg_toggle_config_panel() { int panel; panel = !g_config_control_panel; if(g_rom_version < 0) { panel = 1; /* Stay in config mode */ } if(panel != g_config_control_panel) { cfg_set_config_panel(panel); } } void cfg_set_config_panel(int panel) { int i; g_config_control_panel = panel; if(panel) { // Entering configuration panel video_force_reparse(); cfg_printf("Entering config_control_panel\n"); for(i = 0; i < 20; i++) { // Toss any queued-up keypresses if(adb_read_c000() & 0x80) { (void)adb_access_c010(); } } // HACK: Force adb keyboard (and probably mouse) to "normal"... cfg_home(); g_menu_line = 1; g_menu_inc = 1; g_menu_redraw_needed = 1; g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; g_cfg_select_partition = -1; } else { // Leave config panel, go back to A2 emulation video_force_reparse(); } g_full_refresh_needed = -1; g_a2_screen_buffer_changed = -1; g_adb_repeat_vbl = g_vbl_count + 60; } char * cfg_text_screen_dump(int get_status) { FILE *ofile; char *bufptr; char *filename; if(get_status) { return 0; } filename = "kegs.screen.dump"; printf("Writing text screen to the file %s\n", filename); ofile = fopen(filename, "w"); if(ofile == 0) { fatal_printf("Could not write to file %s, (%d)\n", filename, errno); return 0; } bufptr = cfg_text_screen_str(); fputs(bufptr, ofile); fclose(ofile); return 0; } char g_text_screen_buf[2100] = { 0 }; char * cfg_text_screen_str() { char *bufptr; int pos, start_pos, c, offset; int i, j; // bufptr must be at least (81*24)+2 characters bufptr = &g_text_screen_buf[0]; pos = 0; for(i = 0; i < 24; i++) { start_pos = pos; for(j = 0; j < 40; j++) { offset = g_screen_index[i] + j; if(g_cur_a2_stat & ALL_STAT_VID80) { c = g_slow_memory_ptr[0x10400 + offset] & 0x7f; if(c < 0x20) { c += 0x40; } bufptr[pos++] = c; } c = g_slow_memory_ptr[0x0400 + offset] & 0x7f; if(c < 0x20) { c += 0x40; } if(c == 0x7f) { c = ' '; } bufptr[pos++] = c; } while((pos > start_pos) && (bufptr[pos-1] == ' ')) { /* try to strip out trailing spaces */ pos--; } bufptr[pos++] = '\n'; bufptr[pos] = 0; } return bufptr; } char * cfg_get_serial0_status(int get_status) { return scc_get_serial_status(get_status, 0); } char * cfg_get_serial1_status(int get_status) { return scc_get_serial_status(get_status, 1); } char * cfg_get_current_copy_selection() { return &g_text_screen_buf[0]; } void config_vbl_update(int doit_3_persec) { if(doit_3_persec) { if(g_config_kegs_auto_update && g_config_kegs_update_needed) { (void)config_write_config_kegs_file(0); } } return; } void cfg_file_update_rom(const char *str) { cfg_file_update_ptr(&g_cfg_rom_path, str, 1); } void cfg_file_update_ptr(char **strptr, const char *str, int need_update) { char *newstr; int remote_changed, serial_changed; int i; // Update whatever g_cfg_file_strptr points to. If changing // ROM path or Charrom, then do the proper updates newstr = kegs_malloc_str(str); if(!strptr) { return; } if(*strptr) { free(*strptr); } *strptr = newstr; if(strptr == &(g_cfg_rom_path)) { printf("Updated ROM file\n"); load_roms_init_memory(); do_reset(); } if(strptr == &(g_cfg_charrom_path)) { printf("Updated Char ROM file\n"); cfg_load_charrom(); } for(i = 0; i < 2; i++) { remote_changed = 0; serial_changed = 0; if(strptr == &g_serial_remote_ip[i]) { remote_changed = 1; } if(strptr == &g_serial_device[i]) { serial_changed = 1; } if(remote_changed || serial_changed) { scc_config_changed(i, 0, remote_changed, serial_changed); } } if(need_update) { g_config_kegs_update_needed = 1; printf("Set g_config_kegs_update_needed = 1\n"); } } void cfg_int_update(int *iptr, int new_val) { int old_val, cfg_changed, remote_changed, serial_changed; int i; // Called to handle an integer being changed in the F4 config menus // where it's value may need special handling old_val = *iptr; *iptr = new_val; if(old_val == new_val) { return; } if(iptr == &g_cfg_charrom_pos) { cfg_load_charrom(); } for(i = 0; i < 2; i++) { remote_changed = 0; serial_changed = 0; cfg_changed = 0; if(iptr == &g_serial_cfg[i]) { cfg_changed = 1; } if(iptr == &g_serial_remote_port[i]) { remote_changed = 1; } if(iptr == &g_serial_win_device[i]) { serial_changed = 1; } if(cfg_changed || remote_changed || serial_changed) { scc_config_changed(i, cfg_changed, remote_changed, serial_changed); } } } void cfg_load_charrom() { byte buffer[4096]; dword64 dsize, dret; word32 upos; int fd; printf("Loading character ROM from: %s\n", g_cfg_charrom_path); fd = open(g_cfg_charrom_path, O_RDONLY | O_BINARY); if(fd < 0) { printf("Cannot open %s\n", g_cfg_charrom_path); return; } dsize = cfg_get_fd_size(fd); upos = g_cfg_charrom_pos * 0x1000U; if(dsize < (upos + 0x1000)) { g_cfg_charrom_pos = 0; return; } dret = cfg_read_from_fd(fd, &buffer[0], upos, 4096); if(dret != 0) { prepare_a2_romx_font(&buffer[0]); } } void config_load_roms() { struct stat stat_buf; const char **names_ptr; int more_than_8mb, changed_rom, len, fd, ret; int i; g_rom_version = -1; /* set first entry of g_kegs_rom_names[] to g_cfg_rom_path so that */ /* it becomes the first place searched. */ g_kegs_rom_names[0] = g_cfg_rom_path; ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, &g_kegs_rom_names[0]); if(ret == 0) { // Just get out, let config interface select ROM g_config_control_panel = 1; printf("No ROM, set g_config_control_panel=1\n"); return; } printf("Found ROM at path: %s\n", g_cfg_tmp_path); fd = open(&g_cfg_tmp_path[0], O_RDONLY | O_BINARY); if(fd < 0) { fatal_printf("Open ROM file %s failed:%d, errno:%d\n", &g_cfg_tmp_path[0], fd, errno); g_config_control_panel = 1; return; } ret = fstat(fd, &stat_buf); if(ret != 0) { fatal_printf("fstat returned %d on fd %d, errno: %d\n", ret, fd, errno); g_config_control_panel = 1; return; } len = (int)stat_buf.st_size; memset(&g_rom_fc_ff_ptr[0], 0, 4*65536); /* Clear banks fc-ff to 0 */ if(len == 32*1024) { // Apple //e g_rom_version = 0; g_mem_size_base = 128*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[3*65536 + 32768], len); } else if(len == 128*1024) { g_rom_version = 1; g_mem_size_base = 128*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[2*65536], len); } else if(len == 256*1024) { g_rom_version = 3; g_mem_size_base = 1024*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[0], len); } else { fatal_printf("The ROM size should be 128K or 256K, this file " "is %d bytes\n", len); g_config_control_panel = 1; return; } printf("Read: %d bytes of ROM\n", ret); if(ret != len) { fatal_printf("errno: %d\n", errno); g_config_control_panel = 1; return; } close(fd); memset(&g_rom_cards_ptr[0], 0, 256*16); for(i = 0; i < 256; i++) { // Place HD PROM in slot 7 g_rom_cards_ptr[0x700 + i] = g_rom_c700[i]; // g_rom_cards_ptr[0x500 + i] = g_rom_c700[i]; } /* initialize c600 rom to be diffs from the real ROM, to build-in */ /* Apple II compatibility without distributing ROMs */ if(g_rom_version == 0) { // Apple //e for(i = 0; i < 256; i++) { // Place Disk II PROM in slot 6 g_rom_cards_ptr[0x600 + i] = g_rom_fc_ff_ptr[0x38600+i]; } } else { for(i = 0; i < 256; i++) { g_rom_cards_ptr[0x600 + i] = g_rom_fc_ff_ptr[0x3c600 + i] ^ g_rom_c600_rom01_diffs[i]; } } if(g_rom_version >= 3) { /* some patches */ g_rom_cards_ptr[0x61b] ^= 0x40; g_rom_cards_ptr[0x61c] ^= 0x33; g_rom_cards_ptr[0x632] ^= 0xc0; g_rom_cards_ptr[0x633] ^= 0x33; } for(i = 1; i < 8; i++) { names_ptr = g_kegs_rom_card_list[i]; if(names_ptr == 0) { continue; } if(*names_ptr == 0) { continue; } ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, names_ptr); if(ret != 0) { fd = open(&(g_cfg_tmp_path[0]), O_RDONLY | O_BINARY); if(fd < 0) { fatal_printf("Open card ROM file %s failed: %d " "err:%d\n", &g_cfg_tmp_path[0], fd, errno); continue; } len = 256; ret = (int)read(fd, &g_rom_cards_ptr[i*0x100], len); if(ret != len) { fatal_printf("While reading card ROM %s, file " "is too short. (%d) Expected %d bytes, " "read %d bytes\n", errno, len, ret); continue; } close(fd); } } more_than_8mb = (g_mem_size_exp > 0x800000); /* Only do the patch if users wants more than 8MB of expansion mem */ changed_rom = 0; if(g_rom_version == 1) { /* make some patches to ROM 01 */ #if 0 /* 1: Patch ROM selftest to not do speed test */ printf("Patching out speed test failures from ROM 01\n"); g_rom_fc_ff_ptr[0x3785a] = 0x18; changed_rom = 1; #endif #if 0 /* 2: Patch ROM selftests not to do tests 2,4 */ /* 0 = skip, 1 = do it, test 1 is bit 0 of LSByte */ g_rom_fc_ff_ptr[0x371e9] = 0xf5; g_rom_fc_ff_ptr[0x371ea] = 0xff; changed_rom = 1; #endif if(more_than_8mb) { /* Geoff Weiss patch to use up to 14MB of RAM */ g_rom_fc_ff_ptr[0x30302] = 0xdf; g_rom_fc_ff_ptr[0x30314] = 0xdf; g_rom_fc_ff_ptr[0x3031c] = 0x00; changed_rom = 1; } /* Patch ROM selftest to not do ROM cksum if any changes*/ if(changed_rom) { g_rom_fc_ff_ptr[0x37a06] = 0x18; g_rom_fc_ff_ptr[0x37a07] = 0x18; } } else if(g_rom_version == 3) { /* patch ROM 03 */ printf("Patching ROM 03 smartport bug\n"); /* 1: Patch Smartport code to fix a stupid bug */ /* that causes it to write the IWM status reg into c036, */ /* which is the system speed reg...it's "safe" since */ /* IWM status reg bit 4 must be 0 (7MHz)..., otherwise */ /* it might have turned on shadowing in all banks! */ g_rom_fc_ff_ptr[0x357c9] = 0x00; changed_rom = 1; #if 0 /* patch ROM 03 to not to speed test */ /* skip fast speed test */ g_rom_fc_ff_ptr[0x36ad7] = 0x18; g_rom_fc_ff_ptr[0x36ad8] = 0x18; changed_rom = 1; #endif #if 0 /* skip slow speed test */ g_rom_fc_ff_ptr[0x36ae7] = 0x18; g_rom_fc_ff_ptr[0x36ae8] = 0x6b; changed_rom = 1; #endif #if 0 /* 4: Patch ROM 03 selftests not to do tests 1-4 */ g_rom_fc_ff_ptr[0x364a9] = 0xf0; g_rom_fc_ff_ptr[0x364aa] = 0xff; changed_rom = 1; #endif /* ROM tests are in ff/6403-642x, where 6403 = addr of */ /* test 1, etc. */ if(more_than_8mb) { /* Geoff Weiss patch to use up to 14MB of RAM */ g_rom_fc_ff_ptr[0x30b] = 0xdf; g_rom_fc_ff_ptr[0x31d] = 0xdf; g_rom_fc_ff_ptr[0x325] = 0x00; changed_rom = 1; } if(changed_rom) { /* patch ROM 03 selftest to not do ROM cksum */ g_rom_fc_ff_ptr[0x36cb0] = 0x18; g_rom_fc_ff_ptr[0x36cb1] = 0x18; } } } void config_parse_config_kegs_file() { char *bufptr; const char *str; dword64 dsize; int fd, pos, start, size, last_c, line, ret, c, maxlen; int i; printf("Parsing config.kegs file: %s\n", g_config_kegs_name); clk_bram_zero(); g_highest_smartport_unit = -1; cfg_get_base_path(&g_cfg_cwd_str[0], g_config_kegs_name, 0); if(g_cfg_cwd_str[0] != 0) { ret = chdir(&g_cfg_cwd_str[0]); if(ret != 0) { printf("chdir to %s, errno:%d\n", g_cfg_cwd_str, errno); } // Do basename(g_config_kegs_name)--on it's own buffer str = cfg_str_basename(g_config_kegs_name); maxlen = sizeof(g_config_kegs_name); cfg_strncpy(&g_config_kegs_name[0], str, maxlen); } // In any case, copy the current directory path to g_cfg_cwd_str (void)!getcwd(&g_cfg_cwd_str[0], CFG_PATH_MAX); printf("CWD is now: %s\n", &g_cfg_cwd_str[0]); fd = open(g_config_kegs_name, O_RDONLY | O_BINARY); dsize = 0; if(fd >= 0) { dsize = cfg_get_fd_size(fd); } if((fd < 0) || (dsize >= (1 << 30))) { fatal_printf("cannot open config.kegs at %s, or it is too " "large! Stopping!\n", g_config_kegs_name); my_exit(3); return; } size = (int)dsize; bufptr = malloc(size + 2); ret = (int)cfg_read_from_fd(fd, (byte *)bufptr, 0, size); close(fd); if(ret != size) { free(bufptr); fatal_printf("Could not read config.kegs at %s\n", g_config_kegs_name); my_exit(3); return; } bufptr[size] = 0; // Ensure it's null terminated line = 0; pos = 0; last_c = 0; while(pos < size) { line++; if((bufptr[pos] == '\n') && (last_c == '\r')) { // CR,LF, just eat the LF pos++; } start = pos; while(pos < size) { c = bufptr[pos]; if((c == 0) || (c == '\n') || (c == '\r')) { last_c = c; bufptr[pos] = 0; break; } pos++; } cfg_parse_one_line(&bufptr[start], line); pos++; } free(bufptr); // And now do command line argument overrides for(i = 0; i < g_cfg_argv_num_overrides; i++) { printf("Doing override %d, %s\n", i, g_cfg_argv_overrides[i]); cfg_parse_one_line(g_cfg_argv_overrides[i], 1001 + i); g_config_kegs_update_needed = 1; } } void cfg_parse_one_line(char *buf, int line) { Cfg_menu *menuptr; Cfg_defval *defptr; int *iptr; const char *nameptr; int pos, num_equals, type, val, c, len; int i; // warning: modifies memory of bufptr (turns spaces to nulls) if(line) { // Avoid unused parameter warning } len = (int)strlen(buf); if(len <= 1) { // Not a valid line, just get out return; } // printf("disk_conf[%d]: %s\n", line, buf); if(buf[0] == '#') { iwm_printf("Skipping comment\n"); return; } pos = 0; while((pos < len) && (buf[pos] == ' ' || buf[pos] == '\t') ) { pos++; // Eat whitespace } if(pos >= len) { return; // blank line } if((pos + 5) < len) { c = buf[pos+1]; // Slot number if((buf[pos] == 's') && (buf[pos+2] == 'd') && (c >= '5' && c <= '7')) { // looks like s5d1 through s7d15, parse that cfg_parse_sxdx(buf, pos, len); return; } } // parse buf from pos into option, "=" and then "rest" if(strncmp(&buf[pos], "bram", 4) == 0) { cfg_parse_bram(buf, pos+4, len); return; } // find "name" as first contiguous string //printf("...parse_option: line %d, %s (%s) len:%d\n", line, buf, // &buf[pos], len); nameptr = &buf[pos]; while(pos < len) { c = buf[pos]; if((c == 0) || (c == ' ') || (c == '\t') || (c == '\n') || (c == '=')) { break; } pos++; } buf[pos] = 0; if(strcmp(nameptr, "rom") == 0) { // Translate argument of "-rom" to "g_cfg_rom_path" nameptr = "g_cfg_rom_path"; } pos++; // Eat up all whitespace and '=' num_equals = 0; while(pos < len) { c = buf[pos]; if((c == '=') && (num_equals == 0)) { pos++; num_equals++; } else if(c == ' ' || c == '\t') { pos++; } else { break; } } /* Look up nameptr to find type */ type = -1; defptr = 0; menuptr = 0; for(i = 0; i < g_cfg_defval_index; i++) { defptr = &(g_cfg_defvals[i]); menuptr = defptr->menuptr; if(strcmp(menuptr->name_str, nameptr) == 0) { type = menuptr->cfgtype; break; } } switch(type) { case CFGTYPE_INT: /* use strtol */ val = (int)strtol(&buf[pos], 0, 0); iptr = (int *)menuptr->ptr; cfg_int_update(iptr, val); break; case CFGTYPE_FILE: case CFGTYPE_STR: cfg_file_update_ptr(menuptr->ptr, &buf[pos], 0); break; default: printf("Config file variable %s is unknown type: %d\n", nameptr, type); } } void cfg_parse_bram(char *buf, int pos, int len) { word32 val; int bram_num, offset; // Format: "bram1[00] = xx yy..." or "bram3[00] = xx yy ..." // pos = position just after "bram" if((len < (pos+5)) || (buf[pos+1] != '[') || (buf[pos+4] != ']')) { fatal_printf("While reading config.kegs, found malformed bram " "statement: %s\n", buf); return; } bram_num = buf[pos] - '0'; if((bram_num != 1) && (bram_num != 3)) { fatal_printf("While reading config.kegs, found bad bram " "num: %s\n", buf); return; } bram_num = bram_num >> 1; // turn 3->1 and 1->0 offset = (int)strtoul(&(buf[pos+2]), 0, 16); pos += 5; while(pos < len) { if((buf[pos] == ' ') || (buf[pos] == '\t') || (buf[pos] == 0x0a) || (buf[pos] == 0x0d) || (buf[pos] == '=')) { pos++; continue; } val = (word32)strtoul(&buf[pos], 0, 16); // As hex clk_bram_set(bram_num, offset, val); offset++; pos += 2; } } void cfg_parse_sxdx(char *buf, int pos, int len) { char *name_ptr, *partition_name; word32 dynamic_blocks; int part_num, ejected, slot, drive, c; slot = buf[pos+1] - '0'; drive = buf[pos+3] - '0'; /* skip over slot, drive */ pos += 4; if((buf[pos] >= '0') && (buf[pos] <= '9')) { // Second digit of drive is valid drive = (drive * 10) + buf[pos] - '0'; pos++; } drive--; // make sxd1 mean index 0 while((pos < len) && ((buf[pos] == ' ') || (buf[pos] == '\t') || (buf[pos] == '=')) ) { pos++; } ejected = 0; if(buf[pos] == '#') { // disk is ejected, but read info anyway ejected = 1; pos++; } partition_name = 0; part_num = -1; dynamic_blocks = 0; if(buf[pos] == ':') { // yup, it's got a partition name! pos++; partition_name = &buf[pos]; while((pos < len) && (buf[pos] != ':')) { pos++; } buf[pos] = 0; /* null terminate partition name */ pos++; } c = buf[pos]; if((c == ';') || (c == '@')) { pos++; // ; - partition number; @ - Dynamic ProDOS dir size part_num = 0; while((pos < len) && (buf[pos] != ':')) { part_num = (10*part_num) + buf[pos] - '0'; pos++; } pos++; if(c == '@') { // Dynamic ProDOS directory dynamic_blocks = part_num * 2; part_num = -1; } } /* Get filename */ name_ptr = &(buf[pos]); if((pos >= len) || (name_ptr[0] == 0)) { return; } insert_disk(slot, drive, name_ptr, ejected, partition_name, part_num, dynamic_blocks); } void config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, int with_extras) { char *str; str = outstr; if(with_extras) { if(dsk->fd < 0) { snprintf(str, maxlen - (str - outstr), "#"); str = &outstr[strlen(outstr)]; } if(dsk->dynapro_blocks) { snprintf(str, maxlen - (str - outstr), "@%d:", (dsk->dynapro_blocks + 1) / 2); str = &outstr[strlen(outstr)]; } else if(dsk->partition_name != 0) { snprintf(str, maxlen - (str - outstr), ":%s:", dsk->partition_name); str = &outstr[strlen(outstr)]; } else if(dsk->partition_num >= 0) { snprintf(str, maxlen - (str - outstr), ";%d:", dsk->partition_num); str = &outstr[strlen(outstr)]; } } snprintf(str, maxlen - (str - outstr), "%s", dsk->name_ptr); } char * config_write_config_kegs_file(int get_status) { FILE *fconf; Disk *dsk; Cfg_defval *defptr; Cfg_menu *menuptr; char *curstr, *defstr; int defval, curval, type, slot, drive; int i; if(get_status) { return 0; } printf("Writing config.kegs file to %s\n", g_config_kegs_name); fconf = fopen(g_config_kegs_name, "w+"); if(fconf == 0) { halt_printf("cannot open %s! Stopping!\n", g_config_kegs_name); return 0; } fprintf(fconf, "# KEGS configuration file version %s\n", g_kegs_version_str); for(i = 0; i < MAX_C7_DISKS + 4; i++) { slot = 7; drive = i - 4; if(i < 4) { slot = (i >> 1) + 5; drive = i & 1; } if(drive == 0) { fprintf(fconf, "\n"); /* an extra blank line */ } dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->name_ptr == 0 && (i > 4)) { /* No disk, not even ejected--just skip */ continue; } fprintf(fconf, "s%dd%d = ", slot, drive + 1); if(dsk->name_ptr == 0) { fprintf(fconf, "\n"); continue; } config_generate_config_kegs_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, dsk, 1); fprintf(fconf, "%s\n", &g_cfg_tmp_path[0]); } fprintf(fconf, "\n"); /* See if any variables are different than their default */ for(i = 0; i < g_cfg_defval_index; i++) { defptr = &(g_cfg_defvals[i]); menuptr = defptr->menuptr; defval = defptr->intval; type = menuptr->cfgtype; if(type == CFGTYPE_INT) { curval = *((int *)menuptr->ptr); if(curval != defval) { fprintf(fconf, "%s = %d\n", menuptr->name_str, curval); } } if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { curstr = *((char **)menuptr->ptr); defstr = *((char **)menuptr->defptr); if(strcmp(curstr, defstr) != 0) { fprintf(fconf, "%s = %s\n", menuptr->name_str, curstr); } } } fprintf(fconf, "\n"); /* write bram state */ clk_write_bram(fconf); fclose(fconf); g_config_kegs_update_needed = 0; return 0; } void insert_disk(int slot, int drive, const char *name, int ejected, const char *partition_name, int part_num, word32 dynamic_blocks) { byte buf_2img[512]; Disk *dsk; char *name_ptr, *part_ptr; dword64 dsize, dunix_pos, exp_dsize, dtmp; word32 len_bits, save_ftrack; int image_type, part_len, ret, locked, len, is_po, name_len; int i; g_config_kegs_update_needed = 1; if((slot < 5) || (slot > 7)) { fatal_printf("Invalid slot for insertiing disk: %d\n", slot); return; } if(drive < 0 || ((slot == 7) && (drive >= MAX_C7_DISKS)) || ((slot < 7) && (drive > 1))) { fatal_printf("Invalid drive for inserting disk: %d\n", drive); return; } dsk = iwm_get_dsk_from_slot_drive(slot, drive); #if 1 printf("Inserting disk %s (%s or %d) in slot %d, drive: %d, " "dyna_blocks:%d\n", name, partition_name, part_num, slot, drive, dynamic_blocks); #endif // DO NOT change dsk->just_ejected. If a disk was just ejected, then // leave it alone. Otherwise, if we are a newly inserted disk, // it should already be 0, so leave it alone //dsk->just_ejected = 1; if(dsk->fd >= 0) { iwm_eject_disk(dsk); } /* Before opening, make sure no other mounted disk has this name */ /* If so, unmount it */ if(!ejected) { for(i = 0; i < 2; i++) { iwm_eject_named_disk(5, i, name, partition_name); iwm_eject_named_disk(6, i, name, partition_name); } for(i = 0; i < MAX_C7_DISKS; i++) { iwm_eject_named_disk(7, i, name, partition_name); } } /* free old name_ptr, partition_name */ free(dsk->name_ptr); free(dsk->partition_name); dsk->name_ptr = 0; dsk->partition_name = 0; name_ptr = kegs_malloc_str(name); dsk->name_ptr = name_ptr; name_len = (int)strlen(dsk->name_ptr); part_len = 0; part_ptr = 0; if(partition_name != 0) { part_ptr = kegs_malloc_str(partition_name); part_len = (int)strlen(part_ptr); dsk->partition_name = part_ptr; } dsk->partition_num = part_num; dsk->dynapro_blocks = dynamic_blocks; iwm_printf("Opening up disk image named: %s\n", name_ptr); if(ejected) { /* just get out of here */ dsk->fd = -1; return; } dsk->fd = -1; dsk->raw_data = 0; dsk->image_type = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->write_prot = 0; dsk->write_through_to_unix = 1; image_type = 0; locked = 0; if(dynamic_blocks) { ret = dynapro_mount(dsk, name_ptr, dynamic_blocks); if(ret < 0) { iwm_eject_disk(dsk); return; } image_type = DSK_TYPE_DYNAPRO; printf("After dynapro_mount, write_through:%d\n", dsk->write_through_to_unix); } if((partition_name != 0) || (part_num >= 0)) { ret = cfg_partition_find_by_name_or_num(dsk, partition_name, part_num); printf("partition %s (num %d) mounted, wr_prot: %d, ret:%d\n", partition_name, part_num, dsk->write_prot, ret); if(ret < 0) { iwm_eject_disk(dsk); return; } locked = dsk->write_prot; if(dsk->raw_data) { // .zip file or something similar. Do name matching on // partition name name_len = part_len; name_ptr = part_ptr; locked = 1; } } if((name_len > 3) && !image_type && !cfgcasecmp(".gz", &name_ptr[name_len - 3])) { // it's gzip'ed, try to gunzip it to dsk->raw_data undeflate_gzip(dsk, name_ptr); locked = 1; dsk->dimage_start = 0; dsk->dimage_size = dsk->raw_dsize; name_len -= 3; // So .dsk, .po look for correct chars } if((name_len > 4) && !image_type && !cfgcasecmp(".bz2", &name_ptr[name_len - 4])) { // it's bzip2'ed, try to bunzip2 it to dsk->raw_data config_file_to_pipe(dsk, "bunzip2", name_ptr); locked = 1; // Reduce name_len by 4 so that subsequent compares for .po // look at the correct chars name_len -= 4; } if((name_len > 4) && !image_type && !cfgcasecmp(&name_ptr[name_len - 4], ".sdk")) { // it's a ShrinkIt archive with a disk image in it unshk(dsk, name_ptr); locked = 1; image_type = DSK_TYPE_PRODOS; printf("dsk->fd:%d dsk->raw_data:%p, raw_dsize:%lld\n", dsk->fd, dsk->raw_data, dsk->raw_dsize); dsk->dimage_start = 0; dsk->dimage_size = dsk->raw_dsize; } if((name_len > 4) && !image_type && dsk->disk_525 && !cfgcasecmp(".nib", &name_ptr[name_len-4])) { // Old, obsolete .nib 5.25" nibblized format. Support is // read-only image_type = DSK_TYPE_NIB; locked = 1; } if((dsk->fd < 0) && !locked && !dynamic_blocks) { dsk->fd = open(name_ptr, O_RDWR | O_BINARY, 0x1b6); } if((dsk->fd < 0) && !dynamic_blocks) { printf("Trying to open %s read-only, errno: %d\n", name_ptr, errno); dsk->fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); locked = 2; } iwm_printf("open returned: %d\n", dsk->fd); if((dsk->fd < 0) && !dynamic_blocks) { fatal_printf("Disk image %s does not exist!\n", name_ptr); free(dsk->raw_data); return; } //printf("Checking if it's woz, name_ptr:%s. %d vs %d\n", name_ptr, // name_len, (int)strlen(name_ptr)); if((name_len > 4) && !image_type && !cfgcasecmp(&name_ptr[name_len - 4], ".woz")) { // it's a WOZ applesauce disk image image_type = DSK_TYPE_WOZ; printf("It is woz!\n"); } if(locked) { dsk->write_prot = locked; } save_ftrack = dsk->cur_frac_track; /* save arm position */ /* See if it is in 2IMG format */ if(dsk->raw_data) { // Just do a copy from raw_data for(i = 0; i < 512; i++) { buf_2img[i] = dsk->raw_data[i]; } dsize = dsk->raw_dsize; if(!dsk->dynapro_info_ptr) { dsk->write_through_to_unix = 0; } } else { ret = (int)read(dsk->fd, (char *)&buf_2img[0], 512); dsize = cfg_get_fd_size(dsk->fd); } #if 0 /* Try to guess that there is a Mac Binary header of 128 bytes */ /* See if image size & 0xfff = 0x080 which indicates extra 128 bytes */ if(((dsize & 0xfff) == 0x080) && (dsize < (801*1024)) && !image_type) { printf("Assuming Mac Binary header on %s\n", dsk->name_ptr); dsk->dimage_start += 0x80; dsize -= 0x80; } #endif dsk->dimage_size = dsize; if(!image_type && (buf_2img[0] == '2') && (buf_2img[1] == 'I') && (buf_2img[2] == 'M') && (buf_2img[3] == 'G')) { /* It's a 2IMG disk */ printf("Image named %s is in 2IMG format\n", dsk->name_ptr); image_type = DSK_TYPE_PRODOS; if(buf_2img[12] == 0) { printf("2IMG is in DOS 3.3 sector order\n"); image_type = DSK_TYPE_DOS33; } if(buf_2img[19] & 0x80) { /* disk is locked */ printf("2IMG is write protected\n"); if(dsk->write_prot == 0) { dsk->write_prot = 1; } } if((buf_2img[17] & 1) && (dsk->image_type == DSK_TYPE_DOS33)) { dsk->vol_num = buf_2img[16]; printf("Setting DOS 3.3 vol num to %d\n", dsk->vol_num); } // Some 2IMG archives have the size byte reversed dsize = (buf_2img[31] << 24) + (buf_2img[30] << 16) + (buf_2img[29] << 8) + buf_2img[28]; dunix_pos = (buf_2img[27] << 24) + (buf_2img[26] << 16) + (buf_2img[25] << 8) + buf_2img[24]; if(dsize == 0x800c00) { // Byte reversed 0x0c8000 dsize = 0x0c8000; } if(dsize == 0) { /* Sweet-16 makes some images with size == 0 */ /* Example: Prosel from */ /* www.whatisthe2gs.apple2.org.za/the_ring/ */ if(buf_2img[12] == 1) { /* then get the size from 0x14 in blocks */ dsize = (buf_2img[23] << 24) + (buf_2img[22] << 16) + (buf_2img[21] << 8) + buf_2img[20]; dsize = dsize * 512; /* it was blocks */ } } dsk->dimage_start = dunix_pos; dsk->dimage_size = dsize; } exp_dsize = 800*1024; dsk->fbit_mult = 256; if(dsk->disk_525) { exp_dsize = 140*1024; dsk->fbit_mult = 128; } if(!image_type) { /* See if it might be the Mac diskcopy format */ dtmp = cfg_detect_dc42(&buf_2img[0], dsize, exp_dsize, slot); if(dtmp != 0) { // It's diskcopy 4.2 printf("Image named %s is in Mac diskcopy format\n", dsk->name_ptr); dsk->dimage_start += 0x54; dsk->dimage_size = dtmp; image_type = DSK_TYPE_PRODOS; /* ProDOS */ } } if(!image_type) { /* Assume raw image */ dsk->dimage_size = dsize; image_type = DSK_TYPE_PRODOS; is_po = (name_len > 3) && !cfgcasecmp(".po", &name_ptr[name_len-3]); if(dsk->disk_525) { image_type = DSK_TYPE_DOS33; if(is_po) { image_type = DSK_TYPE_PRODOS; } } } dsk->image_type = image_type; dsk->disk_dirty = 0; dsk->cur_fbit_pos = 0; dsk->cur_track_bits = 0; dsk->cur_trk_ptr = 0; if(image_type == DSK_TYPE_WOZ) { // Special handling ret = woz_open(dsk, 0); if(!ret) { iwm_eject_disk(dsk); return; } } else if(dsk->smartport) { g_highest_smartport_unit = MY_MAX(dsk->drive, g_highest_smartport_unit); iwm_printf("adding smartport device[%d], img_sz:%08llx\n", dsk->drive, dsk->dimage_size); } else if(dsk->disk_525) { dunix_pos = dsk->dimage_start; dsize = dsk->dimage_size; disk_set_num_tracks(dsk, 4*35); len = 0x1000; if(dsk->image_type == DSK_TYPE_NIB) { // Weird .nib format, has no sync bits len = (int)(dsk->dimage_size / 35); for(i = 0; i < 35; i++) { disk_unix_to_nib(dsk, 4*i, dunix_pos, len, len * 8, 0); dunix_pos += len; } } else { for(i = 0; i < 35; i++) { len_bits = iwm_get_default_track_bits(dsk, 4*i); disk_unix_to_nib(dsk, 4*i, dunix_pos, len, len_bits, 0); dunix_pos += len; } } if(dsize != (dword64)35*len) { fatal_printf("Disk 5.25 error: size is %lld, not %d. " "Will try to mount anyway\n", dsize, 35*len); } } else { /* disk_35 */ dunix_pos = dsk->dimage_start; dsize = dsk->dimage_size; if(dsize != 800*1024) { printf("Disk 3.5 Drive %d (Image File: %s), Error: " "size is %lld, not 800K. Will try to mount " "anyway\n", drive+1, name, dsize); } disk_set_num_tracks(dsk, 2*80); for(i = 0; i < 2*80; i++) { len = g_track_bytes_35[i >> 5]; len_bits = iwm_get_default_track_bits(dsk, i); iwm_printf("Trk: %d.%d = unix: %08llx, %04x, %04x\n", i>>1, i & 1, dunix_pos, len, len_bits); disk_unix_to_nib(dsk, i, dunix_pos, len, len_bits, 0); dunix_pos += len; iwm_printf(" trk_bits:%05x\n", dsk->trks[i].track_bits); } } iwm_move_to_ftrack(dsk, save_ftrack, 0, 0); } dword64 cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot) { dword64 ddata_size, dtag_size; int i; // Detect Mac DiskCopy4.2 disk image (often .dmg or .dc or .dc42) // bptr points to just the first 512 bytes of the file if((bptr[0x52] != 1) || (bptr[0x53] != 0)) { return 0; // No "magic number 0x01, 0x00 for DiskCopy4.2 } if(bptr[0] > 0x3f) { return 0; // not a valid image name length (1-0x3f) } ddata_size = 0; dtag_size = 0; for(i = 0; i < 4; i++) { ddata_size = (ddata_size << 8) | bptr[0x40 + i]; dtag_size = (dtag_size << 8) | bptr[0x44 + i]; } if((dtag_size != 0) && (dtag_size != ((ddata_size >> 9) * 12))) { return 0; // Tags are either 0, or 12 bytes per block } if(slot == 7) { if(ddata_size < 140*1024) { return 0; // Allow any size >=140K slot 7 } } else if(ddata_size != exp_dsize) { return 0; // Must be 140K or 800K } if(ddata_size > (dsize - 0x54)) { return 0; // data_size doesn't make sense } if((ddata_size & 0x1ff) || ((ddata_size >> 31) > 1)) { return 0; // data_size not a multiple of 512 bytes } if((ddata_size + dtag_size + 0x54) != dsize) { return 0; // File doesn't appear to be well-formed } return ddata_size; } dword64 cfg_get_fd_size(int fd) { struct stat stat_buf; int ret; ret = fstat(fd, &stat_buf); if(ret != 0) { fprintf(stderr,"fstat returned %d on fd %d, errno: %d\n", ret, fd, errno); stat_buf.st_size = 0; } return stat_buf.st_size; } dword64 cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) { dword64 dret, doff; word32 this_len; dret = kegs_lseek(fd, dpos, SEEK_SET); if(dret != dpos) { printf("lseek failed: %lld\n", dret); return 0; } doff = 0; while(1) { if(doff >= dsize) { break; } this_len = 1UL << 30; if((dsize - doff) < this_len) { this_len = (word32)(dsize - doff); } dret = read(fd, bufptr + doff, this_len); if((dret + 1) == 0) { // dret==-1 printf("read failed\n"); return 0; } if(dret == 0) { printf("Unexpected end of file, tried to read from " "dpos:%lld dsize:%lld\n", dpos, dsize); return 0; } doff += dret; } return doff; } dword64 cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) { dword64 dret; dret = kegs_lseek(fd, dpos, SEEK_SET); if(dret != dpos) { printf("lseek failed: %lld\n", dret); return 0; } return must_write(fd, bufptr, dsize); } int cfg_partition_maybe_add_dotdot() { int part_len; part_len = (int)strlen(&(g_cfg_part_path[0])); if(part_len > 0) { // Add .. entry here cfg_file_add_dirent(&g_cfg_partitionlist, "..", 1, 0, 0, 0, 0); } return part_len; } int cfg_partition_name_check(const byte *name_ptr, int name_len) { int part_len; int i; // Return 0 if name_ptr is not at the right path depth, 1 if OK part_len = (int)strlen(&g_cfg_part_path[0]); for(i = 0; i < part_len; i++) { if(i >= name_len) { return 0; } if(name_ptr[i] != g_cfg_part_path[i]) { return 0; } } return 1; } int cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size) { if(!cfg_read_from_fd(fd, buf, blk * blk_size, blk_size)) { // Read failed return 0; } return blk_size; } int cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, int part_num) { Cfg_dirent *direntptr; const char *partnamestr; int match, num_parts, ret, fd, len, c; int i; #if 0 printf("cfg_partition_find_by_name_or_num: %s, part_num:%d\n", partnamestr, part_num); #endif // We need to copy partnamestr up to the last / to g_cfg_part_path[], // and use just the end as the partition name. cfg_strncpy(&g_cfg_part_path[0], in_partnamestr, CFG_PATH_MAX); len = (int)strlen(in_partnamestr); partnamestr = in_partnamestr; for(i = len - 1; i >= 0; i--) { c = g_cfg_part_path[i]; if(c == '/') { partnamestr = &(in_partnamestr[i+1]); break; } g_cfg_part_path[i] = 0; } fd = open(dsk->name_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { fatal_printf("Cannot open disk image: %s\n", dsk->name_ptr); return -1; } num_parts = cfg_partition_make_list(fd); if(num_parts <= 0) { printf("num_parts: %d\n", num_parts); close(fd); return -1; } for(i = 0; i < g_cfg_partitionlist.last; i++) { direntptr = &(g_cfg_partitionlist.direntptr[i]); match = 0; if((strcmp(partnamestr, direntptr->name) == 0) && (part_num < 0)) { //printf("partition, match1, name:%s %s, part_num:%d\n", // partnamestr, direntptr->name, part_num); match = 1; } if((partnamestr == 0) && (direntptr->part_num == part_num)) { //printf("partition, match2, n:%s, part_num:%d == %d\n", // direntptr->name, direntptr->part_num, part_num); match = 1; } if(match) { //printf("match with dimage_start:%08llx, dimage_size:" // "%08llx\n", dsk->dimage_start, // dsk->dimage_size); printf("match with dimage_start:%08llx, dimage_size:" "%08llx\n", direntptr->dimage_start, direntptr->dsize); ret = i; if(g_cfg_partition_is_zip) { ret = undeflate_zipfile(dsk, fd, direntptr->dimage_start, direntptr->dsize, direntptr->compr_dsize); close(fd); } else { dsk->fd = fd; dsk->dimage_start = direntptr->dimage_start; dsk->dimage_size = direntptr->dsize; } return ret; } } close(fd); // printf("No matches, ret -1\n"); return -1; } int cfg_partition_make_list_from_name(const char *namestr) { int fd, ret; fd = open(namestr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { fatal_printf("Cannot open part image: %s\n", namestr); return 0; } ret = cfg_partition_make_list(fd); close(fd); return ret; } int cfg_partition_make_list(int fd) { Driver_desc *driver_desc_ptr; Part_map *part_map_ptr; word32 *blk_bufptr; dword64 dimage_start, dimage_size, dsize; word32 start, len, data_off, data_len, sig, map_blk_cnt, cur_blk; word32 map_blks, block_size; int is_dir, ret; block_size = 512; g_cfg_partition_is_zip = 0; cfg_free_alldirents(&g_cfg_partitionlist); blk_bufptr = (word32 *)malloc(MAX_PARTITION_BLK_SIZE); cfg_partition_read_block(fd, blk_bufptr, 0, block_size); driver_desc_ptr = (Driver_desc *)blk_bufptr; sig = cfg_get_be_word16(&(driver_desc_ptr->sig)); block_size = cfg_get_be_word16(&(driver_desc_ptr->blk_size)); if(block_size == 0) { block_size = 512; } if((sig != 0x4552) || (block_size < 0x200) || (block_size > MAX_PARTITION_BLK_SIZE)) { printf("Partition error: No driver descriptor map found\n"); free(blk_bufptr); ret = undeflate_zipfile_make_list(fd); if(ret > 0) { g_cfg_partition_is_zip = 1; } return ret; } map_blks = 1; cur_blk = 0; dsize = cfg_get_fd_size(fd); cfg_file_add_dirent(&g_cfg_partitionlist, "None - Whole image", is_dir=0, dsize, 0, 0, -1); while(cur_blk < map_blks) { cur_blk++; cfg_partition_read_block(fd, blk_bufptr, cur_blk, block_size); part_map_ptr = (Part_map *)blk_bufptr; sig = cfg_get_be_word16(&(part_map_ptr->sig)); map_blk_cnt = cfg_get_be_word32(&(part_map_ptr->map_blk_cnt)); if(cur_blk <= 1) { map_blks = MY_MIN(20, map_blk_cnt); } if(sig != 0x504d) { printf("Partition entry %d bad signature:%04x\n", cur_blk, sig); free(blk_bufptr); return g_cfg_partitionlist.last; } /* found it, check for consistency */ start = cfg_get_be_word32(&(part_map_ptr->phys_part_start)); len = cfg_get_be_word32(&(part_map_ptr->part_blk_cnt)); data_off = cfg_get_be_word32(&(part_map_ptr->data_start)); data_len = cfg_get_be_word32(&(part_map_ptr->data_cnt)); if(data_off + data_len > len) { printf("Poorly formed entry\n"); continue; } if(data_len < 10 || start < 1) { printf("Poorly formed entry %d, datalen:%d, " "start:%08x\n", cur_blk, data_len, start); continue; } dimage_size = (dword64)data_len * block_size; dimage_start = ((dword64)start + data_off) * block_size; is_dir = 2*(dimage_size < 800*1024); #if 0 printf(" partition add entry %d = %s %d %08llx %08llx\n", cur_blk, part_map_ptr->part_name, is_dir, dimage_size, dimage_start); #endif cfg_file_add_dirent(&g_cfg_partitionlist, part_map_ptr->part_name, is_dir, dimage_size, dimage_start, 0, cur_blk); } free(blk_bufptr); return g_cfg_partitionlist.last; } int cfg_maybe_insert_disk(int slot, int drive, const char *namestr) { int num_parts; g_cfg_part_path[0] = 0; num_parts = cfg_partition_make_list_from_name(namestr); if(num_parts > 0) { printf("Choose a partition\n"); g_cfg_select_partition = 1; g_cfg_file_pathfield = 0; } else { insert_disk(slot, drive, namestr, 0, 0, -1, 0); return 1; } return 0; } void cfg_insert_disk_dynapro(int slot, int drive, const char *name) { int dynapro_blocks; dynapro_blocks = 280; if(slot == 5) { dynapro_blocks = 1600; } else if(slot == 7) { dynapro_blocks = 65535; } if(g_cfg_newdisk_select && (g_cfg_newdisk_type == 3) && g_cfg_newdisk_blocks) { dynapro_blocks = g_cfg_newdisk_blocks; } insert_disk(slot, drive, name, 0, 0, -1, dynapro_blocks); } int cfg_stat(char *path, struct stat *sb, int do_lstat) { int ret, len, removed_slash; removed_slash = 0; len = 0; /* Windows doesn't like to stat paths ending in a /, so remove it */ len = (int)strlen(path); #ifdef _WIN32 if((len > 1) && (path[len - 1] == '/') ) { path[len - 1] = 0; /* remove the slash */ removed_slash = 1; } #endif if(do_lstat) { ret = lstat(path, sb); } else { ret = stat(path, sb); } /* put the slash back */ if(removed_slash) { path[len - 1] = '/'; } return ret; } word32 cfg_get_le16(byte *bptr) { return bptr[0] | (bptr[1] << 8); } word32 cfg_get_le32(byte *bptr) { return bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | (bptr[3] << 24); } dword64 cfg_get_le64(byte *bptr) { dword64 dval; int i; dval = 0; for(i = 7; i >= 0; i--) { dval = (dval << 8) | bptr[i]; } return dval; } word32 cfg_get_be_word16(word16 *ptr) { byte *bptr; bptr = (byte *)ptr; return (bptr[0] << 8) | bptr[1]; } word32 cfg_get_be_word32(word32 *ptr) { byte *bptr; bptr = (byte *)ptr; return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; } void cfg_set_le32(byte *bptr, word32 val) { *bptr++ = val; *bptr++ = val >> 8; *bptr++ = val >> 16; *bptr++ = val >> 24; } void config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr) { #ifdef _WIN32 printf("Cannot do pipe from cmd %s to %s\n", cmd_ptr, name_ptr); return; #else int output_pipe[2]; byte *bptr2; char *bufptr; pid_t pid; int stat_loc, ret, bufsize, pos, fd; int i; // Create a pipe to cmd_ptr, and send the contents of name_ptr to it // Collect the output in a 32MB buffer. Once complete, allocate // a buffer of the correct size, copy to it, and free the giant buffer // Sample usage: "gunzip", "{filename}.gz" will run gunzip and collect // uncompressed data ret = pipe(&output_pipe[0]); if(ret < 0) { return; } printf("output_pipe[0]=%d, [1]=%d\n", output_pipe[0], output_pipe[1]); bufsize = 32*1024*1024; bufptr = malloc(bufsize); if(bufptr == 0) { return; } pos = 0; pid = fork(); if(pid == 0) { close(output_pipe[0]); ret = dup2(output_pipe[1], 1); if(ret < 0) { exit(1); } // The child. Open 0 as the file, and then do system close(0); fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd != 0) { exit(1); } // Now just run the command. Input is from name_ptr, output is // to a pipe (void)!system(cmd_ptr); exit(0); } else if(pid > 0) { // Parent. Collect output from output_pipe[0], and write it // to bufptr. close(output_pipe[1]); while(1) { if(pos >= bufsize) { break; } ret = read(output_pipe[0], bufptr + pos, bufsize - pos); if(ret <= 0) { break; } pos += ret; } close(output_pipe[0]); waitpid(pid, &stat_loc, 0); } else { // Error case close(output_pipe[1]); close(output_pipe[0]); } // See what we got bptr2 = 0; printf("Read %d bytes from %s\n", pos, name_ptr); if(pos >= 140*1024) { // Looks like it could be an image bptr2 = malloc(pos); for(i = 0; i < pos; i++) { bptr2[i] = bufptr[i]; } dsk->raw_data = bptr2; dsk->fd = 0; // Indicates raw_data is valid dsk->raw_dsize = pos; } free(bufptr); #endif } void cfg_htab_vtab(int x, int y) { if(x > 79) { x = 0; } if(y > 23) { y = 0; } g_cfg_curs_x = x; g_cfg_curs_y = y; g_cfg_curs_inv = 0; g_cfg_curs_mousetext = 0; } void cfg_home() { int i; cfg_htab_vtab(0, 0); for(i = 0; i < 24; i++) { cfg_cleol(); } } void cfg_cleol() { g_cfg_curs_inv = 0; g_cfg_curs_mousetext = 0; cfg_putchar(' '); while(g_cfg_curs_x != 0) { cfg_putchar(' '); } } void cfg_putchar(int c) { int x, y; if(c == '\n') { cfg_cleol(); return; } if(c == '\b') { g_cfg_curs_inv = !g_cfg_curs_inv; return; } if(c == '\t') { g_cfg_curs_mousetext = !g_cfg_curs_mousetext; return; } y = g_cfg_curs_y; x = g_cfg_curs_x; // Normal: 0xa0-0xff for space through lowercase // Inverse: 0x00-0x3f for upper case and numbers, 0x60-0x7f for lcase // Mousetext: 0x40-0x5f if(g_cfg_curs_inv) { if(c >= 0x40 && c < 0x60) { c = c & 0x1f; } } else { c = c | 0x80; } if(g_cfg_curs_mousetext) { c = (c & 0x1f) | 0x40; } g_cfg_screen[y][x] = c; x++; if(x >= 80) { x = 0; y++; if(y >= 24) { y = 0; } } g_cfg_curs_y = y; g_cfg_curs_x = x; g_cfg_screen_changed = 1; } void cfg_printf(const char *fmt, ...) { va_list ap; int c; int i; va_start(ap, fmt); (void)vsnprintf(g_cfg_printf_buf, CFG_PRINTF_BUFSIZE, fmt, ap); va_end(ap); for(i = 0; i < CFG_PRINTF_BUFSIZE; i++) { c = g_cfg_printf_buf[i]; if(c == 0) { return; } cfg_putchar(c); } } void cfg_print_dnum(dword64 dnum, int max_len) { char buf[64]; char buf2[64]; int len, cnt, c; int i, j; /* Prints right-adjusted "num" in field "max_len" wide */ snprintf(&buf[0], 64, "%lld", dnum); len = (int)strlen(buf); for(i = 0; i < 64; i++) { buf2[i] = ' '; } j = max_len + 1; buf2[j] = 0; j--; cnt = 0; for(i = len - 1; (i >= 0) && (j >= 1); i--) { c = buf[i]; if(c >= '0' && c <= '9') { if(cnt >= 3) { buf2[j--] = ','; cnt = 0; } cnt++; } buf2[j--] = c; } cfg_printf(&buf2[1]); } int cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras) { Disk *dsk; int slot, drive; slot = type_ext >> 8; drive = type_ext & 0xff; dsk = iwm_get_dsk_from_slot_drive(slot, drive); outstr[0] = 0; if(dsk->name_ptr == 0) { return 0; } config_generate_config_kegs_name(outstr, maxlen, dsk, with_extras); return dsk->dynapro_blocks; } int cfg_get_disk_locked(int type_ext) { Disk *dsk; int slot, drive; slot = type_ext >> 8; drive = type_ext & 0xff; dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->fd < 0) { return 0; } if(dsk->write_prot) { return 1; } else if(!dsk->write_through_to_unix) { return 2; } return 0; } void cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change) { char valbuf[CFG_OPT_MAXSTR]; char *(*fn_ptr)(int); int *iptr; char **str_ptr; const char *menustr; char *curstr, *defstr, *str, *outstr; void *edit_ptr; int val, num_opts, opt_num, bufpos, outpos, curval, defval, type; int type_ext, opt_get_str, separator, len, c, locked; int i; // For this menu_pos line, create output in g_cfg_opt_buf[] string // Highlight it if menu_pos==highlight_pos // Allow arrows to modify the currently selected item of a list using // change: -1 moves to a previous item, +1 moves to the next g_cfg_opt_buf[0] = 0; num_opts = 0; opt_get_str = 0; separator = ','; menuptr += menu_pos; /* move forward to entry menu_pos */ menustr = menuptr->str; type = menuptr->cfgtype; type_ext = (type >> 4); type = type & 0xf; len = (int)strlen(menustr) + 1; bufpos = 0; outstr = &(g_cfg_opt_buf[0]); outstr[bufpos++] = ' '; // 0 outstr[bufpos++] = ' '; // 1 outstr[bufpos++] = '\t'; // 2 outstr[bufpos++] = '\t'; // 3 outstr[bufpos++] = ' '; // 4 outstr[bufpos++] = ' '; // 5 // Figure out if we should get a checkmark curval = -1; defval = -1; curstr = 0; if(type == CFGTYPE_INT) { iptr = menuptr->ptr; curval = *iptr; iptr = menuptr->defptr; if(!iptr) { printf("BAD MENU, defptr is 0!\n"); } else { defval = *iptr; } if(curval == defval) { g_cfg_opt_buf[3] = 'D'; /* checkmark */ g_cfg_opt_buf[4] = '\t'; } } if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { str_ptr = (char **)menuptr->ptr; curstr = *str_ptr; str_ptr = (char **)menuptr->defptr; if(!str_ptr) { printf("BAD MENU, defptr str is 0!\n"); defstr = ""; } else { defstr = *str_ptr; } if(strcmp(curstr, defstr) == 0) { g_cfg_opt_buf[3] = 'D'; /* checkmark */ g_cfg_opt_buf[4] = '\t'; } } // If it's a menu, give it a special menu indicator if(type == CFGTYPE_MENU) { g_cfg_opt_buf[1] = '\t'; g_cfg_opt_buf[2] = 'M'; /* return-like symbol */ g_cfg_opt_buf[3] = '\t'; g_cfg_opt_buf[4] = ' '; } if(type == CFGTYPE_DISK) { locked = cfg_get_disk_locked(type_ext); if(locked) { g_cfg_opt_buf[4] = '*'; if(locked == 2) { // inverse g_cfg_opt_buf[2] = '\b'; g_cfg_opt_buf[3] = '*'; g_cfg_opt_buf[4] = '\b'; } } } if(menu_pos == highlight_pos) { outstr[bufpos++] = '\b'; } opt_get_str = 2; i = -1; outpos = bufpos; #if 0 printf("cfg menu_pos: %d str len: %d\n", menu_pos, len); #endif while(++i < len) { c = menustr[i]; if(c == separator) { // ','? if(i == 0) { continue; } c = 0; } outstr[outpos++] = c; outstr[outpos] = 0; if(outpos >= CFG_OPT_MAXSTR) { fprintf(stderr, "CFG_OPT_MAXSTR exceeded\n"); my_exit(1); return; } if(c == 0) { if(opt_get_str == 2) { outstr = &(valbuf[0]); bufpos = outpos - 1; opt_get_str = 0; } else if(opt_get_str) { #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d opt %d = %s=%d\n", menu_pos, num_opts, g_cfg_opts_str[0], g_cfg_opts_vals[num_opts]); } #endif num_opts++; outstr = &(valbuf[0]); opt_get_str = 0; if(num_opts >= CFG_MAX_OPTS) { fprintf(stderr, "CFG_MAX_OPTS oflow\n"); my_exit(1); return; } } else { val = (word32)strtoul(valbuf, 0, 0); g_cfg_opts_vals[num_opts] = val; outstr = &(g_cfg_opts_str[0]); if(val != curval) { outstr = valbuf; } opt_get_str = 1; } outpos = 0; outstr[0] = 0; } } if(menu_pos == highlight_pos) { g_cfg_opt_buf[bufpos++] = '\b'; } g_cfg_opt_buf[bufpos] = 0; // Decide what to display on the "right" side str = 0; opt_num = -1; if((type == CFGTYPE_INT) || (type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos++] = '='; g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos] = 0; for(i = 0; i < num_opts; i++) { if(curval == g_cfg_opts_vals[i]) { opt_num = i; break; } } } if(change != 0) { if(type == CFGTYPE_INT) { if(num_opts > 0) { opt_num += change; if(opt_num >= num_opts) { opt_num = 0; } if(opt_num < 0) { opt_num = num_opts - 1; } curval = g_cfg_opts_vals[opt_num]; } else { curval += change; /* HACK: min_val, max_val testing here */ } iptr = (int *)menuptr->ptr; cfg_int_update(iptr, curval); } g_config_kegs_update_needed = 1; } #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d opt_num %d\n", menu_pos, opt_num); } #endif edit_ptr = g_cfg_edit_ptr; if((edit_ptr == menuptr->ptr) && edit_ptr) { // Just show the current edit string str = cfg_shorten_filename(&(g_cfg_edit_buf[0]), 68 - 1); cfg_strlcat(str, "\b \b", CFG_PATH_MAX); } else if(opt_num >= 0) { str = &(g_cfg_opts_str[0]); } else { if(type == CFGTYPE_INT) { str = &(g_cfg_opts_str[0]); snprintf(str, CFG_OPT_MAXSTR, "%d", curval); } else if (type == CFGTYPE_DISK) { str = &(g_cfg_opts_str[0]); (void)cfg_get_disk_name(str, CFG_PATH_MAX, type_ext, 1); str = cfg_shorten_filename(str, 68); } else if ((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { str = cfg_shorten_filename(curstr, 68); } else if(type == CFGTYPE_FUNC) { fn_ptr = (char *(*)(int))menuptr->ptr; str = ""; curstr = (*fn_ptr)(1); if(curstr) { g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos++] = ':'; g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos] = 0; str = cfg_shorten_filename(curstr, 68); free(curstr); } } else { str = ""; } } #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d buf_pos %d, str is %s, %02x, %02x, " "%02x %02x\n", menu_pos, bufpos, str, g_cfg_opt_buf[bufpos-1], g_cfg_opt_buf[bufpos-2], g_cfg_opt_buf[bufpos-3], g_cfg_opt_buf[bufpos-4]); } #endif g_cfg_opt_buf[bufpos] = 0; cfg_strncpy(&(g_cfg_opt_buf[bufpos]), str, CFG_OPT_MAXSTR - bufpos - 1); g_cfg_opt_buf[CFG_OPT_MAXSTR-1] = 0; } void cfg_get_base_path(char *pathptr, const char *inptr, int go_up) { const char *tmpptr; char *slashptr; char *outptr; int add_dotdot, is_dotdot; int len; int c; // Take full filename, copy it to pathptr, and truncate at last slash // inptr and pathptr can be the same. // If go_up is set, then replace a blank dir with ".." // but first, see if path is currently just ../ over and over // if so, just tack .. onto the end and return //printf("cfg_get_base start with %s\n", inptr); g_cfg_file_match[0] = 0; tmpptr = inptr; is_dotdot = 1; while(1) { if(tmpptr[0] == 0) { break; } if((tmpptr[0] == '.') && (tmpptr[1] == '.') && (tmpptr[2] == '/')) { tmpptr += 3; } else { is_dotdot = 0; break; } } slashptr = 0; outptr = pathptr; c = -1; while(c != 0) { c = *inptr++; if(c == '/') { if(*inptr != 0) { /* if not a trailing slash... */ slashptr = outptr; } } *outptr++ = c; } if(!go_up) { /* if not go_up, copy chopped part to g_cfg_file_match*/ /* copy from slashptr+1 to end */ tmpptr = slashptr+1; if(slashptr == 0) { tmpptr = pathptr; } cfg_strncpy(&g_cfg_file_match[0], tmpptr, CFG_PATH_MAX); /* remove trailing / from g_cfg_file_match */ len = (int)strlen(&g_cfg_file_match[0]); if((len > 1) && (len < (CFG_PATH_MAX - 1)) && g_cfg_file_match[len - 1] == '/') { g_cfg_file_match[len - 1] = 0; } //printf("set g_cfg_file_match to %s\n", &g_cfg_file_match[0]); } if(!is_dotdot && (slashptr != 0)) { slashptr[0] = '/'; slashptr[1] = 0; outptr = slashptr + 2; } add_dotdot = 0; if(pathptr[0] == 0 || is_dotdot) { /* path was blank, or consisted of just ../ pattern */ if(go_up) { add_dotdot = 1; } } else if(slashptr == 0) { /* no slashes found, but path was not blank--make it blank */ if(pathptr[0] == '/') { pathptr[1] = 0; } else { pathptr[0] = 0; } } if(add_dotdot) { --outptr; outptr[0] = '.'; outptr[1] = '.'; outptr[2] = '/'; outptr[3] = 0; } //printf("cfg_get_base end with %s, is_dotdot:%d, add_dotdot:%d\n", // pathptr, is_dotdot, add_dotdot); } char * cfg_name_new_image(int get_status) { // Called from menu to create a new disk image, this will pop up the // file selection dialog. Once name is selected, // cfg_create_new_image() is called. if(get_status) { return 0; } printf("cfg_name_new_image called!\n"); g_cfg_slotdrive = g_cfg_newdisk_slotdrive; g_cfg_newdisk_select = 1; cfg_file_init(); return 0; } void cfg_dup_existing_image(word32 slotdrive) { // Set g_cfg_newdisk_* to copy the slotdrive image g_cfg_slotdrive = slotdrive; g_cfg_newdisk_select = 2; // Do DUP cfg_file_init(); } void cfg_dup_image_selected() { Disk *dsk; Woz_info *wozinfo_ptr; byte *bufptr; char *str; dword64 dsize, dret; word32 slotdrive; int fd; // printf("cfg_dup_image_selected\n"); slotdrive = g_cfg_slotdrive; g_cfg_slotdrive = 0; g_menuptr = &g_cfg_disk_menu[0]; g_menu_redraw_needed = 1; g_cfg_newdisk_select = 0; g_cfg_newdisk_slotdrive = 0; printf("slotdrive:%04x\n", slotdrive); if(slotdrive < 0x500) { return; // Invalid slot,drive: Do nothing } dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); if(dsk->fd < 0) { return; // No disk } dsize = dsk->dimage_size; str = &g_cfg_file_path[0], printf("Create dup image %s, dsize:%lld\n", str, dsize); if((word32)dsize != dsize) { return; } fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); if(fd < 0) { printf("Open %s failed, errno:%d\n", str, errno); return; } wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr && !dsk->write_through_to_unix) { // Just write out the WOZ image, and then fully enable this // image printf("Writing out .WOZ image to %s, size:%d\n", str, wozinfo_ptr->woz_size); if((dsk->raw_data == 0) && (dsk->fd >= 0)) { close(dsk->fd); } dsk->fd = fd; woz_rewrite_crc(dsk, wozinfo_ptr->woz_size); // Above will recalc CRC and write out woz_size bytes dsk->raw_data = 0; dsk->write_through_to_unix = 1; free(dsk->name_ptr); dsk->name_ptr = kegs_malloc_str(str); free(dsk->partition_name); dsk->partition_name = 0; dsk->image_type = DSK_TYPE_WOZ; dsk->dimage_size = wozinfo_ptr->woz_size; dsk->dimage_start = 0; g_config_kegs_update_needed = 1; woz_check_file(dsk); } else if(dsk->raw_data) { cfg_write_to_fd(fd, dsk->raw_data, 0, dsize); close(fd); } else { bufptr = malloc((size_t)dsize); if((bufptr != 0) && ((size_t)dsize == dsize)) { dret = cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); if(dret == dsize) { cfg_write_to_fd(fd, bufptr, 0, dsize); } } free(bufptr); close(fd); } } void cfg_validate_image(word32 slotdrive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); dynapro_validate_any_image(dsk); } void cfg_toggle_lock_disk(word32 slotdrive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); iwm_toggle_lock(dsk); } int cfg_create_new_image_act(const char *str, int type, int size_blocks) { byte buf[512]; dword64 dret; int fd, ret; int i; // Called after file dialog selects a new image name, this creates it if(size_blocks == 65536) { size_blocks--; // Shrink to 65535 total blocks } printf("Create new image type:%d, size:%dKB\n", type, size_blocks/2); fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); if(fd < 0) { printf("Open %s failed, errno:%d\n", str, errno); return fd; } for(i = 0; i < 512; i++) { buf[i] = 0; } ret = 0; if(type == 2) { // WOZ (void)woz_new(fd, str, size_blocks/2); } else { for(i = 0; i < size_blocks; i++) { dret = cfg_write_to_fd(fd, &(buf[0]), i * 512U, 512); if(dret != 512) { ret = -1; break; } } } close(fd); return ret; // 0=success, -1 is a failure } void cfg_create_new_image() { word32 dynamic_blocks; int ret; // Type is in g_cfg_file_path. Create this file and prepare it printf("Creating new image: %s\n", &g_cfg_file_path[0]); if(g_cfg_newdisk_select == 2) { cfg_dup_image_selected(); return; } ret = 0; dynamic_blocks = 0; if(g_cfg_newdisk_type == 3) { dynamic_blocks = g_cfg_newdisk_blocks; } else { ret = cfg_create_new_image_act(&g_cfg_file_path[0], g_cfg_newdisk_type, g_cfg_newdisk_blocks); } if(ret < 0) { // Maybe open a dialog? Oh well...do nothing } else { insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &(g_cfg_file_path[0]), 0, 0, -2, dynamic_blocks); } g_cfg_slotdrive = 0; g_menuptr = &g_cfg_disk_menu[0]; g_menu_redraw_needed = 1; g_cfg_newdisk_select = 0; g_cfg_newdisk_slotdrive = 0; } void cfg_file_init() { int slot, drive, is_dynapro; int i; is_dynapro = 0; if((g_cfg_slotdrive & 0xfff) == 0xfff) { // File selection // Just use g_cfg_file_def_name cfg_strncpy(&g_cfg_tmp_path[0], g_cfg_file_def_name, CFG_PATH_MAX); } else { is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, g_cfg_slotdrive, 0); slot = (g_cfg_slotdrive >> 8) & 7; drive = g_cfg_slotdrive & 1; for(i = 0; i < 6; i++) { if(g_cfg_tmp_path[0] != 0) { break; } /* try to get a starting path from some mounted drive */ drive = !drive; if(i & 1) { slot++; if(slot >= 8) { slot = 5; } } is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, (slot << 8) + drive, 0); } } cfg_get_base_path(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], 0); if(is_dynapro) { // Use the full path to the dir (don't strip off last part) cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], CFG_PATH_MAX); } g_cfg_dirlist.invalid = 1; } void cfg_free_alldirents(Cfg_listhdr *listhdrptr) { int i; if(listhdrptr->max > 0) { // Free the old directory listing for(i = 0; i < listhdrptr->last; i++) { free(listhdrptr->direntptr[i].name); } free(listhdrptr->direntptr); } listhdrptr->direntptr = 0; listhdrptr->last = 0; listhdrptr->max = 0; listhdrptr->invalid = 0; listhdrptr->topent = 0; listhdrptr->curent = 0; } void cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num) { Cfg_dirent *direntptr; int num, namelen, this_len; int i; // Loop through all entries, make sure name is unique num = listhdrptr->last; namelen = (int)strlen(nameptr); for(i = 0; i < num; i++) { direntptr = &(listhdrptr->direntptr[i]); this_len = (int)strlen(direntptr->name); if(cfg_str_match(direntptr->name, nameptr, namelen) == 0) { // It's a match...check len if(namelen == this_len) { return; } } } cfg_file_add_dirent(listhdrptr, nameptr, is_dir, dsize, dimage_start, compr_dsize, part_num); } void cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num) { Cfg_dirent *direntptr; char *ptr; int inc_amt, namelen; namelen = (int)strlen(nameptr); if(listhdrptr->last >= listhdrptr->max) { // realloc inc_amt = MY_MAX(64, listhdrptr->max); inc_amt = MY_MIN(inc_amt, 1024); listhdrptr->max += inc_amt; listhdrptr->direntptr = realloc(listhdrptr->direntptr, listhdrptr->max * sizeof(Cfg_dirent)); } ptr = malloc(namelen+1+is_dir); cfg_strncpy(ptr, nameptr, namelen+1); if(is_dir && (namelen >= 1) && (ptr[namelen - 1] != '/')) { // Add a trailing '/' to directories, unless already there cfg_strlcat(ptr, "/", namelen + 1 + is_dir); } #if 0 printf("...file entry %d is %s\n", listhdrptr->last, ptr); #endif direntptr = &(listhdrptr->direntptr[listhdrptr->last]); direntptr->name = ptr; direntptr->is_dir = is_dir; direntptr->dsize = dsize; direntptr->dimage_start = dimage_start; direntptr->compr_dsize = compr_dsize; direntptr->part_num = part_num; listhdrptr->last++; } int cfg_dirent_sortfn(const void *obj1, const void *obj2) { const Cfg_dirent *direntptr1, *direntptr2; int ret; /* Called by qsort to sort directory listings */ direntptr1 = (const Cfg_dirent *)obj1; direntptr2 = (const Cfg_dirent *)obj2; ret = cfg_str_match(direntptr1->name, direntptr2->name, CFG_PATH_MAX); return ret; } int cfg_str_match(const char *str1, const char *str2, int len) { return cfg_str_match_maybecase(str1, str2, len, g_cfg_ignorecase); } int cfg_str_match_maybecase(const char *str1, const char *str2, int len, int ignorecase) { const byte *bptr1, *bptr2; int c, c2; int i; /* basically, work like strcmp or strcasecmp */ bptr1 = (const byte *)str1; bptr2 = (const byte *)str2; for(i = 0; i < len; i++) { c = *bptr1++; c2 = *bptr2++; if(ignorecase) { c = tolower(c); c2 = tolower(c2); } if((c == 0) || (c2 == 0) || (c != c2)) { return c - c2; } } return 0; } int cfgcasecmp(const char *str1, const char *str2) { return cfg_str_match_maybecase(str1, str2, 32767, 1); } int cfg_strlcat(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int destlen, srclen, ret, c; // Concat srcptr to the end of dstptr, ensuring a null within dstsize // Return the total buffer size that would be needed, even if dstsize // is too small. Compat with strlcat() destlen = (int)strlen(dstptr); srclen = (int)strlen(srcptr); ret = destlen + srclen; dstsize--; if(destlen >= dstsize) { return ret; // Do nothing, buf too small } ptr = dstptr + destlen; while(destlen < dstsize) { c = *srcptr++; *ptr++ = c; if(c == 0) { return ret; } destlen++; } dstptr[dstsize] = 0; return ret; } char * cfg_strncpy(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int c; // Copy srcptr to dstptr, ensuring there is room for a null // Compatible with strncpy()--except dstptr is ALWAYS null terminated ptr = dstptr; while(--dstsize > 0) { c = *srcptr++; *ptr++ = c; if(c == 0) { return dstptr; } } *ptr = 0; return dstptr; } const char * cfg_str_basename(const char *str) { int len; int i; // If str is /aa/bb/cc, this routine returns cc len = (int)strlen(str); while(len && (str[len - 1] == '/')) { len--; // Ignore trailing '/' if there are any } for(i = len - 1; i > 0; i--) { if(str[i] == '/') { return str + i + 1; } } return str; } char * cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int c; // If srcptr is /aa/bb/cc, this routine returns /aa/bb/ // Copy srcptr to dstptr, ensuring there is room for a null // Compatible with strncpy()--except dstptr is ALWAYS null terminated ptr = dstptr; while(--dstsize > 0) { c = *srcptr++; *ptr++ = c; if(c == 0) { // Remove any trailing /'s ptr--; while((ptr > dstptr) && (ptr[0] == '/')) { ptr[0] = 0; ptr--; } while(ptr > dstptr) { if(ptr[0] == '/') { ptr[1] = 0; break; } ptr--; } return dstptr; } } *ptr = 0; return dstptr; } void cfg_file_readdir(const char *pathptr) { struct dirent *direntptr; struct stat stat_buf; DIR *dirptr; mode_t fmt; char *str; const char *tmppathptr; int size, ret, is_dir, is_gz, len; int i; if(!strncmp(pathptr, &g_cfg_file_cachedpath[0], CFG_PATH_MAX) && (g_cfg_dirlist.last > 0) && (g_cfg_dirlist.invalid==0)){ return; } // No match, must read new directory // Free all dirents that were cached previously cfg_free_alldirents(&g_cfg_dirlist); cfg_strncpy(&g_cfg_file_cachedpath[0], pathptr, CFG_PATH_MAX); cfg_strncpy(&g_cfg_file_cachedreal[0], pathptr, CFG_PATH_MAX); str = &g_cfg_file_cachedreal[0]; for(i = 0; i < 200; i++) { len = (int)strlen(str); if(len <= 0) { break; } else if(len < CFG_PATH_MAX-2) { if(str[len-1] != '/') { // append / to make various routines work str[len] = '/'; str[len+1] = 0; } } ret = cfg_stat(str, &stat_buf, 0); is_dir = 0; if(ret == 0) { fmt = stat_buf.st_mode & S_IFMT; if(fmt == S_IFDIR) { /* it's a directory */ is_dir = 1; } } if(is_dir) { break; } else { // user is entering more path, use base for display cfg_get_base_path(str, str, 0); } } tmppathptr = str; if(str[0] == 0) { tmppathptr = "."; } cfg_file_add_dirent(&g_cfg_dirlist, "..", 1, 0, 0, 0, -1); dirptr = opendir(tmppathptr); if(dirptr == 0) { printf("Could not open %s as a directory\n", tmppathptr); return; } while(1) { direntptr = readdir(dirptr); if(direntptr == 0) { break; } if(!strcmp(".", direntptr->d_name)) { continue; } if(!strcmp("..", direntptr->d_name)) { continue; } /* Else, see if it is a directory or a file */ cfg_strncpy(&(g_cfg_tmp_path[0]), &(g_cfg_file_cachedreal[0]), CFG_PATH_MAX); cfg_strlcat(&(g_cfg_tmp_path[0]), direntptr->d_name, CFG_PATH_MAX); ret = cfg_stat(&g_cfg_tmp_path[0], &stat_buf, 0); len = (int)strlen(g_cfg_tmp_path); is_dir = 0; is_gz = 0; if((len > 3) && !cfgcasecmp(&g_cfg_tmp_path[len - 3], ".gz")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".bz2")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".zip")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".woz")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".sdk")) { is_gz = 1; } if(ret != 0) { printf("stat %s ret %d, errno:%d\n", &g_cfg_tmp_path[0], ret, errno); stat_buf.st_size = 0; continue; /* skip it */ } else { fmt = stat_buf.st_mode & S_IFMT; size = (int)stat_buf.st_size; if(fmt == S_IFDIR) { /* it's a directory */ is_dir = 1; } else if((fmt == S_IFREG) && (is_gz == 0)) { if((g_cfg_slotdrive & 0xfff) == 0xfff) { /* see if there are size limits */ if((size < g_cfg_file_min_size) || (size > g_cfg_file_max_size)) { continue; /* skip it */ } } else { if(size < 140*1024) { continue; /* skip it */ } } } } cfg_file_add_dirent(&g_cfg_dirlist, direntptr->d_name, is_dir, (dword64)stat_buf.st_size, 0, 0, -1); } closedir(dirptr); /* then sort the results (Mac's HFS+ is sorted, but other FS won't be)*/ qsort(&(g_cfg_dirlist.direntptr[0]), g_cfg_dirlist.last, sizeof(Cfg_dirent), cfg_dirent_sortfn); g_cfg_dirlist.curent = g_cfg_dirlist.last - 1; for(i = g_cfg_dirlist.last - 1; i >= 0; i--) { ret = cfg_str_match(&(g_cfg_file_match[0]), g_cfg_dirlist.direntptr[i].name, CFG_PATH_MAX); if(ret <= 0) { /* set cur ent to closest filename to the match name */ g_cfg_dirlist.curent = i; } } } char * cfg_shorten_filename(const char *in_ptr, int maxlen) { char *out_ptr; int len, c; int i; /* Warning: uses a static string, not reentrant! */ out_ptr = &(g_cfg_file_shortened[0]); len = (int)strlen(in_ptr); maxlen = MY_MIN(len, maxlen); for(i = 0; i < maxlen; i++) { c = in_ptr[i] & 0x7f; if(c < 0x20) { c = '*'; } out_ptr[i] = c; } out_ptr[maxlen] = 0; if(len > maxlen) { for(i = 0; i < (maxlen/2); i++) { c = in_ptr[len-i-1] & 0x7f; if(c < 0x20) { c = '*'; } out_ptr[maxlen-i-1] = c; } out_ptr[(maxlen/2) - 1] = '.'; out_ptr[maxlen/2] = '.'; out_ptr[(maxlen/2) + 1] = '.'; } return out_ptr; } void cfg_fix_topent(Cfg_listhdr *listhdrptr) { int num_to_show; num_to_show = listhdrptr->num_to_show; /* Force curent and topent to make sense */ if(listhdrptr->curent >= listhdrptr->last) { listhdrptr->curent = listhdrptr->last - 1; } if(listhdrptr->curent < 0) { listhdrptr->curent = 0; } if(abs(listhdrptr->curent - listhdrptr->topent) >= num_to_show) { listhdrptr->topent = listhdrptr->curent - (num_to_show/2); } if(listhdrptr->topent > listhdrptr->curent) { listhdrptr->topent = listhdrptr->curent - (num_to_show/2); } if(listhdrptr->topent < 0) { listhdrptr->topent = 0; } } void cfg_file_draw() { Cfg_listhdr *listhdrptr; Cfg_dirent *direntptr; const char *tmp_str; char *str, *fmt; int num_to_show; int yoffset; int x, y; int i; //printf("cfg_file_draw called\n"); cfg_file_readdir(&g_cfg_file_curpath[0]); for(y = 0; y < 21; y++) { cfg_htab_vtab(0, y); cfg_printf("\tZ\t"); for(x = 1; x < 79; x++) { cfg_htab_vtab(x, y); cfg_putchar(' '); } cfg_htab_vtab(79, y); cfg_printf("\t_\t"); } cfg_htab_vtab(1, 0); cfg_putchar('\b'); for(x = 1; x < 79; x++) { cfg_putchar(' '); } if((g_cfg_slotdrive & 0xfff) == 0xfff) { cfg_htab_vtab(5, 0); cfg_printf("\bSelect file to use as %-40s\b", cfg_shorten_filename(g_cfg_file_def_name, 40)); } else { cfg_htab_vtab(30, 0); tmp_str = "Select"; if(g_cfg_newdisk_select == 2) { tmp_str = "Create duplicate"; } else if(g_cfg_newdisk_select) { tmp_str = "Create new"; } cfg_printf("\b%s image for s%dd%d\b", tmp_str, (g_cfg_slotdrive >> 8) & 0xf, (g_cfg_slotdrive & 0xff) + 1); } cfg_htab_vtab(2, 1); cfg_printf("config.kegs path: %-56s", cfg_shorten_filename(&g_config_kegs_name[0], 56)); cfg_htab_vtab(2, 2); cfg_printf("Current KEGS directory: %-50s", cfg_shorten_filename(&g_cfg_cwd_str[0], 50)); cfg_htab_vtab(2, 3); str = ""; if(g_cfg_file_pathfield) { str = "\b \b"; } cfg_printf("Path: %s%s", cfg_shorten_filename(&g_cfg_file_curpath[0], 68), str); cfg_htab_vtab(0, 4); cfg_printf(" \t"); for(x = 1; x < 79; x++) { cfg_putchar('\\'); } cfg_printf("\t "); /* Force curent and topent to make sense */ listhdrptr = &g_cfg_dirlist; num_to_show = CFG_NUM_SHOWENTS; yoffset = 5; if(g_cfg_select_partition > 0) { listhdrptr = &g_cfg_partitionlist; num_to_show -= 2; cfg_htab_vtab(2, yoffset); cfg_printf("Select partition of %-50s", cfg_shorten_filename(&g_cfg_file_path[0], 50), ""); cfg_htab_vtab(2, yoffset + 1); cfg_printf("Current partition: %-50s", cfg_shorten_filename(&g_cfg_part_path[0], 50), ""); yoffset += 2; } listhdrptr->num_to_show = num_to_show; cfg_fix_topent(listhdrptr); for(i = 0; i < num_to_show; i++) { y = i + yoffset; if(listhdrptr->last > (i + listhdrptr->topent)) { direntptr = &(listhdrptr-> direntptr[i + listhdrptr->topent]); cfg_htab_vtab(2, y); if(direntptr->is_dir) { cfg_printf("\tXY\t "); } else { cfg_printf(" "); } if(direntptr->part_num >= 0) { cfg_printf("%3d: ", direntptr->part_num); } str = cfg_shorten_filename(direntptr->name, 50); fmt = "%-50s"; if((i + listhdrptr->topent) == listhdrptr->curent) { if(g_cfg_file_pathfield == 0) { fmt = "\b%-50s\b"; } else { fmt = "%-49s\b \b"; } //printf("file highlight l %d top:%d cur:%d\n", // i, listhdrptr->topent, // listhdrptr->curent); } cfg_printf(fmt, str); if(!direntptr->is_dir) { cfg_print_dnum(direntptr->dsize, 18); } //printf(" :%s:%lld:\n", str, direntptr->dsize); } } cfg_htab_vtab(1, 5 + CFG_NUM_SHOWENTS); cfg_putchar('\t'); for(x = 1; x < 79; x++) { cfg_putchar('L'); } cfg_putchar('\t'); //printf("cfg_file_draw done\n"); } void cfg_partition_select_all() { word32 slot_drive; int part_path_len, curent; slot_drive = g_cfg_slotdrive; part_path_len = (int)strlen(&g_cfg_part_path[0]); curent = g_cfg_partitionlist.curent; while(1) { g_cfg_slotdrive = slot_drive; g_cfg_partitionlist.curent = curent; cfg_partition_selected(); if(g_cfg_slotdrive != 0) { // Something went wrong, get out return; } slot_drive++; curent++; g_cfg_part_path[part_path_len] = 0; if(curent >= g_cfg_partitionlist.last) { return; } if((slot_drive >> 8) == 7) { if((slot_drive & 0xff) >= MAX_C7_DISKS) { return; } if((slot_drive & 0xff) >= 12) { return; } } else if((slot_drive & 0xff) >= 2) { return; } } } void cfg_partition_selected() { char *str; const char *part_str; char *part_str2; int pos; int part_num; pos = g_cfg_partitionlist.curent; str = g_cfg_partitionlist.direntptr[pos].name; if(g_cfg_partitionlist.direntptr[pos].is_dir) { // Add this path to the partition path, and try again if(!strcmp(str, "../")) { /* go up one directory */ cfg_get_base_path(&g_cfg_part_path[0], &g_cfg_part_path[0], 1); } else { cfg_strlcat(&(g_cfg_part_path[0]), str, CFG_PATH_MAX); } cfg_partition_make_list_from_name(&g_cfg_file_path[0]); return; } part_num = -2; part_str = 0; if(str[0] == 0 || (str[0] >= '0' && str[0] <= '9')) { part_num = g_cfg_partitionlist.direntptr[pos].part_num; } else { part_str = str; } part_str2 = 0; if(part_str != 0) { cfg_strlcat(&g_cfg_part_path[0], part_str, CFG_PATH_MAX); part_str2 = kegs_malloc_str(&g_cfg_part_path[0]); g_cfg_part_path[0] = 0; } printf("cfg_partition_selected, pos:%d, g_cfg_file_path[0]:%s, " "part:%s\n", pos, g_cfg_file_path, part_str2); insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &(g_cfg_file_path[0]), 0, part_str2, part_num, 0); free(part_str2); g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; g_cfg_select_partition = -1; } void cfg_file_selected() { struct stat stat_buf; char *str; int fmt, stat_errno, is_cmd_key_down; int ret; is_cmd_key_down = adb_is_cmd_key_down() && ((g_cfg_slotdrive & 0xfff) != 0xfff); // Cmd-Return means create DynaPro image when using slot/drive if(g_cfg_select_partition > 0) { cfg_partition_selected(); return; } if(!is_cmd_key_down && (g_cfg_file_pathfield == 0)) { // in file section area of window str = g_cfg_dirlist.direntptr[g_cfg_dirlist.curent].name; if(!strcmp(str, "../")) { /* go up one directory */ cfg_get_base_path(&g_cfg_file_curpath[0], &g_cfg_file_curpath[0], 1); return; } cfg_strncpy(&(g_cfg_file_path[0]), &(g_cfg_file_cachedreal[0]), CFG_PATH_MAX); cfg_strlcat(&(g_cfg_file_path[0]), str, CFG_PATH_MAX); } else { // just use cfg_file_curpath directly cfg_strncpy(&g_cfg_file_path[0], &g_cfg_file_curpath[0], CFG_PATH_MAX); } ret = cfg_stat(&g_cfg_file_path[0], &stat_buf, 0); stat_errno = errno; fmt = stat_buf.st_mode & S_IFMT; cfg_printf("Stat'ing %s, st_mode is: %08x\n", &g_cfg_file_path[0], (int)stat_buf.st_mode); if((ret == 0) && (fmt == S_IFDIR) && is_cmd_key_down && (g_cfg_newdisk_select != 2)) { // Make a new DynaPro disk cfg_insert_disk_dynapro((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); g_cfg_slotdrive = 0; // End file selection g_cfg_newdisk_select = 0; g_menuptr = &g_cfg_disk_menu[0]; } else if((g_cfg_newdisk_select == 1) && (g_cfg_newdisk_type == 3) && g_cfg_file_pathfield && (fmt == S_IFDIR)) { // Special handling for Dynamic ProDOS directories. User hit // return in the Path field on a directory, use this directory cfg_create_new_image(); } if(ret != 0) { if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { // This looks good, a new file name was entered if(stat_errno == ENOENT) { cfg_create_new_image(); } else { printf("Unknown errno:%d while checking %s\n", stat_errno, &g_cfg_file_path[0]); } } else { printf("stat %s returned %d, errno: %d\n", &g_cfg_file_path[0], ret, stat_errno); } } else if(fmt == S_IFDIR) { /* it's a directory */ cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_file_path[0], CFG_PATH_MAX); } else if(g_cfg_newdisk_select) { // Do not allow selecting files, just ignore it } else if((g_cfg_slotdrive & 0xfff) < 0xfff) { /* select it */ ret = cfg_maybe_insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); if(ret > 0) { g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; } } else { cfg_file_update_ptr(g_cfg_file_strptr, &g_cfg_file_path[0], 1); g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; } } void cfg_file_handle_key(int key) { Cfg_listhdr *listhdrptr; int len, lowkey, got_match_key, is_cmd_key_down; // Modes: g_cfg_slotdrive: 1 to 0xfff: File selection dialog // otherwise: normal menu being shown // g_cfg_file_pathfield: File selection with cursor in Path: field // otherwise: in scrolling file selection field // g_cfg_select_partition: file selection for partition name if(g_cfg_file_pathfield) { if(key >= 0x20 && key < 0x7f) { len = (int)strlen(&g_cfg_file_curpath[0]); if(len < CFG_PATH_MAX-4) { g_cfg_file_curpath[len] = key; g_cfg_file_curpath[len+1] = 0; } return; } } listhdrptr = &g_cfg_dirlist; is_cmd_key_down = 0; if(g_cfg_select_partition > 0) { listhdrptr = &g_cfg_partitionlist; is_cmd_key_down = adb_is_cmd_key_down() && ((g_cfg_slotdrive & 0xfff) != 0xfff); } lowkey = tolower(key); got_match_key = 0; if((g_cfg_file_pathfield == 0) && (lowkey >= 'a') && (lowkey <= 'z') && !is_cmd_key_down) { /* jump to file starting with this letter */ g_cfg_file_match[0] = key; g_cfg_file_match[1] = 0; g_cfg_dirlist.invalid = 1; /* re-read directory */ got_match_key = 1; } switch(key) { case 0x1b: // ESC if(((g_cfg_slotdrive & 0xfff) < 0xfff) && !g_cfg_newdisk_select) { iwm_eject_disk_by_num((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff); } g_cfg_slotdrive = 0; g_cfg_select_partition = -1; g_cfg_dirlist.invalid = 1; g_cfg_newdisk_select = 0; break; case 0x0a: /* down arrow */ if(g_cfg_file_pathfield == 0) { listhdrptr->curent++; cfg_fix_topent(listhdrptr); } break; case 0x0b: /* up arrow */ if(g_cfg_file_pathfield == 0) { listhdrptr->curent--; cfg_fix_topent(listhdrptr); } break; case 0x0d: /* return */ //printf("handling return press\n"); cfg_file_selected(); break; case 0x61: /* 'a' */ if(is_cmd_key_down && (g_cfg_select_partition > 0)) { cfg_partition_select_all(); } break; case 0x09: /* tab */ g_cfg_file_pathfield = !g_cfg_file_pathfield; if(g_cfg_select_partition > 0) { // If selecting file inside zip or partition, don't // allow editing of the Path info g_cfg_file_pathfield = 0; } break; case 0x08: /* left arrow */ case 0x7f: /* delete key */ if(g_cfg_file_pathfield) { // printf("left arrow/delete\n"); len = (int)strlen(&g_cfg_file_curpath[0]) - 1; if(len >= 0) { g_cfg_file_curpath[len] = 0; } } break; default: if(!got_match_key) { printf("key: %02x\n", key); } } #if 0 printf("curent: %d, topent: %d, last: %d\n", g_cfg_dirlist.curent, g_cfg_dirlist.topent, g_cfg_dirlist.last); #endif } void cfg_draw_menu() { const char *str; Cfg_menu *menuptr; int print_eject_help, line, type, match_found, menu_line, max_line; g_menu_redraw_needed = 0; menuptr = g_menuptr; if(menuptr == 0) { menuptr = g_cfg_main_menu; } if(g_rom_version < 0) { /* Must select ROM file */ menuptr = g_cfg_rom_menu; } g_menuptr = menuptr; cfg_home(); line = 1; max_line = 1; match_found = 0; print_eject_help = 0; menu_line = g_menu_line; cfg_printf("%s\n\n", menuptr[0].str); while(line < 24) { str = menuptr[line].str; type = menuptr[line].cfgtype; if(str == 0) { break; } if((type & 0xf) == CFGTYPE_DISK) { print_eject_help = 1; } #if 0 printf("Calling parse_menu line:%d, menu_line:%d, %p\n", line, menu_line, menuptr); #endif cfg_parse_menu(menuptr, line, menu_line, 0); if(line == g_menu_line) { if(type != 0) { match_found = 1; } else if(g_menu_inc) { menu_line++; } else { menu_line--; } } if(line > max_line) { max_line = line; } cfg_printf("%s\n", g_cfg_opt_buf); line++; } if((menu_line < 1) && !match_found) { menu_line = 1; } if((menu_line >= max_line) && !match_found) { menu_line = max_line; } g_menu_line = menu_line; g_menu_max_line = max_line; if(!match_found) { g_menu_redraw_needed = 1; } if(g_rom_version < 0) { cfg_htab_vtab(0, 21); cfg_printf("\bYOU MUST SELECT A VALID ROM FILE\b\n"); } cfg_htab_vtab(0, 23); cfg_printf("Move: \tJ\t \tK\t Change: \tH\t \tU\t \tM\t"); if(print_eject_help) { cfg_printf(" Eject: "); if((g_cfg_slotdrive & 0xfff) > 0) { cfg_printf("\bESC\b"); } else { cfg_printf("E"); cfg_printf(" New image: N Dup image: D Verify: V"); } } if((g_cfg_slotdrive & 0xfff) > 0) { cfg_printf(" Edit Path: \bTAB\b"); if(g_cfg_select_partition > 0) { cfg_printf(" (\bCmd\b-\bA\b to mount all)"); } else if((g_cfg_newdisk_type == 3) || !g_cfg_newdisk_select) { // Dynamic ProDOS, select a directory cfg_printf(" (\bCmd\b-\bEnter\b for DynaPro)"); } if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { cfg_printf(" (Enter new name on Path)"); } } #if 0 cfg_htab_vtab(0, 22); cfg_printf("menu_line: %d line: %d, vbl:%d, adb:%d key_dn:%d\n", menu_line, line, g_cfg_vbl_count, g_adb_repeat_vbl, g_key_down); #endif if((g_cfg_slotdrive & 0xfff) > 0) { cfg_file_draw(); } } void cfg_newdisk_pick_menu(word32 slotdrive) { slotdrive = slotdrive & 0xfff; g_cfg_newdisk_slotdrive = slotdrive; // 0x601: s6d2, 0x500: s5d1 g_menu_line = 1; //printf("N key, g_menuptr=%p\n", g_menuptr); g_cfg_newdisk_type_default = 1; g_cfg_newdisk_type = 1; g_cfg_newdisk_blocks_default = 140*2; g_cfg_newdisk_blocks = 280; if((slotdrive >> 8) == 6) { g_menuptr = g_cfg_newslot6_menu; } else if((slotdrive >> 8) == 5) { g_menuptr = g_cfg_newslot5_menu; g_cfg_newdisk_blocks_default = 1600; g_cfg_newdisk_blocks = 1600; } else { g_menuptr = g_cfg_newslot7_menu; g_cfg_newdisk_blocks_default = 65535; g_cfg_newdisk_blocks = 65535; } } int cfg_control_panel_update() { int ret; int i; ret = cfg_control_panel_update1(); if(g_cfg_screen_changed) { for(i = 0; i < 24; i++) { video_draw_a2_string(i, &g_cfg_screen[i][0]); } } g_cfg_screen_changed = 0; return ret; } void cfg_edit_mode_key(int key) { char *new_str; int *iptr; int len, ival; len = (int)strlen(&g_cfg_edit_buf[0]); if(key == 0x0d) { // Return // Try to accept the change new_str = kegs_malloc_str(&g_cfg_edit_buf[0]); if(g_cfg_edit_type == CFGTYPE_STR) { cfg_file_update_ptr(g_cfg_edit_ptr, new_str, 1); } else if(g_cfg_edit_type == CFGTYPE_INT) { ival = strtol(&g_cfg_edit_buf[0], 0, 0); iptr = (int *)g_cfg_edit_ptr; cfg_int_update(iptr, ival); } g_cfg_edit_ptr = 0; g_config_kegs_update_needed = 1; } else if(key == 0x1b) { // ESC g_cfg_edit_ptr = 0; // Abort out of edit mode, no changes } else if((key == 0x08) || (key == 0x7f)) { // Left arrow or Delete len--; if(len >= 0) { g_cfg_edit_buf[len] = 0; } } else if((key >= 0x20) && (key < 0x7f)) { if(len < (CFG_OPT_MAXSTR - 3)) { g_cfg_edit_buf[len] = key; g_cfg_edit_buf[len+1] = 0; } } } int cfg_control_panel_update1() { char *(*fn_ptr)(int); void *ptr; char **str_ptr; int *iptr; int type, key; while(g_config_control_panel) { if(g_menu_redraw_needed) { cfg_draw_menu(); } if(g_menu_redraw_needed) { cfg_draw_menu(); } key = adb_read_c000(); if(key & 0x80) { key = key & 0x7f; (void)adb_access_c010(); } else { return 0; // No keys } g_menu_redraw_needed = 1; // If we get here, we got a key, figure out what to do with it if(g_cfg_slotdrive & 0xfff) { cfg_file_handle_key(key); continue; } if(g_cfg_edit_ptr) { cfg_edit_mode_key(key); continue; } // Normal menu system switch(key) { case 0x0a: /* down arrow */ g_menu_line++; g_menu_inc = 1; break; case 0x0b: /* up arrow */ g_menu_line--; g_menu_inc = 0; if(g_menu_line < 1) { g_menu_line = 1; } break; case 0x15: /* right arrow */ cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, 1); break; case 0x08: /* left arrow */ cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, -1); break; case 0x0d: type = g_menuptr[g_menu_line].cfgtype; ptr = g_menuptr[g_menu_line].ptr; switch(type & 0xf) { case CFGTYPE_MENU: g_menuptr = (Cfg_menu *)ptr; g_menu_line = 1; break; case CFGTYPE_DISK: g_cfg_slotdrive = (type >> 4) & 0xfff; cfg_file_init(); break; case CFGTYPE_FUNC: fn_ptr = (char * (*)(int))ptr; (void)(*fn_ptr)(0); break; case CFGTYPE_FILE: g_cfg_slotdrive = 0xfff; g_cfg_file_def_name = *((char **)ptr); g_cfg_file_strptr = (char **)ptr; cfg_file_init(); break; case CFGTYPE_STR: str_ptr = (char **)ptr; if(str_ptr) { g_cfg_edit_type = type & 0xf; g_cfg_edit_ptr = str_ptr; cfg_strncpy(&g_cfg_edit_buf[0], *str_ptr, CFG_OPT_MAXSTR); } break; case CFGTYPE_INT: // If there are no ',' in the menu str, then // allow user to enter a manual number if(!strchr(g_menuptr[g_menu_line].str, ',')) { g_cfg_edit_type = type & 0xf; g_cfg_edit_ptr = ptr; iptr = (int *)ptr; snprintf(&g_cfg_edit_buf[0], CFG_OPT_MAXSTR, "%d", *iptr); } } break; case 0x1b: // Jump to last menu entry g_menu_line = g_menu_max_line; break; case 'd': case 'D': // Duplicate an image type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_dup_existing_image(type >> 4); } break; case 'e': case 'E': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { iwm_eject_disk_by_num(type >> 12, (type >> 4) & 0xff); } break; case 'l': case 'L': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_toggle_lock_disk(type >> 4); } break; case 'n': case 'N': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_newdisk_pick_menu(type >> 4); } break; case 'v': case 'V': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_validate_image(type >> 4); } break; default: printf("key: %02x\n", key); } } return 0; } ================================================ FILE: gsplus/src/config.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2019 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #define CONF_BUF_LEN 1024 #define COPY_BUF_SIZE 4096 #define CFG_PRINTF_BUFSIZE 2048 #define CFG_PATH_MAX 1024 #define CFG_NUM_SHOWENTS 16 #define CFGTYPE_MENU 1 #define CFGTYPE_INT 2 #define CFGTYPE_DISK 3 #define CFGTYPE_FUNC 4 #define CFGTYPE_FILE 5 #define CFGTYPE_STR 6 /* CFGTYPE limited to just 4 bits: 0-15 */ /* Cfg_menu, Cfg_dirent and Cfg_listhdr are defined in defc.h */ STRUCT(Cfg_defval) { Cfg_menu *menuptr; int intval; char *strval; }; ================================================ FILE: gsplus/src/cp_gsplus_libs ================================================ #!/usr/bin/perl -w # $KmKId: cp_kegs_libs,v 1.2 2021-02-09 00:35:48+00 kentd Exp $ use strict; use English; if($#ARGV < 2) { die "Usage: executable srclib_dir destlib_dir"; } # Runs objdump on the executable, finds all unresolved dependencies, and # then copies each of those libraries from srclib_dir to destlib_dir # destlib_dir should be APPLICATION/Contents/Frameworks/ my $exe = shift; my $srcdir = shift; my $destdir = shift; if(! -f $exe || ! -d $srcdir || ! -d $destdir) { die "$exe is not a file, or $srcdir or $destdir are not a dir"; } my $do_swiftos = 0; open(RPATHS, "objdump -macho -dylibs-used $exe|") or die "Open failed: $!"; my $line; my $lib; foreach $line () { chomp($line); if($line =~ m:\@rpath/([^ ]*) :) { $lib = $1; print "lib: $lib\n"; `cp $srcdir/$lib $destdir/`; } $do_swiftos = 1; } # And copy libswiftos.dylib if we copied any files if($do_swiftos) { `cp $srcdir/libswiftos.dylib $destdir/`; } ================================================ FILE: gsplus/src/debugger.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include #include #include "defc.h" #include "disas.h" #define LINE_SIZE 160 /* Input buffer size */ #define PRINTF_BUF_SIZE 239 #define DEBUG_ENTRY_MAX_CHARS 80 STRUCT(Debug_entry) { byte str_buf[DEBUG_ENTRY_MAX_CHARS]; }; char g_debug_printf_buf[PRINTF_BUF_SIZE]; char g_debug_stage_buf[PRINTF_BUF_SIZE]; int g_debug_stage_pos = 0; Debug_entry *g_debug_lines_ptr = 0; int g_debug_lines_total = 0; int g_debug_lines_pos = 0; int g_debug_lines_alloc = 0; int g_debug_lines_max = 1024*1024; int g_debug_lines_view = -1; int g_debug_lines_viewable_lines = 20; int g_debugwin_changed = 1; int g_debug_to_stdout = 1; extern byte *g_memory_ptr; extern byte *g_slow_memory_ptr; extern int g_halt_sim; extern word32 g_c068_statereg; extern word32 stop_run_at; extern int Verbose; extern int Halt_on; extern int g_a2_key_to_ascii[][4]; extern Kimage g_debugwin_kimage; extern int g_config_control_panel; extern word32 g_mem_size_total; extern char *g_sound_file_str; extern word32 g_sound_file_bytes; int g_num_breakpoints = 0; Break_point g_break_pts[MAX_BREAK_POINTS]; extern int g_irq_pending; extern dword64 g_last_vbl_dcyc; extern int g_ret1; extern Engine_reg engine; extern dword64 g_dcycles_end; int g_stepping = 0; word32 g_list_kpc; int g_hex_line_len = 0x10; word32 g_a1 = 0; word32 g_a2 = 0; word32 g_a3 = 0; word32 g_a4 = 0; word32 g_a1bank = 0; word32 g_a2bank = 0; word32 g_a3bank = 0; word32 g_a4bank = 0; #define MAX_CMD_BUFFER 229 #define PC_LOG_LEN (2*1024*1024) Pc_log g_pc_log_array[PC_LOG_LEN + 2]; Data_log g_data_log_array[PC_LOG_LEN + 2]; word32 g_log_pc_enable = 0; Pc_log *g_log_pc_ptr = &(g_pc_log_array[0]); Pc_log *g_log_pc_start_ptr = &(g_pc_log_array[0]); Pc_log *g_log_pc_end_ptr = &(g_pc_log_array[PC_LOG_LEN]); Data_log *g_log_data_ptr = &(g_data_log_array[0]); Data_log *g_log_data_start_ptr = &(g_data_log_array[0]); Data_log *g_log_data_end_ptr = &(g_data_log_array[PC_LOG_LEN]); char g_cmd_buffer[MAX_CMD_BUFFER + 2] = { 0 }; int g_cmd_buffer_len = 2; #define MAX_DISAS_BUF 150 char g_disas_buffer[MAX_DISAS_BUF]; void debugger_init() { debugger_help(); g_list_kpc = engine.kpc; #if 0 if(g_num_breakpoints == 0) { set_bp(0xff5a0e, 0xff5a0e, 4); set_bp(0x00c50a, 0x00c50a, 4); set_bp(0x00c50d, 0x00c50d, 4); } #endif } int g_dbg_new_halt = 0; void check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type) { Break_point *bp_ptr; int count; int i; count = g_num_breakpoints; for(i = 0; i < count; i++) { bp_ptr = &(g_break_pts[i]); if((type & bp_ptr->acc_type) == 0) { continue; } if((addr >= (bp_ptr->start_addr & 0xffffff)) && (addr <= (bp_ptr->end_addr & 0xffffff))) { debug_hit_bp(addr, dfcyc, maybe_stack, type, i); } } if((type == 4) && ((addr == 0xe10000) || (addr == 0xe10004))) { FINISH(RET_TOOLTRACE, 0); } } void debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, int pos) { word32 trk_side, side, trk, cmd, unit, buf, blk, param_cnt, list_ptr; word32 status_code, cmd_list, stack, rts; if((addr == 0xff5a0e) && (type == 4)) { trk_side = get_memory_c(0xe10f32); side = (trk_side >> 5) & 1; trk = get_memory_c(0xe10f34) + ((trk_side & 0x1f) << 6); buf = get_memory_c(0x42) | (get_memory_c(0x43) << 8) | (get_memory_c(0x44) << 16); printf("ff5a0e: 3.5 read of track %03x side:%d sector:%03x to " "%06x at %016llx\n", trk, side, get_memory_c(0xe10f33), buf, dfcyc); return; } if((addr == 0x00c50a) && (type == 4)) { cmd = get_memory_c(0x42); unit = get_memory_c(0x43); buf = get_memory_c(0x44) | (get_memory_c(0x45) << 8); blk = get_memory_c(0x46) | (get_memory_c(0x47) << 8); printf("00c50a: cmd %02x u:%02x buf:%04x blk:%04x at %016llx\n", cmd, unit, buf, blk, dfcyc); return; } if((addr == 0x00c50d) && (type == 4)) { stack = maybe_stack & 0xffff; rts = get_memory_c(stack + 1) | (get_memory_c(stack + 2) << 8); cmd = get_memory_c(rts + 1); cmd_list = get_memory_c(rts + 2) | (get_memory_c(rts+3) << 8); param_cnt = get_memory_c(cmd_list); unit = get_memory_c(cmd_list + 1); list_ptr = get_memory_c(cmd_list + 2) | (get_memory_c(cmd_list + 3) << 8); status_code = get_memory_c(cmd_list + 4); printf("00c50d: stack:%04x rts:%04x cmd:%02x cmd_list:%04x " "param_cnt:%02x unit:%02x listptr:%04x " "status:%02x at %016llx\n", stack, rts, cmd, cmd_list, param_cnt, unit, list_ptr, status_code, dfcyc); printf(" list_ptr: %04x: %02x %02x %02x %02x %02x %02x %02x\n", list_ptr, get_memory_c(list_ptr), get_memory_c(list_ptr + 1), get_memory_c(list_ptr + 2), get_memory_c(list_ptr + 3), get_memory_c(list_ptr + 4), get_memory_c(list_ptr + 5), get_memory_c(list_ptr + 6)); return; } dbg_log_info(dfcyc, addr, pos, 0x6270); halt2_printf("Hit breakpoint at %06x\n", addr); } int debugger_run_16ms() { // Called when g_halt_sim is set if(g_dbg_new_halt) { g_list_kpc = engine.kpc; show_regs(); } g_dbg_new_halt = 0; adb_nonmain_check(); // printf("debugger_run_16ms: g_halt_sim:%d\n", g_halt_sim); return 0; } void dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type) { if(dfcyc == 0) { return; // Ignore some IWM t:00e7 events and others } g_log_data_ptr->dfcyc = dfcyc; g_log_data_ptr->stat = 0; g_log_data_ptr->addr = info1; g_log_data_ptr->val = info2; g_log_data_ptr->size = type; // type must be > 4 g_log_data_ptr++; if(g_log_data_ptr >= g_log_data_end_ptr) { g_log_data_ptr = g_log_data_start_ptr; } } void debugger_update_list_kpc() { g_dbg_new_halt = 1; } void debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up) { word32 c025_val, special; int key, pos, changed; pos = 1; c025_val = kimage_ptr->c025_val; if(c025_val & 1) { // Shift is down pos = 2; } else if(c025_val & 4) { // Capslock is down key = g_a2_key_to_ascii[a2code][1]; if((key >= 'a') && (key <= 'z')) { pos = 2; // CAPS LOCK on } } if(c025_val & 2) { // Ctrl is down pos = 3; } key = g_a2_key_to_ascii[a2code][pos]; if(key < 0) { return; } special = (key >> 8) & 0xff; // c025 changes if(is_up) { c025_val = c025_val & (~special); } else { c025_val = c025_val | special; } kimage_ptr->c025_val = c025_val; if(is_up) { return; // Nothing else to do } if(key >= 0x80) { // printf("key: %04x\n", key); if(key == 0x8007) { // F7 - close debugger video_set_active(kimage_ptr, !kimage_ptr->active); printf("Toggled debugger window to:%d\n", kimage_ptr->active); } if((key & 0xff) == 0x74) { // Page up keycode debugger_page_updown(1); } if((key & 0xff) == 0x79) { // Page down keycode debugger_page_updown(-1); } return; } pos = g_cmd_buffer_len; changed = 0; if((key >= 0x20) && (key < 0x7f)) { // printable character, add it if(pos < MAX_CMD_BUFFER) { // printf("cmd[%d]=%c\n", pos, key); g_cmd_buffer[pos++] = key; changed = 1; } } else if((key == 0x08) || (key == 0x7f)) { // Left arrow or backspace if(pos > 2) { pos--; changed = 1; } } else if((key == 0x0d) || (key == 0x0a)) { //dbg_printf("Did return, pos:%d, str:%s\n", pos, g_cmd_buffer); do_debug_cmd(&g_cmd_buffer[2]); pos = 2; changed = 1; } else { // printf("ctrl key:%04x\n", key); } g_cmd_buffer[pos] = 0; g_cmd_buffer_len = pos; g_debug_lines_view = -1; g_debugwin_changed |= changed; // printf("g_cmd_buffer: %s\n", g_cmd_buffer); } void debugger_page_updown(int isup) { int view, max; view = g_debug_lines_view; if(view < 0) { view = 0; } view = view + (isup*g_debug_lines_viewable_lines); if(view < 0) { view = -1; } max = g_debug_lines_pos; if(g_debug_lines_alloc >= g_debug_lines_max) { max = g_debug_lines_alloc - 4; } view = MY_MIN(view, max - g_debug_lines_viewable_lines); // printf("new view:%d, was:%d\n", view, g_debug_lines_view); if(view != g_debug_lines_view) { g_debug_lines_view = view; g_debugwin_changed++; } } void debugger_redraw_screen(Kimage *kimage_ptr) { int line, vid_line, back, border_top, save_pos, num, lines_done; int save_view, save_to_stdout; int i; if((g_debugwin_changed == 0) || (kimage_ptr->active == 0)) { return; // Nothing to do } save_pos = g_debug_lines_pos; save_view = g_debug_lines_view; // printf("DEBUGGER drawing SCREEN!\n"); g_cmd_buffer[0] = '>'; g_cmd_buffer[1] = ' '; g_cmd_buffer[g_cmd_buffer_len] = 0xa0; // Cursor: inverse space g_cmd_buffer[g_cmd_buffer_len+1] = 0; save_to_stdout = g_debug_to_stdout; g_debug_to_stdout = 0; dbg_printf("%s\n", &g_cmd_buffer[0]); g_cmd_buffer[g_cmd_buffer_len] = 0; dbg_printf("g_halt_sim:%02x\n", g_halt_sim); border_top = 8; g_debug_to_stdout = save_to_stdout; vid_line = (((kimage_ptr->a2_height - 2*border_top) / 16) * 8) - 1; num = g_debug_lines_pos - save_pos; if(num < 0) { num = num + g_debug_lines_alloc; } if(num > 4) { // printf("num is > 4!\n"); num = 4; } for(i = 0; i < num; i++) { line = debug_get_view_line(i); debug_draw_debug_line(kimage_ptr, line, vid_line); vid_line -= 8; } g_debug_lines_pos = save_pos; g_debug_lines_view = save_view; back = save_view; if(back < 0) { // -1 means always show most recent back = 0; } lines_done = 0; while(vid_line >= border_top) { line = debug_get_view_line(back); debug_draw_debug_line(kimage_ptr, line, vid_line); back++; vid_line -= 8; lines_done++; #if 0 printf(" did a line, line is now: %d after str:%s\n", line, str); #endif } g_debug_lines_viewable_lines = lines_done; g_debugwin_changed = 0; kimage_ptr->x_refresh_needed = 1; // printf("x_refresh_needed = 1, viewable_lines:%d\n", lines_done); } void debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line) { word32 line_bytes; int i; // printf("draw debug line:%d at vid_line:%d\n", line, vid_line); for(i = 7; i >= 0; i--) { line_bytes = (vid_line << 16) | (40 << 8) | 0; redraw_changed_string(&(g_debug_lines_ptr[line].str_buf[0]), line_bytes, -1L, kimage_ptr->wptr + 8, 0, 0x00ffffff, kimage_ptr->a2_width_full, 1); vid_line--; } } Dbg_longcmd g_debug_bp_clear[] = { { "all", debug_bp_clear_all, 0, "clear all breakpoints" }, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_bp[] = { { "set", debug_bp_set, 0, "Set breakpoint: ADDR or ADDR0-ADDR1" }, { "clear", debug_bp_clear, &g_debug_bp_clear[0], "Clear breakpoint: ADDR OR ADDR0-ADDR1"}, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_logpc[] = { { "on", debug_logpc_on, 0, "Turn on logging of pc and data" }, { "off", debug_logpc_off,0, "Turn off logging of pc and data" }, { "save", debug_logpc_save,0, "logpc save FILE: save to file" }, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_iwm[] = { { "check", debug_iwm_check, 0, "Denibblize current track" }, { 0, 0, 0, 0 } }; // Main table of commands Dbg_longcmd g_debug_longcmds[] = { { "help", debug_help, 0, "Help" }, { "bp", debug_bp, &g_debug_bp[0], "bp ADDR: sets breakpoint on addr" }, { "logpc", debug_logpc, &g_debug_logpc[0], "Log PC" }, { "iwm", debug_iwm, &g_debug_iwm[0], "IWM" }, { "soundfile", debug_soundfile, 0, "Save sound to a WAV file" }, { 0, 0, 0, 0 } }; void debugger_help() { dbg_printf("KEGS Debugger help (courtesy Fredric Devernay\n"); dbg_printf("General command syntax: [bank]/[address][command]\n"); dbg_printf("e.g. 'e1/0010B' to set a breakpoint at the interrupt jump " "pt\n"); dbg_printf("Enter all addresses using lower-case\n"); dbg_printf("As with the IIgs monitor, you can omit the bank number " "after\n"); dbg_printf("having set it: 'e1/0010B' followed by '14B' will set\n"); dbg_printf("breakpoints at e1/0010 and e1/0014\n"); dbg_printf("\n"); dbg_printf("g Go\n"); dbg_printf("[bank]/[addr]g Go from [bank]/[address]\n"); dbg_printf("s Step one instruction\n"); dbg_printf("[bank]/[addr]s Step one instr at [bank]/[addr]\n"); dbg_printf("[bank]/[addr]B Set breakpoint at [bank]/[addr]\n"); dbg_printf("B Show all breakpoints\n"); dbg_printf("[bank]/[addr]D Delete breakpoint at [bank]/" "[addr]\n"); dbg_printf("[bank]/[addr1].[addr2] View memory\n"); dbg_printf("[bank]/[addr]L Disassemble memory\n"); dbg_printf("Z Dump SCC state\n"); dbg_printf("I Dump IWM state\n"); dbg_printf("[drive].[track]I Dump IWM state\n"); dbg_printf("E Dump Ensoniq state\n"); dbg_printf("[osc]E Dump oscillator [osc] state\n"); dbg_printf("R Dump dtime array and events\n"); dbg_printf("T Show toolbox log\n"); dbg_printf("[bank]/[addr]T Dump tools using ptr [bank]/" "[addr]\n"); dbg_printf(" as 'tool_set_info'\n"); dbg_printf("[mode]V XOR verbose with 1=DISK, 2=IRQ,\n"); dbg_printf(" 4=CLK,8=SHADOW,10=IWM,20=DOC,\n"); dbg_printf(" 40=ABD,80=SCC, 100=TEST, 200=" "VIDEO\n"); dbg_printf("[mode]H XOR halt_on with 1=SCAN_INT,\n"); dbg_printf(" 2=IRQ, 4=SHADOW_REG, 8=" "C70D_WRITES\n"); dbg_printf("r Reset\n"); dbg_printf("[0/1]=m Changes m bit for l listings\n"); dbg_printf("[0/1]=x Changes x bit for l listings\n"); dbg_printf("S show_bankptr_bank0 & smartport " "errs\n"); dbg_printf("P show_pmhz\n"); dbg_printf("A show_a2_line_stuff show_adb_log\n"); dbg_printf("Ctrl-e Dump registers\n"); dbg_printf("[bank]/[addr1].[addr2]us[file] Save mem area to [file]\n"); dbg_printf("[bank]/[addr1].[addr2]ul[file] Load mem area from " "[file]\n"); dbg_printf("v Show video information\n"); dbg_printf("q Exit Debugger (and KEGS)\n"); } void dbg_help_show_strs(int help_depth, const char *str, const char *help_str) { const char *blank_str, *pre_str, *post_str; int column, len, blank_len, pre_len, post_len; // Indent by 3*help_depth chars, then output str, then hit // column 14, then output help_str. This can be done in just 2-3 // lines, but I made it longer and clearer to avoid any "overflow" // cases if(help_str == 0) { return; } blank_str = " " " " " "; blank_len = (int)strlen(blank_str); // should be >=17 column = 17; len = (int)strlen(str); if(help_depth < 0) { help_depth = 0; } pre_str = blank_str; pre_len = 3 * help_depth; if(pre_len < blank_len) { pre_str = blank_str + blank_len - pre_len; } post_str = ""; post_len = column - pre_len - len; if((post_len >= 1) && (post_len < blank_len)) { post_str = blank_str + blank_len - post_len; } dbg_printf("%s%s%s: %s\n", pre_str, str, post_str, help_str); } const char * debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, int help_depth) { Dbg_fn *fnptr; Dbg_longcmd *subptr; const char *str, *newstr; int len, c; int i; // See if the command is from the longcmd list while(*line_ptr == ' ') { line_ptr++; // eat spaces } // Output " str :" where : is at column 14 always // printf("dfcit: %s, help_depth:%d\n", line_ptr, help_depth); for(i = 0; i < 1000; i++) { // Provide a limit to avoid hang if table not terminated right str = longptr[i].str; fnptr = longptr[i].fnptr; if(!str) { // End of table break; // No match found } if(help_depth < 0) { // Print the help string for all entries in this table dbg_help_show_strs(-1 - help_depth, str, longptr[i].help_str); continue; } len = (int)strlen(str); if(strncmp(line_ptr, str, len) != 0) { continue; // Not a match } // Ensure next char is either a space, or 0 // Let's us avoid commands which are prefixes, or // which are old Apple II monitor hex+commands c = line_ptr[len]; if((c != 0) && (c != ' ')) { continue; // Not valid } if(help_depth) { dbg_help_show_strs(help_depth, str, longptr[i].help_str); } subptr = longptr[i].subptr; // Try a subcmd first newstr = line_ptr + len; if(subptr != 0) { if(help_depth) { help_depth++; } newstr = debug_find_cmd_in_table(newstr, subptr, help_depth); // If a subcmd was found, newstr is now 0 } if((newstr == 0) || help_depth) { return 0; } if((newstr != 0) && (fnptr != 0)) { (*fnptr)(line_ptr + len); return 0; // Success } } if(help_depth >= 1) { // No subcommands found, print out all entries in this table debug_find_cmd_in_table(line_ptr, longptr, -1 - help_depth); return 0; } return line_ptr; } void do_debug_cmd(const char *in_str) { const char *line_ptr; const char *newstr; int slot_drive, track, ret_val, mode, old_mode, got_num; int save_to_stdout; mode = 0; old_mode = 0; save_to_stdout = g_debug_to_stdout; g_debug_to_stdout = 1; dbg_printf("*%s\n", in_str); line_ptr = in_str; // See if the command is from the longcmd list newstr = debug_find_cmd_in_table(in_str, &(g_debug_longcmds[0]), 0); if(newstr == 0) { g_debug_to_stdout = save_to_stdout; return; // Command found get out } // If we get here, parse an Apple II monitor like command: // {address}{cmd} repeat. while(1) { ret_val = 0; g_a2 = 0; got_num = 0; while(1) { if((mode == 0) && (got_num != 0)) { g_a3 = g_a2; g_a3bank = g_a2bank; g_a1 = g_a2; g_a1bank = g_a2bank; } ret_val = *line_ptr++ & 0x7f; if((ret_val >= '0') && (ret_val <= '9')) { g_a2 = (g_a2 << 4) + ret_val - '0'; got_num = 1; continue; } if((ret_val >= 'a') && (ret_val <= 'f')) { g_a2 = (g_a2 << 4) + ret_val - 'a' + 10; got_num = 1; continue; } if(ret_val == '/') { g_a2bank = g_a2; g_a2 = 0; continue; } break; } old_mode = mode; mode = 0; switch(ret_val) { case 'h': debugger_help(); break; case 'R': show_dtime_array(); show_all_events(); break; case 'I': slot_drive = -1; track = -1; if(got_num) { if(old_mode == '.') { slot_drive = g_a1; } track = g_a2; } iwm_show_track(slot_drive, track, 0); iwm_show_stats(slot_drive); break; case 'E': doc_show_ensoniq_state(); break; case 'T': if(got_num) { show_toolset_tables(g_a2bank, g_a2); } else { show_toolbox_log(); } break; case 'v': if(got_num) { dis_do_compare(); } else { video_show_debug_info(); } break; case 'V': dbg_printf("g_irq_pending: %05x\n", g_irq_pending); dbg_printf("Setting Verbose ^= %04x\n", g_a1); Verbose ^= g_a1; dbg_printf("Verbose is now: %04x\n", Verbose); break; case 'H': dbg_printf("Setting Halt_on ^= %04x\n", g_a1); Halt_on ^= g_a1; dbg_printf("Halt_on is now: %04x\n", Halt_on); break; case 'r': do_reset(); g_list_kpc = engine.kpc; break; case 'm': if(old_mode == '=') { if(!g_a1) { engine.psr &= ~0x20; } else { engine.psr |= 0x20; } if(engine.psr & 0x100) { engine.psr |= 0x30; } } else { dis_do_memmove(); } break; case 'p': dis_do_pattern_search(); break; case 'x': if(old_mode == '=') { if(!g_a1) { engine.psr &= ~0x10; } else { engine.psr |= 0x10; } if(engine.psr & 0x100) { engine.psr |= 0x30; } } break; case 'z': if(old_mode == '=') { stop_run_at = g_a1; dbg_printf("Calling add_event for t:%08x\n", g_a1); add_event_stop(((dword64)g_a1) << 16); dbg_printf("set stop_run_at = %x\n", g_a1); } break; case 'l': case 'L': if(got_num) { g_list_kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } do_debug_list(); break; case 'Z': show_scc_state(); break; case 'S': show_bankptrs_bank0rdwr(); smartport_error(); break; case 'M': show_pmhz(); mockingboard_show(got_num, g_a1); break; case 'A': show_a2_line_stuff(); show_adb_log(); break; case 's': g_stepping = 1; if(got_num) { engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } mode = 's'; g_list_kpc = engine.kpc; break; case 'B': if(got_num) { dbg_printf("got_num:%d, a2bank:%x, g_a2:%x\n", got_num, g_a2bank, g_a2); set_bp((g_a2bank << 16) + g_a2, (g_a2bank << 16) + g_a2, 4); } else { show_bp(); } break; case 'D': if(got_num) { dbg_printf("got_num: %d, a2bank: %x, a2: %x\n", got_num, g_a2bank, g_a2); delete_bp((g_a2bank << 16) + g_a2, (g_a2bank << 16) + g_a2); } break; case 'g': case 'G': dbg_printf("Going..\n"); g_stepping = 0; if(got_num) { engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } do_go(); g_list_kpc = engine.kpc; break; case 'u': dbg_printf("Unix commands\n"); line_ptr = do_debug_unix(line_ptr, old_mode); break; case ':': case '.': case '+': case '-': case '=': case ',': mode = ret_val; dbg_printf("Setting mode = %x\n", mode); break; case ' ': case '\t': if(!got_num) { mode = old_mode; break; } mode = do_blank(mode, old_mode); break; case '<': g_a4 = g_a2; g_a4bank = g_a2bank; break; case 0x05: /* ctrl-e */ case 'Q': case 'q': show_regs(); break; case 0: // The final null char if(old_mode == 's') { mode = do_blank(mode, old_mode); g_debug_to_stdout = save_to_stdout; return; } if(line_ptr == &in_str[1]) { g_a2 = g_a1 | (g_hex_line_len - 1); show_hex_mem(g_a1bank, g_a1, g_a2, -1); g_a1 = g_a2 + 1; } else { if((got_num == 1) || (mode == 's')) { mode = do_blank(mode, old_mode); } } g_debug_to_stdout = save_to_stdout; return; // Get out, all done break; default: dbg_printf("\nUnrecognized command: %s\n", in_str); g_debug_to_stdout = save_to_stdout; return; } } } word32 dis_get_memory_ptr(word32 addr) { word32 tmp1, tmp2, tmp3; tmp1 = get_memory_c(addr); tmp2 = get_memory_c(addr + 1); tmp3 = get_memory_c(addr + 2); return (tmp3 << 16) + (tmp2 << 8) + tmp1; } void show_one_toolset(FILE *toolfile, int toolnum, word32 addr) { word32 rout_addr; int num_routs; int i; num_routs = dis_get_memory_ptr(addr); fprintf(toolfile, "Tool 0x%02x, table: 0x%06x, num_routs:%03x\n", toolnum, addr, num_routs); if((addr < 0x10000) || (num_routs > 0x100)) { fprintf(toolfile, "addr in page 0, or num_routs too large\n"); return; } for(i = 1; i < num_routs; i++) { rout_addr = dis_get_memory_ptr(addr + 4*i); fprintf(toolfile, "%06x = %02x%02x\n", rout_addr, i, toolnum); } } void show_toolset_tables(word32 a2bank, word32 addr) { FILE *toolfile; word32 tool_addr; int num_tools; int i; addr = (a2bank << 16) + (addr & 0xffff); toolfile = fopen("tool_set_info", "w"); if(toolfile == 0) { fprintf(stderr, "fopen of tool_set_info failed: %d\n", errno); exit(2); } num_tools = dis_get_memory_ptr(addr); fprintf(toolfile, "There are 0x%02x tools using ptr at %06x\n", num_tools, addr); if(num_tools > 40) { fprintf(toolfile, "Too many tools, aborting\n"); num_tools = 0; } for(i = 1; i < num_tools; i++) { tool_addr = dis_get_memory_ptr(addr + 4*i); show_one_toolset(toolfile, i, tool_addr); } fclose(toolfile); } word32 debug_getnum(const char **str_ptr) { const char *str; word32 val; int c, got_num; str = *str_ptr; while(*str == ' ') { str++; } got_num = 0; val = 0; while(1) { c = tolower(*str); //printf("got c:%02x %c val was %08x got_num:%d\n", c, c, val, // got_num); if((c >= '0') && (c <= '9')) { val = (val << 4) + (c - '0'); got_num = 1; } else if((c >= 'a') && (c <= 'f')) { val = (val << 4) + 10 + (c - 'a'); got_num = 1; } else { break; } str++; } *str_ptr = str; if(got_num) { return val; } return (word32)-1L; } char * debug_get_filename(const char **str_ptr) { const char *str, *start_str; char *new_str; int c, len; // Go to first whitespace (or end of str), then kegs_malloc_str() // the string and copy to it str = *str_ptr; start_str = 0; //printf("get_filename, str now :%s:\n", str); while(1) { c = *str++; if(c == 0) { break; } if((c == ' ') || (c == '\t') || (c == '\n')) { //printf("c:%02x at str :%s: , start_str:%p\n", c, str, // start_str); if(start_str) { break; } continue; } // Else it's a valid char, set start_str if needed if(!start_str) { start_str = str - 1; //printf("Got c:%02x, start_str :%s:\n", c, start_str); } } new_str = 0; if(start_str) { len = (int)(str - start_str); if(len > 1) { new_str = malloc(len); memcpy(new_str, start_str, len); new_str[len - 1] = 0; } } *str_ptr = str; return new_str; } void debug_help(const char *str) { dbg_printf("Help:\n"); (void)debug_find_cmd_in_table(str, &(g_debug_longcmds[0]), 1); } void debug_bp(const char *str) { // bp without a following set/clear command. Set a breakpoint if // an address range follows, otherwise just print current breakpoints debug_bp_setclr(str, 0); } void debug_bp_set(const char *str) { debug_bp_setclr(str, 1); } void debug_bp_clear(const char *str) { debug_bp_setclr(str, 2); } void debug_bp_clear_all(const char *str) { if(str) { // Use str to avoid warning } if(g_num_breakpoints) { g_num_breakpoints = 0; setup_pageinfo(); dbg_printf("Deleted all breakpoints\n"); } } void debug_bp_setclr(const char *str, int is_set_clear) { word32 addr, end_addr, acc_type; printf("In debug_bp: %s\n", str); addr = debug_getnum(&str); // printf("getnum ret:%08x\n", addr); if(addr == (word32)-1L) { // No argument show_bp(); return; } end_addr = addr; if(*str == '-') { // Range str++; end_addr = debug_getnum(&str); // printf("end_addr is %08x\n", end_addr); if(end_addr == (word32)-1L) { end_addr = addr; } } acc_type = 4; acc_type = debug_getnum(&str); if(acc_type == (word32)-1L) { acc_type = 4; // Code breakpoint } if(is_set_clear == 2) { // clear delete_bp(addr, end_addr); } else { // set, or nothing set_bp(addr, end_addr, acc_type); } } void debug_soundfile(const char *cmd_str) { char *str; // See if there's an argument str = debug_get_filename(&cmd_str); // str=0 if no argument sound_file_start(str); // str==0 means close file } void debug_logpc(const char *str) { if(str) { // Dummy use of argument } dbg_printf("logpc enable:%d, cur offset:%08lx\n", g_log_pc_enable, (long)(g_log_pc_ptr - g_log_pc_start_ptr)); } void debug_logpc_on(const char *str) { if(str) { // Dummy use of argument } g_log_pc_enable = 1; g_dcycles_end = 0; dbg_printf("Enabled logging of PC and data accesses\n"); } void debug_logpc_off(const char *str) { if(str) { // Dummy use of argument } g_log_pc_enable = 0; g_dcycles_end = 0; dbg_printf("Disabled logging of PC and data accesses\n"); } void debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc) { char *str, *shadow_str; dword64 lstat, offset64, offset64slow, addr64; word32 wstat, addr, size, val; addr = log_data_ptr->addr; lstat = (dword64)(log_data_ptr->stat); wstat = lstat & 0xff; addr64 = lstat - wstat + (addr & 0xff); offset64 = addr64 - (dword64)&(g_memory_ptr[0]); str = "IO"; shadow_str = ""; if((wstat & BANK_SHADOW) || (wstat & BANK_SHADOW2)) { shadow_str = "SHADOWED"; } size = log_data_ptr->size; if(size > 32) { fprintf(pcfile, "INFO %08x %08x %04x t:%04x %lld.%02lld\n", log_data_ptr->addr, log_data_ptr->val, size >> 16, size & 0xffff, (log_data_ptr->dfcyc - start_dcyc)>>16, ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16); } else { offset64slow = addr64 - (dword64)&(g_slow_memory_ptr[0]); if(offset64 < g_mem_size_total) { str = "mem"; } else if(offset64slow < 0x20000) { str = "slow_mem"; offset64 = offset64slow; } else { str = "IO"; offset64 = offset64 & 0xff; } val = log_data_ptr->val; fprintf(pcfile, "DATA set %06x = ", addr); if(size == 8) { fprintf(pcfile, "%02x (8) ", val & 0xff); } else if(size == 16) { fprintf(pcfile, "%04x (16) ", val & 0xffff); } else { fprintf(pcfile, "%06x (%d) ", val, size); } fprintf(pcfile, "%lld.%02lld, %s[%06llx] %s\n", (log_data_ptr->dfcyc - start_dcyc) >> 16, ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16, str, offset64 & 0xffffffULL, shadow_str); } } Data_log * debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, int *count_ptr) { while((*data_wrap_ptr < 2) && (log_data_ptr->dfcyc <= dfcyc) && (log_data_ptr->dfcyc >= start_dcyc)) { if(*count_ptr >= PC_LOG_LEN) { break; } debug_logpc_out_data(pcfile, log_data_ptr, base_dcyc); if(log_data_ptr->dfcyc == 0) { break; } log_data_ptr++; (*count_ptr)++; if(log_data_ptr >= g_log_data_end_ptr) { log_data_ptr = g_log_data_start_ptr; (*data_wrap_ptr)++; } } return log_data_ptr; } void debug_logpc_save(const char *cmd_str) { FILE *pcfile; Pc_log *log_pc_ptr; Data_log *log_data_ptr; char *str; dword64 dfcyc, start_dcyc, base_dcyc, max_dcyc; word32 instr, psr, acc, xreg, yreg, stack, direct, dbank, kpc, num; int data_wrap, accsize, xsize, abs_time, data_count; int i; // See if there's an argument num = debug_getnum(&cmd_str); abs_time = 1; if(num != (word32)-1L) { dbg_printf("Doing relative time\n"); abs_time = 0; } pcfile = fopen("logpc_out", "w"); if(pcfile == 0) { fprintf(stderr,"fopen failed...errno: %d\n", errno); exit(2); } log_pc_ptr = g_log_pc_ptr; log_data_ptr = g_log_data_ptr; #if 0 printf("debug_logpc_save called, log_pc_ptr:%p, %p,%p log_data_ptr:%p, " "%p,%p\n", log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr, log_data_ptr, g_log_data_start_ptr, g_log_data_end_ptr); #endif #if 0 fprintf(pcfile, "current pc_log_ptr: %p, start: %p, end: %p\n", log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr); #endif // See if we haven't filled buffer yet if(log_pc_ptr->dfcyc == 0) { log_pc_ptr = g_log_pc_start_ptr; } if(log_data_ptr->dfcyc == 0) { log_data_ptr = g_log_data_start_ptr; data_wrap = 1; } start_dcyc = log_pc_ptr->dfcyc; // Round to an exact usec start_dcyc = (start_dcyc >> 16) << 16; base_dcyc = start_dcyc; if(abs_time) { base_dcyc = 0; // Show absolute time } dfcyc = start_dcyc; data_wrap = 0; data_count = 0; /* find first data entry */ while((data_wrap < 2) && (log_data_ptr->dfcyc < dfcyc)) { log_data_ptr++; if(log_data_ptr >= g_log_data_end_ptr) { log_data_ptr = g_log_data_start_ptr; data_wrap++; } } fprintf(pcfile, "start_dcyc: %016llx, first entry:%016llx\n", start_dcyc, log_pc_ptr->dfcyc); dfcyc = start_dcyc; max_dcyc = dfcyc; for(i = 0; i < PC_LOG_LEN; i++) { dfcyc = log_pc_ptr->dfcyc; log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, base_dcyc, dfcyc, start_dcyc, &data_wrap, &data_count); dbank = (log_pc_ptr->dbank_kpc >> 24) & 0xff; kpc = log_pc_ptr->dbank_kpc & 0xffffff; instr = log_pc_ptr->instr; psr = (log_pc_ptr->psr_acc >> 16) & 0xffff; acc = log_pc_ptr->psr_acc & 0xffff; xreg = (log_pc_ptr->xreg_yreg >> 16) & 0xffff; yreg = log_pc_ptr->xreg_yreg & 0xffff; stack = (log_pc_ptr->stack_direct >> 16) & 0xffff; direct = log_pc_ptr->stack_direct & 0xffff; accsize = 2; xsize = 2; if(psr & 0x20) { accsize = 1; } if(psr & 0x10) { xsize = 1; } str = do_dis(kpc, accsize, xsize, 1, instr, 0); fprintf(pcfile, "%06x] A:%04x X:%04x Y:%04x P:%03x " "S:%04x D:%04x B:%02x %lld.%02lld %s\n", i, acc, xreg, yreg, psr, stack, direct, dbank, (dfcyc - base_dcyc) >> 16, ((dfcyc & 0xffff) * 100) >> 16, str); if((dfcyc == 0) && (i != 0)) { break; } max_dcyc = dfcyc; log_pc_ptr++; if(log_pc_ptr >= g_log_pc_end_ptr) { log_pc_ptr = g_log_pc_start_ptr; } } // Print any more DATA or INFO after last PC entry log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, base_dcyc, max_dcyc + 10 * 65536, start_dcyc, &data_wrap, &data_count); fclose(pcfile); } void set_bp(word32 addr, word32 end_addr, word32 acc_type) { int count; dbg_printf("About to set BP at %06x - %06x, type:%02x\n", addr, end_addr, acc_type); count = g_num_breakpoints; if(count >= MAX_BREAK_POINTS) { dbg_printf("Too many (0x%02x) breakpoints set!\n", count); return; } g_break_pts[count].start_addr = addr; g_break_pts[count].end_addr = end_addr; g_break_pts[count].acc_type = acc_type; g_num_breakpoints = count + 1; fixup_brks(); } void show_bp() { char acc_str[4]; word32 addr, end_addr, acc_type; int i; dbg_printf("Showing breakpoints set\n"); for(i = 0; i < g_num_breakpoints; i++) { addr = g_break_pts[i].start_addr; end_addr = g_break_pts[i].end_addr; acc_type = g_break_pts[i].acc_type; acc_str[0] = ' '; acc_str[1] = ' '; acc_str[2] = ' '; acc_str[3] = 0; if(acc_type & 4) { acc_str[2] = 'X'; } if(acc_type & 2) { acc_str[1] = 'W'; } if(acc_type & 1) { acc_str[0] = 'R'; } if(end_addr != addr) { dbg_printf("bp:%02x: %06x-%06x, t:%02x %s\n", i, addr, end_addr, acc_type, acc_str); } else { dbg_printf("bp:%02x: %06x, t:%02x %s\n", i, addr, acc_type, acc_str); } } } void delete_bp(word32 addr, word32 end_addr) { int count, hit; int i, j; dbg_printf("About to delete BP at %06x\n", addr); count = g_num_breakpoints; hit = -1; for(i = count - 1; i >= 0; i--) { if((g_break_pts[i].start_addr > end_addr) || (g_break_pts[i].end_addr < addr)) { continue; // Not this entry } hit = i; dbg_printf("Deleting brkpoint #0x%02x\n", hit); for(j = i+1; j < count; j++) { g_break_pts[j-1] = g_break_pts[j]; } count--; } g_num_breakpoints = count; if(hit < 0) { dbg_printf("Breakpoint not found!\n"); } else { setup_pageinfo(); } show_bp(); } void debug_iwm(const char *str) { if(str) { // Dummy use of argument } iwm_show_track(-1, -1, 0); } void debug_iwm_check(const char *str) { if(str) { // Dummy use of argument } iwm_check_nibblization(0); } int do_blank(int mode, int old_mode) { int tmp; switch(old_mode) { case 's': tmp = g_a2; if(tmp == 0) { tmp = 1; } #if 0 for(i = 0; i < tmp; i++) { g_stepping = 1; do_step(); if(g_halt_sim != 0) { break; } } #endif g_list_kpc = engine.kpc; /* video_update_through_line(262); */ break; case ':': set_memory_c(((g_a3bank << 16) + g_a3), g_a2, 0); g_a3++; mode = old_mode; break; case '.': case 0: xam_mem(-1); break; case ',': xam_mem(16); break; case '+': dbg_printf("%x\n", g_a1 + g_a2); break; case '-': dbg_printf("%x\n", g_a1 - g_a2); break; default: dbg_printf("Unknown mode at space: %d\n", old_mode); break; } return mode; } void do_go() { /* also called by do_step */ g_config_control_panel = 0; clear_halt(); } void do_step() { int size_mem_imm, size_x_imm; return; // This is not correct do_go(); size_mem_imm = 2; if(engine.psr & 0x20) { size_mem_imm = 1; } size_x_imm = 2; if(engine.psr & 0x10) { size_x_imm = 1; } dbg_printf("%s\n", do_dis(engine.kpc, size_mem_imm, size_x_imm, 0, 0, 0)); } void xam_mem(int count) { show_hex_mem(g_a1bank, g_a1, g_a2, count); g_a1 = g_a2 + 1; } void show_hex_mem(word32 startbank, word32 start, word32 end, int count) { char ascii[MAXNUM_HEX_PER_LINE]; word32 i; int val, offset; if(count < 0) { count = 16 - (start & 0xf); } offset = 0; ascii[0] = 0; dbg_printf("Showing hex mem: bank: %x, start: %x, end: %x\n", startbank, start, end); for(i = start; i <= end; i++) { if( (i==start) || (count == 16) ) { dbg_printf("%04x:",i); } dbg_printf(" %02x", get_memory_c((startbank <<16) + i)); val = get_memory_c((startbank << 16) + i) & 0x7f; if((val < 32) || (val >= 0x7f)) { val = '.'; } ascii[offset++] = val; ascii[offset] = 0; count--; if(count <= 0) { dbg_printf(" %s\n", ascii); offset = 0; ascii[0] = 0; count = 16; } } if(offset > 0) { dbg_printf(" %s\n", ascii); } } void do_debug_list() { char *str; int size, size_mem_imm, size_x_imm; int i; dbg_printf("%d=m %d=x %d=LCBANK\n", (engine.psr >> 5)&1, (engine.psr >> 4) & 1, (g_c068_statereg & 0x4) >> 2); size_mem_imm = 2; if(engine.psr & 0x20) { size_mem_imm = 1; } size_x_imm = 2; if(engine.psr & 0x10) { size_x_imm = 1; } for(i = 0; i < 20; i++) { str = do_dis(g_list_kpc, size_mem_imm, size_x_imm, 0, 0, &size); g_list_kpc += size; dbg_printf("%s\n", str); } } void dis_do_memmove() { word32 val; dbg_printf("Memory move from %02x/%04x.%04x to %02x/%04x\n", g_a1bank, g_a1, g_a2, g_a4bank, g_a4); while(g_a1 <= (g_a2 & 0xffff)) { val = get_memory_c((g_a1bank << 16) + g_a1); set_memory_c((g_a4bank << 16) + g_a4, val, 0); g_a1++; g_a4++; } g_a1 = g_a1 & 0xffff; g_a4 = g_a4 & 0xffff; } void dis_do_pattern_search() { #if 0 word32 match_val, val; int match_shift, count; dbg_printf("Memory pattern search for %04x in %02x/%04x to %02x/%04x\n", g_a4, g_a1bank, g_a1, g_a2bank, g_a2); match_shift = 0; count = 0; match_val = g_a4; while(1) { if(g_a1bank > g_a2bank) { break; } if(g_a1 > g_a2) { break; } val = get_memory_c((g_a1bank << 16) + g_a1); if(val == ((match_val >> match_shift) & 0xff)) { match_shift += 8; if(match_shift >= 16) { dbg_printf("Found %04x at %02x/%04x\n", match_val, g_a1bank, g_a1); count++; } } else { match_shift = 0; } g_a1++; if(g_a1 >= 0x10000) { g_a1 = 0; g_a1bank++; } } #endif } void dis_do_compare() { word32 val1, val2; dbg_printf("Memory Compare from %02x/%04x.%04x with %02x/%04x\n", g_a1bank, g_a1, g_a2, g_a4bank, g_a4); while(g_a1 <= (g_a2 & 0xffff)) { val1 = get_memory_c((g_a1bank << 16) + g_a1); val2 = get_memory_c((g_a4bank << 16) + g_a4); if(val1 != val2) { dbg_printf("%02x/%04x: %02x vs %02x\n", g_a1bank, g_a1, val1, val2); } g_a1++; g_a4++; } g_a1 = g_a1 & 0xffff; g_a4 = g_a4 & 0xffff; } const char * do_debug_unix(const char *str, int old_mode) { char localbuf[LINE_SIZE+2]; byte *bptr; word32 offset, len, a1_val; long ret; int fd, load; int i; load = 0; switch(*str++) { case 'l': case 'L': dbg_printf("Loading.."); load = 1; break; case 's': case 'S': dbg_printf("Saving..."); break; default: dbg_printf("Unknown unix command: %c\n", *(str - 1)); if(str[-1] == 0) { return str - 1; } return str; } while((*str == ' ') || (*str == '\t')) { str++; } i = 0; while(i < LINE_SIZE) { localbuf[i++] = *str++; if((*str==' ') || (*str == '\t') || (*str == '\n') || (*str == 0)) { break; } } localbuf[i] = 0; dbg_printf("About to open: %s,len: %d\n", localbuf, (int)strlen(localbuf)); if(load) { fd = open(localbuf, O_RDONLY | O_BINARY); } else { fd = open(localbuf, O_WRONLY | O_CREAT | O_BINARY, 0x1b6); } if(fd < 0) { dbg_printf("Open %s failed: %d. errno:%d\n", localbuf, fd, errno); return str; } if(load) { offset = g_a1 & 0xffff; len = 0x20000 - offset; } else { if(old_mode == '.') { len = g_a2 - g_a1 + 1; } else { len = 0x100; } } a1_val = (g_a1bank << 16) | g_a1; bptr = &g_memory_ptr[a1_val]; if((g_a1bank >= 0xe0) && (g_a1bank < 0xe2)) { bptr = &g_slow_memory_ptr[a1_val & 0x1ffff]; } if(load) { ret = read(fd, bptr, len); } else { ret = write(fd, bptr, len); } dbg_printf("Read/write: addr %06x for %04x bytes, ret: %lx bytes\n", a1_val, len, ret); if(ret < 0) { dbg_printf("errno: %d\n", errno); } g_a1 = g_a1 + (int)ret; return str; } void do_debug_load() { dbg_printf("Sorry, can't load now\n"); } char * do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, int *size_ptr) { char buffer[MAX_DISAS_BUF]; char buffer2[MAX_DISAS_BUF]; const char *str; word32 val, oldkpc, dtype; int args, type, opcode, signed_val; int i; oldkpc = kpc; if(op_provided) { opcode = (instr >> 24) & 0xff; } else { opcode = (int)get_memory_c(kpc) & 0xff; } kpc++; dtype = disas_types[opcode]; str = disas_opcodes[opcode]; type = dtype & 0xff; args = dtype >> 8; if(args > 3) { if(args == 4) { args = accsize; } else if(args == 5) { args = xsize; } } val = -1; switch(args) { case 0: val = 0; break; case 1: if(op_provided) { val = instr & 0xff; } else { val = get_memory_c(kpc); } break; case 2: if(op_provided) { val = instr & 0xffff; } else { val = get_memory16_c(kpc); } break; case 3: if(op_provided) { val = instr & 0xffffff; } else { val = get_memory24_c(kpc); } break; default: fprintf(stderr, "args out of rang: %d, opcode: %08x\n", args, opcode); break; } kpc += args; if(!op_provided) { instr = (opcode << 24) | (val & 0xffffff); } switch(type) { case ABS: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, val); break; case ABSX: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,X", str, val); break; case ABSY: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,Y", str, val); break; case ABSLONG: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); break; case ABSIND: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x)", str, val); break; case ABSXIND: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x,X)", str, val); break; case IMPLY: case ACCUM: if(args != 0) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s", str); break; case IMMED: if(args == 1) { snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, val); } else if(args == 2) { snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%04x", str, val); } else { dbg_printf("arg # mismatch for opcode %x\n", opcode); } break; case JUST8: case DLOC: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x", str, val); break; case DLOCX: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,X", str, val); break; case DLOCY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,Y", str, val); break; case LONG: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); break; case LONGX: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x,X", str, val); break; case DLOCIND: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x)", str, val); break; case DLOCINDY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x),Y", str, val); break; case DLOCXIND: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,X)", str, val); break; case DLOCBRAK: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x]", str, val); break; case DLOCBRAKY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x],y", str, val); break; case DISP8: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } signed_val = (signed char)val; snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, (kpc + signed_val) & 0xffff); break; case DISP8S: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,S", str, val & 0xff); break; case DISP8SINDY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,S),Y", str, val & 0xff); break; case DISP16: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, (word32)(kpc+(signed)(word16)(val)) & 0xffff); break; case MVPMVN: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,$%02x", str, val & 0xff, val >> 8); break; case SEPVAL: case REPVAL: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, val); break; default: dbg_printf("argument type: %d unexpected\n", type); break; } g_disas_buffer[0] = 0; snprintf(&g_disas_buffer[0], MAX_DISAS_BUF, "%02x/%04x: %02x ", oldkpc >> 16, oldkpc & 0xffff, opcode); for(i = 1; i <= args; i++) { snprintf(&buffer2[0], MAX_DISAS_BUF, "%02x ", instr & 0xff); cfg_strlcat(&g_disas_buffer[0], &buffer2[0], MAX_DISAS_BUF); instr = instr >> 8; } for(; i < 4; i++) { cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); } cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); cfg_strlcat(&g_disas_buffer[0], &buffer[0], MAX_DISAS_BUF); if(size_ptr) { *size_ptr = args + 1; } return (&g_disas_buffer[0]); } int debug_get_view_line(int back) { int pos; // where back==0 means return pos - 1. pos = g_debug_lines_pos - 1; pos = pos - back; if(pos < 0) { if(g_debug_lines_alloc >= g_debug_lines_max) { pos += g_debug_lines_alloc; } else { return 0; // HACK: return -1 } } return pos; } int debug_add_output_line(char *in_str) { Debug_entry *line_ptr; byte *out_bptr; int pos, alloc, view, used_len, c; int i; // printf("debug_add_output_line %s len:%d\n", in_str, len); pos = g_debug_lines_pos; line_ptr = g_debug_lines_ptr; alloc = g_debug_lines_alloc; if(pos >= alloc) { if(alloc < g_debug_lines_max) { alloc = MY_MAX(2048, alloc*3); alloc = MY_MAX(alloc, pos*3); alloc = MY_MIN(alloc, g_debug_lines_max); line_ptr = realloc(line_ptr, alloc * sizeof(Debug_entry)); printf("realloc. now %p, alloc:%d\n", line_ptr, alloc); g_debug_lines_ptr = line_ptr; g_debug_lines_alloc = alloc; printf("Alloced debug lines to %d\n", alloc); } else { pos = 0; } } // Convert to A2 format chars: set high bit of each byte, 80 chars // per line out_bptr = &(line_ptr[pos].str_buf[0]); used_len = 0; for(i = 0; i < DEBUG_ENTRY_MAX_CHARS; i++) { c = ' '; if(*in_str) { c = *in_str++; used_len++; } c = c ^ 0x80; // Set highbit if not already set out_bptr[i] = c; } pos++; g_debug_lines_pos = pos; g_debug_lines_total++; // For updating the window g_debugwin_changed++; view = g_debug_lines_view; if(view >= 0) { view++; // view is back from pos, so to stay the same, // it must be incremented when pos incs if((view - 50) >= g_debug_lines_max) { // We were viewing the oldest page, and by wrapping // around we're about to wipe out this old data // Jump to most recent data view = -1; } g_debug_lines_view = view; } return used_len; } void debug_add_output_string(char *in_str, int len) { int ret, tries; tries = 0; ret = 0; if(g_debug_to_stdout) { puts(in_str); // Send output to stdout, too } while((len > 0) || (tries == 0)) { // printf("DEBUG: adding str: %s, len:%d, ret:%d\n", in_str, // len, ret); ret = debug_add_output_line(in_str); len -= ret; in_str += ret; tries++; } } void debug_add_output_chars(char *str) { int pos, c, tab_spaces; pos = g_debug_stage_pos; tab_spaces = 0; while(1) { if(tab_spaces > 0) { c = ' '; tab_spaces--; } else { c = *str++; if(c == '\t') { tab_spaces = 7 - (pos & 7); c = ' '; } } pos = MY_MIN(pos, (PRINTF_BUF_SIZE - 1)); if((c == '\n') || (pos >= (PRINTF_BUF_SIZE - 1))) { g_debug_stage_buf[pos] = 0; debug_add_output_string(&g_debug_stage_buf[0], pos); pos = 0; g_debug_stage_pos = 0; continue; } if(c == 0) { g_debug_stage_pos = pos; return; } g_debug_stage_buf[pos++] = c; } } int dbg_printf(const char *fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = dbg_vprintf(fmt, args); va_end(args); return ret; } int dbg_vprintf(const char *fmt, va_list args) { int ret; ret = vsnprintf(&g_debug_printf_buf[0], PRINTF_BUF_SIZE, fmt, args); debug_add_output_chars(&g_debug_printf_buf[0]); return ret; } void halt_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); dbg_vprintf(fmt, args); va_end(args); set_halt(1); } void halt2_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); dbg_vprintf(fmt, args); va_end(args); set_halt(2); } ================================================ FILE: gsplus/src/defc.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defcomm.h" #define STRUCT(a) typedef struct a ## _st a; struct a ## _st typedef unsigned char byte; typedef unsigned short word16; typedef unsigned int word32; #if _MSC_VER typedef unsigned __int64 dword64; #else typedef unsigned long long dword64; #endif /* 28MHz crystal, plus every 65th 1MHz cycle is stretched 140ns */ #define CYCS_28_MHZ (28636360) #define DCYCS_28_MHZ (1.0*CYCS_28_MHZ) #define CYCS_3_5_MHZ (CYCS_28_MHZ/8) #define DCYCS_1_MHZ ((DCYCS_28_MHZ/28.0)*(65.0*7/(65.0*7+1.0))) // DCYCS_1_MHZ is 1020484.32016 #define CYCLES_IN_16MS_RAW (262 * 65) /* Use precisely 17030 instead of forcing 60 Hz since this is the number of */ /* 1MHz cycles per screen */ #define DCYCS_IN_16MS ((double)(CYCLES_IN_16MS_RAW)) #define DRECIP_DCYCS_IN_16MS (1.0 / (DCYCS_IN_16MS)) #define VBL_RATE (DCYCS_1_MHZ / DCYCS_IN_16MS) // VBL rate is about 59.9227 frames/sec #define MAXNUM_HEX_PER_LINE 32 #define MAX_SCALE_SIZE 5100 #ifdef __NeXT__ # include #endif #ifndef _WIN32 # include # include # include #endif #include #include #include #include #include #include #include #include #include #ifdef HPUX # include /* for GET_ITIMER */ #endif #ifdef SOLARIS # include #endif #ifdef _WIN32 # include # include # pragma warning(disable : 4996) /* open() is deprecated...sigh */ int ftruncate(int fd, word32 length); int lstat(const char *path, struct stat *bufptr); #endif #ifndef O_BINARY /* work around some Windows junk */ # define O_BINARY 0 #endif #define MAX_CHANGE_RECTS 20 #ifdef __GNUC__ int dbg_printf(const char *fmt, ...) __attribute__ (( __format__(printf, 1, 2))); #endif STRUCT(Pc_log) { dword64 dfcyc; word32 dbank_kpc; word32 instr; word32 psr_acc; word32 xreg_yreg; word32 stack_direct; word32 pad; }; STRUCT(Data_log) { dword64 dfcyc; byte *stat; word32 addr; word32 val; word32 size; }; STRUCT(Event) { dword64 dfcyc; int type; Event *next; }; STRUCT(Fplus) { dword64 dplus_1; dword64 dplus_x_minus_1; }; STRUCT(Engine_reg) { dword64 dfcyc; word32 kpc; word32 acc; word32 xreg; word32 yreg; word32 stack; word32 dbank; word32 direct; word32 psr; Fplus *fplus_ptr; }; STRUCT(Break_point) { word32 start_addr; word32 end_addr; word32 acc_type; }; STRUCT(Change_rect) { int x; int y; int width; int height; }; STRUCT(Kimage) { word32 *wptr; int a2_width_full; int a2_height_full; int a2_width; int a2_height; int x_width; int x_height; int x_refresh_needed; int x_max_width; int x_max_height; int x_xpos; int x_ypos; int active; word32 vbl_of_last_resize; word32 c025_val; word32 scale_width_to_a2; word32 scale_width_a2_to_x; word32 scale_height_to_a2; word32 scale_height_a2_to_x; int num_change_rects; Change_rect change_rect[MAX_CHANGE_RECTS]; word32 scale_width[MAX_SCALE_SIZE + 1]; word32 scale_height[MAX_SCALE_SIZE + 1]; }; typedef byte *Pg_info; STRUCT(Page_info) { Pg_info rd_wr; }; STRUCT(Cfg_menu) { const char *str; void *ptr; const char *name_str; void *defptr; int cfgtype; }; STRUCT(Cfg_dirent) { char *name; int is_dir; int part_num; dword64 dsize; dword64 dimage_start; dword64 compr_dsize; }; STRUCT(Cfg_listhdr) { Cfg_dirent *direntptr; int max; int last; int invalid; int curent; int topent; int num_to_show; }; typedef void (Dbg_fn)(const char *str); STRUCT(Dbg_longcmd) { const char *str; Dbg_fn *fnptr; Dbg_longcmd *subptr; const char *help_str; }; STRUCT(Emustate_intlist) { const char *str; word32 *iptr; }; STRUCT(Emustate_dword64list) { const char *str; dword64 *dptr; }; STRUCT(Emustate_word32list) { const char *str; word32 *wptr; }; STRUCT(Lzw_state) { word32 table[4096 + 2]; word32 entry; int bits; }; #ifdef __LP64__ # define PTR2WORD(a) ((word32)(unsigned long long)(a)) #else # define PTR2WORD(a) ((word32)(unsigned long long)(a)) #endif #define ALTZP (g_c068_statereg & 0x80) /* #define PAGE2 (g_c068_statereg & 0x40) */ #define RAMRD (g_c068_statereg & 0x20) #define RAMWRT (g_c068_statereg & 0x10) #define RDROM (g_c068_statereg & 0x08) #define LCBANK2 (g_c068_statereg & 0x04) #define ROMB (g_c068_statereg & 0x02) // #define INTCX (g_c068_statereg & 0x01) #define C041_EN_25SEC_INTS 0x10 #define C041_EN_VBL_INTS 0x08 #define C041_EN_SWITCH_INTS 0x04 #define C041_EN_MOVE_INTS 0x02 #define C041_EN_MOUSE 0x01 /* WARNING: SCC1 and SCC0 interrupts must be in this order for scc.c */ /* This order matches the SCC hardware, and SCC1_ZEROCNT must be 0x0001 */ #define IRQ_PENDING_SCC1_ZEROCNT 0x00001 #define IRQ_PENDING_SCC1_TX 0x00002 #define IRQ_PENDING_SCC1_RX 0x00004 #define IRQ_PENDING_SCC0_ZEROCNT 0x00008 #define IRQ_PENDING_SCC0_TX 0x00010 #define IRQ_PENDING_SCC0_RX 0x00020 #define IRQ_PENDING_C023_SCAN 0x00100 #define IRQ_PENDING_C023_1SEC 0x00200 #define IRQ_PENDING_C046_25SEC 0x00400 #define IRQ_PENDING_C046_VBL 0x00800 #define IRQ_PENDING_ADB_KBD_SRQ 0x01000 #define IRQ_PENDING_ADB_DATA 0x02000 #define IRQ_PENDING_ADB_MOUSE 0x04000 #define IRQ_PENDING_DOC 0x08000 #define IRQ_PENDING_MOCKINGBOARDA 0x10000 #define IRQ_PENDING_MOCKINGBOARDB 0x20000 /* must be BOARDA*2 */ #define EXTRU(val, pos, len) \ ( ( (len) >= (pos) + 1) ? ((val) >> (31-(pos))) : \ (((val) >> (31-(pos)) ) & ( (1<<(len) ) - 1) ) ) #define DEP1(val, pos, old_val) \ (((old_val) & ~(1 << (31 - (pos))) ) | \ ( ((val) & 1) << (31 - (pos))) ) #define set_halt(val) \ if(val) { set_halt_act(val); } #define clear_halt() \ clr_halt_act() #define GET_PAGE_INFO_RD(page) \ (page_info_rd_wr[page].rd_wr) #define GET_PAGE_INFO_WR(page) \ (page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr) #define SET_PAGE_INFO_RD(page,val) \ ;page_info_rd_wr[page].rd_wr = (Pg_info)val; #define SET_PAGE_INFO_WR(page,val) \ ;page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr = \ (Pg_info)val; #define VERBOSE_DISK 0x001 #define VERBOSE_IRQ 0x002 #define VERBOSE_CLK 0x004 #define VERBOSE_SHADOW 0x008 #define VERBOSE_IWM 0x010 #define VERBOSE_DOC 0x020 #define VERBOSE_ADB 0x040 #define VERBOSE_SCC 0x080 #define VERBOSE_TEST 0x100 #define VERBOSE_VIDEO 0x200 #define VERBOSE_MAC 0x400 #define VERBOSE_DYNA 0x800 #ifdef NO_VERB # define DO_VERBOSE 0 #else # define DO_VERBOSE 1 #endif #define disk_printf if(DO_VERBOSE && (Verbose & VERBOSE_DISK)) printf #define irq_printf if(DO_VERBOSE && (Verbose & VERBOSE_IRQ)) printf #define clk_printf if(DO_VERBOSE && (Verbose & VERBOSE_CLK)) printf #define shadow_printf if(DO_VERBOSE && (Verbose & VERBOSE_SHADOW)) printf #define iwm_printf if(DO_VERBOSE && (Verbose & VERBOSE_IWM)) printf #define doc_printf if(DO_VERBOSE && (Verbose & VERBOSE_DOC)) printf #define adb_printf if(DO_VERBOSE && (Verbose & VERBOSE_ADB)) printf #define scc_printf if(DO_VERBOSE && (Verbose & VERBOSE_SCC)) printf #define test_printf if(DO_VERBOSE && (Verbose & VERBOSE_TEST)) printf #define vid_printf if(DO_VERBOSE && (Verbose & VERBOSE_VIDEO)) printf #define mac_printf if(DO_VERBOSE && (Verbose & VERBOSE_MAC)) printf #define dyna_printf if(DO_VERBOSE && (Verbose & VERBOSE_DYNA)) printf #define HALT_ON_SCAN_INT 0x001 #define HALT_ON_IRQ 0x002 #define HALT_ON_SHADOW_REG 0x004 #define HALT_ON_C70D_WRITES 0x008 #define HALT_ON(a, msg) \ if(Halt_on & a) { \ halt_printf(msg); \ } #define MY_MIN(a,b) (((a) < (b)) ? (a) : (b)) #define MY_MAX(a,b) (((a) > (b)) ? (a) : (b)) #define GET_ITIMER(dest) dest = get_itimer(); #define FINISH(arg1, arg2) g_ret1 = arg1 | ((arg2) << 8); g_dcycles_end=0; #include "iwm.h" #include "protos.h" ================================================ FILE: gsplus/src/defcomm.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsdif_defcomm_h[] = "@(#)$KmKId: defcomm.h,v 1.109 2023-11-12 15:29:41+00 kentd Exp $"; #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #define SHIFT_PER_CHANGE 3 #define CHANGE_SHIFT (5 + SHIFT_PER_CHANGE) #define SLOW_MEM_CH_SIZE (0x20000 >> CHANGE_SHIFT) #define MAXNUM_HEX_PER_LINE 32 /* Different Joystick defines */ #define JOYSTICK_MOUSE 1 #define JOYSTICK_LINUX 2 #define JOYSTICK_KEYPAD 3 #define JOYSTICK_WIN32_1 4 #define JOYSTICK_WIN32_2 5 #define MAX_BREAK_POINTS 0x20 #define MAX_BP_INDEX 0x100 #define MAX_BP_PER_INDEX 3 /* 4 word32s total = 16 bytes */ #define SIZE_BREAKPT_ENTRY_BITS 4 /* 16 bytes = 4 bits */ /* Warning--next defines used by asm! */ #define PAGE_INFO_PAD_SIZE 0x800 #define PAGE_INFO_WR_OFFSET 0x10000+PAGE_INFO_PAD_SIZE #define BANK_IO_BIT 31 #define BANK_SHADOW_BIT 30 #define BANK_SHADOW2_BIT 29 #define BANK_IO2_BIT 28 #define BANK_BREAK_BIT 27 #define BANK_BREAK (1 << (31 - BANK_BREAK_BIT)) #define BANK_IO2_TMP (1 << (31 - BANK_IO2_BIT)) #define BANK_IO_TMP (1 << (31 - BANK_IO_BIT)) #define BANK_SHADOW (1 << (31 - BANK_SHADOW_BIT)) #define BANK_SHADOW2 (1 << (31 - BANK_SHADOW2_BIT)) #define SET_BANK_IO \ (&g_dummy_memory1_ptr[BANK_IO_TMP | BANK_IO2_TMP]) #define BANK_BAD_MEM (&g_dummy_memory1_ptr[0xff]) #define RET_BREAK 0x1 #define RET_COP 0x2 #define RET_WDM 0x3 #define RET_WAI 0x4 #define RET_STP 0x5 #define RET_PSR 0x6 #define RET_IRQ 0x7 #define RET_TOOLTRACE 0x8 #define BIT_ALL_STAT_TEXT 0 #define BIT_ALL_STAT_VID80 1 #define BIT_ALL_STAT_ST80 2 #define BIT_ALL_STAT_COLOR_C021 3 #define BIT_ALL_STAT_MIX_T_GR 4 #define BIT_ALL_STAT_DIS_COLOR_DHIRES 5 /* special val, c029 */ #define BIT_ALL_STAT_PAGE2 6 /* special val, statereg */ #define BIT_ALL_STAT_SUPER_HIRES 7 /* special, c029 */ #define BIT_ALL_STAT_HIRES 8 #define BIT_ALL_STAT_ANNUNC3 9 #define BIT_ALL_STAT_ALTCHARSET 10 #define BIT_ALL_STAT_FLASH_STATE 11 #define BIT_ALL_STAT_BG_COLOR 12 /* 4 bits */ #define BIT_ALL_STAT_TEXT_COLOR 16 /* 4 bits */ /* Text must be just above */ /* bg to match c022 reg */ #define BIT_ALL_STAT_VOC_INTERLACE 20 #define BIT_ALL_STAT_VOC_MAIN 21 #define BIT_ALL_STAT_BORDER 22 #define ALL_STAT_SUPER_HIRES (1 << (BIT_ALL_STAT_SUPER_HIRES)) #define ALL_STAT_TEXT (1 << (BIT_ALL_STAT_TEXT)) #define ALL_STAT_VID80 (1 << (BIT_ALL_STAT_VID80)) #define ALL_STAT_PAGE2 (1 << (BIT_ALL_STAT_PAGE2)) #define ALL_STAT_ST80 (1 << (BIT_ALL_STAT_ST80)) #define ALL_STAT_COLOR_C021 (1 << (BIT_ALL_STAT_COLOR_C021)) #define ALL_STAT_DIS_COLOR_DHIRES (1 << (BIT_ALL_STAT_DIS_COLOR_DHIRES)) #define ALL_STAT_MIX_T_GR (1 << (BIT_ALL_STAT_MIX_T_GR)) #define ALL_STAT_HIRES (1 << (BIT_ALL_STAT_HIRES)) #define ALL_STAT_ANNUNC3 (1 << (BIT_ALL_STAT_ANNUNC3)) #define ALL_STAT_TEXT_COLOR (0xf << (BIT_ALL_STAT_TEXT_COLOR)) #define ALL_STAT_BG_COLOR (0xf << (BIT_ALL_STAT_BG_COLOR)) #define ALL_STAT_ALTCHARSET (1 << (BIT_ALL_STAT_ALTCHARSET)) #define ALL_STAT_FLASH_STATE (1 << (BIT_ALL_STAT_FLASH_STATE)) #define ALL_STAT_VOC_INTERLACE (1 << (BIT_ALL_STAT_VOC_INTERLACE)) #define ALL_STAT_VOC_MAIN (1 << (BIT_ALL_STAT_VOC_MAIN)) #define ALL_STAT_BORDER (1 << (BIT_ALL_STAT_BORDER)) #define BORDER_WIDTH 32 #define EFF_BORDER_WIDTH (BORDER_WIDTH + (640-560)) /* BASE_MARGIN_BOTTOM+MARGIN_TOP must equal 62. There are 262 scan lines */ /* at 60Hz (15.7KHz line rate) and so we just make 62 border lines */ #define BASE_MARGIN_TOP 32 #define BASE_MARGIN_BOTTOM 30 #define BASE_MARGIN_LEFT BORDER_WIDTH #define BASE_MARGIN_RIGHT BORDER_WIDTH #define A2_WINDOW_WIDTH 640 #define A2_WINDOW_HEIGHT 400 #define X_A2_WINDOW_WIDTH (A2_WINDOW_WIDTH + BASE_MARGIN_LEFT + \ BASE_MARGIN_RIGHT) #define X_A2_WINDOW_HEIGHT (A2_WINDOW_HEIGHT + BASE_MARGIN_TOP + \ BASE_MARGIN_BOTTOM) #define MAX_STATUS_LINES 4 #define STATUS_LINE_LENGTH 88 #define BASE_WINDOW_WIDTH (X_A2_WINDOW_WIDTH) #define A2_BORDER_COLOR_NUM 0xfe ================================================ FILE: gsplus/src/defs_instr.h ================================================ // $KmKId: defs_instr.h,v 1.70 2023-11-05 16:22:26+00 kentd Exp $ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #undef GET_DLOC_X_IND_RD #ifdef ACC8 # define GET_DLOC_X_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_X_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DISP8_S_RD #ifdef ACC8 # define GET_DISP8_S_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DISP8_S_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DLOC_RD #ifdef ACC8 # define GET_DLOC_RD() \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY8((direct + arg) & 0xffff, arg); #else # define GET_DLOC_RD() \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY16((direct + arg) & 0xffff, arg, 1); #endif #undef GET_DLOC_RD_RMW #undef GET_MEM_RMW #define GET_MEM_RMW() \ if(!IS_ACC16) { \ if(psr & 0x100) { \ /* emulation re-writes the address */ \ SET_MEMORY8(addr_latch, arg); \ } else { \ /* otherwise, just read addr again */ \ GET_MEMORY8(addr_latch, dummy1); \ } \ } else { \ /* 16-bit re-reads addr+1 again */ \ dummy1 = addr_latch + 1; \ GET_MEMORY8(dummy1, dummy1); \ addr_latch--; \ } #define GET_DLOC_RD_RMW() \ GET_DLOC_RD(); \ GET_MEM_RMW(); #undef GET_DLOC_L_IND_RD #ifdef ACC8 # define GET_DLOC_L_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_L_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_IMM_MEM #ifdef ACC8 # define GET_IMM_MEM() \ GET_1BYTE_ARG; \ INC_KPC_2; #else # define GET_IMM_MEM() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_3; #endif #undef GET_ABS_RD #ifdef ACC8 # define GET_ABS_RD() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ GET_MEMORY8((dbank << 16) + arg, arg); \ INC_KPC_3; #else # define GET_ABS_RD() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ GET_MEMORY16((dbank << 16) + arg, arg, 0); \ INC_KPC_3; #endif #undef GET_ABS_RD_RMW #define GET_ABS_RD_RMW() \ GET_ABS_RD(); \ GET_MEM_RMW(); #undef GET_LONG_RD #ifdef ACC8 # define GET_LONG_RD() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ GET_MEMORY8(arg, arg); \ INC_KPC_4; #else # define GET_LONG_RD() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ GET_MEMORY16(arg, arg, 0); \ INC_KPC_4; #endif #undef GET_DLOC_IND_Y_RD #undef GET_DLOC_IND_Y_ADDR #ifdef ACC8 # define GET_DLOC_IND_Y_RD() \ GET_DLOC_IND_Y_ADDR(0) \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_IND_Y_RD() \ GET_DLOC_IND_Y_ADDR(0) \ GET_MEMORY16(arg, arg, 0); #endif #define GET_DLOC_IND_Y_ADDR(is_write) \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, tmp1, 0); \ tmp1 += (dbank << 16); \ arg = (tmp1 + yreg) & 0xffffff; \ tmp2 = (tmp1 & 0xffff00) | (arg & 0xff); \ if((psr & 0x10) && ((arg != tmp2) | is_write)) { \ GET_MEMORY8(tmp2, tmp1); \ } else if(((psr & 0x10) == 0) | (arg != tmp2) | is_write) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; #undef GET_DLOC_IND_RD #ifdef ACC8 # define GET_DLOC_IND_RD() \ GET_1BYTE_ARG; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ GET_MEMORY8((dbank << 16) + arg, arg); #else # define GET_DLOC_IND_RD() \ GET_1BYTE_ARG; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ GET_MEMORY16((dbank << 16) + arg, arg, 0); #endif #undef GET_DLOC_X_RD #ifdef ACC8 # define GET_DLOC_X_RD() \ GET_1BYTE_ARG; \ CYCLES_PLUS_1; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ arg = (arg + xreg + direct) & 0xffff; \ if(psr & 0x100) { \ if((direct & 0xff) == 0) { \ arg = (direct & 0xff00) | (arg & 0xff); \ } \ } \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_X_RD() \ GET_1BYTE_ARG; \ CYCLES_PLUS_1; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ arg = (arg + xreg + direct) & 0xffff; \ if(!IS_ACC16 && (psr & 0x100)) { \ if((direct & 0xff) == 0) { \ arg = (direct & 0xff00) | (arg & 0xff); \ } \ } \ GET_MEMORY16(arg, arg, 1); #endif #undef GET_DLOC_X_RD_RMW #define GET_DLOC_X_RD_RMW() \ GET_DLOC_X_RD(); \ GET_MEM_RMW(); #undef GET_DISP8_S_IND_Y_RD #ifdef ACC8 # define GET_DISP8_S_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DISP8_S_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DLOC_L_IND_Y_RD #ifdef ACC8 # define GET_DLOC_L_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_L_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_ABS_INDEX_ADDR #define GET_ABS_INDEX_ADDR(index_reg, is_write) \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_3; \ tmp1 = (dbank << 16) + arg; \ arg = tmp1 + index_reg; \ tmp1 = (tmp1 & 0xffff00) + (arg & 0xff); \ if((psr & 0x10) && ((tmp1 != arg) | is_write)) { \ GET_MEMORY8(tmp1, tmp2); \ } else if(((psr & 0x10) == 0) | (tmp1 != arg) | is_write) { \ CYCLES_PLUS_1; \ } #undef GET_ABS_Y_RD #ifdef ACC8 # define GET_ABS_Y_RD() \ GET_ABS_INDEX_ADDR(yreg, 0); \ GET_MEMORY8(arg, arg); #else # define GET_ABS_Y_RD() \ GET_ABS_INDEX_ADDR(yreg, 0); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_ABS_X_RD #undef GET_ABS_X_RD_RMW #ifdef ACC8 # define GET_ABS_X_RD() \ GET_ABS_INDEX_ADDR(xreg, 0); \ GET_MEMORY8(arg, arg); #define GET_ABS_X_RD_RMW() \ GET_ABS_INDEX_ADDR(xreg, 1); \ GET_MEMORY8(arg, arg); \ GET_MEM_RMW(); #else # define GET_ABS_X_RD() \ GET_ABS_INDEX_ADDR(xreg, 0); \ GET_MEMORY16(arg, arg, 0); #define GET_ABS_X_RD_RMW() \ GET_ABS_INDEX_ADDR(xreg, 1); \ GET_MEMORY16(arg, arg, 0); \ GET_MEM_RMW(); #endif #undef GET_LONG_X_RD #ifdef ACC8 # define GET_LONG_X_RD() \ GET_3BYTE_ARG; \ arg = (arg + xreg) & 0xffffff; \ INC_KPC_4; \ CYCLES_PLUS_2; \ GET_MEMORY8(arg, arg); #else # define GET_LONG_X_RD() \ GET_3BYTE_ARG; \ arg = (arg + xreg) & 0xffffff; \ INC_KPC_4; \ CYCLES_PLUS_2; \ GET_MEMORY16(arg, arg, 0); #endif #define SET_NEG_ZERO16(val) \ zero = val; \ neg7 = (val) >> 8; #define SET_NEG_ZERO8(val) \ zero = val; \ neg7 = val; #define SET_CARRY8(val) \ psr = (psr & ~1) + (((val) >> 8) & 1); #define SET_CARRY16(val) \ psr = (psr & ~1) + (((val) >> 16) & 1); #define SET_INDEX_REG(src, dest) \ if(psr & 0x10) { \ dest = (src) & 0xff; \ SET_NEG_ZERO8(dest); \ } else { \ dest = (src) & 0xffff; \ SET_NEG_ZERO16(dest); \ } #define LD_INDEX_INST(index_reg, in_bank) \ if(psr & 0x10) { \ GET_MEMORY8(arg, arg); \ } else { \ GET_MEMORY16(arg, arg, in_bank);\ } \ SET_INDEX_REG(arg, index_reg); #define LDX_INST(in_bank) LD_INDEX_INST(xreg, in_bank) #define LDY_INST(in_bank) LD_INDEX_INST(yreg, in_bank) #undef ORA_INST #ifdef ACC8 # define ORA_INST() \ tmp1 = (acc | arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define ORA_INST() \ acc = (acc | arg); \ SET_NEG_ZERO16(acc); #endif #undef AND_INST #ifdef ACC8 # define AND_INST() \ tmp1 = (acc & arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define AND_INST() \ acc = (acc & arg); \ SET_NEG_ZERO16(acc); #endif #undef EOR_INST #ifdef ACC8 # define EOR_INST() \ tmp1 = (acc ^ arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define EOR_INST() \ acc = (acc ^ arg); \ SET_NEG_ZERO16(acc); #endif #undef LDA_INST #ifdef ACC8 # define LDA_INST() \ acc = (acc & 0xff00) + (arg & 0xff); \ SET_NEG_ZERO8(arg & 0xff); #else # define LDA_INST() \ acc = (arg & 0xffff); \ SET_NEG_ZERO16(acc); #endif #undef ADC_INST #ifdef ACC8 # define ADC_INST() \ tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 0); \ acc = (acc & 0xff00) + (tmp1 & 0xff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #else # define ADC_INST() \ tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 0); \ acc = (tmp1 & 0xffff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #endif #undef SBC_INST #ifdef ACC8 # define SBC_INST() \ tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 1); \ acc = (acc & 0xff00) + (tmp1 & 0xff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #else # define SBC_INST() \ tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 1); \ acc = (tmp1 & 0xffff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #endif #undef CMP_INST #ifdef ACC8 # define CMP_INST() \ arg = (acc & 0xff) + (0x100 - arg); \ SET_CARRY8(arg); \ arg = arg & 0xff; \ SET_NEG_ZERO8(arg & 0xff); #else # define CMP_INST() \ arg = (acc & 0xffff) + (0x10000 - arg); \ SET_CARRY16(arg); \ arg = arg & 0xffff; \ SET_NEG_ZERO16(arg & 0xffff); #endif #undef BIT_INST #ifdef ACC8 # define BIT_INST() \ neg7 = arg; \ zero = (acc & arg & 0xff); \ psr = (psr & (~0x40)) | (arg & 0x40); #else # define BIT_INST() \ neg7 = arg >> 8; \ zero = (acc & arg & 0xffff); \ psr = (psr & (~0x40)) | ((arg >> 8) & 0x40); #endif #undef STA_INST #ifdef ACC8 # define STA_INST(in_bank) \ SET_MEMORY8(arg, acc); #else # define STA_INST(in_bank) \ SET_MEMORY16(arg, acc, in_bank); #endif #undef TSB_INST #ifdef ACC8 # define TSB_INST(in_bank) \ arg = arg & 0xff; \ tmp1 = arg | acc; \ zero = arg & acc; \ SET_MEMORY8(addr_latch, tmp1); #else # define TSB_INST(in_bank) \ tmp1 = arg | acc; \ zero = arg & acc; \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef ASL_INST #ifdef ACC8 # define ASL_INST(in_bank) \ psr = (psr & 0x1fe) + ((arg >> 7) & 1); \ tmp1 = (arg << 1) & 0xff; \ SET_NEG_ZERO8(tmp1); \ SET_MEMORY8(addr_latch, tmp1); #else # define ASL_INST(in_bank) \ psr = (psr & 0x1fe) + ((arg >> 15) & 1);\ tmp1 = (arg << 1) & 0xffff; \ SET_NEG_ZERO16(tmp1); \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef ROL_INST #ifdef ACC8 # define ROL_INST(in_bank) \ arg = arg & 0xff; \ arg = (arg << 1) | (psr & 1); \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg & 0xff); \ SET_CARRY8(arg); #else # define ROL_INST(in_bank) \ arg = (arg << 1) | (psr & 1); \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg & 0xffff); \ SET_CARRY16(arg); #endif #undef LSR_INST #ifdef ACC8 # define LSR_INST(in_bank) \ SET_CARRY8(arg << 8); \ arg = (arg >> 1) & 0x7f; \ SET_NEG_ZERO8(arg); \ SET_MEMORY8(addr_latch, arg); #else # define LSR_INST(in_bank) \ SET_CARRY16(arg << 16); \ arg = (arg >> 1) & 0x7fff; \ SET_NEG_ZERO16(arg); \ SET_MEMORY16(addr_latch, arg, in_bank); #endif #undef ROR_INST #ifdef ACC8 # define ROR_INST(in_bank) \ tmp1 = psr & 1; \ SET_CARRY8(arg << 8); \ arg = ((arg >> 1) & 0x7f) | (tmp1 << 7); \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define ROR_INST(in_bank) \ tmp1 = psr & 1; \ SET_CARRY16(arg << 16); \ arg = ((arg >> 1) & 0x7fff) | (tmp1 << 15); \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef TRB_INST #ifdef ACC8 # define TRB_INST(in_bank) \ arg = arg & 0xff; \ tmp1 = arg & ~acc; \ zero = arg & acc; \ SET_MEMORY8(addr_latch, tmp1); #else # define TRB_INST(in_bank) \ tmp1 = arg & ~acc; \ zero = arg & acc; \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef DEC_INST #ifdef ACC8 # define DEC_INST(in_bank) \ arg = (arg - 1) & 0xff; \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define DEC_INST(in_bank) \ arg = (arg - 1) & 0xffff; \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef INC_INST #ifdef ACC8 # define INC_INST(in_bank) \ arg = (arg + 1) & 0xff; \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define INC_INST(in_bank) \ arg = (arg + 1) & 0xffff; \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef STZ_INST #ifdef ACC8 # define STZ_INST(in_bank) \ SET_MEMORY8(arg, 0); #else # define STZ_INST(in_bank) \ SET_MEMORY16(arg, 0, in_bank); #endif #undef BRANCH_DISP8 #define BRANCH_DISP8(cond) \ GET_1BYTE_ARG; \ tmp2 = kpc & 0xff0000; \ kpc += 2; \ tmp1 = kpc; \ if(cond) { \ kpc = kpc + arg - ((arg & 0x80) << 1); \ CYCLES_PLUS_1; \ if((tmp1 ^ kpc) & psr & 0x100) { \ CYCLES_PLUS_1; \ } \ } \ kpc = tmp2 + (kpc & 0xffff); #undef STY_INST #undef STX_INST #define STY_INST(in_bank) \ if(psr & 0x10) { \ SET_MEMORY8(arg, yreg); \ } else { \ SET_MEMORY16(arg, yreg, in_bank);\ } #define STX_INST(in_bank) \ if(psr & 0x10) { \ SET_MEMORY8(arg, xreg); \ } else { \ SET_MEMORY16(arg, xreg, in_bank);\ } #define C_LDX_ABS_Y() \ GET_ABS_INDEX_ADDR(yreg, 0); \ LDX_INST(0); #define C_LDY_ABS_X() \ GET_ABS_INDEX_ADDR(xreg, 0); \ LDY_INST(0); #define C_LDX_ABS() \ GET_ABS_ADDR(); \ LDX_INST(0); #define C_LDY_ABS() \ GET_ABS_ADDR(); \ LDY_INST(0); #define C_LDX_DLOC() \ GET_DLOC_ADDR(); \ LDX_INST(1); #define C_LDY_DLOC() \ GET_DLOC_ADDR(); \ LDY_INST(1); #define C_LDY_DLOC_X() \ GET_DLOC_X_ADDR(); \ LDY_INST(1); #define C_LDX_DLOC_Y() \ GET_DLOC_Y_ADDR(); \ LDX_INST(1); #define CP_INDEX_VAL(index_reg) \ arg = 0x100 - arg + index_reg; \ if((psr & 0x10) == 0) { \ arg += 0xff00; \ SET_NEG_ZERO16(arg & 0xffff); \ SET_CARRY16(arg); \ } else { \ SET_NEG_ZERO8(arg & 0xff);\ SET_CARRY8(arg); \ } #define CP_INDEX_LOAD(index_reg, in_bank) \ if((psr & 0x10) != 0) { \ GET_MEMORY8(arg, arg); \ } else { \ GET_MEMORY16(arg, arg, in_bank);\ } \ CP_INDEX_VAL(index_reg) #define CPX_INST(in_bank) \ CP_INDEX_LOAD(xreg, in_bank); #define CPY_INST(in_bank) \ CP_INDEX_LOAD(yreg, in_bank); #define C_CPX_IMM() \ INC_KPC_2; \ if((psr & 0x10) == 0) { \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_1; \ } else { \ GET_1BYTE_ARG; \ } \ CP_INDEX_VAL(xreg); #define C_CPY_IMM() \ INC_KPC_2; \ if((psr & 0x10) == 0) { \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_1; \ } else { \ GET_1BYTE_ARG; \ } \ CP_INDEX_VAL(yreg); #define C_CPX_DLOC() \ GET_DLOC_ADDR(); \ CPX_INST(1); #define C_CPY_DLOC() \ GET_DLOC_ADDR(); \ CPY_INST(1); #define C_CPX_ABS() \ GET_ABS_ADDR(); \ CPX_INST(0); #define C_CPY_ABS() \ GET_ABS_ADDR(); \ CPY_INST(0); ================================================ FILE: gsplus/src/dependency ================================================ adb.o: adb.c defc.h defcomm.h iwm.h protos.h protos_base.h engine_c.o: engine_c.c defc.h defcomm.h iwm.h protos.h protos_base.h size_c.h op_routs.h engine.h defs_instr.h instable.h clock.o: clock.c defc.h defcomm.h iwm.h protos.h protos_base.h compile_time.o: compile_time.c config.o: config.c defc.h defcomm.h iwm.h protos.h protos_base.h config.h win_dirent.h debugger.o: debugger.c defc.h defcomm.h iwm.h protos.h protos_base.h disas.h scc.o: scc.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_socket_driver.o: scc_socket_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_windriver.o: scc_windriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_unixdriver.o: scc_unixdriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h iwm.o: iwm.c defc.h defcomm.h iwm.h protos.h protos_base.h joystick_driver.o: joystick_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h moremem.o: moremem.c defc.h defcomm.h iwm.h protos.h protos_base.h paddles.o: paddles.c defc.h defcomm.h iwm.h protos.h protos_base.h mockingboard.o: mockingboard.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sim65816.o: sim65816.c defc.h defcomm.h iwm.h protos.h protos_base.h smartport.o: smartport.c defc.h defcomm.h iwm.h protos.h protos_base.h doc.o: doc.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sound.o: sound.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sound_driver.o: sound_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h woz.o: woz.c defc.h defcomm.h iwm.h protos.h protos_base.h unshk.o: unshk.c defc.h defcomm.h iwm.h protos.h protos_base.h undeflate.o: undeflate.c defc.h defcomm.h iwm.h protos.h protos_base.h dynapro.o: dynapro.c defc.h defcomm.h iwm.h protos.h protos_base.h win_dirent.h dyna_type.o: dyna_type.c defc.h defcomm.h iwm.h protos.h protos_base.h dyna_filt.o: dyna_filt.c defc.h defcomm.h iwm.h protos.h protos_base.h dyna_validate.o: dyna_validate.c defc.h defcomm.h iwm.h protos.h protos_base.h applesingle.o: applesingle.c defc.h defcomm.h iwm.h protos.h protos_base.h video.o: video.c defc.h defcomm.h iwm.h protos.h protos_base.h kegsfont.h voc.o: voc.c defc.h defcomm.h iwm.h protos.h protos_base.h macsnd_driver.o: macsnd_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h ================================================ FILE: gsplus/src/disas.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2020 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ enum { ABS = 1, ABSX, ABSY, ABSLONG, ABSIND, ABSXIND, IMPLY, ACCUM, IMMED, JUST8, DLOC, DLOCX, DLOCY, LONG, LONGX, DLOCIND, DLOCINDY, DLOCXIND, DLOCBRAK, DLOCBRAKY, DISP8, DISP8S, DISP8SINDY, DISP16, MVPMVN, REPVAL, SEPVAL }; const char * const disas_opcodes[256] = { "BRK", "ORA", "COP", "ORA", "TSB", "ORA", "ASL", "ORA", /* 00-07 */ "PHP", "ORA", "ASL", "PHD", "TSB", "ORA", "ASL", "ORA", /* 08-0f */ "BPL", "ORA", "ORA", "ORA", "TRB", "ORA", "ASL", "ORA", /* 10-17 */ "CLC", "ORA", "INC", "TCS", "TRB", "ORA", "ASL", "ORA", /* 18-1f */ "JSR", "AND", "JSL", "AND", "BIT", "AND", "ROL", "AND", /* 20-27 */ "PLP", "AND", "ROL", "PLD", "BIT", "AND", "ROL", "AND", /* 28-2f */ "BMI", "AND", "AND", "AND", "BIT", "AND", "ROL", "AND", /* 30-37 */ "SEC", "AND", "DEC", "TSC", "BIT", "AND", "ROL", "AND", /* 38-3f */ "RTI", "EOR", "WDM", "EOR", "MVP", "EOR", "LSR", "EOR", /* 40-47 */ "PHA", "EOR", "LSR", "PHK", "JMP", "EOR", "LSR", "EOR", /* 48-4f */ "BVC", "EOR", "EOR", "EOR", "MVN", "EOR", "LSR", "EOR", /* 50-57 */ "CLI", "EOR", "PHY", "TCD", "JMP", "EOR", "LSR", "EOR", /* 58-5f */ "RTS", "ADC", "PER", "ADC", "STZ", "ADC", "ROR", "ADC", /* 60-67 */ "PLA", "ADC", "ROR", "RTL", "JMP", "ADC", "ROR", "ADC", /* 68-6f */ "BVS", "ADC", "ADC", "ADC", "STZ", "ADC", "ROR", "ADC", /* 70-77 */ "SEI", "ADC", "PLY", "TDC", "JMP", "ADC", "ROR", "ADC", /* 78-7f */ "BRA", "STA", "BRL", "STA", "STY", "STA", "STX", "STA", /* 80-87 */ "DEY", "BIT", "TXA", "PHB", "STY", "STA", "STX", "STA", /* 88-8f */ "BCC", "STA", "STA", "STA", "STY", "STA", "STX", "STA", /* 90-97 */ "TYA", "STA", "TXS", "TXY", "STZ", "STA", "STZ", "STA", /* 98-9f */ "LDY", "LDA", "LDX", "LDA", "LDY", "LDA", "LDX", "LDA", /* a0-a7 */ "TAY", "LDA", "TAX", "PLB", "LDY", "LDA", "LDX", "LDA", /* a8-af */ "BCS", "LDA", "LDA", "LDA", "LDY", "LDA", "LDX", "LDA", /* b0-b7 */ "CLV", "LDA", "TSX", "TYX", "LDY", "LDA", "LDX", "LDA", /* b8-bf */ "CPY", "CMP", "REP", "CMP", "CPY", "CMP", "DEC", "CMP", /* c0-c7 */ "INY", "CMP", "DEX", "WAI", "CPY", "CMP", "DEC", "CMP", /* c8-cf */ "BNE", "CMP", "CMP", "CMP", "PEI", "CMP", "DEC", "CMP", /* d0-d7 */ "CLD", "CMP", "PHX", "STP", "JML", "CMP", "DEC", "CMP", /* d8-df */ "CPX", "SBC", "SEP", "SBC", "CPX", "SBC", "INC", "SBC", /* e0-e7 */ "INX", "SBC", "NOP", "XBA", "CPX", "SBC", "INC", "SBC", /* e8-ef */ "BEQ", "SBC", "SBC", "SBC", "PEA", "SBC", "INC", "SBC", /* f0-f7 */ "SED", "SBC", "PLX", "XCE", "JSR", "SBC", "INC", "SBC", /* f8-ff */ }; const word32 disas_types[256] = { JUST8+0x100, DLOCXIND+0x100, /* 00-01 */ JUST8+0x100, DISP8S+0x100, /* 02-03 */ DLOC+0x100, DLOC+0x100, /* 04-05 */ DLOC+0x100, DLOCBRAK+0x100, /* 06-07 */ IMPLY+0x000, IMMED+0x400, /* 08-9 */ ACCUM+0x000, IMPLY+0x000, /* 0a-b */ ABS+0x200, ABS+0x200, /* c-d */ ABS+0x200, LONG+0x300, /* e-f */ DISP8+0x100, DLOCINDY+0x100, /* 10-11 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 12-13 */ DLOC+0x100, DLOCX+0x100, /* 14-15 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 16-17 */ IMPLY+0x000, ABSY+0x200, /* 18-19 */ ACCUM+0x000, IMPLY+0x000, /* 1a-1b */ ABS+0x200, ABSX+0x200, /* 1c-1d */ ABSX+0x200, LONGX+0x300, /* 1e-1f */ ABS+0x200, DLOCXIND+0x100, /* 20-21 */ ABSLONG+0x300, DISP8S+0x100, /* 22-23 */ DLOC+0x100, DLOC+0x100, /* 24-25 */ DLOC+0x100, DLOCBRAK+0x100, /* 26-27 */ IMPLY+0x000, IMMED+0x400, /* 28-29 */ ACCUM+0x000, IMPLY+0x000, /* 2a-2b */ ABS+0x200, ABS+0x200, /* 2c-2d */ ABS+0x200, LONG+0x300, /* 2e-2f */ DISP8+0x100, DLOCINDY+0x100, /* 30-31 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 32-33 */ DLOCX+0x100, DLOCX+0x100, /* 34-35 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 36-37 */ IMPLY+0x000, ABSY+0x200, /* 38-39 */ ACCUM+0x000, IMPLY+0x000, /* 3a-3b */ ABSX+0x200, ABSX+0x200, /* 3c-3d */ ABSX+0x200, LONGX+0x300, /* 3e-3f */ IMPLY+0x000, DLOCXIND+0x100, /* 40-41 */ JUST8+0x100, DISP8S+0x100, /* 42-43 */ MVPMVN+0x200, DLOC+0x100, /* 44-45 */ DLOC+0x100, DLOCBRAK+0x100, /* 46-47 */ IMPLY+0x000, IMMED+0x400, /* 48-49 */ ACCUM+0x000, IMPLY+0x000, /* 4a-4b */ ABS+0x200, ABS+0x200, /* 4c-4d */ ABS+0x200, LONG+0x300, /* 4e-4f */ DISP8+0x100, DLOCINDY+0x100, /* 50-51 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 52-53 */ MVPMVN+0x200, DLOCX+0x100, /* 54-55 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 56-57 */ IMPLY+0x000, ABSY+0x200, /* 58-59 */ IMPLY+0x000, IMPLY+0x000, /* 5a-5b */ LONG+0x300, ABSX+0x200, /* 5c-5d */ ABSX+0x200, LONGX+0x300, /* 5e-5f */ IMPLY+0x000, DLOCXIND+0x100, /* 60-61 */ DISP16+0x200, DISP8S+0x100, /* 62-63 */ DLOC+0x100, DLOC+0x100, /* 64-65 */ DLOC+0x100, DLOCBRAK+0x100, /* 66-67 */ IMPLY+0x000, IMMED+0x400, /* 68-69 */ ACCUM+0x000, IMPLY+0x000, /* 6a-6b */ ABSIND+0x200, ABS+0x200, /* 6c-6d */ ABS+0x200, LONG+0x300, /* 6e-6f */ DISP8+0x100, DLOCINDY+0x100, /* 70-71 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 72-73 */ DLOCX+0x100, DLOCX+0x100, /* 74-75 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 76-77 */ IMPLY+0x000, ABSY+0x200, /* 78-79 */ IMPLY+0x000, IMPLY+0x000, /* 7a-7b */ ABSXIND+0x200, ABSX+0x200, /* 7c-7d */ ABSX+0x200, LONGX+0x300, /* 7e-7f */ DISP8+0x100, DLOCXIND+0x100, /* 80-81 */ DISP16+0x200, DISP8S+0x100, /* 82-83 */ DLOC+0x100, DLOC+0x100, /* 84-85 */ DLOC+0x100, DLOCBRAK+0x100, /* 86-87 */ IMPLY+0x000, IMMED+0x400, /* 88-89 */ IMPLY+0x000, IMPLY+0x000, /* 8a-8b */ ABS+0x200, ABS+0x200, /* 8c-8d */ ABS+0x200, LONG+0x300, /* 8e-8f */ DISP8+0x100, DLOCINDY+0x100, /* 90-91 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 92-93 */ DLOCX+0x100, DLOCX+0x100, /* 94-95 */ DLOCY+0x100, DLOCBRAKY+0x100, /* 96-97 */ IMPLY+0x000, ABSY+0x200, /* 98-99 */ IMPLY+0x000, IMPLY+0x000, /* 9a-9b */ ABS+0x200, ABSX+0x200, /* 9c-9d */ ABSX+0x200, LONGX+0x300, /* 9e-9f */ IMMED+0x500, DLOCXIND+0x100, /* a0-a1 */ IMMED+0x500, DISP8S+0x100, /* a2-a3 */ DLOC+0x100, DLOC+0x100, /* a4-a5 */ DLOC+0x100, DLOCBRAK+0x100, /* a6-a7 */ IMPLY+0x000, IMMED+0x400, /* a8-a9 */ IMPLY+0x000, IMPLY+0x000, /* aa-ab */ ABS+0x200, ABS+0x200, /* ac-ad */ ABS+0x200, LONG+0x300, /* ae-af */ DISP8+0x100, DLOCINDY+0x100, /* b0-b1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* b2-b3 */ DLOCX+0x100, DLOCX+0x100, /* b4-b5 */ DLOCY+0x100, DLOCBRAKY+0x100, /* b6-b7 */ IMPLY+0x000, ABSY+0x200, /* b8-b9 */ IMPLY+0x000, IMPLY+0x000, /* ba-bb */ ABSX+0x200, ABSX+0x200, /* bc-bd */ ABSY+0x200, LONGX+0x300, /* be-bf */ IMMED+0x500, DLOCXIND+0x100, /* c0-c1 */ REPVAL+0x100, DISP8S+0x100, /* c2-c3 */ DLOC+0x100, DLOC+0x100, /* c4-c5 */ DLOC+0x100, DLOCBRAK+0x100, /* c6-c7 */ IMPLY+0x000, IMMED+0x400, /* c8-c9 */ IMPLY+0x000, IMPLY+0x000, /* ca-cb */ ABS+0x200, ABS+0x200, /* cc-cd */ ABS+0x200, LONG+0x300, /* ce-cf */ DISP8+0x100, DLOCINDY+0x100, /* d0-d1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* d2-d3 */ DLOC+0x100, DLOCX+0x100, /* d4-d5 */ DLOCX+0x100, DLOCBRAKY+0x100, /* d6-d7 */ IMPLY+0x000, ABSY+0x200, /* d8-d9 */ IMPLY+0x000, IMPLY+0x000, /* da-db */ ABSIND+0x200, ABSX+0x200, /* dc-dd */ ABSX+0x200, LONGX+0x300, /* de-df */ IMMED+0x500, DLOCXIND+0x100, /* e0-e1 */ SEPVAL+0x100, DISP8S+0x100, /* e2-e3 */ DLOC+0x100, DLOC+0x100, /* e4-e5 */ DLOC+0x100, DLOCBRAK+0x100, /* e6-e7 */ IMPLY+0x000, IMMED+0x400, /* e8-e9 */ IMPLY+0x000, IMPLY+0x000, /* ea-eb */ ABS+0x200, ABS+0x200, /* ec-ed */ ABS+0x200, LONG+0x300, /* ee-ef */ DISP8+0x100, DLOCINDY+0x100, /* f0-f1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* f2-f3 */ IMMED+0x200, DLOCX+0x100, /* f4-f5 */ DLOCX+0x100, DLOCBRAKY+0x100, /* f6-f7 */ IMPLY+0x000, ABSY+0x200, /* f8-f9 */ IMPLY+0x000, IMPLY+0x000, /* fa-fb */ ABSXIND+0x200, ABSX+0x200, /* fc-fd */ ABSX+0x200, LONGX+0x300, /* fe-ff */ }; ================================================ FILE: gsplus/src/doc.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Ensoniq 5503 DOC routines // The Ensoniq DOC is controlled through the Sound GLU at $C03C-$C03F, // where $C03E-$C03F are a 16-bit pointer to "what" to access, $C03D is // the data register, and $C03C is a control register with volume. // The DOC operates at 894.886KHZ (7M clock divided by 8). It visits each // oscillator (up to 32) once per clock, plus 2 extra clocks for DOC RAM // refresh. // KEGS cheats and pretends the DOC runs at 48KHz, and visits all oscillators // each "sample". KEGS adjusts the internal accumulators so the right // frequency is achieved. This allows the sample calculations to be // greatly simplified, and achieves higher fidelity when all 32 osc are // enabled (which is generally how most Apple IIgs code works). #include "defc.h" #include "sound.h" #define DOC_LOG(a,b,c,d) extern int Verbose; extern int g_use_shmem; extern word32 g_vbl_count; extern int g_preferred_rate; extern word32 g_c03ef_doc_ptr; extern dword64 g_last_vbl_dfcyc; byte doc_ram[0x10000 + 16]; word32 g_doc_sound_ctl = 0; word32 g_doc_saved_val = 0; int g_doc_num_osc_en = 1; double g_drecip_osc_en_plus_2 = 1.0 / (double)(1 + 2); int g_doc_vol = 8; int g_doc_saved_ctl = 0; int g_num_osc_interrupting = 0; Doc_reg g_doc_regs[32]; word32 doc_reg_e0 = 0xff; extern double g_drecip_audio_rate; extern double g_dsamps_per_dfcyc; extern double g_fcyc_per_samp; /* local function prototypes */ void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); #define DOC_SCAN_RATE (DCYCS_28_MHZ/32.0) #define UPDATE_G_DCYCS_PER_DOC_UPDATE(osc_en) \ g_drecip_osc_en_plus_2 = 1.0 / (double)(osc_en + 2); #define SND_PTR_SHIFT 14 #define SND_PTR_SHIFT_DBL ((double)(1 << SND_PTR_SHIFT)) void doc_init() { Doc_reg *rptr; int i; for(i = 0; i < 32; i++) { rptr = &(g_doc_regs[i]); rptr->dsamp_ev = 0.0; rptr->dsamp_ev2 = 0.0; rptr->complete_dsamp = 0.0; rptr->samps_left = 0; rptr->cur_acc = 0; rptr->cur_inc = 0; rptr->cur_start = 0; rptr->cur_end = 0; rptr->cur_mask = 0; rptr->size_bytes = 0; rptr->event = 0; rptr->running = 0; rptr->has_irq_pending = 0; rptr->freq = 0; rptr->vol = 0; rptr->waveptr = 0; rptr->ctl = 1; rptr->wavesize = 0; rptr->last_samp_val = 0; } } void doc_reset(dword64 dfcyc) { int i; for(i = 0; i < 32; i++) { doc_write_ctl_reg(dfcyc, i, g_doc_regs[i].ctl | 1); doc_reg_e0 = 0xff; if(g_doc_regs[i].has_irq_pending) { halt_printf("reset: has_irq[%02x] = %d\n", i, g_doc_regs[i].has_irq_pending); } g_doc_regs[i].has_irq_pending = 0; } if(g_num_osc_interrupting) { halt_printf("reset: num_osc_int:%d\n", g_num_osc_interrupting); } g_num_osc_interrupting = 0; g_doc_num_osc_en = 1; UPDATE_G_DCYCS_PER_DOC_UPDATE(1); } int doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, int snd_buf_init, int *outptr_start) { Doc_reg *rptr; int *outptr; double complete_dsamp, cur_dsamp; word32 cur_acc, cur_pos, cur_mask, cur_inc, cur_end; int val, val2, imul, off, ctl, num_osc_en; int samps_left, samps_to_do, samp_offset, pos, osc, done; int i, j; num_osc_en = g_doc_num_osc_en; dbg_log_info(dfcyc, num_samps, 0, 0xd0c0); done = 0; // Do Ensoniq oscillators while(!done) { done = 1; for(j = 0; j < num_osc_en; j++) { osc = j; rptr = &(g_doc_regs[osc]); complete_dsamp = rptr->complete_dsamp; samps_left = rptr->samps_left; cur_acc = rptr->cur_acc; cur_mask = rptr->cur_mask; cur_inc = rptr->cur_inc; cur_end = rptr->cur_end; if(!rptr->running || cur_inc == 0 || (complete_dsamp >= dsamp_now)) { continue; } done = 0; ctl = rptr->ctl; samp_offset = 0; if(complete_dsamp > last_dsamp) { samp_offset = (int)(complete_dsamp- last_dsamp); if(samp_offset > num_samps) { rptr->complete_dsamp = dsamp_now; continue; } } outptr = outptr_start + 2 * samp_offset; if(ctl & 0x10) { /* other channel */ outptr += 1; } imul = (rptr->vol * g_doc_vol); off = imul * 128; samps_to_do = MY_MIN(samps_left, num_samps - samp_offset); if(imul == 0 || samps_to_do == 0) { /* produce no sound */ samps_left = samps_left - samps_to_do; cur_acc += cur_inc * samps_to_do; rptr->samps_left = samps_left; rptr->cur_acc = cur_acc; cur_dsamp = last_dsamp + (double)(samps_to_do + samp_offset); DOC_LOG("nosnd", osc, cur_dsamp, samps_to_do); rptr->complete_dsamp = dsamp_now; cur_pos = rptr->cur_start+(cur_acc & cur_mask); if(samps_left <= 0) { doc_sound_end(osc, 1, cur_dsamp, dsamp_now); val = 0; j--; } else { val = doc_ram[cur_pos >> SND_PTR_SHIFT]; } rptr->last_samp_val = val; continue; } if(snd_buf_init == 0) { memset(outptr_start, 0, 2*sizeof(outptr_start[0])*num_samps); snd_buf_init++; } val = 0; rptr->complete_dsamp = dsamp_now; cur_pos = rptr->cur_start + (cur_acc & cur_mask); pos = 0; for(i = 0; i < samps_to_do; i++) { pos = cur_pos >> SND_PTR_SHIFT; cur_pos += cur_inc; cur_acc += cur_inc; val = doc_ram[pos]; val2 = (val * imul - off) >> 4; if((val == 0) || (cur_pos >= cur_end)) { cur_dsamp = last_dsamp + (double)(samp_offset + i + 1); rptr->cur_acc = cur_acc; rptr->samps_left = 0; DOC_LOG("end or 0", osc, cur_dsamp, ((pos & 0xffffU) << 16) | ((i &0xff) << 8) | val); doc_sound_end(osc, val, cur_dsamp, dsamp_now); val = 0; break; } val2 = outptr[0] + val2; samps_left--; *outptr = val2; outptr += 2; } rptr->last_samp_val = val; if(val != 0) { rptr->cur_acc = cur_acc; rptr->samps_left = samps_left; rptr->complete_dsamp = dsamp_now; } DOC_LOG("splayed", osc, dsamp_now, (samps_to_do << 16) + (pos & 0xffff)); } } return snd_buf_init; } void doc_handle_event(int osc, dword64 dfcyc) { /* handle osc stopping and maybe interrupting */ //DOC_LOG("doc_ev", osc, dsamps, 0); g_doc_regs[osc].event = 0; sound_play(dfcyc); } void doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps) { Doc_reg *rptr, *orptr; int mode, omode; int other_osc; int one_shot_stop; int ctl; /* handle osc stopping and maybe interrupting */ if(osc < 0 || osc > 31) { printf("doc_handle_event: osc: %d!\n", osc); return; } rptr = &(g_doc_regs[osc]); ctl = rptr->ctl; if(rptr->event) { remove_event_doc(osc); } rptr->event = 0; rptr->cur_acc = 0; /* reset internal accumulator*/ /* check to make sure osc is running */ if(ctl & 0x01) { /* Oscillator already stopped. */ halt_printf("Osc %d interrupt, but it was already stop!\n",osc); #ifdef HPUX U_STACK_TRACE(); #endif return; } if(ctl & 0x08) { if(rptr->has_irq_pending == 0) { doc_add_sound_irq(osc); } } if(!rptr->running) { halt_printf("Doc event for osc %d, but ! running\n", osc); } rptr->running = 0; mode = (ctl >> 1) & 3; other_osc = osc ^ 1; orptr = &(g_doc_regs[other_osc]); omode = (orptr->ctl >> 1) & 3; /* If either this osc or it's partner is in swap mode, treat the */ /* pair as being in swap mode. This Ensoniq feature pointed out */ /* by Ian Schmidt */ if(mode == 0 && can_repeat) { /* free-running mode with no 0 byte! */ /* start doing it again */ doc_start_sound(osc, eff_dsamps, dsamps); return; } else if((mode == 3) || (omode == 3)) { /* swap mode (even if we're one_shot and partner is swap)! */ /* unless we're one-shot and we hit a 0-byte--then */ /* Olivier Goguel says just stop */ rptr->ctl |= 1; one_shot_stop = (mode == 1) && (!can_repeat); if(!one_shot_stop && !orptr->running && (orptr->ctl & 0x1)) { orptr->ctl = orptr->ctl & (~1); doc_start_sound(other_osc, eff_dsamps, dsamps); } return; } else { /* stop the oscillator */ rptr->ctl |= 1; } return; } void doc_add_sound_irq(int osc) { int num_osc_interrupting; if(g_doc_regs[osc].has_irq_pending) { halt_printf("Adding sound_irq for %02x, but irq_p: %d\n", osc, g_doc_regs[osc].has_irq_pending); } num_osc_interrupting = g_num_osc_interrupting + 1; g_doc_regs[osc].has_irq_pending = num_osc_interrupting; g_num_osc_interrupting = num_osc_interrupting; add_irq(IRQ_PENDING_DOC); if(num_osc_interrupting == 1) { doc_reg_e0 = 0x00 + (osc << 1); } DOC_LOG("add_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); } void doc_remove_sound_irq(int osc, int must) { Doc_reg *rptr; int num_osc_interrupt; int has_irq_pending; int first; int i; doc_printf("remove irq for osc: %d, has_irq: %d\n", osc, g_doc_regs[osc].has_irq_pending); num_osc_interrupt = g_doc_regs[osc].has_irq_pending; first = 0; if(num_osc_interrupt) { g_num_osc_interrupting--; g_doc_regs[osc].has_irq_pending = 0; DOC_LOG("rem_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); if(g_num_osc_interrupting == 0) { remove_irq(IRQ_PENDING_DOC); } first = 0x40 | (doc_reg_e0 >> 1); /* if none found, then def = no ints */ for(i = 0; i < g_doc_num_osc_en; i++) { rptr = &(g_doc_regs[i]); has_irq_pending = rptr->has_irq_pending; if(has_irq_pending > num_osc_interrupt) { has_irq_pending--; rptr->has_irq_pending = has_irq_pending; } if(has_irq_pending == 1) { first = i; } } if(num_osc_interrupt == 1) { doc_reg_e0 = (first << 1); } else { #if 0 halt_printf("remove_sound_irq[%02x]=%d, first:%d\n", osc, num_osc_interrupt, first); #endif } } else { #if 0 /* make sure no int pending */ if(doc_reg_e0 != 0xff) { halt_printf("remove_sound_irq[%02x]=0, but e0: %02x\n", osc, doc_reg_e0); } #endif if(must) { halt_printf("REMOVE_sound_irq[%02x]=0, but e0: %02x\n", osc, doc_reg_e0); } } if(doc_reg_e0 & 0x80) { for(i = 0; i < 0x20; i++) { has_irq_pending = g_doc_regs[i].has_irq_pending; if(has_irq_pending) { halt_printf("remove_sound_irq[%02x], but " "[%02x]=%d!\n", osc,i,has_irq_pending); printf("num_osc_int: %d, first: %02x\n", num_osc_interrupt, first); } } } } void doc_start_sound2(int osc, dword64 dfcyc) { double dsamps; dsamps = dfcyc * g_dsamps_per_dfcyc; doc_start_sound(osc, dsamps, dsamps); } void doc_start_sound(int osc, double eff_dsamps, double dsamps) { Doc_reg *rptr; int ctl; int mode; word32 sz; word32 size; word32 wave_size; if(osc < 0 || osc > 31) { halt_printf("start_sound: osc: %02x!\n", osc); } rptr = &(g_doc_regs[osc]); if(osc >= g_doc_num_osc_en) { rptr->ctl |= 1; return; } ctl = rptr->ctl; mode = (ctl >> 1) & 3; wave_size = rptr->wavesize; sz = ((wave_size >> 3) & 7) + 8; size = 1 << sz; if(size < 0x100) { halt_printf("size: %08x is too small, sz: %08x!\n", size, sz); } if(rptr->running) { halt_printf("start_sound osc: %d, already running!\n", osc); } rptr->running = 1; rptr->complete_dsamp = eff_dsamps; doc_printf("Starting osc %02x, dsamp: %f\n", osc, dsamps); doc_printf("size: %04x\n", size); if((mode == 2) && ((osc & 1) == 0)) { printf("Sync mode osc %d starting!\n", osc); /* set_halt(1); */ /* see if we should start our odd partner */ if((rptr[1].ctl & 7) == 5) { /* odd partner stopped in sync mode--start him */ rptr[1].ctl &= (~1); doc_start_sound(osc + 1, eff_dsamps, dsamps); } else { printf("Osc %d starting sync, but osc %d ctl: %02x\n", osc, osc+1, rptr[1].ctl); } } doc_wave_end_estimate(osc, eff_dsamps, dsamps); DOC_LOG("st playing", osc, eff_dsamps, size); #if 0 if(rptr->cur_acc != 0) { halt_printf("Start osc %02x, acc: %08x\n", osc, rptr->cur_acc); } #endif } void doc_wave_end_estimate2(int osc, dword64 dfcyc) { double dsamps; dsamps = dfcyc * g_dsamps_per_dfcyc; doc_wave_end_estimate(osc, dsamps, dsamps); } void doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps) { Doc_reg *rptr; byte *ptr1; dword64 event_dfcyc; double event_dsamp, dfcycs_per_samp, dsamps_per_byte, num_dsamps; double dcur_inc; word32 tmp1, cur_inc, save_val; int save_size, pos, size, estimate; dfcycs_per_samp = g_fcyc_per_samp; rptr = &(g_doc_regs[osc]); cur_inc = rptr->cur_inc; dcur_inc = (double)cur_inc; dsamps_per_byte = 0.0; if(cur_inc) { dsamps_per_byte = SND_PTR_SHIFT_DBL / (double)dcur_inc; } /* see if there's a zero byte */ tmp1 = rptr->cur_start + (rptr->cur_acc & rptr->cur_mask); pos = tmp1 >> SND_PTR_SHIFT; size = ((rptr->cur_end) >> SND_PTR_SHIFT) - pos; ptr1 = &doc_ram[pos]; estimate = 0; if(rptr->ctl & 0x08 || g_doc_regs[osc ^ 1].ctl & 0x08) { estimate = 1; } #if 0 estimate = 1; #endif if(estimate) { save_size = size; save_val = ptr1[size]; ptr1[size] = 0; size = (int)strlen((char *)ptr1); ptr1[save_size] = save_val; } /* calc samples to play */ num_dsamps = (dsamps_per_byte * (double)size) + 1.0; rptr->samps_left = (int)num_dsamps; if(rptr->event) { remove_event_doc(osc); } rptr->event = 0; event_dsamp = eff_dsamps + num_dsamps; if(estimate) { rptr->event = 1; rptr->dsamp_ev = event_dsamp; rptr->dsamp_ev2 = dsamps; event_dfcyc = (dword64)(event_dsamp * dfcycs_per_samp) + 65536LL; add_event_doc(event_dfcyc, osc); } } void doc_remove_sound_event(int osc) { if(g_doc_regs[osc].event) { g_doc_regs[osc].event = 0; remove_event_doc(osc); } } void doc_write_ctl_reg(dword64 dfcyc, int osc, int val) { Doc_reg *rptr; word32 old_halt, new_halt; int old_val; if(osc < 0 || osc >= 0x20) { halt_printf("doc_write_ctl_reg: osc: %02x, val: %02x\n", osc, val); return; } rptr = &(g_doc_regs[osc]); old_val = rptr->ctl; g_doc_saved_ctl = old_val; if(old_val == val) { return; } //DOC_LOG("ctl_reg", osc, dsamps, (old_val << 16) + val); old_halt = (old_val & 1); new_halt = (val & 1); /* bits are: 28: old int bit */ /* 29: old halt bit */ /* 30: new int bit */ /* 31: new halt bit */ #if 0 if(osc == 0x10) { printf("osc %d new_ctl: %02x, old: %02x\n", osc, val, old_val); } #endif /* no matter what, remove any pending IRQs on this osc */ doc_remove_sound_irq(osc, 0); #if 0 if(old_halt) { printf("doc_write_ctl to osc %d, val: %02x, old: %02x\n", osc, val, old_val); } #endif if(new_halt != 0) { /* make sure sound is stopped */ doc_remove_sound_event(osc); if(old_halt == 0) { /* it was playing, finish it up */ #if 0 halt_printf("Aborted osc %d at eff_dsamps: %f, ctl: " "%02x, oldctl: %02x\n", osc, eff_dsamps, val, old_val); #endif sound_play(dfcyc); } if(((old_val >> 1) & 3) > 0) { /* don't clear acc if free-running */ g_doc_regs[osc].cur_acc = 0; } g_doc_regs[osc].ctl = val; g_doc_regs[osc].running = 0; } else { /* new halt == 0 = make sure sound is running */ if(old_halt != 0) { /* start sound */ //DOC_LOG("ctl_sound_play", osc, eff_dsamps, val); sound_play(dfcyc); g_doc_regs[osc].ctl = val; doc_start_sound2(osc, dfcyc); } else { /* was running, and something changed */ doc_printf("osc %d old ctl:%02x new:%02x!\n", osc, old_val, val); sound_play(dfcyc); g_doc_regs[osc].ctl = val; if((old_val ^ val) & val & 0x8) { /* now has ints on */ doc_wave_end_estimate2(osc, dfcyc); } } } } void doc_recalc_sound_parms(dword64 dfcyc, int osc) { Doc_reg *rptr; double dfreq, dtmp1, dacc, dacc_recip; word32 res, sz, size, wave_size, cur_start, shifted_size; rptr = &(g_doc_regs[osc]); wave_size = rptr->wavesize; dfreq = (double)rptr->freq; sz = ((wave_size >> 3) & 7) + 8; size = 1 << sz; rptr->size_bytes = size; res = wave_size & 7; shifted_size = size << SND_PTR_SHIFT; cur_start = (rptr->waveptr << (8 + SND_PTR_SHIFT)) & (0-shifted_size); dtmp1 = dfreq * (DOC_SCAN_RATE * g_drecip_audio_rate); dacc = (double)(1 << (20 - (17 - sz + res))); dacc_recip = (SND_PTR_SHIFT_DBL) / ((double)(1 << 20)); dtmp1 = dtmp1 * g_drecip_osc_en_plus_2 * dacc * dacc_recip; rptr->cur_inc = (int)(dtmp1); rptr->cur_start = cur_start; rptr->cur_end = cur_start + shifted_size; rptr->cur_mask = (shifted_size - 1); dbg_log_info(dfcyc, (rptr->waveptr << 16) + wave_size, osc, 0xd0cf); } int doc_read_c03c() { return g_doc_sound_ctl; } int doc_read_c03d(dword64 dfcyc) { Doc_reg *rptr; int osc, type, ret; ret = g_doc_saved_val; if(g_doc_sound_ctl & 0x40) { /* Read RAM */ g_doc_saved_val = doc_ram[g_c03ef_doc_ptr]; } else { /* Read DOC */ g_doc_saved_val = 0; osc = g_c03ef_doc_ptr & 0x1f; type = (g_c03ef_doc_ptr >> 5) & 0x7; rptr = &(g_doc_regs[osc]); switch(type) { case 0x0: /* freq lo */ g_doc_saved_val = rptr->freq & 0xff; break; case 0x1: /* freq hi */ g_doc_saved_val = rptr->freq >> 8; break; case 0x2: /* vol */ g_doc_saved_val = rptr->vol; break; case 0x3: /* data register */ sound_play(dfcyc); // Fix for Paperboy GS g_doc_saved_val = rptr->last_samp_val; break; case 0x4: /* wave ptr register */ g_doc_saved_val = rptr->waveptr; break; case 0x5: /* control register */ g_doc_saved_val = rptr->ctl; break; case 0x6: /* control register */ g_doc_saved_val = rptr->wavesize; break; case 0x7: /* 0xe0-0xff */ switch(osc) { case 0x00: /* 0xe0 */ g_doc_saved_val = doc_reg_e0; doc_printf("Reading doc 0xe0, ret: %02x\n", g_doc_saved_val); /* Clear IRQ on read of e0, if any irq pend */ if((doc_reg_e0 & 0x80) == 0) { doc_remove_sound_irq(doc_reg_e0 >> 1, 1); } break; case 0x01: /* 0xe1 */ g_doc_saved_val = (g_doc_num_osc_en - 1) << 1; break; case 0x02: /* 0xe2 */ g_doc_saved_val = 0x80; #if 0 halt_printf("Reading doc 0xe2, ret: %02x\n", g_doc_saved_val); #endif break; default: g_doc_saved_val = 0; halt_printf("Reading bad doc_reg[%04x]: %02x\n", g_c03ef_doc_ptr, g_doc_saved_val); } break; default: g_doc_saved_val = 0; halt_printf("Reading bad doc_reg[%04x]: %02x\n", g_c03ef_doc_ptr, g_doc_saved_val); } } doc_printf("read c03d, doc_ptr: %04x, ret: %02x, saved: %02x\n", g_c03ef_doc_ptr, ret, g_doc_saved_val); //DOC_LOG("read c03d", -1, dsamps, (g_c03ef_doc_ptr << 16) + // (g_doc_saved_val << 8) + ret); if(g_doc_sound_ctl & 0x20) { g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; } return ret; } void doc_write_c03c(dword64 dfcyc, word32 val) { int vol; vol = val & 0xf; dbg_log_info(dfcyc, val, g_doc_vol, 0xc03c); if(g_doc_vol != vol) { sound_play(dfcyc); g_doc_vol = vol; doc_printf("Setting doc vol to 0x%x at %016llx\n", vol, dfcyc); } g_doc_sound_ctl = val; } void doc_write_c03d(dword64 dfcyc, word32 val) { Doc_reg *rptr; int osc, type, ctl, tmp; int i; val = val & 0xff; doc_printf("write c03d, doc_ptr: %04x, val: %02x\n", g_c03ef_doc_ptr, val); dbg_log_info(dfcyc, g_c03ef_doc_ptr, val, 0xc03d); if(g_doc_sound_ctl & 0x40) { /* RAM */ doc_ram[g_c03ef_doc_ptr] = val; } else { /* DOC */ osc = g_c03ef_doc_ptr & 0x1f; type = (g_c03ef_doc_ptr >> 5) & 0x7; rptr = &(g_doc_regs[osc]); ctl = rptr->ctl; #if 0 if((ctl & 1) == 0) { if(type < 2 || type == 4 || type == 6) { halt_printf("Osc %d is running, old ctl: %02x, " "but write reg %02x=%02x\n", osc, ctl, g_c03ef_doc_ptr & 0xff, val); } } #endif switch(type) { case 0x0: /* freq lo */ if((rptr->freq & 0xff) == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("flo_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->freq = (rptr->freq & 0xff00) + val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x1: /* freq hi */ if((rptr->freq >> 8) == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("fhi_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->freq = (rptr->freq & 0xff) + (val << 8); doc_recalc_sound_parms(dfcyc, osc); break; case 0x2: /* vol */ if(rptr->vol == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("vol_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->vol = val; break; case 0x3: /* data register */ #if 0 printf("Writing %02x into doc_data_reg[%02x]!\n", val, osc); #endif break; case 0x4: /* wave ptr register */ if(rptr->waveptr == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("wptr_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->waveptr = val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x5: /* control register */ #if 0 printf("doc_write ctl osc %d, val: %02x\n", osc, val); #endif if(rptr->ctl == (word32)val) { break; } doc_write_ctl_reg(dfcyc, osc, val); break; case 0x6: /* wavesize register */ if(rptr->wavesize == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("wsz_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->wavesize = val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x7: /* 0xe0-0xff */ switch(osc) { case 0x00: /* 0xe0 */ doc_printf("writing doc 0xe0 with %02x, " "was:%02x\n", val, doc_reg_e0); #if 0 if(val != doc_reg_e0) { halt_printf("writing doc 0xe0 with " "%02x, was:%02x\n", val, doc_reg_e0); } #endif break; case 0x01: /* 0xe1 */ doc_printf("Writing doc 0xe1 with %02x\n", val); tmp = val & 0x3e; tmp = (tmp >> 1) + 1; if(tmp < 1) { tmp = 1; } if(tmp > 32) { halt_printf("doc 0xe1: %02x!\n", val); tmp = 32; } g_doc_num_osc_en = tmp; UPDATE_G_DCYCS_PER_DOC_UPDATE(tmp); /* Stop any oscs that were running but now */ /* are disabled */ for(i = g_doc_num_osc_en; i < 0x20; i++) { doc_write_ctl_reg(dfcyc, i, g_doc_regs[i].ctl | 1); } break; default: /* this should be illegal, but Turkey Shoot */ /* and apparently TaskForce, OOTW, etc */ /* writes to e2-ff, for no apparent reason */ doc_printf("Writing doc 0x%x with %02x\n", g_c03ef_doc_ptr, val); break; } break; default: halt_printf("Writing %02x into bad doc_reg[%04x]\n", val, g_c03ef_doc_ptr); } } if(g_doc_sound_ctl & 0x20) { g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; } g_doc_saved_val = val; } void doc_show_ensoniq_state() { Doc_reg *rptr; int i; printf("Ensoniq state\n"); printf("c03c doc_sound_ctl: %02x, doc_saved_val: %02x\n", g_doc_sound_ctl, g_doc_saved_val); printf("doc_ptr: %04x, num_osc_en: %02x, e0: %02x\n", g_c03ef_doc_ptr, g_doc_num_osc_en, doc_reg_e0); for(i = 0; i < 32; i += 8) { printf("irqp: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", i, g_doc_regs[i].has_irq_pending, g_doc_regs[i + 1].has_irq_pending, g_doc_regs[i + 2].has_irq_pending, g_doc_regs[i + 3].has_irq_pending, g_doc_regs[i + 4].has_irq_pending, g_doc_regs[i + 5].has_irq_pending, g_doc_regs[i + 6].has_irq_pending, g_doc_regs[i + 7].has_irq_pending); } for(i = 0; i < 32; i += 8) { printf("freq: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", i, g_doc_regs[i].freq, g_doc_regs[i + 1].freq, g_doc_regs[i + 2].freq, g_doc_regs[i + 3].freq, g_doc_regs[i + 4].freq, g_doc_regs[i + 5].freq, g_doc_regs[i + 6].freq, g_doc_regs[i + 7].freq); } for(i = 0; i < 32; i += 8) { printf("vol: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].vol, g_doc_regs[i + 1].vol, g_doc_regs[i + 2].vol, g_doc_regs[i + 3].vol, g_doc_regs[i + 4].vol, g_doc_regs[i + 5].vol, g_doc_regs[i + 6].vol, g_doc_regs[i + 6].vol); } for(i = 0; i < 32; i += 8) { printf("wptr: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].waveptr, g_doc_regs[i + 1].waveptr, g_doc_regs[i + 2].waveptr, g_doc_regs[i + 3].waveptr, g_doc_regs[i + 4].waveptr, g_doc_regs[i + 5].waveptr, g_doc_regs[i + 6].waveptr, g_doc_regs[i + 7].waveptr); } for(i = 0; i < 32; i += 8) { printf("ctl: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].ctl, g_doc_regs[i + 1].ctl, g_doc_regs[i + 2].ctl, g_doc_regs[i + 3].ctl, g_doc_regs[i + 4].ctl, g_doc_regs[i + 5].ctl, g_doc_regs[i + 6].ctl, g_doc_regs[i + 7].ctl); } for(i = 0; i < 32; i += 8) { printf("wsize: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].wavesize, g_doc_regs[i + 1].wavesize, g_doc_regs[i + 2].wavesize, g_doc_regs[i + 3].wavesize, g_doc_regs[i + 4].wavesize, g_doc_regs[i + 5].wavesize, g_doc_regs[i + 6].wavesize, g_doc_regs[i + 7].wavesize); } for(i = 0; i < 32; i++) { rptr = &(g_doc_regs[i]); printf("%2d: ctl:%02x wp:%02x ws:%02x freq:%04x vol:%02x " "ev:%d run:%d irq:%d sz:%04x\n", i, rptr->ctl, rptr->waveptr, rptr->wavesize, rptr->freq, rptr->vol, rptr->event, rptr->running, rptr->has_irq_pending, rptr->size_bytes); printf(" acc:%08x inc:%08x st:%08x end:%08x m:%08x\n", rptr->cur_acc, rptr->cur_inc, rptr->cur_start, rptr->cur_end, rptr->cur_mask); printf(" compl_ds:%f samps_left:%d ev:%f ev2:%f\n", rptr->complete_dsamp, rptr->samps_left, rptr->dsamp_ev, rptr->dsamp_ev2); } #if 0 for(osc = 0; osc < 32; osc++) { fmax = 0.0; printf("osc %d has %d samps\n", osc, g_fsamp_num[osc]); for(i = 0; i < g_fsamp_num[osc]; i++) { printf("%4d: %f\n", i, g_fsamps[osc][i]); fmax = MY_MAX(fmax, g_fsamps[osc][i]); } printf("osc %d, fmax: %f\n", osc, fmax); } #endif } ================================================ FILE: gsplus/src/dyna_filt.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" // Provide filters for Dynapro to use for copying files from the host to // a ProDOS volume, and for writing changes to the ProDOS volume back to // host files. ================================================ FILE: gsplus/src/dyna_type.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2021-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" // Provide routines for Dynapro to use for detecting the file type on the // host system. Host files can be "basic1.bas", "basic2,tbas,a$801" STRUCT(Dynatype_extensions) { char str[16]; word16 file_type; word16 aux_type; }; Dynatype_extensions g_dynatype_extensions[] = { { "applesingle", 0xfff, 0xffff }, { "txt", 0x04, 0 }, { "c", 0x04, 0 }, // ,ttxt { "s", 0x04, 0 }, // ,ttxt { "h", 0x04, 0 }, // ,ttxt { "bin", 0x06, 0x2000 }, // ,tbin { "bas", 0xfc, 0x0801 }, // ,tbas { "system", 0xff, 0x2000 }, // ,tsys //{ "shr", 0xc0, 0x0002 }, // ,t$c0 { "shk", 0xe0, 0x8002 }, // ,t$e0 { "sdk", 0xe0, 0x8002 }, // ,t$e0 { "", 0, 0 } }; STRUCT(Dynatype_types) { char str[16]; word16 file_type; word16 aux_type; }; Dynatype_types g_dynatype_types[] = { { "non", 0x00, 0 }, { "bad", 0x01, 0 }, { "txt", 0x04, 0 }, { "bin", 0x06, 0x2000 }, { "pnt", 0xc0, 0x0002 }, { "fnd", 0xc9, 0 }, { "icn", 0xca, 0 }, { "cmd", 0xf0, 0 }, { "bas", 0xfc, 0x0801 }, { "sys", 0xff, 0x2000 }, { "", 0, 0 } }; word32 dynatype_scan_extensions(const char *str) { int len; int i; len = (int)(sizeof(g_dynatype_extensions) / sizeof(g_dynatype_extensions[0])); for(i = 0; i < len; i++) { if(cfgcasecmp(str, g_dynatype_extensions[i].str) == 0) { return (g_dynatype_extensions[i].file_type << 16) | g_dynatype_extensions[i].aux_type | 0x1000000; } } return 0; } word32 dynatype_find_prodos_type(const char *str) { word32 file_type; int len; int i; len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); for(i = 0; i < len; i++) { if(cfgcasecmp(str, g_dynatype_types[i].str) == 0) { file_type = g_dynatype_types[i].file_type; return (file_type << 16) | g_dynatype_types[i].aux_type; } } return 0; } const char * dynatype_find_file_type(word32 file_type) { int len; int i; len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); for(i = 0; i < len; i++) { if(g_dynatype_types[i].file_type == file_type) { return g_dynatype_types[i].str; } } return 0; } word32 dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, word32 storage_type) { char ext_buf[32]; const char *str; char *endstr; word32 file_type, aux_type, type_or_aux; int len, this_len, c, pos; // Look for ,tbas and ,a$2000 to get filetype and aux_type info str = cfg_str_basename(path_ptr); len = (int)strlen(str); // Look for .ext and ,tbas, etc. pos = 0; ext_buf[0] = 0; file_type = 0x06; // Default to BIN aux_type = 0; while(pos < len) { c = str[pos++]; if(c == '.') { this_len = dynatype_get_extension(&str[pos], &ext_buf[0], 30); pos += this_len; continue; } else if(c == ',') { this_len = dynatype_comma_arg(&str[pos], &type_or_aux); if(type_or_aux & 0x1000000) { file_type = type_or_aux; } else if(type_or_aux & 0x2000000) { aux_type = type_or_aux; } else { printf("Unknown , extension, %s ignored\n", &str[pos]); } pos += this_len; continue; } else if(c == '#') { // Cadius style encoding: #ff2000 is type=$ff, aux=$2000 type_or_aux = strtol(&str[pos], &endstr, 16); file_type = (type_or_aux & 0xffffff) | 0x1000000; aux_type = 0; pos += (int)(endstr - str); continue; } } // Handle extensions and type. First do extension mapping if(ext_buf[0]) { type_or_aux = dynatype_scan_extensions(&ext_buf[0]); if((type_or_aux) >= 0x0f000000UL) { // AppleSingle storage_type = 0x50; // Forked file } if(file_type < 0x1000000) { file_type = type_or_aux; } if(aux_type < 0x1000000) { aux_type = type_or_aux; } } #if 0 printf("After parsing ext, file_type:%08x, aux_type:%08x\n", file_type, aux_type); #endif fileptr->file_type = (file_type >> 16) & 0xff; if(aux_type == 0) { aux_type = file_type & 0xffff; } fileptr->aux_type = aux_type & 0xffff; return storage_type; } int dynatype_get_extension(const char *str, char *out_ptr, int buf_len) { int c, len; // Will write up to buf_len chars to out_ptr if(buf_len < 1) { return 0; } buf_len--; len = 0; while(1) { c = *str++; *out_ptr = c; if((c == 0) || (c == '.') || (c == ',') || (c == '#') || (len >= buf_len)) { *out_ptr = 0; return len; } out_ptr++; len++; } } int dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr) { char type_buf[8]; char *endstr; word32 val, type_or_aux; int c, len, base, this_len; int i; // Read next char *type_or_aux_ptr = 0; c = *str++; if(c == 0) { return 0; } len = 1; c = tolower(c); type_or_aux = c; // See if next char is $ for hex c = *str; base = 0; if(c == '$') { base = 16; str++; len++; } val = strtol(str, &endstr, base); this_len = (int)(endstr - str); if((val == 0) && (this_len < 2) && (base == 0) && (type_or_aux == 't')) { // Not a valid number for(i = 0; i < 3; i++) { c = *str++; if(c == 0) { return len; } type_buf[i] = c; len++; } type_buf[3] = 0; val = dynatype_find_prodos_type(&type_buf[0]); *type_or_aux_ptr = 0x1000000 | val; } else { len += this_len; } if(type_or_aux == 't') { if(val < 0x100) { *type_or_aux_ptr = 0x1000000 | ((val << 16) & 0xffffff); } } else if(type_or_aux == 'a') { *type_or_aux_ptr = 0x2000000 | (val & 0xffff); } return len; } void dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max) { char buf[16]; Dynapro_file tmpfile; const char *str; word32 aux_type; #if 0 printf("Looking at %s ftype:%02x aux:%04x\n", outbuf_ptr, fileptr->file_type, fileptr->aux_type); #endif if(fileptr->prodos_name[0] >= 0xd0) { return; // Directory, or Dir/Volume Header } if((fileptr->prodos_name[0] & 0xf0) == 0x50) { // Forked file, add .applesingle cfg_strlcat(outbuf_ptr, ".applesingle", path_max); return; } memset(&tmpfile, 0, sizeof(Dynapro_file)); // See what this file defaults to as to type/aux (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); // Otherwise, add ,ttype and ,a$aux as needed if(tmpfile.file_type != fileptr->file_type) { str = dynatype_find_file_type(fileptr->file_type); if(str) { aux_type = dynatype_find_prodos_type(str); } else { str = &buf[0]; buf[15] = 0; snprintf(&buf[0], 15, "$%02x", fileptr->file_type); } cfg_strlcat(outbuf_ptr, ",t", path_max); cfg_strlcat(outbuf_ptr, str, path_max); } (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); aux_type = fileptr->aux_type; if(aux_type != tmpfile.aux_type) { buf[15] = 0; snprintf(&buf[0], 15, ",a$%04x", aux_type & 0xffff); cfg_strlcat(outbuf_ptr, &buf[0], path_max); } // printf("dynatype_new_unix_name: %s\n", outbuf_ptr); // Check that it succeeded (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); if((tmpfile.file_type != fileptr->file_type) || (tmpfile.aux_type != fileptr->aux_type)) { halt_printf("File %s want ftype:%02x aux:%04x, got:%02x %04x\n", outbuf_ptr, fileptr->file_type, fileptr->aux_type, tmpfile.file_type, tmpfile.aux_type); exit(1); } } ================================================ FILE: gsplus/src/dyna_validate.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2021-2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Main information is from Beneath Apple ProDOS which has disk layout // descriptions. Forked files are described in Technote tn-pdos-025. #include "defc.h" word32 dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, word32 parent_dir_byte) { word32 storage_type, exp_type, val, parent_block, exp_val; storage_type = fileptr->prodos_name[0] & 0xf0; exp_type = 0xe0; if(dir_byte == 0x0404) { exp_type = 0xf0; // Volume header } if(storage_type != exp_type) { printf("Volume/Dir header is %02x at %07x\n", storage_type, dir_byte); return 0; } if(fileptr->aux_type != 0x0d27) { printf("entry_length, entries_per_block:%04x at %07x\n", fileptr->aux_type, dir_byte); return 0; } if(exp_type == 0xf0) { // Volume header val = fileptr->lastmod_time >> 16; if(val != 6) { printf("bit_map_ptr:%04x, should be 6\n", val); return 0; } val = fileptr->header_pointer; if(val != (dsk->dimage_size >> 9)) { printf("Num blocks at %07x is wrong: %04x\n", dir_byte, val); return 0; } } else { // Directory header val = fileptr->lastmod_time >> 16; // parent_pointer parent_block = parent_dir_byte >> 9; if(val != parent_block) { printf("Dir at %07x parent:%04x should be %04x\n", dir_byte, val, parent_block); return 0; } val = fileptr->header_pointer; exp_val = ((parent_dir_byte & 0x1ff) - 4) / 0x27; exp_val = (exp_val + 1) | 0x2700; if(val != exp_val) { printf("Parent entry at %07x is:%04x, should be:%04x\n", dir_byte, val, exp_val); return 0; } } return 1; } void dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks) { word32 num_map_blocks, mask; int pos; word32 ui; for(ui = 0; ui < (num_blocks + 7)/8; ui++) { freeblks_ptr[ui] = 0xff; } freeblks_ptr[0] &= 0x3f; if(num_blocks & 7) { freeblks_ptr[num_blocks / 8] = 0xff00 >> (num_blocks & 7); } num_map_blocks = (num_blocks + 4095) >> 12; // 4096 bits per block for(ui = 0; ui < num_map_blocks; ui++) { // Mark blocks used in the bitmap as in use pos = (ui + 6) >> 3; mask = 0x80 >> ((ui + 6) & 7); freeblks_ptr[pos] &= (~mask); } } word32 dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block) { word32 mask, ret; int pos; // Return != 0 if block is free (which is success), returns == 0 // if it is in use (which is an error). Marks block as in use pos = block >> 3; if(block >= (dsk->dimage_size >> 9)) { return 0x100; // Out of range } mask = 0x80 >> (block & 7); ret = freeblks_ptr[pos] & mask; freeblks_ptr[pos] &= (~mask); if(!ret) { printf("Block %04x was already in use\n", block); } return ret; } word32 dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof, int level_first) { byte *bptr; word32 num_blocks, tmp, ret, exp_blocks, extra_blocks; int level, first; int i; level = level_first & 0xf; first = level_first & 0x10; if(!dynapro_validate_freeblk(dsk, freeblks_ptr, block_num)) { return 0; } if(level_first == 0x15) { return dynapro_validate_forked_file(dsk, freeblks_ptr, block_num, eof); } if((level < 1) || (level >= 4)) { printf("level %d out of range, %08x\n", level, level_first); return 0; } if(level == 1) { return 1; } num_blocks = 1; bptr = &(dsk->raw_data[block_num * 0x200]); for(i = 0; i < 256; i++) { tmp = bptr[i] + (bptr[256 + i] << 8); if(tmp == 0) { if(first) { printf("First block is spare, illegal!\n"); return 0; } continue; } ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, first | (level - 1)); if(ret == 0) { return 0; } num_blocks += ret; first = 0; } if(level_first & 0x10) { // Try to estimate exp_blocks based on eof exp_blocks = (eof + 0x1ff) >> 9; if(exp_blocks == 0) { exp_blocks = 1; } else if(exp_blocks > 1) { // Add in sapling blocks extra_blocks = ((exp_blocks + 255) >> 8); if(exp_blocks > 256) { extra_blocks++; } exp_blocks += extra_blocks; } if(num_blocks > exp_blocks) { printf("blocks_used:%04x, eof:%07x, exp:%04x\n", num_blocks, eof, exp_blocks); return 0; } } return num_blocks; } word32 dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof) { byte *bptr; word32 num_blocks, tmp, ret, size, type, exp_blocks; int level; int i; bptr = &(dsk->raw_data[block_num * 0x200]); if(eof != 0x200) { printf("In forked file block %04x, eof in dir:%08x, exp 0200\n", block_num, eof); return 0; } // Check that most of the block is 0 for(i = 44; i < 512; i++) { if((i >= 0x100) && (i < 0x108)) { continue; } if(bptr[i] != 0) { printf("In forked file block:%04x, byte %03x is %02x\n", block_num, i, bptr[i]); return 0; } } // Check for basic Finder Info format for(i = 0; i < 2; i++) { size = bptr[8 + 18*i]; type = bptr[9 + 18*i]; if(((size != 0) && (size != 18)) || (type > 2)) { printf("Finder Info size %04x+%03x=%02x, type:%02x\n", block_num, 8 + 18*i, size, type); return 0; } } num_blocks = 1; for(i = 0; i < 2; i++) { tmp = bptr[1 + 0x100*i] + (bptr[2 + 0x100*i] << 8); if(tmp == 0) { printf("First fork %d block is spare, illegal!\n", i); return 0; } eof = bptr[5 + 0x100*i] + (bptr[6 + 0x100*i] << 8) + (bptr[7 + 0x100*i] << 16); level = bptr[0 + 0x100*i]; ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, 0x10 | level); if(ret == 0) { printf("Fork %d failed, eof:%08x, block:%04x " "fork:%04x, level:%d\n", i, eof, block_num, tmp, level); return 0; } exp_blocks = bptr[3 + 0x100*i] + (bptr[4 + 0x100*i] << 8); if(ret != exp_blocks) { printf("Fork %d at %04x, blocks:%04x, exp:%04x\n", i, block_num, ret, exp_blocks); } num_blocks += ret; } return num_blocks; } word32 dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, word32 parent_dir_byte, word32 exp_blocks_used) { char buf32[32]; Dynapro_file localfile; byte *bptr; word32 start_dir_block, last_block, max_block, tmp_byte, sub_blocks; word32 ret, act_entries, exp_entries, blocks_used, prev, next; int cnt, is_header; // Read directory, make sure each entry is consistent // Return 0 if there is damage, != 0 if OK. bptr = dsk->raw_data; start_dir_block = dir_byte >> 9; last_block = 0; max_block = (word32)(dsk->dimage_size >> 9); cnt = 0; is_header = 1; exp_entries = 0xdeadbeef; act_entries = 0; blocks_used = 0; while(dir_byte) { if((dir_byte & 0x1ff) == 4) { // First entry in this block, check prev/next tmp_byte = dir_byte & -0x200; // Block align prev = dynapro_get_word16(&bptr[tmp_byte + 0]); next = dynapro_get_word16(&bptr[tmp_byte + 2]); if((prev != last_block) || (next >= max_block)) { printf("dir at %07x is damaged in links\n", dir_byte); return 0; } last_block = dir_byte >> 9; ret = dynapro_validate_freeblk(dsk, freeblks_ptr, dir_byte >> 9); if(!ret) { return 0; } blocks_used++; } if(cnt++ >= 65536) { printf("Loop detected, dir_byte:%07x\n", dir_byte); return 0; } ret = dynapro_fill_fileptr_from_prodos(dsk, &localfile, &buf32[0], dir_byte); if(ret == 0) { return 0; } if(ret != 1) { act_entries = act_entries + 1 - is_header; } if(is_header) { if(ret == 1) { printf("Volume/Dir header is erased\n"); return 0; } ret = dynapro_validate_header(dsk, &localfile, dir_byte, parent_dir_byte); if(ret == 0) { return 0; } exp_entries = localfile.lastmod_time & 0xffff; } else if(ret != 1) { if(localfile.header_pointer != start_dir_block) { printf("At %07x, header_ptr:%04x != %04x\n", dir_byte, localfile.header_pointer, start_dir_block); return 0; } if(localfile.prodos_name[0] >= 0xd0) { sub_blocks = localfile.blocks_used; if(localfile.eof != (sub_blocks * 0x200UL)) { printf("At %07x, eof:%08x != %08x\n", dir_byte, localfile.eof, sub_blocks * 0x200U); return 0; } ret = dynapro_validate_dir(dsk, freeblks_ptr, (localfile.key_block * 0x200) + 4, dir_byte, sub_blocks); if(ret == 0) { return 0; } } else { ret = dynapro_validate_file(dsk, freeblks_ptr, localfile.key_block, localfile.eof, 0x10 | (localfile.prodos_name[0] >> 4)); if(ret == 0) { printf("At %07x, bad file\n", dir_byte); return 0; } if(localfile.blocks_used != ret) { printf("At %07x, blocks_used prodos " "%04x != %04x calc\n", dir_byte, localfile.blocks_used, ret); return 0; } } } is_header = 0; dir_byte = dir_byte + 0x27; tmp_byte = (dir_byte & 0x1ff) + 0x27; if(tmp_byte < 0x200) { continue; } tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; if(dir_byte == 0) { if(act_entries != exp_entries) { printf("act_entries:%04x != exp:%04x, " "dir_block:%04x\n", act_entries, exp_entries, start_dir_block); return 0; } if(blocks_used != exp_blocks_used) { printf("At dir %07x, blocks_used:%04x!=%04x " "exp\n", tmp_byte, blocks_used, exp_blocks_used); return 0; } return 1; } dir_byte += 4; if(dir_byte >= (max_block * 0x200L)) { printf(" invalid link pointer %07x\n", dir_byte); return 0; } } return 0; } int dynapro_validate_disk(Disk *dsk) { byte freeblks[65536/8]; // 8KB byte *bptr; word32 num_blocks, ret; word32 ui; num_blocks = (word32)(dsk->dimage_size >> 9); printf("******************************\n"); printf("Validate disk: %s, blocks:%05x\n", dsk->name_ptr, num_blocks); dynapro_validate_init_freeblks(&freeblks[0], num_blocks); // Validate starting at directory in block 2 ret = dynapro_validate_dir(dsk, &freeblks[0], 0x0404, 0, 4); if(!ret) { printf("Disk does not validate!\n"); exit(1); return ret; } // Check freeblks bptr = &(dsk->raw_data[6*0x200]); for(ui = 0; ui < (num_blocks + 7)/8; ui++) { if(freeblks[ui] != bptr[ui]) { printf("Expected free mask for blocks %04x-%04x:%02x, " "but it is %02x\n", ui*8, ui*8 + 7, freeblks[ui], bptr[ui]); exit(1); return 0; } } return 1; } void dynapro_validate_any_image(Disk *dsk) { byte *bufptr; dword64 dsize; int ret; // If dsk->raw_data already set, just use it. Otherwise, we need to // temporarily read in entire image, set it, do validate, and then // free it if(dsk->fd < 0) { return; // No disk } if(dsk->wozinfo_ptr) { return; } dsize = dsk->dimage_size; bufptr = 0; if((dsize >> 31) != 0) { printf("Disk is too large, not valid\n"); ret = 0; } else if(dsk->raw_data == 0) { bufptr = malloc((size_t)dsize); dsk->raw_data = bufptr; cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); ret = dynapro_validate_disk(dsk); dsk->raw_data = 0; free(bufptr); } else { ret = dynapro_validate_disk(dsk); } printf("validate_disk returned is_good: %d (0 is bad)\n", ret); } ================================================ FILE: gsplus/src/dynapro.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2021-2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Main information is from Beneath Apple ProDOS which has disk layout // descriptions. Upper/lowercase is from Technote tn-gsos-008, and // forked files are storage_type $5 from Technote tn-pdos-025. #include "defc.h" #ifdef _WIN32 # include "win_dirent.h" #else # include #endif #include extern int Verbose; #define DYNAPRO_PATH_MAX 2048 char g_dynapro_path_buf[DYNAPRO_PATH_MAX]; extern word32 g_vbl_count, g_iwm_dynapro_last_vbl_count; byte g_prodos_block0[512] = { // From Beagle Bros Pro-Byter disk // This is a ProDOS boot block, able to load PRODOS and jump to it 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x32, 0xa1, 0x86, // 0x000 0x43, 0xc9, 0x03, 0x08, 0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0, // 0x010 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e, 0xa9, 0x03, 0x8d, 0x00, 0x08, // 0x020 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, 0x48, 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x63, 0xb1, // 0x030 0x48, 0x99, 0x94, 0x09, 0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x1d, 0x09, 0xbd, 0x24, // 0x040 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x2b, 0x09, 0x9d, 0x7f, 0x0a, 0xca, 0x10, 0xee, 0xa9, 0x09, 0x85, // 0x050 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, 0xb0, 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, // 0x060 0x4c, 0x84, 0x4e, 0x84, 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85, // 0x070 0x4b, 0x20, 0x12, 0x09, 0xb0, 0x68, 0xe6, 0x61, 0xe6, 0x61, 0xe6, 0x46, 0xa5, 0x46, 0xc9, 0x06, // 0x080 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, 0x0c, 0xd0, 0x6d, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, // 0x090 0x18, 0x6d, 0x23, 0x0c, 0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a, // 0x0a0 0xf0, 0x55, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x02, 0x09, 0x29, 0x0f, 0xa8, 0xb1, 0x4a, 0xd9, 0x02, // 0x0b0 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0x29, 0xf0, 0xc9, 0x20, 0xd0, 0x3b, 0xa0, 0x10, 0xb1, 0x4a, // 0x0c0 0xc9, 0xff, 0xd0, 0x33, 0xc8, 0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, // 0x0d0 0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20, 0x12, 0x09, 0xb0, 0x17, // 0x0e0 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, 0xb1, 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, // 0x0f0 0x11, 0x4a, 0xd0, 0xe7, 0x4c, 0x00, 0x20, 0x4c, 0x3f, 0x09, 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, // 0x100 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa5, 0x60, 0x85, 0x44, 0xa5, 0x61, // 0x110 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, 0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, // 0x120 0x4b, 0xb4, 0xac, 0xa6, 0x2b, 0x18, 0x60, 0x4c, 0xbc, 0x09, 0xa9, 0x9f, 0x48, 0xa9, 0xff, 0x48, // 0x130 0xa9, 0x01, 0xa2, 0x00, 0x4c, 0x79, 0xf4, 0x20, 0x58, 0xfc, 0xa0, 0x1c, 0xb9, 0x50, 0x09, 0x99, // 0x140 0xae, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x4d, 0x09, 0xaa, 0xaa, 0xaa, 0xa0, 0xd5, 0xce, 0xc1, 0xc2, // 0x150 0xcc, 0xc5, 0xa0, 0xd4, 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf, // 0x160 0xd3, 0xa0, 0xaa, 0xaa, 0xaa, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, 0x80, 0xc0, // 0x170 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, // 0x180 0x29, 0x07, 0xc9, 0x04, 0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, // 0x190 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85, 0x27, 0xa6, 0x2b, 0xbd, // 0x1a0 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, // 0x1b0 0xbc, 0x88, 0xc0, 0x60, 0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, // 0x1c0 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02, 0xc6, 0x53, 0x38, 0x20, // 0x1d0 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, // 0x1e0 0x38, 0xc6, 0x52, 0xf0, 0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x00, // 0x1f0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; word32 dynapro_get_word32(byte *bptr) { return (bptr[3] << 24) | (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; } word32 dynapro_get_word24(byte *bptr) { return (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; } word32 dynapro_get_word16(byte *bptr) { return (bptr[1] << 8) | bptr[0]; } void dynapro_set_word24(byte *bptr, word32 val) { // Write 3 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); *bptr++ = (val >> 16); } void dynapro_set_word32(byte *bptr, word32 val) { // Write 4 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); *bptr++ = (val >> 16); *bptr++ = (val >> 24); } void dynapro_set_word16(byte *bptr, word32 val) { // Write 2 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); } void dynapro_error(Disk *dsk, const char *fmt, ...) { Dynapro_info *info_ptr; va_list args; va_start(args, fmt); cfg_err_vprintf("Dynapro", fmt, args); va_end(args); info_ptr = dsk->dynapro_info_ptr; if(info_ptr) { cfg_err_printf("", "Path: %s\n", info_ptr->root_path); } } Dynapro_file * dynapro_alloc_file() { Dynapro_file *fileptr; fileptr = calloc(1, sizeof(Dynapro_file)); return fileptr; } void dynapro_free_file(Dynapro_file *fileptr, int check_map) { if(!fileptr) { return; } if(fileptr->subdir_ptr) { dynapro_free_recursive_file(fileptr->subdir_ptr, check_map); } fileptr->subdir_ptr = 0; free(fileptr->unix_path); fileptr->unix_path = 0; free(fileptr->buffer_ptr); fileptr->buffer_ptr = 0; fileptr->next_ptr = 0; // printf("FREE %p\n", fileptr); if(check_map && (fileptr->map_first_block != 0)) { printf(" ERROR: map_first_block is %08x\n", fileptr->map_first_block); exit(1); } free(fileptr); } void dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map) { Dynapro_file *nextptr; if(!fileptr) { return; } // printf("free_recursive %s\n", fileptr->unix_path); while(fileptr) { nextptr = fileptr->next_ptr; dynapro_free_file(fileptr, check_map); fileptr = nextptr; }; } void dynapro_free_dynapro_info(Disk *dsk) { Dynapro_info *info_ptr; info_ptr = dsk->dynapro_info_ptr; if(info_ptr) { free(info_ptr->root_path); dynapro_free_recursive_file(info_ptr->volume_ptr, 0); info_ptr->volume_ptr = 0; } free(info_ptr); dsk->dynapro_info_ptr = 0; } word32 dynapro_find_free_block_internal(Disk *dsk) { byte *bptr; word32 num_blocks, bitmap_size_bytes, val, mask; word32 ui; int j; num_blocks = (word32)(dsk->raw_dsize >> 9); bitmap_size_bytes = (num_blocks + 7) >> 3; bptr = &(dsk->raw_data[6 * 512]); // Block 6 for(ui = 0; ui < bitmap_size_bytes; ui++) { val = bptr[ui]; if(val == 0) { continue; } mask = 0x80; for(j = 0; j < 8; j++) { if(val & mask) { bptr[ui] = val & (~mask); return 8*ui + j; } mask = mask >> 1; } return 0; } return 0; } word32 dynapro_find_free_block(Disk *dsk) { byte *bptr; word32 block; int i; // Find first free block, and zero it out block = dynapro_find_free_block_internal(dsk); if(block == 0) { return 0; } bptr = &(dsk->raw_data[block * 512]); for(i = 0; i < 512; i++) { bptr[i] = 0; } return block; } byte * dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size) { byte *bptr; dword64 dsize, dpos; int fd; int i; *dsize_ptr = 0; fd = open(path_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return 0; } dsize = cfg_get_fd_size(fd); if((size_t)(dsize + extra_size) != (dsize + extra_size)) { return 0; } bptr = malloc((size_t)(dsize + extra_size)); if(bptr == 0) { return bptr; } // printf("dynapro_malloc_file %p, size:%08lld\n", bptr, dsize); for(i = 0; i < extra_size; i++) { bptr[dsize + i] = 0; } dpos = cfg_read_from_fd(fd, bptr, 0, dsize); close(fd); if(dpos != dsize) { free(bptr); return 0; } *dsize_ptr = dsize; return bptr; } void dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, int path_max) { int len; // Create "unix_path" + "/" + "str" in outstr (which has size path_max) cfg_strncpy(outstr, unix_path, path_max); len = (int)strlen(outstr); if((len > 0) && (outstr[len - 1] != '/')) { cfg_strlcat(outstr, "/", path_max); } cfg_strlcat(outstr, str, path_max); } word32 dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, char *buf32_ptr, word32 dir_byte) { byte *bptr; word32 upper_lower; int len, c; int i; buf32_ptr[0] = 0; if((dir_byte < 0x400) || (dir_byte >= dsk->dimage_size)) { return 0; // Directory is damaged } if(!fileptr) { return 0; } bptr = &(dsk->raw_data[dir_byte]); memset(fileptr, 0, sizeof(Dynapro_file)); fileptr->dir_byte = dir_byte; fileptr->file_type = bptr[0x10]; fileptr->key_block = dynapro_get_word16(&bptr[0x11]); fileptr->blocks_used = dynapro_get_word16(&bptr[0x13]); fileptr->eof = dynapro_get_word24(&bptr[0x15]); //printf("Filling from entry %07x, eof:%06x\n", dir_byte, fileptr->eof); fileptr->creation_time = dynapro_get_word32(&bptr[0x18]); fileptr->upper_lower = dynapro_get_word16(&bptr[0x1c]); fileptr->aux_type = dynapro_get_word16(&bptr[0x1f]); fileptr->lastmod_time = dynapro_get_word32(&bptr[0x21]); fileptr->header_pointer = dynapro_get_word16(&bptr[0x25]); if(dir_byte == 0x404) { // Volume header fileptr->upper_lower = dynapro_get_word32(&bptr[0x1a]); fileptr->creation_time &= 0xffff; } len = (bptr[0] & 0xf) + 1; upper_lower = fileptr->upper_lower; if((upper_lower & 0x8000) == 0) { // Not valid upper_lower = 0; } for(i = 0; i < 16; i++) { c = bptr[i]; if(i > len) { c = 0; } fileptr->prodos_name[i] = c; if(i > 0) { if(upper_lower & 0x4000) { if((c >= 'A') && (c <= 'Z')) { c = c - 'A' + 'a'; // Make lower } } upper_lower = upper_lower << 1; buf32_ptr[i - 1] = c; buf32_ptr[i] = 0; } } if(((bptr[0] & 0xf0) == 0) || ((bptr[0] & 0xf) == 0)) { fileptr->prodos_name[0] = 0; return 1; // Invalid entry } if(fileptr->prodos_name[0] >= 0xe0) { // Dir/Volume header fileptr->key_block = dir_byte >> 9; if((dir_byte & 0x1ff) != 4) { printf("Header at dir_byte:%07x != 4\n", dir_byte); return 0; // Not in first pos } if(bptr[-4] || bptr[-3]) { printf("prev_link %02x,%02x should be 0\n", bptr[-4], bptr[-3]); return 0; // Not first dir block } if(fileptr->prodos_name[0] >= 0xf0) { if(dir_byte != 0x0404) { printf("Volume head dir_byte:%07x\n", dir_byte); return 0; } } else if(dir_byte == 0x0404) { printf("Directory head dir_byte 0x0404\n"); return 0; // 0xe0 in block 2->bad } } else { // Normal entry. Make sure it's not the first entry in a dir if((bptr[-4] == 0) && (bptr[-3] == 0) && ((dir_byte & 0x1ff) == 4)) { printf("dir_byte:%07x, normal, prev:0\n", dir_byte); return 0; // This is a dir/volume header! } } #if 0 printf("Fill resulted in buf32:%s, upper_lower:%04x\n", buf32_ptr, fileptr->upper_lower); #endif return 2; // OK } word32 dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr) { word32 ret, new_storage, old_storage; int i; // Return 0 if the directory is damaged // Return 1 if the entry is invalid (and not case 3!) // Return 3 if the entry was valid and is now deleted // Return 4 if no changes are needed // Return 5 if oldfileptr needs to be rewritten // Return 7 if oldfileptr needs to be erased and replaced with newfile old_storage = oldfileptr->prodos_name[0]; new_storage = newfileptr->prodos_name[0]; if(new_storage == 0) { // Erased if(old_storage >= 0xe0) { // Vol/Dir header return 0; } if(oldfileptr->dir_byte == newfileptr->dir_byte) { return 3; // Entry just deleted } return 1; // Just an invalid entry } if(oldfileptr->dir_byte != newfileptr->dir_byte) { return 0; } ret = 4; // No changes needed // Handle file expanding from seedling to tree if((new_storage >= 0x10) && (new_storage < 0x40) && (old_storage >= 0x10) && (old_storage < 0x40)) { // Copy upper 4 bits from new_storage to old_storage old_storage = (old_storage & 0x0f) | (new_storage & 0xf0); if(oldfileptr->prodos_name[0] != old_storage) { // Storage type changed, rewrite the file oldfileptr->prodos_name[0] = old_storage; ret |= 5; } } for(i = 0; i < 16; i++) { if(oldfileptr->prodos_name[i] != newfileptr->prodos_name[i]) { ret |= 7; // Name changed } oldfileptr->prodos_name[i] = newfileptr->prodos_name[i]; } if(oldfileptr->file_type != newfileptr->file_type) { ret |= 7; // Filetype changed oldfileptr->file_type = newfileptr->file_type; } if(newfileptr->prodos_name[0] < 0xe0) { // Not a directory or volume header if(oldfileptr->key_block != newfileptr->key_block) { ret |= 5; // Key block has changed oldfileptr->key_block = newfileptr->key_block; } if(oldfileptr->blocks_used != newfileptr->blocks_used) { // ret stays 1, we don't care about this field oldfileptr->blocks_used = newfileptr->blocks_used; } if(oldfileptr->eof != newfileptr->eof) { ret |= 5; // eof has changed oldfileptr->eof = newfileptr->eof; } } else { // Directory or volume header // Ignore key_block (used internally by dynapro.c, but not in // the ProDOS disk image), blocks_used, eof. // We ignore file_count at +0x21,0x22. But bitmap_ptr matters // and if it moves, we are damaged if((oldfileptr->lastmod_time >> 16) != (newfileptr->lastmod_time >> 16)) { return 0; // Bitmap_ptr moved, we are damaged } } if(oldfileptr->upper_lower != newfileptr->upper_lower) { ret |= 7; // lowercase flags have changed oldfileptr->upper_lower = newfileptr->upper_lower; } if(oldfileptr->aux_type != newfileptr->aux_type) { ret |= 5; // aux_type has changed oldfileptr->aux_type = newfileptr->aux_type; } if(oldfileptr->header_pointer != newfileptr->header_pointer) { return 0; // We are damaged } if(newfileptr->prodos_name[0] >= 0xe0) { if(ret > 5) { ret = 5; // No renaming volume or dir headers } } return ret; } word32 dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte) { word32 ret, diffs; ret = dynapro_fill_fileptr_from_prodos(dsk, localfile_ptr, buf32_ptr, dir_byte); if((ret == 0) || ((ret == 1) && !fileptr)) { return ret; // Damaged or not valid } if(!fileptr) { return 2; // must allocate new } // Now, head_ptr must be non-null diffs = dynapro_diff_fileptrs(fileptr, localfile_ptr); return diffs; } void dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr) { if(fileptr->prodos_name[0] >= 0xe0) { // This is a volume/directory header. Re-parse entire dir dynapro_handle_write_dir(dsk, fileptr->parent_ptr, fileptr, (fileptr->key_block * 0x200UL) + 4); } else if(fileptr->prodos_name[0] >= 0xd0) { // This is a directory entry. dynapro_handle_write_dir(dsk, fileptr, fileptr->subdir_ptr, (fileptr->key_block * 0x200UL) + 4); } else { dynapro_handle_write_file(dsk, fileptr); } } void dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr) { // Walk entire tree (recursing to dynapro_try_fix_damage) if(!fileptr) { return; } while(fileptr) { if(fileptr->damaged) { dyna_printf("try_fix_damage %p %s\n", fileptr, fileptr->unix_path); dynapro_fix_damaged_entry(dsk, fileptr); } dynapro_try_fix_damage(dsk, fileptr->subdir_ptr); fileptr = fileptr->next_ptr; } } void dynapro_try_fix_damaged_disk(Disk *dsk) { Dynapro_info *info_ptr; info_ptr = dsk->dynapro_info_ptr; if(!info_ptr) { // This is impossible return; } if(info_ptr->damaged == 0) { return; } dyna_printf("************************************\n"); dyna_printf("try_fix_damaged_dsk called, damaged:%d\n", info_ptr->damaged); dyna_printf(" vbl_count:%d, g_iwm_dynapro_last_vbl_count:%d\n", g_vbl_count, g_iwm_dynapro_last_vbl_count); info_ptr->damaged = 0; dynapro_try_fix_damage(dsk, info_ptr->volume_ptr); dyna_printf("try_fix_damaged_dsk, damaged:%d\n", info_ptr->damaged); } void dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, const char *name_str) { if(fileptr->unix_path) { free(fileptr->unix_path); } dynapro_join_path_and_file(&g_dynapro_path_buf[0], path_str, name_str, DYNAPRO_PATH_MAX); dynatype_fix_unix_name(fileptr, &g_dynapro_path_buf[0], DYNAPRO_PATH_MAX); fileptr->unix_path = kegs_malloc_str(&g_dynapro_path_buf[0]); } Dynapro_file * dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file **head_ptr_ptr, word32 dir_byte) { char buf32[32]; Dynapro_file localfile; Dynapro_file *fileptr, *prev_ptr, *head_ptr; byte *bptr; const char *str; word32 tmp_byte, prev, next, ret, last_block, parent_dir_byte; int cnt, error, iret, is_header; head_ptr = *head_ptr_ptr; // head_ptr_ptr must be valid // but head_ptr can be 0 // We can be called with parent_ptr=0, head_ptr != 0: this is for the // volume header. Otherwise, parent_ptr should be valid. // If head_ptr==0, it means we need to allocate directory header and // all other dir entries // head_ptr is a pointer to a directory or volume header. // For all entries, see if anything changed. We need to also // possibly update head_ptr->parent_ptr // Return 0 if the directory is damaged. If directory only contains // damaged files, try to fix them, and always return success // First, unmap the directory blocks (this is done even if nothing // changed, we'll map them back at the end). if(head_ptr) { dynapro_unmap_file(dsk, head_ptr); } parent_dir_byte = 0; if(parent_ptr) { str = parent_ptr->unix_path; parent_dir_byte = parent_ptr->dir_byte; if(head_ptr == 0) { // Do mkdir to make sure it exists #ifdef _WIN32 iret = _mkdir(str); #else iret = mkdir(str, 0x1ff); #endif error = errno; dyna_printf("Did mkdir %s, iret:%d\n", str, iret); if(iret < 0) { if((error == EEXIST) || (error == EISDIR)) { error = 0; // These are OK errors } if(error) { printf("mkdir(%s) failed, error=%d\n", str, error); } } } } else { str = head_ptr->unix_path; // volume header } #if 0 printf("process_write_dir str:%s %p parent:%p\n", str, head_ptr, parent_ptr); #endif // The directory blocks have already been unmapped // Then, walk the directory, noting if anything changed. If new // files appear in the directory, add then to the chain. We may need // to erase existing entries which no longer exist (or their directory // entry was changed to a different file) bptr = dsk->raw_data; prev_ptr = 0; fileptr = head_ptr; last_block = 0; cnt = 0; is_header = 1; while(dir_byte) { //printf("process_write_dir, dir_byte:%07x, prev_ptr:%p\n", // dir_byte, prev_ptr); if((dir_byte & 0x1ff) == 4) { // First entry in this block: check prev/next tmp_byte = dir_byte & -0x200; // Block align prev = dynapro_get_word16(&bptr[tmp_byte + 0]); next = dynapro_get_word16(&bptr[tmp_byte + 2]); if((prev != last_block) || (next >= (dsk->raw_dsize >> 9))) { // This is a damaged directory printf("dir %s is damaged in the link fields\n", str); return 0; } last_block = dir_byte >> 9; } if(cnt++ >= 65536) { printf("dir %s has a loop in block pointers\n", head_ptr->unix_path); return 0; } ret = dynapro_do_one_dir_entry(dsk, fileptr, &localfile, &buf32[0], dir_byte); #if 0 printf(" do_one_dir_entry ret:%08x fileptr:%p, &localfile:%p\n", ret, fileptr, &localfile); #endif if((ret == 7) && !is_header) { // Entry dramatically changed // Erase this file dynapro_mark_damaged(dsk, fileptr); } if(ret == 0) { return 0; } else if((ret == 1) || (ret == 3)) { if((ret == 3) && fileptr) { // This entry was valid and is now deleted. // Erase it right now and fix links dyna_printf("fileptr %p deleted\n", fileptr); prev_ptr->next_ptr = fileptr->next_ptr; dynapro_erase_free_entry(dsk, fileptr); } if(head_ptr == 0) { printf("return, head_ptr==0, deleted file at " "%07x\n", dir_byte); return 0; // Directory damaged } fileptr = prev_ptr; } if(ret == 2) { // prev_ptr->next_ptr is 0, this is a new entry we // need to put on the list if(fileptr) { halt_printf("file %s was ignored!\n", fileptr->unix_path); exit(1); } fileptr = dynapro_alloc_file(); if(!fileptr) { return 0; } *fileptr = localfile; // STRUCT copy! dyna_printf("Allocated new fileptr:%p\n", fileptr); fileptr->parent_ptr = parent_ptr; if(head_ptr) { fileptr->parent_ptr = head_ptr; } } if((ret == 2) || (ret == 7)) { // New entry, or dramatically changed, update path if(!head_ptr) { if(!parent_ptr) { printf("parent_ptr is 0!\n"); return 0; } parent_ptr->subdir_ptr = fileptr; printf("2/7 set %p %s subdir=%p\n", parent_ptr, parent_ptr->unix_path, fileptr); fileptr->unix_path = kegs_malloc_str(str); head_ptr = fileptr; *head_ptr_ptr = head_ptr; } else { dyna_printf("Forming new path: %s buf32:%s\n", str, buf32); dynapro_new_unix_path(fileptr, str, buf32); } // If we are a directory entry (fileptr->subdir_ptr!=0) // then now fileptr->unix_path != subdirptr->unix_path // The subdir will be erased in // dynapro_handle_changed_entry() if(prev_ptr) { prev_ptr->next_ptr = fileptr; } } if((ret == 5) || (ret == 2) || (ret == 7)) { // Changed, or new entry if(is_header) { ret = dynapro_validate_header(dsk, fileptr, dir_byte, parent_dir_byte); if(ret == 0) { return 0; } } else { dynapro_handle_changed_entry(dsk, fileptr); } } prev_ptr = fileptr; fileptr = prev_ptr->next_ptr; dir_byte = dir_byte + 0x27; tmp_byte = (dir_byte & 0x1ff) + 0x27; is_header = 0; if(tmp_byte < 0x200) { continue; } tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; dyna_printf(" dir link at %07x = %04x\n", tmp_byte + 2, dir_byte); if(dir_byte == 0) { if(fileptr) { printf("At dir end, fileptr: %p\n", fileptr); prev_ptr->next_ptr = 0; dynapro_erase_free_dir(dsk, fileptr); return 0; } ret = dynapro_map_dir_blocks(dsk, head_ptr); // printf("process_write_dir %s done, remap ret:%d\n", // head_ptr->unix_path, ret); if(ret == 0) { return 0; } return head_ptr; // Success } dir_byte += 4; if(dir_byte >= dsk->dimage_size) { // printf(" invalid link pointer, dir_byte:%08x\n", // dir_byte); return 0; // Bad link, get out } } dyna_printf("At end of process_write_dir, returning 0\n"); return 0; } void dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file *head_ptr, word32 dir_byte) { Dynapro_file *fileptr; //save_headptr = head_ptr; dyna_printf("handle_write_dir parent_ptr:%p, head_ptr:%p, dir_b:%07x\n", parent_ptr, head_ptr, dir_byte); if(parent_ptr && head_ptr) { dyna_printf(" parent:%s head:%s\n", parent_ptr->unix_path, head_ptr->unix_path); if(parent_ptr->subdir_ptr != head_ptr) { printf("parent subdir:%p does not match %p\n", parent_ptr->subdir_ptr, head_ptr); exit(1); } } if(parent_ptr == 0) { if(head_ptr != dsk->dynapro_info_ptr->volume_ptr) { printf("handle_write_dir %p %p %07x\n", parent_ptr, head_ptr, dir_byte); exit(1); } } fileptr = dynapro_process_write_dir(dsk, parent_ptr, &head_ptr, dir_byte); if(fileptr == 0) { // Directory is damaged. Free and erase it dynapro_erase_free_dir(dsk, head_ptr); head_ptr = 0; } //printf("handle_write_dir, process returned %p (was %p), parent:%p, " // "save:%p\n", fileptr, head_ptr, parent_ptr, save_headptr); if(parent_ptr) { parent_ptr->subdir_ptr = head_ptr; if(!fileptr) { parent_ptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; } dyna_printf("hwd set parent %p %s subdir=%p\n", parent_ptr, parent_ptr->unix_path, head_ptr); } } word32 dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr) { word32 ret; //printf("dynapro_process_write_file %p %s\n", fileptr, // fileptr->unix_path); if(fileptr->subdir_ptr) { printf("dynapro_handle_write_file, has subdir: %s\n", fileptr->unix_path); halt_printf("dynapro_handle_write_file, has subdir: %s\n", fileptr->unix_path); return 0; } // First, unmap the file (the sapling/tree blocks may have changed). dynapro_unmap_file(dsk, fileptr); // Then remap the blocks. This will copy the new data to buffer_ptr dynapro_debug_map(dsk, "handle_write_file, right before re-map"); ret = dynapro_map_file(dsk, fileptr, 1); // printf("dynapro_handle_write_file ending, ret:%d\n", ret); return ret; } void dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr) { word32 ret; ret = dynapro_process_write_file(dsk, fileptr); if(ret == 0) { dynapro_mark_damaged(dsk, fileptr); } } void dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr) { // printf("handle_changed_entry with fileptr:%p\n", fileptr); fileptr->damaged = 0; if(fileptr->prodos_name[0] >= 0xe0) { // Directory header, not valid to be called here fileptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; } else if(fileptr->prodos_name[0] >= 0xd0) { // Directory entry if(fileptr->subdir_ptr) { dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); fileptr->subdir_ptr = 0; } dynapro_handle_write_dir(dsk, fileptr, 0, (fileptr->key_block * 0x200UL) + 4); } else { dynapro_handle_write_file(dsk, fileptr); #if 0 printf("handle_changed_entry called handle_write_file for %p, " "%s\n", fileptr, fileptr->unix_path); #endif } } word32 dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size) { dword64 dret; int fd; fd = open(unix_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); if(fd < 0) { printf("Open %s for writing failed\n", unix_path); exit(1); return 0; } dret = cfg_write_to_fd(fd, data_ptr, 0, size); close(fd); dyna_printf("dynapro_write_to_unix: %s size:%d, dret:%lld\n", unix_path, size, dret); if(size == 0) { return 1; } return (word32)dret; } void dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr) { Dynapro_file *this_fileptr; Dynapro_map *map_ptr; //const char *str; word32 map_block, next_map_block, max_blocks; int i; //printf("File %p: %s is unmapped\n", fileptr, fileptr->unix_path); dynapro_debug_map(dsk, "start unmap file"); // Unmap all blocks to this file/dir map_ptr = dsk->dynapro_info_ptr->block_map_ptr; max_blocks = (word32)(dsk->dimage_size >> 9); map_block = fileptr->map_first_block; //printf(" map_block:%04x, fileptr:%p %s\n", map_block, fileptr, // fileptr->unix_path); fileptr->map_first_block = 0; //printf(" unmap starting, map_block:%08x, max_blocks:%07x\n", // map_block, max_blocks); for(i = 0; i < 65536; i++) { if((map_block == 0) || (map_block >= max_blocks)) { break; } next_map_block = map_ptr[map_block].next_map_block; this_fileptr = map_ptr[map_block].file_ptr; if(this_fileptr != fileptr) { //str = "??"; if(this_fileptr) { //str = this_fileptr->unix_path; this_fileptr->damaged = 1; } #if 0 printf("Found map[%04x]=%s while walking %s\n", map_block, str, fileptr->unix_path); #endif } map_ptr[map_block].file_ptr = 0; map_ptr[map_block].next_map_block = 0; map_ptr[map_block].modified = 0; //printf(" just unmapped block %05x\n", map_block); map_block = next_map_block; } // printf(" unmap ending\n"); } void dynapro_unlink_file(Dynapro_file *fileptr) { const char *str; int ret, err; // Try to unlink unix_path dyna_printf("Unlink %s (%p)\n", fileptr->unix_path, fileptr); if(fileptr->unix_path == 0) { printf("unix_path of %p is null!\n", fileptr); exit(1); } if(fileptr->subdir_ptr != 0) { printf("unlink_file %s, but subdirptr is valid!\n", fileptr->unix_path); exit(1); } ret = unlink(fileptr->unix_path); if(ret != 0) { // Maybe it's a directory, rmdir ret = rmdir(fileptr->unix_path); } if(ret != 0) { // Cannot erase, try to rename cfg_strncpy_dirname(&g_dynapro_path_buf[0], fileptr->unix_path, DYNAPRO_PATH_MAX); cfg_strlcat(&g_dynapro_path_buf[0], ".kegsrm_", DYNAPRO_PATH_MAX); str = cfg_str_basename(fileptr->unix_path); cfg_strlcat(&g_dynapro_path_buf[0], str, DYNAPRO_PATH_MAX); printf("Could not erase %s, renaming to: %s\n", fileptr->unix_path, &g_dynapro_path_buf[0]); ret = rename(fileptr->unix_path, &g_dynapro_path_buf[0]); err = errno; if(ret != 0) { printf("Rename of %s failed, err:%d\n", fileptr->unix_path, err); } } } void dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr) { if(!fileptr) { return; } dynapro_mark_damaged(dsk, fileptr); fileptr->next_ptr = 0; if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { // Free everything--except the volume header dyna_printf("erase_free_entry erasing %p since it != %p\n", fileptr, dsk->dynapro_info_ptr->volume_ptr); dynapro_free_file(fileptr, 1); } } void dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr) { Dynapro_file *nextptr, *parent_ptr, *save_fileptr; dyna_printf("dynapro_erase_free_dir of %p\n", fileptr); if(fileptr == 0) { return; } dyna_printf(" dynapro_erase_free_dir of %s\n", fileptr->unix_path); dsk->dynapro_info_ptr->damaged = 1; parent_ptr = fileptr->parent_ptr; if(parent_ptr) { if(parent_ptr->subdir_ptr) { parent_ptr->damaged = 1; } } save_fileptr = fileptr; nextptr = fileptr->next_ptr; fileptr->next_ptr = 0; fileptr = nextptr; while(fileptr) { nextptr = fileptr->next_ptr; dynapro_erase_free_entry(dsk, fileptr); fileptr = nextptr; } dynapro_erase_free_entry(dsk, save_fileptr); } void dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr) { if(fileptr == 0) { return; } dyna_printf("dynapro_mark_damaged: %s damaged\n", fileptr->unix_path); fileptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; dynapro_unmap_file(dsk, fileptr); if(fileptr->subdir_ptr) { dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); fileptr->subdir_ptr = 0; } if((fileptr->prodos_name[0] >= 0xe0) && fileptr->parent_ptr) { // We are a directory header, mark the directory entry of our // parent as damaged (but don't actually damage it) fileptr->parent_ptr->damaged = 1; } else if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { dynapro_unlink_file(fileptr); } } int dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) { Dynapro_info *info_ptr; Dynapro_map *map_ptr; Dynapro_file *fileptr; byte *bptr; word32 ui, block; int num; int i; // Return 1 if write was done. Return < 0 if an error occurs dyna_printf("\n"); dyna_printf("------------------------------------------------\n"); dyna_printf("dynapro_write to %08llx, size:%08x\n", doffset, size); dynapro_debug_update(dsk); bptr = dsk->raw_data; if((doffset + size) > dsk->dimage_size) { printf("Write past end of disk, ignored\n"); return -1; } for(ui = 0; ui < size; ui++) { #if 0 if((bptr[doffset + ui] != bufptr[ui]) && (diffs < 500)) { printf("%07llx:%02x (was %02x)\n", doffset+ui, bufptr[ui], bptr[doffset + ui]); diffs++; } #endif bptr[doffset + ui] = bufptr[ui]; } info_ptr = dsk->dynapro_info_ptr; if(info_ptr == 0) { printf("dynapro_info_ptr==0\n"); return -1; } num = (size + 511) >> 9; block = (word32)(doffset >> 9); dyna_printf("Marking blocks %05x-%05x modified\n", block, block + num - 1); for(i = 0; i < num; i++) { map_ptr = &(info_ptr->block_map_ptr[block + i]); map_ptr->modified = 1; } for(i = 0; i < num; i++) { map_ptr = &(info_ptr->block_map_ptr[block + i]); if(!map_ptr->modified) { continue; // Already cleared } fileptr = map_ptr->file_ptr; if(fileptr == 0) { continue; } if(fileptr->prodos_name[0] >= 0xe0) { dynapro_handle_write_dir(dsk, fileptr->parent_ptr, fileptr, fileptr->dir_byte); } else { dynapro_handle_write_file(dsk, fileptr); } } return 1; } void dynapro_debug_update(Disk *dsk) { dyna_printf("Writing out DYNAPRO_IMAGE, %p\n", dsk); #if 0 // This causes the file DYNAPRO_IMAGE to be written out as the raw // image after any write to the Dynapro volume. This is for manual // debugging. dynapro_write_to_unix_file("DYNAPRO_IMAGE", dsk->raw_data, (word32)dsk->dimage_size); #endif } void dynapro_debug_map(Disk *dsk, const char *str) { Dynapro_map *map_ptr; Dynapro_file *lastfileptr, *fileptr; const char *newstr; int num_blocks; int i; return; // HACK! num_blocks = (word32)((dsk->dimage_size + 511) >> 9); map_ptr = dsk->dynapro_info_ptr->block_map_ptr; lastfileptr = 0; printf(" Showing map for %s, %05x blocks, %s\n", dsk->dynapro_info_ptr->root_path, num_blocks, str); for(i = 0; i < num_blocks; i++) { fileptr = map_ptr[i].file_ptr; if(fileptr != lastfileptr) { newstr = ""; if(fileptr) { newstr = fileptr->unix_path; } printf(" %04x (%07x): %p %s\n", i, i << 9, fileptr, newstr); } lastfileptr = fileptr; } printf("Recursive file map:\n"); dynapro_debug_recursive_file_map(dsk->dynapro_info_ptr->volume_ptr, 1); } void dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start) { if(!fileptr) { return; } while(fileptr) { printf(" file %p %s map_first_block:%05x, storage:%02x key:" "%04x\n", fileptr, fileptr->unix_path, fileptr->map_first_block, fileptr->prodos_name[0], fileptr->key_block); printf(" n:%p, sub:%p, eof:%06x, parent:%p dam:%d\n", fileptr->next_ptr, fileptr->subdir_ptr, fileptr->eof, fileptr->parent_ptr, fileptr->damaged); if(fileptr->unix_path == 0) { printf("Filename is invalid, exiting\n"); exit(1); } if(!fileptr->parent_ptr && !start) { printf("parent_ptr is 0, exiting\n"); exit(1); } dynapro_debug_recursive_file_map(fileptr->subdir_ptr, 0); start = 0; fileptr = fileptr->next_ptr; } } word32 dynapro_unix_to_prodos_time(const time_t *time_ptr) { struct tm *tm_ptr; word32 ymd, hours_mins, date_time; int year; tm_ptr = localtime(time_ptr); hours_mins = (tm_ptr->tm_hour << 8) | tm_ptr->tm_min; year = tm_ptr->tm_year; // years since 1900 if(year < 80) { year = 80; } else if(year >= 100) { year -= 100; if(year >= 80) { year = 79; } } ymd = (year << 9) | ((tm_ptr->tm_mon + 1) << 5) | (tm_ptr->tm_mday); date_time = (ymd & 0xffff) | (hours_mins << 16); // printf("Unix time:%s results in:%08x\n", asctime(tm_ptr), date_time); return date_time; } int dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, word32 storage_type) { Dynapro_file *thisptr; char *str; word32 upper_lower; int len, outpos, inpos, max_inpos, c, done, dot_pos, inc_pos; int i; #if 0 printf("dynapro_create_prodos_name to %s, match:%p, st:%03x\n", newfileptr->unix_path, matchptr, storage_type); #endif for(i = 0; i < 17; i++) { newfileptr->prodos_name[i] = 0; } str = newfileptr->unix_path; if(!str) { return 0; } inpos = (int)strlen(str); max_inpos = inpos + 1; while(inpos >= 1) { inpos--; if(str[inpos] == '/') { inpos++; break; } } if(storage_type == 0x50) { // printf("max_inpos:%d, inpos:%d\n", max_inpos, inpos); } if((storage_type == 0x50) && ((max_inpos - inpos) > 12)) { // Remove .applesingle extension max_inpos -= 13; } //printf(" inpos:%d max_inpos:%d str:%s\n", inpos, max_inpos, // &(str[inpos])); outpos = 0; while(outpos < (DYNAPRO_PATH_MAX - 1)) { c = 0; if(inpos < max_inpos) { c = str[inpos++]; } g_dynapro_path_buf[outpos] = c; if(c == 0) { break; } outpos++; if((c >= 'A') && (c <= 'Z')) { continue; // This is legal } if((c >= 'a') && (c <= 'z')) { continue; // Also legal } if((outpos > 1) && (c >= '0') && (c <= '9')) { continue; // Also legal } if((outpos > 1) && (c == '.')) { continue; // Also legal } // If this is the first character, make it "A" and continue if(outpos == 1) { g_dynapro_path_buf[0] = 'A'; continue; } if((c == ',') || (c == '#')) { // ,ttxt,a$2000, ignore outpos--; break; // All done } // This is not legal. Make it a '.' if((c >= 0x20) && (c <= 0x7e)) { g_dynapro_path_buf[outpos - 1] = '@'; // do '.' later } else { outpos--; // Ignore it } } g_dynapro_path_buf[outpos] = 0; // printf(" initial path_buf:%s, %d\n", &g_dynapro_path_buf[0], outpos); while((outpos >= 0) && (g_dynapro_path_buf[outpos-1] == '@')) { // Remove trailing '@' since they are not useful outpos--; g_dynapro_path_buf[outpos] = 0; } for(i = 1; i < outpos; i++) { // Convert '@' to '.' to make name legal if(g_dynapro_path_buf[i] == '@') { g_dynapro_path_buf[i] = '.'; } } if(outpos == 0) { // Not a valid file, just skip it return 0; } // Now, it's valid. Squeeze it to 15 character but saving extension len = (int)strlen(&g_dynapro_path_buf[0]); if(len > 15) { // Copy last 8 characters to be in positions 7..14 for(i = 7; i < 16; i++) { g_dynapro_path_buf[i] = g_dynapro_path_buf[len-15 + i]; } } len = (int)strlen(&g_dynapro_path_buf[0]); if((len > 15) || (len == 0)) { printf("Bad filename handling: %s\n", &g_dynapro_path_buf[0]); return 0; } // See if it conflicts with matchptr thisptr = matchptr; for(i = 0; i < 10000; i++) { if(!thisptr || (thisptr == newfileptr)) { thisptr = 0; break; } //printf("Comparing %s to %s\n", &g_dynapro_path_buf[0], // (char *)&(thisptr->prodos_name[1])); len = (int)strlen(&g_dynapro_path_buf[0]); if((len == (thisptr->prodos_name[0] & 0xf)) && (cfgcasecmp(&g_dynapro_path_buf[0], (char *)&(thisptr->prodos_name[1])) == 0)) { dyna_printf(" that was a match\n"); dot_pos = 0; inc_pos = len - 1; for(i = len - 2; i >= 1; i--) { if(g_dynapro_path_buf[i] == '.') { dot_pos = i; } } if(len < 15) { // Append "1" to the end len++; inc_pos = len - 1; g_dynapro_path_buf[len] = 0; g_dynapro_path_buf[len - 1] = '1'; if(dot_pos > 1) { for(i = len - 1; i >= dot_pos; i--) { g_dynapro_path_buf[i] = g_dynapro_path_buf[i-1]; } inc_pos = dot_pos; } g_dynapro_path_buf[inc_pos] = '1'; } else if(dot_pos > 3) { inc_pos = dot_pos - 1; } done = 0; for(i = inc_pos; i >= 1; i--) { c = g_dynapro_path_buf[i]; c++; if(c == ('9' + 1)) { c = '0'; } else if(c == ('z' + 1)) { c = 'a'; } else if(c == ('Z' + 1)) { c = 'A'; } else { done = 1; } g_dynapro_path_buf[i] = c; if(done) { break; } } thisptr = matchptr; } else { thisptr = thisptr->next_ptr; } } if(thisptr) { // File could not be made unique printf("Could not make a unique ProDOS filename: %s\n", newfileptr->unix_path); return 0; } upper_lower = 0; for(i = 0; i < len; i++) { c = g_dynapro_path_buf[i]; if((c >= 'a') && (c <= 'z')) { c = c - 'a' + 'A'; upper_lower |= 0x8000 | (0x4000 >> i); } newfileptr->prodos_name[1 + i] = c; } newfileptr->prodos_name[0] = len | storage_type; newfileptr->upper_lower = upper_lower; return len; } Dynapro_file * dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, Dynapro_file *match_ptr, word32 storage_type) { Dynapro_file *fileptr; int len; int i; #if 0 printf("dynapro_new_unix_file for %s, parent:%p, m:%p, st:%03x\n", path, parent_ptr, match_ptr, storage_type); #endif fileptr = dynapro_alloc_file(); if(!fileptr) { return 0; } fileptr->next_ptr = 0; fileptr->parent_ptr = parent_ptr; fileptr->subdir_ptr = 0; fileptr->buffer_ptr = 0; fileptr->unix_path = kegs_malloc_str(path); for(i = 0; i < 17; i++) { fileptr->prodos_name[i] = 0; } fileptr->dir_byte = 0; fileptr->eof = 0; fileptr->blocks_used = 0; fileptr->creation_time = 0; fileptr->lastmod_time = 0; fileptr->upper_lower = 0; fileptr->key_block = 0; fileptr->aux_type = 0; fileptr->header_pointer = 0; fileptr->map_first_block = 0; fileptr->file_type = 0x0f; // Default to "DIR" fileptr->modified_flag = 0; fileptr->damaged = 0; len = (int)strlen(fileptr->unix_path); for(i = len - 1; i >= 0; i--) { if(fileptr->unix_path[i] == '/') { fileptr->unix_path[i] = 0; // Strip trailing / } else { break; } } if(storage_type < 0xd0) { storage_type = dynatype_detect_file_type(fileptr, fileptr->unix_path, storage_type); } len = dynapro_create_prodos_name(fileptr, match_ptr, storage_type); if(len == 0) { printf("Could not create prodos name for: %s\n", path); free(fileptr); return 0; } #if 0 printf("dynapro_create_new_unix_file: %s prodos:%s, st:%02x, ft:%02x, " "aux:%04x\n", fileptr->unix_path, &(fileptr->prodos_name[1]), fileptr->prodos_name[0], fileptr->file_type, fileptr->aux_type); #endif return fileptr; } int dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, word32 dir_byte) { struct stat stat_buf; struct dirent *direntptr; DIR *opendirptr; Dynapro_file *fileptr, *head_ptr, *prev_ptr; mode_t fmt; word32 storage_type, val; int ret; // Create a directory entry at dir_byte first #if 0 printf("\n"); printf("dynapro_add_files to %s, %p dir_byte:%08x\n", unix_path, parent_ptr, dir_byte); #endif storage_type = 0xe0; // Directory header if(dir_byte < 0x600) { // Block 2: volume header storage_type = 0xf0; } head_ptr = dynapro_new_unix_file(unix_path, parent_ptr, 0, storage_type); if(parent_ptr) { parent_ptr->subdir_ptr = head_ptr; #if 0 printf("set parent %s subdir_ptr=%p\n", parent_ptr->unix_path, head_ptr); #endif } if(dsk->dynapro_info_ptr->volume_ptr == 0) { dsk->dynapro_info_ptr->volume_ptr = head_ptr; } if(head_ptr == 0) { printf("new_file returned 0, skipping %s\n", unix_path); return dir_byte; } head_ptr->key_block = dir_byte >> 9; head_ptr->aux_type = 0x0d27; // 0x27,0x0d head_ptr->file_type = 0x75; // Directory header type if(storage_type >= 0xf0) { head_ptr->file_type = 0x00; } ret = cfg_stat(unix_path, &stat_buf, 0); if(ret != 0) { printf("stat %s ret %d, errno:%d\n", unix_path, ret, errno); return 0; } head_ptr->creation_time = dynapro_unix_to_prodos_time( &stat_buf.st_ctime); dir_byte = dynapro_add_file_entry(dsk, head_ptr, 0, dir_byte, 0); opendirptr = opendir(unix_path); if(opendirptr == 0) { printf("Could not open %s as a dir\n", unix_path); return 0; } prev_ptr = head_ptr; while(1) { direntptr = readdir(opendirptr); if(direntptr == 0) { break; } if(direntptr->d_name[0] == '.') { continue; // Ignore all '.' files } dynapro_join_path_and_file(&(g_dynapro_path_buf[0]), unix_path, direntptr->d_name, DYNAPRO_PATH_MAX); ret = cfg_stat(&(g_dynapro_path_buf[0]), &stat_buf, 0); if(ret != 0) { printf("stat %s ret %d, errno:%d\n", &g_dynapro_path_buf[0], ret, errno); continue; // skip it } fmt = stat_buf.st_mode & S_IFMT; storage_type = 0; if(fmt == S_IFDIR) { // Ignore symlinks to directories (since they may point // outside the base directory, and so dynamically // removing files could be a security issue). ret = cfg_stat(&g_dynapro_path_buf[0], &stat_buf, 1); if(ret != 0) { printf("lstat %s ret %d, errno:%d\n", &g_dynapro_path_buf[0], ret, errno); continue; } storage_type = 0xd0; // Directory } else if(fmt != S_IFREG) { continue; // Skip this } #if 0 printf("GOT file: %s, is_dir:%d (%s), parent:%p\n", &(g_dynapro_path_buf[0]), is_dir, direntptr->d_name, parent_ptr); #endif fileptr = dynapro_new_unix_file(&(g_dynapro_path_buf[0]), head_ptr, head_ptr->next_ptr, storage_type); if(fileptr == 0) { closedir(opendirptr); return 0; } prev_ptr->next_ptr = fileptr; fileptr->key_block = dynapro_find_free_block(dsk); fileptr->creation_time = dynapro_unix_to_prodos_time( &stat_buf.st_ctime); fileptr->lastmod_time = dynapro_unix_to_prodos_time( &stat_buf.st_mtime); if(fileptr->key_block == 0) { printf("Allocating directory block failed\n"); closedir(opendirptr); return 0; } fileptr->blocks_used = 1; fileptr->eof = 1*0x200; dir_byte = dynapro_add_file_entry(dsk, fileptr, head_ptr, dir_byte, 0x27); if(dir_byte == 0) { closedir(opendirptr); return 0; } if(fmt == S_IFDIR) { val = dynapro_create_dir(dsk, fileptr->unix_path, fileptr, (fileptr->key_block << 9) + 4); if(val == 0) { closedir(opendirptr); return 0; } } else { val = dynapro_file_from_unix(dsk, fileptr); if(val == 0) { closedir(opendirptr); return 0; } } prev_ptr = fileptr; } closedir(opendirptr); return dir_byte; } word32 dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, word32 dir_byte, word32 inc) { Dynapro_file *parent_ptr; byte *bptr, *pkeyptr; word32 storage_type, val, ent, new_dir_blk, new_dir_byte; word32 header_pointer; int i; #if 0 printf("dynapro_add_file_entry: %p %p %s head:%p dir_byte:%08x " "inc:%03x\n", dsk, fileptr, fileptr->unix_path, head_ptr, dir_byte, inc); #endif bptr = dsk->raw_data; if(((dir_byte & 0x1ff) + inc + inc) >= 0x200) { // This entry will not fit in this directory block. // Try to step to next block, otherwise allocate a new one new_dir_byte = dir_byte & -0x200L; new_dir_blk = dynapro_get_word16(&bptr[new_dir_byte + 2]); dyna_printf(" Entry does not fit, new_dir_blk:%04x\n", new_dir_blk); if(new_dir_blk != 0) { // Follow to the next block dir_byte = (new_dir_blk * 0x200) + 4; } else if(dir_byte < (6 * 0x200)) { // Otherwise, allocate a new block (not for volume dir) // This is a volume header, always 4 blocks, don't // allocate any more, this is now full printf("Too many file in volume directory\n"); return 0; // Out of space } else { new_dir_blk = dynapro_find_free_block(dsk); if(new_dir_blk == 0) { return 0; } new_dir_byte = new_dir_blk * 512; dynapro_set_word16(&bptr[new_dir_byte], dir_byte >> 9); dynapro_set_word16(&bptr[new_dir_byte + 2], 0); dir_byte = (dir_byte >> 9) << 9; dynapro_set_word16(&bptr[dir_byte + 2], new_dir_blk); dir_byte = new_dir_byte + 4; if(!head_ptr) { dyna_printf("No head:%s\n", fileptr->unix_path); } parent_ptr = head_ptr->parent_ptr; if(!parent_ptr) { printf("No parent: %s\n", fileptr->unix_path); return 0; } parent_ptr->blocks_used++; parent_ptr->eof += 0x200; new_dir_byte = parent_ptr->dir_byte; if(new_dir_byte == 0) { printf("Invalid dir_byte for %s\n", parent_ptr->unix_path); return 0; } dynapro_set_word16(&bptr[new_dir_byte + 0x13], parent_ptr->blocks_used); dynapro_set_word24(&bptr[new_dir_byte + 0x15], parent_ptr->eof); } } else { dir_byte += inc; } bptr = &(dsk->raw_data[dir_byte]); fileptr->dir_byte = dir_byte; for(i = 0; i < 0x27; i++) { bptr[i] = 0; } for(i = 0; i < 16; i++) { bptr[i] = fileptr->prodos_name[i]; // [0] = len,storage_t } bptr[0x10] = fileptr->file_type; dynapro_set_word16(&bptr[0x11], fileptr->key_block); dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); dynapro_set_word24(&bptr[0x15], fileptr->eof); dynapro_set_word32(&bptr[0x18], fileptr->creation_time); // creation date&time bptr[0x1c] = fileptr->upper_lower & 0xff; // Version bptr[0x1d] = fileptr->upper_lower >> 8; // Min_Version bptr[0x1e] = 0xe3; // Access dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); storage_type = bptr[0]; if(storage_type >= 0xf0) { // Volume header dynapro_set_word16(&bptr[0x11], 0); fileptr->lastmod_time = 0x00060000; // low 16 bits: file_count, upper 16 bits: bitmap_block fileptr->header_pointer = (word32)(dsk->raw_dsize >> 9); // Total blocks dynapro_set_word16(&bptr[0x1c], 0x0005); dynapro_set_word16(&bptr[0x16], fileptr->upper_lower); } else if(storage_type >= 0xe0) { // Directory header dynapro_set_word16(&bptr[0x11], 0); dynapro_set_word16(&bptr[0x1c], 0x0005); parent_ptr = fileptr->parent_ptr; // subdir entry if(parent_ptr == 0) { printf("parent_ptr of %s is 0\n", fileptr->unix_path); return 0; } val = parent_ptr->dir_byte >> 9; fileptr->lastmod_time = (val << 16); // Parent block val = parent_ptr->dir_byte & 0x1ff; ent = (val - 4) / 0x27; fileptr->header_pointer = 0x2700 | (ent + 1); } else { // Directory entry, or normal file if(head_ptr == 0) { printf("head_ptr of %s is 0\n", fileptr->unix_path); return 0; } header_pointer = head_ptr->key_block; fileptr->header_pointer = header_pointer; dynapro_set_word16(&bptr[0x25], header_pointer); pkeyptr = &(dsk->raw_data[header_pointer << 9]); val = head_ptr->lastmod_time + 1; head_ptr->lastmod_time = val; dynapro_set_word16(&pkeyptr[4 + 0x21], val); // File count } dynapro_set_word32(&bptr[0x21], fileptr->lastmod_time); // Last Modified date&time (or header info) dynapro_set_word16(&bptr[0x25], fileptr->header_pointer); #if 0 printf("Set dir_byte %07x=%04x\n", dir_byte + 0x25, fileptr->header_pointer); #endif return dir_byte; } // When creating sparse files, always ensure first block is not sparse. GS/OS // does not treat the first block as sparse, it will actually read block 0 // This handles normal files, and one fork of a forked file word32 dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, word32 key_block, dword64 dsize) { byte *bptr; word32 sap_block, tree_block, sap_byte, tree_byte, sparse, block_num; word32 num_blocks, blocks_used, block_off, storage_type; int num_bytes; int i; bptr = &(dsk->raw_data[0]); *storage_type_ptr = 0; sap_block = 0; tree_block = 0; num_blocks = (word32)((dsize + 511) >> 9); if(num_blocks == 0) { // 0-length file num_blocks = 1; } else if(num_blocks > 0x8000) { // >= 16MB (32K*512) printf("File is too large, failing\n"); return 0; } block_off = 0; blocks_used = 1; while(block_off < num_blocks) { sparse = (block_off > 0); // sparse=0 for first block for(i = 0; i < 0x200; i++) { if(fptr[(block_off << 9) + i] != 0) { sparse = 0; break; } } if(sparse) { block_off++; continue; } if((tree_block == 0) && (num_blocks > 256)) { tree_block = dynapro_find_free_block(dsk); if(tree_block == 0) { return 0; } blocks_used++; } tree_byte = (tree_block << 9) + ((block_off >> 8) & 0xff); if(tree_block) { sap_block = bptr[tree_byte + 0] | (bptr[tree_byte + 256] << 8); } if((sap_block == 0) && (num_blocks > 1)) { sap_block = dynapro_find_free_block(dsk); if(sap_block == 0) { return 0; } blocks_used++; if(tree_block) { bptr[tree_byte + 0] = sap_block; bptr[tree_byte + 256] = sap_block >> 8; } } if(block_off == 0) { block_num = key_block; } else { block_num = dynapro_find_free_block(dsk); if(block_num == 0) { return 0; } blocks_used++; } sap_byte = (sap_block << 9) | (block_off & 0xff); if(sap_block) { bptr[sap_byte + 0] = block_num; bptr[sap_byte + 256] = block_num >> 8; } num_bytes = 0x200; if(block_off == (dsize >> 9)) { // Last block num_bytes = dsize & 0x1ff; } for(i = 0; i < num_bytes; i++) { bptr[(block_num << 9) + i] = fptr[(block_off << 9) + i]; } block_off++; } storage_type = 0x10; if(tree_block) { storage_type = 0x30; key_block = tree_block; } else if(sap_block) { storage_type = 0x20; key_block = sap_block; } *storage_type_ptr = storage_type; return (blocks_used << 16) | key_block; } word32 dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr) { byte *bptr, *fptr; dword64 dsize; word32 storage_type, blocks_out, dir_byte; fptr = dynapro_malloc_file(fileptr->unix_path, &dsize, 0x200); fileptr->eof = (word32)dsize; #if 0 printf("file_from_unix %s, size:%08llx, file_type:%02x, dir_byte:" "%07x, storage:%02x\n", fileptr->unix_path, dsize, fileptr->file_type, fileptr->dir_byte, fileptr->prodos_name[0]); #endif storage_type = 0; if((fileptr->prodos_name[0] & 0xf0) == 0x50) { // .applesingle file with data and/or resource forks blocks_out = applesingle_from_unix(dsk, fileptr, fptr, dsize); } else { // Normal file fileptr->prodos_name[0] = (fileptr->prodos_name[0] & 0xf); blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, fileptr->key_block, dsize); } free(fptr); fileptr->prodos_name[0] |= storage_type; fileptr->key_block = blocks_out & 0xffff; fileptr->blocks_used = (blocks_out >> 16) & 0xffff; // Update dir_byte information for this file dir_byte = fileptr->dir_byte; if(dir_byte == 0) { dyna_printf("dir_byte is 0 for %s\n", fileptr->unix_path); } bptr = &(dsk->raw_data[dir_byte]); bptr[0] = fileptr->prodos_name[0]; bptr[0x10] = fileptr->file_type; dynapro_set_word16(&bptr[0x11], fileptr->key_block); dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); dynapro_set_word24(&bptr[0x15], fileptr->eof); dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); #if 0 printf("Set %s dir_byte:%07x+0x10=%02x (file_type)\n", fileptr->unix_path, dir_byte, fileptr->file_type); #endif return blocks_out; } word32 dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks) { Dynapro_info *infoptr; byte *bptr; word32 bitmap_size_bytes, bitmap_size_blocks; int pos; word32 ui; int i; dsk->raw_data = calloc(num_blocks, 512); if(dsk->raw_data == 0) { dynapro_error(dsk, "Could not allocate %d bytes\n", num_blocks * 512); return 0; } dsk->dimage_size = num_blocks * 512LL; dsk->dimage_start = 0; dsk->raw_dsize = num_blocks * 512LL; bptr = &(dsk->raw_data[0]); for(i = 0; i < 512; i++) { bptr[i] = g_prodos_block0[i]; } // Directory is from blocks 2 through 5. Set up prev and next ptrs bptr = &(dsk->raw_data[2 * 0x200]); for(i = 0; i < 3; i++) { // Blocks 2,3,4 (or 3,4,5) dynapro_set_word16(&bptr[(i + 1)*0x200], i + 2); // Prev_blk dynapro_set_word16(&bptr[i*0x200 + 2], i + 3); // Next_blk } // Calculate bitmap to go in blocks 6... bitmap_size_bytes = (num_blocks + 7) >> 3; bitmap_size_blocks = (bitmap_size_bytes + 512 - 1) >> 9; bptr = &(dsk->raw_data[6 * 512]); // Block 6 bptr[0] = 0; for(ui = (6 + bitmap_size_blocks); ui < num_blocks; ui++) { pos = (ui >> 3); bptr[pos] |= (0x80U >> (ui & 7)); } infoptr = calloc(sizeof(Dynapro_info), 1); if(!infoptr) { return 0; } infoptr->root_path = kegs_malloc_str(dir_path); infoptr->volume_ptr = 0; infoptr->block_map_ptr = calloc(num_blocks * sizeof(Dynapro_map), 1); infoptr->damaged = 0; if((infoptr->root_path == 0) || (infoptr->block_map_ptr == 0)) { dynapro_error(dsk, "Could not allocate memory!\n"); return 0; } dsk->dynapro_info_ptr = infoptr; return 1; } word32 dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, word32 file_offset, word32 eof) { Dynapro_info *info_ptr; Dynapro_map *map_ptr; byte *buffer_ptr; word32 size, size_to_end; info_ptr = dsk->dynapro_info_ptr; if(!info_ptr || (block_num >= (dsk->dimage_size >> 9))) { printf(" mapping file %s, block %04x is invalid\n", fileptr->unix_path, block_num); return 0; } if(info_ptr->block_map_ptr == 0) { return 0; } if(block_num == 0) { return 1; } map_ptr = &(info_ptr->block_map_ptr[block_num]); if((map_ptr->file_ptr != 0) || (map_ptr->next_map_block != 0)) { dyna_printf("Mapping %s to block %04x, already has file_ptr:" "%p, next_map:%04x, mod:%d\n", fileptr->unix_path, block_num, map_ptr->file_ptr, map_ptr->next_map_block, map_ptr->modified); if(map_ptr->file_ptr) { dyna_printf(" Existing file: %s\n", map_ptr->file_ptr->unix_path); } return 0; } //printf(" map file %s block %05x off:%08x\n", fileptr->unix_path, // block_num, file_offset); map_ptr->next_map_block = fileptr->map_first_block; fileptr->map_first_block = block_num; map_ptr->modified = 0; map_ptr->file_ptr = fileptr; if(file_offset >= eof) { return 1; // This block was an "overhead" block } buffer_ptr = fileptr->buffer_ptr; if(buffer_ptr) { // Copy this block in at file_offset size = 0x200; size_to_end = eof - file_offset; if(size_to_end < size) { size = size_to_end; } #if 0 printf("mofb: Write to %p + %07x from block %04x, size:%04x\n", buffer_ptr, file_offset, block_num, size); #endif memcpy(buffer_ptr + file_offset, &(dsk->raw_data[block_num * 0x200]), size); } return 1; } word32 dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, int level, word32 file_offset, word32 eof) { byte *bptr; word32 entry_inc, tmp, ret; int i; #if 0 printf("dynapro_map_file_blocks %s block_num %05x level:%d off:%08x\n", fileptr->unix_path, block_num, level, file_offset); #endif if(level == 0) { return 0; // Bad value, should not happen } if(level == 1) { return dynapro_map_one_file_block(dsk, fileptr, block_num, file_offset, eof); } ret = dynapro_map_one_file_block(dsk, fileptr, block_num, 1U << 30, 0); if(ret == 0) { return ret; } entry_inc = 512; if(level == 3) { // Tree entry_inc = 256*512; } bptr = &(dsk->raw_data[block_num * 0x200]); for(i = 0; i < 256; i++) { tmp = bptr[i] + (bptr[256 + i] << 8); if(tmp == 0) { continue; } ret = dynapro_map_file_blocks(dsk, fileptr, tmp, level - 1, file_offset + i*entry_inc, eof); if(ret == 0) { return ret; } } dynapro_debug_map(dsk, "post map_file_blocks"); return 1; } word32 dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data) { word32 block_num, ret; int level; level = (fileptr->prodos_name[0] >> 4) & 0xf; block_num = fileptr->key_block; if(level == 5) { // Forked file return applesingle_map_from_prodos(dsk, fileptr, do_file_data); } else if((level < 0) || (level >= 4)) { printf("Storage_type: %02x for %s is bad\n", level, fileptr->unix_path); return 0; } fileptr->buffer_ptr = 0; if(do_file_data) { // Create a place for data. We will free before returning fileptr->buffer_ptr = calloc(1, fileptr->eof + 0x200); if(fileptr->buffer_ptr == 0) { printf("malloc failed!\n"); return 0; } } // Must not return now before free'ing fileptr->buffer_ptr! ret = dynapro_map_file_blocks(dsk, fileptr, block_num, level, 0, fileptr->eof); // printf(" dynapro_map_file, map_file_blocks ret:%04x\n", ret); if((ret != 0) && (do_file_data)) { // Then, write buffer_ptr to the unix file ret = dynapro_write_to_unix_file(fileptr->unix_path, fileptr->buffer_ptr, fileptr->eof); // printf(" map_file, write_to_unix_file ret:%04x\n", ret); } // And free the buffer_ptr free(fileptr->buffer_ptr); fileptr->buffer_ptr = 0; return ret; } word32 dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr) { byte *bptr; word32 block_num, ret; int cnt; // Loop over all directory blocks marking the map block_num = fileptr->key_block; if(block_num == 0) { printf("dynapro_map_dir_blocks, block_num is 0\n"); return 0; } bptr = &(dsk->raw_data[0]); cnt = 0; fileptr->map_first_block = 0; while(block_num != 0) { ret = dynapro_map_one_file_block(dsk, fileptr, block_num, 1U << 30, 0); if(ret == 0) { printf("dynapro_map_dir_on_block, ret 0, block:%04x\n", block_num); return 0; } block_num = dynapro_get_word16(&bptr[(block_num * 0x200) + 2]); cnt++; if(cnt > 1000) { printf("Directory had loop in it, error\n"); return 0; } } dynapro_debug_map(dsk, "post map_dir_blocks"); return 1; } word32 dynapro_build_map(Disk *dsk, Dynapro_file *fileptr) { word32 ret; if(fileptr == 0) { return 0; } // printf("### dynapro_build_map for dir:%s\n", fileptr->unix_path); // fileptr points to a directory header (volume or subdir). Walk // all siblings and build a map ret = 1; while(fileptr && ret) { if(fileptr->prodos_name[0] >= 0xe0) { // Directory/Volume header ret = dynapro_map_dir_blocks(dsk, fileptr); } else if(fileptr->subdir_ptr) { // Recurse to handle subdirectory ret = dynapro_build_map(dsk, fileptr->subdir_ptr); } else { ret = dynapro_map_file(dsk, fileptr, 0); } fileptr = fileptr->next_ptr; } dynapro_debug_map(dsk, "post build_map"); return ret; } int dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks) { word32 ret; #if 0 printf("dynapro_mount: %p, %s %08x\n", dsk, dir_path, num_blocks); #endif if(num_blocks >= 65536) { num_blocks = 65535; } ret = dynapro_prep_image(dsk, dir_path, num_blocks); if(ret == 0) { return -1; } ret = dynapro_create_dir(dsk, dir_path, 0, 0x404); // Block 2, +4 // printf("dynapro_mount will end with ret:%05x\n", ret); if(ret != 0) { ret = dynapro_build_map(dsk, dsk->dynapro_info_ptr->volume_ptr); } // dynapro_debug_update(dsk); if(ret == 0) { dynapro_error(dsk, "Folder too large. dynapro_build_map " "ret:0\n"); } else { ret = dynapro_validate_disk(dsk); } if(ret == 0) { dynapro_error(dsk, "dynapro_validate_disk ret:0\n"); dynapro_free_dynapro_info(dsk); return -1; } #ifndef _WIN32 setvbuf(stdout, 0, _IOLBF, 0); #endif dsk->fd = 0; return 0; } ================================================ FILE: gsplus/src/engine.h ================================================ // "@(#)$KmKId: engine.h,v 1.9 2023-09-11 12:55:16+00 kentd Exp $" /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ int ENGINE_TYPE (Engine_reg *engine_ptr) { register byte *ptr; byte *arg_ptr; Pc_log *tmp_pc_ptr; Fplus *fplus_ptr; byte *stat; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; register word32 kpc, acc, xreg, yreg, direct, psr, zero, neg7, addr; word32 wstat, arg, stack, dbank, opcode, addr_latch, tmp1, tmp2; word32 getmem_tmp, save_addr, pull_tmp, tmp_bytes, dummy1; tmp_pc_ptr = 0; dummy1 = 0; if(tmp_pc_ptr || dummy1) { // "use" tmp_pc_ptr to avoid warning } kpc = engine_ptr->kpc; acc = engine_ptr->acc; xreg = engine_ptr->xreg; yreg = engine_ptr->yreg; stack = engine_ptr->stack; dbank = engine_ptr->dbank; direct = engine_ptr->direct; psr = engine_ptr->psr; fplus_ptr = engine_ptr->fplus_ptr; zero = !(psr & 2); neg7 = psr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; dfcyc = engine_ptr->dfcyc; g_ret1 = 0; while(dfcyc <= g_dcycles_end) { FETCH_OPCODE; LOG_PC_MACRO(); switch(opcode) { default: halt_printf("acc8 unk op: %02x\n", opcode); arg = 9 #include "defs_instr.h" * 2; break; #include "instable.h" break; } LOG_PC_MACRO2(); } engine_ptr->kpc = kpc; engine_ptr->acc = acc; engine_ptr->xreg = xreg; engine_ptr->yreg = yreg; engine_ptr->stack = stack; engine_ptr->dbank = dbank; engine_ptr->direct = direct; engine_ptr->dfcyc = dfcyc; psr = psr & (~0x82); psr |= (neg7 & 0x80); psr |= ((!zero) << 1); engine_ptr->psr = psr; return g_ret1; } ================================================ FILE: gsplus/src/engine_c.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" // PSR[8:0] is E_NVMX_DIZC extern int g_limit_speed; extern int g_halt_sim; extern int g_engine_recalc_event; extern int g_code_red; extern int g_ignore_halts; extern int g_user_halt_bad; extern dword64 g_dcycles_end; extern dword64 g_last_vbl_dfcyc; extern dword64 g_cur_dfcyc; extern int g_wait_pending; extern int g_irq_pending; extern int g_num_brk; extern int g_num_cop; extern int g_emul_6502_ind_page_cross_bug; extern byte *g_slow_memory_ptr; extern byte *g_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern byte *g_dummy_memory1_ptr; extern int g_num_breakpoints; extern Break_point g_break_pts[]; extern Kimage g_debugwin_kimage; extern word32 g_log_pc_enable; extern Pc_log *g_log_pc_ptr; extern Pc_log *g_log_pc_start_ptr; extern Pc_log *g_log_pc_end_ptr; extern Data_log *g_log_data_ptr; extern Data_log *g_log_data_start_ptr; extern Data_log *g_log_data_end_ptr; int g_ret1 = 0; int size_tab[] = { #include "size_c.h" }; int bogus[] = { 0, #include "op_routs.h" }; #define INC_KPC_1 kpc = (kpc & 0xff0000) + ((kpc + 1) & 0xffff); #define INC_KPC_2 kpc = (kpc & 0xff0000) + ((kpc + 2) & 0xffff); #define INC_KPC_3 kpc = (kpc & 0xff0000) + ((kpc + 3) & 0xffff); #define INC_KPC_4 kpc = (kpc & 0xff0000) + ((kpc + 4) & 0xffff); #define CYCLES_PLUS_1 dfcyc += dplus_1; #define CYCLES_PLUS_2 dfcyc += dplus_1 * 2; #define CYCLES_PLUS_3 dfcyc += dplus_1 * 3; #define CYCLES_PLUS_4 dfcyc += dplus_1 * 4; #define CYCLES_PLUS_5 dfcyc += dplus_1 * 5; #define CYCLES_MINUS_1 dfcyc -= dplus_1; #define CYCLES_MINUS_2 dfcyc -= dplus_1 * 2; #define FCYCLES_ROUND dfcyc = dfcyc + dplus_x_m1; \ dfcyc = (dfcyc >> 16) << 16; #define GET_1BYTE_ARG arg = arg_ptr[1]; #define GET_2BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8); #define GET_3BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8) + (arg_ptr[3]<<16); #define LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) \ g_log_data_ptr->dfcyc = dfcyc; \ g_log_data_ptr->stat = in_stat; \ g_log_data_ptr->addr = in_addr; \ g_log_data_ptr->val = in_val; \ g_log_data_ptr->size = in_size; \ g_log_data_ptr++; \ if(g_log_data_ptr >= g_log_data_end_ptr) { \ g_log_data_ptr = g_log_data_start_ptr; \ } /* HACK HACK HACK */ #define UPDATE_PSR(dummy, old_psr) \ if(psr & 0x100) { \ psr |= 0x30; \ stack = 0x100 + (stack & 0xff); \ } \ if((~old_psr & psr) & 0x10) { \ xreg = xreg & 0xff; \ yreg = yreg & 0xff; \ } \ if(((psr & 4) == 0) && g_irq_pending) { \ FINISH(RET_IRQ, 0); \ } \ if((old_psr ^ psr) & 0x20) { \ FINISH(RET_PSR, 0); \ } extern Page_info page_info_rd_wr[]; extern word32 g_slow_mem_changed[]; #define GET_MEMORY8(addr,dest) \ addr_latch = (addr); \ CYCLES_PLUS_1; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if(wstat & (1 << (31 - BANK_IO_BIT))) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory8_io_stub((addr), stat, \ &dcycles_tmp1, dplus_x_m1); \ dfcyc = dcycles_tmp1; \ } else { \ dest = *ptr; \ } #define GET_MEMORY(addr,dest) GET_MEMORY8(addr, dest) #define GET_MEMORY16(addr, dest, in_bank) \ save_addr = addr; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xff) == 0xff)) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory16_pieces_stub((addr), stat, \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_2; \ dest = ptr[0] + (ptr[1] << 8); \ } \ addr_latch = save_addr; #define GET_MEMORY24(addr, dest, in_bank) \ save_addr = addr; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xfe) == 0xfe)) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory24_pieces_stub((addr), stat, \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_3; \ dest = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16); \ } \ addr_latch = save_addr; #define GET_MEMORY_DIRECT_PAGE16(addr, dest, dloc_x_wrap) \ save_addr = addr; \ if(psr & 0x100) { \ if((direct & 0xff) == 0) { \ save_addr = (save_addr & 0xff) + direct; \ } \ } \ if((psr & 0x100) && (((addr) & 0xff) == 0xff)) { \ GET_MEMORY8(save_addr, getmem_tmp); \ if(dloc_x_wrap) { \ save_addr = (save_addr & 0xff00) | \ ((save_addr + 1) & 0xff); \ } else { \ save_addr = (save_addr + 1) & 0xffff; \ } \ if((direct & 0xff) == 0) { \ save_addr = (save_addr & 0xff) + direct; \ } \ GET_MEMORY8(save_addr, dest); \ dest = (dest << 8) + getmem_tmp; \ } else { \ GET_MEMORY16(save_addr, dest, 1); \ } #define PUSH8(arg) \ SET_MEMORY8(stack, arg); \ stack = (stack - 1) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PUSH16(arg) \ if((stack & 0xfe) == 0) { \ /* stack will cross page! */ \ PUSH8((arg) >> 8); \ PUSH8(arg); \ } else { \ stack = (stack - 2) & 0xffff; \ SET_MEMORY16(stack + 1, arg, 1); \ } #define PUSH16_UNSAFE(arg) \ save_addr = (stack - 1) & 0xffff; \ stack = (stack - 2) & 0xffff; \ SET_MEMORY16(save_addr, arg, 1); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PUSH24_UNSAFE(arg) \ save_addr = (stack - 2) & 0xffff; \ stack = (stack - 3) & 0xffff; \ SET_MEMORY24(save_addr, arg, 1); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL8(dest) \ stack++; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ stack = stack & 0xffff; \ GET_MEMORY8(stack, dest); #define PULL8_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY8(stack, dest); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL16(dest) \ if((stack & 0xfe) == 0xfe) { /* page cross */ \ PULL8(dest); \ PULL8(pull_tmp); \ dest = (pull_tmp << 8) + dest; \ } else { \ GET_MEMORY16(stack + 1, dest, 1); \ stack = (stack + 2) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ } #define PULL16_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY16(stack, dest, 1); \ stack = (stack + 1) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL24(dest) \ if((stack & 0xfc) == 0xfc) { /* page cross */ \ PULL8(dest); \ PULL8(pull_tmp); \ pull_tmp = (pull_tmp << 8) + dest; \ PULL8(dest); \ dest = (dest << 16) + pull_tmp; \ } else { \ GET_MEMORY24(stack + 1, dest, 1); \ stack = (stack + 3) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ } #define PULL24_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY24(stack, dest, 1); \ stack = (stack + 2) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define SET_MEMORY8(addr, val) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 8, stat); \ CYCLES_PLUS_1; \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if(wstat) { \ dcycles_tmp1 = dfcyc; \ set_memory8_io_stub((addr), val, stat, &dcycles_tmp1, \ dplus_x_m1); \ dfcyc = dcycles_tmp1; \ } else { \ *ptr = val; \ } #define SET_MEMORY16(addr, val, in_bank) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 16, stat); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat) || (((addr) & 0xff) == 0xff)) { \ dcycles_tmp1 = dfcyc; \ set_memory16_pieces_stub((addr), (val), \ &dcycles_tmp1, dplus_1, dplus_x_m1, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_2; \ ptr[0] = (val); \ ptr[1] = (val) >> 8; \ } #define SET_MEMORY24(addr, val, in_bank) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 24, stat); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat) || (((addr) & 0xfe) == 0xfe)) { \ dcycles_tmp1 = dfcyc; \ set_memory24_pieces_stub((addr), (val), \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_3; \ ptr[0] = (val); \ ptr[1] = (val) >> 8; \ ptr[2] = (val) >> 16; \ } word32 get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1) { dword64 dfcyc; word32 wstat; byte *ptr; wstat = PTR2WORD(stat) & 0xff; dfcyc = *dcycs_ptr; if(wstat & BANK_BREAK) { check_breakpoints(addr, dfcyc, 0, 1); } if(wstat & BANK_IO2_TMP) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; return get_memory_io((addr), dcycs_ptr); } else { ptr = stat - wstat + (addr & 0xff); return *ptr; } } word32 get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, wstat, ret, tmp1, addr_latch; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; GET_MEMORY8(addr, tmp1); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } GET_MEMORY8(addrp1, ret); *dcycs_ptr = dfcyc; return (ret << 8) + (tmp1); } word32 get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, addrp2, wstat, addr_latch, ret, tmp1, tmp2; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; GET_MEMORY8(addr, tmp1); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } GET_MEMORY8(addrp1, tmp2); addrp2 = addr + 2; if(in_bank) { addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); } GET_MEMORY8(addrp2, ret); *dcycs_ptr = dfcyc; return (ret << 16) + (tmp2 << 8) + tmp1; } void set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1) { byte *ptr; dword64 dfcyc; word32 setmem_tmp1, tmp1, tmp2, wstat; wstat = PTR2WORD(stat) & 0xff; dfcyc = *dcycs_ptr; if(wstat & (1 << (31 - BANK_BREAK_BIT))) { check_breakpoints(addr, dfcyc, 0, 2); } ptr = stat - wstat + ((addr) & 0xff); if(wstat & (1 << (31 - BANK_IO2_BIT))) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; set_memory_io((addr), val, dcycs_ptr); } else if(wstat & (1 << (31 - BANK_SHADOW_BIT))) { if(g_limit_speed) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; } tmp1 = (addr & 0xffff); setmem_tmp1 = g_slow_memory_ptr[tmp1]; *ptr = val; g_slow_memory_ptr[tmp1] = val; if(setmem_tmp1 != ((val) & 0xff)) { g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); } } else if(wstat & (1 << (31 - BANK_SHADOW2_BIT))) { if(g_limit_speed) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; } tmp2 = (addr & 0xffff); tmp1 = 0x10000 + tmp2; setmem_tmp1 = g_slow_memory_ptr[tmp1]; *ptr = val; g_slow_memory_ptr[tmp1] = val; if(setmem_tmp1 != ((val) & 0xff)) { g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); if((tmp1 & 0xff00) == 0x9d00) { scb_changed(dfcyc, tmp1, val, setmem_tmp1); } } } else { /* breakpoint only */ *ptr = val; } } #define LOG_PC_MACRO() #define LOG_PC_MACRO2() #define LOG_DATA_MACRO(addr, val, size, in_stat) void set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, dword64 dplus_1, dword64 dplus_x_m1, int in_bank) { byte *ptr; byte *stat; dword64 dfcyc, dcycles_tmp1; word32 addrp1, wstat; dfcyc = *dcycs_ptr; SET_MEMORY8(addr, val); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } SET_MEMORY8(addrp1, val >> 8); *dcycs_ptr = dfcyc; } void set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; byte *stat; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, addrp2; word32 wstat; dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; SET_MEMORY8(addr, val); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } SET_MEMORY8(addrp1, val >> 8); addrp2 = addr + 2; if(in_bank) { addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); } SET_MEMORY8(addrp2, val >> 16); *dcycs_ptr = dfcyc; } word32 get_memory_c(word32 addr) { byte *stat, *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addr_latch, wstat, ret; dfcyc = 0; dplus_1 = 0; dplus_x_m1 = 0; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } GET_MEMORY8(addr, ret); return ret; } word32 get_memory16_c(word32 addr) { return get_memory_c(addr) + (get_memory_c(addr+1) << 8); } word32 get_memory24_c(word32 addr) { return get_memory_c(addr) + (get_memory_c(addr+1) << 8) + (get_memory_c(addr+2) << 16); } void set_memory_c(word32 addr, word32 val, int do_log) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 wstat; dfcyc = g_cur_dfcyc; dplus_1 = 0; dplus_x_m1 = 0; SET_MEMORY8(addr, val); if(g_log_pc_enable && do_log) { LOG_DATA_MACRO_ACT(addr, val, 8, stat) } } void set_memory16_c(word32 addr, word32 val, int do_log) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 wstat; dfcyc = g_cur_dfcyc; dplus_1 = 0; dplus_x_m1 = 0; SET_MEMORY16(addr, val, 0); if(g_log_pc_enable && do_log) { LOG_DATA_MACRO_ACT(addr, val, 16, stat) } } void set_memory24_c(word32 addr, word32 val) { set_memory_c(addr, val, 1); set_memory_c(addr + 1, val >> 8, 1); set_memory_c(addr + 2, val >> 16, 1); } word32 do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub) { word32 sum, carry, overflow; word32 zero; int decimal; overflow = 0; decimal = psr & 8; if(sub) { in2 = (in2 ^ 0xff); } if(!decimal) { sum = (in1 & 0xff) + in2 + (psr & 1); overflow = ((sum ^ in2) >> 1) & 0x40; } else { /* decimal */ sum = (in1 & 0xf) + (in2 & 0xf) + (psr & 1); if(sub) { if(sum < 0x10) { sum = (sum - 0x6) & 0xf; } } else { if(sum >= 0xa) { sum = (sum - 0xa) | 0x10; } } sum = (in1 & 0xf0) + (in2 & 0xf0) + sum; overflow = ((sum >> 2) ^ (sum >> 1)) & 0x40; if(sub) { if(sum < 0x100) { sum = (sum + 0xa0) & 0xff; } } else { if(sum >= 0xa0) { sum += 0x60; } } } zero = ((sum & 0xff) == 0); carry = (sum >= 0x100); if((in1 ^ in2) & 0x80) { overflow = 0; } psr = psr & (~0xc3); psr = psr + (sum & 0x80) + overflow + (zero << 1) + carry; return (psr << 16) + (sum & 0xff); } word32 do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub) { word32 sum, carry, overflow; word32 tmp1, tmp2; word32 zero; int decimal; overflow = 0; decimal = psr & 8; if(!decimal) { if(sub) { in2 = (in2 ^ 0xffff); } sum = in1 + in2 + (psr & 1); overflow = ((sum ^ in2) >> 9) & 0x40; } else { /* decimal */ if(sub) { tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); psr = (tmp1 >> 16); tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, (in2 >> 8) & 0xff, psr, sub); in2 = (in2 ^ 0xfffff); } else { tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); psr = (tmp1 >> 16); tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, (in2 >> 8) &0xff, psr, sub); } sum = ((tmp2 & 0xff) << 8) + (tmp1 & 0xff) + (((tmp2 >> 16) & 1) << 16); overflow = (tmp2 >> 16) & 0x40; } zero = ((sum & 0xffff) == 0); carry = (sum >= 0x10000); if((in1 ^ in2) & 0x8000) { overflow = 0; } psr = psr & (~0xc3); psr = psr + ((sum & 0x8000) >> 8) + overflow + (zero << 1) + carry; return (psr << 16) + (sum & 0xffff); } void fixed_memory_ptrs_init() { /* set g_slow_memory_ptr, g_rom_fc_ff_ptr, g_dummy_memory1_ptr, */ /* and rom_cards_ptr */ g_slow_memory_ptr = memalloc_align(128*1024, 0, 0); g_dummy_memory1_ptr = memalloc_align(256, 1024, 0); g_rom_fc_ff_ptr = memalloc_align(256*1024, 512, 0); g_rom_cards_ptr = memalloc_align(16*256, 256, 0); #if 0 printf("g_memory_ptr: %08x, dummy_mem: %08x, slow_mem_ptr: %08x\n", (word32)g_memory_ptr, (word32)g_dummy_memory1_ptr, (word32)g_slow_memory_ptr); printf("g_rom_fc_ff_ptr: %08x, g_rom_cards_ptr: %08x\n", (word32)g_rom_fc_ff_ptr, (word32)g_rom_cards_ptr); printf("page_info_rd = %08x, page_info_wr end = %08x\n", (word32)&(page_info_rd_wr[0]), (word32)&(page_info_rd_wr[PAGE_INFO_PAD_SIZE+0x1ffff].rd_wr)); #endif } word32 get_itimer() { #if defined(__i386) && defined(__GNUC__) /* Here's my bad ia32 asm code to do rdtsc */ /* Linux source uses: */ /* asm volatile("rdtsc" : "=a"(ret) : : "edx"); */ /* asm volatile("rdtsc" : "=%eax"(ret) : : "%edx"); */ /* GCC bug report 2001-03/msg00786.html used: */ /*register dword64 dtmp; */ /*asm volatile ("rdtsc" : "=A" (dtmp)); */ /*return (word32)dtmp; */ register word32 ret; asm volatile ("rdtsc;movl %%eax,%0" : "=r"(ret) : : "%eax","%edx"); return ret; #else # if defined(__POWERPC__) && defined(__GNUC__) register word32 ret; asm volatile ("mftb %0" : "=r"(ret)); return ret; # else # if defined(__x86_64__) register word32 ret, hi; //ret = __rdtsc(); asm volatile ("rdtsc" : "=a" (ret), "=d" (hi)); return ret; # else # if defined(__aarch64__) /* 64-bit ARM architecture */ register dword64 ret; asm volatile("mrs %0,CNTVCT_EL0" : "=r"(ret)); return ret; # else return 0; # endif # endif # endif #endif } void engine_recalc_events() { g_dcycles_end = 0; // End inner loop g_engine_recalc_event++; } void set_halt_act(int val) { if((val == 1) && g_ignore_halts && !g_user_halt_bad) { g_code_red++; } else { if(g_halt_sim == 0) { debugger_update_list_kpc(); } g_halt_sim |= val; if(g_halt_sim) { video_set_active(&g_debugwin_kimage, 1); } g_dcycles_end = 0; } } void clr_halt_act() { g_halt_sim = 0; } word32 get_remaining_operands(word32 addr, word32 opcode, word32 psr, dword64 *dcyc_ptr, Fplus *fplus_ptr) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 addr_latch, wstat, save_addr, arg, addrp1; int size; dfcyc = *dcyc_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } size = size_tab[opcode]; addrp1 = (addr & 0xff0000) + ((addr + 1) & 0xffff); switch(size) { case 0: // Always read pc+1 for single-byte opcodes GET_MEMORY8(addrp1, arg); arg = 0; /* no args */ break; case 1: GET_MEMORY8(addrp1, arg); break; /* 1 arg, already done */ case 2: GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; break; case 3: GET_MEMORY24(addrp1, arg, 1); dfcyc = dfcyc - dplus_1 - dplus_1; break; case 4: if(psr & 0x20) { GET_MEMORY8(addrp1, arg); } else { GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; } break; case 5: if(psr & 0x10) { GET_MEMORY8(addrp1, arg); } else { GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; } break; default: printf("Unknown size: %d\n", size); arg = 0; exit(-2); } *dcyc_ptr = dfcyc; return arg; } #define FETCH_OPCODE \ addr = kpc; \ CYCLES_PLUS_2; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ arg_ptr = ptr; \ opcode = *ptr; \ if((wstat & (1 << (31-BANK_IO_BIT))) || ((addr & 0xff) > 0xfc)) {\ CYCLES_MINUS_1; \ if(wstat & BANK_BREAK) { \ check_breakpoints(addr, dfcyc, stack, 4); \ } \ if(wstat & (1 << (31 - BANK_IO2_BIT))) { \ FCYCLES_ROUND; \ dcycles_tmp1 = dfcyc; \ opcode = get_memory_io((addr), &dcycles_tmp1); \ dfcyc = dcycles_tmp1; \ } else { \ opcode = *ptr; \ } \ dcycles_tmp1 = dfcyc; \ arg = get_remaining_operands(addr, opcode, psr, \ &dcycles_tmp1, fplus_ptr); \ dfcyc = dcycles_tmp1; \ arg_ptr = (byte *)&tmp_bytes; \ arg_ptr[1] = arg; \ arg_ptr[2] = arg >> 8; \ arg_ptr[3] = arg >> 16; \ } #define ACC8 #define IS_ACC16 0 #define ENGINE_TYPE enter_engine_acc8 #include "engine.h" // The above creates enter_engine_acc8 #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define IS_ACC16 1 #define ENGINE_TYPE enter_engine_acc16 #include "engine.h" // The above creates enter_engine_acc16 #undef LOG_PC_MACRO #undef LOG_PC_MACRO2 #undef LOG_DATA_MACRO #define LOG_PC_MACRO() \ tmp_pc_ptr = g_log_pc_ptr; \ tmp_pc_ptr->dbank_kpc = (dbank << 24) + kpc; \ tmp_pc_ptr->instr = (opcode << 24) + arg_ptr[1] + \ (arg_ptr[2] << 8) + (arg_ptr[3] << 16); \ tmp_pc_ptr->dfcyc = dfcyc - dplus_1 * 2; #define LOG_PC_MACRO2() \ tmp_pc_ptr->psr_acc = ((psr & ~(0x82)) << 16) | acc | \ ((neg7 & 0x80) << 16) | ((!zero) << 17); \ tmp_pc_ptr->xreg_yreg = (xreg << 16) + yreg; \ tmp_pc_ptr->stack_direct = (stack << 16) + direct; \ tmp_pc_ptr++; \ if(tmp_pc_ptr >= g_log_pc_end_ptr) { \ tmp_pc_ptr = g_log_pc_start_ptr; \ } \ g_log_pc_ptr = tmp_pc_ptr; #define LOG_DATA_MACRO(in_addr, in_val, in_size, in_stat) \ LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define ACC8 #define IS_ACC16 0 #define ENGINE_TYPE enter_engine_acc8_log #include "engine.h" // The above creates enter_engine_acc8_log #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define IS_ACC16 1 #define ENGINE_TYPE enter_engine_acc16_log #include "engine.h" // The above creates enter_engine_acc16_log int enter_engine(Engine_reg *engine_ptr) { dword64 dcycles_end_save; int ret; dcycles_end_save = g_dcycles_end; while(1) { if(g_log_pc_enable) { if(engine_ptr->psr & 0x20) { // 8-bit accumulator ret = enter_engine_acc8_log(engine_ptr); } else { ret = enter_engine_acc16_log(engine_ptr); } } else { if(engine_ptr->psr & 0x20) { // 8-bit accumulator ret = enter_engine_acc8(engine_ptr); } else { ret = enter_engine_acc16(engine_ptr); } } if((ret == RET_PSR) && !g_halt_sim) { g_dcycles_end = dcycles_end_save; continue; } return ret; } } ================================================ FILE: gsplus/src/instable.h ================================================ // "@(#)$KmKId: instable.h,v 1.121 2023-11-12 15:31:14+00 kentd Exp $" /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ case 0x00: /* brk */ GET_1BYTE_ARG; g_num_brk++; INC_KPC_2; psr = (psr & (~0x82)) | (neg7 & 0x80) | ((!zero) << 1); if(psr & 0x100) { PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xfffffe; dbank = 0; } else { PUSH8(kpc >> 16); PUSH16(kpc); PUSH8(psr & 0xff); tmp1 = 0xffffe6; halt_printf("Halting for native break!\n"); } tmp1 = moremem_fix_vector_pull(tmp1); GET_MEMORY16(tmp1, kpc, 0); kpc = kpc & 0xffff; psr |= 0x4; psr &= ~(0x8); break; case 0x01: /* ORA (Dloc,X) */ GET_DLOC_X_IND_RD(); ORA_INST(); break; case 0x02: /* COP */ g_num_cop++; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); if(psr & 0x100) { halt_printf("Halting for emul COP at %04x\n", kpc); PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xfffff4; dbank = 0; } else { PUSH8(kpc >> 16); PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xffffe4; } tmp1 = moremem_fix_vector_pull(tmp1); GET_MEMORY16(tmp1, kpc, 0); kpc = kpc & 0xffff; psr |= 4; psr &= ~(0x8); break; case 0x03: /* ORA Disp8,S */ GET_DISP8_S_RD(); ORA_INST(); break; case 0x04: /* TSB Dloc */ GET_DLOC_RD_RMW(); TSB_INST(1); break; case 0x05: /* ORA Dloc */ GET_DLOC_RD(); ORA_INST(); break; case 0x06: /* ASL Dloc */ GET_DLOC_RD_RMW(); ASL_INST(1); break; case 0x07: /* ORA [Dloc] */ GET_DLOC_L_IND_RD(); ORA_INST(); break; case 0x08: /* PHP */ INC_KPC_1; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); PUSH8(psr); break; case 0x09: /* ORA #imm */ GET_IMM_MEM(); ORA_INST(); break; case 0x0a: /* ASL a */ INC_KPC_1; tmp1 = acc + acc; #ifdef ACC8 SET_CARRY8(tmp1); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(acc & 0xff); #else SET_CARRY16(tmp1); acc = tmp1 & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x0b: /* PHD */ INC_KPC_1; PUSH16_UNSAFE(direct); break; case 0x0c: /* TSB abs */ GET_ABS_RD_RMW(); TSB_INST(0); break; case 0x0d: /* ORA abs */ GET_ABS_RD(); ORA_INST(); break; case 0x0e: /* ASL abs */ GET_ABS_RD_RMW(); ASL_INST(0); break; case 0x0f: /* ORA long */ GET_LONG_RD(); ORA_INST(); break; case 0x10: /* BPL disp8 */ BRANCH_DISP8((neg7 & 0x80) == 0); break; case 0x11: /* ORA (Dloc),y */ GET_DLOC_IND_Y_RD(); ORA_INST(); break; case 0x12: /* ORA (Dloc) */ GET_DLOC_IND_RD(); ORA_INST(); break; case 0x13: /* ORA (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); ORA_INST(); break; case 0x14: /* TRB Dloc */ GET_DLOC_RD_RMW(); TRB_INST(1); break; case 0x15: /* ORA Dloc,x */ GET_DLOC_X_RD(); ORA_INST(); break; case 0x16: /* ASL Dloc,X */ GET_DLOC_X_RD_RMW(); ASL_INST(1); break; case 0x17: /* ORA [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); ORA_INST(); break; case 0x18: /* CLC */ psr = psr & (~1); INC_KPC_1; break; case 0x19: /* ORA abs,y */ GET_ABS_Y_RD(); ORA_INST(); break; case 0x1a: /* INC a */ INC_KPC_1; #ifdef ACC8 acc = (acc & 0xff00) | ((acc + 1) & 0xff); SET_NEG_ZERO8(acc & 0xff); #else acc = (acc + 1) & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x1b: /* TCS */ stack = acc; INC_KPC_1; if(psr & 0x100) { stack = (stack & 0xff) + 0x100; } break; case 0x1c: /* TRB Abs */ GET_ABS_RD_RMW(); TRB_INST(0); break; case 0x1d: /* ORA Abs,X */ GET_ABS_X_RD(); ORA_INST(); break; case 0x1e: /* ASL Abs,X */ GET_ABS_X_RD_RMW(); ASL_INST(0); break; case 0x1f: /* ORA Long,X */ GET_LONG_X_RD(); ORA_INST(); break; case 0x20: /* JSR abs */ GET_2BYTE_ARG; INC_KPC_2; PUSH16(kpc); kpc = (kpc & 0xff0000) + arg; CYCLES_PLUS_2; break; case 0x21: /* AND (Dloc,X) */ GET_DLOC_X_IND_RD(); AND_INST(); break; case 0x22: /* JSL Long */ GET_3BYTE_ARG; tmp1 = arg; CYCLES_PLUS_3; INC_KPC_3; PUSH24_UNSAFE(kpc); kpc = tmp1 & 0xffffff; break; case 0x23: /* AND Disp8,S */ GET_DISP8_S_RD(); AND_INST(); break; case 0x24: /* BIT Dloc */ GET_DLOC_RD(); BIT_INST(); break; case 0x25: /* AND Dloc */ GET_DLOC_RD(); AND_INST(); break; case 0x26: /* ROL Dloc */ GET_DLOC_RD_RMW(); ROL_INST(1); break; case 0x27: /* AND [Dloc] */ GET_DLOC_L_IND_RD(); AND_INST(); break; case 0x28: /* PLP */ PULL8(tmp1); tmp2 = psr; CYCLES_PLUS_1; INC_KPC_1; psr = (psr & ~0xff) | (tmp1 & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0x29: /* AND #imm */ GET_IMM_MEM(); AND_INST(); break; case 0x2a: /* ROL a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) << 1) + (psr & 1); SET_CARRY8(tmp1); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc << 1) + (psr & 1); SET_CARRY16(tmp1); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x2b: /* PLD */ INC_KPC_1; PULL16_UNSAFE(direct); CYCLES_PLUS_1; SET_NEG_ZERO16(direct); break; case 0x2c: /* BIT abs */ GET_ABS_RD(); BIT_INST(); break; case 0x2d: /* AND abs */ GET_ABS_RD(); AND_INST(); break; case 0x2e: /* ROL abs */ GET_ABS_RD_RMW(); ROL_INST(0); break; case 0x2f: /* AND long */ GET_LONG_RD(); AND_INST(); break; case 0x30: /* BMI disp8 */ BRANCH_DISP8(neg7 & 0x80); break; case 0x31: /* AND (Dloc),y */ GET_DLOC_IND_Y_RD(); AND_INST(); break; case 0x32: /* AND (Dloc) */ GET_DLOC_IND_RD(); AND_INST(); break; case 0x33: /* AND (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); AND_INST(); break; case 0x34: /* BIT Dloc,x */ GET_DLOC_X_RD(); BIT_INST(); break; case 0x35: /* AND Dloc,x */ GET_DLOC_X_RD(); AND_INST(); break; case 0x36: /* ROL Dloc,X */ GET_DLOC_X_RD_RMW(); ROL_INST(1); break; case 0x37: /* AND [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); AND_INST(); break; case 0x38: /* SEC */ psr = psr | 1; INC_KPC_1; break; case 0x39: /* AND abs,y */ GET_ABS_Y_RD(); AND_INST(); break; case 0x3a: /* DEC a */ INC_KPC_1; #ifdef ACC8 acc = (acc & 0xff00) | ((acc - 1) & 0xff); SET_NEG_ZERO8(acc & 0xff); #else acc = (acc - 1) & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x3b: /* TSC */ /* set N,Z according to 16 bit acc */ INC_KPC_1; acc = stack; SET_NEG_ZERO16(acc); break; case 0x3c: /* BIT Abs,x */ GET_ABS_X_RD(); BIT_INST(); break; case 0x3d: /* AND Abs,X */ GET_ABS_X_RD(); AND_INST(); break; case 0x3e: /* ROL Abs,X */ GET_ABS_X_RD_RMW(); ROL_INST(0); break; case 0x3f: /* AND Long,X */ GET_LONG_X_RD(); AND_INST(); break; case 0x40: /* RTI */ CYCLES_PLUS_1 if(psr & 0x100) { PULL24(tmp1); kpc = (kpc & 0xff0000) + ((tmp1 >> 8) & 0xffff); tmp2 = psr; psr = (psr & ~0xff) + (tmp1 & 0xff); neg7 = psr; zero = !(psr & 2); UPDATE_PSR(psr, tmp2); } else { PULL8(tmp1); tmp2 = psr; psr = (tmp1 & 0xff); neg7 = psr; zero = !(psr & 2); PULL24(kpc); UPDATE_PSR(psr, tmp2); } break; case 0x41: /* EOR (Dloc,X) */ GET_DLOC_X_IND_RD(); EOR_INST(); break; case 0x42: /* WDM */ GET_2BYTE_ARG; INC_KPC_2; if(arg < 0x100) { // Next byte is 00 INC_KPC_1; // Skip over the BRK } FINISH(RET_WDM, arg); break; case 0x43: /* EOR Disp8,S */ GET_DISP8_S_RD(); EOR_INST(); break; case 0x44: /* MVP */ GET_2BYTE_ARG; /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ if(psr & 0x100) { halt_printf("MVP in emulation!\n"); break; } dbank = arg & 0xff; tmp1 = (arg >> 8) & 0xff; CYCLES_PLUS_1; GET_MEMORY8((tmp1 << 16) + xreg, arg); SET_MEMORY8((dbank << 16) + yreg, arg); CYCLES_PLUS_2; xreg = (xreg - 1) & 0xffff; yreg = (yreg - 1) & 0xffff; if(psr & 0x10) { // 8-bit index registers xreg = xreg & 0xff; yreg = yreg & 0xff; } acc = (acc - 1) & 0xffff; if(acc == 0xffff) { INC_KPC_3; } break; case 0x45: /* EOR Dloc */ GET_DLOC_RD(); EOR_INST(); break; case 0x46: /* LSR Dloc */ GET_DLOC_RD_RMW(); LSR_INST(1); break; case 0x47: /* EOR [Dloc] */ GET_DLOC_L_IND_RD(); EOR_INST(); break; case 0x48: /* PHA */ INC_KPC_1; #ifdef ACC8 PUSH8(acc); #else PUSH16(acc); #endif break; case 0x49: /* EOR #imm */ GET_IMM_MEM(); EOR_INST(); break; case 0x4a: /* LSR a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) >> 1); SET_CARRY8(acc << 8); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc >> 1); SET_CARRY8((acc << 8)); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x4b: /* PHK */ PUSH8(kpc >> 16); INC_KPC_1; break; case 0x4c: /* JMP abs */ GET_2BYTE_ARG; CYCLES_PLUS_1; kpc = (kpc & 0xff0000) + arg; break; case 0x4d: /* EOR abs */ GET_ABS_RD(); EOR_INST(); break; case 0x4e: /* LSR abs */ GET_ABS_RD_RMW(); LSR_INST(0); break; case 0x4f: /* EOR long */ GET_LONG_RD(); EOR_INST(); break; case 0x50: /* BVC disp8 */ BRANCH_DISP8((psr & 0x40) == 0); break; case 0x51: /* EOR (Dloc),y */ GET_DLOC_IND_Y_RD(); EOR_INST(); break; case 0x52: /* EOR (Dloc) */ GET_DLOC_IND_RD(); EOR_INST(); break; case 0x53: /* EOR (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); EOR_INST(); break; case 0x54: /* MVN */ GET_2BYTE_ARG; /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ if(psr & 0x100) { halt_printf("MVN in emulation!\n"); break; } dbank = arg & 0xff; tmp1 = (arg >> 8) & 0xff; CYCLES_PLUS_1; GET_MEMORY8((tmp1 << 16) + xreg, arg); SET_MEMORY8((dbank << 16) + yreg, arg); CYCLES_PLUS_2; xreg = (xreg + 1) & 0xffff; yreg = (yreg + 1) & 0xffff; if(psr & 0x10) { // 8-bit index registers xreg = xreg & 0xff; yreg = yreg & 0xff; } acc = (acc - 1) & 0xffff; if(acc == 0xffff) { INC_KPC_3; } break; case 0x55: /* EOR Dloc,x */ GET_DLOC_X_RD(); EOR_INST(); break; case 0x56: /* LSR Dloc,X */ GET_DLOC_X_RD_RMW(); LSR_INST(1); break; case 0x57: /* EOR [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); EOR_INST(); break; case 0x58: /* CLI */ psr = psr & (~4); INC_KPC_1; if(((psr & 0x4) == 0) && g_irq_pending) { FINISH(RET_IRQ, 0); } break; case 0x59: /* EOR abs,y */ GET_ABS_Y_RD(); EOR_INST(); break; case 0x5a: /* PHY */ INC_KPC_1; if(psr & 0x10) { PUSH8(yreg); } else { PUSH16(yreg); } break; case 0x5b: /* TCD */ INC_KPC_1; direct = acc; SET_NEG_ZERO16(acc); break; case 0x5c: /* JMP Long */ GET_3BYTE_ARG; CYCLES_PLUS_2; kpc = arg; break; case 0x5d: /* EOR Abs,X */ GET_ABS_X_RD(); EOR_INST(); break; case 0x5e: /* LSR Abs,X */ GET_ABS_X_RD_RMW(); LSR_INST(0); break; case 0x5f: /* EOR Long,X */ GET_LONG_X_RD(); EOR_INST(); break; case 0x60: /* RTS */ CYCLES_PLUS_2 PULL16(tmp1); kpc = (kpc & 0xff0000) + ((tmp1 + 1) & 0xffff); break; case 0x61: /* ADC (Dloc,X) */ GET_DLOC_X_IND_RD(); ADC_INST(); break; case 0x62: /* PER */ GET_2BYTE_ARG; CYCLES_PLUS_2; INC_KPC_3; PUSH16_UNSAFE(kpc + arg); break; case 0x63: /* ADC Disp8,S */ GET_DISP8_S_RD(); ADC_INST(); break; case 0x64: /* STZ Dloc */ GET_DLOC_ADDR(); STZ_INST(1); break; case 0x65: /* ADC Dloc */ GET_DLOC_RD(); ADC_INST(); break; case 0x66: /* ROR Dloc */ GET_DLOC_RD_RMW(); ROR_INST(1); break; case 0x67: /* ADC [Dloc] */ GET_DLOC_L_IND_RD(); ADC_INST(); break; case 0x68: /* PLA */ INC_KPC_1; CYCLES_PLUS_1; #ifdef ACC8 PULL8(tmp1); acc = (acc & 0xff00) + tmp1; SET_NEG_ZERO8(tmp1); #else PULL16(tmp1); acc = tmp1; SET_NEG_ZERO16(tmp1); #endif break; case 0x69: /* ADC #imm */ GET_IMM_MEM(); ADC_INST(); break; case 0x6a: /* ROR a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) >> 1) + ((psr & 1) << 7); SET_CARRY8((acc << 8)); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc >> 1) + ((psr & 1) << 15); SET_CARRY16((acc << 16)); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x6b: /* RTL */ CYCLES_PLUS_1; PULL24_UNSAFE(tmp1); kpc = (tmp1 & 0xff0000) + ((tmp1 + 1) & 0xffff); break; case 0x6c: /* JMP (abs) */ GET_2BYTE_ARG; CYCLES_PLUS_1; GET_MEMORY16(arg, tmp1, 1); if((psr & 0x100) && g_emul_6502_ind_page_cross_bug && ((arg & 0xff) == 0xff)) { GET_MEMORY8(arg - 0xff, tmp2); tmp1 = (tmp1 & 0xff) + (tmp2 << 8); halt_printf("Halting for emul ind jmp\n"); } kpc = (kpc & 0xff0000) + tmp1; break; case 0x6d: /* ADC abs */ GET_ABS_RD(); ADC_INST(); break; case 0x6e: /* ROR abs */ GET_ABS_RD_RMW(); ROR_INST(0); break; case 0x6f: /* ADC long */ GET_LONG_RD(); ADC_INST(); break; case 0x70: /* BVS disp8 */ BRANCH_DISP8((psr & 0x40)); break; case 0x71: /* ADC (Dloc),y */ GET_DLOC_IND_Y_RD(); ADC_INST(); break; case 0x72: /* ADC (Dloc) */ GET_DLOC_IND_RD(); ADC_INST(); break; case 0x73: /* ADC (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); ADC_INST(); break; case 0x74: /* STZ Dloc,x */ GET_1BYTE_ARG; GET_DLOC_X_WR(); STZ_INST(1); break; case 0x75: /* ADC Dloc,x */ GET_DLOC_X_RD(); ADC_INST(); break; case 0x76: /* ROR Dloc,X */ GET_DLOC_X_RD_RMW(); ROR_INST(1); break; case 0x77: /* ADC [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); ADC_INST(); break; case 0x78: /* SEI */ psr = psr | 4; INC_KPC_1; break; case 0x79: /* ADC abs,y */ GET_ABS_Y_RD(); ADC_INST(); break; case 0x7a: /* PLY */ INC_KPC_1; CYCLES_PLUS_1 if(psr & 0x10) { PULL8(yreg); SET_NEG_ZERO8(yreg); } else { PULL16(yreg); SET_NEG_ZERO16(yreg); } break; case 0x7b: /* TDC */ INC_KPC_1; acc = direct; SET_NEG_ZERO16(direct); break; case 0x7c: /* JMP (Abs,x) */ /* always access kbank, xreg cannot wrap into next bank */ GET_2BYTE_ARG; arg = (kpc & 0xff0000) + ((xreg + arg) & 0xffff); CYCLES_PLUS_2; GET_MEMORY16(arg, tmp1, 1); kpc = (kpc & 0xff0000) + tmp1; break; case 0x7d: /* ADC Abs,X */ GET_ABS_X_RD(); ADC_INST(); break; case 0x7e: /* ROR Abs,X */ GET_ABS_X_RD_RMW(); ROR_INST(0); break; case 0x7f: /* ADC Long,X */ GET_LONG_X_RD(); ADC_INST(); break; case 0x80: /* BRA */ BRANCH_DISP8(1); break; case 0x81: /* STA (Dloc,X) */ GET_DLOC_X_IND_ADDR(); STA_INST(0); break; case 0x82: /* BRL disp16 */ GET_2BYTE_ARG; CYCLES_PLUS_1; kpc = (kpc & 0xff0000) + ((kpc + 3 + arg) & 0xffff); break; case 0x83: /* STA Disp8,S */ GET_DISP8_S_ADDR(); STA_INST(1); break; case 0x84: /* STY Dloc */ GET_DLOC_ADDR(); STY_INST(1); break; case 0x85: /* STA Dloc */ GET_DLOC_ADDR(); STA_INST(1); break; case 0x86: /* STX Dloc */ GET_DLOC_ADDR(); STX_INST(1); break; case 0x87: /* STA [Dloc] */ GET_DLOC_L_IND_ADDR(); STA_INST(0); break; case 0x88: /* DEY */ INC_KPC_1; SET_INDEX_REG(yreg - 1, yreg); break; case 0x89: /* BIT #imm */ GET_IMM_MEM(); #ifdef ACC8 zero = (acc & arg) & 0xff; #else zero = (acc & arg) & 0xffff; #endif break; case 0x8a: /* TXA */ INC_KPC_1; arg = xreg; LDA_INST(); break; case 0x8b: /* PHB */ INC_KPC_1; PUSH8(dbank); break; case 0x8c: /* STY abs */ GET_ABS_ADDR(); STY_INST(0); break; case 0x8d: /* STA abs */ GET_ABS_ADDR(); STA_INST(0); break; case 0x8e: /* STX abs */ GET_ABS_ADDR(); STX_INST(0); break; case 0x8f: /* STA long */ GET_LONG_ADDR(); STA_INST(0); break; case 0x90: /* BCC disp8 */ BRANCH_DISP8((psr & 0x01) == 0); break; case 0x91: /* STA (Dloc),y */ GET_DLOC_IND_Y_ADDR(1); STA_INST(0); break; case 0x92: /* STA (Dloc) */ GET_DLOC_IND_ADDR(); STA_INST(0); break; case 0x93: /* STA (Disp8,s),y */ GET_DISP8_S_IND_Y_ADDR(); STA_INST(0); break; case 0x94: /* STY Dloc,x */ GET_DLOC_X_ADDR(); STY_INST(1); break; case 0x95: /* STA Dloc,x */ GET_DLOC_X_ADDR(); STA_INST(1); break; case 0x96: /* STX Dloc,Y */ GET_DLOC_Y_ADDR(); STX_INST(1); break; case 0x97: /* STA [Dloc],Y */ GET_DLOC_L_IND_Y_ADDR(); STA_INST(0); break; case 0x98: /* TYA */ INC_KPC_1; arg = yreg; LDA_INST(); break; case 0x99: /* STA abs,y */ GET_ABS_INDEX_ADDR(yreg, 1) STA_INST(0); break; case 0x9a: /* TXS */ stack = xreg; if(psr & 0x100) { stack = 0x100 | (stack & 0xff); } INC_KPC_1; break; case 0x9b: /* TXY */ SET_INDEX_REG(xreg, yreg); INC_KPC_1; break; case 0x9c: /* STZ Abs */ GET_ABS_ADDR(); STZ_INST(0); break; case 0x9d: /* STA Abs,X */ GET_ABS_INDEX_ADDR(xreg, 1); STA_INST(0); break; case 0x9e: /* STZ Abs,X */ GET_ABS_INDEX_ADDR(xreg, 1); STZ_INST(0); break; case 0x9f: /* STA Long,X */ GET_LONG_X_ADDR_FOR_WR(); STA_INST(0); break; case 0xa0: /* LDY #imm */ INC_KPC_2; if((psr & 0x10) == 0) { GET_2BYTE_ARG; CYCLES_PLUS_1 INC_KPC_1; } else { GET_1BYTE_ARG; } SET_INDEX_REG(arg, yreg); break; case 0xa1: /* LDA (Dloc,X) */ GET_DLOC_X_IND_RD(); LDA_INST(); break; case 0xa2: /* LDX #imm */ INC_KPC_2; if((psr & 0x10) == 0) { GET_2BYTE_ARG; CYCLES_PLUS_1 INC_KPC_1; } else { GET_1BYTE_ARG; } SET_INDEX_REG(arg, xreg); break; case 0xa3: /* LDA Disp8,S */ GET_DISP8_S_RD(); LDA_INST(); break; case 0xa4: /* LDY Dloc */ C_LDY_DLOC(); break; case 0xa5: /* LDA Dloc */ GET_DLOC_RD(); LDA_INST(); break; case 0xa6: /* LDX Dloc */ C_LDX_DLOC(); break; case 0xa7: /* LDA [Dloc] */ GET_DLOC_L_IND_RD(); LDA_INST(); break; case 0xa8: /* TAY */ INC_KPC_1; SET_INDEX_REG(acc, yreg); break; case 0xa9: /* LDA #imm */ GET_IMM_MEM(); LDA_INST(); break; case 0xaa: /* TAX */ INC_KPC_1; SET_INDEX_REG(acc, xreg); break; case 0xab: /* PLB */ INC_KPC_1; CYCLES_PLUS_1 PULL8_UNSAFE(dbank); SET_NEG_ZERO8(dbank); break; case 0xac: /* LDY abs */ C_LDY_ABS(); break; case 0xad: /* LDA abs */ GET_ABS_RD(); LDA_INST(); break; case 0xae: /* LDX abs */ C_LDX_ABS(); break; case 0xaf: /* LDA long */ GET_LONG_RD(); LDA_INST(); break; case 0xb0: /* BCS disp8 */ BRANCH_DISP8((psr & 0x01)); break; case 0xb1: /* LDA (Dloc),y */ GET_DLOC_IND_Y_RD(); LDA_INST(); break; case 0xb2: /* LDA (Dloc) */ GET_DLOC_IND_RD(); LDA_INST(); break; case 0xb3: /* LDA (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); LDA_INST(); break; case 0xb4: /* LDY Dloc,x */ C_LDY_DLOC_X(); break; case 0xb5: /* LDA Dloc,x */ GET_DLOC_X_RD(); LDA_INST(); break; case 0xb6: /* LDX Dloc,y */ C_LDX_DLOC_Y(); break; case 0xb7: /* LDA [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); LDA_INST(); break; case 0xb8: /* CLV */ psr = psr & ~0x40; INC_KPC_1; break; case 0xb9: /* LDA abs,y */ GET_ABS_Y_RD(); LDA_INST(); break; case 0xba: /* TSX */ INC_KPC_1; SET_INDEX_REG(stack, xreg); break; case 0xbb: /* TYX */ INC_KPC_1; SET_INDEX_REG(yreg, xreg); break; case 0xbc: /* LDY Abs,X */ C_LDY_ABS_X(); break; case 0xbd: /* LDA Abs,X */ GET_ABS_X_RD(); LDA_INST(); break; case 0xbe: /* LDX Abs,y */ C_LDX_ABS_Y(); break; case 0xbf: /* LDA Long,X */ GET_LONG_X_RD(); LDA_INST(); break; case 0xc0: /* CPY #imm */ C_CPY_IMM(); break; case 0xc1: /* CMP (Dloc,X) */ GET_DLOC_X_IND_RD(); CMP_INST(); break; case 0xc2: /* REP #imm */ GET_1BYTE_ARG; tmp2 = psr; CYCLES_PLUS_1; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); psr = psr & ~(arg & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0xc3: /* CMP Disp8,S */ GET_DISP8_S_RD(); CMP_INST(); break; case 0xc4: /* CPY Dloc */ C_CPY_DLOC(); break; case 0xc5: /* CMP Dloc */ GET_DLOC_RD(); CMP_INST(); break; case 0xc6: /* DEC Dloc */ GET_DLOC_RD_RMW(); DEC_INST(1); break; case 0xc7: /* CMP [Dloc] */ GET_DLOC_L_IND_RD(); CMP_INST(); break; case 0xc8: /* INY */ INC_KPC_1; SET_INDEX_REG(yreg + 1, yreg); break; case 0xc9: /* CMP #imm */ GET_IMM_MEM(); CMP_INST(); break; case 0xca: /* DEX */ INC_KPC_1; SET_INDEX_REG(xreg - 1, xreg); break; case 0xcb: /* WAI */ if(g_irq_pending) { g_wait_pending = 0; INC_KPC_1; } else { g_wait_pending = 1; } break; case 0xcc: /* CPY abs */ C_CPY_ABS(); break; case 0xcd: /* CMP abs */ GET_ABS_RD(); CMP_INST(); break; case 0xce: /* DEC abs */ GET_ABS_RD_RMW(); DEC_INST(0); break; case 0xcf: /* CMP long */ GET_LONG_RD(); CMP_INST(); break; case 0xd0: /* BNE disp8 */ BRANCH_DISP8(zero != 0); break; case 0xd1: /* CMP (Dloc),y */ GET_DLOC_IND_Y_RD(); CMP_INST(); break; case 0xd2: /* CMP (Dloc) */ GET_DLOC_IND_RD(); CMP_INST(); break; case 0xd3: /* CMP (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); CMP_INST(); break; case 0xd4: /* PEI Dloc */ GET_DLOC_ADDR() GET_MEMORY16(arg, arg, 1); CYCLES_PLUS_1; PUSH16_UNSAFE(arg); break; case 0xd5: /* CMP Dloc,x */ GET_DLOC_X_RD(); CMP_INST(); break; case 0xd6: /* DEC Dloc,x */ GET_DLOC_X_RD_RMW(); DEC_INST(1); break; case 0xd7: /* CMP [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); CMP_INST(); break; case 0xd8: /* CLD */ psr = psr & (~0x8); INC_KPC_1; break; case 0xd9: /* CMP abs,y */ GET_ABS_Y_RD(); CMP_INST(); break; case 0xda: /* PHX */ INC_KPC_1; if(psr & 0x10) { PUSH8(xreg); } else { PUSH16(xreg); } break; case 0xdb: /* STP */ FINISH(RET_STP, 0); break; case 0xdc: /* JML (Abs) */ GET_2BYTE_ARG; CYCLES_PLUS_1; GET_MEMORY24(arg, kpc, 1); break; case 0xdd: /* CMP Abs,X */ GET_ABS_X_RD(); CMP_INST(); break; case 0xde: /* DEC Abs,X */ GET_ABS_X_RD_RMW(); DEC_INST(0); break; case 0xdf: /* CMP Long,X */ GET_LONG_X_RD(); CMP_INST(); break; case 0xe0: /* CPX #imm */ C_CPX_IMM(); break; case 0xe1: /* SBC (Dloc,X) */ GET_DLOC_X_IND_RD(); SBC_INST(); break; case 0xe2: /* SEP #imm */ GET_1BYTE_ARG; tmp2 = psr; CYCLES_PLUS_1; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); psr = psr | (arg & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0xe3: /* SBC Disp8,S */ GET_DISP8_S_RD(); SBC_INST(); break; case 0xe4: /* CPX Dloc */ C_CPX_DLOC(); break; case 0xe5: /* SBC Dloc */ GET_DLOC_RD(); SBC_INST(); break; case 0xe6: /* INC Dloc */ GET_DLOC_RD_RMW(); INC_INST(1); break; case 0xe7: /* SBC [Dloc] */ GET_DLOC_L_IND_RD(); SBC_INST(); break; case 0xe8: /* INX */ INC_KPC_1; SET_INDEX_REG(xreg + 1, xreg); break; case 0xe9: /* SBC #imm */ GET_IMM_MEM(); SBC_INST(); break; case 0xea: /* NOP */ INC_KPC_1; break; case 0xeb: /* XBA */ tmp1 = acc & 0xff; CYCLES_PLUS_1 acc = (tmp1 << 8) + (acc >> 8); INC_KPC_1; SET_NEG_ZERO8(acc & 0xff); break; case 0xec: /* CPX abs */ C_CPX_ABS(); break; case 0xed: /* SBC abs */ GET_ABS_RD(); SBC_INST(); break; case 0xee: /* INC abs */ GET_ABS_RD_RMW(); INC_INST(0); break; case 0xef: /* SBC long */ GET_LONG_RD(); SBC_INST(); break; case 0xf0: /* BEQ disp8 */ BRANCH_DISP8(zero == 0); break; case 0xf1: /* SBC (Dloc),y */ GET_DLOC_IND_Y_RD(); SBC_INST(); break; case 0xf2: /* SBC (Dloc) */ GET_DLOC_IND_RD(); SBC_INST(); break; case 0xf3: /* SBC (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); SBC_INST(); break; case 0xf4: /* PEA Abs */ GET_2BYTE_ARG; CYCLES_PLUS_1; INC_KPC_3; PUSH16_UNSAFE(arg); break; case 0xf5: /* SBC Dloc,x */ GET_DLOC_X_RD(); SBC_INST(); break; case 0xf6: /* INC Dloc,x */ GET_DLOC_X_RD_RMW(); INC_INST(1); break; case 0xf7: /* SBC [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); SBC_INST(); break; case 0xf8: /* SED */ INC_KPC_1; psr |= 0x8; break; case 0xf9: /* SBC abs,y */ GET_ABS_Y_RD(); SBC_INST(); break; case 0xfa: /* PLX */ INC_KPC_1; CYCLES_PLUS_1; if(psr & 0x10) { PULL8(xreg); SET_NEG_ZERO8(xreg); } else { PULL16(xreg); SET_NEG_ZERO16(xreg); } break; case 0xfb: /* XCE */ tmp2 = psr; INC_KPC_1; psr = (tmp2 & 0xfe) | ((tmp2 & 1) << 8) | ((tmp2 >> 8) & 1); UPDATE_PSR(psr, tmp2); break; case 0xfc: /* JSR (Abs,X) */ GET_2BYTE_ARG; INC_KPC_2; tmp1 = kpc; arg = (kpc & 0xff0000) + ((arg + xreg) & 0xffff); GET_MEMORY16(arg, tmp2, 1); kpc = (kpc & 0xff0000) + tmp2; CYCLES_PLUS_2 PUSH16_UNSAFE(tmp1); break; case 0xfd: /* SBC Abs,X */ GET_ABS_X_RD(); SBC_INST(); break; case 0xfe: /* INC Abs,X */ GET_ABS_X_RD_RMW(); INC_INST(0); break; case 0xff: /* SBC Long,X */ GET_LONG_X_RD(); SBC_INST(); break; ================================================ FILE: gsplus/src/iwm.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Information from Beneath Apple DOS, Apple IIgs HW reference, Apple IIgs // Firmware reference, and Inside Macintosh Vol 3 (for status35 info). // Neil Parker wrote about the Apple 3.5 drive using IWM at // http://nparker.llx.com/a2/disk and it is very useful. // http://nparker.llx.com/a2/sethook lists hooks for IWM routines, from // $e1/0c00 - 0fab. // ff/5fa4 is STAT35. ff/5fae is CONT35 // When testing DOS3.3, set bp at $b942, which is bad sector detected // IWM mode register: 7:reserved; 6:5: 0; 4: 1=8MHz,0=7MHz (should always be 0); // 3: bit-cells are 2usec, 2: 1sec timer off; 1: async handshake (writes) // 0: latch mode enabled for reads (holds data for 8 bit times) // dsk->fd >= 0 indicates a valid disk. dsk->raw_data != 0 indicates this is // a compressed disk mounted read-only, and ignore dsk->fd (which will always // be 0, to indicate a valid disk). // fbit_pos encodes head position on track in increments of 1/64 usecs for 5.25" // { byte_offset, bit[2:0], sub_bit[8:0] }, so fbit_pos >> 12 gives byte offset // fbit_mult turns dfcyc into fbit_pos, where (512/fbit_mult) = the bit cell // 5.25" bit cell of 4usec has fbit_mult=128; cell of 3.75usec has mult=136. // For 3.5", fbit_mult=256 indicates a 2usec bit cell. // https://support.apple.com/kb/TA39910?locale=en_US&viewlocale=en_US gives // the RPMs of 800KB disks #include "defc.h" int g_halt_arm_move = 0; extern int Verbose; extern word32 g_vbl_count; extern word32 g_c036_val_speed; extern int g_config_kegs_update_needed; extern Engine_reg engine; const byte phys_to_dos_sec[] = { 0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04, 0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f }; const byte phys_to_prodos_sec[] = { 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b, 0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f }; const byte to_disk_byte[] = { 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, /* 0x10 */ 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, /* 0x20 */ 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, /* 0x30 */ 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; int g_track_bytes_35[] = { 0x200*12, 0x200*11, 0x200*10, 0x200*9, 0x200*8 }; int g_track_bits_35[] = { // Do 1.001 * (1020484 * (60/rpm)) / 2 usec = bits per track 77779, // Trks 0-15: 394 rpm 71433, // Trks 16-31: 429 rpm 64926, // Trks 32-47: 472 rpm 58371, // Trks 48-63: 525 rpm 51940 // Trks 64-79: 590 rpm }; int g_fast_disk_emul_en = 1; int g_fast_disk_emul = 0; int g_slow_525_emul_wr = 0; dword64 g_dfcyc_end_emul_wr = 0; int g_fast_disk_unnib = 0; int g_iwm_fake_fast = 0; word32 g_from_disk_byte[256]; int g_from_disk_byte_valid = 0; Iwm g_iwm; int g_iwm_motor_on = 0; // g_iwm_motor_on is set when drive turned on, 0 when $c0e8 is touched. // Use this to throttle speed to 1MHz. // g_iwm.motor_on=1 means drive is spinning. g_iwm.motor_off=1 means we're // in the 1-second countdown of g_iwm.motor_off_vbl_count. int g_check_nibblization = 1; void iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525) { int num_tracks; int i; dsk->dfcyc_last_read = 0; dsk->raw_data = 0; dsk->wozinfo_ptr = 0; dsk->dynapro_info_ptr = 0; dsk->name_ptr = 0; dsk->partition_name = 0; dsk->partition_num = -1; dsk->fd = -1; dsk->dynapro_blocks = 0; dsk->raw_dsize = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->smartport = smartport; dsk->disk_525 = disk_525; dsk->drive = drive; dsk->cur_frac_track = 0; dsk->image_type = 0; dsk->vol_num = 254; dsk->write_prot = 1; dsk->write_through_to_unix = 0; dsk->disk_dirty = 0; dsk->just_ejected = 0; dsk->last_phases = 0; dsk->cur_fbit_pos = 0; dsk->fbit_mult = 128; // 128 for 5.25", 256 for 3.5" dsk->cur_track_bits = 0; dsk->raw_bptr_malloc = 0; dsk->cur_trk_ptr = 0; dsk->num_tracks = 0; dsk->trks = 0; if(!smartport) { // 3.5" and 5.25" drives. This is only called at init time, // so one-time malloc can be done now num_tracks = 2*80; // 3.5" needs 160 dsk->trks = (Trk *)malloc(num_tracks * sizeof(Trk)); for(i = 0; i < num_tracks; i++) { dsk->trks[i].raw_bptr = 0; dsk->trks[i].sync_ptr = 0; dsk->trks[i].dunix_pos = 0; dsk->trks[i].unix_len = 0; dsk->trks[i].dirty = 0; dsk->trks[i].track_bits = 0; } // num_tracks != 0 indicates a valid disk, do not set it here } } void iwm_init() { word32 val; int i; memset(&g_iwm, 0, sizeof(g_iwm)); for(i = 0; i < 2; i++) { iwm_init_drive(&(g_iwm.drive525[i]), 0, i, 1); iwm_init_drive(&(g_iwm.drive35[i]), 0, i, 0); } for(i = 0; i < MAX_C7_DISKS; i++) { iwm_init_drive(&(g_iwm.smartport[i]), 1, i, 0); } if(g_from_disk_byte_valid == 0) { for(i = 0; i < 256; i++) { g_from_disk_byte[i] = 0x100 + i; } for(i = 0; i < 64; i++) { val = to_disk_byte[i]; g_from_disk_byte[val] = i; } g_from_disk_byte_valid = 1; } else { halt_printf("iwm_init called twice!\n"); } } void iwm_reset() { int i; g_iwm.state = 0; g_iwm.motor_off_vbl_count = 0; g_iwm.forced_sync_bit = 32*12345; g_iwm.last_rd_bit = 32*12345; g_iwm.write_val = 0; for(i = 0; i < 5; i++) { g_iwm.wr_last_bit[i] = 0; g_iwm.wr_qtr_track[i] = 0; g_iwm.wr_num_bits[i] = 0; g_iwm.wr_prior_num_bits[i] = 0; g_iwm.wr_delta[i] = 0; } g_iwm.num_active_writes = 0; g_iwm_motor_on = 0; } void disk_set_num_tracks(Disk *dsk, int num_tracks) { if(num_tracks <= 2*80) { dsk->num_tracks = num_tracks; } else { halt_printf("num_tracks out of range: %d\n", num_tracks); } } word32 iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk) { word32 track_bits, extra; extra = (qtr_trk + (qtr_trk >> 5)) & 0xf; // up to 15 extra bits if(dsk->disk_525) { track_bits = NIB_LEN_525 * 8; // 0x18f2 = 51088 } else { track_bits = g_track_bits_35[qtr_trk >> 5]; } return track_bits + extra; } void draw_iwm_status(int line, char *buf) { char *flag[2][2]; int apple35_sel, drive_select; flag[0][0] = " "; flag[0][1] = " "; flag[1][0] = " "; flag[1][1] = " "; apple35_sel = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; drive_select = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; if(g_iwm.state & IWM_STATE_MOTOR_ON) { flag[apple35_sel][drive_select] = "*"; } sprintf(buf, "s6d1:%2d%s s6d2:%2d%s s5d1:%2d/%d%s " "s5d2:%2d/%d%s fast_disk_emul:%d,%d c036:%02x", g_iwm.drive525[0].cur_frac_track >> 18, flag[0][0], g_iwm.drive525[1].cur_frac_track >> 18, flag[0][1], g_iwm.drive35[0].cur_frac_track >> 17, (g_iwm.drive35[0].cur_frac_track >> 16) & 1, flag[1][0], g_iwm.drive35[1].cur_frac_track >> 17, (g_iwm.drive35[1].cur_frac_track >> 16) & 1, flag[1][1], g_fast_disk_emul, g_slow_525_emul_wr, g_c036_val_speed); video_update_status_line(line, buf); } void iwm_flush_cur_disk() { Disk *dsk; dsk = iwm_get_dsk(g_iwm.state); iwm_flush_disk_to_unix(dsk); } void iwm_flush_disk_to_unix(Disk *dsk) { byte buffer[0x4000]; int ret, did_write; int i; if((dsk->disk_dirty == 0) || (dsk->write_through_to_unix == 0)) { return; } if((dsk->raw_data != 0) && !dsk->dynapro_info_ptr) { return; } printf("Writing disk %s to Unix\n", dsk->name_ptr); for(i = 0; i < 160; i++) { ret = iwm_track_to_unix(dsk, i, &(buffer[0])); if((ret != 1) && (ret != 0)) { printf("iwm_flush_disk_to_unix ret: %d, cannot write " "image to unix, qtrk:%04x\n", ret, i); halt_printf("Converting to WOZ format!\n"); woz_reparse_woz(dsk); return; // Don't clear dsk->disk_dirty } if(ret == 1) { did_write = 1; } } if(dsk->wozinfo_ptr && did_write) { woz_check_file(dsk); } dsk->disk_dirty = 0; } /* Check for dirty disk 3 times a second */ word32 g_iwm_dynapro_last_vbl_count = 0; void iwm_vbl_update() { word32 state; int i; state = g_iwm.state; if((state & IWM_STATE_MOTOR_ON) && (state & IWM_STATE_MOTOR_OFF)) { if(g_iwm.motor_off_vbl_count <= g_vbl_count) { printf("Disk timer expired, drive off: %08x\n", g_vbl_count); iwm_flush_cur_disk(); g_iwm.state = state & (~(IWM_STATE_MOTOR_ON | IWM_STATE_MOTOR_OFF)); g_iwm.drive525[0].dfcyc_last_phases = 0; g_iwm.drive525[1].dfcyc_last_phases = 0; } } if((g_vbl_count - g_iwm_dynapro_last_vbl_count) >= 60) { for(i = 0; i < 2; i++) { dynapro_try_fix_damaged_disk(&(g_iwm.drive525[i])); dynapro_try_fix_damaged_disk(&(g_iwm.drive35[i])); } for(i = 0; i < MAX_C7_DISKS; i++) { dynapro_try_fix_damaged_disk(&(g_iwm.smartport[i])); } g_iwm_dynapro_last_vbl_count = g_vbl_count; } } void iwm_update_fast_disk_emul(int fast_disk_emul_en) { if(g_iwm_motor_on == 0) { g_fast_disk_emul = fast_disk_emul_en; } } void iwm_show_stats(int slot_drive) { Trk *trkptr; Disk *dsk; int slot, drive; int i; dbg_printf("IWM state: %07x (slot_drive:%d)\n", g_iwm.state, slot_drive); dbg_printf("g_iwm.drive525[0].fd: %d, [1].fd: %d\n", g_iwm.drive525[0].fd, g_iwm.drive525[1].fd); dbg_printf("g_iwm.drive525[0].last_phases: %d, [1].last_phases: %d\n", g_iwm.drive525[0].last_phases, g_iwm.drive525[1].last_phases); dbg_printf("g_iwm.write_val:%02x, wr_num_bits[0]:%d, last_rd_bit:" "%06x, forced_sync_bit:%06x\n", g_iwm.write_val, g_iwm.wr_num_bits[0], g_iwm.last_rd_bit, g_iwm.forced_sync_bit); if(slot_drive < 0) { return; } drive = slot_drive & 1; slot = 5 + ((slot_drive >> 1) & 1); dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->trks == 0) { return; } for(i = 0; i < 160; i++) { trkptr = &(dsk->trks[i]); printf("Qtrk:%02x: bits:%05x dunix_pos:%08llx unix_len:%06x, " "d:%d %p,%p\n", i, trkptr->track_bits, trkptr->dunix_pos, trkptr->unix_len, trkptr->dirty, trkptr->raw_bptr, trkptr->sync_ptr); } } Disk * iwm_get_dsk(word32 state) { Disk *dsk; int drive; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; if(state & IWM_STATE_C031_APPLE35SEL) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); } return dsk; } Disk * iwm_touch_switches(int loc, dword64 dfcyc) { Disk *dsk; dword64 dadd, dcycs_passed; word32 track_bits, fbit_pos, forced_sync_bit, mask, mask_on, state; int phase, on, motor_on; if(g_iwm.state & IWM_STATE_RESET) { iwm_printf("IWM under reset: %06x\n", g_iwm.state); } dsk = iwm_get_dsk(g_iwm.state); track_bits = dsk->cur_track_bits; fbit_pos = track_bits * 4096; // Set to an illegal value motor_on = g_iwm.state & IWM_STATE_MOTOR_ON; if(motor_on) { dcycs_passed = (dfcyc - dsk->dfcyc_last_read) >> 16; dsk->dfcyc_last_read = dfcyc; // track_bits can be 0, indicating no valid data dadd = dcycs_passed * dsk->fbit_mult; if(track_bits) { if(dadd >= (track_bits * 512)) { dadd = dadd % (track_bits * 512); } } fbit_pos = dsk->cur_fbit_pos + (word32)dadd; if(fbit_pos >= (track_bits * 512)) { fbit_pos -= (track_bits * 512); } if(g_slow_525_emul_wr || (g_iwm.state & IWM_STATE_Q7) || !g_fast_disk_emul || !track_bits) { dsk->cur_fbit_pos = fbit_pos; } else { fbit_pos = track_bits << 12; // out of range value } #if 0 printf("dsk->cur_fbit:%08x, fbit_pos:%08x\n", dsk->cur_fbit_pos, fbit_pos); #endif } dbg_log_info(dfcyc, dsk->cur_fbit_pos, ((loc & 0xff) << 24) | g_iwm.state, ((dsk->cur_frac_track + 0x8000) & 0x1ff0000) | 0xe0); if(loc < 0) { // Not a real access, just a way to update fbit_pos return dsk; } if(dsk->dfcyc_last_phases) { iwm525_update_head(dsk, dfcyc); } on = loc & 1; phase = loc >> 1; state = g_iwm.state; if(loc < 8) { /* phase adjustments. See if motor is on */ mask = (1 << (phase & 3)) << IWM_BIT_PHASES; mask_on = (on << (phase & 3)) << IWM_BIT_PHASES; state = (state & (~mask)) | mask_on; g_iwm.state = state; iwm_printf("Iwm phase %d=%d, all phases: %06x (%016llx)\n", phase, on, state, dfcyc); if(state & IWM_STATE_MOTOR_ON) { if(state & IWM_STATE_C031_APPLE35SEL) { if((phase == 3) && on) { iwm_do_action35(dfcyc); } } else { // Move apple525 head iwm525_update_phases(dsk, dfcyc); } } state = g_iwm.state; /* See if enable or reset is asserted */ if(((state >> IWM_BIT_PHASES) & 5) == 5) { // Ph 0, 2 set state |= IWM_STATE_RESET; iwm_printf("IWM reset active\n"); } else { state &= (~IWM_STATE_RESET); } if(((state >> IWM_BIT_PHASES) & 0xa) == 0xa) { // Ph 1, 3 set state |= IWM_STATE_ENABLE2; iwm_printf("IWM ENABLE2 active\n"); } else { state &= (~IWM_STATE_ENABLE2); } g_iwm.state = state; } else { /* loc >= 8 */ switch(loc) { case 0x8: iwm_printf("Turning IWM motor off!\n"); if(g_iwm.state & 0x04) { // iwm MODE /* Turn off immediately */ state &= (~(IWM_STATE_MOTOR_ON | IWM_STATE_MOTOR_OFF)); } else { /* 1 second delay */ if((state & IWM_STATE_MOTOR_ON) && !(state & IWM_STATE_MOTOR_OFF)){ state |= IWM_STATE_MOTOR_OFF; g_iwm.motor_off_vbl_count = g_vbl_count + 60; } } g_iwm.state = state; #if 0 printf("IWM motor off, g_iwm_motor_on:%d, slow:%d\n", g_iwm_motor_on, g_slow_525_emul_wr); #endif if(g_iwm_motor_on || g_slow_525_emul_wr) { /* recalc current speed */ g_fast_disk_emul = g_fast_disk_emul_en; engine_recalc_events(); iwm_flush_cur_disk(); } g_iwm_motor_on = 0; g_slow_525_emul_wr = 0; break; case 0x9: iwm_printf("Turning IWM motor on!\n"); state |= IWM_STATE_MOTOR_ON; state &= (~IWM_STATE_MOTOR_OFF); state &= (~IWM_STATE_LAST_SEL35); state |= ((state >> 6) & 1) << IWM_BIT_LAST_SEL35; g_iwm.state = state; dsk->dfcyc_last_read = dfcyc; if(g_iwm_motor_on == 0) { /* recalc current speed */ if(dsk->wozinfo_ptr) { g_fast_disk_emul = 0; } engine_recalc_events(); } g_iwm_motor_on = 1; break; case 0xa: case 0xb: if(((state >> IWM_BIT_DRIVE_SEL) & 1) != on) { iwm_flush_cur_disk(); } state &= (~IWM_STATE_DRIVE_SEL); state |= (on << IWM_BIT_DRIVE_SEL); g_iwm.state = state; dsk = iwm_get_dsk(state); if(dsk->wozinfo_ptr && g_iwm_motor_on) { g_fast_disk_emul = 0; } else { g_fast_disk_emul = g_fast_disk_emul_en; } break; case 0xc: case 0xd: mask = IWM_STATE_Q6 | IWM_STATE_Q7 | IWM_STATE_MOTOR_ON | IWM_STATE_ENABLE2; mask_on = IWM_STATE_Q6 | IWM_STATE_MOTOR_ON; if(((state & mask) == mask_on) && !on) { // q6 on, q7 off, !on, motor_on, !enable2 // Switched from $c08d to $c08c, resync now // If latch mode, back up one more bit // (for The School Speller - Tools.woz) forced_sync_bit = iwm_calc_bit_sum( fbit_pos >> 9, 0 - (state & 1), track_bits); g_iwm.forced_sync_bit = forced_sync_bit; g_iwm.last_rd_bit = forced_sync_bit; dbg_log_info(dfcyc, forced_sync_bit*2, 0, 0x500ec); } state &= (~IWM_STATE_Q6); state |= (on << IWM_BIT_Q6); g_iwm.state = state; break; case 0xe: case 0xf: mask = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON | IWM_STATE_ENABLE2; mask_on = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON; if(((state & mask) == mask_on) && !on) { // q7, !on, motor_on, !enable2 #if 0 printf("state:%04x and new q7:%d, write_end\n", state, on); #endif dbg_log_info(dfcyc, state, mask, 0xed); iwm_write_end(dsk, 1, dfcyc); // printf("write end complete, the track:\n"); // iwm_check_nibblization(dfcyc); } state &= (~IWM_STATE_Q7); state |= (on << IWM_BIT_Q7); g_iwm.state = state; break; default: printf("iwm_touch_switches: loc: %02x unknown!\n", loc); exit(2); } } if(!(state & IWM_STATE_Q7)) { g_iwm.num_active_writes = 0; if(g_slow_525_emul_wr) { g_slow_525_emul_wr = 0; engine_recalc_events(); } } return dsk; } void iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc) { Trk *trkptr; word32 track_bits, cur_frac_track, wr_last_bit, write_val; int disk_525, drive, new_track, cur_track, max_track; int num_active_writes; if(dsk->smartport) { return; } cur_frac_track = dsk->cur_frac_track; if(delta != 0) { // 3.5" move, clear out lower fractional track bits new_frac_track = new_frac_track & (-0x10000); if((delta < 0) && (new_frac_track < (word32)(-delta))) { // Moving down past track 0...stop, but preserve side new_frac_track = cur_frac_track & 0x10000; delta = 0; } } new_frac_track = new_frac_track + delta; disk_525 = dsk->disk_525; #if 0 printf("iwm_move_to_track: %07x, num_tracks:%03x (cur:%07x) %016llx\n", new_frac_track, dsk->num_tracks, dsk->cur_frac_track, dfcyc); #endif max_track = 36*4 - 1; // Go to track 35.75 on 5.25" if(dsk->num_tracks >= (max_track + 1)) { max_track = dsk->num_tracks - 1; } if(max_track >= 159) { max_track = 159; // Limit to 159 always } if(new_frac_track > (word32)(max_track << 16)) { new_frac_track = max_track << 16; } new_track = (new_frac_track + 0x8000) >> 16; cur_track = (cur_frac_track + 0x8000) >> 16; num_active_writes = g_iwm.num_active_writes; wr_last_bit = g_iwm.wr_last_bit[0]; write_val = g_iwm.write_val; if(num_active_writes) { iwm_write_end(dsk, 0, dfcyc); printf("moving arm to new_track:%d, write active:%d, bit:%08x, " "write_val:%02x\n", new_track, num_active_writes, wr_last_bit, write_val); } if((cur_track != new_track) || (dsk->cur_trk_ptr == 0)) { drive = dsk->drive + 1; if(1) { // Don't do this printf } else if(disk_525) { printf("s6d%d Track: %d.%02d\n", drive, new_track >> 2, 25* (new_track & 3)); } else { printf("s5d%d Track: %d Side: %d\n", drive, new_track >> 1, new_track & 1); } trkptr = &(dsk->trks[new_track]); track_bits = trkptr->track_bits; if((track_bits == 0) && disk_525) { // Use an adjacent .25 track if it is valid if((new_track > 0) && (trkptr[-1].track_bits != 0)) { new_track--; // printf("Moved dn to qtrk:%04x\n", new_track); } else if((new_track < max_track) && (trkptr[1].track_bits != 0)) { new_track++; // printf("Moved up to qtrk:%04x\n", new_track); } } iwm_flush_disk_to_unix(dsk); iwm_move_to_qtr_track(dsk, new_track); if(dfcyc != 0) { dbg_log_info(dfcyc, cur_frac_track, new_frac_track, (g_iwm.state << 16) | 0x00f000e1); #if 0 printf("Just moved from track %08x to %08x %016llx\n", cur_frac_track, new_frac_track, dfcyc); #endif } } dsk->cur_frac_track = new_frac_track; if(num_active_writes) { iwm_start_write(dsk, wr_last_bit, write_val, 1); } } void iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track) { Trk *trkptr; word32 track_bits, fbit_pos; trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; dsk->cur_trk_ptr = trkptr; dsk->cur_track_bits = track_bits; fbit_pos = dsk->cur_fbit_pos; if(track_bits) { // Moving to a valid track. Ensure fbit_pos in range fbit_pos = fbit_pos % (track_bits * 512); } dsk->cur_fbit_pos = fbit_pos; } dword64 g_iwm_last_phase_dfcyc = 0; void iwm525_update_phases(Disk *dsk, dword64 dfcyc) { word32 ign_mask; int last_phases, new_phases, eff_last_phases, eff_new_phases; int my_phase; // Decide if dsk->last_phases needs to change. last_phases = dsk->last_phases; new_phases = (g_iwm.state >> IWM_BIT_PHASES) & 0xf; if(last_phases != new_phases) { iwm_printf("Phases changing %02x -> %02x, ftrack:%08x at %lld, " "diff:%.2fmsec\n", last_phases, new_phases, dsk->cur_frac_track, dfcyc >> 16, ((dfcyc - g_iwm_last_phase_dfcyc) >> 16) / 1000.0); g_iwm_last_phase_dfcyc = dfcyc; } my_phase = (dsk->cur_frac_track >> 17) & 3; ign_mask = 1 << ((2 + my_phase) & 3); eff_last_phases = last_phases & (~ign_mask); eff_new_phases = new_phases & (~ign_mask); if(eff_last_phases == eff_new_phases) { dsk->last_phases = new_phases; return; // Nothing to do } // Update last_phases iwm525_update_head(dsk, dfcyc); dsk->last_phases = new_phases; dsk->dfcyc_last_phases = dfcyc; dbg_log_info(dfcyc, new_phases, dsk->cur_frac_track, 0x100e1); } void iwm525_update_head(Disk *dsk, dword64 dfcyc) { double dinc; dword64 diff_dusec, dfcyc_last_phases; word32 cur_frac_track, frac_track, sum, num, phases, diff; int new_qtrk, cur_qtrk, my_phase, prev_phase, grind, inc; int i; cur_frac_track = dsk->cur_frac_track; my_phase = (dsk->cur_frac_track >> 17) & 3; // If my_phase is 1, then phase 0 being on should decrement the trk // number, and phase 2 being on should increment the trk number phases = dsk->last_phases & 0xf; // one bit for each phase phases = (phases << 4) | phases; prev_phase = (my_phase + 4 - 1) & 3; phases = (phases >> prev_phase) & 0xf; // Now, phases[0] means decrement trk, phases[1] is where we are, // phases[2] means increment trk, and phases[3] MIGHT mean increment sum = 0; num = 0; grind = 0; for(i = 0; i < 4; i++) { if(((phases >> i) & 1) == 0) { continue; } frac_track = cur_frac_track & -0x20000; switch(i) { case 0: // Previous phase is on, decrement trk if(frac_track >= 0x20000) { frac_track -= 0x20000; } else { frac_track = 0; } sum += frac_track; num++; if(cur_frac_track == 0) { grind++; } break; case 1: // "our" phase, pull to aligned track sum += frac_track; num++; break; case 2: // Next phase, increment track sum += frac_track + 0x20000; num++; break; case 3: // Next next phase: might pull // Phase is nominally 2 away...but if cur_frac_track // is just a little less than a new halftrack, // (0xxx1ffff), then it may be within a half track // and we'll let it pull us frac_track += 0x20000; if((frac_track - cur_frac_track) < 0x30000) { sum += frac_track; num++; } } } frac_track = cur_frac_track; dfcyc_last_phases = dsk->dfcyc_last_phases; dsk->dfcyc_last_phases = 0; if(num) { frac_track = sum / num; // New desired track // Now see if enough time has elapsed that we got to frac_track diff_dusec = (dfcyc - dfcyc_last_phases) >> 16; dinc = 0x20000 / 2800.0; // 2.8msec to move one phase inc = (int)dinc; if(cur_frac_track >= frac_track) { diff = cur_frac_track - frac_track; inc = -inc; } else { diff = frac_track - cur_frac_track; } if(g_fast_disk_emul) { diff = 0; // Always moved enough } // Add inc*diff_dusec to cur_frac_track, unless we already reach if(diff > (dinc * diff_dusec)) { // Enough time has NOT elapsed, so calc where head is // Update frac_track to be cur_frac_track + inc * dusec frac_track = cur_frac_track + (word32)(inc * diff_dusec); dsk->dfcyc_last_phases = dfcyc; } } new_qtrk = (frac_track + 0x8000) >> 16; cur_qtrk = (cur_frac_track + 0x8000) >> 16; if(new_qtrk != cur_qtrk) { if(g_halt_arm_move) { halt_printf("Halt on arm move\n"); g_halt_arm_move = 0; } if(grind) { printf("GRIND GRIND GRIND\n"); } dbg_log_info(dfcyc, frac_track, dsk->cur_frac_track, (phases << 24) | 0x00e1); iwm_move_to_ftrack(dsk, frac_track, 0, dfcyc); if(new_qtrk > 2) { #if 0 printf("Moving to qtr track: %04x (trk:%d.%02d), %d, " "%02x, %08x dfcyc:%lld\n", new_qtrk, new_qtrk >> 2, 25*(new_qtrk & 3), my_phase, phases, g_iwm.state, dfcyc >> 16); #endif } } else { // On the same qtr_track, but update the fraction dsk->cur_frac_track = frac_track; } #if 0 /* sanity check stepping algorithm */ if((qtr_track & 7) == 0) { /* check for just access phase 0 */ if(last_phase != 0) { halt_printf("last_phase: %d!\n", last_phase); } } #endif } int iwm_read_status35(dword64 dfcyc) { Disk *dsk; word32 state; int drive, stat35, tmp; // This is usually done by STAT35 at ff/5fa4 // This code is called on the rising edge of Q6 asserted state = g_iwm.state; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; dsk = &(g_iwm.drive35[drive]); if(state & IWM_STATE_MOTOR_ON) { /* Read status: ph[1], ph[0], disk35, ph[2] */ stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | ((state >> 6) & 2) | (((state >> IWM_BIT_PHASES) >> 2) & 1); iwm_printf("Iwm status read state: %02x\n", state); dbg_log_info(dfcyc, state, stat35, 0xe7); switch(stat35) { case 0x00: /* step direction */ return (state >> IWM_BIT_STEP_DIRECTION35) & 1; break; case 0x01: /* lower head activate */ /* also return instantaneous data from head */ iwm_move_to_ftrack(dsk, (dsk->cur_frac_track & (-0x20000)), 0, dfcyc); return (dsk->cur_fbit_pos >> 15) & 1; break; case 0x02: /* disk in place */ /* 1 = no disk, 0 = disk */ iwm_printf("read disk in place, num_tracks: %d\n", dsk->num_tracks); tmp = (dsk->num_tracks <= 0); dbg_log_info(dfcyc, 0, dsk->num_tracks, 0x100e7); return tmp; break; case 0x03: /* upper head activate */ /* also return instantaneous data from head */ iwm_move_to_ftrack(dsk, dsk->cur_frac_track | 0x10000, 0, dfcyc); return (dsk->cur_fbit_pos >> 15) & 1; break; case 0x04: /* disk is stepping? */ /* 1 = not stepping, 0 = stepping */ return 1; break; case 0x05: /* Unknown function of ROM 03? */ /* 1 = or $20 into 0xe1/f24+drive, 0 = don't */ return 1; break; case 0x06: /* disk is locked */ /* 0 = locked, 1 = unlocked */ return (!dsk->write_prot); break; case 0x08: /* motor on */ /* 0 = on, 1 = off */ return !(state & IWM_STATE_MOTOR_ON35); break; case 0x09: /* number of sides */ /* 1 = 2 sides, 0 = 1 side */ return 1; break; case 0x0a: /* at track 0 */ /* 1 = not at track 0, 0 = there */ tmp = (dsk->cur_frac_track != 0); iwm_printf("Read at track0_35: %d\n", tmp); return tmp; break; case 0x0b: /* disk ready??? */ /* 0 = ready, 1 = not ready? */ tmp = !(state & IWM_STATE_MOTOR_ON35); iwm_printf("Read disk ready, ret: %d\n", tmp); return tmp; break; case 0x0c: /* disk switched?? */ /* 0 = not switched, 1 = switched? */ tmp = (dsk->just_ejected != 0); iwm_printf("Read disk switched: %d\n", tmp); return tmp; break; case 0x0d: /* false read when ejecting disk */ return 1; case 0x0e: /* tachometer */ halt_printf("Reading tachometer!\n"); return (dsk->cur_fbit_pos >> 11) & 1; break; case 0x0f: /* drive installed? */ /* 0 = drive exists, 1 = no drive */ if(drive) { /* pretend no drive 1 */ return 1; } return 0; break; default: halt_printf("Read 3.5 status, stat35: %02x\n", stat35); return 1; } } else { iwm_printf("Read 3.5 status with drive off!\n"); return 1; } } void iwm_do_action35(dword64 dfcyc) { Disk *dsk; word32 state; int drive, stat35; // Actions done by CONT35 routine at ff/5fae // This code is called on the rising edge of phase[3] asserted state = g_iwm.state; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; dsk = &(g_iwm.drive35[drive]); if(state & IWM_STATE_MOTOR_ON) { /* Perform action */ /* stat35: ph[1], ph[0], disk35_ctrl, ph[2] */ stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | ((state >> 6) & 2) | (((state >> IWM_BIT_PHASES) >> 2) & 1); dbg_log_info(dfcyc, state, stat35, 0xf00e7); switch(stat35) { case 0x00: /* Set step direction inward (higher tracks) */ /* towards higher tracks, clear STEP_DIRECTION35 */ state &= (~IWM_STATE_STEP_DIRECTION35); iwm_printf("Iwm set step dir35 = 0\n"); break; case 0x01: /* Set step direction outward (lower tracks) */ /* towards lower tracks */ state |= IWM_STATE_STEP_DIRECTION35; dbg_log_info(dfcyc, state, stat35, 0x300e7); iwm_printf("Iwm set step dir35 = 1\n"); break; case 0x03: /* reset disk-switched flag? */ iwm_printf("Iwm reset disk switch\n"); dsk->just_ejected = 0; break; case 0x04: /* step disk */ if(state & IWM_STATE_STEP_DIRECTION35) { iwm_move_to_ftrack(dsk, dsk->cur_frac_track, -0x20000, dfcyc); } else { iwm_move_to_ftrack(dsk, dsk->cur_frac_track, 0x20000, dfcyc); } break; case 0x08: /* turn motor on */ iwm_printf("Iwm set motor_on35 = 1\n"); state |= IWM_STATE_MOTOR_ON35; break; case 0x09: /* turn motor off */ iwm_printf("Iwm set motor_on35 = 0\n"); state &= (~IWM_STATE_MOTOR_ON35); break; case 0x0d: /* eject disk */ printf("Action 0x0d, will eject disk\n"); iwm_eject_disk(dsk); break; case 0x02: case 0x07: case 0x0b: /* hacks to allow AE 1.6MB driver to not crash me */ break; default: halt_printf("Do 3.5 action, state: %02x\n", state); return; } } else { halt_printf("Set 3.5 status with drive off!\n"); return; } g_iwm.state = state; dbg_log_info(dfcyc, state, stat35, 0x400e7); } int read_iwm(int loc, dword64 dfcyc) { Disk *dsk; word32 status, bit_diff, bit_pos, state; int on, q7_q6, val; loc = loc & 0xf; on = loc & 1; dsk = iwm_touch_switches(loc, dfcyc); state = g_iwm.state; q7_q6 = (state >> IWM_BIT_Q6) & 3; if(on) { /* odd address, return 0 */ return 0; } else { /* even address */ switch(q7_q6) { case 0x00: /* q7 = 0, q6 = 0 */ if(state & IWM_STATE_ENABLE2) { return iwm_read_enable2(dfcyc); } else { if(state & IWM_STATE_MOTOR_ON) { return iwm_read_data(dsk, dfcyc); } else { iwm_printf("read iwm st 0, m off!\n"); /* HACK!!!! */ return 0xff; //return (((int)dfcyc) & 0x7f) + 0x80; } } break; case 0x01: /* q7 = 0, q6 = 1 */ /* read IWM status reg */ if(state & IWM_STATE_ENABLE2) { iwm_printf("Read status under enable2: 1\n"); status = 1; } else { if(state & IWM_STATE_C031_APPLE35SEL) { status = iwm_read_status35(dfcyc); } else { status = dsk->write_prot; } } val = ((status & 1) << 7) | (state & 0x3f); // bit 5 is motor_on, bits[4:0] are iwm_mode iwm_printf("Read status: %02x\n", val); return val; break; case 0x02: /* q7 = 1, q6 = 0 */ /* read handshake register */ if(state & IWM_STATE_ENABLE2) { return iwm_read_enable2_handshake(dfcyc); } else { status = 0xc0; bit_pos = dsk->cur_fbit_pos >> 9; bit_diff = iwm_calc_bit_diff(bit_pos, g_iwm.wr_last_bit[0], dsk->cur_track_bits); if(bit_diff > 8) { iwm_printf("Write underrun!\n"); status = status & 0xbf; } return status; } break; case 0x03: /* q7 = 1, q6 = 1 */ iwm_printf("read iwm q7_q6=3!\n"); return 0; break; } } halt_printf("Got to end of read_iwm, loc: %02x!\n", loc); return 0; } void write_iwm(int loc, int val, dword64 dfcyc) { Disk *dsk; word32 state; int on, q7_q6, drive, fast_writes; loc = loc & 0xf; on = loc & 1; dsk = iwm_touch_switches(loc, dfcyc); state = g_iwm.state; q7_q6 = (state >> IWM_BIT_Q6) & 3; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; fast_writes = g_fast_disk_emul; if(state & IWM_STATE_C031_APPLE35SEL) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); fast_writes = !g_slow_525_emul_wr && fast_writes; } if(on) { /* odd address, write something */ if(q7_q6 == 3) { /* q7, q6 = 1,1 */ if(state & IWM_STATE_MOTOR_ON) { if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { iwm_write_data(dsk, val, dfcyc); } } else { /* write mode register */ // bit 0: latch mode (should set if async hand) // bit 1: async handshake // bit 2: immediate motor off (no 1 sec delay) // bit 3: 2us bit timing // bit 4: Divide input clock by 8 (instead of 7) val = val & 0x1f; state = (state & (~0x1f)) | val; g_iwm.state = state; if(val & 0x10) { iwm_printf("set iwm_mode:%02x!\n",val); } } } else { if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { #if 0 // Flobynoid writes to 0xc0e9 causing these messages... printf("Write iwm1, st: %02x, loc: %x: %02x\n", q7_q6, loc, val); #endif } } return; } else { /* even address */ if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { iwm_printf("Write iwm2, st: %02x, loc: %x: %02x\n", q7_q6, loc, val); } return; } return; } int iwm_read_enable2(dword64 dfcyc) { iwm_printf("Read under enable2 %016llx!\n", dfcyc); return 0xff; } int g_cnt_enable2_handshake = 0; int iwm_read_enable2_handshake(dword64 dfcyc) { int val; iwm_printf("Read handshake under enable2, %016llx!\n", dfcyc); val = 0xc0; g_cnt_enable2_handshake++; if(g_cnt_enable2_handshake > 3) { g_cnt_enable2_handshake = 0; val = 0x80; } return val; } void iwm_write_enable2(int val) { // Smartport is selected (PH3=1, PH1=1, Sel35=0), just ignore this data iwm_printf("Write under enable2: %02x!\n", val); return; } word32 iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc) { double new_fast_cycs; dword64 dfcyc_passed; word32 fbit_pos, fbit_diff, track_bits; // Nox Archaist doesn't finish reading sector header's checksum, but // instead waits for 7 bytes to pass and then writes. This code // tries to allow fast_disk_emul mode to not overwrite the checksum. // Move the fbit_pos forward to try to account for a delay from the // last read to the current write. But accesses to I/O locations // would still take a lot of time. Let's assume there were // 2 slow cycles, and clamp the skip to a min of 1 byte, max 3 bytes. dfcyc_passed = dfcyc - g_iwm.dfcyc_last_fastemul_read; new_fast_cycs = (((double)dfcyc_passed) - 0x20000) / ((double)engine.fplus_ptr->dplus_1); // new_fast_cycs approximates the number of fast cycles that have // passed, so 4.0 means 4 fast cycles have passed iwm_printf("start write, dfcyc:%016llx, new_f_cycs:%f, plus_1:%08llx\n", dfcyc_passed, new_fast_cycs, engine.fplus_ptr->dplus_1); fbit_diff = (word32)(new_fast_cycs * dsk->fbit_mult); if(new_fast_cycs < 0.0) { fbit_diff = 8*512; // 8 bits } else { if(fbit_diff < 8*512) { // 8 bits fbit_diff = 8*512; } else if(fbit_diff > (32*512)) { fbit_diff = 32*512; } } track_bits = dsk->cur_track_bits; fbit_pos = dsk->cur_fbit_pos; if(track_bits == 0) { return fbit_pos; } fbit_pos = fbit_pos + fbit_diff; if(fbit_pos >= (track_bits * 512)) { fbit_pos = fbit_pos - (track_bits * 512); } #if 0 printf(" adjusted fbit_pos from %07x to %07x\n", dsk->cur_fbit_pos, fbit_pos); #endif dbg_log_info(dfcyc, fbit_pos, dsk->cur_fbit_pos, 0xee); dsk->cur_fbit_pos = fbit_pos; return fbit_pos; } word32 iwm_read_data_fast(Disk *dsk, dword64 dfcyc) { dword64 dval, dsync_val_tmp, dsync_val; word32 bit_pos, new_bit_pos, track_bits, val; int msb, dec; if(!g_fast_disk_unnib) { g_iwm.dfcyc_last_fastemul_read = dfcyc; } track_bits = dsk->cur_track_bits; bit_pos = dsk->cur_fbit_pos >> 9; // fbit_pos points just after the LSB of the last nibble. Read +8 // past to get the next nibble. Get more to ensure a valid nibble new_bit_pos = iwm_calc_bit_sum(bit_pos, 15, track_bits); dval = iwm_get_raw_bits(dsk, new_bit_pos, 16, &dsync_val_tmp); dsync_val = dsync_val_tmp; #if 0 printf("fast %010llx syncs:%010llx bit:%07x\n", dval, dsync_val, bit_pos * 2); #endif if(!g_fast_disk_unnib) { //dbg_log_info(dfcyc, dval, dsync_val, 0xeb); } // Find the sync val closest to 15 without going over msb = (dsync_val >> 8) & 0xff; if((msb > 15) || (msb == 0)) { msb = dsync_val & 0xff; } if(msb > 15) { msb = 8; // Just return something } if(msb < 7) { // This can happen when the arm is moved from a long track to a // shorter track, fbit_pos at an arbitrary position at the // track start, placing us at dsync_val=0x1006. Just ignore msb = 7; } val = (word32)(dval >> (msb - 7)); // We've moved new_fbit_pos forward 15 bits, move back 7 bits for msb=15 dec = 15 - msb - 7; if(!g_iwm_motor_on && (msb == 15) && !g_fast_disk_unnib) { // Return this byte properly...but not the next one for the // DOS3.3 RWTS motor on detect code, which reads the drive // without enabling the motor, to see if the motor is on dec--; } if((msb != 15) && !g_fast_disk_unnib) { // Pull a trick to make the disk motor-on test pass ($bd34 in // DOS 3.3 RWTS): if this is a sync byte, don't return whole // byte, but return whole byte next time val = val & 0x7f; dec = dec - 8; } dsk->cur_fbit_pos = iwm_calc_bit_sum(new_bit_pos, dec, track_bits) << 9; #if 0 printf(" val:%02x fbit:%07x new_fbit:%07x dec:%d, msb:%d\n", val & 0xff, dsk->cur_fbit_pos, new_fbit_pos, dec, msb); #endif if(!g_fast_disk_unnib) { dbg_log_info(dfcyc, dsk->cur_fbit_pos, (dec << 24) | (bit_pos * 2), (val << 16) | 0xeb); } return val & 0xff; } dword64 iwm_return_rand_data(Disk *dsk, dword64 dfcyc) { dword64 dval, dval2; if(dsk) { // Use dsk } dval = dfcyc >> 16; dval2 = dval ^ (dval >> 16) ^ (dval << 10) ^ (dval << 26) ^ (dval << 41); dval = dval2 & ((dval >> 6) ^ (dval >> 17)); return dval; } dword64 iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr) { byte *bptr, *sync_ptr; dword64 dval, dtmp_val, sync_dval; word32 track_bits; int pos, bits, total_bits, sync_pos, sync; track_bits = dsk->cur_track_bits; if(track_bits == 0) { halt_printf("iwm_get_raw_bits track_bits 0, %08x\n", dsk->cur_frac_track); return 0; } total_bits = 0; bits = 1 + (bit_pos & 7); // 1..8 pos = bit_pos >> 3; dval = 0; sync_dval = 0; bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); sync_pos = 0; if((bptr == 0) || (sync_ptr == 0)) { halt_printf("bptr:%p, sync:%p, bit_pos:%08x, track_bits:%08x, " "cur_trk_ptr eff:%05lx\n", bptr, sync_ptr, bit_pos, track_bits, dsk->cur_trk_ptr - &(dsk->trks[0])); *dsyncs_ptr = 0xffffffffffffffULL; return 0; } while(total_bits < num_bits) { dtmp_val = bptr[pos]; dtmp_val = dtmp_val >> (8 - bits); dval = dval | (dtmp_val << total_bits); sync = sync_ptr[pos]; if((sync < 8) && (sync >= (8 - bits))) { // MSB is here sync = sync - (8 - bits); sync = sync + total_bits; if((sync_pos < 64) && (sync != (sync_dval & 0xff))){ sync_dval |= ((dword64)sync & 0xff) << sync_pos; sync_pos += 8; } } total_bits += bits; bits = 8; pos--; if(pos < 0) { pos = (track_bits - 1) >> 3; bits = 1 + ((track_bits - 1) & 7); } } if(sync_pos == 0) { sync_dval = 0xff; } *dsyncs_ptr = sync_dval; return dval; } word32 iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits) { word32 val; val = first - last; if(val >= track_bits) { val = val + track_bits; } return val; } word32 iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits) { word32 val; val = bit_pos + add_ival; if(val >= track_bits) { if(add_ival < 0) { val = val + track_bits; } else { val = val - track_bits; } } return val; } dword64 iwm_calc_forced_sync(dword64 dval, int forced_bit) { dword64 sync_dval; // Something like the "E7" protection scheme has toggled // $c0ed in the middle of reading a nibble, causing a new sync. This // is used to "move" sync bits inside disk nibbles, to defeat // nibble copiers (since a 36-cycle sync nibble cannot be // differentiated from a 40-cycle sync nibble in one read pass) // Return these misaligned nibbles until we re-sync (then clear // g_iwm.forced_sync_bit. Toggling $c0ed can set the data latch // to $ff is the disk is write-protected, but this gets cleared // to $00 within 4 CPU cycles always (or less). A phantom 1 will // also shift in dval |= (1ULL << forced_bit); sync_dval = 30; // A default for the previous nibble while(forced_bit >= 0) { // Scan until this bit is set if((dval >> forced_bit) & 1) { // this bit is the MSB of a nibble, note it sync_dval = (sync_dval << 8) | forced_bit; forced_bit -= 7; } forced_bit--; } return sync_dval; } int iwm_calc_forced_sync_0s(dword64 sync_dval, int bits) { int sync, forced_bits, done; int i; // Return number between 7 and bits as to the oldest valid sync bit // in the sync_dval array. 0xff in the first position means no syncs if(bits <= 8) { return bits; } forced_bits = 8; done = 0; if((sync_dval & 0xff) <= 7) { sync_dval = sync_dval >> 8; } for(i = 0; i < 8; i++) { sync = sync_dval & 0xff; if(sync == 0xff) { return bits; } sync_dval = sync_dval >> 8; while(sync > bits) { sync -= 8; // Bring it back into range done = 1; } if(sync < forced_bits) { break; } if(sync < bits) { forced_bits = sync; } if(done) { break; } } return forced_bits; } word32 iwm_read_data(Disk *dsk, dword64 dfcyc) { dword64 dval, sync_dval, dsync_val_tmp, dval0, dval2; word32 track_bits, fbit_pos, bit_pos, val, forced_sync_bit; int msb_bit, bits, forced_bits, diff; track_bits = dsk->cur_track_bits; fbit_pos = dsk->cur_fbit_pos; bit_pos = fbit_pos >> 9; if((track_bits == 0) || (dsk->cur_trk_ptr == 0)) { val = ((fbit_pos * 25) >> 11) & 0xff; iwm_printf("Reading c0ec, track_len 0, returning %02x\n", val); return val; } if(g_fast_disk_emul) { return iwm_read_data_fast(dsk, dfcyc); } // First, get the last few bytes of data bits = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, track_bits) + 10; if(bits < 25) { bits = 25; } else if(bits > 60) { bits = 60; } forced_sync_bit = g_iwm.forced_sync_bit; forced_bits = -1; if(forced_sync_bit < track_bits) { forced_bits = iwm_calc_bit_diff(bit_pos, forced_sync_bit, track_bits); if(forced_bits >= 64) { forced_bits = 63; } if(forced_bits > bits) { bits = forced_bits; } } dval = iwm_get_raw_bits(dsk, bit_pos, bits, &dsync_val_tmp); sync_dval = dsync_val_tmp; // See if there are runs of more than three 0 bits...introduce noise dval0 = (1ULL << bits) | dval; dval0 = dval0 | (dval0 >> 1) | (dval0 >> 2) | (dval0 >> 3); dval0 = (~dval0) & ((1ULL << bits) - 1); if(dval0) { // Each set bit of dval0 indicates the previous 3 bits are 0 dval2 = dval0 | (dval0 << 1); dval2 = dval0 & iwm_return_rand_data(dsk, dfcyc); #if 0 printf("dval0 is %016llx, dval2:%016llx, bits:%d\n", dval0, dval2, bits); #endif dval = dval ^ dval2; if(forced_bits < 0) { forced_bits = iwm_calc_forced_sync_0s(sync_dval, bits); g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, 0 - forced_bits, track_bits); dbg_log_info(dfcyc, g_iwm.forced_sync_bit * 2, (forced_bits << 24) | (bit_pos * 2), 0x400ec); #if 0 printf("Forced bits are %d at %06x, %016llx " "%016llx\n", forced_bits, bit_pos * 2, dval, sync_dval); #endif } } if(forced_bits >= 0) { sync_dval = iwm_calc_forced_sync(dval, forced_bits); dval = dval & ((2ULL << forced_bits) - 1LL); dbg_log_info(dfcyc, (word32)dval, forced_sync_bit * 32, (forced_bits << 16) | 0xea); if(((dsync_val_tmp & 0xff) == (sync_dval & 0xff)) && (!dval0)) { // Only clear it if there are no 0's in the dval, // otherwise we'll reenter and could calc a diff sync g_iwm.forced_sync_bit = 234567*4; //printf("cleared forced_sync\n"); } else { g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, 0 - (sync_dval & 0xf), track_bits); } #if 0 printf("Forced_bits were %d, sync_was %016llx\n", forced_bits, dsync_val_tmp); #endif } #if 0 printf(" Raw bits: %016llx, dsync:%016llx, fbit:%08x\n", dval, sync_dval, fbit_pos); #endif dbg_log_info(dfcyc, (word32)dval, (bits << 24) | ((word32)sync_dval & 0x00ffffffUL), 0x300ec); msb_bit = sync_dval & 0xff; if(g_iwm.state & 1) { // Latch mode (3.5" disk) // last_rd_bit points just past the last bit read (it is // fbit_pos normally, which is one bit past the read bit). // Use latched data if that bit is before the latched LSB diff = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, track_bits); diff = diff + 8; // Calc to the MSB msb_bit = (sync_dval >> 8) & 0xff; if((msb_bit >= 8) && (diff > msb_bit)) { // We've not seen the LSB of this data: ret latched data dbg_log_info(dfcyc, bit_pos * 2, (msb_bit << 16) | (diff & 0xffff), 0x2200ec); bit_pos = iwm_calc_bit_sum(bit_pos, -msb_bit + 8, track_bits); dbg_log_info(dfcyc, bit_pos * 2, g_iwm.last_rd_bit*2, 0x200ec); } else { msb_bit = sync_dval & 0xff; if(diff <= msb_bit) { // Handle case of 10-bit nibble which we've // already returned...don't return it again! msb_bit = 1; } } #if 0 printf("Latch mode diff:%d, msb_bit:%d, new_msb:%d\n", diff, msb_bit, (int)(sync_dval & 0xff)); #endif dbg_log_info(dfcyc, ((word32)diff << 12) | (msb_bit & 0xff), (word32)sync_dval, 0x100ec); } else { if((sync_dval & 0xff) <= 1) { // The LSbit or the next one is a sync bit--but we need // to ignore it. The LSbit is not valid yet, and // the next bit being the MSB of the next nibble will // be ignored to make the 7usec hold time of the // previous nibble work sync_dval = sync_dval >> 8; msb_bit = sync_dval & 0xff; } } val = (word32)dval; if(msb_bit < 8) { // We only have a partial byte val = val & ((2ULL << msb_bit) - 1); } else if(msb_bit < 63) { val = (word32)(dval >> (msb_bit - 8)); } // val is now valid from bit 8 down to bit 1 // We've allowed in to dval an extra bit which we must toss val = val >> 1; g_iwm.last_rd_bit = bit_pos; dbg_log_info(dfcyc, (msb_bit << 24) | (fbit_pos >> 8), (word32)(dval0 << 8) | (val & 0xff), 0xec); #if 0 if(forced_bits >= 0) { printf("Forced bits, msb:%d, val:%02x, dval:%016llx b_p:%06x\n", msb_bit, val, dval, bit_pos * 2); } #endif return val & 0xff; } void iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc) { Trk *trk; word32 track_bits, bit_pos, fbit_pos, bit_last, tmp_val; int bits; trk = dsk->cur_trk_ptr; if((trk == 0) || dsk->write_prot) { return; } track_bits = dsk->cur_track_bits; bit_pos = (dsk->cur_fbit_pos + 511) >> 9; if(track_bits && (bit_pos >= track_bits)) { bit_pos = bit_pos - track_bits; if(bit_pos >= track_bits) { bit_pos = bit_pos % track_bits; } } #if 0 printf("iwm_write_data: %02x %016llx, bit*2:%06x\n", val, dfcyc, bit_pos *2); #endif if(dsk->disk_525) { if(!g_slow_525_emul_wr) { g_slow_525_emul_wr = 1; engine_recalc_events(); if(track_bits && g_fast_disk_emul) { fbit_pos = iwm_fastemul_start_write(dsk, dfcyc); bit_pos = fbit_pos >> 9; } } } dsk->cur_fbit_pos = bit_pos * 512; if(g_iwm.num_active_writes == 0) { // No write was pending, enter write mode now // printf("Starting write of data to the track, track now:\n"); // iwm_show_track(-1, -1, dfcyc); // printf("Write data to track at bit*2:%06x\n", bit_pos); iwm_start_write(dsk, bit_pos, val, 0); return; } if(track_bits == 0) { halt_printf("Impossible: track_bits: 0\n"); return; } if(g_iwm.state & 2) { // async handshake mode, 3.5" iwm_write_data35(dsk, val, dfcyc); return; } // From here on, it's 5.25" disks only bit_last = g_iwm.wr_last_bit[0]; bits = iwm_calc_bit_diff(bit_pos, bit_last, track_bits); if(bits >= 500) { halt_printf("bits are %d. bit*2:%06x, bit_last*2:%06x\n", bits, bit_pos * 2, bit_last * 2); bits = 40; } iwm_write_one_nib(dsk, bits, dfcyc); tmp_val = g_iwm.write_val; dbg_log_info(dfcyc, (g_iwm.wr_last_bit[0] << 25) | (bit_last * 2), (bits << 16) | ((val & 0xff) << 8) | (tmp_val & 0xff), 0xed); g_iwm.write_val = val; } void iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior) { int num; g_iwm.write_val = val; num = 0; num = iwm_start_write_act(dsk, bit_pos, num, no_prior, 0); num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -1); // -0.25 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +1); // +0.25 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -2); // -0.50 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +2); // +0.50 g_iwm.num_active_writes = num; woz_maybe_reparse(dsk); } int iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta) { Trk *trkptr; word32 qtr_track, track_bits, bit_diff, prior_sum, allow_diff; qtr_track = (dsk->cur_frac_track + 0x8000) >> 16; qtr_track += delta; if(qtr_track >= 160) { // Could be unsigned wrap around if(delta == 0) { halt_printf("iwm_start_write_act0, qtr_track:%04x\n", qtr_track); g_iwm.num_active_writes = 0; } return num; } if(!dsk->disk_525 && delta) { return num; // 3.5" does not affect adjacent trks } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { if((delta == -2) || (delta == 2)) { return num; // Nothing to do } if((delta == -1) && (qtr_track > 0)) { if(trkptr[-1].track_bits == 0) { return num; // Nothing to do } } if((delta == 1) && (qtr_track < 159)) { if(trkptr[1].track_bits == 0) { return num; // Nothing to do } } // Otherwise, we need to create this track and write to it track_bits = woz_add_a_track(dsk, qtr_track); } if(track_bits) { bit_pos = bit_pos % track_bits; } bit_diff = iwm_calc_bit_diff(bit_pos, g_iwm.wr_last_bit[num], track_bits); prior_sum = 0; allow_diff = 16 + (16 * g_fast_disk_emul); if((g_iwm.wr_qtr_track[num] == qtr_track) && (bit_diff < allow_diff)) { // consider this write a continuation of the previous write prior_sum = g_iwm.wr_prior_num_bits[num] + g_iwm.wr_num_bits[num] + bit_diff; #if 0 printf("prior_sum is %d, qtr_track:%04x, bit_diff:%d\n", prior_sum, qtr_track, bit_diff); #endif } else { #if 0 printf("No prior_sum, qtr_track:%04x vs %04x, bit_diff:%08x, " "bit_pos:%05x wr_last_bit:%05x\n", g_iwm.wr_qtr_track[num], qtr_track, bit_diff, bit_pos * 2, g_iwm.wr_last_bit[num]*2); #endif } if(no_prior) { prior_sum = 0; } if(delta == 0) { dsk->cur_fbit_pos = bit_pos << 9; iwm_move_to_qtr_track(dsk, qtr_track); } g_iwm.wr_last_bit[num] = bit_pos; g_iwm.wr_qtr_track[num] = qtr_track; g_iwm.wr_num_bits[num] = 0; g_iwm.wr_prior_num_bits[num] = prior_sum; g_iwm.wr_delta[num] = abs(delta); // 0, 1 or 2 return num + 1; } void iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc) { // Just always write 8 bits to the track iwm_write_one_nib(dsk, 8, dfcyc); g_iwm.write_val = val; dsk->cur_fbit_pos = g_iwm.wr_last_bit[0] * 512; } void iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc) { Trk *trkptr; word32 last_bit, qtr_track, num_bits, bit_start, delta, track_bits; word32 prior_sum; int num_active_writes; int i; // Flush out previous write, then turn writing off num_active_writes = g_iwm.num_active_writes; #if 0 printf("In iwm_write_end at %016llx, num:%d, %d\n", dfcyc, num_active_writes, dsk->disk_dirty); #endif if(num_active_writes == 0) { return; // Invalid, not in a write } if(write_through_now) { iwm_write_data(dsk, 0, dfcyc); } for(i = 0; i < num_active_writes; i++) { last_bit = g_iwm.wr_last_bit[i]; qtr_track = g_iwm.wr_qtr_track[i]; num_bits = g_iwm.wr_num_bits[i]; delta = g_iwm.wr_delta[i]; #if 0 printf(" end %d, last_bit:%05x, qtrk:%04x, num_b:%d, " "delta:%d\n", i, last_bit * 2, qtr_track, num_bits, delta); #endif if((num_bits == 0) || (qtr_track >= 160)) { continue; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; prior_sum = g_iwm.wr_prior_num_bits[i]; if((num_bits + prior_sum) >= track_bits) { // Full track write. If delta != 0, erase this track printf("Full track write at qtrk:%04x %016llx\n", qtr_track, dfcyc); #if 0 if(qtr_track == 4) { halt_printf("Full track at qtr_trk:4\n"); } #endif if(delta != 0) { woz_remove_a_track(dsk, qtr_track); printf("TRACK %04x REMOVED\n", qtr_track); continue; } g_iwm.wr_prior_num_bits[i] = 0; } // Otherwise, recalc sync for this track if(num_bits >= track_bits) { num_bits = num_bits % track_bits; } bit_start = iwm_calc_bit_sum(last_bit, -(int)num_bits - 24, track_bits); iwm_recalc_sync_from(dsk, qtr_track, bit_start, dfcyc); #if 0 printf("Wrote %d bits to qtrk:%04x at %05x %016llx, i:%d,%d, " "%d\n", num_bits, qtr_track, bit_start*2, dfcyc, i, num_active_writes, dsk->disk_dirty); #endif } g_iwm.num_active_writes = 0; woz_maybe_reparse(dsk); } void iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc) { word32 qtr_track, bit_pos, delta, val, track_bits; int num; int i; num = g_iwm.num_active_writes; val = g_iwm.write_val; for(i = 0; i < num; i++) { qtr_track = g_iwm.wr_qtr_track[i]; bit_pos = g_iwm.wr_last_bit[i]; delta = g_iwm.wr_delta[i]; dbg_log_info(dfcyc, val, bit_pos * 2, 0x200ed); if(delta == 2) { // Trk +0.50 and -0.50: corrupt val = (val & 0x7f) ^ i ^ 0x0c; } bit_pos = disk_nib_out_raw(dsk, qtr_track, val, bits, bit_pos, dfcyc); track_bits = dsk->trks[qtr_track].track_bits; if(bit_pos >= track_bits) { bit_pos = bit_pos - track_bits; } g_iwm.wr_last_bit[i] = bit_pos; g_iwm.wr_num_bits[i] += bits; dbg_log_info(dfcyc, (bits << 24) | (bit_pos * 2), 2*iwm_calc_bit_sum(bit_pos, 0-g_iwm.wr_num_bits[i], track_bits), 0x300ed); } } void iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc) { Trk *trkptr; byte *bptr, *sync_ptr; word32 track_bits, val, this_sync, sync0; int pos, next_pos, wrap, bit, last_bit, last_byte, match, this_bits; // We are called with a bit_pos 3 bytes before the desired byte. // Look at pos, pos-1, pos+1 in a safe way, and find a valid sync_num if(dfcyc) { //printf("new sync from %d, %016llx %p\n", bit_pos, dfcyc, dsk); } if(qtr_track >= 160) { halt_printf("iwm_recalc_sync_from bad qtr:%04x bit_pos:%06x\n", qtr_track, bit_pos); return; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { halt_printf("iwm_recalc_sync_from track_bits 0 for %04x\n", qtr_track); return; } last_byte = (track_bits - 1) >> 3; last_bit = ((track_bits - 1) & 7) + 1; // 1...8 sync_ptr = &(trkptr->sync_ptr[0]); bptr = &(trkptr->raw_bptr[0]); pos = bit_pos >> 3; bit = bit_pos & 7; // 0...7 sync0 = sync_ptr[pos]; if(sync0 >= 8) { if(pos > 0) { pos--; } else { pos++; // pos is now 1 } sync0 = sync_ptr[pos]; } bit = (7 - sync0) & 7; match = 0; wrap = 0; next_pos = pos + 1; // cnt = 0; // printf("recalc_sync_from pos:%04x, bit:%d\n", pos, bit); while(1) { val = bptr[pos]; this_bits = 8; this_sync = sync_ptr[pos]; sync_ptr[pos] = 0x20; next_pos = pos + 1; if(pos >= last_byte) { #if 0 printf("At last_byte, val:%02x, pos:%04x, last_bit:%d, " "bit:%d\n", val, pos, last_bit, bit); #endif this_bits = last_bit; val = val & (0xff00 >> last_bit); next_pos = 0; wrap++; if(wrap >= 3) { halt_printf("no stable sync found\n"); return; } if(bit >= this_bits) { // Skip over this partial byte to byte 0 bit -= this_bits; pos = 0; continue; } } #if 0 if(wrap || (pos >= 0x2630)) { printf("pos:%04x, bit:%d, val:%04x, this_bits:%d\n", pos, bit, val, this_bits); } #endif #if 0 if((cnt++ < 10) && (dsk->cur_frac_track == 0)) { printf("sync[%04x]=%02x, val:%02x bit:%d, this_b:%d\n", pos, this_sync, val, bit, this_bits); } #endif // bit is within this byte. Find next set bit while(bit < this_bits) { if(((val << bit) & 0x80) == 0) { // Slide to next bit bit++; continue; } sync_ptr[pos] = 7 - bit; if(this_sync == (word32)(7 - bit)) { match++; if(match >= 7) { #if 0 printf("match %d at pos:%04x wrap:%d\n", match, pos, wrap); #endif return; } } else { match = 0; } break; } bit = (bit + 8 - this_bits) & 7; pos = next_pos; } } /* c600 */ void sector_to_partial_nib(byte *in, byte *nib_ptr) { byte *aux_buf, *nib_out; word32 val, val2; int x; int i; /* Convert 256(+1) data bytes to 342+1 disk nibbles */ aux_buf = nib_ptr; nib_out = nib_ptr + 0x56; for(i = 0; i < 0x56; i++) { aux_buf[i] = 0; } x = 0x55; for(i = 0x101; i >= 0; i--) { val = in[i]; if(i >= 0x100) { val = 0; } val2 = (aux_buf[x] << 1) + (val & 1); val = val >> 1; val2 = (val2 << 1) + (val & 1); val = val >> 1; nib_out[i] = val; aux_buf[x] = val2; x--; if(x < 0) { x = 0x55; } } } int disk_unnib_4x4(Disk *dsk) { int val1; int val2; val1 = iwm_read_data_fast(dsk, 0); val2 = iwm_read_data_fast(dsk, 0); return ((val1 << 1) + 1) & val2; } int iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf) { byte aux_buf[0x80]; int sector_done[16]; byte *buf; word32 val, val2, prev_val, save_frac_track; int track_len, vol, track, phys_sec, log_sec, cksum, x, my_nib_cnt; int save_fbit_pos, tmp_fbit_pos, status, ret, num_sectors_done; int i; //printf("iwm_denib_track525\n"); save_fbit_pos = dsk->cur_fbit_pos; save_frac_track = dsk->cur_frac_track; iwm_move_to_qtr_track(dsk, qtr_track); dsk->cur_fbit_pos = 0; g_fast_disk_unnib = 1; track_len = (dsk->cur_track_bits + 7) >> 3; for(i = 0; i < 16; i++) { sector_done[i] = 0; } num_sectors_done = 0; val = 0; status = -1; my_nib_cnt = 0; while(my_nib_cnt++ < 2*track_len) { /* look for start of a sector */ if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0x96) { continue; } /* It's a sector start */ vol = disk_unnib_4x4(dsk); track = disk_unnib_4x4(dsk); phys_sec = disk_unnib_4x4(dsk); if(phys_sec < 0 || phys_sec > 15) { printf("Track %02x, read sec as %02x\n", qtr_track >> 2, phys_sec); break; } if(dsk->image_type == DSK_TYPE_DOS33) { log_sec = phys_to_dos_sec[phys_sec]; } else { log_sec = phys_to_prodos_sec[phys_sec]; } cksum = disk_unnib_4x4(dsk); if((vol ^ track ^ phys_sec ^ cksum) != 0) { /* not correct format */ printf("Track %02x not DOS 3.3 since hdr cksum, %02x " "%02x %02x %02x\n", qtr_track >> 2, vol, track, phys_sec, cksum); break; } /* see what sector it is */ if(track != (qtr_track >> 2) || (phys_sec < 0) || (phys_sec > 15)) { printf("Track %02x bad since track: %02x, sec: %02x\n", qtr_track>>2, track, phys_sec); break; } if(sector_done[phys_sec]) { printf("Already done sector %02x on track %02x!\n", phys_sec, qtr_track>>2); break; } /* So far so good, let's do it! */ val = 0; i = 0; while(i < NIBS_FROM_ADDR_TO_DATA) { i++; if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xad) { continue; } /* got it, just break */ break; } if(i >= NIBS_FROM_ADDR_TO_DATA) { printf("No data header, track %02x, sec %02x at %07x\n", qtr_track>>2, phys_sec, dsk->cur_fbit_pos); break; } buf = outbuf + 0x100*log_sec; /* Data start! */ prev_val = 0; for(i = 0x55; i >= 0; i--) { val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area1, val:%02x,val2:%03x\n", val, val2); printf(" i:%03x, fbit_pos:%08x\n", i, dsk->cur_fbit_pos); break; } prev_val = val2 ^ prev_val; aux_buf[i] = prev_val; } /* rest of data area */ for(i = 0; i < 0x100; i++) { val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area2, read: %02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } prev_val = val2 ^ prev_val; buf[i] = prev_val; } /* checksum */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area3, read: %02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } if(val2 != prev_val) { printf("Bad data cksum, got %02x, wanted: %02x\n", val2, prev_val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xde) { printf("No 0xde at end of sector data:%02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("No 0xde,0xaa at end of sector:%02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } /* Got this far, data is good, merge aux_buf into buf */ x = 0x55; for(i = 0; i < 0x100; i++) { val = aux_buf[x]; val2 = (buf[i] << 1) + (val & 1); val = val >> 1; val2 = (val2 << 1) + (val & 1); buf[i] = val2; val = val >> 1; aux_buf[x] = val; x--; if(x < 0) { x = 0x55; } } sector_done[phys_sec] = 1; num_sectors_done++; if(num_sectors_done >= 16) { status = 0; break; } } tmp_fbit_pos = dsk->cur_fbit_pos; g_fast_disk_unnib = 0; ret = 0; if(status != 0) { printf("Nibblization not done, %02x sectors found qtrk %04x, " "drive:%d, slot:%d\n", num_sectors_done, qtr_track, dsk->drive, dsk->disk_525 + 5); printf("my_nib_cnt: %05x, fbit_pos:%07x, trk_len:%05x\n", my_nib_cnt, tmp_fbit_pos, track_len); ret = 16; for(i = 0; i < 16; i++) { printf("sector_done[%d] = %d\n", i, sector_done[i]); if(sector_done[i]) { ret--; } } iwm_show_a_track(dsk, dsk->cur_trk_ptr, 0); if(!ret) { ret = -1; } } else { //printf("iwm_denib_track525 succeeded\n"); } dsk->cur_fbit_pos = save_fbit_pos; iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); return ret; } int iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf) { word32 buf_c00[0x100]; word32 buf_d00[0x100]; word32 buf_e00[0x100]; int sector_done[16]; byte *buf; word32 tmp_5c, tmp_5d, tmp_5e, tmp_66, tmp_67, val, val2; word32 save_frac_track; int num_sectors_done, track_len, phys_track, phys_sec, phys_side; int phys_capacity, cksum, tmp, track, side, num_sectors, x, y; int carry, my_nib_cnt, save_fbit_pos, status, ret; int i; save_fbit_pos = dsk->cur_fbit_pos; save_frac_track = dsk->cur_frac_track; iwm_move_to_qtr_track(dsk, qtr_track); if(dsk->cur_trk_ptr == 0) { return 0; } dsk->cur_fbit_pos = 0; g_fast_disk_unnib = 1; track_len = dsk->cur_track_bits >> 3; num_sectors = g_track_bytes_35[qtr_track >> 5] >> 9; for(i = 0; i < num_sectors; i++) { sector_done[i] = 0; } num_sectors_done = 0; val = 0; status = -1; my_nib_cnt = 0; track = qtr_track >> 1; side = qtr_track & 1; while(my_nib_cnt++ < 2*track_len) { /* look for start of a sector */ if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0x96) { continue; } /* It's a sector start */ val = iwm_read_data_fast(dsk, 0); phys_track = g_from_disk_byte[val]; if(phys_track != (track & 0x3f)) { printf("Track %02x.%d, read track %02x, %02x\n", track, side, phys_track, val); break; } phys_sec = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if((phys_sec < 0) || (phys_sec >= num_sectors)) { printf("Track %02x.%d, read sector %02x??\n", track, side, phys_sec); break; } phys_side = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(phys_side != ((side << 5) + (track >> 6))) { printf("Track %02x.%d, read side %02x??\n", track, side, phys_side); break; } phys_capacity = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(phys_capacity != 0x24 && phys_capacity != 0x22) { printf("Track %02x.%x capacity: %02x != 0x24/22\n", track, side, phys_capacity); } cksum = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; tmp = phys_track ^ phys_sec ^ phys_side ^ phys_capacity; if(cksum != tmp) { printf("Track %02x.%d, sector %02x, cksum: %02x.%02x\n", track, side, phys_sec, cksum, tmp); break; } if(sector_done[phys_sec]) { printf("Already done sector %02x on track %02x.%x!\n", phys_sec, track, side); break; } /* So far so good, let's do it! */ val = 0; for(i = 0; i < 38; i++) { val = iwm_read_data_fast(dsk, 0); if(val == 0xd5) { break; } } if(val != 0xd5) { printf("No data header, track %02x.%x, sec %02x\n", track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("Bad data hdr1,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xad) { printf("Bad data hdr2,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("dsk->cur_fbit_pos:%07x\n", dsk->cur_fbit_pos); break; } buf = outbuf + (phys_sec << 9); /* check sector again */ tmp = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(tmp != phys_sec) { printf("Bad data hdr3,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Data start! */ tmp_5c = 0; tmp_5d = 0; tmp_5e = 0; y = 0xaf; carry = 0; while(y > 0) { /* 626f */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area1b, read: %02x\n", val); printf(" i:%03x, fbit_pos:%07x\n", i, dsk->cur_fbit_pos); break; } tmp_66 = val2; tmp_5c = tmp_5c << 1; carry = (tmp_5c >> 8); tmp_5c = (tmp_5c + carry) & 0xff; val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area2, read: %02x\n", val); break; } val2 = val2 + ((tmp_66 << 2) & 0xc0); val2 = val2 ^ tmp_5c; buf_c00[y] = val2; tmp_5e = val2 + tmp_5e + carry; carry = (tmp_5e >> 8); tmp_5e = tmp_5e & 0xff; /* 62b8 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; val2 = val2 + ((tmp_66 << 4) & 0xc0); val2 = val2 ^ tmp_5e; buf_d00[y] = val2; tmp_5d = val2 + tmp_5d + carry; carry = (tmp_5d >> 8); tmp_5d = tmp_5d & 0xff; y--; if(y <= 0) { break; } /* 6274 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; val2 = val2 + ((tmp_66 << 6) & 0xc0); val2 = val2 ^ tmp_5d; buf_e00[y+1] = val2; tmp_5c = val2 + tmp_5c + carry; carry = (tmp_5c >> 8); tmp_5c = tmp_5c & 0xff; } /* 62d0 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; tmp_66 = (val2 << 6) & 0xc0; tmp_67 = (val2 << 4) & 0xc0; val2 = (val2 << 2) & 0xc0; val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + val2; if(tmp_5e != (word32)val2) { printf("Checksum 5e bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + tmp_67; if(tmp_5d != (word32)val2) { printf("Checksum 5d bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + tmp_66; if(tmp_5c != (word32)val2) { printf("Checksum 5c bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Whew, got it!...check for DE AA */ val = iwm_read_data_fast(dsk, 0); if(val != 0xde) { printf("Bad data epi1,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("Bad data epi2,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Now, convert buf_c/d/e to output */ /* 6459 */ y = 0; for(x = 0xab; x >= 0; x--) { *buf++ = buf_c00[x]; y++; if(y >= 0x200) { break; } *buf++ = buf_d00[x]; y++; if(y >= 0x200) { break; } *buf++ = buf_e00[x]; y++; if(y >= 0x200) { break; } } sector_done[phys_sec] = 1; num_sectors_done++; if(num_sectors_done >= num_sectors) { status = 0; break; } val = 0; } g_fast_disk_unnib = 0; ret = 0; if(status != 0) { printf("dsk->fbit_pos: %07x, status: %d\n", dsk->cur_fbit_pos, status); for(i = 0; i < num_sectors; i++) { printf("sector done[%d] = %d\n", i, sector_done[i]); } printf("Nibblization not done, %02x blocks found qtrk %04x\n", num_sectors_done, qtr_track); ret = -1; } dsk->cur_fbit_pos = save_fbit_pos; iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); return ret; } /* ret = 1 -> dirty data written out */ /* ret = 0 -> not dirty, no error */ /* ret < 0 -> error */ int iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf) { Trk *trk; dword64 dunix_pos, dret, unix_len; int ret; trk = &(dsk->trks[qtr_track]); if((trk->track_bits == 0) || (trk->dirty == 0)) { return 0; } printf("iwm_track_to_unix dirty qtr:%04x, dirty:%d\n", qtr_track, trk->dirty); #if 0 if((qtr_track & 3) && disk_525) { halt_printf("You wrote to phase %02x! Can't wr bk to unix!\n", qtr_track); dsk->write_through_to_unix = 0; return -1; } #endif if(dsk->wozinfo_ptr) { // WOZ disk outbuf = trk->raw_bptr; ret = 0; } else { if(dsk->disk_525) { if(qtr_track & 3) { // Not a valid track ret = -1; } else { ret = iwm_denib_track525(dsk, qtr_track, outbuf); } } else { ret = iwm_denib_track35(dsk, qtr_track, outbuf); } } if(ret != 0) { return -1; } /* Write it out */ dunix_pos = trk->dunix_pos; unix_len = trk->unix_len; if(unix_len < 0x1000) { halt_printf("Disk:%s trk:%06x, dunix_pos:%08llx, len:%08llx\n", dsk->name_ptr, dsk->cur_frac_track, dunix_pos, unix_len); return -1; } trk->dirty = 0; if(dsk->dynapro_info_ptr) { return dynapro_write(dsk, outbuf, dunix_pos, (word32)unix_len); } dret = cfg_write_to_fd(dsk->fd, outbuf, dunix_pos, unix_len); #if 0 printf("Write: qtr_trk:%04x, dunix_pos:%08llx, %s\n", qtr_track, dunix_pos, dsk->name_ptr); #endif if(dret != unix_len) { printf("write: %08llx, errno:%d, trk: %06x, disk: %s\n", dret, errno, dsk->cur_frac_track, dsk->name_ptr); return -1; } if(dsk->wozinfo_ptr) { // WOZ disk printf("Wrote track %07x to fd:%d off:%08llx, len:%07llx\n", dsk->cur_frac_track, dsk->fd, dunix_pos, unix_len); woz_rewrite_crc(dsk, 0); } return 1; } void show_hex_data(byte *buf, int count) { int i; for(i = 0; i < count; i += 16) { printf("%04x: %02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x\n", i, buf[i+0], buf[i+1], buf[i+2], buf[i+3], buf[i+4], buf[i+5], buf[i+6], buf[i+7], buf[i+8], buf[i+9], buf[i+10], buf[i+11], buf[i+12], buf[i+13], buf[i+14], buf[i+15]); } } void iwm_check_nibblization(dword64 dfcyc) { Disk *dsk; int slot, drive, sel35; drive = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; slot = 6; if(g_iwm.state & IWM_STATE_MOTOR_ON) { sel35 = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; } else { sel35 = (g_iwm.state >> IWM_BIT_LAST_SEL35) & 1; } if(sel35) { dsk = &(g_iwm.drive35[drive]); slot = 5; } else { dsk = &(g_iwm.drive525[drive]); } printf("iwm_check_nibblization, s%d d%d\n", slot, drive); disk_check_nibblization(dsk, 0, 4096, dfcyc); } void disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc) { byte buffer[0x3000]; word32 qtr_track; int ret, ret2; int i; if(size > 0x3000) { printf("size %08x is > 0x3000, disk_check_nibblization\n",size); exit(3); } for(i = 0; i < size; i++) { buffer[i] = 0; } //printf("About to call iwm_denib_track*, here's the track:\n"); //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); qtr_track = (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])); if(qtr_track >= 160) { halt_printf("cur_trk_ptr points to bad qtr_track:%08x\n", qtr_track); return; } if(dsk->disk_525) { ret = iwm_denib_track525(dsk, qtr_track, &(buffer[0])); } else { ret = iwm_denib_track35(dsk, qtr_track, &(buffer[0])); } ret2 = -1; if(in_buf) { for(i = 0; i < size; i++) { if(buffer[i] != in_buf[i]) { printf("buffer[%04x]: %02x != %02x\n", i, buffer[i], in_buf[i]); ret2 = i; break; } } } if((ret != 0) || (ret2 >= 0)) { printf("disk_check_nib ret:%d, ret2:%d for track %06x\n", ret, ret2, dsk->cur_frac_track); if(in_buf) { show_hex_data(in_buf, 0x1000); } show_hex_data(buffer, size); iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); if(ret == 16) { printf("No sectors found, ignore it\n"); return; } halt_printf("Stop\n"); exit(2); } } #define TRACK_BUF_LEN 0x2000 void disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, int len_bits, dword64 dfcyc) { byte track_buf[TRACK_BUF_LEN]; Trk *trk; byte *bptr; dword64 dret, dlen; word32 num_bytes, must_clear_track; int i; /* Read track from dsk int track_buf */ #if 0 printf("disk_unix_to_nib: qtr:%04x, unix_pos:%08llx, unix_len:%08x, " "len_bits:%06x\n", qtr_track, dunix_pos, unix_len, len_bits); #endif must_clear_track = 0; if(unix_len > TRACK_BUF_LEN) { printf("diks_unix_to_nib: requested len of image %s = %05x\n", dsk->name_ptr, unix_len); } bptr = dsk->raw_data; if(bptr != 0) { // raw_data is valid, so use it if((dunix_pos + unix_len) > dsk->raw_dsize) { must_clear_track = 1; } else { bptr += dunix_pos; for(i = 0; i < (int)unix_len; i++) { track_buf[i] = bptr[i]; } } } else if(unix_len > 0) { dret = kegs_lseek(dsk->fd, dunix_pos, SEEK_SET); if(dret != dunix_pos) { printf("lseek of disk %s len 0x%llx ret: %lld, errno:" "%d\n", dsk->name_ptr, dunix_pos, dret, errno); must_clear_track = 1; } dlen = read(dsk->fd, track_buf, unix_len); if(dlen != unix_len) { printf("read of disk %s q_trk %d ret: %lld, errno:%d\n", dsk->name_ptr, qtr_track, dlen, errno); must_clear_track = 1; } } if(must_clear_track) { for(i = 0; i < TRACK_BUF_LEN; i++) { track_buf[i] = 0; } } #if 0 printf("Q_track %02x dumped out\n", qtr_track); for(i = 0; i < 4096; i += 32) { printf("%04x: %02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x\n", i, track_buf[i+0], track_buf[i+1], track_buf[i+2], track_buf[i+3], track_buf[i+4], track_buf[i+5], track_buf[i+6], track_buf[i+7], track_buf[i+8], track_buf[i+9], track_buf[i+10], track_buf[i+11], track_buf[i+12], track_buf[i+13], track_buf[i+14], track_buf[i+15], track_buf[i+16], track_buf[i+17], track_buf[i+18], track_buf[i+19], track_buf[i+20], track_buf[i+21], track_buf[i+22], track_buf[i+23], track_buf[i+24], track_buf[i+25], track_buf[i+26], track_buf[i+27], track_buf[i+28], track_buf[i+29], track_buf[i+30], track_buf[i+31]); } #endif dsk->cur_fbit_pos = 0; /* for consistency */ dsk->raw_bptr_malloc = 1; trk = &(dsk->trks[qtr_track]); num_bytes = 2 + ((len_bits + 7) >> 3) + 4; trk->track_bits = len_bits; trk->dunix_pos = dunix_pos; trk->unix_len = unix_len; trk->dirty = 0; trk->raw_bptr = (byte *)malloc(num_bytes); trk->sync_ptr = (byte *)malloc(num_bytes); iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); /* create nibblized image */ if(dsk->disk_525 && (dsk->image_type == DSK_TYPE_NIB)) { iwm_nibblize_track_nib525(dsk, track_buf, qtr_track, unix_len); } else if(dsk->disk_525) { iwm_nibblize_track_525(dsk, track_buf, qtr_track, dfcyc); } else { iwm_nibblize_track_35(dsk, track_buf, qtr_track, unix_len, dfcyc); } //printf("For qtr_track:%04x, trk->dirty:%d\n", qtr_track, trk->dirty); trk->dirty = 0; } void iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len) { byte *bptr, *sync_ptr; int len; int i; // This is the old, dumb .nib format. It consists of 0x1a00 bytes // per track, but there's no sync information. Just mark each byte // as being sync=7 len = unix_len; bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); for(i = 0; i < len; i++) { bptr[i] = track_buf[i]; } for(i = 0; i < len; i++) { sync_ptr[i] = 7; } if(dsk->cur_track_bits != (unix_len * 8)) { fatal_printf("Track %d.%02d of nib image should be bits:%06x " "but it is: %06x\n", qtr_track >> 2, (qtr_track & 3)*25, unix_len * 8, dsk->cur_track_bits); } iwm_printf("Nibblized q_track %02x\n", qtr_track); } void iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc) { byte partial_nib_buf[0x300]; word32 val, last_val; int phys_sec, log_sec, num_sync; int i; #if 0 printf("nibblize track 525, qtr_track:%04x, trk:%p, trk->raw_bptr:%p, " "sync_ptr:%p\n", qtr_track, trk, trk->raw_bptr, trk->sync_ptr); #endif for(phys_sec = 0; phys_sec < 16; phys_sec++) { if(dsk->image_type == DSK_TYPE_DOS33) { log_sec = phys_to_dos_sec[phys_sec]; } else { log_sec = phys_to_prodos_sec[phys_sec]; } /* Create sync headers */ if(phys_sec == 0) { num_sync = 70; } else { num_sync = 22; } for(i = 0; i < num_sync; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); // prolog: d5,aa,96 disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0x96, 8); disk_4x4_nib_out(dsk, dsk->vol_num); disk_4x4_nib_out(dsk, qtr_track >> 2); disk_4x4_nib_out(dsk, phys_sec); disk_4x4_nib_out(dsk, dsk->vol_num ^ (qtr_track>>2) ^ phys_sec); disk_nib_out(dsk, 0xde, 8); // epilog: de,aa,eb disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xeb, 8); /* Inter sync */ disk_nib_out(dsk, 0xff, 10); for(i = 0; i < 6; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); // data prolog: d5,aa,ad disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xad, 8); sector_to_partial_nib( &(track_buf[log_sec*256]), &(partial_nib_buf[0])); last_val = 0; for(i = 0; i < 0x156; i++) { val = partial_nib_buf[i]; disk_nib_out(dsk, to_disk_byte[last_val ^ val], 8); last_val = val; } disk_nib_out(dsk, to_disk_byte[last_val], 8); /* data epilog */ disk_nib_out(dsk, 0xde, 8); // data epilog: de,aa,eb disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xeb, 8); } /* finish nibblization */ disk_nib_end_track(dsk, dfcyc); iwm_printf("Nibblized q_track %02x\n", qtr_track); if(g_check_nibblization) { disk_check_nibblization(dsk, &(track_buf[0]), 0x1000, dfcyc); } //printf("Showing track after nibblization:\n"); //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); } void iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len, dword64 dfcyc) { int phys_to_log_sec[16]; word32 buf_c00[0x100]; word32 buf_d00[0x100]; word32 buf_e00[0x100]; byte *buf; word32 val, phys_track, phys_side, capacity, cksum, acc_hi; word32 tmp_5c, tmp_5d, tmp_5e, tmp_5f, tmp_63, tmp_64, tmp_65; int num_sectors, log_sec, track, side, num_sync, carry; int interleave, x, y; int i, phys_sec; if(dsk->cur_fbit_pos & 511) { halt_printf("fbit_pos:%07x is not bit-aligned!\n", dsk->cur_fbit_pos); } num_sectors = (unix_len >> 9); for(i = 0; i < num_sectors; i++) { phys_to_log_sec[i] = -1; } phys_sec = 0; interleave = 2; for(log_sec = 0; log_sec < num_sectors; log_sec++) { while(phys_to_log_sec[phys_sec] >= 0) { phys_sec++; if(phys_sec >= num_sectors) { phys_sec = 0; } } phys_to_log_sec[phys_sec] = log_sec; phys_sec += interleave; if(phys_sec >= num_sectors) { phys_sec -= num_sectors; } } track = qtr_track >> 1; side = qtr_track & 1; for(phys_sec = 0; phys_sec < num_sectors; phys_sec++) { log_sec = phys_to_log_sec[phys_sec]; if(log_sec < 0) { printf("Track: %02x.%x phys_sec: %02x = %d!\n", track, side, phys_sec, log_sec); exit(2); } /* Create sync headers */ if(phys_sec == 0) { num_sync = 400; } else { num_sync = 54; } for(i = 0; i < num_sync; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); /* prolog */ disk_nib_out(dsk, 0xaa, 8); /* prolog */ disk_nib_out(dsk, 0x96, 8); /* prolog */ phys_track = track & 0x3f; phys_side = (side << 5) + (track >> 6); capacity = 0x22; disk_nib_out(dsk, to_disk_byte[phys_track], 8); /* trk */ disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec */ disk_nib_out(dsk, to_disk_byte[phys_side], 8); /* sides+trk */ disk_nib_out(dsk, to_disk_byte[capacity], 8); /* capacity*/ cksum = (phys_track ^ log_sec ^ phys_side ^ capacity) & 0x3f; disk_nib_out(dsk, to_disk_byte[cksum], 8); /* cksum*/ disk_nib_out(dsk, 0xde, 8); /* epi */ disk_nib_out(dsk, 0xaa, 8); /* epi */ /* Inter sync */ for(i = 0; i < 5; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); /* data prolog */ disk_nib_out(dsk, 0xaa, 8); /* data prolog */ disk_nib_out(dsk, 0xad, 8); /* data prolog */ disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec again */ /* do nibblizing! */ buf = track_buf + (log_sec << 9); /* 6320 */ tmp_5e = 0; tmp_5d = 0; tmp_5c = 0; y = 0; x = 0xaf; buf_c00[0] = 0; buf_d00[0] = 0; buf_e00[0] = 0; buf_e00[1] = 0; for(y = 0x4; y > 0; y--) { buf_c00[x] = 0; buf_d00[x] = 0; buf_e00[x] = 0; x--; } while(x >= 0) { /* 6338 */ tmp_5c = tmp_5c << 1; carry = (tmp_5c >> 8); tmp_5c = (tmp_5c + carry) & 0xff; val = buf[y]; tmp_5e = val + tmp_5e + carry; carry = (tmp_5e >> 8); tmp_5e = tmp_5e & 0xff; val = val ^ tmp_5c; buf_c00[x] = val; y++; /* 634c */ val = buf[y]; tmp_5d = tmp_5d + val + carry; carry = (tmp_5d >> 8); tmp_5d = tmp_5d & 0xff; val = val ^ tmp_5e; buf_d00[x] = val; y++; x--; if(x <= 0) { break; } /* 632a */ val = buf[y]; tmp_5c = tmp_5c + val + carry; carry = (tmp_5c >> 8); tmp_5c = tmp_5c & 0xff; val = val ^ tmp_5d; buf_e00[x+1] = val; y++; } /* 635f */ val = ((tmp_5c >> 2) ^ tmp_5d) & 0x3f; /* 6367 */ val = (val ^ tmp_5d) >> 2; /* 636b */ val = (val ^ tmp_5e) & 0x3f; /* 636f */ val = (val ^ tmp_5e) >> 2; /* 6373 */ tmp_5f = val; /* 6375 */ tmp_63 = 0; tmp_64 = 0; tmp_65 = 0; acc_hi = 0; y = 0xae; while(y >= 0) { /* 63e4 */ /* write out acc_hi */ val = to_disk_byte[acc_hi & 0x3f]; disk_nib_out(dsk, val, 8); /* 63f2 */ val = to_disk_byte[tmp_63 & 0x3f]; tmp_63 = buf_c00[y]; acc_hi = tmp_63 >> 6; disk_nib_out(dsk, val, 8); /* 640b */ val = to_disk_byte[tmp_64 & 0x3f]; tmp_64 = buf_d00[y]; acc_hi = (acc_hi << 2) + (tmp_64 >> 6); disk_nib_out(dsk, val, 8); y--; if(y < 0) { break; } /* 63cb */ val = to_disk_byte[tmp_65 & 0x3f]; tmp_65 = buf_e00[y+1]; acc_hi = (acc_hi << 2) + (tmp_65 >> 6); disk_nib_out(dsk, val, 8); } /* 6429 */ val = to_disk_byte[tmp_5f & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5e & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5d & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5c & 0x3f]; disk_nib_out(dsk, val, 8); /* 6440 */ /* data epilog */ disk_nib_out(dsk, 0xde, 8); /* epi */ disk_nib_out(dsk, 0xaa, 8); /* epi */ disk_nib_out(dsk, 0xff, 8); } disk_nib_end_track(dsk, dfcyc); if(g_check_nibblization) { disk_check_nibblization(dsk, &(track_buf[0]), unix_len, dfcyc); } } void disk_4x4_nib_out(Disk *dsk, word32 val) { disk_nib_out(dsk, 0xaa | (val >> 1), 8); disk_nib_out(dsk, 0xaa | val, 8); } void disk_nib_out(Disk *dsk, word32 val, int size) { word32 bit_pos; bit_pos = dsk->cur_fbit_pos >> 9; dsk->cur_fbit_pos = disk_nib_out_raw(dsk, (dsk->cur_frac_track + 0x8000) >> 16, val, size, bit_pos, 0) * 512; } void disk_nib_end_track(Disk *dsk, dword64 dfcyc) { // printf("disk_nib_end_track %p\n", dsk); dsk->cur_fbit_pos = 0; dsk->disk_dirty = 0; iwm_recalc_sync_from(dsk, (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])), 0, dfcyc); } word32 disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, word32 bit_pos, dword64 dfcyc) { Trk *trkptr; byte *bptr, *sync_ptr; word32 track_bits, tmp, mask; int pos, next_pos, bit, to_do, shift_left, shift_right, last_byte; int this_bits; int do_print; // write bits from val[7:x]. If bits=3 and val=0xaf, write bits 101. // If bits=10 and val=0xaf, write 0xaf then bits 00. if(qtr_track >= 160) { return bit_pos; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { halt_printf("disk_nib_out_raw track_bits=0, %04x\n", qtr_track); return bit_pos; } last_byte = (track_bits - 1) >> 3; bit = bit_pos & 7; pos = bit_pos >> 3; bptr = &(trkptr->raw_bptr[0]); sync_ptr = &(trkptr->sync_ptr[0]); if(dfcyc != 0) { dbg_log_info(dfcyc, (bits << 24) | (bit_pos << 1), (track_bits << 16) | (val & 0xffff), 0x100ed); } dsk->disk_dirty = 1; trkptr->dirty = 1; do_print = ((pos <= 0x10) || (pos >= 0x18e0)) && (dsk->cur_frac_track == 0xb0000); do_print = 0; if(do_print) { printf("disk_nib_out %02x, %d, %06x\n", val, bits, bit_pos*2); } while(1) { this_bits = 8; next_pos = pos + 1; if(pos >= last_byte) { this_bits = ((track_bits - 1) & 7) + 1; // 1..8 next_pos = 0; } this_bits = (this_bits - bit); // 1..8 to_do = bits; // 1...inf if(to_do > this_bits) { to_do = this_bits; // 1..8 } shift_left = (8 - bit - to_do) & 7; shift_right = 8 - to_do; mask = (1U << to_do) - 1; tmp = (val >> shift_right) & mask; mask = mask << shift_left; if(do_print) { printf(" pos:%04x bit:%d tmp:%02x mask:%02x bits:%d " "bptr[]=%02x new:%02x todo:%d, r:%d l:%d\n", pos, bit, tmp, mask, bits, bptr[pos], (bptr[pos] & (~mask)) | ((tmp << shift_left) & mask), to_do, shift_right, shift_left); } bptr[pos] = (bptr[pos] & (~mask)) | ((tmp << shift_left) & mask); sync_ptr[pos] = 0xff; bits -= to_do; if(bits <= 0) { pos = (pos * 8) + bit + to_do; if((bit + to_do) >= 8) { pos = next_pos * 8; } if((word32)pos >= track_bits) { pos -= track_bits; } if(do_print) { printf(" returning %05x, bits:%d orig:%05x " "%05x\n", pos*2, bits, bit_pos*2, last_byte); } return pos; } val = (val << to_do) & 0xff; bit = 0; pos = next_pos; } } Disk * iwm_get_dsk_from_slot_drive(int slot, int drive) { Disk *dsk; int max_drive; // pass in slot=5,6,7 drive=0,1 (or more for slot 7) max_drive = 2; switch(slot) { case 5: dsk = &(g_iwm.drive35[drive]); break; case 6: dsk = &(g_iwm.drive525[drive]); break; default: // slot 7 max_drive = MAX_C7_DISKS; dsk = &(g_iwm.smartport[drive]); } if(drive >= max_drive) { dsk -= drive; // Move back to drive 0 effectively } return dsk; } void iwm_toggle_lock(Disk *dsk) { printf("iwm_toggle_lock: write_prot:%d, write_through:%d\n", dsk->write_prot, dsk->write_through_to_unix); if(dsk->write_prot == 2) { // nothing to do return; } if(dsk->write_prot) { dsk->write_prot = 0; } else { dsk->write_prot = 1; } printf("New dsk->write_prot: %d\n", dsk->write_prot); if(dsk->wozinfo_ptr && dsk->write_through_to_unix) { woz_rewrite_lock(dsk); printf("Called woz_rewrite_lock()\n"); return; } } void iwm_eject_named_disk(int slot, int drive, const char *name, const char *partition_name) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->fd < 0) { return; } /* If name matches, eject the disk! */ if(!strcmp(dsk->name_ptr, name)) { /* It matches, eject it */ if((partition_name != 0) && (dsk->partition_name != 0)) { /* If both have partitions, and they differ, then */ /* don't eject. Otherwise, eject */ if(strcmp(dsk->partition_name, partition_name) != 0) { /* Don't eject */ return; } } iwm_eject_disk(dsk); } } void iwm_eject_disk_by_num(int slot, int drive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive(slot, drive); iwm_eject_disk(dsk); } void iwm_eject_disk(Disk *dsk) { Woz_info *wozinfo_ptr; word32 state; int motor_on; int i; printf("Ejecting dsk: %s, fd:%d\n", dsk->name_ptr, dsk->fd); if(dsk->fd < 0) { return; } g_config_kegs_update_needed = 1; state = g_iwm.state; motor_on = state & IWM_STATE_MOTOR_ON; if(state & IWM_STATE_C031_APPLE35SEL) { motor_on = state & IWM_STATE_MOTOR_ON35; } if(motor_on) { halt_printf("Try eject dsk:%s, but motor_on!\n", dsk->name_ptr); } dynapro_try_fix_damaged_disk(dsk); iwm_flush_disk_to_unix(dsk); printf("Ejecting disk: %s\n", dsk->name_ptr); /* Free all memory, close file */ /* free the tracks first */ if(dsk->trks != 0) { for(i = 0; i < MAX_TRACKS; i++) { if(dsk->raw_bptr_malloc) { free(dsk->trks[i].raw_bptr); } free(dsk->trks[i].sync_ptr); dsk->trks[i].raw_bptr = 0; dsk->trks[i].sync_ptr = 0; dsk->trks[i].track_bits = 0; } } dsk->num_tracks = 0; dsk->raw_bptr_malloc = 0; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { if(dsk->raw_data == 0) { free(wozinfo_ptr->wozptr); } wozinfo_ptr->wozptr = 0; free(wozinfo_ptr); } dsk->wozinfo_ptr = 0; dynapro_free_dynapro_info(dsk); /* close file, clean up dsk struct */ if(dsk->raw_data) { free(dsk->raw_data); } else { close(dsk->fd); } dsk->fd = -1; dsk->raw_dsize = 0; dsk->raw_data = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->cur_fbit_pos = 0; dsk->cur_track_bits = 0; dsk->disk_dirty = 0; dsk->write_through_to_unix = 0; dsk->write_prot = 1; dsk->just_ejected = 1; /* Leave name_ptr valid */ } void iwm_show_track(int slot_drive, int track, dword64 dfcyc) { Disk *dsk; Trk *trk; word32 state; int drive, sel35, qtr_track; state = g_iwm.state; if(slot_drive < 0) { drive = (state >> IWM_BIT_DRIVE_SEL) & 1; if(state & IWM_STATE_MOTOR_ON) { sel35 = (state >> IWM_BIT_C031_APPLE35SEL) & 1; } else { sel35 = (state >> IWM_BIT_LAST_SEL35) & 1; } } else { drive = slot_drive & 1; sel35 = !((slot_drive >> 1) & 1); } if(sel35) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); } if(track < 0) { qtr_track = dsk->cur_frac_track >> 16; } else { qtr_track = track; } if((dsk->trks == 0) || (qtr_track >= 160)) { return; } trk = &(dsk->trks[qtr_track]); if(trk->track_bits == 0) { dbg_printf("Track_bits: %d\n", trk->track_bits); dbg_printf("No track for type: %d, drive: %d, qtrk:0x%02x\n", sel35, drive, qtr_track); return; } dbg_printf("Current s%dd%d, q_track:0x%02x\n", 6 - sel35, drive + 1, qtr_track); iwm_show_a_track(dsk, trk, dfcyc); } void iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc) { byte *bptr; byte *sync_ptr; word32 val, track_bits, len, shift, line_len, sync; int i, j; track_bits = trk->track_bits; dbg_printf(" Showtrack:track_bits*2: %06x, fbit_pos: %07x, " "trk:%06x, dfcyc:%016llx\n", track_bits*2, dsk->cur_fbit_pos, dsk->cur_frac_track, dfcyc); dbg_printf(" disk_525:%d, drive:%d name:%s fd:%d, dimage_start:" "%08llx, dimage_size:%08llx\n", dsk->disk_525, dsk->drive, dsk->name_ptr, dsk->fd, dsk->dimage_start, dsk->dimage_size); dbg_printf(" image_type:%d, vol_num:%02x, write_prot:%d, " "write_through:%d, disk_dirty:%d\n", dsk->image_type, dsk->vol_num, dsk->write_prot, dsk->write_through_to_unix, dsk->disk_dirty); dbg_printf(" just_ejected:%d, last_phases:%d, num_tracks:%d\n", dsk->just_ejected, dsk->last_phases, dsk->num_tracks); len = (track_bits + 7) >> 3; if(len >= 0x3000) { len = 0x3000; dbg_printf("len too big, using %04x\n", len); } bptr = trk->raw_bptr; sync_ptr = trk->sync_ptr; len = len + 2; // Show an extra 2 bytes for(i = 0; i < (int)len; i += 16) { line_len = 16; if((i + line_len) > len) { line_len = len - i; } // First, print raw bptr bytes dbg_printf("%04x: ", i); for(j = 0; j < (int)line_len; j++) { dbg_printf(" %02x", bptr[i + j]); if(((i + j) * 8U) >= track_bits) { dbg_printf("*"); } } dbg_printf("\n"); dbg_printf(" sync:"); for(j = 0; j < (int)line_len; j++) { dbg_printf(" %2d", sync_ptr[i + j]); } dbg_printf("\n"); dbg_printf(" nibs:"); for(j = 0; j < (int)line_len; j++) { sync = sync_ptr[i+j]; if(sync >= 8) { dbg_printf(" XX"); } else { shift = (7 - sync) & 7; val = (bptr[i + j] << 8) | bptr[i + j + 1]; val = ((val << shift) >> 8) & 0xff; dbg_printf(" %02x", val); } } dbg_printf("\n"); } } void dummy1(word32 psr) { printf("dummy1 psr: %05x\n", psr); } void dummy2(word32 psr) { printf("dummy2 psr: %05x\n", psr); } ================================================ FILE: gsplus/src/iwm.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #define MAX_TRACKS (2*80) #define MAX_C7_DISKS 16 #define NIB_LEN_525 0x18f2 /* 51088 bits per track */ // Expected bits per track: (1020484/5)/4 = 51024. A little extra seems good #define NIBS_FROM_ADDR_TO_DATA 28 // Copy II+ Manual Sector Copy fails if this is 20, so make it 28 // image_type settings. 0 means unknown type #define DSK_TYPE_PRODOS 1 #define DSK_TYPE_DOS33 2 #define DSK_TYPE_DYNAPRO 3 #define DSK_TYPE_NIB 4 #define DSK_TYPE_WOZ 5 // Note: C031_APPLE35SEL must be 6, C031_CTRL must be 7, MOTOR_ON must be 5! // Q7 needs to be adjacent and higher than Q6 // Bits 4:0 are IWM mode register: 0: latch mode; 1: async handshake; // 2: immediate motor off (no 1 sec delay); 3: 2us bit timing; // 4: Divide input clock by 8 (instead of 7) #define IWM_BIT_MOTOR_ON 5 #define IWM_BIT_C031_APPLE35SEL 6 #define IWM_BIT_C031_CTRL 7 #define IWM_BIT_STEP_DIRECTION35 8 #define IWM_BIT_MOTOR_ON35 9 #define IWM_BIT_MOTOR_OFF 10 #define IWM_BIT_DRIVE_SEL 11 #define IWM_BIT_Q6 12 #define IWM_BIT_Q7 13 #define IWM_BIT_ENABLE2 14 #define IWM_BIT_LAST_SEL35 15 #define IWM_BIT_PHASES 16 #define IWM_BIT_RESET 20 #define IWM_STATE_MOTOR_ON (1 << IWM_BIT_MOTOR_ON) #define IWM_STATE_C031_APPLE35SEL (1 << IWM_BIT_C031_APPLE35SEL) #define IWM_STATE_C031_CTRL (1 << IWM_BIT_C031_CTRL) #define IWM_STATE_STEP_DIRECTION35 (1 << IWM_BIT_STEP_DIRECTION35) #define IWM_STATE_MOTOR_ON35 (1 << IWM_BIT_MOTOR_ON35) #define IWM_STATE_MOTOR_OFF (1 << IWM_BIT_MOTOR_OFF) #define IWM_STATE_DRIVE_SEL (1 << IWM_BIT_DRIVE_SEL) #define IWM_STATE_Q6 (1 << IWM_BIT_Q6) #define IWM_STATE_Q7 (1 << IWM_BIT_Q7) #define IWM_STATE_ENABLE2 (1 << IWM_BIT_ENABLE2) #define IWM_STATE_LAST_SEL35 (1 << IWM_BIT_LAST_SEL35) #define IWM_STATE_PHASES (1 << IWM_BIT_PHASES) #define IWM_STATE_RESET (1 << IWM_BIT_RESET) STRUCT(Trk) { byte *raw_bptr; byte *sync_ptr; dword64 dunix_pos; word16 unix_len; word16 dirty; word32 track_bits; }; STRUCT(Woz_info) { byte *wozptr; word32 woz_size; int version; int reparse_needed; word32 max_trk_blocks; int meta_size; int trks_size; int tmap_offset; int trks_offset; int info_offset; int meta_offset; }; typedef struct Dynapro_map_st Dynapro_map; STRUCT(Dynapro_file) { Dynapro_file *next_ptr; Dynapro_file *parent_ptr; Dynapro_file *subdir_ptr; char *unix_path; byte *buffer_ptr; byte prodos_name[17]; // +0x00-0x0f: [0] is len, nul at end word32 dir_byte; // Byte address of this file's dir ent word32 eof; // +0x15-0x17 word32 blocks_used; // +0x13-0x14 word32 creation_time; // +0x18-0x1b word32 lastmod_time; // +0x21-0x24 word16 upper_lower; // +0x1c-0x1d: Versions: lowercase flags word16 key_block; // +0x11-0x12 word16 aux_type; // +0x1f-0x20 word16 header_pointer; // +0x25-0x26 word16 map_first_block; byte file_type; // +0x10 byte modified_flag; byte damaged; }; struct Dynapro_map_st { Dynapro_file *file_ptr; word16 next_map_block; word16 modified; }; STRUCT(Dynapro_info) { char *root_path; Dynapro_file *volume_ptr; Dynapro_map *block_map_ptr; int damaged; }; STRUCT(Disk) { dword64 dfcyc_last_read; byte *raw_data; Woz_info *wozinfo_ptr; Dynapro_info *dynapro_info_ptr; char *name_ptr; char *partition_name; int partition_num; int fd; word32 dynapro_blocks; dword64 raw_dsize; dword64 dimage_start; dword64 dimage_size; int smartport; int disk_525; int drive; word32 cur_frac_track; int image_type; int vol_num; int write_prot; int write_through_to_unix; int disk_dirty; int just_ejected; int last_phases; dword64 dfcyc_last_phases; word32 cur_fbit_pos; word32 fbit_mult; word32 cur_track_bits; int raw_bptr_malloc; Trk *cur_trk_ptr; int num_tracks; Trk *trks; }; STRUCT(Iwm) { Disk drive525[2]; Disk drive35[2]; Disk smartport[MAX_C7_DISKS]; dword64 dfcyc_last_fastemul_read; word32 state; word32 motor_off_vbl_count; word32 forced_sync_bit; word32 last_rd_bit; word32 write_val; word32 wr_last_bit[5]; word32 wr_qtr_track[5]; word32 wr_num_bits[5]; word32 wr_prior_num_bits[5]; word32 wr_delta[5]; int num_active_writes; }; STRUCT(Driver_desc) { word16 sig; word16 blk_size; word32 blk_count; word16 dev_type; word16 dev_id; word32 data; word16 drvr_count; }; STRUCT(Part_map) { word16 sig; word16 sigpad; word32 map_blk_cnt; word32 phys_part_start; word32 part_blk_cnt; char part_name[32]; char part_type[32]; word32 data_start; word32 data_cnt; word32 part_status; word32 log_boot_start; word32 boot_size; word32 boot_load; word32 boot_load2; word32 boot_entry; word32 boot_entry2; word32 boot_cksum; char processor[16]; char junk[128]; }; ================================================ FILE: gsplus/src/joystick_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" #ifdef __linux__ # include #endif #ifdef _WIN32 # include # include #endif extern int g_joystick_native_type1; /* in paddles.c */ extern int g_joystick_native_type2; /* in paddles.c */ extern int g_joystick_native_type; /* in paddles.c */ extern int g_paddle_buttons; extern int g_paddle_val[]; const char *g_joystick_dev = "/dev/js0"; /* default joystick dev file */ #define MAX_JOY_NAME 128 int g_joystick_native_fd = -1; int g_joystick_num_axes = 0; int g_joystick_num_buttons = 0; int g_joystick_callback_buttons = 0; int g_joystick_callback_x = 32767; int g_joystick_callback_y = 32767; #ifdef __linux__ # define JOYSTICK_DEFINED void joystick_init() { char joy_name[MAX_JOY_NAME]; int version, fd; int i; fd = open(g_joystick_dev, O_RDONLY | O_NONBLOCK); if(fd < 0) { printf("Unable to open joystick dev file: %s, errno: %d\n", g_joystick_dev, errno); printf("Defaulting to mouse joystick\n"); return; } strcpy(&joy_name[0], "Unknown Joystick"); version = 0x800; ioctl(fd, JSIOCGNAME(MAX_JOY_NAME), &joy_name[0]); ioctl(fd, JSIOCGAXES, &g_joystick_num_axes); ioctl(fd, JSIOCGBUTTONS, &g_joystick_num_buttons); ioctl(fd, JSIOCGVERSION, &version); printf("Detected joystick: %s [%d axes, %d buttons vers: %08x]\n", joy_name, g_joystick_num_axes, g_joystick_num_buttons, version); g_joystick_native_type1 = 1; g_joystick_native_type2 = -1; g_joystick_native_fd = fd; for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; } /* joystick_update_linux() called from paddles.c. Update g_paddle_val[] */ /* and g_paddle_buttons with current information */ void joystick_update(dword64 dfcyc) { struct js_event js; /* the linux joystick event record */ int mask, val, num, type, ret, len; int i; /* suck up to 20 events, then give up */ len = sizeof(struct js_event); for(i = 0; i < 20; i++) { ret = read(g_joystick_native_fd, &js, len); if(ret != len) { /* just get out */ break; } type = js.type & ~JS_EVENT_INIT; val = js.value; num = js.number & 3; /* clamp to 0-3 */ switch(type) { case JS_EVENT_BUTTON: mask = 1 << num; if(val) { val = mask; } g_paddle_buttons = (g_paddle_buttons & ~mask) | val; break; case JS_EVENT_AXIS: /* val is -32767 to +32767 */ g_paddle_val[num] = val; break; } } if(i > 0) { paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { } #endif /* LINUX */ #ifdef _WIN32 # define JOYSTICK_DEFINED #undef JOYSTICK_DEFINED // HACK: remove #if 0 void joystick_init() { JOYINFO info; JOYCAPS joycap; MMRESULT ret1, ret2; int i; // Check that there is a joystick device if(joyGetNumDevs() <= 0) { printf("No joystick hardware detected\n"); g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; return; } g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; // Check that at least joystick 1 or joystick 2 is available ret1 = joyGetPos(JOYSTICKID1, &info); ret2 = joyGetDevCaps(JOYSTICKID1, &joycap, sizeof(joycap)); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_joystick_native_type1 = JOYSTICKID1; printf("Joystick #1 = %s\n", joycap.szPname); g_joystick_native_type = JOYSTICKID1; } ret1 = joyGetPos(JOYSTICKID2, &info); ret2 = joyGetDevCaps(JOYSTICKID2, &joycap, sizeof(joycap)); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_joystick_native_type2 = JOYSTICKID2; printf("Joystick #2 = %s\n", joycap.szPname); if(g_joystick_native_type < 0) { g_joystick_native_type = JOYSTICKID2; } } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; } void joystick_update(dword64 dfcyc) { JOYCAPS joycap; JOYINFO info; UINT id; MMRESULT ret1, ret2; id = g_joystick_native_type; ret1 = joyGetDevCaps(id, &joycap, sizeof(joycap)); ret2 = joyGetPos(id, &info); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_paddle_val[0] = (info.wXpos - joycap.wXmin) * 32768 / (joycap.wXmax - joycap.wXmin); g_paddle_val[1] = (info.wYpos - joycap.wYmin) * 32768 / (joycap.wYmax - joycap.wYmin); if(info.wButtons & JOY_BUTTON1) { g_paddle_buttons = g_paddle_buttons | 1; } else { g_paddle_buttons = g_paddle_buttons & (~1); } if(info.wButtons & JOY_BUTTON2) { g_paddle_buttons = g_paddle_buttons | 2; } else { g_paddle_buttons = g_paddle_buttons & (~2); } paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { JOYINFOEX info; UINT id; id = g_joystick_native_type; info.dwSize = sizeof(JOYINFOEX); info.dwFlags = JOY_RETURNBUTTONS; if(joyGetPosEx(id, &info) == JOYERR_NOERROR) { if(info.dwButtons & JOY_BUTTON1) { g_paddle_buttons = g_paddle_buttons | 1; } else { g_paddle_buttons = g_paddle_buttons & (~1); } if(info.dwButtons & JOY_BUTTON2) { g_paddle_buttons = g_paddle_buttons | 2; } else { g_paddle_buttons = g_paddle_buttons & (~2); } } } #endif #endif #ifdef MAC # define JOYSTICK_DEFINED #include #include #include #include #include // Headers are at: /Applications/Xcode.app/Contents/Developer/Platforms/ // MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ // Frameworks/IOKit.framework/Headers // Thanks to VirtualC64 and hidapi library for coding example CFIndex g_joystick_min = 0; CFIndex g_joystick_range = 256; int g_joystick_minmax_valid = 0; int g_joystick_dummy = 0; void hid_device_callback(void *ptr, IOReturn result, void *sender, IOHIDValueRef value) { IOHIDElementRef element; word32 mask; int usage_page, usage, ival, button; // This is a callback routine, and it's unclear to me what the state is // For safety, do no printfs() (other than for debug). if((ptr || result || sender || 1) == 0) { printf("Bad\n"); // Avoid unused var warning } element = IOHIDValueGetElement(value); usage_page = IOHIDElementGetUsagePage(element); usage = IOHIDElementGetUsage(element); ival = IOHIDValueGetIntegerValue(value); #if 0 printf(" usage_page:%d, usage:%d, value:%d\n", usage_page, usage, ival); #endif if((usage_page == kHIDPage_GenericDesktop) && ((usage >= kHIDUsage_GD_X) && (usage <= kHIDUsage_GD_Y)) && !g_joystick_minmax_valid) { g_joystick_min = IOHIDElementGetLogicalMin(element); g_joystick_range = IOHIDElementGetLogicalMax(element) + 1 - g_joystick_min; // printf("min:%lld range:%lld\n", (dword64)g_joystick_min, // (dword64)g_joystick_range); if(g_joystick_range == 0) { g_joystick_range = 1; } g_joystick_minmax_valid = 1; } if((usage_page == kHIDPage_GenericDesktop) && (usage == kHIDUsage_GD_X)) { g_joystick_callback_x = ((ival * 65536) / g_joystick_range) - 32768; //printf("g_joystick_callback_x = %d\n", g_joystick_callback_x); } if((usage_page == kHIDPage_GenericDesktop) && (usage == kHIDUsage_GD_Y)) { g_joystick_callback_y = ((ival * 65536) / g_joystick_range) - 32768; //printf("g_joystick_callback_y = %d\n", g_joystick_callback_y); } if((usage_page == kHIDPage_Button) && (usage >= 1) && (usage <= 10)) { // Buttons: usage=1 is button 0, usage=2 is button 1, etc. button = (~usage) & 1; mask = 1 << button; //printf("Button %d (%d) pressed:%d\n", button, usage, ival); if(ival == 0) { // Button released g_joystick_callback_buttons &= (~mask); } else { // Button pressed g_joystick_callback_buttons |= mask; } } } int hid_get_int_property(IOHIDDeviceRef device, CFStringRef key_cfstr) { CFTypeRef ref; Boolean bret; int int_val; ref = IOHIDDeviceGetProperty(device, key_cfstr); if(ref) { bret = CFNumberGetValue((CFNumberRef)ref, kCFNumberIntType, &int_val); if(bret) { return int_val; } } return 0; } void joystick_init() { IOHIDManagerRef hid_mgr; CFSetRef devices_set; CFIndex num_devices; IOHIDDeviceRef *devices_array, device; int vendor, usage_page, usage; int i; g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); IOHIDManagerSetDeviceMatching(hid_mgr, 0); IOHIDManagerOpen(hid_mgr, kIOHIDOptionsTypeNone); devices_set = IOHIDManagerCopyDevices(hid_mgr); num_devices = CFSetGetCount(devices_set); // Sets are hashtables, so we cannot directly access it. The only way // to iterate over all values is to use CFSetGetValues to get a simple // array of the values, and iterate over that devices_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(devices_set, (const void **)devices_array); for(i = 0; i < num_devices; i++) { device = devices_array[i]; vendor = hid_get_int_property(device, CFSTR(kIOHIDVendorIDKey)); // printf(" vendor: %d\n", vendor); usage_page = hid_get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); usage = hid_get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); // printf(" usage_page:%d, usage:%d\n", usage_page, usage); usage_page = hid_get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); usage = hid_get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); // printf(" primary_usage_page:%d, usage:%d\n", usage_page, // usage); if(usage_page != kHIDPage_GenericDesktop) { continue; } if((usage != kHIDUsage_GD_Joystick) && (usage != kHIDUsage_GD_GamePad) && (usage != kHIDUsage_GD_MultiAxisController)) { continue; } printf(" JOYSTICK FOUND, vendor:%08x!\n", vendor); IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone); IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); IOHIDDeviceRegisterInputValueCallback(device, hid_device_callback, 0); g_joystick_native_type1 = 1; return; // Now, hid_device_callback will be called whenever a joystick // value changes. Only set global variables for joystick. } } void joystick_update(dword64 dfcyc) { int i; if(dfcyc) { // Avoid unused parameter warnings } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); g_paddle_val[0] = g_joystick_callback_x; g_paddle_val[1] = g_joystick_callback_y; paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); } } #endif #ifndef JOYSTICK_DEFINED /* stubs for the routines */ void joystick_init() { g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; g_joystick_native_type = -1; } void joystick_update(dword64 dfcyc) { int i; if(dfcyc) { // Avoid unused parameter warnings } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); g_paddle_val[0] = g_joystick_callback_x; g_paddle_val[1] = g_joystick_callback_y; paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); } } #endif void joystick_callback_init(int native_type) { g_joystick_native_type1 = native_type; } void joystick_callback_update(word32 buttons, int paddle_x, int paddle_y) { g_joystick_callback_buttons = (g_paddle_buttons & (~3)) | (buttons & 3); g_joystick_callback_x = paddle_x; g_joystick_callback_y = paddle_y; } ================================================ FILE: gsplus/src/kegsfont.h ================================================ /* $KmKId: kegsfont.h,v 1.1 2002-11-10 03:31:51-05 kadickey Exp $ */ /* char 0x00 (raw 0x40) */ { 0xc7, 0xbb, 0xab, 0xa3, 0xa7, 0xbf, 0xc3, 0xff }, /* char 0x01 (raw 0x41) */ { 0xef, 0xd7, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xff }, /* char 0x02 (raw 0x42) */ { 0x87, 0xbb, 0xbb, 0x87, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x03 (raw 0x43) */ { 0xc7, 0xbb, 0xbf, 0xbf, 0xbf, 0xbb, 0xc7, 0xff }, /* char 0x04 (raw 0x44) */ { 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x05 (raw 0x45) */ { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0x83, 0xff }, /* char 0x06 (raw 0x46) */ { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x07 (raw 0x47) */ { 0xc3, 0xbf, 0xbf, 0xbf, 0xb3, 0xbb, 0xc3, 0xff }, /* char 0x08 (raw 0x48) */ { 0xbb, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x09 (raw 0x49) */ { 0xc7, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x0a (raw 0x4a) */ { 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x0b (raw 0x4b) */ { 0xbb, 0xb7, 0xaf, 0x9f, 0xaf, 0xb7, 0xbb, 0xff }, /* char 0x0c (raw 0x4c) */ { 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x83, 0xff }, /* char 0x0d (raw 0x4d) */ { 0xbb, 0x93, 0xab, 0xab, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x0e (raw 0x4e) */ { 0xbb, 0xbb, 0x9b, 0xab, 0xb3, 0xbb, 0xbb, 0xff }, /* char 0x0f (raw 0x4f) */ { 0xc7, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x10 (raw 0x50) */ { 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x11 (raw 0x51) */ { 0xc7, 0xbb, 0xbb, 0xbb, 0xab, 0xb7, 0xcb, 0xff }, /* char 0x12 (raw 0x52) */ { 0x87, 0xbb, 0xbb, 0x87, 0xaf, 0xb7, 0xbb, 0xff }, /* char 0x13 (raw 0x53) */ { 0xc7, 0xbb, 0xbf, 0xc7, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x14 (raw 0x54) */ { 0x83, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xff }, /* char 0x15 (raw 0x55) */ { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x16 (raw 0x56) */ { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, /* char 0x17 (raw 0x57) */ { 0xbb, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xbb, 0xff }, /* char 0x18 (raw 0x58) */ { 0xbb, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xbb, 0xff }, /* char 0x19 (raw 0x59) */ { 0xbb, 0xbb, 0xd7, 0xef, 0xef, 0xef, 0xef, 0xff }, /* char 0x1a (raw 0x5a) */ { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x83, 0xff }, /* char 0x1b (raw 0x5b) */ { 0x83, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x83, 0xff }, /* char 0x1c (raw 0x5c) */ { 0xff, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xff, 0xff }, /* char 0x1d (raw 0x5d) */ { 0x83, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0x83, 0xff }, /* char 0x1e (raw 0x5e) */ { 0xff, 0xff, 0xef, 0xd7, 0xbb, 0xff, 0xff, 0xff }, /* char 0x1f (raw 0x5f) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 }, /* char 0x20 (raw 0x20) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x21 (raw 0x21) */ { 0xef, 0xef, 0xef, 0xef, 0xef, 0xff, 0xef, 0xff }, /* char 0x22 (raw 0x22) */ { 0xd7, 0xd7, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x23 (raw 0x23) */ { 0xd7, 0xd7, 0x83, 0xd7, 0x83, 0xd7, 0xd7, 0xff }, /* char 0x24 (raw 0x24) */ { 0xef, 0xc3, 0xaf, 0xc7, 0xeb, 0x87, 0xef, 0xff }, /* char 0x25 (raw 0x25) */ { 0x9f, 0x9b, 0xf7, 0xef, 0xdf, 0xb3, 0xf3, 0xff }, /* char 0x26 (raw 0x26) */ { 0xdf, 0xaf, 0xaf, 0xdf, 0xab, 0xb7, 0xcb, 0xff }, /* char 0x27 (raw 0x27) */ { 0xef, 0xef, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x28 (raw 0x28) */ { 0xef, 0xdf, 0xbf, 0xbf, 0xbf, 0xdf, 0xef, 0xff }, /* char 0x29 (raw 0x29) */ { 0xef, 0xf7, 0xfb, 0xfb, 0xfb, 0xf7, 0xef, 0xff }, /* char 0x2a (raw 0x2a) */ { 0xef, 0xab, 0xc7, 0xef, 0xc7, 0xab, 0xef, 0xff }, /* char 0x2b (raw 0x2b) */ { 0xff, 0xef, 0xef, 0x83, 0xef, 0xef, 0xff, 0xff }, /* char 0x2c (raw 0x2c) */ { 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xdf, 0xff }, /* char 0x2d (raw 0x2d) */ { 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff }, /* char 0x2e (raw 0x2e) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff }, /* char 0x2f (raw 0x2f) */ { 0xff, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0xff, 0xff }, /* char 0x30 (raw 0x30) */ { 0xc7, 0xbb, 0xb3, 0xab, 0x9b, 0xbb, 0xc7, 0xff }, /* char 0x31 (raw 0x31) */ { 0xef, 0xcf, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x32 (raw 0x32) */ { 0xc7, 0xbb, 0xfb, 0xe7, 0xdf, 0xbf, 0x83, 0xff }, /* char 0x33 (raw 0x33) */ { 0x83, 0xfb, 0xf7, 0xe7, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x34 (raw 0x34) */ { 0xf7, 0xe7, 0xd7, 0xb7, 0x83, 0xf7, 0xf7, 0xff }, /* char 0x35 (raw 0x35) */ { 0x83, 0xbf, 0x87, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x36 (raw 0x36) */ { 0xe3, 0xdf, 0xbf, 0x87, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x37 (raw 0x37) */ { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xdf, 0xdf, 0xff }, /* char 0x38 (raw 0x38) */ { 0xc7, 0xbb, 0xbb, 0xc7, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x39 (raw 0x39) */ { 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xf7, 0x8f, 0xff }, /* char 0x3a (raw 0x3a) */ { 0xff, 0xff, 0xef, 0xff, 0xef, 0xff, 0xff, 0xff }, /* char 0x3b (raw 0x3b) */ { 0xff, 0xff, 0xef, 0xff, 0xef, 0xef, 0xdf, 0xff }, /* char 0x3c (raw 0x3c) */ { 0xf7, 0xef, 0xdf, 0xbf, 0xdf, 0xef, 0xf7, 0xff }, /* char 0x3d (raw 0x3d) */ { 0xff, 0xff, 0x83, 0xff, 0x83, 0xff, 0xff, 0xff }, /* char 0x3e (raw 0x3e) */ { 0xdf, 0xef, 0xf7, 0xfb, 0xf7, 0xef, 0xdf, 0xff }, /* char 0x3f (raw 0x3f) */ { 0xc7, 0xbb, 0xf7, 0xef, 0xef, 0xff, 0xef, 0xff }, /* char 0x40 (raw 0x14) */ { 0x08, 0x10, 0x6c, 0xfe, 0xfc, 0xfc, 0x7e, 0x6c }, /* char 0x41 (raw 0x11) */ { 0x08, 0x10, 0x6c, 0x82, 0x84, 0x84, 0x52, 0x6c }, /* char 0x42 (raw 0xf5) */ { 0x00, 0x00, 0x40, 0x60, 0x70, 0x78, 0x6c, 0x42 }, /* char 0x43 (raw 0x82) */ { 0xfe, 0x44, 0x28, 0x10, 0x10, 0x28, 0x54, 0xfe }, /* char 0x44 (raw 0xeb) */ { 0x00, 0x02, 0x04, 0x88, 0x50, 0x20, 0x20, 0x00 }, /* char 0x45 (raw 0xe4) */ { 0xfe, 0xfc, 0xfa, 0x36, 0xae, 0xde, 0xde, 0xfe }, /* char 0x46 (raw 0xec) */ { 0xfc, 0xfc, 0xfc, 0xdc, 0x9c, 0x00, 0x9e, 0xde }, /* char 0x47 (raw 0xed) */ { 0xfe, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x00, 0xfe }, /* char 0x48 (raw 0xee) */ { 0x10, 0x20, 0x40, 0xfe, 0x40, 0x20, 0x10, 0x00 }, /* char 0x49 (raw 0xe9) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54 }, /* char 0x4a (raw 0xef) */ { 0x10, 0x10, 0x10, 0x10, 0x92, 0x54, 0x38, 0x10 }, /* char 0x4b (raw 0xf0) */ { 0x10, 0x38, 0x54, 0x92, 0x10, 0x10, 0x10, 0x10 }, /* char 0x4c (raw 0xf1) */ { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0x4d (raw 0xf7) */ { 0x02, 0x02, 0x02, 0x22, 0x62, 0xfe, 0x60, 0x20 }, /* char 0x4e (raw 0xf6) */ { 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc }, /* char 0x4f (raw 0xaf) */ { 0xc8, 0x18, 0x38, 0x7e, 0x38, 0x18, 0x08, 0xf6 }, /* char 0x50 (raw 0xb8) */ { 0x26, 0x30, 0x38, 0xfc, 0x38, 0x30, 0x20, 0xde }, /* char 0x51 (raw 0xce) */ { 0x02, 0x12, 0x10, 0xfe, 0x7c, 0x38, 0x12, 0x02 }, /* char 0x52 (raw 0xe5) */ { 0x02, 0x12, 0x38, 0x7c, 0xfe, 0x10, 0x12, 0x02 }, /* char 0x53 (raw 0xea) */ { 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00 }, /* char 0x54 (raw 0xe6) */ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfe }, /* char 0x55 (raw 0xe8) */ { 0x10, 0x08, 0x04, 0xfe, 0x04, 0x08, 0x10, 0x00 }, /* char 0x56 (raw 0xd7) */ { 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa }, /* char 0x57 (raw 0xe3) */ { 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54 }, /* char 0x58 (raw 0xf4) */ { 0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0xfe, 0x00 }, /* char 0x59 (raw 0xe7) */ { 0x00, 0x00, 0xfc, 0x02, 0x02, 0x02, 0xfe, 0x00 }, /* char 0x5a (raw 0xf3) */ { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, /* char 0x5b (raw 0xd2) */ { 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00 }, /* char 0x5c (raw 0xc7) */ { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0x5d (raw 0xd4) */ { 0x28, 0x28, 0xee, 0x00, 0xee, 0x28, 0x28, 0x00 }, /* char 0x5e (raw 0xdf) */ { 0xfe, 0x02, 0x02, 0x32, 0x32, 0x02, 0x02, 0xfe }, /* char 0x5f (raw 0xd1) */ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /* char 0x60 (raw 0x60) */ { 0xdf, 0xef, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x61 (raw 0x61) */ { 0xff, 0xff, 0xc7, 0xfb, 0xc3, 0xbb, 0xc3, 0xff }, /* char 0x62 (raw 0x62) */ { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x63 (raw 0x63) */ { 0xff, 0xff, 0xc3, 0xbf, 0xbf, 0xbf, 0xc3, 0xff }, /* char 0x64 (raw 0x64) */ { 0xfb, 0xfb, 0xc3, 0xbb, 0xbb, 0xbb, 0xc3, 0xff }, /* char 0x65 (raw 0x65) */ { 0xff, 0xff, 0xc7, 0xbb, 0x83, 0xbf, 0xc3, 0xff }, /* char 0x66 (raw 0x66) */ { 0xe7, 0xdb, 0xdf, 0x87, 0xdf, 0xdf, 0xdf, 0xff }, /* char 0x67 (raw 0x67) */ { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, /* char 0x68 (raw 0x68) */ { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x69 (raw 0x69) */ { 0xef, 0xff, 0xcf, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x6a (raw 0x6a) */ { 0xf7, 0xff, 0xe7, 0xf7, 0xf7, 0xf7, 0xb7, 0xcf }, /* char 0x6b (raw 0x6b) */ { 0xbf, 0xbf, 0xbb, 0xb7, 0x8f, 0xb7, 0xbb, 0xff }, /* char 0x6c (raw 0x6c) */ { 0xcf, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x6d (raw 0x6d) */ { 0xff, 0xff, 0x93, 0xab, 0xab, 0xab, 0xbb, 0xff }, /* char 0x6e (raw 0x6e) */ { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x6f (raw 0x6f) */ { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x70 (raw 0x70) */ { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf }, /* char 0x71 (raw 0x71) */ { 0xff, 0xff, 0xc3, 0xbb, 0xbb, 0xc3, 0xfb, 0xfb }, /* char 0x72 (raw 0x72) */ { 0xff, 0xff, 0xa3, 0x9f, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x73 (raw 0x73) */ { 0xff, 0xff, 0xc3, 0xbf, 0xc7, 0xfb, 0x87, 0xff }, /* char 0x74 (raw 0x74) */ { 0xdf, 0xdf, 0x87, 0xdf, 0xdf, 0xdb, 0xe7, 0xff }, /* char 0x75 (raw 0x75) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xb3, 0xcb, 0xff }, /* char 0x76 (raw 0x76) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, /* char 0x77 (raw 0x77) */ { 0xff, 0xff, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xff }, /* char 0x78 (raw 0x78) */ { 0xff, 0xff, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xff }, /* char 0x79 (raw 0x79) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, /* char 0x7a (raw 0x7a) */ { 0xff, 0xff, 0x83, 0xf7, 0xef, 0xdf, 0x83, 0xff }, /* char 0x7b (raw 0x7b) */ { 0xe3, 0xcf, 0xcf, 0x9f, 0xcf, 0xcf, 0xe3, 0xff }, /* char 0x7c (raw 0x7c) */ { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }, /* char 0x7d (raw 0x7d) */ { 0x8f, 0xe7, 0xe7, 0xf3, 0xe7, 0xe7, 0x8f, 0xff }, /* char 0x7e (raw 0x7e) */ { 0xcb, 0xa7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x7f (raw 0x7f) */ { 0xff, 0xab, 0xd7, 0xab, 0xd7, 0xab, 0xff, 0xff }, /* char 0x80 (raw 0x40) */ { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, /* char 0x81 (raw 0x41) */ { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, /* char 0x82 (raw 0x42) */ { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, /* char 0x83 (raw 0x43) */ { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, /* char 0x84 (raw 0x44) */ { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0x85 (raw 0x45) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, /* char 0x86 (raw 0x46) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0x87 (raw 0x47) */ { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, /* char 0x88 (raw 0x48) */ { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, /* char 0x89 (raw 0x49) */ { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0x8a (raw 0x4a) */ { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0x8b (raw 0x4b) */ { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, /* char 0x8c (raw 0x4c) */ { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, /* char 0x8d (raw 0x4d) */ { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, /* char 0x8e (raw 0x4e) */ { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, /* char 0x8f (raw 0x4f) */ { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0x90 (raw 0x50) */ { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0x91 (raw 0x51) */ { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, /* char 0x92 (raw 0x52) */ { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, /* char 0x93 (raw 0x53) */ { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, /* char 0x94 (raw 0x54) */ { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0x95 (raw 0x55) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0x96 (raw 0x56) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0x97 (raw 0x57) */ { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, /* char 0x98 (raw 0x58) */ { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, /* char 0x99 (raw 0x59) */ { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0x9a (raw 0x5a) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, /* char 0x9b (raw 0x5b) */ { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, /* char 0x9c (raw 0x5c) */ { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, /* char 0x9d (raw 0x5d) */ { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, /* char 0x9e (raw 0x5e) */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, /* char 0x9f (raw 0x5f) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0xa0 (raw 0x20) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa1 (raw 0x21) */ { 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00 }, /* char 0xa2 (raw 0x22) */ { 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa3 (raw 0x23) */ { 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00 }, /* char 0xa4 (raw 0x24) */ { 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00 }, /* char 0xa5 (raw 0x25) */ { 0x60, 0x64, 0x08, 0x10, 0x20, 0x4c, 0x0c, 0x00 }, /* char 0xa6 (raw 0x26) */ { 0x20, 0x50, 0x50, 0x20, 0x54, 0x48, 0x34, 0x00 }, /* char 0xa7 (raw 0x27) */ { 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa8 (raw 0x28) */ { 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00 }, /* char 0xa9 (raw 0x29) */ { 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00 }, /* char 0xaa (raw 0x2a) */ { 0x10, 0x54, 0x38, 0x10, 0x38, 0x54, 0x10, 0x00 }, /* char 0xab (raw 0x2b) */ { 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00 }, /* char 0xac (raw 0x2c) */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00 }, /* char 0xad (raw 0x2d) */ { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00 }, /* char 0xae (raw 0x2e) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00 }, /* char 0xaf (raw 0x2f) */ { 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 }, /* char 0xb0 (raw 0x30) */ { 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00 }, /* char 0xb1 (raw 0x31) */ { 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xb2 (raw 0x32) */ { 0x38, 0x44, 0x04, 0x18, 0x20, 0x40, 0x7c, 0x00 }, /* char 0xb3 (raw 0x33) */ { 0x7c, 0x04, 0x08, 0x18, 0x04, 0x44, 0x38, 0x00 }, /* char 0xb4 (raw 0x34) */ { 0x08, 0x18, 0x28, 0x48, 0x7c, 0x08, 0x08, 0x00 }, /* char 0xb5 (raw 0x35) */ { 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0xb6 (raw 0x36) */ { 0x1c, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00 }, /* char 0xb7 (raw 0x37) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00 }, /* char 0xb8 (raw 0x38) */ { 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00 }, /* char 0xb9 (raw 0x39) */ { 0x38, 0x44, 0x44, 0x3c, 0x04, 0x08, 0x70, 0x00 }, /* char 0xba (raw 0x3a) */ { 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00 }, /* char 0xbb (raw 0x3b) */ { 0x00, 0x00, 0x10, 0x00, 0x10, 0x10, 0x20, 0x00 }, /* char 0xbc (raw 0x3c) */ { 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00 }, /* char 0xbd (raw 0x3d) */ { 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00 }, /* char 0xbe (raw 0x3e) */ { 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00 }, /* char 0xbf (raw 0x3f) */ { 0x38, 0x44, 0x08, 0x10, 0x10, 0x00, 0x10, 0x00 }, /* char 0xc0 (raw 0x40) */ { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, /* char 0xc1 (raw 0x41) */ { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, /* char 0xc2 (raw 0x42) */ { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, /* char 0xc3 (raw 0x43) */ { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, /* char 0xc4 (raw 0x44) */ { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0xc5 (raw 0x45) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, /* char 0xc6 (raw 0x46) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0xc7 (raw 0x47) */ { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, /* char 0xc8 (raw 0x48) */ { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, /* char 0xc9 (raw 0x49) */ { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xca (raw 0x4a) */ { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0xcb (raw 0x4b) */ { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, /* char 0xcc (raw 0x4c) */ { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, /* char 0xcd (raw 0x4d) */ { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, /* char 0xce (raw 0x4e) */ { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, /* char 0xcf (raw 0x4f) */ { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xd0 (raw 0x50) */ { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0xd1 (raw 0x51) */ { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, /* char 0xd2 (raw 0x52) */ { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, /* char 0xd3 (raw 0x53) */ { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, /* char 0xd4 (raw 0x54) */ { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0xd5 (raw 0x55) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xd6 (raw 0x56) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0xd7 (raw 0x57) */ { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, /* char 0xd8 (raw 0x58) */ { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, /* char 0xd9 (raw 0x59) */ { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0xda (raw 0x5a) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, /* char 0xdb (raw 0x5b) */ { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, /* char 0xdc (raw 0x5c) */ { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, /* char 0xdd (raw 0x5d) */ { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, /* char 0xde (raw 0x5e) */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, /* char 0xdf (raw 0x5f) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0xe0 (raw 0x60) */ { 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xe1 (raw 0x61) */ { 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00 }, /* char 0xe2 (raw 0x62) */ { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0xe3 (raw 0x63) */ { 0x00, 0x00, 0x3c, 0x40, 0x40, 0x40, 0x3c, 0x00 }, /* char 0xe4 (raw 0x64) */ { 0x04, 0x04, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x00 }, /* char 0xe5 (raw 0x65) */ { 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00 }, /* char 0xe6 (raw 0x66) */ { 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x20, 0x00 }, /* char 0xe7 (raw 0x67) */ { 0x00, 0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x38 }, /* char 0xe8 (raw 0x68) */ { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, /* char 0xe9 (raw 0x69) */ { 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xea (raw 0x6a) */ { 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x48, 0x30 }, /* char 0xeb (raw 0x6b) */ { 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x00 }, /* char 0xec (raw 0x6c) */ { 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xed (raw 0x6d) */ { 0x00, 0x00, 0x6c, 0x54, 0x54, 0x54, 0x44, 0x00 }, /* char 0xee (raw 0x6e) */ { 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, /* char 0xef (raw 0x6f) */ { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xf0 (raw 0x70) */ { 0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40 }, /* char 0xf1 (raw 0x71) */ { 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x04 }, /* char 0xf2 (raw 0x72) */ { 0x00, 0x00, 0x5c, 0x60, 0x40, 0x40, 0x40, 0x00 }, /* char 0xf3 (raw 0x73) */ { 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00 }, /* char 0xf4 (raw 0x74) */ { 0x20, 0x20, 0x78, 0x20, 0x20, 0x24, 0x18, 0x00 }, /* char 0xf5 (raw 0x75) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x4c, 0x34, 0x00 }, /* char 0xf6 (raw 0x76) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0xf7 (raw 0x77) */ { 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x00 }, /* char 0xf8 (raw 0x78) */ { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, /* char 0xf9 (raw 0x79) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x38 }, /* char 0xfa (raw 0x7a) */ { 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00 }, /* char 0xfb (raw 0x7b) */ { 0x1c, 0x30, 0x30, 0x60, 0x30, 0x30, 0x1c, 0x00 }, /* char 0xfc (raw 0x7c) */ { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }, /* char 0xfd (raw 0x7d) */ { 0x70, 0x18, 0x18, 0x0c, 0x18, 0x18, 0x70, 0x00 }, /* char 0xfe (raw 0x7e) */ { 0x34, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xff (raw 0x7f) */ { 0x00, 0x54, 0x28, 0x54, 0x28, 0x54, 0x00, 0x00 }, ================================================ FILE: gsplus/src/kegswin.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kegswin", "kegswin.vcxproj", "{C24D318A-501E-4B8C-901A-15977CB9C00C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.ActiveCfg = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.Build.0 = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.ActiveCfg = Debug|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.Build.0 = Debug|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.ActiveCfg = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.Build.0 = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.ActiveCfg = Release|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {32BE13D0-68A7-486D-874C-EA158125775B} EndGlobalSection EndGlobal ================================================ FILE: gsplus/src/kegswin.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 17.0 {C24D318A-501E-4B8C-901A-15977CB9C00C} Win32Proj Application true v143 Application false v143 Application true v143 Application false v143 true true WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreadedDebugDLL Level3 ProgramDatabase Disabled MachineX86 true Windows WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL Level3 ProgramDatabase MachineX86 true Console true true wsock32.lib;dsound.lib;winmm.lib;%(AdditionalDependencies) kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;wsock32.lib;dsound.lib;winmm.lib;odbccp32.lib;%(AdditionalDependencies) Console true Level3 ================================================ FILE: gsplus/src/ldvars ================================================ OBJECTS = adb.o engine_c.o clock.o config.o debugger.o scc.o scc_socket_driver.o scc_windriver.o scc_unixdriver.o iwm.o joystick_driver.o moremem.o paddles.o mockingboard.o sim65816.o smartport.o doc.o sound.o sound_driver.o woz.o unshk.o undeflate.o dynapro.o dyna_type.o dyna_filt.o dyna_validate.o applesingle.o video.o voc.o PROJROOT = .. ================================================ FILE: gsplus/src/macsnd_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Headers at: /Applications/Xcode.app/Contents/Developer/Platforms/ // MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ // Frameworks/AudioToolbox.framework/Headers // Some ideas from SDL code: // http://www-personal.umich.edu/~bazald/l/api/_s_d_l__coreaudio_8c_source.html #include "defc.h" #include "sound.h" #include #include #include // Mac OS X 12.0 deprecates kAudioObjectPropertyElementMaster. So now we // need to use kAudioObjectPropertyElementMain, and set it to ...Master if it // isn't already set. This is beyond dumb #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) # if __MAC_OS_X_VERSION_MAX_ALLOWED < 120000 # define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster # endif #endif #define MACSND_REBUF_SIZE (64*1024) word32 g_macsnd_rebuf[MACSND_REBUF_SIZE]; volatile int g_macsnd_rd_pos; volatile int g_macsnd_wr_pos; volatile int g_macsnd_playing = 0; int g_macsnd_channel_warn = 0; extern int g_sound_min_samples; // About 33ms extern int g_sound_max_multiplier; // About 6, so 6*33 ~= 200ms. extern int Verbose; extern int g_preferred_rate; extern word32 *g_sound_shm_addr; extern int g_sound_size; int g_call_num = 0; AURenderCallbackStruct g_callback_struct = { 0 }; static OSStatus audio_callback(void *in_ref_ptr, AudioUnitRenderActionFlags *aura_flags_ptr, const AudioTimeStamp *a_timestamp_ptr, UInt32 bus_number, UInt32 in_num_frame, AudioBufferList *abuflist_ptr) { word32 *wptr; int num_buffers, num_channels, num_samps, sample_num, samps_avail; int max_samples, min_samples; int i, j; if(in_ref_ptr || aura_flags_ptr || a_timestamp_ptr || bus_number) { // Avoid unused parameter warning } #if 0 printf("CB: audio_callback called, in_ref:%p, bus:%d, in_frame:%d\n", in_ref_ptr, bus_number, in_num_frame); #endif num_buffers = abuflist_ptr->mNumberBuffers; min_samples = g_sound_min_samples; max_samples = min_samples * g_sound_max_multiplier; #if 0 printf("CB: num_buffers: %d. sample_time:%lf, host_time:%016llx " "rate_scalar:%lf, word_clock_time:%016llx, flags:%04x\n", num_buffers, a_timestamp_ptr->mSampleTime, a_timestamp_ptr->mHostTime, a_timestamp_ptr->mRateScalar, a_timestamp_ptr->mWordClockTime, a_timestamp_ptr->mFlags); #endif sample_num = g_macsnd_rd_pos; samps_avail = (g_macsnd_wr_pos - sample_num) & (MACSND_REBUF_SIZE - 1); if((int)in_num_frame > samps_avail) { // We don't have enough samples, must pause g_macsnd_playing = 0; sample_num = g_macsnd_wr_pos; // Eat remaining samps } if((g_macsnd_playing == 0) && (samps_avail > (min_samples + (int)in_num_frame))) { // We can unpause g_macsnd_playing = 1; } if(g_macsnd_playing && (samps_avail > max_samples)) { printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); sample_num += (max_samples / 2); sample_num = sample_num & (MACSND_REBUF_SIZE - 1); } #if 0 printf("CB: in_frame:%d, samps_avail:%d, playing:%d\n", in_num_frame, samps_avail, g_macsnd_playing); #endif for(i = 0; i < num_buffers; i++) { num_channels = abuflist_ptr->mBuffers[i].mNumberChannels; if((num_channels != 2) && !g_macsnd_channel_warn) { printf("mNumberChannels:%d\n", num_channels); g_macsnd_channel_warn = 1; } num_samps = abuflist_ptr->mBuffers[i].mDataByteSize / 4; wptr = (word32 *)abuflist_ptr->mBuffers[i].mData; #if 0 printf("CB buf[%d]: num_ch:%d, num_samps:%d, wptr:%p\n", i, num_channels, num_samps, wptr); #endif #if 0 if((g_call_num & 31) == 0) { printf("%d play %d samples, samps_avail:%d\n", g_call_num, num_samps, samps_avail); } #endif if(g_macsnd_playing) { for(j = 0; j < num_samps; j++) { wptr[j] = g_macsnd_rebuf[sample_num]; sample_num++; if(sample_num >= MACSND_REBUF_SIZE) { sample_num = 0; } } } else { for(j = 0; j < num_samps; j++) { wptr[j] = 0; } } } g_call_num++; g_macsnd_rd_pos = sample_num; return 0; } int mac_send_audio(byte *ptr, int in_size) { word32 *wptr, *macptr; int samps, sample_num; int i; samps = in_size / 4; wptr = (word32 *)ptr; sample_num = g_macsnd_wr_pos; macptr = &(g_macsnd_rebuf[0]); for(i = 0; i < samps; i++) { macptr[sample_num] = *wptr++; sample_num++; if(sample_num >= MACSND_REBUF_SIZE) { sample_num = 0; } } g_macsnd_wr_pos = sample_num; return in_size; } AudioObjectPropertyAddress g_aopa = { 0 }; void macsnd_init() { AudioComponentInstance ac_inst; AudioStreamBasicDescription *str_desc_ptr; AudioComponentDescription *ac_descr_ptr; AudioComponent ac; AudioDeviceID device; OSStatus result; UInt32 size; g_macsnd_rd_pos = 0; g_macsnd_wr_pos = 0; mac_printf("macsnd_init called\n"); g_aopa.mSelector = kAudioHardwarePropertyDefaultOutputDevice; g_aopa.mScope = kAudioObjectPropertyScopeGlobal; g_aopa.mElement = kAudioObjectPropertyElementMain; size = 4; result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &g_aopa, 0, 0, &size, &device); if(result != 0) { printf("AudioObjectGetPropertData on DefaultOutputDevice:%d\n", result); return; } mac_printf("Audio Device number: %d\n", device); str_desc_ptr = calloc(1, sizeof(AudioStreamBasicDescription)); str_desc_ptr->mFormatID = kAudioFormatLinearPCM; str_desc_ptr->mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; str_desc_ptr->mChannelsPerFrame = 2; str_desc_ptr->mSampleRate = g_preferred_rate; str_desc_ptr->mFramesPerPacket = 1; str_desc_ptr->mBitsPerChannel = 16; // 16-bit samples str_desc_ptr->mBytesPerFrame = (16 * 2) / 8; str_desc_ptr->mBytesPerPacket = str_desc_ptr->mBytesPerFrame; ac_descr_ptr = calloc(1, sizeof(AudioComponentDescription)); ac_descr_ptr->componentType = kAudioUnitType_Output; ac_descr_ptr->componentManufacturer = kAudioUnitManufacturer_Apple; ac_descr_ptr->componentSubType = kAudioUnitSubType_DefaultOutput; ac = AudioComponentFindNext(0, ac_descr_ptr); mac_printf("AudioComponentFindNext ret: %p\n", ac); if(ac == 0) { exit(1); } result = AudioComponentInstanceNew(ac, &ac_inst); mac_printf("AudioComponentInstanceNew ret:%d\n", result); if(result != 0) { exit(1); } result = AudioUnitSetProperty(ac_inst, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device, sizeof(device)); mac_printf("AudioUnitSetProperty CurrentDevice ret: %d\n", result); if(result != 0) { exit(1); } result = AudioUnitSetProperty(ac_inst, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, str_desc_ptr, sizeof(*str_desc_ptr)); mac_printf("AudioUnitSetProperty StreamFormat ret: %d\n", result); if(result != 0) { exit(1); } g_callback_struct.inputProc = audio_callback; g_callback_struct.inputProcRefCon = (void *)1; result = AudioUnitSetProperty(ac_inst, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &g_callback_struct, sizeof(g_callback_struct)); mac_printf("AudioUnitSetProperty SetRenderCallback ret: %d\n", result); if(result != 0) { exit(1); } result = AudioUnitInitialize(ac_inst); mac_printf("AudioUnitInitialize ret: %d\n", result); if(result != 0) { exit(1); } result = AudioOutputUnitStart(ac_inst); mac_printf("AudioOutputUnitStart ret: %d\n", result); if(result != 0) { exit(1); } sound_set_audio_rate(g_preferred_rate); mac_printf("End of child_sound_init_mac\n"); fflush(stdout); } ================================================ FILE: gsplus/src/mockingboard.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" #include "sound.h" // Mockingboard contains two pairs of a 6522/AY-8913, where the 6522 interfaces // the AY-8913 (which makes the sounds) to the Apple II. Each AY-8913 // contains 3 channels of sound: A,B,C. Model each pair separately. // The AY-8913 has 16 registers. The documentation numbers them using octal! // The AY8913 is accessed using ORB of the 6522 as control: 0 = Reset, 4=Idle // 5=Reg read; 6=Write reg; 7=Latch reg address extern Mockingboard g_mockingboard; dword64 g_mockingboard_last_int_dusec = 0ULL; dword64 g_mockingboard_event_int_dusec = 0ULL; // 0 -> no event pending extern int g_irq_pending; extern double g_dsamps_per_dfcyc; void mock_ay8913_reset(int pair_num, dword64 dfcyc) { Ay8913 *ay8913ptr; int i; if(dfcyc) { // Avoid unused parameter warning } ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); ay8913ptr->reg_addr_latch = 16; // out-of-range, mb-audit1.3 for(i = 0; i < 16; i++) { ay8913ptr->regs[i] = 0; } for(i = 0; i < 3; i++) { ay8913ptr->toggle_tone[i] = 0; ay8913ptr->tone_samp[i] = 0; } ay8913ptr->noise_val = 0x12345678; ay8913ptr->noise_samp = 0; ay8913ptr->env_dsamp = 0; } void mockingboard_reset(dword64 dfcyc) { word32 timer1_latch; timer1_latch = g_mockingboard.pair[0].mos6522.timer1_latch; memset(&g_mockingboard, 0, sizeof(g_mockingboard)); g_mockingboard_last_int_dusec = (dfcyc >> 16) << 16; if(g_mockingboard_event_int_dusec != 0) { (void)remove_event_mockingboard(); } g_mockingboard_event_int_dusec = 0; printf("At reset, timer1_latch: %08x\n", timer1_latch); if((timer1_latch & 0xffff) == 0x234) { // MB-audit g_mockingboard.pair[0].mos6522.timer1_latch = timer1_latch; } else { g_mockingboard.pair[0].mos6522.timer1_latch = 0xff00; } g_mockingboard.pair[0].mos6522.timer1_counter = 0x2ff00; g_mockingboard.pair[0].mos6522.timer2_counter = 0x2ff00; g_mockingboard.pair[1].mos6522.timer1_latch = 0xff00; g_mockingboard.pair[1].mos6522.timer1_counter = 0x2ff00; g_mockingboard.pair[1].mos6522.timer2_counter = 0x2ff00; mock_ay8913_reset(0, dfcyc); mock_ay8913_reset(1, dfcyc); } void mock_show_pair(int pair_num, dword64 dfcyc, const char *str) { Mos6522 *mos6522ptr; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); printf("Mock %d %s, t1_lat:%05x, t1_c:%05x, t2_l:%05x t2_c:%05x, ifr:" "%02x, acr:%02x, ier:%02x\n", pair_num, str, mos6522ptr->timer1_latch, mos6522ptr->timer1_counter, mos6522ptr->timer2_latch, mos6522ptr->timer2_counter, mos6522ptr->ifr, mos6522ptr->acr, mos6522ptr->ier); printf(" dfcyc:%016llx, event_int:%lld\n", dfcyc, g_mockingboard_event_int_dusec); } // Timers work as follows: if written with '10' in cycle 0, on cycle 1 the // counters loads with '10', and counts down to 9 on cycle 2...down to 1 // on cycle 10, then 0 on cycle 11, and then signals an interrupt on cycle 12 // To handle this, timers are "read value + 1" so that timer==0 means the // timer should interrupt right now. So to write '10' to a timer, the code // needs to write 10+2=12 to the variable, so that on the next cycle, it is // 11, which will be returned as 10 to software reading the reg. void mock_update_timers(int doit, dword64 dfcyc) { Mos6522 *mos6522ptr; dword64 dusec, ddiff, dleft, timer1_int_dusec, timer2_int_dusec; dword64 closest_int_dusec, event_int_dusec; word32 timer_val, ier, timer_eff, timer_latch, log_ifr; int i; dbg_log_info(dfcyc, doit, g_mockingboard.pair[0].mos6522.ifr | (g_mockingboard.pair[0].mos6522.ier << 8), 0xc0); dusec = dfcyc >> 16; ddiff = dusec - g_mockingboard_last_int_dusec; if(!doit && (dusec <= g_mockingboard_last_int_dusec)) { return; // Nothing more to do } // printf("mock_update_timers at %lf %016llx, ddiff:%llx\n", dfcyc, // dusec, ddiff); // Update timers by ddiff integer cycles, calculate next event time g_mockingboard_last_int_dusec = dusec; closest_int_dusec = 0; for(i = 0; i < 2; i++) { // pair_num mos6522ptr = &(g_mockingboard.pair[i].mos6522); timer1_int_dusec = 0; timer_val = mos6522ptr->timer1_counter; ier = mos6522ptr->ier; timer_eff = (timer_val & 0x1ffff); dleft = ddiff; timer_latch = mos6522ptr->timer1_latch + 2; dbg_log_info(dfcyc, (word32)dleft, timer_val, 0xcb); dbg_log_info(dfcyc, ier, timer_latch, 0xcb); if(dleft < timer_eff) { // Move ahead only a little, no triggering timer_val = timer_val - (word32)dleft; if(ddiff) { // printf("New timer1_val:%05x, dleft:%08llx\n", // timer_val, dleft); } if(((mos6522ptr->ifr & 0x40) == 0) && (ier & 0x40)) { // IFR not set yet, prepare an event timer1_int_dusec = dusec + (timer_val & 0x1ffff); dbg_log_info(dfcyc, (word32)timer1_int_dusec, timer_val, 0xcd); // printf("t1_int_dusec: %016llx\n", // timer1_int_dusec); } } else { // Timer1 has triggered now (maybe rolled over more // than once). log_ifr = 0; if((timer_val & 0x20000) == 0) { // Either free-running, or not one-shot already // triggered // Set interrupt: Ensure IFR | 0x40 is set mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, mos6522ptr->ifr | 0x40, ier); log_ifr = 1; } dleft -= timer_eff; if(dleft >= timer_latch) { // It's rolled over several times, remove those dleft = dleft % timer_latch; dbg_log_info(dfcyc, (word32)dleft, timer_latch, 0xcc); } if(dleft == 0) { dleft = timer_latch; } timer_val = (timer_latch - dleft) & 0x1ffff; if((mos6522ptr->acr & 0x40) == 0) { // ACR6=0: One shot mode, mark it as triggered timer_val |= 0x20000; } if(log_ifr) { dbg_log_info(dfcyc, mos6522ptr->ifr | (ier << 8), timer_val, 0xc3); } } #if 0 printf("%016llx ch%d timer1 was %05x, now %05x\n", dfcyc, i, mos6522ptr->timer1_counter, timer_val); #endif mos6522ptr->timer1_counter = timer_val; dbg_log_info(dfcyc, timer_val, timer_latch, 0xce); // Handle timer2 timer2_int_dusec = 0; timer_val = mos6522ptr->timer2_counter; timer_eff = timer_val & 0x1ffff; dleft = ddiff; if(mos6522ptr->acr & 0x20) { // Count pulses mode. Just don't count dleft = 0; } if(dleft < timer_eff) { // Move ahead only a little, no triggering timer_val = timer_val - (word32)dleft; if(((mos6522ptr->ifr & 0x20) == 0) && (ier & 0x20)) { // IFR not set yet, prepare an event timer2_int_dusec = dusec + (timer_val & 0x1ffff); //printf("t2_int_dusec: %016llx\n", // timer1_int_dusec); } } else if(timer_val & 0x20000) { // And already triggered once, just update count timer_val = ((timer_eff - dleft) & 0xffff) | 0x20000; } else { // Has not triggered once yet, but it will now mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, mos6522ptr->ifr | 0x20, ier); timer_val = ((timer_val - dleft) & 0xffff) | 0x20000; dbg_log_info(dfcyc, mos6522ptr->ifr | (ier << 8), timer_val, 0xc4); } // printf("ch%d timer2 was %05x, now %05x\n", i, // mos6522ptr->timer2_counter, timer_val); mos6522ptr->timer2_counter = timer_val; if(timer1_int_dusec && timer2_int_dusec) { timer1_int_dusec = MY_MIN(timer1_int_dusec, timer2_int_dusec); } if(timer1_int_dusec) { if(closest_int_dusec) { closest_int_dusec = MY_MIN(closest_int_dusec, timer1_int_dusec); } else { closest_int_dusec = timer1_int_dusec; } } } event_int_dusec = g_mockingboard_event_int_dusec; if(closest_int_dusec) { // See if this is sooner than the current pending event // printf("closest_int_dusec: %016llx, event_int:%016llx\n", // closest_int_dusec, event_int_dusec); doit = 0; if(event_int_dusec && (closest_int_dusec < event_int_dusec)) { // There was a pending event. Discard it // printf("Call remove_event_mockingboard\n"); remove_event_mockingboard(); doit = 1; } if(!event_int_dusec || doit) { //printf("Call add_event_mockingboard: %016llx %lld\n", // closest_int_dusec, closest_int_dusec); add_event_mockingboard(closest_int_dusec << 16); g_mockingboard_event_int_dusec = closest_int_dusec; dbg_log_info(dfcyc, (word32)(closest_int_dusec - (dfcyc >> 16)), 0, 0xc1); } } } void mockingboard_event(dword64 dfcyc) { // Received an event--we believe we may need to set an IRQ now. // Event was already removed from the event queue // printf("Mockingboard_event received at %016llx\n", dfcyc); dbg_log_info(dfcyc, 0, 0, 0xc2); g_mockingboard_event_int_dusec = 0; mock_update_timers(1, dfcyc); } word32 mockingboard_read(word32 loc, dword64 dfcyc) { int pair_num; // printf("mockingboard read: %04x\n", loc); pair_num = (loc >> 7) & 1; // 0 or 1 return mock_6522_read(pair_num, loc & 0xf, dfcyc); } void mockingboard_write(word32 loc, word32 val, dword64 dfcyc) { int pair_num; // printf("mockingboard write: %04x=%02x\n", loc, val); pair_num = (loc >> 7) & 1; // 0 or 1 mock_6522_write(pair_num, loc & 0xf, val, dfcyc); } word32 mock_6522_read(int pair_num, word32 loc, dword64 dfcyc) { Mos6522 *mos6522ptr; word32 val; // Read from 6522 #pair_num loc (0-15) mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); val = 0; switch(loc) { case 0x0: // ORB/IRB // Connected to AY8913 { RESET, BDIR, BC1 } val = mos6522ptr->orb; // There are no outputs from AY8913 to the 6522 B Port break; case 0x1: // ORA case 0xf: // ORA, no handshake val = mos6522ptr->ora; break; case 0x2: // DDRB val = mos6522ptr->ddrb; break; case 0x3: // DDRA val = mos6522ptr->ddra; break; case 0x4: // T1C-L (timer[0]) mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_counter - 1) & 0xff; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); // Prepare another int (maybe) dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc5); break; case 0x5: // T1C-H mock_update_timers(1, dfcyc); val = ((mos6522ptr->timer1_counter - 1) >> 8) & 0xff; break; case 0x6: // T1L-L val = mos6522ptr->timer1_latch & 0xff; break; case 0x7: // T1L-H val = (mos6522ptr->timer1_latch >> 8) & 0xff; break; case 0x8: // T2C-L mock_update_timers(1, dfcyc); val = (mos6522ptr->timer2_counter - 1) & 0xff; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x20), mos6522ptr->ier); // Clear Bit 5 mock_update_timers(1, dfcyc); // Prepare another int (maybe) dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc6); break; case 0x9: // T2C-H mock_update_timers(1, dfcyc); val = ((mos6522ptr->timer2_counter - 1) >> 8) & 0xff; break; case 0xa: // SR val = mos6522ptr->sr; //halt_printf("Reading SR %d %02x\n", pair_num, val); break; case 0xb: // ACR val = mos6522ptr->acr; break; case 0xc: // PCR val = mos6522ptr->pcr; break; case 0xd: // IFR mock_update_timers(1, dfcyc); val = mos6522ptr->ifr; break; case 0xe: // IER val = mos6522ptr->ier | 0x80; // Despite MOS6522 break; // datasheet, bit 7 = 1 } // printf("6522 %d loc:%x ret:%02x\n", pair_num, loc, val); return val; } void mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc) { Mos6522 *mos6522ptr; word32 ora, orb, new_val, mask; // Write to 6522 #num6522 loc (0-15) // printf("6522 %d loc:%x write:%02x\n", pair_num, loc, val); mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); switch(loc) { case 0x0: // ORB mask = mos6522ptr->ddrb; orb = mos6522ptr->orb; new_val = (val & mask) | (orb & (~mask)); if(orb != new_val) { mock_ay8913_control_update(pair_num, new_val, orb, dfcyc); } mos6522ptr->orb = new_val; break; case 0x1: // ORA case 0xf: // ORA, no handshake mask = mos6522ptr->ddra; ora = mos6522ptr->ora; new_val = (val & mask) | (ora & (~mask)); mos6522ptr->ora = new_val; break; case 0x2: // DDRB orb = mos6522ptr->orb; new_val = (orb & val) | (orb & (~val)); if(orb != new_val) { mock_ay8913_control_update(pair_num, new_val, orb, dfcyc); } mos6522ptr->orb = new_val; mos6522ptr->ddrb = val; return; case 0x3: // DDRA ora = mos6522ptr->ora; mos6522ptr->ora = (ora & val) | (ora & (~val)); mos6522ptr->ddra = val; return; case 0x4: // T1C-L mock_update_timers(0, dfcyc); mos6522ptr->timer1_latch = (mos6522ptr->timer1_latch & 0x1ff00) | val; // printf("Set T1C-L, timer1_latch=%05x\n", // mos6522ptr->timer1_latch); break; case 0x5: // T1C-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); mos6522ptr->timer1_latch = val; mos6522ptr->timer1_counter = val + 2; // The actual timer1_counter update happens next cycle, // so we want val+1, plus another 1 // printf("Set T1C-H, timer1_latch=%05x\n", // mos6522ptr->timer1_latch); mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc7); break; case 0x6: // T1L-L mock_update_timers(0, dfcyc); mos6522ptr->timer1_latch = (mos6522ptr->timer1_latch & 0x1ff00) | val; break; case 0x7: // T1L-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); mos6522ptr->timer1_latch = val; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); // mock_show_pair(pair_num, dfcyc, "Wrote T1L-H"); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc8); break; case 0x8: // T2C-L mos6522ptr->timer2_latch = (mos6522ptr->timer2_latch & 0xff00) | val; break; case 0x9: // T2C-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer2_latch & 0xff) | (val << 8); mos6522ptr->timer2_latch = val; mos6522ptr->timer2_counter = val + 2; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x20), mos6522ptr->ier); // Clear bit 5 mock_update_timers(1, dfcyc); break; case 0xa: // SR mos6522ptr->sr = val; halt_printf("Wrote SR reg: %d %02x\n", pair_num, val); break; case 0xb: // ACR mock_update_timers(0, dfcyc); mos6522ptr->acr = val; mock_update_timers(1, dfcyc); break; case 0xc: // PCR mos6522ptr->pcr = val; break; case 0xd: // IFR mock_update_timers(1, dfcyc); mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~val), mos6522ptr->ier); mock_update_timers(1, dfcyc); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc9); break; case 0xe: // IER // Recalculate effective IFR with new IER mock_update_timers(1, dfcyc); if(val & 0x80) { // Set EIR bits val = mos6522ptr->ier | val; } else { // Clear EIR bits val = mos6522ptr->ier & (~val); } val = val & 0x7f; mos6522ptr->ier = val; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr, val); mock_update_timers(1, dfcyc); // mock_show_pair(pair_num, dfcyc, "Wrote IER"); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xca); break; } } word32 mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier) { word32 irq_mask; // Determine if there are any interrupts pending now irq_mask = IRQ_PENDING_MOCKINGBOARDA << pair_num; if((ifr & ier & 0x7f) == 0) { // No IRQ pending anymore ifr = ifr & 0x7f; // Clear bit 7 if(g_irq_pending & irq_mask) { // printf("MOCK INT OFF\n"); } remove_irq(irq_mask); dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); } else { // IRQ is pending ifr = ifr | 0x80; // Set bit 7 if(!(g_irq_pending & irq_mask)) { // printf("MOCK INT ON\n"); } add_irq(irq_mask); dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); } return ifr; } void mock_ay8913_reg_read(int pair_num) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; word32 reg_addr_latch, mask, val, ora; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); reg_addr_latch = ay8913ptr->reg_addr_latch; val = 0; if(reg_addr_latch < 16) { val = ay8913ptr->regs[reg_addr_latch]; } // ORA at 6522 is merge of ORA using DDRA mask = mos6522ptr->ddra; ora = mos6522ptr->ora; mos6522ptr->ora = (ora & mask) | (val & (~mask)); } word32 g_mock_channel_regs[3] = { 0x39c3, // channel A: regs 0,1,6,7,8,11,12,13 0x3acc, // channel B: regs 2,3,6,7,9,11,12,13 0x3cf0 // channel C: regs 4,5,6,7,10,11,12,13 }; void mock_ay8913_reg_write(int pair_num, dword64 dfcyc) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; double dsamps; word32 reg_addr_latch, ora, mask, rmask, do_print; int i; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); reg_addr_latch = ay8913ptr->reg_addr_latch; ora = mos6522ptr->ora; dsamps = dfcyc * g_dsamps_per_dfcyc; if(reg_addr_latch < 16) { mask = (g_mockingboard.disable_mask >> (3*pair_num)) & 7; rmask = 0; do_print = 0; for(i = 0; i < 3; i++) { if(((mask >> i) & 1) == 0) { rmask |= g_mock_channel_regs[i]; } } do_print = (rmask >> reg_addr_latch) & 1; if((ora != ay8913ptr->regs[reg_addr_latch]) || (reg_addr_latch == 13)) { // New value, or writing to Envelope control do_print = 0; if(do_print) { printf("%.2lf %016llx mock pair%d reg[%d]=" "%02x. [2,3]=%02x_%02x [67]=%02x,%02x, " "[9]=%02x, [12,11]=%02x_%02x [13]=" "%02x\n", dsamps, dfcyc, pair_num, reg_addr_latch, ora, ay8913ptr->regs[3], ay8913ptr->regs[2], ay8913ptr->regs[6], ay8913ptr->regs[7], ay8913ptr->regs[9], ay8913ptr->regs[12], ay8913ptr->regs[11], ay8913ptr->regs[13]); } sound_play(dfcyc); } ay8913ptr->regs[reg_addr_latch] = ora; if(reg_addr_latch == 13) { // Envelope control ay8913ptr->env_dsamp &= 0x1fffffffffffULL; // Clear "hold" in (env_val & (0x80 << 40)) } } } void mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, dword64 dfcyc) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); // printf("ay8913 %d control now:%02x\n", pair_num, new_val); // new_val and prev_val are { reset_l, BDIR, BC1 } // 4=Idle; 5=Read; 6=Write; 7=Latch_addr // Latch new address and write data at the time the ctl changes to Idle // Do read as soon as the ctl indicates to do a read. if((new_val & 4) == 0) { mock_ay8913_reset(pair_num, dfcyc); return; } new_val = new_val & 7; prev_val = prev_val & 7; if(prev_val == 7) { // Latch new address, latch it now ay8913ptr->reg_addr_latch = mos6522ptr->ora; } else if(prev_val == 6) { // Write data, do it now mock_ay8913_reg_write(pair_num, dfcyc); } if(new_val == 5) { mock_ay8913_reg_read(pair_num); } } void mockingboard_show(int got_num, word32 disable_mask) { int i, j; if(got_num) { g_mockingboard.disable_mask = disable_mask; } printf("g_mockingboard.disable_mask:%02x\n", g_mockingboard.disable_mask); for(i = 0; i < 2; i++) { for(j = 0; j < 14; j++) { printf("Mockingboard pair[%d].reg[%d]=%02x\n", i, j, g_mockingboard.pair[i].ay8913.regs[j]); } } } ================================================ FILE: gsplus/src/moremem.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" extern char g_kegs_version_str[]; extern byte *g_memory_ptr; extern byte *g_dummy_memory1_ptr; extern byte *g_slow_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern word32 g_slow_mem_changed[]; extern int g_num_breakpoints; extern Break_point g_break_pts[]; extern Page_info page_info_rd_wr[]; extern int Verbose; extern int g_rom_version; extern int g_user_page2_shadow; extern Iwm g_iwm; extern int g_halt_sim; extern int g_config_control_panel; /* from iwm.c */ int g_num_shadow_all_banks = 0; #define IOR(val) ( (val) ? 0x80 : 0x00 ) extern int g_cur_a2_stat; int g_em_emubyte_cnt = 0; int g_paddle_buttons = 0; int g_irq_pending = 0; word32 g_c023_val = 0; word32 g_c029_val_some = 0x41; word32 g_c02b_val = 0x08; word32 g_c02d_int_crom = 0; word32 g_c033_data = 0; word32 g_c034_val = 0; word32 g_c035_shadow_reg = 0x08; // A bit set inhibits that shadowing // [6]=Inhibit I/O and LC, [4]=Inhibit Aux hires, [3]=Inh SHR // [2]=Inh hires pg 2, [1]=Inh hires pg 1, [0]=Inh text pages word32 g_c036_val_speed = 0x80; // [7]=Fast, [4]=Shadow in all banks, // [3:0]=slot 7..4 disk motor detect word32 g_c03ef_doc_ptr = 0; word32 g_c041_val = 0; /* C041_EN_25SEC_INTS, C041_EN_MOVE_INTS */ word32 g_c046_val = 0; word32 g_c05x_annuncs = 0; word32 g_c068_statereg = 0; // [7]=ALTZP, [6]=PAGE2, [5]=RAMRD, [4]=RAMWRT // [3]=RDROM, [2]=LCBANK2, [1]=ROMBANK, [0]=INTCXROM // KEGS special: [8]=PREWRITE_LC, [9]=WRDEFRAM, // [10]=INTC8ROM word32 g_c06d_val = 0; word32 g_c06f_val = 0; word32 g_zipgs_unlock = 0; word32 g_zipgs_reg_c059 = 0x5f; // 7=LC cache dis, 6==5ms paddle del en, 5==5ms ext del en, // 4==5ms c02e enab, 3==CPS follow enab, 2-0: 111 word32 g_zipgs_reg_c05a = 0x0f; // 7:4 = current ZIP speed, 0=100%, 1=93.75%, F=6.25% // 3:0: always 1111 word32 g_zipgs_reg_c05b = 0x40; // 7==1ms clock, 6==cshupd: tag data at c05f updated // 5==LC cache disable, 4==bd is disabled, 3==delay in effect, // 2==rombank, 1-0==ram size (00:8K, 01=16K, 10=32K, 11=64K) word32 g_zipgs_reg_c05c = 0x00; // 7:1==slot delay enable (for 52-54ms), 0==speaker 5ms delay word32 g_c06c_latched_cyc = 0; #define SLINKY_RAM_SIZE 0x100000 /* 1MB */ word32 g_slinky_addr = 0; byte g_slinky_ram[SLINKY_RAM_SIZE]; #define EMUSTATE(a) { #a, &a } Emustate_intlist g_emustate_intlist[] = { // EMUSTATE(g_cur_a2_stat), // EMUSTATE(g_paddle_buttons), // EMUSTATE(g_em_emubyte_cnt), // EMUSTATE(g_irq_pending), EMUSTATE(g_c023_val), EMUSTATE(g_c029_val_some), EMUSTATE(g_c02b_val), EMUSTATE(g_c02d_int_crom), EMUSTATE(g_c033_data), EMUSTATE(g_c034_val), EMUSTATE(g_c035_shadow_reg), EMUSTATE(g_c036_val_speed), EMUSTATE(g_c041_val), EMUSTATE(g_c046_val), EMUSTATE(g_c05x_annuncs), EMUSTATE(g_c068_statereg), EMUSTATE(g_zipgs_unlock), EMUSTATE(g_zipgs_reg_c059), EMUSTATE(g_zipgs_reg_c05a), EMUSTATE(g_zipgs_reg_c05b), EMUSTATE(g_zipgs_reg_c05c), { 0, 0, } }; extern dword64 g_paddle_trig_dfcyc; extern dword64 g_last_vbl_dfcyc; Emustate_dword64list g_emustate_dword64list[] = { EMUSTATE(g_paddle_trig_dfcyc), EMUSTATE(g_last_vbl_dfcyc), { 0, 0, } }; extern word32 g_mem_size_total; Emustate_word32list g_emustate_word32list[] = { EMUSTATE(g_mem_size_total), EMUSTATE(g_c03ef_doc_ptr), { 0, 0, } }; #define UNIMPL_READ \ halt_printf("UNIMP READ to addr %08x\n", loc); \ return 0; #define UNIMPL_WRITE \ halt_printf("UNIMP WRITE to addr %08x, val: %04x\n", loc, val); \ return; void fixup_brks() { word32 page, tmp, tmp2, end_page; Pg_info val; int is_wr_only, num; int i; num = g_num_breakpoints; for(i = 0; i < num; i++) { page = (g_break_pts[i].start_addr >> 8) & 0xffff; end_page = (g_break_pts[i].end_addr >> 8) & 0xffff; is_wr_only = (g_break_pts[i].start_addr >> 24) & 1; while(page <= end_page) { if(!is_wr_only) { val = GET_PAGE_INFO_RD(page); tmp = PTR2WORD(val) & 0xff; tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; SET_PAGE_INFO_RD(page, val - tmp + tmp2); } val = GET_PAGE_INFO_WR(page); tmp = PTR2WORD(val) & 0xff; tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; SET_PAGE_INFO_WR(page, val - tmp + tmp2); page++; } } } void fixup_hires_on() { if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { return; } fixup_bank0_2000_4000(); fixup_brks(); } void fixup_bank0_2000_4000() { byte *mem0rd; byte *mem0wr; word32 start_page; int i; // Do banks $00 and $E0 for(i = 0; i < 2; i++) { mem0rd = &(g_memory_ptr[0x2000]); start_page = 0x20; if(i) { start_page += 0xe000; // Bank $E0 mem0rd = &(g_slow_memory_ptr[0x2000]); } mem0wr = mem0rd; if((g_cur_a2_stat & ALL_STAT_ST80) && (g_cur_a2_stat & ALL_STAT_HIRES)) { if(g_cur_a2_stat & ALL_STAT_PAGE2) { mem0rd += 0x10000; mem0wr += 0x10000; if(((g_c035_shadow_reg & 0x12) == 0) || ((g_c035_shadow_reg & 0x8) == 0) || i) { mem0wr += BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { mem0wr += BANK_SHADOW; } } else { if(RAMRD) { mem0rd += 0x10000; } if(RAMWRT) { mem0wr += 0x10000; if(((g_c035_shadow_reg & 0x12) == 0) || ((g_c035_shadow_reg & 0x8) == 0) || i) { mem0wr += BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { mem0wr += BANK_SHADOW; } } fixup_any_bank_any_page(start_page, 0x20, mem0rd, mem0wr); } } void fixup_bank0_0400_0800() { byte *mem0rd; byte *mem0wr; word32 start_page, shadow; int i; for(i = 0; i < 2; i++) { mem0rd = &(g_memory_ptr[0x400]); start_page = 4; if(i) { start_page += 0xe000; // Bank $E0 mem0rd = &(g_slow_memory_ptr[0x400]); } mem0wr = mem0rd; shadow = BANK_SHADOW; if(g_cur_a2_stat & ALL_STAT_ST80) { if(g_cur_a2_stat & ALL_STAT_PAGE2) { shadow = BANK_SHADOW2; mem0rd += 0x10000; mem0wr += 0x10000; } } else { if(RAMWRT) { shadow = BANK_SHADOW2; mem0wr += 0x10000; } if(RAMRD) { mem0rd += 0x10000; } } if(((g_c035_shadow_reg & 0x01) == 0) || i) { mem0wr += shadow; } fixup_any_bank_any_page(start_page, 4, mem0rd, mem0wr); } } void fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, byte *mem0wr) { int i; for(i = 0; i < num_pages; i++) { SET_PAGE_INFO_RD(i + start_page, mem0rd); mem0rd += 0x100; } for(i = 0; i < num_pages; i++) { SET_PAGE_INFO_WR(i + start_page, mem0wr); mem0wr += 0x100; } } void fixup_intcx() { byte *rom10000; byte *rom_inc; word32 intcx, mask; int no_io_shadow, off, start_k; int j, k; rom10000 = &(g_rom_fc_ff_ptr[0x30000]); no_io_shadow = (g_c035_shadow_reg & 0x40); start_k = 0; if(no_io_shadow) { /* if not shadowing, banks 0 and 1 are not affected by intcx */ start_k = 2; } intcx = (g_c068_statereg & 1); // set means use internal rom // printf("fixup_intcx, intcx:%d, no_io:%d, c02d:%02x\n", intcx, // no_io_shadow, g_c02d_int_crom); for(k = start_k; k < 4; k++) { off = k; if(k >= 2) { off += (0xe0 - 2); } /* step off through 0x00, 0x01, 0xe0, 0xe1 */ off = off << 8; // Now 0x0000, 0x0100, 0xe000, 0xe100 SET_PAGE_INFO_RD(0xc0 + off, SET_BANK_IO); for(j = 0xc1; j < 0xc8; j++) { mask = 1 << (j & 0xf); rom_inc = SET_BANK_IO; if(((g_c02d_int_crom & mask) == 0) || intcx) { rom_inc = rom10000 + (j << 8); } else { // User-slot rom rom_inc = &(g_rom_cards_ptr[0]) + ((j - 0xc0) << 8); if(j == 0xc4) { // Mockingboard rom_inc = SET_BANK_IO; } } if((j == 0xc3) && !(g_c068_statereg & 0x400)) { // If INTC8ROM not set, we need to // watch for reads from C3xx to set it rom_inc = SET_BANK_IO; } SET_PAGE_INFO_RD(j + off, rom_inc); } for(j = 0xc8; j < 0xd0; j++) { /* c800 - cfff */ if(intcx || (g_c068_statereg & 0x400)) { // INTCXROM or INTC8ROM is set rom_inc = rom10000 + (j << 8); if((j == 0xcf) && (g_c068_statereg & 0x400)) { rom_inc = SET_BANK_IO; } } else { // c800 space not necessarily mapped rom_inc = SET_BANK_IO; } SET_PAGE_INFO_RD(j + off, rom_inc); } for(j = 0xc0; j < 0xd0; j++) { SET_PAGE_INFO_WR(j + off, SET_BANK_IO); } } fixup_brks(); } void fixup_st80col(dword64 dfcyc) { int cur_a2_stat; cur_a2_stat = g_cur_a2_stat; fixup_bank0_0400_0800(); if(cur_a2_stat & ALL_STAT_HIRES) { /* fixup no matter what PAGE2 since PAGE2 and RAMRD/WR */ /* can work against each other */ fixup_bank0_2000_4000(); } if(cur_a2_stat & ALL_STAT_PAGE2) { change_display_mode(dfcyc); } fixup_brks(); } void fixup_altzp() { byte *mem0rd; word32 start_page, altzp; int i; altzp = ALTZP; for(i = 0; i < 2; i++) { start_page = 0; mem0rd = &(g_memory_ptr[0]); if(i) { start_page = 0xe000; mem0rd = &(g_slow_memory_ptr[0]); } if(altzp) { mem0rd += 0x10000; } fixup_any_bank_any_page(start_page, 2, mem0rd, mem0rd); } /* No need for fixup_brks since called from set_statereg() */ } void fixup_page2(dword64 dfcyc) { if((g_cur_a2_stat & ALL_STAT_ST80)) { fixup_bank0_0400_0800(); if((g_cur_a2_stat & ALL_STAT_HIRES)) { fixup_bank0_2000_4000(); } } else { change_display_mode(dfcyc); } } void fixup_ramrd() { byte *mem0rd; word32 start_page, cur_a2_stat; int i, j; cur_a2_stat = g_cur_a2_stat; if((cur_a2_stat & ALL_STAT_ST80) == 0) { fixup_bank0_0400_0800(); } if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { fixup_bank0_2000_4000(); } for(i = 0; i < 2; i++) { start_page = 0; mem0rd = &(g_memory_ptr[0x0000]); if(i) { start_page = 0xe000; mem0rd = &(g_slow_memory_ptr[0]); } if(RAMRD) { mem0rd += 0x10000; } SET_PAGE_INFO_RD(start_page + 2, mem0rd + 0x200); SET_PAGE_INFO_RD(start_page + 3, mem0rd + 0x300); for(j = 8; j < 0x20; j++) { SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); } for(j = 0x40; j < 0xc0; j++) { SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); } } /* No need for fixup_brks since only called from set_statereg() */ } void fixup_ramwrt() { byte *mem0wr; word32 start_page, cur_a2_stat, shadow, ramwrt; int i, j; cur_a2_stat = g_cur_a2_stat; if((cur_a2_stat & ALL_STAT_ST80) == 0) { fixup_bank0_0400_0800(); } if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { fixup_bank0_2000_4000(); } for(i = 0; i < 2; i++) { start_page = 0; mem0wr = &(g_memory_ptr[0x0000]); if(i) { start_page = 0xe000; mem0wr = &(g_slow_memory_ptr[0]); } ramwrt = RAMWRT; if(ramwrt) { mem0wr += 0x10000; } SET_PAGE_INFO_WR(start_page + 2, mem0wr + 0x200); SET_PAGE_INFO_WR(start_page + 3, mem0wr + 0x300); shadow = BANK_SHADOW; if(ramwrt) { shadow = BANK_SHADOW2; } if( ((g_c035_shadow_reg & 0x20) != 0) || ((g_rom_version == 1) && !g_user_page2_shadow)) { if(!i) { shadow = 0; } } for(j = 8; j < 0x0c; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } for(j = 0xc; j < 0x20; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); } shadow = 0; if(ramwrt) { if(((g_c035_shadow_reg & 0x14) == 0) || ((g_c035_shadow_reg & 0x08) == 0) || i) { shadow = BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x04) == 0) || i) { shadow = BANK_SHADOW; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } shadow = 0; if(ramwrt && (((g_c035_shadow_reg & 0x08) == 0) || i)) { /* shr shadowing */ shadow = BANK_SHADOW2; } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } for(j = 0xa0; j < 0xc0; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); } } /* No need for fixup_brks() since only called from set_statereg() */ } void fixup_lc() { byte *mem0rd, *mem0wr; word32 bank, lcbank2, c08x_wrdefram, rdrom; int k; // Fixup memory from 0xd000 - 0xffff in banks 0, 1, $e0, $e1 for(k = 0; k < 4; k++) { bank = k; mem0rd = &(g_memory_ptr[k << 16]); if(k >= 2) { bank += (0xe0 - 2); //k==2->bank=$e0, k==3->bank=$e1 mem0rd = &(g_slow_memory_ptr[(k & 1) << 16]); } /* step bank through 0x00, 0x01, 0xe0, 0xe1 */ if(((k & 1) == 0) && ALTZP) { mem0rd += 0x10000; } lcbank2 = LCBANK2; c08x_wrdefram = g_c068_statereg & 0x200; rdrom = RDROM; if((k < 2) && (g_c035_shadow_reg & 0x40)) { lcbank2 = 0; c08x_wrdefram = 1; rdrom = 0; } mem0wr = mem0rd; if((k < 2) && !c08x_wrdefram) { mem0wr += (BANK_IO_TMP | BANK_IO2_TMP); } if((k < 2) && rdrom) { mem0rd = &(g_rom_fc_ff_ptr[0x30000]); } fixup_any_bank_any_page(bank*0x100 + 0xe0, 0x20, mem0rd + 0xe000, mem0wr + 0xe000); if(! lcbank2) { // lcbank1, 0xd000 is ram at 0xc000-cfff if(!rdrom) { mem0rd -= 0x1000; } mem0wr -= 0x1000; } fixup_any_bank_any_page(bank*0x100 + 0xd0, 0x10, mem0rd + 0xd000, mem0wr + 0xd000); } /* No need for fixup_brks() since only called from set_statereg(), */ /* or from other routines which will handle it */ } void set_statereg(dword64 dfcyc, word32 val) { word32 xor; dbg_log_info(dfcyc, val, g_c068_statereg, 0xc068); xor = val ^ g_c068_statereg; g_c068_statereg = val; if(xor == 0) { return; } if(xor & 0x28c) { // WRDEFRAM, ALTZP, RDROM, LCBANK2 fixup_lc(); if(xor & 0x80) { /* altzp */ fixup_altzp(); } } if(xor & 0x40) { // page2 g_cur_a2_stat = (g_cur_a2_stat & ~ALL_STAT_PAGE2) | (val & ALL_STAT_PAGE2); fixup_page2(dfcyc); } if(xor & 0x20) { // RAMRD fixup_ramrd(); } if(xor & 0x10) { // RAMWRT fixup_ramwrt(); } if(xor & 0x02) { // ROMBANK halt_printf("Just set rombank = %d\n", ROMB); } if(xor & 0x401) { // INTC8ROM, INTCX fixup_intcx(); } if(xor) { fixup_brks(); } } void fixup_shadow_txt1() { byte *mem0wr; int j; fixup_bank0_0400_0800(); mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x01) == 0) { mem0wr += BANK_SHADOW2; } for(j = 4; j < 8; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_txt2() { byte *mem0wr; int shadow; int j; /* bank 0 */ mem0wr = &(g_memory_ptr[0x00000]); shadow = BANK_SHADOW; if(RAMWRT) { mem0wr += 0x10000; shadow = BANK_SHADOW2; } if(((g_c035_shadow_reg & 0x20) == 0) && ((g_rom_version != 1) || g_user_page2_shadow)) { mem0wr += shadow; } for(j = 8; j < 0xc; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if(((g_c035_shadow_reg & 0x20) == 0) && ((g_rom_version != 1) || g_user_page2_shadow)) { mem0wr += BANK_SHADOW2; } for(j = 8; j < 0xc; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_hires1() { byte *mem0wr; int j; fixup_bank0_2000_4000(); /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x12) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x20; j < 0x40; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_hires2() { byte *mem0wr; int j; /* bank 0 */ mem0wr = &(g_memory_ptr[0x00000]); if(RAMWRT) { mem0wr += 0x10000; if((g_c035_shadow_reg & 0x14) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } } else if((g_c035_shadow_reg & 0x04) == 0) { mem0wr += BANK_SHADOW; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x14) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_shr() { byte *mem0wr; int j; /* bank 0, only pages 0x60 - 0xa0 */ mem0wr = &(g_memory_ptr[0x00000]); if(RAMWRT) { mem0wr += 0x10000; if((g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1, only pages 0x60 - 0xa0 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_iolc() { byte *mem0rd; int k; if(g_c035_shadow_reg & 0x40) { /* Disable language card area */ for(k = 0; k < 2; k++) { mem0rd = &(g_memory_ptr[k << 16]); fixup_any_bank_any_page((k << 8) + 0xc0, 0x10, mem0rd + 0xd000, mem0rd + 0xd000); if(k == 0 && ALTZP) { mem0rd += 0x10000; } fixup_any_bank_any_page((k << 8) + 0xd0, 0x10, mem0rd + 0xc000, mem0rd + 0xc000); fixup_any_bank_any_page((k << 8) + 0xe0, 0x20, mem0rd + 0xe000, mem0rd + 0xe000); } } else { /* 0xc000 area */ fixup_intcx(); // Fix 0xd000-0xffff banks 0, 1, 0xe0, 0xe1 fixup_lc(); } } void update_shadow_reg(dword64 dfcyc, word32 val) { int xor; dbg_log_info(dfcyc, val, g_c035_shadow_reg, 0xc035); if(g_c035_shadow_reg == val) { return; } xor = g_c035_shadow_reg ^ val; g_c035_shadow_reg = val; if(xor & 8) { fixup_shadow_hires1(); fixup_shadow_hires2(); fixup_shadow_shr(); xor = xor & (~0x16); } if(xor & 0x10) { fixup_shadow_hires1(); fixup_shadow_hires2(); xor = xor & (~0x6); } if(xor & 2) { fixup_shadow_hires1(); } if(xor & 4) { fixup_shadow_hires2(); } if(xor & 1) { fixup_shadow_txt1(); } if((xor & 0x20) && ((g_rom_version != 1) || g_user_page2_shadow)) { fixup_shadow_txt2(); } if(xor & 0x40) { fixup_shadow_iolc(); } if(xor) { fixup_brks(); } } void fixup_shadow_all_banks() { byte *mem0rd; int shadow; int num_banks; int j, k; /* Assume Ninja Force Megademo */ /* only do banks 3 - num_banks by 2, shadowing into e1 */ shadow = 0; if((g_c036_val_speed & 0x10) && ((g_c035_shadow_reg & 0x08) == 0)) { shadow = BANK_SHADOW2; } num_banks = g_mem_size_total >> 16; for(k = 3; k < num_banks; k += 2) { mem0rd = &(g_memory_ptr[k*0x10000 + 0x2000]) + shadow; for(j = 0x20; j < 0xa0; j++) { SET_PAGE_INFO_WR(k*0x100 + j, mem0rd); mem0rd += 0x100; } } fixup_brks(); } void setup_pageinfo() { byte *mem0rd; word32 mem_size_pages; /* first, set all of memory to point to itself */ mem_size_pages = g_mem_size_total >> 8; mem0rd = &(g_memory_ptr[0]); fixup_any_bank_any_page(0, mem_size_pages, mem0rd, mem0rd); /* mark unused memory as BAD_MEM */ fixup_any_bank_any_page(mem_size_pages, 0xfc00-mem_size_pages, BANK_BAD_MEM, BANK_BAD_MEM); fixup_shadow_all_banks(); /* ROM */ mem0rd = &(g_rom_fc_ff_ptr[0]); fixup_any_bank_any_page(0xfc00, 0x400, mem0rd, mem0rd + (BANK_IO_TMP | BANK_IO2_TMP)); /* banks e0, e1 */ mem0rd = &(g_slow_memory_ptr[0]); fixup_any_bank_any_page(0xe000, 0x200, mem0rd, mem0rd); // First, did all 128KB fixup_any_bank_any_page(0xe004, 0x08, mem0rd + 0x0400, mem0rd + 0x0400 + BANK_SHADOW); fixup_any_bank_any_page(0xe020, 0x80, mem0rd + 0x2000, mem0rd + 0x2000 + BANK_SHADOW); mem0rd = &(g_slow_memory_ptr[0x10000]); fixup_any_bank_any_page(0xe104, 0x08, mem0rd + 0x0400, mem0rd + 0x0400 + BANK_SHADOW2); fixup_any_bank_any_page(0xe120, 0x80, mem0rd + 0x2000, mem0rd + 0x2000 + BANK_SHADOW2); fixup_intcx(); /* correct banks 0xe0,0xe1, 0xc000-0xcfff area */ fixup_lc(); /* correct 0xd000-0xdfff area */ fixup_bank0_2000_4000(); fixup_bank0_0400_0800(); fixup_altzp(); fixup_ramrd(); fixup_ramwrt(); fixup_shadow_txt1(); fixup_shadow_txt2(); fixup_shadow_hires1(); fixup_shadow_hires2(); fixup_shadow_shr(); fixup_shadow_iolc(); fixup_brks(); } void show_bankptrs_bank0rdwr() { show_bankptrs(0); show_bankptrs(1); show_bankptrs(0xe0); show_bankptrs(0xe1); printf("statereg: %02x\n", g_c068_statereg); } void show_bankptrs(int bnk) { int i; Pg_info rd, wr; byte *ptr_rd, *ptr_wr; printf("g_memory_ptr: %p, dummy_mem: %p, slow_mem_ptr: %p\n", g_memory_ptr, g_dummy_memory1_ptr, g_slow_memory_ptr); printf("g_rom_fc_ff_ptr: %p\n", g_rom_fc_ff_ptr); printf("Showing bank_info array for %02x\n", bnk); for(i = 0; i < 256; i++) { rd = GET_PAGE_INFO_RD(bnk*0x100 + i); wr = GET_PAGE_INFO_WR(bnk*0x100 + i); ptr_rd = (byte *)rd; ptr_wr = (byte *)wr; printf("%04x rd: ", bnk*256 + i); show_addr(ptr_rd); printf(" wr: "); show_addr(ptr_wr); printf("\n"); } } void show_addr(byte *ptr) { word32 mem_size; mem_size = g_mem_size_total; if(ptr >= g_memory_ptr && ptr < &g_memory_ptr[mem_size]) { printf("%p--memory[%06x]", ptr, (word32)(ptr - g_memory_ptr)); } else if(ptr >= g_rom_fc_ff_ptr && ptr < &g_rom_fc_ff_ptr[256*1024]) { printf("%p--rom_fc_ff[%06x]", ptr, (word32)(ptr - g_rom_fc_ff_ptr)); } else if(ptr >= g_slow_memory_ptr && ptr<&g_slow_memory_ptr[128*1024]){ printf("%p--slow_memory[%06x]", ptr, (word32)(ptr - g_slow_memory_ptr)); } else if(ptr >=g_dummy_memory1_ptr && ptr < &g_dummy_memory1_ptr[256]){ printf("%p--dummy_memory[%06x]", ptr, (word32)(ptr - g_dummy_memory1_ptr)); } else { printf("%p--unknown", ptr); } } word32 moremem_fix_vector_pull(word32 addr) { // Default vector for BRK will come from 0xfffffe. But if // I/O shadowing is off, or we're a //e, then get from bank0 if((g_c035_shadow_reg & 0x40) || (g_rom_version == 0)) { // I/O shadowing off, or Apple //e: use RAM loc addr = addr & 0xffff; } return addr; } word32 io_read(word32 loc, dword64 *cyc_ptr) { dword64 dfcyc; word32 val; word32 new_lcbank2, new_wrdefram, new_prewrite, new_rdrom, tmp; int i; dfcyc = *cyc_ptr; /* IO space */ switch((loc >> 8) & 0xf) { case 0: /* 0xc000 - 0xc0ff */ switch(loc & 0xff) { /* 0xc000 - 0xc00f */ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: return(adb_read_c000()); /* 0xc010 - 0xc01f */ case 0x10: /* c010 */ return(adb_access_c010()); case 0x11: /* c011 = RDLCBANK2 */ return IOR(LCBANK2); case 0x12: /* c012= RDLCRAM */ return IOR(!RDROM); case 0x13: /* c013=rdramd */ return IOR(RAMRD); case 0x14: /* c014=rdramwrt */ return IOR(RAMWRT); case 0x15: /* c015 = RDCXROM = INTCX */ return IOR(g_c068_statereg & 1); case 0x16: /* c016: Read ALTZP, 0 = Read Main ZP; 1 = Alt ZP */ return IOR(ALTZP); case 0x17: /* c017: rdc3rom */ return IOR(g_c02d_int_crom & (1 << 3)); case 0x18: /* c018: rd80c0l */ return IOR((g_cur_a2_stat & ALL_STAT_ST80)); case 0x19: /* c019: rdvblbar: MSB set if in VBL */ tmp = in_vblank(dfcyc); if(g_rom_version == 0) { // Apple //e tmp = tmp ^ 1; // 1=not in VBL on //e } return IOR(tmp); case 0x1a: /* c01a: rdtext */ return IOR(g_cur_a2_stat & ALL_STAT_TEXT); case 0x1b: /* c01b: rdmix */ return IOR(g_cur_a2_stat & ALL_STAT_MIX_T_GR); case 0x1c: /* c01c: rdpage2 */ return IOR(g_cur_a2_stat & ALL_STAT_PAGE2); case 0x1d: /* c01d: rdhires */ return IOR(g_cur_a2_stat & ALL_STAT_HIRES); case 0x1e: /* c01e: altcharset on? */ return IOR(g_cur_a2_stat & ALL_STAT_ALTCHARSET); case 0x1f: /* c01f: rd80vid */ return IOR(g_cur_a2_stat & ALL_STAT_VID80); /* 0xc020 - 0xc02f */ case 0x20: /* 0xc020 */ /* Click cassette port */ return float_bus(dfcyc); case 0x21: /* 0xc021 */ /* Not documented, but let's return COLOR_C021 */ return IOR(g_cur_a2_stat & ALL_STAT_COLOR_C021); case 0x22: /* 0xc022 */ return (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; case 0x23: /* 0xc023 */ return g_c023_val; case 0x24: /* 0xc024 */ return mouse_read_c024(dfcyc); case 0x25: /* 0xc025 */ return adb_read_c025(); case 0x26: /* 0xc026 */ return adb_read_c026(); case 0x27: /* 0xc027 */ return adb_read_c027(); case 0x28: /* 0xc028 */ return 0; case 0x29: /* 0xc029 */ return((g_cur_a2_stat & 0xa0) | g_c029_val_some); case 0x2a: /* 0xc02a */ #if 0 printf("Reading c02a...returning 0\n"); #endif return 0; case 0x2b: /* 0xc02b */ return g_c02b_val; case 0x2c: /* 0xc02c */ /* printf("reading c02c, returning 0\n"); */ if(g_c06d_val == 0x40) { // Test mode $40 return read_video_data(dfcyc); } return 0; case 0x2d: /* 0xc02d */ return g_c02d_int_crom; case 0x2e: /* 0xc02e */ case 0x2f: /* 0xc02f */ return read_vid_counters(loc, dfcyc); /* 0xc030 - 0xc03f */ case 0x30: /* 0xc030 */ /* click speaker */ return sound_read_c030(dfcyc); case 0x31: /* 0xc031 */ /* 3.5" control */ return g_iwm.state & 0xc0; case 0x32: /* 0xc032 */ /* scan int */ return 0; case 0x33: /* 0xc033 = CLOCKDATA*/ return g_c033_data; case 0x34: /* 0xc034 = CLOCKCTL */ return g_c034_val; case 0x35: /* 0xc035 */ return g_c035_shadow_reg; case 0x36: /* 0xc036 = CYAREG */ return g_c036_val_speed; case 0x37: /* 0xc037 */ return 0; case 0x38: /* 0xc038 */ return scc_read_reg(dfcyc, 1); case 0x39: /* 0xc039 */ return scc_read_reg(dfcyc, 0); case 0x3a: /* 0xc03a */ return scc_read_data(dfcyc, 1); case 0x3b: /* 0xc03b */ return scc_read_data(dfcyc, 0); case 0x3c: /* 0xc03c */ /* doc control */ return doc_read_c03c(); case 0x3d: /* 0xc03d */ return doc_read_c03d(dfcyc); case 0x3e: /* 0xc03e */ return (g_c03ef_doc_ptr & 0xff); case 0x3f: /* 0xc03f */ return (g_c03ef_doc_ptr >> 8); /* 0xc040 - 0xc04f */ case 0x40: /* 0xc040 */ /* cassette */ return 0; case 0x41: /* 0xc041 */ return g_c041_val; case 0x45: /* 0xc045 */ //halt_printf("Mega II mouse read: c045\n"); return 0; case 0x46: /* 0xc046 */ tmp = g_c046_val; g_c046_val = (tmp & 0xbf) + ((tmp & 0x80) >> 1); return tmp; case 0x47: /* 0xc047 */ remove_irq(IRQ_PENDING_C046_25SEC | IRQ_PENDING_C046_VBL); g_c046_val &= 0xe7; /* clear vbl_int, 1/4sec int*/ return 0; case 0x42: /* 0xc042 */ case 0x43: /* 0xc043 */ return 0; case 0x4f: /* 0xc04f */ /* for information on c04f, see: */ /* www.sheppyware.net/tech/hardware/softswitches.html */ /* write to $c04f to start. Then read $c04f to get */ /* emulator ($16=sweet16, $fe=bernie II). */ /* Then read again to get version: $21 == 2.1 */ switch(g_em_emubyte_cnt) { case 1: g_em_emubyte_cnt = 2; return 'K'; case 2: g_em_emubyte_cnt = 0; tmp = g_kegs_version_str[0] - '0'; i = g_kegs_version_str[2] - '0'; return ((tmp & 0xf) << 4) + (i & 0xf); default: g_em_emubyte_cnt = 0; return 0; } case 0x44: /* 0xc044 */ case 0x48: /* 0xc048 */ case 0x49: /* 0xc049 */ case 0x4a: /* 0xc04a */ case 0x4b: /* 0xc04b */ case 0x4c: /* 0xc04c */ case 0x4d: /* 0xc04d */ case 0x4e: /* 0xc04e */ UNIMPL_READ; /* 0xc050 - 0xc05f */ case 0x50: /* 0xc050 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_TEXT) { g_cur_a2_stat &= (~ALL_STAT_TEXT); change_display_mode(dfcyc); } return val; case 0x51: /* 0xc051 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { g_cur_a2_stat |= (ALL_STAT_TEXT); change_display_mode(dfcyc); } return val; case 0x52: /* 0xc052 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return val; case 0x53: /* 0xc053 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return val; case 0x54: /* 0xc054 */ val = float_bus(dfcyc); set_statereg(dfcyc, g_c068_statereg & (~0x40)); return val; return float_bus(dfcyc); case 0x55: /* 0xc055 */ val = float_bus(dfcyc); set_statereg(dfcyc, g_c068_statereg | 0x40); return val; case 0x56: /* 0xc056 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_HIRES) { g_cur_a2_stat &= (~ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return val; case 0x57: /* 0xc057 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { g_cur_a2_stat |= (ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return val; case 0x58: /* 0xc058 */ if(g_zipgs_unlock < 4) { g_c05x_annuncs &= (~1); } return 0; case 0x59: /* 0xc059 */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c059; } else { g_c05x_annuncs |= 1; } return 0; case 0x5a: /* 0xc05a */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c05a; } else { g_c05x_annuncs &= (~2); } return 0; case 0x5b: /* 0xc05b */ if(g_zipgs_unlock >= 4) { // Bit 7 is 1msclk, clock with 1ms period. tmp = (dfcyc >> 25) & 1; return (tmp << 7) + (g_zipgs_reg_c05b & 0x7f); } else { g_c05x_annuncs |= 2; } return 0; case 0x5c: /* 0xc05c */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c05c; } else { g_c05x_annuncs &= (~4); } return 0; case 0x5d: /* 0xc05d */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05d!\n"); } else { g_c05x_annuncs |= 4; } return 0; case 0x5e: /* 0xc05e */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05e!\n"); } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return 0; case 0x5f: /* 0xc05f */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05f!\n"); } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { g_cur_a2_stat |= (ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return 0; /* 0xc060 - 0xc06f */ case 0x60: /* 0xc060 */ return IOR(g_paddle_buttons & 8); case 0x61: /* 0xc061 */ return IOR(adb_is_cmd_key_down() || g_paddle_buttons & 1); case 0x62: /* 0xc062 */ return IOR(adb_is_option_key_down() || g_paddle_buttons & 2); case 0x63: /* 0xc063 */ return IOR(g_paddle_buttons & 4); case 0x64: /* 0xc064 */ return read_paddles(dfcyc, 0); case 0x65: /* 0xc065 */ return read_paddles(dfcyc, 1); case 0x66: /* 0xc066 */ return read_paddles(dfcyc, 2); case 0x67: /* 0xc067 */ return read_paddles(dfcyc, 3); case 0x68: /* 0xc068 = STATEREG */ return g_c068_statereg & 0xff; case 0x69: /* 0xc069 */ /* Reserved reg, return 0 */ return 0; case 0x6a: /* 0xc06a */ case 0x6b: /* 0xc06b */ UNIMPL_READ; case 0x6c: /* 0xc06c */ case 0x6d: /* 0xc06d */ case 0x6e: /* 0xc06e */ case 0x6f: /* 0xc06f */ return g_c06c_latched_cyc >> (8 * (loc & 3)) & 0xff; /* 0xc070 - 0xc07f */ case 0x70: /* c070 */ paddle_trigger(dfcyc); return float_bus(dfcyc); case 0x71: /* 0xc071 */ case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: return g_rom_fc_ff_ptr[3*65536 + 0xc000 + (loc & 0xff)]; /* 0xc080 - 0xc08f */ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; // new_rdrom is set if loc[0] ^ loc[1] is true // 8=RDROM, 0=read from LC RAM new_prewrite = (loc & 1) << 8; // Odd read set PREWRITE new_wrdefram = g_c068_statereg & 0x200; if((loc & 1) == 0) { new_wrdefram = 0; // Any even access clrs } else { new_wrdefram |= (g_c068_statereg << 1) & 0x200; // Odd read also makes Ram wr if PREWR was set } set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | new_lcbank2 | new_rdrom | new_prewrite | new_wrdefram); // access 0-7: lcbank1, 8-f: lcbank0 // a0!=a1: set rdrom (c081,c082,c085,c086, etc.) // a0=1 && rd set prewrite. a0=0, or wr: clear prewrite // wrdefram is clr if a0=0, set if prewrite and // old_prewrite are set, otherwise stays same. // From Apple language card schematics: // wr_def = (wr_def_ff || (prewr && prewr_ff)) && a0; // prewr = read && a0 return float_bus(dfcyc); /* 0xc090 - 0xc09f */ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: /* UNIMPL_READ; */ return 0; /* 0xc0a0 - 0xc0af */ case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: return 0; /* UNIMPL_READ; */ /* 0xc0b0 - 0xc0bf */ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: return voc_devsel_read(loc, dfcyc); /* 0xc0c0 - 0xc0cf */ case 0xc0: case 0xc1: case 0xc2: case 0xc3: // Slot 4 has a Slinky RAM card return slinky_devsel_read(dfcyc, loc); case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: return 0; /* 0xc0d0 - 0xc0df */ case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7: case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: return 0; /* 0xc0e0 - 0xc0ef */ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: return read_iwm(loc, dfcyc); /* 0xc0f0 - 0xc0ff */ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: return 0; default: printf("loc: %04x bad\n", loc); UNIMPL_READ; } case 1: case 2: case 5: case 6: case 7: /* c100 - c7ff, (except c3xx, c4xx) */ return float_bus(dfcyc); case 3: // c300 return c3xx_read(dfcyc, loc); case 4: return mockingboard_read(loc, dfcyc); case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: return float_bus(dfcyc); // UNIMPL_READ; case 0xf: // $CFxx if(g_c068_statereg & 0x401) { // INTC8ROM or INTCX val = g_rom_fc_ff_ptr[0x3cf00 + (loc & 0xff)]; } else { val = float_bus(dfcyc); } if((loc & 0xfff) == 0xfff) { if(g_c068_statereg & 0x400) { set_statereg(dfcyc, g_c068_statereg & (~0x400)); } } return val; // UNIMPL_READ; } halt_printf("io_read: hit end, loc: %06x\n", loc); return 0xff; } void io_write(word32 loc, word32 val, dword64 *cyc_ptr) { dword64 dfcyc; word32 new_lcbank2, new_wrdefram, new_rdrom, new_prewrite, tmp; word32 new_tmp; int fixup; dfcyc = *cyc_ptr; val = val & 0xff; switch((loc >> 8) & 0xf) { case 0: /* 0xc000 - 0xc0ff */ switch(loc & 0xff) { /* 0xc000 - 0xc00f */ case 0x00: /* 0xc000: CLR80COL */ if(g_cur_a2_stat & ALL_STAT_ST80) { g_cur_a2_stat &= (~ALL_STAT_ST80); fixup_st80col(dfcyc); } return; case 0x01: /* 0xc001: SET80COL, enable 80-column store */ if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { g_cur_a2_stat |= (ALL_STAT_ST80); fixup_st80col(dfcyc); } return; case 0x02: /* 0xc002: Set RDMAINRAM */ set_statereg(dfcyc, g_c068_statereg & ~0x20); return; case 0x03: /* 0xc003: Set RDCARDRAM (alt) */ set_statereg(dfcyc, g_c068_statereg | 0x20); return; case 0x04: /* 0xc004: Set WRMAINRAM */ set_statereg(dfcyc, g_c068_statereg & ~0x10); return; case 0x05: /* 0xc005: Set WRCARDRAM (alt) */ set_statereg(dfcyc, g_c068_statereg | 0x10); return; case 0x06: /* 0xc006 = SETSLOTCXROM, use slot rom c800 */ set_statereg(dfcyc, g_c068_statereg & ~0x01); return; case 0x07: /* 0xc007 = SETINTXROM, use int rom c800 */ set_statereg(dfcyc, g_c068_statereg | 0x01); return; case 0x08: /* 0xc008: SETSTDZP (main ZP/LC) */ set_statereg(dfcyc, g_c068_statereg & ~0x80); return; case 0x09: /* 0xc009: SETALTZP (alt ZP/LC) */ set_statereg(dfcyc, g_c068_statereg | 0x80); return; case 0x0a: /* 0xc00a = SETINTC3ROM, use internal ROM slot 3*/ tmp = 1 << 3; if((g_c02d_int_crom & tmp) != 0) { g_c02d_int_crom &= ~tmp; fixup_intcx(); } return; case 0x0b: /* 0xc00b = SETSLOTC3ROM, use slot rom slot 3 */ tmp = 1 << 3; if((g_c02d_int_crom & tmp) == 0) { g_c02d_int_crom |= tmp; fixup_intcx(); } return; case 0x0c: /* 0xc00c = CLR80VID, disable 80 col hardware */ if(g_cur_a2_stat & ALL_STAT_VID80) { g_cur_a2_stat &= (~ALL_STAT_VID80); change_display_mode(dfcyc); } return; case 0x0d: /* 0xc00d: SET80VID, enable 80 col hardware */ if((g_cur_a2_stat & ALL_STAT_VID80) == 0) { g_cur_a2_stat |= (ALL_STAT_VID80); change_display_mode(dfcyc); } return; case 0x0e: /* 0xc00e: CLRALTCHAR */ if(g_cur_a2_stat & ALL_STAT_ALTCHARSET) { g_cur_a2_stat &= (~ALL_STAT_ALTCHARSET); change_display_mode(dfcyc); } return; case 0x0f: /* 0xc00f: SETALTCHAR */ if((g_cur_a2_stat & ALL_STAT_ALTCHARSET) == 0) { g_cur_a2_stat |= (ALL_STAT_ALTCHARSET); change_display_mode(dfcyc); } return; /* 0xc010 - 0xc01f */ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: adb_access_c010(); return; /* 0xc020 - 0xc02f */ case 0x20: /* 0xc020 */ /* WRITE CASSETTE?? */ return; case 0x21: /* 0xc021 */ new_tmp = ((val >> 7) & 1) << (31 - BIT_ALL_STAT_COLOR_C021); if((g_cur_a2_stat & ALL_STAT_COLOR_C021) != new_tmp) { g_cur_a2_stat ^= new_tmp; change_display_mode(dfcyc); } return; case 0x22: /* 0xc022 */ /* change text color */ tmp = (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; if(val != tmp) { /* change text/bg color! */ g_cur_a2_stat &= ~(ALL_STAT_TEXT_COLOR | ALL_STAT_BG_COLOR); g_cur_a2_stat += (val << BIT_ALL_STAT_BG_COLOR); change_display_mode(dfcyc); } return; case 0x23: /* 0xc023 */ if((val & 0x19) != 0) { halt_printf("c023 write of %02x!!!\n", val); } tmp = (g_c023_val & 0x70) | (val & 0x0f); if((tmp & 0x22) == 0x22) { add_irq(IRQ_PENDING_C023_SCAN); } if(!(tmp & 2)) { remove_irq(IRQ_PENDING_C023_SCAN); } if((tmp & 0x44) == 0x44) { add_irq(IRQ_PENDING_C023_1SEC); } if(!(tmp & 0x4)) { remove_irq(IRQ_PENDING_C023_1SEC); } if(g_irq_pending & (IRQ_PENDING_C023_SCAN | IRQ_PENDING_C023_1SEC)) { tmp |= 0x80; } g_c023_val = tmp; return; case 0x24: /* 0xc024 */ /* Write to mouse reg: Throw it away */ return; case 0x26: /* 0xc026 */ adb_write_c026(val); return; case 0x27: /* 0xc027 */ adb_write_c027(val); return; case 0x29: /* 0xc029 */ g_c029_val_some = val & 0x41; if((val & 1) == 0) { halt_printf("c029: %02x\n", val); } new_tmp = val & 0xa0; if(new_tmp != (g_cur_a2_stat & 0xa0)) { g_cur_a2_stat = (g_cur_a2_stat & (~0xa0)) + new_tmp; change_display_mode(dfcyc); } return; case 0x2a: /* 0xc02a */ #if 0 printf("Writing c02a with %02x\n", val); #endif return; case 0x2b: /* 0xc02b */ g_c02b_val = val; if((val != 0x08) && (val != 0x00)) { printf("Writing c02b with %02x\n", val); } return; case 0x2d: /* 0xc02d = Slot Reg. Bit set means use slot rom */ if((val & 0x9) != 0) { halt_printf("Illegal c02d write: %02x!\n", val); } fixup = (val != g_c02d_int_crom); g_c02d_int_crom = val; if(fixup) { vid_printf("Write c02d of %02x\n", val); fixup_intcx(); } return; case 0x28: /* 0xc028 */ case 0x2c: /* 0xc02c */ UNIMPL_WRITE; case 0x25: /* 0xc025 */ /* Space Shark writes to c025--ignore */ case 0x2e: /* 0xc02e */ case 0x2f: /* 0xc02f */ /* Modulae writes to this--just ignore them */ return; break; /* 0xc030 - 0xc03f */ case 0x30: /* 0xc030 */ #if 0 printf("Write speaker?\n"); #endif sound_write_c030(dfcyc); return; case 0x31: /* 0xc031 */ tmp = val ^ g_iwm.state; iwm_flush_cur_disk(); // In case APPLE35SEL changes g_iwm.state = (g_iwm.state & (~0xc0)) | (val & 0xc0); if(tmp & IWM_STATE_C031_APPLE35SEL) { /* apple35_sel changed, maybe speed change */ engine_recalc_events(); } return; case 0x32: /* 0xc032 */ tmp = g_c023_val & 0x7f; if(((val & 0x40) == 0) && (tmp & 0x40)) { /* clear 1 sec int */ remove_irq(IRQ_PENDING_C023_1SEC); tmp &= 0xbf; g_c023_val = tmp; } if(((val & 0x20) == 0) && (tmp & 0x20)) { /* clear scan line int */ remove_irq(IRQ_PENDING_C023_SCAN); g_c023_val = tmp & 0xdf; check_for_new_scan_int(dfcyc); } if(g_irq_pending & (IRQ_PENDING_C023_1SEC | IRQ_PENDING_C023_SCAN)) { g_c023_val |= 0x80; } if((val & 0x9f) != 0x9f) { irq_printf("c032: wrote %02x!\n", val); } return; case 0x33: /* 0xc033 = CLOCKDATA*/ g_c033_data = val; return; case 0x34: /* 0xc034 = CLOCKCTL */ tmp = val ^ g_c034_val; clock_write_c034(val); if(tmp & 0xf) { change_border_color(dfcyc, val & 0xf); } return; case 0x35: /* 0xc035 */ update_shadow_reg(dfcyc, val); return; case 0x36: /* 0xc036 = CYAREG */ tmp = val ^ g_c036_val_speed; g_c036_val_speed = (val & ~0x20); /* clr bit 5 */ if(tmp & 0x80) { /* to recalculate times since speed changing */ engine_recalc_events(); } if(tmp & 0xf) { /* slot_motor_detect changed */ engine_recalc_events(); } if((val & 0x60) != 0) { /* for ROM 03, 0x40 is the power-on status */ /* and can be read/write */ if(((val & 0x60) != 0x40) || (g_rom_version < 3)) { g_c036_val_speed &= (~0x60); halt_printf("c036: %2x\n", val); } } if(tmp & 0x10) { /* shadow in all banks! */ if(g_num_shadow_all_banks++ == 0) { printf("Shadowing all banks...This " "must be the NFC Megademo\n"); } fixup_shadow_all_banks(); } return; case 0x37: /* 0xc037 */ /* just ignore, probably someone writing c036 m=0 */ return; case 0x38: /* 0xc038 */ scc_write_reg(dfcyc, 1, val); return; case 0x39: /* 0xc039 */ scc_write_reg(dfcyc, 0, val); return; case 0x3a: /* 0xc03a */ scc_write_data(dfcyc, 1, val); return; case 0x3b: /* 0xc03b */ scc_write_data(dfcyc, 0, val); return; case 0x3c: /* 0xc03c */ /* doc ctl */ doc_write_c03c(dfcyc, val); return; case 0x3d: /* 0xc03d */ /* doc data reg */ doc_write_c03d(dfcyc, val); return; case 0x3e: /* 0xc03e */ g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff00) + val; return; case 0x3f: /* 0xc03f */ g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff) + (val << 8); return; /* 0xc040 - 0xc04f */ case 0x41: /* c041 */ g_c041_val = val & 0x1f; if((val & 0xe7) != 0) { halt_printf("write c041: %02x\n", val); } if(!(val & C041_EN_VBL_INTS)) { /* no more vbl interrupt */ remove_irq(IRQ_PENDING_C046_VBL); } if(!(val & C041_EN_25SEC_INTS)) { remove_irq(IRQ_PENDING_C046_25SEC); } return; case 0x46: /* c046 */ /* ignore writes to c046 */ return; case 0x47: /* c047 */ remove_irq(IRQ_PENDING_C046_VBL | IRQ_PENDING_C046_25SEC); g_c046_val &= 0xe7; /* clear vblint, 1/4sec int*/ return; case 0x48: /* c048 */ /* diversitune writes this--ignore it */ return; case 0x42: /* c042 */ case 0x43: /* c043 */ return; case 0x4f: /* c04f */ g_em_emubyte_cnt = 1; return; case 0x45: /* c045 */ return; case 0x40: /* c040 */ case 0x44: /* c044 */ case 0x49: /* c049 */ case 0x4a: /* c04a */ case 0x4b: /* c04b */ case 0x4c: /* c04c */ case 0x4d: /* c04d */ case 0x4e: /* c04e */ UNIMPL_WRITE; /* 0xc050 - 0xc05f */ case 0x50: /* 0xc050 */ if(g_cur_a2_stat & ALL_STAT_TEXT) { g_cur_a2_stat &= (~ALL_STAT_TEXT); change_display_mode(dfcyc); } return; case 0x51: /* 0xc051 */ if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { g_cur_a2_stat |= (ALL_STAT_TEXT); change_display_mode(dfcyc); } return; case 0x52: /* 0xc052 */ if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return; case 0x53: /* 0xc053 */ if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return; case 0x54: /* 0xc054 */ set_statereg(dfcyc, g_c068_statereg & (~0x40)); return; case 0x55: /* 0xc055 */ set_statereg(dfcyc, g_c068_statereg | 0x40); return; case 0x56: /* 0xc056 */ if(g_cur_a2_stat & ALL_STAT_HIRES) { g_cur_a2_stat &= (~ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return; case 0x57: /* 0xc057 */ if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { g_cur_a2_stat |= (ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return; case 0x58: /* 0xc058 */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c059 &= 0x4; /* last reset cold */ } else { g_c05x_annuncs &= (~1); } return; case 0x59: /* 0xc059 */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c059 = (val & 0xf8) | (g_zipgs_reg_c059 & 0x7); } else { g_c05x_annuncs |= 1; } return; case 0x5a: /* 0xc05a */ g_c05x_annuncs &= (~2); if((val & 0xf0) == 0x50) { g_zipgs_unlock++; } else if((val & 0xf0) == 0xa0) { g_zipgs_unlock = 0; } else if(g_zipgs_unlock >= 4) { if((g_zipgs_reg_c05b & 0x10) == 0) { /* to recalculate times */ engine_recalc_events(); } g_zipgs_reg_c05b |= 0x10; // disable } return; case 0x5b: /* 0xc05b */ if(g_zipgs_unlock >= 4) { if((g_zipgs_reg_c05b & 0x10) != 0) { /* to recalculate times */ engine_recalc_events(); } g_zipgs_reg_c05b &= (~0x10); // enable } else { g_c05x_annuncs |= 2; } return; case 0x5c: /* 0xc05c */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c05c = val; } else { g_c05x_annuncs &= (~4); } return; case 0x5d: /* 0xc05d */ if(g_zipgs_unlock >= 4) { if(((g_zipgs_reg_c05a ^ val) >= 0x10) && ((g_zipgs_reg_c05b & 0x10) == 0)) { engine_recalc_events(); } g_zipgs_reg_c05a = val | 0xf; } else { g_c05x_annuncs |= 4; } return; case 0x5e: /* 0xc05e: SETAN3, clear AN3, double-hires on */ if(g_zipgs_unlock >= 4) { /* Zippy writes 0x80 and 0x00 here... */ } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return; case 0x5f: /* 0xc05f: CLRAN3, set AN3, double-hires off */ if(g_zipgs_unlock >= 4) { halt_printf("Wrote ZipGS $c05f: %02x\n", val); } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { g_cur_a2_stat |= (ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return; /* 0xc060 - 0xc06f */ case 0x60: /* 0xc060 */ case 0x61: /* 0xc061 */ case 0x62: /* 0xc062 */ case 0x63: /* 0xc063 */ case 0x64: /* 0xc064 */ case 0x65: /* 0xc065 */ case 0x66: /* 0xc066 */ case 0x67: /* 0xc067 */ /* all the above do nothing--return */ return; case 0x68: /* 0xc068 = STATEREG */ set_statereg(dfcyc, (g_c068_statereg & ~0xff) | val); return; case 0x69: /* 0xc069 */ /* just ignore, someone writing c068 with m=0 */ return; case 0x6a: /* 0xc06a */ case 0x6b: /* 0xc06b */ UNIMPL_WRITE; case 0x6c: /* 0xc06c */ g_c06c_latched_cyc = (word32)(dfcyc >> 16); return; case 0x6d: /* 0xc06d */ // Affect what reads to $C02C can see, only $40 now if((g_c06f_val & 0x100) == 0) { // Locked val = 0; } g_c06d_val = val; // Test mode return; case 0x6e: /* 0xc06e */ g_c06f_val = 0; return; case 0x6f: /* 0xc06f */ if((g_c06f_val == 0xda) && (val == 0x61)) { val |= 0x100; // Unlock } g_c06f_val = val; return; /* 0xc070 - 0xc07f */ case 0x70: /* 0xc070 = Trigger paddles */ paddle_trigger(dfcyc); return; case 0x73: /* 0xc073 = multibank ram card bank addr? */ return; case 0x71: /* 0xc071 = another multibank ram card enable? */ case 0x7e: /* 0xc07e */ case 0x7f: /* 0xc07f */ return; case 0x72: /* 0xc072 */ case 0x74: /* 0xc074 */ case 0x75: /* 0xc075 */ case 0x76: /* 0xc076 */ case 0x77: /* 0xc077 */ case 0x78: /* 0xc078 */ case 0x79: /* 0xc079 */ case 0x7a: /* 0xc07a */ case 0x7b: /* 0xc07b */ case 0x7c: /* 0xc07c */ case 0x7d: /* 0xc07d */ return; /* 0xc080 - 0xc08f */ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; // new_rdrom is set if loc0 ^ loc1 is set // 8=RDROM, 0=read from LC RAM new_prewrite = 0; // Writes clear PREWRITE new_wrdefram = g_c068_statereg & 0x200; if((loc & 1) == 0) { new_wrdefram = 0; // Any even access clrs } set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | new_lcbank2 | new_rdrom | new_prewrite | new_wrdefram); return; /* 0xc090 - 0xc09f */ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: UNIMPL_WRITE; /* 0xc0a0 - 0xc0af */ case 0xa0: case 0xa1: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: UNIMPL_WRITE; case 0xa2: /* Burger Times writes here on error */ case 0xa8: /* Kurzweil SMP writes to 0xc0a8, ignore it */ return; /* 0xc0b0 - 0xc0bf */ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: voc_devsel_write(loc, val, dfcyc); return; /* 0xc0c0 - 0xc0cf */ case 0xc0: case 0xc1: case 0xc2: case 0xc3: // Slot 4 has a Slinky RAM card slinky_devsel_write(dfcyc, loc, val); return; case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: UNIMPL_WRITE; /* 0xc0d0 - 0xc0df */ case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7: case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: UNIMPL_WRITE; /* 0xc0e0 - 0xc0ef */ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: write_iwm(loc, val, dfcyc); return; /* 0xc0f0 - 0xc0ff */ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: UNIMPL_WRITE; default: printf("Write loc: %x\n",loc); exit(-300); } break; case 1: case 2: case 5: case 6: case 7: /* c1000 - c7ff (but not c3xx,c4xx) */ if((loc & 0xff) == 0) { // Frogger writes $ff to $Cx00 return; } UNIMPL_WRITE; case 3: if(((g_c02d_int_crom & 8) == 0) && ((g_c068_statereg & 0x400) == 0)) { // SLOTC3ROM clear, INTC8rom clear: Set INTC8ROM set_statereg(dfcyc, g_c068_statereg | 0x400); } return; case 4: if((g_c02d_int_crom & 0x10) && !(g_c068_statereg & 1)) { // Slot 4 is set to Your Card and not INTCX mockingboard_write(loc, val, dfcyc); return; } return; case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: // UNIMPL_WRITE; return; case 0xf: if((loc & 0xfff) == 0xfff) { /* cfff */ if(g_c068_statereg & 0x400) { set_statereg(dfcyc, g_c068_statereg & (~0x400)); } return; } // UNIMPL_WRITE; // Wings of Fury writes to $cf00-$cfff when reading a 0x300 // sector where it wants to load 0x200 to 0xd000-0xd1ff return; } printf("Huh2? Write loc: %x\n", loc); exit(-290); } word32 slinky_devsel_read(dword64 dfcyc, word32 loc) { word32 val; loc = loc & 0xf; val = 0; if(loc <= 2) { val = (g_slinky_addr >> (8*loc)) & 0xff; } if(loc == 3) { val = g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)]; dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c3); g_slinky_addr++; } return val; } void slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val) { word32 mask; loc = loc & 0xf; dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c0 + loc); if(loc <= 2) { mask = 0xff << (8 * loc); val = val * 0x010101; g_slinky_addr = (g_slinky_addr & (~mask)) | (val & mask); } if(loc == 3) { g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)] = val; g_slinky_addr++; } } word32 c3xx_read(dword64 dfcyc, word32 loc) { // We may have been marked IO so that we could set INTC8ROM, // but we still need to return ROM if(((g_c02d_int_crom & 8) == 0) && ((g_c068_statereg & 0x400) == 0)) { // SLOTC3ROM is not set: Set INTC8ROM set_statereg(dfcyc, g_c068_statereg | 0x400); } if(((g_c02d_int_crom & 8) == 0) || (g_c068_statereg & 1) || (g_rom_version == 0)) { // Access ROM for slot 3 return g_rom_fc_ff_ptr[0x3c300 + (loc & 0xff)]; } return float_bus(dfcyc); } // IIgs vertical line counters // 0x7d - 0x7f: in vbl, top of screen // 0x80 - 0xdf: not in vbl, drawing screen // 0xe0 - 0xff: in vbl, bottom of screen // Note: lines are then 0-0x60 effectively, for 192 lines, from 0x80-0xdf // vertical blanking engages on line 192, even if in super hires mode // (Last 8 lines in SHR are drawn with vbl_active set word32 get_lines_since_vbl(dword64 dfcyc) { double dusecs_since_last_vbl, dlines_since_vbl, dcyc_line_start; word32 lines_since_vbl; int offset; dusecs_since_last_vbl = (double)((dfcyc >> 16) - (g_last_vbl_dfcyc >> 16)); dlines_since_vbl = dusecs_since_last_vbl * (1.0 / 65.0); lines_since_vbl = (int)dlines_since_vbl; dcyc_line_start = (double)lines_since_vbl * 65.0; offset = ((int)(dusecs_since_last_vbl - dcyc_line_start)) & 0xff; lines_since_vbl = (lines_since_vbl << 8) + offset; if(!g_halt_sim && !g_config_control_panel) { dbg_log_info(dfcyc, (word32)dusecs_since_last_vbl, lines_since_vbl, 0xc02e); } if(lines_since_vbl < 0x10541) { return lines_since_vbl; } else { // We've entered the next frame, but update_60hz() hasn't been // called yet. Produce the proper c02e/c02f counter values // for the first line of the display lines_since_vbl = lines_since_vbl - 0x10600; #if 0 printf("lines_since_vbl was:%08x, now:%08x\n", lines_since_vbl + 0x10600, lines_since_vbl); halt_printf("lines_since_vbl: %08x!\n", lines_since_vbl); printf("du_s_l_v: %f, dfcyc: %016llx, last_vbl_cycs: %016llx\n", dusecs_since_last_vbl, dfcyc, g_last_vbl_dfcyc); show_dtime_array(); show_all_events(); /* U_STACK_TRACE(); */ #endif } return lines_since_vbl; } int in_vblank(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); // Testing indicates $c019 is a cycle delayed from $C02F counter if(lines_since_vbl > 0xc000) { // Exclude line 192 first cycle! return 1; // 1=in VBL } if(lines_since_vbl == 0) { return 1; // Handle 1-cycle delay in reading c019 } return 0; } // horizontal video counter goes from 0x00,0x40 - 0x7f, then 0x80,0xc0-0xff // over 2*65 cycles. The last visible screen pos is 0x7f and 0xff // KEGS starts it's "line 0" at the start of the right border for line -1 // See get_lines_since_vbl comment for the format of the vertical counter int read_vid_counters(int loc, dword64 dfcyc) { word32 mask; int lines_since_vbl; loc = loc & 0xf; lines_since_vbl = get_lines_since_vbl(dfcyc); lines_since_vbl += 0x10000; if(lines_since_vbl >= 0x20000) { lines_since_vbl = lines_since_vbl - 0x20000 + 0xfa00; } if(lines_since_vbl > 0x1ffff) { halt_printf("lines_since_vbl: %04x, dfcyc: %016llx, last_vbl:" "%016llx\n", lines_since_vbl, dfcyc, g_last_vbl_dfcyc); } if(loc == 0xe) { // c02e: Vertical count return (lines_since_vbl >> 9) & 0xff; } mask = (lines_since_vbl >> 1) & 0x80; lines_since_vbl = (lines_since_vbl & 0xff); if(lines_since_vbl >= 0x01) { lines_since_vbl = (lines_since_vbl + 0x3f) & 0x7f; } return (mask | (lines_since_vbl & 0xff)); } ================================================ FILE: gsplus/src/op_routs.h ================================================ // $KmKId: op_routs.h,v 1.47 2023-11-05 16:21:51+00 kentd Exp $ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #define GET_DLOC_X_IND_WR() \ CYCLES_PLUS_1; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ arg = arg + xreg + direct; \ GET_MEMORY_DIRECT_PAGE16(arg & 0xffff, arg, 1); \ arg = (dbank << 16) + arg; #define GET_DLOC_X_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR() #define GET_DISP8_S_WR() \ CYCLES_PLUS_1; \ arg = (arg + stack) & 0xffff; \ INC_KPC_2; #define GET_DISP8_S_ADDR() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR() #define GET_DLOC_WR() \ arg = (arg + direct) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; #define GET_DLOC_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_WR() #define GET_DLOC_L_IND_WR() \ arg = (arg + direct) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY24(arg, arg, 1); #define GET_DLOC_L_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR() #define GET_DLOC_IND_WR() \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ arg = (dbank << 16) + arg; #define GET_DLOC_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_IND_WR(); #define GET_DLOC_INDEX_WR(index_reg) \ CYCLES_PLUS_1; \ arg = (arg & 0xff) + index_reg; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ if((psr & 0x100) && ((direct & 0xff) == 0)) { \ arg = (arg & 0xff); \ } \ arg = (arg + direct) & 0xffff; #define GET_DLOC_X_WR() \ GET_DLOC_INDEX_WR(xreg) #define GET_DLOC_Y_WR() \ GET_DLOC_INDEX_WR(yreg) #define GET_DLOC_X_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_INDEX_WR(xreg) #define GET_DLOC_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_INDEX_WR(yreg) #define GET_DISP8_S_IND_Y_WR() \ arg = (stack + arg) & 0xffff; \ GET_MEMORY16(arg,arg,1); \ CYCLES_PLUS_2; \ arg += (dbank << 16); \ INC_KPC_2; \ arg = (arg + yreg) & 0xffffff; #define GET_DISP8_S_IND_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR() #define GET_DLOC_L_IND_Y_WR() \ arg = (direct + arg) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY24(arg,arg,1); \ INC_KPC_2; \ arg = (arg + yreg) & 0xffffff; #define GET_DLOC_L_IND_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR() #define GET_ABS_ADDR() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ arg = arg + (dbank << 16); \ INC_KPC_3; #define GET_LONG_ADDR() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ INC_KPC_4; #define GET_LONG_X_ADDR_FOR_WR() \ GET_3BYTE_ARG; \ INC_KPC_4; \ arg = (arg + xreg) & 0xffffff; \ CYCLES_PLUS_2; ================================================ FILE: gsplus/src/paddles.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" extern int g_mouse_raw_x; /* from adb.c */ extern int g_mouse_raw_y; dword64 g_paddle_trig_dfcyc = 0; int g_swap_paddles = 0; int g_invert_paddles = 0; int g_joystick_scale_factor_x = 0x100; int g_joystick_scale_factor_y = 0x100; int g_joystick_trim_amount_x = 0; int g_joystick_trim_amount_y = 0; int g_joystick_type = 0; /* 0 = Keypad Joystick */ int g_joystick_native_type1 = -1; int g_joystick_native_type2 = -1; int g_joystick_native_type = -1; extern int g_paddle_buttons; int g_paddle_val[4] = { 0, 0, 0, 0 }; /* g_paddle_val[0]: Joystick X coord, [1]:Y coord */ dword64 g_paddle_dfcyc[4] = { 0, 0, 0, 0 }; /* g_paddle_dfcyc are the dfcyc the paddle goes to 0 */ void paddle_fixup_joystick_type() { /* If g_joystick_type points to an illegal value, change it */ if(g_joystick_type == 2) { g_joystick_native_type = g_joystick_native_type1; if(g_joystick_native_type1 < 0) { g_joystick_type = 0; } } if(g_joystick_type == 3) { g_joystick_native_type = g_joystick_native_type2; if(g_joystick_native_type2 < 0) { g_joystick_type = 0; } } } void paddle_trigger(dword64 dfcyc) { /* Called by read/write to $c070 */ g_paddle_trig_dfcyc = dfcyc; /* Determine what all the paddle values are right now */ paddle_fixup_joystick_type(); switch(g_joystick_type) { case 0: /* Keypad Joystick */ paddle_trigger_keypad(dfcyc); break; case 1: /* Mouse Joystick */ paddle_trigger_mouse(dfcyc); break; default: joystick_update(dfcyc); } } void paddle_trigger_mouse(dword64 dfcyc) { int val_x, val_y; int mouse_x, mouse_y; val_x = 0; mouse_x = g_mouse_raw_x; mouse_y = g_mouse_raw_y; /* mous_phys_x is 0->560, convert that to -32768 to + 32767 cyc */ /* So subtract 280 then mult by 117 */ val_x = (mouse_x - 280) * 117; /* mous_phys_y is 0->384, convert that to -32768 to + 32767 cyc */ /* so subtract 192 then mult by 180 to overscale it a bit */ val_y = (mouse_y - 192) * 180; g_paddle_val[0] = val_x; g_paddle_val[1] = val_y; g_paddle_val[2] = 32767; g_paddle_val[3] = 32767; g_paddle_buttons |= 0xc; paddle_update_trigger_dcycs(dfcyc); } void paddle_trigger_keypad(dword64 dfcyc) { int get_y, val_x, val_y; val_x = adb_get_keypad_xy(get_y=0); val_y = adb_get_keypad_xy(get_y=1); /* val_x and val_y are already scale -32768 to +32768 */ g_paddle_val[0] = val_x; g_paddle_val[1] = val_y; g_paddle_val[2] = 32767; g_paddle_val[3] = 32767; g_paddle_buttons |= 0xc; paddle_update_trigger_dcycs(dfcyc); } void paddle_update_trigger_dcycs(dword64 dfcyc) { dword64 trig_dfcyc; int val, paddle_num, scale, trim; int i; for(i = 0; i < 4; i++) { paddle_num = i; if(g_swap_paddles) { paddle_num = i ^ 1; } val = g_paddle_val[paddle_num]; if(g_invert_paddles) { val = -val; } /* convert -32768 to +32768 into 0->2816.0 cycles (the */ /* paddle delay const) */ /* First multiply by the scale factor to adjust range */ if(paddle_num & 1) { scale = g_joystick_scale_factor_y; trim = g_joystick_trim_amount_y; } else { scale = g_joystick_scale_factor_x; trim = g_joystick_trim_amount_x; } #if 0 if(i == 0) { printf("val was %04x(%d) * scale %03x = %d\n", val, val, scale, (val*scale)>>16); } #endif val = (val * scale) >> 16; /* Val is now from -128 to + 128 since scale is */ /* 256=1.0, 128 = 0.5 */ val = val + 128 + trim; if(val >= 255) { val = 280; /* increase range */ } trig_dfcyc = dfcyc + (dword64)((val * (2816/255.0)) * 65536); g_paddle_dfcyc[i] = trig_dfcyc; if(i < 2) { dbg_log_info(dfcyc, (scale << 16) | (val & 0xffff), (trim << 16) | i, 0x70); } } } int read_paddles(dword64 dfcyc, int paddle) { dword64 trig_dfcyc; trig_dfcyc = g_paddle_dfcyc[paddle & 3]; if(dfcyc < trig_dfcyc) { return 0x80; } else { return 0x00; } } void paddle_update_buttons() { paddle_fixup_joystick_type(); joystick_update_buttons(); } ================================================ FILE: gsplus/src/protos.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2019 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #ifdef INCLUDE_RCSID_C #endif #include "protos_base.h" ================================================ FILE: gsplus/src/protos_base.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #ifdef INCLUDE_RCSID_C #endif #ifdef __GNUC__ void halt_printf(const char *fmt, ...) __attribute__ (( __format__(printf, 1, 2))); void cfg_err_printf(const char *pre_str, const char *fmt, ...) __attribute__ (( __format__(printf, 2, 3))); void dynapro_error(Disk *dsk, const char *fmt, ...) __attribute__ (( __format__(printf, 2, 3))); #endif /* xdriver.c and macdriver.c and windriver.c */ int win_nonblock_read_stdin(int fd, char *bufptr, int len); /* special scc_unixdriver.c prototypes */ void scc_serial_unix_open(int port); void scc_serial_unix_close(int port); void scc_serial_unix_change_params(int port); void scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_serial_unix_empty_writebuf(int port); /* special scc_windriver.c prototypes */ void scc_serial_win_open(int port); void scc_serial_win_close(int port); void scc_serial_win_change_params(int port); void scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_serial_win_empty_writebuf(int port); /* special joystick_driver.c prototypes */ void joystick_init(void); void joystick_update(dword64 dfcyc); void joystick_update_buttons(void); /* END_HDR */ /* adb.c */ int adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr); int adb_get_copy_requested(void); void adb_nonmain_check(void); void adb_init(void); void adb_reset(void); void adb_log(word32 addr, int val); void show_adb_log(void); void adb_error(void); void adb_add_kbd_srq(void); void adb_clear_kbd_srq(void); void adb_add_data_int(void); void adb_add_mouse_int(void); void adb_clear_data_int(void); void adb_clear_mouse_int(void); void adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2); void adb_send_1byte(word32 val); void adb_response_packet(int num_bytes, word32 val); void adb_kbd_reg0_data(int a2code, int is_up); void adb_kbd_talk_reg0(void); void adb_set_config(word32 val0, word32 val1, word32 val2); void adb_set_new_mode(word32 val); int adb_read_c026(void); void adb_write_c026(int val); void do_adb_cmd(void); int adb_read_c027(void); void adb_write_c027(int val); int read_adb_ram(word32 addr); void write_adb_ram(word32 addr, int val); int adb_get_keypad_xy(int get_y); int adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, int buttons_valid); int mouse_read_c024(dword64 dfcyc); void mouse_compress_fifo(dword64 dfcyc); void adb_paste_update_state(void); int adb_paste_add_buf(word32 key); void adb_key_event(int a2code, int is_up); word32 adb_read_c000(void); word32 adb_access_c010(void); word32 adb_read_c025(void); int adb_is_cmd_key_down(void); int adb_is_option_key_down(void); void adb_increment_speed(void); void adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask); int adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr); void adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, int is_up); void adb_maybe_virtual_key_update(int a2code, int is_up); void adb_virtual_key_update(int a2code, int is_up); void adb_kbd_repeat_off(void); void adb_mainwin_focus(int has_focus); /* engine_c.c */ word32 get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); word32 get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); word32 get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); void set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); void set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, dword64 dplus_1, dword64 dplus_x_m1, int in_bank); void set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); word32 get_memory_c(word32 addr); word32 get_memory16_c(word32 addr); word32 get_memory24_c(word32 addr); void set_memory_c(word32 addr, word32 val, int do_log); void set_memory16_c(word32 addr, word32 val, int do_log); void set_memory24_c(word32 addr, word32 val); word32 do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub); word32 do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub); void fixed_memory_ptrs_init(void); word32 get_itimer(void); void engine_recalc_events(void); void set_halt_act(int val); void clr_halt_act(void); word32 get_remaining_operands(word32 addr, word32 opcode, word32 psr, dword64 *dcyc_ptr, Fplus *fplus_ptr); int enter_engine(Engine_reg *engine_ptr); /* clock.c */ double get_dtime(void); int micro_sleep(double dtime); void clk_bram_zero(void); void clk_bram_set(int bram_num, int offset, int val); void clk_setup_bram_version(void); void clk_write_bram(FILE *fconf); void update_cur_time(void); void clock_update(void); void clock_update_if_needed(void); void clock_write_c034(word32 val); void do_clock_data(void); /* compile_time.c */ /* config.c */ int config_add_argv_override(const char *str1, const char *str2); void config_set_config_kegs_name(const char *str1); void config_init_menus(Cfg_menu *menuptr); void config_init(void); void cfg_find_config_kegs_file(void); int config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr); int config_expand_path(char *out_ptr, const char *in_ptr, int maxlen); char *cfg_exit(int get_status); void cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap); void cfg_err_printf(const char *pre_str, const char *fmt, ...); void cfg_toggle_config_panel(void); void cfg_set_config_panel(int panel); char *cfg_text_screen_dump(int get_status); char *cfg_text_screen_str(void); char *cfg_get_serial0_status(int get_status); char *cfg_get_serial1_status(int get_status); char *cfg_get_current_copy_selection(void); void config_vbl_update(int doit_3_persec); void cfg_file_update_rom(const char *str); void cfg_file_update_ptr(char **strptr, const char *str, int need_update); void cfg_int_update(int *iptr, int new_val); void cfg_load_charrom(void); void config_load_roms(void); void config_parse_config_kegs_file(void); void cfg_parse_one_line(char *buf, int line); void cfg_parse_bram(char *buf, int pos, int len); void cfg_parse_sxdx(char *buf, int pos, int len); void config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, int with_extras); char *config_write_config_kegs_file(int get_status); void insert_disk(int slot, int drive, const char *name, int ejected, const char *partition_name, int part_num, word32 dynamic_blocks); dword64 cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot); dword64 cfg_get_fd_size(int fd); dword64 cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); dword64 cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); int cfg_partition_maybe_add_dotdot(void); int cfg_partition_name_check(const byte *name_ptr, int name_len); int cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size); int cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, int part_num); int cfg_partition_make_list_from_name(const char *namestr); int cfg_partition_make_list(int fd); int cfg_maybe_insert_disk(int slot, int drive, const char *namestr); void cfg_insert_disk_dynapro(int slot, int drive, const char *name); int cfg_stat(char *path, struct stat *sb, int do_lstat); word32 cfg_get_le16(byte *bptr); word32 cfg_get_le32(byte *bptr); dword64 cfg_get_le64(byte *bptr); word32 cfg_get_be_word16(word16 *ptr); word32 cfg_get_be_word32(word32 *ptr); void cfg_set_le32(byte *bptr, word32 val); void config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr); void cfg_htab_vtab(int x, int y); void cfg_home(void); void cfg_cleol(void); void cfg_putchar(int c); void cfg_printf(const char *fmt, ...); void cfg_print_dnum(dword64 dnum, int max_len); int cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras); int cfg_get_disk_locked(int type_ext); void cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change); void cfg_get_base_path(char *pathptr, const char *inptr, int go_up); char *cfg_name_new_image(int get_status); void cfg_dup_existing_image(word32 slotdrive); void cfg_dup_image_selected(void); void cfg_validate_image(word32 slotdrive); void cfg_toggle_lock_disk(word32 slotdrive); int cfg_create_new_image_act(const char *str, int type, int size_blocks); void cfg_create_new_image(void); void cfg_file_init(void); void cfg_free_alldirents(Cfg_listhdr *listhdrptr); void cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); void cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); int cfg_dirent_sortfn(const void *obj1, const void *obj2); int cfg_str_match(const char *str1, const char *str2, int len); int cfg_str_match_maybecase(const char *str1, const char *str2, int len, int ignorecase); int cfgcasecmp(const char *str1, const char *str2); int cfg_strlcat(char *dstptr, const char *srcptr, int dstsize); char *cfg_strncpy(char *dstptr, const char *srcptr, int dstsize); const char *cfg_str_basename(const char *str); char *cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize); void cfg_file_readdir(const char *pathptr); char *cfg_shorten_filename(const char *in_ptr, int maxlen); void cfg_fix_topent(Cfg_listhdr *listhdrptr); void cfg_file_draw(void); void cfg_partition_select_all(void); void cfg_partition_selected(void); void cfg_file_selected(void); void cfg_file_handle_key(int key); void cfg_draw_menu(void); void cfg_newdisk_pick_menu(word32 slotdrive); int cfg_control_panel_update(void); void cfg_edit_mode_key(int key); int cfg_control_panel_update1(void); /* debugger.c */ void debugger_init(void); void check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type); void debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, int pos); int debugger_run_16ms(void); void dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type); void debugger_update_list_kpc(void); void debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up); void debugger_page_updown(int isup); void debugger_redraw_screen(Kimage *kimage_ptr); void debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line); void debugger_help(void); void dbg_help_show_strs(int help_depth, const char *str, const char *help_str); const char *debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, int help_depth); void do_debug_cmd(const char *in_str); word32 dis_get_memory_ptr(word32 addr); void show_one_toolset(FILE *toolfile, int toolnum, word32 addr); void show_toolset_tables(word32 a2bank, word32 addr); word32 debug_getnum(const char **str_ptr); char *debug_get_filename(const char **str_ptr); void debug_help(const char *str); void debug_bp(const char *str); void debug_bp_set(const char *str); void debug_bp_clear(const char *str); void debug_bp_clear_all(const char *str); void debug_bp_setclr(const char *str, int is_set_clear); void debug_soundfile(const char *cmd_str); void debug_logpc(const char *str); void debug_logpc_on(const char *str); void debug_logpc_off(const char *str); void debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc); Data_log *debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, int *count_ptr); void debug_logpc_save(const char *cmd_str); void set_bp(word32 addr, word32 end_addr, word32 acc_type); void show_bp(void); void delete_bp(word32 addr, word32 end_addr); void debug_iwm(const char *str); void debug_iwm_check(const char *str); int do_blank(int mode, int old_mode); void do_go(void); void do_step(void); void xam_mem(int count); void show_hex_mem(word32 startbank, word32 start, word32 end, int count); void do_debug_list(void); void dis_do_memmove(void); void dis_do_pattern_search(void); void dis_do_compare(void); const char *do_debug_unix(const char *str, int old_mode); void do_debug_load(void); char *do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, int *size_ptr); int debug_get_view_line(int back); int debug_add_output_line(char *in_str); void debug_add_output_string(char *in_str, int len); void debug_add_output_chars(char *str); int dbg_printf(const char *fmt, ...); int dbg_vprintf(const char *fmt, va_list args); void halt_printf(const char *fmt, ...); void halt2_printf(const char *fmt, ...); /* scc.c */ void scc_init(void); void scc_reset(void); void scc_hard_reset_port(int port); void scc_reset_port(int port); void scc_regen_clocks(int port); void scc_port_close(int port); void scc_port_open(dword64 dfcyc, int port); int scc_is_port_closed(dword64 dfcyc, int port); char *scc_get_serial_status(int get_status, int port); void scc_config_changed(int port, int cfg_changed, int remote_changed, int serial_dev_changed); void scc_update(dword64 dfcyc); void scc_try_to_empty_writebuf(dword64 dfcyc, int port); void scc_try_fill_readbuf(dword64 dfcyc, int port); void scc_do_event(dword64 dfcyc, int type); void show_scc_state(void); word32 scc_read_reg(dword64 dfcyc, int port); void scc_write_reg(dword64 dfcyc, int port, word32 val); word32 scc_read_data(dword64 dfcyc, int port); void scc_write_data(dword64 dfcyc, int port, word32 val); word32 scc_do_read_rr2b(void); void scc_maybe_br_event(dword64 dfcyc, int port); void scc_evaluate_ints(int port); void scc_maybe_rx_event(dword64 dfcyc, int port); void scc_maybe_rx_int(int port); void scc_clr_rx_int(int port); void scc_handle_tx_event(int port); void scc_maybe_tx_event(dword64 dfcyc, int port); void scc_clr_tx_int(int port); void scc_set_zerocnt_int(int port); void scc_clr_zerocnt_int(int port); void scc_add_to_readbuf(dword64 dfcyc, int port, word32 val); void scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...); void scc_transmit(dword64 dfcyc, int port, word32 val); void scc_add_to_writebuf(dword64 dfcyc, int port, word32 val); /* scc_socket_driver.c */ void scc_socket_open(dword64 dfcyc, int port, int cfg); void scc_socket_close(int port); void scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry); void scc_socket_maybe_open(dword64 dfcyc, int port, int must); void scc_socket_open_incoming(dword64 dfcyc, int port); void scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, int remote_port); void scc_socket_make_nonblock(dword64 dfcyc, int port); void scc_accept_socket(dword64 dfcyc, int port); void scc_socket_telnet_reqs(dword64 dfcyc, int port); void scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_socket_recvd_char(dword64 dfcyc, int port, int c); void scc_socket_empty_writebuf(dword64 dfcyc, int port); void scc_socket_modem_write(dword64 dfcyc, int port, int c); void scc_socket_do_cmd_str(dword64 dfcyc, int port); void scc_socket_send_modem_code(dword64 dfcyc, int port, int code); void scc_socket_modem_connect(dword64 dfcyc, int port); void scc_socket_modem_do_ring(dword64 dfcyc, int port); void scc_socket_do_answer(dword64 dfcyc, int port); /* scc_windriver.c */ /* scc_unixdriver.c */ /* iwm.c */ void iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525); void iwm_init(void); void iwm_reset(void); void disk_set_num_tracks(Disk *dsk, int num_tracks); word32 iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk); void draw_iwm_status(int line, char *buf); void iwm_flush_cur_disk(void); void iwm_flush_disk_to_unix(Disk *dsk); void iwm_vbl_update(void); void iwm_update_fast_disk_emul(int fast_disk_emul_en); void iwm_show_stats(int slot_drive); Disk *iwm_get_dsk(word32 state); Disk *iwm_touch_switches(int loc, dword64 dfcyc); void iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc); void iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track); void iwm525_update_phases(Disk *dsk, dword64 dfcyc); void iwm525_update_head(Disk *dsk, dword64 dfcyc); int iwm_read_status35(dword64 dfcyc); void iwm_do_action35(dword64 dfcyc); int read_iwm(int loc, dword64 dfcyc); void write_iwm(int loc, int val, dword64 dfcyc); int iwm_read_enable2(dword64 dfcyc); int iwm_read_enable2_handshake(dword64 dfcyc); void iwm_write_enable2(int val); word32 iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc); word32 iwm_read_data_fast(Disk *dsk, dword64 dfcyc); dword64 iwm_return_rand_data(Disk *dsk, dword64 dfcyc); dword64 iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr); word32 iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits); word32 iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits); dword64 iwm_calc_forced_sync(dword64 dval, int forced_bit); int iwm_calc_forced_sync_0s(dword64 sync_dval, int bits); word32 iwm_read_data(Disk *dsk, dword64 dfcyc); void iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc); void iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior); int iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta); void iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc); void iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc); void iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc); void iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc); void sector_to_partial_nib(byte *in, byte *nib_ptr); int disk_unnib_4x4(Disk *dsk); int iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf); int iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf); int iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf); void show_hex_data(byte *buf, int count); void iwm_check_nibblization(dword64 dfcyc); void disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc); void disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, int len_bits, dword64 dfcyc); void iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len); void iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc); void iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len, dword64 dfcyc); void disk_4x4_nib_out(Disk *dsk, word32 val); void disk_nib_out(Disk *dsk, word32 val, int size); void disk_nib_end_track(Disk *dsk, dword64 dfcyc); word32 disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, word32 bit_pos, dword64 dfcyc); Disk *iwm_get_dsk_from_slot_drive(int slot, int drive); void iwm_toggle_lock(Disk *dsk); void iwm_eject_named_disk(int slot, int drive, const char *name, const char *partition_name); void iwm_eject_disk_by_num(int slot, int drive); void iwm_eject_disk(Disk *dsk); void iwm_show_track(int slot_drive, int track, dword64 dfcyc); void iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc); void dummy1(word32 psr); void dummy2(word32 psr); /* joystick_driver.c */ void joystick_callback_init(int native_type); void joystick_callback_update(word32 buttons, int paddle_x, int paddle_y); /* moremem.c */ void fixup_brks(void); void fixup_hires_on(void); void fixup_bank0_2000_4000(void); void fixup_bank0_0400_0800(void); void fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, byte *mem0wr); void fixup_intcx(void); void fixup_st80col(dword64 dfcyc); void fixup_altzp(void); void fixup_page2(dword64 dfcyc); void fixup_ramrd(void); void fixup_ramwrt(void); void fixup_lc(void); void set_statereg(dword64 dfcyc, word32 val); void fixup_shadow_txt1(void); void fixup_shadow_txt2(void); void fixup_shadow_hires1(void); void fixup_shadow_hires2(void); void fixup_shadow_shr(void); void fixup_shadow_iolc(void); void update_shadow_reg(dword64 dfcyc, word32 val); void fixup_shadow_all_banks(void); void setup_pageinfo(void); void show_bankptrs_bank0rdwr(void); void show_bankptrs(int bnk); void show_addr(byte *ptr); word32 moremem_fix_vector_pull(word32 addr); word32 io_read(word32 loc, dword64 *cyc_ptr); void io_write(word32 loc, word32 val, dword64 *cyc_ptr); word32 slinky_devsel_read(dword64 dfcyc, word32 loc); void slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val); word32 c3xx_read(dword64 dfcyc, word32 loc); word32 get_lines_since_vbl(dword64 dfcyc); int in_vblank(dword64 dfcyc); int read_vid_counters(int loc, dword64 dfcyc); /* paddles.c */ void paddle_fixup_joystick_type(void); void paddle_trigger(dword64 dfcyc); void paddle_trigger_mouse(dword64 dfcyc); void paddle_trigger_keypad(dword64 dfcyc); void paddle_update_trigger_dcycs(dword64 dfcyc); int read_paddles(dword64 dfcyc, int paddle); void paddle_update_buttons(void); /* mockingboard.c */ void mock_ay8913_reset(int pair_num, dword64 dfcyc); void mockingboard_reset(dword64 dfcyc); void mock_show_pair(int pair_num, dword64 dfcyc, const char *str); void mock_update_timers(int doit, dword64 dfcyc); void mockingboard_event(dword64 dfcyc); word32 mockingboard_read(word32 loc, dword64 dfcyc); void mockingboard_write(word32 loc, word32 val, dword64 dfcyc); word32 mock_6522_read(int pair_num, word32 loc, dword64 dfcyc); void mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc); word32 mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier); void mock_ay8913_reg_read(int pair_num); void mock_ay8913_reg_write(int pair_num, dword64 dfcyc); void mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, dword64 dfcyc); void mockingboard_show(int got_num, word32 disable_mask); /* sim65816.c */ int sim_get_force_depth(void); int sim_get_use_shmem(void); void sim_set_use_shmem(int use_shmem); word32 toolbox_debug_4byte(word32 addr); void toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr); void show_toolbox_log(void); word32 get_memory_io(word32 loc, dword64 *dcyc_ptr); void set_memory_io(word32 loc, int val, dword64 *dcyc_ptr); void show_regs_act(Engine_reg *eptr); void show_regs(void); void my_exit(int ret); void do_reset(void); byte *memalloc_align(int size, int skip_amt, void **alloc_ptr); void memory_ptr_init(void); int parse_argv(int argc, char **argv, int slashes_to_find); int kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window); void load_roms_init_memory(void); void initialize_events(void); void check_for_one_event_type(int type, word32 mask); void add_event_entry(dword64 dfcyc, int type); dword64 remove_event_entry(int type, word32 mask); void add_event_stop(dword64 dfcyc); void add_event_doc(dword64 dfcyc, int osc); void add_event_scc(dword64 dfcyc, int type); void add_event_vbl(void); void add_event_vid_upd(int line); void add_event_mockingboard(dword64 dfcyc); void add_event_scan_int(dword64 dfcyc, int line); dword64 remove_event_doc(int osc); dword64 remove_event_scc(int type); void remove_event_mockingboard(void); void show_all_events(void); void show_pmhz(void); void setup_zip_speeds(void); int run_16ms(void); int run_a2_one_vbl(void); void add_irq(word32 irq_mask); void remove_irq(word32 irq_mask); void take_irq(void); void show_dtime_array(void); void update_60hz(dword64 dfcyc, double dtime_now); void do_vbl_int(void); void do_scan_int(dword64 dfcyc, int line); void check_scan_line_int(int cur_video_line); void check_for_new_scan_int(dword64 dfcyc); void scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val); void init_reg(void); void handle_action(word32 ret); void do_break(word32 ret); void do_cop(word32 ret); void do_wdm(word32 arg); void do_wai(void); void do_stp(void); void do_wdm_emulator_id(void); void size_fail(int val, word32 v1, word32 v2); int fatal_printf(const char *fmt, ...); int kegs_vprintf(const char *fmt, va_list ap); dword64 must_write(int fd, byte *bufptr, dword64 dsize); void clear_fatal_logs(void); char *kegs_malloc_str(const char *in_str); dword64 kegs_lseek(int fd, dword64 offs, int whence); /* smartport.c */ void smartport_error(void); void smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list); void do_c70d(word32 arg0); void do_c70a(word32 arg0); int do_read_c7(int unit_num, word32 buf, word32 blk); int do_write_c7(int unit_num, word32 buf, word32 blk); int smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); int do_format_c7(int unit_num); void do_c700(word32 ret); /* doc.c */ void doc_init(void); void doc_reset(dword64 dfcyc); int doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, int snd_buf_init, int *outptr_start); void doc_handle_event(int osc, dword64 dfcyc); void doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps); void doc_add_sound_irq(int osc); void doc_remove_sound_irq(int osc, int must); void doc_start_sound2(int osc, dword64 dfcyc); void doc_start_sound(int osc, double eff_dsamps, double dsamps); void doc_wave_end_estimate2(int osc, dword64 dfcyc); void doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps); void doc_remove_sound_event(int osc); void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); void doc_recalc_sound_parms(dword64 dfcyc, int osc); int doc_read_c03c(void); int doc_read_c03d(dword64 dfcyc); void doc_write_c03c(dword64 dfcyc, word32 val); void doc_write_c03d(dword64 dfcyc, word32 val); void doc_show_ensoniq_state(void); /* sound.c */ void sound_init(void); void sound_set_audio_rate(int rate); void sound_reset(dword64 dfcyc); void sound_shutdown(void); void sound_update(dword64 dfcyc); void sound_file_start(char *filename); void sound_file_open(void); void sound_file_close(void); void send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps); void show_c030_state(dword64 dfcyc); void show_c030_samps(dword64 dfcyc, int *outptr, int num); int sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps); void sound_play(dword64 dfcyc); void sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr); void sound_mock_noise(int pair, byte *noise_ptr, int num_samps); void sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, byte *noise_ptr, int *vol_ptr, int num_samps); word32 sound_read_c030(dword64 dfcyc); void sound_write_c030(dword64 dfcyc); /* sound_driver.c */ void snddrv_init(void); void sound_child_fork(int size); void parent_sound_get_sample_rate(int read_fd); void snddrv_shutdown(void); void snddrv_send_sound(int real_samps, int size); void child_sound_playit(word32 tmp); void reliable_buf_write(word32 *shm_addr, int pos, int size); void reliable_zero_write(int amt); int child_send_samples(byte *ptr, int size); void child_sound_loop(int read_fd, int write_fd, word32 *shm_addr); /* woz.c */ void woz_crc_init(void); word32 woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip); void woz_rewrite_crc(Disk *dsk, int min_write_size); void woz_rewrite_lock(Disk *dsk); void woz_check_file(Disk *dsk); void woz_parse_meta(Disk *dsk, int offset, int size); void woz_parse_info(Disk *dsk, int offset, int size); void woz_parse_tmap(Disk *dsk, int offset, int size); void woz_parse_trks(Disk *dsk, int offset, int size); int woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc); int woz_parse_header(Disk *dsk); Woz_info *woz_malloc(byte *wozptr, word32 woz_size); int woz_reopen(Disk *dsk, dword64 dfcyc); int woz_open(Disk *dsk, dword64 dfcyc); byte *woz_append_bytes(byte *wozptr, byte *in_bptr, int len); byte *woz_append_word32(byte *wozptr, word32 val); int woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, byte *bptr); byte *woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, word32 *num_blocks_ptr, dword64 *tmap_dptr); Woz_info *woz_new_from_woz(Disk *dsk, int disk_525); int woz_new(int fd, const char *str, int size_kb); void woz_maybe_reparse(Disk *dsk); void woz_set_reparse(Disk *dsk); void woz_reparse_woz(Disk *dsk); void woz_remove_a_track(Disk *dsk, word32 qtr_track); word32 woz_add_a_track(Disk *dsk, word32 qtr_track); /* unshk.c */ word32 unshk_get_long4(byte *bptr); word32 unshk_get_word2(byte *bptr); word32 unshk_calc_crc(byte *bptr, int size, word32 start_crc); int unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr); void unshk_lzw_clear(Lzw_state *lzw_ptr); byte *unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen); void unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, word32 uncompr_size, word32 thread_format, byte *base_cptr); void unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr); void unshk(Disk *dsk, const char *name_str); void unshk_dsk_raw_data(Disk *dsk); /* undeflate.c */ void *undeflate_realloc(void *ptr, dword64 dsize); void *undeflate_malloc(dword64 dsize); void show_bits(unsigned *llptr, int nl); void show_huftb(unsigned *tabptr, int bits); void undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start); void undeflate_init_bit_rev_tab(word32 *tabptr, int num); word32 undeflate_bit_reverse(word32 val, word32 bits); word32 undeflate_calc_crc32(byte *bptr, word32 len); byte *undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len); void undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, word32 entry); word32 *undeflate_init_fixed_tabs(void); word32 *undeflate_init_tables(void); void undeflate_free_tables(void); void undeflate_check_bit_reverse(void); word32 *undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, word32 *bl_count_ptr, int max_bits); word32 *undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base); byte *undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, byte *cptr_end); byte *undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size); void undeflate_gzip(Disk *dsk, const char *name_str); byte *undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size); int undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, dword64 uncompr_dsize, dword64 compr_dsize); int undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, int min_size); int undeflate_zipfile_make_list(int fd); /* dynapro.c */ word32 dynapro_get_word32(byte *bptr); word32 dynapro_get_word24(byte *bptr); word32 dynapro_get_word16(byte *bptr); void dynapro_set_word24(byte *bptr, word32 val); void dynapro_set_word32(byte *bptr, word32 val); void dynapro_set_word16(byte *bptr, word32 val); void dynapro_error(Disk *dsk, const char *fmt, ...); Dynapro_file *dynapro_alloc_file(void); void dynapro_free_file(Dynapro_file *fileptr, int check_map); void dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map); void dynapro_free_dynapro_info(Disk *dsk); word32 dynapro_find_free_block_internal(Disk *dsk); word32 dynapro_find_free_block(Disk *dsk); byte *dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size); void dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, int path_max); word32 dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, char *buf32_ptr, word32 dir_byte); word32 dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr); word32 dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte); void dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr); void dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr); void dynapro_try_fix_damaged_disk(Disk *dsk); void dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, const char *name_str); Dynapro_file *dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file **head_ptr_ptr, word32 dir_byte); void dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file *head_ptr, word32 dir_byte); word32 dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size); void dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_unlink_file(Dynapro_file *fileptr); void dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr); void dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr); void dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr); int dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); void dynapro_debug_update(Disk *dsk); void dynapro_debug_map(Disk *dsk, const char *str); void dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start); word32 dynapro_unix_to_prodos_time(const time_t *time_ptr); int dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, word32 storage_type); Dynapro_file *dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, Dynapro_file *match_ptr, word32 storage_type); int dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, word32 dir_byte); word32 dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, word32 dir_byte, word32 inc); word32 dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, word32 key_block, dword64 dsize); word32 dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks); word32 dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, word32 file_offset, word32 eof); word32 dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, int level, word32 file_offset, word32 eof); word32 dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data); word32 dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_build_map(Disk *dsk, Dynapro_file *fileptr); int dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks); /* dyna_type.c */ word32 dynatype_scan_extensions(const char *str); word32 dynatype_find_prodos_type(const char *str); const char *dynatype_find_file_type(word32 file_type); word32 dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, word32 storage_type); int dynatype_get_extension(const char *str, char *out_ptr, int buf_len); int dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr); void dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max); /* dyna_filt.c */ /* dyna_validate.c */ word32 dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, word32 parent_dir_byte); void dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks); word32 dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block); word32 dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof, int level_first); word32 dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof); word32 dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, word32 parent_dir_byte, word32 exp_blocks_used); int dynapro_validate_disk(Disk *dsk); void dynapro_validate_any_image(Disk *dsk); /* applesingle.c */ word32 applesingle_get_be32(const byte *bptr); word32 applesingle_get_be16(const byte *bptr); void applesingle_set_be32(byte *bptr, word32 val); void applesingle_set_be16(byte *bptr, word32 val); word32 applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data); word32 applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, dword64 dsize); word32 applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length); /* video.c */ void video_set_red_mask(word32 red_mask); void video_set_green_mask(word32 green_mask); void video_set_blue_mask(word32 blue_mask); void video_set_alpha_mask(word32 alpha_mask); void video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, int *shift_right_ptr); void video_set_palette(void); void video_set_redraw_skip_amt(int amt); Kimage *video_get_kimage(int win_id); char *video_get_status_ptr(int line); void video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh); int video_get_active(Kimage *kimage_ptr); void video_set_active(Kimage *kimage_ptr, int active); void video_init(int mdepth, int screen_width, int screen_height, int no_scale_window); void video_init_kimage(Kimage *kimage_ptr, int width, int height, int screen_width, int screen_height); void show_a2_line_stuff(void); void video_reset(void); void video_update(void); word32 video_all_stat_to_filt_stat(int line, word32 new_all_stat); void change_display_mode(dword64 dfcyc); void video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl); void change_border_color(dword64 dfcyc, int val); void update_border_info(void); void update_border_line(int st_line_offset, int end_line_offset, int color); void video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, int color, int st_off, int end_off); word32 video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse); void video_update_edges(int line, int left, int right, const char *str); void redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); void redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, word32 *in_wptr, word32 bg_pixel, word32 fg_pixel, int pixels_per_line, int dbl); void redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); void video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, int end_byte, int pixels_per_line, word32 filt_stat); void redraw_changed_hgr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); int video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, int reparse); word32 redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, int pixels_per_line, int y, int scan, word32 ch_mask); void redraw_changed_super_hires_bank(int bank, int start_line, int reparse, word32 *wptr, int pixels_per_line); void redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, int pixels_per_line, word32 filt_stat); void video_copy_changed2(void); void video_update_event_line(int line); void video_force_reparse(void); void video_update_through_line(int line); void video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat); void video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat); void prepare_a2_font(void); void prepare_a2_romx_font(byte *font_ptr); void video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height); void video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix); void video_form_change_rects(void); int video_get_a2_width(Kimage *kimage_ptr); int video_get_x_width(Kimage *kimage_ptr); int video_get_a2_height(Kimage *kimage_ptr); int video_get_x_height(Kimage *kimage_ptr); int video_get_x_xpos(Kimage *kimage_ptr); int video_get_x_ypos(Kimage *kimage_ptr); void video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos); int video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height); void video_update_status_enable(Kimage *kimage_ptr); int video_out_query(Kimage *kimage_ptr); void video_out_done(Kimage *kimage_ptr); int video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr, int pos); int video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); int video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); word32 video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv); void video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, int must_update); int video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width); int video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height); int video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width); int video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height); void video_update_color_raw(int bank, int col_num, int a2_color); void video_update_status_line(int line, const char *string); void video_draw_a2_string(int line, const byte *bptr); void video_show_debug_info(void); word32 read_video_data(dword64 dfcyc); word32 float_bus(dword64 dfcyc); word32 float_bus_lines(dword64 dfcyc, word32 lines_since_vbl); /* voc.c */ word32 voc_devsel_read(word32 loc, dword64 dfcyc); void voc_devsel_write(word32 loc, word32 val, dword64 dfcyc); void voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc); void voc_reset(void); word32 voc_read_reg0(dword64 dfcyc); void voc_update_interlace(dword64 dfcyc); ================================================ FILE: gsplus/src/protos_macdriver.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2019 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* END_HDR */ /* macdriver.c */ pascal OSStatus quit_event_handler(EventHandlerCallRef call_ref, EventRef event, void *ignore); void show_simple_alert(char *str1, char *str2, char *str3, int num); void x_dialog_create_kegs_conf(const char *str); int x_show_alert(int is_fatal, const char *str); pascal OSStatus my_cmd_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); void update_window(void); void show_event(UInt32 event_class, UInt32 event_kind, int handled); pascal OSStatus my_win_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); pascal OSStatus dummy_event_handler(EventHandlerCallRef call_ref, EventRef in_event, void *ignore); void mac_update_modifiers(word32 state); void mac_warp_mouse(void); void check_input_events(void); void temp_run_application_event_loop(void); int main(int argc, char *argv[]); void x_update_color(int col_num, int red, int green, int blue, word32 rgb); void x_update_physical_colormap(void); void show_xcolor_array(void); void xdriver_end(void); void x_get_kimage(Kimage *kimage_ptr); void dev_video_init(void); void x_redraw_status_lines(void); void x_push_kimage(Kimage *kimage_ptr, int destx, int desty, int srcx, int srcy, int width, int height); void x_push_done(void); void x_auto_repeat_on(int must); void x_auto_repeat_off(int must); void x_hide_pointer(int do_hide); void x_full_screen(int do_full); void update_main_window_size(void); ================================================ FILE: gsplus/src/protos_macsnd_driver.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* END_HDR */ /* macsnd_driver.c */ int mac_send_audio(byte *ptr, int in_size); void macsnd_init(void); ================================================ FILE: gsplus/src/protos_pulseaudio_driver.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2020 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* END_HDR */ /* pulseaudio_driver.c */ int pulse_audio_send_audio(byte *ptr, int in_size); void pulse_audio_main_events(void); void pulse_audio_write_to_stream(int dbg_count, int in_sz); int pulse_audio_start_stream(void); int pulse_audio_do_init(void); int pulse_audio_init(void); void pulse_audio_shutdown(void); ================================================ FILE: gsplus/src/protos_windriver.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // $KmKId: protos_windriver.h,v 1.15 2023-05-17 22:37:57+00 kentd Exp $ /* END_HDR */ /* windriver.c */ Window_info *win_find_win_info_ptr(HWND hwnd); void win_hide_pointer(Window_info *win_info_ptr, int do_hide); int win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); void win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam); void win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down); void win_event_redraw(HWND hwnd); void win_event_destroy(HWND hwnd); void win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam); void win_event_minmaxinfo(HWND hwnd, LPARAM lParam); void win_event_focus(HWND hwnd, int gain_focus); LRESULT CALLBACK win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam); int main(int argc, char **argv); void check_input_events(void); void win_video_init(int mdepth); void win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, int mdepth); void win_create_window(Window_info *win_info_ptr); void xdriver_end(void); void win_resize_window(Window_info *win_info_ptr); void x_update_display(Window_info *win_info_ptr); void x_hide_pointer(int do_hide); int opendir_int(DIR *dirp, const char *in_filename); DIR *opendir(const char *in_filename); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); int lstat(const char *path, struct stat *bufptr); int ftruncate(int fd, word32 length); /* win32snd_driver.c */ void win32snd_init(word32 *shmaddr); void win32snd_shutdown(void); void CALLBACK handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); void check_wave_error(int res, char *str); void child_sound_init_win32(void); void win32snd_set_playing(int snd_playing); void win32_send_audio2(byte *ptr, int size); int win32_send_audio(byte *ptr, int in_size); ================================================ FILE: gsplus/src/protos_xdriver.h ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* END_HDR */ /* xdriver.c */ int main(int argc, char **argv); int my_error_handler(Display *display, XErrorEvent *ev); void xdriver_end(void); void x_try_xset_r(void); void x_badpipe(int signum); int kegs_x_io_error_handler(Display *display); int x_video_get_mdepth(void); int x_try_find_visual(int depth, int screen_num); void x_video_init(void); void x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str); void x_create_window(Window_info *win_info_ptr); int xhandle_shm_error(Display *display, XErrorEvent *event); void x_allocate_window_data(Window_info *win_info_ptr); void get_shm(Window_info *win_info_ptr, int width, int height); void get_ximage(Window_info *win_info_ptr, int width, int height); void x_set_size_hints(Window_info *win_info_ptr); void x_resize_window(Window_info *win_info_ptr); void x_update_display(Window_info *win_info_ptr); Window_info *x_find_xwin(Window in_win); void x_send_copy_data(Window_info *win_info_ptr); void x_handle_copy(XSelectionRequestEvent *req_ev_ptr); void x_handle_targets(XSelectionRequestEvent *req_ev_ptr); void x_request_paste_data(Window_info *win_info_ptr); void x_handle_paste(Window w, Atom property); int x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); void x_input_events(void); void x_hide_pointer(Window_info *win_info_ptr, int do_hide); void x_handle_keysym(XEvent *xev_in); int x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up); void x_update_modifier_state(Window_info *win_info_ptr, int state); void x_auto_repeat_on(int must); void x_auto_repeat_off(int must); void x_full_screen(int do_full); ================================================ FILE: gsplus/src/pulseaudio_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2020 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #ifdef PULSE_AUDIO // Ignore entire file if PULSE_AUDIO is not defined! // Some ideas from Sample code: // https://github.com/gavv/snippets/blob/master/pa/pa_play_async_poll.c #include "defc.h" #include "sound.h" #include "protos_pulseaudio_driver.h" #include #include #define PULSE_REBUF_SIZE (64*1024) word32 g_pulseaudio_rebuf[PULSE_REBUF_SIZE]; volatile int g_pulseaudio_rd_pos; volatile int g_pulseaudio_wr_pos; volatile int g_pulseaudio_playing = 0; extern int Verbose; extern int g_preferred_rate; extern int g_audio_enable; extern word32 *g_sound_shm_addr; extern int g_sound_min_samples; extern int g_sound_max_multiplier; extern int g_sound_size; extern word32 g_vbl_count; int g_call_num = 0; pa_mainloop *g_pa_mainloop_ptr = 0; pa_context *g_pa_context_ptr = 0; pa_stream *g_pa_stream_ptr = 0; pa_sample_spec g_pa_sample_spec = { 0 }; pa_buffer_attr g_pa_buffer_attr = { 0 }; int pulse_audio_send_audio(byte *ptr, int in_size) { word32 *wptr, *pa_wptr; int samps, sample_num; int i; samps = in_size / 4; wptr = (word32 *)ptr; sample_num = g_pulseaudio_wr_pos; pa_wptr = &(g_pulseaudio_rebuf[0]); for(i = 0; i < samps; i++) { pa_wptr[sample_num] = *wptr++; sample_num++; if(sample_num >= PULSE_REBUF_SIZE) { sample_num = 0; } } g_pulseaudio_wr_pos = sample_num; pulse_audio_main_events(); return in_size; } void pulse_audio_main_events() { pa_stream_state_t stream_state; pa_context_state_t context_state; int count, num, sz, do_write; count = 0; do_write = 1; while(1) { // Do a few mainloop cycles to see if samples are needed num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); //printf("pa_mainloop_iterate ret:%d count:%d\n", num, count); if(num < 0) { return; } context_state = pa_context_get_state(g_pa_context_ptr); if((context_state == PA_CONTEXT_FAILED) || (context_state == PA_CONTEXT_TERMINATED)) { printf("context_state is bad: %d\n", context_state); g_audio_enable = 0; return; } stream_state = pa_stream_get_state(g_pa_stream_ptr); if((stream_state == PA_STREAM_FAILED) || (stream_state == PA_STREAM_TERMINATED)) { printf("stream state bad: %d\n", stream_state); g_audio_enable = 0; return; } if(do_write) { sz = pa_stream_writable_size(g_pa_stream_ptr); if(sz > 0) { pulse_audio_write_to_stream(count, sz); do_write = 0; } } count++; if(count > 50) { printf("pulse_audio_main_events() looped %d times\n", count); return; } if(num == 0) { // Nothing else to do return; } } } void pulse_audio_write_to_stream(int dbg_count, int in_sz) { word32 *wptr; void *vptr; // const pa_timing_info *pa_timing_info_ptr; // pa_usec_t pa_latency; size_t sz; int num_samps, sample_num, samps_avail, err, samps_needed; int min_samples, max_samples; int i; samps_needed = in_sz / 4; sample_num = g_pulseaudio_rd_pos; samps_avail = (g_pulseaudio_wr_pos - sample_num) & (PULSE_REBUF_SIZE - 1); min_samples = g_sound_min_samples; max_samples = min_samples * g_sound_max_multiplier; if(samps_needed > min_samples) { min_samples = samps_needed; } #if 0 if(samps_needed > samps_avail) { // We don't have enough samples, must pause g_pulseaudio_playing = 0; sample_num = g_pulseaudio_wr_pos; // Eat remaining samps } #endif if((g_pulseaudio_playing == 0) && (samps_avail > min_samples)) { // We can unpause g_pulseaudio_playing = 1; } if(g_pulseaudio_playing && (samps_avail > max_samples)) { printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); sample_num += (max_samples / 2); sample_num = sample_num & (PULSE_REBUF_SIZE - 1); } #if 0 if(g_call_num < 100) { printf("call_num:%d playing:%d g_vbl_count:%d samps_needed:%d, " "samps_avail:%d\n", g_call_num, g_pulseaudio_playing, g_vbl_count, samps_needed, samps_avail); } #endif g_call_num++; num_samps = MIN(samps_avail, samps_needed); if(g_pulseaudio_playing) { vptr = 0; // Let it allocate for us sz = num_samps * 4; err = pa_stream_begin_write(g_pa_stream_ptr, &vptr, &sz); wptr = vptr; if(err) { g_audio_enable = 0; printf("pa_stream_begin_write failed: %s\n", pa_strerror(err)); return; } num_samps = sz / 4; for(i = 0; i < num_samps; i++) { wptr[i] = g_pulseaudio_rebuf[sample_num]; sample_num++; if(sample_num >= PULSE_REBUF_SIZE) { sample_num = 0; } } } else { err = pa_stream_cancel_write(g_pa_stream_ptr); // Just get out...don't let us get further behind by sending // silence frames. return; } g_pulseaudio_rd_pos = sample_num; #if 0 pa_timing_info_ptr = pa_stream_get_timing_info(g_pa_stream_ptr); err = pa_stream_get_latency(g_pa_stream_ptr, &pa_latency, 0); printf(" will send %d samples to the stream, write_index:%lld, " "latency:%lld\n", num_samps * 4, (word64)pa_timing_info_ptr->write_index, (word64)pa_latency); #endif err = pa_stream_write(g_pa_stream_ptr, wptr, num_samps * 4, 0, 0, PA_SEEK_RELATIVE); if(err) { printf("pa_stream_write: %s\n", pa_strerror(err)); g_audio_enable = 0; } } int pulse_audio_start_stream() { int flags, ret; g_pa_sample_spec.format = PA_SAMPLE_S16LE; g_pa_sample_spec.rate = g_preferred_rate; g_pa_sample_spec.channels = 2; printf("Set requested rate=%d\n", g_pa_sample_spec.rate); g_pa_stream_ptr = pa_stream_new(g_pa_context_ptr, "KEGS", &g_pa_sample_spec, 0); if(!g_pa_stream_ptr) { printf("pa_stream_new failed\n"); return 1; } g_pa_buffer_attr.maxlength = -1; // Maximum server buffer g_pa_buffer_attr.tlength = 4*g_preferred_rate/10; // 1/10th sec //g_pa_buffer_attr.prebuf = 4*g_preferred_rate/100; // 1/100th sec g_pa_buffer_attr.prebuf = -1; g_pa_buffer_attr.minreq = 4*g_preferred_rate/60; // 1/60th sec flags = PA_STREAM_ADJUST_LATENCY; //flags = PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | // PA_STREAM_ADJUST_LATENCY; // PA_STREAM_AUTO_TIMING_UPDATE and PA_STREAM_INTERPOLATE_TIMING are // to get latency info from the server. PA_STREAM_ADJUST_LATENCY // means the total latency including the server output sink buffer // tries to be tlength ret = pa_stream_connect_playback(g_pa_stream_ptr, 0, &g_pa_buffer_attr, flags, 0, 0); if(ret) { printf("pa_stream_connect_playback failed: %d\n", ret); return ret; } return 0; // Success! } int pulse_audio_do_init() { pa_stream_state_t stream_state; pa_context_state_t context_state; int ret, count, num; g_pa_mainloop_ptr = pa_mainloop_new(); g_pa_context_ptr = pa_context_new( pa_mainloop_get_api(g_pa_mainloop_ptr), "KEGS"); if(!g_pa_context_ptr) { printf("Pulse Audio pa_context_new() failed\n"); return 1; } ret = pa_context_connect(g_pa_context_ptr, 0, 0, 0); if(ret != 0) { printf("pa_context_connect failed: %d\n", ret); return 1; } count = 0; while(1) { // Do a few mainloop cycles to get stream initialized num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); #if 0 printf("pa_mainloop_iterate ret: %d, count:%d g_pa_stream_ptr:" "%p\n", num, count, g_pa_stream_ptr); #endif if(num < 0) { return 1; } if(num == 0) { usleep(10*1000); if(count++ > 50) { // Waited more than 500ms, just give up printf("Timed out waiting for Pulse Audio to " "start\n"); return 1; } } // See if context is ready context_state = pa_context_get_state(g_pa_context_ptr); if((context_state == PA_CONTEXT_FAILED) || (context_state == PA_CONTEXT_TERMINATED)) { printf("context_state is bad: %d\n", context_state); return 1; } if((context_state == PA_CONTEXT_READY) && !g_pa_stream_ptr) { ret = pulse_audio_start_stream(); if(ret) { return ret; } } if(g_pa_stream_ptr) { stream_state = pa_stream_get_state(g_pa_stream_ptr); if((stream_state == PA_STREAM_FAILED) || (stream_state == PA_STREAM_TERMINATED)){ printf("stream state bad: %d\n", stream_state); return 1; } if(stream_state == PA_STREAM_READY) { printf("Pulse Audio stream is now ready!\n"); return 0; } } } } int pulse_audio_init() { int ret; g_pulseaudio_rd_pos = 0; g_pulseaudio_wr_pos = 0; ret = pulse_audio_do_init(); // printf("pulse_audio_init ret:%d\n", ret); if(ret != 0) { // Free structures, disable sound if(g_pa_stream_ptr) { pa_stream_disconnect(g_pa_stream_ptr); pa_stream_unref(g_pa_stream_ptr); g_pa_stream_ptr = 0; } if(g_pa_context_ptr) { pa_context_disconnect(g_pa_context_ptr); pa_context_unref(g_pa_context_ptr); g_pa_context_ptr = 0; } if(g_pa_mainloop_ptr) { pa_mainloop_free(g_pa_mainloop_ptr); g_pa_mainloop_ptr = 0; } return ret; } sound_set_audio_rate(g_preferred_rate); return 0; } void pulse_audio_shutdown() { printf("pulse_audio_shutdown\n"); } #endif /* PULSE_AUDIO */ ================================================ FILE: gsplus/src/scc.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Driver for the Zilog SCC Z8530, which implements two channels (A,B) of // serial ports, controlled by $C038-$C03B #include "defc.h" extern int Verbose; extern int g_code_yellow; extern dword64 g_cur_dfcyc; extern int g_serial_cfg[2]; extern int g_serial_mask[2]; extern char *g_serial_remote_ip[2]; extern int g_serial_remote_port[2]; extern char *g_serial_device[2]; extern int g_serial_win_device[2]; extern int g_irq_pending; /* scc port 0 == channel A = slot 1 = c039/c03b */ /* port 1 == channel B = slot 2 = c038/c03a */ #include "scc.h" #define SCC_R14_DPLL_SOURCE_BRG 0x100 #define SCC_R14_DPLL_SOURCE_RTXC 0x200 #define SCC_DCYCS_PER_PCLK ((DCYCS_1_MHZ) / ((DCYCS_28_MHZ) /8)) #define SCC_DCYCS_PER_XTAL ((DCYCS_1_MHZ) / 3686400.0) // PCLK is 3.5795MHz #define SCC_BR_EVENT 1 #define SCC_TX_EVENT 2 #define SCC_RX_EVENT 3 #define SCC_MAKE_EVENT(port, a) (((a) << 1) + (port)) Scc g_scc[2]; int g_baud_table[] = { 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; int g_scc_overflow = 0; int g_scc_init = 0; // cur_state >= 0 and matches g_serial_cfg[port]: port is in that mode // cur_state = -1: port is in no particular mode and should go to g_serial_cfg[] // cur_state = -2: port failed to enter g_serial_cfg[], do not try again until // something changes void scc_init() { Scc *scc_ptr; int i, j; for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); memset(scc_ptr, 0, sizeof(*scc_ptr)); scc_ptr->cur_state = -1; scc_ptr->modem_state = 0; scc_ptr->sockfd = INVALID_SOCKET; scc_ptr->rdwrfd = INVALID_SOCKET; scc_ptr->sockaddr_ptr = 0; scc_ptr->sockaddr_size = 0; scc_ptr->unix_dev_fd = -1; scc_ptr->win_com_handle = 0; scc_ptr->win_dcb_ptr = 0; scc_ptr->br_event_pending = 0; scc_ptr->rx_event_pending = 0; scc_ptr->tx_event_pending = 0; scc_ptr->char_size = 8; scc_ptr->baud_rate = 9600; scc_ptr->telnet_mode = 0; scc_ptr->telnet_iac = 0; scc_ptr->out_char_dfcyc = 0; scc_ptr->socket_error = 0; scc_ptr->socket_num_rings = 0; scc_ptr->socket_last_ring_dfcyc = 0; scc_ptr->modem_mode = 0; scc_ptr->modem_plus_mode = 0; scc_ptr->modem_s0_val = 0; scc_ptr->modem_s2_val = '+'; scc_ptr->modem_cmd_len = 0; scc_ptr->modem_out_portnum = 23; scc_ptr->modem_cmd_str[0] = 0; for(j = 0; j < 2; j++) { scc_ptr->telnet_local_mode[j] = 0; scc_ptr->telnet_remote_mode[j] = 0; scc_ptr->telnet_reqwill_mode[j] = 0; scc_ptr->telnet_reqdo_mode[j] = 0; } } g_scc_init = 1; } void scc_reset() { Scc *scc_ptr; int i; if(!g_scc_init) { halt_printf("scc_reset called before init\n"); return; } for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); scc_ptr->mode = 0; scc_ptr->reg_ptr = 0; scc_ptr->in_rdptr = 0; scc_ptr->in_wrptr = 0; scc_ptr->out_rdptr = 0; scc_ptr->out_wrptr = 0; scc_ptr->dcd = 1 - i; // 1 for slot 1, 0 for slot 2 scc_ptr->wantint_rx = 0; scc_ptr->wantint_tx = 0; scc_ptr->wantint_zerocnt = 0; scc_ptr->read_called_this_vbl = 0; scc_ptr->write_called_this_vbl = 0; scc_evaluate_ints(i); scc_hard_reset_port(i); } } void scc_hard_reset_port(int port) { Scc *scc_ptr; scc_reset_port(port); scc_ptr = &(g_scc[port]); scc_ptr->reg[14] = 0; /* zero bottom two bits */ scc_ptr->reg[13] = 0; scc_ptr->reg[12] = 0; scc_ptr->reg[11] = 0x08; scc_ptr->reg[10] = 0; scc_ptr->reg[7] = 0; scc_ptr->reg[6] = 0; scc_ptr->reg[5] = 0; scc_ptr->reg[4] = 0x04; scc_ptr->reg[3] = 0; scc_ptr->reg[2] = 0; scc_ptr->reg[1] = 0; /* HACK HACK: */ g_scc[0].reg[9] = 0; /* Clear all interrupts */ scc_evaluate_ints(port); scc_regen_clocks(port); } void scc_reset_port(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); scc_ptr->reg[15] = 0xf8; scc_ptr->reg[14] &= 0x03; /* 0 most (including >= 0x100) bits */ scc_ptr->reg[10] = 0; scc_ptr->reg[5] &= 0x65; /* leave tx bits and sdlc/crc bits */ scc_ptr->reg[4] |= 0x04; /* Set async mode */ scc_ptr->reg[3] &= 0xfe; /* clear receiver enable */ scc_ptr->reg[1] &= 0xfe; /* clear ext int enable */ scc_ptr->br_is_zero = 0; scc_ptr->tx_buf_empty = 1; scc_ptr->wantint_rx = 0; scc_ptr->wantint_tx = 0; scc_ptr->wantint_zerocnt = 0; scc_ptr->rx_queue_depth = 0; scc_evaluate_ints(port); scc_regen_clocks(port); scc_clr_tx_int(port); scc_clr_rx_int(port); } void scc_regen_clocks(int port) { Scc *scc_ptr; double br_dcycs, tx_dcycs, rx_dcycs, rx_char_size, tx_char_size; double clock_mult, dpll_dcycs; word32 reg4, reg11, reg14, br_const, max_diff, diff; int baud, cur_state, baud_entries, pos; int i; /* Always do baud rate generator */ scc_ptr = &(g_scc[port]); br_const = (scc_ptr->reg[13] << 8) + scc_ptr->reg[12]; br_const += 2; /* counts down past 0 */ reg4 = scc_ptr->reg[4]; // Transmit/Receive misc params clock_mult = 1.0; switch((reg4 >> 6) & 3) { case 0: /* x1 */ clock_mult = 1.0; break; case 1: /* x16 */ clock_mult = 16.0; break; case 2: /* x32 */ clock_mult = 32.0; break; case 3: /* x64 */ clock_mult = 64.0; break; } br_dcycs = 0.01; reg14 = scc_ptr->reg[14]; if(reg14 & 0x1) { br_dcycs = SCC_DCYCS_PER_XTAL; if(reg14 & 0x2) { br_dcycs = SCC_DCYCS_PER_PCLK; } } br_dcycs = br_dcycs * (double)br_const * 2.0; dpll_dcycs = 0.1; if(reg14 & SCC_R14_DPLL_SOURCE_BRG) { dpll_dcycs = br_dcycs; } else if(reg14 & SCC_R14_DPLL_SOURCE_RTXC) { dpll_dcycs = SCC_DCYCS_PER_XTAL; } tx_dcycs = 1; reg11 = scc_ptr->reg[11]; switch((reg11 >> 3) & 3) { case 0: // /RTxC pin tx_dcycs = SCC_DCYCS_PER_XTAL; break; case 2: // BR generator output tx_dcycs = br_dcycs; break; case 3: // DPLL output tx_dcycs = dpll_dcycs; break; } tx_dcycs = tx_dcycs * clock_mult; rx_dcycs = 1; switch((reg11 >> 5) & 3) { case 0: // /RTxC pin rx_dcycs = SCC_DCYCS_PER_XTAL; break; case 2: // BR generator output rx_dcycs = br_dcycs; break; case 3: // DPLL output rx_dcycs = dpll_dcycs; break; } rx_dcycs = rx_dcycs * clock_mult; tx_char_size = 8.0; switch((scc_ptr->reg[5] >> 5) & 0x3) { case 0: // 5 bits tx_char_size = 5.0; break; case 1: // 7 bits tx_char_size = 7.0; break; case 2: // 6 bits tx_char_size = 6.0; break; } scc_ptr->char_size = (int)tx_char_size; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 1: // 1 stop bit tx_char_size += 2.0; // 1 stop + 1 start bit break; case 2: // 1.5 stop bit tx_char_size += 2.5; // 1.5 stop + 1 start bit break; case 3: // 2 stop bits tx_char_size += 3.0; // 2.0 stop + 1 start bit break; } if(scc_ptr->reg[4] & 1) { // parity enabled tx_char_size += 1.0; } if(scc_ptr->reg[14] & 0x10) { /* loopback mode, make it go faster...*/ rx_char_size = 1.0; tx_char_size = 1.0; } rx_char_size = tx_char_size; /* HACK */ baud = (int)(DCYCS_1_MHZ / tx_dcycs); max_diff = 5000000; pos = 0; baud_entries = sizeof(g_baud_table)/sizeof(g_baud_table[0]); for(i = 0; i < baud_entries; i++) { diff = abs(g_baud_table[i] - baud); if(diff < max_diff) { pos = i; max_diff = diff; } } scc_ptr->baud_rate = g_baud_table[pos]; scc_ptr->br_dcycs = br_dcycs; scc_ptr->tx_dcycs = tx_dcycs * tx_char_size; scc_ptr->rx_dcycs = rx_dcycs * rx_char_size; cur_state = scc_ptr->cur_state; if(cur_state == 0) { // real serial ports #ifdef _WIN32 scc_serial_win_change_params(port); #else scc_serial_unix_change_params(port); #endif } } void scc_port_close(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); #ifdef _WIN32 scc_serial_win_close(port); #else scc_serial_unix_close(port); #endif scc_socket_close(port); scc_ptr->cur_state = -1; // Nothing open } void scc_port_open(dword64 dfcyc, int port) { int cfg; cfg = g_serial_cfg[port]; printf("scc_port_open port:%d cfg:%d\n", port, cfg); if(cfg == 0) { // Real host serial port #ifdef _WIN32 scc_serial_win_open(port); #else scc_serial_unix_open(port); #endif } else if(cfg >= 1) { scc_socket_open(dfcyc, port, cfg); } printf(" open socketfd:%ld\n", (long)g_scc[port].sockfd); } int scc_is_port_closed(dword64 dfcyc, int port) { Scc *scc_ptr; // Returns 1 is the port is closed (not working). Returns 0 // if the port is open. Tries to open the port if it is not in error scc_ptr = &(g_scc[port]); if(scc_ptr->cur_state == -1) { scc_port_open(dfcyc, port); } if(scc_ptr->cur_state < 0) { scc_ptr->cur_state = -2; //printf("scc_is_port_closed p:%d returning 0\n", port); return 1; // Not open } return 0; // Port is open! } char * scc_get_serial_status(int get_status, int port) { char buf[80]; char *str; int cur_state; if(get_status == 0) { return 0; } cur_state = g_scc[port].cur_state; str = ""; switch(cur_state) { case -1: str = "Not initialized yet"; break; case 0: snprintf(buf, 80, "Opened %s OK", g_serial_device[port]); str = buf; break; case 1: snprintf(buf, 80, "Virtual modem, sockfd:%d", (int)g_scc[port].sockfd); str = buf; break; case 2: snprintf(buf, 80, "Outgoing to %s:%d", g_serial_remote_ip[port], g_serial_remote_port[port]); str = buf; break; case 3: snprintf(buf, 80, "Opened %d, sockfd:%d", 6501 + port, (int)g_scc[port].sockfd); str = buf; break; default: str = "Open failed, port is closed"; } return kegs_malloc_str(str); } void scc_config_changed(int port, int cfg_changed, int remote_changed, int serial_dev_changed) { Scc *scc_ptr; int must_change; // Check if scc_init() was called, if not get out if(!g_scc_init) { return; } // F4 may have changed the serial port config. If so, close old // port, open new one. scc_ptr = &(g_scc[port]); must_change = cfg_changed; switch(scc_ptr->cur_state) { case 0: // Using serial port must_change |= serial_dev_changed; break; case 2: // Using remote connection must_change |= remote_changed; break; } if(must_change) { scc_port_close(port); } } void scc_update(dword64 dfcyc) { int i; // called each VBL update for(i = 0; i < 2; i++) { g_scc[i].write_called_this_vbl = 0; g_scc[i].read_called_this_vbl = 0; // These calls will try to open the port if it's closed scc_try_to_empty_writebuf(dfcyc, i); scc_try_fill_readbuf(dfcyc, i); g_scc[i].write_called_this_vbl = 0; g_scc[i].read_called_this_vbl = 0; } } void scc_try_to_empty_writebuf(dword64 dfcyc, int port) { Scc *scc_ptr; int cur_state; scc_ptr = &(g_scc[port]); cur_state = scc_ptr->cur_state; if(scc_ptr->write_called_this_vbl) { return; } if(scc_is_port_closed(dfcyc, port)) { return; // Port is not open } scc_ptr->write_called_this_vbl = 1; if(cur_state == 0) { #if defined(_WIN32) scc_serial_win_empty_writebuf(port); #else scc_serial_unix_empty_writebuf(port); #endif } else if(cur_state >= 1) { scc_socket_empty_writebuf(dfcyc, port); } } void scc_try_fill_readbuf(dword64 dfcyc, int port) { Scc *scc_ptr; int space_used, space_left, cur_state; scc_ptr = &(g_scc[port]); space_used = scc_ptr->in_wrptr - scc_ptr->in_rdptr; if(space_used < 0) { space_used += SCC_INBUF_SIZE; } space_left = (7*SCC_INBUF_SIZE/8) - space_used; if(space_left < 1) { /* Buffer is pretty full, don't try to get more */ return; } if(scc_is_port_closed(dfcyc, port)) { return; // Port is not open } #if 0 if(scc_ptr->read_called_this_vbl) { return; } #endif scc_ptr->read_called_this_vbl = 1; cur_state = scc_ptr->cur_state; if(cur_state == 0) { #if defined(_WIN32) scc_serial_win_fill_readbuf(dfcyc, port, space_left); #else scc_serial_unix_fill_readbuf(dfcyc, port, space_left); #endif } else if(cur_state >= 1) { scc_socket_fill_readbuf(dfcyc, port, space_left); } } void scc_do_event(dword64 dfcyc, int type) { Scc *scc_ptr; int port; port = type & 1; type = (type >> 1); scc_ptr = &(g_scc[port]); if(type == SCC_BR_EVENT) { /* baud rate generator counted down to 0 */ scc_ptr->br_event_pending = 0; scc_set_zerocnt_int(port); scc_maybe_br_event(dfcyc, port); } else if(type == SCC_TX_EVENT) { scc_ptr->tx_event_pending = 0; scc_ptr->tx_buf_empty = 1; scc_handle_tx_event(port); } else if(type == SCC_RX_EVENT) { scc_ptr->rx_event_pending = 0; scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } else { halt_printf("scc_do_event: %08x!\n", type); } return; } void show_scc_state() { Scc *scc_ptr; int i, j; for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); printf("SCC port: %d\n", i); for(j = 0; j < 16; j += 4) { printf("Reg %2d-%2d: %02x %02x %02x %02x\n", j, j+3, scc_ptr->reg[j], scc_ptr->reg[j+1], scc_ptr->reg[j+2], scc_ptr->reg[j+3]); } printf("state: %d, sockfd:%llx rdwrfd:%llx, win_com:%p, " "win_dcb:%p\n", scc_ptr->cur_state, (dword64)scc_ptr->sockfd, (dword64)scc_ptr->rdwrfd, scc_ptr->win_com_handle, scc_ptr->win_dcb_ptr); printf("in_rdptr: %04x, in_wr:%04x, out_rd:%04x, out_wr:%04x\n", scc_ptr->in_rdptr, scc_ptr->in_wrptr, scc_ptr->out_rdptr, scc_ptr->out_wrptr); printf("rx_queue_depth: %d, queue: %02x, %02x, %02x, %02x\n", scc_ptr->rx_queue_depth, scc_ptr->rx_queue[0], scc_ptr->rx_queue[1], scc_ptr->rx_queue[2], scc_ptr->rx_queue[3]); printf("want_ints: rx:%d, tx:%d, zc:%d\n", scc_ptr->wantint_rx, scc_ptr->wantint_tx, scc_ptr->wantint_zerocnt); printf("ev_pendings: rx:%d, tx:%d, br:%d\n", scc_ptr->rx_event_pending, scc_ptr->tx_event_pending, scc_ptr->br_event_pending); printf("br_dcycs: %f, tx_dcycs: %f, rx_dcycs: %f\n", scc_ptr->br_dcycs, scc_ptr->tx_dcycs, scc_ptr->rx_dcycs); printf("char_size: %d, baud_rate: %d, mode: %d\n", scc_ptr->char_size, scc_ptr->baud_rate, scc_ptr->mode); printf("modem_state: %dtelnet_mode:%d iac:%d, " "modem_cmd_len:%d\n", scc_ptr->modem_state, scc_ptr->telnet_mode, scc_ptr->telnet_iac, scc_ptr->modem_cmd_len); printf("telnet_loc_modes:%08x %08x, telnet_rem_motes:" "%08x %08x\n", scc_ptr->telnet_local_mode[0], scc_ptr->telnet_local_mode[1], scc_ptr->telnet_remote_mode[0], scc_ptr->telnet_remote_mode[1]); printf("modem_mode:%08x plus_mode:%d, out_char_dfcyc:%016llx\n", scc_ptr->modem_mode, scc_ptr->modem_plus_mode, scc_ptr->out_char_dfcyc); } } word32 scc_read_reg(dword64 dfcyc, int port) { Scc *scc_ptr; word32 ret; int regnum; scc_ptr = &(g_scc[port]); scc_ptr->mode = 0; regnum = scc_ptr->reg_ptr; /* port 0 is channel A, port 1 is channel B */ switch(regnum) { case 0: case 4: ret = 0x60; /* 0x44 = no dcd, no cts,0x6c = dcd ok, cts ok*/ if(scc_ptr->dcd) { ret |= 0x08; } //ret |= 0x8; /* HACK HACK */ if(scc_ptr->rx_queue_depth) { ret |= 0x01; } if(scc_ptr->tx_buf_empty) { ret |= 0x04; } if(scc_ptr->br_is_zero) { ret |= 0x02; } //printf("Read scc[%d] stat: %016llx : %02x dcd:%d\n", port, // dfcyc, ret, scc_ptr->dcd); break; case 1: case 5: /* HACK: residue codes not right */ ret = 0x07; /* all sent */ break; case 2: case 6: if(port == 0) { ret = scc_ptr->reg[2]; } else { // Port B, read RR2B int stat // The TELNET.SYSTEM by Colin Leroy-Mira uses RR2B ret = scc_do_read_rr2b() << 1; if(g_scc[0].reg[9] & 0x10) { // wr9 status high // Map bit 3->4, 2->5, 1->6 ret = ((ret << 1) & 0x10) | ((ret << 3) & 0x20) | ((ret << 5) & 0x40); } } break; case 3: case 7: if(port == 0) { ret = (g_irq_pending & 0x3f); } else { ret = 0; } break; case 8: ret = scc_read_data(dfcyc, port); break; case 9: case 13: ret = scc_ptr->reg[13]; break; case 10: case 14: ret = 0; break; case 11: case 15: ret = scc_ptr->reg[15]; break; case 12: ret = scc_ptr->reg[12]; break; default: halt_printf("Tried reading c03%x with regnum: %d!\n", 8+port, regnum); ret = 0; } scc_ptr->reg_ptr = 0; scc_printf("Read c03%x, rr%d, ret: %02x\n", 8+port, regnum, ret); dbg_log_info(dfcyc, 0, ret, (regnum << 20) | (0xc039 - port)); return ret; } void scc_write_reg(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; word32 old_val, changed_bits, irq_mask; int regnum, mode, tmp1; scc_ptr = &(g_scc[port]); regnum = scc_ptr->reg_ptr & 0xf; mode = scc_ptr->mode; if(mode == 0) { if((val & 0xf0) == 0) { /* Set reg_ptr */ scc_ptr->reg_ptr = val & 0xf; regnum = 0; scc_ptr->mode = 1; } } else { scc_ptr->reg_ptr = 0; scc_ptr->mode = 0; } old_val = scc_ptr->reg[regnum]; changed_bits = (old_val ^ val) & 0xff; dbg_log_info(dfcyc, (mode << 16) | scc_ptr->reg_ptr, (changed_bits << 16) | val, (regnum << 20) | (0x1c039 - port)); /* Set reg reg */ switch(regnum) { case 0: /* wr0 */ tmp1 = (val >> 3) & 0x7; switch(tmp1) { case 0x0: case 0x1: break; case 0x2: /* reset ext/status ints */ /* should clear other ext ints */ scc_clr_zerocnt_int(port); break; case 0x5: /* reset tx int pending */ scc_clr_tx_int(port); break; case 0x6: /* reset rr1 bits */ break; case 0x7: /* reset highest pri int pending */ irq_mask = g_irq_pending; if(port == 0) { /* Move SCC0 ints into SCC1 positions */ irq_mask = irq_mask >> 3; } if(irq_mask & IRQ_PENDING_SCC1_RX) { scc_clr_rx_int(port); } else if(irq_mask & IRQ_PENDING_SCC1_TX) { scc_clr_tx_int(port); } else if(irq_mask & IRQ_PENDING_SCC1_ZEROCNT) { scc_clr_zerocnt_int(port); } break; case 0x4: /* enable int on next rx char */ default: halt_printf("Wr c03%x to wr0 of %02x, bad cmd cd:%x!\n", 9-port, val, tmp1); } tmp1 = (val >> 6) & 0x3; switch(tmp1) { case 0x0: /* null code */ break; case 0x1: /* reset rx crc */ case 0x2: /* reset tx crc */ printf("Wr c03%x to wr0 of %02x!\n", 9-port, val); break; case 0x3: /* reset tx underrun/eom latch */ /* if no extern status pending, or being reset now */ /* and tx disabled, ext int with tx underrun */ /* ah, just do nothing */ break; } return; case 1: /* wr1 */ /* proterm sets this == 0x10, which is int on all rx */ scc_ptr->reg[regnum] = val; return; case 2: /* wr2 */ /* All values do nothing, let 'em all through! */ scc_ptr->reg[regnum] = val; return; case 3: /* wr3 */ if((val & 0x1e) != 0x0) { halt_printf("Wr c03%x to wr3 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 4: /* wr4 */ if((val & 0x30) != 0x00 || (val & 0x0c) == 0) { halt_printf("Wr c03%x to wr4 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 5: /* wr5 */ if((val & 0x15) != 0x0) { halt_printf("Wr c03%x to wr5 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits & 0x60) { scc_regen_clocks(port); } if(changed_bits & 0x80) { scc_printf("SCC port %d DTR:%d\n", port, val & 0x80); } return; case 6: /* wr6 */ if(val != 0) { halt_printf("Wr c03%x to wr6 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 7: /* wr7 */ if(val != 0) { halt_printf("Wr c03%x to wr7 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 8: /* wr8 */ scc_write_data(dfcyc, port, val); return; case 9: /* wr9 */ if((val & 0xc0)) { if(val & 0x80) { scc_reset_port(0); } if(val & 0x40) { scc_reset_port(1); } if((val & 0xc0) == 0xc0) { scc_hard_reset_port(0); scc_hard_reset_port(1); } } // Bit 5 is software interrupt ack, which does not exist on NMOS // Bit 2 sets IEO pin low, which doesn't exist either old_val = g_scc[0].reg[9]; g_scc[0].reg[9] = val; scc_evaluate_ints(0); scc_evaluate_ints(1); return; case 10: /* wr10 */ if((val & 0xff) != 0x00) { printf("Wr c03%x to wr10 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 11: /* wr11 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 12: /* wr12 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 13: /* wr13 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 14: /* wr14 */ val = val | (old_val & (~0xffU)); switch((val >> 5) & 0x7) { case 0x0: // Null command (change nothing) break; case 0x1: // Enter search mode case 0x2: // Reset missing clock case 0x3: // Disable PLL case 0x6: // Set FM mode case 0x7: // Set NRZI mode // Disable the PLL effectively val = val & 0xff; // Clear all upper bits break; case 0x4: // DPLL source is BR gen val = (val & 0xff) | SCC_R14_DPLL_SOURCE_BRG; break; case 0x5: // DPLL source is RTxC val = (val & 0xff) | SCC_R14_DPLL_SOURCE_RTXC; break; default: halt_printf("Wr c03%x to wr14 of %02x, bad dpll cd!\n", 8+port, val); } if((val & 0x0c) != 0x0) { halt_printf("Wr c03%x to wr14 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits || (val != old_val)) { scc_regen_clocks(port); } scc_maybe_br_event(dfcyc, port); return; case 15: /* wr15 */ /* ignore all accesses since IIgs self test messes with it */ if((val & 0xff) != 0x0) { scc_printf("Write c03%x to wr15 of %02x!\n", 8+port, val); } if((g_scc[0].reg[9] & 0x8) && (val != 0)) { printf("Write wr15:%02x and master int en = 1!\n",val); /* set_halt(1); */ } scc_ptr->reg[regnum] = val; scc_maybe_br_event(dfcyc, port); scc_evaluate_ints(port); return; default: halt_printf("Wr c03%x to wr%d of %02x!\n", 8+port, regnum, val); return; } } // scc_read_data: Read from 0xc03b or 0xc03a word32 scc_read_data(dword64 dfcyc, int port) { Scc *scc_ptr; word32 ret; int depth; int i; scc_ptr = &(g_scc[port]); scc_try_fill_readbuf(dfcyc, port); depth = scc_ptr->rx_queue_depth; ret = 0; if(depth != 0) { ret = scc_ptr->rx_queue[0]; for(i = 1; i < depth; i++) { scc_ptr->rx_queue[i-1] = scc_ptr->rx_queue[i]; } scc_ptr->rx_queue_depth = depth - 1; scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } scc_printf("SCC read %04x: ret %02x, depth:%d\n", 0xc03b-port, ret, depth); dbg_log_info(dfcyc, 0, ret, 0xc03b - port); return ret; } void scc_write_data(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; scc_printf("SCC write %04x: %02x\n", 0xc03b-port, val); dbg_log_info(dfcyc, val, 0, 0x1c03b - port); scc_ptr = &(g_scc[port]); if(scc_ptr->reg[14] & 0x10) { /* local loopback! */ scc_add_to_readbuf(dfcyc, port, val); } else { scc_transmit(dfcyc, port, val); } scc_try_to_empty_writebuf(dfcyc, port); scc_maybe_tx_event(dfcyc, port); } word32 scc_do_read_rr2b() { word32 val; val = g_irq_pending & 0x3f; if(val == 0) { return 3; // 011 if no interrupts pending } // Do Channel A first. Priority order from SCC documentation if(val & IRQ_PENDING_SCC0_RX) { return 6; // 110 Ch A Rx char available } if(val & IRQ_PENDING_SCC0_TX) { return 4; // 100 Ch A Tx buffer empty } if(val & IRQ_PENDING_SCC0_ZEROCNT) { return 5; // 101 Ch A External/Status change } if(val & IRQ_PENDING_SCC1_RX) { return 2; // 010 Ch B Rx char available } if(val & IRQ_PENDING_SCC1_TX) { return 0; // 000 Ch B Tx buffer empty } if(val & IRQ_PENDING_SCC1_ZEROCNT) { return 1; // 001 Ch B External/Status change } return 3; } void scc_maybe_br_event(dword64 dfcyc, int port) { Scc *scc_ptr; double br_dcycs; scc_ptr = &(g_scc[port]); if(((scc_ptr->reg[14] & 0x01) == 0) || scc_ptr->br_event_pending) { return; } /* also, if ext ints not enabled, don't do baud rate ints */ if(((scc_ptr->reg[15] & 0x02) == 0) || ((scc_ptr->reg[9] & 8) == 0)) { return; } br_dcycs = scc_ptr->br_dcycs; if(br_dcycs < 1.0) { halt_printf("br_dcycs: %f!\n", br_dcycs); #if 0 printf("br_dcycs: %f!\n", br_dcycs); dbg_log_info(dfcyc, (word32)(br_dcycs * 65536), (scc_ptr->reg[15] << 24) | (scc_ptr->reg[14] << 16) | (scc_ptr->reg[13] << 8) | scc_ptr->reg[12], 0xdc1c); dbg_log_info(dfcyc, (scc_ptr->reg[11] << 24) | (scc_ptr->reg[10] << 16) | (scc_ptr->reg[9] << 8) | scc_ptr->reg[5], (scc_ptr->reg[4] << 24) | (scc_ptr->reg[3] << 16) | (scc_ptr->reg[1] << 8) | scc_ptr->reg[0], 0xdc1b); #endif } scc_ptr->br_event_pending = 1; add_event_scc(dfcyc + (dword64)(br_dcycs * 65536.0), SCC_MAKE_EVENT(port, SCC_BR_EVENT)); } void scc_evaluate_ints(int port) { Scc *scc_ptr; word32 irq_add_mask, irq_remove_mask; int mie; scc_ptr = &(g_scc[port]); mie = g_scc[0].reg[9] & 0x8; /* Master int en */ if(!mie) { /* There can be no interrupts if MIE=0 */ remove_irq(IRQ_PENDING_SCC1_RX | IRQ_PENDING_SCC1_TX | IRQ_PENDING_SCC1_ZEROCNT | IRQ_PENDING_SCC0_RX | IRQ_PENDING_SCC0_TX | IRQ_PENDING_SCC0_ZEROCNT); return; } irq_add_mask = 0; irq_remove_mask = 0; if(scc_ptr->wantint_rx) { irq_add_mask |= IRQ_PENDING_SCC1_RX; } else { irq_remove_mask |= IRQ_PENDING_SCC1_RX; } if(scc_ptr->wantint_tx) { irq_add_mask |= IRQ_PENDING_SCC1_TX; } else { irq_remove_mask |= IRQ_PENDING_SCC1_TX; } if(scc_ptr->wantint_zerocnt) { irq_add_mask |= IRQ_PENDING_SCC1_ZEROCNT; } else { irq_remove_mask |= IRQ_PENDING_SCC1_ZEROCNT; } if(port == 0) { /* Port 1 is in bits 0-2 and port 0 is in bits 3-5 */ irq_add_mask = irq_add_mask << 3; irq_remove_mask = irq_remove_mask << 3; } if(irq_add_mask) { add_irq(irq_add_mask); } if(irq_remove_mask) { remove_irq(irq_remove_mask); } } void scc_maybe_rx_event(dword64 dfcyc, int port) { Scc *scc_ptr; double rx_dcycs; int in_rdptr, in_wrptr, depth; scc_ptr = &(g_scc[port]); if(scc_ptr->rx_event_pending) { /* one pending already, wait for the event to arrive */ return; } in_rdptr = scc_ptr->in_rdptr; in_wrptr = scc_ptr->in_wrptr; depth = scc_ptr->rx_queue_depth; if((in_rdptr == in_wrptr) || (depth >= 3)) { /* no more chars or no more space, just get out */ return; } if(depth < 0) { depth = 0; } /* pull char from in_rdptr into queue */ scc_ptr->rx_queue[depth] = scc_ptr->in_buf[in_rdptr]; scc_ptr->in_rdptr = (in_rdptr + 1) & (SCC_INBUF_SIZE - 1); scc_ptr->rx_queue_depth = depth + 1; scc_maybe_rx_int(port); rx_dcycs = scc_ptr->rx_dcycs; scc_ptr->rx_event_pending = 1; add_event_scc(dfcyc + (dword64)(rx_dcycs*65536.0), SCC_MAKE_EVENT(port, SCC_RX_EVENT)); } void scc_maybe_rx_int(int port) { Scc *scc_ptr; int depth; int rx_int_mode; scc_ptr = &(g_scc[port]); depth = scc_ptr->rx_queue_depth; if(depth <= 0) { /* no more chars, just get out */ scc_clr_rx_int(port); return; } rx_int_mode = (scc_ptr->reg[1] >> 3) & 0x3; if((rx_int_mode == 1) || (rx_int_mode == 2)) { scc_ptr->wantint_rx = 1; } scc_evaluate_ints(port); } void scc_clr_rx_int(int port) { g_scc[port].wantint_rx = 0; scc_evaluate_ints(port); } void scc_handle_tx_event(int port) { Scc *scc_ptr; int tx_int_mode; scc_ptr = &(g_scc[port]); /* nothing pending, see if ints on */ tx_int_mode = (scc_ptr->reg[1] & 0x2); if(tx_int_mode) { scc_ptr->wantint_tx = 1; } scc_evaluate_ints(port); } void scc_maybe_tx_event(dword64 dfcyc, int port) { Scc *scc_ptr; double tx_dcycs; scc_ptr = &(g_scc[port]); if(scc_ptr->tx_event_pending) { /* one pending already, tx_buf is full */ scc_ptr->tx_buf_empty = 0; } else { /* nothing pending, see ints on */ scc_ptr->tx_buf_empty = 0; scc_evaluate_ints(port); tx_dcycs = scc_ptr->tx_dcycs; scc_ptr->tx_event_pending = 1; add_event_scc(dfcyc + (dword64)(tx_dcycs * 65536.0), SCC_MAKE_EVENT(port, SCC_TX_EVENT)); } } void scc_clr_tx_int(int port) { g_scc[port].wantint_tx = 0; scc_evaluate_ints(port); } void scc_set_zerocnt_int(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); if(scc_ptr->reg[15] & 0x2) { scc_ptr->wantint_zerocnt = 1; } scc_evaluate_ints(port); } void scc_clr_zerocnt_int(int port) { g_scc[port].wantint_zerocnt = 0; scc_evaluate_ints(port); } void scc_add_to_readbuf(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int in_wrptr, in_wrptr_next, in_rdptr, safe_val; scc_ptr = &(g_scc[port]); if((scc_ptr->reg[5] & 0x60) != 0x60) { // HACK: this is tx char size! val = val & 0x7f; } in_wrptr = scc_ptr->in_wrptr; in_rdptr = scc_ptr->in_rdptr; in_wrptr_next = (in_wrptr + 1) & (SCC_INBUF_SIZE - 1); if(in_wrptr_next != in_rdptr) { scc_ptr->in_buf[in_wrptr] = val; scc_ptr->in_wrptr = in_wrptr_next; safe_val = val & 0x7f; if((safe_val < 0x20) || (safe_val >= 0x7f)) { safe_val = '.'; } scc_printf("scc in port[%d] add 0x%02x (%c), %d,%d != %d\n", port, val, safe_val, in_wrptr, in_wrptr_next, in_rdptr); g_scc_overflow = 0; } else { if(g_scc_overflow == 0) { g_code_yellow++; printf("scc inbuf overflow port %d\n", port); } g_scc_overflow = 1; } scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } void scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...) { va_list ap; char *bufptr; int len, c; int i; va_start(ap, fmt); bufptr = malloc(4096); bufptr[0] = 0; vsnprintf(bufptr, 4090, fmt, ap); len = (int)strlen(bufptr); for(i = 0; i < len; i++) { c = bufptr[i]; if(c == 0x0a) { scc_add_to_readbuf(dfcyc, port, 0x0d); } scc_add_to_readbuf(dfcyc, port, c); } va_end(ap); } void scc_transmit(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int out_wrptr, out_rdptr; scc_ptr = &(g_scc[port]); // printf("scc_transmit port:%d val:%02x \n", port, val); /* See if port initialized, if not, do so now */ if(scc_is_port_closed(dfcyc, port)) { printf(" port %d is closed, cur_state:%d\n", port, scc_ptr->cur_state); return; // No working serial port, just toss it and go } if(!scc_ptr->tx_buf_empty) { /* toss character! */ printf("Tossing char\n"); return; } out_wrptr = scc_ptr->out_wrptr; out_rdptr = scc_ptr->out_rdptr; if(scc_ptr->tx_dcycs < 1.0) { if(out_wrptr != out_rdptr) { /* do just one char, then get out */ printf("tx_dcycs < 1\n"); return; } } if(g_serial_mask[port] || (scc_ptr->reg[5] & 0x60) != 0x60) { val = val & 0x7f; } scc_add_to_writebuf(dfcyc, port, val); } void scc_add_to_writebuf(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int out_wrptr, out_wrptr_next, out_rdptr; // printf("scc_add_to_writebuf p:%d, val:%02x\n", port, val); if(scc_is_port_closed(dfcyc, port)) { return; // Port is closed } scc_ptr = &(g_scc[port]); out_wrptr = scc_ptr->out_wrptr; out_rdptr = scc_ptr->out_rdptr; out_wrptr_next = (out_wrptr + 1) & (SCC_OUTBUF_SIZE - 1); if(out_wrptr_next != out_rdptr) { scc_ptr->out_buf[out_wrptr] = val; scc_ptr->out_wrptr = out_wrptr_next; scc_printf("scc wrbuf port %d had char 0x%02x added\n", port, val); g_scc_overflow = 0; } else { if(g_scc_overflow == 0) { g_code_yellow++; printf("scc outbuf overflow port %d\n", port); } g_scc_overflow = 1; } } ================================================ FILE: gsplus/src/scc.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include #ifdef _WIN32 # include #else # include # include # include #endif #if defined(HPUX) || defined(__linux__) || defined(SOLARIS) # define SCC_SOCKETS #endif #if defined(MAC) || defined(__MACH__) || defined(_WIN32) # define SCC_SOCKETS #endif /* my scc port 0 == channel A, port 1 = channel B */ #define SCC_INBUF_SIZE 512 /* must be a power of 2 */ #define SCC_OUTBUF_SIZE 512 /* must be a power of 2 */ #define SCC_MODEM_MAX_CMD_STR 128 #ifndef _WIN32 # define SOCKET int /* For non-Windows */ # define INVALID_SOCKET (-1) /* For non-Windows */ #endif STRUCT(Scc) { int cur_state; int modem_state; SOCKET sockfd; SOCKET rdwrfd; void *sockaddr_ptr; // Socket: pointer to sockaddr struct int sockaddr_size; // Socket: sizeof(sockaddr_in) int unix_dev_fd; // Unix fd to real serial device void *win_com_handle; // Win32 handle to COMx port void *win_dcb_ptr; // Win32 ptr to COMx DCB int read_called_this_vbl; int write_called_this_vbl; byte dcd; byte reg_ptr; byte br_is_zero; byte tx_buf_empty; word32 mode; byte reg[16]; int rx_queue_depth; byte rx_queue[4]; int in_rdptr; int in_wrptr; byte in_buf[SCC_INBUF_SIZE]; int out_rdptr; int out_wrptr; byte out_buf[SCC_OUTBUF_SIZE]; int wantint_rx; int wantint_tx; int wantint_zerocnt; double br_dcycs; double tx_dcycs; double rx_dcycs; int br_event_pending; int rx_event_pending; int tx_event_pending; int char_size; int baud_rate; dword64 out_char_dfcyc; int socket_error; int socket_num_rings; dword64 socket_last_ring_dfcyc; word32 modem_mode; int modem_plus_mode; int modem_s0_val; int modem_s2_val; int telnet_mode; int telnet_iac; word32 telnet_local_mode[2]; word32 telnet_remote_mode[2]; word32 telnet_reqwill_mode[2]; word32 telnet_reqdo_mode[2]; word32 modem_out_portnum; int modem_cmd_len; byte modem_cmd_str[SCC_MODEM_MAX_CMD_STR + 5]; }; #define SCCMODEM_NOECHO 0x0001 #define SCCMODEM_NOVERBOSE 0x0002 ================================================ FILE: gsplus/src/scc_socket_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // This file contains the socket calls for Win32 and Mac/Linux // Win32: see: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ // Modem init string GBBS GSPORT.HST: ats0=1s2=128v0 // Modem init string Warp6: atm0e0v0s0=0s7=25 atx4h0 at&c1&d2 #include "defc.h" #include "scc.h" #include extern int Verbose; extern Scc g_scc[2]; extern int g_serial_cfg[2]; extern char *g_serial_remote_ip[2]; extern int g_serial_remote_port[2]; extern int g_serial_modem_response_code; extern int g_serial_modem_allow_incoming; extern int g_serial_modem_init_telnet; #ifdef _WIN32 #include typedef int socklen_t; #endif #ifndef _WIN32 extern int h_errno; #endif int g_wsastartup_called = 0; #ifndef _WIN32 // SOCKET is defined in scc.h, and is an "int" for non-Windows systems // INVALID_SOCKET is -1 for non-WINDOWS # define closesocket(s) close(s) #endif /* Usage: scc_socket_open() called to init socket mode */ /* At all times, we try to have a listen running on the incoming socket */ /* If we want to dial out, we close the incoming socket and create a new */ /* outgoing socket. Any hang-up causes the socket to be closed and it will */ /* then re-open on a subsequent call to scc_socket_open */ void scc_socket_open(dword64 dfcyc, int port, int cfg) { Scc *scc_ptr; // printf("scc_socket_open p:%d cfg:%d\n", port, cfg); #if defined(_WIN32) && defined(SCC_SOCKETS) WSADATA wsadata; int ret; if(g_wsastartup_called == 0) { ret = WSAStartup(MAKEWORD(2,0), &wsadata); printf("WSAStartup ret: %d\n", ret); g_wsastartup_called = 1; } #endif scc_ptr = &(g_scc[port]); scc_ptr->cur_state = cfg; // successful socket scc_ptr->sockfd = INVALID_SOCKET; /* Indicate no socket open yet */ scc_ptr->rdwrfd = INVALID_SOCKET; /* Indicate no socket open yet */ scc_ptr->modem_state = 0; /* 0 means talk to socket */ /* 1 means talk to modem */ scc_ptr->socket_error = 0; scc_ptr->socket_num_rings = 0; scc_ptr->socket_last_ring_dfcyc = 0; scc_ptr->dcd = 1; /* 1 means carrier */ scc_ptr->modem_s0_val = 0; // Number of rings before answer scc_ptr->sockaddr_size = sizeof(struct sockaddr_in); free(scc_ptr->sockaddr_ptr); scc_ptr->sockaddr_ptr = malloc(scc_ptr->sockaddr_size); // cfg==1: Virtual Modem. All set up, wait for AT commands now // cfg==2: Outgoing connection to g_serial_remote_ip[], do it now // cfg==3: Incoming connection on 6501/6502. Do it now if(cfg == 2) { scc_ptr->modem_state = 0; // Do not open it now, it will be opened when output is // sent to the port } if(cfg == 1) { scc_ptr->modem_state = 1; scc_ptr->dcd = 0; scc_socket_open_incoming(dfcyc, port); } else if(cfg == 3) { scc_ptr->modem_state = 0; scc_socket_open_incoming(dfcyc, port); } } void scc_socket_close(int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET rdwrfd; SOCKET sockfd; int do_init; int i; scc_ptr = &(g_scc[port]); // printf("In scc_socket_close, %d\n", port); rdwrfd = scc_ptr->rdwrfd; do_init = 0; if(rdwrfd != INVALID_SOCKET) { printf("socket_close: rdwrfd=%llx, closing\n", (dword64)rdwrfd); closesocket(rdwrfd); do_init = 1; } sockfd = scc_ptr->sockfd; if((sockfd != INVALID_SOCKET) && (rdwrfd != sockfd)) { printf("socket_close: sockfd=%llx, closing\n", (dword64)sockfd); closesocket(sockfd); do_init = 1; } scc_ptr->modem_state = 0; scc_ptr->socket_num_rings = 0; if(scc_ptr->cur_state == 1) { // Virtual modem scc_ptr->modem_state = 1; scc_ptr->dcd = 0; } if(do_init) { scc_ptr->modem_cmd_len = 0; scc_ptr->telnet_iac = 0; for(i = 0; i < 2; i++) { scc_ptr->telnet_local_mode[i] = 0; scc_ptr->telnet_remote_mode[i] = 0; scc_ptr->telnet_reqwill_mode[i] = 0; scc_ptr->telnet_reqdo_mode[i] = 0; } scc_ptr->rdwrfd = INVALID_SOCKET; scc_ptr->sockfd = INVALID_SOCKET; } #endif } void scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry) { #ifdef SCC_SOCKETS Scc *scc_ptr; // printf("In scc_socket_close_extended, %d, %016llx\n", port, dfcyc); scc_socket_close(port); scc_ptr = &(g_scc[port]); if(scc_ptr->cur_state == 1) { // Virtual modem mode scc_socket_send_modem_code(dfcyc, port, 3); scc_ptr->modem_state = 1; // and go back to modem mode } else if((scc_ptr->cur_state == 2) && !allow_retry) { // Remote IP scc_ptr->cur_state = -2; // Error, give up } #endif } void scc_socket_maybe_open(dword64 dfcyc, int port, int must) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd != INVALID_SOCKET) { // Valid socket. See if we should hang up if((scc_ptr->reg[5] & 0x80) && (scc_ptr->cur_state == 1)) { // Handle DTR forcing modem hang-up now printf("DTR is deasserted, hanging up\n"); scc_socket_close(port); } return; } if(scc_ptr->cur_state == 2) { if(must) { scc_socket_open_outgoing(dfcyc, port, g_serial_remote_ip[port], g_serial_remote_port[port]); } } else { scc_socket_open_incoming(dfcyc, port); } } void scc_socket_open_incoming(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS SOCKET sockfd; struct sockaddr_in sa_in; Scc *scc_ptr; int on, ret, inc; inc = 0; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd != INVALID_SOCKET) { /* it's already open, get out */ return; } //printf("scc_socket_open_incoming: scc socket close being called\n"); scc_socket_close(port); scc_ptr->socket_num_rings = 0; memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); if(scc_ptr->cur_state == 2) { // Outgoing connection only, never accept an incoming connect return; } if(scc_ptr->cur_state == 1) { if(!g_serial_modem_allow_incoming) { // Virtual modem with incoming connections disallowed return; } if(scc_ptr->reg[5] & 0x80) { // DTR is forcing hang-up. Don't open incoming socket return; } } while(1) { sockfd = socket(AF_INET, SOCK_STREAM, 0); //printf("sockfd ret: %llx\n", (dword64)sockfd); if(sockfd == INVALID_SOCKET) { printf("socket ret: -1, errno: %d\n", errno); scc_socket_close(port); scc_ptr->socket_error = -1; return; } /* printf("socket ret: %d\n", sockfd); */ on = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if(ret < 0) { printf("setsockopt REUSEADDR ret: %d, err:%d\n", ret, errno); scc_socket_close(port); return; } memset(&sa_in, 0, sizeof(sa_in)); sa_in.sin_family = AF_INET; sa_in.sin_port = htons(6501 + port + inc); sa_in.sin_addr.s_addr = htonl(INADDR_ANY); ret = bind(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); printf("bind ret:%d\n", ret); if(ret >= 0) { ret = listen(sockfd, 1); break; } /* else ret to bind was < 0 */ printf("bind or listen ret: %d, errno: %d\n", ret, errno); inc++; closesocket(sockfd); printf("Trying next port: %d\n", 6501 + port + inc); if(inc >= 10) { printf("Too many retries, quitting\n"); scc_socket_close(port); scc_ptr->socket_error = -1; return; } } printf("SCC port %d is at unix port %d\n", port, 6501 + port + inc); scc_ptr->sockfd = sockfd; scc_ptr->socket_error = 0; scc_socket_make_nonblock(dfcyc, port); #endif } void scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, int remote_port) { #ifdef SCC_SOCKETS Scc *scc_ptr; struct sockaddr_in sa_in; struct hostent *hostentptr; int on; int ret; SOCKET sockfd; scc_ptr = &(g_scc[port]); // printf("scc socket close being called from socket_open_out\n"); scc_socket_close(port); memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); sockfd = socket(AF_INET, SOCK_STREAM, 0); // printf("outgoing sockfd ret: %llx\n", (dword64)sockfd); if(sockfd == INVALID_SOCKET) { printf("socket ret: %llx, errno: %d\n", (dword64)sockfd, errno); scc_socket_close_extended(dfcyc, port, 0); return; } /* printf("socket ret: %d\n", sockfd); */ on = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if(ret < 0) { printf("setsockopt REUSEADDR ret: %d, err:%d\n", ret, errno); scc_socket_close_extended(dfcyc, port, 0); return; } memset(&sa_in, 0, sizeof(sa_in)); sa_in.sin_family = AF_INET; sa_in.sin_port = htons(remote_port); while(*remote_ip_str == ' ') { remote_ip_str++; // Skip leading blanks } hostentptr = gethostbyname(remote_ip_str); printf("Connecting to %s, port:%d\n", remote_ip_str, remote_port); if(hostentptr == 0) { #ifdef _WIN32 fatal_printf("Lookup host %s failed\n", &scc_ptr->modem_cmd_str[0]); #else fatal_printf("Lookup host %s failed, herrno: %d\n", &scc_ptr->modem_cmd_str[0], h_errno); #endif closesocket(sockfd); scc_socket_close_extended(dfcyc, port, 0); return; } memcpy(&sa_in.sin_addr.s_addr, hostentptr->h_addr, hostentptr->h_length); /* The above copies the 32-bit internet address into */ /* sin_addr.s_addr. It's in correct network format */ ret = connect(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); if(ret < 0) { printf("connect ret: %d, errno: %d\n", ret, errno); closesocket(sockfd); scc_socket_close_extended(dfcyc, port, 0); return; } scc_socket_modem_connect(dfcyc, port); scc_ptr->dcd = 1; /* carrier on */ scc_ptr->modem_state = 0; /* talk to socket */ scc_ptr->socket_num_rings = 0; printf("SCC port %d is now outgoing to %s:%d\n", port, remote_ip_str, remote_port); scc_ptr->sockfd = sockfd; scc_socket_make_nonblock(dfcyc, port); scc_ptr->rdwrfd = scc_ptr->sockfd; #endif } void scc_socket_make_nonblock(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET sockfd; int ret; # ifdef _WIN32 u_long flags; # else int flags; # endif scc_ptr = &(g_scc[port]); sockfd = scc_ptr->sockfd; # ifdef _WIN32 flags = 1; ret = ioctlsocket(sockfd, FIONBIO, &flags); if(ret != 0) { printf("ioctlsocket ret: %d\n", ret); } # else flags = fcntl(sockfd, F_GETFL, 0); if(flags == -1) { printf("fcntl GETFL ret: %d, errno: %d\n", flags, errno); scc_socket_close_extended(dfcyc, port, 0); return; } ret = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); if(ret == -1) { printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); scc_socket_close_extended(dfcyc, port, 0); return; } # endif #endif } void scc_accept_socket(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET rdwrfd; socklen_t address_len; int flags, num_rings, ret; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd == INVALID_SOCKET) { // printf("in accept_socket, call socket_open\n"); scc_socket_open_incoming(dfcyc, port); } if(scc_ptr->sockfd == INVALID_SOCKET) { return; /* just give up */ } if(scc_ptr->rdwrfd == INVALID_SOCKET) { address_len = scc_ptr->sockaddr_size; rdwrfd = accept(scc_ptr->sockfd, scc_ptr->sockaddr_ptr, &address_len); scc_ptr->sockaddr_size = (int)address_len; if(rdwrfd == INVALID_SOCKET) { return; } flags = 0; ret = 0; #ifndef _WIN32 /* For Linux, we need to set O_NONBLOCK on the rdwrfd */ flags = fcntl(rdwrfd, F_GETFL, 0); if(flags == -1) { printf("fcntl GETFL ret: %d, errno: %d\n", flags,errno); return; } ret = fcntl(rdwrfd, F_SETFL, flags | O_NONBLOCK); if(ret == -1) { printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); return; } #endif scc_ptr->rdwrfd = rdwrfd; printf("Set port[%d].rdwrfd = %llx\n", port, (dword64)rdwrfd); num_rings = 4; if(scc_ptr->modem_s0_val > 0) { num_rings = scc_ptr->modem_s0_val; } scc_ptr->socket_num_rings = num_rings; scc_ptr->socket_last_ring_dfcyc = 0; /* do ring now*/ /* and send some telnet codes */ if(g_serial_modem_init_telnet) { scc_ptr->telnet_reqwill_mode[0] = 0xa; scc_ptr->telnet_reqdo_mode[0] = 0xa; /* 3=GO_AH and 1=ECHO */ scc_ptr->telnet_reqdo_mode[1] = 0x4; // 34=LINEMODE } printf("Telnet reqwill and reqdo's initialized: %08x_%08x," "%08x_%08x\n", scc_ptr->telnet_reqwill_mode[1], scc_ptr->telnet_reqwill_mode[0], scc_ptr->telnet_reqdo_mode[1], scc_ptr->telnet_reqdo_mode[0]); scc_socket_modem_do_ring(dfcyc, port); } #endif } void scc_socket_telnet_reqs(dword64 dfcyc, int port) { Scc *scc_ptr; word32 mask, willmask, domask; int i, j; scc_ptr = &(g_scc[port]); for(i = 0; i < 64; i++) { j = i >> 5; mask = 1 << (i & 31); willmask = scc_ptr->telnet_reqwill_mode[j]; if(willmask & mask) { scc_printf("Telnet reqwill %d\n", i); scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfb); /* WILL */ scc_add_to_writebuf(dfcyc, port, i); } domask = scc_ptr->telnet_reqdo_mode[j]; if(domask & mask) { scc_printf("Telnet reqdo %d\n", i); scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfd); /* DO */ scc_add_to_writebuf(dfcyc, port, i); } } } void scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left) { #ifdef SCC_SOCKETS byte tmp_buf[256]; Scc *scc_ptr; SOCKET rdwrfd; int ret; int i; scc_ptr = &(g_scc[port]); scc_accept_socket(dfcyc, port); scc_socket_modem_do_ring(dfcyc, port); if(scc_ptr->modem_state) { /* Just get out, this is modem mode */ /* The transmit function stuffs any bytes in receive buf */ return; } rdwrfd = scc_ptr->rdwrfd; if(rdwrfd == INVALID_SOCKET) { return; /* just get out */ } /* Try reading some bytes */ space_left = MY_MIN(space_left, 256); ret = (int)recv(rdwrfd, tmp_buf, space_left, 0); if(ret > 0) { for(i = 0; i < ret; i++) { if(tmp_buf[i] == 0) { /* Skip null chars */ continue; } scc_socket_recvd_char(dfcyc, port, tmp_buf[i]); } } else if(ret == 0) { /* assume socket close */ printf("recv got 0 from rdwrfd=%llx, closing\n", (dword64)rdwrfd); scc_socket_close_extended(dfcyc, port, 1); } #endif } void scc_socket_recvd_char(dword64 dfcyc, int port, int c) { Scc *scc_ptr; word32 locmask, remmask, mask, reqwillmask, reqdomask; int telnet_mode, telnet_iac, eff_c, cpos, reply; scc_ptr = &(g_scc[port]); telnet_mode = scc_ptr->telnet_mode; telnet_iac = scc_ptr->telnet_iac; eff_c = c; if(scc_ptr->cur_state == 2) { // Outgoing only connection, support 8-bit transfers with // no telnet command support telnet_mode = 0; telnet_iac = 0; } else if(telnet_iac == 0) { if(c == 0xff) { scc_ptr->telnet_iac = 0xff; return; /* and just get out */ } } else { /* last char was 0xff, see if this is 0xff */ if(c != 0xff) { /* this is some kind of command */ eff_c = eff_c | 0x100; /* indicate prev char IAC */ } } scc_ptr->telnet_iac = 0; mask = 1 << (c & 31); cpos = (c >> 5) & 1; locmask = scc_ptr->telnet_local_mode[cpos] & mask; remmask = scc_ptr->telnet_remote_mode[cpos] & mask; reqwillmask = scc_ptr->telnet_reqwill_mode[cpos] & mask; reqdomask = scc_ptr->telnet_reqdo_mode[cpos] & mask; switch(telnet_mode) { case 0: /* just passing through bytes */ switch(eff_c) { case 0x1fe: /* DON'T */ case 0x1fd: /* DO */ case 0x1fc: /* WON'T */ case 0x1fb: /* WILL */ case 0x1fa: /* SB */ telnet_mode = c; break; default: if(eff_c < 0x100) { scc_add_to_readbuf(dfcyc, port, c); } break; } break; case 3: /* LINEMODE SB SLC, octet 0 */ if(eff_c == 0x1f0) { /* SE, the end */ telnet_mode = 0; } scc_printf("LINEMODE SLC octet 0: %02x\n", c); telnet_mode = 4; break; case 4: /* LINEMODE SB SLC, octet 1 */ scc_printf("LINEMODE SLC octet 1: %02x\n", c); telnet_mode = 5; if(eff_c == 0x1f0) { /* SE, the end */ scc_printf("Got SE at octet 1...\n"); telnet_mode = 0; } break; case 5: /* LINEMODE SB SLC, octet 2 */ scc_printf("LINEMODE SLC octet 2: %02x\n", c); telnet_mode = 3; if(eff_c == 0x1f0) { /* SE, the end */ printf("Got Telnet SE at octet 2...\n"); telnet_mode = 0; } break; case 34: /* LINEMODE SB beginning */ switch(c) { case 3: /* SLC */ telnet_mode = 3; break; default: telnet_mode = 0xee; /* go to SB eat */ break; } break; case 0xfa: /* in 0xfa = SB mode, eat up subcommands */ switch(c) { case 34: /* LINEMODE */ telnet_mode = 34; break; default: telnet_mode = 0xee; /* SB eat mode */ break; } break; case 0xee: /* in SB eat mode */ if(eff_c == 0x1f0) { /* SE, end of sub-command */ telnet_mode = 0; } else { /* just stay in eat mode */ } break; case 0xfe: /* previous char was "DON'T" */ if(locmask && (reqwillmask == 0)) { /* it's a mode change */ /* always OK to turn off a mode that we had on */ scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfc); /* WON'T */ scc_add_to_writebuf(dfcyc, port, c); } scc_ptr->telnet_local_mode[cpos] &= ~mask; scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfd: /* previous char was "DO" */ reply = 0xfc; if(locmask == 0 && (reqwillmask == 0)) { /* it's a mode change, send some response */ reply = 0xfc; /* nack it with WON'T */ if(c == 0x03 || c == 0x01) { reply = 0xfb; /* Ack with WILL */ } scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, reply); scc_add_to_writebuf(dfcyc, port, c); } if(reqwillmask || (reply == 0xfb)) { scc_ptr->telnet_local_mode[cpos] |= mask; } scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfc: /* previous char was "WON'T" */ if(remmask && (reqdomask == 0)) { /* it's a mode change, ack with DON'T */ scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfe); /* DON'T */ scc_add_to_writebuf(dfcyc, port, c); } scc_ptr->telnet_remote_mode[cpos] &= ~mask; scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfb: /* previous char was "WILL" */ reply = 0xfe; /* nack it with DON'T */ if(remmask == 0 && (reqdomask == 0)) { /* it's a mode change, send some response */ if(c == 0x03 || c == 0x01) { reply = 0xfd; /* Ack with DO */ } scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, reply); scc_add_to_writebuf(dfcyc, port, c); } if(reqdomask || (reply == 0xfd)) { scc_ptr->telnet_remote_mode[cpos] |= mask; } scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; telnet_mode = 0; break; default: telnet_mode = 0; break; } scc_ptr->telnet_mode = telnet_mode; } void scc_socket_empty_writebuf(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS # ifndef _WIN32 struct sigaction newact, oldact; # endif Scc *scc_ptr; dword64 diff_dusec; SOCKET rdwrfd; int plus_mode, plus_char, rdptr, wrptr, done, ret, len, c; int i; scc_socket_maybe_open(dfcyc, port, 0); scc_ptr = &(g_scc[port]); /* See if +++ done and we should go to command mode */ diff_dusec = (dfcyc - scc_ptr->out_char_dfcyc) >> 16; if((diff_dusec > 900LL*1000) && (scc_ptr->modem_plus_mode == 3) && (scc_ptr->modem_state == 0) && (scc_ptr->cur_state == 1)) { scc_ptr->modem_state = 1; /* go modem mode, stay connect*/ scc_ptr->modem_plus_mode = 0; scc_socket_send_modem_code(dfcyc, port, 0); // "OK" } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { done = 1; break; } rdwrfd = scc_ptr->rdwrfd; len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } //printf("Writing data, %d bytes, modem_state:%d\n", len, // scc_ptr->modem_state); if(len <= 0) { done = 1; break; } scc_socket_maybe_open(dfcyc, port, 1); if(scc_ptr->modem_state) { len = 1; scc_socket_modem_write(dfcyc, port, scc_ptr->out_buf[rdptr]); scc_ptr->write_called_this_vbl = 0; ret = 1; } else { if(rdwrfd == INVALID_SOCKET) { return; } plus_char = scc_ptr->modem_s2_val; if((plus_char == 0) || (plus_char >= 128)) { plus_char = 0xfff; // Invalid scc_ptr->modem_plus_mode = 0; } // Look for '+++' within .8 seconds for(i = 0; i < len; i++) { c = scc_ptr->out_buf[rdptr + i]; plus_mode = scc_ptr->modem_plus_mode; diff_dusec = (dfcyc - scc_ptr->out_char_dfcyc) >> 16; if(c == plus_char && plus_mode == 0) { if(diff_dusec > 500LL*1000) { scc_ptr->modem_plus_mode = 1; } } else if(c == plus_char) { if(diff_dusec < 800LL*1000) { plus_mode++; scc_ptr->modem_plus_mode = plus_mode; } } else { scc_ptr->modem_plus_mode = 0; } scc_ptr->out_char_dfcyc = dfcyc; } # ifndef _WIN32 // ignore SIGPIPE around writes to the socket, so we // can catch a closed socket and prepare to accept // a new connection. Otherwise, SIGPIPE kills KEGS sigemptyset(&newact.sa_mask); newact.sa_handler = SIG_IGN; newact.sa_flags = 0; sigaction(SIGPIPE, &newact, &oldact); # endif ret = (int)send(rdwrfd, &(scc_ptr->out_buf[rdptr]), len, 0); # ifndef _WIN32 sigaction(SIGPIPE, &oldact, 0); /* restore previous SIGPIPE behavior */ # endif #if 0 printf("sock output: %02x, len:%d\n", scc_ptr->out_buf[rdptr], len); #endif } if(ret == 0) { done = 1; /* give up for now */ break; } else if(ret < 0) { /* assume socket is dead */ printf("socket write failed, resuming modem mode\n"); scc_socket_close_extended(dfcyc, port, 1); done = 1; break; } else { rdptr = rdptr + ret; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } #endif } void scc_socket_modem_write(dword64 dfcyc, int port, int c) { Scc *scc_ptr; char *str; word32 modem_mode; int do_echo, got_at, len; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd == INVALID_SOCKET) { scc_socket_open_incoming(dfcyc, port); } modem_mode = scc_ptr->modem_mode; str = (char *)&(scc_ptr->modem_cmd_str[0]); do_echo = ((modem_mode & SCCMODEM_NOECHO) == 0); len = scc_ptr->modem_cmd_len; got_at = 0; #if 0 printf("T:%016llx M[%d][%d]: %02x\n", dfcyc, port, len, c); #endif if(len >= 2 && str[0] == 'a' && str[1] == 't') { /* we've got an 'at', do not back up past it */ got_at = 1; } if(c == 0x0d) { if(do_echo) { scc_add_to_readbuf(dfcyc, port, c); /* echo cr */ scc_add_to_readbuf(dfcyc, port, 0x0a); /* echo lf */ } do_echo = 0; /* already did the echo */ scc_socket_do_cmd_str(dfcyc, port); scc_ptr->modem_cmd_len = 0; len = 0; str[0] = 0; } else if(c == 0x08) { if(len <= 0) { do_echo = 0; /* do not go past left margin */ } else if(len == 2 && got_at) { do_echo = 0; /* do not erase "AT" */ } else { /* erase a character */ len--; str[len] = 0; } } else if(c < 0x20) { /* ignore all control characters, don't echo */ /* includes line feeds and nulls */ do_echo = 0; } else { /* other characters */ if(len < SCC_MODEM_MAX_CMD_STR) { str[len] = tolower(c); str[len+1] = 0; len++; } } scc_ptr->modem_cmd_len = len; if(do_echo) { scc_add_to_readbuf(dfcyc, port, c); /* echo */ } } void scc_socket_do_cmd_str(dword64 dfcyc, int port) { Scc *scc_ptr; char *str; int pos, len, ret_val, reg, reg_val, was_amp, out_port, c; int i; scc_ptr = &(g_scc[port]); str = (char *)&(scc_ptr->modem_cmd_str[0]); printf("Got modem string :%s:=%02x %02x %02x\n", str, str[0], str[1], str[2]); len = scc_ptr->modem_cmd_len; str[len] = 0; str[len+1] = 0; str[len+2] = 0; pos = -1; if(len < 2) { /* just ignore it */ return; } if(str[0] != 'a' || str[1] != 't') { return; } /* Some AT command received--make sure socket 6501/6502 is open */ printf("Some AT command received, sockfd=%llx\n", (dword64)scc_ptr->sockfd); pos = 2 - 1; ret_val = 0; /* "OK" */ was_amp = 0; while(++pos < len) { c = str[pos] + was_amp; was_amp = 0; switch(c) { case '&': /* at& */ was_amp = 0x100; break; case 'z': /* atz */ scc_ptr->modem_mode = 0; scc_ptr->modem_s0_val = 0; pos = len; /* ignore any other commands */ break; case 'e': /* ate = echo */ c = str[pos+1]; if(c == '1') { scc_ptr->modem_mode &= ~SCCMODEM_NOECHO; pos++; } else { scc_ptr->modem_mode |= SCCMODEM_NOECHO; pos++; } break; case 'v': /* atv = verbose */ c = str[pos+1]; if(c == '1') { scc_ptr->modem_mode &= ~SCCMODEM_NOVERBOSE; pos++; } else { scc_ptr->modem_mode |= SCCMODEM_NOVERBOSE; pos++; } break; case 'o': /* ato = go online */ printf("ato\n"); if(scc_ptr->dcd && scc_ptr->modem_state && (scc_ptr->rdwrfd != INVALID_SOCKET)) { printf("Going back online\n"); scc_socket_modem_connect(dfcyc, port); scc_ptr->modem_state = 0; // talk to socket ret_val = -1; } break; case 'h': /* ath = hang up */ printf("ath, hanging up\n"); scc_socket_close(port); if(scc_ptr->rdwrfd != INVALID_SOCKET) { scc_socket_close_extended(dfcyc, port, 0); } /* scc_socket_maybe_open_incoming(dfcyc, port); */ /* reopen listen */ break; case 'a': /* ata */ printf("Doing ATA\n"); scc_socket_do_answer(dfcyc, port); ret_val = -1; break; case 'd': /* atd */ scc_ptr->modem_out_portnum = 23; pos++; c = str[pos]; if(c == 't' || c == 'p') { /* skip tone or pulse */ pos++; } /* see if it is 111 */ if(strcmp(&str[pos], "111") == 0) { /* Do PPP! */ } else { /* get string to connect to */ /* Shift string so hostname moves to str[0] */ for(i = 0; i < len; i++) { c = str[pos]; if(c == ':') { /* get port number now */ out_port = (int)strtol( &str[pos+1], 0, 10); if(out_port <= 1) { out_port = 23; } scc_ptr->modem_out_portnum = out_port; c = 0; } str[i] = c; if((pos >= len) || (c == 0)) { break; } pos++; } } scc_socket_open_outgoing(dfcyc, port, (char *)&scc_ptr->modem_cmd_str[0], scc_ptr->modem_out_portnum); ret_val = -1; pos = len; /* always eat rest of the line */ break; case 's': /* atsnn=yy */ pos++; reg = 0; while(1) { c = str[pos]; if(c < '0' || c > '9') { break; } reg = (reg * 10) + c - '0'; pos++; } if(c == '?') { /* display S-register */ if(reg == 0) { scc_add_to_readbufv(dfcyc, port, "S0=%d\n", scc_ptr->modem_s0_val); } break; } if(c != '=') { break; } pos++; reg_val = 0; while(1) { c = str[pos]; if(c < '0' || c >'9') { break; } reg_val = (reg_val * 10) + c - '0'; pos++; } printf("ats%d = %d\n", reg, reg_val); if(reg == 0) { scc_ptr->modem_s0_val = reg_val; } if(reg == 2) { scc_ptr->modem_s2_val = reg_val; } pos--; break; default: /* some command--peek into next chars to finish it */ while(1) { c = str[pos+1]; if(c >= '0' && c <= '9') { /* eat numbers */ pos++; continue; } if(c == '=') { /* eat this as well */ pos++; continue; } /* else get out */ break; } } } if(ret_val >= 0) { scc_socket_send_modem_code(dfcyc, port, ret_val); } } void scc_socket_send_modem_code(dword64 dfcyc, int port, int code) { Scc *scc_ptr; char *str; word32 modem_mode; scc_ptr = &(g_scc[port]); switch(code) { case 0: str = "OK"; break; case 1: str = "CONNECT"; break; case 2: str = "RING"; break; case 3: str = "NO CARRIER"; break; case 4: str = "ERROR"; break; case 5: str = "CONNECT 1200"; break; case 10: str = "CONNECT 2400"; break; case 12: str = "CONNECT 9600"; break; // Generic AT docs/Warp6 BBS case 13: str = "CONNECT 9600"; break; // US Robotics Sportster/HST case 14: str = "CONNECT 19200"; break; // Warp6 BBS case 16: str = "CONNECT 19200"; break; // Generic AT docs case 25: str = "CONNECT 14400"; break; // US Robotics Sportster case 85: str = "CONNECT 19200"; break; // US Robotics Sportster case 18: str = "CONNECT 57600"; break; // Generic AT docs/Warp6 BBS case 28: str = "CONNECT 38400"; break; // Warp6 BBS/Hayes default: str = "ERROR"; } printf("Sending modem code %d = %s\n", code, str); modem_mode = scc_ptr->modem_mode; if(modem_mode & SCCMODEM_NOVERBOSE) { /* just the number */ scc_add_to_readbufv(dfcyc, port, "%d", code); scc_add_to_readbuf(dfcyc, port, 0x0d); } else { scc_add_to_readbufv(dfcyc, port, "%s\n", str); } } void scc_socket_modem_connect(dword64 dfcyc, int port) { // Send code telling Term program the connect speed if(g_scc[port].cur_state == 1) { // Virtual modem scc_socket_send_modem_code(dfcyc, port, g_serial_modem_response_code); /*28=38400*/ } } void scc_socket_modem_do_ring(dword64 dfcyc, int port) { Scc *scc_ptr; dword64 diff_dusecs; int num_rings; scc_ptr = &(g_scc[port]); num_rings = scc_ptr->socket_num_rings; if((num_rings > 0) && scc_ptr->modem_state) { num_rings--; diff_dusecs = (dfcyc - scc_ptr->socket_last_ring_dfcyc) >> 16; if(diff_dusecs < 2LL*1000*1000) { return; /* nothing more to do */ } scc_ptr->socket_num_rings = num_rings; scc_ptr->socket_last_ring_dfcyc = dfcyc; if(num_rings <= 0) { /* decide on answering */ if(scc_ptr->modem_s0_val) { scc_socket_do_answer(dfcyc, port); } else { printf("No answer, closing socket\n"); scc_socket_close(port); } } else { scc_socket_send_modem_code(dfcyc, port, 2); /* RING */ } } } void scc_socket_do_answer(dword64 dfcyc, int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); scc_accept_socket(dfcyc, port); if(scc_ptr->rdwrfd == INVALID_SOCKET) { printf("Answer when rdwrfd=-1, closing\n"); scc_socket_close_extended(dfcyc, port, 0); /* send NO CARRIER message */ } else { scc_socket_telnet_reqs(dfcyc, port); printf("Send telnet reqs\n"); if(scc_ptr->modem_state) { scc_socket_modem_connect(dfcyc, port); } scc_ptr->modem_state = 0; // Talk to socket scc_ptr->dcd = 1; // carrier on scc_ptr->socket_num_rings = 0; } } ================================================ FILE: gsplus/src/scc_unixdriver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* This file contains the Mac/Linux calls to a real serial port */ #include "defc.h" #include "scc.h" #ifndef _WIN32 # include #endif extern Scc g_scc[2]; extern word32 g_c025_val; extern char *g_serial_device[2]; #ifndef _WIN32 void scc_serial_unix_open(int port) { Scc *scc_ptr; int fd; scc_ptr = &(g_scc[port]); fd = open(&g_serial_device[port][0], O_RDWR | O_NONBLOCK); scc_ptr->unix_dev_fd = fd; printf("scc_serial_unix_init %d called, fd: %d\n", port, fd); if(fd < 0) { scc_ptr->unix_dev_fd = -1; scc_ptr->cur_state = -1; // Failed to open return; } scc_serial_unix_change_params(port); scc_ptr->cur_state = 0; // Actual Serial device } void scc_serial_unix_close(int port) { Scc *scc_ptr; int fd; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd >= 0) { close(fd); } scc_ptr->unix_dev_fd = -1; } void scc_serial_unix_change_params(int port) { struct termios termios_buf; Scc *scc_ptr; int fd, csz, ret; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; printf("scc_serial_unix_change_parms port: %d, fd: %d\n", port, fd); if(fd <= 0) { return; } ret = tcgetattr(fd, &termios_buf); if(ret != 0) { printf("tcgetattr port%d ret: %d\n", port, ret); } #if 1 printf("baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, (int)termios_buf.c_lflag); #endif memset(&termios_buf, 0, sizeof(struct termios)); cfmakeraw(&termios_buf); cfsetspeed(&termios_buf, scc_ptr->baud_rate); csz = scc_ptr->char_size; termios_buf.c_cflag = CREAD | CLOCAL; termios_buf.c_cflag |= (csz == 5) ? CS5 : (csz == 6) ? CS6 : (csz == 7) ? CS7 : CS8; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 2: // 1.5 stop bits termios_buf.c_cflag |= CSTOPB; /* no 1.5 stop bit setting.*/ break; case 3: // 2 stop bits termios_buf.c_cflag |= CSTOPB; break; } switch((scc_ptr->reg[4]) & 0x3) { case 1: // Odd parity termios_buf.c_cflag |= (PARENB | PARODD); break; case 3: // Even parity termios_buf.c_cflag |= PARENB; break; } /* always enabled DTR and RTS control */ #ifdef CRTSCTS termios_buf.c_cflag |= CRTSCTS; // Linux: CTS/RTS #endif #ifdef CRTS_IFLOW termios_buf.c_cflag |= CDTR_IFLOW | CRTS_IFLOW; // Mac: CTS/RTS #endif printf("fd: %d, baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", fd, (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, (int)termios_buf.c_lflag); ret = tcsetattr(fd, TCSANOW, &termios_buf); if(ret != 0) { printf("tcsetattr ret: %d\n", ret); } } void scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left) { byte tmp_buf[256]; Scc *scc_ptr; int fd, ret, flags, dcd; int i; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd <= 0) { return; } /* Try reading some bytes */ space_left = MY_MIN(space_left, 256); ret = read(fd, tmp_buf, space_left); if(ret > 0) { for(i = 0; i < ret; i++) { scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); } } flags = 0; dcd = 0; #if defined(TIOCMGET) && defined(TIOCM_CAR) ret = ioctl(fd, TIOCMGET, &flags); if(ret == 0) { dcd = 0; if(flags & TIOCM_CAR) { // DCD dcd = 1; } scc_ptr->dcd = dcd; } #endif } void scc_serial_unix_empty_writebuf(int port) { Scc *scc_ptr; int fd, rdptr, wrptr, done, ret, len; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd <= 0) { return; } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { //printf("...rdptr == wrptr\n"); done = 1; break; } len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } if(len <= 0) { done = 1; break; } ret = write(fd, &(scc_ptr->out_buf[rdptr]), len); if(ret <= 0) { done = 1; break; } else { rdptr = rdptr + ret; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } } #endif /* !_WIN32 */ ================================================ FILE: gsplus/src/scc_windriver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ /* This file contains the Win32 COM1/COM2 calls */ #include "defc.h" #include "scc.h" extern Scc g_scc[2]; extern word32 g_c025_val; extern int g_serial_win_device[2]; #ifdef _WIN32 void scc_serial_win_open(int port) { COMMTIMEOUTS commtimeouts; char str_buf[32]; Scc *scc_ptr; HANDLE com_handle; int ret; scc_ptr = &(g_scc[port]); snprintf(&str_buf[0], sizeof(str_buf), "COM%d", g_serial_win_device[port]); com_handle = CreateFile(&str_buf[0], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); scc_ptr->win_com_handle = com_handle; printf("scc_serial_win_init %d called, com_handle: %p\n", port, com_handle); if(com_handle == INVALID_HANDLE_VALUE) { scc_ptr->cur_state = -1; // Failed to open return; } scc_ptr->win_dcb_ptr = malloc(sizeof(DCB)); scc_serial_win_change_params(port); commtimeouts.ReadIntervalTimeout = MAXDWORD; commtimeouts.ReadTotalTimeoutMultiplier = 0; commtimeouts.ReadTotalTimeoutConstant = 0; commtimeouts.WriteTotalTimeoutMultiplier = 0; commtimeouts.WriteTotalTimeoutConstant = 10; ret = SetCommTimeouts(com_handle, &commtimeouts); if(ret == 0) { printf("setcommtimeout ret: %d\n", ret); } scc_ptr->cur_state = 0; // COM* is open } void scc_serial_win_close(int port) { Scc *scc_ptr; HANDLE com_handle; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; scc_ptr->win_com_handle = INVALID_HANDLE_VALUE; if(com_handle == INVALID_HANDLE_VALUE) { return; } CloseHandle(com_handle); free(scc_ptr->win_dcb_ptr); scc_ptr->win_dcb_ptr = 0; } void scc_serial_win_change_params(int port) { DCB *dcbptr; HANDLE com_handle; Scc *scc_ptr; int ret; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; dcbptr = scc_ptr->win_dcb_ptr; if((com_handle == INVALID_HANDLE_VALUE) || (scc_ptr->cur_state != 0)) { return; } ret = GetCommState(com_handle, dcbptr); if(ret == 0) { printf("getcomm port%d ret: %d\n", port, ret); } #if 1 printf("dcb.baudrate: %d, bytesize:%d, stops:%d, parity:%d\n", (int)dcbptr->BaudRate, (int)dcbptr->ByteSize, (int)dcbptr->StopBits, (int)dcbptr->Parity); printf("dcb.binary: %d, ctsflow: %d, dsrflow: %d, dtr: %d, dsr: %d\n", (int)dcbptr->fBinary, (int)dcbptr->fOutxCtsFlow, (int)dcbptr->fOutxDsrFlow, (int)dcbptr->fDtrControl, (int)dcbptr->fDsrSensitivity); printf("dcb.txonxoff:%d, outx:%d, inx: %d, null: %d, rts: %d\n", (int)dcbptr->fTXContinueOnXoff, (int)dcbptr->fOutX, (int)dcbptr->fInX, (int)dcbptr->fNull, (int)dcbptr->fRtsControl); printf("dcb.fAbortOnErr:%d, fParity:%d\n", (int)dcbptr->fAbortOnError, (int)dcbptr->fParity); #endif dcbptr->fAbortOnError = 0; dcbptr->BaudRate = scc_ptr->baud_rate; dcbptr->ByteSize = scc_ptr->char_size; dcbptr->StopBits = ONESTOPBIT; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 2: // 1.5 stop bits dcbptr->StopBits = ONE5STOPBITS; break; case 3: // 2 stop bits dcbptr->StopBits = TWOSTOPBITS; break; } dcbptr->Parity = NOPARITY; switch((scc_ptr->reg[4]) & 0x3) { case 1: // Odd parity dcbptr->Parity = ODDPARITY; break; case 3: // Even parity dcbptr->Parity = EVENPARITY; break; } dcbptr->fNull = 0; dcbptr->fDtrControl = DTR_CONTROL_ENABLE; dcbptr->fDsrSensitivity = 0; dcbptr->fOutxCtsFlow = 0; dcbptr->fOutxDsrFlow = 0; dcbptr->fParity = 0; dcbptr->fInX = 0; dcbptr->fOutX = 0; dcbptr->fRtsControl = RTS_CONTROL_ENABLE; ret = SetCommState(com_handle, dcbptr); if(ret == 0) { printf("SetCommState ret: %d, new baud: %d\n", ret, (int)dcbptr->BaudRate); } } void scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left) { byte tmp_buf[256]; Scc *scc_ptr; HANDLE com_handle; DWORD bytes_read; int ret; int i; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; if(com_handle == INVALID_HANDLE_VALUE) { return; } /* Try reading some bytes */ space_left = MY_MIN(256, space_left); ret = ReadFile(com_handle, tmp_buf, space_left, &bytes_read, NULL); if(ret == 0) { printf("ReadFile ret 0\n"); } if(ret && (bytes_read > 0)) { for(i = 0; i < (int)bytes_read; i++) { scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); } } } void scc_serial_win_empty_writebuf(int port) { Scc *scc_ptr; HANDLE com_handle; DWORD bytes_written; word32 err_code; int rdptr, wrptr, done, ret, len; scc_ptr = &(g_scc[port]); //printf("win_empty_writebuf, com_h: %d\n", scc_ptr->win_com_handle); com_handle = scc_ptr->win_com_handle; if(com_handle == INVALID_HANDLE_VALUE) { return; } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { //printf("...rdptr == wrptr\n"); done = 1; break; } len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } if(len <= 0) { done = 1; break; } bytes_written = 1; ret = WriteFile(com_handle, &(scc_ptr->out_buf[rdptr]), len, &bytes_written, NULL); printf("WriteFile ret: %d, bytes_written:%d, len:%d\n", ret, (int)bytes_written, len); err_code = (word32)-1; if(ret == 0) { err_code = (word32)GetLastError(); printf("WriteFile ret:0, err_code: %08x\n", err_code); } if(ret == 0 || (bytes_written == 0)) { done = 1; break; } else { rdptr = rdptr + bytes_written; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } } #endif ================================================ FILE: gsplus/src/sim65816.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include #define INCLUDE_RCSID_C #include "defc.h" #undef INCLUDE_RCSID_C double g_dtime_sleep = 0; double g_dtime_in_sleep = 0; extern char *g_argv0_path; #define MAX_EVENTS 64 /* All EV_* must be less than 256, since upper bits reserved for other use */ /* e.g., DOC_INT uses upper bits to encode oscillator */ #define EV_60HZ 1 #define EV_STOP 2 #define EV_SCAN_INT 3 #define EV_DOC_INT 4 #define EV_VBL_INT 5 #define EV_SCC 6 #define EV_VID_UPD 7 #define EV_MOCKINGBOARD 8 extern int g_stepping; extern word32 g_c068_statereg; extern int g_cur_a2_stat; extern word32 g_c02d_int_crom; extern word32 g_c035_shadow_reg; extern word32 g_c036_val_speed; extern word32 g_c023_val; extern word32 g_c041_val; extern word32 g_c046_val; extern word32 g_zipgs_reg_c059; extern word32 g_zipgs_reg_c05a; extern word32 g_zipgs_reg_c05b; extern word32 g_zipgs_unlock; extern Iwm g_iwm; Engine_reg engine; extern word32 table8[]; extern word32 table16[]; extern byte doc_ram[]; extern int g_iwm_motor_on; extern int g_fast_disk_emul; extern int g_slow_525_emul_wr; extern int g_config_control_panel; extern int g_audio_enable; extern int g_preferred_rate; int g_a2_fatal_err = 0; dword64 g_dcycles_end = 0; int g_halt_sim = 0; int g_rom_version = -1; int g_user_halt_bad = 0; int g_halt_on_bad_read = 0; int g_ignore_bad_acc = 1; int g_ignore_halts = 1; int g_code_red = 0; int g_code_yellow = 0; int g_emul_6502_ind_page_cross_bug = 0; int g_config_iwm_vbl_count = 0; const char g_kegs_version_str[] = "1.38"; dword64 g_last_vbl_dfcyc = 0; dword64 g_cur_dfcyc = 1; double g_last_vbl_dadjcycs = 0; double g_dadjcycs = 0; int g_wait_pending = 0; int g_stp_pending = 0; extern int g_irq_pending; int g_num_irq = 0; int g_num_brk = 0; int g_num_cop = 0; int g_num_enter_engine = 0; int g_io_amt = 0; int g_engine_action = 0; int g_engine_recalc_event = 0; int g_engine_scan_int = 0; int g_engine_doc_int = 0; #define MAX_FATAL_LOGS 20 int g_debug_file_fd = -1; int g_fatal_log = -1; char *g_fatal_log_strs[MAX_FATAL_LOGS]; word32 stop_run_at; int g_25sec_cntr = 0; int g_1sec_cntr = 0; double g_dnatcycs_1sec = 0.0; word32 g_natcycs_lastvbl = 0; int Verbose = 0; int Halt_on = 0; word32 g_mem_size_base = 128*1024; /* size of motherboard memory */ word32 g_mem_size_exp = 8*1024*1024; /* size of expansion RAM card */ word32 g_mem_size_total = 128*1024; /* Total contiguous RAM from 0 */ extern word32 g_slow_mem_changed[]; byte *g_slow_memory_ptr = 0; byte *g_memory_ptr = 0; byte *g_dummy_memory1_ptr = 0; byte *g_rom_fc_ff_ptr = 0; byte *g_rom_cards_ptr = 0; void *g_memory_alloc_ptr = 0; /* for freeing memory area */ Page_info page_info_rd_wr[2*65536 + PAGE_INFO_PAD_SIZE]; word32 g_word32_tmp = 0; int g_force_depth = -1; int g_use_shmem = 1; extern word32 g_cycs_in_40col; extern word32 g_cycs_in_xredraw; extern word32 g_cycs_in_refresh_line; extern word32 g_cycs_outside_run_16ms; extern word32 g_refresh_bytes_xfer; extern int g_num_snd_plays; extern int g_num_recalc_snd_parms; extern int g_doc_vol; extern int g_status_refresh_needed; int sim_get_force_depth() { return g_force_depth; } int sim_get_use_shmem() { return g_use_shmem; } void sim_set_use_shmem(int use_shmem) { g_use_shmem = use_shmem; } #define TOOLBOX_LOG_LEN 64 int g_toolbox_log_pos = 0; word32 g_toolbox_log_array[TOOLBOX_LOG_LEN][8]; word32 toolbox_debug_4byte(word32 addr) { word32 part1, part2; /* If addr looks safe, use it */ if(addr > 0xbffc) { return (word32)-1; } part1 = get_memory16_c(addr); part1 = (part1 >> 8) + ((part1 & 0xff) << 8); part2 = get_memory16_c(addr+2); part2 = (part2 >> 8) + ((part2 & 0xff) << 8); return (part1 << 16) + part2; } void toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr) { int pos; pos = g_toolbox_log_pos; stack += 9; g_toolbox_log_array[pos][0] = (word32) ((g_last_vbl_dfcyc + *dcyc_ptr) >> 16); g_toolbox_log_array[pos][1] = stack+1; g_toolbox_log_array[pos][2] = xreg; g_toolbox_log_array[pos][3] = toolbox_debug_4byte(stack+1); g_toolbox_log_array[pos][4] = toolbox_debug_4byte(stack+5); g_toolbox_log_array[pos][5] = toolbox_debug_4byte(stack+9); g_toolbox_log_array[pos][6] = toolbox_debug_4byte(stack+13); g_toolbox_log_array[pos][7] = toolbox_debug_4byte(stack+17); pos++; if(pos >= TOOLBOX_LOG_LEN) { pos = 0; } g_toolbox_log_pos = pos; } void show_toolbox_log() { int pos; int i; pos = g_toolbox_log_pos; for(i = TOOLBOX_LOG_LEN - 1; i >= 0; i--) { printf("%2d:%2d: %08x %06x %04x: %08x %08x %08x %08x %08x\n", i, pos, g_toolbox_log_array[pos][0], g_toolbox_log_array[pos][1], g_toolbox_log_array[pos][2], g_toolbox_log_array[pos][3], g_toolbox_log_array[pos][4], g_toolbox_log_array[pos][5], g_toolbox_log_array[pos][6], g_toolbox_log_array[pos][7]); pos++; if(pos >= TOOLBOX_LOG_LEN) { pos = 0; } } } word32 get_memory_io(word32 loc, dword64 *dcyc_ptr) { int tmp; if(loc > 0xffffff) { halt_printf("get_memory_io:%08x out of range==halt!\n", loc); return 0; } tmp = loc & 0xfef000; if(tmp == 0xc000 || tmp == 0xe0c000) { return(io_read(loc & 0xfff, dcyc_ptr)); } /* Else it's an illegal addr...skip if memory sizing */ if(loc >= g_mem_size_total) { if((loc & 0xfffe) == 0) { //printf("get_io assuming mem sizing, not halting\n"); return 0; } } /* Skip reads to f80000 and f00000, just return 0 */ if((loc & 0xf70000) == 0xf00000) { return 0; } if((loc & 0xff0000) == 0xef0000) { /* DOC RAM */ return (doc_ram[loc & 0xffff]); } if((loc & 0xffff00) == 0xbcff00) { /* TWGS mapped some ROM here, we'll force in all 0's */ /* If user has selected >= 12MB of memory, then mem will be */ /* returned and we won't ever get here */ return 0; } g_code_yellow++; if(g_ignore_bad_acc && !g_user_halt_bad) { /* print no message, just get out. User doesn't want */ /* to be bothered by buggy programs */ return 0; } printf("get_memory_io for addr: %06x\n", loc); printf("stat for addr: %06x = %p\n", loc, GET_PAGE_INFO_RD((loc >> 8) & 0xffff)); set_halt(g_halt_on_bad_read | g_user_halt_bad); return 0; } void set_memory_io(word32 loc, int val, dword64 *dcyc_ptr) { word32 tmp; tmp = loc & 0xfef000; if(tmp == 0xc000 || tmp == 0xe0c000) { io_write(loc, val, dcyc_ptr); return; } /* Else it's an illegal addr */ if(loc >= g_mem_size_total) { if((loc & 0xfffe) == 0) { //printf("set_io assuming mem sizing, not halting\n"); return; } } /* ignore writes to ROM */ if((loc & 0xfc0000) == 0xfc0000) { return; } if((loc & 0xff0000) == 0xef0000) { /* DOC RAM */ doc_ram[loc & 0xffff] = val; return; } if(g_ignore_bad_acc && !g_user_halt_bad) { /* print no message, just get out. User doesn't want */ /* to be bothered by buggy programs */ return; } if((loc & 0xffc000) == 0x00c000) { printf("set_memory %06x = %02x, warning\n", loc, val); return; } halt_printf("set_memory %06x = %02x, stopping\n", loc, val); return; } void show_regs_act(Engine_reg *eptr) { int tmp_acc, tmp_x, tmp_y, tmp_psw, kpc, direct_page, dbank, stack; kpc = eptr->kpc; tmp_acc = eptr->acc; direct_page = eptr->direct; dbank = eptr->dbank; stack = eptr->stack; tmp_x = eptr->xreg; tmp_y = eptr->yreg; tmp_psw = eptr->psr; dbg_printf(" PC=%02x.%04x A=%04x X=%04x Y=%04x P=%03x", kpc>>16, kpc & 0xffff ,tmp_acc,tmp_x,tmp_y,tmp_psw); dbg_printf(" S=%04x D=%04x B=%02x,cyc:%016llx\n", stack, direct_page, dbank, g_cur_dfcyc); } void show_regs() { show_regs_act(&engine); } void my_exit(int ret) { g_a2_fatal_err = 0x10 + ret; printf("exiting\n"); } void do_reset() { g_c035_shadow_reg = 0; g_c068_statereg = 0x200 | 0x08 | 0x04; // Set wrdefram, rdrom, lcbank2 g_c02d_int_crom = 0xff; if(g_rom_version != 0) { // IIgs ROM01 or ROM03 g_c068_statereg |= 0x01; // also set intcx g_c02d_int_crom = 0; } g_c023_val = 0; g_c041_val = 0; engine.psr = (engine.psr | 0x134) & ~(0x08); engine.stack = 0x100 + (engine.stack & 0xff); engine.dbank = 0; engine.direct = 0; engine.xreg &= 0xff; engine.yreg &= 0xff; g_wait_pending = 0; g_stp_pending = 0; video_reset(); adb_reset(); iwm_reset(); scc_reset(); sound_reset(g_cur_dfcyc); setup_pageinfo(); change_display_mode(g_cur_dfcyc); g_irq_pending = 0; g_code_yellow = 0; g_code_red = 0; engine.kpc = get_memory16_c(0x00fffc); g_stepping = 0; } #define CHECK(start, var, value, var1, var2) \ var2 = PTR2WORD(&(var)); \ var1 = PTR2WORD((start)); \ if((var2 - var1) != value) { \ printf("CHECK: " #var " is 0x%x, but " #value " is 0x%x\n", \ (var2 - var1), value); \ exit(5); \ } byte * memalloc_align(int size, int skip_amt, void **alloc_ptr) { byte *bptr; word32 addr; word32 offset; skip_amt = MY_MAX(256, skip_amt); bptr = calloc(size + skip_amt, 1); if(alloc_ptr) { /* Save allocation address */ *alloc_ptr = bptr; } addr = PTR2WORD(bptr) & 0xff; /* must align bptr to be 256-byte aligned */ /* this code should work even if ptrs are > 32 bits */ offset = ((addr + skip_amt - 1) & (~0xff)) - addr; return (bptr + offset); } void memory_ptr_init() { word32 mem_size; /* This routine may be called several times--each time the ROM file */ /* changes this will be called */ mem_size = MY_MIN(0xdf0000, g_mem_size_base + g_mem_size_exp); if(g_rom_version == 0) { // Apple //e mem_size = g_mem_size_base; } g_mem_size_total = mem_size; if(g_memory_alloc_ptr) { free(g_memory_alloc_ptr); g_memory_alloc_ptr = 0; } g_memory_ptr = memalloc_align(mem_size, 256, &g_memory_alloc_ptr); printf("RAM size is 0 - %06x (%.2fMB)\n", mem_size, (double)mem_size/(1024.0*1024.0)); } extern int g_screen_redraw_skip_amt; extern int g_use_dhr140; extern int g_use_bw_hires; char g_display_env[512]; int g_screen_depth = 8; int parse_argv(int argc, char **argv, int slashes_to_find) { byte *bptr; char *str, *arg2_str; int skip_amt, tmp1, len; int i; #if 0 // If launched from Finder, no stdout, so send it to /tmp/out1 fflush(stdout); setvbuf(stdout, 0, _IONBF, 0); close(1); (void)open("/tmp/out1", O_WRONLY | O_CREAT | O_TRUNC, 0x1b6); #endif printf("Starting KEGS v%s\n", &g_kegs_version_str[0]); // parse arguments // First, Check if KEGS_BIG_ENDIAN is set correctly g_word32_tmp = 0x01020304; bptr = (byte *)&(g_word32_tmp); #ifdef KEGS_BIG_ENDIAN bptr[0] = 6; bptr[3] = 5; #else bptr[0] = 5; bptr[3] = 6; #endif if(g_word32_tmp != 0x06020305) { fatal_printf("KEGS_BIG_ENDIAN is not properly set\n"); return 1; } str = kegs_malloc_str(argv[0]); len = (int)strlen(str); for(i = len; i > 0; i--) { if(str[i] == '/') { if(--slashes_to_find <= 0) { str[i] = 0; g_argv0_path = str; break; } } } printf("g_argv0_path: %s\n", g_argv0_path); i = 0; while(++i < argc) { printf("argv[%d] = %s\n", i, argv[i]); if(!strcmp("-badrd", argv[i])) { printf("Halting on bad reads\n"); g_halt_on_bad_read = 2; } else if(!strcmp("-noignbadacc", argv[i])) { printf("Not ignoring bad memory accesses\n"); g_ignore_bad_acc = 0; } else if(!strcmp("-noignhalt", argv[i])) { printf("Not ignoring code red halts\n"); g_ignore_halts = 0; } else if(!strcmp("-24", argv[i])) { printf("Using 24-bit visual\n"); g_force_depth = 24; } else if(!strcmp("-16", argv[i])) { printf("Using 16-bit visual\n"); g_force_depth = 16; } else if(!strcmp("-15", argv[i])) { printf("Using 15-bit visual\n"); g_force_depth = 15; } else if(!strcmp("-mem", argv[i])) { if((i+1) >= argc) { printf("Missing argument\n"); return 1; } g_mem_size_exp = strtol(argv[i+1], 0, 0) & 0x00ff0000; printf("Using %d as memory size\n", g_mem_size_exp); i++; } else if(!strcmp("-skip", argv[i])) { if((i+1) >= argc) { printf("Missing to skip argument\n"); return 1; } skip_amt = (int)strtol(argv[i+1], 0, 0); printf("Using %d as skip_amt\n", skip_amt); g_screen_redraw_skip_amt = skip_amt; i++; } else if(!strcmp("-audio", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -audio\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Using %d as audio enable val\n", tmp1); g_audio_enable = tmp1; i++; } else if(!strcmp("-arate", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -arate\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Using %d as preferred audio rate\n", tmp1); g_preferred_rate = tmp1; i++; } else if(!strcmp("-v", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -v\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Setting Verbose = 0x%03x\n", tmp1); Verbose = tmp1; i++; } else if(!strcmp("-display", argv[i])) { if((i+1) >= argc) { printf("Missing argument\n"); exit(1); } printf("Using %s as display\n", argv[i+1]); snprintf(g_display_env, sizeof(g_display_env), "DISPLAY=%s", argv[i+1]); putenv(&g_display_env[0]); i++; } else if(!strcmp("-noshm", argv[i])) { printf("Not using X shared memory\n"); g_use_shmem = 0; } else if(!strcmp("-joystick", argv[i])) { printf("Ignoring -joystick option\n"); } else if(!strcmp("-dhr140", argv[i])) { printf("Using simple dhires color map\n"); g_use_dhr140 = 1; } else if(!strcmp("-bw", argv[i])) { printf("Forcing black-and-white hires modes\n"); g_cur_a2_stat |= ALL_STAT_COLOR_C021; g_use_bw_hires = 1; } else if(!strncmp("-NS", argv[i], 3)) { // Some Mac argument, just ignore it if((i + 1) < argc) { // If the next argument doesn't start with '-', // then ignore it, too if(argv[i+1][0] != '-') { i++; } } } else if(!strcmp("-logpc", argv[i])) { printf("Force logpc enable\n"); debug_logpc_on("on"); } else if(!strncmp("-cfg", argv[i], 4)) { if((i + 1) < argc) { config_set_config_kegs_name(argv[i+1]); } i++; } else if(argv[i][0] == '-') { arg2_str = 0; if((i + 1) < argc) { arg2_str = argv[i+1]; } i += config_add_argv_override(&argv[i][1], arg2_str); } else { printf("Bad option: %s\n", argv[i]); return 3; } } return 0; } int kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window) { g_config_control_panel = 0; woz_crc_init(); fixed_memory_ptrs_init(); if(sizeof(word32) != 4) { printf("sizeof(word32) = %d, must be 4!\n", (int)sizeof(word32)); return 1; } prepare_a2_font(); // Prepare default built-in font scc_init(); iwm_init(); init_reg(); adb_init(); initialize_events(); debugger_init(); setup_pageinfo(); config_init(); load_roms_init_memory(); clear_halt(); video_init(mdepth, screen_width, screen_height, no_scale_window); sound_init(); joystick_init(); if(g_rom_version >= 3) { g_c036_val_speed |= 0x40; /* set power-on bit */ } do_reset(); g_stepping = 0; clear_halt(); cfg_set_config_panel(g_config_control_panel); return 0; } void load_roms_init_memory() { config_load_roms(); memory_ptr_init(); clk_setup_bram_version(); /* Must be after config_load_roms */ if(g_rom_version >= 3) { g_c036_val_speed |= 0x40; /* set power-on bit */ } else { g_c036_val_speed &= (~0x40); /* clear the bit */ } // Do not call do_reset(), caller is responsible for that /* if user booted ROM 01, switches to ROM 03, then switches back */ /* to ROM 01, then the reset routines call to Tool $0102 looks */ /* at uninitialized $e1/15fe and if it is negative it will JMP */ /* through $e1/1688 which ROM 03 left pointing to fc/0199 */ /* So set e1/15fe = 0 */ set_memory16_c(0xe115fe, 0, 1); } Event g_event_list[MAX_EVENTS]; Event g_event_free; Event g_event_start; void initialize_events() { int i; for(i = 1; i < MAX_EVENTS; i++) { g_event_list[i-1].next = &g_event_list[i]; } g_event_free.next = &g_event_list[0]; g_event_list[MAX_EVENTS-1].next = 0; g_event_start.next = 0; g_event_start.dfcyc = 0; add_event_entry(CYCLES_IN_16MS_RAW << 16, EV_60HZ); } void check_for_one_event_type(int type, word32 mask) { Event *ptr; int count, depth; type = type & 0xff; count = 0; depth = 0; ptr = g_event_start.next; while(ptr != 0) { depth++; if((ptr->type & mask) == (word32)type) { count++; if(count != 1) { halt_printf("in check_for_1, type %04x found " "at depth: %d, count: %d, at %016llx\n", ptr->type, depth, count, ptr->dfcyc); } } ptr = ptr->next; } } void add_event_entry(dword64 dfcyc, int type) { Event *this_event; Event *ptr, *prev_ptr; int done; this_event = g_event_free.next; if(this_event == 0) { halt_printf("Out of queue entries!\n"); show_all_events(); return; } g_event_free.next = this_event->next; this_event->type = type; if((dfcyc > (g_cur_dfcyc + (50LL*1000*1000 << 16))) || (dfcyc < g_cur_dfcyc)) { halt_printf("add_event bad dfcyc:%016llx, type:%05x, " "cur_dfcyc: %016llx!\n", dfcyc, type, g_cur_dfcyc); dfcyc = g_cur_dfcyc + (1000LL << 16); } ptr = g_event_start.next; if(ptr && (dfcyc < ptr->dfcyc)) { /* create event before next expected event */ /* do this by calling engine_recalc_events */ engine_recalc_events(); } prev_ptr = &g_event_start; ptr = g_event_start.next; done = 0; while(!done) { if(ptr == 0) { this_event->next = ptr; this_event->dfcyc = dfcyc; prev_ptr->next = this_event; break; } else { if(ptr->dfcyc < dfcyc) { // step across this guy prev_ptr = ptr; ptr = ptr->next; } else { // go before this guy */ this_event->dfcyc = dfcyc; this_event->next = ptr; prev_ptr->next = this_event; break; } } } check_for_one_event_type(type, 0xffff); } extern int g_doc_saved_ctl; dword64 remove_event_entry(int type, word32 mask) { Event *ptr, *prev_ptr; Event *next_ptr; ptr = g_event_start.next; prev_ptr = &g_event_start; while(ptr != 0) { if((ptr->type & mask) == (word32)type) { // got it next_ptr = ptr->next; // remove it prev_ptr->next = next_ptr; /* Add ptr to free list */ ptr->next = g_event_free.next; g_event_free.next = ptr; return ptr->dfcyc; } prev_ptr = ptr; ptr = ptr->next; } halt_printf("remove event_entry: %08x, but not found!\n", type); if((type & 0xff) == EV_DOC_INT) { printf("DOC, g_doc_saved_ctl = %02x\n", g_doc_saved_ctl); } show_all_events(); return 0; } void add_event_stop(dword64 dfcyc) { add_event_entry(dfcyc, EV_STOP); } void add_event_doc(dword64 dfcyc, int osc) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; //halt_printf("add_event_doc: dfcyc:%016llx, cur_dfcyc:" // "%016llx\n", dfcyc, g_cur_dfcyc); } add_event_entry(dfcyc, EV_DOC_INT + (osc << 8)); } void add_event_scc(dword64 dfcyc, int type) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_SCC + (type << 8)); } void add_event_vbl() { dword64 dfcyc; dfcyc = g_last_vbl_dfcyc + ((192*65LL) << 16); add_event_entry(dfcyc, EV_VBL_INT); } void add_event_vid_upd(int line) { dword64 dfcyc; dfcyc = g_last_vbl_dfcyc + (((line + 1) * 65LL) << 16); add_event_entry(dfcyc, EV_VID_UPD + (line << 8)); // Redraw line when video counters first read video data } void add_event_mockingboard(dword64 dfcyc) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_MOCKINGBOARD); } dword64 g_dfcyc_scan_int = 0; void add_event_scan_int(dword64 dfcyc, int line) { dword64 dfcyc_scan_int; dfcyc_scan_int = g_dfcyc_scan_int; if(dfcyc_scan_int) { // Event is pending if(dfcyc >= g_dfcyc_scan_int) { // We are after (or the same) as current, do nothing return; } remove_event_entry(EV_SCAN_INT, 0xff); } if(dfcyc < g_cur_dfcyc) { // scan_int for line 0 is found during EV_60HZ, and some // cycles may have passed before the EV_60HZ was handled. // We need it to happen now, so just adjust dfcyc dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_SCAN_INT + (line << 8)); g_dfcyc_scan_int = dfcyc; check_for_one_event_type(EV_SCAN_INT, 0xff); } dword64 remove_event_doc(int osc) { return remove_event_entry(EV_DOC_INT + (osc << 8), 0xffff); } dword64 remove_event_scc(int type) { return remove_event_entry(EV_SCC + (type << 8), 0xffff); } void remove_event_mockingboard() { (void)remove_event_entry(EV_MOCKINGBOARD, 0xff); } void show_all_events() { Event *ptr; dword64 dfcyc; int count; count = 0; ptr = g_event_start.next; while(ptr != 0) { dfcyc = ptr->dfcyc; printf("Event: %02x: type: %05x, dfcyc: %016llx (%08llx)\n", count, ptr->type, dfcyc, dfcyc - g_cur_dfcyc); ptr = ptr->next; count++; } } word32 g_vbl_count = 0; int g_vbl_index_count = 0; double dtime_array[60]; double g_dadjcycs_array[60]; double g_dtime_diff3_array[60]; double g_dtime_this_vbl_array[60]; double g_dtime_exp_array[60]; double g_dtime_pmhz_array[60]; double g_dtime_eff_pmhz_array[60]; double g_dtime_in_run_16ms = 0; double g_dtime_outside_run_16ms = 0; double g_dtime_end_16ms = 0; int g_limit_speed = 3; int g_zip_speed_mhz = 16; // 16MHz default double sim_time[60]; double g_sim_sum = 0.0; double g_cur_sim_dtime = 0.0; double g_projected_pmhz = 1.0; double g_zip_pmhz = 8.0; double g_sim_mhz = 100.0; int g_line_ref_amt = 1; int g_video_line_update_interval = 0; dword64 g_video_pixel_dcount = 0; Fplus g_recip_projected_pmhz_slow; Fplus g_recip_projected_pmhz_fast; Fplus g_recip_projected_pmhz_zip; Fplus g_recip_projected_pmhz_unl; void show_pmhz() { printf("Pmhz: %f, c036:%02x, limit: %d\n", g_projected_pmhz, g_c036_val_speed, g_limit_speed); } void setup_zip_speeds() { dword64 drecip; double fmhz; int mult; mult = 16 - ((g_zipgs_reg_c05a >> 4) & 0xf); // 16 = full speed, 1 = 1/16th speed fmhz = (g_zip_speed_mhz * mult) / 16.0; #if 0 if(mult == 16) { /* increase full speed by 19% to make zipgs freq measuring */ /* programs work correctly */ fmhz = fmhz * 1.19; } #endif drecip = (dword64)(65536 / fmhz); g_zip_pmhz = fmhz; g_recip_projected_pmhz_zip.dplus_1 = drecip; if(fmhz <= 2.0) { g_recip_projected_pmhz_zip.dplus_x_minus_1 = (dword64)(1.01 * 65536); } else { g_recip_projected_pmhz_zip.dplus_x_minus_1 = (dword64)(1.01 * 65536 - drecip); } } word32 g_cycs_end_16ms = 0; int run_16ms() { double dtime_start, dtime_end, dtime_end2, dtime_outside; int ret; dtime_start = get_dtime(); ret = 0; fflush(stdout); g_dtime_sleep = 1.0/61.0; // For control_panel/debugger if(g_config_control_panel) { ret = cfg_control_panel_update(); if(!g_config_control_panel) { return 0; // Was just switched off, get out } } else { if(g_halt_sim) { ret = debugger_run_16ms(); } else { ret = run_a2_one_vbl(); } } video_update(); g_vbl_count++; dtime_end = get_dtime(); g_dtime_in_run_16ms += (dtime_end - dtime_start); // If we are ahead, then do the sleep now micro_sleep(g_dtime_sleep); dtime_end2 = get_dtime(); //printf("Did sleep for %f, dtime passed:%f\n", g_dtime_sleep, // dtime_end2 - dtime_end); g_dtime_sleep = 0.0; g_dtime_in_sleep += (dtime_end2 - dtime_end); dtime_outside = 0.0; if(g_vbl_count > 1) { dtime_outside += (dtime_start - g_dtime_end_16ms); } g_dtime_outside_run_16ms += dtime_outside; g_dtime_end_16ms = dtime_end2; #if 0 if((g_vbl_count & 0xf) == 0) { printf("run_16ms end at %.3lf, dtime_16ms:%1.5lf out:%1.5lf\n", dtime_end, dtime_end - dtime_start, dtime_outside); } #endif return ret | g_a2_fatal_err; } int run_a2_one_vbl() { Fplus *fplus_ptr; Event *this_event, *db1; double now_dtime, prev_dtime, fspeed_mult; dword64 dfcyc, dfcyc_stop, prerun_dfcyc; word32 ret, zip_speed_0tof, zip_speed_0tof_new; int zip_en, zip_follow_cps, type, motor_on, iwm_1, iwm_25, fast; int limit_speed, apple35_sel, zip_speed, faster_than_28, unl_speed; fflush(stdout); g_cur_sim_dtime = 0.0; g_recip_projected_pmhz_slow.dplus_1 = 0x10000; g_recip_projected_pmhz_slow.dplus_x_minus_1 = (dword64)(0.9 * 0x10000); g_recip_projected_pmhz_fast.dplus_1 = (dword64)(0x10000 / 2.8); g_recip_projected_pmhz_fast.dplus_x_minus_1 = (dword64) ((1.98 - (1.0/2.8)) * 0x10000); zip_speed_0tof = g_zipgs_reg_c05a & 0xf0; setup_zip_speeds(); if(engine.fplus_ptr == 0) { g_recip_projected_pmhz_unl = g_recip_projected_pmhz_slow; } while(1) { fflush(stdout); if(g_irq_pending && !(engine.psr & 0x4)) { irq_printf("taking an irq!\n"); take_irq(); /* Interrupt! */ } motor_on = g_iwm_motor_on; limit_speed = g_limit_speed; apple35_sel = g_iwm.state & IWM_STATE_C031_APPLE35SEL; zip_en = ((g_zipgs_reg_c05b & 0x10) == 0); zip_follow_cps = ((g_zipgs_reg_c059 & 0x8) != 0); zip_speed_0tof_new = g_zipgs_reg_c05a & 0xf0; fast = (g_c036_val_speed & 0x80) || (zip_en && !zip_follow_cps); if(zip_speed_0tof_new != zip_speed_0tof) { zip_speed_0tof = zip_speed_0tof_new; setup_zip_speeds(); } iwm_1 = motor_on && !apple35_sel && (g_c036_val_speed & 0x4) && (g_slow_525_emul_wr || !g_fast_disk_emul); iwm_25 = (motor_on && apple35_sel) && !g_fast_disk_emul; faster_than_28 = fast && (!iwm_1 && !iwm_25) && zip_en && ((limit_speed == 0) || (limit_speed == 3)); zip_speed = faster_than_28 && ((zip_speed_0tof != 0) || (limit_speed == 3) || (g_zipgs_unlock >= 4) ); unl_speed = faster_than_28 && !zip_speed; if(unl_speed) { /* use unlimited speed */ fspeed_mult = g_projected_pmhz; fplus_ptr = &g_recip_projected_pmhz_unl; } else if(zip_speed) { fspeed_mult = g_zip_pmhz; fplus_ptr = &g_recip_projected_pmhz_zip; } else if(fast && !iwm_1 && (iwm_25 || (limit_speed != 1))) { fspeed_mult = 2.5; fplus_ptr = &g_recip_projected_pmhz_fast; } else { /* else run slow */ fspeed_mult = 1.0; fplus_ptr = &g_recip_projected_pmhz_slow; } engine.fplus_ptr = fplus_ptr; prerun_dfcyc = g_cur_dfcyc; engine.dfcyc = prerun_dfcyc; dfcyc_stop = g_event_start.next->dfcyc + 1; if(g_stepping) { dfcyc_stop = prerun_dfcyc + 1; } g_dcycles_end = dfcyc_stop; #if 0 printf("Enter engine, fcycs: %f, stop: %f\n", prerun_fcycles, fcycles_stop); printf("g_cur_dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", g_cur_dfcyc, g_last_vbl_dfcyc); #endif g_num_enter_engine++; prev_dtime = get_dtime(); ret = enter_engine(&engine); now_dtime = get_dtime(); g_cur_sim_dtime += (now_dtime - prev_dtime); dfcyc = engine.dfcyc; g_cur_dfcyc = dfcyc; g_dadjcycs += (engine.dfcyc - prerun_dfcyc) * (1/65536.0) * fspeed_mult; #if 0 printf("...back, engine.dfcyc: %016llx, dfcyc: %016llx\n", (double)engine.dfcyc, dfcyc); #endif if(ret != 0) { g_engine_action++; handle_action(ret); } this_event = g_event_start.next; while(dfcyc >= this_event->dfcyc) { /* Pop this guy off of the queue */ g_event_start.next = this_event->next; type = this_event->type; this_event->next = g_event_free.next; g_event_free.next = this_event; dbg_log_info(dfcyc, type, 0, 0x101); switch(type & 0xff) { case EV_60HZ: update_60hz(dfcyc, now_dtime); return 0; break; case EV_STOP: printf("type: EV_STOP\n"); printf("next: %p, dfcyc: %016llx\n", g_event_start.next, dfcyc); db1 = g_event_start.next; halt_printf("next.dfcyc:%016llx\n", db1->dfcyc); break; case EV_SCAN_INT: g_engine_scan_int++; irq_printf("type: scan int\n"); do_scan_int(dfcyc, type >> 8); break; case EV_DOC_INT: g_engine_doc_int++; doc_handle_event(type >> 8, dfcyc); break; case EV_VBL_INT: do_vbl_int(); break; case EV_SCC: scc_do_event(dfcyc, type >> 8); break; case EV_VID_UPD: video_update_event_line(type >> 8); break; case EV_MOCKINGBOARD: mockingboard_event(dfcyc); break; default: printf("Unknown event: %d!\n", type); exit(3); } this_event = g_event_start.next; } if(g_event_start.next == 0) { halt_printf("ERROR...run_prog, event_start.n=0!\n"); } if(g_halt_sim) { break; } if(g_stepping) { break; } } printf("leaving run_prog, g_halt_sim:%d\n", g_halt_sim); return 0; } void add_irq(word32 irq_mask) { if(g_irq_pending & irq_mask) { /* Already requested, just get out */ return; } g_irq_pending |= irq_mask; engine_recalc_events(); } void remove_irq(word32 irq_mask) { g_irq_pending = g_irq_pending & (~irq_mask); } void take_irq() { word32 new_kpc, va; irq_printf("Taking irq, at: %02x/%04x, psw: %02x, dfcyc:%016llx\n", engine.kpc >> 16, engine.kpc & 0xffff, engine.psr, g_cur_dfcyc); g_num_irq++; if(g_wait_pending) { /* step over WAI instruction */ engine.kpc = (engine.kpc & 0xff0000) | ((engine.kpc + 1) & 0xffff); g_wait_pending = 0; } if(engine.psr & 0x100) { /* Emulation */ set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xff) + 0x100; set_memory_c(engine.stack, engine.kpc & 0xff, 1); engine.stack = ((engine.stack -1) & 0xff) + 0x100; set_memory_c(engine.stack, (engine.psr & 0xef), 1); /* Clear B bit in psr on stack */ engine.stack = ((engine.stack -1) & 0xff) + 0x100; va = 0xfffffe; } else { /* native */ set_memory_c(engine.stack, (engine.kpc >> 16) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, engine.kpc & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, engine.psr & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); va = 0xffffee; } va = moremem_fix_vector_pull(va); new_kpc = get_memory_c(va); new_kpc = new_kpc + (get_memory_c(va + 1) << 8); engine.psr = ((engine.psr & 0x1f3) | 0x4); engine.kpc = new_kpc; HALT_ON(HALT_ON_IRQ, "Halting on IRQ\n"); } double g_dtime_last_vbl = 0.0; double g_dtime_expected = (1.0/VBL_RATE); // Approximately 1.0/60.0 int g_scan_int_events = 0; void show_dtime_array() { double dfirst_time; double first_total_cycs; int i; int pos; dfirst_time = 0.0; first_total_cycs = 0.0; for(i = 0; i < 60; i++) { pos = (g_vbl_index_count + i) % 60; printf("%2d:%2d dt:%.5f adjc:%9.1f this_vbl:%.6f " "exp:%.5f p:%2.2f ep:%2.2f\n", i, pos, dtime_array[pos] - dfirst_time, g_dadjcycs_array[pos] - first_total_cycs, g_dtime_this_vbl_array[pos], g_dtime_exp_array[pos] - dfirst_time, g_dtime_pmhz_array[pos], g_dtime_eff_pmhz_array[pos]); dfirst_time = dtime_array[pos]; first_total_cycs = g_dadjcycs_array[pos]; } } void update_60hz(dword64 dfcyc, double dtime_now) { register word32 end_time; char status_buf[1024]; char sim_mhz_buf[128]; char total_mhz_buf[128]; char sp_buf[128]; char *sim_mhz_ptr, *total_mhz_ptr, *code_str1, *code_str2, *sp_str; dword64 planned_dcyc; double eff_pmhz, predicted_pmhz, recip_predicted_pmhz; double dtime_this_vbl_sim, dtime_diff_1sec, dratio, dtime_diff; double dtime_till_expected, dtime_this_vbl, dadjcycs_this_vbl; double dadj_cycles_1sec, dnatcycs_1sec; int tmp, doit_3_persec, cur_vbl_index, prev_vbl_index; /* NOTE: this event is defined to occur before line 0 */ /* It's actually happening at the start of the border for line (-1) */ /* All other timings should be adjusted for this */ irq_printf("vbl_60hz: vbl: %d, dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", g_vbl_count, dfcyc, g_last_vbl_dfcyc); planned_dcyc = CYCLES_IN_16MS_RAW << 16; g_last_vbl_dfcyc = g_last_vbl_dfcyc + planned_dcyc; add_event_entry(g_last_vbl_dfcyc + planned_dcyc, EV_60HZ); check_for_one_event_type(EV_60HZ, 0xff); cur_vbl_index = g_vbl_index_count; /* figure out dtime spent running SIM, not all the overhead */ dtime_this_vbl_sim = g_cur_sim_dtime; g_cur_sim_dtime = 0.0; g_sim_sum = g_sim_sum - sim_time[cur_vbl_index] + dtime_this_vbl_sim; sim_time[cur_vbl_index] = dtime_this_vbl_sim; dadj_cycles_1sec = g_dadjcycs - g_dadjcycs_array[cur_vbl_index]; /* dtime_diff_1sec is dtime total spent over the last 60 ticks */ dtime_diff_1sec = dtime_now - dtime_array[cur_vbl_index]; dtime_array[cur_vbl_index] = dtime_now; g_dadjcycs_array[cur_vbl_index] = g_dadjcycs; prev_vbl_index = cur_vbl_index; cur_vbl_index = prev_vbl_index + 1; if(cur_vbl_index >= 60) { cur_vbl_index = 0; } g_vbl_index_count = cur_vbl_index; GET_ITIMER(end_time); g_dnatcycs_1sec += (double)(end_time - g_natcycs_lastvbl); g_natcycs_lastvbl = end_time; if(prev_vbl_index == 0) { if(g_sim_sum < (1.0/250.0)) { sim_mhz_ptr = "???"; g_sim_mhz = 250.0; } else { g_sim_mhz = (dadj_cycles_1sec / g_sim_sum) / (1000.0*1000.0); if(g_sim_mhz > 8000.0) { g_sim_mhz = 8000.0; } snprintf(sim_mhz_buf, sizeof(sim_mhz_buf), "%6.2f", g_sim_mhz); sim_mhz_ptr = sim_mhz_buf; } if(dtime_diff_1sec < (1.0/250.0)) { total_mhz_ptr = "???"; } else { snprintf(total_mhz_buf, sizeof(total_mhz_buf), "%6.2f", (dadj_cycles_1sec / dtime_diff_1sec) / (1000000.0)); total_mhz_ptr = total_mhz_buf; } switch(g_limit_speed) { case 1: sp_str = "1Mhz"; break; case 2: sp_str = "2.8Mhz"; break; case 3: sp_str = "8.0Mhz"; break; default: sp_str = "Unlimited"; break; } if(g_limit_speed == 3) { // ZipGS snprintf(sp_buf, sizeof(sp_buf), "%1.1fMHz", g_zip_pmhz); sp_str = sp_buf; } snprintf(status_buf, sizeof(status_buf), "dfcyc:%7.1f sim " "MHz:%s Eff MHz:%s, sec:%1.3f vol:%02x Limit:%s", (double)(dfcyc >> 20)/65536.0, sim_mhz_ptr, total_mhz_ptr, dtime_diff_1sec, g_doc_vol, sp_str); video_update_status_line(0, status_buf); if(g_video_line_update_interval == 0) { if(g_sim_mhz > 12.0) { /* just set video line_ref_amt to 1 */ g_line_ref_amt = 1; } else if(g_line_ref_amt == 1 && g_sim_mhz < 4.0) { g_line_ref_amt = 8; } } else { g_line_ref_amt = g_video_line_update_interval; } dnatcycs_1sec = g_dnatcycs_1sec; if(g_dnatcycs_1sec < (1000.0*1000.0)) { /* make it so large that all %'s become 0 */ dnatcycs_1sec = 800.0*1000.0*1000.0*1000.0; } dnatcycs_1sec = dnatcycs_1sec / 100.0; /* eff mult by 100 */ g_video_pixel_dcount = 0; code_str1 = ""; code_str2 = ""; if(g_code_yellow) { code_str1 = "Code: Yellow"; code_str2 = "Emulated state suspect"; } if(g_code_red) { code_str1 = "Code: RED"; code_str2 = "Emulated state corrupt?"; } snprintf(status_buf, sizeof(status_buf), "sleep_dtime:%8.6f, " "out_16ms:%8.6f, in_16ms:%8.6f, snd_pl:%d", g_dtime_in_sleep, g_dtime_outside_run_16ms, g_dtime_in_run_16ms, g_num_snd_plays); video_update_status_line(1, status_buf); draw_iwm_status(2, status_buf); snprintf(status_buf, sizeof(status_buf), " KEGS v%-6s " "Press F4 for Config Menu %s %s", g_kegs_version_str, code_str1, code_str2); video_update_status_line(3, status_buf); g_status_refresh_needed = 1; g_num_irq = 0; g_num_brk = 0; g_num_cop = 0; g_num_enter_engine = 0; g_io_amt = 0; g_engine_action = 0; g_engine_recalc_event = 0; g_engine_scan_int = 0; g_engine_doc_int = 0; g_cycs_in_40col = 0; g_cycs_in_xredraw = 0; g_cycs_in_refresh_line = 0; g_dnatcycs_1sec = 0.0; g_dtime_outside_run_16ms = 0.0; g_dtime_in_run_16ms = 0.0; g_refresh_bytes_xfer = 0; g_dtime_in_sleep = 0; g_num_snd_plays = 0; g_num_recalc_snd_parms = 0; } dtime_this_vbl = dtime_now - g_dtime_last_vbl; if(dtime_this_vbl < 0.001) { dtime_this_vbl = 0.001; } g_dtime_last_vbl = dtime_now; dadjcycs_this_vbl = g_dadjcycs - g_last_vbl_dadjcycs; g_last_vbl_dadjcycs = g_dadjcycs; g_dtime_expected += (1.0/VBL_RATE); // Approx. 1/60 eff_pmhz = (dadjcycs_this_vbl / dtime_this_vbl) / DCYCS_1_MHZ; /* using eff_pmhz, predict how many cycles can be run by */ /* g_dtime_expected */ dtime_till_expected = g_dtime_expected - dtime_now; dratio = VBL_RATE * dtime_till_expected; // Approx. 60*dtime_exp predicted_pmhz = eff_pmhz * dratio; if(! (predicted_pmhz < (1.4 * g_projected_pmhz))) { predicted_pmhz = 1.4 * g_projected_pmhz; } if(! (predicted_pmhz > (0.7 * g_projected_pmhz))) { predicted_pmhz = 0.7 * g_projected_pmhz; } if(!(predicted_pmhz >= 1.0)) { irq_printf("predicted: %f, setting to 1.0\n", predicted_pmhz); predicted_pmhz = 1.0; } if(!(predicted_pmhz < 4500.0)) { irq_printf("predicted: %f, set to 1900.0\n", predicted_pmhz); predicted_pmhz = 4500.0; } recip_predicted_pmhz = 1.0/predicted_pmhz; g_projected_pmhz = predicted_pmhz; g_recip_projected_pmhz_unl.dplus_1 = (dword64) (65536 * recip_predicted_pmhz); g_recip_projected_pmhz_unl.dplus_x_minus_1 = (dword64)(65536 * (1.01 - recip_predicted_pmhz)); if(dtime_till_expected < -0.125) { /* If we were way off, get back on track */ /* this happens because our sim took much longer than */ /* expected, so we're going to skip some VBL */ irq_printf("adj1: dtexp:%f, dt_new:%f\n", g_dtime_expected, dtime_now); dtime_diff = -dtime_till_expected; irq_printf("dtime_till_exp:%f, dtime_diff:%f, dfcyc:%016llx\n", dtime_till_expected, dtime_diff, dfcyc); g_dtime_expected += dtime_diff; } g_dtime_sleep = 0.0; if(dtime_till_expected > (1.0/VBL_RATE)) { /* we're running fast, usleep */ g_dtime_sleep = dtime_till_expected - (1.0/VBL_RATE); } #if 0 printf("Sleep %f, till_exp:%f, dtime_now:%f, exp:%f\n", g_dtime_sleep, dtime_till_expected, dtime_now, g_dtime_expected); #endif g_dtime_this_vbl_array[prev_vbl_index] = dtime_this_vbl; g_dtime_exp_array[prev_vbl_index] = g_dtime_expected; g_dtime_pmhz_array[prev_vbl_index] = predicted_pmhz; g_dtime_eff_pmhz_array[prev_vbl_index] = eff_pmhz; if(g_c041_val & C041_EN_VBL_INTS) { add_event_vbl(); } g_25sec_cntr++; if(g_25sec_cntr >= 16) { g_25sec_cntr = 0; if(g_c041_val & C041_EN_25SEC_INTS) { add_irq(IRQ_PENDING_C046_25SEC); g_c046_val |= 0x10; irq_printf("Setting c046 .25 sec int, g_irq_pend:%d\n", g_irq_pending); } } g_1sec_cntr++; if(g_1sec_cntr >= 60) { g_1sec_cntr = 0; tmp = g_c023_val; tmp |= 0x40; /* set 1sec int */ if(tmp & 0x04) { tmp |= 0x80; add_irq(IRQ_PENDING_C023_1SEC); irq_printf("Setting c023 to %02x irq_pend: %d\n", tmp, g_irq_pending); } g_c023_val = tmp; } if(!g_scan_int_events) { check_scan_line_int(0); } doit_3_persec = 0; if(g_config_iwm_vbl_count > 0) { g_config_iwm_vbl_count--; } else { g_config_iwm_vbl_count = 20; doit_3_persec = 1; } iwm_vbl_update(); config_vbl_update(doit_3_persec); sound_update(dfcyc); clock_update(); scc_update(dfcyc); paddle_update_buttons(); } void do_vbl_int() { if(g_c041_val & C041_EN_VBL_INTS) { g_c046_val |= 0x08; add_irq(IRQ_PENDING_C046_VBL); irq_printf("Setting c046 vbl_int_status to 1, irq_pend: %d\n", g_irq_pending); } } void do_scan_int(dword64 dfcyc, int line) { int c023_val; if(dfcyc) { // Avoid unused param warning } g_scan_int_events = 0; g_dfcyc_scan_int = 0; c023_val = g_c023_val; if(c023_val & 0x20) { halt_printf("c023 scan_int and another on line %03x\n", line); } #if 0 dvbl = (dfcyc >> 16) / 17030; dline = ((dfcyc >> 16) - (dvbl * 17030)) / 65; printf("do_scan_int at time %lld (%lld,line %lld), line:%d, SCB:%02x, " "a2_stat_ok:%d, c023:%02x\n", dfcyc >> 16, dvbl, dline, line, (g_slow_memory_ptr[0x19d00 + line] & 0x40), (g_cur_a2_stat & ALL_STAT_SUPER_HIRES) != 0, c023_val); for(i = 0; i < 200; i++) { if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { printf(" Line %d has SCB:%02x\n", i, g_slow_memory_ptr[0x19d00 + i]); } } #endif /* make sure scan int is still enabled for this line */ if((g_slow_memory_ptr[0x19d00 + line] & 0x40) && (g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { /* valid interrupt, do it */ c023_val |= 0xa0; /* vgc_int and scan_int */ if(c023_val & 0x02) { add_irq(IRQ_PENDING_C023_SCAN); irq_printf("Setting c023 to %02x, irq_pend: %d\n", c023_val, g_irq_pending); } g_c023_val = c023_val; HALT_ON(HALT_ON_SCAN_INT, "In do_scan_int\n"); } else { /* scan int bit cleared on scan line control byte */ /* look for next line, if any */ check_scan_line_int(line+1); } } void check_scan_line_int(int cur_video_line) { dword64 ddelay; int start, line; int i; /* Called during VBL interrupt phase */ if(!(g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { return; } if(g_c023_val & 0x20) { /* don't check for any more */ return; } start = cur_video_line; if(start < 0) { halt_printf("check_scan_line_int: cur_video_line: %d\n", cur_video_line); start = 0; } for(line = start; line < 200; line++) { i = line; if(i < 0 || i >= 200) { halt_printf("check_new_scan_int:i:%d, line:%d, st:%d\n", i, line, start); i = 0; } if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { irq_printf("Adding scan_int for line %d\n", i); ddelay = (65ULL * line) << 16; add_event_scan_int(g_last_vbl_dfcyc + ddelay, line); g_scan_int_events = 1; break; } } } void check_for_new_scan_int(dword64 dfcyc) { int cur_video_line; cur_video_line = get_lines_since_vbl(dfcyc) >> 8; check_scan_line_int(cur_video_line); } void scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val) { if(new_val & (~old_val) & 0x40) { check_for_new_scan_int(dfcyc); } if(addr) { } } void init_reg() { memset(&engine, 0, sizeof(engine)); engine.acc = 0; engine.xreg = 0; engine.yreg = 0; engine.stack = 0x1ff; engine.direct = 0; engine.psr = 0x134; engine.fplus_ptr = 0; } void handle_action(word32 ret) { int type, arg; type = ret & 0xff; arg = ret >> 8; switch(type) { case RET_BREAK: do_break(arg); break; case RET_COP: do_cop(arg); break; case RET_IRQ: irq_printf("Special fast IRQ response. irq_pending: %x\n", g_irq_pending); break; case RET_WDM: do_wdm(arg); break; case RET_STP: do_stp(); break; case RET_TOOLTRACE: dbg_log_info(g_cur_dfcyc, engine.kpc, engine.xreg, (engine.stack << 16) | 0xe100); break; default: halt_printf("Unknown special action: %08x!\n", ret); } } void do_break(word32 ret) { printf("I think I got a break, second byte: %02x!\n", ret); printf("kpc: %06x\n", engine.kpc); halt_printf("do_break, kpc: %06x\n", engine.kpc); } void do_cop(word32 ret) { halt_printf("COP instr %02x!\n", ret); fflush(stdout); } void do_wdm(word32 arg) { if(arg == 0x00c7) { // WDM, 0xc7, 0x00: WDM in Slot 7 if(engine.psr & 0x40) { // Overflow set: $C700 called do_c700(arg); } else if(engine.psr & 1) { // V=0, C=1: $C70D called do_c70d(arg); } else { // V=0, C=0, $C70A called do_c70a(arg); } return; } if(arg == 0x00ea) { // WDM, 0xea, 0x00: WDM emulator ID do_wdm_emulator_id(); return; } if((arg == 0xeaea) && ((engine.psr & 0x171) == 0x41) && (engine.acc == 0x4d44)) { // WDM $EA,$EA with V=1,C=1 and ACC=0x4d44 ("EM") engine.psr = engine.psr & 0x1bf; // V=0 // printf("WDM $EA,$EA, cleared V=0, psr:%04x\n", engine.psr); return; } switch(arg & 0xff) { case 0x8d: /* Bouncin Ferno does WDM 8d */ break; case 0xea: // Detectiong feature, don't flag an error break; case 0xfc: // HOST.FST "head_call" for ATINIT for ProDOS 8 case 0xfd: // HOST.FST "tail_call" for ATINIT for ProDOS 8 case 0xff: // HOST.FST "call_host" for GS/OS driver break; default: halt_printf("do_wdm: %04x!\n", arg); } } void do_wai() { halt_printf("do_wai!\n"); } void do_stp() { if(!g_stp_pending) { g_stp_pending = 1; halt_printf("Hit STP instruction at: %06x, press RESET to " "continue\n", engine.kpc); } } char g_emulator_name[64]; void do_wdm_emulator_id() { word32 addr, version, subvers; int maxlen, len, c, got_dot; int i; // WDM, $EA, $00: WDM emulator ID // dbank.acc = address to write emulator description string // X = size of buffer to hold string // Y = 0 (not checked) // Returns: X: actual length of emulator string (always <= // value in X at call) // ACC: emulator version as: $VVMN as BCD, so 1.32 is $0132 // Y: Emulation feature flags. bit 0: $c06c-$c06f timer available // Works in emulation mode printf("WDM EA at %06x. acc:%04x, dbank:%02x xreg:%04x\n", engine.kpc, engine.acc, engine.dbank, engine.xreg); maxlen = engine.xreg; cfg_strncpy(&g_emulator_name[0], "KEGS v", 64); cfg_strlcat(&g_emulator_name[0], &g_kegs_version_str[0], 64); len = (int)strlen(&g_emulator_name[0]); addr = engine.acc; engine.xreg = 0; for(i = 0; i < len; i++) { if(i >= maxlen) { break; } addr = (engine.dbank << 8) | (addr & 0xffff); set_memory_c(addr, 0x80 | g_emulator_name[i], 1); addr++; engine.xreg = i + 1; } version = 0; subvers = 0; len = (int)strlen(&g_kegs_version_str[0]); got_dot = 0; for(i = 0; i < len; i++) { c = g_kegs_version_str[i]; if(c == '.') { got_dot++; } if(got_dot >= 3) { break; } if((c >= '0') && (c <= '9')) { c = c - '0'; if(got_dot) { subvers = (subvers << 4) | c; got_dot++; } else { version = (version << 4) | c; } } } engine.acc = ((version & 0xff) << 8) | (subvers & 0xff); engine.yreg = 0x01; // $C06C timer available } void size_fail(int val, word32 v1, word32 v2) { halt_printf("Size failure, val: %08x, %08x %08x\n", val, v1, v2); } int fatal_printf(const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); if(g_fatal_log < 0) { g_fatal_log = 0; } ret = kegs_vprintf(fmt, ap); va_end(ap); return ret; } int kegs_vprintf(const char *fmt, va_list ap) { char *bufptr, *buf2ptr; int len, ret; bufptr = malloc(4096); ret = vsnprintf(bufptr, 4090, fmt, ap); len = (int)strlen(bufptr); if(g_fatal_log >= 0 && g_fatal_log < MAX_FATAL_LOGS) { buf2ptr = malloc(len+1); memcpy(buf2ptr, bufptr, len+1); g_fatal_log_strs[g_fatal_log++] = buf2ptr; } (void)must_write(1, (byte *)bufptr, len); if(g_debug_file_fd >= 0) { (void)must_write(g_debug_file_fd, (byte *)bufptr, len); } free(bufptr); return ret; } dword64 must_write(int fd, byte *bufptr, dword64 dsize) { dword64 dlen; long long ret; word32 this_len; dlen = dsize; while(dlen != 0) { // Support Windows64, which can only rd/wr 2GB max per call this_len = (1UL << 30); if(dlen < this_len) { this_len = (word32)dlen; } ret = write(fd, bufptr, this_len); if(ret >= 0) { dlen -= ret; bufptr += ret; } else if((errno != EAGAIN) && (errno != EINTR)) { return 0; // just get out } } return dsize; } void clear_fatal_logs() { int i; for(i = 0; i < g_fatal_log; i++) { free(g_fatal_log_strs[i]); g_fatal_log_strs[i] = 0; } g_fatal_log = -1; } char * kegs_malloc_str(const char *in_str) { char *str; int len; len = (int)strlen(in_str) + 1; str = malloc(len); memcpy(str, in_str, len); return str; } dword64 kegs_lseek(int fd, dword64 offs, int whence) { #ifdef _WIN32 return _lseeki64(fd, offs, whence); #else return lseek(fd, offs, whence); #endif } ================================================ FILE: gsplus/src/size_c.h ================================================ // "@(#)$KmKId: size_c.h,v 1.2 2023-11-12 15:32:17+00 kentd Exp $" /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2020 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ 0x1, /* 00 */ /* brk */ 0x1, /* 01 */ /* ORA (Dloc,X) */ 0x1, /* 02 */ /* COP */ 0x1, /* 03 */ /* ORA Disp8,S */ 0x1, /* 04 */ /* TSB Dloc */ 0x1, /* 05 */ /* ORA Dloc */ 0x1, /* 06 */ /* ASL Dloc */ 0x1, /* 07 */ /* ORA [Dloc] */ 0x0, /* 08 */ /* PHP */ 0x4, /* 09 */ /* ORA #imm */ 0x0, /* 0a */ /* ASL a */ 0x0, /* 0b */ /* PHD */ 0x2, /* 0c */ /* TSB abs */ 0x2, /* 0d */ /* ORA abs */ 0x2, /* 0e */ /* ASL abs */ 0x3, /* 0f */ /* ORA long */ 0x1, /* 10 */ /* BPL disp8 */ 0x1, /* 11 */ /* ORA (),y */ 0x1, /* 12 */ /* ORA () */ 0x1, /* 13 */ /* ORA (disp8,s),y */ 0x1, /* 14 */ /* TRB Dloc */ 0x1, /* 15 */ /* ORA Dloc,x */ 0x1, /* 16 */ /* ASL Dloc,x */ 0x1, /* 17 */ /* ORA [],y */ 0x0, /* 18 */ /* clc */ 0x2, /* 19 */ /* ORA abs,y */ 0x0, /* 1a */ /* INC a */ 0x0, /* 1b */ /* TCS */ 0x2, /* 1c */ /* TRB Abs */ 0x2, /* 1d */ /* ORA Abs,X */ 0x2, /* 1e */ /* ASL abs,x */ 0x3, /* 1f */ /* ORA Long,x */ 0x2, /* 20 */ /* JSR abs */ 0x1, /* 21 */ /* AND (Dloc,X) */ 0x3, /* 22 */ /* JSL Abslong */ 0x1, /* 23 */ /* AND Disp8,S */ 0x1, /* 24 */ /* BIT Dloc */ 0x1, /* 25 */ /* AND Dloc */ 0x1, /* 26 */ /* ROL Dloc */ 0x1, /* 27 */ /* AND [Dloc] */ 0x0, /* 28 */ /* PLP */ 0x4, /* 29 */ /* AND #imm */ 0x0, /* 2a */ /* ROL a */ 0x0, /* 2b */ /* PLD */ 0x2, /* 2c */ /* BIT abs */ 0x2, /* 2d */ /* AND abs */ 0x2, /* 2e */ /* ROL abs */ 0x3, /* 2f */ /* AND long */ 0x1, /* 30 */ /* BMI disp8 */ 0x1, /* 31 */ /* AND (),y */ 0x1, /* 32 */ /* AND () */ 0x1, /* 33 */ /* AND (disp8,s),y */ 0x1, /* 34 */ /* BIT Dloc,X */ 0x1, /* 35 */ /* AND Dloc,x */ 0x1, /* 36 */ /* ROL Dloc,x */ 0x1, /* 37 */ /* AND [],y */ 0x0, /* 38 */ /* SEC */ 0x2, /* 39 */ /* AND abs,y */ 0x0, /* 3a */ /* DEC a */ 0x0, /* 3b */ /* TSC */ 0x2, /* 3c */ /* BIT Abs,X */ 0x2, /* 3d */ /* AND Abs,X */ 0x2, /* 3e */ /* ROL abs,x */ 0x3, /* 3f */ /* AND Long,x */ 0x0, /* 40 */ /* RTI */ 0x1, /* 41 */ /* EOR (Dloc,X) */ 0x2, /* 42 */ /* WDM HACK: uses 2 args */ 0x1, /* 43 */ /* EOR Disp8,S */ 0x2, /* 44 */ /* MVP I,J */ 0x1, /* 45 */ /* EOR Dloc */ 0x1, /* 46 */ /* LSR Dloc */ 0x1, /* 47 */ /* EOR [Dloc] */ 0x0, /* 48 */ /* PHA */ 0x4, /* 49 */ /* EOR #imm */ 0x0, /* 4a */ /* LSR a */ 0x0, /* 4b */ /* PHK */ 0x2, /* 4c */ /* JMP abs */ 0x2, /* 4d */ /* EOR abs */ 0x2, /* 4e */ /* LSR abs */ 0x3, /* 4f */ /* EOR long */ 0x1, /* 50 */ /* BVC disp8 */ 0x1, /* 51 */ /* EOR (),y */ 0x1, /* 52 */ /* EOR () */ 0x1, /* 53 */ /* EOR (disp8,s),y */ 0x2, /* 54 */ /* MVN I,J */ 0x1, /* 55 */ /* EOR Dloc,x */ 0x1, /* 56 */ /* LSR Dloc,x */ 0x1, /* 57 */ /* EOR [],y */ 0x0, /* 58 */ /* CLI */ 0x2, /* 59 */ /* EOR abs,y */ 0x0, /* 5a */ /* PHY */ 0x0, /* 5b */ /* TCD */ 0x3, /* 5c */ /* JMP Long */ 0x2, /* 5d */ /* EOR Abs,X */ 0x2, /* 5e */ /* LSR abs,x */ 0x3, /* 5f */ /* EOR Long,x */ 0x0, /* 60 */ /* RTS */ 0x1, /* 61 */ /* ADC (Dloc,X) */ 0x2, /* 62 */ /* PER DISP16 */ 0x1, /* 63 */ /* ADC Disp8,S */ 0x1, /* 64 */ /* STZ Dloc */ 0x1, /* 65 */ /* ADC Dloc */ 0x1, /* 66 */ /* ROR Dloc */ 0x1, /* 67 */ /* ADC [Dloc] */ 0x0, /* 68 */ /* PLA */ 0x4, /* 69 */ /* ADC #imm */ 0x0, /* 6a */ /* ROR a */ 0x0, /* 6b */ /* RTL */ 0x2, /* 6c */ /* JMP (abs) */ 0x2, /* 6d */ /* ADC abs */ 0x2, /* 6e */ /* ROR abs */ 0x3, /* 6f */ /* ADC long */ 0x1, /* 70 */ /* BVS disp8 */ 0x1, /* 71 */ /* ADC (),y */ 0x1, /* 72 */ /* ADC () */ 0x1, /* 73 */ /* ADC (disp8,s),y */ 0x1, /* 74 */ /* STZ Dloc,X */ 0x1, /* 75 */ /* ADC Dloc,x */ 0x1, /* 76 */ /* ROR Dloc,x */ 0x1, /* 77 */ /* ADC [],y */ 0x0, /* 78 */ /* SEI */ 0x2, /* 79 */ /* ADC abs,y */ 0x0, /* 7a */ /* PLY */ 0x0, /* 7b */ /* TDC */ 0x2, /* 7c */ /* JMP (abs,x) */ 0x2, /* 7d */ /* ADC Abs,X */ 0x2, /* 7e */ /* ROR abs,x */ 0x3, /* 7f */ /* ADC Long,x */ 0x1, /* 80 */ /* BRA Disp8 */ 0x1, /* 81 */ /* STA (Dloc,X) */ 0x2, /* 82 */ /* BRL DISP16 */ 0x1, /* 83 */ /* STA Disp8,S */ 0x1, /* 84 */ /* STY Dloc */ 0x1, /* 85 */ /* STA Dloc */ 0x1, /* 86 */ /* STX Dloc */ 0x1, /* 87 */ /* STA [Dloc] */ 0x0, /* 88 */ /* DEY */ 0x4, /* 89 */ /* BIT #imm */ 0x0, /* 8a */ /* TXA */ 0x0, /* 8b */ /* PHB */ 0x2, /* 8c */ /* STY abs */ 0x2, /* 8d */ /* STA abs */ 0x2, /* 8e */ /* STX abs */ 0x3, /* 8f */ /* STA long */ 0x1, /* 90 */ /* BCC disp8 */ 0x1, /* 91 */ /* STA (),y */ 0x1, /* 92 */ /* STA () */ 0x1, /* 93 */ /* STA (disp8,s),y */ 0x1, /* 94 */ /* STY Dloc,X */ 0x1, /* 95 */ /* STA Dloc,x */ 0x1, /* 96 */ /* STX Dloc,y */ 0x1, /* 97 */ /* STA [],y */ 0x0, /* 98 */ /* TYA */ 0x2, /* 99 */ /* STA abs,y */ 0x0, /* 9a */ /* TXS */ 0x0, /* 9b */ /* TXY */ 0x2, /* 9c */ /* STX abs */ 0x2, /* 9d */ /* STA Abs,X */ 0x2, /* 9e */ /* STZ abs,x */ 0x3, /* 9f */ /* STA Long,x */ 0x5, /* a0 */ /* LDY #imm */ 0x1, /* a1 */ /* LDA (Dloc,X) */ 0x5, /* a2 */ /* LDX #imm */ 0x1, /* a3 */ /* LDA Disp8,S */ 0x1, /* a4 */ /* LDY Dloc */ 0x1, /* a5 */ /* LDA Dloc */ 0x1, /* a6 */ /* LDX Dloc */ 0x1, /* a7 */ /* LDA [Dloc] */ 0x0, /* a8 */ /* TAY */ 0x4, /* a9 */ /* LDA #imm */ 0x0, /* aa */ /* TAX */ 0x0, /* ab */ /* PLB */ 0x2, /* ac */ /* LDY abs */ 0x2, /* ad */ /* LDA abs */ 0x2, /* ae */ /* LDX abs */ 0x3, /* af */ /* LDA long */ 0x1, /* b0 */ /* BCS disp8 */ 0x1, /* b1 */ /* LDA (),y */ 0x1, /* b2 */ /* LDA () */ 0x1, /* b3 */ /* LDA (disp8,s),y */ 0x1, /* b4 */ /* LDY Dloc,X */ 0x1, /* b5 */ /* LDA Dloc,x */ 0x1, /* b6 */ /* LDX Dloc,y */ 0x1, /* b7 */ /* LDA [],y */ 0x0, /* b8 */ /* CLV */ 0x2, /* b9 */ /* LDA abs,y */ 0x0, /* ba */ /* TSX */ 0x0, /* bb */ /* TYX */ 0x2, /* bc */ /* LDY abs,x */ 0x2, /* bd */ /* LDA Abs,X */ 0x2, /* be */ /* LDX abs,y */ 0x3, /* bf */ /* LDA Long,x */ 0x5, /* c0 */ /* CPY #Imm */ 0x1, /* c1 */ /* CMP (Dloc,X) */ 0x1, /* c2 */ /* REP #8bit */ 0x1, /* c3 */ /* CMP Disp8,S */ 0x1, /* c4 */ /* CPY Dloc */ 0x1, /* c5 */ /* CMP Dloc */ 0x1, /* c6 */ /* DEC Dloc */ 0x1, /* c7 */ /* CMP [Dloc] */ 0x0, /* c8 */ /* INY */ 0x4, /* c9 */ /* CMP #imm */ 0x0, /* ca */ /* DEX */ 0x0, /* cb */ /* WAI */ 0x2, /* cc */ /* CPY abs */ 0x2, /* cd */ /* CMP abs */ 0x2, /* ce */ /* DEC abs */ 0x3, /* cf */ /* CMP long */ 0x1, /* d0 */ /* BNE disp8 */ 0x1, /* d1 */ /* CMP (),y */ 0x1, /* d2 */ /* CMP () */ 0x1, /* d3 */ /* CMP (disp8,s),y */ 0x1, /* d4 */ /* PEI Dloc */ 0x1, /* d5 */ /* CMP Dloc,x */ 0x1, /* d6 */ /* DEC Dloc,x */ 0x1, /* d7 */ /* CMP [],y */ 0x0, /* d8 */ /* CLD */ 0x2, /* d9 */ /* CMP abs,y */ 0x0, /* da */ /* PHX */ 0x0, /* db */ /* STP */ 0x2, /* dc */ /* JML (Abs) */ 0x2, /* dd */ /* CMP Abs,X */ 0x2, /* de */ /* DEC abs,x */ 0x3, /* df */ /* CMP Long,x */ 0x5, /* e0 */ /* CPX #Imm */ 0x1, /* e1 */ /* SBC (Dloc,X) */ 0x1, /* e2 */ /* SEP #8bit */ 0x1, /* e3 */ /* SBC Disp8,S */ 0x1, /* e4 */ /* CPX Dloc */ 0x1, /* e5 */ /* SBC Dloc */ 0x1, /* e6 */ /* INC Dloc */ 0x1, /* e7 */ /* SBC [Dloc] */ 0x0, /* e8 */ /* INX */ 0x4, /* e9 */ /* SBC #imm */ 0x0, /* ea */ /* NOP */ 0x0, /* eb */ /* XBA */ 0x2, /* ec */ /* CPX abs */ 0x2, /* ed */ /* SBC abs */ 0x2, /* ee */ /* INC abs */ 0x3, /* ef */ /* SBC long */ 0x1, /* f0 */ /* BEQ disp8 */ 0x1, /* f1 */ /* SBC (),y */ 0x1, /* f2 */ /* SBC () */ 0x1, /* f3 */ /* SBC (disp8,s),y */ 0x2, /* f4 */ /* PEA Imm */ 0x1, /* f5 */ /* SBC Dloc,x */ 0x1, /* f6 */ /* INC Dloc,x */ 0x1, /* f7 */ /* SBC [],y */ 0x0, /* f8 */ /* SED */ 0x2, /* f9 */ /* SBC abs,y */ 0x0, /* fa */ /* PLX */ 0x0, /* fb */ /* XCE */ 0x2, /* fc */ /* JSR (Abs,x) */ 0x2, /* fd */ /* SBC Abs,X */ 0x2, /* fe */ /* INC abs,x */ 0x3, /* ff */ /* SBC Long,x */ ================================================ FILE: gsplus/src/smartport.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" extern int Verbose; extern int Halt_on; extern int g_rom_version; extern int g_io_amt; extern int g_highest_smartport_unit; extern dword64 g_cur_dfcyc; extern Engine_reg engine; extern Iwm g_iwm; #define LEN_SMPT_LOG 16 STRUCT(Smpt_log) { word32 start_addr; int cmd; int rts_addr; int cmd_list; int extras; int unit; int buf; int blk; }; Smpt_log g_smpt_log[LEN_SMPT_LOG]; int g_smpt_log_pos = 0; void smartport_error(void) { int pos; int i; pos = g_smpt_log_pos; printf("Smartport log pos: %d\n", pos); for(i = 0; i < LEN_SMPT_LOG; i++) { pos--; if(pos < 0) { pos = LEN_SMPT_LOG - 1; } printf("%d:%d: t:%04x, cmd:%02x, rts:%04x, " "cmd_l:%04x, x:%d, unit:%d, buf:%04x, blk:%04x\n", i, pos, g_smpt_log[pos].start_addr, g_smpt_log[pos].cmd, g_smpt_log[pos].rts_addr, g_smpt_log[pos].cmd_list, g_smpt_log[pos].extras, g_smpt_log[pos].unit, g_smpt_log[pos].buf, g_smpt_log[pos].blk); } } void smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list) { int pos; pos = g_smpt_log_pos; if(start_addr != 0) { g_smpt_log[pos].start_addr = start_addr; g_smpt_log[pos].cmd = cmd; g_smpt_log[pos].rts_addr = rts_addr; g_smpt_log[pos].cmd_list = cmd_list; g_smpt_log[pos].extras = 0; g_smpt_log[pos].unit = 0; g_smpt_log[pos].buf = 0; g_smpt_log[pos].blk = 0; } else { pos--; if(pos < 0) { pos = LEN_SMPT_LOG - 1; } g_smpt_log[pos].extras = 1; g_smpt_log[pos].unit = cmd; g_smpt_log[pos].buf = rts_addr; g_smpt_log[pos].blk = cmd_list; } pos++; if(pos >= LEN_SMPT_LOG) { pos = 0; } g_smpt_log_pos = pos; } void do_c70d(word32 arg0) { dword64 dsize; word32 status_ptr, rts_addr, cmd_list, cmd_list_lo, cmd_list_mid; word32 cmd_list_hi, status_ptr_lo, status_ptr_mid, status_ptr_hi; word32 rts_lo, rts_hi, buf_ptr_lo, buf_ptr_hi, buf_ptr, mask, cmd; word32 block_lo, block_mid, block_hi, block_hi2, unit, ctl_code; word32 ctl_ptr_lo, ctl_ptr_hi, ctl_ptr, block, stat_val; int param_cnt, ret, ext, slot; int i; slot = (engine.kpc >> 8) & 7; set_memory_c(0x7f8, 0xc0 | slot, 1); if((engine.psr & 0x100) == 0) { disk_printf("c70d %02x called in native mode!\n", arg0); if((engine.psr & 0x30) != 0x30) { halt_printf("c70d called native, psr: %03x!\n", engine.psr); } } engine.stack = ((engine.stack + 1) & 0xff) + 0x100; rts_lo = get_memory_c(engine.stack); engine.stack = ((engine.stack + 1) & 0xff) + 0x100; rts_hi = get_memory_c(engine.stack); rts_addr = (rts_lo + (256*rts_hi) + 1) & 0xffff; disk_printf("rts_addr: %04x\n", rts_addr); cmd = get_memory_c(rts_addr); cmd_list_lo = get_memory_c((rts_addr + 1) & 0xffff); cmd_list_mid = get_memory_c((rts_addr + 2) & 0xffff); cmd_list_hi = 0; mask = 0xffff; ext = 0; if(cmd & 0x40) { ext = 2; mask = 0xffffff; cmd_list_hi = get_memory_c((rts_addr + 3) & 0xffff); } cmd_list = cmd_list_lo + (256*cmd_list_mid) + (65536*cmd_list_hi); disk_printf("cmd: %02x, cmd_list: %06x\n", cmd, cmd_list); param_cnt = get_memory_c(cmd_list); unit = get_memory_c((cmd_list + 1) & mask); ctl_code = get_memory_c((cmd_list + 4 + ext) & mask); smartport_log(0xc70d, cmd, rts_addr, cmd_list); dbg_log_info(g_cur_dfcyc, (rts_addr << 16) | (unit << 8) | cmd, cmd_list, 0xc70d); #if 0 if(cmd != 0x41) { printf("SMTPT: c70d %08x, %08x at %016llx\n", (rts_addr << 16) | (unit << 8) | cmd, cmd_list, g_cur_dfcyc); } #endif ret = 0; if((unit >= 1) && (unit <= MAX_C7_DISKS) && ext) { if(g_iwm.smartport[unit-1].just_ejected) { ret = 0x2e; // DISKSW error } g_iwm.smartport[unit-1].just_ejected = 0; } switch(cmd & 0x3f) { case 0x00: /* Status == 0x00 and 0x40 */ if(param_cnt != 3) { disk_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } status_ptr_lo = get_memory_c((cmd_list+2) & mask); status_ptr_mid = get_memory_c((cmd_list+3) & mask); status_ptr_hi = 0; if(cmd & 0x40) { status_ptr_hi = get_memory_c((cmd_list+4) & mask); } status_ptr = status_ptr_lo + (256*status_ptr_mid) + (65536*status_ptr_hi); smartport_log(0, unit, status_ptr, ctl_code); dbg_log_info(g_cur_dfcyc, (ctl_code << 16) | unit, cmd_list, 0xc700); disk_printf("unit: %02x, status_ptr: %06x, code: %02x\n", unit, status_ptr, ctl_code); if((unit == 0) && (ctl_code == 0)) { /* Smartport driver status */ /* see technotes/smpt/tn-smpt-002 */ set_memory_c(status_ptr, MAX_C7_DISKS, 1); set_memory_c(status_ptr+1, 0xff, 1); // intrpt stat set_memory16_c(status_ptr+2, 0x004b, 1); // vendor id set_memory16_c(status_ptr+4, 0x1000, 1); // version set_memory16_c(status_ptr+6, 0x0000, 1); //printf(" driver status, highest_unit:%02x\n", // g_highest_smartport_unit+1); engine.xreg = 8; engine.yreg = 0; } else if((unit > 0) && (ctl_code == 0)) { /* status for unit x */ if((unit > MAX_C7_DISKS) || (g_iwm.smartport[unit-1].fd < 0)) { stat_val = 0x80; dsize = 0; ret = 0; // Not DISK_SWITCHed error } else { stat_val = 0xf8; dsize = g_iwm.smartport[unit-1].dimage_size; dsize = (dsize+511) / 512; if(g_iwm.smartport[unit-1].write_prot) { stat_val |= 4; // Write prot } } #if 0 printf(" status unit:%02x just_ejected:%d, " "stat_val:%02x\n", unit, g_iwm.smartport[unit-1].just_ejected, stat_val); #endif set_memory_c(status_ptr, stat_val, 1); set_memory24_c(status_ptr + 1, (word32)dsize); engine.xreg = 4; if(cmd & 0x40) { set_memory_c(status_ptr + 4, (dsize >> 24) & 0xff, 1); engine.xreg = 5; } engine.yreg = 0; disk_printf("just finished unit %d, stat 0\n", unit); } else if(ctl_code == 3) { if((unit > MAX_C7_DISKS) || (g_iwm.smartport[unit-1].fd < 0)) { stat_val = 0x80; dsize = 0; ret = 0; // Not a disk-switched error } else { stat_val = 0xf8; dsize = g_iwm.smartport[unit-1].dimage_size; dsize = (dsize + 511) / 512; } if(cmd & 0x40) { disk_printf("extended for stat_code 3!\n"); } /* DIB for unit 1 */ set_memory_c(status_ptr, stat_val, 1); set_memory24_c(status_ptr + 1, (word32)dsize); if(cmd & 0x40) { set_memory_c(status_ptr + 4, (dsize >> 24) & 0xff, 1); status_ptr++; } set_memory_c(status_ptr + 4, 4, 1); for(i = 5; i < 21; i++) { set_memory_c(status_ptr + i, 0x20, 1); } set_memory_c(status_ptr + 5, 'K', 1); set_memory_c(status_ptr + 6, 'E', 1); set_memory_c(status_ptr + 7, 'G', 1); set_memory_c(status_ptr + 8, 'S', 1); // Profile hard disk supporting extended calls+disk_sw set_memory16_c(status_ptr + 21, 0xc002, 1); set_memory16_c(status_ptr + 23, 0x0000, 1); if(cmd & 0x40) { engine.xreg = 26; } else { engine.xreg = 25; } #if 0 printf(" DIB unit:%02x just_ejected:%d, " "stat_val:%02x\n", unit, g_iwm.smartport[unit-1].just_ejected, stat_val); #endif engine.yreg = 0; disk_printf("Just finished unit %d, stat 3\n", unit); if(unit == 0 || unit > MAX_C7_DISKS) { ret = 0x28; // NODRIVE error } } else { printf("cmd: 00, unknown unit/status code %02x!\n", ctl_code); ret = 0x21; // BADCTL } break; case 0x01: /* Read Block == 0x01 and 0x41 */ if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } buf_ptr_lo = get_memory_c((cmd_list+2) & mask); buf_ptr_hi = get_memory_c((cmd_list+3) & mask); buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); if(cmd & 0x40) { buf_ptr_lo = get_memory_c((cmd_list+4) & mask); buf_ptr_hi = get_memory_c((cmd_list+5) & mask); buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; cmd_list += 2; } block_lo = get_memory_c((cmd_list+4) & mask); block_mid = get_memory_c((cmd_list+5) & mask); block_hi = get_memory_c((cmd_list+6) & mask); block_hi2 = 0; if(cmd & 0x40) { block_hi2 = get_memory_c((cmd_list+7) & mask); } block = (block_hi2 << 24) | (block_hi << 16) | (block_mid << 8) | block_lo; disk_printf("smartport read unit %d of block %06x to %06x\n", unit, block, buf_ptr); if(unit < 1 || unit > MAX_C7_DISKS) { halt_printf("Unknown unit #: %d\n", unit); } smartport_log(0, unit - 1, buf_ptr, block); if(ret == 0) { ret = do_read_c7(unit - 1, buf_ptr, block); } engine.xreg = 0; engine.yreg = 2; break; case 0x02: /* Write Block == 0x02 and 0x42 */ if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } buf_ptr_lo = get_memory_c((cmd_list+2) & mask); buf_ptr_hi = get_memory_c((cmd_list+3) & mask); buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); if(cmd & 0x40) { buf_ptr_lo = get_memory_c((cmd_list+4) & mask); buf_ptr_hi = get_memory_c((cmd_list+5) & mask); buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; cmd_list += 2; } block_lo = get_memory_c((cmd_list+4) & mask); block_mid = get_memory_c((cmd_list+5) & mask); block_hi = get_memory_c((cmd_list+6) & mask); block_hi2 = 0; if(cmd & 0x40) { block_hi2 = get_memory_c((cmd_list+7) & mask); } block = (block_hi2 << 24) | (block_hi << 16) | (block_mid << 8) | block_lo; disk_printf("smartport write unit %d of block %04x from %04x\n", unit, block, buf_ptr); if(unit < 1 || unit > MAX_C7_DISKS) { halt_printf("Unknown unit #: %d\n", unit); } smartport_log(0, unit - 1, buf_ptr, block); if(ret == 0) { ret = do_write_c7(unit - 1, buf_ptr, block); } engine.xreg = 0; engine.yreg = 2; HALT_ON(HALT_ON_C70D_WRITES, "c70d Write done\n"); break; case 0x03: /* Format == 0x03 and 0x43 */ if(param_cnt != 1) { halt_printf("param_cnt %d is != 1!\n", param_cnt); ret = 0x04; // BADPCNT break; } if((unit < 1) || (unit > MAX_C7_DISKS)) { halt_printf("Unknown unit #: %d\n", unit); ret = 0x11; // BADUNIT } smartport_log(0, unit - 1, 0, 0); if(ret == 0) { ret = do_format_c7(unit - 1); } engine.xreg = 0; engine.yreg = 2; HALT_ON(HALT_ON_C70D_WRITES, "c70d Format done\n"); break; case 0x04: /* Control == 0x04 and 0x44 */ if(cmd == 0x44) { halt_printf("smartport code 0x44 not supported\n"); } if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); break; } ctl_ptr_lo = get_memory_c((cmd_list+2) & mask); ctl_ptr_hi = get_memory_c((cmd_list+3) & mask); ctl_ptr = (ctl_ptr_hi << 8) + ctl_ptr_lo; if(cmd & 0x40) { ctl_ptr_lo = get_memory_c((cmd_list+4) & mask); ctl_ptr_hi = get_memory_c((cmd_list+5) & mask); ctl_ptr += ((ctl_ptr_hi << 8) + ctl_ptr_lo) << 16; cmd_list += 2; } switch(ctl_code) { case 0x00: printf("Performing a reset on unit %d\n", unit); break; default: halt_printf("control code: %02x ptr:%06x unknown!\n", ctl_code, ctl_ptr); } // printf("CONTROL, ctl_code:%02x\n", ctl_code); engine.xreg = 0; engine.yreg = 2; break; default: /* Unknown command! */ /* set acc = 1, and set carry, and set kpc */ engine.xreg = (rts_addr) & 0xff; engine.yreg = (rts_addr >> 8) & 0xff; ret = 0x01; // BADCMD error if((cmd != 0x4b) && (cmd != 0x48) && (cmd != 0x4a)) { // Finder does 0x4a before dialog for formatting disk // Finder does 0x4b call before formatting disk // Many things do 0x48 call to see online drives // So: ignore those, just return BADCMD halt_printf("Just did smtport cmd:%02x rts_addr:%04x, " "cmdlst:%06x\n", cmd, rts_addr, cmd_list); } } engine.acc = (engine.acc & 0xff00) | (ret & 0xff); engine.psr &= ~1; if(ret) { engine.psr |= 1; printf("Smtport cmd:%02x unit:%02x ctl_code:%02x ret:%02x\n", cmd, unit, ctl_code, ret); } engine.kpc = (rts_addr + 3 + ext) & 0xffff; // printf(" ret:%02x psr_c:%d\n", ret & 0xff, engine.psr & 1); } // $C70A is the ProDOS entry point, documented in ProDOS 8 Technical Ref // Manual, section 6.3. void do_c70a(word32 arg0) { dword64 dsize; word32 cmd, unit, buf_lo, buf_hi, blk_lo, blk_hi, blk, buf; word32 prodos_unit; int ret, slot; slot = (engine.kpc >> 8) & 7; set_memory_c(0x7f8, 0xc0 | slot, 1); cmd = get_memory_c((engine.direct + 0x42) & 0xffff); prodos_unit = get_memory_c((engine.direct + 0x43) & 0xffff); buf_lo = get_memory_c((engine.direct + 0x44) & 0xffff); buf_hi = get_memory_c((engine.direct + 0x45) & 0xffff); blk_lo = get_memory_c((engine.direct + 0x46) & 0xffff); blk_hi = get_memory_c((engine.direct + 0x47) & 0xffff); blk = (blk_hi << 8) + blk_lo; buf = (buf_hi << 8) + buf_lo; disk_printf("c70a %02x cmd:%02x, pro_unit:%02x, buf:%04x, blk:%04x\n", arg0, cmd, prodos_unit, buf, blk); unit = 0 + (prodos_unit >> 7); // units 0,1 if((prodos_unit & 0x7f) != (slot << 4)) { unit += 2; // units 2,3 } smartport_log(0xc70a, cmd, blk, buf); dbg_log_info(g_cur_dfcyc, (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk, 0xc70a); #if 0 if(cmd != 0x1ff) { printf("SMTPT: c70a %08x %08x\n", (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk); } #endif engine.psr &= ~1; /* clear carry */ ret = 0x27; /* I/O error */ if(cmd == 0x00) { dsize = g_iwm.smartport[unit].dimage_size; dsize = (dsize + 511) / 512; smartport_log(0, unit, (word32)dsize, 0); dbg_log_info(g_cur_dfcyc, ((unit & 0xff) << 8) | (cmd & 0xff), (word32)dsize, 0x1c700); ret = 0; engine.xreg = dsize & 0xff; engine.yreg = (word32)(dsize >> 8); } else if(cmd == 0x01) { smartport_log(0, unit, buf, blk); ret = do_read_c7(unit, buf, blk); } else if(cmd == 0x02) { smartport_log(0, unit, buf, blk); ret = do_write_c7(unit, buf, blk); } else if(cmd == 0x03) { /* format */ smartport_log(0, unit, buf, blk); ret = do_format_c7(unit); } engine.acc = (engine.acc & 0xff00) | (ret & 0xff); if(ret != 0) { engine.psr |= 1; // Set carry } return; } int do_read_c7(int unit_num, word32 buf, word32 blk) { byte local_buf[0x200]; Disk *dsk; byte *bptr; dword64 dimage_start, dimage_size, dret; word32 val; int len, fd; int i; dbg_log_info(g_cur_dfcyc, (buf << 8) | (unit_num & 0xff), blk, 0xc701); if((unit_num < 0) || (unit_num > MAX_C7_DISKS)) { halt_printf("do_read_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { printf("c7_fd == %d!\n", fd); #if 0 if(blk != 2 && blk != 0) { /* don't print error if only reading directory */ smartport_error(); halt_printf("Read unit:%02x blk:%04x\n", unit_num, blk); } #endif return 0x2f; } if(((blk + 1) * 0x200ULL) > (dimage_start + dimage_size)) { halt_printf("Tried to read past %08llx on disk (blk:%04x)\n", dimage_start + dimage_size, blk); smartport_error(); return 0x27; } if(dsk->raw_data) { // image was compressed and is in dsk->raw_data bptr = dsk->raw_data + dimage_start + (blk*0x200ULL); for(i = 0; i < 0x200; i++) { local_buf[i] = bptr[i]; } } else { dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); if(dret != (dimage_start + blk*0x200ULL)) { halt_printf("lseek ret %08llx, errno:%d\n", dret, errno); smartport_error(); return 0x27; } len = (int)read(fd, &local_buf[0], 0x200); if(len != 0x200) { printf("read returned %08x, errno:%d, blk:%04x, unit:" "%02x\n", len, errno, blk, unit_num); halt_printf("name: %s\n", dsk->name_ptr); smartport_error(); return 0x27; } } g_io_amt += 0x200; if(buf >= 0xfc0000) { disk_printf("reading into ROM, just returning\n"); return 0; } for(i = 0; i < 0x200; i += 2) { val = (local_buf[i+1] << 8) + local_buf[i]; set_memory16_c(buf + i, val, 0); } return 0; } int do_write_c7(int unit_num, word32 buf, word32 blk) { byte local_buf[0x200]; Disk *dsk; dword64 dret, dimage_start, dimage_size; int len, fd, ret; int i; dbg_log_info(g_cur_dfcyc, (buf << 16) | (unit_num & 0xff), blk, 0xc702); if(unit_num < 0 || unit_num > MAX_C7_DISKS) { halt_printf("do_write_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { halt_printf("c7_fd == %d!\n", fd); smartport_error(); return 0x28; } for(i = 0; i < 0x200; i++) { local_buf[i] = get_memory_c(buf + i); } if(dsk->write_prot) { printf("Write, but s7d%d %s is write protected!\n", unit_num + 1, dsk->name_ptr); return 0x2b; } if(dsk->write_through_to_unix == 0) { //halt_printf("Write to %s, but not wr_thru!\n", dsk->name_ptr); if(dsk->raw_data) { // Update the memory copy ret = smartport_memory_write(dsk, &local_buf[0], blk * 0x200ULL, 0x200); if(ret) { return 0x27; // I/O Error } } return 0x00; } if(dsk->dynapro_info_ptr) { dynapro_write(dsk, &local_buf[0], blk*0x200UL, 0x200); } else { dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); if(dret != (dimage_start + blk*0x200ULL)) { halt_printf("lseek returned %08llx, errno: %d\n", dret, errno); smartport_error(); return 0x27; } if(dret >= (dimage_start + dimage_size)) { halt_printf("Tried to write to %08llx\n", dret); smartport_error(); return 0x27; } len = (int)write(fd, &local_buf[0], 0x200); if(len != 0x200) { halt_printf("write ret %08x bytes, errno: %d\n", len, errno); smartport_error(); dsk->write_prot = 1; return 0x2b; // Write protected } } g_io_amt += 0x200; return 0; } int smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) { byte *bptr; word32 ui; bptr = dsk->raw_data; if((bptr == 0) || ((doffset + size) > dsk->dimage_size)) { printf("Write to %s failed, %08llx past end %08llx\n", dsk->name_ptr, doffset, dsk->dimage_size); return -1; } for(ui = 0; ui < size; ui++) { bptr[doffset + ui] = bufptr[ui]; } return 0; } int do_format_c7(int unit_num) { byte local_buf[0x1000]; Disk *dsk; dword64 dimage_start, dimage_size, dret, dtotal, dsum; int len, max, fd, ret; int i; dbg_log_info(g_cur_dfcyc, (unit_num & 0xff), 0, 0xc703); if(unit_num < 0 || unit_num > MAX_C7_DISKS) { halt_printf("do_format_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { halt_printf("c7_fd == %d!\n", fd); smartport_error(); return 0x28; } if(dsk->write_prot || (dsk->raw_data && !dsk->dynapro_info_ptr)) { printf("Format, but %s is write protected!\n", dsk->name_ptr); return 0x2b; } if(dsk->write_through_to_unix == 0) { if(!dsk->raw_data) { printf("Format of %s ignored\n", dsk->name_ptr); return 0x00; } } for(i = 0; i < 0x1000; i++) { local_buf[i] = 0; } if(!dsk->dynapro_info_ptr) { dret = kegs_lseek(fd, dimage_start, SEEK_SET); if(dret != dimage_start) { halt_printf("lseek returned %08llx, errno: %d\n", dret, errno); smartport_error(); return 0x27; } } dsum = 0; dtotal = dimage_size; while(dsum < dtotal) { max = (int)MY_MIN(0x1000, dtotal - dsum); len = max; if(dsk->dynapro_info_ptr) { dynapro_write(dsk, &local_buf[0], dsum, max); } else if(dsk->raw_data) { ret = smartport_memory_write(dsk, &local_buf[0], dsum, max); if(ret) { return 0x27; // I/O Error } } else { len = (int)write(fd, &local_buf[0], max); } if(len != max) { halt_printf("write ret %08x, errno:%d\n", len, errno); smartport_error(); dsk->write_prot = 1; return 0x2b; // Write-protected } dsum += len; } return 0; } void do_c700(word32 ret) { int slot; disk_printf("do_c700 called, ret: %08x\n", ret); dbg_log_info(g_cur_dfcyc, 0, 0, 0xc700); slot = (engine.kpc >> 8) & 7; ret = do_read_c7(0, 0x800, 0); // Always read unit 0, block 0 set_memory_c(0x7f8, slot, 1); set_memory16_c(0x42, (slot << 12) | 1, 1); set_memory16_c(0x44, 0x0800, 1); set_memory16_c(0x46, 0x0000, 1); engine.xreg = slot << 4; // 0x70 for slot 7 engine.kpc = 0x801; if(ret != 0) { printf("Failure reading boot disk in s7d1, trying slot 5!\n"); engine.kpc = 0xc500; // Try to boot slot 5 if((slot == 5) || (g_rom_version == 0)) { engine.kpc = 0xc600; // Try to boot slot 6 } } } ================================================ FILE: gsplus/src/sound.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include "defc.h" #define INCLUDE_RCSID_C #include "sound.h" #undef INCLUDE_RCSID_C #define DOC_LOG(a,b,c,d) extern int Verbose; extern int g_use_shmem; extern word32 g_vbl_count; extern int g_preferred_rate; extern word32 g_c03ef_doc_ptr; extern int g_doc_vol; extern dword64 g_last_vbl_dfcyc; int g_queued_samps = 0; int g_queued_nonsamps = 0; #if defined(HPUX) || defined(__linux__) || defined(_WIN32) || defined(MAC) int g_audio_enable = -1; #else int g_audio_enable = 0; /* Not supported: default to off */ #endif int g_sound_min_msecs = 32; // 32 msecs int g_sound_min_msecs_pulse = 150; // 150 msecs int g_sound_max_multiplier = 6; // 6*32 = ~200 msecs int g_sound_min_samples = 48000 * 32/1000; // 32 msecs Mockingboard g_mockingboard; // The AY8913 chip has non-linear amplitudes (it has 16 levels) and the // documentation does not match measured results. But all the measurements // should really be done at the final speaker/jack since all the stuff in // the path affects it. But: no one's done this for Mockingboard that I // have found, so I'm taking measurements from the AY8913 chip itself. // AY8913 amplitudes from https://groups.google.com/forum/#!original/ // comp.sys.sinclair/-zCR2kxMryY/XgvaDICaldUJ // by Matthew Westcott on December 21, 2001. double g_ay8913_ampl_factor_westcott[16] = { // NOT USED 0.000, // level[0] 0.010, // level[1] 0.015, // level[2] 0.022, // level[3] 0.031, // level[4] 0.046, // level[5] 0.064, // level[6] 0.106, // level[7] 0.132, // level[8] 0.216, // level[9] 0.297, // level[10] 0.391, // level[11] 0.513, // level[12] 0.637, // level[13] 0.819, // level[14] 1.000, // level[15] }; // https://sourceforge.net/p/fuse-emulator/mailman/message/34065660/ // refers to some Russian-language measurements at: // http://forum.tslabs.info/viewtopic.php?f=6&t=539 (translate from // Russian), they give: // 0000,028F,03B3,0564, 07DC,0BA9,1083,1B7C, // 2068,347A,4ACE,5F72, 7E16,A2A4,CE3A,FFFF double g_ay8913_ampl_factor[16] = { 0.000, // level[0] 0.010, // level[1] 0.014, // level[2] 0.021, // level[3] 0.031, // level[4] 0.046, // level[5] 0.064, // level[6] 0.107, // level[7] 0.127, // level[8] 0.205, // level[9] 0.292, // level[10] 0.373, // level[11] 0.493, // level[12] 0.635, // level[13] 0.806, // level[14] 1.000, // level[15] }; // MAME also appears to try to figure out how the channels get "summed" // together. KEGS code adds them in a completely independent way, and due // to the circuit used on the AY8913, this is certainly incorrect. #define MAX_MOCK_ENV_SAMPLES 2000 int g_mock_env_vol[MAX_MOCK_ENV_SAMPLES]; byte g_mock_noise_bytes[MAX_MOCK_ENV_SAMPLES]; int g_mock_volume[16]; // Sample for each of the 16 amplitudes word32 g_last_mock_vbl_count = 0; #define VAL_MOCK_RANGE (39000) int g_audio_rate = 0; double g_daudio_rate = 0.0; double g_drecip_audio_rate = 0.0; double g_dsamps_per_dfcyc = 0.0; double g_fcyc_per_samp = 0.0; double g_last_sound_play_dsamp = 0.0; #define VAL_C030_POS_VAL (20400*16/15) // C030_POS_VAL is multiplied by g_doc_vol (0-15) and then // divided by 16. So scale this value up by 16/15 so that // g_doc_vol==15 gives the intended value (+/-20400) #define MAX_C030_TIMES 18000 float g_c030_fsamps[MAX_C030_TIMES + 2]; int g_num_c030_fsamps = 0; int g_c030_state = 0; int g_c030_val = (VAL_C030_POS_VAL / 2); dword64 g_c030_dsamp_last_toggle = 0; word32 *g_sound_shm_addr = 0; int g_sound_shm_pos = 0; extern dword64 g_cur_dfcyc; #define MAX_SND_BUF 65536 int g_samp_buf[2*MAX_SND_BUF]; byte g_zero_buf[4096]; double g_doc_dsamps_extra = 0.0; int g_num_snd_plays = 0; int g_num_recalc_snd_parms = 0; char *g_sound_file_str = 0; int g_sound_file_fd = -1; int g_sound_file_bytes = 0; // WAV file information: // From https://docs.fileformat.com/audio/wav/ // left channel is first: https://web.archive.org/web/20080113195252/ // http://www.borg.com/~jglatt/tech/wave.htm byte g_wav_hdr[44] = { 'R', 'I', 'F', 'F', 0xff, 0xff, 0xff, 0xff, // 0x00-0x07 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ', // 0x08-0x0f 16, 0, 0, 0, 1, 0, 2, 0, // 0x10-0x17 // 16=length of 'fmt ' chunk, 1=PCM, 2=stereo. 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0x18-0x1f 4, 0, 16, 0, 'd', 'a', 't', 'a', // 0x20-0x27 0xff, 0xff, 0xff, 0xff // 0x28-0x2b }; // Bytes 4-7 are the total file size-8, so [0x28:0x2b]+0x24 // Bytes [0x18-0x1b]is the sample rate (so 44100 or so) // [0x1c-0x1f] is bytes-per-second: [0x18-0x1b]*[0x10]*[0x16]/8 void sound_init() { doc_init(); snddrv_init(); } void sound_set_audio_rate(int rate) { g_audio_rate = rate; g_daudio_rate = (rate)*1.0; g_drecip_audio_rate = 1.0/(rate); g_dsamps_per_dfcyc = ((rate*1.0) / (DCYCS_1_MHZ * 65536.0)); g_fcyc_per_samp = (DCYCS_1_MHZ * 65536.0 / (rate*1.0)); g_sound_min_samples = rate * g_sound_min_msecs / 1000; printf("Set g_audio_rate = %d in main KEGS process, min_samples:%d\n", rate, g_sound_min_samples); } void sound_reset(dword64 dfcyc) { doc_reset(dfcyc); mockingboard_reset(dfcyc); } void sound_shutdown() { snddrv_shutdown(); } void sound_update(dword64 dfcyc) { /* Called every VBL time to update sound status */ /* "play" sounds for this vbl */ //DOC_LOG("do_snd_pl", -1, dsamps, 0); sound_play(dfcyc); } void sound_file_start(char *filename) { sound_file_close(); g_sound_file_str = filename; // Can be NULL, if so, do not start if(filename) { printf("Set audio save file to: %s\n", filename); } } void sound_file_open() { char *filename; word32 exp_size; int fd; filename = g_sound_file_str; if(!filename) { return; } fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); if(fd < 0) { printf("open_sound_file open ret: %d, errno: %d\n", fd, errno); sound_file_close(); return; } exp_size = 1024*1024; // Default to 1MB, changed at close dynapro_set_word32(&g_wav_hdr[4], exp_size + 44 - 8); // File size dynapro_set_word32(&g_wav_hdr[0x28], exp_size); // data size dynapro_set_word32(&g_wav_hdr[0x18], g_audio_rate); // Sample rate dynapro_set_word32(&g_wav_hdr[0x1c], g_audio_rate * 2 * 2); // bytes-per-sec (void)cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); g_sound_file_fd = fd; g_sound_file_bytes = 0; printf("Opened file %s for sound\n", filename); } void sound_file_close() { int fd; fd = g_sound_file_fd; if(fd >= 0) { dynapro_set_word32(&g_wav_hdr[0x28], g_sound_file_bytes); dynapro_set_word32(&g_wav_hdr[4], g_sound_file_bytes + 44 - 8); cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); // Rewrite first 44 bytes with WAV header printf("Close sound file %s, fd:%d\n", g_sound_file_str, fd); close(fd); } free(g_sound_file_str); g_sound_file_fd = -1; g_sound_file_str = 0; } void send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps) { int size, this_size; if(!real_samps && g_sound_file_bytes) { // Don't do anything return; } if(g_sound_file_fd < 0) { sound_file_open(); } if(!wptr) { // No real samps size = real_samps * 4; while(size) { this_size = size; if(this_size > 4096) { this_size = 4096; } must_write(g_sound_file_fd, &g_zero_buf[0], this_size); size -= this_size; } return; } size = 0; if((num_samps + shm_pos) > SOUND_SHM_SAMP_SIZE) { size = SOUND_SHM_SAMP_SIZE - shm_pos; g_sound_file_bytes += (size * 4); must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*size); shm_pos = 0; num_samps -= size; } g_sound_file_bytes += (num_samps * 4); must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*num_samps); } void show_c030_state(dword64 dfcyc) { show_c030_samps(dfcyc, &(g_samp_buf[0]), 100); } void show_c030_samps(dword64 dfcyc, int *outptr, int num) { int last; int i; if(!g_num_c030_fsamps) { return; } printf("c030_fsamps[]: %d, dfcyc:%015llx\n", g_num_c030_fsamps, dfcyc); for(i = 0; i < g_num_c030_fsamps+2; i++) { printf("%3d: %5.3f\n", i, g_c030_fsamps[i]); } printf("Samples[] = %d\n", num); last = 0x0dadbeef; for(i = 0; i < num; i++) { if((last != outptr[0]) || (i == (num - 1))) { printf("Samp[%4d]: %d\n", i, outptr[0]); last = outptr[0]; } outptr += 2; } } int sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps) { int *outptr; dword64 dsamp_min; float ftmp, fsampnum, next_fsampnum, fpercent; int val, num, c030_state, c030_val, pos, sampnum, next_sampnum; int doc_vol, min_i, mul; int i, j; // Handle $C030 speaker clicks. Clicks for the past num_samps are // in g_c030_fsamps[] giving the sample position when the click // occurred. Turn this into samples, tracking multiple clicks per // sample into an intermediate value. After 500ms of no clicks, // transition the speakers from +/-20400 to 0, so it's idle. // The speaker is affected by the DOC volume in g_doc_vol, like a real // IIgs (this is used during the system beep). This code reacts // to DOC volume changes when they happen by causing sound_play() // to be called, so all samples with the old volume are played then // new clicks are collected. num = g_num_c030_fsamps; // Number of clicks if(!num) { if(g_c030_val == 0) { return 0; // Speaker is at rest } } pos = 0; outptr = outptr_start; c030_state = g_c030_state; c030_val = g_c030_val; // c030_val may be less than max due decay after 500ms. // Always use it first, until speaker toggles, which should // restore the full speaker range doc_vol = g_doc_vol; if(!num) { // No clicks. See if we should begin transitioning the // speaker output to 0. I tried multiplying by .9999 but // that seemed to take too long at the end, so just use a // linear ramp down. Do this ramp based on the last click // time, not VBL, since this is more consistent dsamp_min = g_c030_dsamp_last_toggle + (g_audio_rate >> 4); if(dsamp >= dsamp_min) { min_i = 0; } else { min_i = (int)(dsamp_min - dsamp); } mul = (2 * c030_state - 1) * doc_vol; val = (c030_val * mul) >> 4; for(i = 0; i < num_samps; i++) { if(i >= min_i) { if(c030_val > 4) { c030_val -= 4; } else { c030_val = 0; } val = (c030_val * mul) >> 4; } outptr[0] = val; outptr[1] = val; outptr += 2; } #if 0 printf("at %015llx, num_samps:%d val at start:%d, at end:%d, " "min_i:%d\n", dfcyc, num_samps, g_c030_val, c030_val, min_i); #endif g_c030_val = c030_val; if(c030_val == 0) { //printf("Speaker at rest\n"); } return 1; } g_c030_fsamps[num] = (float)(num_samps); num++; fsampnum = g_c030_fsamps[0]; sampnum = (int)fsampnum; fpercent = (float)0.0; i = 0; while(i < num) { if(sampnum < 0 || sampnum > num_samps) { halt_printf("play c030: [%d]:%f is %d, > %d\n", i, fsampnum, sampnum, num_samps); break; } /* write in samples to all samps < me */ val = ((2 * c030_state) - 1) * ((c030_val * doc_vol) >> 4); if(num <= 1) { printf("num:%d i:%d pos:%d, sampnum:%d c030_state:%d " " at %015llx\n", num, i, pos, sampnum, c030_state, dfcyc); } for(j = pos; j < sampnum; j++) { outptr[0] = val; outptr[1] = val; outptr += 2; pos++; } if((sampnum >= num_samps) || ((i + 1) >= num)) { break; // All done } /* now, calculate me */ fpercent = (float)0.0; if(c030_state) { fpercent = (fsampnum - (float)sampnum); } c030_state = !c030_state; c030_val = VAL_C030_POS_VAL; g_c030_dsamp_last_toggle = dsamp + i; next_fsampnum = g_c030_fsamps[i+1]; next_sampnum = (int)next_fsampnum; // Handle all the changes during this one sample while(next_sampnum == sampnum) { if(c030_state) { fpercent += (next_fsampnum - fsampnum); } i++; fsampnum = next_fsampnum; if(i > num) { break; // This should not happen! } next_fsampnum = g_c030_fsamps[i+1]; next_sampnum = (int)next_fsampnum; c030_state = !c030_state; } if(c030_state) { // add in time until next sample ftmp = (float)(int)(fsampnum + (float)1.0); fpercent += (ftmp - fsampnum); } if((fpercent < (float)0.0) || (fpercent > (float)1.0)) { halt_printf("fpercent: %d = %f\n", i, fpercent); show_c030_samps(dfcyc, outptr_start, num_samps); break; } val = (int)((2*fpercent - 1) * ((c030_val * doc_vol) >> 4)); outptr[0] = val; outptr[1] = val; outptr += 2; pos++; i++; sampnum = next_sampnum; fsampnum = next_fsampnum; } g_c030_state = c030_state; g_c030_val = c030_val; #if 0 if(g_sound_file_str) { show_c030_samps(dfcyc, outptr_start, num_samps); } #endif // See if there are any entries >= fsampnum, copy them back down // to the beginning of the array pos = 0; num--; fsampnum = (float)num_samps; while(i < num) { g_c030_fsamps[pos] = g_c030_fsamps[i] - fsampnum; #if 0 printf("Copied [%d] %f to [%d] as %f\n", i, g_c030_fsamps[i], pos, g_c030_fsamps[pos]); #endif i++; pos++; } g_num_c030_fsamps = pos; return 1; } int g_sound_play_depth = 0; // sound_play(): forms the samples from the last sample time to the current // time. Can be called anytime from anywhere. This is how KEGS handles // dynamic sound changes (say, disabling an Ensoniq oscillator manually): // when it's turned off, call sound_play() to play up to this moment, then // the next time sound_play() is called, it will just know this osc is off // So, on any sound-related state change, call sound_play(). void sound_play(dword64 dfcyc) { Ay8913 *ay8913ptr; int *outptr, *outptr_start; word32 *sndptr; double last_dsamp, dsamp_now, dvolume, dsamps; word32 uval1, uval0; int val, val0, val1, pos, snd_buf_init, num_samps, num_pairs; int sound_mask, ivol; int i, j; g_num_snd_plays++; if(g_sound_play_depth) { halt_printf("Nested sound_play!\n"); } g_sound_play_depth++; /* calc sample num */ dsamps = dfcyc * g_dsamps_per_dfcyc; last_dsamp = g_last_sound_play_dsamp; num_samps = (int)(dsamps - g_last_sound_play_dsamp); dsamp_now = last_dsamp + (double)num_samps; if(num_samps < 1) { /* just say no */ g_sound_play_depth--; return; } dbg_log_info(dfcyc, (word32)(dword64)dsamp_now, num_samps, 0x200); if(num_samps > MAX_SND_BUF) { printf("num_samps: %d, too big!\n", num_samps); g_sound_play_depth--; return; } outptr_start = &(g_samp_buf[0]); outptr = outptr_start; snd_buf_init = sound_play_c030(dfcyc, (dword64)dsamp_now, outptr_start, num_samps); snd_buf_init = doc_play(dfcyc, last_dsamp, dsamp_now, num_samps, snd_buf_init, outptr_start); num_pairs = 0; // Do Mockinboard channels for(i = 0; i < 2; i++) { // Pair: 0 or 1 ay8913ptr = &(g_mockingboard.pair[i].ay8913); for(j = 0; j < 3; j++) { // Channels: A, B, or C if((ay8913ptr->regs[8 + j] & 0x1f) == 0) { continue; } num_pairs = 2; g_last_mock_vbl_count = g_vbl_count; break; } } if((g_vbl_count - g_last_mock_vbl_count) < 120) { // Keep playing for 2 seconds, to avoid some static issues num_pairs = 2; } if(num_pairs) { sound_mask = -1; if(snd_buf_init == 0) { sound_mask = 0; snd_buf_init++; } outptr = outptr_start; ivol = -((VAL_MOCK_RANGE * 3 / (8 * 15)) * g_doc_vol); // Do 3/8 of range below 0, leaving 5/8 above 0 for(i = 0; i < num_samps; i++) { outptr[0] = (outptr[0] & sound_mask) + ivol; outptr[1] = (outptr[1] & sound_mask) + ivol; outptr += 2; } for(i = 0; i < 16; i++) { dvolume = (g_doc_vol * VAL_MOCK_RANGE) / (15.0 * 3.0); ivol = (int)(g_ay8913_ampl_factor[i] * dvolume); g_mock_volume[i] = ivol; } } for(i = 0; i < num_pairs; i++) { if(g_mockingboard.disable_mask) { printf("dsamp:%lf\n", dsamps); } sound_mock_envelope(i, &(g_mock_env_vol[0]), num_samps, &(g_mock_volume[0])); sound_mock_noise(i, &(g_mock_noise_bytes[0]), num_samps); for(j = 0; j < 3; j++) { sound_mock_play(i, j, outptr_start, &(g_mock_env_vol[0]), &(g_mock_noise_bytes[0]), &(g_mock_volume[0]), num_samps); } } g_last_sound_play_dsamp = dsamp_now; outptr = outptr_start; pos = g_sound_shm_pos; sndptr = g_sound_shm_addr; #if 0 printf("samps_left: %d, num_samps: %d\n", samps_left, num_samps); #endif if(g_audio_enable != 0) { if(snd_buf_init) { /* convert sound buf */ for(i = 0; i < num_samps; i++) { val0 = outptr[0]; val1 = outptr[1]; val = val0; if(val0 > 32767) { val = 32767; } if(val0 < -32768) { val = -32768; } uval0 = val & 0xffffU; val = val1; if(val1 > 32767) { val = 32767; } if(val1 < -32768) { val = -32768; } uval1 = val & 0xffffU; outptr += 2; #if defined(__linux__) || defined(OSS) /* Linux seems to expect little-endian */ /* samples always, even on PowerPC */ # ifdef KEGS_BIG_ENDIAN sndptr[pos] = ((uval1 & 0xff) << 24) + ((uval1 & 0xff00) << 8) + ((uval0 & 0xff) << 8) + ((uval0 >> 8) & 0xff); # else sndptr[pos] = (uval1 << 16) + (uval0 & 0xffff); # endif #else # ifdef KEGS_BIG_ENDIAN sndptr[pos] = (uval0 << 16) + uval1; # else sndptr[pos] = (uval1 << 16) + uval0; # endif #endif pos++; if(pos >= SOUND_SHM_SAMP_SIZE) { pos = 0; } } if(g_queued_nonsamps) { /* force out old 0 samps */ snddrv_send_sound(0, g_queued_nonsamps); g_queued_nonsamps = 0; } if(g_sound_file_str) { send_sound_to_file(g_sound_shm_addr, g_sound_shm_pos, num_samps, 1); } g_queued_samps += num_samps; } else { /* move pos */ pos += num_samps; while(pos >= SOUND_SHM_SAMP_SIZE) { pos -= SOUND_SHM_SAMP_SIZE; } if(g_sound_file_str) { send_sound_to_file(0, g_sound_shm_pos, num_samps, 0); } if(g_queued_samps) { /* force out old non-0 samps */ snddrv_send_sound(1, g_queued_samps); g_queued_samps = 0; } g_queued_nonsamps += num_samps; } } g_sound_shm_pos = pos; if(g_audio_enable != 0) { if(g_queued_samps >= (g_audio_rate/60)) { snddrv_send_sound(1, g_queued_samps); g_queued_samps = 0; } if(g_queued_nonsamps >= (g_audio_rate/60)) { snddrv_send_sound(0, g_queued_nonsamps); g_queued_nonsamps = 0; } } g_last_sound_play_dsamp = dsamp_now; g_sound_play_depth--; } void sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr) { Ay8913 *ay8913ptr; double dmul, denv_period, dusecs_per_samp; dword64 env_dsamp, dsamp_inc; word32 ampl, eff_ampl, reg13, env_val, env_period; int i; // This routine calculates a fixed-point increment to apply // to env_dsamp, where the envelope value is in bits 44:40 (bit // 44 is to track the alternating waveform, 43:40 is the env_ampl). // This algorithm does not properly handle dynamically changing the // envelope period in the middle of a step. In the AY8913, the // part counts up to the env_period, and if the period is changed // to a value smaller than the current count, it steps immediately // to the next step. This routine will wait for enough fraction // to accumulate before stepping. At most, this can delay the step // by almost the new count time (if the new period is smaller), but // no more. I suspect this is not noticeable. if(num_samps > MAX_MOCK_ENV_SAMPLES) { halt_printf("envelope overflow!: %d\n", num_samps); return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); ampl = ay8913ptr->regs[8] | ay8913ptr->regs[9] | ay8913ptr->regs[10]; if((ampl & 0x10) == 0) { // No one uses the envelope return; } env_dsamp = ay8913ptr->env_dsamp; env_period = ay8913ptr->regs[11] + (256 * ay8913ptr->regs[12]); if(env_period == 0) { denv_period = 0.5; // To match MAME } else { denv_period = (double)env_period; } dmul = (1.0 / 16.0) * (1 << 20) * (1 << 20); // (1ULL << 40) / 16.0 // Calculate amount counter will count in one sample. // inc_per_tick 62.5KHz tick: (1/env_period) // inc_per_dfcyc: (1/(16*env_period)) // inc_per_samp = inc_per_dfcyc * g_fcyc_per_samp dusecs_per_samp = g_fcyc_per_samp / 65536.0; dsamp_inc = (dword64)((dmul * dusecs_per_samp / denv_period)); // Amount to inc per sample, fixed point, 40 bit frac reg13 = ay8913ptr->regs[13]; // "reg15", env ctrl eff_ampl = 0; for(i = 0; i < num_samps; i++) { env_dsamp = (env_dsamp + dsamp_inc) & 0x9fffffffffffULL; env_val = (env_dsamp >> 40) & 0xff; eff_ampl = env_val & 0xf; if((reg13 & 4) == 0) { eff_ampl = 15 - eff_ampl; // not attack } if((reg13 & 8) && (reg13 & 2)) { // continue and alternate if(env_val & 0x10) { eff_ampl = 15 - eff_ampl; } } if(((reg13 & 8) == 0) && (env_val >= 0x10)) { eff_ampl = 0; ampl = 0; // Turn off envelope env_dsamp |= (0x80ULL << 40); } else if((reg13 & 1) && (env_val >= 0x10)) { eff_ampl = ((reg13 >> 1) ^ (reg13 >> 2)) & 1; eff_ampl = eff_ampl * 15; ampl = eff_ampl; // Turn off envelope env_dsamp |= (0x80ULL << 40); } *env_ptr++ = vol_ptr[eff_ampl & 0xf]; } ay8913ptr->env_dsamp = env_dsamp; } void sound_mock_noise(int pair, byte *noise_ptr, int num_samps) { Ay8913 *ay8913ptr; word32 ampl, mix, noise_val, noise_samp, noise_period, xor, samp_inc; int doit; int i; if(num_samps > MAX_MOCK_ENV_SAMPLES) { halt_printf("noise overflow!: %d\n", num_samps); return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); doit = 0; for(i = 0; i < 3; i++) { ampl = ay8913ptr->regs[8 + i]; mix = ay8913ptr->regs[7] >> i; if((ampl != 0) && ((mix & 8) == 0)) { doit = 1; break; } } if(!doit) { // No channel looks at noise, don't bother return; } noise_val = ay8913ptr->noise_val; noise_samp = ay8913ptr->noise_samp; noise_period = (ay8913ptr->regs[6] & 0x1f); noise_period = noise_period << 16; samp_inc = (word32)(g_fcyc_per_samp / 16.0); // Amount to inc per sample if(noise_samp >= noise_period) { // Period changed during sound, reset noise_samp = noise_period; } for(i = 0; i < num_samps; i++) { noise_samp += samp_inc; if(noise_samp >= noise_period) { // HACK: handle fraction // 17-bit LFSR, algorithm from MAME:sound/ay8910.cpp // val = val ^ (((val & 1) ^ ((val >> 3) & 1)) << 17) xor = 0; xor = (noise_val ^ (noise_val >> 3)) & 1; noise_val = (noise_val ^ (xor << 17)) >> 1; noise_samp -= noise_period; } noise_ptr[i] = noise_val & 1; } ay8913ptr->noise_samp = noise_samp; ay8913ptr->noise_val = noise_val; } int g_did_mock_print = 100; void sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, byte *noise_ptr, int *vol_ptr, int num_samps) { Ay8913 *ay8913ptr; word32 ampl, mix, tone_samp, tone_period, toggle_tone; word32 samp_inc, noise_val; int out, ival, do_print; int i; if((g_mockingboard.disable_mask >> ((pair * 3) + channel)) & 1) { // This channel is disabled return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); ampl = ay8913ptr->regs[8 + channel] & 0x1f; if(ampl == 0) { return; } toggle_tone = ay8913ptr->toggle_tone[channel]; // 0 or 1 mix = (ay8913ptr->regs[7] >> channel) & 9; if(mix == 9) { // constant tone: output will be ampl for this channel. if(ampl & 0x10) { // Envelope! // The envelope can make the tone, so must calculate it } else { // HACK: do nothing for now return; } } outptr += pair; // pair[1] is right tone_samp = ay8913ptr->tone_samp[channel]; tone_period = ay8913ptr->regs[2*channel] + (256 * ay8913ptr->regs[2*channel + 1]); tone_period = tone_period << 16; samp_inc = (word32)(g_fcyc_per_samp / 8.0); // Amount to inc per sample do_print = 0; if(g_mockingboard.disable_mask) { printf("Doing %d samps, mix:%d, ampl:%02x\n", num_samps, mix, ampl); do_print = 1; g_did_mock_print = 0; } if((num_samps > 500) && (g_did_mock_print == 0)) { do_print = 1; g_did_mock_print = 1; printf("Start of %d sample, channel %d mix:%02x ampl:%02x " "toggle_tone:%02x\n", num_samps, channel, mix, ampl, toggle_tone); printf(" tone_period:%08x, tone_samp:%08x, samp_inc:%08x\n", tone_period, tone_samp, samp_inc); } if(tone_samp >= tone_period) { // Period changed during sound, reset it tone_samp = tone_period; } for(i = 0; i < num_samps; i++) { tone_samp += samp_inc; if(tone_samp >= tone_period) { // HACK: handle toggling mid-sample... toggle_tone ^= 1; if(do_print) { printf("i:%d tone_toggled to %d, tone_period:" "%04x, pre tone_samp:%08x\n", i, toggle_tone, tone_period, tone_samp); } tone_samp -= tone_period; if(do_print) { printf("post tone_samp:%08x\n", tone_samp); } } noise_val = noise_ptr[i] & 1; out = (toggle_tone || (mix & 1)) & ((noise_val & 1) || (mix & 8)); // Careful mix of || and & above... ival = vol_ptr[ampl & 0xf]; if(ampl & 0x10) { // Envelope ival = env_ptr[i]; } *outptr += ival*out; outptr += 2; } ay8913ptr->tone_samp[channel] = tone_samp; ay8913ptr->toggle_tone[channel] = toggle_tone; } word32 sound_read_c030(dword64 dfcyc) { sound_write_c030(dfcyc); return float_bus(dfcyc); } void sound_write_c030(dword64 dfcyc) { int num; num = g_num_c030_fsamps; if(num >= MAX_C030_TIMES) { halt_printf("Too many clicks per vbl: %d\n", num); return; } g_c030_fsamps[num] = (float)(dfcyc * g_dsamps_per_dfcyc - g_last_sound_play_dsamp); g_num_c030_fsamps = num + 1; dbg_log_info(dfcyc, num, 0, 0xc030); doc_printf("touch c030, num this vbl: %04x\n", num); } ================================================ FILE: gsplus/src/sound.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #if !defined(_WIN32) && !defined(__CYGWIN__) # include # include #endif #define SOUND_SHM_SAMP_SIZE (32*1024) #define SAMPLE_SIZE 2 #define NUM_CHANNELS 2 #define SAMPLE_CHAN_SIZE (SAMPLE_SIZE * NUM_CHANNELS) STRUCT(Doc_reg) { double dsamp_ev; double dsamp_ev2; double complete_dsamp; int samps_left; word32 cur_acc; word32 cur_inc; word32 cur_start; word32 cur_end; word32 cur_mask; int size_bytes; int event; int running; int has_irq_pending; word32 freq; word32 vol; word32 waveptr; word32 ctl; word32 wavesize; word32 last_samp_val; }; // Mockingboard contains two pairs. Each pair is a 6522 interfacing // to an AY-8913 to generate sounds. Eacho AY-8913 contains 3 channels of // sound. Model each pair separately. STRUCT(Mos6522) { byte orb; byte ora; byte ddrb; byte ddra; word32 timer1_latch; word32 timer1_counter; word32 timer2_latch; word32 timer2_counter; byte sr; byte acr; byte pcr; byte ifr; byte ier; }; STRUCT(Ay8913) { byte regs[16]; byte reg_addr_latch; byte toggle_tone[3]; // Channel A,B,C: 0 = low, 1 = high word32 tone_samp[3]; word32 noise_val; word32 noise_samp; dword64 env_dsamp; }; STRUCT(Mock_pair) { Mos6522 mos6522; Ay8913 ay8913; }; STRUCT(Mockingboard) { Mock_pair pair[2]; word32 disable_mask; }; /* prototypes for win32snd_driver.c functions */ void win32snd_init(word32 *); void win32snd_shutdown(void); void child_sound_init_win32(void); int win32_send_audio(byte *ptr, int size); /* Prototypes for macsnd_driver.c functions */ int mac_send_audio(byte *ptr, int in_size); void macsnd_init(); /* Prototypes for pulseaudio_driver.c functions */ int pulse_audio_init(); int pulse_audio_send_audio(byte *ptr, int in_size); void pulse_audio_shutdown(void); ================================================ FILE: gsplus/src/sound_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Routines for managing sending sound samples to the hardware. The // primary routines are snddrv_init() for initializing the sound hardware, // and snddrv_send_sound() which calls the driver for the sound hardware // in use to play samples. // Linux forks a child process to manage /dev/dsp (so KEGS will not block) // so the lowerlevel routines for all sound hardware start with child_(). #include "defc.h" #include "sound.h" #if defined(__linux__) || defined(OSS) # include #endif #ifndef _WIN32 # include # include #endif #include #if defined(_WIN32) || defined(__CYGWIN__) || defined(MAC) # define KEGS_CAN_FORK 0 #else // Linux, or other Unix, we may fork and run sound in the child # define KEGS_CAN_FORK 1 #endif extern int Verbose; extern int g_audio_rate; extern int g_audio_enable; extern double g_last_sound_play_dsamp; extern word32 *g_sound_shm_addr; int g_preferred_rate = 48000; int g_audio_socket = -1; int g_bytes_written = 0; int g_pulse_audio = 0; int g_pipe_fd[2] = { -1, -1 }; int g_pipe2_fd[2] = { -1, -1 }; #define ZERO_BUF_SIZE 2048 word32 g_snd_zero_buf[ZERO_BUF_SIZE]; #define ZERO_PAUSE_SAFETY_SAMPS (g_audio_rate >> 5) #define ZERO_PAUSE_NUM_SAMPS (4*g_audio_rate) int g_zeroes_buffered = 0; int g_zeroes_seen = 0; int g_sound_paused = 0; int g_childsnd_vbl = 0; int g_childsnd_pos = 0; int child_sound_init_linux(void); void snddrv_init() { word32 *shmaddr; int size, ret, use_shm; ret = 0; if(ret) { // Avoid unused var warning } g_zeroes_buffered = 0; g_zeroes_seen = 0; g_sound_paused = 0; g_childsnd_pos = 0; g_childsnd_vbl = 0; if(g_audio_enable == 0) { sound_set_audio_rate(g_preferred_rate); return; } printf("snddrv_init, g_audio_enable:%d\n", g_audio_enable); size = SOUND_SHM_SAMP_SIZE * SAMPLE_CHAN_SIZE; use_shm = KEGS_CAN_FORK; #ifdef PULSE_AUDIO use_shm = 0; #endif if(!use_shm) { /* windows and mac, and Linux Pulse Audio */ shmaddr = malloc(size); memset(shmaddr, 0, size); g_sound_shm_addr = shmaddr; #ifdef MAC macsnd_init(); return; #endif #ifdef _WIN32 win32snd_init(shmaddr); return; #endif #ifdef PULSE_AUDIO ret = pulse_audio_init(shmaddr); if(ret == 0) { g_pulse_audio = 1; return; // Success! } free(shmaddr); g_sound_shm_addr = 0; use_shm = 1; // Otherwise, fall back on /dev/dsp #endif } if(use_shm) { sound_child_fork(size); } } void sound_child_fork(int size) { #if KEGS_CAN_FORK word32 *shmaddr; int shmid, pid, tmp, ret; int i; doc_printf("In sound_child_fork, size:%d\n", size); shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); if(shmid < 0) { printf("sound_init: shmget ret: %d, errno: %d\n", shmid, errno); exit(2); } shmaddr = shmat(shmid, 0, 0); tmp = (int)PTR2WORD(shmaddr); if(tmp == -1) { printf("sound_init: shmat ret: %p, errno: %d\n", shmaddr, errno); exit(3); } ret = shmctl(shmid, IPC_RMID, 0); if(ret < 0) { printf("sound_init: shmctl ret: %d, errno: %d\n", ret, errno); exit(4); } g_sound_shm_addr = shmaddr; printf("shmaddr: %p\n", shmaddr); fflush(stdout); /* prepare pipe so parent can signal child each other */ /* pipe[0] = read side, pipe[1] = write end */ ret = pipe(&g_pipe_fd[0]); if(ret < 0) { printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); exit(5); } ret = pipe(&g_pipe2_fd[0]); if(ret < 0) { printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); exit(5); } doc_printf("pipes: pipe_fd = %d, %d pipe2_fd: %d,%d\n", g_pipe_fd[0], g_pipe_fd[1], g_pipe2_fd[0], g_pipe2_fd[1]); fflush(stdout); pid = fork(); switch(pid) { case 0: /* child */ /* close stdin and write-side of pipe */ close(0); /* Close other fds to make sure X window fd is closed */ for(i = 3; i < 100; i++) { if((i != g_pipe_fd[0]) && (i != g_pipe2_fd[1])) { close(i); } } close(g_pipe_fd[1]); /*make sure write pipe closed*/ close(g_pipe2_fd[0]); /*make sure read pipe closed*/ child_sound_loop(g_pipe_fd[0], g_pipe2_fd[1], g_sound_shm_addr); printf("Child sound loop returned\n"); exit(0); case -1: /* error */ printf("sound_init: fork ret: -1, errno: %d\n", errno); exit(6); default: /* parent */ /* close read-side of pipe1, and the write side of pipe2 */ close(g_pipe_fd[0]); close(g_pipe2_fd[1]); doc_printf("Child is pid: %d\n", pid); } parent_sound_get_sample_rate(g_pipe2_fd[0]); #endif if(size) { // Avoid unused param warning } } void parent_sound_get_sample_rate(int read_fd) { word32 audio_rate, tmp; int ret; ret = (int)read(read_fd, &audio_rate, 4); if(ret != 4) { printf("parent dying, could not get sample rate from child\n"); printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); exit(1); } ret = (int)read(read_fd, &tmp, 4); if(ret != 4) { printf("parent dying, could not get audio status from child\n"); printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); exit(1); } if(tmp == 0) { g_audio_enable = 0; printf("Failed to init Sound, turning off audio\n"); } close(read_fd); sound_set_audio_rate(audio_rate); } void snddrv_shutdown() { #ifdef _WIN32 win32snd_shutdown(); #else if((g_audio_enable != 0) && (g_pipe_fd[1] >= 0)) { close(g_pipe_fd[1]); } #endif #ifdef PULSE_AUDIO if(g_pulse_audio) { pulse_audio_shutdown(); } #endif } void snddrv_send_sound(int real_samps, int size) { word32 tmp; int ret, call_playit; if(g_audio_enable == 0) { printf("Entered send_sound but audio off!\n"); exit(2); } if(real_samps) { tmp = size + 0xa2000000; } else { tmp = size + 0xa1000000; } //doc_log_rout("send_sound", -1, g_last_sound_play_dsamp, // (real_samps << 30) + size); call_playit = 0; #if defined(MAC) || defined(_WIN32) call_playit = 1; // Never fork child mac/windows #endif if(call_playit || g_pulse_audio) { child_sound_playit(tmp); return; } /* Although this looks like a big/little-endian issue, since the */ /* child is also reading an int, it just works with no byte swap */ ret = (int)write(g_pipe_fd[1], &tmp, 4); if(ret != 4) { halt_printf("send_sound, wr ret: %d, errno: %d\n", ret, errno); } } void child_sound_playit(word32 tmp) { int size; size = tmp & 0xffffff; //printf("child_sound_playit: %08x\n", tmp); if((tmp >> 24) == 0xa2) { // play sound if(g_zeroes_buffered) { reliable_zero_write(g_zeroes_buffered); } g_zeroes_buffered = 0; g_zeroes_seen = 0; if((size + g_childsnd_pos) > SOUND_SHM_SAMP_SIZE) { reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, SOUND_SHM_SAMP_SIZE - g_childsnd_pos); size = (g_childsnd_pos + size) - SOUND_SHM_SAMP_SIZE; g_childsnd_pos = 0; } reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, size); if(g_sound_paused) { printf("Unpausing sound, zb: %d\n", g_zeroes_buffered); g_sound_paused = 0; } } else if((tmp >> 24) == 0xa1) { // play zeroes if(g_sound_paused) { if(g_zeroes_buffered < ZERO_PAUSE_SAFETY_SAMPS) { g_zeroes_buffered += size; } } else { /* not paused, send it through */ g_zeroes_seen += size; reliable_zero_write(size); if(g_zeroes_seen >= ZERO_PAUSE_NUM_SAMPS) { printf("Pausing sound\n"); g_sound_paused = 1; } } } else { printf("tmp received bad: %08x\n", tmp); exit(3); } g_childsnd_pos += size; while(g_childsnd_pos >= SOUND_SHM_SAMP_SIZE) { g_childsnd_pos -= SOUND_SHM_SAMP_SIZE; } g_childsnd_vbl++; if(g_childsnd_vbl >= 60) { g_childsnd_vbl = 0; #if 0 printf("sound bytes written: %06x\n", g_bytes_written); #endif g_bytes_written = 0; } } void reliable_buf_write(word32 *shm_addr, int pos, int size) { byte *ptr; int ret; if(size < 1 || pos < 0 || pos > SOUND_SHM_SAMP_SIZE || size > SOUND_SHM_SAMP_SIZE || (pos + size) > SOUND_SHM_SAMP_SIZE) { printf("reliable_buf_write: pos: %04x, size: %04x\n", pos, size); exit(1); } ptr = (byte *)&(shm_addr[pos]); size = size * 4; while(size > 0) { ret = child_send_samples(ptr, size); if(ret < 0) { printf("audio write, errno: %d %s\n", errno, strerror(errno)); exit(1); } size = size - ret; ptr += ret; g_bytes_written += ret; } } void reliable_zero_write(int amt) { int len; while(amt > 0) { len = MY_MIN(amt, ZERO_BUF_SIZE); reliable_buf_write(g_snd_zero_buf, 0, len); amt -= len; } } int child_send_samples(byte *ptr, int size) { #ifdef _WIN32 return win32_send_audio(ptr, size); #else # ifdef MAC return mac_send_audio(ptr, size); # else # ifdef PULSE_AUDIO if(g_pulse_audio) { return pulse_audio_send_audio(ptr, size); } # endif return (int)write(g_audio_socket, ptr, size); # endif #endif } // child_sound_loop(): used by Linux child process as the main loop, to read // from pipe to get sample info every VBL, and use shm_addr to get samples void child_sound_loop(int read_fd, int write_fd, word32 *shm_addr) { word32 tmp, did_init; int ret, ret1, ret2; doc_printf("Child pipe fd: %d, shm_addr:%p\n", read_fd, shm_addr); g_audio_rate = g_preferred_rate; did_init = 0; #if defined(__linux__) || defined(OSS) did_init = child_sound_init_linux(); #endif tmp = g_audio_rate; ret1 = (int)write(write_fd, &tmp, 4); tmp = did_init; ret2 = (int)write(write_fd, &tmp, 4); if((ret1) != 4 || (ret2 != 4)) { printf("Unable to send back audio rate to parent\n"); printf("ret1: %d,%d fd: %d, errno:%d\n", ret1, ret2, write_fd, errno); exit(1); } doc_printf("Wrote to fd %d the audio rate\n", write_fd); close(write_fd); while(1) { errno = 0; ret = (int)read(read_fd, &tmp, 4); if(ret <= 0) { printf("child dying from ret: %d, errno: %d\n", ret, errno); break; } child_sound_playit(tmp); } close(g_audio_socket); exit(0); } #if defined(__linux__) || defined(OSS) int child_sound_init_linux() { int stereo, sample_size, rate, fmt, ret; g_audio_socket = open("/dev/dsp", O_WRONLY, 0); if(g_audio_socket < 0) { printf("open /dev/dsp failed, ret: %d, errno:%d\n", g_audio_socket, errno); return 0; } #if 0 fragment = 0x00200009; ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFRAGMENT, &fragment); if(ret < 0) { printf("ioctl SETFRAGEMNT failed, ret:%d, errno:%d\n", ret, errno); return 0; } #endif sample_size = 16; ret = ioctl(g_audio_socket, SNDCTL_DSP_SAMPLESIZE, &sample_size); if(ret < 0) { printf("ioctl SNDCTL_DSP_SAMPLESIZE failed, ret:%d, errno:%d\n", ret, errno); return 0; } #ifdef KEGS_BIG_ENDIAN fmt = AFMT_S16_BE; #else fmt = AFMT_S16_LE; #endif ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFMT, &fmt); if(ret < 0) { printf("ioctl SNDCTL_DSP_SETFMT failed, ret:%d, errno:%d\n", ret, errno); return 0; } stereo = 1; ret = ioctl(g_audio_socket, SNDCTL_DSP_STEREO, &stereo); if(ret < 0) { printf("ioctl SNDCTL_DSP_STEREO failed, ret:%d, errno:%d\n", ret, errno); return 0; } rate = g_audio_rate; ret = ioctl(g_audio_socket, SNDCTL_DSP_SPEED, &rate); if(ret < 0) { printf("ioctl SNDCTL_DSP_SPEED failed, ret:%d, errno:%d\n", ret, errno); return 0; } if(ret > 0) { rate = ret; /* rate is returned value */ } if(rate < 8000) { printf("Audio rate of %d which is < 8000!\n", rate); return 0; } g_audio_rate = rate; printf("Sound initialized\n"); return 1; } #endif ================================================ FILE: gsplus/src/style_check ================================================ #!/usr/bin/perl -w # $KmKId: style_check,v 1.1 2020-06-14 02:52:13+00 kentd Exp $ # Perl script to check for coding conventions use English; $some_bad = 0; while($#ARGV >= 0) { $file = shift; $check_spaces = 0; if($file =~ /\.c$/) { $check_spaces = 1; } elsif($file =~ /\.s$/) { $check_spaces = 1; } elsif($file =~ /\.k$/) { $check_spaces = 1; } elsif($file =~ /\.h$/) { $check_spaces = 1; if($file =~ /^protos.*.h$/) { next; # skip global_names.h } if($file =~ /^global_names.h$/) { next; # skip global_names.h } if($file =~ /^knobs.h$/) { next; # skip global_names.h } } else { next; # skip this file } print "Style check: $file\n"; if(-x $file) { print "File mode is executable\n"; $some_bad++; } open(FILE, "<$file") || die "Open: $file: $1\n"; $line_num = 1; foreach $line () { chomp($line); $ign_tab_space = 0; if($line =~ m:^[/\t]+{.*}:) { $ign_tab_space = 1; } $len = 0; $prev = 0; $pprev = 0; $bad = 0; $last_tab = -10; if($check_spaces) { if($line =~ / $/ || $line =~ / $/) { print "Line ends in tab or space\n"; $bad++; } } if($line =~ /\r$/) { # Line ends with Ctrl-M print "Windows linebreak detected\n"; $bad = 101; } while($line =~ m/^([^"]*)("[^"]*")(.*)$/) { # Convert text in strings to '-' to avoid space checks $prev = $1; $post = $3; $quot = &dotify($2); $line = $prev . $quot . $post; } while($line =~ m:^(.*)//(.*)$:) { # Convert text in comments to '-' to avoid space checks $prev = $1; $quot = &dotify($2); $line = $prev . "::" . $quot; } @chars = split(//, $line); foreach $char (@chars) { $len++; if(!$check_spaces) { # do nothing } elsif($char eq "\t") { $len = (($len + 7) >> 3) * 8; if($prev eq ' ') { print "Space followed by tab\n"; $bad++; } $last_tab = $len; } elsif($char eq " ") { if($prev eq "\t" && !$ign_tab_space) { print "Tab followed by space\n"; $bad++; } if($prev eq " " && $pprev eq " " && (($len - $last_tab) > 4)) { print "Too many spaces\n"; $bad++; last; } } $pprev = $prev; $prev = $char } if($check_spaces) { if(($len > 80) && ($line_num > 2)) { print "Line more than 80 columns\n"; $bad++; } #print "line $line has len $len\n"; } if($bad) { $some_bad++; print "...at line $line_num in file $file\n"; if($some_bad > 20) { die "Too many style errors\n"; } if($bad >= 100) { next; # Skip to next file } } $line_num++; } } if($some_bad) { die "Style errors\n"; } exit 0; sub dotify { my ($str) = @_; my @chars = (); my @outchars = (); my ($char, $result); @chars = split(//, $str); @outchars = (); foreach $char (@chars) { if($char ne "\t") { $char = "-"; } push(@outchars, $char); } $result = join('', @outchars); #print "Old quote :$str:\n"; #print "New quote :$result:\n"; return $result; } ================================================ FILE: gsplus/src/undeflate.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // This file has routines for the undeflate uncompression algorithm for // gzip/zip, and routines for reading .zip files. // Based on https://www.ietf.org/rfc/rfc1951.txt for Deflate algorithm, // and https://www.ietf.org/rfc/rfc1952.txt for gzip file format. // .zip file format from: // https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT #include "defc.h" // FILE *g_outf = 0; #define LENGTH_ENCODED 0xffffff444449ULL // LENGTH_ENCODED encodes the first table of section 3.2.5 for // fixed Huffman: 257-264 = 0 extra bits, length=3-10 (so 8 entries) // then 265-268 = 1 extra bit, length=11... (so 4 entries), etc. // which is encoded in each nibble of this word. Code 285 (entry 29) // has extra_bits=0, indicated by the encoded nibble being 0xf. #define DIST_ENCODED 0xff22222222222224ULL // DIST_ENCODED encodes the second table of section 3.2.5 for the // fixed Huffman table: Codes 0-3 have 0 extra bits, dist=1,2,3,4 // (so 4 entries), codes 4,5 have 1 extra bit, dest=5... (so 2 // entries), etc. 0xf indicates an invalid entry word32 g_undeflate_fixed_len_tab[512+1]; // extrabits << 20 | bits << 16 | len/literal word32 g_undeflate_fixed_dist_tab[32+1]; // extrabits << 20 | bits << 16 | distance word32 g_undeflate_length_tab[32+1]; // extra_bits << 20 | len word32 g_undeflate_dist_tab[32+1]; // extra_bits << 20 | dist word32 g_undeflate_bit_rev[512]; word32 g_undeflate_lencode_positions[19] = { 0x200310, 0x300311, 0x700b12, 0x100, 0x108, 0x107, 0x109, 0x106, 0x10a, 0x105, 0x10b, 0x104, 0x10c, 0x103, 0x10d, 0x102, 0x10e, 0x101, 0x10f }; word32 g_undeflate_lencode_tab[128 + 1]; word32 *g_undeflate_dynamic_tabptr = 0; word32 g_undeflate_dynamic_bits = 0; word32 *g_undeflate_dynamic_dist_tabptr = 0; word32 g_undeflate_dynamic_dist_bits = 0; void * undeflate_realloc(void *ptr, dword64 dsize) { if((size_t)dsize != dsize) { return 0; } return realloc(ptr, (size_t)dsize); } void * undeflate_malloc(dword64 dsize) { if((size_t)dsize != dsize) { return 0; } return malloc((size_t)dsize); } void show_bits(unsigned *llptr, int nl) { int i; fprintf(stdout, "Showing %03x bits entries\n", nl); for(i = 0; i < nl; i++) { fprintf(stdout, "%03x: %03x\n", i, (llptr[i] >> 16) & 0xf); } } void show_huftb(unsigned *tabptr, int bits) { unsigned char seen[512]; word32 entry, code, val; int i, j; printf("Showing hufftab of %d bits\n", bits); for(i = 0; i < 256+32; i++) { seen[i] = 0; } for(i = 0; i < (1 << bits); i++) { entry = tabptr[i]; code = entry & 0xff; if((entry >> 24) & 1) { val = entry & 0xfff0ffffUL; for(j = 0; j < 32; j++) { if(val == g_undeflate_length_tab[j]) { code = 256 + j; break; } } if(code < 256) { printf("entry %08x (%08x) not found, [0]=%08x " "[1]=%08x\n", entry, val, g_undeflate_length_tab[0], g_undeflate_length_tab[1]); code = 256 + 31; } } if(!seen[code]) { printf("code %03x has bits:%d huffcode:%04x\n", code, (entry >> 16) & 0xf, i); seen[code] = 1; } } } void undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start) { word32 pos, repeats, extra_bits; int i; // Initializes g_undeflate_length_tab[] and g_underflate_dist_tab[] // printf("undeflate len_dist_tab repeats:%016llx\n", drepeats); pos = 0; extra_bits = 0; while(pos < 30) { repeats = drepeats & 0xf; drepeats = drepeats >> 4; if(repeats == 0xf) { // Special handling for code=285 (pos=29) extra_bits = 0; start--; // 258, not 259 repeats = 1; } for(i = 0; i < (int)repeats; i++) { tabptr[pos] = start | (extra_bits << 20) | (1 << 24); //printf("Set table[%d]=%08x (%d) i:%d out of %d\n", // pos, tabptr[pos], start, i, repeats); pos++; start += (1 << extra_bits); } extra_bits++; } } void undeflate_init_bit_rev_tab(word32 *tabptr, int num) { word32 pos, val; int i, j; // Initializes g_undeflate_bit_rev[] for(i = 0; i < num; i++) { pos = i; val = 0; for(j = 0; j < 9; j++) { val = (val << 1) | (pos & 1); pos = pos >> 1; } tabptr[i] = val; // printf("Bit reverse[%03x]=%03x\n", i, val); } } word32 undeflate_bit_reverse(word32 val, word32 bits) { word32 new_val, val2, shift; new_val = g_undeflate_bit_rev[val & 0x1ff]; // at most 9 bits shift = 9 - bits; if(bits <= 9) { return new_val >> shift; } else if(bits <= 18) { shift += 9; // bits:10->shift=8, bits:11->shift=7,.. val2 = g_undeflate_bit_rev[(val >> 9) & 0x1ff] >> shift; shift = bits - 9; return (new_val << shift) | val2; } printf("Cannot reverse %08x bits:%d!\n", val, bits); return 0; } word32 undeflate_calc_crc32(byte *bptr, word32 len) { word32 crc, c, xor; int i; // Old version, don't use other than for testing purposes. // Use woz_calc_crc32() instead. // Generate CCITT-32 CRC, with remainder initialized to -1 and return // the complement of the CRC value // This is slow--but it doesn't matter for KEGS, where the images // are generally only 800KB max. crc = (word32)-1; while(len != 0) { c = *bptr++; len--; for(i = 0; i < 8; i++) { xor = 0; if((crc ^ c) & 1) { xor = 0xedb88320UL; } crc = (crc >> 1) ^ xor; c = c >> 1; } } return (~crc); } byte * undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len) { byte *raw_ptr; dword64 raw_dsize, dimage_size; word32 new_image_size; raw_dsize = dsk->raw_dsize; dimage_size = dsk->dimage_size; if(ucptr) { new_image_size = (word32)(ucptr - dsk->raw_data); if(new_image_size < dimage_size) { printf("ucptr moved backwards!\n"); return 0; } if((new_image_size >> 31) != 0) { printf("Output file > 2GB, failing\n"); return 0; } dimage_size = new_image_size; dsk->dimage_size = dimage_size; } if(dimage_size > raw_dsize) { printf("dimage_size %08llx overflowed raw_dsize %08llx\n", dimage_size, raw_dsize); return 0; } if((dimage_size + len) > raw_dsize) { raw_dsize = ((dimage_size + len) * 3ULL) / 2; raw_ptr = undeflate_realloc(dsk->raw_data, raw_dsize); //printf("Did realloc to %08x, new new_data:%p, was %p\n", // raw_size, raw_ptr, dsk->raw_data); if(raw_ptr == 0) { printf("undeflate realloc failed\n"); free(dsk->raw_data); dsk->raw_data = 0; return 0; } dsk->raw_data = raw_ptr; dsk->raw_dsize = raw_dsize; } #if 0 printf("undeflate_ensure_dest_len will ret %p, dsk->raw_data:%p, " "image_size:%08llx, raw_dsize:%08llx\n", dsk->raw_data + dimage_size, dsk->raw_data, dimage_size, raw_dsize); #endif return dsk->raw_data + dimage_size; } void undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, word32 entry) { word32 rev_code, bits, pos, tab_size; int num; int i; if(tabsz_lg2 > 15) { printf("tabsz_lg2: %04x is not supported\n", tabsz_lg2); return; } tab_size = 1 << tabsz_lg2; rev_code = 0; bits = (entry >> 16) & 0xf; rev_code = undeflate_bit_reverse(code, bits); if(rev_code >= tab_size) { printf("rev_code:%04x out of range for entry %08x\n", rev_code, entry); tabptr[tab_size] = 1; return; } num = 1 << (tabsz_lg2 - bits); if(num < 0) { printf("num %d out of range for entry %08x\n", num, entry); tabptr[tab_size] = 1; return; } for(i = 0; i < num; i++) { pos = rev_code | (i << bits); if(tabptr[pos] != 0) { printf("Overwriting old [%04x]=%08x with %08x\n", pos, tabptr[pos], entry); tabptr[tab_size] = 1; } #if 0 if(i >= 0) { printf("Set code tab[%04x]=%08x (code:%04x)\n", pos, entry, save_code); } #endif tabptr[pos] = entry; } } word32 * undeflate_init_fixed_tabs() { word32 *tabptr; int i; tabptr = &(g_undeflate_fixed_len_tab[0]); // Init g_undeflate_fixed_len_tab[] for the fixed Huffman code for(i = 0; i < 513; i++) { tabptr[i] = 0; } // printf("Add fixed_len_tab for literals 0 - 143\n"); for(i = 0; i < 144; i++) { undeflate_add_tab_code(tabptr, 9, 0x30 + i, (8 << 16) | i); } // printf("Add fixed_len_tab for literals 144 - 255\n"); for(i = 144; i < 256; i++) { undeflate_add_tab_code(tabptr, 9, 0x190 + i - 144, (9 << 16) | i); } // printf("Add fixed_len_tab for length codes 256 - 279\n"); for(i = 256; i < 280; i++) { // printf("code: %03x fixed_len_tab[%03x]=%08x\n", i, i - 256, // g_undeflate_length_tab[i - 256]); undeflate_add_tab_code(tabptr, 9, 0 + i - 256, (7 << 16) | g_undeflate_length_tab[i - 256]); } // printf("Add fixed_len_tab for length codes 280 - 287\n"); for(i = 280; i < 288; i++) { undeflate_add_tab_code(tabptr, 9, 0xc0 + i - 280, (8 << 16) | g_undeflate_length_tab[i - 256]); } if(tabptr[512]) { return 0; } // And init g_undeflate_fixed_dist_tab[] tabptr = &(g_undeflate_fixed_dist_tab[0]); for(i = 0; i < 33; i++) { tabptr[i] = 0; } // printf("Add fixed_dist_tab for codes 0 - 29\n"); for(i = 0; i < 30; i++) { undeflate_add_tab_code(tabptr, 5, i, (5 << 16) | g_undeflate_dist_tab[i]); } if(tabptr[32]) { return 0; } return tabptr; } word32 * undeflate_init_tables() { undeflate_init_len_dist_tab(&(g_undeflate_length_tab[0]), LENGTH_ENCODED, 2); // code=257 has length 3, but the first entry is really code=256 // so set 256 to length=2 undeflate_init_len_dist_tab(&(g_undeflate_dist_tab[0]), DIST_ENCODED, 1); undeflate_init_bit_rev_tab(&(g_undeflate_bit_rev[0]), 512); // undeflate_check_bit_reverse(); return undeflate_init_fixed_tabs(); } void undeflate_free_tables() { free(g_undeflate_dynamic_tabptr); g_undeflate_dynamic_tabptr = 0; free(g_undeflate_dynamic_dist_tabptr); g_undeflate_dynamic_dist_tabptr = 0; } void undeflate_check_bit_reverse() { word32 rev, tmp, checked; int i, j, bits; // Check bit-reverse function. Reverse all values from 0-32767 checked = 0; for(bits = 1; bits <= 16; bits++) { // printf("Checking bit reverse bits=%d\n", bits); for(i = 0; i < 65536; i++) { if(i >= (1 << bits)) { break; } tmp = i; rev = 0; for(j = 0; j < bits; j++) { rev = (rev << 1) | (tmp & 1); tmp = tmp >> 1; } tmp = undeflate_bit_reverse(i, bits); if(tmp != rev) { printf("Reverse %04x bits:%d ret:%04x, " "exp:%04x\n", i, bits, tmp, rev); exit(2); } checked++; } } printf("Checked %08x values\n", checked); } word32 * undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, word32 *bl_count_ptr, int max_bits) { word32 next_code[16]; word32 code, tab_size, bits, entry; int i; tab_size = (1 << max_bits); if(max_bits > 15) { printf("max_bits: %d out of range\n", max_bits); return 0; } next_code[0] = 0; bl_count_ptr[0] = 0; // Force number of 0-bit lengths to 0 code = 0; // printf("build_huff_tab, max_bits:%d, tab_size:%08x\n", max_bits, // tab_size); for(i = 1; i <= max_bits; i++) { // printf("bl_count[%d] = %03x\n", i - 1, bl_count_ptr[i-1]); code = (code + bl_count_ptr[i - 1]) << 1; next_code[i] = code; // printf("Set next_code[%d] = %03x\n", i, code); } for(i = 0; i < (int)tab_size; i++) { tabptr[i] = 0; } tabptr[tab_size] = 0; for(i = 0; i < (int)len_size; i++) { entry = entry_ptr[i]; bits = (entry >> 16) & 0xf; //printf("i:%03x, bits:%d, entry:%08x\n", i, bits, entry); if(!bits) { continue; } code = next_code[bits]++; //printf("Set tab code:%03x = %08x\n", code, entry); undeflate_add_tab_code(tabptr, max_bits, code, entry); } // printf("All done, returning tabptr\n"); return tabptr; } word32 * undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base) { word32 code_list[256+32+32+1]; word32 len_codes[19]; word32 bl_count[19], bl_count_dist[16]; word32 *tabptr, *tabptr_dist; byte *cptr_start; word32 bit_pos, val, hlit, hdist, hclen, pos, max_bits, code_pos; word32 total_codes_needed, repeat, mask, entry, bits; word32 max_length_bits, max_distance_bits, extra_bits; int i; // This is compressed compressed huffman lengths. First // get the length codes, then get the actual lengths // Get 14 bits, which always fits in 3 bytes bit_pos = *bit_pos_ptr; cptr_start = cptr; val = (cptr[0] + (cptr[1] << 8) + (cptr[2] << 16)) >> bit_pos; hlit = (val & 0x1f) + 257; // 257 - 288 hdist = ((val >> 5) & 0x1f) + 1; hclen = (val >> 10) & 0xf; #if 0 printf("At +%06x, bit:%d, hlit:%02x hdist:%02x, hclen:%02x\n", (word32)(cptr - cptr_base), bit_pos, hlit, hdist, hclen); #endif if(cptr_base) { // Avoid unused parameter warning } bit_pos += 14; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; for(i = 0; i < 19; i++) { len_codes[i] = 0; bl_count[i] = 0; } hclen += 4; // 19*3 = 57 bits, at most max_bits = 0; for(i = 0; i < (int)hclen; i++) { val = ((cptr[0] + (cptr[1] << 8)) >> bit_pos) & 7; entry = g_undeflate_lencode_positions[i]; entry = entry & (~0xf0000); // clear bits from entry pos = entry & 0x1f; len_codes[pos] = entry | (val << 16); // printf("len_codes[%d]=%08x\n", pos, len_codes[pos]); bl_count[val]++; if(val > max_bits) { max_bits = val; } // printf("Num bits for len code %02x = %d\n", pos, val); bit_pos += 3; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } // Build huffman table tabptr = undeflate_build_huff_tab(&(g_undeflate_lencode_tab[0]), &(len_codes[0]), 19, &(bl_count[0]), max_bits); if(tabptr == 0) { printf("Bad table\n"); return 0; } // Now we've made the table in tabptr. Read the length codes now total_codes_needed = hlit + hdist; // printf("Getting %04x total codes\n", total_codes_needed); code_pos = 0; mask = (1 << max_bits) - 1; if(total_codes_needed > (256+32+32)) { printf("total_codes_needed high: %04x\n", total_codes_needed); return 0; } for(i = 0; i < 16; i++) { bl_count[i] = 0; bl_count_dist[i] = 0; } while(code_pos < total_codes_needed) { pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; pos = pos & mask; entry = tabptr[pos & mask]; #if 0 printf("At +%06x, bit:%d: Raw code: %02x, entry:%08x\n", (word32)(cptr - cptr_base), bit_pos, pos, entry); #endif val = entry & 0x1f; bits = (entry >> 16) & 7; extra_bits = (entry >> 20) & 7; repeat = (entry >> 8) & 0xf; entry = (val << 16); // Set bits bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; #if 0 printf("At +%06x, bit:%d: Raw pos:%04x\n", (word32)(cptr - cptr_base), bit_pos, pos); #endif pos = pos & ((1 << extra_bits) - 1); repeat = repeat + pos; bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(!repeat) { printf("Bad repeat value\n"); return 0; } if(val >= 0x10) { entry = 0; if(val == 0x10) { // Repeat prev entry entry = code_list[code_pos - 1]; if(!code_pos) { printf("Got repeat code 0x10 at 0!\n"); return 0; } } } for(i = 0; i < (int)repeat; i++) { code_list[code_pos] = entry; // printf("Added code_list[%03x] = %08x\n", code_pos, // entry); code_pos++; } } // Fix lengths and literals max_length_bits = 0; for(i = 0; i < (int)hlit; i++) { entry = code_list[i]; bits = (entry >> 16) & 0xf; bl_count[bits]++; if(i >= 256) { entry |= g_undeflate_length_tab[i - 256]; } else { entry |= i; } code_list[i] = entry; if(bits > max_length_bits) { max_length_bits = bits; } } // Fix distances max_distance_bits = 0; for(i = 0; i < (int)hdist; i++) { entry = code_list[i + hlit]; bits = (entry >> 16) & 0xf; bl_count_dist[bits]++; entry |= g_undeflate_dist_tab[i]; code_list[i + hlit] = entry; if(bits > max_distance_bits) { max_distance_bits = bits; } } if(code_pos != total_codes_needed) { printf("Got %03x codes, needed %03x codes\n", code_pos, total_codes_needed); return 0; } // printf("max_length_bits: %d, max_distance_bits: %d\n", // max_length_bits, max_distance_bits); tabptr = g_undeflate_dynamic_tabptr; if(!tabptr) { tabptr = malloc(sizeof(word32)*((1 << 15) + 1)); g_undeflate_dynamic_tabptr = tabptr; // printf("malloc literal table\n"); } g_undeflate_dynamic_bits = max_length_bits; //printf("Building literal/length table, %d entries, %d bits\n", hlit, // max_length_bits); //show_bits(&(code_list[0]), hlit); tabptr = undeflate_build_huff_tab(tabptr, &(code_list[0]), hlit, &(bl_count[0]), max_length_bits); if(tabptr == 0) { printf("Building literal table failed\n"); return 0; } //show_huftb(tabptr, max_length_bits); tabptr_dist = g_undeflate_dynamic_dist_tabptr; if(!tabptr_dist) { tabptr_dist = malloc(sizeof(word32) * ((1 << 15) + 1)); g_undeflate_dynamic_dist_tabptr = tabptr_dist; // printf("malloc dist table\n"); } g_undeflate_dynamic_dist_bits = max_distance_bits; tabptr_dist = undeflate_build_huff_tab(tabptr_dist, &(code_list[hlit]), hdist, &(bl_count_dist[0]), max_distance_bits); if(tabptr_dist == 0) { printf("Building dist table failed\n"); return 0; } // Update *bit_pos_ptr to skip over the table *bit_pos_ptr = bit_pos + (int)(8*(cptr - cptr_start)); return tabptr; } byte * undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, byte *cptr_end) { word32 *lit_tabptr, *dist_tabptr; byte *ucptr, *ucptr_end; word32 bfinal, btype, bit_pos, len, pos, extra_bits, entry, dist_entry; word32 bits, is_len, dist, lit_mask, dist_mask, tmp; int i; bit_pos = *bit_pos_ptr; // printf("At file offset %08x,bit %d cptr[0]:%02x %02x\n", // (word32)(cptr - cptr_base), bit_pos, cptr[0], cptr[1]); bfinal = (cptr[0] >> bit_pos) & 1; bit_pos++; btype = (((cptr[1] << 8) | cptr[0]) >> bit_pos) & 3; bit_pos += 2; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // printf("bfinal:%d, btype:%d\n", bfinal, btype); if(bfinal) { dsk->fd = 0; // Last block } if(btype == 3) { // Reserved: error return 0; } else if(btype == 0) { // uncompressed // Align cptr to next byte bit_pos += 7; cptr += (bit_pos >> 3); *bit_pos_ptr = 0; len = cptr[0] + (cptr[1] << 8); ucptr = undeflate_ensure_dest_len(dsk, 0, len); if(!ucptr) { return 0; } cptr += 4; for(i = 0; i < (int)len; i++) { *ucptr++ = *cptr++; } dsk->dimage_size += len; return cptr; } if(btype == 1) { // Fixed Huffman codes lit_tabptr = &(g_undeflate_fixed_len_tab[0]); dist_tabptr = &(g_undeflate_fixed_dist_tab[0]); lit_mask = 0x1ff; dist_mask = 0x1f; } else { // Dynamic Huffman codes *bit_pos_ptr = bit_pos; lit_tabptr = undeflate_dynamic_table(cptr, bit_pos_ptr, cptr_base); dist_tabptr = g_undeflate_dynamic_dist_tabptr; // printf("dynamic table used %d bits\n", // *bit_pos_ptr - bit_pos); lit_mask = (1 << g_undeflate_dynamic_bits) - 1; dist_mask = (1 << g_undeflate_dynamic_dist_bits) - 1; bit_pos = *bit_pos_ptr; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } if(!lit_tabptr || !dist_tabptr) { printf("Code table failure\n"); return 0; } ucptr = undeflate_ensure_dest_len(dsk, 0, 65536); // Just a guess if(!ucptr) { return 0; } ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; while(cptr < cptr_end) { #if 0 printf("Top of loop, cptr:%p, lit_tabptr:%p, dsk->raw:%p\n", cptr, lit_tabptr, dsk->raw_data); #endif if(ucptr > ucptr_end) { ucptr = undeflate_ensure_dest_len(dsk, ucptr, 65536); ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; // printf("Update ucptr to %p\n", ucptr); if(!ucptr) { return 0; } } pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); pos = pos >> bit_pos; entry = lit_tabptr[pos & lit_mask]; bits = (entry >> 16) & 0xf; is_len = (entry >> 24) & 1; len = entry & 0xffff; #if 0 printf("At offset +%08x bit:%d, huffcode=%04x, is %d bits, " "entry=%08x\n", (int)(cptr - cptr_base), bit_pos, pos & lit_mask, bits, entry); #endif if(bits == 0) { printf("bits=0, %08x bad table\n", lit_mask); return 0; } bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(!is_len) { // Literal // literal byte // printf(" Out +%06x: %02x\n", // (int)(ucptr - dsk->raw_data), len & 0xff); //putc(len, g_outf); *ucptr++ = len; } else { if(len == 2) { // Code=0x100, end block // All done // printf("Got the 0x100 code! All done!\n"); *bit_pos_ptr = bit_pos; dsk->dimage_size = ucptr - dsk->raw_data; // printf("Set dsk->image_size = %08x\n", // dsk->image_size); return cptr; } extra_bits = (entry >> 20) & 7; if(extra_bits) { pos = cptr[0] | (cptr[1] << 8); pos = pos >> bit_pos; pos = pos & ((1 << extra_bits) - 1); len += pos; } #if 0 printf("At offset +%08x, bit:%d got extra_bits=%d, " "len=%08x\n", (int)(cptr - cptr_base), bit_pos, extra_bits, len); #endif bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // Get distance code pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); pos = pos >> bit_pos; #if 0 printf("At offset +%08x, bit:%d raw distance code: " "%02x\n", (int)(cptr - cptr_base), bit_pos, pos & dist_mask); #endif dist_entry = dist_tabptr[pos & dist_mask]; bits = (dist_entry >> 16) & 0xf; if(bits == 0) { printf("bits=0 for dist_entry:%08x %08x\n", dist_entry, pos); } extra_bits = (dist_entry >> 20) & 0xf; dist = dist_entry & 0xffff; //printf("dist_entry:%08x, extra_bits:%d, dist:%05x\n", // dist_entry, extra_bits, dist); bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(extra_bits) { pos = (cptr[0] | (cptr[1] << 8) | (cptr[2] << 16)) >> bit_pos; #if 0 printf(" At offset +%08x, bit:%d, raw ex:" "%08x\n", (int)(cptr - cptr_base), bit_pos, pos); #endif tmp = pos & ((1 << extra_bits) - 1); dist += tmp; #if 0 printf("at offset +%08x, got %d extra dist " "for total dist=%d (%05x)\n", (int)(cptr - cptr_base), extra_bits, dist, pos); #endif bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } //printf("Repeating %d bytes from dist:%05x\n", len, // dist); if(ucptr < (dsk->raw_data + dist)) { printf("Dist out of bounds:%04x %p %p\n", dist, ucptr, dsk->raw_data); return 0; } for(i = 0; i < (int)len; i++) { ucptr[0] = ucptr[0-(int)dist]; #if 0 putc(ucptr[0], g_outf); printf(" Out +%06x: %02x\n", (int)(ucptr - dsk->raw_data), ucptr[0]); #endif ucptr++; } } } printf("Ran out of compressed data, bad gzip file\n"); return 0; } byte * undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size) { word32 *wptr; byte *cptr_base, *cptr_end; word32 flg, xfl, xlen, bit_offset, exp_crc, len, crc; cptr_base = cptr; cptr_end = cptr + compr_size; if((cptr[0] != 0x1f) || (cptr[1] != 0x8b) || (cptr[2] != 0x08)) { printf("Not gzip file, exiting\n"); return 0; } flg = cptr[3]; xfl = cptr[8]; printf("flg:%02x and xflags:%02x\n", flg, xfl); cptr += 10; if(flg & 4) { // FEXTRA set xlen = cptr[0] + (cptr[1] * 256); printf("FEXTRA XLEN is %d, skipping that many bytes\n", xlen); cptr += 2 + xlen; } if(flg & 8) { // FNAME set cptr += strlen((char *)cptr) + 1; } if(flg & 0x10) { // FCOMMENT set cptr += strlen((char *)cptr) + 1; } if(flg & 2) { // FHCRC set cptr += 2; } printf("gzip header was %02x bytes long\n", (int)(cptr - cptr_base)); dsk->raw_dsize = 140*1024; // Just a guess, alloc size dsk->raw_data = undeflate_malloc(dsk->raw_dsize); if(dsk->raw_data == 0) { return 0; } printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); dsk->dimage_size = 0; // Used size wptr = undeflate_init_tables(); if(wptr == 0) { return 0; // Some sort of error, get out } bit_offset = 0; while(cptr < cptr_end) { cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, cptr_end); if(cptr == 0) { // Failed break; } if(dsk->fd == 0) { printf("undeflate_block set fd=0, success\n"); // Done, success! // Check crc if(bit_offset) { cptr++; } if((cptr + 8) > cptr_end) { printf("No CRC or LEN fields at end\n"); break; } exp_crc = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16) | (cptr[3] << 24); len = cptr[4] | (cptr[5] << 8) | (cptr[6] << 16) | (cptr[7] << 24); if(len != dsk->dimage_size) { printf("Len mismatch: exp %08x != %08llx\n", len, dsk->dimage_size); break; } crc = woz_calc_crc32(dsk->raw_data, len, 0); if(crc != exp_crc) { printf("CRC mismatch: %08x != exp %08x\n", crc, exp_crc); break; } // Real success, set raw_dsize dsk->raw_data = undeflate_realloc(dsk->raw_data, dsk->dimage_size); dsk->raw_dsize = dsk->dimage_size; return cptr; } } printf("Failed\n"); // Disk image thread not found, get out free(dsk->raw_data); dsk->fd = -1; dsk->dimage_size = 0; dsk->raw_data = 0; dsk->raw_dsize = 0; return 0; } void undeflate_gzip(Disk *dsk, const char *name_str) { byte *cptr; dword64 compr_dsize, dret; word32 compr_size; int fd; int i; // On success, set dsk->fd=0 and dsk->raw_data,raw_dsize properly. printf("undeflate_gzip on file %s\n", name_str); fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return; } compr_dsize = cfg_get_fd_size(fd); printf("size: %lld\n", compr_dsize); if((compr_dsize >> 31) != 0) { // > 2GB...too big for this code printf("gzip file is too large\n"); dsk->fd = -1; return; } compr_size = (word32)compr_dsize; cptr = malloc(compr_size + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } dret = cfg_read_from_fd(fd, cptr, 0, compr_size); if(dret != compr_size) { compr_size = 0; // Make header searching fail } //g_outf = fopen("out.dbg", "w"); undeflate_gzip_header(dsk, cptr, compr_size); free(cptr); undeflate_free_tables(); } byte * undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size) { word32 *wptr; byte *cptr_base, *cptr_end; word32 bit_offset; cptr_base = cptr; cptr_end = cptr + dcompr_size; dsk->raw_data = undeflate_malloc(dsk->raw_dsize); if(dsk->raw_data == 0) { return 0; } printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); dsk->dimage_size = 0; // Used size wptr = undeflate_init_tables(); if(wptr == 0) { return 0; // Some sort of error, get out } bit_offset = 0; while(cptr < cptr_end) { cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, cptr_end); if(cptr == 0) { // Failed break; } if(dsk->fd == 0) { printf("undeflate_block set fd=0, success\n"); // Done, success! // Check crc if(bit_offset) { cptr++; } // Real success, set raw_dsize dsk->raw_data = undeflate_realloc(dsk->raw_data, dsk->dimage_size); dsk->raw_dsize = dsk->dimage_size; return cptr; } } printf("Failed\n"); // Disk image thread not found, get out free(dsk->raw_data); dsk->fd = -1; dsk->dimage_size = 0; dsk->raw_data = 0; dsk->raw_dsize = 0; return 0; } byte g_zip_local_file_header[] = { 0x50, 0x4b, 0x03, 0x04 }; byte g_zip_central_file_header[] = { 0x50, 0x4b, 0x01, 0x02 }; byte g_zip_end_central_dir_header[] = { 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0 }; byte g_zip64_end_central_dir_locator[] = { 0x50, 0x4b, 0x06, 0x07, 0, 0, 0, 0 }; byte g_zip64_end_central_dir_header[] = { 0x50, 0x4b, 0x06, 0x06 }; extern Cfg_listhdr g_cfg_partitionlist; int undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, dword64 uncompr_dsize, dword64 compr_dsize) { byte buf[64]; byte *cptr, *cptr2; dword64 dret, compr_doffset; word32 compr_method, name_len, extra_len; word32 bit_flags; int ret; int i; // return -1 on failure, >= 0 on success printf("undeflate_zipfile called, fd:%d, offset:%08llx, unc:%lld " "compr:%lld\n", fd, dlocal_header_off, uncompr_dsize, compr_dsize); dret = cfg_read_from_fd(fd, &buf[0], dlocal_header_off, 64); if(dret != 64) { printf("read dret:%08llx != 64\n", dret); return -1; } for(i = 0; i < 4; i++) { if(buf[i] != g_zip_local_file_header[i]) { printf("hdr[%d]=%02x\n", i, buf[i]); return -1; } } if(((uncompr_dsize | compr_dsize) >> 31) != 0) { printf("Size >2GB, not supported\n"); return -1; } bit_flags = cfg_get_le16(&(buf[6])); compr_method = cfg_get_le16(&(buf[8])); // compr_size = cfg_get_le32(&(buf[18])); // Probably 0 // uncompr_size = cfg_get_le32(&(buf[22])); // Probably 0 name_len = cfg_get_le16(&(buf[26])); extra_len = cfg_get_le16(&(buf[28])); // The ZIP file format is annoying, the local header doesn't have // compr_size and uncompr_size generally (if bit_flags bit 3 is set). // Even if it does, it's fine to always use the central directory printf("bit_flags: %04x\n", bit_flags); compr_doffset = dlocal_header_off + 30 + name_len + extra_len; cptr = undeflate_malloc(compr_dsize + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_dsize + i] = 0; } dret = cfg_read_from_fd(fd, cptr, compr_doffset, compr_dsize); if(dret != compr_dsize) { return -1; } dsk->raw_dsize = uncompr_dsize; dsk->dimage_size = uncompr_dsize; ret = -1; if(compr_method == 0) { // Stored, just use cptr dsk->raw_data = cptr; dsk->raw_dsize = uncompr_dsize; dsk->dimage_start = 0; close(fd); dsk->fd = 0; cptr = 0; // So free(cptr) does nothing ret = 0; } else if(compr_method == 8) { // Deflate cptr2 = undeflate_zipfile_blocks(dsk, cptr, compr_dsize); printf("undeflate_zipfile_blocks ret:%p\n", cptr2); if(cptr2 != 0) { ret = 0; } } else { printf("Unknown compr_method:%04x\n", compr_method); } free(cptr); undeflate_free_tables(); return ret; } int undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, int min_size) { int pos, good; int i; // Search for cmp_ptr in the bptr buffer (basically, look for "PKxx" // header strings). pos = size - min_size; good = 0; while(pos >= 0) { good = 1; for(i = 0; i < cmp_len; i++) { if(bptr[pos + i] != cmp_ptr[i]) { good = 0; break; } } if(good) { break; } pos--; } if(!good) { return -1; } return pos; } int undeflate_zipfile_make_list(int fd) { byte buf[1024]; dword64 dret, dsize, dir_doff, dir_dsize, unc_dsize, compr_dsize; dword64 local_dheader, dneg1, dval, doff, dpos, dlen; byte *dirptr, *name_ptr, *bptr, *bptr2; char *str; word32 extra_len, comment_len, ent, entries, part_len, inc; word32 tmp_off, ex_off, this_size, this_id; int pos, good, add_it, need_compr, need_unc, need_dheader; int name_len; int i; dret = cfg_read_from_fd(fd, &buf[0], 0, 64); if(dret != 64) { return 0; // Not a ZIP file } // See if it's a PKZIP file, starting 0x50, 0x4b, 0x03, 0x04 for(i = 0; i < 4; i++) { if(buf[i] != g_zip_local_file_header[i]) { return 0; } } printf("This looks like a .zip file\n"); // Find end of central directory record in last 1024 bytes. If it's // not there, this is too complex of a ZIP file for us, give up dsize = cfg_get_fd_size(fd); for(i = 0; i < 1024; i++) { buf[i] = 0; } dpos = 0; dlen = dsize; if(dsize > 1024) { dpos = dsize - 1024; dlen = 1024; } dret = cfg_read_from_fd(fd, &buf[0], dpos, dlen); if(dret != dlen) { return 0; // Unknown problem } pos = undeflate_zipfile_search(&buf[0], &g_zip_end_central_dir_header[0], 1024, 8, 22); // End of Central Directory is at least 22 bytes if(pos < 0) { printf("Cannot parse this .zip file\n"); return 0; } entries = cfg_get_le16(&(buf[pos + 8])); dir_dsize = cfg_get_le32(&(buf[pos + 12])); dir_doff = cfg_get_le32(&(buf[pos + 16])); #if 0 printf(".zip entries:%04x, dir_dsize:%06llx, dir_doff:%08llx\n", entries, dir_dsize, dir_doff); #endif dneg1 = 0xffffffffULL; if(dir_doff == dneg1) { printf("We must look for the ZIP64 end dir locator\n"); pos = undeflate_zipfile_search(&buf[0], &g_zip64_end_central_dir_locator[0], 1024, 8, 20); if(pos < 0) { printf("Cannot parse this ZIP64 file\n"); return 0; } doff = cfg_get_le64(&(buf[pos + 8])); printf("ZIP64 end of central dir record at 0x%08llx\n", doff); if((doff + 64) > dsize) { printf("End Central Dir record out of bounds\n"); return 0; } // Now read end of central directory record. Just read 64 bytes // It has to be at least 56 bytes, and the locator had to be // after, so it must fit dret = cfg_read_from_fd(fd, &buf[0], doff, 64); if(dret != 64) { return 0; // Unknown problem } pos = undeflate_zipfile_search(&buf[0], &g_zip64_end_central_dir_header[0], 64, 4, 64); if(pos != 0) { printf("ZIP64 end of central dir record not found\n"); return 0; } entries = cfg_get_le32(&(buf[32])); dir_dsize = cfg_get_le64(&(buf[40])); dir_doff = cfg_get_le64(&(buf[48])); } if((entries < 1) || (dir_dsize > dsize) || (dir_dsize > (1L << 20)) || ((dir_doff + dir_dsize) > dsize)) { printf("Malformed zip file\n"); return 0; } dirptr = undeflate_malloc(dir_dsize); dret = cfg_read_from_fd(fd, dirptr, dir_doff, dir_dsize); if(dret != dir_dsize) { printf("Couldn't read central dir\n"); return 0; } part_len = cfg_partition_maybe_add_dotdot(); // part_len is strlen(g_cfg_part_path[]); pos = 0; ent = 0; while(pos < (int)dir_dsize) { #if 0 printf("Working on ent %d at pos %d\n", ent, pos); #endif if(ent >= entries) { break; // all done } good = 1; for(i = 0; i < 4; i++) { if(dirptr[pos + i] != g_zip_central_file_header[i]) { // corrupt index, get out printf("At pos %04x, i:%d bad hdr\n", pos, i); good = 0; break; } } if(!good) { break; } compr_dsize = cfg_get_le32(&dirptr[pos + 20]); unc_dsize = cfg_get_le32(&dirptr[pos + 24]); name_len = cfg_get_le16(&dirptr[pos + 28]); extra_len = cfg_get_le16(&dirptr[pos + 30]); comment_len = cfg_get_le16(&dirptr[pos + 32]); local_dheader = cfg_get_le32(&dirptr[pos + 42]); if((pos + 46UL + name_len) > dir_dsize) { printf("Corrupt entry: pos:%04x, name_len:%04x, " "dir_dsize:%05llx\n", pos, name_len, dir_dsize); break; } need_unc = (unc_dsize == dneg1); need_compr = (compr_dsize == dneg1); need_dheader = (local_dheader == dneg1); // Walk extras to update unc/compr size and file offset, if // the standard fields are 0xffffffff. bptr = &(dirptr[pos + 46 + name_len]); ex_off = 0; add_it = 1; while(ex_off < extra_len) { #if 0 printf("Working on ex_off:%d out of %d\n", ex_off, extra_len); #endif this_id = cfg_get_le16(&bptr[ex_off]); this_size = cfg_get_le16(&bptr[ex_off + 2]); if((this_size + ex_off + pos + 46UL + name_len) > dir_dsize) { printf("Corrupt ZIP64 extra info entry\n"); add_it = 0; break; } ex_off += 4; if(this_id == 0x0001) { tmp_off = 0; bptr2 = &(bptr[ex_off]); while(tmp_off < this_size) { dval = cfg_get_le64(bptr2); #if 0 printf("tmp_off %d of %d, dval:" "%016llx\n", tmp_off, this_size, dval); #endif if(need_compr) { compr_dsize = dval; need_compr = 0; } else if(need_unc) { unc_dsize = dval; need_unc = 0; } else if(need_dheader) { local_dheader = dval; need_dheader = 0; } else { printf("Corrupt ZIP64\n"); add_it = 0; } tmp_off += 8; bptr += 8; } } ex_off += this_size; } if(need_unc || need_compr || need_dheader) { printf("Bad ZIP64 overrides\n"); add_it = 0; } // See if filename is at the proper depth name_ptr = &(dirptr[pos + 46]); if(add_it) { add_it = cfg_partition_name_check(name_ptr, name_len); } //printf("ent:%d name:%s len:%d had add_it:%d, part_len:%d\n", // ent, name_ptr, name_len, add_it, part_len); inc = 46 + name_len + extra_len + comment_len; if(add_it) { // Handle directories either explicitly listed, as // foo/, foo/bar/, foo/bar/1, foo/bar/2 ; or // implied: foo/bar/1 and foo/bar/2 as entries // implies foo/ and foo/bar/ are directories. // Add any name at the current part_len level, but // make sure it's unique (don't add lots of "foo"s). name_ptr += part_len; name_len -= part_len; if(name_len <= 0) { add_it = 0; } for(i = 0; i < name_len; i++) { if(name_ptr[i] == '/') { // This ends this name at this level if(i > 0) { add_it = 2; name_len = i + 1; } else { add_it = 0; } break; } } } if((add_it < 2) && (unc_dsize < 140*1024)) { add_it = 0; } if(add_it) { str = malloc(name_len + 1); cfg_strncpy(str, (char *)&name_ptr[0], name_len + 1); cfg_file_add_dirent_unique(&g_cfg_partitionlist, str, add_it - 1, unc_dsize, local_dheader, compr_dsize, ent); free(str); } pos += inc; ent++; } free(dirptr); printf("Returning %d, pos:%05x, dir_dsize:%05llx\n", ent, pos, dir_dsize); return g_cfg_partitionlist.last; } ================================================ FILE: gsplus/src/unshk.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2021 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // This code is based on the official NuFX documentation in Apple II FTN.e08002 // available at: http://nulib.com/library/FTN.e08002.htm. Andy McFadden has // reverse-engineered GSHK (and its quirks) in Nulib, at: http://nulib.com/, // and that code was very helpful in getting the basic algorithms correct. #include "defc.h" word32 unshk_get_long4(byte *bptr) { word32 val; int i; // Get 4 bytes in little-endian form val = 0; for(i = 3; i >=0 ; i--) { val = (val << 8) | bptr[i]; } return val; } word32 unshk_get_word2(byte *bptr) { // Get 2 bytes in little-endian form return (bptr[1] << 8) | bptr[0]; } word32 unshk_calc_crc(byte *bptr, int size, word32 start_crc) { word32 crc; int i, j; // No table used: do basic CRC operation on size bytes. For CCITT-16 // (the one used in ShrinkIt), xor the byte into the upper 8 bits of // the current crc, then xor in 0x1021 for each '1' bit shifted out // the top. Use a 32-bit crc variable to get the bit shifted out. crc = start_crc & 0xffff; for(i = 0; i < size; i++) { crc = crc ^ (bptr[i] << 8); for(j = 0; j < 8; j++) { crc = crc << 1; if(crc & 0x10000) { crc = crc ^ 0x11021; // XOR in 0x1021, and clear bit 16 as well. } } // printf("CRC after [%04x]=%02x is %04x\n", i, bptr[i], crc); } return crc & 0xffff; } int unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr) { byte *start_ucptr; word32 c; int outlen, count; int i; // RLE is 3 bytes: { 0xdb, char, count}, where count==0 means output // one char. start_ucptr = ucptr; while(len > 0) { c = *cptr++; len--; if(c == rle_delim) { c = *cptr++; count = *cptr++; len -= 2; for(i = 0; i <= count; i++) { *ucptr++ = c; } } else { *ucptr++ = c; } } outlen = (int)(ucptr - start_ucptr); if(outlen != 0x1000) { printf("RLE failed, output %d bytes\n", outlen); return 1; } return 0; } void unshk_lzw_clear(Lzw_state *lzw_ptr) { int i; lzw_ptr->entry = 0x100; // First expected table pos lzw_ptr->bits = 9; for(i = 0; i < 256; i++) { lzw_ptr->table[i] = i << 12; // Encodes depth==0 as well } lzw_ptr->table[0x100] = 0; } // LZW Table format in 32-bit word: { depth[11:0], finalc[7:0], code[11:0] } byte * unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen) { byte *end_ucptr, *bptr; word32 mask, val, entry, newcode, finalc_code; int bit_pos, depth, bits; // This routine handles ShrinkIt LZW/1 and LZW/2 streams. It expects // the caller has set entry=0x100 and bits=9 at the start of each // LZW/1 chunk entry = lzw_ptr->entry; bits = lzw_ptr->bits; end_ucptr = ucptr + uclen; //printf("unlzw block: format:%d, uclen:%04x\n", thread_format, uclen); mask = (1 << bits) - 1; bit_pos = 0; while(ucptr < end_ucptr) { newcode = (cptr[2] << 16) | (cptr[1] << 8) | cptr[0]; newcode = (newcode >> bit_pos) & mask; bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // printf("At entry:%04x, bits:%d newcode:%04x\n", entry, bits, // newcode); if((entry + 1) >= mask) { bits++; mask = (mask << 1) | 1; // Note this is one too early, but this is needed to // match ShrinkIt } // Newcode is up to 12-bits, where <= 0xff means just this // char, and >= 0x101 means chase down that code, output the // character in that table entry, and repeat if(newcode == 0x100) { // printf("Got clear code\n"); entry = 0x100; bits = 9; mask = 0x1ff; continue; } if(newcode > entry) { printf("Bad code: %04x, entry:%04x\n", newcode, entry); return 0; } #if 0 if(newcode == entry) { // KwKwK case: operate on oldcode printf("KwKwK case!\n"); } #endif finalc_code = newcode; depth = lzw_ptr->table[newcode & 0xfff] >> 20; // depth will be 0 for 1 character, 1 for 2 characters, etc. bptr = ucptr + depth; while(bptr >= ucptr) { finalc_code = lzw_ptr->table[finalc_code & 0xfff]; *bptr-- = (finalc_code >> 12) & 0xff; } val = lzw_ptr->table[entry]; lzw_ptr->table[entry] = (val & (~0xff000)) | (finalc_code & 0xff000); // [entry] has code from last iteration (which stuck in // the last finalc char, which we need to toss now), // and update it with the correct finalc character. // printf("Table[%04x]=%08x\n", entry, lzw_ptr->table[entry]); depth++; ucptr += depth; #if 0 bptr = ucptr - depth; printf("src:%04x, out+%06x: ", (int)(cptr - cptr_start), (int)(ucptr - depth - (end_ucptr - uclen))); for(i = 0; i < depth; i++) { printf(" %02x", *bptr++); } printf("\n"); #endif lzw_ptr->table[entry + 1] = (depth << 20) | (finalc_code & 0xff000) | newcode; // Set tab[entry+1] for KwKwK case, with this newcode, // and this finalc character. This also saves this // newcode when the next code is received. entry++; } lzw_ptr->entry = entry; lzw_ptr->bits = bits; if(bit_pos) { // We used part of this byte, use it cptr++; } return cptr; } void unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, word32 uncompr_size, word32 thread_format, byte *base_cptr) { Lzw_state lzw_state; byte *end_cptr, *end_ucptr, *rle_inptr; word32 rle_delim, len, use_lzw, lzw_len, crc, chunk_crc; int ret; int i; printf("Uncompress %d compress bytes into %d bytes, source offset:" "%08x\n", compr_size, uncompr_size, (word32)(cptr - base_cptr)); // LZW/1 format: crc_lo, crc_hi, vol, rle_delim then start the chunk // each chunk: rle_len_lo, rle_len_hi, lzw_used // LZW/2 format: vol, rle_delim then start the chunk // each chunk: rle_len_lo, rle_len_hi, lzw_len_lo, lzw_len_hi // where rle_len_hi[7]==1 means LZW was used end_cptr = cptr + compr_size; end_ucptr = ucptr + uncompr_size; chunk_crc = 0; if(thread_format != 3) { // LZW/1 chunk_crc = (cptr[1] << 8) | cptr[0]; cptr += 2; // Skip over CRC bytes } dsk->vol_num = cptr[0]; // LZW/1 rle_delim = cptr[1]; cptr += 2; unshk_lzw_clear(&lzw_state); // printf("vol_num:%02x, rle_delim:%02x\n", dsk->vol_num, rle_delim); // LZW/1 format for each chunk: len_lo, len_hi, use_lzw // LZW/2 format for each chunk: len_lo, len_hi. If len_hi[7]=1, then // two more bytes: lzw_len_lo, lzw_len_hi while(cptr < (end_cptr - 4)) { if(ucptr >= end_ucptr) { break; } len = (cptr[1] << 8) | cptr[0]; #if 0 printf("chunk at +%08x, len:%04x, dest offset:%08x\n", (word32)(cptr - base_cptr), len, (word32)(ucptr - (end_ucptr - uncompr_size))); #endif cptr += 2; use_lzw = (len >> 15) & 1; if(len & 0x6000) { printf("Illegal length: %04x\n", len); return; // Ilegal length } len = len & 0x1fff; lzw_len = 0; if(thread_format == 3) { // LZW/2 if(use_lzw) { lzw_len = (cptr[1] << 8) | cptr[0]; if(lzw_len > 0x1004) { printf("Bad lzw_len: %04x\n", lzw_len); return; // Illegal } cptr += 2; lzw_len -= 4; // Counts from [-4] } } else { // LZW/1 use_lzw = *cptr++; if(use_lzw >= 2) { printf("Bad use_lzw:%02x\n", use_lzw); return; // Bad format } } rle_inptr = cptr; if(use_lzw) { //printf("lzw on %02x.%02x.%02x.., %d bytes (rle:%d)\n", // cptr[0], cptr[1], cptr[2], lzw_len, len); rle_inptr = ucptr; if(len != 0x1000) { // RLE pass is needed: Write to ucptr+0x1000, // and then UnRLE down to ucptr; rle_inptr = ucptr + 0x1000; } cptr = unshk_unlzw(cptr, &lzw_state, rle_inptr, len); if(cptr == 0) { printf("Bad LZW stream\n"); return; } if(thread_format != 3) { // LZW/1 lzw_state.entry = 0x100; // Reset table lzw_state.bits = 9; } } else { lzw_state.entry = 0x100; // Reset table lzw_state.bits = 9; } if(len != 0x1000) { // printf("RLE on %02x.%02x.%02x... %d bytes\n", // cptr[0], cptr[1], cptr[2], len); ret = unshk_unrle(rle_inptr, len, rle_delim, ucptr); if(ret) { printf("unRLE failed\n"); return; } if(!use_lzw) { cptr += len; } } else if(!use_lzw) { // Uncompressed // printf("Uncompressed %02x.%02x.%02x....%d bytes\n", // cptr[0], cptr[1], cptr[2], len); for(i = 0; i < 0x1000; i++) { ucptr[i] = *cptr++; } } // write(g_out_fd, ucptr, 0x1000); ucptr += 0x1000; } printf("cptr:%p, end_cptr:%p, uncompr_size:%08x\n", cptr, end_cptr, uncompr_size); if(thread_format != 3) { // LZW/1 crc = unshk_calc_crc(ucptr - uncompr_size, uncompr_size, 0); //printf("LZW/1 calc CRC %04x vs CRC %04x\n", crc, chunk_crc); if(crc != chunk_crc) { printf("Bad LZW/1 CRC: %04x != %04x\n", crc, chunk_crc); return; } } dsk->fd = 0; } void unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr) { byte *cptr_end, *dptr, *ucptr; word32 total_records, attrib_count, total_threads, thread_class; word32 thread_format, thread_kind, thread_eof, comp_thread_eof; word32 thread_crc, crc, version, disk_size, block_size, num_blocks; word32 filename_length; int i; cptr_end = cptr + compr_size; if(compr_size < 0xa0) { printf("Didn't read everything\n"); return; } // Parse NuFX format: "NuFile" with alternating high bits if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || (cptr[3] != 0xe9) || (cptr[4] != 0x6c) || (cptr[5] != 0xe5)) { printf("Not NuFile, exiting\n"); return; } total_records = unshk_get_long4(&cptr[8]); if(total_records < 1) { return; } // Master Header to NuFile is apparently 48 bytes. Look for "NuFX" // Header to describe threads cptr += 0x30; if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || (cptr[3] != 0xd8)) { return; } attrib_count = unshk_get_word2(&cptr[6]); version = unshk_get_word2(&cptr[8]); // >= 3 means File CRC total_threads = unshk_get_long4(&cptr[10]); num_blocks = unshk_get_long4(&cptr[26]); // extra_type block_size = unshk_get_word2(&cptr[30]); // storage_type // P8 ShrinkIt is riddled with bugs. Disk archives have incorrect // thread_eof for the uncompressed total size. So we need to do // num_blocks * block_size to get the real size. But this can be // buggy too! These fixes are from NuFxLib::Thread.c actualThreadEOF // comments // First, fix block_size. SHK v3.0.1 stored it as a small value if(block_size < 256) { block_size = 512; } disk_size = block_size * num_blocks; if(disk_size == (70*1024)) { // Old GSHK apparently set block_size==256 but blocks=280 for // 5.25" DOS 3.3 disks...block size must be 512 to equal 140K. disk_size = 140*1024; } if(disk_size < 140*1024) { printf("disk_size %dK is invalid\n", disk_size >> 10); return; } cptr += attrib_count; filename_length = unshk_get_word2(&cptr[-2]); // filename_length cptr += filename_length; dptr = cptr + 16*total_threads; // Each thread is 16 bytes, so the data is at +16*total_threads // The data is in the same order as the header for the threads // We ignore anything other than a data thread for SDK for(i = 0; i < (int)total_threads; i++) { if((dptr >= cptr_end) || (cptr >= cptr_end)) { return; } thread_class = unshk_get_word2(&cptr[0]); thread_format = unshk_get_word2(&cptr[2]); thread_kind = unshk_get_word2(&cptr[4]); thread_crc = unshk_get_word2(&cptr[6]); //thread_eof = unshk_get_long4(&cptr[8]); // thread_eof is wrong in P8 ShrinkIt, so just use disk_size thread_eof = disk_size; comp_thread_eof = unshk_get_long4(&cptr[12]); if((dptr + comp_thread_eof) > cptr_end) { return; // Corrupt } if((thread_class == 2) && (thread_kind == 1)) { // Disk image! ucptr = malloc(thread_eof + 0x1000); unshk_data(dsk, dptr, comp_thread_eof, ucptr, thread_eof, thread_format, base_cptr); if(dsk->fd == 0) { // Success, so far. Check CRC printf("Version:%d, thread_crc:%04x\n", version, thread_crc); if(version >= 3) { // CRC is valid crc = unshk_calc_crc(ucptr, thread_eof, 0xffff); #if 0 printf("Thread CRC:%04x, exp:%04x\n", crc, thread_crc); #endif if(crc != thread_crc) { printf("Bad CRC: %04x != exp " "%04x\n", crc, thread_crc); dsk->fd = -1; } } } if(dsk->fd < 0) { free(ucptr); } else { // Real success, set raw_size dsk->raw_dsize = thread_eof; dsk->raw_data = ucptr; return; } } else { dptr += comp_thread_eof; cptr += 16; } } // Disk image thread not found, get out } void unshk(Disk *dsk, const char *name_str) { byte *cptr; int compr_size, fd, pos, ret; int i; printf("unshk %s\n", name_str); // Handle .sdk inside a .zip if(dsk->raw_data) { unshk_dsk_raw_data(dsk); return; } // File is not opened yet, try to open it fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return; } compr_size = (int)cfg_get_fd_size(fd); printf("size: %d\n", compr_size); cptr = malloc(compr_size + 0x1000); pos = 0; for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } while(1) { if(pos >= compr_size) { break; } ret = read(fd, cptr + pos, compr_size - pos); if(ret <= 0) { break; } pos += ret; } close(fd); if(pos != compr_size) { compr_size = 0; // Make header searching fail } unshk_parse_header(dsk, cptr, compr_size, cptr); free(cptr); } void unshk_dsk_raw_data(Disk *dsk) { byte *save_raw_data, *cptr; dword64 save_raw_dsize; int save_fd, compr_size; int i; // This code handles the case of .sdk inside a .zip (for example). // Since unshk() code uses dsk->fd, dsk->raw_data, and dsk->raw_dsize // to communicate success in unshk'ing the disk, we need to copy // those, and restore them, if the unshk fails save_fd = dsk->fd; save_raw_data = dsk->raw_data; save_raw_dsize = dsk->raw_dsize; if(save_raw_dsize >= (1ULL << 30)) { return; // Too large } dsk->fd = -1; dsk->raw_data = 0; dsk->raw_dsize = 0; compr_size = (int)save_raw_dsize; cptr = malloc(compr_size + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } for(i = 0; i < compr_size; i++) { cptr[i] = save_raw_data[i]; } unshk_parse_header(dsk, cptr, compr_size, cptr); free(cptr); if(dsk->raw_data) { // Success, free the old raw data free(save_raw_data); return; } dsk->fd = save_fd; dsk->raw_data = save_raw_data; dsk->raw_dsize = save_raw_dsize; } ================================================ FILE: gsplus/src/vars ================================================ TARGET = gsplus OBJECTS1 = macsnd_driver.o CCOPTS = -Wall -O2 -DMAC SUFFIX = NAME = gsplus XOPTS = ================================================ FILE: gsplus/src/vars_mac ================================================ TARGET = gsplus OBJECTS1 = macsnd_driver.o CCOPTS = -Wall -O2 -DMAC SUFFIX = NAME = gsplus XOPTS = ================================================ FILE: gsplus/src/vars_mac_x ================================================ TARGET = gsplus OBJECTS1 = macsnd_driver.o xdriver.o CCOPTS = -O2 -DMAC -Wall -I/usr/X11/include SUFFIX = NAME = gsplus XOPTS = LDOPTS = -Wl,-framework,CoreAudio -Wl,-framework,CoreFoundation -Wl,-framework,AudioToolbox ================================================ FILE: gsplus/src/vars_x86linux ================================================ TARGET = gsplus OBJECTS1 = pulseaudio_driver.o xdriver.o CCOPTS = -O2 -Wall -fomit-frame-pointer -DPULSE_AUDIO NAME = gsplus LD = $(CC) EXTRA_LIBS = -lXext -lpulse EXTRA_SPECIALS = XOPTS = -I/usr/X11R6/include ================================================ FILE: gsplus/src/video.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ #include #include "defc.h" extern int Verbose; word32 g_a2_filt_stat[200]; int g_a2_line_left_edge[200]; int g_a2_line_right_edge[200]; byte g_cur_border_colors[270]; word32 g_a2_screen_buffer_changed = (word32)-1; word32 g_full_refresh_needed = (word32)-1; word32 g_cycs_in_40col = 0; word32 g_cycs_in_xredraw = 0; word32 g_refresh_bytes_xfer = 0; extern byte *g_slow_memory_ptr; extern int g_fatal_log; extern dword64 g_cur_dfcyc; extern int g_line_ref_amt; extern word32 g_c034_val; extern int g_config_control_panel; extern int g_halt_sim; word32 g_slow_mem_changed[SLOW_MEM_CH_SIZE]; word32 g_slow_mem_ch2[SLOW_MEM_CH_SIZE]; word32 g_a2font_bits[0x100][8]; word32 g_superhires_scan_save[2][256]; Kimage g_mainwin_kimage = { 0 }; Kimage g_debugwin_kimage = { 0 }; int g_debugwin_last_total = 0; extern int g_debug_lines_total; extern dword64 g_last_vbl_dfcyc; extern dword64 g_video_pixel_dcount; dword64 g_video_dfcyc_check_input = 0; int g_video_act_margin_left = BASE_MARGIN_LEFT; int g_video_act_margin_right = BASE_MARGIN_RIGHT; int g_video_act_margin_top = BASE_MARGIN_TOP; int g_video_act_margin_bottom = BASE_MARGIN_BOTTOM; int g_video_act_width = X_A2_WINDOW_WIDTH; int g_video_act_height = X_A2_WINDOW_HEIGHT; int g_mainwin_width = X_A2_WINDOW_WIDTH; int g_mainwin_height = X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2; int g_mainwin_xpos = 100; int g_mainwin_ypos = 300; int g_video_no_scale_window = 0; word32 g_palette_change_cnt[2][16]; int g_border_sides_refresh_needed = 1; int g_border_special_refresh_needed = 1; int g_border_line24_refresh_needed = 1; int g_status_refresh_needed = 1; int g_vbl_border_color = 0; int g_border_last_vbl_changes = 0; int g_border_reparse = 0; int g_use_dhr140 = 0; int g_use_bw_hires = 0; int g_vid_update_last_line = 0; int g_video_save_all_stat_pos = 0; int g_cur_a2_stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | (0xf << BIT_ALL_STAT_TEXT_COLOR); word32 g_palette_8to1624[2][256]; word32 g_a2palette_1624[16]; word32 g_saved_line_palettes[2][200][8]; word32 g_cycs_in_refresh_line = 0; word32 g_cycs_in_refresh_ximage = 0; word32 g_cycs_in_run_16ms = 0; int g_num_lines_superhires = 0; int g_num_lines_superhires640 = 0; int g_num_lines_prev_superhires = 0; int g_num_lines_prev_superhires640 = 0; int g_screen_redraw_skip_count = 0; int g_screen_redraw_skip_amt = -1; word32 g_alpha_mask = 0; word32 g_red_mask = 0xff; word32 g_green_mask = 0xff; word32 g_blue_mask = 0xff; int g_red_left_shift = 16; int g_green_left_shift = 8; int g_blue_left_shift = 0; int g_red_right_shift = 0; int g_green_right_shift = 0; int g_blue_right_shift = 0; int g_status_enable = 1; int g_status_enable_previous = 1; char g_status_buf[MAX_STATUS_LINES][STATUS_LINE_LENGTH + 1]; char *g_status_ptrs[MAX_STATUS_LINES] = { 0 }; word16 g_pixels_widened[128]; int g_video_scale_algorithm = 0; STRUCT(Video_all_stat) { word32 lines_since_vbl; word32 cur_all_stat; }; #define MAX_VIDEO_ALL_STAT ((200*42) + 40) int g_video_all_stat_pos = 0; Video_all_stat g_video_all_stat[MAX_VIDEO_ALL_STAT]; STRUCT(Video_filt_stat) { word32 line_bytes; word32 filt_stat; }; #define MAX_VIDEO_FILT_STAT 10000 int g_video_filt_stat_pos = 0; Video_filt_stat g_video_filt_stat[MAX_VIDEO_FILT_STAT]; int g_video_stat_old_pos = 0; Video_filt_stat g_video_filt_stat_old[MAX_VIDEO_FILT_STAT]; word16 g_dhires_convert[4096]; /* look up { next4, this4, prev 4 } */ const byte g_dhires_colors_16[] = { // Convert dhires to lores color 0x00, /* 0x0 black */ 0x02, /* 0x1 dark blue */ 0x04, /* 0x2 dark green */ 0x06, /* 0x3 medium blue */ 0x08, /* 0x4 brown */ 0x0a, /* 0x5 light gray */ 0x0c, /* 0x6 green */ 0x0e, /* 0x7 aquamarine */ 0x01, /* 0x8 deep red */ 0x03, /* 0x9 purple */ 0x05, /* 0xa dark gray */ 0x07, /* 0xb light blue */ 0x09, /* 0xc orange */ 0x0b, /* 0xd pink */ 0x0d, /* 0xe yellow */ 0x0f /* 0xf white */ }; const int g_lores_colors[] = { // From IIgs Technote #63 /* rgb */ 0x000, /* 0x0 black */ 0xd03, /* 0x1 deep red */ 0x009, /* 0x2 dark blue */ 0xd2d, /* 0x3 purple */ 0x072, /* 0x4 dark green */ 0x555, /* 0x5 dark gray */ 0x22f, /* 0x6 medium blue */ 0x6af, /* 0x7 light blue */ 0x850, /* 0x8 brown */ 0xf60, /* 0x9 orange */ 0xaaa, /* 0xa light gray */ 0xf98, /* 0xb pink */ 0x1d0, /* 0xc green */ 0xff0, /* 0xd yellow */ 0x4f9, /* 0xe aquamarine */ 0xfff /* 0xf white */ }; const byte g_hires_lookup[64] = { // Indexed by { next_bit, this_bit, prev_bit, hibit, odd_byte, odd_col }. // Return lores colors: 0, 3, 6, 9, 0xc, 0xf 0x00, // 00,0000 // black: this and next are 0 0x00, // 00,0001 0x00, // 00,0010 0x00, // 00,0011 0x00, // 00,0100 0x00, // 00,0101 0x00, // 00,0110 0x00, // 00,0111 0x00, // 00,1000 0x00, // 00,1001 0x00, // 00,1010 0x00, // 00,1011 0x00, // 00,1100 0x00, // 00,1101 0x00, // 00,1110 0x00, // 00,1111 0x03, // 01,0000 // purple 0x03, // 01,0001 // purple 0x0c, // 01,0010 // green (odd column) 0x0c, // 01,0011 // green 0x06, // 01,0100 // blue 0x06, // 01,0101 // blue 0x09, // 01,0110 // orange 0x09, // 01,0111 // orange 0x0f, // 01,1000 // white: this and prev are 1 0x0f, // 01,1001 0x0f, // 01,1010 0x0f, // 01,1011 0x0f, // 01,1100 0x0f, // 01,1101 0x0f, // 01,1110 0x0f, // 01,1111 0x00, // 10,0000 // black 0x00, // 10,0001 // black 0x00, // 10,0010 // black 0x00, // 10,0011 // black 0x00, // 10,0100 // black 0x00, // 10,0101 // black 0x00, // 10,0110 // black 0x00, // 10,0111 // black 0x0c, // 10,1000 // green 0x0c, // 10,1001 // green 0x03, // 10,1010 // purple 0x03, // 10,1011 // purple 0x09, // 10,1100 // orange 0x09, // 10,1101 // orange 0x06, // 10,1110 // blue 0x06, // 10,1111 // blue 0x0f, // 11,0000 // white 0x0f, // 11,0001 0x0f, // 11,0010 0x0f, // 11,0011 0x0f, // 11,0100 0x0f, // 11,0101 0x0f, // 11,0110 0x0f, // 11,0111 0x0f, // 11,1000 0x0f, // 11,1001 0x0f, // 11,1010 0x0f, // 11,1011 0x0f, // 11,1100 0x0f, // 11,1101 0x0f, // 11,1110 0x0f // 11,1111 }; const int g_screen_index[] = { 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x028, 0x0a8, 0x128, 0x1a8, 0x228, 0x2a8, 0x328, 0x3a8, 0x050, 0x0d0, 0x150, 0x1d0, 0x250, 0x2d0, 0x350, 0x3d0, 0x078, 0x0f8, 0x178, 0x1f8, 0x278, 0x2f8, 0x378, 0x3f8 // Last row is for float_bus() during VBL }; byte g_font_array[256][8] = { #include "kegsfont.h" }; void video_set_red_mask(word32 red_mask) { video_set_mask_and_shift(red_mask, &g_red_mask, &g_red_left_shift, &g_red_right_shift); } void video_set_green_mask(word32 green_mask) { video_set_mask_and_shift(green_mask, &g_green_mask, &g_green_left_shift, &g_green_right_shift); } void video_set_blue_mask(word32 blue_mask) { video_set_mask_and_shift(blue_mask, &g_blue_mask, &g_blue_left_shift, &g_blue_right_shift); } void video_set_alpha_mask(word32 alpha_mask) { g_alpha_mask = alpha_mask; printf("Set g_alpha_mask=%08x\n", alpha_mask); } void video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, int *shift_right_ptr) { int shift; int i; /* Shift until we find first set bit in mask, then remember mask,shift*/ shift = 0; for(i = 0; i < 32; i++) { if(x_mask & 1) { /* we're done! */ break; } x_mask = x_mask >> 1; shift++; } *mask_ptr = x_mask; *shift_left_ptr = shift; /* Now, calculate shift_right_ptr */ shift = 0; x_mask |= 1; // make sure at least one bit is set for(i = 0; i < 32; i++) { if(x_mask >= 0x80) { break; } shift++; x_mask = x_mask << 1; } *shift_right_ptr = shift; } void video_set_palette() { int i; for(i = 0; i < 16; i++) { video_update_color_raw(0, i, g_lores_colors[i]); g_a2palette_1624[i] = g_palette_8to1624[0][i]; } } void video_set_redraw_skip_amt(int amt) { if(g_screen_redraw_skip_amt < amt) { g_screen_redraw_skip_amt = amt; printf("Set g_screen_redraw_skip_amt = %d\n", amt); } } Kimage * video_get_kimage(int win_id) { if(win_id == 0) { return &g_mainwin_kimage; } if(win_id == 1) { return &g_debugwin_kimage; } printf("win_id: %d not supported\n", win_id); exit(1); } char * video_get_status_ptr(int line) { if(line < MAX_STATUS_LINES) { return g_status_ptrs[line]; } return 0; } #if 0 int video_get_x_refresh_needed(Kimage *kimage_ptr) { int ret; ret = kimage_ptr->x_refresh_needed; kimage_ptr->x_refresh_needed = 0; return ret; } #endif void video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh) { kimage_ptr->x_refresh_needed = do_refresh; } int video_get_active(Kimage *kimage_ptr) { return kimage_ptr->active; } void video_set_active(Kimage *kimage_ptr, int active) { kimage_ptr->active = active; if(kimage_ptr != &g_mainwin_kimage) { adb_nonmain_check(); } } void video_init(int mdepth, int screen_width, int screen_height, int no_scale_window) { word32 col[4]; word32 val0, val1, val2, val3, next_col, next2_col; word32 val, cur_col; int i, j; // Initialize video system (called one-time only) g_video_no_scale_window = no_scale_window; for(i = 0; i < 200; i++) { g_a2_line_left_edge[i] = 0; g_a2_line_right_edge[i] = 0; } for(i = 0; i < 200; i++) { g_a2_filt_stat[i] = -1; for(j = 0; j < 8; j++) { g_saved_line_palettes[0][i][j] = (word32)-1; g_saved_line_palettes[1][i][j] = (word32)-1; } } for(i = 0; i < 262; i++) { g_cur_border_colors[i] = -1; } for(i = 0; i < 128; i++) { val0 = i; val1 = 0; for(j = 0; j < 7; j++) { val1 = val1 << 2; if(val0 & 0x40) { val1 |= 3; } val0 = val0 << 1; } g_pixels_widened[i] = val1; } vid_printf("Zeroing out video memory, mdepth:%d\n", mdepth); for(i = 0; i < SLOW_MEM_CH_SIZE; i++) { g_slow_mem_changed[i] = (word32)-1; g_slow_mem_ch2[i] = 0; } // create g_dhires_convert[] array // TODO: Look at patent #4786893 for details on VGC and dhr for(i = 0; i < 4096; i++) { /* Convert index bits 11:0 where 3:0 is the previous color */ /* and 7:4 is the current color to translate */ /* Bit 4 will be the first pixel displayed on the screen */ for(j = 0; j < 4; j++) { cur_col = (i >> (1 + j)) & 0xf; next_col = (i >> (2 + j)) & 0xf; next2_col = (i >> (3 + j)) & 0xf; cur_col = (((cur_col << 4) + cur_col) >> (3 - j)) & 0xf; if((cur_col == 0xf) || (next_col == 0xf) || (next2_col == 0xf)) { cur_col = 0xf; col[j] = cur_col; } else if((cur_col == 0) || (next_col == 0) || (next2_col == 0)) { cur_col = 0; col[j] = cur_col; } else { col[j] = cur_col; } } if(g_use_dhr140) { for(j = 0; j < 4; j++) { col[j] = (i >> 4) & 0xf; } } val0 = g_dhires_colors_16[col[0] & 0xf]; val1 = g_dhires_colors_16[col[1] & 0xf]; val2 = g_dhires_colors_16[col[2] & 0xf]; val3 = g_dhires_colors_16[col[3] & 0xf]; val = val0 | (val1 << 4) | (val2 << 8) | (val3 << 12); g_dhires_convert[i] = val; if((i == 0x7bc) || (i == 0xfff)) { //printf("g_dhires_convert[%03x] = %04x\n", i, val); } } video_init_kimage(&g_mainwin_kimage, X_A2_WINDOW_WIDTH, X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2, screen_width, screen_height); video_init_kimage(&g_debugwin_kimage, 80*8 + 8 + 8, 25*16 + 8 + 8, screen_width, screen_height); change_display_mode(g_cur_dfcyc); video_reset(); g_vid_update_last_line = 0; g_video_all_stat_pos = 1; g_video_all_stat[0].cur_all_stat = 0; g_video_all_stat[0].lines_since_vbl = 0; g_video_save_all_stat_pos = 0; g_video_filt_stat_pos = 0; video_update_status_enable(&g_mainwin_kimage); video_update_through_line(262); printf("g_mainwin_kimage created and init'ed\n"); fflush(stdout); } int video_clamp(int value, int min, int max) { // Ensure value is >= min and <= max. If max <= min, return min if(value > max) { value = max; } if(value <= min) { value = min; } return value; } void video_init_kimage(Kimage *kimage_ptr, int width, int height, int screen_width, int screen_height) { int x_width, x_height, a2_height, x_xpos, x_ypos; int i; if(screen_width < width) { screen_width = width; } if(screen_height < height) { screen_width = height; } x_width = width; x_height = height; a2_height = height; x_xpos = 100; x_ypos = 300; if(kimage_ptr == &g_mainwin_kimage) { x_width = g_mainwin_width; x_height = g_mainwin_height; x_xpos = g_mainwin_xpos; x_ypos = g_mainwin_ypos; // Handle status lines now if(!g_status_enable) { a2_height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; } } x_width = video_clamp(x_width, width, screen_width); x_height = video_clamp(x_height, height, screen_height); x_xpos = video_clamp(x_xpos, 0, screen_width - 640); x_ypos = video_clamp(x_ypos, 0, screen_height - 420); kimage_ptr->wptr = (word32 *)calloc(1, width * (height + 2) * 4); // Scaling routines read from line+1, expect it to be 0 kimage_ptr->a2_width_full = width; kimage_ptr->a2_height_full = height; kimage_ptr->a2_width = width; kimage_ptr->a2_height = a2_height; kimage_ptr->x_width = x_width; kimage_ptr->x_height = x_height; kimage_ptr->x_refresh_needed = 1; kimage_ptr->x_max_width = screen_width; kimage_ptr->x_max_height = screen_height; kimage_ptr->x_xpos = x_xpos; kimage_ptr->x_ypos = x_ypos; kimage_ptr->active = 0; kimage_ptr->vbl_of_last_resize = 0; kimage_ptr->c025_val = 0; //printf("Created window, width:%d x_width:%d height:%d x_height:%d\n", // width, x_width, height, x_height); kimage_ptr->scale_width_to_a2 = 0x10000; kimage_ptr->scale_width_a2_to_x = 0x10000; kimage_ptr->scale_height_to_a2 = 0x10000; kimage_ptr->scale_height_a2_to_x = 0x10000; kimage_ptr->num_change_rects = 0; for(i = 0; i <= MAX_SCALE_SIZE; i++) { kimage_ptr->scale_width[i] = i; kimage_ptr->scale_height[i] = i; } video_update_scale(kimage_ptr, x_width, x_height, 1); } void show_a2_line_stuff() { int num, num_filt; int i; for(i = 0; i < 200; i++) { printf("line: %d: stat: %07x, " "left_edge:%d, right_edge:%d\n", i, g_a2_filt_stat[i], g_a2_line_left_edge[i], g_a2_line_right_edge[i]); } num = g_video_all_stat_pos; num_filt = g_video_stat_old_pos; printf("cur_a2_stat:%04x, all_stat_pos:%d, num_filt:%d\n", g_cur_a2_stat, num, num_filt); for(i = 0; i < num; i++) { printf("all_stat[%3d]=%08x stat:%08x\n", i, g_video_all_stat[i].lines_since_vbl, g_video_all_stat[i].cur_all_stat); } for(i = 0; i < num_filt; i++) { printf("filt[%3d]=%08x filt_stat:%08x\n", i, g_video_filt_stat_old[i].line_bytes, g_video_filt_stat_old[i].filt_stat); } } int g_flash_count = 0; void video_reset() { int stat; int i; voc_reset(); stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | (0xf << BIT_ALL_STAT_TEXT_COLOR); if(g_use_bw_hires) { stat |= ALL_STAT_COLOR_C021; } if(g_config_control_panel) { /* Don't update cur_a2_stat when in configuration panel */ //g_save_cur_a2_stat = stat; } else { g_cur_a2_stat = stat; } for(i = 0; i < 16; i++) { g_palette_change_cnt[0][i] = 0; g_palette_change_cnt[1][i] = 0; } } word32 g_cycs_in_check_input = 0; void video_update() { int did_video; if(g_fatal_log > 0) { // NOT IMPLEMENTED YET //adb_all_keys_up(); clear_fatal_logs(); } if(g_status_enable != g_status_enable_previous) { g_status_enable_previous = g_status_enable; video_update_status_enable(&g_mainwin_kimage); } debugger_redraw_screen(&g_debugwin_kimage); if(g_config_control_panel) { return; // Nothing else to do } g_screen_redraw_skip_count--; did_video = 0; if(g_screen_redraw_skip_count < 0) { did_video = 1; video_copy_changed2(); video_update_event_line(262); update_border_info(); g_screen_redraw_skip_count = g_screen_redraw_skip_amt; } /* update flash */ g_flash_count++; if(g_flash_count >= 16) { g_flash_count = 0; g_cur_a2_stat ^= ALL_STAT_FLASH_STATE; change_display_mode(g_cur_dfcyc); } if(did_video) { g_vid_update_last_line = 0; g_video_all_stat_pos = 1; g_video_all_stat[0].cur_all_stat = g_cur_a2_stat; g_video_all_stat[0].lines_since_vbl = 0; g_video_save_all_stat_pos = 0; g_video_filt_stat_pos = 0; } } word32 video_all_stat_to_filt_stat(int line, word32 new_all_stat) { word32 filt_stat, merge_mask, mix_t_gr; filt_stat = new_all_stat & ALL_STAT_TEXT; merge_mask = 0; if((new_all_stat & ALL_STAT_ST80) == 0) { merge_mask = ALL_STAT_PAGE2; } mix_t_gr = new_all_stat & ALL_STAT_MIX_T_GR; if(new_all_stat & ALL_STAT_SUPER_HIRES) { filt_stat = ALL_STAT_SUPER_HIRES; merge_mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; } else if(line >= 192) { filt_stat = ALL_STAT_BORDER; } else if(filt_stat || (line >= 160 && mix_t_gr)) { // text mode filt_stat |= ALL_STAT_TEXT; merge_mask |= ALL_STAT_ALTCHARSET | ALL_STAT_BG_COLOR | ALL_STAT_TEXT_COLOR | ALL_STAT_VID80; if((new_all_stat & ALL_STAT_ALTCHARSET) == 0) { merge_mask |= ALL_STAT_FLASH_STATE; } } else { // GR or Hires merge_mask |= ALL_STAT_ANNUNC3 | ALL_STAT_HIRES; if((new_all_stat & ALL_STAT_ANNUNC3) == 0) { // AN3 must be 0 to enable dbl-lores or dbl-hires merge_mask |= ALL_STAT_VID80; } if(new_all_stat & ALL_STAT_HIRES) { merge_mask |= ALL_STAT_COLOR_C021 | ALL_STAT_DIS_COLOR_DHIRES; } } filt_stat = filt_stat | (new_all_stat & merge_mask); return filt_stat; } void change_display_mode(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); video_add_new_all_stat(dfcyc, lines_since_vbl); } void video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl) { word32 my_start, first_start, prev_lines_since_vbl; int pos, prev; pos = g_video_all_stat_pos; my_start = lines_since_vbl & 0x1ff00; first_start = my_start + 24; if(lines_since_vbl >= (200 << 8)) { return; // In VBL, don't log this } if(pos && (lines_since_vbl < first_start)) { prev = pos - 1; prev_lines_since_vbl = g_video_all_stat[prev].lines_since_vbl; // If the previous toggle has the same line, and it is before // offset 24, then ignore it and overwrite it if((my_start <= prev_lines_since_vbl) && (prev_lines_since_vbl < first_start)) { // needless toggling during HBL, just toss earlier pos = prev; } } g_video_all_stat[pos].lines_since_vbl = lines_since_vbl; g_video_all_stat[pos].cur_all_stat = g_cur_a2_stat; if(!g_halt_sim || g_config_control_panel) { dbg_log_info(dfcyc, g_cur_a2_stat, lines_since_vbl, (pos << 16) | 0x102); } pos++; if(pos >= MAX_VIDEO_ALL_STAT) { pos--; } g_video_all_stat_pos = pos; } #define MAX_BORDER_CHANGES 16384 STRUCT(Border_changes) { word32 usec; int val; }; Border_changes g_border_changes[MAX_BORDER_CHANGES]; int g_num_border_changes = 0; void change_border_color(dword64 dfcyc, int val) { int pos; pos = g_num_border_changes; g_border_changes[pos].usec = (word32)((dfcyc - g_last_vbl_dfcyc) >> 16); g_border_changes[pos].val = val; pos++; g_num_border_changes = pos; if(pos >= MAX_BORDER_CHANGES) { halt_printf("num border changes: %d\n", pos); g_num_border_changes = 0; } } void update_border_info() { dword64 drecip_usec, dline; word32 usec; int offset, new_line_offset, last_line_offset, new_line, new_val; int limit, color_now; int i; /* to get this routine to redraw the border, change */ /* g_vbl_border_color, set g_border_last_vbl_changes = 1 */ /* and change the cur_border_colors[] array */ color_now = g_vbl_border_color; drecip_usec = (65536LL * 65536LL) / 65; limit = g_num_border_changes; if(g_border_last_vbl_changes || limit || g_border_reparse) { /* add a dummy entry */ g_border_changes[limit].usec = CYCLES_IN_16MS_RAW + 21; g_border_changes[limit].val = (g_c034_val & 0xf); limit++; } last_line_offset = (((word32)-1L) << 8) + 44; for(i = 0; i < limit; i++) { usec = g_border_changes[i].usec; dline = usec * drecip_usec; new_line = dline >> 32; offset = ((dword64)(word32)dline * 65ULL) >> 32; /* here comes the tricky part */ /* offset is from 0 to 65, where 0-3 is the right border of */ /* the previous line, 4-20 is horiz blanking, 21-24 is the */ /* left border and 25-64 is the main window */ /* Convert this to a new notation which is 0-3 is the left */ /* border, 4-43 is the main window, and 44-47 is the right */ /* basically, add -21 to offset, and wrap < 0 to previous ln */ /* note this makes line -1 offset 44-47 the left hand border */ /* for true line 261 on the screen */ offset -= 21; if(offset < 0) { new_line--; offset += 64; } new_val = g_border_changes[i].val; new_line_offset = (new_line << 8) + offset; if((new_line_offset < -256) || (new_line_offset > (262*256 + 0x80))) { printf("new_line_offset: %05x\n", new_line_offset); new_line_offset = last_line_offset; } while(last_line_offset < new_line_offset) { /* see if this will finish it */ if((last_line_offset & -256)==(new_line_offset & -256)){ update_border_line(last_line_offset, new_line_offset, color_now); last_line_offset = new_line_offset; } else { update_border_line(last_line_offset, (last_line_offset & -256) + 65, color_now); last_line_offset =(last_line_offset & -256)+256; } } color_now = new_val; } #if 0 if(g_num_border_changes) { printf("Border changes: %d\n", g_num_border_changes); } #endif if(limit > 1) { g_border_last_vbl_changes = 1; } else { g_border_last_vbl_changes = 0; } g_num_border_changes = 0; g_border_reparse = 0; g_vbl_border_color = (g_c034_val & 0xf); } void update_border_line(int st_line_offset, int end_line_offset, int color) { word32 filt_stat; int st_offset, end_offset, left, right, width, line; line = st_line_offset >> 8; if(line != (end_line_offset >> 8)) { halt_printf("ubl, %04x %04x %02x!\n", st_line_offset, end_line_offset, color); } if(line < -1 || line >= 262) { halt_printf("ubl-b, mod line is %d\n", line); line = 0; } if(line < 0 || line >= 262) { line = 0; } st_offset = st_line_offset & 0xff; end_offset = end_line_offset & 0xff; if((st_offset == 0) && (end_offset >= 0x41) && !g_border_reparse) { /* might be the same as last time, save some work */ if(g_cur_border_colors[line] == color) { return; } g_cur_border_colors[line] = color; } else { g_cur_border_colors[line] = -1; } /* 0-3: left border, 4-43: main window, 44-47: right border */ /* 48-65: horiz blanking */ /* first, do the sides from line 0 to line 199 */ if((line < 200) || (line >= 262)) { if(line >= 262) { line = 0; } if(st_offset < 4) { /* left side */ left = st_offset; right = MY_MIN(4, end_offset); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, (left * BORDER_WIDTH)/4, (right * BORDER_WIDTH) / 4); g_border_sides_refresh_needed = 1; } if((st_offset < 48) && (end_offset >= 44)) { /* right side */ filt_stat = g_a2_filt_stat[line]; width = BORDER_WIDTH; if((filt_stat & ALL_STAT_SUPER_HIRES) == 0) { width += 80; } left = MY_MAX(0, st_offset - 44); right = MY_MIN(4, end_offset - 44); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, X_A2_WINDOW_WIDTH - width + (left * width/4), X_A2_WINDOW_WIDTH - width + (right * width/4)); g_border_sides_refresh_needed = 1; } } if((line >= 192) && (line < 200)) { filt_stat = g_a2_filt_stat[line]; if((filt_stat & ALL_STAT_BORDER) && (st_offset < 44) && (end_offset > 4)) { left = MY_MAX(0, st_offset - 4); right = MY_MIN(40, end_offset - 4); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, g_video_act_margin_left + (left * 640 / 40), g_video_act_margin_left + (right * 640 / 40)); g_border_line24_refresh_needed = 1; } } /* now do the bottom, lines 200 to 215 */ if((line >= 200) && (line < (200 + BASE_MARGIN_BOTTOM/2)) ) { line -= 200; left = st_offset; right = MY_MIN(48, end_offset); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 200*2 + 2*line, 2, color, (left * X_A2_WINDOW_WIDTH / 48), (right * X_A2_WINDOW_WIDTH / 48)); g_border_special_refresh_needed = 1; } /* and top, lines 236 to 262 */ if((line >= (262 - BASE_MARGIN_TOP/2)) && (line < 262)) { line -= (262 - BASE_MARGIN_TOP/2); left = st_offset; right = MY_MIN(48, end_offset); video_border_pixel_write(&g_mainwin_kimage, 2*line, 2, color, (left * X_A2_WINDOW_WIDTH / 48), (right * X_A2_WINDOW_WIDTH / 48)); g_border_special_refresh_needed = 1; } } void video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, int color, int st_off, int end_off) { word32 *wptr, *wptr0; word32 pixel; int width, width_full, offset; int i, j; if(end_off <= st_off) { return; } width = end_off - st_off; width_full = kimage_ptr->a2_width_full; if(width > width_full) { halt_printf("border write but width %d > act %d\n", width, width_full); return; } if((starty + num_lines) > kimage_ptr->a2_height) { halt_printf("border write line %d, > act %d\n", starty+num_lines, kimage_ptr->a2_height); return; } pixel = g_a2palette_1624[color & 0xf]; offset = starty * width_full; wptr0 = kimage_ptr->wptr; wptr0 += offset; for(i = 0; i < num_lines; i++) { wptr = wptr0 + st_off; for(j = 0; j < width; j++) { *wptr++ = pixel; } wptr0 += width_full; } } word32 video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse) { word32 ch_mask, mask; int shift; if(reparse) { return (word32)-1; } shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; mask = (1 << (40 >> SHIFT_PER_CHANGE)) - 1; ch_mask = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]; if(filt_stat & ALL_STAT_VID80) { mem_ptr += 0x10000; ch_mask |= (g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT]); ch_mask |= (g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]); } ch_mask = (ch_mask >> shift) & mask; return ch_mask; } void video_update_edges(int line, int left, int right, const char *str) { g_a2_line_left_edge[line] = MY_MIN(left, g_a2_line_left_edge[line]); g_a2_line_right_edge[line] = MY_MAX(right, g_a2_line_right_edge[line]); if((left < 0) || (right < 0) || (left > 640) || (right > 640)) { printf("video_update_edges: %s: line %d: %d (left) >= %d " "(right)\n", str, line, left, right); } } void redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { byte str_buf[81]; byte *slow_mem_ptr; word32 ch_mask, line_mask, mem_ptr, val0, val1, bg_pixel, fg_pixel; int flash_state, y, bg_color, fg_color, start_line; int x1, x2; // Redraws a single line, will be called over 8 lines to finish a byte. start_line = line_bytes >> 16; bg_color = (filt_stat >> BIT_ALL_STAT_BG_COLOR) & 0xf; fg_color = (filt_stat >> BIT_ALL_STAT_TEXT_COLOR) & 0xf; bg_pixel = g_a2palette_1624[bg_color]; fg_pixel = g_a2palette_1624[fg_color]; y = start_line >> 3; line_mask = 1 << y; mem_ptr = 0x400 + g_screen_index[y]; if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x400; } if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { halt_printf("redraw_changed_text: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(!ch_mask) { return; } g_a2_screen_buffer_changed |= line_mask; x2 = 0; slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); flash_state = -0x40; if(g_cur_a2_stat & ALL_STAT_FLASH_STATE) { flash_state = 0x40; } for(x1 = 0; x1 < 40; x1++) { val0 = slow_mem_ptr[0x10000]; val1 = *slow_mem_ptr++; if(!(filt_stat & ALL_STAT_ALTCHARSET)) { if((val0 >= 0x40) && (val0 < 0x80)) { val0 += flash_state; } if((val1 >= 0x40) && (val1 < 0x80)) { val1 += flash_state; } } if(filt_stat & ALL_STAT_VID80) { str_buf[x2++] = val0; // aux mem } str_buf[x2++] = val1; // main mem } str_buf[x2] = 0; // null terminate redraw_changed_string(&str_buf[0], line_bytes, ch_mask, in_wptr, bg_pixel, fg_pixel, pixels_per_line, (filt_stat & ALL_STAT_VID80)); } void redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, word32 *in_wptr, word32 bg_pixel, word32 fg_pixel, int pixels_per_line, int dbl) { register word32 start_time, end_time; word32 *wptr; word32 val0, val1, val2, val3, pixel; int left, right, st_line_mod8, offset, pos, shift, start_line; int start_byte, end_byte; int x1, j; left = 40; right = 0; GET_ITIMER(start_time); start_line = line_bytes >> 16; start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; st_line_mod8 = start_line & 7; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + 1, right); offset = (start_line * 2 * pixels_per_line) + x1*14; pos = x1; if(dbl) { pos = pos * 2; } wptr = in_wptr + offset; val0 = bptr[pos]; if(dbl) { pos++; } val1 = bptr[pos++]; val2 = g_a2font_bits[val0][st_line_mod8]; val3 = g_a2font_bits[val1][st_line_mod8]; // val2, [6:0] is 80-column character bits, and // [21:8] are the 40-column char bits (double-wide) if(dbl) { val2 = (val3 << 7) | (val2 & 0x7f); } else { val2 = val3 >> 8; // 40-column format } for(j = 0; j < 14; j++) { pixel = bg_pixel; if(val2 & 1) { // LSB is first pixel pixel = fg_pixel; } wptr[pixels_per_line] = pixel; *wptr++ = pixel; val2 = val2 >> 1; } } GET_ITIMER(end_time); if(start_line < 200) { video_update_edges(start_line, left * 14, right * 14, "text"); } if((left >= right) || (left < 0) || (right < 0)) { printf("str line %d, 40: left >= right: %d >= %d\n", start_line, left, right); printf(" line_bytes:%08x ch_mask:%08x\n", line_bytes, ch_mask); } g_cycs_in_40col += (end_time - start_time); } // gr with an3=0: // 0=0 // 1,0=3 (purple). 1,1=0 // 2,0=c (green). 2,1=0 // 3,0=f (white). 3,1=0 // 4,0=0. 4,1=c (green) // 5,0=3 (purple). 5,1=c (green) // 6,0=c (green). 6,1=c (green) // 7,0=f (white). 7,1=c (green) // 8,0=0 (black). 7,1=3 (purple) // 9,0=3 (purple). 9,1=3 (purple) // a,0=c (green). a,1=3 (purple) // b,0=f (white). b,1=3 (purple) // c,0=0 (black). c,1=f (white) // d,0=3 (purple). d,1=f (white) // e,0=c (green). e,1=f (white) // f,0=f (white). e,1=f (white) void redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { word32 *wptr; byte *slow_mem_ptr; word32 line_mask, mem_ptr, val0, val1, pixel0, pixel1, ch_mask; int y, shift, left, right, st_line_mod8, start_line, offset; int start_byte, end_byte; int x1, i; start_line = line_bytes >> 16; st_line_mod8 = start_line & 7; y = start_line >> 3; line_mask = 1 << (y); mem_ptr = 0x400 + g_screen_index[y]; if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x400; } if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { halt_printf("redraw_changed_gr: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(!ch_mask) { return; } g_a2_screen_buffer_changed |= line_mask; left = 40; right = 0; slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); offset = (start_line * 2 * pixels_per_line); start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + 1, right); wptr = in_wptr + offset + x1*14; val0 = slow_mem_ptr[0x10000 + x1]; val1 = slow_mem_ptr[x1]; if(st_line_mod8 >= 4) { val0 = val0 >> 4; val1 = val1 >> 4; } if(filt_stat & ALL_STAT_VID80) { // aux pixel is { [2:0],[3] } val0 = (val0 << 1) | ((val0 >> 3) & 1); } else if((filt_stat & ALL_STAT_ANNUNC3) == 0) { if(x1 & 1) { // odd cols val0 = ((val1 >> 1) & 2) | ((val1 >> 3) & 1); } else { val0 = val1 & 3; // even cols } // map val0: 0->0, 1->3, 2->c, 3->f val1 = 0; if(val0 & 1) { val1 |= 3; } if(val0 & 2) { val1 |= 0xc; } val0 = val1; } else { val0 = val1; } pixel0 = g_a2palette_1624[val0 & 0xf]; pixel1 = g_a2palette_1624[val1 & 0xf]; for(i = 0; i < 7; i++) { wptr[pixels_per_line] = pixel0; wptr[pixels_per_line + 7] = pixel1; wptr[0] = pixel0; wptr[7] = pixel1; wptr++; } } video_update_edges(start_line, left * 14, right * 14, "gr"); } void video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, int end_byte, int pixels_per_line, word32 filt_stat) { word32 val0, val1, val2, prev_bits, val1_hi, dbl_step, pixel, color; word32 monochrome; int shift; int x2, i; monochrome = filt_stat & (ALL_STAT_COLOR_C021 | ALL_STAT_DIS_COLOR_DHIRES); prev_bits = 0; if(start_byte) { prev_bits = (slow_mem_ptr[-1] >> 3) & 0xf; if(!(filt_stat & ALL_STAT_VID80)) { // prev_bits is 4 bits, widen to 8 for std HGR prev_bits = g_pixels_widened[prev_bits] >> 4; } prev_bits = prev_bits & 0xf; } for(x2 = start_byte; x2 < end_byte; x2++) { val0 = slow_mem_ptr[0x10000]; val1 = *slow_mem_ptr++; val2 = slow_mem_ptr[0x10000]; // next pixel, aux mem if(x2 >= 39) { val2 = 0; } val1_hi = ((val1 >> 5) & 4) | ((x2 & 1) << 1); // Hi-order bit in bit 2, odd pixel is in bit 0 dbl_step = 3; if(filt_stat & ALL_STAT_VID80) { // aux+1[6:0], main[6:0], aux[6:0], prev[3:0] val0 = (val2 << 18) | ((val1 & 0x7f) << 11) | ((val0 & 0x7f) << 4); if(!monochrome && (x2 & 1)) { // Get 6 bits from prev val0 = (val0 << 2); dbl_step = 1; } val0 = val0 | prev_bits; } else if(monochrome) { val0 = g_pixels_widened[val1 & 0x7f] << 4; } else { // color, normal hgr val2 = g_pixels_widened[*slow_mem_ptr & 0x7f]; if(x2 >= 39) { val2 = 0; } val0 = ((val1 & 0x7f) << 4) | prev_bits | (val2 << 11); if((filt_stat & ALL_STAT_ANNUNC3) == 0) { val1_hi = val1_hi & 3; } } #if 0 if(st_line < 8) { printf("hgrl %d c:%d,d:%d, off:%03x val0:%02x 1:%02x\n", st_line, monochrome, dbl, x1 + x2, val0, val1); } #endif for(i = 0; i < 14; i++) { color = 0; // black if(monochrome) { if(val0 & 0x10) { color = 0xf; // white } val0 = val0 >> 1; } else { // color if(filt_stat & ALL_STAT_VID80) { color = g_dhires_convert[val0 & 0xfff]; shift = (x2 + x2 + i) & 3; color = color >> (4 * shift); if((i & 3) == dbl_step) { val0 = val0 >> 4; } } else { val2 = (val0 & 0x38) ^ val1_hi ^(i & 3); color = g_hires_lookup[val2 & 0x7f]; if(i & 1) { val0 = val0 >> 1; } } } pixel = g_a2palette_1624[color & 0xf]; wptr[pixels_per_line] = pixel; *wptr++ = pixel; } if((filt_stat & ALL_STAT_VID80) && ((x2 & 1) == 0)) { prev_bits = val0 & 0x3f; } else { prev_bits = val0 & 0xf; } } } void redraw_changed_hgr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { word32 *wptr; byte *slow_mem_ptr; word32 ch_mask, line_mask, mem_ptr; int y, shift, st_line_mod8, start_line, offset, start_byte; int end_byte; int x1; start_line = line_bytes >> 16; start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; // Usually '40' y = start_line >> 3; st_line_mod8 = start_line & 7; line_mask = 1 << y; mem_ptr = 0x2000 + g_screen_index[y] + (st_line_mod8 * 0x400); if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x2000; } if((mem_ptr < 0x2000) || (mem_ptr >= 0x6000)) { halt_printf("redraw_changed_hgr: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(ch_mask == 0) { return; } // Hires depends on adjacent bits, so also reparse adjacent regions // to handle redrawing of pixels on the boundaries ch_mask = ch_mask | (ch_mask >> 1) | (ch_mask << 1); g_a2_screen_buffer_changed |= line_mask; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); offset = (start_line * 2 * pixels_per_line) + x1*14; wptr = in_wptr + offset; video_hgr_line_segment(slow_mem_ptr, wptr, x1, end_byte, pixels_per_line, filt_stat); video_update_edges(start_line, x1 * 14, end_byte * 14, "hgr"); break; } } int video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, int reparse) { word32 *word_ptr; byte *byte_ptr; word32 ch_mask, mem_ptr, scan, old_scan, val0, val1; int diffs, palette; int j; palette = scan_info & 0xf; mem_ptr = (bank << 16) + 0x9e00 + (palette * 0x20); ch_mask = video_get_ch_mask(mem_ptr, 0, 0); old_scan = g_superhires_scan_save[bank][line]; scan = (scan_info & 0xfaf) + (g_palette_change_cnt[bank][palette] << 12); g_superhires_scan_save[bank][line] = scan; #if 0 if(line == 1) { word_ptr = (word32 *)&(g_slow_memory_ptr[0x19e00+palette*0x20]); printf("y1vrshp, ch:%08x, s:%08x,os:%08x %d = %08x %08x %08x " "%08x %08x %08x %08x %08x\n", ch_mask, scan, old_scan, reparse, word_ptr[0], word_ptr[1], word_ptr[2], word_ptr[3], word_ptr[4], word_ptr[5], word_ptr[6], word_ptr[7]); } #endif diffs = reparse | ((scan ^ old_scan) & 0xf0f); /* we must do full reparse if palette changed for this line */ if(!diffs && (ch_mask == 0) && (((scan ^ old_scan) & (~0xf0)) == 0)) { /* nothing changed, get out fast */ return 0; } if(ch_mask) { /* indicates the palette has changed, and other scan lines */ /* using this palette need to do a full 32-byte compare to */ /* decide if they need to update or not */ g_palette_change_cnt[bank][palette]++; } word_ptr = (word32 *)&(g_slow_memory_ptr[(bank << 16) + 0x9e00 + palette*0x20]); for(j = 0; j < 8; j++) { if(word_ptr[j] != g_saved_line_palettes[bank][line][j]) { diffs = 1; break; } } if(diffs == 0) { return 0; } /* first, save this word_ptr into saved_line_palettes */ byte_ptr = (byte *)word_ptr; for(j = 0; j < 8; j++) { g_saved_line_palettes[bank][line][j] = word_ptr[j]; } byte_ptr = (byte *)word_ptr; /* this palette has changed */ for(j = 0; j < 16; j++) { val0 = *byte_ptr++; val1 = *byte_ptr++; video_update_color_raw(bank, palette*16 + j, (val1<<8) + val0); } return 1; } word32 redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, int pixels_per_line, int y, int scan, word32 ch_mask) { word32 *palptr, *wptr; byte *slow_mem_ptr; word32 mem_ptr, val0, pal, pix0, pix1, pix2, pix3, save_pix; int offset, shift_per, left, right, shift; int x1, x2; mem_ptr = (bank << 16) + 0x2000 + (0xa0 * y); shift_per = (1 << SHIFT_PER_CHANGE); pal = (scan & 0xf); save_pix = 0; if(scan & 0x20) { // Fill mode ch_mask = (word32)-1; } palptr = &(g_palette_8to1624[bank][pal * 16]); left = 160; right = 0; for(x1 = 0; x1 < 0xa0; x1 += shift_per) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + shift_per, right); slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); offset = x1*4; wptr = in_wptr + offset; for(x2 = 0; x2 < shift_per; x2++) { val0 = *slow_mem_ptr++; if(scan & 0x80) { // 640 mode pix0 = (val0 >> 6) & 3; pix1 = (val0 >> 4) & 3; pix2 = (val0 >> 2) & 3; pix3 = val0 & 3; pix0 = palptr[pix0 + 8]; pix1 = palptr[pix1 + 12]; pix2 = palptr[pix2 + 0]; pix3 = palptr[pix3 + 4]; } else { /* 320 mode */ pix0 = (val0 >> 4); pix2 = (val0 & 0xf); if(scan & 0x20) { // Fill mode if(!pix0) { // 0 = repeat last color pix0 = save_pix; } if(!pix2) { pix2 = pix0; } save_pix = pix2; } pix0 = palptr[pix0]; pix1 = pix0; pix2 = palptr[pix2]; pix3 = pix2; } wptr[pixels_per_line] = pix0; *wptr++ = pix0; wptr[pixels_per_line] = pix1; *wptr++ = pix1; wptr[pixels_per_line] = pix2; *wptr++ = pix2; wptr[pixels_per_line] = pix3; *wptr++ = pix3; } } return (left << 16) | (right & 0xffff); } void redraw_changed_super_hires_bank(int bank, int start_line, int reparse, word32 *wptr, int pixels_per_line) { dword64 dval, dval1; word32 this_check, mask, tmp, scan, old_scan, mem_ptr; int left, right, ret, shift; mem_ptr = (bank << 16) + 0x2000 + (160 * start_line); dval1 = g_slow_mem_changed[(mem_ptr >> CHANGE_SHIFT) + 1] | g_slow_mem_ch2[(mem_ptr >> CHANGE_SHIFT) + 1]; dval = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT] | (dval1 << 32); shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; mask = (1 << (160 >> SHIFT_PER_CHANGE)) - 1; this_check = (dval >> shift) & mask; scan = g_slow_memory_ptr[(bank << 16) + 0x9d00 + start_line]; old_scan = g_superhires_scan_save[bank][start_line]; ret = video_rebuild_super_hires_palette(bank, scan, start_line, reparse); if(ret || reparse || ((scan ^ old_scan) & 0xa0)) { /* 0x80 == mode640, 0x20 = fill */ this_check = (word32)-1; } if(!this_check) { return; // Nothing to do, get out } if(scan & 0x80) { // 640 mode g_num_lines_superhires640++; } if((scan >> 5) & 1) { // fill mode--redraw whole line this_check = (word32)-1; } g_a2_screen_buffer_changed |= (1 << (start_line >> 3)); tmp = redraw_changed_super_hires_oneline(bank, wptr, pixels_per_line, start_line, scan, this_check); left = tmp >> 16; right = tmp & 0xffff; video_update_edges(start_line, left * 4, right * 4, "shr"); } void redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, int pixels_per_line, word32 filt_stat) { int bank, start_line; start_line = line_bytes >> 16; wptr += start_line * 2 * pixels_per_line; if(filt_stat & ALL_STAT_VOC_INTERLACE) { // Do 400 interlaced lines. Do aux first, then main mem redraw_changed_super_hires_bank(1, start_line, reparse, wptr, 0); redraw_changed_super_hires_bank(0, start_line, reparse, wptr + pixels_per_line, 0); } else { bank = 1; if(filt_stat & ALL_STAT_VOC_MAIN) { bank = 0; // VOC SHR in main memory } redraw_changed_super_hires_bank(bank, start_line, reparse, wptr, pixels_per_line); } } void video_copy_changed2() { word32 *ch_ptr, *ch2_ptr; int bank1_off; int i; // Copy entries from g_slow_mem_changed[] to g_slow_mem_ch2[] and // clear g_slow_mem_changed[] ch_ptr = &g_slow_mem_changed[0]; ch2_ptr = &g_slow_mem_ch2[0]; bank1_off = 0x10000 >> CHANGE_SHIFT; for(i = 4; i < 0xa0; i++) { // Pages 0x0400 through 0x9fff ch2_ptr[i] = ch_ptr[i]; ch2_ptr[i + bank1_off] = ch_ptr[i + bank1_off]; ch_ptr[i] = 0; ch_ptr[i + bank1_off] = 0; } } void video_update_event_line(int line) { int new_line; video_update_through_line(line); new_line = line + g_line_ref_amt; if(new_line < 200) { if(!g_config_control_panel && !g_halt_sim) { add_event_vid_upd(new_line); } } else if(line >= 262) { if(!g_config_control_panel && !g_halt_sim) { add_event_vid_upd(0); /* add event for new screen */ } } } void video_force_reparse() { word32 *wptr; int height, width_full; int i, j; g_video_stat_old_pos = 1; g_video_filt_stat_old[0].filt_stat = (word32)-1; height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; height = MY_MIN(height, g_mainwin_kimage.a2_height); width_full = g_mainwin_kimage.a2_width_full; wptr = g_mainwin_kimage.wptr; for(i = 0; i < height; i++) { for(j = 0; j < width_full; j++) { *wptr++ = 0; } } g_border_reparse = 1; } void video_update_through_line(int line) { register word32 start_time; register word32 end_time; word32 my_start_lines, my_end_lines, prev_all_stat, next_all_stat; word32 prev_lines_since_vbl, next_lines_since_vbl; int last_line, pos, last_pos, end, num; int i; #if 0 vid_printf("\nvideo_upd for line %d, lines: %06x\n", line, get_lines_since_vbl(g_cur_dfcyc)); #endif GET_ITIMER(start_time); last_line = MY_MIN(200, line+1); /* go through line, but not past 200 */ pos = g_video_save_all_stat_pos; last_pos = g_video_all_stat_pos; prev_all_stat = g_video_all_stat[pos].cur_all_stat; prev_lines_since_vbl = g_video_all_stat[pos].lines_since_vbl; g_video_all_stat[last_pos].cur_all_stat = g_cur_a2_stat; g_video_all_stat[last_pos].lines_since_vbl = (line + 1) << 8; next_all_stat = g_video_all_stat[pos+1].cur_all_stat; next_lines_since_vbl = g_video_all_stat[pos+1].lines_since_vbl; for(i = g_vid_update_last_line; i < last_line; i++) { // We need to step through pos in g_video_all_stat[] and find // the start/end pairs for each line g_a2_line_left_edge[i] = 640; g_a2_line_right_edge[i] = 0; my_start_lines = (i << 8) + 25; my_end_lines = (i << 8) + 65; if(prev_lines_since_vbl > my_start_lines) { printf("prev:%08x > %08x start at i:%d\n", prev_lines_since_vbl, my_start_lines, i); } while(my_start_lines < my_end_lines) { while(next_lines_since_vbl <= my_start_lines) { // Step into next entry prev_all_stat = next_all_stat; prev_lines_since_vbl = next_lines_since_vbl; pos++; g_video_save_all_stat_pos = pos; next_all_stat = g_video_all_stat[pos+1].cur_all_stat; next_lines_since_vbl = g_video_all_stat[pos+1].lines_since_vbl; if(pos >= last_pos) { printf("FELL OFF %d %d!\n", pos, last_pos); pos--; break; } } end = 65; if(next_lines_since_vbl < my_end_lines) { end = (next_lines_since_vbl & 0xff); if(end < 25) { printf("i:%d next_lines_since_vbl:" "%08x!\n", i, next_lines_since_vbl); end = 25; } } video_do_partial_line(my_start_lines, end, prev_all_stat); my_start_lines = (i << 8) + end; } } g_vid_update_last_line = last_line; g_video_save_all_stat_pos = pos; /* deal with border and forming rects for xdriver.c to use */ if(line >= 262) { if(g_num_lines_prev_superhires != g_num_lines_superhires) { /* switched in/out from superhires--refresh borders */ g_border_sides_refresh_needed = 1; } video_form_change_rects(); g_num_lines_prev_superhires = g_num_lines_superhires; g_num_lines_prev_superhires640 = g_num_lines_superhires640; g_num_lines_superhires = 0; g_num_lines_superhires640 = 0; num = g_video_filt_stat_pos; g_video_stat_old_pos = num; for(i = 0; i < num; i++) { g_video_filt_stat_old[i] = g_video_filt_stat[i]; } g_video_filt_stat_pos = 0; } GET_ITIMER(end_time); g_cycs_in_refresh_line += (end_time - start_time); } extern word32 g_vbl_count; void video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat) { word32 filt_stat, old_filt_stat, line_bytes, old_line_bytes; int pos, old_pos, reparse, line; pos = g_video_filt_stat_pos; old_pos = g_video_stat_old_pos; filt_stat = video_all_stat_to_filt_stat(lines_since_vbl >> 8, cur_all_stat); line_bytes = ((lines_since_vbl & 0x1ff00) << 8) | ((end - 25) << 8) | ((lines_since_vbl - 25) & 0x3f); g_video_filt_stat[pos].line_bytes = line_bytes; g_video_filt_stat[pos].filt_stat = filt_stat; reparse = 1; old_filt_stat = (word32)-1; old_line_bytes = (word32)-1; if(pos < old_pos) { old_filt_stat = g_video_filt_stat_old[pos].filt_stat; old_line_bytes = g_video_filt_stat_old[pos].line_bytes; } if((old_filt_stat == filt_stat) && (line_bytes == old_line_bytes)) { reparse = 0; } else if((old_filt_stat ^ filt_stat) & ALL_STAT_SUPER_HIRES) { g_border_reparse = 1; } video_refresh_line(line_bytes, reparse, filt_stat); line = lines_since_vbl >> 8; if(line < 200) { g_a2_filt_stat[line] = filt_stat; } else { printf("partial_line %08x %d %08x out of range!\n", lines_since_vbl, end, cur_all_stat); } if((end <= 25) || (end < (int)(lines_since_vbl & 0xff))) { printf("Bad lsv:%08x, end:%d, stat:%08x\n", lines_since_vbl, end, filt_stat); } pos++; if(pos >= MAX_VIDEO_FILT_STAT) { pos--; } g_video_filt_stat_pos = pos; } void video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat) { word32 *wptr; int pixels_per_line, offset, line; line = line_bytes >> 16; if((word32)line >= 200) { printf("video_refresh %08x %d %08x!\n", line_bytes, must_reparse, filt_stat); return; } wptr = g_mainwin_kimage.wptr; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top) + g_video_act_margin_left; wptr = wptr + offset; if(filt_stat & ALL_STAT_SUPER_HIRES) { g_num_lines_superhires++; redraw_changed_super_hires(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else if(filt_stat & ALL_STAT_BORDER) { if(line < 192) { halt_printf("Border line not 192: %d\n", line); } g_a2_line_left_edge[line] = 0; g_a2_line_right_edge[line] = 560; if(g_border_line24_refresh_needed) { g_border_line24_refresh_needed = 0; g_a2_screen_buffer_changed |= (1 << 24); } } else if(filt_stat & ALL_STAT_TEXT) { redraw_changed_text(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else if(filt_stat & ALL_STAT_HIRES) { redraw_changed_hgr(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else { redraw_changed_gr(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } } void prepare_a2_font() { word32 val0, val1, val2; int i, j, k; // Prepare g_a2font_bits[char][line] where each entry indicates the // set pixels in this line of the character. Bits 6:0 are an // 80-column character, and bits 21:8 are the 14 expanded bits of a // 40-columns character, both with the first visible bit at the // rightmost bit address. But g_font_array[] is in big-endian bit // order, which is less useful for(i = 0; i < 256; i++) { for(j = 0; j < 8; j++) { val0 = g_font_array[i][j] >> 1; val1 = 0; // 80-column bits val2 = 0; // 40-column bits (doubled) for(k = 0; k < 7; k++) { val1 = val1 << 1; val2 = val2 << 2; if(val0 & 1) { val1 |= 1; val2 |= 3; } val0 = val0 >> 1; } g_a2font_bits[i][j] = (val2 << 8) | val1; } } } void prepare_a2_romx_font(byte *font_ptr) { word32 val0, val1, val2; int i, j, k; // ROMX file for(i = 0; i < 256; i++) { for(j = 0; j < 8; j++) { val0 = font_ptr[i*8 + j]; val1 = 0; // 80-column bits val2 = 0; // 40-column bits (doubled) for(k = 0; k < 7; k++) { val1 = val1 << 1; val2 = val2 << 2; if((val0 & 0x40) == 0) { val1 |= 1; val2 |= 3; } val0 = val0 << 1; } g_a2font_bits[i][j] = (val2 << 8) | val1; } } } void video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height) { int pos; pos = kimage_ptr->num_change_rects++; if(pos >= MAX_CHANGE_RECTS) { return; // This will be handled later } kimage_ptr->change_rect[pos].x = x; kimage_ptr->change_rect[pos].y = y; kimage_ptr->change_rect[pos].width = width; kimage_ptr->change_rect[pos].height = height; g_video_pixel_dcount += (width * height); #if 0 printf("Add rect %d, x:%d y:%d, w:%d h:%d\n", pos, x, y, width, height); #endif } void video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix) { int srcy; if((left_pix >= right_pix) || (left_pix < 0) || (right_pix <= 0)) { halt_printf("video_push_lines: lines %d to %d, pix %d to %d\n", start_line, end_line, left_pix, right_pix); printf("a2_screen_buf_ch:%08x, g_full_refr:%08x\n", g_a2_screen_buffer_changed, g_full_refresh_needed); return; } srcy = 2*start_line; video_add_rect(&g_mainwin_kimage, g_video_act_margin_left + left_pix, g_video_act_margin_top + srcy, (right_pix - left_pix), 2*(end_line - start_line)); } void video_form_change_rects() { Kimage *kimage_ptr; register word32 start_time; register word32 end_time; dword64 save_pixel_dcount; word32 mask; int start, line, left_pix, right_pix, left, right, line_div8; int x, y, width, height; kimage_ptr = &g_mainwin_kimage; if(g_border_sides_refresh_needed) { g_border_sides_refresh_needed = 0; // Add left side border video_add_rect(kimage_ptr, 0, g_video_act_margin_top, BORDER_WIDTH, A2_WINDOW_HEIGHT); // Add right-side border. Resend x from 560 through // X_A2_WINDOW_WIDTH x = g_video_act_margin_left + 560; width = X_A2_WINDOW_WIDTH - x; video_add_rect(kimage_ptr, x, g_video_act_margin_top, width, A2_WINDOW_HEIGHT); } if(g_border_special_refresh_needed) { g_border_special_refresh_needed = 0; // Do top border width = g_video_act_width; height = g_video_act_margin_top; video_add_rect(kimage_ptr, 0, 0, width, height); // Do bottom border height = g_video_act_margin_bottom; y = g_video_act_margin_top + A2_WINDOW_HEIGHT; video_add_rect(kimage_ptr, 0, y, width, height); } if(g_status_refresh_needed) { g_status_refresh_needed = 0; width = g_mainwin_kimage.a2_width; y = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; height = kimage_ptr->a2_height - y; if(height > 0) { save_pixel_dcount = g_video_pixel_dcount; video_add_rect(kimage_ptr, 0, y, width, height); g_video_pixel_dcount = save_pixel_dcount; } } if(g_a2_screen_buffer_changed == 0) { return; } GET_ITIMER(start_time); start = -1; left_pix = 640; right_pix = 0; for(line = 0; line < 200; line++) { line_div8 = line >> 3; mask = 1 << (line_div8); if((g_full_refresh_needed & mask) != 0) { left = 0; right = 640; } else { left = g_a2_line_left_edge[line]; right = g_a2_line_right_edge[line]; } if(!(g_a2_screen_buffer_changed & mask) || (left >= right)) { /* No need to update this line */ /* Refresh previous chunks of lines, if any */ if(start >= 0) { video_add_a2_rect(start, line, left_pix, right_pix); start = -1; left_pix = 640; right_pix = 0; } } else { /* Need to update this line */ if(start < 0) { start = line; } left_pix = MY_MIN(left, left_pix); right_pix = MY_MAX(right, right_pix); } } if(start >= 0) { video_add_a2_rect(start, 200, left_pix, right_pix); } g_a2_screen_buffer_changed = 0; g_full_refresh_needed = 0; GET_ITIMER(end_time); g_cycs_in_xredraw += (end_time - start_time); } int video_get_a2_width(Kimage *kimage_ptr) { return kimage_ptr->a2_width; } int video_get_x_width(Kimage *kimage_ptr) { return kimage_ptr->x_width; } int video_get_a2_height(Kimage *kimage_ptr) { return kimage_ptr->a2_height; } int video_get_x_height(Kimage *kimage_ptr) { return kimage_ptr->x_height; } int video_get_x_xpos(Kimage *kimage_ptr) { return kimage_ptr->x_xpos; } int video_get_x_ypos(Kimage *kimage_ptr) { return kimage_ptr->x_ypos; } void video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos) { x_xpos = video_clamp(x_xpos, 0, kimage_ptr->x_max_width - 640); x_ypos = video_clamp(x_ypos, 0, kimage_ptr->x_max_height - 420); kimage_ptr->x_xpos = x_xpos; kimage_ptr->x_ypos = x_ypos; if(kimage_ptr == &g_mainwin_kimage) { g_mainwin_xpos = x_xpos; g_mainwin_ypos = x_ypos; // printf("Set g_mainwin_xpos:%d, ypos:%d\n", x_xpos, x_ypos); } } int video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height) { // Return 1 if the passed in height, width do not match the kimage // aspect-corrected version, and at least 2 VBL periods have passed if((kimage_ptr->vbl_of_last_resize + 6) > g_vbl_count) { return 0; } if((kimage_ptr->x_height != x_height) || (kimage_ptr->x_width != x_width)) { #if 0 printf("change_aspect_needed, vbl:%d kimage width:%d height:%d " "but x width:%d height:%d\n", g_vbl_count, kimage_ptr->x_width, kimage_ptr->x_height, x_width, x_height); #endif return 1; } return 0; } void video_update_status_enable(Kimage *kimage_ptr) { int height, a2_height; height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; a2_height = height; if(g_status_enable) { a2_height = kimage_ptr->a2_height_full; } kimage_ptr->a2_height = a2_height; height = (a2_height * kimage_ptr->scale_width_a2_to_x) >> 16; if(height > kimage_ptr->x_max_height) { height = kimage_ptr->x_max_height; } kimage_ptr->x_height = height; #if 0 printf("new a2_height:%d, x_height:%d\n", kimage_ptr->a2_height, kimage_ptr->x_height); #endif //printf("Calling video_update_scale from video_update_status_en\n"); video_update_scale(kimage_ptr, kimage_ptr->x_width, height, 0); } // video_out_query: return 0 if no screen drawing at all is needed. // returns 1 or the number of change_rects if any drawing is needed int video_out_query(Kimage *kimage_ptr) { int num_change_rects, x_refresh_needed; num_change_rects = kimage_ptr->num_change_rects; x_refresh_needed = kimage_ptr->x_refresh_needed; if(x_refresh_needed) { return 1; } return num_change_rects; } // video_out_done: used by specialize xdriver platform code which needs to // clear the num_change_rects=0. void video_out_done(Kimage *kimage_ptr) { kimage_ptr->num_change_rects = 0; kimage_ptr->x_refresh_needed = 0; } // Called by xdriver.c to copy KEGS's kimage data to the vptr buffer int video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr, int pos) { word32 *out_wptr, *wptr; int a2_width, a2_width_full, width, a2_height, height, x, eff_y; int x_width, x_height, num_change_rects, x_refresh_needed; int i, j; // Copy from kimage_ptr->wptr to vptr num_change_rects = kimage_ptr->num_change_rects; x_refresh_needed = kimage_ptr->x_refresh_needed; if(((pos >= num_change_rects) || (pos >= MAX_CHANGE_RECTS)) && !x_refresh_needed) { kimage_ptr->num_change_rects = 0; return 0; } a2_width = kimage_ptr->a2_width; a2_width_full = kimage_ptr->a2_width_full; a2_height = kimage_ptr->a2_height; if((num_change_rects >= MAX_CHANGE_RECTS) || x_refresh_needed) { // Table overflow, just copy everything in one go kimage_ptr->x_refresh_needed = 0; if(pos >= 1) { kimage_ptr->num_change_rects = 0; return 0; // No more to do } // Force full update rectptr->x = 0; rectptr->y = 0; rectptr->width = a2_width; rectptr->height = a2_height; } else { *rectptr = kimage_ptr->change_rect[pos]; // Struct copy } #if 0 printf("video_out_data, %p rectptr:%p, pos:%d, x:%d y:%d w:%d h:%d, " "wptr:%p\n", vptr, rectptr, pos, rectptr->x, rectptr->y, rectptr->width, rectptr->height, kimage_ptr->wptr); #endif width = rectptr->width; height = rectptr->height; x = rectptr->x; x_width = kimage_ptr->x_width; x_height = kimage_ptr->x_height; if(!g_video_no_scale_window && ((a2_width != x_width) || (a2_height != x_height))) { #if 0 printf("a2_width:%d, x_width:%d, a2_height:%d, x_height:" "%d\n", a2_width, x_width, a2_height, x_height); #endif return video_out_data_scaled(vptr, kimage_ptr, out_width_act, rectptr); } else { out_wptr = (word32 *)vptr; for(i = 0; i < height; i++) { eff_y = rectptr->y + i; wptr = kimage_ptr->wptr + (eff_y * a2_width_full) + x; out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + x; for(j = 0; j < width; j++) { *out_wptr++ = *wptr++; } } } return 1; } int video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr) { word32 *out_wptr, *wptr; word32 pos_scale, alpha_mask; int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; int i, j; // Faster scaling routine which does simple pixel replication rather // than blending. Intended for scales >= 3.0 (or so) since at // these scales, replication looks fine. x = rectptr->x; y = rectptr->y; max_x = rectptr->width + x; max_y = rectptr->height + y; max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); x = MY_MAX(0, x - 1); y = MY_MAX(0, y - 1); a2_width_full = kimage_ptr->a2_width_full; out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; out_max_x = MY_MIN(out_max_x, out_width_act); out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); out_width = out_max_x - out_x; out_height = out_max_y - out_y; out_wptr = (word32 *)vptr; rectptr->x = out_x; rectptr->y = out_y; rectptr->width = out_width; rectptr->height = out_height; alpha_mask = g_alpha_mask; for(i = 0; i < out_height; i++) { eff_y = out_y + i; pos_scale = kimage_ptr->scale_height[eff_y]; src_y = pos_scale >> 16; wptr = kimage_ptr->wptr + (src_y * a2_width_full); out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; for(j = 0; j < out_width; j++) { new_x = j + out_x; pos_scale = kimage_ptr->scale_width[new_x]; pos = pos_scale >> 16; *out_wptr++ = wptr[pos] | alpha_mask; } } rectptr->width = kimage_ptr->x_width - rectptr->x; return 1; } int video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr) { word32 *out_wptr, *wptr; dword64 dval0a, dval0b, dval1a, dval1b, dscale, dscale_y, dval; word32 new_val, pos_scale, alpha_mask; int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; int i, j; if((kimage_ptr->scale_width_a2_to_x >= 0x34000) || (kimage_ptr->scale_height_a2_to_x >= 0x34000)) { return video_out_data_intscaled(vptr, kimage_ptr, out_width_act, rectptr); } x = rectptr->x; y = rectptr->y; max_x = rectptr->width + x; max_y = rectptr->height + y; max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); x = MY_MAX(0, x - 1); y = MY_MAX(0, y - 1); a2_width_full = kimage_ptr->a2_width_full; out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; out_max_x = MY_MIN(out_max_x, out_width_act); out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); out_width = out_max_x - out_x; out_height = out_max_y - out_y; #if 0 printf("scaled: in %d,%d %d,%d becomes %d,%d %d,%d\n", x, y, width, height, out_x, out_y, out_width, out_height); #endif out_wptr = (word32 *)vptr; rectptr->x = out_x; rectptr->y = out_y; rectptr->width = out_width; rectptr->height = out_height; alpha_mask = g_alpha_mask; for(i = 0; i < out_height; i++) { eff_y = out_y + i; pos_scale = kimage_ptr->scale_height[eff_y]; src_y = pos_scale >> 16; dscale_y = (pos_scale & 0xffff) >> 8; wptr = kimage_ptr->wptr + (src_y * a2_width_full); out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; for(j = 0; j < out_width; j++) { new_x = j + out_x; pos_scale = kimage_ptr->scale_width[new_x]; pos = pos_scale >> 16; dscale = (pos_scale & 0xffff) >> 8; dval0a = wptr[pos]; dval0a = (dval0a & 0x00ff00ffULL) | ((dval0a & 0xff00ff00ULL) << 24); dval0b = wptr[pos + 1]; dval0b = (dval0b & 0x00ff00ffULL) | ((dval0b & 0xff00ff00ULL) << 24); dval1a = wptr[pos + a2_width_full]; dval1a = (dval1a & 0x00ff00ffULL) | ((dval1a & 0xff00ff00ULL) << 24); dval1b = wptr[pos + 1 + a2_width_full]; dval1b = (dval1b & 0x00ff00ffULL) | ((dval1b & 0xff00ff00ULL) << 24); dval0a = ((0x100 - dscale) * dval0a) + (dscale * dval0b); dval1a = ((0x100 - dscale) * dval1a) + (dscale * dval1b); dval0a = (dval0a >> 8) & 0x00ff00ff00ff00ffULL; dval1a = (dval1a >> 8) & 0x00ff00ff00ff00ffULL; dval = ((0x100 - dscale_y) * dval0a) + (dscale_y * dval1a); new_val = ((dval >> 8) & 0x00ff00ffULL) | ((dval >> 32) & 0xff00ff00ULL); *out_wptr++ = new_val | alpha_mask; #if 0 if((pos == 300) && (eff_y == 100)) { printf("x:%d pos:%d %08x. %016llx,%016llx " "pos_sc:%08x, %08x\n", new_x, pos, new_val, dval0a, dval0b, pos_scale, wptr[pos]); } #endif } } rectptr->width = kimage_ptr->x_width - rectptr->x; #if 0 for(i = 0; i < kimage_ptr->x_height; i++) { out_wptr = ((word32 *)vptr) + (i * out_width_act) + kimage_ptr->x_width - 1; *out_wptr = 0x00ff00ff; # if 0 for(j = 0; j < 10; j++) { if(*out_wptr != 0) { printf("out_wptr:%p is %08x at %d,%d\n", out_wptr, *out_wptr, out_width_act - 1 - j, i); } out_wptr--; } # endif } #endif return 1; } word32 video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv) { word32 frac, frac_to_next, new_frac; frac = pos * frac_inc; if(frac >= max) { return max; // Clear frac bits } if(g_video_scale_algorithm == 2) { return frac & -65536; // nearest neighbor } if(g_video_scale_algorithm == 1) { return frac; // bilinear interp } // Do proper scaling. fraction=0 means 100% this pixel, fraction=ffff // means 99.99% the next pixel frac_to_next = frac_inc + (frac & 0xffff); if(frac_to_next < 65536) { frac_to_next = 0; } frac_to_next = (frac_to_next & 0xffff) * frac_inc_inv; frac_to_next = frac_to_next >> 16; new_frac = (frac & -65536) | (frac_to_next & 0xffff); #if 0 if((frac >= (30 << 16)) && (frac < (38 << 16))) { printf("scale %d (%02x) -> %08x (was %08x) %08x %08x\n", pos, pos, new_frac, frac, frac_inc, frac_inc_inv); } #endif return new_frac; } void video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, int must_update) { word32 frac_inc, frac_inc_inv, new_frac, max; int a2_width, a2_height, exp_width, exp_height; int i; out_width = video_clamp(out_width, 1, kimage_ptr->x_max_width); out_width = video_clamp(out_width, 1, MAX_SCALE_SIZE); out_height = video_clamp(out_height, 1, kimage_ptr->x_max_height); out_height = video_clamp(out_height, 1, MAX_SCALE_SIZE); a2_width = kimage_ptr->a2_width; a2_height = kimage_ptr->a2_height; kimage_ptr->vbl_of_last_resize = g_vbl_count; // Handle aspect ratio. Calculate height/width based on the other's // aspect ratio, and pick the smaller value exp_width = (a2_width * out_height) / a2_height; exp_height = (a2_height * out_width) / a2_width; if(exp_width < a2_width) { exp_width = a2_width; } if(exp_height < a2_height) { exp_height = a2_height; } if(exp_width < out_width) { // Allow off-by-one to be OK, so window doesn't keep resizing if((exp_width + 1) != out_width) { out_width = exp_width; } } if(exp_height < out_height) { if((exp_height + 1) != out_height) { out_height = exp_height; } } if(out_width <= 0) { out_width = 1; } if(out_height <= 0) { out_height = 1; } // See if anything changed. If it's unchanged, don't do anything if((kimage_ptr->x_width == out_width) && !must_update && (kimage_ptr->x_height == out_height)) { return; } kimage_ptr->x_width = out_width; kimage_ptr->x_height = out_height; kimage_ptr->x_refresh_needed = 1; if(kimage_ptr == &g_mainwin_kimage) { g_mainwin_width = out_width; g_mainwin_height = out_height; //printf("Set g_mainwin_width=%d, g_mainwin_height=%d\n", // out_width, out_height); } // the per-pixel inc = a2_width / out_width. Scale by 65536 frac_inc = (a2_width * 65536UL) / out_width; kimage_ptr->scale_width_to_a2 = frac_inc; frac_inc_inv = (out_width * 65536UL) / a2_width; kimage_ptr->scale_width_a2_to_x = frac_inc_inv; #if 0 printf("scale_width_to_a2: %08x, a2_to_x:%08x, is_debugwin:%d\n", kimage_ptr->scale_width_to_a2, kimage_ptr->scale_width_a2_to_x, (kimage_ptr == &g_debugwin_kimage)); #endif max = (a2_width - 1) << 16; for(i = 0; i < out_width + 1; i++) { new_frac = video_scale_calc_frac(i, max, frac_inc, frac_inc_inv); kimage_ptr->scale_width[i] = new_frac; } frac_inc = (a2_height * 65536UL) / out_height; kimage_ptr->scale_height_to_a2 = frac_inc; frac_inc_inv = (out_height * 65536UL) / a2_height; kimage_ptr->scale_height_a2_to_x = frac_inc_inv; #if 0 printf("scale_height_to_a2: %08x, a2_to_x:%08x. w:%d h:%d\n", kimage_ptr->scale_height_to_a2, kimage_ptr->scale_height_a2_to_x, out_width, out_height); #endif max = (a2_height - 1) << 16; for(i = 0; i < out_height + 1; i++) { new_frac = video_scale_calc_frac(i, max, frac_inc, frac_inc_inv); kimage_ptr->scale_height[i] = new_frac; } } int video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width) { int x; // raw_x is in output coordinates. Scale down to a2 coordinates if(x_width == 0) { x = (kimage_ptr->scale_width_to_a2 * raw_x) / 65536; } else { // Scale raw_x using x_width x = (raw_x * kimage_ptr->a2_width_full) / x_width; } x = x - BASE_MARGIN_LEFT; return x; } int video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height) { int y; // raw_y is in output coordinates. Scale down to a2 coordinates if(y_height == 0) { y = (kimage_ptr->scale_height_to_a2 * raw_y) / 65536; } else { // Scale raw_y using y_height y = (raw_y * kimage_ptr->a2_height) / y_height; } y = y - BASE_MARGIN_TOP; return y; } int video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width) { int x; // Convert a2_x to output coordinates x = a2_x + BASE_MARGIN_LEFT; if(x_width == 0) { x = (kimage_ptr->scale_width_a2_to_x * x) / 65536; } else { // Scale a2_x using x_width x = (x * x_width) / kimage_ptr->a2_width_full; } return x; } int video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height) { int y; // Convert a2_y to output coordinates y = a2_y + BASE_MARGIN_TOP; if(y_height == 0) { y = (kimage_ptr->scale_height_a2_to_x * y) / 65536; } else { // Scale a2_y using y_height y = (y * y_height) / kimage_ptr->a2_height; } return y; } void video_update_color_raw(int bank, int col_num, int a2_color) { word32 tmp; int red, green, blue, newred, newgreen, newblue; if(col_num >= 256 || col_num < 0) { halt_printf("video_update_color_raw: col: %03x\n", col_num); return; } red = (a2_color >> 8) & 0xf; green = (a2_color >> 4) & 0xf; blue = (a2_color) & 0xf; red = ((red << 4) + red); green = ((green << 4) + green); blue = ((blue << 4) + blue); newred = red >> g_red_right_shift; newgreen = green >> g_green_right_shift; newblue = blue >> g_blue_right_shift; tmp = ((newred & g_red_mask) << g_red_left_shift) + ((newgreen & g_green_mask) << g_green_left_shift) + ((newblue & g_blue_mask) << g_blue_left_shift); g_palette_8to1624[bank][col_num] = tmp; } void video_update_status_line(int line, const char *string) { byte a2_str_buf[STATUS_LINE_LENGTH+1]; word32 *wptr; char *buf; const char *ptr; word32 line_bytes; int start_line, c, pixels_per_line, offset; int i; if(line >= MAX_STATUS_LINES || line < 0) { printf("update_status_line: line: %d!\n", line); exit(1); } ptr = string; buf = &(g_status_buf[line][0]); g_status_ptrs[line] = buf; for(i = 0; i < STATUS_LINE_LENGTH; i++) { if(*ptr) { c = *ptr++; } else { c = ' '; } buf[i] = c; a2_str_buf[i] = c | 0x80; } buf[STATUS_LINE_LENGTH] = 0; a2_str_buf[STATUS_LINE_LENGTH] = 0; start_line = (200 + 2*8) + line*8; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top); wptr = g_mainwin_kimage.wptr; wptr += offset; for(i = 0; i < 8; i++) { line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; redraw_changed_string(&(a2_str_buf[0]), line_bytes, -1L, wptr, 0, 0x00ffffff, pixels_per_line, 1); } // Don't add rectangle here, video_form_change_rects will do it //video_add_a2_rect(start_line, start_line + 8, 0, 640); } void video_draw_a2_string(int line, const byte *bptr) { word32 *wptr; word32 line_bytes; int start_line, pixels_per_line, offset; int i; start_line = line*8; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top) + g_video_act_margin_left; wptr = g_mainwin_kimage.wptr; wptr += offset; for(i = 0; i < 8; i++) { line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; redraw_changed_string(bptr, line_bytes, -1L, wptr, 0, 0x00ffffff, pixels_per_line, 1); } g_mainwin_kimage.x_refresh_needed = 1; } void video_show_debug_info() { word32 tmp1; printf("g_cur_dfcyc: %016llx, last_vbl: %016llx\n", g_cur_dfcyc, g_last_vbl_dfcyc); tmp1 = get_lines_since_vbl(g_cur_dfcyc); printf("lines since vbl: %06x\n", tmp1); printf("Last line updated: %d\n", g_vid_update_last_line); } word32 read_video_data(dword64 dfcyc) { word32 val, val2; int lines_since_vbl, line; // Return Charrom data at $C02C for SuperConvert 4 TDM mode lines_since_vbl = get_lines_since_vbl(dfcyc); val = float_bus_lines(dfcyc, lines_since_vbl); line = lines_since_vbl >> 8; if(line < 192) { // Always do the character ROM val2 = g_a2font_bits[val & 0xff][line & 7]; dbg_log_info(dfcyc, val, (lines_since_vbl << 8) | (val2 & 0xff), 0xc02c); val = ~val2; // Invert it, maybe } return val & 0xff; } word32 float_bus(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); return float_bus_lines(dfcyc, lines_since_vbl); } word32 float_bus_lines(dword64 dfcyc, word32 lines_since_vbl) { word32 val; int line, eff_line, line24, all_stat, byte_offset; int hires, page2, addr; /* For floating bus, model hires style: Visible lines 0-191 are simply the */ /* data being displayed at that time. Lines 192-255 are lines 0 - 63 again */ /* and lines 256-261 are lines 58-63 again */ /* For each line, figure out starting byte at -25 mod 128 bytes from this */ /* line's start */ /* This emulates an Apple II style floating bus. A real IIgs does not */ /* drive anything meaningful during the 25 horizontal blanking cycles, */ /* nor during veritical blanking. The data seems to be 0 or related to */ /* the instruction fetches on a real IIgs during blankings */ line = lines_since_vbl >> 8; byte_offset = lines_since_vbl & 0xff; // byte offset is from 0 through 64, where the visible screen is drawn // from 25 through 64 eff_line = line; if(eff_line >= 0x100) { eff_line = (eff_line - 6) & 0xff; } if(byte_offset == 0) { byte_offset = 1; } all_stat = g_cur_a2_stat; hires = (all_stat & ALL_STAT_HIRES) && !(all_stat & ALL_STAT_TEXT); if((all_stat & ALL_STAT_MIX_T_GR) && (line >= 160)) { hires = 0; } page2 = EXTRU(all_stat, 31 - BIT_ALL_STAT_PAGE2, 1); if(all_stat & ALL_STAT_ST80) { page2 = 0; } line24 = (eff_line >> 3) & 0x1f; addr = g_screen_index[line24] & 0x3ff; addr = (addr & 0x380) + (((addr & 0x7f) - 25 + byte_offset) & 0x7f); if(hires) { addr = 0x2000 + addr + ((eff_line & 7) << 10) + (page2 << 13); } else { addr = 0x400 + addr + (page2 << 10); } val = g_slow_memory_ptr[addr]; #if 0 printf("For %04x (%d) addr=%04x, val=%02x, dfcyc:%016llx\n", lines_since_vbl, eff_line, addr, val, dfcyc - g_last_vbl_dfcyc); #endif dbg_log_info(dfcyc, ((lines_since_vbl >> 11) << 24) | (lines_since_vbl - 25), (addr << 8) | val, 0xff); return val; } ================================================ FILE: gsplus/src/voc.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // This file provides emulation of the Apple Video Overlay Card, which // will appear to be in slot 3 if g_voc_enable=1 (there's a config.c // setting to control enabling VOC). The only currently supported VOC // feature is the SHR interlaced display using both Main and Aux memory // to provide a 640x400 (or 320x400) pixel display. #include "defc.h" extern word32 g_c02b_val; extern int g_cur_a2_stat; extern word32 g_vbl_count; int g_voc_enable = 0; // Default to disabled for now word32 g_voc_reg1 = 0x09; word32 g_voc_reg3 = 0; word32 g_voc_reg4 = 0; word32 g_voc_reg5 = 0; word32 g_voc_reg6 = 0; word32 voc_devsel_read(word32 loc, dword64 dfcyc) { // Reads to $c0b0-$c0bf. loc = loc & 0xf; switch(loc) { case 0: // 0xc0b0 return voc_read_reg0(dfcyc); break; case 1: // 0xc0b1 return g_voc_reg1; break; case 3: // 0xc0b3 return g_voc_reg3; break; case 4: // 0xc0b4 return g_voc_reg4; break; case 5: // 0xc0b5 return g_voc_reg5; break; case 6: // 0xc0b6 return g_voc_reg6; break; case 7: // 0xc0b7, possible Uthernet 2 detection return 0x00; break; case 8: // 0xc0b8, Second Sight detection by jpeGS program return 0x00; // Second Sight returns 0x01 break; case 0xd: // 0xc0bd, A2OSX Uthernet 1 detection code return 0x00; break; } halt_printf("Tried to read: %04x\n", 0xc0b0 + loc); return 0; } void voc_devsel_write(word32 loc, word32 val, dword64 dfcyc) { // Writes to $c0b0-$c0bf. loc = loc & 0xf; switch(loc) { case 0: // 0xc0b0 // Write 0 to clear VBL interrupts if(val != 0) { halt_printf("VOC write %04x = %02x\n", loc, val); } return; break; case 1: // 0xc0b1 // bit 0: R/W: 1=GG Bus Enable // When 0, I think VOC ignores writes to $c023,etc. // bit 2: R/W: 0=OutChromaFilter enabled, 1=ChromaFilter disab // bit 2 is also TextMonoOver somehow using bit[5]==1 // bit 3: R/W: 1=MainPageLin // bits 5:4: R/W: 00=Aux mem; 01=Main Memory; 11=Interlaced // bit 6: R/W: 1=Enable VBL Interrupt // bit 7: R/W: 1=Enable Line interrupts if(!g_voc_enable) { val = 0; } if(val & 0xc0) { halt_printf("VOC write %04x = %02x\n", loc, val); } #if 0 if(val != g_voc_reg1) { printf("$c0b1:%02x (was %02x)\n", val, g_voc_reg1); } #endif g_voc_reg1 = val; voc_update_interlace(dfcyc); return; break; case 3: // 0xc0b3 // bits 2:0: R/W: Key Dissolve, 0=100% graphics, 7=100% video // bit 3: R/W: 1=Enhanced Dissolve enabled // bits 6:4: R/W: Non-Key Dissolve, 0=100% graphics, 7=100% vid // bit 7: R/W: 0=Output Setup Enabled, 1=Output Setup Disabled g_voc_reg3 = val; return; break; case 4: // 0xc0b4 // bits 3:0: R/W: KeyColor Blue // bits 7:4: R/W: KeyColor Green g_voc_reg4 = val; return; break; case 5: // 0xc0b5 // bits 3:0: R/W: KeyColor Red // bit 4: R/W: OutExtBlank: 0=Graphics, 1=External // bit 5: R/W: 0=GenLock enabled, 1=GenLock disabled // bit 6: R/W: 0=KeyColor enabled, 1=KeyColor disabled // bit 7: R/W: 1=Interlace mode enabled g_voc_reg5 = val; voc_update_interlace(dfcyc); return; break; case 6: // 0xc0b6 // Write 0 to cause AdjSave to occur // Write 8, then 9, then 8 again to cause AdjInc for Hue // Write a, then b, then a again to cause AdjDec for Hue // Write 4, then 5, then 4 again to cause AdjInc for Saturation // Write 6, then 7, then 6 again to cause AdjDec for Saturation // bit 3: hue, bit 2: saturation g_voc_reg6 = val; return; break; case 7: // 0xc0b7 // Written by System Disk 1.1 Desktop.sys to 0xfd, ignore if(val == 0xfd) { return; } break; case 0xa: case 0xb: // 0xc0ba,0xc0bb written to 0 by A2OSX Uthernet1 detect if(val == 0) { return; } break; } halt_printf("Unknown Write %04x = %02x %016llx\n", 0xc0b0 + loc, val, dfcyc); } void voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc) { // Writes to $c300-$c3ff halt_printf("Wrote VOC %04x = %02x %016llx\n", 0xc300 + (loc & 0xff), val, dfcyc); } void voc_reset() { g_voc_reg1 = 0x0d; // [0]: GG Bus enable, [3]:MainPageLin g_voc_reg3 = 0x07; g_voc_reg4 = 0; g_voc_reg5 = 0x40; g_voc_reg6 = 0; } double g_voc_last_pal_vbl = 0; word32 voc_read_reg0(dword64 dfcyc) { word32 frame, in_vbl; if(!g_voc_enable) { return 0; } // Reading $c0b0. // c0b0: bit 2: R/O: 1=In VBL // c0b0: bit 3: R/O: 0=No Video Detected, 1=Video Detected // c0b0: bit 4: R/O: 1=Video Genlocked // c0b0: bit 5: R/O: 0=showing Field 0, 1=showing Field 1 // c0b0: bit 6: R/O: 1=VBL Int Request pending // c0b0: bit 7: R/O: 1=Line Int Request pending in_vbl = in_vblank(dfcyc); dbg_log_info(dfcyc, 0, in_vbl, 0x1c0b0); frame = g_vbl_count & 1; return (frame << 5) | (in_vbl << 2); } void voc_update_interlace(dword64 dfcyc) { word32 new_stat, mask; new_stat = 0; if(((g_voc_reg1 & 0x30) == 0x30) && (g_voc_reg5 & 0x80)) { new_stat = ALL_STAT_VOC_INTERLACE; } if((g_voc_reg1 & 0x30) == 0x10) { // Draw SHR from mainmem new_stat = ALL_STAT_VOC_MAIN; } mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; if((g_cur_a2_stat ^ new_stat) & mask) { // Interlace mode has changed g_cur_a2_stat &= (~mask); g_cur_a2_stat |= new_stat; printf("Change VOC interlace mode: %08x\n", new_stat); change_display_mode(dfcyc); } } ================================================ FILE: gsplus/src/win32snd_driver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2023 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Audio is sent every 1/60th of a second to win32_send_audio(). Play it // using waveOutWrite() as long as we've got at least 2/60th of a second // buffered--otherwise waveOutPause(). If we get more than 10 buffers // queued, drop buffers until we get down to 6 buffers queued again. // Track headers completed in g_wavehdr_rd_pos using callback function. #include "defc.h" #include "sound.h" #include #include extern int Verbose; extern int g_audio_rate; unsigned int __stdcall child_sound_loop_win32(void *param); void check_wave_error(int res, char *str); #define NUM_WAVE_HEADERS 32 HWAVEOUT g_wave_handle; WAVEHDR g_wavehdr[NUM_WAVE_HEADERS]; // Each header is for 1/60th of a second of sound (generally). Pause // until 2 headers are available, then unpause. Experimentally it appears // we keep about 5 headers (5/60th of a second = 80msec) ahead, which is // excellent latency extern int g_audio_enable; extern word32 *g_sound_shm_addr; extern int g_preferred_rate; int g_win32snd_buflen = 0x1000; int g_win32_snd_playing = 0; int g_win32_snd_to_drop = 0; word32 g_win32_snd_dropped = 0; volatile int g_wavehdr_rd_pos = 0; volatile int g_wavehdr_wr_pos = 0; void win32snd_init(word32 *shmaddr) { printf("win32snd_init\n"); child_sound_init_win32(); } void win32snd_shutdown() { /* hmm */ } void CALLBACK handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { LPWAVEHDR lpwavehdr; int pos; /* Only service "buffer done playing messages */ if(uMsg == WOM_DONE) { lpwavehdr = (LPWAVEHDR)dwParam1; if(lpwavehdr->dwFlags == (WHDR_DONE | WHDR_PREPARED)) { lpwavehdr->dwUser = FALSE; } pos = (int)(lpwavehdr - &g_wavehdr[0]); // printf("At %.3f, pos %d is done\n", get_dtime(), pos); if(pos == g_wavehdr_rd_pos) { pos = (pos + 1) % NUM_WAVE_HEADERS; g_wavehdr_rd_pos = pos; } else { printf("wavehdr %d finished, exp %d\n", pos, g_wavehdr_rd_pos); } } return; } void check_wave_error(int res, char *str) { char buf[256]; if(res == MMSYSERR_NOERROR) { return; } waveOutGetErrorText(res, &buf[0], sizeof(buf)); printf("%s: %s\n", str, buf); exit(1); } void child_sound_init_win32() { WAVEFORMATEX wavefmt; WAVEOUTCAPS caps; byte *bptr; UINT wave_id; int bits_per_sample, channels, block_align, blen, res; int i; memset(&wavefmt, 0, sizeof(WAVEFORMATEX)); wavefmt.wFormatTag = WAVE_FORMAT_PCM; bits_per_sample = 16; channels = 2; wavefmt.wBitsPerSample = bits_per_sample; wavefmt.nChannels = channels; wavefmt.nSamplesPerSec = g_preferred_rate; block_align = channels * (bits_per_sample / 8); wavefmt.nBlockAlign = block_align; wavefmt.nAvgBytesPerSec = block_align * g_audio_rate; res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, 0, 0, WAVE_FORMAT_QUERY); if(res != MMSYSERR_NOERROR) { printf("Cannot open audio device, res:%d, g_audio_rate:%d\n", res, g_preferred_rate); g_audio_enable = 0; return; } res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, (DWORD_PTR)handle_wav_snd, 0, CALLBACK_FUNCTION | WAVE_ALLOWSYNC); if(res != MMSYSERR_NOERROR) { printf("Cannot register audio\n"); g_audio_enable = 0; return; } g_audio_rate = wavefmt.nSamplesPerSec; blen = (((g_audio_rate * block_align) / 60) * 5) / 4; // Size buffer 25% larger than expected, to add some margin blen = (blen + 15) & -16L; g_win32snd_buflen = blen; bptr = malloc(blen * NUM_WAVE_HEADERS); if(bptr == NULL) { printf("Unabled to allocate sound buffer\n"); exit(1); } for(i = 0; i < NUM_WAVE_HEADERS; i++) { memset(&g_wavehdr[i], 0, sizeof(WAVEHDR)); g_wavehdr[i].dwUser = FALSE; g_wavehdr[i].lpData = (char *)&(bptr[i * blen]); g_wavehdr[i].dwBufferLength = blen; g_wavehdr[i].dwFlags = 0; g_wavehdr[i].dwLoops = 0; res = waveOutPrepareHeader(g_wave_handle, &g_wavehdr[i], sizeof(WAVEHDR)); check_wave_error(res, "waveOutPrepareHeader"); } res = waveOutGetID(g_wave_handle, &wave_id); res = waveOutGetDevCaps(wave_id, &caps, sizeof(caps)); check_wave_error(res, "waveOutGetDevCaps"); printf("Using %s, buflen:%d\n", caps.szPname, g_win32snd_buflen); printf(" Bits per Sample = %d. Channels = %d\n", wavefmt.wBitsPerSample, wavefmt.nChannels); printf(" Sampling rate = %d, avg_bytes_per_sec = %d\n", (int)wavefmt.nSamplesPerSec, (int)wavefmt.nAvgBytesPerSec); sound_set_audio_rate(g_audio_rate); } void win32snd_set_playing(int snd_playing) { g_win32_snd_playing = snd_playing; if(snd_playing) { waveOutRestart(g_wave_handle); #if 0 printf("win32 restarted sound wr:%d rd:%d\n", g_wavehdr_wr_pos, g_wavehdr_rd_pos); #endif } else { waveOutPause(g_wave_handle); #if 0 printf("win32 paused sound wr:%d rd:%d\n", g_wavehdr_wr_pos, g_wavehdr_rd_pos); #endif } } void win32_send_audio2(byte *ptr, int size) { int res, wr_pos, rd_pos, new_pos, bufs_in_use; wr_pos = g_wavehdr_wr_pos; rd_pos = g_wavehdr_rd_pos; #if 0 if(wr_pos == 0) { printf("send_audio2 wr:%d rd:%d sz:%d at %.3f\n", wr_pos, rd_pos, size, get_dtime()); } #endif if(g_wavehdr[wr_pos].dwUser != FALSE) { // Audio buffer busy...should not happen! printf("Audio buffer %d is busy!\n", wr_pos); return; } bufs_in_use = (NUM_WAVE_HEADERS + wr_pos - rd_pos) % NUM_WAVE_HEADERS; if(g_win32_snd_to_drop) { g_win32_snd_to_drop--; g_win32_snd_dropped += size; if((bufs_in_use < 4) && (g_win32_snd_to_drop != 0)) { #if 0 printf("bufs_in_use:%d, snd_to_drop:%d\n", bufs_in_use, g_win32_snd_to_drop); #endif g_win32_snd_to_drop = 0; } if(g_win32_snd_to_drop == 0) { printf("Dropped %d bytes of sound\n", g_win32_snd_dropped); } return; } #if 0 if(g_win32_snd_playing && (bufs_in_use <= 2)) { printf("bufs_in_use:%d, wr:%d, rd:%d\n", bufs_in_use, wr_pos, rd_pos); } #endif if(bufs_in_use == 0) { #if 0 printf("bufs_in_use:%d, wr_pos:%d rd_pos:%d\n", bufs_in_use, wr_pos, rd_pos); #endif // We've underflowed, so pause sound until we get some buffered win32snd_set_playing(0); } else if(g_win32_snd_playing == 0) { if(bufs_in_use >= 2) { //printf("bufs_in_use:%d, will start\n", bufs_in_use); win32snd_set_playing(1); } } else { if(bufs_in_use >= 14) { // About 230msec // Drop 6 buffers to get us back down to 100msec delay printf("bufs_in_use:%d, wr:%d will drop 6\n", bufs_in_use, wr_pos); g_win32_snd_to_drop = 6; g_win32_snd_dropped = 0; } } memcpy(g_wavehdr[wr_pos].lpData, ptr, size); g_wavehdr[wr_pos].dwBufferLength = size; g_wavehdr[wr_pos].dwUser = TRUE; new_pos = (wr_pos + 1) % NUM_WAVE_HEADERS; g_wavehdr_wr_pos = new_pos; res = waveOutWrite(g_wave_handle, &g_wavehdr[wr_pos], sizeof(g_wavehdr)); check_wave_error(res, "waveOutWrite"); return; } int win32_send_audio(byte *ptr, int in_size) { int size; int tmpsize; // printf("send_audio %d bytes at %.3f\n", in_size, get_dtime()); size = in_size; while(size > 0) { tmpsize = size; if(size > g_win32snd_buflen) { tmpsize = g_win32snd_buflen; } win32_send_audio2(ptr, tmpsize); ptr += tmpsize; if(size != tmpsize) { #if 0 printf("Orig size:%d, reduced to %d\n", in_size, tmpsize); #endif } size = size - tmpsize; } return in_size; } ================================================ FILE: gsplus/src/win_dirent.h ================================================ #ifdef INCLUDE_RCSID_C #endif /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2022 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Hacky defines to get something to compile for now typedef unsigned short mode_t; struct dirent { char d_name[1024]; }; struct DIR_t { int find_data_valid; void *win_handle; void *find_data_ptr; struct dirent dirent; }; typedef struct DIR_t DIR; DIR *opendir(const char *filename); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); ================================================ FILE: gsplus/src/windriver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2024 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Based on code from Chea Chee Keong from KEGS32, which was available at // http://www.geocities.com/akilgard/kegs32 (geocities is gone now) #define WIN32_LEAN_AND_MEAN /* Tell windows we want less header gunk */ #define STRICT /* Tell Windows we want compile type checks */ #include #include #include #include #include #include /* For _get_osfhandle */ #include "defc.h" #include "win_dirent.h" extern int Verbose; typedef struct windowinfo { HWND win_hwnd; HDC win_dc; HDC win_cdc; BITMAPINFO *win_bmapinfo_ptr; BITMAPINFOHEADER *win_bmaphdr_ptr; HBITMAP win_dev_handle; Kimage *kimage_ptr; // KEGS Image pointer for window content char *name_str; byte *data_ptr; int motion; int mdepth; int active; int pixels_per_line; int x_xpos; int x_ypos; int width; int height; int extra_width; int extra_height; } Window_info; #include "protos_windriver.h" Window_info g_mainwin_info = { 0 }; Window_info g_debugwin_info = { 0 }; int g_win_max_width = 0; int g_win_max_height = 0; int g_num_a2_keycodes = 0; int g_win_button_states = 0; int g_win_hide_pointer = 0; int g_win_warp_pointer = 0; int g_win_warp_x = 0; int g_win_warp_y = 0; /* this table is used to search for the Windows VK_* in col 1 or 2 */ /* flags bit 8 is or'ed into the VK, so we can distinguish keypad keys */ /* regardless of numlock */ int g_a2_key_to_wsym[][3] = { { 0x35, VK_ESCAPE, 0 }, { 0x7a, VK_F1, 0 }, { 0x78, VK_F2, 0 }, { 0x63, VK_F3, 0 }, { 0x76, VK_F4, 0 }, { 0x60, VK_F5, 0 }, { 0x61, VK_F6, 0 }, { 0x62, VK_F7, 0 }, { 0x64, VK_F8, 0 }, { 0x65, VK_F9, 0 }, { 0x6d, VK_F10, 0 }, { 0x67, VK_F11, 0 }, { 0x6f, VK_F12, 0 }, { 0x69, VK_F13, 0 }, { 0x6b, VK_F14, 0 }, { 0x71, VK_F15, 0 }, { 0x7f, VK_PAUSE, VK_CANCEL+0x100 }, // Reset { 0x12, '1', 0 }, { 0x13, '2', 0 }, { 0x14, '3', 0 }, { 0x15, '4', 0 }, { 0x17, '5', 0 }, { 0x16, '6', 0 }, { 0x1a, '7', 0 }, { 0x1c, '8', 0 }, { 0x19, '9', 0 }, { 0x1d, '0', 0 }, { 0x1b, 0xbd, 0 }, /* '-' */ { 0x18, 0xbb, 0 }, /* '=' */ { 0x33, VK_BACK, 0 }, /* backspace */ { 0x72, VK_INSERT+0x100, 0 }, /* Insert key */ { 0x74, VK_PRIOR+0x100, 0 }, /* pageup */ { 0x47, VK_NUMLOCK, VK_NUMLOCK+0x100 }, /* clear */ { 0x51, VK_HOME+0x100, 0 }, /* KP_equal is HOME key */ { 0x4b, VK_DIVIDE, VK_DIVIDE+0x100 }, // KP / { 0x43, VK_MULTIPLY, VK_MULTIPLY+0x100 }, // KP * { 0x30, VK_TAB, 0 }, { 0x32, 0xc0, 0 }, /* '`' */ { 0x0c, 'Q', 0 }, { 0x0d, 'W', 0 }, { 0x0e, 'E', 0 }, { 0x0f, 'R', 0 }, { 0x11, 'T', 0 }, { 0x10, 'Y', 0 }, { 0x20, 'U', 0 }, { 0x22, 'I', 0 }, { 0x1f, 'O', 0 }, { 0x23, 'P', 0 }, { 0x21, 0xdb, 0 }, /* [ */ { 0x1e, 0xdd, 0 }, /* ] */ { 0x2a, 0xdc, 0 }, /* backslash, bar */ { 0x75, VK_DELETE+0x100, 0 }, { 0x77, VK_END+0x100, VK_END }, { 0x79, VK_NEXT+0x100, 0 }, { 0x59, VK_NUMPAD7, VK_HOME }, { 0x5b, VK_NUMPAD8, VK_UP }, { 0x5c, VK_NUMPAD9, VK_PRIOR }, { 0x4e, VK_SUBTRACT, VK_SUBTRACT+0x100 }, { 0x39, VK_CAPITAL, 0 }, // Capslock { 0x00, 'A', 0 }, { 0x01, 'S', 0 }, { 0x02, 'D', 0 }, { 0x03, 'F', 0 }, { 0x05, 'G', 0 }, { 0x04, 'H', 0 }, { 0x26, 'J', 0 }, { 0x28, 'K', 0 }, { 0x25, 'L', 0 }, { 0x29, 0xba, 0 }, /* ; */ { 0x27, 0xde, 0 }, /* single quote */ { 0x24, VK_RETURN, 0 }, { 0x56, VK_NUMPAD4, VK_LEFT }, { 0x57, VK_NUMPAD5, VK_CLEAR }, { 0x58, VK_NUMPAD6, VK_RIGHT }, { 0x45, VK_ADD, 0 }, { 0x38, VK_SHIFT, 0 }, { 0x06, 'Z', 0 }, { 0x07, 'X', 0 }, { 0x08, 'C', 0 }, { 0x09, 'V', 0 }, { 0x0b, 'B', 0 }, { 0x2d, 'N', 0 }, { 0x2e, 'M', 0 }, { 0x2b, 0xbc, 0 }, /* , */ { 0x2f, 0xbe, 0 }, /* . */ { 0x2c, 0xbf, 0 }, /* / */ { 0x3e, VK_UP+0x100, 0 }, { 0x53, VK_NUMPAD1, VK_END }, { 0x54, VK_NUMPAD2, VK_DOWN }, { 0x55, VK_NUMPAD3, VK_NEXT }, { 0x36, VK_CONTROL, VK_CONTROL+0x100 }, { 0x37, VK_SCROLL, VK_MENU+0x100 }, // Command=scr_lock or alt-r { 0x3a, VK_SNAPSHOT+0x100, VK_MENU }, // Opt=prntscrn or alt-l { 0x31, ' ', 0 }, { 0x3b, VK_LEFT+0x100, 0 }, { 0x3d, VK_DOWN+0x100, 0 }, { 0x3c, VK_RIGHT+0x100, 0 }, { 0x52, VK_NUMPAD0, VK_INSERT }, { 0x41, VK_DECIMAL, VK_DELETE }, { 0x4c, VK_RETURN+0x100, 0 }, { -1, -1, -1 } }; #if 0 int win_nonblock_read_stdin(int fd, char *bufptr, int len) { HANDLE oshandle; DWORD dwret; int ret; errno = EAGAIN; oshandle = (HANDLE)_get_osfhandle(fd); // get stdin handle dwret = WaitForSingleObject(oshandle, 1); // wait 1msec for data ret = -1; if(dwret == WAIT_OBJECT_0) { ret = read(fd, bufptr, len); } return ret; } #endif Window_info * win_find_win_info_ptr(HWND hwnd) { if(hwnd == g_mainwin_info.win_hwnd) { return &g_mainwin_info; } if(hwnd == g_debugwin_info.win_hwnd) { return &g_debugwin_info; } return 0; } void win_hide_pointer(Window_info *win_info_ptr, int do_hide) { ShowCursor(!do_hide); // printf("Doing ShowCursor(%d)\n", !do_hide); } int win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid) { Kimage *kimage_ptr; int buttons_changed, x, y; kimage_ptr = win_info_ptr->kimage_ptr; x = video_scale_mouse_x(kimage_ptr, raw_x, 0); y = video_scale_mouse_y(kimage_ptr, raw_y, 0); // printf("wum: %d,%d -> %d,%d\n", raw_x, raw_y, x, y); buttons_changed = ((g_win_button_states & buttons_valid) != button_states); g_win_button_states = (g_win_button_states & ~buttons_valid) | (button_states & buttons_valid); if(g_win_warp_pointer && (raw_x == g_win_warp_x) && (raw_y == g_win_warp_y) && (!buttons_changed) ) { /* tell adb routs to recenter but ignore this motion */ adb_update_mouse(kimage_ptr, x, y, 0, -1); return 0; } return adb_update_mouse(kimage_ptr, x, y, button_states, buttons_valid & 7); } void win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; word32 flags; int buttons, x, y, hide, warp; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } flags = (word32)wParam; x = LOWORD(lParam); y = HIWORD(lParam); buttons = (flags & 1) | (((flags >> 1) & 1) << 2) | (((flags >> 4) & 1) << 1); #if 0 printf("Mouse at %d, %d fl: %08x, but: %d\n", x, y, flags, buttons); #endif win_info_ptr->motion |= win_update_mouse(win_info_ptr, x, y, buttons, 7); hide = 0; warp = 0; hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); if(warp != g_win_warp_pointer) { win_info_ptr->motion = 1; } g_win_warp_pointer = warp; if(g_win_hide_pointer != hide) { win_hide_pointer(win_info_ptr, hide); } g_win_hide_pointer = hide; } void win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down) { Window_info *win_info_ptr; Kimage *kimage_ptr; word32 vk, raw_vk, flags, capslock_state; int a2code, is_up; int i; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } kimage_ptr = win_info_ptr->kimage_ptr; raw_vk = (word32)wParam; flags = HIWORD(lParam); #if 0 printf("win_event_key: raw:%04x lParam:%08x d:%d flags:%08x\n", raw_vk, (word32)lParam, down, flags); #endif if((flags & 0x4000) && down) { /* auto-repeating, just ignore it */ return; } vk = raw_vk + (flags & 0x100); #if 0 printf("Key event, vk=%04x, down:%d, repeat: %d, flags: %08x\n", vk, down, repeat, flags); #endif /* remap a few keys here.. sigh */ if((vk & 0xff) == VK_APPS) { /* remap to command */ vk = VK_MENU; } if((vk & 0xff) == VK_CAPITAL) { // Fix up capslock info: Windows gives us a down, then up event // when the capslock key itself is pressed and released. We // need to ask for the true toggle state instead capslock_state = (GetKeyState(VK_CAPITAL) & 1); down = capslock_state; } /* search a2key_to_wsym to find wsym in col 1 or 2 */ i = 0; is_up = !down; for(i = g_num_a2_keycodes-1; i >= 0; i--) { a2code = g_a2_key_to_wsym[i][0]; if((vk == g_a2_key_to_wsym[i][1]) || (vk == g_a2_key_to_wsym[i][2])) { vid_printf("Found vk:%04x = %02x\n", vk, a2code); adb_physical_key_update(kimage_ptr, a2code, 0, is_up); return; } } printf("VK: %04x unknown\n", vk); } void win_event_redraw(HWND hwnd) { Window_info *win_info_ptr; win_info_ptr = win_find_win_info_ptr(hwnd); if(win_info_ptr) { video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); } } void win_event_destroy(HWND hwnd) { Window_info *win_info_ptr; win_info_ptr = win_find_win_info_ptr(hwnd); if(win_info_ptr == 0) { return; } video_set_active(win_info_ptr->kimage_ptr, 0); win_info_ptr->active = 0; if(win_info_ptr == &g_mainwin_info) { my_exit(0); } else { ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); ReleaseDC(hwnd, win_info_ptr->win_dc); DeleteDC(win_info_ptr->win_cdc); DeleteObject(win_info_ptr->win_dev_handle); GlobalFree(win_info_ptr->win_bmapinfo_ptr); win_info_ptr->win_hwnd = 0; win_info_ptr->data_ptr = 0; } } void win_event_move(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; int x_xpos, x_ypos; // These WM_MOVE events indicate the window is being moved win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } // printf("WM_MOVE: %04x %08x\n", (word32)wParam, (word32)lParam); x_xpos = lParam & 0xffff; x_ypos = (lParam >> 16) & 0xffff; video_update_xpos_ypos(win_info_ptr->kimage_ptr, x_xpos, x_ypos); } void win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; int width, height; // These WM_SIZE events indicate the window is being resized win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } // printf("WM_SIZE: %04x %08x\n", (word32)wParam, (word32)lParam); width = lParam & 0xffff; height = (lParam >> 16) & 0xffff; video_update_scale(win_info_ptr->kimage_ptr, width, height, 0); #if 0 printf("Frac width: %f\n", win_info_ptr->kimage_ptr->scale_width_a2_to_x / 65536.0); #endif // The following try to do "live updating" of the resize win_info_ptr->kimage_ptr->x_refresh_needed = 1; x_update_display(win_info_ptr); } void win_event_minmaxinfo(HWND hwnd, LPARAM lParam) { Window_info *win_info_ptr; MINMAXINFO *minmax_ptr; int a2_width, a2_height; // Windows sends WM_GETMINMAXINFO events when resizing is occurring, // and we can modify the *lParam MINMAXINFO structure to set the // minimum and maximum Track size (the size of the window) // This code forces the minimum to be the A2 window size, and the // maximum to be the screen size win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } minmax_ptr = (MINMAXINFO *)lParam; #if 0 printf("MinMax: mintrack.x:%d, mintrack.y:%d\n", minmax_ptr->ptMinTrackSize.x, minmax_ptr->ptMinTrackSize.y); #endif a2_width = video_get_a2_width(win_info_ptr->kimage_ptr); a2_height = video_get_a2_height(win_info_ptr->kimage_ptr); minmax_ptr->ptMinTrackSize.x = a2_width + win_info_ptr->extra_width; minmax_ptr->ptMinTrackSize.y = a2_height + win_info_ptr->extra_height; minmax_ptr->ptMaxTrackSize.x = g_win_max_width + win_info_ptr->extra_width; minmax_ptr->ptMaxTrackSize.y = g_win_max_height + win_info_ptr->extra_height; } void win_event_focus(HWND hwnd, int gain_focus) { Window_info *win_info_ptr; word32 c025_val, info; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } if(gain_focus) { // printf("Got focus on %p\n", hwnd); // Get shift, ctrl, capslock state c025_val = 0; info = GetKeyState(VK_SHIFT); // left or right if(info & 0x8000) { c025_val |= 1; // Shift key is down } info = GetKeyState(VK_CONTROL); // left or right if(info & 0x8000) { c025_val |= 2; } info = GetKeyState(VK_CAPITAL); // Capslock? if(info & 1) { c025_val |= 4; // Capslock key is down } //printf("Calling update_c025 with %03x\n", c025_val); adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); } else { // printf("Lost focus on %p\n", hwnd); } if(win_info_ptr == &g_mainwin_info) { adb_kbd_repeat_off(); adb_mainwin_focus(gain_focus); } } LRESULT CALLBACK win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { #if 0 printf("Message: umsg: %04x, wparam:%04x lParam:%08x\n", umsg, (word32)wParam, (word32)lParam); #endif switch(umsg) { case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: win_event_mouse(hwnd, wParam, lParam); return 0; case WM_KEYUP: case WM_SYSKEYUP: win_event_key(hwnd, wParam, lParam, 0); return 0; case WM_KEYDOWN: case WM_SYSKEYDOWN: win_event_key(hwnd, wParam, lParam, 1); return 0; case WM_SYSCOMMAND: // Alt key press can cause this. Return 0 for SC_KEYMENU if(wParam == SC_KEYMENU) { return 0; } break; case WM_KILLFOCUS: win_event_focus(hwnd, 0); break; case WM_SETFOCUS: win_event_focus(hwnd, 1); break; case WM_DESTROY: win_event_destroy(hwnd); return 0; case WM_PAINT: win_event_redraw(hwnd); break; case WM_MOVE: win_event_move(hwnd, wParam, lParam); break; case WM_SIZE: win_event_size(hwnd, wParam, lParam); break; case WM_GETMINMAXINFO: win_event_minmaxinfo(hwnd, lParam); break; } #if 0 switch(umsg) { HANDLE_MSG(hwnd, WM_KEYUP, win_event_key); HANDLE_MSG(hwnd, WM_KEYDOWN, win_event_key); HANDLE_MSG(hwnd, WM_SYSKEYUP, win_event_key); HANDLE_MSG(hwnd, WM_SYSKEYDOWN, win_event_key); HANDLE_MSG(hwnd, WM_DESTROY, win_event_destroy); } #endif #if 0 switch(umsg) { case WM_NCACTIVATE: case WM_NCHITTEST: case WM_NCMOUSEMOVE: case WM_SETCURSOR: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_CONTEXTMENU: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_PAINT: break; default: printf("Got umsg2: %d\n", umsg); } #endif return DefWindowProc(hwnd, umsg, wParam, lParam); } int main(int argc, char **argv) { int ret, mdepth; ret = parse_argv(argc, argv, 1); if(ret) { printf("parse_argv ret: %d, stopping\n", ret); exit(1); } mdepth = 32; video_set_blue_mask(0x0000ff); video_set_green_mask(0x00ff00); video_set_red_mask(0xff0000); g_win_max_width = GetSystemMetrics(SM_CXSCREEN); g_win_max_height = GetSystemMetrics(SM_CYSCREEN); vid_printf("g_win_max_width:%d, g_win_max_height:%d\n", g_win_max_width, g_win_max_height); ret = kegs_init(mdepth, g_win_max_width, g_win_max_height, 0); printf("kegs_init done\n"); if(ret) { printf("kegs_init ret: %d, stopping\n", ret); exit(1); } win_video_init(mdepth); printf("Entering main loop!\n"); fflush(stdout); while(1) { ret = run_16ms(); if(ret != 0) { printf("run_16ms returned: %d\n", ret); break; } x_update_display(&g_mainwin_info); x_update_display(&g_debugwin_info); check_input_events(); } xdriver_end(); exit(0); } void check_input_events() { MSG msg; POINT pt; BOOL ret; Window_info *win_info_ptr; while(PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) { if(GetMessage(&msg, 0, 0, 0) > 0) { //TranslateMessage(&msg); DispatchMessage(&msg); } else { printf("GetMessage returned <= 0\n"); my_exit(2); } } win_info_ptr = &g_mainwin_info; if(win_info_ptr->motion == 0) { return; } // ONLY look at g_mainwin_info! win_info_ptr->motion = 0; if(g_win_warp_pointer) { /* move mouse to center of screen */ g_win_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, BASE_MARGIN_LEFT + (A2_WINDOW_WIDTH/2), 0); g_win_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); pt.x = g_win_warp_x; pt.y = g_win_warp_y; ClientToScreen(win_info_ptr->win_hwnd, &pt); ret = SetCursorPos(pt.x, pt.y); #if 0 printf("Did SetCursorPos(%d, %d) warp_x:%d,y:%d, ret:%d\n", pt.x, pt.y, g_win_warp_x, g_win_warp_y, ret); #endif } return; } void win_video_init(int mdepth) { WNDCLASS wndclass; int a2code; int i; video_set_palette(); g_num_a2_keycodes = 0; for(i = 0; i < 0x7f; i++) { a2code = g_a2_key_to_wsym[i][0]; if(a2code < 0) { g_num_a2_keycodes = i; break; } } wndclass.style = 0; wndclass.lpfnWndProc = (WNDPROC)win_event_handler; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = GetModuleHandle(NULL); wndclass.hIcon = LoadIcon((HINSTANCE)NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "kegswin"; // Register the window if(!RegisterClass(&wndclass)) { printf("Registering window failed\n"); exit(1); } win_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS", mdepth); win_init_window(&g_debugwin_info, video_get_kimage(1), "KEGS Debugger", mdepth); win_create_window(&g_mainwin_info); } void win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, int mdepth) { int height, width, x_xpos, x_ypos; height = video_get_x_height(kimage_ptr); width = video_get_x_width(kimage_ptr); x_xpos = video_get_x_xpos(kimage_ptr); x_ypos = video_get_x_ypos(kimage_ptr); win_info_ptr->win_hwnd = 0; win_info_ptr->win_dc = 0; win_info_ptr->win_cdc = 0; win_info_ptr->win_bmapinfo_ptr = 0; win_info_ptr->win_bmaphdr_ptr = 0; win_info_ptr->win_dev_handle = 0; win_info_ptr->kimage_ptr = kimage_ptr; win_info_ptr->name_str = name_str; win_info_ptr->data_ptr = 0; win_info_ptr->motion = 0; win_info_ptr->mdepth = mdepth; win_info_ptr->active = 0; win_info_ptr->pixels_per_line = width; win_info_ptr->x_xpos = x_xpos; win_info_ptr->x_ypos = x_ypos; win_info_ptr->width = width; win_info_ptr->height = height; } void win_create_window(Window_info *win_info_ptr) { HWND win_hwnd; RECT rect; BITMAPINFO *bmapinfo_ptr; BITMAPINFOHEADER *bmaphdr_ptr; HBITMAP win_dev_handle; Kimage *kimage_ptr; int height, width, extra_width, extra_height; int extra_size, w_flags; kimage_ptr = win_info_ptr->kimage_ptr; height = win_info_ptr->height; width = win_info_ptr->width; printf("Got height: %d, width:%d\n", height, width); // We must call CreateWindow with a width,height that accounts for // the title bar and any other stuff. Use AdjustWindowRect() to // calculate this info for us w_flags = WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX; rect.left = 0; rect.top = 0; rect.right = width; rect.bottom = height; (void)AdjustWindowRect(&rect, w_flags, 0); extra_width = rect.right - rect.left - width; extra_height = rect.bottom - rect.top - height; win_info_ptr->extra_width = extra_width; win_info_ptr->extra_height = extra_height; win_hwnd = CreateWindow("kegswin", win_info_ptr->name_str, w_flags, win_info_ptr->x_xpos, win_info_ptr->x_ypos, width + extra_width, height + extra_height, NULL, NULL, GetModuleHandle(NULL), NULL); win_info_ptr->win_hwnd = win_hwnd; win_info_ptr->active = 0; video_set_active(kimage_ptr, 1); video_update_scale(kimage_ptr, win_info_ptr->width, win_info_ptr->height, 1); printf("win_hwnd = %p, height = %d\n", win_hwnd, height); GetWindowRect(win_hwnd, &rect); printf("...rect is: %ld, %ld, %ld, %ld\n", rect.left, rect.top, rect.right, rect.bottom); win_info_ptr->win_dc = GetDC(win_hwnd); SetTextColor(win_info_ptr->win_dc, 0); SetBkColor(win_info_ptr->win_dc, 0xffffff); win_info_ptr->win_cdc = CreateCompatibleDC(win_info_ptr->win_dc); printf("win_cdc: %p, win_dc:%p\n", win_info_ptr->win_cdc, win_info_ptr->win_dc); printf("Getting height, kimage_ptr:%p\n", kimage_ptr); fflush(stdout); win_info_ptr->data_ptr = 0; extra_size = sizeof(RGBQUAD); bmapinfo_ptr = (BITMAPINFO *)GlobalAlloc(GPTR, sizeof(BITMAPINFOHEADER) + extra_size); win_info_ptr->win_bmapinfo_ptr = bmapinfo_ptr; bmaphdr_ptr = (BITMAPINFOHEADER *)bmapinfo_ptr; win_info_ptr->win_bmaphdr_ptr = bmaphdr_ptr; bmaphdr_ptr->biSize = sizeof(BITMAPINFOHEADER); bmaphdr_ptr->biWidth = g_win_max_width; bmaphdr_ptr->biHeight = -g_win_max_height; bmaphdr_ptr->biPlanes = 1; bmaphdr_ptr->biBitCount = win_info_ptr->mdepth; bmaphdr_ptr->biCompression = BI_RGB; bmaphdr_ptr->biClrUsed = 0; /* Use g_bmapinfo_ptr, adjusting width, height */ printf("bmaphdr_ptr:%p\n", bmaphdr_ptr); printf("About to call CreateDIBSection, win_dc:%p\n", win_info_ptr->win_dc); fflush(stdout); win_dev_handle = CreateDIBSection(win_info_ptr->win_dc, win_info_ptr->win_bmapinfo_ptr, DIB_RGB_COLORS, (VOID **)&(win_info_ptr->data_ptr), NULL, 0); win_info_ptr->win_dev_handle = win_dev_handle; win_info_ptr->pixels_per_line = g_win_max_width; printf("kim: %p, dev:%p data: %p\n", kimage_ptr, win_dev_handle, win_info_ptr->data_ptr); fflush(stdout); } void xdriver_end() { printf("xdriver_end\n"); } void win_resize_window(Window_info *win_info_ptr) { RECT rect1, rect2; BOOL ret; Kimage *kimage_ptr; int x_width, x_height; kimage_ptr = win_info_ptr->kimage_ptr; x_width = video_get_x_width(kimage_ptr); x_height = video_get_x_height(kimage_ptr); // printf("win_resize_window, x_w:%d, x_h:%d\n", x_width, x_height); ret = GetWindowRect(win_info_ptr->win_hwnd, &rect1); ret = GetClientRect(win_info_ptr->win_hwnd, &rect2); #if 0 printf("window_rect: l:%d, t:%d, r:%d, b:%d\n", rect1.left, rect1.top, rect1.right, rect1.bottom); printf("client_rect: l:%d, t:%d, r:%d, b:%d\n", rect2.left, rect2.top, rect2.right, rect2.bottom); #endif ret = MoveWindow(win_info_ptr->win_hwnd, rect1.left, rect1.top, x_width + win_info_ptr->extra_width, x_height + win_info_ptr->extra_height, TRUE); // printf("MoveWindow ret:%d\n", ret); win_info_ptr->width = x_width; win_info_ptr->height = x_height; } void x_update_display(Window_info *win_info_ptr) { Change_rect rect; void *bitm_old; //POINT point; int valid, a2_active, x_active; int i; a2_active = video_get_active(win_info_ptr->kimage_ptr); x_active = win_info_ptr->active; if(x_active && !a2_active) { // We need to SW_HIDE this window ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); x_active = 0; win_info_ptr->active = x_active; } if(!x_active && a2_active) { // We need to SW_SHOWDEFAULT this window (and maybe create it) if(win_info_ptr->win_hwnd == 0) { win_create_window(win_info_ptr); } ShowWindow(win_info_ptr->win_hwnd, SW_SHOWDEFAULT); UpdateWindow(win_info_ptr->win_hwnd); x_active = 1; win_info_ptr->active = x_active; } if(x_active == 0) { return; } if(video_change_aspect_needed(win_info_ptr->kimage_ptr, win_info_ptr->width, win_info_ptr->height)) { win_resize_window(win_info_ptr); } for(i = 0; i < MAX_CHANGE_RECTS; i++) { valid = video_out_data(win_info_ptr->data_ptr, win_info_ptr->kimage_ptr, win_info_ptr->pixels_per_line, &rect, i); if(!valid) { break; } #if 0 point.x = 0; point.y = 0; ClientToScreen(win_info_ptr->win_hwnd, &point); #endif bitm_old = SelectObject(win_info_ptr->win_cdc, win_info_ptr->win_dev_handle); BitBlt(win_info_ptr->win_dc, rect.x, rect.y, rect.width, rect.height, win_info_ptr->win_cdc, rect.x, rect.y, SRCCOPY); SelectObject(win_info_ptr->win_cdc, bitm_old); } } void x_hide_pointer(int do_hide) { if(do_hide) { ShowCursor(0); } else { ShowCursor(1); } } int opendir_int(DIR *dirp, const char *in_filename) { HANDLE handle1; wchar_t *wcstr; char *filename; size_t ret_val; int buflen, len; int i; printf("opendir on %s\n", in_filename); len = (int)strlen(in_filename); buflen = len + 8; if(buflen >= sizeof(dirp->dirent.d_name)) { printf("buflen %d >= d_name %d\n", buflen, (int)sizeof(dirp->dirent.d_name)); return 1; } filename = &dirp->dirent.d_name[0]; memcpy(filename, in_filename, len + 1); while(len && (filename[len-1] == '/')) { filename[len - 1] = 0; len--; } cfg_strlcat(filename, "/*.*", buflen); for(i = 0; i < len; i++) { if(filename[i] == '/') { filename[i] = '\\'; } } len = (int)strlen(filename); wcstr = malloc(buflen * 2); (void)mbstowcs_s(&ret_val, wcstr, buflen, filename, _TRUNCATE); handle1 = FindFirstFileW(wcstr, dirp->find_data_ptr); dirp->win_handle = handle1; free(wcstr); if(handle1) { dirp->find_data_valid = 1; return 0; } return 1; } DIR * opendir(const char *in_filename) { DIR *dirp; int ret; dirp = calloc(1, sizeof(DIR)); if(!dirp) { return 0; } dirp->find_data_valid = 1; dirp->find_data_ptr = calloc(1, sizeof(WIN32_FIND_DATAW)); ret = 1; if(dirp->find_data_ptr) { ret = opendir_int(dirp, in_filename); } if(ret) { // Bad free(dirp->find_data_ptr); // free(0) is OK free(dirp); return 0; } return dirp; } struct dirent * readdir(DIR *dirp) { WIN32_FIND_DATAW *find_data_ptr; HANDLE handle1; size_t ret_val; BOOL ret; handle1 = dirp->win_handle; find_data_ptr = dirp->find_data_ptr; if(!handle1 || !find_data_ptr) { return 0; } ret = 1; if(!dirp->find_data_valid) { if(handle1) { find_data_ptr->cFileName[MAX_PATH-1] = 0; ret = FindNextFileW(handle1, find_data_ptr); } } dirp->find_data_valid = 0; if(!ret) { return 0; } (void)wcstombs_s(&ret_val, &(dirp->dirent.d_name[0]), (int)sizeof(dirp->dirent.d_name), &(find_data_ptr->cFileName[0]), _TRUNCATE); printf("Returning file %s\n", &(dirp->dirent.d_name[0])); return &(dirp->dirent);; } int closedir(DIR *dirp) { FindClose(dirp->win_handle); free(dirp->find_data_ptr); free(dirp); return 0; } int lstat(const char *path, struct stat *bufptr) { return stat(path, bufptr); } int ftruncate(int fd, word32 length) { HANDLE handle1; handle1 = (HANDLE)_get_osfhandle(fd); SetFilePointer(handle1, length, 0, FILE_BEGIN); SetEndOfFile(handle1); return 0; } ================================================ FILE: gsplus/src/woz.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ // Based on WOZ 2.0/1.0 spec at https://applesaucefdc.com/woz/reference2/ #include "defc.h" extern const char g_kegs_version_str[]; byte g_woz_hdr_bytes[12] = { 0x57, 0x4f, 0x5a, 0x32, // "WOZ2" 0xff, 0x0a, 0x0d, 0x0a, 0, 0, 0, 0 }; word32 g_woz_crc32_tab[256]; void woz_crc_init() { word32 crc, val, xor; int i, j; for(i = 0; i < 256; i++) { crc = 0; val = i; for(j = 0; j < 8; j++) { xor = 0; if((val ^ crc) & 1) { xor = 0xedb88320UL; } crc = (crc >> 1) ^ xor; val = val >> 1; } g_woz_crc32_tab[i] = crc; // printf("crc32_tab[%d] = %08x\n", i, crc); } } word32 woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip) { word32 crc, c; crc = (~0U); if(bytes_to_skip > dlen) { dlen = 0; } else { bptr += bytes_to_skip; dlen -= bytes_to_skip; } while(dlen != 0) { c = *bptr++; dlen--; crc = g_woz_crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); } return (~crc); } void woz_rewrite_crc(Disk *dsk, int min_write_size) { Woz_info *wozinfo_ptr; byte *wozptr; word32 crc; // Recalculate WOZ image CRC and write it to disk wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr || (dsk->fd < 0)) { return; } wozptr = wozinfo_ptr->wozptr; crc = woz_calc_crc32(&wozptr[0], wozinfo_ptr->woz_size, 12); cfg_set_le32(&wozptr[8], crc); if(min_write_size < 12) { min_write_size = 12; } cfg_write_to_fd(dsk->fd, wozptr, 0, min_write_size); } void woz_rewrite_lock(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr; int offset; wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr || (dsk->fd < 0)) { return; } wozptr = wozinfo_ptr->wozptr; offset = wozinfo_ptr->info_offset; wozptr[offset + 2] = (dsk->write_prot != 0); // Update locked woz_rewrite_crc(dsk, offset + 2 + 1); } void woz_check_file(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr, *newwozptr, *bptr; word32 fdval, memval, crc, mem_crc, crcnew, file_size; int woz_size; int i; wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr) { return; } woz_size = wozinfo_ptr->woz_size; wozptr = wozinfo_ptr->wozptr; crc = woz_calc_crc32(wozptr, woz_size, 12); mem_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | (wozptr[11] << 24); if(crc != mem_crc) { halt_printf("WOZ CRC calc:%08x, from mem:%08x\n", crc, mem_crc); } if((dsk->fd < 0) || dsk->raw_data || !dsk->write_through_to_unix) { printf("woz_check_file: CRC check done, cannot check fd\n"); return; } file_size = (word32)cfg_get_fd_size(dsk->fd); if(file_size != (word32)woz_size) { halt_printf("woz_size:%08x != file_size %08x\n", woz_size, file_size); if((word32)woz_size > file_size) { woz_size = file_size; } } newwozptr = malloc(woz_size); cfg_read_from_fd(dsk->fd, newwozptr, 0, woz_size); for(i = 0; i < woz_size; i++) { fdval = newwozptr[i]; memval = wozptr[i]; if(fdval == memval) { continue; } halt_printf("byte %07x of %07x: mem %02x != %02x fd\n", i, woz_size, memval, fdval); } crcnew = woz_calc_crc32(newwozptr, woz_size, 12); free(newwozptr); printf("Woz check file complete. mem %08x vs fd %08x, freed %p\n", crc, crcnew, newwozptr); bptr = dsk->cur_trk_ptr->raw_bptr; printf("dsk->cur_trk_ptr->raw_bptr = %p. offset:%d\n", bptr, (int)(bptr - wozptr)); } void woz_parse_meta(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; byte *wozptr, *bptr; int c; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->meta_offset) { printf("Bad WOZ file, 2 META chunks\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->meta_offset = offset; wozinfo_ptr->meta_size = size; printf("META field, %d bytes:\n", size); bptr = &(wozptr[offset]); for(i = 0; i < size; i++) { c = bptr[i]; if(c == 0) { break; } putchar(c); } putchar('\n'); } void woz_parse_info(Disk *dsk, int offset, int size) { byte new_buf[36]; Woz_info *wozinfo_ptr; byte *wozptr, *bptr; int info_version, disk_type, write_protect, synchronized; int cleaned, ram, largest_track; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->info_offset) { printf("Two INFO chunks, bad WOZ file\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->info_offset = offset; bptr = &(wozptr[offset]); if(size < 60) { printf("INFO field is %d, too short\n", size); wozinfo_ptr->woz_size = 0; return; } info_version = bptr[0]; // Only "1" or "2" is supported disk_type = bptr[1]; // 1==5.25", 2=3.5" write_protect = bptr[2]; // 1==write protected synchronized = bptr[3]; // 1==cross track sync during imaging cleaned = bptr[4]; // 1==MC3470 fake bits have been removed memcpy(&new_buf[0], &(bptr[5]), 32); new_buf[32] = 0; // Null terminate printf("INFO, %d bytes. info_version:%d, disk_type:%d, wp:%d, sync:" "%d, cleaned:%d\n", size, info_version, disk_type, write_protect, synchronized, cleaned); printf("Creator: %s\n", (char *)&new_buf[0]); if(info_version >= 2) { ram = bptr[42] + (bptr[43] << 8); largest_track = (bptr[44] + (bptr[45] << 8)) * 512; printf("Disk sides:%d, boot_format:%d bit_timing:%d, hw:" "%02x%02x, ram:%d, largest_track:0x%07x\n", bptr[37], bptr[38], bptr[39], bptr[41], bptr[40], ram, largest_track); } if(write_protect) { printf("Write protected\n"); dsk->write_prot = 1; } } void woz_parse_tmap(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; byte *bptr, *wozptr; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->tmap_offset) { printf("Second TMAP chunk, bad WOZ file!\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->tmap_offset = offset; printf("TMAP field, %d bytes\n", size); bptr = &(wozptr[offset]); for(i = 0; i < 40; i++) { printf("Track %2d.00: %02x, %2d.25:%02x %2d.50:%02x %2d.75:" "%02x\n", i, bptr[0], i, bptr[1], i, bptr[2], i, bptr[3]); bptr += 4; } } void woz_parse_trks(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; printf("TRKS field, %d bytes, offset: %d\n", size, offset); wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr->trks_offset) { printf("Second TRKS chunk, illegal Woz file\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->trks_offset = offset; wozinfo_ptr->trks_size = size; } int woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc) { Woz_info *wozinfo_ptr; Trk *trk; byte *wozptr, *sync_ptr, *bptr; word32 raw_bytes, num_bytes, trks_size, len_bits, offset, num_blocks; word32 block; int trks_offset; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; trks_offset = wozinfo_ptr->trks_offset; trks_size = wozinfo_ptr->trks_size; if(wozinfo_ptr->version == 1) { offset = tmap * 6656; if((offset + 6656) > trks_size) { printf("Trk %d is out of range!\n", tmap); return 0; } offset = trks_offset + offset; bptr = &(wozptr[offset]); len_bits = bptr[6648] | (bptr[6649] << 8); if(len_bits > (6656*8)) { printf("Trk bits: %d too big\n", len_bits); return 0; } } else { bptr = &(wozptr[trks_offset + (tmap * 8)]); // This is a TRK 8-byte structure block = cfg_get_le16(&bptr[0]); // Starting Block num_blocks = cfg_get_le16(&bptr[2]); // Block Count len_bits = cfg_get_le32(&bptr[4]); // Bits Count #if 0 printf("qtr_track:%02x, block:%04x, num_blocks:%04x, " "len_bits:%06x\n", qtr_track, block, num_blocks, len_bits); printf("File offset: %05lx\n", bptr - wozinfo_ptr->wozptr); #endif if(block < 3) { printf("block %04x is < 3\n", block); return 0; } offset = (block * 512); // Offset from wozptr if((offset + (num_blocks * 512)) > (trks_size + trks_offset)) { printf("Trk %d is out of range!\n", tmap); return 0; } bptr = &(wozptr[offset]); #if 0 printf("Qtr_track %03x offset:%06x, bptr:%p, trks_bptr:%p\n", qtr_track, offset, bptr, trks_bptr); printf(" len_bits:%d %06x bptr-wozptr: %07lx\n", len_bits, len_bits, bptr - wozinfo_ptr->wozptr); #endif if(len_bits > (num_blocks * 512 * 8)) { printf("Trk bits: %d too big\n", len_bits); return 0; } } dsk->raw_bptr_malloc = 0; raw_bytes = (len_bits + 7) >> 3; num_bytes = raw_bytes + 8; trk = &(dsk->trks[qtr_track]); trk->raw_bptr = bptr; trk->dunix_pos = offset; trk->unix_len = raw_bytes; trk->dirty = 0; trk->track_bits = len_bits; #if 0 printf("track %d.%d dunix_pos:%08llx\n", qtr_track >> 2, (qtr_track & 3) * 25, trk->dunix_pos); #endif trk->sync_ptr = (byte *)malloc(num_bytes); dsk->cur_trk_ptr = 0; iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); sync_ptr = &(trk->sync_ptr[0]); for(i = 0; i < (int)raw_bytes; i++) { sync_ptr[i] = 0xff; } iwm_recalc_sync_from(dsk, qtr_track, 0, dfcyc); if(qtr_track == 0) { printf("Track 0 data begins: %02x %02x %02x, offset:%d\n", bptr[0], bptr[1], bptr[2], offset); } for(i = 0; i < qtr_track; i++) { trk = &(dsk->trks[i]); if(trk->track_bits && (trk->raw_bptr == bptr)) { // Multiple tracks point to the same woz track // This is not allowed, reparse wozinfo_ptr->reparse_needed = 1; printf("Track %04x matchs track %04x, reparse needed\n", qtr_track, i); break; } } return 1; } int woz_parse_header(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr, *bptr; word32 chunk_id, size, woz_size; int pos, version; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; version = 2; if(woz_size < 8) { return 0; } for(i = 0; i < 8; i++) { if(wozptr[i] != g_woz_hdr_bytes[i]) { if(i == 3) { // Check for WOZ1 if(wozptr[i] == 0x31) { // WOZ1 version = 1; continue; } } printf("WOZ header[%d]=%02x, invalid\n", i, wozptr[i]); return 0; } } wozinfo_ptr->version = version; printf("WOZ version: %d\n", version); pos = 12; while(pos < (int)woz_size) { bptr = &(wozptr[pos]); chunk_id = bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | (bptr[3] << 24); size = bptr[4] | (bptr[5] << 8) | (bptr[6] << 16) | (bptr[7] << 24); pos += 8; printf("chunk_id: %08x, size:%08x\n", chunk_id, size); if(((pos + size) > woz_size) || (size < 8) || ((size >> 30) != 0)) { return 0; } bptr = &(wozptr[pos]); if(chunk_id == 0x4f464e49) { // "INFO" woz_parse_info(dsk, pos, size); } else if(chunk_id == 0x50414d54) { // "TMAP" woz_parse_tmap(dsk, pos, size); } else if(chunk_id == 0x534b5254) { // "TRKS" woz_parse_trks(dsk, pos, size); } else if(chunk_id == 0x4154454d) { // "META" woz_parse_meta(dsk, pos, size); } else { printf("Chunk header %08x is unknown\n", chunk_id); } pos += size; } return 1; // Good so far } Woz_info * woz_malloc(byte *wozptr, word32 woz_size) { Woz_info *wozinfo_ptr; wozinfo_ptr = malloc(sizeof(Woz_info)); printf("malloc wozinfo_ptr:%p\n", wozinfo_ptr); if(!wozinfo_ptr) { fprintf(stderr, "Out of memory\n"); exit(1); } wozinfo_ptr->wozptr = wozptr; wozinfo_ptr->woz_size = woz_size; wozinfo_ptr->version = 0; wozinfo_ptr->reparse_needed = 0; wozinfo_ptr->max_trk_blocks = 0; wozinfo_ptr->meta_size = 0; wozinfo_ptr->trks_size = 0; wozinfo_ptr->tmap_offset = 0; wozinfo_ptr->trks_offset = 0; wozinfo_ptr->info_offset = 0; wozinfo_ptr->meta_offset = 0; if(woz_size < 12) { if(wozptr) { free(wozptr); } wozptr = 0; } if(!wozptr) { wozptr = malloc(12); woz_append_bytes(wozptr, &g_woz_hdr_bytes[0], 12); wozinfo_ptr->wozptr = wozptr; wozinfo_ptr->woz_size = 12; } return wozinfo_ptr; } int woz_reopen(Disk *dsk, dword64 dfcyc) { byte act_tmap[160]; Woz_info *wozinfo_ptr; byte *wozptr, *tmap_bptr; word32 tmap, prev_tmap, last_act; int ret, num_tracks, num_match; int i, j; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(!wozinfo_ptr->tmap_offset) { printf("No TMAP found\n"); return 0; } if(!wozinfo_ptr->trks_offset) { printf("No TRKS found\n"); return 0; } if(!wozinfo_ptr->info_offset) { printf("No INFO found\n"); return 0; } if(wozinfo_ptr->woz_size == 0) { printf("woz_size is 0!\n"); return 0; } tmap_bptr = wozptr + wozinfo_ptr->tmap_offset; dsk->cur_fbit_pos = 0; num_tracks = 4*35; for(i = num_tracks; i < 160; i++) { // See what the largest track is, go to track 39.50 if(tmap_bptr[i] != 0xff) { num_tracks = i + 1; //printf("Will set num_tracks=%d\n", num_tracks); } } dsk->fbit_mult = 128; // 5.25" multipler value if(!dsk->disk_525) { num_tracks = 160; dsk->fbit_mult = 256; // 3.5" multipler value } disk_set_num_tracks(dsk, num_tracks); for(i = 0; i < 160; i++) { act_tmap[i] = 0xff; } for(i = 0; i < 160; i++) { tmap = tmap_bptr[i]; if(tmap >= 0xff) { continue; // Skip } // WOZ format adds dup entries for adjacent qtr tracks, so // track 2.0 has entries at 1.75 and 2.25 pointing to 2.0. // KEGS doesn't want these, it handles this itself, so remove // them. if(dsk->disk_525) { num_match = 1; for(j = i + 1; j < 160; j++) { if(tmap_bptr[j] != tmap) { break; } num_match++; } // From i, num_match is the number of time tmap repeats prev_tmap = 0xff; last_act = 0xff; if(i) { prev_tmap = tmap_bptr[i - 1]; last_act = act_tmap[i - 1]; } else if(num_match == 3) { // Weird case where WOZ starts with 0,0,0, we // should treat track 0.25 as the real track continue; } if(num_match >= 3) { // long run of repeats, add a track every other // one. if(last_act == tmap) { // Just did trk continue; } if(prev_tmap != tmap) { // Start of run continue; } } else if(num_match == 2) { // Handle A, B, [B], B, C and A, B, B, [B], B, C // ALWAYS add this track } else { // num_match==1 if(prev_tmap == tmap) { continue; } // Otherwise, a lone track, always add it } } ret = woz_add_track(dsk, i, tmap, dfcyc); if(ret == 0) { printf("woz_add_track i:%04x tmap:%04x ret 0\n", i, tmap); return ret; } } return 1; // WOZ file is good! } int woz_open(Disk *dsk, dword64 dfcyc) { Woz_info *wozinfo_ptr; byte *wozptr; dword64 doff; word32 woz_size, crc, file_crc; int fd, ret; // return 0 for bad WOZ file, 1 for success // We set dsk->wozinfo_ptr, and caller will free it if we return 0 printf("woz_open on file %s, write_prot:%d\n", dsk->name_ptr, dsk->write_prot); if(dsk->trks == 0) { return 0; // Smartport? } if(dsk->raw_data) { wozptr = dsk->raw_data; woz_size = (word32)dsk->raw_dsize; dsk->write_prot = 1; dsk->write_through_to_unix = 0; } else { fd = dsk->fd; if(fd < 0) { return 0; } woz_size = (word32)cfg_get_fd_size(fd); printf("size: %d\n", woz_size); wozptr = malloc(woz_size); doff = cfg_read_from_fd(fd, wozptr, 0, woz_size); if(doff != woz_size) { close(fd); return 0; } } wozinfo_ptr = woz_malloc(wozptr, woz_size); dsk->wozinfo_ptr = wozinfo_ptr; if(woz_size < 16) { return 0; } crc = woz_calc_crc32(wozptr, woz_size, 12); file_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | (wozptr[11] << 24); if((crc != file_crc) && (file_crc != 0)) { printf("Bad Woz CRC:%08x in file, calc:%08x\n", file_crc, crc); return 0; } ret = woz_parse_header(dsk); printf("woz_parse_header ret:%d, write_prot:%d\n", ret, dsk->write_prot); if(ret == 0) { return ret; } ret = woz_reopen(dsk, dfcyc); printf("woz_reopen ret:%d\n", ret); woz_maybe_reparse(dsk); return ret; } byte * woz_append_bytes(byte *wozptr, byte *in_bptr, int len) { int i; for(i = 0; i < len; i++) { *wozptr++ = *in_bptr++; } return wozptr; } byte * woz_append_word32(byte *wozptr, word32 val) { int i; for(i = 0; i < 4; i++) { *wozptr++ = val & 0xff; val = val >> 8; } return wozptr; } int woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, byte *bptr) { byte *wozptr, *new_wozptr, *save_wozptr; word32 woz_size, new_size; int offset; wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; new_size = woz_size + 4 + 4 + length; new_wozptr = realloc(wozptr, new_size); if(new_wozptr == 0) { return 0; } wozptr = new_wozptr + woz_size; wozptr = woz_append_word32(wozptr, chunk_id); save_wozptr = woz_append_word32(wozptr, length); wozptr = woz_append_bytes(save_wozptr, bptr, length); wozinfo_ptr->wozptr = new_wozptr; wozinfo_ptr->woz_size = new_size; offset = (int)(save_wozptr - new_wozptr); switch(chunk_id) { case 0x4f464e49: // "INFO" wozinfo_ptr->info_offset = offset; break; case 0x50414d54: // "TMAP" wozinfo_ptr->tmap_offset = offset; break; case 0x534b5254: // "TRKS" wozinfo_ptr->trks_offset = offset; wozinfo_ptr->trks_size = new_size; break; } if(wozptr != (new_wozptr + new_size)) { halt_printf("wozptr:%p != %p + %08x\n", wozptr, new_wozptr, new_size); return 0; } return 1; } byte * woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, word32 *num_blocks_ptr, dword64 *tmap_dptr) { byte *new_bptr; word32 num_blocks, blocks_start, this_blocks, size_bytes, track_bits; int i; // Align trks_buf_size to 512 bytes blocks_start = *num_blocks_ptr; track_bits = dsk->trks[trk_num].track_bits; size_bytes = (dsk->trks[trk_num].track_bits + 7) >> 3; this_blocks = (size_bytes + 511) >> 9; if(wozinfo_ptr->max_trk_blocks < this_blocks) { wozinfo_ptr->max_trk_blocks = this_blocks; printf("max_trk_blocks=%d from trk %03x\n", this_blocks, trk_num); } num_blocks = blocks_start + this_blocks; *num_blocks_ptr = num_blocks; *tmap_dptr = (((dword64)track_bits) << 32) | (this_blocks << 16) | blocks_start; new_bptr = realloc(bptr, num_blocks << 9); if(new_bptr == 0) { return 0; } // Zero out last 512 byte block, to ensure a partial track has all 0's // in the last block for(i = 0; i < 512; i++) { new_bptr[(num_blocks << 9) - 512 + i] = 0; } woz_append_bytes(new_bptr + (blocks_start << 9), dsk->trks[trk_num].raw_bptr, size_bytes); return new_bptr; } Woz_info * woz_new_from_woz(Disk *dsk, int disk_525) { dword64 tmap_dvals[160]; int tmap_qtrk[160]; byte buf[160]; Woz_info *wozinfo_ptr, *in_wozinfo_ptr; Trk *trkptr; byte *in_wozptr, *wozptr, *trks_bufptr; dword64 dval; word32 type, woz_size, num_blocks, crc, track_bits; int c, num_valid_tmap, pos, offset, raw_bytes; int i, j; wozinfo_ptr = woz_malloc(0, 0); // New wozinfo in_wozptr = 0; in_wozinfo_ptr = 0; if(dsk) { in_wozinfo_ptr = dsk->wozinfo_ptr; if(!in_wozinfo_ptr) { halt_printf("Changing to WOZ format!\n"); } } if(in_wozinfo_ptr) { in_wozptr = in_wozinfo_ptr->wozptr; } printf(" START woz_new_from_woz, in_wozinfo_ptr:%p, in_wozptr:%p\n", in_wozinfo_ptr, in_wozptr); for(i = 0; i < 60; i++) { buf[i] = 0; } if(in_wozptr) { // Output the INFO chunk memcpy(&buf[0], &in_wozptr[20], 60); } else { // Output an INFO chunk for KEGS buf[3] = 1; // 1=synchronized tracks buf[4] = 1; // 1=MC3470 fake bits removed for(i = 0; i < 32; i++) { buf[5 + i] = ' '; // Creator field } memcpy(&buf[5], "KEGS", 4); // And put the revision number after it for(i = 0; i < 20; i++) { c = g_kegs_version_str[i]; if(c == 0) { break; } buf[5 + 5 + i] = c; } } buf[0] = 2; // WOZ2 version type = 2 - disk_525; // 1=5.25, 2=3.5 buf[1] = type; buf[2] = 0; // write_prot=0 if(buf[37] == 0) { buf[37] = type; // sides, re-use type } if(buf[39] == 0) { buf[39] = 16 + 16 * (type & 1); // 5.25=32, 3.5=16 } woz_append_chunk(wozinfo_ptr, 0x4f464e49, 60, &buf[0]); // INFO // TMAP for(i = 0; i < 160; i++) { buf[i] = 0xff; } num_blocks = 6; // 1280 + 256 = 6x512 trks_bufptr = malloc(num_blocks << 9); printf("trk_bufptr = %p\n", trks_bufptr); for(i = 0; i < (int)(num_blocks << 9); i++) { trks_bufptr[i] = 0; } num_valid_tmap = 0; if((dsk != 0) && (dsk->trks != 0)) { // Output all valid tracks, and set the TMAP for adjacent .25 for(i = 0; i < 160; i++) { trkptr = &(dsk->trks[i]); if(trkptr->track_bits >= 10000) { buf[i] = num_valid_tmap; if(i && (buf[i-1] == 0xff)) { buf[i-1] = num_valid_tmap; } if((i < 159) && (trkptr[1].track_bits < 10000)){ buf[i+1] = num_valid_tmap; } trks_bufptr = woz_append_a_trk(wozinfo_ptr, dsk, i, trks_bufptr, &num_blocks, &tmap_dvals[num_valid_tmap]); tmap_qtrk[num_valid_tmap] = i; printf("Did append, tmap:%04x qtrk:%04x dval:" "%016llx\n", num_valid_tmap, i, tmap_dvals[num_valid_tmap]); num_valid_tmap++; } } } woz_append_chunk(wozinfo_ptr, 0x50414d54, 160, &buf[0]); // TMAP // TRKS. Already all 0 if there are no tracks. Fill in tmap_dvals[] for(i = 0; i < num_valid_tmap; i++) { pos = 256 + i*8; dval = tmap_dvals[i]; for(j = 0; j < 8; j++) { trks_bufptr[pos + j] = dval & 0xff; dval = dval >> 8; } } woz_append_chunk(wozinfo_ptr, 0x534b5254, (num_blocks << 9) - 256, trks_bufptr + 256); // TRKS, 1280 minimum size free(trks_bufptr); // Final META chunk...just remove, we've added or removed tracks from // an original WOZ image, so the Meta data is no longer accurate wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; printf(" new wozptr:%p, woz_size:%08x\n", wozptr, woz_size); wozptr[64] = wozinfo_ptr->max_trk_blocks; // largest track crc = woz_calc_crc32(wozptr, woz_size, 12); cfg_set_le32(&wozptr[8], crc); if(dsk) { pos = 0; for(i = 0; i < 160; i++) { // Go through and fix up trks structure to match new WOZ trkptr = &(dsk->trks[i]); if((pos < num_valid_tmap) && (tmap_qtrk[pos] == i)) { // This is a valid track dval = tmap_dvals[pos]; track_bits = dval >> 32; offset = (dval & 0xffff) << 9; raw_bytes = (track_bits + 7) >> 3; if(trkptr->unix_len == 0) { free(trkptr->raw_bptr); } trkptr->raw_bptr = &wozptr[offset]; trkptr->dunix_pos = offset; trkptr->unix_len = raw_bytes; trkptr->dirty = 0; trkptr->track_bits = track_bits; if(trkptr->sync_ptr == 0) { halt_printf("sync_ptr 0 qtrk:%04x\n", i); } pos++; } else { // No longer a valid track, free any ptrs if(dsk->raw_bptr_malloc) { free(trkptr->raw_bptr); } free(trkptr->sync_ptr); trkptr->raw_bptr = 0; trkptr->sync_ptr = 0; trkptr->dunix_pos = 0; trkptr->unix_len = 0; trkptr->dirty = 0; trkptr->track_bits = 0; } } dsk->raw_bptr_malloc = 0; if(dsk->raw_data) { free(dsk->raw_data); dsk->raw_data = wozptr; dsk->raw_dsize = woz_size; dsk->dimage_start = 0; dsk->dimage_size = woz_size; in_wozptr = 0; } free(in_wozptr); free(in_wozinfo_ptr); if(in_wozinfo_ptr == 0) { dsk->write_through_to_unix = 0; halt_printf("Force write_through_to_unix since image " "changed to WOZ format\n"); } } printf(" END woz_new_from_woz, wozinfo_ptr:%p wozptr:%p\n", wozinfo_ptr, wozinfo_ptr->wozptr); return wozinfo_ptr; } int woz_new(int fd, const char *str, int size_kb) { Woz_info *wozinfo_ptr; byte *wozptr; word32 size, woz_size; int disk_525; disk_525 = 0; if(size_kb <= 140) { disk_525 = 1; } wozinfo_ptr = woz_new_from_woz(0, disk_525); wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; size = (word32)must_write(fd, wozptr, woz_size); free(wozptr); free(wozinfo_ptr); if(size != woz_size) { return -1; } if(str) { // Avoid unused var warning } return 0; } void woz_maybe_reparse(Disk *dsk) { Woz_info *wozinfo_ptr; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { if(wozinfo_ptr->reparse_needed) { woz_reparse_woz(dsk); } } } void woz_set_reparse(Disk *dsk) { Woz_info *wozinfo_ptr; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { wozinfo_ptr->reparse_needed = 1; } else { woz_reparse_woz(dsk); } } void woz_reparse_woz(Disk *dsk) { Woz_info *wozinfo_ptr; #if 0 printf("In woz_reparse_woz, showing track 0\n"); iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); iwm_show_stats(3); #endif wozinfo_ptr = woz_new_from_woz(dsk, dsk->disk_525); // This wozinfo_ptr has reparse_needed==0 dsk->wozinfo_ptr = wozinfo_ptr; if(!dsk->raw_data && dsk->write_through_to_unix) { (void)!ftruncate(dsk->fd, wozinfo_ptr->woz_size); (void)cfg_write_to_fd(dsk->fd, wozinfo_ptr->wozptr, 0, wozinfo_ptr->woz_size); printf("did ftruncate and write of WOZ to %s\n", dsk->name_ptr); } // Need to recalculate dsk->cur_track_bits, cur_trk_ptr dsk->cur_trk_ptr = 0; iwm_move_to_ftrack(dsk, dsk->cur_frac_track, 0, 0); #if 0 printf("End of woz_reparse_woz, showing track 0\n"); iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); iwm_show_stats(3); #endif woz_check_file(dsk); printf("woz_reparse_woz complete!\n"); } void woz_remove_a_track(Disk *dsk, word32 qtr_track) { Trk *trkptr; printf("woz_remove_track: %s qtr_track:%03x\n", dsk->name_ptr, qtr_track); trkptr = &(dsk->trks[qtr_track]); trkptr->track_bits = 0; // Track invalid woz_set_reparse(dsk); } word32 woz_add_a_track(Disk *dsk, word32 qtr_track) { Trk *trkptr, *other_trkptr; byte *bptr, *other_bptr, *sync_ptr, *other_sync_ptr; word32 track_bits, val; int raw_bytes; int i; // Return track_bits for the new track trkptr = &(dsk->trks[qtr_track]); other_trkptr = 0; if((qtr_track > 0) && (trkptr[-1].track_bits > 0)) { // Copy this track other_trkptr = trkptr - 1; } else if((qtr_track < 159) && (trkptr[1].track_bits > 0)) { other_trkptr = trkptr + 1; } other_trkptr = 0; // HACK if(dsk->disk_525 && other_trkptr) { // We're .25 tracks away from a valid track, copy it's data track_bits = other_trkptr->track_bits; raw_bytes = (track_bits + 7) >> 3; trkptr->track_bits = track_bits; trkptr->raw_bptr = malloc(raw_bytes + 8); trkptr->sync_ptr = malloc(raw_bytes + 8); printf(" add a track, copy bptr:%p sync_ptr:%p size:%08x\n", trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); bptr = trkptr->raw_bptr; sync_ptr = trkptr->sync_ptr; other_bptr = other_trkptr->raw_bptr; other_sync_ptr = other_trkptr->sync_ptr; for(i = 0; i < raw_bytes; i++) { bptr[i] = other_bptr[i]; sync_ptr[i] = other_sync_ptr[i]; } } else { track_bits = iwm_get_default_track_bits(dsk, qtr_track); raw_bytes = (track_bits + 7) >> 3; trkptr->track_bits = track_bits; trkptr->raw_bptr = malloc(raw_bytes + 8); trkptr->sync_ptr = malloc(raw_bytes + 8); printf(" add a track, raw_bptr:%p sync_ptr:%p size:%08x\n", trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); bptr = trkptr->raw_bptr; sync_ptr = trkptr->sync_ptr; for(i = 0; i < raw_bytes; i++) { val = ((i >> 6) ^ i) & 0x7f; if(((val & 0xf0) == 0) || ((val & 0x0f) == 0)) { val |= 0x21; } bptr[i] = val; sync_ptr[i] = 0xff; } bptr[raw_bytes - 1] = 0; iwm_recalc_sync_from(dsk, qtr_track, 0, 0); } trkptr->dunix_pos = 0; trkptr->unix_len = 0; // Mark as a newly created trk trkptr->dirty = 0; printf("woz_add_new_track: %s qtr_track:%03x\n", dsk->name_ptr, qtr_track); woz_set_reparse(dsk); return track_bits; } ================================================ FILE: gsplus/src/xdriver.c ================================================ /**********************************************************************/ /* GSplus - Apple //gs Emulator */ /* Based on KEGS by Kent Dickey */ /* Copyright 2002-2025 Kent Dickey */ /* Copyright 2025-2026 GSplus Contributors */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /**********************************************************************/ # if !defined(__CYGWIN__) && !defined(__POWERPC__) /* No shared memory on Cygwin */ # define X_SHARED_MEM #endif /* CYGWIN */ #include #include #include #include #include #include #include #ifdef X_SHARED_MEM # include # include # include #endif int XShmQueryExtension(Display *display); void _XInitImageFuncPtrs(XImage *xim); #include "defc.h" extern int g_video_scale_algorithm; extern int g_audio_enable; typedef struct windowinfo { XShmSegmentInfo *seginfo; XImage *xim; Kimage *kimage_ptr; // KEGS Image pointer for window content char *name_str; Window x_win; GC x_winGC; Atom delete_atom; int x_use_shmem; int active; int width_req; int pixels_per_line; int main_height; int full_min_width; int full_min_height; } Window_info; #include "protos_xdriver.h" int g_x_warp_x = 0; int g_x_warp_y = 0; int g_x_warp_pointer = 0; int g_x_hide_pointer = 0; extern int Verbose; int g_x_screen_depth = 24; int g_x_screen_mdepth = 32; int g_x_max_width = 0; int g_x_max_height = 0; int g_screen_num = 0; extern int _Xdebug; int g_auto_repeat_on = -1; Display *g_display = 0; Visual *g_vis = 0; Colormap g_default_colormap = 0; Window_info g_mainwin_info = { 0 }; Window_info g_debugwin_info = { 0 }; Cursor g_cursor; Pixmap g_cursor_shape; Pixmap g_cursor_mask; XColor g_xcolor_black = { 0, 0x0000, 0x0000, 0x0000, DoRed|DoGreen|DoBlue, 0 }; XColor g_xcolor_white = { 0, 0xffff, 0xffff, 0xffff, DoRed|DoGreen|DoBlue, 0 }; const char *g_x_selection_strings[3] = { // If we get SelectionRequests with target=Atom("TARGETS"), then // send XA_STRING plus this list to say what we can provide for copy "UTF8_STRING", "text/plain", "text/plain;charset=utf-8" }; int g_x_num_targets = 0; Atom g_x_targets_array[5] = { 0 }; Atom g_x_atom_targets = None; // Will be set to "TARGETS" int g_depth_attempt_list[] = { 24, 16, 15 }; #define X_EVENT_LIST_ALL_WIN \ (ExposureMask | ButtonPressMask | ButtonReleaseMask | \ OwnerGrabButtonMask | KeyPressMask | KeyReleaseMask | \ KeymapStateMask | FocusChangeMask) #define X_BASE_WIN_EVENT_LIST \ (X_EVENT_LIST_ALL_WIN | PointerMotionMask | ButtonMotionMask | \ StructureNotifyMask) #define X_A2_WIN_EVENT_LIST \ (X_BASE_WIN_EVENT_LIST) int g_num_a2_keycodes = 0; int g_x_a2_key_to_xsym[][3] = { { 0x35, XK_Escape, 0 }, { 0x7a, XK_F1, 0 }, { 0x78, XK_F2, 0 }, { 0x63, XK_F3, 0 }, { 0x76, XK_F4, 0 }, { 0x60, XK_F5, 0 }, { 0x61, XK_F6, 0 }, { 0x62, XK_F7, 0 }, { 0x64, XK_F8, 0 }, { 0x65, XK_F9, 0 }, { 0x6d, XK_F10, 0 }, { 0x67, XK_F11, 0 }, { 0x6f, XK_F12, 0 }, { 0x69, XK_F13, 0 }, { 0x6b, XK_F14, 0 }, { 0x71, XK_F15, 0 }, { 0x7f, XK_Pause, XK_Break }, { 0x32, '`', '~' }, /* Key number 18? */ { 0x12, '1', '!' }, { 0x13, '2', '@' }, { 0x14, '3', '#' }, { 0x15, '4', '$' }, { 0x17, '5', '%' }, { 0x16, '6', '^' }, { 0x1a, '7', '&' }, { 0x1c, '8', '*' }, { 0x19, '9', '(' }, { 0x1d, '0', ')' }, { 0x1b, '-', '_' }, { 0x18, '=', '+' }, { 0x33, XK_BackSpace, 0 }, { 0x72, XK_Insert, XK_Help }, /* Help? */ /* { 0x73, XK_Home, 0 }, alias XK_Home to be XK_KP_Equal! */ { 0x74, XK_Page_Up, 0 }, { 0x47, XK_Num_Lock, XK_Clear }, /* Clear */ { 0x51, XK_KP_Equal, XK_Home }, /* Note XK_Home alias! */ { 0x4b, XK_KP_Divide, 0 }, { 0x43, XK_KP_Multiply, 0 }, { 0x30, XK_Tab, 0 }, { 0x0c, 'q', 'Q' }, { 0x0d, 'w', 'W' }, { 0x0e, 'e', 'E' }, { 0x0f, 'r', 'R' }, { 0x11, 't', 'T' }, { 0x10, 'y', 'Y' }, { 0x20, 'u', 'U' }, { 0x22, 'i', 'I' }, { 0x1f, 'o', 'O' }, { 0x23, 'p', 'P' }, { 0x21, '[', '{' }, { 0x1e, ']', '}' }, { 0x2a, 0x5c, '|' }, /* backslash, bar */ { 0x75, XK_Delete, 0 }, { 0x77, XK_End, 0 }, { 0x79, XK_Page_Down, 0 }, { 0x59, XK_KP_7, XK_KP_Home }, { 0x5b, XK_KP_8, XK_KP_Up }, { 0x5c, XK_KP_9, XK_KP_Page_Up }, { 0x4e, XK_KP_Subtract, 0 }, { 0x39, XK_Caps_Lock, 0 }, { 0x00, 'a', 'A' }, { 0x01, 's', 'S' }, { 0x02, 'd', 'D' }, { 0x03, 'f', 'F' }, { 0x05, 'g', 'G' }, { 0x04, 'h', 'H' }, { 0x26, 'j', 'J' }, { 0x28, 'k', 'K' }, { 0x25, 'l', 'L' }, { 0x29, ';', ':' }, { 0x27, 0x27, '"' }, /* single quote */ { 0x24, XK_Return, 0 }, { 0x56, XK_KP_4, XK_KP_Left }, { 0x57, XK_KP_5, XK_KP_Begin }, { 0x58, XK_KP_6, XK_KP_Right }, { 0x45, XK_KP_Add, 0 }, { 0x38, XK_Shift_L, XK_Shift_R }, { 0x06, 'z', 'Z' }, { 0x07, 'x', 'X' }, { 0x08, 'c', 'C' }, { 0x09, 'v', 'V' }, { 0x0b, 'b', 'B' }, { 0x2d, 'n', 'N' }, { 0x2e, 'm', 'M' }, { 0x2b, ',', '<' }, { 0x2f, '.', '>' }, { 0x2c, '/', '?' }, { 0x3e, XK_Up, 0 }, { 0x53, XK_KP_1, XK_KP_End }, { 0x54, XK_KP_2, XK_KP_Down }, { 0x55, XK_KP_3, XK_KP_Page_Down }, { 0x36, XK_Control_L, XK_Control_R }, { 0x3a, XK_Print, XK_Sys_Req }, /* Option */ { 0x37, XK_Scroll_Lock, 0 }, /* Command */ { 0x31, ' ', 0 }, { 0x3b, XK_Left, 0 }, { 0x3d, XK_Down, 0 }, { 0x3c, XK_Right, 0 }, { 0x52, XK_KP_0, XK_KP_Insert }, { 0x41, XK_KP_Decimal, XK_KP_Separator }, { 0x4c, XK_KP_Enter, 0 }, { -1, -1, -1 } }; int main(int argc, char **argv) { int ret, mdepth; ret = parse_argv(argc, argv, 1); if(ret) { printf("kegsmain ret: %d, stopping\n", ret); exit(1); } mdepth = x_video_get_mdepth(); ret = kegs_init(mdepth, g_x_max_width, g_x_max_height, 0); printf("kegs_init done\n"); if(ret) { printf("kegs_init ret: %d, stopping\n", ret); exit(1); } x_video_init(); // This is the main loop of KEGS, when this exits, KEGS exits // run_16ms() does one video frame worth of instructions and video // updates: 17030 1MHz clock cycles. while(1) { ret = run_16ms(); if(ret != 0) { printf("run_16ms returned: %d\n", ret); break; } x_input_events(); x_update_display(&g_mainwin_info); x_update_display(&g_debugwin_info); } xdriver_end(); exit(0); } int my_error_handler(Display *display, XErrorEvent *ev) { char msg[1024]; XGetErrorText(display, ev->error_code, msg, 1000); printf("X Error code %s\n", msg); fflush(stdout); return 0; } void xdriver_end() { printf("xdriver_end\n"); if(g_display) { x_auto_repeat_on(1); XFlush(g_display); } } void x_try_xset_r() { /* attempt "xset r" */ (void)!system("xset r"); xdriver_end(); exit(5); } void x_badpipe(int signum) { /* restore normal sigpipe handling */ signal(SIGPIPE, SIG_DFL); x_try_xset_r(); } int kegs_x_io_error_handler(Display *display) { printf("kegs_x_io_error_handler called (likely window closed)\n"); g_display = 0; x_try_xset_r(); return 0; } int x_video_get_mdepth() { XWindowAttributes get_attr; int depth, len, ret, force_depth; int i; printf("Preparing X Windows graphics system\n"); ret = 0; signal(SIGPIPE, x_badpipe); signal(SIGPIPE, x_badpipe); #if 0 printf("Setting _Xdebug = 1, makes X synchronous\n"); _Xdebug = 1; #endif g_display = XOpenDisplay(NULL); if(g_display == NULL) { fprintf(stderr, "Can't open display\n"); exit(1); } vid_printf("Just opened display = %p\n", g_display); fflush(stdout); g_screen_num = DefaultScreen(g_display); get_attr.width = 0; get_attr.height = 0; ret = XGetWindowAttributes(g_display, DefaultRootWindow(g_display), &get_attr); printf("XGetWindowAttributes ret: %d\n", ret); g_x_max_width = get_attr.width; g_x_max_height = get_attr.height; printf("get_attr.width:%d, height:%d\n", g_x_max_width, g_x_max_height); len = sizeof(g_depth_attempt_list)/sizeof(int); force_depth = sim_get_force_depth(); if(force_depth > 0) { /* Only use the requested user depth */ len = 1; g_depth_attempt_list[0] = force_depth; } for(i = 0; i < len; i++) { depth = g_depth_attempt_list[i]; g_x_screen_mdepth = x_try_find_visual(depth, g_screen_num); if(g_x_screen_mdepth != 0) { break; } } if(g_x_screen_mdepth == 0) { fprintf(stderr, "Couldn't find any visuals at any depth!\n"); exit(2); } return g_x_screen_mdepth; } int x_try_find_visual(int depth, int screen_num) { XVisualInfo *visualList; XVisualInfo *v_chosen; XVisualInfo vTemplate; int visualsMatched, visual_chosen, mdepth; int i; vTemplate.screen = screen_num; vTemplate.depth = depth; visualList = XGetVisualInfo(g_display, (VisualScreenMask | VisualDepthMask), &vTemplate, &visualsMatched); vid_printf("visuals matched: %d\n", visualsMatched); if(visualsMatched == 0) { return 0; } visual_chosen = -1; for(i = 0; i < visualsMatched; i++) { printf("Visual %d\n", i); printf(" id: %08x, screen: %d, depth: %d, class: %d\n", (word32)visualList[i].visualid, visualList[i].screen, visualList[i].depth, visualList[i].class); printf(" red: %08lx, green: %08lx, blue: %08lx\n", visualList[i].red_mask, visualList[i].green_mask, visualList[i].blue_mask); printf(" cmap size: %d, bits_per_rgb: %d\n", visualList[i].colormap_size, visualList[i].bits_per_rgb); if((depth != 8) && (visualList[i].class == TrueColor)) { visual_chosen = i; break; } } if(visual_chosen < 0) { printf("Couldn't find any good visuals at depth %d!\n", depth); return 0; } printf("Chose visual: %d\n", visual_chosen); v_chosen = &(visualList[visual_chosen]); video_set_red_mask((word32)v_chosen->red_mask); video_set_green_mask((word32)v_chosen->green_mask); video_set_blue_mask((word32)v_chosen->blue_mask); video_set_palette(); // Uses above masks to initialize palettes g_x_screen_depth = depth; mdepth = depth; if(depth > 8) { mdepth = 16; } if(depth > 16) { mdepth = 32; } // XFree(visualList); -- Cannot free, still using g_vis... g_vis = v_chosen->visual; return mdepth; } void x_video_init() { int tmp_array[0x80]; Atom target; char cursor_data; int keycode, num_targets, max_targets, num; int i; printf("Opening X Window now\n"); g_num_a2_keycodes = 0; for(i = 0; i <= 0x7f; i++) { tmp_array[i] = 0; } for(i = 0; i < 0x7f; i++) { keycode = g_x_a2_key_to_xsym[i][0]; if(keycode < 0) { g_num_a2_keycodes = i; break; } else if(keycode > 0x7f) { printf("a2_key_to_xsym[%d] = %02x!\n", i, keycode); exit(2); } else { if(tmp_array[keycode]) { printf("a2_key_to_x[%d] = %02x used by %d\n", i, keycode, tmp_array[keycode] - 1); } tmp_array[keycode] = i + 1; } } g_default_colormap = XDefaultColormap(g_display, g_screen_num); if(!g_default_colormap) { printf("g_default_colormap == 0!\n"); exit(4); } /* and define cursor */ cursor_data = 0; g_cursor_shape = XCreatePixmapFromBitmapData(g_display, RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); g_cursor_mask = XCreatePixmapFromBitmapData(g_display, RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); g_cursor = XCreatePixmapCursor(g_display, g_cursor_shape, g_cursor_mask, &g_xcolor_black, &g_xcolor_white, 0, 0); XFreePixmap(g_display, g_cursor_shape); XFreePixmap(g_display, g_cursor_mask); XFlush(g_display); g_x_atom_targets = XInternAtom(g_display, "TARGETS", 0); num = sizeof(g_x_selection_strings)/sizeof(g_x_selection_strings[0]); g_x_targets_array[0] = XA_STRING; num_targets = 1; for(i = 0; i < num; i++) { target = XInternAtom(g_display, g_x_selection_strings[i], 0); if(target != None) { g_x_targets_array[num_targets++] = target; } } g_x_num_targets = num_targets; max_targets = sizeof(g_x_targets_array)/sizeof(g_x_targets_array[0]); if(num_targets > max_targets) { printf("Overflowed g_x_targets_array: %d out of %d\n", num_targets, max_targets); exit(-1); } x_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS"); x_init_window(&g_debugwin_info, video_get_kimage(1), "KEGS Debugger"); x_create_window(&g_mainwin_info); vid_printf("Set error handler to my_x_handler\n"); XSetIOErrorHandler(kegs_x_io_error_handler); } void x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str) { int width, height, x_use_shmem, ret; height = video_get_x_height(kimage_ptr); width = video_get_x_width(kimage_ptr); win_info_ptr->seginfo = 0; win_info_ptr->xim = 0; win_info_ptr->kimage_ptr = kimage_ptr; win_info_ptr->name_str = name_str; win_info_ptr->x_win = 0; win_info_ptr->x_winGC = 0; win_info_ptr->delete_atom = 0; win_info_ptr->active = 0; win_info_ptr->x_use_shmem = 0; win_info_ptr->width_req = width; win_info_ptr->pixels_per_line = width; win_info_ptr->main_height = height; win_info_ptr->full_min_width = width; win_info_ptr->full_min_height = height; vid_printf("init win %p (main:%p) width:%d, height:%d\n", win_info_ptr, &g_mainwin_info, width, height); x_use_shmem = 0; // Allow cmd-line args to force shmem off /* Check for XShm */ #ifdef X_SHARED_MEM if(sim_get_use_shmem()) { ret = XShmQueryExtension(g_display); if(ret == 0) { printf("XShmQueryExt ret: %d, not using shared mem\n", ret); } else { vid_printf("Will use shared memory for X\n"); x_use_shmem = 1; } } #endif win_info_ptr->x_use_shmem = x_use_shmem; video_update_scale(kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height, 1); } void x_create_window(Window_info *win_info_ptr) { XGCValues new_gc; XSetWindowAttributes win_attr; Window x_win; GC x_winGC; word32 create_win_list; int width, height, x_xpos, x_ypos; win_attr.event_mask = X_A2_WIN_EVENT_LIST; win_attr.colormap = g_default_colormap; win_attr.backing_store = WhenMapped; win_attr.border_pixel = 1; win_attr.background_pixel = 0; if(g_x_warp_pointer) { win_attr.cursor = g_cursor; } else { win_attr.cursor = None; } vid_printf("About to win, depth: %d\n", g_x_screen_depth); fflush(stdout); create_win_list = CWEventMask | CWBackingStore | CWCursor; create_win_list |= CWColormap | CWBorderPixel | CWBackPixel; x_xpos = video_get_x_xpos(win_info_ptr->kimage_ptr); x_ypos = video_get_x_ypos(win_info_ptr->kimage_ptr); width = win_info_ptr->width_req; height = win_info_ptr->main_height; x_win = XCreateWindow(g_display, RootWindow(g_display, g_screen_num), x_xpos, x_ypos, width, height, 0, g_x_screen_depth, InputOutput, g_vis, create_win_list, &win_attr); vid_printf("x_win = %d, width:%d, height:%d\n", (int)x_win, width, height); XFlush(g_display); win_info_ptr->x_win = x_win; win_info_ptr->active = 0; video_set_active(win_info_ptr->kimage_ptr, 1); x_allocate_window_data(win_info_ptr); if(!win_info_ptr->x_use_shmem) { printf("Not using shared memory, setting skip_amt = 2, " "g_audio_enable=0\n"); video_set_redraw_skip_amt(2); g_audio_enable = 0; } x_set_size_hints(win_info_ptr); XMapRaised(g_display, x_win); if(win_info_ptr != &g_mainwin_info) { // Debugger window win_info_ptr->delete_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False); XSetWMProtocols(g_display, x_win, &(win_info_ptr->delete_atom), 1); } XSync(g_display, False); x_winGC = XCreateGC(g_display, x_win, 0, (XGCValues *) 0); win_info_ptr->x_winGC = x_winGC; win_info_ptr->active = 1; new_gc.fill_style = FillSolid; XChangeGC(g_display, x_winGC, GCFillStyle, &new_gc); /* XSync(g_display, False); */ XFlush(g_display); fflush(stdout); } int g_xshm_error = 0; int xhandle_shm_error(Display *display, XErrorEvent *event) { g_xshm_error = 1; return 0; } void x_allocate_window_data(Window_info *win_info_ptr) { int width, height; width = g_x_max_width; height = g_x_max_height; if(win_info_ptr->x_use_shmem) { win_info_ptr->x_use_shmem = 0; // Default to no shmem get_shm(win_info_ptr, width, height); } if(!win_info_ptr->x_use_shmem) { get_ximage(win_info_ptr, width, height); } } void get_shm(Window_info *win_info_ptr, int width, int height) { #ifdef X_SHARED_MEM XShmSegmentInfo *seginfo; XImage *xim; int (*old_x_handler)(Display *, XErrorEvent *); int depth, size; depth = g_x_screen_depth; // 24, actual bits per pixel seginfo = (XShmSegmentInfo *)malloc(sizeof(XShmSegmentInfo)); xim = XShmCreateImage(g_display, g_vis, depth, ZPixmap, (char *)0, seginfo, width, height); /* check mdepth, which should be 32 */ if(xim->bits_per_pixel != g_x_screen_mdepth) { printf("get_shm bits_per_pix: %d != %d\n", xim->bits_per_pixel, g_x_screen_mdepth); } vid_printf("xim: %p, DO_VERBOSE:%d, Verbose:%d, VERBOSE_VIDEO:%d\n", xim, DO_VERBOSE, Verbose, VERBOSE_VIDEO); vid_printf("xim: %p\n", xim); win_info_ptr->seginfo = seginfo; if(xim == 0) { return; } vid_printf("bytes_per_line:%d, height:%d\n", xim->bytes_per_line, xim->height); size = xim->bytes_per_line * xim->height; vid_printf("size: %d\n", size); /* It worked, we got it */ seginfo->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); vid_printf("seginfo->shmid = %d, errno:%d, %s\n", seginfo->shmid, errno, strerror(errno)); if(seginfo->shmid < 0) { XDestroyImage(xim); return; } /* Still working */ seginfo->shmaddr = (char *)shmat(seginfo->shmid, 0, 0); vid_printf("seginfo->shmaddr: %p\n", seginfo->shmaddr); if(seginfo->shmaddr == ((char *) -1)) { XDestroyImage(xim); return; } /* Still working */ xim->data = seginfo->shmaddr; seginfo->readOnly = False; vid_printf("xim->data is %p, size:%08x\n", xim->data, size); /* XShmAttach will trigger X error if server is remote, so catch it */ g_xshm_error = 0; old_x_handler = XSetErrorHandler(xhandle_shm_error); XShmAttach(g_display, seginfo); XSync(g_display, False); vid_printf("about to RMID the shmid\n"); shmctl(seginfo->shmid, IPC_RMID, 0); XFlush(g_display); XSetErrorHandler(old_x_handler); if(g_xshm_error) { XDestroyImage(xim); /* We could release the shared mem segment, but by doing the */ /* RMID, it will go away when we die now, so just leave it */ printf("Not using shared memory\n"); return; } width = xim->bytes_per_line; win_info_ptr->pixels_per_line = width / 4; win_info_ptr->xim = xim; win_info_ptr->x_use_shmem = 1; vid_printf("Sharing memory. xim: %p, xim->data: %p, width:%d\n", xim, xim->data, win_info_ptr->pixels_per_line); #endif /* X_SHARED_MEM */ } void get_ximage(Window_info *win_info_ptr, int width, int height) { XImage *xim; byte *ptr; int depth, mdepth, size; depth = g_x_screen_depth; mdepth = g_x_screen_mdepth; size = (width * height * mdepth) >> 3; printf("Get_ximage, w:%d, h:%d, mdepth:%d, size:%08x\n", width, height, mdepth, size); ptr = (byte *)malloc(size); vid_printf("ptr: %p\n", ptr); if(ptr == 0) { printf("malloc for data failed, mdepth: %d\n", mdepth); exit(2); } win_info_ptr->pixels_per_line = width; xim = XCreateImage(g_display, g_vis, depth, ZPixmap, 0, (char *)ptr, width, height, 8, 0); #ifdef KEGS_BIG_ENDIAN xim->byte_order = MSBFirst; #else xim->byte_order = LSBFirst; #endif _XInitImageFuncPtrs(xim); /* adjust to new byte order */ /* check mdepth! */ if(xim->bits_per_pixel != mdepth) { printf("shm_ximage bits_per_pix: %d != %d\n", xim->bits_per_pixel, mdepth); } vid_printf("xim: %p\n", xim); win_info_ptr->xim = xim; win_info_ptr->x_use_shmem = 0; return; } void x_set_size_hints(Window_info *win_info_ptr) { XSizeHints my_winSizeHints; XClassHint my_winClassHint; XTextProperty my_winText; Kimage *kimage_ptr; int width, height, a2_width, a2_height; width = win_info_ptr->width_req; height = win_info_ptr->main_height; kimage_ptr = win_info_ptr->kimage_ptr; video_update_scale(kimage_ptr, width, height, 0); a2_width = video_get_a2_width(kimage_ptr); a2_height = video_get_a2_height(kimage_ptr); XStringListToTextProperty(&(win_info_ptr->name_str), 1, &my_winText); my_winSizeHints.flags = PSize | PMinSize | PMaxSize | PAspect; my_winSizeHints.width = width; my_winSizeHints.height = height; my_winSizeHints.min_width = a2_width; my_winSizeHints.min_height = a2_height; my_winSizeHints.max_width = g_x_max_width; my_winSizeHints.max_height = g_x_max_height; my_winSizeHints.min_aspect.x = a2_width - 1; my_winSizeHints.min_aspect.y = a2_height; my_winSizeHints.max_aspect.x = a2_width + 1; my_winSizeHints.max_aspect.y = a2_height; my_winClassHint.res_name = win_info_ptr->name_str; my_winClassHint.res_class = win_info_ptr->name_str; XSetWMProperties(g_display, win_info_ptr->x_win, &my_winText, &my_winText, 0, 0, &my_winSizeHints, 0, &my_winClassHint); // printf("Did XSetWMProperties w:%d h:%d\n", width, height); } void x_resize_window(Window_info *win_info_ptr) { Kimage *kimage_ptr; int x_width, x_height, ret; kimage_ptr = win_info_ptr->kimage_ptr; x_width = video_get_x_width(kimage_ptr); x_height = video_get_x_height(kimage_ptr); win_info_ptr->main_height = MY_MIN(x_height, g_x_max_height); win_info_ptr->width_req = MY_MIN(x_width, g_x_max_width); ret = XResizeWindow(g_display, win_info_ptr->x_win, x_width, x_height); if(0) { printf("XResizeWindow ret:%d, w:%d, h:%d\n", ret, x_width, x_height); } } void x_update_display(Window_info *win_info_ptr) { Change_rect rect; int did_copy, valid, x_active, a2_active; int i; // Update active state a2_active = video_get_active(win_info_ptr->kimage_ptr); x_active = win_info_ptr->active; if(x_active && !a2_active) { // We need to unmap this window XUnmapWindow(g_display, win_info_ptr->x_win); x_active = 0; win_info_ptr->active = x_active; } if(!x_active && a2_active) { // We need to map this window (and maybe create it) if(win_info_ptr->xim == 0) { x_create_window(win_info_ptr); } XMapWindow(g_display, win_info_ptr->x_win); x_active = 1; win_info_ptr->active = x_active; } if(x_active == 0) { return; } if(video_change_aspect_needed(win_info_ptr->kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height)) { x_resize_window(win_info_ptr); } did_copy = 0; for(i = 0; i < MAX_CHANGE_RECTS; i++) { valid = video_out_data(win_info_ptr->xim->data, win_info_ptr->kimage_ptr, win_info_ptr->pixels_per_line, &rect, i); if(!valid) { break; } #if 0 if(win_info_ptr == &g_debugwin_info) { printf(" i:%d valid:%d, w:%d h:%d\n", i, valid, rect.width, rect.height); } #endif did_copy = 1; #ifdef X_SHARED_MEM if(win_info_ptr->x_use_shmem) { XShmPutImage(g_display, win_info_ptr->x_win, win_info_ptr->x_winGC, win_info_ptr->xim, rect.x, rect.y, rect.x, rect.y, rect.width, rect.height, False); } #endif if(!win_info_ptr->x_use_shmem) { XPutImage(g_display, win_info_ptr->x_win, win_info_ptr->x_winGC, win_info_ptr->xim, rect.x, rect.y, rect.x, rect.y, rect.width, rect.height); } } if(did_copy) { XFlush(g_display); } } Window_info * x_find_xwin(Window in_win) { if(g_mainwin_info.kimage_ptr) { if(g_mainwin_info.x_win == in_win) { return &g_mainwin_info; } } if(g_debugwin_info.kimage_ptr) { if(g_debugwin_info.x_win == in_win) { return &g_debugwin_info; } } printf("in_win:%d not found\n", (int)in_win); exit(1); } #define KEYBUFLEN 128 int g_num_check_input_calls = 0; int g_check_input_flush_rate = 2; // https://stackoverflow.com/questions/72236711/trouble-with-xsetselectionowner // See answer from "n.m" on May 17th. void x_send_copy_data(Window_info *win_info_ptr) { printf("x_send_copy_data!\n"); XSetSelectionOwner(g_display, XA_PRIMARY, win_info_ptr->x_win, CurrentTime); (void)cfg_text_screen_str(); } void x_handle_copy(XSelectionRequestEvent *req_ev_ptr) { byte *bptr; int ret; bptr = (byte *)cfg_get_current_copy_selection(); ret = XChangeProperty(g_display, req_ev_ptr->requestor, req_ev_ptr->property, req_ev_ptr->target, 8, PropModeReplace, bptr, strlen((char *)bptr)); // req_ev_ptr->target is either XA_STRING, or equivalent if(0) { // Seems to return 1, BadRequest always, but it works printf("XChangeProperty ret: %d\n", ret); } } void x_handle_targets(XSelectionRequestEvent *req_ev_ptr) { int ret; // Tell the other client what targets we can supply from // g_x_targets_array[] ret = XChangeProperty(g_display, req_ev_ptr->requestor, req_ev_ptr->property, XA_ATOM, 32, PropModeReplace, (byte *)&g_x_targets_array[0], g_x_num_targets); if(0) { // Seems to return 1, BadRequest always, but it works printf("XChangeProperty TARGETS ret: %d\n", ret); } } void x_request_paste_data(Window_info *win_info_ptr) { printf("Pasting selection\n"); // printf("Calling XConvertSelection\n"); XConvertSelection(g_display, XA_PRIMARY, XA_STRING, XA_STRING, win_info_ptr->x_win, CurrentTime); // This will cause a SelectionNotify event, and we get the data // by using XGetWindowProperty on our own window. This will eventually // call x_handle_paste(). } void x_handle_paste(Window w, Atom property) { byte *bptr; Atom sel_type; unsigned long sel_nitems, sel_bytes_after; long sel_length; int sel_format, ret, ret2, c; int i; sel_length = 16384; sel_type = 0; sel_format = 0; sel_nitems = 0; sel_bytes_after = 0; bptr = 0; ret = XGetWindowProperty(g_display, w, property, 0, sel_length, 1, AnyPropertyType, &sel_type, &sel_format, &sel_nitems, &sel_bytes_after, &bptr); #if 0 printf("XGetWindowProperty ret:%d, sel_type:%ld, sel_format:%d, " "sel_nitems:%ld, sel_bytes_after:%ld, bptr:%p\n", ret, sel_type, sel_format, sel_nitems, sel_bytes_after, bptr); #endif if(bptr && (sel_type == property) && sel_nitems && (sel_format == 8)) { //printf("bptr: %s\n", (char *)bptr); for(i = 0; i < sel_nitems; i++) { c = bptr[i]; ret2 = adb_paste_add_buf(c); if(ret2) { printf("Paste buffer full!\n"); break; } } } if(ret == 0) { XFree(bptr); } } int x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid) { Kimage *kimage_ptr; int x, y, ret; if((button_states & buttons_valid & 2) == 2) { // Middle button: Paste request x_request_paste_data(win_info_ptr); button_states = button_states & (~2); } kimage_ptr = win_info_ptr->kimage_ptr; x = video_scale_mouse_x(kimage_ptr, raw_x, 0); y = video_scale_mouse_y(kimage_ptr, raw_y, 0); if(g_x_warp_pointer && (raw_x == g_x_warp_x) && (raw_y == g_x_warp_y) && (buttons_valid == 0) ) { /* tell adb routs to recenter but ignore this motion */ adb_update_mouse(kimage_ptr, x, y, 0, -1); return 0; } ret = adb_update_mouse(kimage_ptr, x, y, button_states, buttons_valid & 7); return ret; } void x_input_events() { XEvent ev, response; XSelectionRequestEvent *req_ev_ptr; Window_info *win_info_ptr; char *str; int len, motion, key_or_mouse, refresh_needed, buttons, hide, warp; int width, height, resp_property, match, x_xpos, x_ypos; int i; str = 0; if(str) { // Use str } if(adb_get_copy_requested()) { x_send_copy_data(&g_mainwin_info); } g_num_check_input_calls--; if(g_num_check_input_calls < 0) { len = XPending(g_display); g_num_check_input_calls = g_check_input_flush_rate; } else { len = QLength(g_display); } motion = 0; win_info_ptr = 0; refresh_needed = 0; key_or_mouse = 0; while(len > 0) { XNextEvent(g_display, &ev); len--; if(len == 0) { /* Xaccel on linux only buffers one X event */ /* must look for more now */ len = XPending(g_display); } switch(ev.type) { case FocusIn: case FocusOut: win_info_ptr = x_find_xwin(ev.xfocus.window); if(ev.xfocus.type == FocusOut) { /* Allow keyrepeat again! */ vid_printf("Left window, auto repeat on\n"); x_auto_repeat_on(0); } else if(ev.xfocus.type == FocusIn && (win_info_ptr == &g_mainwin_info)) { /* Allow keyrepeat again! */ vid_printf("Enter window, auto repeat off\n"); x_auto_repeat_off(0); } break; case EnterNotify: case LeaveNotify: /* These events are disabled now */ printf("Enter/Leave event for winow %08x, sub: %08x\n", (word32)ev.xcrossing.window, (word32)ev.xcrossing.subwindow); printf("Enter/L mode: %08x, detail: %08x, type:%02x\n", ev.xcrossing.mode, ev.xcrossing.detail, ev.xcrossing.type); break; case ButtonPress: win_info_ptr = x_find_xwin(ev.xbutton.window); vid_printf("Got button press of button %d!\n", ev.xbutton.button); buttons = (1 << ev.xbutton.button) >> 1; motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, ev.xbutton.y, buttons, buttons & 7); key_or_mouse = 1; break; case ButtonRelease: win_info_ptr = x_find_xwin(ev.xbutton.window); buttons = (1 << ev.xbutton.button) >> 1; motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, ev.xbutton.y, 0, buttons & 7); key_or_mouse = 1; break; case MotionNotify: win_info_ptr = x_find_xwin(ev.xmotion.window); motion |= x_update_mouse(win_info_ptr, ev.xmotion.x, ev.xmotion.y, 0, 0); key_or_mouse = 1; break; case Expose: win_info_ptr = x_find_xwin(ev.xexpose.window); refresh_needed = -1; //printf("Got an Expose event\n"); break; case NoExpose: /* do nothing */ break; case KeyPress: case KeyRelease: x_handle_keysym(&ev); key_or_mouse = 1; break; case KeymapNotify: break; case DestroyNotify: win_info_ptr = x_find_xwin(ev.xdestroywindow.window); video_set_active(win_info_ptr->kimage_ptr, 0); win_info_ptr->active = 0; printf("Destroy %s\n", win_info_ptr->name_str); if(win_info_ptr == &g_mainwin_info) { x_try_xset_r(); // Mainwin: quit BURST } break; case ReparentNotify: case UnmapNotify: case MapNotify: break; case ClientMessage: win_info_ptr = x_find_xwin(ev.xclient.window); if(ev.xclient.data.l[0] == win_info_ptr->delete_atom) { // This is a WM_DELETE_WINDOW event // Just unmap the window win_info_ptr->kimage_ptr->active = 0; } else { printf("unknown ClientMessage\n"); } break; case ConfigureNotify: win_info_ptr = x_find_xwin(ev.xconfigure.window); width = ev.xconfigure.width; height = ev.xconfigure.height; #if 0 printf("ConfigureNotify, width:%d, height:%d\n", width, height); #endif video_update_scale(win_info_ptr->kimage_ptr, width, height, 0); x_xpos = ev.xconfigure.x; x_ypos = ev.xconfigure.y; video_update_xpos_ypos(win_info_ptr->kimage_ptr, x_xpos, x_ypos); break; case SelectionRequest: //printf("SelectionRequest received\n"); req_ev_ptr = &(ev.xselectionrequest); // This is part of the dance for copy: Another client // is asking us what format we can supply (TARGETS), // or is doing to tell us one at a time what types // it would like. #if 0 printf("req:%ld, property:%ld, target:%ld, " "selection:%ld\n", req_ev_ptr->requestor, req_ev_ptr->property, req_ev_ptr->target, req_ev_ptr->selection); str = XGetAtomName(g_display, req_ev_ptr->target); printf("XAtom target str: %s\n", str); XFree(str); str = XGetAtomName(g_display, req_ev_ptr->property); printf("XAtom property str: %s\n", str); XFree(str); #endif resp_property = None; match = 0; for(i = 0; i < g_x_num_targets; i++) { if(req_ev_ptr->target == g_x_targets_array[i]) { match = 1; break; } } if(match) { x_handle_copy(req_ev_ptr); resp_property = req_ev_ptr->property; } else if(req_ev_ptr->target == g_x_atom_targets) { // Some other agent is asking us "TARGETS", // so send our list of targets x_handle_targets(req_ev_ptr); } // But no matter what the request target was, respond // so it will send an eventual request for XA_STRING response.xselection.type = SelectionNotify; response.xselection.display = req_ev_ptr->display; response.xselection.requestor = req_ev_ptr->requestor; response.xselection.selection = req_ev_ptr->selection; response.xselection.target = req_ev_ptr->target; response.xselection.property = resp_property; response.xselection.time = req_ev_ptr->time; XSendEvent(g_display, req_ev_ptr->requestor, 0, 0, &response); XFlush(g_display); // Speed up getting more resp break; case SelectionNotify: // We get this event after we requested the PRIMARY // selection, so paste this to adb(). vid_printf("SelectionNotify received\n"); vid_printf("req:%ld, selection:%ld, target:%ld, " "property:%ld\n", ev.xselection.requestor, ev.xselection.selection, ev.xselection.target, ev.xselection.property); if(ev.xselection.property == None) { printf("No selection\n"); break; } x_handle_paste(ev.xselection.requestor, ev.xselection.property); break; default: printf("X event 0x%08x is unknown!\n", ev.type); break; } } if(key_or_mouse && (win_info_ptr == &g_mainwin_info)) { hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); if(warp != g_x_warp_pointer) { motion = 1; } g_x_warp_pointer = warp; if(g_x_hide_pointer != hide) { x_hide_pointer(&g_mainwin_info, hide); } g_x_hide_pointer = hide; } if(motion && g_x_warp_pointer && (win_info_ptr == &g_mainwin_info)) { // Calculate where to warp to g_x_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, BASE_MARGIN_LEFT + (BASE_WINDOW_WIDTH/2), 0); g_x_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); XWarpPointer(g_display, None, win_info_ptr->x_win, 0, 0, 0, 0, g_x_warp_x, g_x_warp_y); } if(refresh_needed && win_info_ptr) { //printf("...at end, refresh_needed:%d\n", refresh_needed); video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); } } void x_hide_pointer(Window_info *win_info_ptr, int do_hide) { if(do_hide) { // invisible XDefineCursor(g_display, win_info_ptr->x_win, g_cursor); } else { // Default cursor XDefineCursor(g_display, win_info_ptr->x_win, None); } } void x_handle_keysym(XEvent *xev_in) { Window_info *win_info_ptr; KeySym keysym; word32 state; int keycode, a2code, type, is_up; win_info_ptr = x_find_xwin(xev_in->xkey.window); keycode = xev_in->xkey.keycode; type = xev_in->xkey.type; keysym = XLookupKeysym(&(xev_in->xkey), 0); state = xev_in->xkey.state; vid_printf("keycode: %d, type: %d, state:%d, sym: %08x\n", keycode, type, state, (word32)keysym); x_update_modifier_state(win_info_ptr, state); is_up = 0; if(type == KeyRelease) { is_up = 1; } /* first, do conversions */ switch(keysym) { case XK_Alt_L: case XK_Meta_R: // Windows key on right side case XK_Super_R: case XK_Mode_switch: case XK_Cancel: keysym = XK_Print; /* option */ break; case XK_Meta_L: // Windows key on left side case XK_Alt_R: case XK_Super_L: case XK_Menu: keysym = XK_Scroll_Lock; /* cmd */ break; case XK_F5: break; case XK_F10: if(!is_up) { g_video_scale_algorithm++; if(g_video_scale_algorithm >= 3) { g_video_scale_algorithm = 0; } printf("g_video_scale_algorithm = %d\n", g_video_scale_algorithm); video_update_scale(win_info_ptr->kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height, 1); } break; case 0x1000003: if(keycode == 0x3c) { /* enter key on Mac OS X laptop--make it option */ keysym = XK_Print; } break; case NoSymbol: switch(keycode) { /* 94-95 are for my PC101 kbd + windows keys on HPUX */ case 0x0095: /* left windows key = option */ keysym = XK_Print; break; case 0x0096: case 0x0094: /* right windows key = cmd */ keysym = XK_Scroll_Lock; break; /* 0072 is for cra@WPI.EDU who says it's Break under XFree86 */ case 0x0072: /* 006e is break according to mic@research.nj.nec.com */ case 0x006e: keysym = XK_Break; break; /* 0x0042, 0x0046, and 0x0048 are the windows keys according */ /* to Geoff Weiss on Solaris x86 */ case 0x0042: case 0x0046: /* flying windows == open apple */ keysym = XK_Scroll_Lock; break; case 0x0048: case 0x0076: /* Windows menu key on Mac OS X */ /* menu windows == option */ keysym = XK_Print; break; } } a2code = x_keysym_to_a2code(win_info_ptr, (int)keysym, is_up); if(a2code >= 0) { adb_physical_key_update(win_info_ptr->kimage_ptr, a2code, 0, is_up); } else if(a2code != -2) { printf("Keysym: %04x of keycode: %02x unknown\n", (word32)keysym, keycode); } } int x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up) { int i; if(keysym == 0) { return -1; } /* Look up Apple 2 keycode */ for(i = g_num_a2_keycodes - 1; i >= 0; i--) { if((keysym == g_x_a2_key_to_xsym[i][1]) || (keysym == g_x_a2_key_to_xsym[i][2])) { vid_printf("Found keysym:%04x = a[%d] = %04x or %04x\n", (int)keysym, i, g_x_a2_key_to_xsym[i][1], g_x_a2_key_to_xsym[i][2]); return g_x_a2_key_to_xsym[i][0]; } } return -1; } void x_update_modifier_state(Window_info *win_info_ptr, int state) { word32 c025_val; c025_val = 0; if(state & ShiftMask) { c025_val |= 1; } if(state & ControlMask) { c025_val |= 2; } if(state & LockMask) { c025_val |= 4; } adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); } void x_auto_repeat_on(int must) { if((g_auto_repeat_on <= 0) || must) { g_auto_repeat_on = 1; XAutoRepeatOn(g_display); XFlush(g_display); adb_kbd_repeat_off(); } } void x_auto_repeat_off(int must) { if((g_auto_repeat_on != 0) || must) { XAutoRepeatOff(g_display); XFlush(g_display); g_auto_repeat_on = 0; adb_kbd_repeat_off(); } } void x_full_screen(int do_full) { return; } ================================================ FILE: upstream/KEGS.version ================================================ kegs.1.38 ================================================ FILE: upstream/kegs/config.kegs ================================================ # KEGS configuration file version 1.07 s5d1 = XMAS_DEMO.gz s5d2 = s6d1 = #dos33.dsk s6d2 = s7d1 = NUCLEUS03.gz g_limit_speed = 2 ================================================ FILE: upstream/kegs/doc/CHANGES.txt ================================================ Changes in KEGS v1.38 since v1.35 (04/29/25) - Improve serial port handling so BBS'es like GBBS and Warp6 run. - Fix a crash reported by Tom Charlesworth where resizing the Debugger window would crash on Windows. - Fix a timing anomaly also reported by Tom Charlesworth where 3- and 4-byte instructions just before a page crossing would take too many cycles to execute. - Add Alex Lee's icon on the Mac. Changes in KEGS v1.35 since v1.34 (01/07/25) - Fix handling of .zip files, it somehow got broken so subdirectories in zip files weren't selectable. - DCD fixes for the virtual modem, and fix CONNECT terse response code to try to support the Warp6 BBS. Changes in KEGS v1.34 since v1.33 (01/15/24) - Fix bug where no config.kegs file could sometimes cause a crash. - Richard Bennett fixes: Change menus to be retina, and add About dialog on a Mac - Save the main window size and position in config.kegs (but not automatically) Changes in KEGS v1.33 since v1.32 (12/10/23) - Add command line argument support to set any disk (-s5d1=Data.po or -s7d12 bigdisk.hdv) or any knob that is listed in config.kegs. - Add -cfg path_and_name_of_config_kegs file to use a particular config.kegs file. - Fix Code Red when leaving the configuration screen (F4) using the last menu item "Exit Config". - Allow pasting of control-characters such as Ctrl-D and Ctrl-H. Changes in KEGS v1.32 since v1.31 (11/22/23) - Fix (dloc,x) in emulation mode to wrap as described at https://github.com/gilyon/snes-tests/tree/main/cputest - Improve virtual hard drive in slot 7 to use a small driver at $C700 which uses WDM $C7,$00 to call back to KEGS for handling commands. - Improve SCC8530 emulation to add support for RTxC as clock for higher speeds. - Fix a GS/OS visual anomaly with the mouse cursor sometimes disappearing when moving upwards, due to Scanline interrupts not being taken properly. - Support qkumba's code to run from $C050 by having get_remaing_opcodes() track time properly, and to have reads to $C050-$C057 call float_bus() before doing the softswitch action. Changes in KEGS v1.31 since v1.30 (11/04/23) - Fix Windows failure where KEGS would quit on startup if config.kegs contained a new ROM path. - Fix a Code Red halt running the Printer57.6 driver where KEGS thought it might need to generate a baud rate event every .5 cycles. - Fix disk image selection screen bug where s7d10-s7d12 could wrap and make it hard to leave the screen. - Add a Slinky RAM card in slot 4 (with no firmware), works even with Mockingboard. - Fix scanline interrupts which were happening too early starting with version 1.24. - Another false read bug was causing 16-bit RMW cycles to read the next address (which is incorrect). Changes in KEGS v1.30 since v1.29 (09/23/23) - Proper emulation of the $C080-$C08F language card soft switches. - Improved INTC8ROM emulation, so a2audit passes (with Apple //e ROMs) - Fix SCC RR2B register emulation for Colin Leroy-Mira's telnet.system. - Fix SCC remote IP mode to restart the connection if the remote side ended it due to idleness, to better handle connecting to printers. - RAMRD/RAMWRT/ST80COL/etc. apply to bank $E0 as well as to bank $00. This was not emulated properly before. This fixes the AppleLink Terminal application. Changes in KEGS v1.29 since v1.28 (09/05/23) - Improved disk arm emulation for 5.25" disks - Enable use of a real serial port on Linux, and improve real serial port emulation on Mac. - Add serial "outgoing IP" to allow slot 1 serial to be sent directly to a real printer (often port 9100). - When mounting an image from a .zip file, you can press Cmd-A and all subsequent images will also be mounted in consecutive drives. This is useful for the new wita2gs_0_70.zip. - Allow "unlocking" locked images from .zip files, to allow code to write to them (but it's all in memory, so all changes are lost when KEGS exits). - Fix DiskCopy4.2 image detection, to handle images of sizes other than 800KB. Changes in KEGS v1.28 since v1.27 (06/21/23) - Reduce status lines under the window from 7 lines of text to 4. - Fix Windows10 crash when KEGS was minimized (Windows set the window size to 0, which was unexpected, and led to a divide-by-0). - Enable live window resizing on Windows64. Speed up the video scaling for X11 and Windows. - Allow the ZipGS speed, which was fixed at 8MHz previously), be set to 8MHz, 16MHz, 32MHz, 64MHz, or 128MHz. - Fix a false read bug which broke SCC emulation. LDA $BFFD,X where X=$3D was "false" reading $C039, not $BF39 as it should, leading to SCC state being incorrect. Changes in KEGS v1.27 since v1.26 (06/13/23) - Ignore WDM 0xfc,0xfd,0xff to avoid HOST.FST causing Code Red. - Fix $C019 reading to match Deater's Midline demo - Add false reads for RMW instruction, (dloc),y, abs,x, and abs,y modes. Only 8-bit false reads are done currently (this only affect 16-bit RMW, where I know of nothing using the false reads). - Fix reported bug where long paths in the file selection screens didn't truncate the files so the endings could always be seen. - Allow 140K .SDK images to be selected in the image file selection screen. - Fix $C030 speaker toggle emulation to eliminate a 60-Hertz buzz caused by the code being organized around 60Hz screen refresh and not counting on the last toggle correctly in each video frame in all cases. The $C030 speaker output ramps down to 0 after about 60msec to avoid the annoying "click" 4 seconds later when KEGS pauses sound output. Changes in KEGS v1.26 since v1.25 (05/22/23) - Fix Win64 Dynapro issues (O_BINARY, setvbuf was causing a crash). - Fix a KEGS bug in the Bank $E0 memory map which could corrupt data in the Apple IIgs memory from $E0/6000 - $E0/A000 introduced in KEGS 1.20. Changes in KEGS v1.25 since v1.24 (05/21/23) - Actual Win32 support. 1.24 was only Win64. Changes in KEGS v1.24 since v1.23 (05/17/23) - Win64 support. kegswin.exe now part of the standard release. The Windows port is still beta quality. - Try to fix jerky video to make KEGS seem smoother. Changes in KEGS v1.23 since v1.22 (05/05/23) - Change the way KEGS tracks time from a double to a unsigned long long, which enables higher speeds. - Support video mode changes in the middle of lines. Changes in KEGS v1.22 since v1.21 (04/27/23) - Remove debugging printfs from iwm.c. - Fix the way video updates are done to fix Dagen Brock's HDGR demo (which switches pages to double the GR vertical resolution). Changes in KEGS v1.21 since v1.20 (04/15/23) - Fix Antoine's reported issue where KEGS would hang after ejecting 3.5" disks. Caused by a debug statement accidentally left in for the release. - Fix Stephan's reported issue with keys repeating forever with French keyboards. Changes in KEGS v1.20 since v1.19 (03/31/23) - Ctrl-F9 is now Copy. The text screen is copied to your host system clipboard. On a Mac, Edit->Copy Text Screen can be selected, too. - Fix Mockingboard emulation to pass mb-audit.1.3 (it was a reset-related issue). - Fix VOC support for unreleased "Fat Screen" VOC SHR from main-memory to work properly. Changes in KEGS v1.19 since v1.18 (03/11/23) - 'L' on the disk selection screen locks/unlocks images. - Ignore case when detecting image extensions like .PO. - Allow setting ROM image on command line: -rom=/path/to/rom/file - Big changes to disk emulation for better WOZ image compability. Writing to WOZ images works properly now, recalculating CRC correctly. Automatically changes a floppy disk image to .WOZ if writing to the image makes it no longer a standard format. User can rename image to save the new .WOZ changes. Changes in KEGS v1.18 since v1.17 (02/09/22) - Alpha version of KEGSWIN, KEGS can now run on Windows 10 or later. (No binary yet) Changes in KEGS v1.17 since v1.16 (02/09/22) - Implement $C02C "Test Mode" reading of the character ROM. This enables SuperConvert 4 TDM conversions to work properly. - Add Video->Swap Command/Option menu item to support Windows keyboards better. Changes in KEGS v1.16 since v1.14 (01/23/22) - Better cursor focus tracking, less likely to have an invisible cursor when KEGS is no longer the active window. - F5 now toggles the status display at the bottom on/off. This state can be saved in config.kegs. - Added Dynapro image support--mount a host directory as a ProDOS image of up to 32MB, to allow easy moving of files to/from emulation. See README.dynapro.txt. - Add limited Video Overlay Card (VOC) support to add new SHR mode of 640x400 (interlaced). - You can "D"uplicate any disk image to a new file, and "V"erify any ProDOS image. Changes in KEGS v1.14 since v1.13 (11/14/21) - Better support for disk images inside .zip files. - Linux sound fixes to make PULSE_AUDIO work a little better. - Better handle being run from the Finder, and go right to the Config page to select a ROM file if no ROM is found. - Add NSHighResolutionCapable=False to speed up graphics operations on some Macs - Fix serial port code to properly return the DCD status as a modem. Changes in KEGS v1.13 since v1.12 (09/23/21) - Better support for joysticks on Macs. Select Native Joystick 1. Changes in KEGS v1.12 since v1.11 (08/22/21) - Preliminary support for joysticks on Macs. Select Native Joystick 1. - Fixes for Keypad Joystick to work again (the non-US keyboard support broke them). Changes in KEGS v1.11 since v1.08 (08/19/21) - KEGS should support many non-US keyboards, converting your local keys to the US equivalents: [,],|, etc. Italian works least well, sorry. - KEGS can choose character ROMs out of a ROMX-compatible file. Use F4, "Character ROM Selection", and then pick the file and font. If you pick an unreadable font and cannot undo it for some reason, quit KEGS, and using a text editor, delete g_cfg_charrom_* lines from config.kegs. - Taking an IRQ logs the stack accesses in the datalog properly. - Fixed BRK and COP exceptions to always pull their vectors from ROM. Bug was found by Applecorn: https://github.com/bobbimanners/Applecorn Changes in KEGS v1.08 since v1.07 (06/29/21) - Fixed a stupid bug in iwm.c that would cause KEGS to crash when many WOZ images were mounted. Changes in KEGS v1.07 since v1.05 (06/26/21) - Can create new disk images while running: press N in the disk image selection screen. - Support for loading disk images directly out of .zip archives (read-only). In the disk image selection dialog, select the .zip file, and then it will step into the archive and you can select the file inside. - Major rewrite of IWM routines to now add support for WOZ version 1 and version 2 5.25" disk images. - Many Mockingboard fixes to enable mb-audit v0.7 to pass. https://github.com/tomcw/mb-audit Changes in KEGS v1.05 since v1.04 (01/24/21) - The Mac executable is now universal, support M1 silicon and x86_64. - F8 works again to confine the mouse to the KEGS window. Press F8 again to release the cursor. Changes in KEGS v1.04 since v1.03 (01/10/21) - Paste works from the host to the emulated machine. On the Mac, select text in another app, do Cmd-C, and then in KEGS select the Edit menu->Paste. On X11, select the text in another application, then in KEGS, click the middle mouse button to paste. Up to ~32KB can be pasted, but I recommend smaller amounts. - On the Mac, the Config menu item will bring up the Configuration screen (same as pressing F4). - Fix bug where Nox Archaist running on 5.25" floppies with fast_disk_emul on (which is the default) would cause KEGS to halt and not write modified data back to the disk image. Nox Archaist doesn't read the entire sector header, waits about 7 disk nibble times, then writes new sector data. This confused fast disk emulation which doesn't move the emulated disk position just by waiting. Now, when a write begins, KEGS will move the emulated disk position. - Added logpc debugger command in the F7 debugger window. KEGS can keep track of all registers after each instruction for up to 2 million instructions. This is dumped out to the file pc_log_out in the directory where config.kegs was found. In the debugger window "help logpc" gives basic help. "logpc on" turns on logging, and "logpc save" writes out the last 2 million instructions. - Reading $C020 and $C030 return a floating bus value. This fixes Beyond Castle Wolfenstain randomness as reported by Vladimir Ivanov. https://groups.google.com/g/comp.sys.apple2/c/3gH0dUpLI3Q/m/JJYnhRYBrY4J Changes in KEGS v1.03 since v1.00 (12/11/20) - This is beta quality - Add Mockingboard support. In the IIgs control panel, set slot 4 to Your Card, and KEGS will emulate a stereo Mockingboard A. - Disk images can be gzip compressed (master.dsk.gz, for example) and KEGS can load them as readonly. - .sdk disk images can be loaded directly (read only). Only the first disk image in a .sdk archive will be opened. - Debugger is now a separate window. Press F7. "bp" allows setting a range, so "bp c400-c4ff" sets breakpoints on all addresses in that range. - If the debugger window pops up, it means emulation has halted. Press 'g' and then return in the debugger window to try to restart KEGS. It may just halt again. This area needs improvement, you will likely need to quit out of KEGS and start over if 'g' a few times doesn't continue emulation. - KEGS allows resizing windows. On Linux X11, KEGS does the scaling. - Removed HP PA-RISC assembly, which simplifies compiling a bit. - 2.8MHz speed now emulated at 2.8MHz, which is a little fast, but makes some very timing-sensitive code in the NFS Megademo work better. -------------------------------------------------------------------- Changes in KEGS v1.00 since v0.91 (12/15/19) - This is pre-alpha quality - Major rewrite of how the host interacts with the KEGS emulation code. - Now supports MAC OS X Swift native compiles on Mojave (and later) -------------------------------------------------------------------- Changes in KEGS v0.91 since v0.90 (12/06/04) - Fixed serious bug in engine_c.c that could cause Finder file copies to silently corrupt data. - Virtual Modem support--modem appears on serial port, allows outgoing and incoming connections. - Sockets (and Virtual Modem) supported on Windows. - Fixed various reset bugs (where pressing Ctrl-Reset would cause infinite beeps, etc). - Allow user to select ROM file from config panel if not found. - Improved Mac OS X interface: Full Screen support and error dialogs. - Better floppy support by always having 5.25" read nearest track regardless of head position (supports Last Gladiator game bad crack by emulating other emulators). Changes in KEGS v0.90 since v0.89 (10/19/04) - Make Keypad Joystick the default joystick emulation - Fix timezone calculation on Mac OS X for central time zone. - Fix handling of long paths in config panel, reported by David Scruffham. - Always call joystick_init at startup. - Fix F2 keymappings for X Windows, to fix some issue reported by David Wilson. - Fixed some documentation issues reported by David Wilson. - Fixed a bug in joystick_driver.c reported by Doug Mitton. - Add README.a2.compatibility to discuss known issues with programs. Changes in KEGS v0.89 since v0.88 (10/17/04) - Make old mouse button presses disappear after .5 seconds. - Add Keypad Joystick, along with configuration menu choices to enable it. The keypad numbers move the joystick to the indicated direction, with chording allowing in-between values. The keypad '0' is button 0 and keypad '1' is button 1. - Also add jostick scaling factor and trim adjustment. - Allow user to increase keyboard and mouse scan rate to 240Hz from 60Hz for some better game response. Changes in KEGS v0.88 since v0.87 (10/13/04) - Add configuration setting to debug halt on code red halts. Also add configuration mode (on by default) to shadow text page 2 on ROM 01, which is an enhancement over a real IIgs. - Handle mac binary header on images. Handle compressed .po images. - Fix refresh rate to 59.923Hz from 60Hz so that exactly 17030 1MHz cycles pass in one screen refresh period. - Enhance trace-to-file function to also write out data values stored using the Data_log info. - Debugger adds memory move and memory compare functions. - Support "floating bus" where reading certain $C0xx locations returns the current video data. This allows Bob Bishop's split-screen demos to run and enables Drol's between-level animations to work fully. Changes in KEGS v0.87 since v0.86 (10/05/04) - Remove all of Jonathan Kalbfeld's and Gilles Tschopp's contributions. All of Solaris audio is removed. - Fix config screen not drawing correctly if emulator was currently displaying video page 2. - Fix STP instruction. - Fix mouse-joystick which was halving the Y dimension. Changes in KEGS v0.86 since v0.85 (03/23/04) - Add patch for Solaris sound by Jonathan Kalbfeld. - Fix so that F4 enters config panel even while running Prosel-16 - Major mouse pointer changes, based on some ideas from Geoff Weiss. The GSOS mouse now exactly tracks the host pointer automatically. - Fixed an accidental debug halt when Prosel-16 disables the keyboard/mouse. Changes in KEGS v0.85 since v0.84 (01/09/04) - Fix some minor 65816 bank-crossing bugs. - Add -noignhalt to allow user to stop on code red halts. - Fix Win32 capslock problem as reported by Edward Moore - Fixed DreamVoir app on the sample image (it was corrupt) Changes in KEGS v0.84 since v0.83 (11/21/03) - Add new speed, 8.0MHz directly using right-clicking or F6. - Sim speed and Video update interval added to Config panel. - Various cycle timing bugs in engine_c.c fixed. - Add Config Panel entry to mask serial output to 7-bit, to enable PR#2 to work better with an external telnet. - In Config Panel file selection, typing a letter jumps to the first file beginning with that letter. - Fixed various serial socket bugs. Now you can disconnect a telnet session and start a new one, and a Linux hang is fixed. - Default GS memory size increased to 8MB. - Small fix to double-hires color table. - X windows can now send displays to other-endian X servers. Changes in KEGS v0.83 since v0.82 (11/19/03) - Add Memory Size to config panel, with support for up to 14MB of memory (Geoff Weiss) - Add $C04F EMUBYTE support which Bernie II the Rescue defined. (Geoff Weiss) - Fix $CFFF code red's reported by David Wilson. - Add smartport $C70A Format routine (David Wilson). Changes in KEGS v0.82 since v0.81 (11/06/03) - Fix superhires display glitch introduced in v0.81. - Improved border handling--XMAS demo looks great. - Fix some X build problems introduced in v0.81. Changes in KEGS v0.81 since v0.80 (11/04/03) - Code Red/Yellow warnings about emulation stability - Windows file browsing fixes - Built-in C600 ROM for Apple II 5.25" game compatibility - Turns key repeat back on when exiting from X-windows version - Windows F8 captures the cursor Changes in KEGS v0.80 since v0.71 (10/31/03) - Configuration Panel means no more hand-editing configuration files - All emulator state is now saved in "config.kegs" - 3200 color pictures! Video system much improved for display accuracy. - F8 Pointer grabbing works on Mac - ZipGS emulation Changes in KEGS v0.71 since v0.70 (11/20/02) - Improved double-hires colors a lot. -dhr140 is no longer the default - Airheart relies on the PC going from 0xffff to 0x0000, so I undid the change from KEGS v0.54 which allowed PC to overflow to 0x10000. This slows KEGS down by about 5%. - Fixed X shared memory bug in KEGS v0.70 with fix from Jonathan Stark. Changes in KEGS v0.70 since v0.60 (11/18/02) - New buttons: Middle button is enter-debugger, and right button changes speed - New function key mapping (see README.kegs) - Mac OS X port - Win32 port - Centralized much of what had been "xdriver.c" code into video.c, to move true platform-specific stuff into the various *driver.c codes. Kimage struct tracks video display buffers in a dev-independent way. From video.c, the calls to the platform code start with "x_" mostly. Code in video.c cleaned up somewhat. Borders are now always in native buffer format, while text/hires/ and superhires are in 8-bit buffers and translated to native later. - Mac and Windows sound are all done in one process--no child sound process. - Revamped key press handling and mouse clicks--all is now handled in adb.c for a consistent user interface. Now KEGS implements the same function keys on all platforms. See README.kegs for fn key maps. - I copied the debugger help from Frederic Devernay's KEGS-SDL port. - Fixed an old IWM bug causing bad nibblization due to using uninit vars. - Gilles Tschopp workaround to use corrupted 2IMG files (from KEGS-OSX). - Gilles Tschopp provided code to zero //gs memory at startup (from KEGS-OSX) - Simple code to try to use Mac Diskcopy format disks - Ignore writes to 0xc0a8 - Search in $HOME and the launch directory (for mac) for kegs_conf/ROM - Remove font65.sim file by integrating it into kegsfont.h. - "-bw" option forces black and white hires mode. Changes in KEGS v0.60 since v0.59 (10/03/00) - The 16-bit colors were still wrong due to another coding error. It would be much easier to get this right if I had a 16-bit color display... A user says it works now. Changes in KEGS v0.59 since v0.58 (7/07/00) - Added support for multiple paths to the default files and also multiple names for many default files. This should make the .rpm distribution work better. - Add another keycode to mean break according to mic@research.nj.nec.com. - Add support for various ROMs to get plugged into slot 1-7. - Fix code so that it should compile on 64-bit platforms. Changes in KEGS v0.58 since v0.57 (2/08/00) - Setting the execute bit on the disk image no longer means no-write-thru. Too many new users were getting confused by this. - Fixed another bug with Apple //e bank switching created by v0.56 Reported by phoenyx. - Add command line option "-v" to turn on some verbose debugging flags. - Fixed potential core-dump bug with non-8 bit visuals. - Fixed double-lo-res color problem. - The X driver should work with any visual depth display now and get the colors right. Ian Schmidt reported his 16-bit card had bad colors. Changes in KEGS v0.57 since v0.56 (12/27/99) - Another try at making timezone stuff work across all Unix variants. Let me know if the time in the Apple //gs control panel doesn't match your real local time. - Fix a bug created in v0.56 where the fast //e bank switch code had a typo. This prevented ZBasic from working correctly. Changes in KEGS v0.56 since v0.55 (10/31/99) - Faster Apple //e bank switch emulation. - Simplified number of global variables for various softswitches. - Fixed a bug which made 3.5" and 5.25" disk access much slower than necessary. - Improved scan-line interrupt accuracy (lets MEGADEMO run). - Improved sound interrupt accuracy (was hoping this would fix some sound issues, but it doesn't seem to help). - Add Mode_switch as an alias for the Option key - I noticed the //gs self-tests were broken again--fixed. Changes in KEGS v0.55 since v0.54 (10/19/99) - In LOG_PC debug aid, add cycles to the trace - Fix MEGADEMO bug where 3.5" disks weren't properly ejected. Needed to look at iwm.motor_on35 not iwm.motor_on. - Temp fix for MEGADEMO to not halt if shadow-in-all-banks is on in $c036. - Another MEGADEMO fix to not take a scan-line int if the SCB was cleared right before the raster got to this line. - Fix bug in smartport.c that was causing core dumps if you tried to init a disk is s7dx. Changes in KEGS v0.54 since v0.53 (10/10/99) - Add support for Out Of This World's direct reading of ADB RAM loc 0xb to get key status. This lets shift/control work in OOTW. - Code simplification to get rid of most set_halt() calls and use halt_printf. - Speed improvement: track kpc (merged kbank and pc in one 32 bit variable) which makes the inner loop faster. This does make KEGS not accurately model a 65816 code crossing bank boundaries, but just about every other emulator gets it wrong, and the speed improvement is 5-10%. And I don't know of any code which relies on it working correctly. - Fix to allow better GS/OS compatibility: after each smartport call, set 0x7f8 = 0xc7. - Fixed ZipGS emulation bug where KEGS was not re-locking Zip at the right time, which made double-hires not work after booting GS/OS. Changes in KEGS v0.53 since v0.52 (8/3/99) - Move all the "fcycles" timing calculations to use double instead of float. - Fix display shadowing bug reported by "phoenyx" which caused the text display to not always be updated correctly with funny bank switching. - Added the "Home" key as an alias for the '=' on the keypad. - Changed the way X modifiers are interpreted to increase compatibility of Caps Lock to more X servers. - Add -dhr140 option to use old double-hires color mode that results in exactly 140 horizontal pixels with no bleeding. It's set default to "on" for now while I work out double-hires colors. - Started adding some ZipGS compatibility--control panels run, but all the controls are effectively ignored by KEGS. Changes in KEGS v0.52 since v0.51 (6/27/99) - Small speed-up of interpreter loop to avoid checking the global variable "halt_sim" after every instruction. - Smartport fixes to avoid halts when the SCSI CD player NDA is installed. - Fix to autodetect X visual depth (it didn't work at all in v0.51). - Fix to HP binary--KEGS v0.51 hit an HP linker bug which caused the executable to not run correctly. (It didn't obey an assembly- language alignment command correctly). Re-ordering the object list works around the problem. Changes in KEGS v0.51 since v0.50 (6/1/99) - Fixed many bugs that crept into scanline interrupts over the last few months. - RAM size is now settable on the commandline: -mem 0x400000 will use a 4MB expansion RAM card (giving you 4.25MB of memory with ROM 01). - VBL time used to be a variable (which was wrong)--it's now always the same number of cycles. - Typo preventing joystick_driver.c from compiling fixed. - Auto senses X visual depth, searching for 8 bit, then 15 bit, then 24, then 16 bit visuals. Can still override this with commandline. Changes in KEGS v0.50 since v0.49 (5/31/99) - Added Linux joystick support with code provided by Jonathan Stark. Activate with "-joystick" command line option. - Small improvements in s7 device handling. If you have no s7 devices or no bootable devices, KEGS launches Applesoft. - Bug fix in scan-line interrupts--they were occurring at the wrong time previously. - Rewrote double-hires color routines. They're still not quite right, but it's a lot better than it used to be. Changes in KEGS v0.49 since v0.48 (5/3/99) - Fixed a key-repeat bug in v0.48 caused usually with shift-key sequences. - Fixed bug where GNO would not work with ROM 03. ROM area at $C071-$C07F is different from ROM 01. - Ian Schmidt pointed out a special Ensoniq case where an oscillator in one-shot mode can cause it's partner to start if it is in swap mode. - Integrated in Geoff Weiss's Solaris x86 ports. I might have broken it making a few last-minute changes... Changes in KEGS v0.48 since v0.47 (4/13/99) - Even better ADB key repeat--key rollover works more like a real Apple //gs. - IWM fix: some "smarport" modes were being activated sometimes during normal 3.5" accesses, resulting in some games not loading correctly. - Some fixes to serial port emulation to handle programs writing to the serial port in MIDI mode when the chars will not be consumed. - Smartport fix to set zero-page locations $42-$47, needed by some poorly- written game loaders - The "oscilloscope" effect in some sound-demos now shows the sounds being played. Changes in KEGS v0.47 since v0.46 (4/7/99) - ADB fix #1: reading $c010 should give key-down status better - ADB fix #2: key repeat was stopping if modifier key pressed - ADB fix #3: The game "Pirates" was crashing on startup due to a small bug. - Bard's Tale 2 was freezing on startup due to a bug in the WAI instruction. - Major serial port rewrite. Diversi-Tune now runs and sound OK (but there are some small problems) and serial port emulation is better. Changes in KEGS v0.46 since v0.45 (3/21/99) - Fix for undefined var in engine_c.c. Oops. - Fix for old bug in engine_c.c causing KEGS to sometimes misinterpret instructions which cross page boundaries. Was causing Thexder not to work, at least. Changes in KEGS v0.45 since v0.44 (3/20/99) - Fix for COP instruction in engine_c.c. Pointed out by Kelvin Sherlock. - Major fixes to Ensoniq emulation, SynthLab sounds much better. - Fix to iwm.c to deal with corrupt 2IMG archives a little better. Changes in KEGS v0.44 since v0.43 (2/23/99) - -audio 0 option would often cause programs to hang. Bug was that the audio rate was defaulting to '0' which confused KEGS. - Made keycode 0x072 be the XK_Break key for XFree86 Changes in KEGS v0.43 since v0.42 (2/19/99) - Support .nib 5.25" format as read-only - Faster 3.5" nibblization routines (should make startup faster) - Fixed a very-old 3.5" disk writing bug that made bit-copiers not work Changes in KEGS v0.42 since v0.41 (2/1/99) - Include to fix Linux compile problem - Fix relative branch timing bug that was making IWM emulation flaky (backward branches should count as 3 cycles if to the same page, and 4 if to a different page in emulation mode. Bug always counted them as 4) - Gave up on fast 5.25" writes--KEGS always slows to 1MHz for 5.25" writes since the timing and kludges just got too annoying. - add "-arate 22050" option to change audio sample rate on the command-line. Slower audio rates can hit more audio bugs (I'm working on them). - fixed little-endian bug in smartport.c and partls.c - fixed side border redraw bug that would sometimes leave super-hires images on the right-side border. Changes in KEGS v0.41 since v0.40 (1/19/99) - Fixed bug where fill-line mode would not always redraw the screen correctly - Changed some // comments to /* */ to help David Wilson's Solaris port - Fixed little-endian bugs in smartport.c preventing mounting of parititioned disks. Fix submitted by Jonathan Stark. - Christopher Neufeld noted that fast space/delete option in the control panel caused KEGS to hit breakpoints. I fixed this and fast arrows and fast mouse options (they are now just ignored). - Solaris port by David Wilson now provides a Makefile_solaris Changes in KEGS v0.40 since v0.39 (10/25/98) - 15 and 24 bit depth displays now supported (though somewhat slower than 8 bit displays). But Super-hires displays now show 256 simultaneous colors on a 16- or 24-bit X display. Select a 15-bit display with the cmd line option "-15" and a 24-bit display with "-24". Otherwise, KEGS defaults to looking for an 8-bit display, and fails if it cannot find one. - Some border fixes--border colors now update correctly when palette changes occur (like via F10). - Alias F1 to ESC for OS/2. Changes in KEGS v0.39 since v0.38 (9/13/98) - OS/2 port by Tschopp Gilles - handle cr&lf better in disk_conf - Drive letters work and are not confused with partition names, so s7d1 = D:\images\cd:1 will open partition 1 correctly. - KEGS no longer uses system() to do file copies, it does it all using POSIX calls. - Unix-specific socket calls moved from scc.c to scc_driver.h - Default X handler re-installed properly now for better debug - Nasty core dump bug found and fixed by Tschopp Gilles in disk switch code Changes in KEGS v0.38 since v0.37 (7/28/98) - IWM bugs: - fast_disk_emul off under GS/OS caused I/O errors. KEGS was always slowing down to 1MHz when 5.25" drive was on, when it should have been obeying the $C036 register. - bug in IWM on little-endian processors - disk ejection should now work, but a beta user claimed some bugs on x86 Linux. - 2IMG support, but only lightly tested. - Removed some internal breaks on access to $C0B0 for tool033. - Modulae also stumbled into some breakpoints by writing to $C02F, which does nothing. - Screen refresh simplified (for me) by redrawing the screen while raster is on first scan-line, rather than line 200. However, a side effect is some of the graphics during the XMAS DEMO look a bit choppier. - More SCC fixes to avoid breakpoints under GNO. - Start support for sound under Linux, but it sounds horrible right now. Any Linux sound gurus want to help out? - Fixed possible array-overrun bug in video.c around border effects. Maybe shared memory works under x86 Linux now? - Made changes for OS/2 port to fopen() text files. From Blue Neon. Changes in KEGS v0.37 since v0.36 (7/13/98) - Linux PPC port completed and functional. KEGS has been tested to run quite well and quite fast on a 240MHz 604e running MkLinux pre-DR3. - Change LITTLE_ENDIAN define to KEGS_LITTLE_ENDIAN since Linux always defines LITTLE_ENDIAN as a silly macro. - Dumb bug in IWM 3.5" routines could cause core dumps if disk arm moved from outer track to inner track very quickly. - Deleted some breakpoints that some Second Sight searching code would hit. - Ignore some SCC reset commands GNO would use that caused KEGS to stop. - Handle odd partitions better--some //gs formatted Zips had a blocksize of 0, which defaults to 512 now. - Handle some keysyms better to avoid MkLinux bug with keysym 0. Changes in KEGS v0.36 since v0.35 (5/30/98) - Linux x86 port completed and functional with help from Karl Pfleger - Linux clock fixes--should handle daylight savings better on Linux - LITTLE_ENDIAN defines - Start making fixes for NeXTStep due to Eric Sunshine - Fixed bug in HP asm code with I/O fetches--caused //gs selftests to fail and a bug in scc.c was also causing self-tests to fail. Changes in KEGS v0.35 since v0.34 (5/17/98) - engine_c.c fully implemented--KEGS now has a version completely written in C, and now portable to other Unix machines. - KEGS got another 5% faster with more tweaks to the asm dispatch loop. Changes in KEGS v0.34 since v0.33 - KEGS is 10-15% faster due to finally implementing a planned recoding of the dispatch loop. Changes in KEGS v0.33 since v0.32 (5/7/98) - Fixed bug in engine_s.s that prevented compiling on pre-10.20 systems. - ADB mouse interrupts work now. Fixed "bug" where GSHK would think mouse button was depressed at startup. (GS/OS is looking at mouse button 1 status, which accidentally was reading as down). - ADB emulation of read char_sets and read_kbd_layouts now matches a real //gs. - optimization to allow dereferencing page_info[] even if BANK_IO is set, to get a small speed improvement in engines_s:dispatch(). - SCC logs are 'Z' at the disas prompt. - Tool decoded is 'T' at the disas prompt. - SCC changes to support slot 1 == port 6501 and slot 2 == port 6502, with limited interrupt support. Most serial tasks won't work still, but some do. PR#1/2 and IN#1/2 work fine. getty under GNO doesn't. - -audio [0/1] forces audio off/on. This just stops the sound playing-- internally all Ensoniq interrupts/etc are fully emulated. If display is not using shared memory (i.e., it's remote), audio defaults to off. (but can be forced on with -audio 1). - -display {foo} sends X display to {foo}. Changes in KEGS v0.32 since v0.31 (10/23/97) - Faster dispatch loop, for a 10-15% overall performance improvement - Fixed sound bug where Oversampler would make KEGS halt (Oversampler said turn on 128 oscillators, and KEGS tried to...) - Fixed bug where KEGS would not work on 24-bit displays due to a typo. - Added frame skipping support (-skip n) and auto frame skipping if you are not using shared memory (like displaying KEGS to a remote machine). - Added -noshm support for forcing off shared memory, so you can see how much it helps. Changes in KEGS v0.31 since v0.30 (9/23/97) - New mouse handling--Press F8 to hide X windows cursor and constrain cursor inside window. Makes using the mouse much easier. F8 toggles back to normal. - Add revision to status area. - Remove "slow memory" calculation. KEGS was emulating slowing down to 1MHz to write to slow memory (bank $E0 or $E1). But true //gs accelerators have a smarter trick, so I just removed it from KEGS. KEGS still slows down for I/O reads and writes. This eliminates the confusing 40MHz speed numbers you'd sometimes get. KEGS can also now run faster when it would have slowed down to 1MHz before. - Turn off accurate IWM emulation be default, for much faster emulation. Bit copiers won't work by default now. Toggle accurate IWM with F7. Accurate IWM forces 1MHz speed for 5.25" and 2.5MHz for 3.5", but less accurate IWM runs as fast as possible. - Add optional size to s7dx entries in disk_conf, to allow using /dev/rfloppy. - Allow mounting partitions by number, instead of just by name, since some Mac-formatted Zip disks don't have partition names. - Add -ignbadacc to ignore bad memory accesses. - Increase MAX_C030_TIMES. Otherwise, fast workstations could generate too many clicks per VBL, causing an assertion to fail. - Small speed increase detecting changes in the superhires screen. - Alt_L is now Open-Apple, and Alt_R is Closed-Apple. - KEGS now uses just one private colormap, so xwd can get screendumps. ================================================ FILE: upstream/kegs/doc/COPYING.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: upstream/kegs/doc/INTERNALS.iwm.txt ================================================ // $Id: INTERNALS.iwm.txt,v 1.3 2021/10/03 19:44:15 kentd Exp $ KEGS's Apple IIgs IWM emulation routines. The IWM code does 5.25" and 3.5" reads & writes, and updates the Unix disk image on writes. All 5.25" and 3.5" disks are always emulated internally in a low-level format that is compatible with WOZ disk images. Support for WOZ images is very good, but it's not complete yet. How the disk emulation works: The routines have a nibblized image of each track of each drive (two 5.25" and two 3.5" drives are supported) in memory. Each track's data is stored as an array of bytes of the raw disk bits. This is basically the raw data from WOZ images for that track. A second array for the track, of the same length, is allocated to calculate the sync bit positions. For each byte, the sync byte indicates the nearest synchronized MSB from the LSB of the same disk byte. With this encoding, if a disk read is done, and then 1000 cycles pass, the code can quickly index to the proper disk byte, and then use the sync information to return the correct data. It is just as much work for 4 cycles to pass as 5000 cycles. Several levels of emulation accuracy are provided. By default, KEGS defaults to Fast Disk Emulation, where the following changes are done: - KEGS does not slow down to 1MHz (or 2.8MHz for 3.5" accesses) for reads. - However, KEGS always slows down to 1MHz for 5.25" writes. - Each read of IWM data from $C0EC will return the next aligned disk nibble. - No matter how much time passes, the read position does not move. When accessing a WOZ image, or if Fast Disk Emulation is toggled off (using Shift-F6), or if a write is being done, then: - KEGS slows to 1MHz (2.8MHz for 3.5" accesses) when the drive motor is on. - Exact timing is maintained--a valid disk nibble is held in the read latch for 7 clock cycles plus the amount of time for the next sync bit to shift in, just like the hardware works. The arm stepping code is pretty dumb, it does not support 1/4 tracks since I have no mechanism to test them. I'll look for a WOZ image with 1/4 tracks and then implement it properly. Smartport support is sufficient to claim that there are no smartport devices. This is necessary since the ROM tries to see if there are smartport devices at power-on. I used Copy II+ to nibble copy some disks, and it worked fine. Note: KEGS does not support writing to "empty" WOZ images. This is on the TO-DO list. IWM description: The IWM in the IIgs supports 3.5" and 5.25" disks, as well as Smartport devices (KEGS does not support real Smartport devices, but it does cheat and make large images available on slot 7). The IWM has several modes: - Motor off. Accesses go to internal Mode register and Status register. - Motor on ($C0E9 is on; touch $C0E8 to turn motor off). - $C031 enables 3.5" if bit 6 is set - The disk phases $C0E0-$C0E7 + $C031 bit 7 provide a 4-bit command code to the 3.5" controller, and can be used to move the disk arm, eject a disk, etc. The action is triggered by phase 3 going on. Note the actual 3.5" motor is not controlled by $C0E9, but a special command must be given through this phase-3-latch mechanism to turn the motor on and off. - Phases 0,1,2 and $C031 bit 7 select a status bit to return when Q7=0,Q6=1 is read (the Status Register). -If phase 0 and 2 are enabled, this resets the IWM. -If phase 1 and 3 are enabled, this enables accessing the Smartport Bus. KEGS supports this enough to indicate that there are no Smartport devices. This is called ENABLE2 mode. - $C031 disables 3.5" if bit 6 is clear: - The disk phases $C0E0-$C0E7 move the 5.25" disk arm. - Reading any even address returns: Q7=0,Q6=0: The data register (byte from the drive) Q7=0,Q6=1: Status register (write protect) Q7=1,Q6=0: Handshake register (not used by software) The 3.5" IWM modes provide for much faster emulation--the controller returns a status when the head has stepped a track, when the motor is up to speed, etc., and KEGS returns "it's ready now" always to make the accesses faster. With Fast Disk Emulation, an entire 800K disk can be read in under 2 seconds, and an entire 140K disk can be read in well under a second. Code description: Most code is in iwm.c, with some defines in iwm.h. Each track is stored in raw data form--so a 10-bit 0xff, 10-bit 0xff, 0xd5, 0xaa, 0x96 would be stored as: 0x3f, 0xcf, 0xfd, 0x5a, 0xa9, 0x6X. To remember the bit position on the track with sufficient accuracy, the head position on the track is in "quarter-bits", or "qbits". There are 32-qbits to an 8-bit byte. For 5.25" disks, a qbit is exactly one 1MHz cycle. Iwm state is encoded in the Iwm structure. drive525[2]: Disk structure for each 5.25" disk in slot 6 drive35[2]: Disk structure for each 3.5" disk in slot 5 smarport[32]: Disk structure for each "smartport" device emulated via slot 7 (see smartport.c) motor_on: True if IWM motor_on signal (c0e9) is asserted. Some drive is on. motor_off: True if motor has been turned off in software, but the 1 second timeout has not expired yet. motor_on35: True if 3.5" motor is on (controlled differently than 5.25" c0e9). motor_off_vbl_count: VBL count to turn motor off. head35, step_direction35: 3.5" controls. iwm_phase[4]: Has '1' for each 5.25" phase that is on. iwm_mode: IWM mode register. drive_select: 0 = drive 1, 1 = drive 2. q6, q7: IWM q6, q7 registers. enable2: Smartport /ENABLE2 asserted. reset: Smartport /RESET asserted. write_val: Value written to the disk data latch, will be written after 8 bit times has passed. qbit_wr_start: Qbit position when writes started, a large value if not doing a write currently. qbit_wr_last: Qbit position where write_val should go. forced_sync_qbit: Overrides the calculated sync_bits temporarily, and forces this to be the qbit of the next sync byte. Each disk (3.5" and 5.25") is encoded in the Disk struct: dycs_last_read: Time (in 1MHz cycles) when last access occurred. fd: Unix file descriptor. If < 0, no disk. raw_data: For an image from a .zip file, this is a pointer to the raw disk data. fd will be 0 when this is valid. name_ptr: Unix file name for this disk. partition_name: .zip file name. raw_dsize: size of raw_data dimage_start: offset from beginning of file for this partition (0 if not a partition or zip file entry). dimage_size: size of this partition. smartport: 1 if this is a smartport image, 0 if it is 5.25" or 3.5" disk_525: 1 if this is a 5.25" image, 0 if it is 3.5" drive: 0 = drive 1, 1 = drive 2. cur_qtr_track: Current qtr track. So track 1 == qtr_track 4. For 3.5", there are no half-tracks or quarter tracks, so cur_qtr_track encodes the (track << 1) + side. vol_num: DOS 3.3 volume number to use. Generally 254. write_prot: True if disk is write protected. write_through_to_unix: True if writes should be passed through to the unix image. If this is false, you can write to the image in memory, but it won't get reflected into the Unix file. disk_dirty: Some track has dirty data that need to be flushed. just_ejected: Ejection flag. last_phase: Phase number last accessed. cur_qbit_pos: Qbit position for reading. cur_track_qbits: Number of qbits on this track. num_tracks: Number of tracks: 140 for 5.25" (it counts quarter- tracks) and 160 for 3.5" (80 tracks on 2 sides) *trks: pointer to array of raw Track data *cur_trk_ptr: Pointer to the current track Each track is represented by the Trk structure: *raw_bptr: Pointer to the raw disk data. *sync_ptr: Pointer to the sync values for each byte. dunix_pos: Offset into disk image for this track data. unix_len: always 4096 for 5.25" disks, variable for 3.5" disks track_qbits: Length of raw_bptr in qbits. Externally callable routines: iwm_init(): Init various data structures at simulation start. iwm_reset(): Called at Apple IIgs reset time. iwm_vbl_update(): Called every VBL (60 Hz) period. Used to turn motor off, and flush out dirty data. g_vbl_count is the count of VBL ticks (so it counts at 60 times a second). iwm_touch_switches(int loc, double dcycs): Handles all accesses to $C0E0-$C0EF, changing state as needed. read_iwm(loc, dcycs): Read from 0xc0e0 + loc. Loc is between 0x0 and 0xf. Dcycs is a double holding the number of Apple IIgs cycles since the emulator started. Dcycs always counts at 1.024MHz. If you are running at 2.8MHz, it increments by ~0.4 every "cycle". Reading from even addresses with the drive on will read the current disk data if q6=0,q7=0. write_iwm(int loc, int val, double dcycs): Write to 0xc0e0 + loc. Just like read_iwm, but write "val" into loc. If q6=1 and q7=1, and the motor is on, writes data to the track. Tricky routines: iwm_read_data(): called by read_iwm() if q6,q7 = 0,0. dsk->cur_qbit_pos is the current head position, the code returns the data to be returned at this position. 16.0 dcycs need to pass for an 8 bit nibble for 3.5" accesses, and 32.0 dcycs for an 8 bit nibble for 5.25". g_fast_disk_emul == 1 says don't mess around with accuracy, and always return the next fully-formed nibble. There is a lot of complexity in this routine. For 5.25", it looks backwards to see where the most recent MSB of a disk byte would have occurred using sync_info, and then forms: - That byte, if that MSB was 8 bits ago, plus 7 cycles, plus any time for 0 sync bits that may be after it. - The current effective IWM shift register value--if it is determined we are 4 bits into 0xaa, then it will return the first 4 bits only as 0x0a. For 3.5", it uses iwm_read_data_latch(), which re-uses g_iwm.qbit_wr_last to remember if the latch is valid. iwm_write_data(): called by write_iwm() if q6,q7 = 1,1. Similar to above. Handles async and sync mode writes. Handles partial writes. Routine disk_nib_out_raw(dsk, val, bits, qbit_pos, dcycs) does the actual work of merging the bits into the track image. disk_nib_out_raw(): called by iwm_write_data() and iwm_nibblize_track_*(). Writes byte into cur_trk_ptr->raw_bptr[]. In iwm_nibblize_track_35(), the comments with hex numbers correspond to the ROM 01 addresses which I disassembled to determine the checksum algorithm. The code is not well written--it's basically hand-translated 65816 assembly code. I'll clean it up someday. To avoid special casing reading and writing data at the end of the track, trk_ptr->raw_bptr[] has the actual track data start at byte 2, and there are at least 2 bytes spare at the end. The iwm.c code keeps the raw_bptr[0] and raw_bptr[1] always equal to the last 16 bits on the track--so that most code can look at raw_bptr[-1] and raw_ptr[-2] to peer backwards to the previous track data, without worrying about wraparound. ================================================ FILE: upstream/kegs/doc/INTERNALS.overview.txt ================================================ KEGS Internals -------------- The INTERNALS* files describe the internal structure of KEGS and how it works. It is meant to be useful to those who would attempt to port KEGS to non-Unix platforms, or just want to know how KEGS works. Documentation files: -------------------- INTERNALS.overview: Provides overview of KEGS's file structure KEGS SOURCE FILES: ----------------- adb.c: ADB emulation routines. This includes keyboard, mouse, and joystick. adb.h: Defines for ADB routines. clock.c: Clock, BRAM, and some timing routines. compile_time.c: Trick file to put the time you compiled in g_compile_time[]. defc.h: Global defines included by all .c files. Useful trick to avoid complex multi-level include problems. defcomm.h: Global defines included by all files (must be assembly and C safe, such as #defines). defs_instr.h: Definitions for various addressing modes and for some repeated instructions (like LDA, STA, etc). debugger.c: Disassembler and debugger interface. The debugger interface is similar to the Apple // monitor, but has many more commands. disas.h: Tables for disassembling 65816 instructions. Not very efficient, but it works. engine_c.c: 65816 emulation and helper routines engine.h: Actual 65816 inner loop, so it can be instantiated with and without logging, and 8-bit and 16-bit accumulator. instable.h: Instruction table. iwm.c: IWM routines for 5.25" and 3.5" disks. See INTERNALS.iwm iwm.h: IWM defines moremem.c: Awful name--this file contains the page table change routines (fixup_*) and io_read() and io_write() to emulate all $C000 I/O accesses. op_routs.h: More macros for 65816 emulation. protos.h: Prototypes for all C functions. Auto-generated through the "cproto" program (not included). protos_xdriver.h: Prototypes for functions in xdriver.c. scc.c: Serial chip emulation. scc_driver.h: Unix-specific socket routines, stubbed out if you're not on Linux or HP-UX. scc.h: Defines for scc.c. sim65816.c: main() is here along with many other housekeeping functions, such as events, and interrupts. The main loop of KEGS is run_16ms(). size_c.h: Used to get the size of instructions smartport.c: Smartport emulation, emulates s7dx devices. sound.c: Sound emulation. Builds sound samples, but does not send sound to output device. sound.h: Header file for sound.c. sound_driver.c: Sound driver routines. Takes samples generated by sound.c and sends them to the correct output device. Supports /dev/audio, /dev/dsp, Linux PulseAudio, and Mac audio. video.c: Display routines. Builds 32-bit video buffers in a device independent way. Functions here know nothing about X windows or Macs. xdriver.c: X windows driver routines. Takes buffers from video.c and sends them on to X windows. Get keypresses from X and sends them to adb.c. mockingboard.c: Mockingboard A emulation (two 6522, two AY-8913 sound chips). unshk.c: Can open disk images in .sdk files undeflate.c: Zip routines, for selecting images insize .zip files, or gzip compressed files. woz.c: Support for .woz images, Version 1 or Version 2. Porting Advice: -------------- To a non-unix platform, disabling scc emulation would be a good start. Just make sure you get the dummy stub routines in scc_driver.h. If you don't have gettimeofday(), clock.c will need to modified with whatever timer facilities are available. A high-resolution clock works best, but even a low-resolution clock will work. Modify sound_driver.c to get it to compile, if necessary. Always run with "-audio 0" to turn audio off. No routines in sound_driver.c will get called if you run with -audio 0. Replace xdriver.c with new routines for whatever platform you are porting to. See INTERNALS.xdriver for more information. Useful IIgs information: ----------------------- The Apple manuals (Hardware reference, Firmware Reference) are the most useful. But they leave a lot out, or have innaccuracies. Some details on IIgs fast/slow mode and refresh information: https://www.kansasfest.org/wp-content/uploads/2011-krue-fpi.pdf ================================================ FILE: upstream/kegs/doc/INTERNALS.video.txt ================================================ The video.c code in KEGS is responsible for handling the Apple IIgs video modes. A detailed description of how Apple II video works is in Understanding the Apple //e by Jim Sather. This is a carefully researched book (I'm in awe of the amount of time that went into writing it) with a great deal of details in chapters 3, 5, and 8 on how Apple II video works. KEGS video overview: ------------------- KEGS' emulates one screen draw period per call to run_16ms(). The screen shown by a monitor starts at the top left of the screen, and is the top border lines. Then the Apple II graphics modes are shown as lines 0 through 199 (or 191 for non-Super-Hires modes), with a left border and right border, then the bottom border lines are shown. Top Border Lines.... Top Border Lines.... |-----------------------------------------------| <- Time 0 Line0 | | Line1 | | ... Line198 | | Line199 | | |-----------------------------------------------| Bottom Border Lines... Bottom Border Lines... A screen draw period is exactly 17030 1MHz cycles, which is 262 lines of 65 cycles each. KEGS calls the start of its screen Time 0, and it is just at what would be the start of the right border for Line -1 (so just before Line 0, the first Apple II video mode line). KEGS time 0 corresponds to when the $C019 VBL signal transitions from 1 to 0 (this sense is inverted when KEGS is in //e mode). Time 0 corresponds to $C02E=0x80 (vertical line count / 2, encoded) and $C02F=00 (horizontal count, encoded). $C02E counts from 0x80 until 0xe0 (lines 0 to 192) when $C019 VBL will be set again. $C02E counts through $FF, then wraps to $7D, then counts through $7F. $C02F's has a high order bit which is the low bit of the line counter, so if $C02E=0x90 and $C02F=0x80, then the current line is $21. The horizontal counter in the low 7 bits of $C02F counts as follows: $00, $40-$7f, and then repeating. The rightmost pixel of each Apple II video mode is fetched from memory when $C02f bits 6:0 = $7f. The rightmost border pixel is drawn whtn $C02F bits 6:0 = $00. The leftmost pixel of each Apple II video mode is fetched from memory with $C02F bits 6:0 = $58. So the 40 cycles drawing data are when $C02F = $58 through $7f = 40 values. KEGS currently draws each line of the screen at the moment when $C02F=$58. KEGS uses the memory in banks $E0 (and $E1) for the line being drawn, but does track the mode changes to handle changes within a line. Toggling the mode ($C050-$C057, for example) in the middle of a line will be drawn roughly correctly (it's complex when changing from 80-column to hires, for example, one cycle of data may not be shown like the real hardware). ================================================ FILE: upstream/kegs/doc/INTERNALS.xdriver.txt ================================================ xdriver.c contains the routines for interfacing to X windows. The rest of KEGS interacts with X windows only through xdriver.c routines. Externally called routines are: show_xcolor_array(): Debug routine, it does not need to do anything. dev_video_init(): Called at startup, it should open up the window and do other initialization. update_physical_colormap(): Updates the X windows palette with the colors from xcolor_a2vid_array[], which is maintained by other xdriver routines. update_status_line(): Call to update the internal array of chars representing the status lines at the bottom of the window. Does not draw the chars to the screen. xdriver_end(): Shutdown routine check_input_events(): Called up to 60 times a second (see video_update() in video.c) to handle any X window events and get keypresses. On a mouse press, call update_mouse() with the new x, y coordinates, and the status of the mouse button. If g_warp_pointer is set, constrain mouse within the window. On keypress, calls handle_keysym(). handle_keysym(): Takes X keysym, and converts to the appropriate a2code using the a2_key_to_xsym[] lookup table. The a2codes are the Apple // ADB keycodes. Special work is done to handle shift and control properly since Apple only has one keycode for both shift and control keys. Then call adb_physical_key_update() with the a2 keycode and is_up = 1 if keyup, 0 = if key down. In addition, this routine handles all the Function keys doing special actions, which should be easy to port. x_refresh_ximage(): Redraws the window using the a2_line_* arrays. Described in more detail below. update_color_array(): Interface to the color map. Sets color[col_num] of the internal colormap array to a2_color. a2_color is the 12 bit apple color of the form: (red << 8) + (green << 4) + (blue). There are 16 palettes of 16 colors each, managed as one 256-color colormap. See discussion of g_a2vid_palette below. x_auto_repeat_on(): The X routines turn off key repeat when the cursor enters the graphics window automatically, and turn it back on when the cursor leaves. But if the debugger gets control due to a breakpoint, keyrepeat would be left off. So the debugger calls this routine to make sure key repeat is back on. redraw_status_lines(): Draw the status lines from the g_status_buf[][] array to the graphics window. Externally referenced data: g_use_shmem: Set by main() to enable/disable MIT-SHM for X. Also used by sound routines to auto-turn-off sound if not using MIT-SHM. Bytes representing screen data: byte *data_text[2]: Array of bytes for the lores and text pages 1 and 2. Just 400*640 bytes. byte *data_hires[2]: Array of bytes for the hires pages 1 and 2. byte *data_superhires: Array of bytes for superhires screen. byte *data_border_sides: Array of bytes representing the border sides. Basically just A2_WINDOW_HEIGHT*EFF_BORDER_WIDTH bytes. byte *data_border_special: Top and bottom border bytes. (X_A2_WINDOW_HEIGHT - A2_WINDOW_HEIGHT + 2*8) * (X_A2_WINDOW_WIDTH) bytes. Handles used for X windows drawing: XImage *ximage_hires[2]: Opaque handle to XImage object for hires page 1 and page 2. XImage *ximage_text[2]: Text pages 1 and 2. XImage *ximage_superhires: Superhires graphics XImage XImage *ximage_border_special: Top and bottom border XImage. XImage *ximage_border_sides: Left and right sides (only one copy, it is drawn at two different locations to be both sides). Basic operation of xdriver: -------------------------- X windows can push arrays of bytes to the screen through structures called XImages. An XImage is a structure describing an offscreen bitmap. For efficiency of page flipping, KEGS maintains separate bitmaps for the two lores/text screens, the two hires screens, and the superhires screen. It also maintains bitmaps for the border. For MIT-SHM to work, X requires a unique XImage for each bitmap, and the bitmap must be allocated within xdriver.c since it must be obtained through an shmat() call. The X code also has non-MIT-SHM code which allocates the data_* buffers just through malloc(). All bitmaps are 8-bits of Pseudo-color. The color arrays are managed through the update_color_array() and update_physical_colormap() routines. KEGS manages all 256 colors in the colormap as 16 palettes of 16 colors. One of the palettes is reserved for the 16 lores colors, and is indicated by the variable g_a2vid_palette. It defaults to 0xe. Update_color_array() is called to update superhires colormap entries. Update_color_array must not update colors corresponding to g_a2vid_palette. Update_physical_colormap() pushes the color array managed by update_color_array() to the screen, but first forces the lores colors into the g_a2vid_palette palette. g_installed_full_superhires_colormap is always false in KEGS for now. video.c calls update_color_array and changes g_a2vid_palette. No xdriver routines gets notified when g_a2vid_palette changes, so update_physical_colormap must handle the case where g_a2vid_palette might have changed since it was last called. x_redraw_ximage(): Routines in video.c are free to draw into the corresponding data_* arrays to change any byte at any time. video.c manages remembering which lines need to be redrawn and which parts of the screen are in which video mode via the a2_line_* arrays. KEGS divides the video screen up into 25 groups, corresponding to each text line. Each of these groups consists of 16 sublines. 25*8 = 400 lines. (video.c has already doubled the vertical resolution in all video modes). KEGS can allow any group to be from any of the five screens it manages: The two text/lores pages, the two hires pages, and the superhires screen. For each group, KEGS keeps track of what part of it needs to be redrawn. g_a2_screen_buffer_changed has a bit set for each group which has changed since the last call to x_redraw_ximage(). The rightmost bit (bit 0) corresponds to group 0. If g_a2_screen_buffer_changed == 0, no groups need to be redrawn. x_redraw_ximage clears out g_a2_screen_buffer_changed after drawing the screen. For each group, a2_line_left_edge[] and a2_line_right_edge give the pixel offsets of what should be redrawn. a2_line_xim[] gives the ximage handle of what needs to be redrawn. KEGS always redraws 8 verticals of a group. g_full_refresh_needed also has one bit set in it for each group, which indicates overriding the a2_line_*_edge functions and redraw from 0 to 640 pixels of each group that needs to be redrawn. x_redraw_ximage() interprets this information now using a simple algorithm: Skip over groups which have not changed (using g_a2_screen_buffer_changed). Save the ximage of this group, the left pixel and the right pixel. Continue with the next group if it has changed. Widen the pixel region and keep sucking up new groups to the same ximage. At group 25, or when the ximage changes, call x_refresh_lines to redraw this large rectangle from this ximage. x_refresh_lines() knows the ximage corresponding to the border for the last group has to be handled specially since the border group is not 640*400 pixels like the others. Other porting info: a2_key_to_xsym[][3] contains the mapping function from X keysyms to a2 keycodes. The first element is the a2 keycode, the second element is the unshifted X keysym, and the third element is the shifted keysym. A port must make the conversion to a2 keycodes, and provide up and down events. ================================================ FILE: upstream/kegs/doc/README.ROM.files.txt ================================================ Place a ROM, ROM.01 or ROM.03 file in your home directory, or in the directory you run KEGS from, or in the directory you placed a config.kegs file. You may manually select the ROM to use while running KEGS by pressing F4, and then selecting "ROM File Selection", then "ROM File", and then using the file selector to locate the ROM file. KEGS supports Apple //e ROMs as well. The ROM needs to be 32KB, with the actual 16KB of useful ROM in the last 16KB, and requires a Disk II PROM at offset $600 in the file (this just seems to be the format other emulators use, and I'm just following it). There are several places that have Apple IIgs ROMs. Most work, here's a list of ROM files which will work with KEGS: KEGS accepts the following ROM file names (if you have a file with this name at the right location, it will just work): ROM, ROM.01, ROM.03, APPLE2GS.ROM, APPLE2GS.ROM2, xgs.rom, XGS.ROM, Rom03gd, 342-0077-b. rom01.zip contains APPLE2GS.ROM. This file is a good ROM01 a rom with this name and use it. rom03.zip contains APPLE2GS.ROM2. This file is a good ROM03 xgs.rom is a good ROM01. iigs_rom01.zip contains APPLE2GS.ROM. This file is a good ROM01. iigs_rom03.zip contains APPLE2GS.ROM2. This file is a good ROM03. gsrom01.zip contains xgs.rom. This file is a good ROM01. gsrom03.zip contains Rom03gd. This file is a good ROM03. appleiigs_rom01.zip contains XGS.ROM. This file is a good ROM01. apple2_roms.zip contains xgs.rom. This file is a good ROM01. It also contains gsrom03.zip which contains Rom03gd which is a good ROM03. It also contains gsrom01.zip which contains xgs.rom again, which is a good ROM01. Files that won't work: APPLE Computer and Peripheral Card Roms Collection.zip: Don't use, the file "Apple IIgs ROM1 - 342-0077-B - 27C1001.bin" is not in the format KEGS expects. MAME ROM files can often be found with the search "site:archive.org mame-merged", with the GS ROM in apple2gs.zip. The file 342-0077-b is a ROM.01 file and KEGS will just use it (if you place it in the proper location). The 341-0728 and 341-0748 files can be a good ROM03 file, but it's in 2 files (KEGS expects ROMs in a single file), and the second file is in a weird order (MAME expects contributors to remove ROMs from systems, use a ROM reader to dump out the raw contents, and then it uses that, and apparently this puts the data in a weird order for ROM.03 images). You can convert these to files to the ROM.03 KEGS expects as follows on Mac or Linux: dd if=341-0748 of=tmp1 bs=64k skip=1 dd if=341-0748 of=tmp2 bs=64k count=1 cat 341-0728 tmp1 tmp2 > ROM.03 KEGS does not support ROM0 ROMs. -------------------------------------------------------------------------- How to capture the ROM file from an Apple IIgs ---------------------------------------------- Enter this BASIC program after booting PRODOS 8, and have available at least 128KB (or 256KB for ROM.03) on the disk: 10 D$ = CHR$ (4) 20 GOSUB 200:P = 0 30 FOR I = 254 TO 255 40 POKE 778,I 50 FOR J = 0 TO 192 STEP 64 60 POKE 777,J 70 CALL 768 80 PRINT D$;"BSAVE ROM,A$2000,L$4000,B";P 90 PRINT P:P = P + 16384 100 NEXT : NEXT 150 END 200 FOR I = 0 TO 20 210 READ D: POKE 768 + I,D 220 NEXT : RETURN 230 DATA 24,251,194,48,162,254,63,191 240 DATA 0,0,254,157,0,32,202,202 250 DATA 16,245,56,251,96 RUN When typing it in, the spaces are all optional you don't need to type them. This program captures the 128KB of a ROM.01 into the file "ROM". To capture the 256KB of a ROM.03, change line 30 to: 30 FOR I = 252 TO 255 ---------------------------------------------------------------------------- ================================================ FILE: upstream/kegs/doc/README.a2.compatibility.txt ================================================ # $Id: README.a2.compatibility.txt,v 1.6 2021/06/26 16:54:23 kentd Exp $ Bard's Tale II GS: Problem: Doesn't recognize any save disk as a ProDOS disk. It's detecting a "ProDOS" disk by checking for a string on block 0 at offset 0x15e. GSOS on system 6 has moved the string to 0x162, so disks inited under GSOS will be detected as "Not a PRODOS disk". Just make a copy of the Bard's Tale disk image to another file and then mount that new image and remove all the files using the Finder. Then rename the volume and you have a working save disk. Beyond Castle Wolfenstein: Make sure your disk is writeable (not compressed!) Breakout: Has trouble loading from the cracked copy. From the BASIC prompt, do: "CALL -151" then "C083 N C083" then "BLOAD INTBASIC" then run breakout. Then it runs fine. Burgertime: This is a bad crack. Loader starts the game by writing the game entry point into $0036/$0037, and then executing a BRK instruction. The BRK handler on an old Apple II will try to write out a message by calling through $0036/$0037, and this will start the game. But on a IIgs, the ROM sets the $0036/$0037 vectors back to the default, and so we crash into the monitor instead. Here's a memory fix and a disk-image fix: From the crack screen, press F7 to open the KEGS debugger. In the KEGS debugger window enter: "1d0a:ea 6c 36 0". Then just continue the game from the crack screen by pressing a key. You can make this fix to the disk image using a sector editor and change Track $1E sector $09 offset $0A from "60 78 A9 03" to "EA 6C 36 00" and write it back. Caverns of Callisto: Requires old C600 ROM in slot 6 (Slot 6==Your Card). Championship Loderunner: Requires disk to be writeable or else it starts the level and then jumps immediately back to the title page. Copy II+ 8.1: When doing a bit-copy, if the pattern at $5c13-$5c1b is detected ($d5,$aa,$96,xx,xx,xx,xx,$aa,$aa, which is DOS 3.3 sector $00) at the start of the track, then it writes $5c21-$5c24 out just before it: $d5,$ab,$cd,$df when doing bit copies. This may be either a serial number code, or just a way to detect bit-copied disks. 5.25" bit copies always seem to write 9-bit sync bytes, which is wasteful. Drol: Needs slot 6 set to "Your Card" from the IIgs control panel (Ctrl-Cmd-ESC, then choose "Slots"). I found Drol hard, so here are some cheats. First, the standard cheat for Drol is to have infinite lives, this cheat is to edit the disk image: Track $0B, Sector $0A, byte $22 to EA EA Track $11, Sector $0A, byte $10 to EA EA Track $17, Sector $09, byte $B2 to EA EA I didn't create those cheats, I got it from textfiles.com. My cheats are for the monsters to never kill you--just run right through them. While playing Drol, press F7 to open the KEGS debugger, and then in the debugger window type: 0/f28:18 18 # Monsters' missiles won't kill you 0/e05:90 0c # Monsters won't kill you Other things, like the bird, axes, swords still kill you. To easily solve the third screen, move your man to the far right side on the top level, so that you are directly above the woman on the bottom row. Fly into the air "A" and then get to the KEGS debugger, and type: 0/c:4 and then continue playing by pressing "Z" and you will go all the way down and land on the woman and end the level. It's useful to have made the two above patches so that touching monsters won't kill you. Two more patches that only apply to level 3, and so most be made in memory each time you enter level 3: 6cf3:18 18 18 # Axes won't kill you 6f70:38 38 # Swords/arrows won't kill you The bird and the guy you can't kill will still kill you. These cheats were enough to make the game easily playable. In the game, your death is indicated by setting location $001E to $FF. Setting breakpoints there can let you find other cheats. Flobynoid: Must disable Fast Disk Emul (hit F7 to toggle it off) since game's loader relies on the sector order on the disk (reads 8 sectors from the start without checking headers, assumes every other physical sector is skipped due to decode delays). Jeopardy: Disk must be writeable or else it displays "DISK ERROR" and then crashes into the monitor. mb-audit v0.7: You must run KEGS at 2.8MHz, but in the IIgs control panel (Ctrl-Cmd-ESC) set the speed to slow. Ensure slot 4 is "Your Card". Microbe: Crashes upon booting. Code at $599E tries to pull a return address off of a location beneath the stack pointer and jump to it, but it doesn't add 1 correctly so it jumps to $5917 when it meant to jump to $5918. On a IIgs, this causes a BRK to be executed and the game to crash. This can be patched in memory in two places: 0/599e:ba ca 9a 7d 00 01 48 98 7d 01 01 9d 01 01 60 0/6f1d:ba ca 9a 7d 00 01 48 98 7d 01 01 9d 01 01 60 The original byte sequence for both locations is: 00/599e: ba TSX 00/599f: 7d ff 00 ADC $00ff,X 00/59a2: 85 94 STA $94 00/59a4: 98 TYA 00/59a5: 7d 00 01 ADC $0100,X 00/59a8: 85 95 STA $95 00/59aa: 6c 94 00 JMP ($0094) You can also patch the code onto the disk image. I found the $599E version on Track $05, Sector $06, Byte $9E. I found the $6F1D version on the image at Track $0C, Sector $00, at byte $1D. Moon Patrol: Crashes into the monitor after completing checkpoint E. To fix the Moon Patrol/Dung beetles version, from within KEGS: BLOAD MOON PATROL CALL -151 1E15:EA 919G and it will work fine. If you have the booting version that just has Moon Patrol on it, then from any point after the crack screen is shown, open the KEGS debugger with F7 and in that window enter: 0/1e15:ea and then it will play fine. The bug is that the code executes an instruction with opcode $02, which is a NOP on 6502, but is a COP instruction to 65816. The COP instruction acts like BRK and will crash. The patch just makes it a real NOP. Planetfall.woz: Do not start it by booting DOS/ProDOS and then doing PR#6. It doesn't reset the output vector value at $36/$37, so as soon as it tries to do any output, it reboots! Oops! You can boot it from ProDOS/DOS 3.3 by: call -151 then c600g Robotron 2084: Robot Battle: These cracks use a "Fastloader" which is buggy. It tries to JMP $F3D4 and expects to hit an RTS soon. But on a IIgs it will access some illegal memory causing a code yellow. You can just ignore the code yellow. Ultima IV: Play fine, but Mockingboard won't work. There is apparently a patch in U4MBonGSv22.SHK, but I haven't tried it. Ultima V: Plays fine, but doesn't work with the Mockingboard enabled. This is an Ultima V bug: it's running code with ALTZP=1 which takes an interrupt. it expects to run on the ALTZP stack. But on a IIgs, the ROM will clear ALTZP and restores a safe stack in main memory, which Ultima V is not expecting. So it will hang/crash. There is apparently a patch in U5MBonGSv11.SHK which can fix this, but I haven't tried it. ================================================ FILE: upstream/kegs/doc/README.compile.txt ================================================ # $Id: README.compile.txt,v 1.26 2023/06/14 02:56:10 kentd Exp $ General build instructions: -------------------------- KEGS supports Mac OS X, Linux, and Win64 compiles. For Mac and Linux, the build is done from the command line using a "make" utility. There's a default Makefile, which should work for nearly any environment. The Makefile includes a file called "vars" which defines the platform-dependent variables. You need to make vars point to the appropriate file for your machine. For Windows, see below on using Visual Studio Community Edition. A KEGSMAC.app pre-built application for Mac OS 10.13 (High Sierra) is provided. A xkegs Linux app pre-built for 64-bit Redhat RHEL 7.2 is also provided. I'm hoping it works on other Linuxes. Mac OS X build instructions (the default): ------------------------------------------ KEGS is easy to compile, but you must install XCode first. Go to the Apple App store, select Xcode, and install it. You then must execute the Xcode.app and agree to terms. It will want to download additional components, you need those, too. Apple only makes the latest XCode available from the App Store, so if you're on Mojave, for example, you can no longer download that XCode. You need to create a free developer account, and then go to developer.apple.com/download/more/ and download the right version. For Mojave (10.14), you want XCode 11.3.1. The latest list of XCode versions for each Mac OS version is at en.wikipedia.org/wiki/XCode Then, cd to the src directory of the KEGS release and type "make -j 20". KEGS requires perl to be in your path, which is /usr/bin/ on the MAC (which should be in your $PATH). Even after installing XCode, the "make" may pop up a dialog saying something like "The make command requires the command line developer tools. Would you like to install the tools now?". Click "Install". After the "make" has finished, it will create the application KEGSMAC. To run, see README.mac. Linux build instructions: ------------------------ Use the vars_x86linux file with: rm vars; ln -s vars_x86linux vars make -j 20 The resulting executable is called "xkegs". The build scripts assume perl is in your path. If it is somewhere else, you need to edit the "PERL = perl" line in the Makefile to point to the correct place. KEGS by default requires Pulse Audio to compile. To compile to use /dev/dsp (not really supported by any current Linux as far as I know), edit vars to remove pulseaudio_driver.o, the -lpulse of EXTRA_LIBS, and remove -DPULSE_AUDIO from CCOPTS. You may not have the required include files on your system to compile BURST. On RHEL, use "yum provides {type path to file you got an error on}". For example, pulseaudio requires pulse/pulseadio.h which is indicated by the error message "pulse/pulseaudio.h: No such file". All include files are under /usr/include, so do: yum provides /usr/include/pulse/pulseadio.h to see that you need something like pulseaudio-libs-devel-10.0-5.el7.x86_64, and then do: su yum install pulseaudio-libs-devel-10.0-5.el7.x86_64 On my machine, I also had to install the 32-bit libs and headers: yum install pulseaudio-libs-devel-10.0-5.el7.i686 And I got an error for: "xdriver.c: fatal error: X11/Xlib.h: No such file or directory". So: yum provides /usr/include/X11/Xlib.h gives libX11-devel-1.6.7-3.el7_9.x86_64, so: yum install libX11-devel-1.6.7-3.el7_9.x86_64 And /usr/include/X11/extensions/XShm.h is needed, so: yum install libXext-devel-1.3.3-3.el7.x86_64 Then, /usr/lib64/libpulse.so wasn't found, so: yum install pulseaudio-libs-devel-10.0-6.el7_9.x86_64 Mac X11 build instructions: ---------------------------- Use the vars_mac_x vars file by: rm vars; ln -s vars_mac_x vars make -j 20 The build assumes you've installed XQuartz and run it to initialize itself. The executable is called xkegs. KEGS generally does the fastest display updates in X11 mode--but requires Xshm. Mac misconfigures the shared memory limits to be too small to be used as Xshm memory (4MB limit, we need 40MB for a retina display), so do: sudo sysctl kern.sysv.shmmax=67108864 # One segment max: 64MB sudo sysctl kern.sysv.shmall=131072 # in 4KB pages Total mem: 512MB Windows WIN64 build instructions (Windows 10 and later) ------------------------------------------------------- KEGS is easy to compile, but you must install Visual Studio (Community Edition) first, which is free. https://visualstudio.microsoft.com/vs/community/ I'm using the 2022 version. The executable in the KEGS release was built on Windows 10 for 64-bit Windows. Open a PowerShell (or any terminal window) and go to where you unpacked the KEGS source: cd src start kegswin.vcxproj You may also be able to just double-click the kegswin.vcproj file. Press 'F7' to generate an executable in x64\Release\kegswin.exe. Other platform "C" build instructions: ------------------------------------- If you are porting to an X-windows and Unix-based machine, it should be easy. Start with vars_x86linux. Remove things causing problems, add things as needed. Good luck! ================================================ FILE: upstream/kegs/doc/README.dynapro.txt ================================================ DYNAPRO ------- Dynapro is a KEGS feature where an emulated disk (140KB 5.25" in slot 6, 800KB 3.5" in slot 5, or any size up to 32MB in slot 7) volume is mapped to a host (Mac, Linux) directory (and subdirectories). This allows easy access to move files from emulation--just look at the files from outside KEGS on your host computer, they are always up to date. It also provide a simple way to move files into emulation: update the file under the Dynapro mount point, and then remount the volume to ensure KEGS/ProDOS can see the changes. How to enable ------------- Create a folder on your Mac/Linux system. You can place files in it, or leave it empty. From within KEGS, press F4, select Disk Configuration, and then use arrows to move to the slot and drive you want to mount that folder as. You have two choices: A slightly longer process where you can set the DynaPro image size (if you want less than 32MB for a slot 7 image), or a shorter process that always mounts the largest size for that slot. Selecting the size: ------------------ Press "n" to create a new image. Select the size you would like with left/right arrows if you picked slot 7. (slot 5 is always 800KB, and slot 6 is always 140KB). It's fine to always pick 32MB, the default. Scroll down to Type, select "Dynamic ProDOS directory". Scroll down to "Create and name the image". Use the normal image selection interface to navigate to the directory you want to use. Press tab to switch to the path view (you can manually enter parts of the path if you like), and while on the path, press Cmd-Enter to select the named directory. Note: The directory that will be selected is the path listed on the "Path: " line (relative to the Current KEGS Directory if it doesn't start with a "/"). Using the maximum size: ---------------------- Press return on the slot/drive line you want to mount as a Dynapro directory. Using selection, select into the directory you want to use, then press Cmd-Enter to select that directory. A maximum size Dynapro directory will be mounted. The maximum size for slot 7 images is 32767.5KB (65535 blocks). How to use ---------- If you pick a directory that has files whose total size exceeds the size you selected, it will fail to mount. The slot/drive entry will be commented out showing no disk mounted. Now, from within KEGS, you can do any operation to this volume that you would have if it were a normal disk image--copy files, initialize it, etc. This works under ProDOS 8 or GS/OS, or any program that supports ProDOS images. All changes to the image as ProDOS volume are immediately reflected in the directory on your host machine. Gotchas to watch out for ------------------------ KEGS aggressively makes the directory match the ProDOS volume. If you erase a file using GS/OS (as an example), KEGS will erase that file from the directory you selected. If you format the volume, KEGS will erase all files in the directory you selected. So: be careful what you point KEGS at! How it works ------------ KEGS keeps a version of the ProDOS volume in memory (so 32MB if you mount a 32MB Dynapro directory). Whenever any writes happen, KEGS looks to see what file(s) are affected, and reparses them to recreate the files in the Dynapro directory on your host system. Since ProDOS and GS/OS write one block at a time, there are many times during a write to a file where the file is invalid. KEGS erases all files from the Dynapro host directory when the ProDOS file appears invalid, even briefly. So, writing to a file can cause it to be erased from the Dynapro host directory until the file is completely written and appears valid again. How to copy files from your host system to KEGS emulation --------------------------------------------------------- ProDOS and GS/OS do not expect ProDOS volumes to change behind their back. To reliably transfer files from your host system into the emulated //gs system, you should unmount and re-mount the Dynapro directory. This tells ProDOS 8 and GS/OS to expect changes to the volume, and to see any changes you've made. So if you create a new file called "NEW.TXT" in the Dynapro host directory, it doesn't show up in KEGS right away. Press F4, go to the Disk Configuration page (you may already be there if you left the F4 configuration here), select the slot/drive of the Dynapro volume to remount and press Return. A file selection dialog appears. Just press Cmd-Enter to re-mount the image (the selection for the existing volume will already be correct). Press F4 to return to the emulated system. ================================================ FILE: upstream/kegs/doc/README.kegs.txt ================================================ KEGS: Kent's Emulated GS version 1.34 http://kegs.sourceforge.net/ What is this? ------------- KEGS is an Apple IIgs emulator for Mac OS X, Linux, and Windows. The Apple IIgs was first sold in 1986 and was the most powerful computer in the Apple II line. An Apple IIgs has the capability to run almost all Apple II, Apple II+, Apple IIe, and Apple IIc programs. KEGS supports all Apple IIgs graphics modes (which include all Apple //e modes), plus plays all Apple IIgs sounds accurately. It supports serial port emulation through TCP/IP connections, or can use real serial ports on Linux and Mac OS X. The ROMs and GS/OS (the Apple IIgs operating system) are not included with KEGS since they are not freely distributable. KEGS is a little user-hostile now, so if something doesn't work, let me know what went wrong, and I'll try to help you out. See my email address at the end of this file. KEGS QuickStart: --------------- Run the KEGSMAC executable (see README.mac.txt on how to get around Mac security restrictions), or kegswin.exe on Windows 10 or later, or xkegs on Linux. You must download and place a ROM file in the directory you run KEGS from, or in your $HOME directory. GS ROM version 01 should be named ROM.01, and GS ROM version 03 should be named ROM.03. See Getting ROMs below. Once KEGS is running, if it cannot find a ROM file, it will ask you to select one using an Apple-II style directory selection screen. When done, Press F4 to start. Press F4 while KEGS is running to select Disk images. The default config.kegs will boot Nucleus in s7d1. If you unmount it (F4->Disk Configuration, go down to s7d1, press E to eject, then F4, then do Ctrl-F1-F12 to reset and reboot, then the XMAS demo will start). Apple IIgs programs need Command and Option keys, and these sometimes are used by windowing systems on Linux and Windows. F1 is always Command (or Open-Apple) and F2 is always Option (or closed-Apple), and F3 is an alias for ESC. So entering the IIgs control panel can be done with Ctrl-F1-F3. Press F5 to toggle the status display. Press F6 to toggle speed between 1MHz, 2.8MHz, 8MHz, and Unlimited. You can only see the current speed with the status lines enabled. You can resize the KEGS window. KEGS features: ------------- Fast 65816 emulation: About 500-600MHz on a modern Mac Intel machines. 1GHz on Mac M1. Emulates low-level 5.25" and 3.5" drive accesses (even nibble-copiers work!). Emulates classic Apple II sound and 32-voice Ensoniq sound. All sound is played in 16-bit stereo at 48KHz. Emulates all Apple IIgs and Apple II graphics modes, including border effects. Can handle display changes at any time (superhires at the top, lores at the bottom). Always does 60 full screen video updates per second. Supports 3200-color pictures. Mouse and joystick support. Emulated battery RAM remembers control panel settings. Limited SCC (serial port) emulation to enable PR#1/2 IN#1/2 and Virtual Modem enables standard Apple terminal programs to telnet to any internet address (or receive connections from another telnet). Mockingboard A support. Set slot 4 to "Your card" in the IIgs control panel. Note that the mouse doesn't work in GS/OS on ROM01 if slot 4 is set to "Your card". Dynapro: You can mount host directories as volumes for ProDOS 8 and GS/OS, allowing easy transfer of data to/from KEGS emulation. Changes to files inside KEGS emulation are immediately made to the files visible in the host directory. KEGS by default emulates a 8MB Apple IIgs, but you can change this from the Configuration Panel. KEGS is so accurate, even the built-in ROM selftests pass (you must be in 2.8MHz speed mode to pass the self-tests and you must set the Configuration Panel entry "Enable Text Page 2 shadow" to "Disabled on ROM 01" for ROM 01. Otherwise, the selftests will fail with code 040000). Release info: ------------ Included files: CHANGES - Description of changes since last release README.kegs.txt - you're here README.compile.txt - Describes how to build KEGS README.linux.rpm.txt - Describes how to install KEGS's RPM for Linux README.mac.txt - Mac OS X special directions README.a2.compatibility.txt - List of programs which need tweaking src/INTERNALS.overview - description of how KEGS code works src/INTERNALS.xdriver - Describes the xdriver.c routines for porting src/INTERNALS.iwm - Describes the internal 3.5" and 5.25" disk handling routines KEGSMAC - the Mac OS X executable kegswin.exe - the Windows executable config.kegs - disk image configuration info src/ - All the source code, with a Makefile You need to provide: 1) Patience. 2) a ROM file called "ROM", "ROM.01" or "ROM.03" in the KEGS directory (or in your home directory). It can be either from a ROM 01 (131072 bytes long) or from a ROM 03 machine (262144 bytes long.) 3) A disk image to boot. This can be either "raw" format, 2IMG, or Woz. See discussion below. Getting ROMs ------------ You need a copy of the memory from fe/0000 - ff/ffff from a ROM 01 GS or fc/0000 - ff/ffff from a ROM 03 GS, and put that in a file called "ROM". I'll eventually write detailed instructions on how to do this. There are links to download ROM 01 and ROM 03 at https://www.whatisthe2gs.apple2.org.za. Running KEGS: ------------ The distribution comes with the full source code for all platforms in the src/ directory, the Linux executable as xkegs, the Windows executable is kegswin.exe, and the Mac OS X executable as KEGSMAC.app. See the README.compile.txt file for more info about compiling for Linux. On Linux, you must start KEGS from a terminal window. You can double-click on KEGSMAC.app or kegswin.exe to run those executables. You need to place the "config.kegs" file someplace where KEGS can find it. The simplest place is in your home directory, so copy it there with the Finder (or using the Terminal). You can also make the directory Library/KEGS from your home directory, and then place config.kegs there along with the ROM file. You do not need a starting config.kegs file on a Mac--KEGS will offer to make it for you if it cannot find one. Start kegs by Double-clicking the KEGSMAC icon on a MAC, or by running the executable (kegswin on Windows, and kegs on Linux). KEGSMAC can be run by the Terminal window on a Mac as well (which enables access to more debug information) by typing: "./KEGSMAC.app/Contents/MacOS/KEGSMAC". There may be permissions/security issues on a Mac, see README.mac.txt for details. Assuming all goes well, KEGS will then boot up but probably not find any disk images. See below for how to tell KEGS what disk images to use. Tip: Hitting "F8" locks the mouse in the window (and hides the host cursor) until you hit "F8" again. Configuration Panel: ------------------- You enter the Configuration panel by pressing F4 at any time. You tell KEGS what disk images to use through the Configuration panel. (If KEGS couldn't find a ROM file, you will be forced into the Configuration Panel mode until you select a valid ROM file). To select a ROM file, select "ROM File Selection" and then select your ROM file. If you were not forced into the panel at startup, the KEGS found one and you can skip this step. Disk Images ----------- The primary use of the Configuration Panel is to select disk images. To change disk images being used, select "Disk Configuration". Each slot and drive that can be loaded with an image is listed. "s5d1" means slot 5, drive 1. Slot 5 devices are 3.5" 800K disks, and slot 6 devices are 5.25" 140K disks. Slot 7 devices are virtual hard drives, and can be any size at all (although ProDOS-formatted images should be less than 32MB). Just use the arrow keys to navigate to the device entry to change, and then select it by pressing Return. A scrollable file selection interface is presented, letting you locate your image files. To quickly jump to a particular path, you can press Tab to toggle between entering a path manually, and using the file selector. Press Return on ".." entries to go up a directory level. When you find the image you want, just press Return. If the image has partitions that KEGS supports, another selection dialog will have you select which partition to mount. You will probably only have partitions on direct devices you mount (or on a Mac, of .dmg images of CDs). For instance, on a Mac, /dev/disk1 can sometimes be the CDROM drive. KEGS can handle "raw", .dsk, .po, 2IMG, 5.25" ".nib" images, most Mac Diskcopy images and partitioned images, .zip files, .sdk archives, and .woz files. The .dsk and .po formats you often find on the web are really "raw" formats, and so they work fine. KEGS uses the host file permissions to encode the read/write status of the image. KEGS can open any image file compressed with gzip (with the extension ".gz") or inside a .zip archive automatically as a read-only disk image. An image is the representation of an Apple IIgs disk, but in a file on your computer. For 3.5" disks, for example, a raw image would be exactly 800K bytes long (819200 bytes). KEGS directs the emulated GS accesses to the image, and does the correct reads and writes of the Unix file instead. To do "useful" things with KEGS, you need to get a bootable disk image. You can go to: https://archive.org/details/softwarelibrary_apple2gs to download GS/OS 6.0.1 (the last official Apple release). You can also get Apple II programs in ".dsk" format from a variety of sites on the internet, and these should all work on KEGS as well. KEGS also supports partitioned devices. For instance, if you have a CD-ROM on your computer, just pop an Apple II CD in, and KEGS can mount it, if you have a Unix-based system (Linux, any Unix, and Mac OS X). Avoid having KEGS access a writeable image which have mounted on your Mac at the same time (always unmount it from your Mac before letting KEGS access it)! If you do not have any disk mounted in s7d1, KEGS will jump into the monitor. To boot slot 6 (or slot 5), use the Apple IIgs Control Panel by pressing Ctrl-Command-ESC (Ctrl-F1-F3 on Windows, for example). Support for 5.25" Woz nibblized images is limited to using provided images-- KEGS cannot create new images currently. In addition to changing disks, you can also just "eject" and image by moving the cursor to select that slot/drive and then press "E". The emulated IIgs will immediately detect changes to s5d1 and s5d2. Care should be taken when changing images in slot 7--KEGS does not notify GSOS that images have changed (or been ejected), and so it's best to make changes when GSOS is not running. Key summary: ----------- F1: Alias of Command F2: Alias of Option F3: Alias of ESC for Windows compatibility. F4: Configuration Panel F5: Toggle status lines on/off F6: Toggle through the 4 speeds: Unlimited, 1MHz, 2.8MHz, 8.0MHz (ZipGS) Shift-F6: Enter KEGS debugger F7: Toggle debugger window Shift-F7: Toggle fast_disk_emul on/off F8: Toggle pointer hiding on/off. F9: Invert the sense of the joystick. Shift-F9: Swap x and y joystick/paddle axes. Ctrl-F9: Copy Text screen to host system clipboard F12: Alias of Pause/Break which is treated as Reset F2, Alt_R, Meta_r, Menu, Print, Mode_switch, Option: Option key F1, Alt_L, Meta_L, Cancel, Scroll_lock, Command: Command key Num_Lock: Keypad "Clear". F12, Pause, Break: Reset. Must press Ctrl to get Apple IIgs to reset "Home": Alias for "=" on the keypad (since my Unix keyboard doesn't have an =). Alt keys don't work well for Command and Option on Windows. They only can be detected when another key is pressed. So Alt_L-Q will be detected as Command-Q. But just pressing Alt_L alone will not do anything. Using KEGS: ---------- The host computer mouse is the Apple IIgs mouse and joystick by default. By default, the host pointer is not constrained inside the window and remains visible. Press F8 to hide the cursor and constrain the mouse. F8 again toggles out of constrain mode. The middle mouse button or Shift-F6 causes KEGS to stop emulation, and enter the debugger. You can continue with "g" then return in the debug window. You can also disassemble memory, etc. The section "Debugging KEGS" above describes the debugger interface a little more. KEGS has no pop-up menus or other interactive interfaces (other than the debug window, and the occasional error dialogs on Mac OS X). Input to the debug window is only acted upon when the emulation is stopped (Shift-F6, middle mouse button, or hitting a breakpoint). Quitting KEGS: ------------- Just close the main KEGS window, and KEGS will exit cleanly. Or you can select Quit from the menu. Or enter ctrl-c in the debugger window. Or press the middle-mouse button in the emulation window, and then type "q" return in the debug window. Command/Option keys: ------------------- If you have a keyboard with the special Windows keys, you can use them as the command/option keys. For those without those keys, there are several alternatives. The following keys are Option (closed-apple) (not all keyboards have all keys): F2, Meta_R, Alt_R, Cancel, Print_screen, Mode_switch, Option, or the Windows key just to the right of the spacebar. The following keys are Command (open-apple): F1, Meta_L, Alt_L, Menu, Scroll_lock, Command, the Windows key left of the spacebar, and the Windows key on the far right that looks like a pull-down menu. You can use F1 and F2 if you cannot make anything else work (especially useful if your OS is intercepting some Alt or Command key sequences). If you can't get any of these to work on your machine, let me know. Note that X Windows often has other things mapped to Meta- and Alt- key sequences, so they often don't get passed through to KEGS. So it's best to use another key instead of Alt or Meta. The joystick/paddle buttons are just the Command and Option keys. Reset: ----- The reset key is Pause/Break or F12. You must hit it with Ctrl to get it to take effect (just like a real Apple IIgs). Ctrl-Command-Reset forces a reboot. Ctrl-Command-Option-Reset enters selftests. Selftests will pass if you force speed to 2.8MHz using the middle button or F6 (and also set Enable Text Page 2 shadow = Disabled for ROM 01). Watch out for ctrl-shift-Break--it will likely kill an X Windows session. Also note that the Unix olvwm X window manager interprets ctrl-F12 and will not pass it on to KEGS--you'll need to use Break for reset in that case. Full Screen mode (MAC OS X ONLY): -------------------------------- KEGS can run in full screen mode--which is especially useful when letting small kids use KEGS (but it is not really a lock, so do not let a 2 year old bang on the keyboard while running KEGS). Full Screen mode is toggled by clicking the green maximize button in the title bar. To exit full screen mode, move the mouse to the top of the screen, and the title bar will reappear, and you can click the green maximize button to shrink the window down. Resizing the KEGS window: ------------------------ All versions of KEGS allow the window to be resized, so you can make the window take up nearly the full screen without being "full screen" mode. KEGS will save the window position and size when it updates config.kegs, but currently doesn't automatically update config.kegs. So if you resize the window, and then do something which changes config.kegs (inserting or ejecting disks always immediately update config.kegs, or selecting the F4 menu option "Save changes to config.kegs" will update config.kegs). Joystick Emulation (Mouse, Keypad, or real native joystick): ----------------------------------------------------------- The default joystick is the mouse position. Upper left is 0,0. Lower right is 255,255. Press Shift-F9 to swap the X and Y axes. Press F9 to reverse the sense of both paddles (so 0 becomes 255, etc). Swapping and reversing are convenient with paddle-based games like "Little Brick Out" so that the mouse will be moving like the paddle on the screen. "Little Brick Out" is on the DOS 3.3 master disk. The joystick does not work properly if the pointer is constrained in the window. You can also select from a "Keypad Joystick" or a real joystick from the Configuration panel. Press return on the "Joystick Configuration" entry, and then select between Mouse Joystick, Keypad Joystick, or one of two native joysticks. The Keypad Joystick uses your keypad number keys as a joystick, where keypad 7 means move to the upper left, and keypad 3 means move to the lower right. Pressing multiple keys together averages the results, allowing finer control than just 8 directions. Also, joystick scaling is selectable here for games which require a greater range of motion to work correctly, along with trim adjustment which moves the centering point. Adjusting scaling usually means you will need to adjust the trim as well. The left mouse button is the mouse button for KEGS. The right mouse button (if you have it) or F6 toggles between four speed modes. Mode 0 (the default) means run as fast as possible. Mode 1 means run at 1MHz. Mode 2 means run at 2.8MHz. Mode 3 means run at 8.0MHz (about the speed of a ZipGS accelerator). Most Apple //e (or earlier) games need to be run at 1MHz. Many Apple IIgs demos must run at 2.8MHz or they will not operate correctly. Try running ornery programs at 2.8MHz. 3200 pictures generally only display correctly at 2.8MHz or sometimes 8.0MHz. Debugging KEGS: -------------- KEGS by default now continues emulation even when it detects buggy programs running. (Now I know why Appleworks GS always seemed to crash!). KEGS divides buggy programs into two severities: Code Yellow and Code Red. The status is displayed in words in the text area under the emulation window. If nothing is wrong, nothing is printed. A Yellow bug is a mild bug where an Apple IIgs program merely read an invalid location. Although completely harmless, it indicates the potential for some Apple IIgs program bug which may become more severe shortly. For instance, closing the "About This Apple IIgs" window in the Finder causes a code yellow alert, but it seems quite harmless. A Code Red bug is a more serious problem. The Apple IIgs program either tried to write non-existent memory, entered an invalid system state, or perhaps just tried to use an Apple IIgs feature which KEGS does not implement yet. Note that entering GSBUG tends to cause a Code Red alert always, so if you intended to enter it, you can ignore it. My recommendation is to save work immediately (to new files) and restart KEGS if you get into the Red mode. KEGS also supports breakpoints and watchpoints. In the debug window, you set a breakpoint at an address by typing 'bp' and then the address. To set a breakpoint on the interrupt jump point, type: bp e10010 To list all breakpoints, just type 'bp' with no number after it. To delete a breakpoint, enter 'bp clear" and then the address: bp clear e10010 deletes the above breakpoint. You can also break on a range of addresses: bp e10400-e107ff You can erase all breakpoints with: bp clear all This is a "transparent" breakpoint--memory is not changed. But any read (including instruction fetch) or write to that address will cause KEGS to halt right after the access occurs. So you can set breakpoints on I/O addresses, or ROM, or whatever. Setting a breakpoint slows KEGS down somewhat, but only on accesses to the 256 byte "page" the breakpoint is on. Breakpoints are not just instruction breakpoints, they also cause KEGS to halt on any data access, too (usually called watchpoints). You can get more debugger window help with "help" and "help bp". Useful locations for setting breakpoints: bp 3f0 - Break handler bp c000 - Keyboard latch, programs read keys from this address For debug, KEGS can log the instruction executed, saving the last 2 million instructions. Enable logging with (in the debug window, press F7): logpc on Then run whatever you want, and stop KEGS (Shift-F6, middle mouse, or hitting a breakpoint). Then in the debug window do: logpc save This will create the text file "logpc_out" which you can look at. You can start KEGS with "logpc on" by passing in the "-logpc" option to KEGS when you start it. KEGS command-line option summary: -------------------------------- There are others, but the Configuration panel provides a better way to set them so they are no longer listed here. -skip: KEGS will "skip" that many screen redraws between refreshes. -skip 0 will do 60 frames per second, -skip 1 will do 30 fps, -skip 5 will do 10 fps. -audio [0/1]: Forces audio [off/on]. By default, audio is on unless the X display is a remote machine or shared memory is off. This switch can override the default. -audio 0 causes KEGS to not fork the background audio process, but Ensoniq emulation is still 100% accurate, just the sound is not sent to the workstation speaker. Audio defaults off on Linux for now. -arate {num}: Forces audio sample rate to {num}. 44100 and 48000 are usual, you can try 22050 to reduce KEGS's overhead. On a reasonably fast machine (>250MHz or so), you shouldn't need to mess with this. -dhr140: Will use the old Double-hires color algorithm that results in exactly 140 colors across the screen, as opposed to the blending being done by default. -logpc: Turn on PC logging. Useful for debugging early startup problems. X-Windows/Linux options -15: KEGS will only look for a 15-bit X-Window display. -16: KEGS will only look for a 16-bit X-Window display (not tested, probably will get red colors wrong). -24: KEGS will only look for a 24-bit X-Window display. -display {machine:0.0}: Same as setting the environment variable DISPLAY. Sends X display to {machine:0.0}. -noshm: KEGS will not try to used shared memory for the X graphics display. This will make KEGS much slower on graphics-intensive tasks, by as much as a factor of 10! By default, -noshm causes an effective -skip of 3 which is 15 fps. You can override this default by specifying a -skip explicitly. -cfg: Next argument gives the path and name of the config.kegs file to use -rom=path_to_rom or -rom path_to_rom: Selects the ROM file, either a //e ROM file, or a IIgs ROM 01 or ROM 03 file. -s5d1=path_to_image or -s5d1 path_to_image mounts the selected image in Slot 5 Drive 1 (800KB disk). Can use -s5d1 through -s6d2, as well as -s7d1 through -s7d12. Apple IIgs Control Panel: ------------------------ You can get to the Apple IIgs control panel (unless some application has locked it out) using Ctrl-Command-ESC. On Windows, Command and ESC can be tricky to enter (Windows grabs them for itself), so Ctrl-F1-F3 is the same as Ctrl-Command-ESC. Important things you can do here: Change the speed to 1MHz (for Apple II compatibility) (or just F6 to change the speed to 1MHz), change the boot device (so you can boot s6d1 directly), and change Slot 4 to Your Card to enable Mockingboard. Moving data into and out of KEGS -------------------------------- Use the Dynapro image support to mount a folder on your host machine as a ProDOS volume for ProDOS 8 or GS/OS. Then use GS/OS (or any other program) to move files to emulated volumes, or just keep using Dynapro. See README.dynapro.txt for more details. From the debugger (press F7 to open the Debugger window), you can load and save any memory address to a Unix host file. To save the hires page 1 screen, use: 2000.3fffus hgr1 Saves from $2000 through $3fff to the file "hgr1". You can also load memory from a file using: 300ul helper Loads the contents of the file "helper" at $300. Using the VOC ------------- KEGS has limited support for the Apple Video Overlay Card (VOC). The VOC supports a special 640x400 interlaced SHR video mode which is what KEGS also supports. To turn on this mode, you need to do: Enable VOC (Press F4, select "Video setting"", then select "Enable VOC = Enabled") The following write done manually will enable the VOC: CALL -151 c029:c1 # Turn on SHR c0b1:39 # Set bits [5:4]=11 to enable Interlaced mode c0b5:80 # Set bit 7 to enable Interlace mode And then KEGS will show 640x400 (or 320x400) SHR screen, where even lines (0,2,4,etc) come from bank $e1 like normal SHR mode, and odd lines come from bank $e0 (from $2000-$9fff, using it's own palettes, etc.). The real VOC shows interlaced data--it draws one frame of data from bank $e1, then the next from of data from bank $e0, shifted down one line, etc., where each from takes 16msec, for an effective display of 30fps. KEGS just draws both bank $e1 and bank $e1 changes every frame at 60fps. This can be fixed if anyone uses this mode and wants better accuracy. Details on config.kegs and disk images -------------------------------------- The file "config.kegs" describes the images KEGS will use. Although you can edit the file manually, in general you can use the Configuration Panel to make all the changes you need. This information is for reference. KEGS by default will boot s7d1 (unless you've changed that using the Apple IIgs control panel), so you should put an image in that slot. KEGS, by default, runs the IWM (3.5" and 5.25" disks) emulation in an "approximate" mode, called "fast_disk_emul". In this mode, KEGS emulates the hardware "faster" than real, meaning the data the code being emulated expects is made available much faster than on a real Apple IIgs, providing a nice speed boost. For instance, the 5.25" drives run 100x the real speed usually. Almost everything will work except for nibble copiers, which don't like the data coming this fast. Also, formatting new 5.25" disks doesn't work will in fast_disk_emul mode. (Meaning, unless you're using a nibble copier or formatting a disk, you shouldn't run into an issue. Almost all games/demos/etc run fine in this mode). To make nibble copiers work, Press Shift-F7. If you start with a .dsk or other raw image, and do writes to it that are no longer ProDOS or DOS 3.3 standard, KEGS will automatically treat the image as "Not-write-through-to-Image" from then on. This mode means KEGS will continue to emulate the disk properly in memory, but it cannot encode the changes in the standard .dsk or .po image format. It prints a message saying it has done so. However, the "disk" in emulation is fully useable as long as KEGS is running. A standard reformatting will not cause an image to flip to not-write- through-to-Image, but running things like a "drive-speed" test will cause further changes not to propagate to the Unix file. You will need to "eject" the image and re-insert it before writes will take effect. In full accuracy mode (i.e., not fast_disk_emul), 5.25" drive accesses force KEGS to run at 1MHz, and 3.5" drive accesses force KEGS to run at 2.5MHz. KEGS supports WOZ version 1 and version 2 images. These are fully read/write, but most Woz images are marked read-only in the header. When accessing a Woz image, KEGS automatically acts as if fast_disk_emul is disabled. KEGS Timing: ----------- KEGS supports running at four speeds: 1MHz, 2.8MHz, 8.0MHz-128MHz (ZipGS), and Unlimited. Pressing the the F6 key cycles between these modes. The 1MHz and 2.8MHz speeds force KEGS to run at exactly those speeds, providing accurate reproduction of a real Apple IIgs. On regular host machines now, sitting at the BASIC prompt can make KEGS say the speed is 4000MHz or higher. This is because the emulated code is reading the keyboard (or other softswitch) more often than 500,000 times per second, and due to how slow accesses work, that's the most that can be done in one second, and so KEGS has lots of spare time, and makes the Eff MHz or Sim MHz be too high. You have to run a BASIC program or do something not checking for keypresses so often to see what the true emulation speed is, so something like: 10 A = SIN(A) + 1 20 GOTO 10 is a fine test. KEGS will always run at 1MHz at least. If it is unable to keep up, it will extend the emulated time to maintain the illusion of running at 1MHz. That is, it may do just 40 screen refreshes per real second, instead of the usual 60. This happens rarely. (There's a Mac OS X bug in recent releases after 10.13 which can cause KEGS to draw VERY slowly to the screen, such that KEGS falls below 1MHz. On your Mac, go to the Apple Menu->System Settings, then select Displays. Select the "Color" tab. Change to a different setting, usually one with "RGB" in the name. See if KEGS now easily exceeds 1MHz). If you force KEGS to run at 1MHz, it will strive to run at exactly 1MHz (well, really 1.022MHz). If it is running faster (almost always), it will pause briefly several times a second to maintain the 1MHz speed. It does this in a friendly way that makes time available to other tasks. This makes older Apple II games very playable just like a real Apple IIgs on slow speed. KEGS is running at exactly the same speed as an Apple //e when in 1MHz mode. The 1MHz mode you set through the F6 key overrides the "fast" mode you can access through the control panel. But, 3.5" accesses will "speed up" to 2.8MHz to enable that code to operate correctly while the 3.5" disk is being accessed. If you force KEGS to run at 2.8MHz, KEGS tries to run at exactly 2.8MHz. But like a real unaccelerated Apple IIgs, if you set the control panel to "slow", it will really be running at 1MHz. Accesses to 5.25" disk automatically slow down to 1MHz, when running the IWM in accurate mode (F7) or when accessing a Woz image. Many Apple IIgs demos must be run at 2.8MHz. The built-in selftests (cmd-option-ctrl-Reset) must run at 2.8MHz. Many Apple IIgs action games are more playable at 2.8MHz. The 8.0MHz setting means follow the ZipGS-selected speed, but don't go faster than 8.0MHz. This 8.0MHz value can be changed to 8MHz, 16MHz, 32MHz, 64MHz, or 128MHz at the Configuration settings (Press F4), and change "ZipGS Speed = ". If your host computer cannot keep up, then the emulated second will be extended, and KEGS will have choppy audio and video. You can use the ZipGS control panel, or ZIPPY.GS on the sample disk image to set the emulated ZipGS speed to any 1/16th increment of the selected speed (so if you pick a ZipGS speed of 64MHz on the KEGS Configuration screen (F4), you can set the emulation speed to any value from 4-64MHz in 4MHz increments) The Unlimited setting means run as fast as possible, whatever speed that is (but always above 1MHz). Eff MHz gives you the current Apple IIgs equivalent speed. Many games will be unplayable at the unlimited setting. Setting the IIgs control panel speed to "slow" will slow down to 1MHz. Sound output has an important relationship to KEGS timing. KEGS must play one second of sound per second of emulated time. Normally, this works out exactly right. But as noted above, if KEGS can't maintain the needed speed, it extends the emulated second. If it extends the second to 1.4 real seconds, that means KEGS only produces 1.0 second of sound data every 1.4 seconds--the sound breaks up! In all cases, 1MHz to KEGS is 1.022MHz. It's just easier to say 1MHz. KEGS SAMPLE_DISK: ---------------- I'm providing a sample disk of freely available utilities/programs to demonstrate a little of what KEGS can do. I'm also including my simple changes to a benchmark called "SPEEDTEST" to make it run under ProDOS and time itself automatically. The SAMPLE_DISK is not bootable since I'm not sure if I can distribute PRODOS (the OS). SPEEDTEST: --------- In the folder "SPEEDTEST", there are two BASIC programs. OLD.SPEEDTEST is the old, unmodified DOS 3.3 emulator benchmark by Clayten Hamacher. It does not run properly under ProDOS 8. My modified version is SPEED.PRO, meaning converted to ProDOS. I made few modifications, other than to make the benchmarks time themselves. To run, just say "RUN SPEED.PRO". To run benchmarks, press "B". If you say "A)ll tests", make sure you have a 5.25" disk image in s6d1! (A blank 140K image will work fine). This modified SPEED.PRO can run on ANY Apple IIgs emulator (or on the real thing). GSOS7, GSOS5, BYE.SYSTEM: ------------------------ These are handy utilities I use on my s7d1 boot disk. Get a GS/OS 6.x bootable disk image. (See GSOS.INFO file for how to get GS/OS). Remove "PRODOS" from that disk's root directory, and copy GSOS7 to the root directory. Then copy SYSTEM/P8 to PRODOS. Then move BASIC.System into SYSTEM/. Then copy BYE.SYSTEM to the root directory, then move BASIC.SYSTEM back to the root directory. What all this means is that now the root directory of your system disk is: GSOS7, (other stuff), PRODOS, BYE.SYSTEM, and BASIC.SYSTEM. When you boot, ProDOS will boot (this is PRODOS 8) and will search for the first *.SYSTEM file, and run it. BYE.SYSTEM just does a BYE command, which puts you in the PRODOS 8 textual launcher. If you now select GSOS7 (the first entry, already highlighted, just hit return), it will boot GSOS on slot 7. (Use GSOS5 to boot slot5). Or, just move down and select BASIC.SYSTEM to go to BASIC. A very simple program launcher!? Note that I didn't write GSOS5 or GSOS7--I just made a one byte hack to the default GS/OS launcher. No real wizardry is going on here. SHRINKIT3.4, GSHK1.1: -------------------- Useful for unpacking .SHK files you can download off of the net. Always use GSHK (GS/OS version of ShrinkIt) for GS programs since they may have resource forks. It's also faster. GSHK must be run from GS/OS. LISTV2.0: -------- ProDOS 8 text file lister, useful for viewing text files. Wolfenstein3D: ------ Wolfenstein 3D for the Apple IIgs. No kidding! Must be run from GS/OS. SOUND22: ------- Cool little ProDOS 8 program (SOUND.EDITOR) that plays hi-fidelity (relatively) through the old Apple II speaker. This is included as a demonstration of how accurate KEGS sound emulation is. Sound.Smith.95: -------------- GS/OS application that plays SoundSmith songs, which are spreadsheet music, like MODs. I included some sample songs--FILE.11, FILE.16, FILE.17, and SPACE.HARRIER. Enjoy! SOLITAIRE: --------- Klondike. I like the interface on this game. CAT.DOCTOR: ---------- From Prosel8 (which is now public domain), this utility is very handy for sorting directories (among other things). Useful for arranging GSOS7, and BYE.SYSTEM mentioned above. BGSOUND: ------- This CDA lets you play Soundsmith songs in the background while other applications are running. Very handy for playing Solitaire with some music. DOCVu.CDA: --------- This CDA shows the current DOC contents in real-time. It has neat visual effects while playing Soundsmith songs. Zippy.gs -------- Very useful ProDOS 8 program by Andy McFadden for setting ZipGS parameters. In KEGS, you'll want to use this to change the Zip speed to less than 100% to make the "Unlimited" speed become limited to 7.5MHz, which is useful for some games. KEGS: What works: ----------------- Basically, just about every Apple II program works. See the file README.a2.compatibility for directions on how to make certain games/programs work. KEGS is EXTREMELY compatible. But, I haven't tested everything. Let me know if you find a program which is not working correctly. Some old Apple II 5.25" games require the old C600 ROM image, and don't work with the default Apple IIgs ROM. This is not KEGS's fault--these games don't run on a real Apple IIgs either. KEGS has built-in the old Apple II Disk PROM which you can enable by using the IIgs control panel to set Slot 6 to "Your Card". This allows many more Apple II games to run, and is the recommended setting. The NinjaForce Megademo mostly works, but sometimes hangs in the BBS Demo. Just skip that demo if it happens. The California Demo hangs at startup unless you use the IIgs control panel to boot from slot 5, and then do a ctrl-Open_Apple-Reset to boot--doing the above lets it work fine. This seems to be a bug in the demo. KEGS bugs: --------- On a ROM03, KEGS makes a patch to the ROM image (inside emulation, not to the Unix file) to fix a bug in the ROM code. Both ROM01 and ROM03 are patched to enable use of more than 8MB of memory. I then patch the ROM self-tests to make the ROM checksum pass. But other programs, like the Apple IIgs Diagnostic Disk, will detect a ROM checksum mismatch. Don't worry about it. Sound breaks up if KEGS is unable to keep up, this should not happen on any modern computer, but resizing the window can cause issues. Sound emulation: --------------- KEGS supports very accurate classic Apple II sound (clicking of the speaker using $C030) and fairly accurate Ensoniq sound. When KEGS determines that no sound has been produced for more than 5 seconds, it turns off the sound calculation routines for a small speedup. It describes that it has done this by saying "Pausing sound" in the debug window. If your display is not using shared memory, audio defaults to off unless you override it with "-audio 1". Mockingboard emulation is just for the AY8913 sound chip, not for the SC01 speech chip. To use the Mockingboard, you must do Ctrl-Cmd-ESC (Ctrl-F1-F3 on Windows, for example) to get to the IIgs control panel, select Control Panel, then Slots, then set slot 4 to "Your card". Make sure you press return. Then get out, and then do Ctrl-Cmd-Reset. The reset is a limitation of the IIgs ROM, it often doesn't seem to make proper register changes until the next Reset). SCC (Serial Port) emulation: --------------------------- See README.serial.ports.txt. KEGS status area: ---------------- The status area is updated once each second. It displays info I am (or was at some time) interested in seeing. Line 1: (Emulation speed info) dfcyc: number of seconds since KEGS was started sim MHz: Effective speed of KEGS instruction emulation, not counting overhead for video or sound routines. Eff MHz: Above, but with overhead accounted for. Eff MHz is the speed of an equivalent true Apple IIgs. This is extremely accurate. sec: The number of real seconds that have passed during on of KEGS's emulated seconds. Should be 1.00 +/- .01. Under 1 means KEGS is running a bit fast, over 1 means KEGS is running slow. When you force speed to 2.8MHz, if KEGS can't keep up, it extends sec, so you can see how slow it's really going here. vol: Apple IIgs main audio volume control, in hex, from 0-F. Limit: Prints which speed setting the user has requested: 1MHz, 2.8MHz, 8MHz=128MHz, or Unlimited. Line 2: (Host overhead) sleep_dtime: Amount of time, in seconds, where KEGS "slept" for the last second. Indicates how idle KEGS was. out_16ms: Amount of time, in seconds, KEGS spent drawing to the host screen and other overhead, in the last second. in_16ms: Amount of time, in seconds, KEGS spent emulating the Apple IIgs in the last second. snd_pl: Number of times a sound parameter was changed while it was playing, but there will always be 60 per second minimum. Line 3: (IWM info) For each IWM device, this line displays the current track (and side for 3.5" disks). If a disk is spinning, there will be an "*" next to the track number. Only updated once a second, so the disk arm moving may appear to jump by several tracks. "fast_disk_emul:1" shows that KEGS is using less accurate, but faster, IWM emulation. Press F7 to toggle to accurate disk emulation. Documentation To-Do: ------------------- Describe the tracing and breakpoint debug features. Describe the debug interface. Describe how the code works. Describe more of what's known to work. Describe my changes to SPEEDTEST. KEGS To-Do: ---------- Fix the Ensoniq bugs to make sound more accurate. ------------------- If you have any problems/questions/etc., just let me know. Special thanks to Jeff Smoot of climbingwashington.com for letting me use the picture of a keg for the Mac icon. Kent Dickey kadickey@alumni.princeton.edu X Window (Linux) interface information: -------------------------------------------- Every version of Linux is different. Supporting this is very difficult especially since I do not run Linux myself. If KEGS fails to start, try the following options: kegs -audio 0 -noshm There may be a bug with drawing the border on x86 Linux with Shared Memory-- add the options "-noshm -skip 0" to fix this up (but lose some graphics performance, sorry). Try KEGS without these options first, but use this as a workaround if necessary. If you want the display to go somewhere different, make sure the shell environment variable $DISPLAY is set, or give the command-line argument "-display {foo}". KEGS also forks off a subprocess to help handle the sound if audio is active. If KEGS crashes in a unusual way (a core dump, for instance), you may have to manually kill the subprocess. ("ps -ef| grep kegs;kill xxxxx"). User geoff@gwlink.net adds some notes for mounting disks/floppies/CDs under Solaris: To use a CDROM, insert the CD and let Volume Management mount it. Edit kegs_conf and use the filesystem that shows up in the "df -k" listing. The volume name of the CDROM must be included. For example, a CDROM in an IDE drive would look like this: /vol/dev/dsk/c1t0d0/ciscocd A CDROM in a SCSI drive would look like this: /vol/dev/dsk/c0t6d0/j1170_10804 To provide low-level ADB emulation, KEGS turns off Unix key repeat when the focus is in the KEGS window. It should be turned back on every time the pointer leaves the KEGS window, but sometimes it doesn't. Re-running KEGS (and then quitting it quickly) should turn key-repeat back on, or you can type 'xset r' in another terminal window. Sometimes the converse is true--key repeat is "on" when the cursor is in the KEGS window. Moving the cursor out of the window and then back in should solve it. This is sometimes noticeable when running Wolfenstein 3D GS. I haven't spent much time debugging the problem. I think it may be the X Server. KEGS uses a private color-map for its X-window in 8-bit mode. This may cause colormap "flash" when your cursor enters the window. KEGS details/troubleshooting ---------------------------- KEGS will work on all platforms with a 15/16-bit, 24-bit, or 32-bit color display. KEGS also supports an 8-bit display on X windows only. On all platforms, it autodetects the color depth--no color switching is necessary as long as you're at a supported depth. Disk Image Details Images loaded into slot 6 (drive 1 or 2) are assumed to be 140K 5.25" disks, which is usually have the extension ".dsk". Images loaded into slot 5 (drive 1 or 2) are assumed to be 800K disk images and can be in any supported imahe format (including partitions, if you have 800K partitions). Images loaded into slot 7 (drives 1 through 32) can be in any format and can be any size up to 4GB. KEGS boots s7d1 by default. You can change this using the emulated IIgs control panel, just like a real Apple IIgs. KEGS emulates a IIgs with two 5.25" drives in slot 6, two 3.5" drives in slot 5, and up to 32 "hard drives" in slot 7. However, the current Configuration Panel only lets you set through s7d11. ProDOS 8 can access disks up to s7d8, but GSOS has no limit, so it's best to put HFS images past s7d8 in order to leave more slots for ProDOS images. If you're trying to use a real host device (CD-ROM, or hard drive, or floppy), you should make the permissions on the /dev/disk* files something like (meaning, everyone should have read permission): brw-r--r-- 1 root operator 14, 0 Jun 10 00:01 /dev/disk2 You can do this on a Mac with: sudo chmod 644 /dev/disk2 DO NOT RUN KEGS AS ROOT. It is not designed for this and it's almost certain problems will ensue. ================================================ FILE: upstream/kegs/doc/README.linux.partitions.txt ================================================ [ This info provided by Mike Thomas ] [ Updated 10/30/2003 by Kent: This file mentions editing "kegs.conf" to ] [ mount images--this is now replaced by the built-in Configuration Panel. ] Setup and configuration for x86 Linux: -------------------------------------- KEGS is very easy to setup on your Linux box, but not foolproof. First you will need to decide where it will live. When doing this you will have to take into consideration any users of your machine. It really doesn't matter where it goes but it should have it's own directory and any supplied sub-directories should be there. You may decide to use the /usr/local path where most systems recommend you install applications. Again, this is entirely up to you. On my system I have a separate partition for KEGS and all the miscellaneous files I've accumulated for it. This makes it easy for me to reinstall the system should the need arise. Since I fool around with a large variety of software and OS's this happens more often than it would for normal users. Once you have put the files into the proper place you will need to compile it. You should not need to be 'root' to do this part. The file README.compile explains the steps required for this. Basically all you should need to do is set the vars link to point to the file vars_x86linux. You will want to check the file and make sure it is accurate for your system. On my Redhat 6.0 system the default compile setup works fine. I use the pentium GCC compiler instead of the supplied EGCS since it seems to build better binaries. I do not recommend using optimization levels higher than 2. Once you have successfully built the binaries you will need to copy them to the KEGS directory. At a minimum copy the file kegs and to_pro. Ok, now that you have the binaries you're almost ready. You will need a copy of the IIgs rom placed in the KEGS directory. It should be named ROM. You will also need some disk images. This is the hardest part. You will need to create an HD image to work with. Kent mentions an easy way in his readme. From the shell type this: echo "testfile" > testfile to_pro -16384 testfile If you're using bash try this: echo "testfile" > testfile ./to_pro -16384 testfile This should create a 16 megabyte HD image. This image will NOT be properly formatted to boot a system. The block zero is not properly setup. There is no easy way to fix this with the current KEGS/Linux system. There seems to be a problem formating HD files for Prodos using KEGS. The system will easily erase them but this doesn't help you. One solution is to make the primary boot drive use a disk partition. This is more involved and will be explained later. Another solution is to have access to the utility Block.Warden or some other P8 block utility. What you need to do is copy the boot blocks (blocks 0 and 1) from any bootable disk to the HD image. With that done you can now install GS/OS. Make sure you set the proper file permissions on files needed by KEGS. You will not be able to properly use it while logged on as root. KEGS uses the file permissions to test if it should write the image to disk or the memory image. As root, KEGS will never write the image since it thinks root always has execute privilege. The main files which you will need read/write access to are bram.data.1 and disk_conf. I suggest you have read access to all the other files in the directory. Once you've got all the proper permissions set, log onto the system with your normal account. Start up X and a shell and cd to the KEGS directory. Assuming you have the disk images for GS/OS edit your disk_conf file so it looks similar to this: # boot from install disk s7d1 = /usr/local/fst/gsos1 # our HD image # you should rename POOF1 file created with to_pro # I use *.hdv to be compatible with other utilities s7d2 = /usr/local/fst/boot.hdv # other GSOS images s7d3 = /usr/local/fst/gsos2 ... If you include all the GSOS images this way you will have a simple setup. Execute KEGS. For now go with the simplest command line possible. Since the newer versions support auto detect on video resolutions this should be the command kegs by itself. Hopefully you will boot into the emulator. If so, install GSOS and you're ready to go. Sound ----- Kent says sound doesn't work under Linux. This is partially true and much depends on the sound system you have installed. I have been successful with sound on both Soundblaster and Soundpro systems. For long compositions the sound may break up but it is functional for games and system sounds. System Video Setup ------------------ This is rather personal and based upon system hardware so I'll just give you my thoughts and setup info on this. My normal X system is configured @ 1152x864 15bpp due to constraints in the X server for my video system. I have custom configured it to boot into this mode and then I change to 800x600 by using the CTRL+ALT+(keypad)PLUS sequence when I use KEGS. This makes the system much easier to read. KEGS and disk partitions ------------------------ Kent mentions using partitions in his readme file but doesn't go into much details. I suspect this is mostly for accessing CD-roms. But it also works well for HFS and Prodos formatted partitions also. Linux can also handle HFS partitions but not Prodos. To accomplish this you will need some free space on your hard disk to create the partitions. You should not attempt this unless you know what you are doing. You have been warned! This task is not easy, nor is it supported by Kent. This was done and tested on my own system so I know it works. You will need the HFS support utilities for Linux. These are available on several Linux software sites. The primary need for these utilities is to change an ext2fs partition to an HFS partition. You can also use them to format the HFS volumes and copy files to and from the partition. Newer versions of the Linux kernel support HFS file systems but you will still need the utilities to create the original volume. You must decide how you want to divide this partition. You can use it all for HFS or you can create Prodos volumes and HFS volumes. There are pros and cons for using Prodos partitions instead of files. The pros, it is a little faster than using an HD file. It is a real Prodos partition, formatted by KEGS. The cons, It must be backed up by using software on the emulator. You can't just copy the HD file to another drive. You must weigh these pros and cons and decide for yourself. Of course you are not limited to using partitions. I have a mix of partitions and files which works quite well. I like the P8 partitions for holding my boot system and applications. I back them up with GSHK to an HFS volume which I can then transfer to another drive if I need even more security. If you decide to use the whole partition for HFS then all you need to do is run the HFS utilities and setup the HFS volume. Read the documentation which comes with the utility package and follow it faithfully. If you want to use P8 and HFS partitions you have some more work to do. If you have never worked with low level partitions or are worried about destroying your HD then you should probably forget this. For this to work you will have to change the partition table on your HD. This can and most likely will ruin any data you already have on there. I can not state this enough. Back up any important data on the hard drive! It is possible to change the partitions in Linux and not destroy the system. I have done this on mine but I also used the last defined partition to make the changes and designed the system for change should it be necessary. For those of you who know how to handle this, take the partition you have decided to use for KEGS and divide it into at least one 32 meg partition. More is better but you will have to use the emulator to back up any drives setup this way since Linux can't access a Prodos partition (yet). I have setup 4 32 meg P8 partitions and several smaller HFS partitions of about 96 megs. The reason I use smaller HFS partitions is because HFS isn't real efficient with larger drives, but that's another story. The reason for the separate HFS partitions is so Linux can mount the HFS volumes like any other file system. I find this works quite easily for accessing files instead of using the HFS utilities. Just my opinion though. For P8 utilities you will still need to use the HFS utility and configure the drive as an HFS volume. This lets KEGS read the partition when it loads the partition the first time. KEGS doesn't like the Linux file system. Ok, everybody got their partitions defined? You want to use the HFS tools and setup all the partitions (P8 and HFS) as HFS volumes. Next you will have to setup the HFS partitions. No, I'm not repeating myself. This is not the same thing as the low level partitions. HFS volumes have their own partition table. For our purposes the table will only hold one partition on each whole volume. The utility will give you the block count when you setup the partitions, write it down so you don't forget. After you have the volume partition setup you can format the drive. Yeah I know you made a Prodos partition but it doesn't hurt anything and KEGS will be able to read the partition when it boots up. Well, I think I covered everything needed to set them up. Now you will need to edit the /etc/fstab file. Make sure there are no references to the original partition. If you want to access the HFS volumes you will need to add them to this file. You will also need to make sure that your Linux can understand the HFS format. Most newer kernels will as long as you've compiled it into the kernel or set it up as a module. KEGS doesn't need these entries to access the volumes, they are just here for your convenience. In fact, if you don't need Linux access I suggest you either leave them out or set them up as NOAUTO and mount them only when needed. Unmount them when finished to avoid any potential problems also. Do not give Linux access to any P8 partitions. For one thing it can't recognize them and most likely will give you problems when you boot the system. For safety's sake the only partition I have listed in my /etc/fstab is a volume called transfer. You must set the filetype to HFS so Linux doesn't complain about the partitions if you mount them. Ok, all partitions are defined, the /etc/fstab is setup correct, now you need to change the permissions on the device files associated with the partitions. This allows you to access the files as a normal user. (Thanks Kent, guess I got too involved and forgot it should be treated like the CD). You will need to reboot to ensure the system sees the new partitions and has the correct /dev/hd?# device files. If you setup the partitions with fdisk you should know the correct hd info to select the files. For the sake of example let's assume the device is HDB and the partitions numbers are 1,2,3. From the shell, cd /dev chmod 666 /dev/hdb1 chmod 666 /dev/hdb2 chmod 666 /dev/hdb3 After you start KEGS you can format the Prodos partitions. If you use the method mentioned earlier for installing GS/OS you will want to quit the installer and run the advanced disk utilities on the utilities disk, then quit back to the installer program or reboot. Now I know this all sounds like a lot of trouble but (IMHO) it's worth it. For one thing, KEGS will format a Prodos partition without problems (unlike an HD file image) which makes a great boot system. And with GS/OS 6.01 you can access large HFS volumes for storage and GS applications. You can also download from the net to the HFS volume (if it's mounted) and avoid the trouble of copying files to an image with to_pro. Not to mention the fact that the newest GNO works with HFS drives. One more note, if you use HFS you will need to patch the HFS fst. There is a one byte bug which mis-calculates HFS block sizes and WILL trash your HFS drive. The patch is at several places on the net or ask someone in one of the comp.sys.apple2 news groups. Miscellanea ----------- To ease access to the KEGS binary, make an executable script which contains any command line parms you need. Then put this script somewhere in the path so you can execute it from any shell. Depending on the desktop you use you may want to setup links for editing the disk_conf file also. With GNO/ME you can launch KEGS without the shell but I don't recommend this since you won't know what happened when it dies. With KDE you can set up the launcher to use a shell, this is much better but until you have your setup stable you will want to use a regular terminal so you can keep track of what's going on. Like GNO/ME, the KDE shell will close as soon as KEGS quits with an error. Thanks ------ I hope this info helps you enjoy KEGS. Many thanks to Kent for creating this fine emulator and for putting up with me bugging him with 'bug' reports. Many of these weren't actually bugs but were my own fault due to lack of knowledge about *nix systems. But Kent was prompt in fixing the ones which truly were bugs. Thanks to him I can now play my favorite game, Questron 2 (gs version) which requires a player disk in slot 5. I know no other emulator which can actually play this game. Mike Thomas ================================================ FILE: upstream/kegs/doc/README.mac.txt ================================================ MAC OS X port of KEGS (KEGSMAC): http://kegs.sourceforge.net ------------------------------------------------------------- This is a Mac OS X Swift port. Most of KEGS is written in C, but the UI is in Swift. Downloading, and making it runnable: ----------------------------------- Download from http://kegs.sourceforge.net/. If you're using Safari in it's default mode (unpack "Safe" files after downloading), you'll end up with kegs.1.xx.tar in your Downloads/ directory. (where xx will be the version). Mac OS X will not let you run unsigned quarantined code you find on the net (which is good, generally), so you need to remove quarantine. At a Terminal window, cd to where you want to install KEGS (it can just be your home directory): cd cat ~/Downloads/kegs.1.xx.tar | tar xvf - If you have a compressed file like kegs.1.xx.tar.gz, do: cd gunzip < ~/Downloads/kegs.1.xx.tar.gz | tar xvf - You will now have a directory names kegs.1.xx and in that, KEGSMACK.app/. If you've let Safari or the Finder unpack KEGS, then you can do this command from the Terminal in the directory you unpacked KEGSMAC to remove the quarantine: xattr -r -d com.apple.quarantine KEGSMAC.app Usage: ----- Like most other KEGS versions, KEGSMAC is best run from a Terminal window. Just type "./KEGSMAC.app/Contents/MacOS/KEGSMAC" in the directory you installed/compiled it in. You need to have a ROM file (named ROM, ROM.01, or ROM.03) and a config.kegs in the same directory or in your home directory (read the README--these files are searched for in various places). KEGSMAC can also be run from the Finder by double-clicking on the icon, or by doing "open KEGSMAC", but if you do this, debugging problems is a little tougher. To quit, either click the window close box, or select Quit from the menu. To go full screen, click the Maximize button in the window. You can resize the window to any size you like. Mac OS X now has "quarantine" and Disk Access Permissions, which can be a pain for users of KEGSMAC. You must remove Quarantine or it won't work at all, but the disk access permissions are used to protect special Folders like Documents and Downloads. System Preferences->Security&Privacy->Files and Folders should let you add permissions to KEGS--but it doesn't seem to always stick. I'm not sure why this is. A simple workaround is to place your ROMs and disk images in a directory like "Apple2" or something similar in your home directory. Then KEGSMAC won't need access to Desktop or Downloads and should work better. If you have code development suggestions on how to have KEGSMAC work better, please let me know. Compile directions ------------------ See README.compile.txt ================================================ FILE: upstream/kegs/doc/README.serial.ports.txt ================================================ KEGS documentation: Serial Ports SCC (Serial Port) emulation: --------------------------- KEGS emulates the two serial ports on a IIgs as being sockets (outgoing or incoming), or connected to real devices, or being a Virtual Modem. The incoming ports KEGS will use is 6501 for slot 1 (the printer port) and 6502 for slot 2 (the usual modem port). By default, slot 1 is emulated as a real device (but defaults to desiabled), and slot 2 emulates a Virtual Modem. You configure how you want the serial emulation to work on the Configuration screen (F4), then select "Serial Configuration". There are settings for slot 1 and slot 2 separately. "Main Setting" sets the overall connection type: "Use Real Device", "Use a virtual modem", "Use Remote IP", and "Use incoming port 6501" (or 6502). The "Status" line under Serial Port Configuration says whether the port is opened. Status doesn't change while you're in the F4 page, so if you make a change and want to see if it worked, press F4 (to leave the Configuration screen) and F4 again (to come right back) and see what the status is. Apple II communications programs often want to initialize modems themselves, and so using them with any direct connection can cause them to fail to initialize themselves properly. For instance, if you use ProTerm, it will often say "failed to initialize hardware"--just keep selecting "ok", and then force it online with Cmd-Ctrl-T. Once "online", I always do "ATZ" to get the text modem responses (OK, RING, etc.). Real Device ----------- On Windows, you can select COM1 through COM4 as the serial device. You select which one on the "Windows Device" line, and then KEGS will try to open it. On a Mac or Linux, KEGS let's you select any /dev/ device to be your serial connection (if it's a USB to serial port, pretty likely since serial ports don't exist much any more, it'll be something like /dev/tty.usbserial-1480 on a Mac, or /dev/USB0 on Linux). Select this on the "Real Device" line, and it's a file selector like for disk images. Make sure you check "Status" to make sure the device opens. Virtual Modem ------------- A Virtual Modem means KEGS acts as if a modem is on the serial port allowing Apple II communcation programs to fully work, but connect to internet-enabled sockets. KEGS emulates a "Hayes-Compatible" modem, meaning it accepts "AT" commands. You can use KEGS to connect to free telnet-BBSs, or run a BBS program on KEGS and become a telnet BBS yourself. All "AT" commands in KEGS work in upper or lower case. This description gives all commands in upper case since it's a little easier to read. The two main AT commands are: ATDT for dialing out, and ATA for receiving calls. To dial out, enter "ATDThostname", or for example, "ATDTboycot.no-ip.com" (which is down at the moment, unfortunately). You can also enter an IP address, like "ATDT127.0.0.1". On a Mac or Linux, to create a simple socket server to allow connections for testing, do in a terminal window: nc -l 127.0.0.1 9111 where nc is the NetCat program (generally available), 127.0.0.1 is the IP address of the local machine always, and 9111 is the port number (any random number you want above 1024). Then in your terminal program running inside KEGS, do: ATDT127.0.0.1:9111 And then you can type back and forth. "nc" buffers all input from the keyboard, and sends it once you hit return. And nc isn't going to do proper terminal emulation, so return doesn't scroll to the next line. Modems use "+++" to get the attention of the modem: press +++ within 1 second, and then wait 1 second. KEGS will say "OK", and then you can hang up with "ATH". KEGS' virtual modem also accepts incoming "calls". While using the virtual modem on slot 2, port 6502 is open ready for incoming "calls" (unless you're connected using ATDT to some other address). So in your terminal program, turn on verbose settings with "ATZ" and then connect to port 6502 using nc: nc -t 127.0.0.1 6502 KEGS will start printing "RING" every second, for 4 seconds. You need to type "ATA" within that time, and then you will connect. Alternatively, you can tell the virtual modem to automatically pick up after 2 rings: ATS0=2 KEGS will answer after 2 rings. ATS0=0 means never answer. Summary of AT commands: ATA answer if RING'ing ATZ Reset all settings to default (which is verbose) ATE1 Turns on echo ATE0 Turns off echo ATV1 Turns on verbose ATV0 Turns off verbose ATO Goes online (if you did +++ and want to stay connected) ATH Hang up ATDThostname:port_num Opens connection to "hostname" on port_num ATS0=2 Answers after 2 rings without needing ATA. On Windows, when KEGS tries to open this incoming socket, you'll need to enable it and click Unblock to the dialog that Windows pops up. This may happen when you first start KEGS since the virtual modem is now on by default. Remote IP --------- In this mode, KEGS tries to open a socket to the address in "Remote IP", to the port in "Remote Port". If you set Remote IP to your printer, and Remote Port to 9100, then KEGS will open a connection to your printer. Use incoming port 6501 (or 6502) -------------------------------- KEGS also supports an older, simpler socket interface where it accepts any connection to port 6501 (or 6502 for slot 2). In KEGS, from APPLESOFT, if you PR#1, all output will then be sent to socket port 6501. You can see this data by connecting to the port using nc: In another terminal window, do: "nc -t localhost 6501" and then you will see all the output going to the "printer". Under APPLESOFT, you can PR#1 and IN#1. This gets input from the socket also. You can type in the telnet window, it will be sent on to the emulated IIgs. You may want to edit the Serial Configuration option "Serial Mask" to "Mask off high bit" to make the PR#1 output easier to handle (Apple II's set the high-bit in most bytes output, which is not what any modern computer wants). You can "print" from BASIC by using something like PR#1 in KEGS and "nc -t localhost 6501 | tee file.out" in another window. ================================================ FILE: upstream/kegs/doc/README.win32.txt ================================================ How to compile KEGS for Windows ------------------------------- Go to the src/ directory in the KEGS release, and double-click the kegswin.vcxproj file. Alternatively, from a PowerShell prompt, you can do: start ./kegswin.vcxproj This will launch Visual Studio. Microsoft has a free Visual Studio Community Edition which is what I use. To compile, click Build->Build Solution, or just press F7. It will leave an executable in x64/Release/kegswin.exe if you use the default 64-bit build. OLD Information (probably best to ignore this) --------------- These are raw notes to myself. Probably not useful for others, just ignore, it's how I got KEGS into Visual Studio in the first place. ---- Open Visual Studio 2022 (community edition). On the right side, "Get Started" list, select the small link underneath that says "Continue without code ->". In this new VS2022 window, select File->New->Project from Existing Code. Select Visual C++. Project name: kegswin Project file location: Select \win10\kegs.1.16\src Select Finish On the left hand side, the main window has Solution explorer. Open Source Files. Right click on "macsnd_driver.c" and "xdriver.c" to select "Exclude from Project" Then, we need to add 3 DLLs. In the left view area, select "kegswin", and then Do Project->Properties. You should see a dialog "kegswin Property Pages". If it says anything else, and if the line underneath says stuff like "Configuration: N/A" then change view (Solution Explorer is good), and make sure you select kegswin. Set Configuration: Release, Platform: x64. Linker->Input, then on the right, the first item "Additional Dependencies:" add: "wsock32.lib;dsound.lib;winmm.lib;". I added it near the end, before odbccp32.lib. Set Platform: x86, and add the same libraries for Win32. To fix "unresolved external symbol _WinMain@16 referenced in function", To compile: mkkegswinmac -j 20 cd obj start ./kegswin.vcxproj Then Build->Build kegswin (Ctrl-b) ================================================ FILE: upstream/kegs/lib/make_mac_icon ================================================ #!/usr/bin/perl -w # Based on https://gist.github.com/ansarizafar/6fa64f44aa933794c4d6638eec32b9aa # and https://github.com/retifrav/generate-iconset # We need to create a directory of the icon in several scaled sizes, # (the Mac command "sips" can do this), then run iconutil to form the # .icns file. # kegsicon.png created by Alex Lee my $icondir; my $img_file = ""; my $ext = ".png"; my $scale; my $sz; my $pixels; my $scale_str; if($#ARGV == 0) { $img_file = shift; if($img_file =~ /^.*\.(^\.*)$/) { $ext = $1; print "Set ext to $ext\n"; } } else { die "Usage: $0 image_file.jpg/.png" } $icondir = "./icon.iconset"; # Must have .iconset extension if(-d $icondir) { `rm -rf $icondir`; } `mkdir $icondir`; for($scale = 1; $scale <= 2; $scale++) { for($sz = 16; $sz <= 512; $sz = $sz * 2) { if($sz == 64) { next; } $pixels = $sz * $scale; $scale_str = ""; if($scale == 2) { $scale_str = '@2x'; } @cmd = ("sips", "-z", $pixels, $pixels, $img_file, "--matchTo", "/System/Library/ColorSync/Profiles/sRGB\\ Profile.icc", "--out", $icondir . "/" . "icon_" . $sz . "x" . $sz . $scale_str . $ext); print "cmd: @cmd\n"; `@cmd`; } } print "Calling: iconutil -o kegs.icns -c icns $icondir"; `iconutil -o kegs.icns -c icns $icondir`; `rm -rf $icondir`; ================================================ FILE: upstream/kegs/src/AppDelegate.swift ================================================ // $KmKId: AppDelegate.swift,v 1.32 2024-09-15 13:55:35+00 kentd Exp $ // Copyright 2019-2024 by Kent Dickey // This code is covered by the GNU GPL v3 // See the file COPYING.txt or https://www.gnu.org/licenses/ // // /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/ // Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreAudio.framework/ // Versions/A/Headers import Cocoa let Context_draw = false // Default: use safe draw function. // If true, use NSGraphicsContext.current.data to try to write // directly to screen memory (in a different ARGB format) class Window_info { var x_win : NSWindow? = nil var mac_view : MainView? = nil var kimage_ptr : UnsafeMutablePointer! = nil var title : String = "" var app_delegate : AppDelegate! = nil var mac_a2_height : Int = 0 // init(_ new_is_main: Bool) { // is_main = new_is_main // } func set_kimage(_ kimage_ptr : UnsafeMutablePointer!, title: String, delegate: AppDelegate!) { self.kimage_ptr = kimage_ptr self.title = title self.app_delegate = delegate } func create_window() { let x_xpos = Int(video_get_x_xpos(kimage_ptr)) let x_ypos = Int(video_get_x_ypos(kimage_ptr)) let width = Int(video_get_x_width(kimage_ptr)) let height = Int(video_get_x_height(kimage_ptr)) let windowRect = NSRect(x: x_xpos, y: x_ypos, width: width, height: height) var window : NSWindow! var view : MainView! let style : NSWindow.StyleMask = [.titled, .closable, .resizable] window = NSWindow(contentRect: windowRect, styleMask: style, backing: .buffered, defer: false) let viewRect = NSRect(x: 0, y: 0, width: windowRect.size.width, height: windowRect.size.height) print("About to init MainView"); view = MainView(frame: viewRect) print("About to set kimage_ptr"); view.kimage_ptr = kimage_ptr; print("About to call initialize"); view.initialize() view.closed = false window.delegate = app_delegate window.contentView = view window.makeKeyAndOrderFront(app_delegate) window.acceptsMouseMovedEvents = true window.title = title window.showsToolbarButton = true window.contentAspectRatio = NSSize(width: width, height: height) video_set_active(kimage_ptr, Int32(1)) video_update_scale(kimage_ptr, Int32(width), Int32(height), Int32(1)) x_win = window mac_view = view mac_a2_height = height; window.makeKey() } func update() { // Decide if window should be opened/closed (if it's the // debugger window), and call mac_update_display() to update let new_height = Int(video_get_a2_height(kimage_ptr)) let a2_active = video_get_active(kimage_ptr) if let view = mac_view { if(new_height != mac_a2_height) { mac_resize_window() } if(a2_active == 0 && !view.closed) { print("a2_active 0 on \(title), calling close") x_win!.orderOut(x_win) view.closed = true } else if(a2_active != 0 && view.closed) { print("Opening closed window \(title)") view.closed = false x_win!.orderFront(x_win) x_win!.makeKey() // Move to front } else if(a2_active != 0) { view.mac_update_display() } if((a2_active != 0) && !view.closed) { if(adb_get_copy_requested() != 0) { view.do_copy_text(view); } } } else { if(a2_active != 0) { print("Opening window \(title)") create_window() } } } func mac_resize_window() { let a2_height = Int(video_get_a2_height(kimage_ptr)) let a2_width = Int(video_get_a2_width(kimage_ptr)) let ratio = CGFloat(a2_height) / CGFloat(a2_width) let cur_width = x_win!.frame.size.width let new_height = cur_width * ratio // CGFloat var newframe = x_win!.frame // NSRect mac_a2_height = a2_height newframe.size.height = new_height x_win!.contentAspectRatio = NSSize(width: a2_width, height: a2_height) x_win!.setFrame(newframe, display: true, animate: true) mac_view!.initialize() // Must call initialize for the case where the // status lines were enabled, we need more lines // print("Call video_update_scale from mac_resize_window\n"); video_update_scale(kimage_ptr, Int32(x_win!.frame.width), Int32(x_win!.frame.height), 0) video_update_xpos_ypos(kimage_ptr, Int32(x_win!.frame.origin.x), Int32(x_win!.frame.origin.y)) //print("Did mac_resize window to \(a2_width), \(a2_height)" + // " frame:\(x_win!.frame.width), " + // "\(x_win!.frame.height)" + // " ratio:\(ratio)\n") } func update_window_size(width: Int, height: Int) { // print("Call video_update_scale from update_window_size\n"); video_update_scale(kimage_ptr, Int32(width), Int32(height), 0); } } @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { var mainwin_info = Window_info(); var debugwin_info = Window_info(); func find_win_info(_ window: NSWindow) -> Window_info { if(mainwin_info.x_win == window) { return mainwin_info } return debugwin_info } @objc func do_about(_:AnyObject) { print("About") if let ver_str = Bundle.main.infoDictionary?[ "CFBundleShortVersionString"] as? String { NSApplication.shared.orderFrontStandardAboutPanel( options: [.applicationVersion: ver_str, .version: "", .applicationName: "KEGS"]) } } func applicationDidFinishLaunching(_ aNotification: Notification) { // This is your first real entry point into the app print("start!") set_menu_for_kegs() main_init() } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } func applicationShouldTerminateAfterLastWindowClosed( _ theApplication: NSApplication) -> Bool { // Application will close if main window is closed return true } func windowDidBecomeKey(_ notification: Notification) { if let w = notification.object as? NSWindow { if(w == mainwin_info.x_win) { adb_mainwin_focus(Int32(1)); //print("Main window became KEY") } } //print("DidbecomeKey") // If window focus is changing, turn off key repeat adb_kbd_repeat_off() } func windowDidResignKey(_ notification: Notification) { //print("DidResignKey") adb_kbd_repeat_off() adb_mainwin_focus(Int32(0)) CGDisplayShowCursor(CGMainDisplayID()) } func windowDidMove(_ notification: Notification) { //print("DidMove") if let w = notification.object as? NSWindow { let win_info = find_win_info(w) video_update_xpos_ypos(win_info.kimage_ptr, Int32(w.frame.origin.x), Int32(w.frame.origin.y)) } } func windowWillResize(_ window: NSWindow, to frameSize: NSSize) -> NSSize { // print("WILL RESIZE app \(frameSize)") let width = Int(frameSize.width) let height = Int(frameSize.width) let win_info = find_win_info(window) win_info.update_window_size(width: width, height: height) return frameSize } func windowShouldClose(_ window: NSWindow) -> Bool { print("windowShouldClose") let win_info = find_win_info(window) if(mainwin_info.x_win == window) { // User has clicked the close box on the main emulator // window. Just exit the app NSApp.terminate(window) return true // Let main window close } else { video_set_active(win_info.kimage_ptr, Int32(0)) win_info.update() return false } } func set_menu_for_kegs() { let appname = "Kegs" if let menu = NSApp.mainMenu { show_menu(menu, depth: 0) menu.removeAllItems() print("Installing my menu now") let kegs = NSMenu(title: appname) kegs.addItem(withTitle: "About \(appname)", action: #selector(AppDelegate.do_about(_:)), keyEquivalent: "") kegs.addItem(NSMenuItem.separator()) let quit_item = NSMenuItem(title: "Quit \(appname)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") quit_item.keyEquivalentModifierMask = [.option, .command ] kegs.addItem(quit_item) let kegs_item = NSMenuItem() kegs_item.title = appname kegs_item.submenu = kegs menu.addItem(kegs_item) // First menu of "Kegs" now done. Add "Edit" menu let edit = NSMenu(title: "Edit") edit.addItem(withTitle: "Copy Text Screen", action: #selector(MainView.do_copy_text(_:)), keyEquivalent: "") edit.addItem(NSMenuItem.separator()) edit.addItem(withTitle: "Paste", action: #selector(MainView.do_paste(_:)), keyEquivalent: "") let edit_item = NSMenuItem() edit_item.title = "Edit" edit_item.submenu = edit menu.addItem(edit_item) // Edit menu of "Kegs" now done. Add "Config" menu let config = NSMenu(title: "Config") config.addItem(withTitle: "Configuration F4", action: #selector(MainView.do_config(_:)), keyEquivalent: "") let config_item = NSMenuItem() config_item.title = "Config" config_item.submenu = config menu.addItem(config_item) show_menu(menu, depth: 0) } } func show_menu(_ menu: NSMenu, depth: Int) { if(depth >= -10) { return // HACK: remove to see debug output! } print("menu at depth \(depth): \(menu.title)") if(depth > 5) { // Prevent infinite recursion return } for menuit in menu.items { print("menuit: depth:\(depth) \(menuit.title)") print(" keyeq:\(menuit.keyEquivalent)") print(" modifiers:\(menuit.keyEquivalentModifierMask)") print(" isSeparator:\(menuit.isSeparatorItem)") if let sub = menuit.submenu { show_menu(sub, depth: depth+1) } } } var mainWindow : NSWindow! var mainwin_view : MainView! func main_init() { var kimage_ptr : UnsafeMutablePointer! var rect = NSRect.zero let argc = CommandLine.argc let argv = CommandLine.unsafeArgv parse_argv(argc, argv, 3); rect.size.width = 2560 rect.size.height = 1440 if let screen = NSScreen.main { rect = screen.frame } kegs_init(24, Int32(rect.size.width), Int32(rect.size.height), Int32(1)) if(Context_draw) { video_set_blue_mask(UInt32(0x0000ff)) video_set_green_mask(UInt32(0x00ff00)) video_set_red_mask(UInt32(0xff0000)) } else { video_set_red_mask(UInt32(0x0000ff)) video_set_green_mask(UInt32(0x00ff00)) video_set_blue_mask(UInt32(0xff0000)) } video_set_palette() kimage_ptr = video_get_kimage(Int32(0)) mainwin_info.set_kimage(kimage_ptr, title: "KEGS", delegate: self) kimage_ptr = video_get_kimage(Int32(1)) debugwin_info.set_kimage(kimage_ptr, title: "Debugger", delegate: self) mainwin_info.create_window() main_run_loop() } func main_run_loop() { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) { self.main_run_loop() } let ret = run_16ms() if(ret != 0) { exit(ret); } mainwin_info.update() debugwin_info.update() } } ================================================ FILE: upstream/kegs/src/Info.plist ================================================ BuildMachineOSBuild 18G103 CFBundleDevelopmentRegion en CFBundleExecutable KEGSMAC CFBundleIconFile kegs.icns CFBundleIdentifier com.provalid.Kegs CFBundleInfoDictionaryVersion 6.0 CFBundleName Kegs CFBundlePackageType APPL CFBundleShortVersionString 1.38 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 11A1027 DTPlatformVersion GM DTSDKBuild 19A547 DTSDKName macosx10.15 DTXcode 1110 DTXcodeBuild 11A1027 LSMinimumSystemVersion 10.13 NSHumanReadableCopyright Copyright © 2025 Kent Dickey. All rights reserved. NSHighResolutionCapable NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: upstream/kegs/src/Kegs-Bridging-Header.h ================================================ // $KmKId: Kegs-Bridging-Header.h,v 1.1 2019-10-14 22:33:09+00 kentd Exp $ // Use this file to import your target's public headers that you would like to expose to Swift. // #import "defc.h" ================================================ FILE: upstream/kegs/src/MainView.swift ================================================ // $KmKId: MainView.swift,v 1.42 2024-09-15 13:55:35+00 kentd Exp $ // Copyright 2019-2024 by Kent Dickey // This code is covered by the GNU GPL v3 // See the file COPYING.txt or https://www.gnu.org/licenses/ // import Cocoa import CoreGraphics import AudioToolbox class MainView: NSView { var bitmapContext : CGContext! var bitmapData : UnsafeMutableRawPointer! var rawData : UnsafeMutablePointer! var current_flags : UInt = 0 var mouse_moved : Bool = false var mac_warp_pointer : Int32 = 0 var mac_hide_pointer : Int32 = 0 var kimage_ptr : UnsafeMutablePointer! var closed : Bool = false var pixels_per_line = 640 var max_height = 600 let is_cmd = UInt(NSEvent.ModifierFlags.command.rawValue) let is_control = UInt(NSEvent.ModifierFlags.control.rawValue) let is_shift = NSEvent.ModifierFlags.shift.rawValue let is_capslock = NSEvent.ModifierFlags.capsLock.rawValue let is_option = NSEvent.ModifierFlags.option.rawValue override init(frame frameRect: NSRect) { super.init(frame: frameRect) } required init?(coder: NSCoder) { super.init(coder: coder) } func windowDidResize(_ notification: Notification) { print("DID RESIZE") } override func performKeyEquivalent(with event: NSEvent) -> Bool { let keycode = event.keyCode let is_repeat = event.isARepeat let unicode_key = get_unicode_key_from_event(event) // print(".performKeyEquiv keycode: \(keycode), is_repeat: " + // "\(is_repeat)") if(((current_flags & is_cmd) == 0) || is_repeat) { // If CMD isn't being held down, just ignore this return false } // Otherwise, manually do down, then up, for this key adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 0); adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 1); return true } override var acceptsFirstResponder: Bool { return true } func get_unicode_key_from_event(_ event: NSEvent) -> UInt32 { var unicode_key = UInt32(0); if let str = event.charactersIgnoringModifiers { //print(" keydown unmod str: \(str), " + // "code:\(event.keyCode)") let arr_chars = Array(str.unicodeScalars) //print(" arr_chars: \(arr_chars)") if(arr_chars.count == 1) { unicode_key = UInt32(arr_chars[0]); //print("key1:\(unicode_key)") } } return unicode_key } override func keyDown(with event: NSEvent) { let keycode = event.keyCode let is_repeat = event.isARepeat; // print(".keyDown code: \(keycode), repeat: \(is_repeat)") //if let str = event.characters { // print(" keydown str: \(str), code:\(keycode)") //} let unicode_key = get_unicode_key_from_event(event) if(is_repeat) { // If we do CMD-E, then we never get a down for the E, // but we will get repeat events for that E. Let's // ignore those return } adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 0); } override func keyUp(with event: NSEvent) { let keycode = event.keyCode // let is_repeat = event.isARepeat; // print(".keyUp code: \(keycode), repeat: \(is_repeat)") let unicode_key = get_unicode_key_from_event(event) adb_physical_key_update(kimage_ptr, Int32(keycode), UInt32(unicode_key), 1); } override func flagsChanged(with event: NSEvent) { let flags = event.modifierFlags.rawValue & (is_cmd | is_control | is_shift | is_capslock | is_option) var c025_val = UInt32(0); if((flags & is_shift) != 0) { c025_val |= 1; } if((flags & is_control) != 0) { c025_val |= 2; } if((flags & is_capslock) != 0) { c025_val |= 4; } if((flags & is_option) != 0) { c025_val |= 0x40; } if((flags & is_cmd) != 0) { c025_val |= 0x80; } adb_update_c025_mask(kimage_ptr, c025_val, UInt32(0xc7)); current_flags = flags //print("flagsChanged: \(flags) and keycode: \(event.keyCode)") } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { // This is to let the first click when changing to this window // through to the app, I probably don't want this. return false } override func mouseMoved(with event: NSEvent) { //let type = event.eventNumber //print(" event type: \(type)") mac_update_mouse(event: event, buttons_state:0, buttons_valid:0) } override func mouseDragged(with event: NSEvent) { mac_update_mouse(event: event, buttons_state: 0, buttons_valid: 0) } override func otherMouseDown(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:2, buttons_valid:2) } override func otherMouseUp(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:0, buttons_valid:2) } override func mouseEntered(with event: NSEvent) { print("mouse entered") } override func rightMouseUp(with event: NSEvent) { mac_update_mouse(event: event, buttons_state:0, buttons_valid:4) } override func rightMouseDown(with event: NSEvent) { print("Right mouse down") mac_update_mouse(event: event, buttons_state:4, buttons_valid:4) } override func mouseUp(with event: NSEvent) { // print("Mouse up \(event.locationInWindow.x)," + // "\(event.locationInWindow.y)") mac_update_mouse(event: event, buttons_state:0, buttons_valid:1) } override func mouseDown(with event: NSEvent) { //print("Mouse down \(event.locationInWindow.x)," + // "\(event.locationInWindow.y)") mac_update_mouse(event: event, buttons_state:1, buttons_valid:1) } func mac_update_mouse(event: NSEvent, buttons_state: Int, buttons_valid: Int) { var warp = Int32(0) var x_width = 0 var y_height = 0 var x = Int32(0) var y = Int32(0) var do_delta = Int(0) let hide = adb_get_hide_warp_info(kimage_ptr, &warp) if(warp != mac_warp_pointer) { mouse_moved = true } mac_warp_pointer = warp if(mac_hide_pointer != hide) { mac_hide_pointer(hide: hide) } mac_hide_pointer = hide let location = event.locationInWindow if(!Context_draw) { // We're letting the Mac scale the window for us, // so video_scale_mouse*() doesn't know the scale // factor, so pass it in x_width = Int(bounds.size.width); y_height = Int(bounds.size.height); } let raw_x = location.x let raw_y = bounds.size.height - 1 - location.y // raw_y is 0 at the top of the window now x = video_scale_mouse_x(kimage_ptr, Int32(raw_x), Int32(x_width)) y = video_scale_mouse_y(kimage_ptr, Int32(raw_y), Int32(y_height)) do_delta = 0; if(mac_warp_pointer != 0) { do_delta |= 0x1000; // x,y are deltas x = Int32(event.deltaX) y = Int32(event.deltaY) } let ret = adb_update_mouse(kimage_ptr, x, y, Int32(buttons_state), Int32(buttons_valid | do_delta)) if(ret != 0) { mouse_moved = true } guard let win = window else { return // No valid window } if(mouse_moved) { var rect1 = NSRect.zero // If warp, warp cursor to middle of window. Moving // the cursor requires absolute screen coordinates, // where y=0 is the top of the screen. We must convert // window coords (where y=0 is the bottom of the win). mouse_moved = false let warp_x = CGFloat(video_unscale_mouse_x(kimage_ptr, Int32(A2_WINDOW_WIDTH/2), Int32(x_width))) let warp_y = CGFloat(video_unscale_mouse_y(kimage_ptr, Int32(A2_WINDOW_HEIGHT/2), Int32(y_height))) let scr_height = CGDisplayPixelsHigh(CGMainDisplayID()); rect1.origin.x = CGFloat(warp_x) rect1.origin.y = bounds.size.height - 1 - CGFloat(warp_y) rect1.size.width = 1; rect1.size.height = 0; let screen_rect = win.convertToScreen(rect1) let screen_rect_y = CGFloat(scr_height) - screen_rect.origin.y let cg_loc = CGPoint(x: screen_rect.origin.x, y: CGFloat(screen_rect_y)) //print("scr_rect:\(screen_rect)") if(warp != 0) { // Warp to middle of the window CGDisplayMoveCursorToPoint(CGMainDisplayID(), cg_loc); } } } func mac_hide_pointer(hide: Int32) { // print("Hide called: \(hide)") if(hide != 0) { CGDisplayHideCursor(CGMainDisplayID()) } else { CGDisplayShowCursor(CGMainDisplayID()) } } func initialize() { //let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) print("Initialize view called") // Get width,height from video.c to handle toggling status lines let width = Int(video_get_a2_width(kimage_ptr)) let height = Int(video_get_a2_height(kimage_ptr)) //if let screen = NSScreen.main { // let rect = screen.frame // width = Int(rect.size.width) // height = Int(rect.size.height) //} pixels_per_line = width max_height = height //print("pixels_per_line: \(pixels_per_line), " + // "max_height: \(max_height)") let color_space = CGDisplayCopyColorSpace(CGMainDisplayID()) //let colorSpace = CGColorSpaceCreateDeviceRGB() bitmapContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: color_space, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) //CGImageAlphaInfo.noneSkipLast.rawValue bitmapData = bitmapContext.data! // Set the intial value of the data to black (0) bitmapData.initializeMemory(as: UInt32.self, repeating: 0, count: height * width) rawData = bitmapData.bindMemory(to: UInt32.self, capacity: height * width) //print("Calling video_update_scale from MainViewinitialize()") let x_width = Int(video_get_x_width(kimage_ptr)) let x_height = Int(video_get_x_height(kimage_ptr)) video_update_scale(kimage_ptr, Int32(x_width), Int32(x_height), Int32(1)) if(Context_draw) { video_set_alpha_mask(UInt32(0xff000000)) // Set video.c alpha mask, since 0 means transparent } } override func draw(_ dirtyRect: NSRect) { var rect : Change_rect //super.draw(dirtyRect) // Documentation says super.draw not needed... // Draw the current image buffer to the screen let context = NSGraphicsContext.current!.cgContext if(!Context_draw) { // Safe, simple drawing let image = bitmapContext.makeImage()! context.draw(image, in: bounds) // The above call does the actual copy of // data to the screen, and can take a while //print("Draw, bounds:\(bounds), dirtyr:\(dirtyRect)") } else { // Unsafe, more direct drawing by peeking into // NSGraphicsContext.current.data if let data = context.data { rect = Change_rect(x:0, y:0, width:Int32(context.width), height:Int32(context.height)); video_update_scale(kimage_ptr, Int32(context.width), Int32(context.height), Int32(0)) video_out_data_scaled(data, kimage_ptr, Int32(context.bytesPerRow/4), &rect); video_out_done(kimage_ptr); print("Did out_data_scaled, rect:\(rect)"); } } } @objc func do_config(_ : AnyObject) { // Create a "virtual" F4 press //print("do_config") // Create a keydown for the F4 key (keycode:0x76) adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 0); // and create a keyup for the F4 key (keycode:0x76) adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 1); } @objc func do_copy_text(_ : AnyObject) { // print("do_copy"); //let text_buf_cstr = UnsafeMutablePointer.allocate( // capacity: 2100); if let cstr = cfg_text_screen_str() { let str = String(cString: cstr); NSPasteboard.general.clearContents(); NSPasteboard.general.setString(str, forType: NSPasteboard.PasteboardType.string); } } @objc func do_paste(_ : AnyObject) { // print("do_paste") let general = NSPasteboard.general; guard let str = general.string(forType: .string) else { print("Cannot paste, nothing in clipboard"); return } //print("str: \(str)") for raw_c in str.utf8 { let c = UInt32(raw_c) let ret = adb_paste_add_buf(c) if(ret != 0) { print("Paste too large!") return; } } } func mac_update_display() { var valid : Int32 var rect : Change_rect var dirty_rect = NSRect.zero if(Context_draw) { // We just need to know if there are any changes, // don't actually do the copies now valid = video_out_query(kimage_ptr); if(valid != 0) { self.setNeedsDisplay(bounds) print("Needs display"); } return; } // Otherwise, update rawData in our Bitmap now rect = Change_rect(x:0, y:0, width:0, height:0) for i in 0...MAX_CHANGE_RECTS { // MAX_CHANGE_RECTS valid = video_out_data(rawData, kimage_ptr, Int32(pixels_per_line), &rect, Int32(i)) if(valid == 0) { break } dirty_rect.origin.x = CGFloat(rect.x) dirty_rect.origin.y = bounds.size.height - CGFloat(rect.y) - CGFloat(rect.height) dirty_rect.size.width = CGFloat(rect.width) dirty_rect.size.height = CGFloat(rect.height) self.setNeedsDisplay(bounds) // It's necessary to redraw the whole screen, // there's no mechanism to just redraw part // The coordinates would need transformation // (since Mac 0,0 is the lower left corner) //print("bounds: w:\(bounds.size.width) " + // "h:\(bounds.size.height)\n") //self.setNeedsDisplay(dirty_rect) } } } ================================================ FILE: upstream/kegs/src/Makefile ================================================ # $KmKId: makefile,v 1.48 2025-04-28 15:12:19+00 kentd Exp $ XOPTS_WIN = -Wall -fomit-frame-pointer -march=pentium SWIFTOBJS = AppDelegate.o MainView.o include vars include ldvars .SUFFIXES: .dep .proto AS = $(CC) XLIBS = -L/usr/X11/lib PERL = perl all: $(TARGET) specials: specials_clean: clean: rm -f *.o kegsmac # Mac builds: kegsmac: $(OBJECTS) $(OBJECTS1) compile_time.o $(SWIFTOBJS) clang $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) $(SWIFTOBJS) \ compile_time.o $(LDFLAGS) -o kegsmac $(EXTRA_LIBS) \ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \ -Xlinker -rpath -Xlinker @executable_path/../Frameworks \ -Xlinker -rpath -Xlinker /usr/lib/swift \ -Xlinker -no_deduplicate -fobjc-link-runtime \ -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \ -L/usr/lib/swift mkdir -p ../KEGSMAC.app/Contents/Resources/Base.lproj/ mkdir -p ../KEGSMAC.app/Contents/MacOS mkdir -p ../KEGSMAC.app/Contents/Frameworks $(PERL) cp_kegs_libs kegsmac /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx ../KEGSMAC.app/Contents/Frameworks mv kegsmac ../KEGSMAC.app/Contents/MacOS/KEGSMAC echo "APPL????" > ../KEGSMAC.app/Contents/PkgInfo cp -f Info.plist ../KEGSMAC.app/Contents/ cp -f $(PROJROOT)/lib/MainMenu.nib ../KEGSMAC.app/Contents/Resources/Base.lproj/ $(PROJROOT)/lib/make_mac_icon $(PROJROOT)/lib/kegsicon.png cp -f kegs.icns ../KEGSMAC.app/Contents/Resources/ touch '../KEGSMAC.app/Icon?' #cp -f $(PROJROOT)/lib/2mg.icns ../KEGSMAC.app/Contents/Resources/ #cp -f $(PROJROOT)/lib/525.icns ../KEGSMAC.app/Contents/Resources/ # Linux for X builds: xkegs: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ -lX11 -lXext mv xkegs .. # Cygwin for X builds: kegs.exe: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ -lXext -lX11 -lm mv kegs.exe .. # Mingw32 (native windows) builds: (broken, doesn't work currently) kegswin.exe: $(OBJECTS) $(OBJECTS1) compile_time.o $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ $(LDFLAGS) -o $(NAME)$(SUFFIX) $(EXTRA_LIBS) \ -lwinmm -lgdi32 -ldsound -lcomctl32 -lws2_32 mv $(NAME)$(SUFFIX) .. .s.o: $(AS) -c $(OPTS) -I. $*.s .c.o: $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.c .m.o: $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.m AppDelegate.o: AppDelegate.swift sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ MainView.swift -o $*.o MainView.o: MainView.swift sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ AppDelegate.swift -o $*.o win32.o: win32.rc windres -o win32.o win32.rc .c.proto: $(KMKROOT)/bin/kmkproto $(XOPTS) $*.c > $*.proto echo >> $*.proto .m.proto: $(KMKROOT)/bin/kmkproto $(XOPTS) $*.m > $*.proto echo >> $*.proto $(PROTO_OUT): $(PROTO_FILE_LIST) ldvars proto_vars $(KMKROOT)/bin/kmkproto_head $(PROTO_OUT) tmp_protos.h cat /dev/null $(PROTO_FILE_LIST) >> tmp_protos.h rm -f $(PROTO_OUT) mv tmp_protos.h $(PROTO_OUT) kmk_cp_if_diff $(PROTO_OUT) .. rm -f *.proto compile_time.o: $(OBJECTS) $(OBJECTS1) include dependency ================================================ FILE: upstream/kegs/src/adb.c ================================================ const char rcsid_adb_c[] = "@(#)$KmKId: adb.c,v 1.116 2024-09-15 13:56:12+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ /* adb_mode bit 3 and bit 2 (faster repeats for arrows and space/del) not done*/ #include "defc.h" extern int Verbose; extern word32 g_vbl_count; extern int g_num_lines_prev_superhires640; extern int g_num_lines_prev_superhires; extern int g_rom_version; extern int g_fast_disk_emul_en; extern int g_limit_speed; extern int g_irq_pending; extern int g_swap_paddles; extern int g_invert_paddles; extern int g_joystick_type; extern int g_config_control_panel; extern int g_status_enable; extern dword64 g_cur_dfcyc; extern byte *g_slow_memory_ptr; extern byte *g_memory_ptr; extern word32 g_mem_size_total; extern Kimage g_mainwin_kimage; extern Kimage g_debugwin_kimage; enum { ADB_IDLE = 0, ADB_IN_CMD, ADB_SENDING_DATA, }; #define ADB_C027_MOUSE_DATA 0x80 #define ADB_C027_MOUSE_INT 0x40 #define ADB_C027_DATA_VALID 0x20 #define ADB_C027_DATA_INT 0x10 #define ADB_C027_KBD_VALID 0x08 #define ADB_C027_KBD_INT 0x04 #define ADB_C027_MOUSE_COORD 0x02 #define ADB_C027_CMD_FULL 0x01 #define ADB_C027_NEG_MASK ( ~ ( \ ADB_C027_MOUSE_DATA | ADB_C027_DATA_VALID | \ ADB_C027_KBD_VALID | ADB_C027_MOUSE_COORD | \ ADB_C027_CMD_FULL)) int halt_on_all_c027 = 0; word32 g_adb_repeat_delay = 45; word32 g_adb_repeat_rate = 3; word32 g_adb_repeat_info = 0x23; word32 g_adb_char_set = 0x0; word32 g_adb_layout_lang = 0x0; word32 g_adb_interrupt_byte = 0; int g_adb_state = ADB_IDLE; word32 g_adb_cmd = (word32)-1; int g_adb_cmd_len = 0; int g_adb_cmd_so_far = 0; word32 g_adb_cmd_data[16]; #define MAX_ADB_DATA_PEND 16 word32 g_adb_data[MAX_ADB_DATA_PEND]; int g_adb_data_pending = 0; word32 g_c027_val = 0; word32 g_c025_val = 0; byte adb_memory[256]; word32 g_adb_mode = 0; /* mode set via set_modes, clear_modes */ int g_warp_pointer = 0; int g_hide_pointer = 0; int g_unhide_pointer = 0; int g_adb_copy_requested = 0; int g_mouse_a2_x = 0; int g_mouse_a2_y = 0; int g_mouse_a2_button = 0; int g_mouse_fifo_pos = 0; int g_mouse_raw_x = 0; int g_mouse_raw_y = 0; #define ADB_MOUSE_FIFO 8 STRUCT(Mouse_fifo) { dword64 dfcyc; int x; int y; int buttons; }; Mouse_fifo g_mouse_fifo[ADB_MOUSE_FIFO] = { { 0, 0, 0, 0 } }; int g_adb_mouse_valid_data = 0; int g_adb_mouse_coord = 0; #define MAX_KBD_BUF 8 #define MAX_KBD_PASTE_BUF 32768 int g_adb_mainwin_has_focus = 1; #if defined(__linux__) || defined(_WIN32) int g_adb_swap_command_option = 1; // Default to swap on Linux/Win #else int g_adb_swap_command_option = 0; #endif int g_key_down = 0; int g_hard_key_down = 0; int g_a2code_down = 0; int g_kbd_read_no_update = 0; int g_kbd_chars_buffered = 0; int g_kbd_buf[MAX_KBD_BUF]; int g_kbd_paste_rd_pos = 0; int g_kbd_paste_wr_pos = 0; byte g_kbd_paste_buf[MAX_KBD_PASTE_BUF]; word32 g_kbd_paste_last_key = 0; word32 g_adb_repeat_vbl = 0; int g_kbd_dev_addr = 2; /* ADB physical kbd addr */ int g_mouse_dev_addr = 3; /* ADB physical mouse addr */ int g_kbd_ctl_addr = 2; /* ADB microcontroller's kbd addr */ int g_mouse_ctl_addr = 3; /* ADB ucontroller's mouse addr*/ /* above are ucontroller's VIEW of where mouse/kbd */ /* are...if they are moved, mouse/keyboard funcs */ /* should stop (c025, c000, c024, etc). */ word32 g_virtual_key_up[4]; /* bitmask of all possible 128 a2codes */ /* indicates which keys are up=1 by bit */ int g_rawa2_to_a2code[128]; int g_keypad_key_is_down[10] = { 0 };/* List from 0-9 of which keypad */ /* keys are currently pressed */ #define SHIFT_DOWN ( (g_c025_val & 0x01) ) #define CTRL_DOWN ( (g_c025_val & 0x02) ) #define CAPS_LOCK_DOWN ( (g_c025_val & 0x04) ) #define OPTION_DOWN ( (g_c025_val & 0x40) ) #define CMD_DOWN ( (g_c025_val & 0x80) ) #define MAX_ADB_KBD_REG3 16 int g_kbd_reg0_pos = 0; int g_kbd_reg0_data[MAX_ADB_KBD_REG3]; int g_kbd_reg3_16bit = 0x602; /* also set in adb_reset()! */ int g_adb_init = 0; /* Format: a2code, ascii if no shift, ascii if shift, ascii if ctl */ const int g_a2_key_to_ascii[][4] = { { 0x00, 'a', 'A', 0x01 }, { 0x01, 's', 'S', 0x13 }, { 0x02, 'd', 'D', 0x04 }, { 0x03, 'f', 'F', 0x06 }, { 0x04, 'h', 'H', 0x08 }, { 0x05, 'g', 'G', 0x07 }, { 0x06, 'z', 'Z', 0x1a }, { 0x07, 'x', 'X', 0x18 }, { 0x08, 'c', 'C', 0x03 }, { 0x09, 'v', 'V', 0x16 }, { 0x0a, -1, -1, -1 }, { 0x0b, 'b', 'B', 0x02 }, { 0x0c, 'q', 'Q', 0x11 }, { 0x0d, 'w', 'W', 0x17 }, { 0x0e, 'e', 'E', 0x05 }, { 0x0f, 'r', 'R', 0x12 }, { 0x10, 'y', 'Y', 0x19 }, { 0x11, 't', 'T', 0x14 }, { 0x12, '1', '!', -1 }, { 0x13, '2', '@', 0x00 }, { 0x14, '3', '#', -1 }, { 0x15, '4', '$', -1 }, { 0x16, '6', '^', 0x1e }, { 0x17, '5', '%', -1 }, { 0x18, '=', '+', -1 }, { 0x19, '9', '(', -1 }, { 0x1a, '7', '&', -1 }, { 0x1b, '-', '_', 0x1f }, { 0x1c, '8', '*', -1 }, { 0x1d, '0', ')', -1 }, { 0x1e, ']', '}', 0x1d }, { 0x1f, 'o', 'O', 0x0f }, { 0x20, 'u', 'U', 0x15 }, { 0x21, '[', '{', 0x1b }, { 0x22, 'i', 'I', 0x09 }, { 0x23, 'p', 'P', 0x10 }, { 0x24, 0x0d, 0x0d, -1 }, /* return */ { 0x25, 'l', 'L', 0x0c }, { 0x26, 'j', 'J', 0x0a }, { 0x27, 0x27, '"', -1 }, /* single quote */ { 0x28, 'k', 'K', 0x0b }, { 0x29, ';', ':', -1 }, { 0x2a, 0x5c, '|', 0x1c }, /* \, | */ { 0x2b, ',', '<', -1 }, { 0x2c, '/', '?', 0x7f }, { 0x2d, 'n', 'N', 0x0e }, { 0x2e, 'm', 'M', 0x0d }, { 0x2f, '.', '>', -1 }, { 0x30, 0x09, 0x09, -1 }, /* tab */ { 0x31, ' ', ' ', -1 }, { 0x32, '`', '~', -1 }, { 0x33, 0x7f, 0x7f, -1 }, /* Delete */ { 0x34, -1, -1, -1 }, { 0x35, 0x1b, 0x1b, -1 }, /* Esc */ { 0x36, 0x0200, 0x0200, -1 }, /* control */ { 0x37, 0x8000, 0x8000, -1 }, /* Command */ { 0x38, 0x0100, 0x0100, -1 }, /* shift */ { 0x39, 0x0400, 0x0400, -1 }, /* caps lock */ { 0x3a, 0x4000, 0x4000, -1 }, /* Option */ { 0x3b, 0x08, 0x08, -1 }, /* left */ { 0x3c, 0x15, 0x15, -1 }, /* right */ { 0x3d, 0x0a, 0x0a, -1 }, /* down */ { 0x3e, 0x0b, 0x0b, -1 }, /* up arrow */ { 0x3f, -1, -1, -1 }, { 0x40, -1, -1, -1 }, { 0x41, 0x102e, 0x102c, -1 }, /* keypad . */ { 0x42, -1, -1, -1 }, { 0x43, 0x102a, 0x102a, -1 }, /* keypad * */ { 0x44, -1, -1, -1 }, { 0x45, 0x102b, 0x102b, -1 }, /* keypad + */ { 0x46, -1, -1, -1 }, { 0x47, 0x1018, 0x1018, -1 }, /* keypad Clear */ { 0x48, -1, -1, -1 }, { 0x49, -1, -1, -1 }, { 0x4a, -1, -1, -1 }, { 0x4b, 0x102f, 0x102f, -1 }, /* keypad / */ { 0x4c, 0x100d, 0x100d, -1 }, /* keypad enter */ { 0x4d, -1, -1, -1 }, { 0x4e, 0x102d, 0x102d, -1 }, /* keypad - */ { 0x4f, -1, -1, -1 }, { 0x50, -1, -1, -1 }, { 0x51, 0x103d, 0x103d, -1 }, /* keypad = */ { 0x52, 0x1030, 0x1030, -1 }, /* keypad 0 */ { 0x53, 0x1031, 0x1031, -1 }, /* keypad 1 */ { 0x54, 0x1032, 0x1032, -1 }, /* keypad 2 */ { 0x55, 0x1033, 0x1033, -1 }, /* keypad 3 */ { 0x56, 0x1034, 0x1034, -1 }, /* keypad 4 */ { 0x57, 0x1035, 0x1035, -1 }, /* keypad 5 */ { 0x58, 0x1036, 0x1036, -1 }, /* keypad 6 */ { 0x59, 0x1037, 0x1037, -1 }, /* keypad 7 */ { 0x5a, 'a', 'A', 0x01 }, /* probably not necessary */ { 0x5b, 0x1038, 0x1038, -1 }, /* keypad 8 */ { 0x5c, 0x1039, 0x1039, -1 }, /* keypad 9 */ { 0x5d, -1, -1, -1 }, { 0x5e, -1, -1, -1 }, { 0x5f, -1, -1, -1 }, { 0x60, 0x8005, 0x1060, -1 }, /* F5 */ { 0x61, 0x8006, 0x1061, -1 }, /* F6 */ { 0x62, 0x8007, 0x1062, -1 }, /* F7 */ { 0x63, 0x8003, 0x1063, -1 }, /* F3 */ { 0x64, 0x8008, 0x1064, -1 }, /* F8 */ { 0x65, 0x8009, 0x1065, -1 }, /* F9 */ { 0x66, -1, -1, -1 }, { 0x67, 0x800b, 0x1067, -1 }, /* F11 */ { 0x68, -1, -1, -1 }, { 0x69, 0x800d, 0x1069, -1 }, /* F13 */ { 0x6a, -1, -1, -1 }, { 0x6b, 0x800e, 0x106b, -1 }, /* F14 */ { 0x6c, -1, -1, -1 }, { 0x6d, 0x800a, 0x106d, -1 }, /* F10 */ { 0x6e, 0x4000, 0x4000, -1 }, /* windows key alias to option */ { 0x6f, 0x800c, 0x106f, -1 }, /* F12 */ { 0x70, -1, -1, -1 }, { 0x71, 0x800f, 0x1071, -1 }, /* F15 */ { 0x72, 0x1072, 0x1072, -1 }, /* Help, insert */ { 0x73, 0x1073, 0x1073, -1 }, /* Home */ { 0x74, 0x1074, 0x1074, -1 }, /* Page up */ { 0x75, 0x1075, 0x1075, -1 }, /* keypad delete */ { 0x76, 0x8004, 0x1076, -1 }, /* F4 */ { 0x77, 0x1077, 0x1077, -1 }, /* keypad end */ { 0x78, 0x8002, 0x1078, -1 }, /* F2 */ { 0x79, 0x1079, 0x1079, -1 }, /* keypad page down */ { 0x7a, 0x8001, 0x107a, -1 }, /* F1 */ { 0x7b, 0x08, 0x08, -1 }, /* left */ /* remapped to 0x3b */ { 0x7c, 0x15, 0x15, -1 }, /* right */ /* remapped to 0x3c */ { 0x7d, 0x0a, 0x0a, -1 }, /* down */ /* remapped to 0x3d */ { 0x7e, 0x0b, 0x0b, -1 }, /* up arrow */ /* remapped to 0x3e */ { 0x7f, -1, -1, -1 }, /* Reset */ }; int adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr) { if((kimage_ptr == &g_mainwin_kimage) && g_adb_mainwin_has_focus) { *warpptr = g_warp_pointer; return g_hide_pointer; } *warpptr = 0; return 0; } int adb_get_copy_requested() { int ret; ret = g_adb_copy_requested; g_adb_copy_requested = 0; return ret; } void adb_nonmain_check() { // Debug window active. Undo F8 pointer warping g_warp_pointer = 0; g_hide_pointer = 0; } void adb_init() { int keycode; int i; if(g_adb_init) { halt_printf("g_adb_init = %d!\n", g_adb_init); } g_adb_init = 1; for(i = 0; i < 128; i++) { keycode = g_a2_key_to_ascii[i][0]; if(keycode != i) { printf("ADB keycode lost/skipped: i=%x: keycode=%x\n", i, keycode); my_exit(1); } g_rawa2_to_a2code[i] = -1; } g_c025_val = 0; for(i = 0; i < 4; i++) { g_virtual_key_up[i] = -1; } for(i = 0; i < 10; i++) { g_keypad_key_is_down[i] = 0; } } void adb_reset() { g_c027_val = 0; g_key_down = 0; g_kbd_paste_rd_pos = 0; g_kbd_paste_wr_pos = 0; g_kbd_chars_buffered = 0; g_kbd_dev_addr = 2; g_mouse_dev_addr = 3; g_kbd_ctl_addr = 2; g_mouse_ctl_addr = 3; adb_clear_data_int(); adb_clear_mouse_int(); adb_clear_kbd_srq(); g_adb_data_pending = 0; g_adb_interrupt_byte = 0; g_adb_state = ADB_IDLE; g_adb_mouse_coord = 0; g_adb_mouse_valid_data = 0; g_kbd_reg0_pos = 0; g_kbd_reg3_16bit = 0x602; } #define LEN_ADB_LOG 16 STRUCT(Adb_log) { word32 addr; int val; int state; }; Adb_log g_adb_log[LEN_ADB_LOG]; int g_adb_log_pos = 0; void adb_log(word32 addr, int val) { int pos; pos = g_adb_log_pos; g_adb_log[pos].addr = addr; g_adb_log[pos].val = val; g_adb_log[pos].state = g_adb_state; pos++; if(pos >= LEN_ADB_LOG) { pos = 0; } g_adb_log_pos = pos; } void show_adb_log(void) { int pos; int i; pos = g_adb_log_pos; printf("ADB log pos: %d\n", pos); for(i = 0; i < LEN_ADB_LOG; i++) { pos--; if(pos < 0) { pos = LEN_ADB_LOG - 1; } printf("%d:%d: addr:%04x = %02x, st:%d\n", i, pos, g_adb_log[pos].addr, g_adb_log[pos].val, g_adb_log[pos].state); } printf("kbd: dev: %x, ctl: %x; mouse: dev: %x, ctl: %x\n", g_kbd_dev_addr, g_kbd_ctl_addr, g_mouse_dev_addr, g_mouse_ctl_addr); printf("g_adb_state: %d, g_adb_interrupt_byte: %02x\n", g_adb_state, g_adb_interrupt_byte); } void adb_error(void) { halt_printf("Adb Error\n"); show_adb_log(); } void adb_add_kbd_srq() { if(g_kbd_reg3_16bit & 0x200) { /* generate SRQ */ g_adb_interrupt_byte |= 0x08; add_irq(IRQ_PENDING_ADB_KBD_SRQ); } else { printf("Got keycode but no kbd SRQ!\n"); } } void adb_clear_kbd_srq() { remove_irq(IRQ_PENDING_ADB_KBD_SRQ); /* kbd SRQ's are the only ones to handle now, so just clean it out */ g_adb_interrupt_byte &= (~(0x08)); } void adb_add_data_int() { if(g_c027_val & ADB_C027_DATA_INT) { add_irq(IRQ_PENDING_ADB_DATA); } } void adb_add_mouse_int() { if(g_c027_val & ADB_C027_MOUSE_INT) { add_irq(IRQ_PENDING_ADB_MOUSE); } } void adb_clear_data_int() { remove_irq(IRQ_PENDING_ADB_DATA); } void adb_clear_mouse_int() { remove_irq(IRQ_PENDING_ADB_MOUSE); } void adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2) { word32 val; int shift_amount; int i; if((num_bytes >= 12) || (num_bytes >= MAX_ADB_DATA_PEND)) { halt_printf("adb_send_bytes: %d is too many!\n", num_bytes); } g_adb_state = ADB_SENDING_DATA; g_adb_data_pending = num_bytes; adb_add_data_int(); for(i = 0; i < num_bytes; i++) { if(i < 4) { val = val0; } else if(i < 8) { val = val1; } else { val = val2; } shift_amount = 8*(3 - i); g_adb_data[i] = (val >> shift_amount) & 0xff; adb_printf("adb_send_bytes[%d] = %02x\n", i, g_adb_data[i]); } } void adb_send_1byte(word32 val) { if(g_adb_data_pending != 0) { halt_printf("g_adb_data_pending: %d\n", g_adb_data_pending); } adb_send_bytes(1, val << 24, 0, 0); } void adb_response_packet(int num_bytes, word32 val) { if(g_adb_data_pending != 0) { halt_printf("adb_response_packet, but pending: %d\n", g_adb_data_pending); } g_adb_state = ADB_IDLE; g_adb_data_pending = num_bytes; g_adb_data[0] = val & 0xff; g_adb_data[1] = (val >> 8) & 0xff; g_adb_data[2] = (val >> 16) & 0xff; g_adb_data[3] = (val >> 24) & 0xff; if(num_bytes) { g_adb_interrupt_byte |= 0x80 + num_bytes - 1; } else { g_adb_interrupt_byte |= 0x80; } adb_printf("adb_response packet: %d: %08x\n", num_bytes, val); adb_add_data_int(); } void adb_kbd_reg0_data(int a2code, int is_up) { if(g_kbd_reg0_pos >= MAX_ADB_KBD_REG3) { /* too many keys, toss */ halt_printf("Had to toss key: %02x, %d\n", a2code, is_up); return; } g_kbd_reg0_data[g_kbd_reg0_pos] = a2code + (is_up << 7); adb_printf("g_kbd_reg0_data[%d] = %02x\n", g_kbd_reg0_pos, g_kbd_reg0_data[g_kbd_reg0_pos]); g_kbd_reg0_pos++; adb_add_kbd_srq(); } void adb_kbd_talk_reg0() { word32 val0, val1, reg; int num_bytes, num; int i; num = 0; val0 = g_kbd_reg0_data[0]; val1 = g_kbd_reg0_data[1]; num_bytes = 0; if(g_kbd_reg0_pos > 0) { num_bytes = 2; num = 1; if((val0 & 0x7f) == 0x7f) { /* reset */ val1 = val0; } else if(g_kbd_reg0_pos > 1) { num = 2; if((val1 & 0x7f) == 0x7f) { /* If first byte some other key, don't */ /* put RESET next! */ num = 1; val1 = 0xff; } } else { val1 = 0xff; } } if(num) { for(i = num; i < g_kbd_reg0_pos; i++) { g_kbd_reg0_data[i-1] = g_kbd_reg0_data[i]; } g_kbd_reg0_pos -= num; } reg = (val0 << 8) + val1; adb_printf("adb_kbd_talk0: %04x\n", reg); adb_response_packet(num_bytes, reg); if(g_kbd_reg0_pos == 0) { adb_clear_kbd_srq(); } } void adb_set_config(word32 val0, word32 val1, word32 val2) { int new_mouse; int new_kbd; int tmp1; new_mouse = val0 >> 4; new_kbd = val0 & 0xf; if(new_mouse != g_mouse_ctl_addr) { printf("ADB config: mouse from %x to %x!\n", g_mouse_ctl_addr, new_mouse); adb_error(); g_mouse_ctl_addr = new_mouse; } if(new_kbd != g_kbd_ctl_addr) { printf("ADB config: kbd from %x to %x!\n", g_kbd_ctl_addr, new_kbd); adb_error(); g_kbd_ctl_addr = new_kbd; } if(val1) { // Do nothing } tmp1 = val2 >> 4; if(tmp1 == 4) { g_adb_repeat_delay = 0; } else if(tmp1 < 4) { g_adb_repeat_delay = (tmp1 + 1) * 15; } else { halt_printf("Bad ADB repeat delay: %02x\n", tmp1); } tmp1 = val2 & 0xf; if(g_rom_version >= 3) { tmp1 = 9 - tmp1; } switch(tmp1) { case 0: g_adb_repeat_rate = 1; break; case 1: g_adb_repeat_rate = 2; break; case 2: g_adb_repeat_rate = 3; break; case 3: g_adb_repeat_rate = 3; break; case 4: g_adb_repeat_rate = 4; break; case 5: g_adb_repeat_rate = 5; break; case 6: g_adb_repeat_rate = 7; break; case 7: g_adb_repeat_rate = 15; break; case 8: /* I don't know what this should be, ROM 03 uses it */ g_adb_repeat_rate = 30; break; case 9: /* I don't know what this should be, ROM 03 uses it */ g_adb_repeat_rate = 60; break; default: halt_printf("Bad repeat rate: %02x\n", tmp1); } } void adb_set_new_mode(word32 val) { if(val & 0x03) { printf("Disabling keyboard/mouse:%02x!\n", val); } if(val & 0xa2) { halt_printf("ADB set mode: %02x!\n", val); adb_error(); } g_adb_mode = val; } int adb_read_c026() { word32 ret; int i; ret = 0; switch(g_adb_state) { case ADB_IDLE: ret = g_adb_interrupt_byte; g_adb_interrupt_byte = 0; if(g_irq_pending & IRQ_PENDING_ADB_KBD_SRQ) { g_adb_interrupt_byte |= 0x08; } if(g_adb_data_pending == 0) { if(ret & 0x80) { halt_printf("read_c026: ret:%02x, pend:%d\n", ret, g_adb_data_pending); } adb_clear_data_int(); } if(g_adb_data_pending) { if(g_adb_state != ADB_IN_CMD) { g_adb_state = ADB_SENDING_DATA; } } break; case ADB_IN_CMD: ret = 0; break; case ADB_SENDING_DATA: ret = g_adb_data[0]; for(i = 1; i < g_adb_data_pending; i++) { g_adb_data[i-1] = g_adb_data[i]; } g_adb_data_pending--; if(g_adb_data_pending <= 0) { g_adb_data_pending = 0; g_adb_state = ADB_IDLE; adb_clear_data_int(); } break; default: halt_printf("Bad ADB state: %d!\n", g_adb_state); adb_clear_data_int(); break; } adb_printf("Reading c026. Returning %02x, st: %02x, pend: %d\n", ret, g_adb_state, g_adb_data_pending); adb_log(0xc026, ret); return (ret & 0xff); } void adb_write_c026(int val) { word32 tmp; int dev; adb_printf("Writing c026 with %02x\n", val); adb_log(0x1c026, val); switch(g_adb_state) { case ADB_IDLE: g_adb_cmd = val; g_adb_cmd_so_far = 0; g_adb_cmd_len = 0; dev = val & 0xf; switch(val) { case 0x01: /* Abort */ adb_printf("Performing adb abort\n"); /* adb_abort() */ break; case 0x03: /* Flush keyboard buffer */ adb_printf("Flushing adb keyboard buffer\n"); /* Do nothing */ break; case 0x04: /* Set modes */ adb_printf("ADB set modes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x05: /* Clear modes */ adb_printf("ADB clear modes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x06: /* Set config */ adb_printf("ADB set config\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 3; break; case 0x07: /* Sync */ adb_printf("Performing sync cmd!\n"); g_adb_state = ADB_IN_CMD; if(g_rom_version == 1) { g_adb_cmd_len = 4; } else { g_adb_cmd_len = 8; } break; case 0x08: /* Write mem */ adb_printf("Starting write_mem cmd\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0x09: /* Read mem */ adb_printf("Performing read_mem cmd!\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0x0a: /* Read modes byte */ printf("Performing read_modes cmd!\n"); /* set_halt(1); */ adb_send_1byte(g_adb_mode); break; case 0x0b: /* Read config bytes */ printf("Performing read_configs cmd!\n"); tmp = (g_mouse_ctl_addr << 20) + (g_kbd_ctl_addr << 16) + (g_adb_char_set << 12) + (g_adb_layout_lang << 8) + (g_adb_repeat_info << 0); tmp = (0x82U << 24) + tmp; adb_send_bytes(4, tmp, 0, 0); break; case 0x0d: /* Get Version */ adb_printf("Performing get_version cmd!\n"); val = 0; if(g_rom_version == 1) { /* ROM 01 = revision 5 */ val = 5; } else { /* ROM 03 checks for rev >= 6 */ val = 6; } adb_send_1byte(val); break; case 0x0e: /* Read avail char sets */ adb_printf("Performing read avail char sets cmd!\n"); adb_send_bytes(2, /* just 2 bytes */ 0x08000000, /* number of ch sets=0x8 */ 0, 0); /* set_halt(1); */ break; case 0x0f: /* Read avail kbd layouts */ adb_printf("Performing read avail kbd layouts cmd!\n"); adb_send_bytes(0x2, /* number of kbd layouts=0xa */ 0x0a000000, 0, 0); /* set_halt(1); */ break; case 0x10: /* Reset */ printf("ADB reset, cmd 0x10\n"); do_reset(); break; case 0x11: /* Send ADB keycodes */ adb_printf("Sending ADB keycodes\n"); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 1; break; case 0x12: /* ADB cmd 12: ROM 03 only! */ if(g_rom_version >= 3) { g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; } else { printf("ADB cmd 12, but not ROM 3!\n"); adb_error(); } break; case 0x13: /* ADB cmd 13: ROM 03 only! */ if(g_rom_version >= 3) { g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; } else { printf("ADB cmd 13, but not ROM 3!\n"); adb_error(); } break; case 0x73: /* Disable SRQ device 3: mouse */ adb_printf("Disabling Mouse SRQ's (device 3)\n"); /* HACK HACK...should deal with SRQs on mouse */ break; case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: /* Listen dev x reg 3 */ adb_printf("Sending data to dev %x reg 3\n", dev); g_adb_state = ADB_IN_CMD; g_adb_cmd_len = 2; break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: /* Talk dev x reg 0 */ adb_printf("Performing talk dev %x reg 0\n", dev); if(dev == g_kbd_dev_addr) { adb_kbd_talk_reg0(); } else { printf("Unknown talk dev %x reg 0!\n", dev); /* send no data, on SRQ, system polls devs */ /* so we don't want to send anything */ adb_error(); } break; case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: /* Talk dev x reg 3 */ adb_printf("Performing talk dev %x reg 3\n", dev); if(dev == g_kbd_dev_addr) { adb_response_packet(2, g_kbd_reg3_16bit); } else { printf("Performing talk dev %x reg 3!!\n", dev); adb_error(); } break; default: halt_printf("ADB ucontroller cmd %02x unknown!\n", val); /* The Gog's says ACS Demo 2 has a bug and writes to */ /* c026 */ break; } break; case ADB_IN_CMD: adb_printf("Setting byte %d of cmd %02x to %02x\n", g_adb_cmd_so_far, g_adb_cmd, val); g_adb_cmd_data[g_adb_cmd_so_far] = val; g_adb_cmd_so_far++; if(g_adb_cmd_so_far >= g_adb_cmd_len) { adb_printf("Finished cmd %02x\n", g_adb_cmd); do_adb_cmd(); } break; default: printf("adb_state: %02x is unknown! Setting it to ADB_IDLE\n", g_adb_state); g_adb_state = ADB_IDLE; adb_error(); halt_on_all_c027 = 1; break; } return; } void do_adb_cmd() { word32 val; int dev, new_kbd, addr; dev = g_adb_cmd & 0xf; g_adb_state = ADB_IDLE; switch(g_adb_cmd) { case 0x04: /* Set modes */ adb_printf("Performing ADB set mode: OR'ing in %02x\n", g_adb_cmd_data[0]); val = g_adb_cmd_data[0] | g_adb_mode; adb_set_new_mode(val); break; case 0x05: /* clear modes */ adb_printf("Performing ADB clear mode: AND'ing in ~%02x\n", g_adb_cmd_data[0]); val = g_adb_cmd_data[0]; val = g_adb_mode & (~val); adb_set_new_mode(val); break; case 0x06: /* Set config */ adb_printf("Set ADB config to %02x %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1],g_adb_cmd_data[2]); adb_set_config(g_adb_cmd_data[0], g_adb_cmd_data[1], g_adb_cmd_data[2]); break; case 0x07: /* SYNC */ adb_printf("Performing ADB SYNC\n"); adb_printf("data: %02x %02x %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1], g_adb_cmd_data[2], g_adb_cmd_data[3]); adb_set_new_mode(g_adb_cmd_data[0]); adb_set_config(g_adb_cmd_data[1], g_adb_cmd_data[2], g_adb_cmd_data[3]); if(g_rom_version >= 3) { adb_printf(" and cmd12:%02x %02x cmd13:%02x %02x\n", g_adb_cmd_data[4], g_adb_cmd_data[5], g_adb_cmd_data[6], g_adb_cmd_data[7]); } break; case 0x08: /* Write mem */ addr = g_adb_cmd_data[0]; val = g_adb_cmd_data[1]; write_adb_ram(addr, val); break; case 0x09: /* Read mem */ addr = (g_adb_cmd_data[1] << 8) + g_adb_cmd_data[0]; adb_printf("Performing mem read to addr %04x\n", addr); adb_send_1byte(read_adb_ram(addr)); break; case 0x11: /* Send ADB keycodes */ val = g_adb_cmd_data[0]; adb_printf("Performing send ADB keycodes: %02x\n", val); adb_virtual_key_update(val & 0x7f, val >> 7); break; case 0x12: /* ADB cmd12 */ adb_printf("Performing ADB cmd 12\n"); adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); break; case 0x13: /* ADB cmd13 */ adb_printf("Performing ADB cmd 13\n"); adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); break; case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: /* Listen dev x reg 3 */ if(dev == g_kbd_dev_addr) { if(g_adb_cmd_data[1] == 0xfe) { /* change keyboard addr? */ new_kbd = g_adb_cmd_data[0] & 0xf; if(new_kbd != dev) { printf("Moving kbd to dev %x!\n", new_kbd); adb_error(); } g_kbd_dev_addr = new_kbd; } else if(g_adb_cmd_data[1] != 1) { /* see what new device handler id is */ printf("KBD listen to dev %x reg 3: 1:%02x\n", dev, g_adb_cmd_data[1]); adb_error(); } if(g_adb_cmd_data[0] != (word32)g_kbd_dev_addr) { /* see if app is trying to change addr */ printf("KBD listen to dev %x reg 3: 0:%02x!\n", dev, g_adb_cmd_data[0]); adb_error(); } g_kbd_reg3_16bit = ((g_adb_cmd_data[0] & 0xf) << 12) + (g_kbd_reg3_16bit & 0x0fff); } else if(dev == g_mouse_dev_addr) { if(g_adb_cmd_data[0] != (word32)dev) { /* see if app is trying to change mouse addr */ printf("MOUS listen to dev %x reg3: 0:%02x!\n", dev, g_adb_cmd_data[0]); adb_error(); } if(g_adb_cmd_data[1] != 1 && g_adb_cmd_data[1] != 2) { /* see what new device handler id is */ printf("MOUS listen to dev %x reg 3: 1:%02x\n", dev, g_adb_cmd_data[1]); adb_error(); } } else { printf("Listen cmd to dev %x reg3????\n", dev); printf("data0: %02x, data1: %02x ????\n", g_adb_cmd_data[0], g_adb_cmd_data[1]); adb_error(); } break; default: printf("Doing adb_cmd %02x: UNKNOWN!\n", g_adb_cmd); break; } } int adb_read_c027() { word32 ret; if(halt_on_all_c027) { halt_printf("halting on all c027 reads!\n"); } if(g_c027_val & (~ADB_C027_NEG_MASK)) { halt_printf("read_c027: g_c027_val: %02x\n", g_c027_val); } ret = (g_c027_val & ADB_C027_NEG_MASK); if(g_adb_mouse_valid_data) { ret |= ADB_C027_MOUSE_DATA; } if(g_adb_interrupt_byte != 0) { ret |= ADB_C027_DATA_VALID; } else if(g_adb_data_pending > 0) { if((g_adb_state != ADB_IN_CMD)) { ret |= ADB_C027_DATA_VALID; } } if(g_adb_mouse_coord) { ret |= ADB_C027_MOUSE_COORD; } #if 0 adb_printf("Read c027: %02x, int_byte: %02x, d_pend: %d\n", ret, g_adb_interrupt_byte, g_adb_data_pending); #endif #if 0 adb_log(0xc027, ret); #endif return ret; } void adb_write_c027(int val) { word32 old_val; word32 new_int; word32 old_int; adb_printf("Writing c027 with %02x\n", val); adb_log(0x1c027, val); old_val = g_c027_val; g_c027_val = (val & ADB_C027_NEG_MASK); new_int = g_c027_val & ADB_C027_MOUSE_INT; old_int = old_val & ADB_C027_MOUSE_INT; if(!new_int && old_int) { adb_clear_mouse_int(); } new_int = g_c027_val & ADB_C027_DATA_INT; old_int = old_val & ADB_C027_DATA_INT; if(!new_int && old_int) { /* ints were on, now off */ adb_clear_data_int(); } if(g_c027_val & ADB_C027_KBD_INT) { halt_printf("Can't support kbd interrupts!\n"); } return; } int read_adb_ram(word32 addr) { int val; adb_printf("Reading adb ram addr: %02x\n", addr); if(addr >= 0x100) { if(addr >= 0x1000 && addr < 0x2000) { /* ROM self-test checksum */ if(addr == 0x1400) { val = 0x72; } else if(addr == 0x1401) { val = 0xf7; } else { val = 0; } } else { printf("adb ram addr out of range: %04x!\n", addr); val = 0; } } else { val = adb_memory[addr]; if((addr == 0xb) && (g_rom_version == 1)) { // read special key state byte for Out of This World val = (g_c025_val >> 1) & 0x43; val |= (g_c025_val << 2) & 0x4; val |= (g_c025_val >> 2) & 0x10; } if((addr == 0xc) && (g_rom_version >= 3)) { // read special key state byte for Out of This World val = g_c025_val & 0xc7; printf("val is %02x\n", val); } } adb_printf("adb_ram returning %02x\n", val); return val; } void write_adb_ram(word32 addr, int val) { adb_printf("Writing adb_ram addr: %02x: %02x\n", addr, val); if(addr >= 0x100) { printf("write adb_ram addr: %02x: %02x!\n", addr, val); adb_error(); } else { adb_memory[addr] = val; } } int adb_get_keypad_xy(int get_y) { int x, y, key, num_keys; int i, j; key = 1; num_keys = 0; x = 0; y = 0; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) { if(g_keypad_key_is_down[key]) { num_keys++; x = x + (j - 1)*32768; y = y + (1 - i)*32768; } key++; } } if(num_keys == 0) { num_keys = 1; } adb_printf("get_xy=%d, num_keys: %d, x:%d, y:%d\n", get_y, num_keys, x, y); if(get_y) { return y / num_keys; } else { return x / num_keys; } } // g_mouse_raw_x/y: Current position (in A2 coordinates) of mouse on host screen // g_mouse_fifo[0].x/y: Current position (in A2 coords) of where we "want" // mouse on the A2 screen. // g_mouse_a2_x/y: last x,y returned through $c024 to software. // So, reading $c024 return g_mouse_fifo[].x - g_mouse_a2_x. // And, in simple cases, host mouse movement just sets g_mouse_fifo[0].x=raw_x int adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, int buttons_valid) { dword64 dfcyc; int button1_changed, mouse_moved, unhide, pos; int i; if(kimage_ptr != &g_mainwin_kimage) { adb_nonmain_check(); } dfcyc = g_cur_dfcyc; unhide = (g_adb_mainwin_has_focus == 0); if((buttons_valid >= 0) && (buttons_valid & 0x1000)) { // x, y are really deltas buttons_valid &= 0xfff; x = g_mouse_raw_x + x; y = g_mouse_raw_y + y; g_mouse_raw_x = x; g_mouse_raw_y = y; } else { g_mouse_raw_x = x; g_mouse_raw_y = y; // Clamp mouse to 0-639, 0-399 to make GSOS work nicely if(x < 0) { x = 0; unhide = 1; } if(x >= 640) { x = 639; unhide = 1; } if(y < 0) { y = 0; unhide = 1; } if(y >= 400) { y = 399; unhide = 1; } } g_unhide_pointer = unhide && !g_warp_pointer; if(kimage_ptr != &g_mainwin_kimage) { // In debugger window...just get out return 0; } if(!g_warp_pointer) { if(g_hide_pointer && g_unhide_pointer) { /* cursor has left a2 window, show it */ g_hide_pointer = 0; } if((g_num_lines_prev_superhires == 200) && (g_num_lines_prev_superhires640 == 0) && ((g_slow_memory_ptr[0x19d00] & 0x80) == 0)) { // In 320-mode superhires, cut mouse range in half x = x >> 1; } y = y >> 1; } mouse_compress_fifo(dfcyc); #if 0 printf("Update Mouse called with buttons:%d x,y:%d,%d, fifo:%d,%d, " " a2: %d,%d\n", buttons_valid, x, y, g_mouse_fifo[0].x, g_mouse_fifo[0].y, g_mouse_a2_x, g_mouse_a2_y); #endif if((buttons_valid < 0) && g_warp_pointer) { /* Warping the pointer causes it to jump here...this is not */ /* real motion, just update info and get out */ g_mouse_a2_x += (x - g_mouse_fifo[0].x); g_mouse_a2_y += (y - g_mouse_fifo[0].y); g_mouse_fifo[0].x = x; g_mouse_fifo[0].y = y; return 0; } #if 0 printf("...real move, new x: %d, %d, a2:%d,%d\n", g_mouse_fifo[0].x, g_mouse_fifo[0].y, g_mouse_a2_x, g_mouse_a2_y); #endif mouse_moved = (g_mouse_fifo[0].x != x) || (g_mouse_fifo[0].y != y); g_mouse_fifo[0].x = x; g_mouse_fifo[0].y = y; g_mouse_fifo[0].dfcyc = dfcyc; button1_changed = (buttons_valid & 1) && ((button_states & 1) != (g_mouse_fifo[0].buttons & 1)); if((button_states & 4) && !(g_mouse_fifo[0].buttons & 4) && (buttons_valid & 4)) { /* right button pressed */ adb_increment_speed(); } if((button_states & 2) && !(g_mouse_fifo[0].buttons & 2) && (buttons_valid & 2)) { /* middle button pressed */ halt2_printf("Middle button pressed\n"); } pos = g_mouse_fifo_pos; if((pos < (ADB_MOUSE_FIFO - 2)) && button1_changed) { /* copy delta to overflow, set overflow */ /* overflow ensures the mouse button state is precise at */ /* button up/down times. Using a mouse event list where */ /* deltas accumulate until a button change would work, too */ for(i = pos; i >= 0; i--) { g_mouse_fifo[i + 1] = g_mouse_fifo[i]; /* copy struct*/ } g_mouse_fifo_pos = pos + 1; } g_mouse_fifo[0].buttons = (button_states & buttons_valid) | (g_mouse_fifo[0].buttons & ~buttons_valid); if(mouse_moved || button1_changed) { if( (g_mouse_ctl_addr == g_mouse_dev_addr) && ((g_adb_mode & 0x2) == 0)) { g_adb_mouse_valid_data = 1; adb_add_mouse_int(); } } return mouse_moved; } int mouse_read_c024(dword64 dfcyc) { word32 ret, tool_start; int em_active, target_x, target_y, delta_x, delta_y, a2_x, a2_y; int mouse_button, clamped, pos; if(((g_adb_mode & 0x2) != 0) || (g_mouse_dev_addr != g_mouse_ctl_addr)){ /* mouse is off, return 0, or mouse is not autopoll */ g_adb_mouse_valid_data = 0; adb_clear_mouse_int(); return 0; } mouse_compress_fifo(dfcyc); pos = g_mouse_fifo_pos; target_x = g_mouse_fifo[pos].x; target_y = g_mouse_fifo[pos].y; mouse_button = (g_mouse_fifo[pos].buttons & 1); delta_x = target_x - g_mouse_a2_x; delta_y = target_y - g_mouse_a2_y; clamped = 0; if(delta_x > 0x3f) { delta_x = 0x3f; clamped = 1; } else if(delta_x < -0x3f) { delta_x = -0x3f; clamped = 1; } if(delta_y > 0x3f) { delta_y = 0x3f; clamped = 1; } else if(delta_y < -0x3f) { delta_y = -0x3f; clamped = 1; } if(pos > 0) { /* peek into next entry's button info if we are not clamped */ /* and we're returning the y-coord */ if(!clamped && g_adb_mouse_coord) { mouse_button = g_mouse_fifo[pos - 1].buttons & 1; } } if(g_adb_mouse_coord) { /* y coord */ delta_x = 0; /* clear unneeded x delta */ } else { delta_y = 0; /* clear unneeded y delta */ } adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x100e9], g_slow_memory_ptr[0x100ea], g_slow_memory_ptr[0x100eb], g_slow_memory_ptr[0x100ec]); adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); /* Update event manager internal state */ tool_start = (g_slow_memory_ptr[0x103ca] << 16) + (g_slow_memory_ptr[0x103c9] << 8) + g_slow_memory_ptr[0x103c8]; em_active = 0; if((tool_start >= 0x20000) && (tool_start < (g_mem_size_total - 28)) ) { /* seems to be valid ptr to addr of mem space for tools */ /* see if event manager appears to be active */ em_active = g_memory_ptr[tool_start + 6*4] + (g_memory_ptr[tool_start + 6*4 + 1] << 8); if(g_warp_pointer) { em_active = 0; } } //em_active = 0; // HACK! a2_x = g_mouse_a2_x; a2_y = g_mouse_a2_y; if(em_active) { if((!g_hide_pointer) && (g_num_lines_prev_superhires == 200) && !g_unhide_pointer) { /* if super-hires and forcing tracking, then hide */ g_hide_pointer = 1; } if(g_adb_mouse_coord == 0) { /* update x coord values */ g_slow_memory_ptr[0x47c] = a2_x & 0xff; g_slow_memory_ptr[0x57c] = a2_x >> 8; g_memory_ptr[0x47c] = a2_x & 0xff; g_memory_ptr[0x57c] = a2_x >> 8; g_slow_memory_ptr[0x10190] = a2_x & 0xff; g_slow_memory_ptr[0x10192] = a2_x >> 8; } else { g_slow_memory_ptr[0x4fc] = a2_y & 0xff; g_slow_memory_ptr[0x5fc] = a2_y >> 8; g_memory_ptr[0x4fc] = a2_y & 0xff; g_memory_ptr[0x5fc] = a2_y >> 8; g_slow_memory_ptr[0x10191] = a2_y & 0xff; g_slow_memory_ptr[0x10193] = a2_y >> 8; } } else { if(g_hide_pointer && !g_warp_pointer) { g_hide_pointer = 0; } } ret = ((!mouse_button) << 7) + ((delta_x | delta_y) & 0x7f); if(g_adb_mouse_coord) { g_mouse_a2_button = mouse_button; /* y coord has button*/ } else { ret |= 0x80; /* mouse button not down on x coord rd */ } a2_x += delta_x; a2_y += delta_y; g_mouse_a2_x = a2_x; g_mouse_a2_y = a2_y; if(g_mouse_fifo_pos) { if((target_x == a2_x) && (target_y == a2_y) && (g_mouse_a2_button == mouse_button)) { g_mouse_fifo_pos--; } } adb_printf("Rd c024, mouse is_y:%d, %02x, vbl:%08x, dfcyc:%016llx, em:" "%d\n", g_adb_mouse_coord, ret, g_vbl_count, dfcyc, em_active); adb_printf("...mouse targ_x:%d,%d delta_x,y:%d,%d fifo:%d, a2:%d,%d\n", target_x, target_y, delta_x, delta_y, g_mouse_fifo_pos, a2_x, a2_y); adb_printf(" post a2_x:%02x,%02x,%02x,%02x\n", g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); if((g_mouse_fifo_pos == 0) && (g_mouse_fifo[0].x == a2_x) && (g_mouse_fifo[0].y == a2_y) && ((g_mouse_fifo[0].buttons & 1) == g_mouse_a2_button)) { g_adb_mouse_valid_data = 0; adb_clear_mouse_int(); } g_adb_mouse_coord = !g_adb_mouse_coord; return ret; } void mouse_compress_fifo(dword64 dfcyc) { dword64 ddelta; int pos; /* The mouse fifo exists so that fast button changes don't get lost */ /* if the emulator lags behind the mouse events */ /* But the FIFO means really old mouse events are saved if */ /* the emulated code isn't looking at the mouse registers */ /* This routine compresses all mouse events > 0.5 seconds old */ ddelta = (500LL*1000) << 16; for(pos = g_mouse_fifo_pos; pos >= 1; pos--) { if((g_mouse_fifo[pos].dfcyc + ddelta) < dfcyc) { /* Remove this entry */ adb_printf("Old mouse FIFO pos %d removed\n", pos); g_mouse_fifo_pos = pos - 1; continue; } /* Else, stop searching the FIFO */ break; } } void adb_paste_update_state() { int rd_pos, wr_pos; rd_pos = g_kbd_paste_rd_pos; wr_pos = g_kbd_paste_wr_pos; if(rd_pos >= wr_pos) { g_kbd_paste_rd_pos = 0; g_kbd_paste_wr_pos = 0; return; } if(g_kbd_chars_buffered == 0) { g_kbd_buf[0] = g_kbd_paste_buf[rd_pos]; g_kbd_paste_rd_pos = rd_pos + 1; g_kbd_chars_buffered = 1; } } int adb_paste_add_buf(word32 key) { word32 last_key; int pos; // Applesoft reads $C000 to check for ctrl-C after each statement. // So if we dropped all chars into g_kbd_buf[], we could end up // losing chars due to multiple reads of $C000 without writes to $C010 // causing g_kbd_read_no_update to toss a paste char. // Instead, have a separate buffer, and when g_kbd_chars_buffered==0, // copy one paste char to g_kbd_buf[0]. This also solves a problem // where Applesoft is doing: 10 GOTO 10 and it needs to see a Ctrl-C // to stop--but a paste buffer is in the way. // But, now pressing keys while a paste is pending causes those keys // to take priority during the paste. last_key = g_kbd_paste_last_key; g_kbd_paste_last_key = key; if(key == 10) { // \n, newline on Unix key = 13; // \r, return if(last_key == 13) { key = 0; // CR, then LF--eat the LF } } if((key == 0) || (key >= 0x80)) { return 0; // Just skip these keys } pos = g_kbd_paste_wr_pos; if(pos >= MAX_KBD_PASTE_BUF) { return 1; } g_kbd_paste_buf[pos] = key | 0x80; g_kbd_paste_wr_pos = pos + 1; adb_paste_update_state(); return 0; } void adb_key_event(int a2code, int is_up) { word32 special, vbl_count; int key, hard_key, pos, tmp_ascii, ascii; if(is_up) { adb_printf("adb_key_event, key:%02x, is up, g_key_down: %02x\n", a2code, g_key_down); } if(a2code < 0 || a2code > 0x7f) { halt_printf("add_key_event: a2code: %04x!\n", a2code); return; } if(!is_up && a2code == 0x35) { /* ESC pressed, see if ctrl & cmd key down */ if(CTRL_DOWN && CMD_DOWN) { /* Desk mgr int */ printf("Desk mgr int!\n"); g_adb_interrupt_byte |= 0x20; adb_add_data_int(); } } /* convert key to ascii, if possible */ hard_key = 0; if(g_a2_key_to_ascii[a2code][1] & 0xef00) { /* special key */ } else { /* we have ascii */ hard_key = 1; } pos = 1; ascii = g_a2_key_to_ascii[a2code][1]; if(CAPS_LOCK_DOWN && (ascii >= 'a') && (ascii <= 'z')) { pos = 2; if(SHIFT_DOWN && (g_adb_mode & 0x40)) { /* xor shift mode--capslock and shift == lowercase */ pos = 1; } } else if(SHIFT_DOWN) { pos = 2; } ascii = g_a2_key_to_ascii[a2code][pos]; if(CTRL_DOWN) { tmp_ascii = g_a2_key_to_ascii[a2code][3]; if(tmp_ascii >= 0) { ascii = tmp_ascii; } } key = (ascii & 0x7f) + 0x80; special = (ascii >> 8) & 0xff; if(ascii < 0) { printf("ascii1: %d, a2code: %02x, pos: %d\n", ascii,a2code,pos); ascii = 0; special = 0; } if(!is_up) { if(hard_key) { g_kbd_buf[g_kbd_chars_buffered] = key; g_kbd_chars_buffered++; if(g_kbd_chars_buffered >= MAX_KBD_BUF) { g_kbd_chars_buffered = MAX_KBD_BUF - 1; } g_key_down = 1; g_a2code_down = a2code; /* first key down, set up autorepeat */ vbl_count = g_vbl_count; g_adb_repeat_vbl = vbl_count + g_adb_repeat_delay; if(g_adb_repeat_delay == 0) { g_key_down = 0; } g_hard_key_down = 1; } g_c025_val = g_c025_val | special; adb_printf("new c025_or: %02x\n", g_c025_val); } else { if(hard_key && (a2code == g_a2code_down)) { g_hard_key_down = 0; /* Turn off repeat */ g_key_down = 0; } g_c025_val = g_c025_val & (~ special); adb_printf("new c025_and: %02x\n", g_c025_val); } if(g_key_down) { g_c025_val = g_c025_val & (~0x20); } else { /* If no hard key down, set update mod latch */ g_c025_val = g_c025_val | 0x20; } } word32 adb_read_c000() { word32 vbl_count; if( ((g_kbd_buf[0] & 0x80) == 0) && (g_key_down == 0)) { /* nothing happening, just get out */ return g_kbd_buf[0]; } if(g_kbd_buf[0] & 0x80) { /* got one */ if((g_kbd_read_no_update++ > 5) && (g_kbd_chars_buffered > 1)) { /* read 5 times, keys pending, let's move it along */ printf("Read %02x %d times, tossing\n", g_kbd_buf[0], g_kbd_read_no_update); adb_access_c010(); } } else { vbl_count = g_vbl_count; if(g_key_down && (vbl_count >= g_adb_repeat_vbl)) { /* repeat the g_key_down */ g_c025_val |= 0x8; adb_key_event(g_a2code_down, 0); g_adb_repeat_vbl = vbl_count + g_adb_repeat_rate; } } return g_kbd_buf[0]; } word32 adb_access_c010() { int tmp; int i; g_kbd_read_no_update = 0; tmp = g_kbd_buf[0] & 0x7f; g_kbd_buf[0] = tmp; tmp = tmp | (g_hard_key_down << 7); if(g_kbd_chars_buffered) { for(i = 1; i < g_kbd_chars_buffered; i++) { g_kbd_buf[i - 1] = g_kbd_buf[i]; } g_kbd_chars_buffered--; if(g_kbd_chars_buffered == 0) { adb_paste_update_state(); } } g_c025_val = g_c025_val & (~ (0x08)); return tmp; } word32 adb_read_c025() { return g_c025_val; } int adb_is_cmd_key_down() { return CMD_DOWN; } int adb_is_option_key_down() { return OPTION_DOWN; } void adb_increment_speed() { const char *str; g_limit_speed++; if(g_limit_speed > 3) { g_limit_speed = 0; } str = ""; switch(g_limit_speed) { case 0: str = "...as fast as possible!"; break; case 1: str = "...1.024MHz!"; break; case 2: str = "...2.8MHz!"; break; case 3: str = "...8.0MHz!"; break; } printf("Toggling g_limit_speed to %d%s\n", g_limit_speed, str); } void adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask) { // Called by *driver.c host drivers to handle focus changes and // capslock state (so if capslock is on, we leave the window, release // capslock, then reenter the window, we update things properly). if(kimage_ptr == &g_mainwin_kimage) { g_c025_val = (g_c025_val & (~mask)) | new_c025_val; } else { kimage_ptr->c025_val = (kimage_ptr->c025_val & (~mask)) | new_c025_val; } } int adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr) { int i; switch(unicode_c) { case 0x00a3: // British pound unicode_c = '#'; break; case 0x00e0: // a with left accent unicode_c = '@'; break; case 0x00b0: // degrees (French) case 0x00c4: // A with umlaut (German, Swedish) case 0x00a1: // ! upside down (Spanish) case 0x00c6: // AE (Danish) unicode_c = '['; break; case 0x00e7: // c with tail (French/Italian) case 0x00d1: // N with ~ (Spanish) case 0x00d6: // O with umlaut (German, Swedish) case 0x00d8: // O with slash (Danish) unicode_c = '\\'; break; case 0x00a7: // ss like thing (French) case 0x00dc: // U with umlaut case 0x00bf: // ? upside down (Spanish) case 0x00c5: // A with circle (Danish) //case 0x00e9: // e with right accent (Italian) unicode_c = ']'; break; //case 0x0000: // u with left accent (Italian) // unicode_c = '`'; // break; case 0x00e4: // a with umlaut (german) case 0x00e9: // e with accent (french) case 0x0000: // ae (Danish) unicode_c = '{'; break; case 0x00f6: // o with umlaut (German/Swedish) case 0x00f9: // u with left accent (French) case 0x00f8: // o with slash (Danish) case 0x00f1: // n with ~ (Spanish) case 0x00f2: // o with ` (Italian) unicode_c = '|'; break; case 0x00e8: // e with ` (French, Italian) case 0x00fc: // u with umlaut (German) case 0x00e5: // a with circle (Danish/Swedish) unicode_c = '}'; break; case 0x00a8: // two high dots (French) case 0x00ec: // i with ` (Italian) case 0x00df: // german B thing (German) unicode_c = '~'; break; } if(unicode_c > 0x7f) { return a2code; // Use a2code instead } if((g_a2_key_to_ascii[a2code][1] & 0xf000) == 0x1000) { // Keypad // Don't remap keypad keys, we need them for Keypad Joystick if((unicode_c >= '0') && (unicode_c <= '9')) { return a2code; } } for(i = 0; i < 128; i++) { if(g_a2_key_to_ascii[i][1] == unicode_c) { // Not-shifted *shift_down_ptr = 0; return g_a2_key_to_ascii[i][0]; } if(g_a2_key_to_ascii[i][2] == unicode_c) { // Shifted *shift_down_ptr = 1; return g_a2_key_to_ascii[i][0]; } } return a2code; // Not found, use default a2code } void adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, int is_up) { word32 restore_c025_val, restorek_c025_val; int special, ascii_and_type, ascii, new_shift, a2code, other_a2code; /* this routine called by xdriver to pass raw codes--handle */ /* ucontroller and ADB bus protocol issues here */ /* if autopoll on, pass it on through to c025,c000 regs */ /* else only put it in kbd reg 3, and pull SRQ if needed */ adb_printf("adb_phys_key_update: %02x, %d\n", raw_a2code, is_up); if((raw_a2code < 0) || (raw_a2code > 0x7f)) { halt_printf("raw_a2code: %04x!\n", raw_a2code); return; } a2code = raw_a2code; restore_c025_val = 0; restorek_c025_val = 0; if(unicode_c > 0) { // To enable international keyboards, ignore a2code, look up // what U.S. keycode would be and return that new_shift = g_c025_val & 1; a2code = adb_ascii_to_a2code(unicode_c, a2code, &new_shift); if(a2code && ((g_c025_val & 1) != new_shift)) { restore_c025_val = g_c025_val | 0x100; restorek_c025_val = kimage_ptr->c025_val; g_c025_val = (g_c025_val & -2) | new_shift; kimage_ptr->c025_val = (kimage_ptr->c025_val & -2) | new_shift; } if(!is_up) { g_rawa2_to_a2code[raw_a2code & 0x7f] = a2code; } } /* Remap 0x7b-0x7e to 0x3b-0x3e (arrow keys on new mac keyboards) */ if((a2code >= 0x7b) && (a2code <= 0x7e)) { a2code = a2code - 0x40; } if(g_adb_swap_command_option) { if(a2code == 0x37) { // Command? a2code = 0x3a; // -> Option } else if(a2code == 0x3a) { // Option? a2code = 0x37; // -> Command } } /* Now check for special keys (function keys, etc) */ ascii_and_type = g_a2_key_to_ascii[a2code][1]; special = 0; if((ascii_and_type & 0xf000) == 0x8000) { /* special function key */ special = ascii_and_type & 0xff; switch(special) { case 0x01: /* F1 - remap to cmd */ a2code = 0x37; special = 0; break; case 0x02: /* F2 - remap to option */ a2code = 0x3a; special = 0; break; case 0x03: /* F3 - remap to escape for OS/2 */ a2code = 0x35; special = 0; break; case 0x0c: /* F12 - remap to reset */ a2code = 0x7f; special = 0; break; default: break; } } /* Only process reset requests here */ if((is_up == 0) && (a2code == 0x7f) && CTRL_DOWN) { /* Reset pressed! */ printf("Reset pressed since CTRL_DOWN: %d\n", CTRL_DOWN); do_reset(); return; } if(special && !is_up) { switch(special) { case 0x04: /* F4 - Emulator config panel */ cfg_toggle_config_panel(); break; case 0x05: /* F5 - Force Refresh */ g_status_enable = !g_status_enable; // video_update() will call video_update_status_enable() break; case 0x06: /* F6 - emulator speed */ if(SHIFT_DOWN) { halt2_printf("Shift-F6 pressed\n"); } else { adb_increment_speed(); } break; case 0x07: /* F7 - toggle debugger window, SHIFT:fast disk */ if(SHIFT_DOWN) { g_fast_disk_emul_en = !g_fast_disk_emul_en; iwm_update_fast_disk_emul(g_fast_disk_emul_en); printf("g_fast_disk_emul_en is now %d\n", g_fast_disk_emul_en); } else { video_set_active(&g_debugwin_kimage, !g_debugwin_kimage.active); printf("Toggled debugger window to:%d\n", g_debugwin_kimage.active); } break; case 0x08: /* F8 - warp pointer */ g_warp_pointer = !g_warp_pointer; g_hide_pointer = g_warp_pointer; printf("New warp:%d, new hide:%d\n", g_warp_pointer, g_hide_pointer); break; case 0x09: /* F9 - swap paddles */ if(CTRL_DOWN) { g_adb_copy_requested = 1; } else if(SHIFT_DOWN) { g_swap_paddles = !g_swap_paddles; printf("Swap paddles is now: %d\n", g_swap_paddles); } else { g_invert_paddles = !g_invert_paddles; printf("Invert paddles is now: %d\n", g_invert_paddles); } break; case 0x0a: /* F10 - nothing */ break; case 0x0b: /* F11 - full screen */ break; } return; } if(kimage_ptr == &g_debugwin_kimage) { debugger_key_event(kimage_ptr, a2code, is_up); if(restore_c025_val) { g_c025_val = restore_c025_val & 0xff; // Restore shift kimage_ptr->c025_val = restorek_c025_val; } return; } /* Handle Keypad Joystick here partly...if keypad key pressed */ /* while in Keypad Joystick mode, do not pass it on as a key press */ if((ascii_and_type & 0xff00) == 0x1000) { /* Keep track of keypad number keys being up or down even */ /* if joystick mode isn't keypad. This avoid funny cases */ /* if joystick mode is changed while a key is pressed */ ascii = ascii_and_type & 0xff; if(ascii > 0x30 && ascii <= 0x39) { g_keypad_key_is_down[ascii - 0x30] = !is_up; } if(g_joystick_type == 0) { /* If Joystick type is keypad, then do not let these */ /* keypress pass on further, except for cmd/opt */ if(ascii == 0x30) { /* remap '0' to cmd */ a2code = 0x37; } else if(ascii == 0x2e || ascii == 0x2c) { /* remap '.' and ',' to option */ a2code = 0x3a; } else { /* Just ignore it in this mode */ return; } } } adb_maybe_virtual_key_update(a2code, is_up); other_a2code = g_rawa2_to_a2code[raw_a2code & 0x7f]; if((other_a2code >= 0) && is_up) { adb_maybe_virtual_key_update(other_a2code, is_up); g_rawa2_to_a2code[raw_a2code & 0x7f] = -1; } if(restore_c025_val) { g_c025_val = restore_c025_val & 0xff; // Restore shift } } void adb_maybe_virtual_key_update(int a2code, int is_up) { int autopoll; autopoll = 1; if(g_adb_mode & 1) { /* autopoll is explicitly off */ autopoll = 0; } if(g_kbd_dev_addr != g_kbd_ctl_addr) { /* autopoll is off because ucontroller doesn't know kbd moved */ autopoll = 0; } if(g_config_control_panel) { /* always do autopoll */ autopoll = 1; } if(is_up) { if(!autopoll) { /* no auto keys, generate SRQ! */ adb_kbd_reg0_data(a2code, is_up); } else { adb_virtual_key_update(a2code, is_up); } } else { if(!autopoll) { /* no auto keys, generate SRQ! */ adb_kbd_reg0_data(a2code, is_up); } else { /* was up, now down */ adb_virtual_key_update(a2code, is_up); } } } void adb_virtual_key_update(int a2code, int is_up) { word32 mask; int bitpos; int i; adb_printf("Virtual handle a2code: %02x, is_up: %d\n", a2code, is_up); if(a2code < 0 || a2code > 0x7f) { halt_printf("a2code: %04x!\n", a2code); return; } i = (a2code >> 5) & 3; bitpos = a2code & 0x1f; mask = (1 << bitpos); if(is_up) { if(g_virtual_key_up[i] & mask) { /* already up, do nothing */ } else { g_virtual_key_up[i] |= mask; adb_key_event(a2code, is_up); } } else { if(g_virtual_key_up[i] & mask) { g_virtual_key_up[i] &= (~mask); adb_key_event(a2code, is_up); } } } #if 0 void adb_all_keys_up() { word32 mask; int i, j; for(i = 0; i < 4; i++) { for(j = 0; j < 32; j++) { mask = 1 << j; if((g_virtual_key_up[i] & mask) == 0) { /* create key-up event */ adb_physical_key_update(i*32 + j, 1); } } } } #endif void adb_kbd_repeat_off() { g_key_down = 0; } void adb_mainwin_focus(int has_focus) { g_adb_mainwin_has_focus = has_focus; // printf("g_adb_mainwin_has_focus=%d\n", g_adb_mainwin_has_focus); if(!has_focus) { adb_nonmain_check(); } } ================================================ FILE: upstream/kegs/src/applesingle.c ================================================ const char rcsid_applesing_c[] = "@(#)$KmKId: applesingle.c,v 1.5 2021-12-19 04:14:31+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // From Wikipedia AppleSingle_and_AppleDouble_formats): // https://web.archive.org/web/20180311140826/http://kaiser-edv.de/ // documents/AppleSingle_AppleDouble.pdf // All fields in an Applesingle file are in big-endian format // ProDOS forked files are described in Technote tn-pdos-025. #include "defc.h" word32 applesingle_get_be32(const byte *bptr) { return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; } word32 applesingle_get_be16(const byte *bptr) { return (bptr[0] << 8) | bptr[1]; } void applesingle_set_be32(byte *bptr, word32 val) { bptr[3] = val; bptr[2] = val >> 8; bptr[1] = val >> 16; bptr[0] = val >> 24; } void applesingle_set_be16(byte *bptr, word32 val) { bptr[1] = val; bptr[0] = val >> 8; } word32 applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data) { byte *fptr, *bptr; word32 data_size, resource_size, rounded_data_size, num_entries; word32 rounded_resource_size, hdr_size, max_size, hdr_pos, data_pos; word32 block, key_block, ret, good, has_finder_info, offset; int level; int i, j; #if 0 printf("applesingle_map_from_prodos: %s do_file_data:%d\n", fileptr->unix_path, do_file_data); #endif // First, handle mini directory describing the forks key_block = fileptr->key_block; ret = dynapro_map_one_file_block(dsk, fileptr, key_block, 1U << 30, 0); if(ret == 0) { printf(" dynapro_map_one_file_block ret 0, applesingle done\n"); return 0; } bptr = &(dsk->raw_data[key_block << 9]); data_size = dynapro_get_word24(&bptr[5]); resource_size = dynapro_get_word24(&bptr[0x100 + 5]); has_finder_info = bptr[9] | bptr[27]; num_entries = 1; // ProDOS info always if(has_finder_info) { num_entries++; } rounded_data_size = data_size; if(data_size) { rounded_data_size = (data_size + 0x200) & -0x200; num_entries++; } rounded_resource_size = resource_size; if(resource_size) { rounded_resource_size = (resource_size + 0x200) & -0x200; num_entries++; } hdr_size = 256; max_size = hdr_size + rounded_resource_size + rounded_data_size; fileptr->buffer_ptr = 0; fptr = 0; if(do_file_data) { fptr = calloc(1, max_size + 0x200); #if 0 printf(" fptr:%p, max_size:%08x, res:%08x, data:%08x\n", fptr, max_size, rounded_resource_size, rounded_data_size); #endif } // From now on, errors cannot return without free'ing fptr good = 1; if(resource_size) { block = dynapro_get_word16(&bptr[0x100 + 1]); level = bptr[0x100]; if(fptr) { fileptr->buffer_ptr = fptr + 256; } ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, resource_size); if(ret == 0) { good = 0; } } if(data_size) { block = dynapro_get_word16(&bptr[1]); level = bptr[0]; if(fptr) { fileptr->buffer_ptr = fptr + 256 + rounded_resource_size; } ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, data_size); if(ret == 0) { good = 0; } } fileptr->buffer_ptr = 0; // Now prepare the header if(fptr) { applesingle_set_be32(&fptr[0], 0x00051600); // Magic applesingle_set_be32(&fptr[4], 0x00020000); // Version applesingle_set_be16(&fptr[24], num_entries); // Version hdr_pos = 26; data_pos = 192; // First do ProDOS entry applesingle_set_be32(&fptr[hdr_pos + 0], 11); // ProDOS Info applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); applesingle_set_be32(&fptr[hdr_pos + 8], 8); applesingle_set_be16(&fptr[data_pos + 0], 0xc3); applesingle_set_be16(&fptr[data_pos + 2], fileptr->file_type); applesingle_set_be32(&fptr[data_pos + 4], fileptr->aux_type); hdr_pos += 12; data_pos += 8; // Then do FinderInfo if(has_finder_info) { applesingle_set_be32(&fptr[hdr_pos + 0], 9); //Finder applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); applesingle_set_be32(&fptr[hdr_pos + 8], 32); for(i = 0; i < 2; i++) { offset = bptr[9 + 18*i]; if(!offset) { continue; // skip it } offset = ((offset - 1) & 1) * 8; for(j = 0; j < 9; j++) { fptr[data_pos + offset + j] = bptr[10 + 18*i + j]; } } hdr_pos += 12; data_pos += 32; } if(data_pos >= 256) { printf("data_pos:%08x is too big\n", data_pos); good = 0; } // First, do data fork if(data_size) { applesingle_set_be32(&fptr[hdr_pos + 0], 1); // Data applesingle_set_be32(&fptr[hdr_pos + 4], 256 + rounded_resource_size); applesingle_set_be32(&fptr[hdr_pos + 8], data_size); hdr_pos += 12; } // Then do resource fork if(resource_size) { applesingle_set_be32(&fptr[hdr_pos + 0], 2); // Rsrc applesingle_set_be32(&fptr[hdr_pos + 4], 256); applesingle_set_be32(&fptr[hdr_pos + 8], resource_size); hdr_pos += 12; } if(hdr_pos > 192) { printf("hdr:%08x stomped on data\n", hdr_pos); good = 0; } if(good) { ret = dynapro_write_to_unix_file(fileptr->unix_path, fptr, 256 + rounded_resource_size + data_size); if(ret == 0) { good = 0; } } free(fptr); } // printf("applesingle_map_from_prodos done, good:%d\n", good); return good; } word32 applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, dword64 dsize) { byte *bptr, *tptr; word32 key_block, blocks_used, entry_id, blocks_out, offset, length; word32 magic, version, hdr_pos, did_fork; int num_entries; int i; // Return 0 if anything is wrong with the .applesingle file // Otherwise, return (blocks_used << 16) | (key_block & 0xffff) #if 0 printf("applesingle_from_unix %s, size:%08llx\n", fileptr->unix_path, dsize); #endif key_block = fileptr->key_block; bptr = &(dsk->raw_data[key_block << 9]); if(dsize < 50) { printf("Applesingle is too small\n"); return 0; } magic = applesingle_get_be32(&fptr[0]); version = applesingle_get_be32(&fptr[4]); num_entries = applesingle_get_be16(&fptr[24]); if((magic != 0x00051600) || (version < 0x00020000)) { printf("Bad Applesingle magic number is: %08x, vers:%08x\n", magic, version); return 0; } hdr_pos = 26; blocks_used = 1; did_fork = 0; // printf(" num_entries:%d\n", num_entries); for(i = 0; i < num_entries; i++) { if((hdr_pos + 24) > dsize) { printf("Applesingle header exceeds file size i:%d of " "%d, hdr_pos:%04x dsize:%08llx\n", i, num_entries, hdr_pos, dsize); return 0; } entry_id = applesingle_get_be32(&fptr[hdr_pos + 0]); offset = applesingle_get_be32(&fptr[hdr_pos + 4]); length = applesingle_get_be32(&fptr[hdr_pos + 8]); #if 0 printf(" header[%d] at +%04x: id:%d, offset:%08x, len:%08x\n", i, hdr_pos, entry_id, offset, length); #endif if((offset + length) > dsize) { printf("Applesingle entry_id:%d exceeds file size\n", entry_id); return 0; } switch(entry_id) { case 1: // Data fork case 2: // Resource fork tptr = bptr; if(entry_id == 2) { // Resource fork tptr += 0x100; } #if 0 printf(" for entry_id %d, offset:%08x, length:%08x, " "fptr:%p\n", entry_id, offset, length, fptr); #endif if(did_fork & (1 << entry_id)) { printf("fork %d repeated!\n", entry_id); return 0; } did_fork |= (1 << entry_id); blocks_out = applesingle_make_prodos_fork(dsk, fptr + offset, tptr, length); if(blocks_out == 0) { return 0; } blocks_used += (blocks_out >> 16); break; case 9: // Finder Info if(length < 32) { printf("Invalid Finder info, len:%d\n", length); } bptr[8] = 0x12; bptr[8 + 18] = 0x12; bptr[9] = 1; bptr[9 + 18] = 2; for(i = 0; i < 16; i++) { bptr[10 + i] = fptr[offset + i]; bptr[10 + 18 + i] = fptr[offset + 16 + i]; } break; case 11: // ProDOS File Info fileptr->file_type = fptr[offset + 3]; fileptr->aux_type = (fptr[offset + 6] << 8) | fptr[offset + 7]; break; default: break; // Ignore it } hdr_pos += 12; } for(i = 1; i < 3; i++) { if((did_fork >> i) & 1) { continue; } // Create one block for this fork even though it's length==0 // i==1: no data fork; i==2: no resource fork printf(" Doing dummy fork, i:%d, fptr:%p\n", i, fptr); blocks_out = applesingle_make_prodos_fork(dsk, fptr, bptr + ((i & 2) * 0x80), 0); if(blocks_out == 0) { return blocks_out; } blocks_used += (blocks_out >> 16); } fileptr->eof = 0x200; return (blocks_used << 16) | key_block; } word32 applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length) { word32 block, blocks_out, storage_type; #if 0 printf("applesingle_make_prodos_fork: fptr:%p, tptr:%p, length:%08x\n", fptr, tptr, length); #endif // Handle creating either a resource or data fork block = dynapro_find_free_block(dsk); if(block == 0) { return 0; } blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, block, length); // printf(" dynapro_fork_from_unix ret: %08x, storage:%02x\n", // blocks_out, storage_type); tptr[0] = storage_type >> 4; tptr[1] = blocks_out & 0xff; // key_block lo tptr[2] = (blocks_out >> 8) & 0xff; // key_block hi tptr[3] = (blocks_out >> 16) & 0xff; // blocks_used lo tptr[4] = (blocks_out >> 24) & 0xff; // blocks_used hi tptr[5] = length & 0xff; // eof lo tptr[6] = (length >> 8) & 0xff; // eof mid tptr[7] = (length >> 16) & 0xff; // eof hi return blocks_out; } ================================================ FILE: upstream/kegs/src/clock.c ================================================ const char rcsid_clock_c[] = "@(#)$KmKId: clock.c,v 1.40 2023-09-23 17:51:22+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" #include #ifdef _WIN32 # include # include #else # include #endif extern int Verbose; extern word32 g_vbl_count; extern int g_rom_version; extern int g_config_kegs_update_needed; #define CLK_IDLE 1 #define CLK_TIME 2 #define CLK_INTERNAL 3 #define CLK_BRAM1 4 #define CLK_BRAM2 5 int g_clk_mode = CLK_IDLE; int g_clk_read = 0; int g_clk_reg1 = 0; extern word32 g_c033_data; extern word32 g_c034_val; byte g_bram[2][256]; byte *g_bram_ptr = &(g_bram[0][0]); word32 g_clk_cur_time = 0xa0000000; int g_clk_next_vbl_update = 0; double get_dtime() { #ifdef _WIN32 FILETIME filetime; dword64 dlow, dhigh; #else struct timeval tp1; double dsec; double dusec; #endif double dtime; /* Routine used to return actual system time as a double */ /* No routine cares about the absolute value, only deltas--maybe */ /* take advantage of that in future to increase usec accuracy */ #ifdef _WIN32 //dtime = timeGetTime() / 1000.0; GetSystemTimePreciseAsFileTime(&filetime); dlow = filetime.dwLowDateTime; dhigh = filetime.dwHighDateTime; dlow = (dhigh << 32) | dlow; dtime = (double)dlow; dtime = dtime / (1000*1000*10.0); // FILETIME is in 100ns incs #else # ifdef SOLARIS gettimeofday(&tp1, (void *)0); # else gettimeofday(&tp1, (struct timezone *)0); # endif dsec = (double)tp1.tv_sec; dusec = (double)tp1.tv_usec; dtime = dsec + (dusec / (1000.0 * 1000.0)); #endif return dtime; } int micro_sleep(double dtime) { #ifndef _WIN32 struct timeval Timer; int ret; #endif if(dtime <= 0.0) { return 0; } if(dtime >= 1.0) { halt_printf("micro_sleep called with %f!!\n", dtime); return -1; } #if 0 printf("usleep: %f\n", dtime); #endif #ifdef _WIN32 Sleep((word32)(dtime * 1000)); #else Timer.tv_sec = 0; Timer.tv_usec = (dtime * 1000000.0); if( (ret = select(0, 0, 0, 0, &Timer)) < 0) { fprintf(stderr, "micro_sleep (select) ret: %d, errno: %d\n", ret, errno); return -1; } #endif return 0; } void clk_bram_zero() { int i, j; /* zero out all bram */ for(i = 0; i < 2; i++) { for(j = 0; j < 256; j++) { g_bram[i][j] = 0; } } g_bram_ptr = &(g_bram[0][0]); } void clk_bram_set(int bram_num, int offset, int val) { if((bram_num < 0) || (bram_num > 2)) { printf("bram_num %d out of range\n", bram_num); return; } if((offset < 0) || (offset > 0x100)) { printf("bram offset %05x out of range\n", offset); return; } g_bram[bram_num][offset] = val; } void clk_setup_bram_version() { if(g_rom_version < 3) { g_bram_ptr = (&g_bram[0][0]); // ROM 01 } else { g_bram_ptr = (&g_bram[1][0]); // ROM 03 } } void clk_write_bram(FILE *fconf) { int i, j, k; for(i = 0; i < 2; i++) { fprintf(fconf, "\n"); for(j = 0; j < 256; j += 16) { fprintf(fconf, "bram%d[%02x] =", 2*i + 1, j); for(k = 0; k < 16; k++) { fprintf(fconf, " %02x", g_bram[i][j+k]); } fprintf(fconf, "\n"); } } } void update_cur_time() { struct tm *tm_ptr; time_t cur_time, secs, secs2; cur_time = time(0); /* Figure out the timezone (effectively) by diffing two times. */ /* this is probably not right for a few hours around daylight savings*/ /* time transition */ secs2 = mktime(gmtime(&cur_time)); tm_ptr = localtime(&cur_time); secs = mktime(tm_ptr); secs2 = secs2 - secs; // this is the timezone offset #ifdef MAC /* Mac OS X's mktime function modifies the tm_ptr passed in for */ /* the CDT timezone and breaks this algorithm. So on a Mac, we */ /* will use the tm_ptr->gmtoff member to correct the time */ secs = secs + tm_ptr->tm_gmtoff; #else secs = cur_time - secs2; if(tm_ptr->tm_isdst) { /* adjust for daylight savings time */ secs += 3600; } #endif /* add in secs to make date based on Apple Jan 1, 1904 instead of */ /* Unix's Jan 1, 1970 */ /* So add in 66 years and 17 leap year days (1904 is a leap year) */ secs += ((66*365) + 17) * (24*3600); g_clk_cur_time = (word32)secs; clk_printf("Update g_clk_cur_time to %08x\n", g_clk_cur_time); g_clk_next_vbl_update = g_vbl_count + 5; } /* clock_update called by sim65816 every VBL */ void clock_update() { /* Nothing to do */ } void clock_update_if_needed() { int diff; diff = g_clk_next_vbl_update - g_vbl_count; if(diff < 0 || diff > 60) { /* Been a while, re-read the clock */ update_cur_time(); } } void clock_write_c034(word32 val) { g_c034_val = val & 0x7f; if((val & 0x80) != 0) { if((val & 0x20) == 0) { printf("c034 write not last = 1\n"); /* set_halt(1); */ } do_clock_data(); } } void do_clock_data() { word32 mask, read, op; clk_printf("In do_clock_data, g_clk_mode: %02x\n", g_clk_mode); read = g_c034_val & 0x40; switch(g_clk_mode) { case CLK_IDLE: g_clk_read = (g_c033_data >> 7) & 1; g_clk_reg1 = (g_c033_data >> 2) & 3; op = (g_c033_data >> 4) & 7; if(!read) { /* write */ switch(op) { case 0x0: /* Read/write seconds register */ g_clk_mode = CLK_TIME; clock_update_if_needed(); break; case 0x3: /* internal registers */ g_clk_mode = CLK_INTERNAL; if(g_clk_reg1 & 0x2) { /* extend BRAM read */ g_clk_mode = CLK_BRAM2; g_clk_reg1 = (g_c033_data & 7) << 5; } break; case 0x2: /* read/write ram 0x10-0x13 */ g_clk_mode = CLK_BRAM1; g_clk_reg1 += 0x10; break; case 0x4: /* read/write ram 0x00-0x0f */ case 0x5: case 0x6: case 0x7: g_clk_mode = CLK_BRAM1; g_clk_reg1 = (g_c033_data >> 2) & 0xf; break; default: halt_printf("Bad c033_data in CLK_IDLE: %02x\n", g_c033_data); } } else { printf("clk read from IDLE mode!\n"); /* set_halt(1); */ g_clk_mode = CLK_IDLE; } break; case CLK_BRAM2: if(!read) { /* get more bits of bram addr */ if((g_c033_data & 0x83) == 0x00) { /* more address bits */ g_clk_reg1 |= ((g_c033_data >> 2) & 0x1f); g_clk_mode = CLK_BRAM1; } else { halt_printf("CLK_BRAM2: c033_data: %02x!\n", g_c033_data); g_clk_mode = CLK_IDLE; } } else { halt_printf("CLK_BRAM2: clock read!\n"); g_clk_mode = CLK_IDLE; } break; case CLK_BRAM1: /* access battery ram addr g_clk_reg1 */ if(read) { if(g_clk_read) { /* Yup, read */ g_c033_data = g_bram_ptr[g_clk_reg1]; clk_printf("Reading BRAM loc %02x: %02x\n", g_clk_reg1, g_c033_data); } else { halt_printf("CLK_BRAM1: said wr, now read\n"); } } else { if(g_clk_read) { halt_printf("CLK_BRAM1: said rd, now write\n"); } else { /* Yup, write */ clk_printf("Writing BRAM loc %02x with %02x\n", g_clk_reg1, g_c033_data); g_bram_ptr[g_clk_reg1] = g_c033_data; g_config_kegs_update_needed = 1; } } g_clk_mode = CLK_IDLE; break; case CLK_TIME: if(read) { if(g_clk_read == 0) { halt_printf("Reading time, but in set mode!\n"); } g_c033_data = (g_clk_cur_time >> (g_clk_reg1 * 8)) & 0xff; clk_printf("Returning time byte %d: %02x\n", g_clk_reg1, g_c033_data); } else { /* Write */ if(g_clk_read) { halt_printf("Write time, but in read mode!\n"); } clk_printf("Writing TIME loc %d with %02x\n", g_clk_reg1, g_c033_data); mask = 0xff << (8 * g_clk_reg1); g_clk_cur_time = (g_clk_cur_time & (~mask)) | ((g_c033_data & 0xff) << (8 * g_clk_reg1)); } g_clk_mode = CLK_IDLE; break; case CLK_INTERNAL: if(read) { printf("Attempting to read internal reg %02x!\n", g_clk_reg1); } else { switch(g_clk_reg1) { case 0x0: /* test register */ if(g_c033_data & 0xc0) { printf("Writing test reg: %02x!\n", g_c033_data); /* set_halt(1); */ } break; case 0x1: /* write protect reg */ clk_printf("Writing clk wr_protect: %02x\n", g_c033_data); if(g_c033_data & 0x80) { printf("Stop, wr clk wr_prot: %02x\n", g_c033_data); /* set_halt(1); */ } break; default: halt_printf("Writing int reg: %02x with %02x\n", g_clk_reg1, g_c033_data); } } g_clk_mode = CLK_IDLE; break; default: halt_printf("clk mode: %d unknown!\n", g_clk_mode); g_clk_mode = CLK_IDLE; break; } } ================================================ FILE: upstream/kegs/src/comp_swift ================================================ #!/bin/bash # $KmKId: comp_swift,v 1.2 2020-12-11 22:58:32+00 kentd Exp $ echo "args are: " "$@" /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c \ -enable-objc-interop \ -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \ -swift-version 4 -Onone \ -serialize-debugging-options \ -import-objc-header Kegs-Bridging-Header.h \ -module-name Kegs \ "$@" ================================================ FILE: upstream/kegs/src/compile_time.c ================================================ const char rcsid_compile_time_c[] = "@(#)$KmKId: compile_time.c,v 1.2 2002-11-14 06:02:44+00 kadickey Exp $"; char g_compile_time[] = "Compiled: " __DATE__ " " __TIME__ ; ================================================ FILE: upstream/kegs/src/config.c ================================================ const char rcsid_config_c[] = "@(#)$KmKId: config.c,v 1.169 2025-01-11 23:42:49+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // g_cfg_slotdrive: 0: not doing file selection at all // 1-0x7ff: doing file selection for given slot/drive // 0xfff: doing file selection for ROM or charrom #include "defc.h" #include #include "config.h" #ifdef _WIN32 # include "win_dirent.h" #else # include #endif extern int Verbose; extern word32 g_vbl_count; extern int g_track_bytes_35[]; extern int g_c031_disk35; extern int g_cur_a2_stat; extern byte *g_slow_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern double g_cur_dcycs; extern int g_rom_version; extern word32 g_adb_repeat_vbl; extern int g_adb_swap_command_option; extern int g_limit_speed; extern int g_zip_speed_mhz; extern int g_force_depth; int g_serial_cfg[2] = { 0, 1 }; // Slot 1=0=Real serial (printer?) // Slot 2=1=Virt Modem int g_serial_mask[2] = { 0, 0 }; char *g_serial_remote_ip[2] = { "", "" }; // cfg_init_menus will malloc() int g_serial_remote_port[2] = { 9100, 9100 }; char *g_serial_device[2] = { "/dev/tty.USB.0", "/dev/tty.USB.1" }; // cfg_init_menus() will malloc() the above int g_serial_win_device[2] = { 0, 0 }; // Disabled int g_serial_modem_response_code = 10; // 10 - 2400 int g_serial_modem_allow_incoming = 0; // 1 for BBS'es int g_serial_modem_init_telnet = 1; // 1 for BBS'es extern word32 g_mem_size_base; extern word32 g_mem_size_exp; extern int g_video_line_update_interval; extern int g_user_halt_bad; extern int g_joystick_type; extern int g_joystick_scale_factor_x; extern int g_joystick_scale_factor_y; extern int g_joystick_trim_amount_x; extern int g_joystick_trim_amount_y; extern int g_swap_paddles; extern int g_invert_paddles; extern int g_voc_enable; extern int g_status_enable; extern int g_mainwin_width; extern int g_mainwin_height; extern int g_mainwin_xpos; extern int g_mainwin_ypos; extern int g_screen_index[]; extern word32 g_full_refresh_needed; extern word32 g_a2_screen_buffer_changed; extern int g_key_down; extern const char g_kegs_version_str[]; int g_config_control_panel = 0; char g_config_kegs_name[1024] = { 0 }; char g_cfg_cwd_str[CFG_PATH_MAX] = { 0 }; int g_config_kegs_auto_update = 1; int g_config_kegs_update_needed = 0; int g_cfg_newdisk_select = 0; int g_cfg_newdisk_blocks = 0; int g_cfg_newdisk_blocks_default = 140*2; int g_cfg_newdisk_type = 1; int g_cfg_newdisk_type_default = 1; word32 g_cfg_newdisk_slotdrive = 0; const char *g_config_kegs_name_list[] = { "config.kegs", "kegs_conf", ".config.kegs", 0 }; int g_highest_smartport_unit = -1; int g_reparse_delay = 0; int g_user_page2_shadow = 1; char g_cfg_printf_buf[CFG_PRINTF_BUFSIZE]; char g_config_kegs_buf[CONF_BUF_LEN]; #define CFG_ERR_BUFSIZE 80 #define CFG_ERR_MAX 5 int g_cfg_err_pos = 0; char g_cfg_err_bufs[CFG_ERR_MAX][CFG_ERR_BUFSIZE]; int g_cfg_curs_x = 0; int g_cfg_curs_y = 0; int g_cfg_curs_inv = 0; int g_cfg_curs_mousetext = 0; int g_cfg_screen_changed = 0; byte g_cfg_screen[24][80]; #if defined(MAC) || defined(_WIN32) int g_cfg_ignorecase = 1; // Ignore case in filenames #else int g_cfg_ignorecase = 0; #endif #define CFG_MAX_OPTS 34 #define CFG_OPT_MAXSTR 100 int g_cfg_opts_vals[CFG_MAX_OPTS]; char g_cfg_opts_str[CFG_PATH_MAX]; char g_cfg_opt_buf[CFG_OPT_MAXSTR]; char g_cfg_edit_buf[CFG_OPT_MAXSTR]; char *g_cfg_rom_path = "ROM"; // config_init_menus will malloc char *g_cfg_charrom_path = "Undefined"; // config_init_menus will malloc int g_cfg_charrom_pos = 0; char *g_cfg_file_def_name = "Undefined"; char **g_cfg_file_strptr = 0; int g_cfg_file_min_size = 1024; int g_cfg_file_max_size = 2047*1024*1024; int g_cfg_edit_type = 0; void *g_cfg_edit_ptr = 0; #define MAX_PARTITION_BLK_SIZE 65536 char *g_argv0_path = "."; const char *g_kegs_default_paths[] = { "", "./", "${HOME}/", "${HOME}/Library/KEGS/", "${0}/../", "${0}/", "${0}/Contents/Resources/", 0 }; extern Cfg_menu g_cfg_main_menu[]; #define KNMP(a) &a, #a, 0 #define KNM(a) &a, #a Cfg_menu g_cfg_disk_menu[] = { { "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { "s5d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x5000 }, { "s5d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x5010 }, { "", 0, 0, 0, 0 }, { "s6d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x6000 }, { "s6d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x6010 }, { "", 0, 0, 0, 0 }, { "s7d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x7000 }, { "s7d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x7010 }, { "s7d3 = ", 0, 0, 0, CFGTYPE_DISK + 0x7020 }, { "s7d4 = ", 0, 0, 0, CFGTYPE_DISK + 0x7030 }, { "s7d5 = ", 0, 0, 0, CFGTYPE_DISK + 0x7040 }, { "s7d6 = ", 0, 0, 0, CFGTYPE_DISK + 0x7050 }, { "s7d7 = ", 0, 0, 0, CFGTYPE_DISK + 0x7060 }, { "s7d8 = ", 0, 0, 0, CFGTYPE_DISK + 0x7070 }, { "s7d9 = ", 0, 0, 0, CFGTYPE_DISK + 0x7080 }, { "s7d10= ", 0, 0, 0, CFGTYPE_DISK + 0x7090 }, { "s7d11= ", 0, 0, 0, CFGTYPE_DISK + 0x70a0 }, { "s7d12= ", 0, 0, 0, CFGTYPE_DISK + 0x70b0 }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot6_menu[] = { { "New 5.25\" disk image Configuration", g_cfg_newslot6_menu, 0, 0, CFGTYPE_MENU }, { "size,280,140KB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS/DOS 3.3,2,WOZ image,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot5_menu[] = { { "New 3.5\" disk image Configuration", g_cfg_newslot5_menu, 0, 0,CFGTYPE_MENU}, { "size,1600,800KB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS,2,WOZ image,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_newslot7_menu[] = { { "New Smartport disk image Configuration", g_cfg_newslot7_menu, 0, 0, CFGTYPE_MENU}, { "size,1600,800KB,3200,1600KB,16384,8MB,32768,16MB,65535,32MB", KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, { "Type,1,ProDOS,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), &g_cfg_newdisk_type_default, CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_joystick_menu[] = { { "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, { "Joystick Emulation,0,Keypad Joystick,1,Mouse Joystick,2,Native Joystick 1," "3,Native Joystick 2", KNMP(g_joystick_type), CFGTYPE_INT }, { "Joystick Scale X,0x100,Standard,0x119,+10%,0x133,+20%," "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", KNMP(g_joystick_scale_factor_x), CFGTYPE_INT }, { "Joystick Scale Y,0x100,Standard,0x119,+10%,0x133,+20%," "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", KNMP(g_joystick_scale_factor_y), CFGTYPE_INT }, { "Joystick Trim X", KNMP(g_joystick_trim_amount_x), CFGTYPE_INT }, { "Joystick Trim Y", KNMP(g_joystick_trim_amount_y), CFGTYPE_INT }, { "Swap Joystick X and Y,0,Normal operation,1,Paddle 1 and Paddle 0 swapped", KNMP(g_swap_paddles), CFGTYPE_INT }, { "Invert Joystick,0,Normal operation,1,Left becomes right and up becomes down", KNMP(g_invert_paddles), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_rom_menu[] = { { "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, { "ROM File", KNMP(g_cfg_rom_path), CFGTYPE_FILE }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_charrom_menu[] = { { "Character ROM File Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, { "Character ROM File", KNMP(g_cfg_charrom_path), CFGTYPE_FILE }, { "Character Set,0,0x00 US Enhanced,1,0x01 US Un-enhanced," "2,0x02 Clinton Turner V1 Enhanced,3,0x03 ReActiveMicro Enhanced," "4,0x04 Dan Paymar Enhanced,5,0x05 Blippo Black Enhanced," "6,0x06 Byte Enhanced,7,0x07 Colossal Enhanced," "8,0x08 Count Enhanced,9,0x09 Flow Enhanced," "10,0x0a Gothic Enhanced,11,0x0b Outline Enhanced," "12,0x0c Pigfont Enhanced,13,0x0d Pinocchio Enhanced," "14,0x0e Slant Enhanced,15,0x0f Stop Enhanced," "16,0x10 Euro Un-Enhanced,17,0x11 Euro Enhanced," "18,0x12 Clinton Turner V2 Enhanced,19,0x13 Improved German Enhanced," "20,0x14 Improved German Un-Enhanced,21,0x15 Franch Canadian Enhanced," "22,0x16 French Canadian Un-Enhanced,23,0x17 Hebrew Enhanced," "24,0x18 Hebrew Un-Enhanced,25,0x19 Apple II+ Enhanced," "26,0x1a Apple II+ Un-Enhanced,27,0x1b Katakana Enhanced," "28,0x1c Cyrillic Enhanced,29,0x1d Greek Enhanced," "30,0x1e Esperanto Enhanced,31,0x1f Videx Enhanced", KNMP(g_cfg_charrom_pos), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_serial_menu[] = { { "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, { "Slot 1 (port 0) settings", 0, 0, 0, 0 }, { " Main setting ,0,Use Real Device below,1,Use a virtual modem," "2,Use Remote IP below,3,Use incoming port 6501", KNMP(g_serial_cfg[0]), CFGTYPE_INT }, { " Status ", (void *)cfg_get_serial0_status, 0, 0, CFGTYPE_FUNC }, { " Real Device ", KNMP(g_serial_device[0]), CFGTYPE_FILE }, { " Windows Device,0,Disabled,1,COM1,2,COM2,3,COM3,4,COM4", KNMP(g_serial_win_device[0]), CFGTYPE_INT }, { " Remote IP ", KNMP(g_serial_remote_ip[0]), CFGTYPE_STR }, { " Remote Port ", KNMP(g_serial_remote_port[0]), CFGTYPE_INT }, { " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", KNMP(g_serial_mask[0]), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Slot 2 (port 1) settings", 0, 0, 0, 0, }, { " Main setting ,0,Use Real Device below,1,Use a virtual modem," "2,Use Remote IP below,3,Use incoming port 6502", KNMP(g_serial_cfg[1]), CFGTYPE_INT }, { " Status ", (void *)cfg_get_serial1_status, 0, 0, CFGTYPE_FUNC }, { " Real Device ", KNMP(g_serial_device[1]), CFGTYPE_FILE }, { " Windows Device,1,COM1,2,COM2,3,COM3,4,COM4", KNMP(g_serial_win_device[1]), CFGTYPE_INT }, { " Remote IP ", KNMP(g_serial_remote_ip[1]), CFGTYPE_STR }, { " Remote Port ", KNMP(g_serial_remote_port[1]), CFGTYPE_INT }, { " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", KNMP(g_serial_mask[1]), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_modem_menu[] = { { "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, { "Modem Speed Response Code ,5,5 - CONNECT 1200,10,10 - CONNECT 2400," "12,12 - CONNECT 9600 (HAYES/Warp6)," "13,13 - CONNECT 9600 (USR/HST)," "14,14 - CONNECT 19200 (HAYES/Warp6)," "28,28 - CONNECT 38400 (HAYES/Warp6)", KNMP(g_serial_modem_response_code), CFGTYPE_INT }, { "Allow Modem incoming on 6501/6502 ,0,Outgoing only," "1,Incoming and outgoing (BBS)", KNMP(g_serial_modem_allow_incoming), CFGTYPE_INT }, { "Send Telnet Escape codes ,0,Disable Telnet,1,Send Telnet codes", KNMP(g_serial_modem_init_telnet), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_video_menu[] = { { "Force X-windows display depth", KNMP(g_force_depth), CFGTYPE_INT }, { "Enable VOC,0,Disabled,1,Enabled", KNMP(g_voc_enable), CFGTYPE_INT }, { "Default Main Window width", KNMP(g_mainwin_width), CFGTYPE_INT }, { "Default Main Window height", KNMP(g_mainwin_height), CFGTYPE_INT }, { "Main Window X position", KNMP(g_mainwin_xpos), CFGTYPE_INT }, { "Main Window Y position", KNMP(g_mainwin_ypos), CFGTYPE_INT }, { "3200 Color Enable,0,Auto (Full if fast enough),1,Full (Update every line)," "8,Off (Update video every 8 lines)", KNMP(g_video_line_update_interval), CFGTYPE_INT }, { "Dump text screen to file", (void *)cfg_text_screen_dump, 0, 0, CFGTYPE_FUNC}, { "", 0, 0, 0, 0 }, { "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { 0, 0, 0, 0, 0 }, }; Cfg_menu g_cfg_main_menu[] = { { "KEGS Configuration", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, { "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, { "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, { "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, { "Character ROM Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, { "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, { "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, { "Video Settings", g_cfg_video_menu, 0, 0, CFGTYPE_MENU }, { "Auto-update config.kegs,0,Manual,1,Immediately", KNMP(g_config_kegs_auto_update), CFGTYPE_INT }, { "Speed,0,Unlimited,1,1.0MHz,2,2.8MHz,3,8.0MHz (Zip)", KNMP(g_limit_speed), CFGTYPE_INT }, { "ZipGS Speed,8,8MHz,16,16MHz,32,32MHz,64,64MHz,128,128MHz", KNMP(g_zip_speed_mhz), CFGTYPE_INT }, { "Expansion Mem Size,0,0MB,0x100000,1MB,0x200000,2MB,0x300000,3MB," "0x400000,4MB,0x600000,6MB,0x800000,8MB,0xa00000,10MB,0xc00000,12MB," "0xe00000,14MB", KNMP(g_mem_size_exp), CFGTYPE_INT }, { "Show Status lines,0,Disabled,1,Enabled", KNMP(g_status_enable), CFGTYPE_INT}, { "Code Red Halts,0,Do not stop on bad accesses,1,Enter debugger on bad " "accesses", KNMP(g_user_halt_bad), CFGTYPE_INT }, { "Enable Text Page 2 Shadow,0,Disabled on ROM 01 (matches real hardware)," "1,Enabled on ROM 01 and 03", KNMP(g_user_page2_shadow), CFGTYPE_INT }, { "Swap Command/Option keys,0,Disabled,1,Swapped", KNMP(g_adb_swap_command_option), CFGTYPE_INT }, { "", 0, 0, 0, 0 }, { "Save changes to config.kegs", (void *)config_write_config_kegs_file, 0, 0, CFGTYPE_FUNC }, { "", 0, 0, 0, 0 }, { "Exit Config (or press F4)", (void *)cfg_exit, 0, 0, CFGTYPE_FUNC }, { 0, 0, 0, 0, 0 }, }; #define CFG_MAX_DEFVALS 128 Cfg_defval g_cfg_defvals[CFG_MAX_DEFVALS]; int g_cfg_defval_index = 0; word32 g_cfg_slotdrive = 0; int g_cfg_select_partition = -1; char g_cfg_tmp_path[CFG_PATH_MAX]; char g_cfg_file_path[CFG_PATH_MAX]; char g_cfg_file_cachedpath[CFG_PATH_MAX]; char g_cfg_file_cachedreal[CFG_PATH_MAX]; char g_cfg_file_curpath[CFG_PATH_MAX]; char g_cfg_file_shortened[CFG_PATH_MAX]; char g_cfg_file_match[CFG_PATH_MAX]; char g_cfg_part_path[CFG_PATH_MAX]; int g_cfg_partition_is_zip = 0; Cfg_listhdr g_cfg_dirlist = { 0 }; Cfg_listhdr g_cfg_partitionlist = { 0 }; int g_cfg_file_pathfield = 0; const char *g_kegs_rom_names[] = { "ROM", "ROM", "ROM.01", "ROM.03", "APPLE2GS.ROM", "APPLE2GS.ROM2", "xgs.rom", "XGS.ROM", "Rom03gd", "342-0077-b", // MAME ROM.01 0 }; /* First entry is special--it will be overwritten by g_cfg_rom_path */ const char *g_kegs_c1rom_names[] = { 0 }; const char *g_kegs_c2rom_names[] = { 0 }; const char *g_kegs_c3rom_names[] = { 0 }; const char *g_kegs_c4rom_names[] = { 0 }; const char *g_kegs_c5rom_names[] = { 0 }; const char *g_kegs_c6rom_names[] = { "c600.rom", "controller.rom", "disk.rom", "DISK.ROM", "diskII.prom", 0 }; const char *g_kegs_c7rom_names[] = { 0 }; const char **g_kegs_rom_card_list[8] = { 0, g_kegs_c1rom_names, g_kegs_c2rom_names, g_kegs_c3rom_names, g_kegs_c4rom_names, g_kegs_c5rom_names, g_kegs_c6rom_names, g_kegs_c7rom_names }; byte g_rom_c600_rom01_diffs[256] = { 0x00, 0x00, 0x00, 0x00, 0xc6, 0x00, 0xe2, 0x00, 0xd0, 0x50, 0x0f, 0x77, 0x5b, 0xb9, 0xc3, 0xb1, 0xb1, 0xf8, 0xcb, 0x4e, 0xb8, 0x60, 0xc7, 0x2e, 0xfc, 0xe0, 0xbf, 0x1f, 0x66, 0x37, 0x4a, 0x70, 0x55, 0x2c, 0x3c, 0xfc, 0xc2, 0xa5, 0x08, 0x29, 0xac, 0x21, 0xcc, 0x09, 0x55, 0x03, 0x17, 0x35, 0x4e, 0xe2, 0x0c, 0xe9, 0x3f, 0x9d, 0xc2, 0x06, 0x18, 0x88, 0x0d, 0x58, 0x57, 0x6d, 0x83, 0x8c, 0x22, 0xd3, 0x4f, 0x0a, 0xe5, 0xb7, 0x9f, 0x7d, 0x2c, 0x3e, 0xae, 0x7f, 0x24, 0x78, 0xfd, 0xd0, 0xb5, 0xd6, 0xe5, 0x26, 0x85, 0x3d, 0x8d, 0xc9, 0x79, 0x0c, 0x75, 0xec, 0x98, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x6c, 0x44, 0xce, 0x4c, 0x01, 0x08, 0x00, 0x00 }; byte g_rom_c700[256] = { //0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x3c, // For Apple //e 0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x00, //^^= LDX #$20; LDY #$00, LDX #$03 CMP #$3c 0x80, 0x0c, 0x18, 0xb8, 0x70, 0x38, 0xb8, 0x42, //^^= BRA $c716; CLC; CLV; BVS $c746 (SEC); CLV; WDM $c7,$00 0xc7, 0x00, 0x60, 0x00, 0x00, 0xea, 0xe2, 0x41, //^^= ...; RTS..............; NOP; SEP #$41 0x70, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //^^= BVS $c70f 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // So does WDM $c7,$00 with psr.v=1 for $c700; v=0,c=0 for $c70a, // and v=0,c=1 for $c70d 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xbf, 0x0a }; Cfg_menu *g_menuptr = 0; int g_menu_line = 1; int g_menu_inc = 1; int g_menu_max_line = 1; int g_menu_redraw_needed = 1; #define MAX_CFG_ARGV_OVERRIDES 64 int g_cfg_argv_num_overrides = 0; char *g_cfg_argv_overrides[MAX_CFG_ARGV_OVERRIDES]; int config_add_argv_override(const char *str1, const char *str2) { const char *equal_ptr; char *str; int ret, pos, len; // Handle things like "rom=rompath" and "rom", "rompath" // Look through str1, see if there is '=', if so ignore str2 equal_ptr = strchr(str1, '='); ret = 1; if(equal_ptr) { // str1 has '=' in it ret = 0; // Don't eat up str2 argument str = kegs_malloc_str(str1); } else { // We need to form a new string of str1, =, str2 len = (int)(strlen(str1) + strlen(str2) + 2); str = malloc(len); cfg_strncpy(str, str1, len); cfg_strlcat(str, "=", len); cfg_strlcat(str, str2, len); } pos = g_cfg_argv_num_overrides++; if(pos >= MAX_CFG_ARGV_OVERRIDES) { g_cfg_argv_num_overrides = MAX_CFG_ARGV_OVERRIDES; fatal_printf("MAX_CFG_ARGV_OVERRIDES overflow\n"); my_exit(5); return ret; } g_cfg_argv_overrides[pos] = str; printf("Added config override %d, %s\n", pos, str); return ret; } void config_set_config_kegs_name(const char *str1) { int maxlen; // Command line override "-cfg cfg_file" g_config_kegs_name[0] = 0; maxlen = (int)sizeof(g_config_kegs_name); cfg_strncpy(&g_config_kegs_name[0], str1, maxlen); } void config_init_menus(Cfg_menu *menuptr) { void *voidptr; const char *name_str; Cfg_defval *defptr; char **str_ptr; char *str; int type, pos, val; if(menuptr[0].defptr != 0) { return; } menuptr[0].defptr = (void *)1; pos = 0; while(pos < 100) { type = menuptr->cfgtype; voidptr = menuptr->ptr; name_str = menuptr->name_str; if(menuptr->str == 0) { break; } if(name_str != 0) { defptr = &(g_cfg_defvals[g_cfg_defval_index++]); if(g_cfg_defval_index >= CFG_MAX_DEFVALS) { fatal_printf("CFG_MAX_DEFVAL overflow\n"); my_exit(5); return; } defptr->menuptr = menuptr; defptr->intval = 0; defptr->strval = 0; switch(type) { case CFGTYPE_INT: val = *((int *)voidptr); defptr->intval = val; menuptr->defptr = &(defptr->intval); break; case CFGTYPE_FILE: case CFGTYPE_STR: str_ptr = (char **)menuptr->ptr; str = *str_ptr; // We need to malloc this string since all // string values must be dynamically alloced defptr->strval = str; // this can have a copy *str_ptr = kegs_malloc_str(str); menuptr->defptr = &(defptr->strval); break; default: fatal_printf("name_str is %p = %s, but type: " "%d\n", name_str, name_str, type); my_exit(5); return; } } if(type == CFGTYPE_MENU) { config_init_menus((Cfg_menu *)voidptr); } pos++; menuptr++; } } void config_init() { config_init_menus(g_cfg_main_menu); // Find the config.kegs file if(g_config_kegs_name[0] == 0) { cfg_find_config_kegs_file(); } config_parse_config_kegs_file(); } void cfg_find_config_kegs_file() { const char **path_ptr; int maxlen, fd; g_config_kegs_name[0] = 0; maxlen = sizeof(g_config_kegs_name); fd = 0; if(!config_setup_kegs_file(&g_config_kegs_name[0], maxlen, &g_config_kegs_name_list[0])) { // Try to create config.kegs fd = -1; path_ptr = &g_kegs_default_paths[0]; while(*path_ptr) { config_expand_path(&g_config_kegs_name[0], *path_ptr, maxlen); cfg_strlcat(&g_config_kegs_name[0], "config.kegs", maxlen); printf("Trying to create %s\n", &g_config_kegs_name[0]); fd = open(&g_config_kegs_name[0], O_CREAT | O_TRUNC | O_WRONLY, 0x1b6); close(fd); if(fd >= 0) { break; } path_ptr++; } } if(fd < 0) { fatal_printf("Could not create config.kegs!\n"); my_exit(2); } } int config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr) { struct stat stat_buf; const char **path_ptr, **cur_name_ptr; mode_t fmt; int ret, len; outname[0] = 0; path_ptr = &g_kegs_default_paths[0]; // Array of strings while(*path_ptr) { len = config_expand_path(outname, *path_ptr, maxlen); if(len != (int)strlen(outname)) { printf("config_expand_path ret %d, but strlen:%d!\n", len, (int)strlen(outname)); } cur_name_ptr = name_ptr; while(*cur_name_ptr && (len < maxlen)) { outname[len] = 0; cfg_strlcat(outname, *cur_name_ptr, maxlen); // printf("Doing stat on %s\n", outname); ret = cfg_stat(outname, &stat_buf, 0); if(ret == 0) { fmt = stat_buf.st_mode & S_IFMT; if(fmt != S_IFDIR) { /* got it! */ return 1; } } cur_name_ptr++; } path_ptr++; } return 0; } int config_expand_path(char *out_ptr, const char *in_ptr, int maxlen) { char name_buf[256]; char *tmp_ptr; int name_len, in_char, state, pos; out_ptr[0] = 0; pos = 0; name_len = 0; state = 0; /* See if in_ptr has ${} notation, replace with getenv or argv0 */ while(pos < (maxlen - 1)) { in_char = *in_ptr++; out_ptr[pos++] = in_char; out_ptr[pos] = 0; if(in_char == 0) { return pos - 1; } if(state == 0) { /* No $ seen yet, look for it */ if(in_char == '$') { state = 1; } } else if(state == 1) { /* See if next char is '{' (dummy }) */ if(in_char == '{') { /* add dummy } */ state = 2; name_len = 0; pos -= 2; out_ptr[pos] = 0; } else { state = 0; } } else if(state == 2) { /* fill name_buf ... dummy '{' */ pos--; out_ptr[pos] = 0; if(in_char == '}') { name_buf[name_len] = 0; /* got token, now look it up */ tmp_ptr = ""; if(!strncmp("0", name_buf, 128)) { /* Replace ${0} with g_argv0_path */ tmp_ptr = g_argv0_path; } else { tmp_ptr = getenv(name_buf); if(tmp_ptr == 0) { tmp_ptr = ""; } } pos = cfg_strlcat(out_ptr, tmp_ptr, maxlen); state = 0; } else { name_buf[name_len++] = in_char; if(name_len >= 250) { name_len--; } } } } return pos; } char * cfg_exit(int get_status) { /* printf("In cfg exit\n"); */ if(get_status) { return 0; } cfg_toggle_config_panel(); return 0; } void cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap) { char *bufptr; int pos, len, bufsize; pos = g_cfg_err_pos; if(pos >= CFG_ERR_MAX) { pos = CFG_ERR_MAX - 1; } bufptr = &g_cfg_err_bufs[pos][0]; len = 0; bufsize = CFG_ERR_BUFSIZE; if(pre_str && *pre_str) { cfg_strncpy(bufptr, pre_str, CFG_ERR_BUFSIZE); cfg_strlcat(bufptr, " error: ", CFG_ERR_BUFSIZE); len = (int)strlen(bufptr); bufsize = CFG_ERR_BUFSIZE - len; } if(bufsize > 0) { vsnprintf(&bufptr[len], bufsize, fmt, ap); } fputs(bufptr, stderr); g_cfg_err_pos = pos + 1; } void cfg_err_printf(const char *pre_str, const char *fmt, ...) { va_list ap; va_start(ap, fmt); cfg_err_vprintf(pre_str, fmt, ap); va_end(ap); } void cfg_toggle_config_panel() { int panel; panel = !g_config_control_panel; if(g_rom_version < 0) { panel = 1; /* Stay in config mode */ } if(panel != g_config_control_panel) { cfg_set_config_panel(panel); } } void cfg_set_config_panel(int panel) { int i; g_config_control_panel = panel; if(panel) { // Entering configuration panel video_force_reparse(); cfg_printf("Entering config_control_panel\n"); for(i = 0; i < 20; i++) { // Toss any queued-up keypresses if(adb_read_c000() & 0x80) { (void)adb_access_c010(); } } // HACK: Force adb keyboard (and probably mouse) to "normal"... cfg_home(); g_menu_line = 1; g_menu_inc = 1; g_menu_redraw_needed = 1; g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; g_cfg_select_partition = -1; } else { // Leave config panel, go back to A2 emulation video_force_reparse(); } g_full_refresh_needed = -1; g_a2_screen_buffer_changed = -1; g_adb_repeat_vbl = g_vbl_count + 60; } char * cfg_text_screen_dump(int get_status) { FILE *ofile; char *bufptr; char *filename; if(get_status) { return 0; } filename = "kegs.screen.dump"; printf("Writing text screen to the file %s\n", filename); ofile = fopen(filename, "w"); if(ofile == 0) { fatal_printf("Could not write to file %s, (%d)\n", filename, errno); return 0; } bufptr = cfg_text_screen_str(); fputs(bufptr, ofile); fclose(ofile); return 0; } char g_text_screen_buf[2100] = { 0 }; char * cfg_text_screen_str() { char *bufptr; int pos, start_pos, c, offset; int i, j; // bufptr must be at least (81*24)+2 characters bufptr = &g_text_screen_buf[0]; pos = 0; for(i = 0; i < 24; i++) { start_pos = pos; for(j = 0; j < 40; j++) { offset = g_screen_index[i] + j; if(g_cur_a2_stat & ALL_STAT_VID80) { c = g_slow_memory_ptr[0x10400 + offset] & 0x7f; if(c < 0x20) { c += 0x40; } bufptr[pos++] = c; } c = g_slow_memory_ptr[0x0400 + offset] & 0x7f; if(c < 0x20) { c += 0x40; } if(c == 0x7f) { c = ' '; } bufptr[pos++] = c; } while((pos > start_pos) && (bufptr[pos-1] == ' ')) { /* try to strip out trailing spaces */ pos--; } bufptr[pos++] = '\n'; bufptr[pos] = 0; } return bufptr; } char * cfg_get_serial0_status(int get_status) { return scc_get_serial_status(get_status, 0); } char * cfg_get_serial1_status(int get_status) { return scc_get_serial_status(get_status, 1); } char * cfg_get_current_copy_selection() { return &g_text_screen_buf[0]; } void config_vbl_update(int doit_3_persec) { if(doit_3_persec) { if(g_config_kegs_auto_update && g_config_kegs_update_needed) { (void)config_write_config_kegs_file(0); } } return; } void cfg_file_update_rom(const char *str) { cfg_file_update_ptr(&g_cfg_rom_path, str, 1); } void cfg_file_update_ptr(char **strptr, const char *str, int need_update) { char *newstr; int remote_changed, serial_changed; int i; // Update whatever g_cfg_file_strptr points to. If changing // ROM path or Charrom, then do the proper updates newstr = kegs_malloc_str(str); if(!strptr) { return; } if(*strptr) { free(*strptr); } *strptr = newstr; if(strptr == &(g_cfg_rom_path)) { printf("Updated ROM file\n"); load_roms_init_memory(); do_reset(); } if(strptr == &(g_cfg_charrom_path)) { printf("Updated Char ROM file\n"); cfg_load_charrom(); } for(i = 0; i < 2; i++) { remote_changed = 0; serial_changed = 0; if(strptr == &g_serial_remote_ip[i]) { remote_changed = 1; } if(strptr == &g_serial_device[i]) { serial_changed = 1; } if(remote_changed || serial_changed) { scc_config_changed(i, 0, remote_changed, serial_changed); } } if(need_update) { g_config_kegs_update_needed = 1; printf("Set g_config_kegs_update_needed = 1\n"); } } void cfg_int_update(int *iptr, int new_val) { int old_val, cfg_changed, remote_changed, serial_changed; int i; // Called to handle an integer being changed in the F4 config menus // where it's value may need special handling old_val = *iptr; *iptr = new_val; if(old_val == new_val) { return; } if(iptr == &g_cfg_charrom_pos) { cfg_load_charrom(); } for(i = 0; i < 2; i++) { remote_changed = 0; serial_changed = 0; cfg_changed = 0; if(iptr == &g_serial_cfg[i]) { cfg_changed = 1; } if(iptr == &g_serial_remote_port[i]) { remote_changed = 1; } if(iptr == &g_serial_win_device[i]) { serial_changed = 1; } if(cfg_changed || remote_changed || serial_changed) { scc_config_changed(i, cfg_changed, remote_changed, serial_changed); } } } void cfg_load_charrom() { byte buffer[4096]; dword64 dsize, dret; word32 upos; int fd; printf("Loading character ROM from: %s\n", g_cfg_charrom_path); fd = open(g_cfg_charrom_path, O_RDONLY | O_BINARY); if(fd < 0) { printf("Cannot open %s\n", g_cfg_charrom_path); return; } dsize = cfg_get_fd_size(fd); upos = g_cfg_charrom_pos * 0x1000U; if(dsize < (upos + 0x1000)) { g_cfg_charrom_pos = 0; return; } dret = cfg_read_from_fd(fd, &buffer[0], upos, 4096); if(dret != 0) { prepare_a2_romx_font(&buffer[0]); } } void config_load_roms() { struct stat stat_buf; const char **names_ptr; int more_than_8mb, changed_rom, len, fd, ret; int i; g_rom_version = -1; /* set first entry of g_kegs_rom_names[] to g_cfg_rom_path so that */ /* it becomes the first place searched. */ g_kegs_rom_names[0] = g_cfg_rom_path; ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, &g_kegs_rom_names[0]); if(ret == 0) { // Just get out, let config interface select ROM g_config_control_panel = 1; printf("No ROM, set g_config_control_panel=1\n"); return; } printf("Found ROM at path: %s\n", g_cfg_tmp_path); fd = open(&g_cfg_tmp_path[0], O_RDONLY | O_BINARY); if(fd < 0) { fatal_printf("Open ROM file %s failed:%d, errno:%d\n", &g_cfg_tmp_path[0], fd, errno); g_config_control_panel = 1; return; } ret = fstat(fd, &stat_buf); if(ret != 0) { fatal_printf("fstat returned %d on fd %d, errno: %d\n", ret, fd, errno); g_config_control_panel = 1; return; } len = (int)stat_buf.st_size; memset(&g_rom_fc_ff_ptr[0], 0, 4*65536); /* Clear banks fc-ff to 0 */ if(len == 32*1024) { // Apple //e g_rom_version = 0; g_mem_size_base = 128*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[3*65536 + 32768], len); } else if(len == 128*1024) { g_rom_version = 1; g_mem_size_base = 128*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[2*65536], len); } else if(len == 256*1024) { g_rom_version = 3; g_mem_size_base = 1024*1024; ret = (int)read(fd, &g_rom_fc_ff_ptr[0], len); } else { fatal_printf("The ROM size should be 128K or 256K, this file " "is %d bytes\n", len); g_config_control_panel = 1; return; } printf("Read: %d bytes of ROM\n", ret); if(ret != len) { fatal_printf("errno: %d\n", errno); g_config_control_panel = 1; return; } close(fd); memset(&g_rom_cards_ptr[0], 0, 256*16); for(i = 0; i < 256; i++) { // Place HD PROM in slot 7 g_rom_cards_ptr[0x700 + i] = g_rom_c700[i]; // g_rom_cards_ptr[0x500 + i] = g_rom_c700[i]; } /* initialize c600 rom to be diffs from the real ROM, to build-in */ /* Apple II compatibility without distributing ROMs */ if(g_rom_version == 0) { // Apple //e for(i = 0; i < 256; i++) { // Place Disk II PROM in slot 6 g_rom_cards_ptr[0x600 + i] = g_rom_fc_ff_ptr[0x38600+i]; } } else { for(i = 0; i < 256; i++) { g_rom_cards_ptr[0x600 + i] = g_rom_fc_ff_ptr[0x3c600 + i] ^ g_rom_c600_rom01_diffs[i]; } } if(g_rom_version >= 3) { /* some patches */ g_rom_cards_ptr[0x61b] ^= 0x40; g_rom_cards_ptr[0x61c] ^= 0x33; g_rom_cards_ptr[0x632] ^= 0xc0; g_rom_cards_ptr[0x633] ^= 0x33; } for(i = 1; i < 8; i++) { names_ptr = g_kegs_rom_card_list[i]; if(names_ptr == 0) { continue; } if(*names_ptr == 0) { continue; } ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, names_ptr); if(ret != 0) { fd = open(&(g_cfg_tmp_path[0]), O_RDONLY | O_BINARY); if(fd < 0) { fatal_printf("Open card ROM file %s failed: %d " "err:%d\n", &g_cfg_tmp_path[0], fd, errno); continue; } len = 256; ret = (int)read(fd, &g_rom_cards_ptr[i*0x100], len); if(ret != len) { fatal_printf("While reading card ROM %s, file " "is too short. (%d) Expected %d bytes, " "read %d bytes\n", errno, len, ret); continue; } close(fd); } } more_than_8mb = (g_mem_size_exp > 0x800000); /* Only do the patch if users wants more than 8MB of expansion mem */ changed_rom = 0; if(g_rom_version == 1) { /* make some patches to ROM 01 */ #if 0 /* 1: Patch ROM selftest to not do speed test */ printf("Patching out speed test failures from ROM 01\n"); g_rom_fc_ff_ptr[0x3785a] = 0x18; changed_rom = 1; #endif #if 0 /* 2: Patch ROM selftests not to do tests 2,4 */ /* 0 = skip, 1 = do it, test 1 is bit 0 of LSByte */ g_rom_fc_ff_ptr[0x371e9] = 0xf5; g_rom_fc_ff_ptr[0x371ea] = 0xff; changed_rom = 1; #endif if(more_than_8mb) { /* Geoff Weiss patch to use up to 14MB of RAM */ g_rom_fc_ff_ptr[0x30302] = 0xdf; g_rom_fc_ff_ptr[0x30314] = 0xdf; g_rom_fc_ff_ptr[0x3031c] = 0x00; changed_rom = 1; } /* Patch ROM selftest to not do ROM cksum if any changes*/ if(changed_rom) { g_rom_fc_ff_ptr[0x37a06] = 0x18; g_rom_fc_ff_ptr[0x37a07] = 0x18; } } else if(g_rom_version == 3) { /* patch ROM 03 */ printf("Patching ROM 03 smartport bug\n"); /* 1: Patch Smartport code to fix a stupid bug */ /* that causes it to write the IWM status reg into c036, */ /* which is the system speed reg...it's "safe" since */ /* IWM status reg bit 4 must be 0 (7MHz)..., otherwise */ /* it might have turned on shadowing in all banks! */ g_rom_fc_ff_ptr[0x357c9] = 0x00; changed_rom = 1; #if 0 /* patch ROM 03 to not to speed test */ /* skip fast speed test */ g_rom_fc_ff_ptr[0x36ad7] = 0x18; g_rom_fc_ff_ptr[0x36ad8] = 0x18; changed_rom = 1; #endif #if 0 /* skip slow speed test */ g_rom_fc_ff_ptr[0x36ae7] = 0x18; g_rom_fc_ff_ptr[0x36ae8] = 0x6b; changed_rom = 1; #endif #if 0 /* 4: Patch ROM 03 selftests not to do tests 1-4 */ g_rom_fc_ff_ptr[0x364a9] = 0xf0; g_rom_fc_ff_ptr[0x364aa] = 0xff; changed_rom = 1; #endif /* ROM tests are in ff/6403-642x, where 6403 = addr of */ /* test 1, etc. */ if(more_than_8mb) { /* Geoff Weiss patch to use up to 14MB of RAM */ g_rom_fc_ff_ptr[0x30b] = 0xdf; g_rom_fc_ff_ptr[0x31d] = 0xdf; g_rom_fc_ff_ptr[0x325] = 0x00; changed_rom = 1; } if(changed_rom) { /* patch ROM 03 selftest to not do ROM cksum */ g_rom_fc_ff_ptr[0x36cb0] = 0x18; g_rom_fc_ff_ptr[0x36cb1] = 0x18; } } } void config_parse_config_kegs_file() { char *bufptr; const char *str; dword64 dsize; int fd, pos, start, size, last_c, line, ret, c, maxlen; int i; printf("Parsing config.kegs file: %s\n", g_config_kegs_name); clk_bram_zero(); g_highest_smartport_unit = -1; cfg_get_base_path(&g_cfg_cwd_str[0], g_config_kegs_name, 0); if(g_cfg_cwd_str[0] != 0) { ret = chdir(&g_cfg_cwd_str[0]); if(ret != 0) { printf("chdir to %s, errno:%d\n", g_cfg_cwd_str, errno); } // Do basename(g_config_kegs_name)--on it's own buffer str = cfg_str_basename(g_config_kegs_name); maxlen = sizeof(g_config_kegs_name); cfg_strncpy(&g_config_kegs_name[0], str, maxlen); } // In any case, copy the current directory path to g_cfg_cwd_str (void)!getcwd(&g_cfg_cwd_str[0], CFG_PATH_MAX); printf("CWD is now: %s\n", &g_cfg_cwd_str[0]); fd = open(g_config_kegs_name, O_RDONLY | O_BINARY); dsize = 0; if(fd >= 0) { dsize = cfg_get_fd_size(fd); } if((fd < 0) || (dsize >= (1 << 30))) { fatal_printf("cannot open config.kegs at %s, or it is too " "large! Stopping!\n", g_config_kegs_name); my_exit(3); return; } size = (int)dsize; bufptr = malloc(size + 2); ret = (int)cfg_read_from_fd(fd, (byte *)bufptr, 0, size); close(fd); if(ret != size) { free(bufptr); fatal_printf("Could not read config.kegs at %s\n", g_config_kegs_name); my_exit(3); return; } bufptr[size] = 0; // Ensure it's null terminated line = 0; pos = 0; last_c = 0; while(pos < size) { line++; if((bufptr[pos] == '\n') && (last_c == '\r')) { // CR,LF, just eat the LF pos++; } start = pos; while(pos < size) { c = bufptr[pos]; if((c == 0) || (c == '\n') || (c == '\r')) { last_c = c; bufptr[pos] = 0; break; } pos++; } cfg_parse_one_line(&bufptr[start], line); pos++; } free(bufptr); // And now do command line argument overrides for(i = 0; i < g_cfg_argv_num_overrides; i++) { printf("Doing override %d, %s\n", i, g_cfg_argv_overrides[i]); cfg_parse_one_line(g_cfg_argv_overrides[i], 1001 + i); g_config_kegs_update_needed = 1; } } void cfg_parse_one_line(char *buf, int line) { Cfg_menu *menuptr; Cfg_defval *defptr; int *iptr; const char *nameptr; int pos, num_equals, type, val, c, len; int i; // warning: modifies memory of bufptr (turns spaces to nulls) if(line) { // Avoid unused parameter warning } len = (int)strlen(buf); if(len <= 1) { // Not a valid line, just get out return; } // printf("disk_conf[%d]: %s\n", line, buf); if(buf[0] == '#') { iwm_printf("Skipping comment\n"); return; } pos = 0; while((pos < len) && (buf[pos] == ' ' || buf[pos] == '\t') ) { pos++; // Eat whitespace } if(pos >= len) { return; // blank line } if((pos + 5) < len) { c = buf[pos+1]; // Slot number if((buf[pos] == 's') && (buf[pos+2] == 'd') && (c >= '5' && c <= '7')) { // looks like s5d1 through s7d15, parse that cfg_parse_sxdx(buf, pos, len); return; } } // parse buf from pos into option, "=" and then "rest" if(strncmp(&buf[pos], "bram", 4) == 0) { cfg_parse_bram(buf, pos+4, len); return; } // find "name" as first contiguous string //printf("...parse_option: line %d, %s (%s) len:%d\n", line, buf, // &buf[pos], len); nameptr = &buf[pos]; while(pos < len) { c = buf[pos]; if((c == 0) || (c == ' ') || (c == '\t') || (c == '\n') || (c == '=')) { break; } pos++; } buf[pos] = 0; if(strcmp(nameptr, "rom") == 0) { // Translate argument of "-rom" to "g_cfg_rom_path" nameptr = "g_cfg_rom_path"; } pos++; // Eat up all whitespace and '=' num_equals = 0; while(pos < len) { c = buf[pos]; if((c == '=') && (num_equals == 0)) { pos++; num_equals++; } else if(c == ' ' || c == '\t') { pos++; } else { break; } } /* Look up nameptr to find type */ type = -1; defptr = 0; menuptr = 0; for(i = 0; i < g_cfg_defval_index; i++) { defptr = &(g_cfg_defvals[i]); menuptr = defptr->menuptr; if(strcmp(menuptr->name_str, nameptr) == 0) { type = menuptr->cfgtype; break; } } switch(type) { case CFGTYPE_INT: /* use strtol */ val = (int)strtol(&buf[pos], 0, 0); iptr = (int *)menuptr->ptr; cfg_int_update(iptr, val); break; case CFGTYPE_FILE: case CFGTYPE_STR: cfg_file_update_ptr(menuptr->ptr, &buf[pos], 0); break; default: printf("Config file variable %s is unknown type: %d\n", nameptr, type); } } void cfg_parse_bram(char *buf, int pos, int len) { word32 val; int bram_num, offset; // Format: "bram1[00] = xx yy..." or "bram3[00] = xx yy ..." // pos = position just after "bram" if((len < (pos+5)) || (buf[pos+1] != '[') || (buf[pos+4] != ']')) { fatal_printf("While reading config.kegs, found malformed bram " "statement: %s\n", buf); return; } bram_num = buf[pos] - '0'; if((bram_num != 1) && (bram_num != 3)) { fatal_printf("While reading config.kegs, found bad bram " "num: %s\n", buf); return; } bram_num = bram_num >> 1; // turn 3->1 and 1->0 offset = (int)strtoul(&(buf[pos+2]), 0, 16); pos += 5; while(pos < len) { if((buf[pos] == ' ') || (buf[pos] == '\t') || (buf[pos] == 0x0a) || (buf[pos] == 0x0d) || (buf[pos] == '=')) { pos++; continue; } val = (word32)strtoul(&buf[pos], 0, 16); // As hex clk_bram_set(bram_num, offset, val); offset++; pos += 2; } } void cfg_parse_sxdx(char *buf, int pos, int len) { char *name_ptr, *partition_name; word32 dynamic_blocks; int part_num, ejected, slot, drive, c; slot = buf[pos+1] - '0'; drive = buf[pos+3] - '0'; /* skip over slot, drive */ pos += 4; if((buf[pos] >= '0') && (buf[pos] <= '9')) { // Second digit of drive is valid drive = (drive * 10) + buf[pos] - '0'; pos++; } drive--; // make sxd1 mean index 0 while((pos < len) && ((buf[pos] == ' ') || (buf[pos] == '\t') || (buf[pos] == '=')) ) { pos++; } ejected = 0; if(buf[pos] == '#') { // disk is ejected, but read info anyway ejected = 1; pos++; } partition_name = 0; part_num = -1; dynamic_blocks = 0; if(buf[pos] == ':') { // yup, it's got a partition name! pos++; partition_name = &buf[pos]; while((pos < len) && (buf[pos] != ':')) { pos++; } buf[pos] = 0; /* null terminate partition name */ pos++; } c = buf[pos]; if((c == ';') || (c == '@')) { pos++; // ; - partition number; @ - Dynamic ProDOS dir size part_num = 0; while((pos < len) && (buf[pos] != ':')) { part_num = (10*part_num) + buf[pos] - '0'; pos++; } pos++; if(c == '@') { // Dynamic ProDOS directory dynamic_blocks = part_num * 2; part_num = -1; } } /* Get filename */ name_ptr = &(buf[pos]); if((pos >= len) || (name_ptr[0] == 0)) { return; } insert_disk(slot, drive, name_ptr, ejected, partition_name, part_num, dynamic_blocks); } void config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, int with_extras) { char *str; str = outstr; if(with_extras) { if(dsk->fd < 0) { snprintf(str, maxlen - (str - outstr), "#"); str = &outstr[strlen(outstr)]; } if(dsk->dynapro_blocks) { snprintf(str, maxlen - (str - outstr), "@%d:", (dsk->dynapro_blocks + 1) / 2); str = &outstr[strlen(outstr)]; } else if(dsk->partition_name != 0) { snprintf(str, maxlen - (str - outstr), ":%s:", dsk->partition_name); str = &outstr[strlen(outstr)]; } else if(dsk->partition_num >= 0) { snprintf(str, maxlen - (str - outstr), ";%d:", dsk->partition_num); str = &outstr[strlen(outstr)]; } } snprintf(str, maxlen - (str - outstr), "%s", dsk->name_ptr); } char * config_write_config_kegs_file(int get_status) { FILE *fconf; Disk *dsk; Cfg_defval *defptr; Cfg_menu *menuptr; char *curstr, *defstr; int defval, curval, type, slot, drive; int i; if(get_status) { return 0; } printf("Writing config.kegs file to %s\n", g_config_kegs_name); fconf = fopen(g_config_kegs_name, "w+"); if(fconf == 0) { halt_printf("cannot open %s! Stopping!\n", g_config_kegs_name); return 0; } fprintf(fconf, "# KEGS configuration file version %s\n", g_kegs_version_str); for(i = 0; i < MAX_C7_DISKS + 4; i++) { slot = 7; drive = i - 4; if(i < 4) { slot = (i >> 1) + 5; drive = i & 1; } if(drive == 0) { fprintf(fconf, "\n"); /* an extra blank line */ } dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->name_ptr == 0 && (i > 4)) { /* No disk, not even ejected--just skip */ continue; } fprintf(fconf, "s%dd%d = ", slot, drive + 1); if(dsk->name_ptr == 0) { fprintf(fconf, "\n"); continue; } config_generate_config_kegs_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, dsk, 1); fprintf(fconf, "%s\n", &g_cfg_tmp_path[0]); } fprintf(fconf, "\n"); /* See if any variables are different than their default */ for(i = 0; i < g_cfg_defval_index; i++) { defptr = &(g_cfg_defvals[i]); menuptr = defptr->menuptr; defval = defptr->intval; type = menuptr->cfgtype; if(type == CFGTYPE_INT) { curval = *((int *)menuptr->ptr); if(curval != defval) { fprintf(fconf, "%s = %d\n", menuptr->name_str, curval); } } if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { curstr = *((char **)menuptr->ptr); defstr = *((char **)menuptr->defptr); if(strcmp(curstr, defstr) != 0) { fprintf(fconf, "%s = %s\n", menuptr->name_str, curstr); } } } fprintf(fconf, "\n"); /* write bram state */ clk_write_bram(fconf); fclose(fconf); g_config_kegs_update_needed = 0; return 0; } void insert_disk(int slot, int drive, const char *name, int ejected, const char *partition_name, int part_num, word32 dynamic_blocks) { byte buf_2img[512]; Disk *dsk; char *name_ptr, *part_ptr; dword64 dsize, dunix_pos, exp_dsize, dtmp; word32 len_bits, save_ftrack; int image_type, part_len, ret, locked, len, is_po, name_len; int i; g_config_kegs_update_needed = 1; if((slot < 5) || (slot > 7)) { fatal_printf("Invalid slot for insertiing disk: %d\n", slot); return; } if(drive < 0 || ((slot == 7) && (drive >= MAX_C7_DISKS)) || ((slot < 7) && (drive > 1))) { fatal_printf("Invalid drive for inserting disk: %d\n", drive); return; } dsk = iwm_get_dsk_from_slot_drive(slot, drive); #if 1 printf("Inserting disk %s (%s or %d) in slot %d, drive: %d, " "dyna_blocks:%d\n", name, partition_name, part_num, slot, drive, dynamic_blocks); #endif // DO NOT change dsk->just_ejected. If a disk was just ejected, then // leave it alone. Otherwise, if we are a newly inserted disk, // it should already be 0, so leave it alone //dsk->just_ejected = 1; if(dsk->fd >= 0) { iwm_eject_disk(dsk); } /* Before opening, make sure no other mounted disk has this name */ /* If so, unmount it */ if(!ejected) { for(i = 0; i < 2; i++) { iwm_eject_named_disk(5, i, name, partition_name); iwm_eject_named_disk(6, i, name, partition_name); } for(i = 0; i < MAX_C7_DISKS; i++) { iwm_eject_named_disk(7, i, name, partition_name); } } /* free old name_ptr, partition_name */ free(dsk->name_ptr); free(dsk->partition_name); dsk->name_ptr = 0; dsk->partition_name = 0; name_ptr = kegs_malloc_str(name); dsk->name_ptr = name_ptr; name_len = (int)strlen(dsk->name_ptr); part_len = 0; part_ptr = 0; if(partition_name != 0) { part_ptr = kegs_malloc_str(partition_name); part_len = (int)strlen(part_ptr); dsk->partition_name = part_ptr; } dsk->partition_num = part_num; dsk->dynapro_blocks = dynamic_blocks; iwm_printf("Opening up disk image named: %s\n", name_ptr); if(ejected) { /* just get out of here */ dsk->fd = -1; return; } dsk->fd = -1; dsk->raw_data = 0; dsk->image_type = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->write_prot = 0; dsk->write_through_to_unix = 1; image_type = 0; locked = 0; if(dynamic_blocks) { ret = dynapro_mount(dsk, name_ptr, dynamic_blocks); if(ret < 0) { iwm_eject_disk(dsk); return; } image_type = DSK_TYPE_DYNAPRO; printf("After dynapro_mount, write_through:%d\n", dsk->write_through_to_unix); } if((partition_name != 0) || (part_num >= 0)) { ret = cfg_partition_find_by_name_or_num(dsk, partition_name, part_num); printf("partition %s (num %d) mounted, wr_prot: %d, ret:%d\n", partition_name, part_num, dsk->write_prot, ret); if(ret < 0) { iwm_eject_disk(dsk); return; } locked = dsk->write_prot; if(dsk->raw_data) { // .zip file or something similar. Do name matching on // partition name name_len = part_len; name_ptr = part_ptr; locked = 1; } } if((name_len > 3) && !image_type && !cfgcasecmp(".gz", &name_ptr[name_len - 3])) { // it's gzip'ed, try to gunzip it to dsk->raw_data undeflate_gzip(dsk, name_ptr); locked = 1; dsk->dimage_start = 0; dsk->dimage_size = dsk->raw_dsize; name_len -= 3; // So .dsk, .po look for correct chars } if((name_len > 4) && !image_type && !cfgcasecmp(".bz2", &name_ptr[name_len - 4])) { // it's bzip2'ed, try to bunzip2 it to dsk->raw_data config_file_to_pipe(dsk, "bunzip2", name_ptr); locked = 1; // Reduce name_len by 4 so that subsequent compares for .po // look at the correct chars name_len -= 4; } if((name_len > 4) && !image_type && !cfgcasecmp(&name_ptr[name_len - 4], ".sdk")) { // it's a ShrinkIt archive with a disk image in it unshk(dsk, name_ptr); locked = 1; image_type = DSK_TYPE_PRODOS; printf("dsk->fd:%d dsk->raw_data:%p, raw_dsize:%lld\n", dsk->fd, dsk->raw_data, dsk->raw_dsize); dsk->dimage_start = 0; dsk->dimage_size = dsk->raw_dsize; } if((name_len > 4) && !image_type && dsk->disk_525 && !cfgcasecmp(".nib", &name_ptr[name_len-4])) { // Old, obsolete .nib 5.25" nibblized format. Support is // read-only image_type = DSK_TYPE_NIB; locked = 1; } if((dsk->fd < 0) && !locked && !dynamic_blocks) { dsk->fd = open(name_ptr, O_RDWR | O_BINARY, 0x1b6); } if((dsk->fd < 0) && !dynamic_blocks) { printf("Trying to open %s read-only, errno: %d\n", name_ptr, errno); dsk->fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); locked = 2; } iwm_printf("open returned: %d\n", dsk->fd); if((dsk->fd < 0) && !dynamic_blocks) { fatal_printf("Disk image %s does not exist!\n", name_ptr); free(dsk->raw_data); return; } //printf("Checking if it's woz, name_ptr:%s. %d vs %d\n", name_ptr, // name_len, (int)strlen(name_ptr)); if((name_len > 4) && !image_type && !cfgcasecmp(&name_ptr[name_len - 4], ".woz")) { // it's a WOZ applesauce disk image image_type = DSK_TYPE_WOZ; printf("It is woz!\n"); } if(locked) { dsk->write_prot = locked; } save_ftrack = dsk->cur_frac_track; /* save arm position */ /* See if it is in 2IMG format */ if(dsk->raw_data) { // Just do a copy from raw_data for(i = 0; i < 512; i++) { buf_2img[i] = dsk->raw_data[i]; } dsize = dsk->raw_dsize; if(!dsk->dynapro_info_ptr) { dsk->write_through_to_unix = 0; } } else { ret = (int)read(dsk->fd, (char *)&buf_2img[0], 512); dsize = cfg_get_fd_size(dsk->fd); } #if 0 /* Try to guess that there is a Mac Binary header of 128 bytes */ /* See if image size & 0xfff = 0x080 which indicates extra 128 bytes */ if(((dsize & 0xfff) == 0x080) && (dsize < (801*1024)) && !image_type) { printf("Assuming Mac Binary header on %s\n", dsk->name_ptr); dsk->dimage_start += 0x80; dsize -= 0x80; } #endif dsk->dimage_size = dsize; if(!image_type && (buf_2img[0] == '2') && (buf_2img[1] == 'I') && (buf_2img[2] == 'M') && (buf_2img[3] == 'G')) { /* It's a 2IMG disk */ printf("Image named %s is in 2IMG format\n", dsk->name_ptr); image_type = DSK_TYPE_PRODOS; if(buf_2img[12] == 0) { printf("2IMG is in DOS 3.3 sector order\n"); image_type = DSK_TYPE_DOS33; } if(buf_2img[19] & 0x80) { /* disk is locked */ printf("2IMG is write protected\n"); if(dsk->write_prot == 0) { dsk->write_prot = 1; } } if((buf_2img[17] & 1) && (dsk->image_type == DSK_TYPE_DOS33)) { dsk->vol_num = buf_2img[16]; printf("Setting DOS 3.3 vol num to %d\n", dsk->vol_num); } // Some 2IMG archives have the size byte reversed dsize = (buf_2img[31] << 24) + (buf_2img[30] << 16) + (buf_2img[29] << 8) + buf_2img[28]; dunix_pos = (buf_2img[27] << 24) + (buf_2img[26] << 16) + (buf_2img[25] << 8) + buf_2img[24]; if(dsize == 0x800c00) { // Byte reversed 0x0c8000 dsize = 0x0c8000; } if(dsize == 0) { /* Sweet-16 makes some images with size == 0 */ /* Example: Prosel from */ /* www.whatisthe2gs.apple2.org.za/the_ring/ */ if(buf_2img[12] == 1) { /* then get the size from 0x14 in blocks */ dsize = (buf_2img[23] << 24) + (buf_2img[22] << 16) + (buf_2img[21] << 8) + buf_2img[20]; dsize = dsize * 512; /* it was blocks */ } } dsk->dimage_start = dunix_pos; dsk->dimage_size = dsize; } exp_dsize = 800*1024; dsk->fbit_mult = 256; if(dsk->disk_525) { exp_dsize = 140*1024; dsk->fbit_mult = 128; } if(!image_type) { /* See if it might be the Mac diskcopy format */ dtmp = cfg_detect_dc42(&buf_2img[0], dsize, exp_dsize, slot); if(dtmp != 0) { // It's diskcopy 4.2 printf("Image named %s is in Mac diskcopy format\n", dsk->name_ptr); dsk->dimage_start += 0x54; dsk->dimage_size = dtmp; image_type = DSK_TYPE_PRODOS; /* ProDOS */ } } if(!image_type) { /* Assume raw image */ dsk->dimage_size = dsize; image_type = DSK_TYPE_PRODOS; is_po = (name_len > 3) && !cfgcasecmp(".po", &name_ptr[name_len-3]); if(dsk->disk_525) { image_type = DSK_TYPE_DOS33; if(is_po) { image_type = DSK_TYPE_PRODOS; } } } dsk->image_type = image_type; dsk->disk_dirty = 0; dsk->cur_fbit_pos = 0; dsk->cur_track_bits = 0; dsk->cur_trk_ptr = 0; if(image_type == DSK_TYPE_WOZ) { // Special handling ret = woz_open(dsk, 0); if(!ret) { iwm_eject_disk(dsk); return; } } else if(dsk->smartport) { g_highest_smartport_unit = MY_MAX(dsk->drive, g_highest_smartport_unit); iwm_printf("adding smartport device[%d], img_sz:%08llx\n", dsk->drive, dsk->dimage_size); } else if(dsk->disk_525) { dunix_pos = dsk->dimage_start; dsize = dsk->dimage_size; disk_set_num_tracks(dsk, 4*35); len = 0x1000; if(dsk->image_type == DSK_TYPE_NIB) { // Weird .nib format, has no sync bits len = (int)(dsk->dimage_size / 35); for(i = 0; i < 35; i++) { disk_unix_to_nib(dsk, 4*i, dunix_pos, len, len * 8, 0); dunix_pos += len; } } else { for(i = 0; i < 35; i++) { len_bits = iwm_get_default_track_bits(dsk, 4*i); disk_unix_to_nib(dsk, 4*i, dunix_pos, len, len_bits, 0); dunix_pos += len; } } if(dsize != (dword64)35*len) { fatal_printf("Disk 5.25 error: size is %lld, not %d. " "Will try to mount anyway\n", dsize, 35*len); } } else { /* disk_35 */ dunix_pos = dsk->dimage_start; dsize = dsk->dimage_size; if(dsize != 800*1024) { printf("Disk 3.5 Drive %d (Image File: %s), Error: " "size is %lld, not 800K. Will try to mount " "anyway\n", drive+1, name, dsize); } disk_set_num_tracks(dsk, 2*80); for(i = 0; i < 2*80; i++) { len = g_track_bytes_35[i >> 5]; len_bits = iwm_get_default_track_bits(dsk, i); iwm_printf("Trk: %d.%d = unix: %08llx, %04x, %04x\n", i>>1, i & 1, dunix_pos, len, len_bits); disk_unix_to_nib(dsk, i, dunix_pos, len, len_bits, 0); dunix_pos += len; iwm_printf(" trk_bits:%05x\n", dsk->trks[i].track_bits); } } iwm_move_to_ftrack(dsk, save_ftrack, 0, 0); } dword64 cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot) { dword64 ddata_size, dtag_size; int i; // Detect Mac DiskCopy4.2 disk image (often .dmg or .dc or .dc42) // bptr points to just the first 512 bytes of the file if((bptr[0x52] != 1) || (bptr[0x53] != 0)) { return 0; // No "magic number 0x01, 0x00 for DiskCopy4.2 } if(bptr[0] > 0x3f) { return 0; // not a valid image name length (1-0x3f) } ddata_size = 0; dtag_size = 0; for(i = 0; i < 4; i++) { ddata_size = (ddata_size << 8) | bptr[0x40 + i]; dtag_size = (dtag_size << 8) | bptr[0x44 + i]; } if((dtag_size != 0) && (dtag_size != ((ddata_size >> 9) * 12))) { return 0; // Tags are either 0, or 12 bytes per block } if(slot == 7) { if(ddata_size < 140*1024) { return 0; // Allow any size >=140K slot 7 } } else if(ddata_size != exp_dsize) { return 0; // Must be 140K or 800K } if(ddata_size > (dsize - 0x54)) { return 0; // data_size doesn't make sense } if((ddata_size & 0x1ff) || ((ddata_size >> 31) > 1)) { return 0; // data_size not a multiple of 512 bytes } if((ddata_size + dtag_size + 0x54) != dsize) { return 0; // File doesn't appear to be well-formed } return ddata_size; } dword64 cfg_get_fd_size(int fd) { struct stat stat_buf; int ret; ret = fstat(fd, &stat_buf); if(ret != 0) { fprintf(stderr,"fstat returned %d on fd %d, errno: %d\n", ret, fd, errno); stat_buf.st_size = 0; } return stat_buf.st_size; } dword64 cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) { dword64 dret, doff; word32 this_len; dret = kegs_lseek(fd, dpos, SEEK_SET); if(dret != dpos) { printf("lseek failed: %lld\n", dret); return 0; } doff = 0; while(1) { if(doff >= dsize) { break; } this_len = 1UL << 30; if((dsize - doff) < this_len) { this_len = (word32)(dsize - doff); } dret = read(fd, bufptr + doff, this_len); if((dret + 1) == 0) { // dret==-1 printf("read failed\n"); return 0; } if(dret == 0) { printf("Unexpected end of file, tried to read from " "dpos:%lld dsize:%lld\n", dpos, dsize); return 0; } doff += dret; } return doff; } dword64 cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) { dword64 dret; dret = kegs_lseek(fd, dpos, SEEK_SET); if(dret != dpos) { printf("lseek failed: %lld\n", dret); return 0; } return must_write(fd, bufptr, dsize); } int cfg_partition_maybe_add_dotdot() { int part_len; part_len = (int)strlen(&(g_cfg_part_path[0])); if(part_len > 0) { // Add .. entry here cfg_file_add_dirent(&g_cfg_partitionlist, "..", 1, 0, 0, 0, 0); } return part_len; } int cfg_partition_name_check(const byte *name_ptr, int name_len) { int part_len; int i; // Return 0 if name_ptr is not at the right path depth, 1 if OK part_len = (int)strlen(&g_cfg_part_path[0]); for(i = 0; i < part_len; i++) { if(i >= name_len) { return 0; } if(name_ptr[i] != g_cfg_part_path[i]) { return 0; } } return 1; } int cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size) { if(!cfg_read_from_fd(fd, buf, blk * blk_size, blk_size)) { // Read failed return 0; } return blk_size; } int cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, int part_num) { Cfg_dirent *direntptr; const char *partnamestr; int match, num_parts, ret, fd, len, c; int i; #if 0 printf("cfg_partition_find_by_name_or_num: %s, part_num:%d\n", partnamestr, part_num); #endif // We need to copy partnamestr up to the last / to g_cfg_part_path[], // and use just the end as the partition name. cfg_strncpy(&g_cfg_part_path[0], in_partnamestr, CFG_PATH_MAX); len = (int)strlen(in_partnamestr); partnamestr = in_partnamestr; for(i = len - 1; i >= 0; i--) { c = g_cfg_part_path[i]; if(c == '/') { partnamestr = &(in_partnamestr[i+1]); break; } g_cfg_part_path[i] = 0; } fd = open(dsk->name_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { fatal_printf("Cannot open disk image: %s\n", dsk->name_ptr); return -1; } num_parts = cfg_partition_make_list(fd); if(num_parts <= 0) { printf("num_parts: %d\n", num_parts); close(fd); return -1; } for(i = 0; i < g_cfg_partitionlist.last; i++) { direntptr = &(g_cfg_partitionlist.direntptr[i]); match = 0; if((strcmp(partnamestr, direntptr->name) == 0) && (part_num < 0)) { //printf("partition, match1, name:%s %s, part_num:%d\n", // partnamestr, direntptr->name, part_num); match = 1; } if((partnamestr == 0) && (direntptr->part_num == part_num)) { //printf("partition, match2, n:%s, part_num:%d == %d\n", // direntptr->name, direntptr->part_num, part_num); match = 1; } if(match) { //printf("match with dimage_start:%08llx, dimage_size:" // "%08llx\n", dsk->dimage_start, // dsk->dimage_size); printf("match with dimage_start:%08llx, dimage_size:" "%08llx\n", direntptr->dimage_start, direntptr->dsize); ret = i; if(g_cfg_partition_is_zip) { ret = undeflate_zipfile(dsk, fd, direntptr->dimage_start, direntptr->dsize, direntptr->compr_dsize); close(fd); } else { dsk->fd = fd; dsk->dimage_start = direntptr->dimage_start; dsk->dimage_size = direntptr->dsize; } return ret; } } close(fd); // printf("No matches, ret -1\n"); return -1; } int cfg_partition_make_list_from_name(const char *namestr) { int fd, ret; fd = open(namestr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { fatal_printf("Cannot open part image: %s\n", namestr); return 0; } ret = cfg_partition_make_list(fd); close(fd); return ret; } int cfg_partition_make_list(int fd) { Driver_desc *driver_desc_ptr; Part_map *part_map_ptr; word32 *blk_bufptr; dword64 dimage_start, dimage_size, dsize; word32 start, len, data_off, data_len, sig, map_blk_cnt, cur_blk; word32 map_blks, block_size; int is_dir, ret; block_size = 512; g_cfg_partition_is_zip = 0; cfg_free_alldirents(&g_cfg_partitionlist); blk_bufptr = (word32 *)malloc(MAX_PARTITION_BLK_SIZE); cfg_partition_read_block(fd, blk_bufptr, 0, block_size); driver_desc_ptr = (Driver_desc *)blk_bufptr; sig = cfg_get_be_word16(&(driver_desc_ptr->sig)); block_size = cfg_get_be_word16(&(driver_desc_ptr->blk_size)); if(block_size == 0) { block_size = 512; } if((sig != 0x4552) || (block_size < 0x200) || (block_size > MAX_PARTITION_BLK_SIZE)) { printf("Partition error: No driver descriptor map found\n"); free(blk_bufptr); ret = undeflate_zipfile_make_list(fd); if(ret > 0) { g_cfg_partition_is_zip = 1; } return ret; } map_blks = 1; cur_blk = 0; dsize = cfg_get_fd_size(fd); cfg_file_add_dirent(&g_cfg_partitionlist, "None - Whole image", is_dir=0, dsize, 0, 0, -1); while(cur_blk < map_blks) { cur_blk++; cfg_partition_read_block(fd, blk_bufptr, cur_blk, block_size); part_map_ptr = (Part_map *)blk_bufptr; sig = cfg_get_be_word16(&(part_map_ptr->sig)); map_blk_cnt = cfg_get_be_word32(&(part_map_ptr->map_blk_cnt)); if(cur_blk <= 1) { map_blks = MY_MIN(20, map_blk_cnt); } if(sig != 0x504d) { printf("Partition entry %d bad signature:%04x\n", cur_blk, sig); free(blk_bufptr); return g_cfg_partitionlist.last; } /* found it, check for consistency */ start = cfg_get_be_word32(&(part_map_ptr->phys_part_start)); len = cfg_get_be_word32(&(part_map_ptr->part_blk_cnt)); data_off = cfg_get_be_word32(&(part_map_ptr->data_start)); data_len = cfg_get_be_word32(&(part_map_ptr->data_cnt)); if(data_off + data_len > len) { printf("Poorly formed entry\n"); continue; } if(data_len < 10 || start < 1) { printf("Poorly formed entry %d, datalen:%d, " "start:%08x\n", cur_blk, data_len, start); continue; } dimage_size = (dword64)data_len * block_size; dimage_start = ((dword64)start + data_off) * block_size; is_dir = 2*(dimage_size < 800*1024); #if 0 printf(" partition add entry %d = %s %d %08llx %08llx\n", cur_blk, part_map_ptr->part_name, is_dir, dimage_size, dimage_start); #endif cfg_file_add_dirent(&g_cfg_partitionlist, part_map_ptr->part_name, is_dir, dimage_size, dimage_start, 0, cur_blk); } free(blk_bufptr); return g_cfg_partitionlist.last; } int cfg_maybe_insert_disk(int slot, int drive, const char *namestr) { int num_parts; g_cfg_part_path[0] = 0; num_parts = cfg_partition_make_list_from_name(namestr); if(num_parts > 0) { printf("Choose a partition\n"); g_cfg_select_partition = 1; g_cfg_file_pathfield = 0; } else { insert_disk(slot, drive, namestr, 0, 0, -1, 0); return 1; } return 0; } void cfg_insert_disk_dynapro(int slot, int drive, const char *name) { int dynapro_blocks; dynapro_blocks = 280; if(slot == 5) { dynapro_blocks = 1600; } else if(slot == 7) { dynapro_blocks = 65535; } if(g_cfg_newdisk_select && (g_cfg_newdisk_type == 3) && g_cfg_newdisk_blocks) { dynapro_blocks = g_cfg_newdisk_blocks; } insert_disk(slot, drive, name, 0, 0, -1, dynapro_blocks); } int cfg_stat(char *path, struct stat *sb, int do_lstat) { int ret, len, removed_slash; removed_slash = 0; len = 0; /* Windows doesn't like to stat paths ending in a /, so remove it */ len = (int)strlen(path); #ifdef _WIN32 if((len > 1) && (path[len - 1] == '/') ) { path[len - 1] = 0; /* remove the slash */ removed_slash = 1; } #endif if(do_lstat) { ret = lstat(path, sb); } else { ret = stat(path, sb); } /* put the slash back */ if(removed_slash) { path[len - 1] = '/'; } return ret; } word32 cfg_get_le16(byte *bptr) { return bptr[0] | (bptr[1] << 8); } word32 cfg_get_le32(byte *bptr) { return bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | (bptr[3] << 24); } dword64 cfg_get_le64(byte *bptr) { dword64 dval; int i; dval = 0; for(i = 7; i >= 0; i--) { dval = (dval << 8) | bptr[i]; } return dval; } word32 cfg_get_be_word16(word16 *ptr) { byte *bptr; bptr = (byte *)ptr; return (bptr[0] << 8) | bptr[1]; } word32 cfg_get_be_word32(word32 *ptr) { byte *bptr; bptr = (byte *)ptr; return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; } void cfg_set_le32(byte *bptr, word32 val) { *bptr++ = val; *bptr++ = val >> 8; *bptr++ = val >> 16; *bptr++ = val >> 24; } void config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr) { #ifdef _WIN32 printf("Cannot do pipe from cmd %s to %s\n", cmd_ptr, name_ptr); return; #else int output_pipe[2]; byte *bptr2; char *bufptr; pid_t pid; int stat_loc, ret, bufsize, pos, fd; int i; // Create a pipe to cmd_ptr, and send the contents of name_ptr to it // Collect the output in a 32MB buffer. Once complete, allocate // a buffer of the correct size, copy to it, and free the giant buffer // Sample usage: "gunzip", "{filename}.gz" will run gunzip and collect // uncompressed data ret = pipe(&output_pipe[0]); if(ret < 0) { return; } printf("output_pipe[0]=%d, [1]=%d\n", output_pipe[0], output_pipe[1]); bufsize = 32*1024*1024; bufptr = malloc(bufsize); if(bufptr == 0) { return; } pos = 0; pid = fork(); if(pid == 0) { close(output_pipe[0]); ret = dup2(output_pipe[1], 1); if(ret < 0) { exit(1); } // The child. Open 0 as the file, and then do system close(0); fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd != 0) { exit(1); } // Now just run the command. Input is from name_ptr, output is // to a pipe (void)!system(cmd_ptr); exit(0); } else if(pid > 0) { // Parent. Collect output from output_pipe[0], and write it // to bufptr. close(output_pipe[1]); while(1) { if(pos >= bufsize) { break; } ret = read(output_pipe[0], bufptr + pos, bufsize - pos); if(ret <= 0) { break; } pos += ret; } close(output_pipe[0]); waitpid(pid, &stat_loc, 0); } else { // Error case close(output_pipe[1]); close(output_pipe[0]); } // See what we got bptr2 = 0; printf("Read %d bytes from %s\n", pos, name_ptr); if(pos >= 140*1024) { // Looks like it could be an image bptr2 = malloc(pos); for(i = 0; i < pos; i++) { bptr2[i] = bufptr[i]; } dsk->raw_data = bptr2; dsk->fd = 0; // Indicates raw_data is valid dsk->raw_dsize = pos; } free(bufptr); #endif } void cfg_htab_vtab(int x, int y) { if(x > 79) { x = 0; } if(y > 23) { y = 0; } g_cfg_curs_x = x; g_cfg_curs_y = y; g_cfg_curs_inv = 0; g_cfg_curs_mousetext = 0; } void cfg_home() { int i; cfg_htab_vtab(0, 0); for(i = 0; i < 24; i++) { cfg_cleol(); } } void cfg_cleol() { g_cfg_curs_inv = 0; g_cfg_curs_mousetext = 0; cfg_putchar(' '); while(g_cfg_curs_x != 0) { cfg_putchar(' '); } } void cfg_putchar(int c) { int x, y; if(c == '\n') { cfg_cleol(); return; } if(c == '\b') { g_cfg_curs_inv = !g_cfg_curs_inv; return; } if(c == '\t') { g_cfg_curs_mousetext = !g_cfg_curs_mousetext; return; } y = g_cfg_curs_y; x = g_cfg_curs_x; // Normal: 0xa0-0xff for space through lowercase // Inverse: 0x00-0x3f for upper case and numbers, 0x60-0x7f for lcase // Mousetext: 0x40-0x5f if(g_cfg_curs_inv) { if(c >= 0x40 && c < 0x60) { c = c & 0x1f; } } else { c = c | 0x80; } if(g_cfg_curs_mousetext) { c = (c & 0x1f) | 0x40; } g_cfg_screen[y][x] = c; x++; if(x >= 80) { x = 0; y++; if(y >= 24) { y = 0; } } g_cfg_curs_y = y; g_cfg_curs_x = x; g_cfg_screen_changed = 1; } void cfg_printf(const char *fmt, ...) { va_list ap; int c; int i; va_start(ap, fmt); (void)vsnprintf(g_cfg_printf_buf, CFG_PRINTF_BUFSIZE, fmt, ap); va_end(ap); for(i = 0; i < CFG_PRINTF_BUFSIZE; i++) { c = g_cfg_printf_buf[i]; if(c == 0) { return; } cfg_putchar(c); } } void cfg_print_dnum(dword64 dnum, int max_len) { char buf[64]; char buf2[64]; int len, cnt, c; int i, j; /* Prints right-adjusted "num" in field "max_len" wide */ snprintf(&buf[0], 64, "%lld", dnum); len = (int)strlen(buf); for(i = 0; i < 64; i++) { buf2[i] = ' '; } j = max_len + 1; buf2[j] = 0; j--; cnt = 0; for(i = len - 1; (i >= 0) && (j >= 1); i--) { c = buf[i]; if(c >= '0' && c <= '9') { if(cnt >= 3) { buf2[j--] = ','; cnt = 0; } cnt++; } buf2[j--] = c; } cfg_printf(&buf2[1]); } int cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras) { Disk *dsk; int slot, drive; slot = type_ext >> 8; drive = type_ext & 0xff; dsk = iwm_get_dsk_from_slot_drive(slot, drive); outstr[0] = 0; if(dsk->name_ptr == 0) { return 0; } config_generate_config_kegs_name(outstr, maxlen, dsk, with_extras); return dsk->dynapro_blocks; } int cfg_get_disk_locked(int type_ext) { Disk *dsk; int slot, drive; slot = type_ext >> 8; drive = type_ext & 0xff; dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->fd < 0) { return 0; } if(dsk->write_prot) { return 1; } else if(!dsk->write_through_to_unix) { return 2; } return 0; } void cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change) { char valbuf[CFG_OPT_MAXSTR]; char *(*fn_ptr)(int); int *iptr; char **str_ptr; const char *menustr; char *curstr, *defstr, *str, *outstr; void *edit_ptr; int val, num_opts, opt_num, bufpos, outpos, curval, defval, type; int type_ext, opt_get_str, separator, len, c, locked; int i; // For this menu_pos line, create output in g_cfg_opt_buf[] string // Highlight it if menu_pos==highlight_pos // Allow arrows to modify the currently selected item of a list using // change: -1 moves to a previous item, +1 moves to the next g_cfg_opt_buf[0] = 0; num_opts = 0; opt_get_str = 0; separator = ','; menuptr += menu_pos; /* move forward to entry menu_pos */ menustr = menuptr->str; type = menuptr->cfgtype; type_ext = (type >> 4); type = type & 0xf; len = (int)strlen(menustr) + 1; bufpos = 0; outstr = &(g_cfg_opt_buf[0]); outstr[bufpos++] = ' '; // 0 outstr[bufpos++] = ' '; // 1 outstr[bufpos++] = '\t'; // 2 outstr[bufpos++] = '\t'; // 3 outstr[bufpos++] = ' '; // 4 outstr[bufpos++] = ' '; // 5 // Figure out if we should get a checkmark curval = -1; defval = -1; curstr = 0; if(type == CFGTYPE_INT) { iptr = menuptr->ptr; curval = *iptr; iptr = menuptr->defptr; if(!iptr) { printf("BAD MENU, defptr is 0!\n"); } else { defval = *iptr; } if(curval == defval) { g_cfg_opt_buf[3] = 'D'; /* checkmark */ g_cfg_opt_buf[4] = '\t'; } } if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { str_ptr = (char **)menuptr->ptr; curstr = *str_ptr; str_ptr = (char **)menuptr->defptr; if(!str_ptr) { printf("BAD MENU, defptr str is 0!\n"); defstr = ""; } else { defstr = *str_ptr; } if(strcmp(curstr, defstr) == 0) { g_cfg_opt_buf[3] = 'D'; /* checkmark */ g_cfg_opt_buf[4] = '\t'; } } // If it's a menu, give it a special menu indicator if(type == CFGTYPE_MENU) { g_cfg_opt_buf[1] = '\t'; g_cfg_opt_buf[2] = 'M'; /* return-like symbol */ g_cfg_opt_buf[3] = '\t'; g_cfg_opt_buf[4] = ' '; } if(type == CFGTYPE_DISK) { locked = cfg_get_disk_locked(type_ext); if(locked) { g_cfg_opt_buf[4] = '*'; if(locked == 2) { // inverse g_cfg_opt_buf[2] = '\b'; g_cfg_opt_buf[3] = '*'; g_cfg_opt_buf[4] = '\b'; } } } if(menu_pos == highlight_pos) { outstr[bufpos++] = '\b'; } opt_get_str = 2; i = -1; outpos = bufpos; #if 0 printf("cfg menu_pos: %d str len: %d\n", menu_pos, len); #endif while(++i < len) { c = menustr[i]; if(c == separator) { // ','? if(i == 0) { continue; } c = 0; } outstr[outpos++] = c; outstr[outpos] = 0; if(outpos >= CFG_OPT_MAXSTR) { fprintf(stderr, "CFG_OPT_MAXSTR exceeded\n"); my_exit(1); return; } if(c == 0) { if(opt_get_str == 2) { outstr = &(valbuf[0]); bufpos = outpos - 1; opt_get_str = 0; } else if(opt_get_str) { #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d opt %d = %s=%d\n", menu_pos, num_opts, g_cfg_opts_str[0], g_cfg_opts_vals[num_opts]); } #endif num_opts++; outstr = &(valbuf[0]); opt_get_str = 0; if(num_opts >= CFG_MAX_OPTS) { fprintf(stderr, "CFG_MAX_OPTS oflow\n"); my_exit(1); return; } } else { val = (word32)strtoul(valbuf, 0, 0); g_cfg_opts_vals[num_opts] = val; outstr = &(g_cfg_opts_str[0]); if(val != curval) { outstr = valbuf; } opt_get_str = 1; } outpos = 0; outstr[0] = 0; } } if(menu_pos == highlight_pos) { g_cfg_opt_buf[bufpos++] = '\b'; } g_cfg_opt_buf[bufpos] = 0; // Decide what to display on the "right" side str = 0; opt_num = -1; if((type == CFGTYPE_INT) || (type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos++] = '='; g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos] = 0; for(i = 0; i < num_opts; i++) { if(curval == g_cfg_opts_vals[i]) { opt_num = i; break; } } } if(change != 0) { if(type == CFGTYPE_INT) { if(num_opts > 0) { opt_num += change; if(opt_num >= num_opts) { opt_num = 0; } if(opt_num < 0) { opt_num = num_opts - 1; } curval = g_cfg_opts_vals[opt_num]; } else { curval += change; /* HACK: min_val, max_val testing here */ } iptr = (int *)menuptr->ptr; cfg_int_update(iptr, curval); } g_config_kegs_update_needed = 1; } #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d opt_num %d\n", menu_pos, opt_num); } #endif edit_ptr = g_cfg_edit_ptr; if((edit_ptr == menuptr->ptr) && edit_ptr) { // Just show the current edit string str = cfg_shorten_filename(&(g_cfg_edit_buf[0]), 68 - 1); cfg_strlcat(str, "\b \b", CFG_PATH_MAX); } else if(opt_num >= 0) { str = &(g_cfg_opts_str[0]); } else { if(type == CFGTYPE_INT) { str = &(g_cfg_opts_str[0]); snprintf(str, CFG_OPT_MAXSTR, "%d", curval); } else if (type == CFGTYPE_DISK) { str = &(g_cfg_opts_str[0]); (void)cfg_get_disk_name(str, CFG_PATH_MAX, type_ext, 1); str = cfg_shorten_filename(str, 68); } else if ((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { str = cfg_shorten_filename(curstr, 68); } else if(type == CFGTYPE_FUNC) { fn_ptr = (char *(*)(int))menuptr->ptr; str = ""; curstr = (*fn_ptr)(1); if(curstr) { g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos++] = ':'; g_cfg_opt_buf[bufpos++] = ' '; g_cfg_opt_buf[bufpos] = 0; str = cfg_shorten_filename(curstr, 68); free(curstr); } } else { str = ""; } } #if 0 if(menu_pos == highlight_pos) { printf("menu_pos %d buf_pos %d, str is %s, %02x, %02x, " "%02x %02x\n", menu_pos, bufpos, str, g_cfg_opt_buf[bufpos-1], g_cfg_opt_buf[bufpos-2], g_cfg_opt_buf[bufpos-3], g_cfg_opt_buf[bufpos-4]); } #endif g_cfg_opt_buf[bufpos] = 0; cfg_strncpy(&(g_cfg_opt_buf[bufpos]), str, CFG_OPT_MAXSTR - bufpos - 1); g_cfg_opt_buf[CFG_OPT_MAXSTR-1] = 0; } void cfg_get_base_path(char *pathptr, const char *inptr, int go_up) { const char *tmpptr; char *slashptr; char *outptr; int add_dotdot, is_dotdot; int len; int c; // Take full filename, copy it to pathptr, and truncate at last slash // inptr and pathptr can be the same. // If go_up is set, then replace a blank dir with ".." // but first, see if path is currently just ../ over and over // if so, just tack .. onto the end and return //printf("cfg_get_base start with %s\n", inptr); g_cfg_file_match[0] = 0; tmpptr = inptr; is_dotdot = 1; while(1) { if(tmpptr[0] == 0) { break; } if((tmpptr[0] == '.') && (tmpptr[1] == '.') && (tmpptr[2] == '/')) { tmpptr += 3; } else { is_dotdot = 0; break; } } slashptr = 0; outptr = pathptr; c = -1; while(c != 0) { c = *inptr++; if(c == '/') { if(*inptr != 0) { /* if not a trailing slash... */ slashptr = outptr; } } *outptr++ = c; } if(!go_up) { /* if not go_up, copy chopped part to g_cfg_file_match*/ /* copy from slashptr+1 to end */ tmpptr = slashptr+1; if(slashptr == 0) { tmpptr = pathptr; } cfg_strncpy(&g_cfg_file_match[0], tmpptr, CFG_PATH_MAX); /* remove trailing / from g_cfg_file_match */ len = (int)strlen(&g_cfg_file_match[0]); if((len > 1) && (len < (CFG_PATH_MAX - 1)) && g_cfg_file_match[len - 1] == '/') { g_cfg_file_match[len - 1] = 0; } //printf("set g_cfg_file_match to %s\n", &g_cfg_file_match[0]); } if(!is_dotdot && (slashptr != 0)) { slashptr[0] = '/'; slashptr[1] = 0; outptr = slashptr + 2; } add_dotdot = 0; if(pathptr[0] == 0 || is_dotdot) { /* path was blank, or consisted of just ../ pattern */ if(go_up) { add_dotdot = 1; } } else if(slashptr == 0) { /* no slashes found, but path was not blank--make it blank */ if(pathptr[0] == '/') { pathptr[1] = 0; } else { pathptr[0] = 0; } } if(add_dotdot) { --outptr; outptr[0] = '.'; outptr[1] = '.'; outptr[2] = '/'; outptr[3] = 0; } //printf("cfg_get_base end with %s, is_dotdot:%d, add_dotdot:%d\n", // pathptr, is_dotdot, add_dotdot); } char * cfg_name_new_image(int get_status) { // Called from menu to create a new disk image, this will pop up the // file selection dialog. Once name is selected, // cfg_create_new_image() is called. if(get_status) { return 0; } printf("cfg_name_new_image called!\n"); g_cfg_slotdrive = g_cfg_newdisk_slotdrive; g_cfg_newdisk_select = 1; cfg_file_init(); return 0; } void cfg_dup_existing_image(word32 slotdrive) { // Set g_cfg_newdisk_* to copy the slotdrive image g_cfg_slotdrive = slotdrive; g_cfg_newdisk_select = 2; // Do DUP cfg_file_init(); } void cfg_dup_image_selected() { Disk *dsk; Woz_info *wozinfo_ptr; byte *bufptr; char *str; dword64 dsize, dret; word32 slotdrive; int fd; // printf("cfg_dup_image_selected\n"); slotdrive = g_cfg_slotdrive; g_cfg_slotdrive = 0; g_menuptr = &g_cfg_disk_menu[0]; g_menu_redraw_needed = 1; g_cfg_newdisk_select = 0; g_cfg_newdisk_slotdrive = 0; printf("slotdrive:%04x\n", slotdrive); if(slotdrive < 0x500) { return; // Invalid slot,drive: Do nothing } dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); if(dsk->fd < 0) { return; // No disk } dsize = dsk->dimage_size; str = &g_cfg_file_path[0], printf("Create dup image %s, dsize:%lld\n", str, dsize); if((word32)dsize != dsize) { return; } fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); if(fd < 0) { printf("Open %s failed, errno:%d\n", str, errno); return; } wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr && !dsk->write_through_to_unix) { // Just write out the WOZ image, and then fully enable this // image printf("Writing out .WOZ image to %s, size:%d\n", str, wozinfo_ptr->woz_size); if((dsk->raw_data == 0) && (dsk->fd >= 0)) { close(dsk->fd); } dsk->fd = fd; woz_rewrite_crc(dsk, wozinfo_ptr->woz_size); // Above will recalc CRC and write out woz_size bytes dsk->raw_data = 0; dsk->write_through_to_unix = 1; free(dsk->name_ptr); dsk->name_ptr = kegs_malloc_str(str); free(dsk->partition_name); dsk->partition_name = 0; dsk->image_type = DSK_TYPE_WOZ; dsk->dimage_size = wozinfo_ptr->woz_size; dsk->dimage_start = 0; g_config_kegs_update_needed = 1; woz_check_file(dsk); } else if(dsk->raw_data) { cfg_write_to_fd(fd, dsk->raw_data, 0, dsize); close(fd); } else { bufptr = malloc((size_t)dsize); if((bufptr != 0) && ((size_t)dsize == dsize)) { dret = cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); if(dret == dsize) { cfg_write_to_fd(fd, bufptr, 0, dsize); } } free(bufptr); close(fd); } } void cfg_validate_image(word32 slotdrive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); dynapro_validate_any_image(dsk); } void cfg_toggle_lock_disk(word32 slotdrive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, slotdrive & 0xff); iwm_toggle_lock(dsk); } int cfg_create_new_image_act(const char *str, int type, int size_blocks) { byte buf[512]; dword64 dret; int fd, ret; int i; // Called after file dialog selects a new image name, this creates it if(size_blocks == 65536) { size_blocks--; // Shrink to 65535 total blocks } printf("Create new image type:%d, size:%dKB\n", type, size_blocks/2); fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); if(fd < 0) { printf("Open %s failed, errno:%d\n", str, errno); return fd; } for(i = 0; i < 512; i++) { buf[i] = 0; } ret = 0; if(type == 2) { // WOZ (void)woz_new(fd, str, size_blocks/2); } else { for(i = 0; i < size_blocks; i++) { dret = cfg_write_to_fd(fd, &(buf[0]), i * 512U, 512); if(dret != 512) { ret = -1; break; } } } close(fd); return ret; // 0=success, -1 is a failure } void cfg_create_new_image() { word32 dynamic_blocks; int ret; // Type is in g_cfg_file_path. Create this file and prepare it printf("Creating new image: %s\n", &g_cfg_file_path[0]); if(g_cfg_newdisk_select == 2) { cfg_dup_image_selected(); return; } ret = 0; dynamic_blocks = 0; if(g_cfg_newdisk_type == 3) { dynamic_blocks = g_cfg_newdisk_blocks; } else { ret = cfg_create_new_image_act(&g_cfg_file_path[0], g_cfg_newdisk_type, g_cfg_newdisk_blocks); } if(ret < 0) { // Maybe open a dialog? Oh well...do nothing } else { insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &(g_cfg_file_path[0]), 0, 0, -2, dynamic_blocks); } g_cfg_slotdrive = 0; g_menuptr = &g_cfg_disk_menu[0]; g_menu_redraw_needed = 1; g_cfg_newdisk_select = 0; g_cfg_newdisk_slotdrive = 0; } void cfg_file_init() { int slot, drive, is_dynapro; int i; is_dynapro = 0; if((g_cfg_slotdrive & 0xfff) == 0xfff) { // File selection // Just use g_cfg_file_def_name cfg_strncpy(&g_cfg_tmp_path[0], g_cfg_file_def_name, CFG_PATH_MAX); } else { is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, g_cfg_slotdrive, 0); slot = (g_cfg_slotdrive >> 8) & 7; drive = g_cfg_slotdrive & 1; for(i = 0; i < 6; i++) { if(g_cfg_tmp_path[0] != 0) { break; } /* try to get a starting path from some mounted drive */ drive = !drive; if(i & 1) { slot++; if(slot >= 8) { slot = 5; } } is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, (slot << 8) + drive, 0); } } cfg_get_base_path(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], 0); if(is_dynapro) { // Use the full path to the dir (don't strip off last part) cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], CFG_PATH_MAX); } g_cfg_dirlist.invalid = 1; } void cfg_free_alldirents(Cfg_listhdr *listhdrptr) { int i; if(listhdrptr->max > 0) { // Free the old directory listing for(i = 0; i < listhdrptr->last; i++) { free(listhdrptr->direntptr[i].name); } free(listhdrptr->direntptr); } listhdrptr->direntptr = 0; listhdrptr->last = 0; listhdrptr->max = 0; listhdrptr->invalid = 0; listhdrptr->topent = 0; listhdrptr->curent = 0; } void cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num) { Cfg_dirent *direntptr; int num, namelen, this_len; int i; // Loop through all entries, make sure name is unique num = listhdrptr->last; namelen = (int)strlen(nameptr); for(i = 0; i < num; i++) { direntptr = &(listhdrptr->direntptr[i]); this_len = (int)strlen(direntptr->name); if(cfg_str_match(direntptr->name, nameptr, namelen) == 0) { // It's a match...check len if(namelen == this_len) { return; } } } cfg_file_add_dirent(listhdrptr, nameptr, is_dir, dsize, dimage_start, compr_dsize, part_num); } void cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num) { Cfg_dirent *direntptr; char *ptr; int inc_amt, namelen; namelen = (int)strlen(nameptr); if(listhdrptr->last >= listhdrptr->max) { // realloc inc_amt = MY_MAX(64, listhdrptr->max); inc_amt = MY_MIN(inc_amt, 1024); listhdrptr->max += inc_amt; listhdrptr->direntptr = realloc(listhdrptr->direntptr, listhdrptr->max * sizeof(Cfg_dirent)); } ptr = malloc(namelen+1+is_dir); cfg_strncpy(ptr, nameptr, namelen+1); if(is_dir && (namelen >= 1) && (ptr[namelen - 1] != '/')) { // Add a trailing '/' to directories, unless already there cfg_strlcat(ptr, "/", namelen + 1 + is_dir); } #if 0 printf("...file entry %d is %s\n", listhdrptr->last, ptr); #endif direntptr = &(listhdrptr->direntptr[listhdrptr->last]); direntptr->name = ptr; direntptr->is_dir = is_dir; direntptr->dsize = dsize; direntptr->dimage_start = dimage_start; direntptr->compr_dsize = compr_dsize; direntptr->part_num = part_num; listhdrptr->last++; } int cfg_dirent_sortfn(const void *obj1, const void *obj2) { const Cfg_dirent *direntptr1, *direntptr2; int ret; /* Called by qsort to sort directory listings */ direntptr1 = (const Cfg_dirent *)obj1; direntptr2 = (const Cfg_dirent *)obj2; ret = cfg_str_match(direntptr1->name, direntptr2->name, CFG_PATH_MAX); return ret; } int cfg_str_match(const char *str1, const char *str2, int len) { return cfg_str_match_maybecase(str1, str2, len, g_cfg_ignorecase); } int cfg_str_match_maybecase(const char *str1, const char *str2, int len, int ignorecase) { const byte *bptr1, *bptr2; int c, c2; int i; /* basically, work like strcmp or strcasecmp */ bptr1 = (const byte *)str1; bptr2 = (const byte *)str2; for(i = 0; i < len; i++) { c = *bptr1++; c2 = *bptr2++; if(ignorecase) { c = tolower(c); c2 = tolower(c2); } if((c == 0) || (c2 == 0) || (c != c2)) { return c - c2; } } return 0; } int cfgcasecmp(const char *str1, const char *str2) { return cfg_str_match_maybecase(str1, str2, 32767, 1); } int cfg_strlcat(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int destlen, srclen, ret, c; // Concat srcptr to the end of dstptr, ensuring a null within dstsize // Return the total buffer size that would be needed, even if dstsize // is too small. Compat with strlcat() destlen = (int)strlen(dstptr); srclen = (int)strlen(srcptr); ret = destlen + srclen; dstsize--; if(destlen >= dstsize) { return ret; // Do nothing, buf too small } ptr = dstptr + destlen; while(destlen < dstsize) { c = *srcptr++; *ptr++ = c; if(c == 0) { return ret; } destlen++; } dstptr[dstsize] = 0; return ret; } char * cfg_strncpy(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int c; // Copy srcptr to dstptr, ensuring there is room for a null // Compatible with strncpy()--except dstptr is ALWAYS null terminated ptr = dstptr; while(--dstsize > 0) { c = *srcptr++; *ptr++ = c; if(c == 0) { return dstptr; } } *ptr = 0; return dstptr; } const char * cfg_str_basename(const char *str) { int len; int i; // If str is /aa/bb/cc, this routine returns cc len = (int)strlen(str); while(len && (str[len - 1] == '/')) { len--; // Ignore trailing '/' if there are any } for(i = len - 1; i > 0; i--) { if(str[i] == '/') { return str + i + 1; } } return str; } char * cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize) { char *ptr; int c; // If srcptr is /aa/bb/cc, this routine returns /aa/bb/ // Copy srcptr to dstptr, ensuring there is room for a null // Compatible with strncpy()--except dstptr is ALWAYS null terminated ptr = dstptr; while(--dstsize > 0) { c = *srcptr++; *ptr++ = c; if(c == 0) { // Remove any trailing /'s ptr--; while((ptr > dstptr) && (ptr[0] == '/')) { ptr[0] = 0; ptr--; } while(ptr > dstptr) { if(ptr[0] == '/') { ptr[1] = 0; break; } ptr--; } return dstptr; } } *ptr = 0; return dstptr; } void cfg_file_readdir(const char *pathptr) { struct dirent *direntptr; struct stat stat_buf; DIR *dirptr; mode_t fmt; char *str; const char *tmppathptr; int size, ret, is_dir, is_gz, len; int i; if(!strncmp(pathptr, &g_cfg_file_cachedpath[0], CFG_PATH_MAX) && (g_cfg_dirlist.last > 0) && (g_cfg_dirlist.invalid==0)){ return; } // No match, must read new directory // Free all dirents that were cached previously cfg_free_alldirents(&g_cfg_dirlist); cfg_strncpy(&g_cfg_file_cachedpath[0], pathptr, CFG_PATH_MAX); cfg_strncpy(&g_cfg_file_cachedreal[0], pathptr, CFG_PATH_MAX); str = &g_cfg_file_cachedreal[0]; for(i = 0; i < 200; i++) { len = (int)strlen(str); if(len <= 0) { break; } else if(len < CFG_PATH_MAX-2) { if(str[len-1] != '/') { // append / to make various routines work str[len] = '/'; str[len+1] = 0; } } ret = cfg_stat(str, &stat_buf, 0); is_dir = 0; if(ret == 0) { fmt = stat_buf.st_mode & S_IFMT; if(fmt == S_IFDIR) { /* it's a directory */ is_dir = 1; } } if(is_dir) { break; } else { // user is entering more path, use base for display cfg_get_base_path(str, str, 0); } } tmppathptr = str; if(str[0] == 0) { tmppathptr = "."; } cfg_file_add_dirent(&g_cfg_dirlist, "..", 1, 0, 0, 0, -1); dirptr = opendir(tmppathptr); if(dirptr == 0) { printf("Could not open %s as a directory\n", tmppathptr); return; } while(1) { direntptr = readdir(dirptr); if(direntptr == 0) { break; } if(!strcmp(".", direntptr->d_name)) { continue; } if(!strcmp("..", direntptr->d_name)) { continue; } /* Else, see if it is a directory or a file */ cfg_strncpy(&(g_cfg_tmp_path[0]), &(g_cfg_file_cachedreal[0]), CFG_PATH_MAX); cfg_strlcat(&(g_cfg_tmp_path[0]), direntptr->d_name, CFG_PATH_MAX); ret = cfg_stat(&g_cfg_tmp_path[0], &stat_buf, 0); len = (int)strlen(g_cfg_tmp_path); is_dir = 0; is_gz = 0; if((len > 3) && !cfgcasecmp(&g_cfg_tmp_path[len - 3], ".gz")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".bz2")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".zip")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".woz")) { is_gz = 1; } if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".sdk")) { is_gz = 1; } if(ret != 0) { printf("stat %s ret %d, errno:%d\n", &g_cfg_tmp_path[0], ret, errno); stat_buf.st_size = 0; continue; /* skip it */ } else { fmt = stat_buf.st_mode & S_IFMT; size = (int)stat_buf.st_size; if(fmt == S_IFDIR) { /* it's a directory */ is_dir = 1; } else if((fmt == S_IFREG) && (is_gz == 0)) { if((g_cfg_slotdrive & 0xfff) == 0xfff) { /* see if there are size limits */ if((size < g_cfg_file_min_size) || (size > g_cfg_file_max_size)) { continue; /* skip it */ } } else { if(size < 140*1024) { continue; /* skip it */ } } } } cfg_file_add_dirent(&g_cfg_dirlist, direntptr->d_name, is_dir, (dword64)stat_buf.st_size, 0, 0, -1); } closedir(dirptr); /* then sort the results (Mac's HFS+ is sorted, but other FS won't be)*/ qsort(&(g_cfg_dirlist.direntptr[0]), g_cfg_dirlist.last, sizeof(Cfg_dirent), cfg_dirent_sortfn); g_cfg_dirlist.curent = g_cfg_dirlist.last - 1; for(i = g_cfg_dirlist.last - 1; i >= 0; i--) { ret = cfg_str_match(&(g_cfg_file_match[0]), g_cfg_dirlist.direntptr[i].name, CFG_PATH_MAX); if(ret <= 0) { /* set cur ent to closest filename to the match name */ g_cfg_dirlist.curent = i; } } } char * cfg_shorten_filename(const char *in_ptr, int maxlen) { char *out_ptr; int len, c; int i; /* Warning: uses a static string, not reentrant! */ out_ptr = &(g_cfg_file_shortened[0]); len = (int)strlen(in_ptr); maxlen = MY_MIN(len, maxlen); for(i = 0; i < maxlen; i++) { c = in_ptr[i] & 0x7f; if(c < 0x20) { c = '*'; } out_ptr[i] = c; } out_ptr[maxlen] = 0; if(len > maxlen) { for(i = 0; i < (maxlen/2); i++) { c = in_ptr[len-i-1] & 0x7f; if(c < 0x20) { c = '*'; } out_ptr[maxlen-i-1] = c; } out_ptr[(maxlen/2) - 1] = '.'; out_ptr[maxlen/2] = '.'; out_ptr[(maxlen/2) + 1] = '.'; } return out_ptr; } void cfg_fix_topent(Cfg_listhdr *listhdrptr) { int num_to_show; num_to_show = listhdrptr->num_to_show; /* Force curent and topent to make sense */ if(listhdrptr->curent >= listhdrptr->last) { listhdrptr->curent = listhdrptr->last - 1; } if(listhdrptr->curent < 0) { listhdrptr->curent = 0; } if(abs(listhdrptr->curent - listhdrptr->topent) >= num_to_show) { listhdrptr->topent = listhdrptr->curent - (num_to_show/2); } if(listhdrptr->topent > listhdrptr->curent) { listhdrptr->topent = listhdrptr->curent - (num_to_show/2); } if(listhdrptr->topent < 0) { listhdrptr->topent = 0; } } void cfg_file_draw() { Cfg_listhdr *listhdrptr; Cfg_dirent *direntptr; const char *tmp_str; char *str, *fmt; int num_to_show; int yoffset; int x, y; int i; //printf("cfg_file_draw called\n"); cfg_file_readdir(&g_cfg_file_curpath[0]); for(y = 0; y < 21; y++) { cfg_htab_vtab(0, y); cfg_printf("\tZ\t"); for(x = 1; x < 79; x++) { cfg_htab_vtab(x, y); cfg_putchar(' '); } cfg_htab_vtab(79, y); cfg_printf("\t_\t"); } cfg_htab_vtab(1, 0); cfg_putchar('\b'); for(x = 1; x < 79; x++) { cfg_putchar(' '); } if((g_cfg_slotdrive & 0xfff) == 0xfff) { cfg_htab_vtab(5, 0); cfg_printf("\bSelect file to use as %-40s\b", cfg_shorten_filename(g_cfg_file_def_name, 40)); } else { cfg_htab_vtab(30, 0); tmp_str = "Select"; if(g_cfg_newdisk_select == 2) { tmp_str = "Create duplicate"; } else if(g_cfg_newdisk_select) { tmp_str = "Create new"; } cfg_printf("\b%s image for s%dd%d\b", tmp_str, (g_cfg_slotdrive >> 8) & 0xf, (g_cfg_slotdrive & 0xff) + 1); } cfg_htab_vtab(2, 1); cfg_printf("config.kegs path: %-56s", cfg_shorten_filename(&g_config_kegs_name[0], 56)); cfg_htab_vtab(2, 2); cfg_printf("Current KEGS directory: %-50s", cfg_shorten_filename(&g_cfg_cwd_str[0], 50)); cfg_htab_vtab(2, 3); str = ""; if(g_cfg_file_pathfield) { str = "\b \b"; } cfg_printf("Path: %s%s", cfg_shorten_filename(&g_cfg_file_curpath[0], 68), str); cfg_htab_vtab(0, 4); cfg_printf(" \t"); for(x = 1; x < 79; x++) { cfg_putchar('\\'); } cfg_printf("\t "); /* Force curent and topent to make sense */ listhdrptr = &g_cfg_dirlist; num_to_show = CFG_NUM_SHOWENTS; yoffset = 5; if(g_cfg_select_partition > 0) { listhdrptr = &g_cfg_partitionlist; num_to_show -= 2; cfg_htab_vtab(2, yoffset); cfg_printf("Select partition of %-50s", cfg_shorten_filename(&g_cfg_file_path[0], 50), ""); cfg_htab_vtab(2, yoffset + 1); cfg_printf("Current partition: %-50s", cfg_shorten_filename(&g_cfg_part_path[0], 50), ""); yoffset += 2; } listhdrptr->num_to_show = num_to_show; cfg_fix_topent(listhdrptr); for(i = 0; i < num_to_show; i++) { y = i + yoffset; if(listhdrptr->last > (i + listhdrptr->topent)) { direntptr = &(listhdrptr-> direntptr[i + listhdrptr->topent]); cfg_htab_vtab(2, y); if(direntptr->is_dir) { cfg_printf("\tXY\t "); } else { cfg_printf(" "); } if(direntptr->part_num >= 0) { cfg_printf("%3d: ", direntptr->part_num); } str = cfg_shorten_filename(direntptr->name, 50); fmt = "%-50s"; if((i + listhdrptr->topent) == listhdrptr->curent) { if(g_cfg_file_pathfield == 0) { fmt = "\b%-50s\b"; } else { fmt = "%-49s\b \b"; } //printf("file highlight l %d top:%d cur:%d\n", // i, listhdrptr->topent, // listhdrptr->curent); } cfg_printf(fmt, str); if(!direntptr->is_dir) { cfg_print_dnum(direntptr->dsize, 18); } //printf(" :%s:%lld:\n", str, direntptr->dsize); } } cfg_htab_vtab(1, 5 + CFG_NUM_SHOWENTS); cfg_putchar('\t'); for(x = 1; x < 79; x++) { cfg_putchar('L'); } cfg_putchar('\t'); //printf("cfg_file_draw done\n"); } void cfg_partition_select_all() { word32 slot_drive; int part_path_len, curent; slot_drive = g_cfg_slotdrive; part_path_len = (int)strlen(&g_cfg_part_path[0]); curent = g_cfg_partitionlist.curent; while(1) { g_cfg_slotdrive = slot_drive; g_cfg_partitionlist.curent = curent; cfg_partition_selected(); if(g_cfg_slotdrive != 0) { // Something went wrong, get out return; } slot_drive++; curent++; g_cfg_part_path[part_path_len] = 0; if(curent >= g_cfg_partitionlist.last) { return; } if((slot_drive >> 8) == 7) { if((slot_drive & 0xff) >= MAX_C7_DISKS) { return; } if((slot_drive & 0xff) >= 12) { return; } } else if((slot_drive & 0xff) >= 2) { return; } } } void cfg_partition_selected() { char *str; const char *part_str; char *part_str2; int pos; int part_num; pos = g_cfg_partitionlist.curent; str = g_cfg_partitionlist.direntptr[pos].name; if(g_cfg_partitionlist.direntptr[pos].is_dir) { // Add this path to the partition path, and try again if(!strcmp(str, "../")) { /* go up one directory */ cfg_get_base_path(&g_cfg_part_path[0], &g_cfg_part_path[0], 1); } else { cfg_strlcat(&(g_cfg_part_path[0]), str, CFG_PATH_MAX); } cfg_partition_make_list_from_name(&g_cfg_file_path[0]); return; } part_num = -2; part_str = 0; if(str[0] == 0 || (str[0] >= '0' && str[0] <= '9')) { part_num = g_cfg_partitionlist.direntptr[pos].part_num; } else { part_str = str; } part_str2 = 0; if(part_str != 0) { cfg_strlcat(&g_cfg_part_path[0], part_str, CFG_PATH_MAX); part_str2 = kegs_malloc_str(&g_cfg_part_path[0]); g_cfg_part_path[0] = 0; } printf("cfg_partition_selected, pos:%d, g_cfg_file_path[0]:%s, " "part:%s\n", pos, g_cfg_file_path, part_str2); insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &(g_cfg_file_path[0]), 0, part_str2, part_num, 0); free(part_str2); g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; g_cfg_select_partition = -1; } void cfg_file_selected() { struct stat stat_buf; char *str; int fmt, stat_errno, is_cmd_key_down; int ret; is_cmd_key_down = adb_is_cmd_key_down() && ((g_cfg_slotdrive & 0xfff) != 0xfff); // Cmd-Return means create DynaPro image when using slot/drive if(g_cfg_select_partition > 0) { cfg_partition_selected(); return; } if(!is_cmd_key_down && (g_cfg_file_pathfield == 0)) { // in file section area of window str = g_cfg_dirlist.direntptr[g_cfg_dirlist.curent].name; if(!strcmp(str, "../")) { /* go up one directory */ cfg_get_base_path(&g_cfg_file_curpath[0], &g_cfg_file_curpath[0], 1); return; } cfg_strncpy(&(g_cfg_file_path[0]), &(g_cfg_file_cachedreal[0]), CFG_PATH_MAX); cfg_strlcat(&(g_cfg_file_path[0]), str, CFG_PATH_MAX); } else { // just use cfg_file_curpath directly cfg_strncpy(&g_cfg_file_path[0], &g_cfg_file_curpath[0], CFG_PATH_MAX); } ret = cfg_stat(&g_cfg_file_path[0], &stat_buf, 0); stat_errno = errno; fmt = stat_buf.st_mode & S_IFMT; cfg_printf("Stat'ing %s, st_mode is: %08x\n", &g_cfg_file_path[0], (int)stat_buf.st_mode); if((ret == 0) && (fmt == S_IFDIR) && is_cmd_key_down && (g_cfg_newdisk_select != 2)) { // Make a new DynaPro disk cfg_insert_disk_dynapro((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); g_cfg_slotdrive = 0; // End file selection g_cfg_newdisk_select = 0; g_menuptr = &g_cfg_disk_menu[0]; } else if((g_cfg_newdisk_select == 1) && (g_cfg_newdisk_type == 3) && g_cfg_file_pathfield && (fmt == S_IFDIR)) { // Special handling for Dynamic ProDOS directories. User hit // return in the Path field on a directory, use this directory cfg_create_new_image(); } if(ret != 0) { if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { // This looks good, a new file name was entered if(stat_errno == ENOENT) { cfg_create_new_image(); } else { printf("Unknown errno:%d while checking %s\n", stat_errno, &g_cfg_file_path[0]); } } else { printf("stat %s returned %d, errno: %d\n", &g_cfg_file_path[0], ret, stat_errno); } } else if(fmt == S_IFDIR) { /* it's a directory */ cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_file_path[0], CFG_PATH_MAX); } else if(g_cfg_newdisk_select) { // Do not allow selecting files, just ignore it } else if((g_cfg_slotdrive & 0xfff) < 0xfff) { /* select it */ ret = cfg_maybe_insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); if(ret > 0) { g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; } } else { cfg_file_update_ptr(g_cfg_file_strptr, &g_cfg_file_path[0], 1); g_cfg_slotdrive = 0; g_cfg_newdisk_select = 0; } } void cfg_file_handle_key(int key) { Cfg_listhdr *listhdrptr; int len, lowkey, got_match_key, is_cmd_key_down; // Modes: g_cfg_slotdrive: 1 to 0xfff: File selection dialog // otherwise: normal menu being shown // g_cfg_file_pathfield: File selection with cursor in Path: field // otherwise: in scrolling file selection field // g_cfg_select_partition: file selection for partition name if(g_cfg_file_pathfield) { if(key >= 0x20 && key < 0x7f) { len = (int)strlen(&g_cfg_file_curpath[0]); if(len < CFG_PATH_MAX-4) { g_cfg_file_curpath[len] = key; g_cfg_file_curpath[len+1] = 0; } return; } } listhdrptr = &g_cfg_dirlist; is_cmd_key_down = 0; if(g_cfg_select_partition > 0) { listhdrptr = &g_cfg_partitionlist; is_cmd_key_down = adb_is_cmd_key_down() && ((g_cfg_slotdrive & 0xfff) != 0xfff); } lowkey = tolower(key); got_match_key = 0; if((g_cfg_file_pathfield == 0) && (lowkey >= 'a') && (lowkey <= 'z') && !is_cmd_key_down) { /* jump to file starting with this letter */ g_cfg_file_match[0] = key; g_cfg_file_match[1] = 0; g_cfg_dirlist.invalid = 1; /* re-read directory */ got_match_key = 1; } switch(key) { case 0x1b: // ESC if(((g_cfg_slotdrive & 0xfff) < 0xfff) && !g_cfg_newdisk_select) { iwm_eject_disk_by_num((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff); } g_cfg_slotdrive = 0; g_cfg_select_partition = -1; g_cfg_dirlist.invalid = 1; g_cfg_newdisk_select = 0; break; case 0x0a: /* down arrow */ if(g_cfg_file_pathfield == 0) { listhdrptr->curent++; cfg_fix_topent(listhdrptr); } break; case 0x0b: /* up arrow */ if(g_cfg_file_pathfield == 0) { listhdrptr->curent--; cfg_fix_topent(listhdrptr); } break; case 0x0d: /* return */ //printf("handling return press\n"); cfg_file_selected(); break; case 0x61: /* 'a' */ if(is_cmd_key_down && (g_cfg_select_partition > 0)) { cfg_partition_select_all(); } break; case 0x09: /* tab */ g_cfg_file_pathfield = !g_cfg_file_pathfield; if(g_cfg_select_partition > 0) { // If selecting file inside zip or partition, don't // allow editing of the Path info g_cfg_file_pathfield = 0; } break; case 0x08: /* left arrow */ case 0x7f: /* delete key */ if(g_cfg_file_pathfield) { // printf("left arrow/delete\n"); len = (int)strlen(&g_cfg_file_curpath[0]) - 1; if(len >= 0) { g_cfg_file_curpath[len] = 0; } } break; default: if(!got_match_key) { printf("key: %02x\n", key); } } #if 0 printf("curent: %d, topent: %d, last: %d\n", g_cfg_dirlist.curent, g_cfg_dirlist.topent, g_cfg_dirlist.last); #endif } void cfg_draw_menu() { const char *str; Cfg_menu *menuptr; int print_eject_help, line, type, match_found, menu_line, max_line; g_menu_redraw_needed = 0; menuptr = g_menuptr; if(menuptr == 0) { menuptr = g_cfg_main_menu; } if(g_rom_version < 0) { /* Must select ROM file */ menuptr = g_cfg_rom_menu; } g_menuptr = menuptr; cfg_home(); line = 1; max_line = 1; match_found = 0; print_eject_help = 0; menu_line = g_menu_line; cfg_printf("%s\n\n", menuptr[0].str); while(line < 24) { str = menuptr[line].str; type = menuptr[line].cfgtype; if(str == 0) { break; } if((type & 0xf) == CFGTYPE_DISK) { print_eject_help = 1; } #if 0 printf("Calling parse_menu line:%d, menu_line:%d, %p\n", line, menu_line, menuptr); #endif cfg_parse_menu(menuptr, line, menu_line, 0); if(line == g_menu_line) { if(type != 0) { match_found = 1; } else if(g_menu_inc) { menu_line++; } else { menu_line--; } } if(line > max_line) { max_line = line; } cfg_printf("%s\n", g_cfg_opt_buf); line++; } if((menu_line < 1) && !match_found) { menu_line = 1; } if((menu_line >= max_line) && !match_found) { menu_line = max_line; } g_menu_line = menu_line; g_menu_max_line = max_line; if(!match_found) { g_menu_redraw_needed = 1; } if(g_rom_version < 0) { cfg_htab_vtab(0, 21); cfg_printf("\bYOU MUST SELECT A VALID ROM FILE\b\n"); } cfg_htab_vtab(0, 23); cfg_printf("Move: \tJ\t \tK\t Change: \tH\t \tU\t \tM\t"); if(print_eject_help) { cfg_printf(" Eject: "); if((g_cfg_slotdrive & 0xfff) > 0) { cfg_printf("\bESC\b"); } else { cfg_printf("E"); cfg_printf(" New image: N Dup image: D Verify: V"); } } if((g_cfg_slotdrive & 0xfff) > 0) { cfg_printf(" Edit Path: \bTAB\b"); if(g_cfg_select_partition > 0) { cfg_printf(" (\bCmd\b-\bA\b to mount all)"); } else if((g_cfg_newdisk_type == 3) || !g_cfg_newdisk_select) { // Dynamic ProDOS, select a directory cfg_printf(" (\bCmd\b-\bEnter\b for DynaPro)"); } if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { cfg_printf(" (Enter new name on Path)"); } } #if 0 cfg_htab_vtab(0, 22); cfg_printf("menu_line: %d line: %d, vbl:%d, adb:%d key_dn:%d\n", menu_line, line, g_cfg_vbl_count, g_adb_repeat_vbl, g_key_down); #endif if((g_cfg_slotdrive & 0xfff) > 0) { cfg_file_draw(); } } void cfg_newdisk_pick_menu(word32 slotdrive) { slotdrive = slotdrive & 0xfff; g_cfg_newdisk_slotdrive = slotdrive; // 0x601: s6d2, 0x500: s5d1 g_menu_line = 1; //printf("N key, g_menuptr=%p\n", g_menuptr); g_cfg_newdisk_type_default = 1; g_cfg_newdisk_type = 1; g_cfg_newdisk_blocks_default = 140*2; g_cfg_newdisk_blocks = 280; if((slotdrive >> 8) == 6) { g_menuptr = g_cfg_newslot6_menu; } else if((slotdrive >> 8) == 5) { g_menuptr = g_cfg_newslot5_menu; g_cfg_newdisk_blocks_default = 1600; g_cfg_newdisk_blocks = 1600; } else { g_menuptr = g_cfg_newslot7_menu; g_cfg_newdisk_blocks_default = 65535; g_cfg_newdisk_blocks = 65535; } } int cfg_control_panel_update() { int ret; int i; ret = cfg_control_panel_update1(); if(g_cfg_screen_changed) { for(i = 0; i < 24; i++) { video_draw_a2_string(i, &g_cfg_screen[i][0]); } } g_cfg_screen_changed = 0; return ret; } void cfg_edit_mode_key(int key) { char *new_str; int *iptr; int len, ival; len = (int)strlen(&g_cfg_edit_buf[0]); if(key == 0x0d) { // Return // Try to accept the change new_str = kegs_malloc_str(&g_cfg_edit_buf[0]); if(g_cfg_edit_type == CFGTYPE_STR) { cfg_file_update_ptr(g_cfg_edit_ptr, new_str, 1); } else if(g_cfg_edit_type == CFGTYPE_INT) { ival = strtol(&g_cfg_edit_buf[0], 0, 0); iptr = (int *)g_cfg_edit_ptr; cfg_int_update(iptr, ival); } g_cfg_edit_ptr = 0; g_config_kegs_update_needed = 1; } else if(key == 0x1b) { // ESC g_cfg_edit_ptr = 0; // Abort out of edit mode, no changes } else if((key == 0x08) || (key == 0x7f)) { // Left arrow or Delete len--; if(len >= 0) { g_cfg_edit_buf[len] = 0; } } else if((key >= 0x20) && (key < 0x7f)) { if(len < (CFG_OPT_MAXSTR - 3)) { g_cfg_edit_buf[len] = key; g_cfg_edit_buf[len+1] = 0; } } } int cfg_control_panel_update1() { char *(*fn_ptr)(int); void *ptr; char **str_ptr; int *iptr; int type, key; while(g_config_control_panel) { if(g_menu_redraw_needed) { cfg_draw_menu(); } if(g_menu_redraw_needed) { cfg_draw_menu(); } key = adb_read_c000(); if(key & 0x80) { key = key & 0x7f; (void)adb_access_c010(); } else { return 0; // No keys } g_menu_redraw_needed = 1; // If we get here, we got a key, figure out what to do with it if(g_cfg_slotdrive & 0xfff) { cfg_file_handle_key(key); continue; } if(g_cfg_edit_ptr) { cfg_edit_mode_key(key); continue; } // Normal menu system switch(key) { case 0x0a: /* down arrow */ g_menu_line++; g_menu_inc = 1; break; case 0x0b: /* up arrow */ g_menu_line--; g_menu_inc = 0; if(g_menu_line < 1) { g_menu_line = 1; } break; case 0x15: /* right arrow */ cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, 1); break; case 0x08: /* left arrow */ cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, -1); break; case 0x0d: type = g_menuptr[g_menu_line].cfgtype; ptr = g_menuptr[g_menu_line].ptr; switch(type & 0xf) { case CFGTYPE_MENU: g_menuptr = (Cfg_menu *)ptr; g_menu_line = 1; break; case CFGTYPE_DISK: g_cfg_slotdrive = (type >> 4) & 0xfff; cfg_file_init(); break; case CFGTYPE_FUNC: fn_ptr = (char * (*)(int))ptr; (void)(*fn_ptr)(0); break; case CFGTYPE_FILE: g_cfg_slotdrive = 0xfff; g_cfg_file_def_name = *((char **)ptr); g_cfg_file_strptr = (char **)ptr; cfg_file_init(); break; case CFGTYPE_STR: str_ptr = (char **)ptr; if(str_ptr) { g_cfg_edit_type = type & 0xf; g_cfg_edit_ptr = str_ptr; cfg_strncpy(&g_cfg_edit_buf[0], *str_ptr, CFG_OPT_MAXSTR); } break; case CFGTYPE_INT: // If there are no ',' in the menu str, then // allow user to enter a manual number if(!strchr(g_menuptr[g_menu_line].str, ',')) { g_cfg_edit_type = type & 0xf; g_cfg_edit_ptr = ptr; iptr = (int *)ptr; snprintf(&g_cfg_edit_buf[0], CFG_OPT_MAXSTR, "%d", *iptr); } } break; case 0x1b: // Jump to last menu entry g_menu_line = g_menu_max_line; break; case 'd': case 'D': // Duplicate an image type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_dup_existing_image(type >> 4); } break; case 'e': case 'E': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { iwm_eject_disk_by_num(type >> 12, (type >> 4) & 0xff); } break; case 'l': case 'L': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_toggle_lock_disk(type >> 4); } break; case 'n': case 'N': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_newdisk_pick_menu(type >> 4); } break; case 'v': case 'V': type = g_menuptr[g_menu_line].cfgtype; if((type & 0xf) == CFGTYPE_DISK) { cfg_validate_image(type >> 4); } break; default: printf("key: %02x\n", key); } } return 0; } ================================================ FILE: upstream/kegs/src/config.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_config_h[] = "@(#)$KmKId: config.h,v 1.12 2023-08-28 01:59:55+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2019 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #define CONF_BUF_LEN 1024 #define COPY_BUF_SIZE 4096 #define CFG_PRINTF_BUFSIZE 2048 #define CFG_PATH_MAX 1024 #define CFG_NUM_SHOWENTS 16 #define CFGTYPE_MENU 1 #define CFGTYPE_INT 2 #define CFGTYPE_DISK 3 #define CFGTYPE_FUNC 4 #define CFGTYPE_FILE 5 #define CFGTYPE_STR 6 /* CFGTYPE limited to just 4 bits: 0-15 */ /* Cfg_menu, Cfg_dirent and Cfg_listhdr are defined in defc.h */ STRUCT(Cfg_defval) { Cfg_menu *menuptr; int intval; char *strval; }; ================================================ FILE: upstream/kegs/src/cp_kegs_libs ================================================ #!/usr/bin/perl -w # $KmKId: cp_kegs_libs,v 1.2 2021-02-09 00:35:48+00 kentd Exp $ use strict; use English; if($#ARGV < 2) { die "Usage: executable srclib_dir destlib_dir"; } # Runs objdump on the executable, finds all unresolved dependencies, and # then copies each of those libraries from srclib_dir to destlib_dir # destlib_dir should be APPLICATION/Contents/Frameworks/ my $exe = shift; my $srcdir = shift; my $destdir = shift; if(! -f $exe || ! -d $srcdir || ! -d $destdir) { die "$exe is not a file, or $srcdir or $destdir are not a dir"; } my $do_swiftos = 0; open(RPATHS, "objdump -macho -dylibs-used $exe|") or die "Open failed: $!"; my $line; my $lib; foreach $line () { chomp($line); if($line =~ m:\@rpath/([^ ]*) :) { $lib = $1; print "lib: $lib\n"; `cp $srcdir/$lib $destdir/`; } $do_swiftos = 1; } # And copy libswiftos.dylib if we copied any files if($do_swiftos) { `cp $srcdir/libswiftos.dylib $destdir/`; } ================================================ FILE: upstream/kegs/src/debugger.c ================================================ const char rcsid_debugger_c[] = "@(#)$KmKId: debugger.c,v 1.60 2023-09-11 12:55:28+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include #include #include "defc.h" #include "disas.h" #define LINE_SIZE 160 /* Input buffer size */ #define PRINTF_BUF_SIZE 239 #define DEBUG_ENTRY_MAX_CHARS 80 STRUCT(Debug_entry) { byte str_buf[DEBUG_ENTRY_MAX_CHARS]; }; char g_debug_printf_buf[PRINTF_BUF_SIZE]; char g_debug_stage_buf[PRINTF_BUF_SIZE]; int g_debug_stage_pos = 0; Debug_entry *g_debug_lines_ptr = 0; int g_debug_lines_total = 0; int g_debug_lines_pos = 0; int g_debug_lines_alloc = 0; int g_debug_lines_max = 1024*1024; int g_debug_lines_view = -1; int g_debug_lines_viewable_lines = 20; int g_debugwin_changed = 1; int g_debug_to_stdout = 1; extern byte *g_memory_ptr; extern byte *g_slow_memory_ptr; extern int g_halt_sim; extern word32 g_c068_statereg; extern word32 stop_run_at; extern int Verbose; extern int Halt_on; extern int g_a2_key_to_ascii[][4]; extern Kimage g_debugwin_kimage; extern int g_config_control_panel; extern word32 g_mem_size_total; extern char *g_sound_file_str; extern word32 g_sound_file_bytes; int g_num_breakpoints = 0; Break_point g_break_pts[MAX_BREAK_POINTS]; extern int g_irq_pending; extern dword64 g_last_vbl_dcyc; extern int g_ret1; extern Engine_reg engine; extern dword64 g_dcycles_end; int g_stepping = 0; word32 g_list_kpc; int g_hex_line_len = 0x10; word32 g_a1 = 0; word32 g_a2 = 0; word32 g_a3 = 0; word32 g_a4 = 0; word32 g_a1bank = 0; word32 g_a2bank = 0; word32 g_a3bank = 0; word32 g_a4bank = 0; #define MAX_CMD_BUFFER 229 #define PC_LOG_LEN (2*1024*1024) Pc_log g_pc_log_array[PC_LOG_LEN + 2]; Data_log g_data_log_array[PC_LOG_LEN + 2]; word32 g_log_pc_enable = 0; Pc_log *g_log_pc_ptr = &(g_pc_log_array[0]); Pc_log *g_log_pc_start_ptr = &(g_pc_log_array[0]); Pc_log *g_log_pc_end_ptr = &(g_pc_log_array[PC_LOG_LEN]); Data_log *g_log_data_ptr = &(g_data_log_array[0]); Data_log *g_log_data_start_ptr = &(g_data_log_array[0]); Data_log *g_log_data_end_ptr = &(g_data_log_array[PC_LOG_LEN]); char g_cmd_buffer[MAX_CMD_BUFFER + 2] = { 0 }; int g_cmd_buffer_len = 2; #define MAX_DISAS_BUF 150 char g_disas_buffer[MAX_DISAS_BUF]; void debugger_init() { debugger_help(); g_list_kpc = engine.kpc; #if 0 if(g_num_breakpoints == 0) { set_bp(0xff5a0e, 0xff5a0e, 4); set_bp(0x00c50a, 0x00c50a, 4); set_bp(0x00c50d, 0x00c50d, 4); } #endif } int g_dbg_new_halt = 0; void check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type) { Break_point *bp_ptr; int count; int i; count = g_num_breakpoints; for(i = 0; i < count; i++) { bp_ptr = &(g_break_pts[i]); if((type & bp_ptr->acc_type) == 0) { continue; } if((addr >= (bp_ptr->start_addr & 0xffffff)) && (addr <= (bp_ptr->end_addr & 0xffffff))) { debug_hit_bp(addr, dfcyc, maybe_stack, type, i); } } if((type == 4) && ((addr == 0xe10000) || (addr == 0xe10004))) { FINISH(RET_TOOLTRACE, 0); } } void debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, int pos) { word32 trk_side, side, trk, cmd, unit, buf, blk, param_cnt, list_ptr; word32 status_code, cmd_list, stack, rts; if((addr == 0xff5a0e) && (type == 4)) { trk_side = get_memory_c(0xe10f32); side = (trk_side >> 5) & 1; trk = get_memory_c(0xe10f34) + ((trk_side & 0x1f) << 6); buf = get_memory_c(0x42) | (get_memory_c(0x43) << 8) | (get_memory_c(0x44) << 16); printf("ff5a0e: 3.5 read of track %03x side:%d sector:%03x to " "%06x at %016llx\n", trk, side, get_memory_c(0xe10f33), buf, dfcyc); return; } if((addr == 0x00c50a) && (type == 4)) { cmd = get_memory_c(0x42); unit = get_memory_c(0x43); buf = get_memory_c(0x44) | (get_memory_c(0x45) << 8); blk = get_memory_c(0x46) | (get_memory_c(0x47) << 8); printf("00c50a: cmd %02x u:%02x buf:%04x blk:%04x at %016llx\n", cmd, unit, buf, blk, dfcyc); return; } if((addr == 0x00c50d) && (type == 4)) { stack = maybe_stack & 0xffff; rts = get_memory_c(stack + 1) | (get_memory_c(stack + 2) << 8); cmd = get_memory_c(rts + 1); cmd_list = get_memory_c(rts + 2) | (get_memory_c(rts+3) << 8); param_cnt = get_memory_c(cmd_list); unit = get_memory_c(cmd_list + 1); list_ptr = get_memory_c(cmd_list + 2) | (get_memory_c(cmd_list + 3) << 8); status_code = get_memory_c(cmd_list + 4); printf("00c50d: stack:%04x rts:%04x cmd:%02x cmd_list:%04x " "param_cnt:%02x unit:%02x listptr:%04x " "status:%02x at %016llx\n", stack, rts, cmd, cmd_list, param_cnt, unit, list_ptr, status_code, dfcyc); printf(" list_ptr: %04x: %02x %02x %02x %02x %02x %02x %02x\n", list_ptr, get_memory_c(list_ptr), get_memory_c(list_ptr + 1), get_memory_c(list_ptr + 2), get_memory_c(list_ptr + 3), get_memory_c(list_ptr + 4), get_memory_c(list_ptr + 5), get_memory_c(list_ptr + 6)); return; } dbg_log_info(dfcyc, addr, pos, 0x6270); halt2_printf("Hit breakpoint at %06x\n", addr); } int debugger_run_16ms() { // Called when g_halt_sim is set if(g_dbg_new_halt) { g_list_kpc = engine.kpc; show_regs(); } g_dbg_new_halt = 0; adb_nonmain_check(); // printf("debugger_run_16ms: g_halt_sim:%d\n", g_halt_sim); return 0; } void dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type) { if(dfcyc == 0) { return; // Ignore some IWM t:00e7 events and others } g_log_data_ptr->dfcyc = dfcyc; g_log_data_ptr->stat = 0; g_log_data_ptr->addr = info1; g_log_data_ptr->val = info2; g_log_data_ptr->size = type; // type must be > 4 g_log_data_ptr++; if(g_log_data_ptr >= g_log_data_end_ptr) { g_log_data_ptr = g_log_data_start_ptr; } } void debugger_update_list_kpc() { g_dbg_new_halt = 1; } void debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up) { word32 c025_val, special; int key, pos, changed; pos = 1; c025_val = kimage_ptr->c025_val; if(c025_val & 1) { // Shift is down pos = 2; } else if(c025_val & 4) { // Capslock is down key = g_a2_key_to_ascii[a2code][1]; if((key >= 'a') && (key <= 'z')) { pos = 2; // CAPS LOCK on } } if(c025_val & 2) { // Ctrl is down pos = 3; } key = g_a2_key_to_ascii[a2code][pos]; if(key < 0) { return; } special = (key >> 8) & 0xff; // c025 changes if(is_up) { c025_val = c025_val & (~special); } else { c025_val = c025_val | special; } kimage_ptr->c025_val = c025_val; if(is_up) { return; // Nothing else to do } if(key >= 0x80) { // printf("key: %04x\n", key); if(key == 0x8007) { // F7 - close debugger video_set_active(kimage_ptr, !kimage_ptr->active); printf("Toggled debugger window to:%d\n", kimage_ptr->active); } if((key & 0xff) == 0x74) { // Page up keycode debugger_page_updown(1); } if((key & 0xff) == 0x79) { // Page down keycode debugger_page_updown(-1); } return; } pos = g_cmd_buffer_len; changed = 0; if((key >= 0x20) && (key < 0x7f)) { // printable character, add it if(pos < MAX_CMD_BUFFER) { // printf("cmd[%d]=%c\n", pos, key); g_cmd_buffer[pos++] = key; changed = 1; } } else if((key == 0x08) || (key == 0x7f)) { // Left arrow or backspace if(pos > 2) { pos--; changed = 1; } } else if((key == 0x0d) || (key == 0x0a)) { //dbg_printf("Did return, pos:%d, str:%s\n", pos, g_cmd_buffer); do_debug_cmd(&g_cmd_buffer[2]); pos = 2; changed = 1; } else { // printf("ctrl key:%04x\n", key); } g_cmd_buffer[pos] = 0; g_cmd_buffer_len = pos; g_debug_lines_view = -1; g_debugwin_changed |= changed; // printf("g_cmd_buffer: %s\n", g_cmd_buffer); } void debugger_page_updown(int isup) { int view, max; view = g_debug_lines_view; if(view < 0) { view = 0; } view = view + (isup*g_debug_lines_viewable_lines); if(view < 0) { view = -1; } max = g_debug_lines_pos; if(g_debug_lines_alloc >= g_debug_lines_max) { max = g_debug_lines_alloc - 4; } view = MY_MIN(view, max - g_debug_lines_viewable_lines); // printf("new view:%d, was:%d\n", view, g_debug_lines_view); if(view != g_debug_lines_view) { g_debug_lines_view = view; g_debugwin_changed++; } } void debugger_redraw_screen(Kimage *kimage_ptr) { int line, vid_line, back, border_top, save_pos, num, lines_done; int save_view, save_to_stdout; int i; if((g_debugwin_changed == 0) || (kimage_ptr->active == 0)) { return; // Nothing to do } save_pos = g_debug_lines_pos; save_view = g_debug_lines_view; // printf("DEBUGGER drawing SCREEN!\n"); g_cmd_buffer[0] = '>'; g_cmd_buffer[1] = ' '; g_cmd_buffer[g_cmd_buffer_len] = 0xa0; // Cursor: inverse space g_cmd_buffer[g_cmd_buffer_len+1] = 0; save_to_stdout = g_debug_to_stdout; g_debug_to_stdout = 0; dbg_printf("%s\n", &g_cmd_buffer[0]); g_cmd_buffer[g_cmd_buffer_len] = 0; dbg_printf("g_halt_sim:%02x\n", g_halt_sim); border_top = 8; g_debug_to_stdout = save_to_stdout; vid_line = (((kimage_ptr->a2_height - 2*border_top) / 16) * 8) - 1; num = g_debug_lines_pos - save_pos; if(num < 0) { num = num + g_debug_lines_alloc; } if(num > 4) { // printf("num is > 4!\n"); num = 4; } for(i = 0; i < num; i++) { line = debug_get_view_line(i); debug_draw_debug_line(kimage_ptr, line, vid_line); vid_line -= 8; } g_debug_lines_pos = save_pos; g_debug_lines_view = save_view; back = save_view; if(back < 0) { // -1 means always show most recent back = 0; } lines_done = 0; while(vid_line >= border_top) { line = debug_get_view_line(back); debug_draw_debug_line(kimage_ptr, line, vid_line); back++; vid_line -= 8; lines_done++; #if 0 printf(" did a line, line is now: %d after str:%s\n", line, str); #endif } g_debug_lines_viewable_lines = lines_done; g_debugwin_changed = 0; kimage_ptr->x_refresh_needed = 1; // printf("x_refresh_needed = 1, viewable_lines:%d\n", lines_done); } void debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line) { word32 line_bytes; int i; // printf("draw debug line:%d at vid_line:%d\n", line, vid_line); for(i = 7; i >= 0; i--) { line_bytes = (vid_line << 16) | (40 << 8) | 0; redraw_changed_string(&(g_debug_lines_ptr[line].str_buf[0]), line_bytes, -1L, kimage_ptr->wptr + 8, 0, 0x00ffffff, kimage_ptr->a2_width_full, 1); vid_line--; } } Dbg_longcmd g_debug_bp_clear[] = { { "all", debug_bp_clear_all, 0, "clear all breakpoints" }, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_bp[] = { { "set", debug_bp_set, 0, "Set breakpoint: ADDR or ADDR0-ADDR1" }, { "clear", debug_bp_clear, &g_debug_bp_clear[0], "Clear breakpoint: ADDR OR ADDR0-ADDR1"}, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_logpc[] = { { "on", debug_logpc_on, 0, "Turn on logging of pc and data" }, { "off", debug_logpc_off,0, "Turn off logging of pc and data" }, { "save", debug_logpc_save,0, "logpc save FILE: save to file" }, { 0, 0, 0, 0 } }; Dbg_longcmd g_debug_iwm[] = { { "check", debug_iwm_check, 0, "Denibblize current track" }, { 0, 0, 0, 0 } }; // Main table of commands Dbg_longcmd g_debug_longcmds[] = { { "help", debug_help, 0, "Help" }, { "bp", debug_bp, &g_debug_bp[0], "bp ADDR: sets breakpoint on addr" }, { "logpc", debug_logpc, &g_debug_logpc[0], "Log PC" }, { "iwm", debug_iwm, &g_debug_iwm[0], "IWM" }, { "soundfile", debug_soundfile, 0, "Save sound to a WAV file" }, { 0, 0, 0, 0 } }; void debugger_help() { dbg_printf("KEGS Debugger help (courtesy Fredric Devernay\n"); dbg_printf("General command syntax: [bank]/[address][command]\n"); dbg_printf("e.g. 'e1/0010B' to set a breakpoint at the interrupt jump " "pt\n"); dbg_printf("Enter all addresses using lower-case\n"); dbg_printf("As with the IIgs monitor, you can omit the bank number " "after\n"); dbg_printf("having set it: 'e1/0010B' followed by '14B' will set\n"); dbg_printf("breakpoints at e1/0010 and e1/0014\n"); dbg_printf("\n"); dbg_printf("g Go\n"); dbg_printf("[bank]/[addr]g Go from [bank]/[address]\n"); dbg_printf("s Step one instruction\n"); dbg_printf("[bank]/[addr]s Step one instr at [bank]/[addr]\n"); dbg_printf("[bank]/[addr]B Set breakpoint at [bank]/[addr]\n"); dbg_printf("B Show all breakpoints\n"); dbg_printf("[bank]/[addr]D Delete breakpoint at [bank]/" "[addr]\n"); dbg_printf("[bank]/[addr1].[addr2] View memory\n"); dbg_printf("[bank]/[addr]L Disassemble memory\n"); dbg_printf("Z Dump SCC state\n"); dbg_printf("I Dump IWM state\n"); dbg_printf("[drive].[track]I Dump IWM state\n"); dbg_printf("E Dump Ensoniq state\n"); dbg_printf("[osc]E Dump oscillator [osc] state\n"); dbg_printf("R Dump dtime array and events\n"); dbg_printf("T Show toolbox log\n"); dbg_printf("[bank]/[addr]T Dump tools using ptr [bank]/" "[addr]\n"); dbg_printf(" as 'tool_set_info'\n"); dbg_printf("[mode]V XOR verbose with 1=DISK, 2=IRQ,\n"); dbg_printf(" 4=CLK,8=SHADOW,10=IWM,20=DOC,\n"); dbg_printf(" 40=ABD,80=SCC, 100=TEST, 200=" "VIDEO\n"); dbg_printf("[mode]H XOR halt_on with 1=SCAN_INT,\n"); dbg_printf(" 2=IRQ, 4=SHADOW_REG, 8=" "C70D_WRITES\n"); dbg_printf("r Reset\n"); dbg_printf("[0/1]=m Changes m bit for l listings\n"); dbg_printf("[0/1]=x Changes x bit for l listings\n"); dbg_printf("S show_bankptr_bank0 & smartport " "errs\n"); dbg_printf("P show_pmhz\n"); dbg_printf("A show_a2_line_stuff show_adb_log\n"); dbg_printf("Ctrl-e Dump registers\n"); dbg_printf("[bank]/[addr1].[addr2]us[file] Save mem area to [file]\n"); dbg_printf("[bank]/[addr1].[addr2]ul[file] Load mem area from " "[file]\n"); dbg_printf("v Show video information\n"); dbg_printf("q Exit Debugger (and KEGS)\n"); } void dbg_help_show_strs(int help_depth, const char *str, const char *help_str) { const char *blank_str, *pre_str, *post_str; int column, len, blank_len, pre_len, post_len; // Indent by 3*help_depth chars, then output str, then hit // column 14, then output help_str. This can be done in just 2-3 // lines, but I made it longer and clearer to avoid any "overflow" // cases if(help_str == 0) { return; } blank_str = " " " " " "; blank_len = (int)strlen(blank_str); // should be >=17 column = 17; len = (int)strlen(str); if(help_depth < 0) { help_depth = 0; } pre_str = blank_str; pre_len = 3 * help_depth; if(pre_len < blank_len) { pre_str = blank_str + blank_len - pre_len; } post_str = ""; post_len = column - pre_len - len; if((post_len >= 1) && (post_len < blank_len)) { post_str = blank_str + blank_len - post_len; } dbg_printf("%s%s%s: %s\n", pre_str, str, post_str, help_str); } const char * debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, int help_depth) { Dbg_fn *fnptr; Dbg_longcmd *subptr; const char *str, *newstr; int len, c; int i; // See if the command is from the longcmd list while(*line_ptr == ' ') { line_ptr++; // eat spaces } // Output " str :" where : is at column 14 always // printf("dfcit: %s, help_depth:%d\n", line_ptr, help_depth); for(i = 0; i < 1000; i++) { // Provide a limit to avoid hang if table not terminated right str = longptr[i].str; fnptr = longptr[i].fnptr; if(!str) { // End of table break; // No match found } if(help_depth < 0) { // Print the help string for all entries in this table dbg_help_show_strs(-1 - help_depth, str, longptr[i].help_str); continue; } len = (int)strlen(str); if(strncmp(line_ptr, str, len) != 0) { continue; // Not a match } // Ensure next char is either a space, or 0 // Let's us avoid commands which are prefixes, or // which are old Apple II monitor hex+commands c = line_ptr[len]; if((c != 0) && (c != ' ')) { continue; // Not valid } if(help_depth) { dbg_help_show_strs(help_depth, str, longptr[i].help_str); } subptr = longptr[i].subptr; // Try a subcmd first newstr = line_ptr + len; if(subptr != 0) { if(help_depth) { help_depth++; } newstr = debug_find_cmd_in_table(newstr, subptr, help_depth); // If a subcmd was found, newstr is now 0 } if((newstr == 0) || help_depth) { return 0; } if((newstr != 0) && (fnptr != 0)) { (*fnptr)(line_ptr + len); return 0; // Success } } if(help_depth >= 1) { // No subcommands found, print out all entries in this table debug_find_cmd_in_table(line_ptr, longptr, -1 - help_depth); return 0; } return line_ptr; } void do_debug_cmd(const char *in_str) { const char *line_ptr; const char *newstr; int slot_drive, track, ret_val, mode, old_mode, got_num; int save_to_stdout; mode = 0; old_mode = 0; save_to_stdout = g_debug_to_stdout; g_debug_to_stdout = 1; dbg_printf("*%s\n", in_str); line_ptr = in_str; // See if the command is from the longcmd list newstr = debug_find_cmd_in_table(in_str, &(g_debug_longcmds[0]), 0); if(newstr == 0) { g_debug_to_stdout = save_to_stdout; return; // Command found get out } // If we get here, parse an Apple II monitor like command: // {address}{cmd} repeat. while(1) { ret_val = 0; g_a2 = 0; got_num = 0; while(1) { if((mode == 0) && (got_num != 0)) { g_a3 = g_a2; g_a3bank = g_a2bank; g_a1 = g_a2; g_a1bank = g_a2bank; } ret_val = *line_ptr++ & 0x7f; if((ret_val >= '0') && (ret_val <= '9')) { g_a2 = (g_a2 << 4) + ret_val - '0'; got_num = 1; continue; } if((ret_val >= 'a') && (ret_val <= 'f')) { g_a2 = (g_a2 << 4) + ret_val - 'a' + 10; got_num = 1; continue; } if(ret_val == '/') { g_a2bank = g_a2; g_a2 = 0; continue; } break; } old_mode = mode; mode = 0; switch(ret_val) { case 'h': debugger_help(); break; case 'R': show_dtime_array(); show_all_events(); break; case 'I': slot_drive = -1; track = -1; if(got_num) { if(old_mode == '.') { slot_drive = g_a1; } track = g_a2; } iwm_show_track(slot_drive, track, 0); iwm_show_stats(slot_drive); break; case 'E': doc_show_ensoniq_state(); break; case 'T': if(got_num) { show_toolset_tables(g_a2bank, g_a2); } else { show_toolbox_log(); } break; case 'v': if(got_num) { dis_do_compare(); } else { video_show_debug_info(); } break; case 'V': dbg_printf("g_irq_pending: %05x\n", g_irq_pending); dbg_printf("Setting Verbose ^= %04x\n", g_a1); Verbose ^= g_a1; dbg_printf("Verbose is now: %04x\n", Verbose); break; case 'H': dbg_printf("Setting Halt_on ^= %04x\n", g_a1); Halt_on ^= g_a1; dbg_printf("Halt_on is now: %04x\n", Halt_on); break; case 'r': do_reset(); g_list_kpc = engine.kpc; break; case 'm': if(old_mode == '=') { if(!g_a1) { engine.psr &= ~0x20; } else { engine.psr |= 0x20; } if(engine.psr & 0x100) { engine.psr |= 0x30; } } else { dis_do_memmove(); } break; case 'p': dis_do_pattern_search(); break; case 'x': if(old_mode == '=') { if(!g_a1) { engine.psr &= ~0x10; } else { engine.psr |= 0x10; } if(engine.psr & 0x100) { engine.psr |= 0x30; } } break; case 'z': if(old_mode == '=') { stop_run_at = g_a1; dbg_printf("Calling add_event for t:%08x\n", g_a1); add_event_stop(((dword64)g_a1) << 16); dbg_printf("set stop_run_at = %x\n", g_a1); } break; case 'l': case 'L': if(got_num) { g_list_kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } do_debug_list(); break; case 'Z': show_scc_state(); break; case 'S': show_bankptrs_bank0rdwr(); smartport_error(); break; case 'M': show_pmhz(); mockingboard_show(got_num, g_a1); break; case 'A': show_a2_line_stuff(); show_adb_log(); break; case 's': g_stepping = 1; if(got_num) { engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } mode = 's'; g_list_kpc = engine.kpc; break; case 'B': if(got_num) { dbg_printf("got_num:%d, a2bank:%x, g_a2:%x\n", got_num, g_a2bank, g_a2); set_bp((g_a2bank << 16) + g_a2, (g_a2bank << 16) + g_a2, 4); } else { show_bp(); } break; case 'D': if(got_num) { dbg_printf("got_num: %d, a2bank: %x, a2: %x\n", got_num, g_a2bank, g_a2); delete_bp((g_a2bank << 16) + g_a2, (g_a2bank << 16) + g_a2); } break; case 'g': case 'G': dbg_printf("Going..\n"); g_stepping = 0; if(got_num) { engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); } do_go(); g_list_kpc = engine.kpc; break; case 'u': dbg_printf("Unix commands\n"); line_ptr = do_debug_unix(line_ptr, old_mode); break; case ':': case '.': case '+': case '-': case '=': case ',': mode = ret_val; dbg_printf("Setting mode = %x\n", mode); break; case ' ': case '\t': if(!got_num) { mode = old_mode; break; } mode = do_blank(mode, old_mode); break; case '<': g_a4 = g_a2; g_a4bank = g_a2bank; break; case 0x05: /* ctrl-e */ case 'Q': case 'q': show_regs(); break; case 0: // The final null char if(old_mode == 's') { mode = do_blank(mode, old_mode); g_debug_to_stdout = save_to_stdout; return; } if(line_ptr == &in_str[1]) { g_a2 = g_a1 | (g_hex_line_len - 1); show_hex_mem(g_a1bank, g_a1, g_a2, -1); g_a1 = g_a2 + 1; } else { if((got_num == 1) || (mode == 's')) { mode = do_blank(mode, old_mode); } } g_debug_to_stdout = save_to_stdout; return; // Get out, all done break; default: dbg_printf("\nUnrecognized command: %s\n", in_str); g_debug_to_stdout = save_to_stdout; return; } } } word32 dis_get_memory_ptr(word32 addr) { word32 tmp1, tmp2, tmp3; tmp1 = get_memory_c(addr); tmp2 = get_memory_c(addr + 1); tmp3 = get_memory_c(addr + 2); return (tmp3 << 16) + (tmp2 << 8) + tmp1; } void show_one_toolset(FILE *toolfile, int toolnum, word32 addr) { word32 rout_addr; int num_routs; int i; num_routs = dis_get_memory_ptr(addr); fprintf(toolfile, "Tool 0x%02x, table: 0x%06x, num_routs:%03x\n", toolnum, addr, num_routs); if((addr < 0x10000) || (num_routs > 0x100)) { fprintf(toolfile, "addr in page 0, or num_routs too large\n"); return; } for(i = 1; i < num_routs; i++) { rout_addr = dis_get_memory_ptr(addr + 4*i); fprintf(toolfile, "%06x = %02x%02x\n", rout_addr, i, toolnum); } } void show_toolset_tables(word32 a2bank, word32 addr) { FILE *toolfile; word32 tool_addr; int num_tools; int i; addr = (a2bank << 16) + (addr & 0xffff); toolfile = fopen("tool_set_info", "w"); if(toolfile == 0) { fprintf(stderr, "fopen of tool_set_info failed: %d\n", errno); exit(2); } num_tools = dis_get_memory_ptr(addr); fprintf(toolfile, "There are 0x%02x tools using ptr at %06x\n", num_tools, addr); if(num_tools > 40) { fprintf(toolfile, "Too many tools, aborting\n"); num_tools = 0; } for(i = 1; i < num_tools; i++) { tool_addr = dis_get_memory_ptr(addr + 4*i); show_one_toolset(toolfile, i, tool_addr); } fclose(toolfile); } word32 debug_getnum(const char **str_ptr) { const char *str; word32 val; int c, got_num; str = *str_ptr; while(*str == ' ') { str++; } got_num = 0; val = 0; while(1) { c = tolower(*str); //printf("got c:%02x %c val was %08x got_num:%d\n", c, c, val, // got_num); if((c >= '0') && (c <= '9')) { val = (val << 4) + (c - '0'); got_num = 1; } else if((c >= 'a') && (c <= 'f')) { val = (val << 4) + 10 + (c - 'a'); got_num = 1; } else { break; } str++; } *str_ptr = str; if(got_num) { return val; } return (word32)-1L; } char * debug_get_filename(const char **str_ptr) { const char *str, *start_str; char *new_str; int c, len; // Go to first whitespace (or end of str), then kegs_malloc_str() // the string and copy to it str = *str_ptr; start_str = 0; //printf("get_filename, str now :%s:\n", str); while(1) { c = *str++; if(c == 0) { break; } if((c == ' ') || (c == '\t') || (c == '\n')) { //printf("c:%02x at str :%s: , start_str:%p\n", c, str, // start_str); if(start_str) { break; } continue; } // Else it's a valid char, set start_str if needed if(!start_str) { start_str = str - 1; //printf("Got c:%02x, start_str :%s:\n", c, start_str); } } new_str = 0; if(start_str) { len = (int)(str - start_str); if(len > 1) { new_str = malloc(len); memcpy(new_str, start_str, len); new_str[len - 1] = 0; } } *str_ptr = str; return new_str; } void debug_help(const char *str) { dbg_printf("Help:\n"); (void)debug_find_cmd_in_table(str, &(g_debug_longcmds[0]), 1); } void debug_bp(const char *str) { // bp without a following set/clear command. Set a breakpoint if // an address range follows, otherwise just print current breakpoints debug_bp_setclr(str, 0); } void debug_bp_set(const char *str) { debug_bp_setclr(str, 1); } void debug_bp_clear(const char *str) { debug_bp_setclr(str, 2); } void debug_bp_clear_all(const char *str) { if(str) { // Use str to avoid warning } if(g_num_breakpoints) { g_num_breakpoints = 0; setup_pageinfo(); dbg_printf("Deleted all breakpoints\n"); } } void debug_bp_setclr(const char *str, int is_set_clear) { word32 addr, end_addr, acc_type; printf("In debug_bp: %s\n", str); addr = debug_getnum(&str); // printf("getnum ret:%08x\n", addr); if(addr == (word32)-1L) { // No argument show_bp(); return; } end_addr = addr; if(*str == '-') { // Range str++; end_addr = debug_getnum(&str); // printf("end_addr is %08x\n", end_addr); if(end_addr == (word32)-1L) { end_addr = addr; } } acc_type = 4; acc_type = debug_getnum(&str); if(acc_type == (word32)-1L) { acc_type = 4; // Code breakpoint } if(is_set_clear == 2) { // clear delete_bp(addr, end_addr); } else { // set, or nothing set_bp(addr, end_addr, acc_type); } } void debug_soundfile(const char *cmd_str) { char *str; // See if there's an argument str = debug_get_filename(&cmd_str); // str=0 if no argument sound_file_start(str); // str==0 means close file } void debug_logpc(const char *str) { if(str) { // Dummy use of argument } dbg_printf("logpc enable:%d, cur offset:%08lx\n", g_log_pc_enable, (long)(g_log_pc_ptr - g_log_pc_start_ptr)); } void debug_logpc_on(const char *str) { if(str) { // Dummy use of argument } g_log_pc_enable = 1; g_dcycles_end = 0; dbg_printf("Enabled logging of PC and data accesses\n"); } void debug_logpc_off(const char *str) { if(str) { // Dummy use of argument } g_log_pc_enable = 0; g_dcycles_end = 0; dbg_printf("Disabled logging of PC and data accesses\n"); } void debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc) { char *str, *shadow_str; dword64 lstat, offset64, offset64slow, addr64; word32 wstat, addr, size, val; addr = log_data_ptr->addr; lstat = (dword64)(log_data_ptr->stat); wstat = lstat & 0xff; addr64 = lstat - wstat + (addr & 0xff); offset64 = addr64 - (dword64)&(g_memory_ptr[0]); str = "IO"; shadow_str = ""; if((wstat & BANK_SHADOW) || (wstat & BANK_SHADOW2)) { shadow_str = "SHADOWED"; } size = log_data_ptr->size; if(size > 32) { fprintf(pcfile, "INFO %08x %08x %04x t:%04x %lld.%02lld\n", log_data_ptr->addr, log_data_ptr->val, size >> 16, size & 0xffff, (log_data_ptr->dfcyc - start_dcyc)>>16, ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16); } else { offset64slow = addr64 - (dword64)&(g_slow_memory_ptr[0]); if(offset64 < g_mem_size_total) { str = "mem"; } else if(offset64slow < 0x20000) { str = "slow_mem"; offset64 = offset64slow; } else { str = "IO"; offset64 = offset64 & 0xff; } val = log_data_ptr->val; fprintf(pcfile, "DATA set %06x = ", addr); if(size == 8) { fprintf(pcfile, "%02x (8) ", val & 0xff); } else if(size == 16) { fprintf(pcfile, "%04x (16) ", val & 0xffff); } else { fprintf(pcfile, "%06x (%d) ", val, size); } fprintf(pcfile, "%lld.%02lld, %s[%06llx] %s\n", (log_data_ptr->dfcyc - start_dcyc) >> 16, ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16, str, offset64 & 0xffffffULL, shadow_str); } } Data_log * debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, int *count_ptr) { while((*data_wrap_ptr < 2) && (log_data_ptr->dfcyc <= dfcyc) && (log_data_ptr->dfcyc >= start_dcyc)) { if(*count_ptr >= PC_LOG_LEN) { break; } debug_logpc_out_data(pcfile, log_data_ptr, base_dcyc); if(log_data_ptr->dfcyc == 0) { break; } log_data_ptr++; (*count_ptr)++; if(log_data_ptr >= g_log_data_end_ptr) { log_data_ptr = g_log_data_start_ptr; (*data_wrap_ptr)++; } } return log_data_ptr; } void debug_logpc_save(const char *cmd_str) { FILE *pcfile; Pc_log *log_pc_ptr; Data_log *log_data_ptr; char *str; dword64 dfcyc, start_dcyc, base_dcyc, max_dcyc; word32 instr, psr, acc, xreg, yreg, stack, direct, dbank, kpc, num; int data_wrap, accsize, xsize, abs_time, data_count; int i; // See if there's an argument num = debug_getnum(&cmd_str); abs_time = 1; if(num != (word32)-1L) { dbg_printf("Doing relative time\n"); abs_time = 0; } pcfile = fopen("logpc_out", "w"); if(pcfile == 0) { fprintf(stderr,"fopen failed...errno: %d\n", errno); exit(2); } log_pc_ptr = g_log_pc_ptr; log_data_ptr = g_log_data_ptr; #if 0 printf("debug_logpc_save called, log_pc_ptr:%p, %p,%p log_data_ptr:%p, " "%p,%p\n", log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr, log_data_ptr, g_log_data_start_ptr, g_log_data_end_ptr); #endif #if 0 fprintf(pcfile, "current pc_log_ptr: %p, start: %p, end: %p\n", log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr); #endif // See if we haven't filled buffer yet if(log_pc_ptr->dfcyc == 0) { log_pc_ptr = g_log_pc_start_ptr; } if(log_data_ptr->dfcyc == 0) { log_data_ptr = g_log_data_start_ptr; data_wrap = 1; } start_dcyc = log_pc_ptr->dfcyc; // Round to an exact usec start_dcyc = (start_dcyc >> 16) << 16; base_dcyc = start_dcyc; if(abs_time) { base_dcyc = 0; // Show absolute time } dfcyc = start_dcyc; data_wrap = 0; data_count = 0; /* find first data entry */ while((data_wrap < 2) && (log_data_ptr->dfcyc < dfcyc)) { log_data_ptr++; if(log_data_ptr >= g_log_data_end_ptr) { log_data_ptr = g_log_data_start_ptr; data_wrap++; } } fprintf(pcfile, "start_dcyc: %016llx, first entry:%016llx\n", start_dcyc, log_pc_ptr->dfcyc); dfcyc = start_dcyc; max_dcyc = dfcyc; for(i = 0; i < PC_LOG_LEN; i++) { dfcyc = log_pc_ptr->dfcyc; log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, base_dcyc, dfcyc, start_dcyc, &data_wrap, &data_count); dbank = (log_pc_ptr->dbank_kpc >> 24) & 0xff; kpc = log_pc_ptr->dbank_kpc & 0xffffff; instr = log_pc_ptr->instr; psr = (log_pc_ptr->psr_acc >> 16) & 0xffff; acc = log_pc_ptr->psr_acc & 0xffff; xreg = (log_pc_ptr->xreg_yreg >> 16) & 0xffff; yreg = log_pc_ptr->xreg_yreg & 0xffff; stack = (log_pc_ptr->stack_direct >> 16) & 0xffff; direct = log_pc_ptr->stack_direct & 0xffff; accsize = 2; xsize = 2; if(psr & 0x20) { accsize = 1; } if(psr & 0x10) { xsize = 1; } str = do_dis(kpc, accsize, xsize, 1, instr, 0); fprintf(pcfile, "%06x] A:%04x X:%04x Y:%04x P:%03x " "S:%04x D:%04x B:%02x %lld.%02lld %s\n", i, acc, xreg, yreg, psr, stack, direct, dbank, (dfcyc - base_dcyc) >> 16, ((dfcyc & 0xffff) * 100) >> 16, str); if((dfcyc == 0) && (i != 0)) { break; } max_dcyc = dfcyc; log_pc_ptr++; if(log_pc_ptr >= g_log_pc_end_ptr) { log_pc_ptr = g_log_pc_start_ptr; } } // Print any more DATA or INFO after last PC entry log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, base_dcyc, max_dcyc + 10 * 65536, start_dcyc, &data_wrap, &data_count); fclose(pcfile); } void set_bp(word32 addr, word32 end_addr, word32 acc_type) { int count; dbg_printf("About to set BP at %06x - %06x, type:%02x\n", addr, end_addr, acc_type); count = g_num_breakpoints; if(count >= MAX_BREAK_POINTS) { dbg_printf("Too many (0x%02x) breakpoints set!\n", count); return; } g_break_pts[count].start_addr = addr; g_break_pts[count].end_addr = end_addr; g_break_pts[count].acc_type = acc_type; g_num_breakpoints = count + 1; fixup_brks(); } void show_bp() { char acc_str[4]; word32 addr, end_addr, acc_type; int i; dbg_printf("Showing breakpoints set\n"); for(i = 0; i < g_num_breakpoints; i++) { addr = g_break_pts[i].start_addr; end_addr = g_break_pts[i].end_addr; acc_type = g_break_pts[i].acc_type; acc_str[0] = ' '; acc_str[1] = ' '; acc_str[2] = ' '; acc_str[3] = 0; if(acc_type & 4) { acc_str[2] = 'X'; } if(acc_type & 2) { acc_str[1] = 'W'; } if(acc_type & 1) { acc_str[0] = 'R'; } if(end_addr != addr) { dbg_printf("bp:%02x: %06x-%06x, t:%02x %s\n", i, addr, end_addr, acc_type, acc_str); } else { dbg_printf("bp:%02x: %06x, t:%02x %s\n", i, addr, acc_type, acc_str); } } } void delete_bp(word32 addr, word32 end_addr) { int count, hit; int i, j; dbg_printf("About to delete BP at %06x\n", addr); count = g_num_breakpoints; hit = -1; for(i = count - 1; i >= 0; i--) { if((g_break_pts[i].start_addr > end_addr) || (g_break_pts[i].end_addr < addr)) { continue; // Not this entry } hit = i; dbg_printf("Deleting brkpoint #0x%02x\n", hit); for(j = i+1; j < count; j++) { g_break_pts[j-1] = g_break_pts[j]; } count--; } g_num_breakpoints = count; if(hit < 0) { dbg_printf("Breakpoint not found!\n"); } else { setup_pageinfo(); } show_bp(); } void debug_iwm(const char *str) { if(str) { // Dummy use of argument } iwm_show_track(-1, -1, 0); } void debug_iwm_check(const char *str) { if(str) { // Dummy use of argument } iwm_check_nibblization(0); } int do_blank(int mode, int old_mode) { int tmp; switch(old_mode) { case 's': tmp = g_a2; if(tmp == 0) { tmp = 1; } #if 0 for(i = 0; i < tmp; i++) { g_stepping = 1; do_step(); if(g_halt_sim != 0) { break; } } #endif g_list_kpc = engine.kpc; /* video_update_through_line(262); */ break; case ':': set_memory_c(((g_a3bank << 16) + g_a3), g_a2, 0); g_a3++; mode = old_mode; break; case '.': case 0: xam_mem(-1); break; case ',': xam_mem(16); break; case '+': dbg_printf("%x\n", g_a1 + g_a2); break; case '-': dbg_printf("%x\n", g_a1 - g_a2); break; default: dbg_printf("Unknown mode at space: %d\n", old_mode); break; } return mode; } void do_go() { /* also called by do_step */ g_config_control_panel = 0; clear_halt(); } void do_step() { int size_mem_imm, size_x_imm; return; // This is not correct do_go(); size_mem_imm = 2; if(engine.psr & 0x20) { size_mem_imm = 1; } size_x_imm = 2; if(engine.psr & 0x10) { size_x_imm = 1; } dbg_printf("%s\n", do_dis(engine.kpc, size_mem_imm, size_x_imm, 0, 0, 0)); } void xam_mem(int count) { show_hex_mem(g_a1bank, g_a1, g_a2, count); g_a1 = g_a2 + 1; } void show_hex_mem(word32 startbank, word32 start, word32 end, int count) { char ascii[MAXNUM_HEX_PER_LINE]; word32 i; int val, offset; if(count < 0) { count = 16 - (start & 0xf); } offset = 0; ascii[0] = 0; dbg_printf("Showing hex mem: bank: %x, start: %x, end: %x\n", startbank, start, end); for(i = start; i <= end; i++) { if( (i==start) || (count == 16) ) { dbg_printf("%04x:",i); } dbg_printf(" %02x", get_memory_c((startbank <<16) + i)); val = get_memory_c((startbank << 16) + i) & 0x7f; if((val < 32) || (val >= 0x7f)) { val = '.'; } ascii[offset++] = val; ascii[offset] = 0; count--; if(count <= 0) { dbg_printf(" %s\n", ascii); offset = 0; ascii[0] = 0; count = 16; } } if(offset > 0) { dbg_printf(" %s\n", ascii); } } void do_debug_list() { char *str; int size, size_mem_imm, size_x_imm; int i; dbg_printf("%d=m %d=x %d=LCBANK\n", (engine.psr >> 5)&1, (engine.psr >> 4) & 1, (g_c068_statereg & 0x4) >> 2); size_mem_imm = 2; if(engine.psr & 0x20) { size_mem_imm = 1; } size_x_imm = 2; if(engine.psr & 0x10) { size_x_imm = 1; } for(i = 0; i < 20; i++) { str = do_dis(g_list_kpc, size_mem_imm, size_x_imm, 0, 0, &size); g_list_kpc += size; dbg_printf("%s\n", str); } } void dis_do_memmove() { word32 val; dbg_printf("Memory move from %02x/%04x.%04x to %02x/%04x\n", g_a1bank, g_a1, g_a2, g_a4bank, g_a4); while(g_a1 <= (g_a2 & 0xffff)) { val = get_memory_c((g_a1bank << 16) + g_a1); set_memory_c((g_a4bank << 16) + g_a4, val, 0); g_a1++; g_a4++; } g_a1 = g_a1 & 0xffff; g_a4 = g_a4 & 0xffff; } void dis_do_pattern_search() { #if 0 word32 match_val, val; int match_shift, count; dbg_printf("Memory pattern search for %04x in %02x/%04x to %02x/%04x\n", g_a4, g_a1bank, g_a1, g_a2bank, g_a2); match_shift = 0; count = 0; match_val = g_a4; while(1) { if(g_a1bank > g_a2bank) { break; } if(g_a1 > g_a2) { break; } val = get_memory_c((g_a1bank << 16) + g_a1); if(val == ((match_val >> match_shift) & 0xff)) { match_shift += 8; if(match_shift >= 16) { dbg_printf("Found %04x at %02x/%04x\n", match_val, g_a1bank, g_a1); count++; } } else { match_shift = 0; } g_a1++; if(g_a1 >= 0x10000) { g_a1 = 0; g_a1bank++; } } #endif } void dis_do_compare() { word32 val1, val2; dbg_printf("Memory Compare from %02x/%04x.%04x with %02x/%04x\n", g_a1bank, g_a1, g_a2, g_a4bank, g_a4); while(g_a1 <= (g_a2 & 0xffff)) { val1 = get_memory_c((g_a1bank << 16) + g_a1); val2 = get_memory_c((g_a4bank << 16) + g_a4); if(val1 != val2) { dbg_printf("%02x/%04x: %02x vs %02x\n", g_a1bank, g_a1, val1, val2); } g_a1++; g_a4++; } g_a1 = g_a1 & 0xffff; g_a4 = g_a4 & 0xffff; } const char * do_debug_unix(const char *str, int old_mode) { char localbuf[LINE_SIZE+2]; byte *bptr; word32 offset, len, a1_val; long ret; int fd, load; int i; load = 0; switch(*str++) { case 'l': case 'L': dbg_printf("Loading.."); load = 1; break; case 's': case 'S': dbg_printf("Saving..."); break; default: dbg_printf("Unknown unix command: %c\n", *(str - 1)); if(str[-1] == 0) { return str - 1; } return str; } while((*str == ' ') || (*str == '\t')) { str++; } i = 0; while(i < LINE_SIZE) { localbuf[i++] = *str++; if((*str==' ') || (*str == '\t') || (*str == '\n') || (*str == 0)) { break; } } localbuf[i] = 0; dbg_printf("About to open: %s,len: %d\n", localbuf, (int)strlen(localbuf)); if(load) { fd = open(localbuf, O_RDONLY | O_BINARY); } else { fd = open(localbuf, O_WRONLY | O_CREAT | O_BINARY, 0x1b6); } if(fd < 0) { dbg_printf("Open %s failed: %d. errno:%d\n", localbuf, fd, errno); return str; } if(load) { offset = g_a1 & 0xffff; len = 0x20000 - offset; } else { if(old_mode == '.') { len = g_a2 - g_a1 + 1; } else { len = 0x100; } } a1_val = (g_a1bank << 16) | g_a1; bptr = &g_memory_ptr[a1_val]; if((g_a1bank >= 0xe0) && (g_a1bank < 0xe2)) { bptr = &g_slow_memory_ptr[a1_val & 0x1ffff]; } if(load) { ret = read(fd, bptr, len); } else { ret = write(fd, bptr, len); } dbg_printf("Read/write: addr %06x for %04x bytes, ret: %lx bytes\n", a1_val, len, ret); if(ret < 0) { dbg_printf("errno: %d\n", errno); } g_a1 = g_a1 + (int)ret; return str; } void do_debug_load() { dbg_printf("Sorry, can't load now\n"); } char * do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, int *size_ptr) { char buffer[MAX_DISAS_BUF]; char buffer2[MAX_DISAS_BUF]; const char *str; word32 val, oldkpc, dtype; int args, type, opcode, signed_val; int i; oldkpc = kpc; if(op_provided) { opcode = (instr >> 24) & 0xff; } else { opcode = (int)get_memory_c(kpc) & 0xff; } kpc++; dtype = disas_types[opcode]; str = disas_opcodes[opcode]; type = dtype & 0xff; args = dtype >> 8; if(args > 3) { if(args == 4) { args = accsize; } else if(args == 5) { args = xsize; } } val = -1; switch(args) { case 0: val = 0; break; case 1: if(op_provided) { val = instr & 0xff; } else { val = get_memory_c(kpc); } break; case 2: if(op_provided) { val = instr & 0xffff; } else { val = get_memory16_c(kpc); } break; case 3: if(op_provided) { val = instr & 0xffffff; } else { val = get_memory24_c(kpc); } break; default: fprintf(stderr, "args out of rang: %d, opcode: %08x\n", args, opcode); break; } kpc += args; if(!op_provided) { instr = (opcode << 24) | (val & 0xffffff); } switch(type) { case ABS: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, val); break; case ABSX: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,X", str, val); break; case ABSY: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,Y", str, val); break; case ABSLONG: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); break; case ABSIND: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x)", str, val); break; case ABSXIND: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x,X)", str, val); break; case IMPLY: case ACCUM: if(args != 0) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s", str); break; case IMMED: if(args == 1) { snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, val); } else if(args == 2) { snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%04x", str, val); } else { dbg_printf("arg # mismatch for opcode %x\n", opcode); } break; case JUST8: case DLOC: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x", str, val); break; case DLOCX: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,X", str, val); break; case DLOCY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,Y", str, val); break; case LONG: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); break; case LONGX: if(args != 3) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x,X", str, val); break; case DLOCIND: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x)", str, val); break; case DLOCINDY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x),Y", str, val); break; case DLOCXIND: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,X)", str, val); break; case DLOCBRAK: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x]", str, val); break; case DLOCBRAKY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x],y", str, val); break; case DISP8: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } signed_val = (signed char)val; snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, (kpc + signed_val) & 0xffff); break; case DISP8S: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,S", str, val & 0xff); break; case DISP8SINDY: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,S),Y", str, val & 0xff); break; case DISP16: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, (word32)(kpc+(signed)(word16)(val)) & 0xffff); break; case MVPMVN: if(args != 2) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,$%02x", str, val & 0xff, val >> 8); break; case SEPVAL: case REPVAL: if(args != 1) { dbg_printf("arg # mismatch for opcode %x\n", opcode); } snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, val); break; default: dbg_printf("argument type: %d unexpected\n", type); break; } g_disas_buffer[0] = 0; snprintf(&g_disas_buffer[0], MAX_DISAS_BUF, "%02x/%04x: %02x ", oldkpc >> 16, oldkpc & 0xffff, opcode); for(i = 1; i <= args; i++) { snprintf(&buffer2[0], MAX_DISAS_BUF, "%02x ", instr & 0xff); cfg_strlcat(&g_disas_buffer[0], &buffer2[0], MAX_DISAS_BUF); instr = instr >> 8; } for(; i < 4; i++) { cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); } cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); cfg_strlcat(&g_disas_buffer[0], &buffer[0], MAX_DISAS_BUF); if(size_ptr) { *size_ptr = args + 1; } return (&g_disas_buffer[0]); } int debug_get_view_line(int back) { int pos; // where back==0 means return pos - 1. pos = g_debug_lines_pos - 1; pos = pos - back; if(pos < 0) { if(g_debug_lines_alloc >= g_debug_lines_max) { pos += g_debug_lines_alloc; } else { return 0; // HACK: return -1 } } return pos; } int debug_add_output_line(char *in_str) { Debug_entry *line_ptr; byte *out_bptr; int pos, alloc, view, used_len, c; int i; // printf("debug_add_output_line %s len:%d\n", in_str, len); pos = g_debug_lines_pos; line_ptr = g_debug_lines_ptr; alloc = g_debug_lines_alloc; if(pos >= alloc) { if(alloc < g_debug_lines_max) { alloc = MY_MAX(2048, alloc*3); alloc = MY_MAX(alloc, pos*3); alloc = MY_MIN(alloc, g_debug_lines_max); line_ptr = realloc(line_ptr, alloc * sizeof(Debug_entry)); printf("realloc. now %p, alloc:%d\n", line_ptr, alloc); g_debug_lines_ptr = line_ptr; g_debug_lines_alloc = alloc; printf("Alloced debug lines to %d\n", alloc); } else { pos = 0; } } // Convert to A2 format chars: set high bit of each byte, 80 chars // per line out_bptr = &(line_ptr[pos].str_buf[0]); used_len = 0; for(i = 0; i < DEBUG_ENTRY_MAX_CHARS; i++) { c = ' '; if(*in_str) { c = *in_str++; used_len++; } c = c ^ 0x80; // Set highbit if not already set out_bptr[i] = c; } pos++; g_debug_lines_pos = pos; g_debug_lines_total++; // For updating the window g_debugwin_changed++; view = g_debug_lines_view; if(view >= 0) { view++; // view is back from pos, so to stay the same, // it must be incremented when pos incs if((view - 50) >= g_debug_lines_max) { // We were viewing the oldest page, and by wrapping // around we're about to wipe out this old data // Jump to most recent data view = -1; } g_debug_lines_view = view; } return used_len; } void debug_add_output_string(char *in_str, int len) { int ret, tries; tries = 0; ret = 0; if(g_debug_to_stdout) { puts(in_str); // Send output to stdout, too } while((len > 0) || (tries == 0)) { // printf("DEBUG: adding str: %s, len:%d, ret:%d\n", in_str, // len, ret); ret = debug_add_output_line(in_str); len -= ret; in_str += ret; tries++; } } void debug_add_output_chars(char *str) { int pos, c, tab_spaces; pos = g_debug_stage_pos; tab_spaces = 0; while(1) { if(tab_spaces > 0) { c = ' '; tab_spaces--; } else { c = *str++; if(c == '\t') { tab_spaces = 7 - (pos & 7); c = ' '; } } pos = MY_MIN(pos, (PRINTF_BUF_SIZE - 1)); if((c == '\n') || (pos >= (PRINTF_BUF_SIZE - 1))) { g_debug_stage_buf[pos] = 0; debug_add_output_string(&g_debug_stage_buf[0], pos); pos = 0; g_debug_stage_pos = 0; continue; } if(c == 0) { g_debug_stage_pos = pos; return; } g_debug_stage_buf[pos++] = c; } } int dbg_printf(const char *fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = dbg_vprintf(fmt, args); va_end(args); return ret; } int dbg_vprintf(const char *fmt, va_list args) { int ret; ret = vsnprintf(&g_debug_printf_buf[0], PRINTF_BUF_SIZE, fmt, args); debug_add_output_chars(&g_debug_printf_buf[0]); return ret; } void halt_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); dbg_vprintf(fmt, args); va_end(args); set_halt(1); } void halt2_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); dbg_vprintf(fmt, args); va_end(args); set_halt(2); } ================================================ FILE: upstream/kegs/src/defc.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_defc_h[] = "@(#)$KmKId: defc.h,v 1.142 2024-09-15 13:56:12+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defcomm.h" #define STRUCT(a) typedef struct a ## _st a; struct a ## _st typedef unsigned char byte; typedef unsigned short word16; typedef unsigned int word32; #if _MSC_VER typedef unsigned __int64 dword64; #else typedef unsigned long long dword64; #endif /* 28MHz crystal, plus every 65th 1MHz cycle is stretched 140ns */ #define CYCS_28_MHZ (28636360) #define DCYCS_28_MHZ (1.0*CYCS_28_MHZ) #define CYCS_3_5_MHZ (CYCS_28_MHZ/8) #define DCYCS_1_MHZ ((DCYCS_28_MHZ/28.0)*(65.0*7/(65.0*7+1.0))) // DCYCS_1_MHZ is 1020484.32016 #define CYCLES_IN_16MS_RAW (262 * 65) /* Use precisely 17030 instead of forcing 60 Hz since this is the number of */ /* 1MHz cycles per screen */ #define DCYCS_IN_16MS ((double)(CYCLES_IN_16MS_RAW)) #define DRECIP_DCYCS_IN_16MS (1.0 / (DCYCS_IN_16MS)) #define VBL_RATE (DCYCS_1_MHZ / DCYCS_IN_16MS) // VBL rate is about 59.9227 frames/sec #define MAXNUM_HEX_PER_LINE 32 #define MAX_SCALE_SIZE 5100 #ifdef __NeXT__ # include #endif #ifndef _WIN32 # include # include # include #endif #include #include #include #include #include #include #include #include #include #ifdef HPUX # include /* for GET_ITIMER */ #endif #ifdef SOLARIS # include #endif #ifdef _WIN32 # include # include # pragma warning(disable : 4996) /* open() is deprecated...sigh */ int ftruncate(int fd, word32 length); int lstat(const char *path, struct stat *bufptr); #endif #ifndef O_BINARY /* work around some Windows junk */ # define O_BINARY 0 #endif #define MAX_CHANGE_RECTS 20 #ifdef __GNUC__ int dbg_printf(const char *fmt, ...) __attribute__ (( __format__(printf, 1, 2))); #endif STRUCT(Pc_log) { dword64 dfcyc; word32 dbank_kpc; word32 instr; word32 psr_acc; word32 xreg_yreg; word32 stack_direct; word32 pad; }; STRUCT(Data_log) { dword64 dfcyc; byte *stat; word32 addr; word32 val; word32 size; }; STRUCT(Event) { dword64 dfcyc; int type; Event *next; }; STRUCT(Fplus) { dword64 dplus_1; dword64 dplus_x_minus_1; }; STRUCT(Engine_reg) { dword64 dfcyc; word32 kpc; word32 acc; word32 xreg; word32 yreg; word32 stack; word32 dbank; word32 direct; word32 psr; Fplus *fplus_ptr; }; STRUCT(Break_point) { word32 start_addr; word32 end_addr; word32 acc_type; }; STRUCT(Change_rect) { int x; int y; int width; int height; }; STRUCT(Kimage) { word32 *wptr; int a2_width_full; int a2_height_full; int a2_width; int a2_height; int x_width; int x_height; int x_refresh_needed; int x_max_width; int x_max_height; int x_xpos; int x_ypos; int active; word32 vbl_of_last_resize; word32 c025_val; word32 scale_width_to_a2; word32 scale_width_a2_to_x; word32 scale_height_to_a2; word32 scale_height_a2_to_x; int num_change_rects; Change_rect change_rect[MAX_CHANGE_RECTS]; word32 scale_width[MAX_SCALE_SIZE + 1]; word32 scale_height[MAX_SCALE_SIZE + 1]; }; typedef byte *Pg_info; STRUCT(Page_info) { Pg_info rd_wr; }; STRUCT(Cfg_menu) { const char *str; void *ptr; const char *name_str; void *defptr; int cfgtype; }; STRUCT(Cfg_dirent) { char *name; int is_dir; int part_num; dword64 dsize; dword64 dimage_start; dword64 compr_dsize; }; STRUCT(Cfg_listhdr) { Cfg_dirent *direntptr; int max; int last; int invalid; int curent; int topent; int num_to_show; }; typedef void (Dbg_fn)(const char *str); STRUCT(Dbg_longcmd) { const char *str; Dbg_fn *fnptr; Dbg_longcmd *subptr; const char *help_str; }; STRUCT(Emustate_intlist) { const char *str; word32 *iptr; }; STRUCT(Emustate_dword64list) { const char *str; dword64 *dptr; }; STRUCT(Emustate_word32list) { const char *str; word32 *wptr; }; STRUCT(Lzw_state) { word32 table[4096 + 2]; word32 entry; int bits; }; #ifdef __LP64__ # define PTR2WORD(a) ((word32)(unsigned long long)(a)) #else # define PTR2WORD(a) ((word32)(unsigned long long)(a)) #endif #define ALTZP (g_c068_statereg & 0x80) /* #define PAGE2 (g_c068_statereg & 0x40) */ #define RAMRD (g_c068_statereg & 0x20) #define RAMWRT (g_c068_statereg & 0x10) #define RDROM (g_c068_statereg & 0x08) #define LCBANK2 (g_c068_statereg & 0x04) #define ROMB (g_c068_statereg & 0x02) // #define INTCX (g_c068_statereg & 0x01) #define C041_EN_25SEC_INTS 0x10 #define C041_EN_VBL_INTS 0x08 #define C041_EN_SWITCH_INTS 0x04 #define C041_EN_MOVE_INTS 0x02 #define C041_EN_MOUSE 0x01 /* WARNING: SCC1 and SCC0 interrupts must be in this order for scc.c */ /* This order matches the SCC hardware, and SCC1_ZEROCNT must be 0x0001 */ #define IRQ_PENDING_SCC1_ZEROCNT 0x00001 #define IRQ_PENDING_SCC1_TX 0x00002 #define IRQ_PENDING_SCC1_RX 0x00004 #define IRQ_PENDING_SCC0_ZEROCNT 0x00008 #define IRQ_PENDING_SCC0_TX 0x00010 #define IRQ_PENDING_SCC0_RX 0x00020 #define IRQ_PENDING_C023_SCAN 0x00100 #define IRQ_PENDING_C023_1SEC 0x00200 #define IRQ_PENDING_C046_25SEC 0x00400 #define IRQ_PENDING_C046_VBL 0x00800 #define IRQ_PENDING_ADB_KBD_SRQ 0x01000 #define IRQ_PENDING_ADB_DATA 0x02000 #define IRQ_PENDING_ADB_MOUSE 0x04000 #define IRQ_PENDING_DOC 0x08000 #define IRQ_PENDING_MOCKINGBOARDA 0x10000 #define IRQ_PENDING_MOCKINGBOARDB 0x20000 /* must be BOARDA*2 */ #define EXTRU(val, pos, len) \ ( ( (len) >= (pos) + 1) ? ((val) >> (31-(pos))) : \ (((val) >> (31-(pos)) ) & ( (1<<(len) ) - 1) ) ) #define DEP1(val, pos, old_val) \ (((old_val) & ~(1 << (31 - (pos))) ) | \ ( ((val) & 1) << (31 - (pos))) ) #define set_halt(val) \ if(val) { set_halt_act(val); } #define clear_halt() \ clr_halt_act() #define GET_PAGE_INFO_RD(page) \ (page_info_rd_wr[page].rd_wr) #define GET_PAGE_INFO_WR(page) \ (page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr) #define SET_PAGE_INFO_RD(page,val) \ ;page_info_rd_wr[page].rd_wr = (Pg_info)val; #define SET_PAGE_INFO_WR(page,val) \ ;page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr = \ (Pg_info)val; #define VERBOSE_DISK 0x001 #define VERBOSE_IRQ 0x002 #define VERBOSE_CLK 0x004 #define VERBOSE_SHADOW 0x008 #define VERBOSE_IWM 0x010 #define VERBOSE_DOC 0x020 #define VERBOSE_ADB 0x040 #define VERBOSE_SCC 0x080 #define VERBOSE_TEST 0x100 #define VERBOSE_VIDEO 0x200 #define VERBOSE_MAC 0x400 #define VERBOSE_DYNA 0x800 #ifdef NO_VERB # define DO_VERBOSE 0 #else # define DO_VERBOSE 1 #endif #define disk_printf if(DO_VERBOSE && (Verbose & VERBOSE_DISK)) printf #define irq_printf if(DO_VERBOSE && (Verbose & VERBOSE_IRQ)) printf #define clk_printf if(DO_VERBOSE && (Verbose & VERBOSE_CLK)) printf #define shadow_printf if(DO_VERBOSE && (Verbose & VERBOSE_SHADOW)) printf #define iwm_printf if(DO_VERBOSE && (Verbose & VERBOSE_IWM)) printf #define doc_printf if(DO_VERBOSE && (Verbose & VERBOSE_DOC)) printf #define adb_printf if(DO_VERBOSE && (Verbose & VERBOSE_ADB)) printf #define scc_printf if(DO_VERBOSE && (Verbose & VERBOSE_SCC)) printf #define test_printf if(DO_VERBOSE && (Verbose & VERBOSE_TEST)) printf #define vid_printf if(DO_VERBOSE && (Verbose & VERBOSE_VIDEO)) printf #define mac_printf if(DO_VERBOSE && (Verbose & VERBOSE_MAC)) printf #define dyna_printf if(DO_VERBOSE && (Verbose & VERBOSE_DYNA)) printf #define HALT_ON_SCAN_INT 0x001 #define HALT_ON_IRQ 0x002 #define HALT_ON_SHADOW_REG 0x004 #define HALT_ON_C70D_WRITES 0x008 #define HALT_ON(a, msg) \ if(Halt_on & a) { \ halt_printf(msg); \ } #define MY_MIN(a,b) (((a) < (b)) ? (a) : (b)) #define MY_MAX(a,b) (((a) > (b)) ? (a) : (b)) #define GET_ITIMER(dest) dest = get_itimer(); #define FINISH(arg1, arg2) g_ret1 = arg1 | ((arg2) << 8); g_dcycles_end=0; #include "iwm.h" #include "protos.h" ================================================ FILE: upstream/kegs/src/defcomm.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsdif_defcomm_h[] = "@(#)$KmKId: defcomm.h,v 1.109 2023-11-12 15:29:41+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #define SHIFT_PER_CHANGE 3 #define CHANGE_SHIFT (5 + SHIFT_PER_CHANGE) #define SLOW_MEM_CH_SIZE (0x20000 >> CHANGE_SHIFT) #define MAXNUM_HEX_PER_LINE 32 /* Different Joystick defines */ #define JOYSTICK_MOUSE 1 #define JOYSTICK_LINUX 2 #define JOYSTICK_KEYPAD 3 #define JOYSTICK_WIN32_1 4 #define JOYSTICK_WIN32_2 5 #define MAX_BREAK_POINTS 0x20 #define MAX_BP_INDEX 0x100 #define MAX_BP_PER_INDEX 3 /* 4 word32s total = 16 bytes */ #define SIZE_BREAKPT_ENTRY_BITS 4 /* 16 bytes = 4 bits */ /* Warning--next defines used by asm! */ #define PAGE_INFO_PAD_SIZE 0x800 #define PAGE_INFO_WR_OFFSET 0x10000+PAGE_INFO_PAD_SIZE #define BANK_IO_BIT 31 #define BANK_SHADOW_BIT 30 #define BANK_SHADOW2_BIT 29 #define BANK_IO2_BIT 28 #define BANK_BREAK_BIT 27 #define BANK_BREAK (1 << (31 - BANK_BREAK_BIT)) #define BANK_IO2_TMP (1 << (31 - BANK_IO2_BIT)) #define BANK_IO_TMP (1 << (31 - BANK_IO_BIT)) #define BANK_SHADOW (1 << (31 - BANK_SHADOW_BIT)) #define BANK_SHADOW2 (1 << (31 - BANK_SHADOW2_BIT)) #define SET_BANK_IO \ (&g_dummy_memory1_ptr[BANK_IO_TMP | BANK_IO2_TMP]) #define BANK_BAD_MEM (&g_dummy_memory1_ptr[0xff]) #define RET_BREAK 0x1 #define RET_COP 0x2 #define RET_WDM 0x3 #define RET_WAI 0x4 #define RET_STP 0x5 #define RET_PSR 0x6 #define RET_IRQ 0x7 #define RET_TOOLTRACE 0x8 #define BIT_ALL_STAT_TEXT 0 #define BIT_ALL_STAT_VID80 1 #define BIT_ALL_STAT_ST80 2 #define BIT_ALL_STAT_COLOR_C021 3 #define BIT_ALL_STAT_MIX_T_GR 4 #define BIT_ALL_STAT_DIS_COLOR_DHIRES 5 /* special val, c029 */ #define BIT_ALL_STAT_PAGE2 6 /* special val, statereg */ #define BIT_ALL_STAT_SUPER_HIRES 7 /* special, c029 */ #define BIT_ALL_STAT_HIRES 8 #define BIT_ALL_STAT_ANNUNC3 9 #define BIT_ALL_STAT_ALTCHARSET 10 #define BIT_ALL_STAT_FLASH_STATE 11 #define BIT_ALL_STAT_BG_COLOR 12 /* 4 bits */ #define BIT_ALL_STAT_TEXT_COLOR 16 /* 4 bits */ /* Text must be just above */ /* bg to match c022 reg */ #define BIT_ALL_STAT_VOC_INTERLACE 20 #define BIT_ALL_STAT_VOC_MAIN 21 #define BIT_ALL_STAT_BORDER 22 #define ALL_STAT_SUPER_HIRES (1 << (BIT_ALL_STAT_SUPER_HIRES)) #define ALL_STAT_TEXT (1 << (BIT_ALL_STAT_TEXT)) #define ALL_STAT_VID80 (1 << (BIT_ALL_STAT_VID80)) #define ALL_STAT_PAGE2 (1 << (BIT_ALL_STAT_PAGE2)) #define ALL_STAT_ST80 (1 << (BIT_ALL_STAT_ST80)) #define ALL_STAT_COLOR_C021 (1 << (BIT_ALL_STAT_COLOR_C021)) #define ALL_STAT_DIS_COLOR_DHIRES (1 << (BIT_ALL_STAT_DIS_COLOR_DHIRES)) #define ALL_STAT_MIX_T_GR (1 << (BIT_ALL_STAT_MIX_T_GR)) #define ALL_STAT_HIRES (1 << (BIT_ALL_STAT_HIRES)) #define ALL_STAT_ANNUNC3 (1 << (BIT_ALL_STAT_ANNUNC3)) #define ALL_STAT_TEXT_COLOR (0xf << (BIT_ALL_STAT_TEXT_COLOR)) #define ALL_STAT_BG_COLOR (0xf << (BIT_ALL_STAT_BG_COLOR)) #define ALL_STAT_ALTCHARSET (1 << (BIT_ALL_STAT_ALTCHARSET)) #define ALL_STAT_FLASH_STATE (1 << (BIT_ALL_STAT_FLASH_STATE)) #define ALL_STAT_VOC_INTERLACE (1 << (BIT_ALL_STAT_VOC_INTERLACE)) #define ALL_STAT_VOC_MAIN (1 << (BIT_ALL_STAT_VOC_MAIN)) #define ALL_STAT_BORDER (1 << (BIT_ALL_STAT_BORDER)) #define BORDER_WIDTH 32 #define EFF_BORDER_WIDTH (BORDER_WIDTH + (640-560)) /* BASE_MARGIN_BOTTOM+MARGIN_TOP must equal 62. There are 262 scan lines */ /* at 60Hz (15.7KHz line rate) and so we just make 62 border lines */ #define BASE_MARGIN_TOP 32 #define BASE_MARGIN_BOTTOM 30 #define BASE_MARGIN_LEFT BORDER_WIDTH #define BASE_MARGIN_RIGHT BORDER_WIDTH #define A2_WINDOW_WIDTH 640 #define A2_WINDOW_HEIGHT 400 #define X_A2_WINDOW_WIDTH (A2_WINDOW_WIDTH + BASE_MARGIN_LEFT + \ BASE_MARGIN_RIGHT) #define X_A2_WINDOW_HEIGHT (A2_WINDOW_HEIGHT + BASE_MARGIN_TOP + \ BASE_MARGIN_BOTTOM) #define MAX_STATUS_LINES 4 #define STATUS_LINE_LENGTH 88 #define BASE_WINDOW_WIDTH (X_A2_WINDOW_WIDTH) #define A2_BORDER_COLOR_NUM 0xfe ================================================ FILE: upstream/kegs/src/defs_instr.h ================================================ // $KmKId: defs_instr.h,v 1.70 2023-11-05 16:22:26+00 kentd Exp $ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #undef GET_DLOC_X_IND_RD #ifdef ACC8 # define GET_DLOC_X_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_X_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DISP8_S_RD #ifdef ACC8 # define GET_DISP8_S_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DISP8_S_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DLOC_RD #ifdef ACC8 # define GET_DLOC_RD() \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY8((direct + arg) & 0xffff, arg); #else # define GET_DLOC_RD() \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY16((direct + arg) & 0xffff, arg, 1); #endif #undef GET_DLOC_RD_RMW #undef GET_MEM_RMW #define GET_MEM_RMW() \ if(!IS_ACC16) { \ if(psr & 0x100) { \ /* emulation re-writes the address */ \ SET_MEMORY8(addr_latch, arg); \ } else { \ /* otherwise, just read addr again */ \ GET_MEMORY8(addr_latch, dummy1); \ } \ } else { \ /* 16-bit re-reads addr+1 again */ \ dummy1 = addr_latch + 1; \ GET_MEMORY8(dummy1, dummy1); \ addr_latch--; \ } #define GET_DLOC_RD_RMW() \ GET_DLOC_RD(); \ GET_MEM_RMW(); #undef GET_DLOC_L_IND_RD #ifdef ACC8 # define GET_DLOC_L_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_L_IND_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_IMM_MEM #ifdef ACC8 # define GET_IMM_MEM() \ GET_1BYTE_ARG; \ INC_KPC_2; #else # define GET_IMM_MEM() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_3; #endif #undef GET_ABS_RD #ifdef ACC8 # define GET_ABS_RD() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ GET_MEMORY8((dbank << 16) + arg, arg); \ INC_KPC_3; #else # define GET_ABS_RD() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ GET_MEMORY16((dbank << 16) + arg, arg, 0); \ INC_KPC_3; #endif #undef GET_ABS_RD_RMW #define GET_ABS_RD_RMW() \ GET_ABS_RD(); \ GET_MEM_RMW(); #undef GET_LONG_RD #ifdef ACC8 # define GET_LONG_RD() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ GET_MEMORY8(arg, arg); \ INC_KPC_4; #else # define GET_LONG_RD() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ GET_MEMORY16(arg, arg, 0); \ INC_KPC_4; #endif #undef GET_DLOC_IND_Y_RD #undef GET_DLOC_IND_Y_ADDR #ifdef ACC8 # define GET_DLOC_IND_Y_RD() \ GET_DLOC_IND_Y_ADDR(0) \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_IND_Y_RD() \ GET_DLOC_IND_Y_ADDR(0) \ GET_MEMORY16(arg, arg, 0); #endif #define GET_DLOC_IND_Y_ADDR(is_write) \ GET_1BYTE_ARG; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, tmp1, 0); \ tmp1 += (dbank << 16); \ arg = (tmp1 + yreg) & 0xffffff; \ tmp2 = (tmp1 & 0xffff00) | (arg & 0xff); \ if((psr & 0x10) && ((arg != tmp2) | is_write)) { \ GET_MEMORY8(tmp2, tmp1); \ } else if(((psr & 0x10) == 0) | (arg != tmp2) | is_write) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; #undef GET_DLOC_IND_RD #ifdef ACC8 # define GET_DLOC_IND_RD() \ GET_1BYTE_ARG; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ GET_MEMORY8((dbank << 16) + arg, arg); #else # define GET_DLOC_IND_RD() \ GET_1BYTE_ARG; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ GET_MEMORY16((dbank << 16) + arg, arg, 0); #endif #undef GET_DLOC_X_RD #ifdef ACC8 # define GET_DLOC_X_RD() \ GET_1BYTE_ARG; \ CYCLES_PLUS_1; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ arg = (arg + xreg + direct) & 0xffff; \ if(psr & 0x100) { \ if((direct & 0xff) == 0) { \ arg = (direct & 0xff00) | (arg & 0xff); \ } \ } \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_X_RD() \ GET_1BYTE_ARG; \ CYCLES_PLUS_1; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ arg = (arg + xreg + direct) & 0xffff; \ if(!IS_ACC16 && (psr & 0x100)) { \ if((direct & 0xff) == 0) { \ arg = (direct & 0xff00) | (arg & 0xff); \ } \ } \ GET_MEMORY16(arg, arg, 1); #endif #undef GET_DLOC_X_RD_RMW #define GET_DLOC_X_RD_RMW() \ GET_DLOC_X_RD(); \ GET_MEM_RMW(); #undef GET_DISP8_S_IND_Y_RD #ifdef ACC8 # define GET_DISP8_S_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DISP8_S_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_DLOC_L_IND_Y_RD #ifdef ACC8 # define GET_DLOC_L_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR(); \ GET_MEMORY8(arg, arg); #else # define GET_DLOC_L_IND_Y_RD() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR(); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_ABS_INDEX_ADDR #define GET_ABS_INDEX_ADDR(index_reg, is_write) \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_3; \ tmp1 = (dbank << 16) + arg; \ arg = tmp1 + index_reg; \ tmp1 = (tmp1 & 0xffff00) + (arg & 0xff); \ if((psr & 0x10) && ((tmp1 != arg) | is_write)) { \ GET_MEMORY8(tmp1, tmp2); \ } else if(((psr & 0x10) == 0) | (tmp1 != arg) | is_write) { \ CYCLES_PLUS_1; \ } #undef GET_ABS_Y_RD #ifdef ACC8 # define GET_ABS_Y_RD() \ GET_ABS_INDEX_ADDR(yreg, 0); \ GET_MEMORY8(arg, arg); #else # define GET_ABS_Y_RD() \ GET_ABS_INDEX_ADDR(yreg, 0); \ GET_MEMORY16(arg, arg, 0); #endif #undef GET_ABS_X_RD #undef GET_ABS_X_RD_RMW #ifdef ACC8 # define GET_ABS_X_RD() \ GET_ABS_INDEX_ADDR(xreg, 0); \ GET_MEMORY8(arg, arg); #define GET_ABS_X_RD_RMW() \ GET_ABS_INDEX_ADDR(xreg, 1); \ GET_MEMORY8(arg, arg); \ GET_MEM_RMW(); #else # define GET_ABS_X_RD() \ GET_ABS_INDEX_ADDR(xreg, 0); \ GET_MEMORY16(arg, arg, 0); #define GET_ABS_X_RD_RMW() \ GET_ABS_INDEX_ADDR(xreg, 1); \ GET_MEMORY16(arg, arg, 0); \ GET_MEM_RMW(); #endif #undef GET_LONG_X_RD #ifdef ACC8 # define GET_LONG_X_RD() \ GET_3BYTE_ARG; \ arg = (arg + xreg) & 0xffffff; \ INC_KPC_4; \ CYCLES_PLUS_2; \ GET_MEMORY8(arg, arg); #else # define GET_LONG_X_RD() \ GET_3BYTE_ARG; \ arg = (arg + xreg) & 0xffffff; \ INC_KPC_4; \ CYCLES_PLUS_2; \ GET_MEMORY16(arg, arg, 0); #endif #define SET_NEG_ZERO16(val) \ zero = val; \ neg7 = (val) >> 8; #define SET_NEG_ZERO8(val) \ zero = val; \ neg7 = val; #define SET_CARRY8(val) \ psr = (psr & ~1) + (((val) >> 8) & 1); #define SET_CARRY16(val) \ psr = (psr & ~1) + (((val) >> 16) & 1); #define SET_INDEX_REG(src, dest) \ if(psr & 0x10) { \ dest = (src) & 0xff; \ SET_NEG_ZERO8(dest); \ } else { \ dest = (src) & 0xffff; \ SET_NEG_ZERO16(dest); \ } #define LD_INDEX_INST(index_reg, in_bank) \ if(psr & 0x10) { \ GET_MEMORY8(arg, arg); \ } else { \ GET_MEMORY16(arg, arg, in_bank);\ } \ SET_INDEX_REG(arg, index_reg); #define LDX_INST(in_bank) LD_INDEX_INST(xreg, in_bank) #define LDY_INST(in_bank) LD_INDEX_INST(yreg, in_bank) #undef ORA_INST #ifdef ACC8 # define ORA_INST() \ tmp1 = (acc | arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define ORA_INST() \ acc = (acc | arg); \ SET_NEG_ZERO16(acc); #endif #undef AND_INST #ifdef ACC8 # define AND_INST() \ tmp1 = (acc & arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define AND_INST() \ acc = (acc & arg); \ SET_NEG_ZERO16(acc); #endif #undef EOR_INST #ifdef ACC8 # define EOR_INST() \ tmp1 = (acc ^ arg) & 0xff; \ acc = (acc & 0xff00) + tmp1; \ SET_NEG_ZERO8(tmp1); #else # define EOR_INST() \ acc = (acc ^ arg); \ SET_NEG_ZERO16(acc); #endif #undef LDA_INST #ifdef ACC8 # define LDA_INST() \ acc = (acc & 0xff00) + (arg & 0xff); \ SET_NEG_ZERO8(arg & 0xff); #else # define LDA_INST() \ acc = (arg & 0xffff); \ SET_NEG_ZERO16(acc); #endif #undef ADC_INST #ifdef ACC8 # define ADC_INST() \ tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 0); \ acc = (acc & 0xff00) + (tmp1 & 0xff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #else # define ADC_INST() \ tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 0); \ acc = (tmp1 & 0xffff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #endif #undef SBC_INST #ifdef ACC8 # define SBC_INST() \ tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 1); \ acc = (acc & 0xff00) + (tmp1 & 0xff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #else # define SBC_INST() \ tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 1); \ acc = (tmp1 & 0xffff); \ psr = (tmp1 >> 16); \ zero = !(psr & 0x2); \ neg7 = psr; #endif #undef CMP_INST #ifdef ACC8 # define CMP_INST() \ arg = (acc & 0xff) + (0x100 - arg); \ SET_CARRY8(arg); \ arg = arg & 0xff; \ SET_NEG_ZERO8(arg & 0xff); #else # define CMP_INST() \ arg = (acc & 0xffff) + (0x10000 - arg); \ SET_CARRY16(arg); \ arg = arg & 0xffff; \ SET_NEG_ZERO16(arg & 0xffff); #endif #undef BIT_INST #ifdef ACC8 # define BIT_INST() \ neg7 = arg; \ zero = (acc & arg & 0xff); \ psr = (psr & (~0x40)) | (arg & 0x40); #else # define BIT_INST() \ neg7 = arg >> 8; \ zero = (acc & arg & 0xffff); \ psr = (psr & (~0x40)) | ((arg >> 8) & 0x40); #endif #undef STA_INST #ifdef ACC8 # define STA_INST(in_bank) \ SET_MEMORY8(arg, acc); #else # define STA_INST(in_bank) \ SET_MEMORY16(arg, acc, in_bank); #endif #undef TSB_INST #ifdef ACC8 # define TSB_INST(in_bank) \ arg = arg & 0xff; \ tmp1 = arg | acc; \ zero = arg & acc; \ SET_MEMORY8(addr_latch, tmp1); #else # define TSB_INST(in_bank) \ tmp1 = arg | acc; \ zero = arg & acc; \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef ASL_INST #ifdef ACC8 # define ASL_INST(in_bank) \ psr = (psr & 0x1fe) + ((arg >> 7) & 1); \ tmp1 = (arg << 1) & 0xff; \ SET_NEG_ZERO8(tmp1); \ SET_MEMORY8(addr_latch, tmp1); #else # define ASL_INST(in_bank) \ psr = (psr & 0x1fe) + ((arg >> 15) & 1);\ tmp1 = (arg << 1) & 0xffff; \ SET_NEG_ZERO16(tmp1); \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef ROL_INST #ifdef ACC8 # define ROL_INST(in_bank) \ arg = arg & 0xff; \ arg = (arg << 1) | (psr & 1); \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg & 0xff); \ SET_CARRY8(arg); #else # define ROL_INST(in_bank) \ arg = (arg << 1) | (psr & 1); \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg & 0xffff); \ SET_CARRY16(arg); #endif #undef LSR_INST #ifdef ACC8 # define LSR_INST(in_bank) \ SET_CARRY8(arg << 8); \ arg = (arg >> 1) & 0x7f; \ SET_NEG_ZERO8(arg); \ SET_MEMORY8(addr_latch, arg); #else # define LSR_INST(in_bank) \ SET_CARRY16(arg << 16); \ arg = (arg >> 1) & 0x7fff; \ SET_NEG_ZERO16(arg); \ SET_MEMORY16(addr_latch, arg, in_bank); #endif #undef ROR_INST #ifdef ACC8 # define ROR_INST(in_bank) \ tmp1 = psr & 1; \ SET_CARRY8(arg << 8); \ arg = ((arg >> 1) & 0x7f) | (tmp1 << 7); \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define ROR_INST(in_bank) \ tmp1 = psr & 1; \ SET_CARRY16(arg << 16); \ arg = ((arg >> 1) & 0x7fff) | (tmp1 << 15); \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef TRB_INST #ifdef ACC8 # define TRB_INST(in_bank) \ arg = arg & 0xff; \ tmp1 = arg & ~acc; \ zero = arg & acc; \ SET_MEMORY8(addr_latch, tmp1); #else # define TRB_INST(in_bank) \ tmp1 = arg & ~acc; \ zero = arg & acc; \ SET_MEMORY16(addr_latch, tmp1, in_bank); #endif #undef DEC_INST #ifdef ACC8 # define DEC_INST(in_bank) \ arg = (arg - 1) & 0xff; \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define DEC_INST(in_bank) \ arg = (arg - 1) & 0xffff; \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef INC_INST #ifdef ACC8 # define INC_INST(in_bank) \ arg = (arg + 1) & 0xff; \ SET_MEMORY8(addr_latch, arg); \ SET_NEG_ZERO8(arg); #else # define INC_INST(in_bank) \ arg = (arg + 1) & 0xffff; \ SET_MEMORY16(addr_latch, arg, in_bank); \ SET_NEG_ZERO16(arg); #endif #undef STZ_INST #ifdef ACC8 # define STZ_INST(in_bank) \ SET_MEMORY8(arg, 0); #else # define STZ_INST(in_bank) \ SET_MEMORY16(arg, 0, in_bank); #endif #undef BRANCH_DISP8 #define BRANCH_DISP8(cond) \ GET_1BYTE_ARG; \ tmp2 = kpc & 0xff0000; \ kpc += 2; \ tmp1 = kpc; \ if(cond) { \ kpc = kpc + arg - ((arg & 0x80) << 1); \ CYCLES_PLUS_1; \ if((tmp1 ^ kpc) & psr & 0x100) { \ CYCLES_PLUS_1; \ } \ } \ kpc = tmp2 + (kpc & 0xffff); #undef STY_INST #undef STX_INST #define STY_INST(in_bank) \ if(psr & 0x10) { \ SET_MEMORY8(arg, yreg); \ } else { \ SET_MEMORY16(arg, yreg, in_bank);\ } #define STX_INST(in_bank) \ if(psr & 0x10) { \ SET_MEMORY8(arg, xreg); \ } else { \ SET_MEMORY16(arg, xreg, in_bank);\ } #define C_LDX_ABS_Y() \ GET_ABS_INDEX_ADDR(yreg, 0); \ LDX_INST(0); #define C_LDY_ABS_X() \ GET_ABS_INDEX_ADDR(xreg, 0); \ LDY_INST(0); #define C_LDX_ABS() \ GET_ABS_ADDR(); \ LDX_INST(0); #define C_LDY_ABS() \ GET_ABS_ADDR(); \ LDY_INST(0); #define C_LDX_DLOC() \ GET_DLOC_ADDR(); \ LDX_INST(1); #define C_LDY_DLOC() \ GET_DLOC_ADDR(); \ LDY_INST(1); #define C_LDY_DLOC_X() \ GET_DLOC_X_ADDR(); \ LDY_INST(1); #define C_LDX_DLOC_Y() \ GET_DLOC_Y_ADDR(); \ LDX_INST(1); #define CP_INDEX_VAL(index_reg) \ arg = 0x100 - arg + index_reg; \ if((psr & 0x10) == 0) { \ arg += 0xff00; \ SET_NEG_ZERO16(arg & 0xffff); \ SET_CARRY16(arg); \ } else { \ SET_NEG_ZERO8(arg & 0xff);\ SET_CARRY8(arg); \ } #define CP_INDEX_LOAD(index_reg, in_bank) \ if((psr & 0x10) != 0) { \ GET_MEMORY8(arg, arg); \ } else { \ GET_MEMORY16(arg, arg, in_bank);\ } \ CP_INDEX_VAL(index_reg) #define CPX_INST(in_bank) \ CP_INDEX_LOAD(xreg, in_bank); #define CPY_INST(in_bank) \ CP_INDEX_LOAD(yreg, in_bank); #define C_CPX_IMM() \ INC_KPC_2; \ if((psr & 0x10) == 0) { \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_1; \ } else { \ GET_1BYTE_ARG; \ } \ CP_INDEX_VAL(xreg); #define C_CPY_IMM() \ INC_KPC_2; \ if((psr & 0x10) == 0) { \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ INC_KPC_1; \ } else { \ GET_1BYTE_ARG; \ } \ CP_INDEX_VAL(yreg); #define C_CPX_DLOC() \ GET_DLOC_ADDR(); \ CPX_INST(1); #define C_CPY_DLOC() \ GET_DLOC_ADDR(); \ CPY_INST(1); #define C_CPX_ABS() \ GET_ABS_ADDR(); \ CPX_INST(0); #define C_CPY_ABS() \ GET_ABS_ADDR(); \ CPY_INST(0); ================================================ FILE: upstream/kegs/src/dependency ================================================ adb.o: adb.c defc.h defcomm.h iwm.h protos.h protos_base.h engine_c.o: engine_c.c defc.h defcomm.h iwm.h protos.h protos_base.h size_c.h op_routs.h engine.h defs_instr.h instable.h clock.o: clock.c defc.h defcomm.h iwm.h protos.h protos_base.h compile_time.o: compile_time.c config.o: config.c defc.h defcomm.h iwm.h protos.h protos_base.h config.h win_dirent.h debugger.o: debugger.c defc.h defcomm.h iwm.h protos.h protos_base.h disas.h scc.o: scc.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_socket_driver.o: scc_socket_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_windriver.o: scc_windriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h scc_unixdriver.o: scc_unixdriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h iwm.o: iwm.c defc.h defcomm.h iwm.h protos.h protos_base.h joystick_driver.o: joystick_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h moremem.o: moremem.c defc.h defcomm.h iwm.h protos.h protos_base.h paddles.o: paddles.c defc.h defcomm.h iwm.h protos.h protos_base.h mockingboard.o: mockingboard.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sim65816.o: sim65816.c defc.h defcomm.h iwm.h protos.h protos_base.h smartport.o: smartport.c defc.h defcomm.h iwm.h protos.h protos_base.h doc.o: doc.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sound.o: sound.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h sound_driver.o: sound_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h woz.o: woz.c defc.h defcomm.h iwm.h protos.h protos_base.h unshk.o: unshk.c defc.h defcomm.h iwm.h protos.h protos_base.h undeflate.o: undeflate.c defc.h defcomm.h iwm.h protos.h protos_base.h dynapro.o: dynapro.c defc.h defcomm.h iwm.h protos.h protos_base.h win_dirent.h dyna_type.o: dyna_type.c defc.h defcomm.h iwm.h protos.h protos_base.h dyna_filt.o: dyna_filt.c defc.h defcomm.h iwm.h protos.h protos_base.h dyna_validate.o: dyna_validate.c defc.h defcomm.h iwm.h protos.h protos_base.h applesingle.o: applesingle.c defc.h defcomm.h iwm.h protos.h protos_base.h video.o: video.c defc.h defcomm.h iwm.h protos.h protos_base.h kegsfont.h voc.o: voc.c defc.h defcomm.h iwm.h protos.h protos_base.h macsnd_driver.o: macsnd_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h ================================================ FILE: upstream/kegs/src/disas.h ================================================ const char rcsid_disas_h[] = "@(#)$KmKId: disas.h,v 1.12 2020-06-17 02:25:23+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2020 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ enum { ABS = 1, ABSX, ABSY, ABSLONG, ABSIND, ABSXIND, IMPLY, ACCUM, IMMED, JUST8, DLOC, DLOCX, DLOCY, LONG, LONGX, DLOCIND, DLOCINDY, DLOCXIND, DLOCBRAK, DLOCBRAKY, DISP8, DISP8S, DISP8SINDY, DISP16, MVPMVN, REPVAL, SEPVAL }; const char * const disas_opcodes[256] = { "BRK", "ORA", "COP", "ORA", "TSB", "ORA", "ASL", "ORA", /* 00-07 */ "PHP", "ORA", "ASL", "PHD", "TSB", "ORA", "ASL", "ORA", /* 08-0f */ "BPL", "ORA", "ORA", "ORA", "TRB", "ORA", "ASL", "ORA", /* 10-17 */ "CLC", "ORA", "INC", "TCS", "TRB", "ORA", "ASL", "ORA", /* 18-1f */ "JSR", "AND", "JSL", "AND", "BIT", "AND", "ROL", "AND", /* 20-27 */ "PLP", "AND", "ROL", "PLD", "BIT", "AND", "ROL", "AND", /* 28-2f */ "BMI", "AND", "AND", "AND", "BIT", "AND", "ROL", "AND", /* 30-37 */ "SEC", "AND", "DEC", "TSC", "BIT", "AND", "ROL", "AND", /* 38-3f */ "RTI", "EOR", "WDM", "EOR", "MVP", "EOR", "LSR", "EOR", /* 40-47 */ "PHA", "EOR", "LSR", "PHK", "JMP", "EOR", "LSR", "EOR", /* 48-4f */ "BVC", "EOR", "EOR", "EOR", "MVN", "EOR", "LSR", "EOR", /* 50-57 */ "CLI", "EOR", "PHY", "TCD", "JMP", "EOR", "LSR", "EOR", /* 58-5f */ "RTS", "ADC", "PER", "ADC", "STZ", "ADC", "ROR", "ADC", /* 60-67 */ "PLA", "ADC", "ROR", "RTL", "JMP", "ADC", "ROR", "ADC", /* 68-6f */ "BVS", "ADC", "ADC", "ADC", "STZ", "ADC", "ROR", "ADC", /* 70-77 */ "SEI", "ADC", "PLY", "TDC", "JMP", "ADC", "ROR", "ADC", /* 78-7f */ "BRA", "STA", "BRL", "STA", "STY", "STA", "STX", "STA", /* 80-87 */ "DEY", "BIT", "TXA", "PHB", "STY", "STA", "STX", "STA", /* 88-8f */ "BCC", "STA", "STA", "STA", "STY", "STA", "STX", "STA", /* 90-97 */ "TYA", "STA", "TXS", "TXY", "STZ", "STA", "STZ", "STA", /* 98-9f */ "LDY", "LDA", "LDX", "LDA", "LDY", "LDA", "LDX", "LDA", /* a0-a7 */ "TAY", "LDA", "TAX", "PLB", "LDY", "LDA", "LDX", "LDA", /* a8-af */ "BCS", "LDA", "LDA", "LDA", "LDY", "LDA", "LDX", "LDA", /* b0-b7 */ "CLV", "LDA", "TSX", "TYX", "LDY", "LDA", "LDX", "LDA", /* b8-bf */ "CPY", "CMP", "REP", "CMP", "CPY", "CMP", "DEC", "CMP", /* c0-c7 */ "INY", "CMP", "DEX", "WAI", "CPY", "CMP", "DEC", "CMP", /* c8-cf */ "BNE", "CMP", "CMP", "CMP", "PEI", "CMP", "DEC", "CMP", /* d0-d7 */ "CLD", "CMP", "PHX", "STP", "JML", "CMP", "DEC", "CMP", /* d8-df */ "CPX", "SBC", "SEP", "SBC", "CPX", "SBC", "INC", "SBC", /* e0-e7 */ "INX", "SBC", "NOP", "XBA", "CPX", "SBC", "INC", "SBC", /* e8-ef */ "BEQ", "SBC", "SBC", "SBC", "PEA", "SBC", "INC", "SBC", /* f0-f7 */ "SED", "SBC", "PLX", "XCE", "JSR", "SBC", "INC", "SBC", /* f8-ff */ }; const word32 disas_types[256] = { JUST8+0x100, DLOCXIND+0x100, /* 00-01 */ JUST8+0x100, DISP8S+0x100, /* 02-03 */ DLOC+0x100, DLOC+0x100, /* 04-05 */ DLOC+0x100, DLOCBRAK+0x100, /* 06-07 */ IMPLY+0x000, IMMED+0x400, /* 08-9 */ ACCUM+0x000, IMPLY+0x000, /* 0a-b */ ABS+0x200, ABS+0x200, /* c-d */ ABS+0x200, LONG+0x300, /* e-f */ DISP8+0x100, DLOCINDY+0x100, /* 10-11 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 12-13 */ DLOC+0x100, DLOCX+0x100, /* 14-15 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 16-17 */ IMPLY+0x000, ABSY+0x200, /* 18-19 */ ACCUM+0x000, IMPLY+0x000, /* 1a-1b */ ABS+0x200, ABSX+0x200, /* 1c-1d */ ABSX+0x200, LONGX+0x300, /* 1e-1f */ ABS+0x200, DLOCXIND+0x100, /* 20-21 */ ABSLONG+0x300, DISP8S+0x100, /* 22-23 */ DLOC+0x100, DLOC+0x100, /* 24-25 */ DLOC+0x100, DLOCBRAK+0x100, /* 26-27 */ IMPLY+0x000, IMMED+0x400, /* 28-29 */ ACCUM+0x000, IMPLY+0x000, /* 2a-2b */ ABS+0x200, ABS+0x200, /* 2c-2d */ ABS+0x200, LONG+0x300, /* 2e-2f */ DISP8+0x100, DLOCINDY+0x100, /* 30-31 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 32-33 */ DLOCX+0x100, DLOCX+0x100, /* 34-35 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 36-37 */ IMPLY+0x000, ABSY+0x200, /* 38-39 */ ACCUM+0x000, IMPLY+0x000, /* 3a-3b */ ABSX+0x200, ABSX+0x200, /* 3c-3d */ ABSX+0x200, LONGX+0x300, /* 3e-3f */ IMPLY+0x000, DLOCXIND+0x100, /* 40-41 */ JUST8+0x100, DISP8S+0x100, /* 42-43 */ MVPMVN+0x200, DLOC+0x100, /* 44-45 */ DLOC+0x100, DLOCBRAK+0x100, /* 46-47 */ IMPLY+0x000, IMMED+0x400, /* 48-49 */ ACCUM+0x000, IMPLY+0x000, /* 4a-4b */ ABS+0x200, ABS+0x200, /* 4c-4d */ ABS+0x200, LONG+0x300, /* 4e-4f */ DISP8+0x100, DLOCINDY+0x100, /* 50-51 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 52-53 */ MVPMVN+0x200, DLOCX+0x100, /* 54-55 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 56-57 */ IMPLY+0x000, ABSY+0x200, /* 58-59 */ IMPLY+0x000, IMPLY+0x000, /* 5a-5b */ LONG+0x300, ABSX+0x200, /* 5c-5d */ ABSX+0x200, LONGX+0x300, /* 5e-5f */ IMPLY+0x000, DLOCXIND+0x100, /* 60-61 */ DISP16+0x200, DISP8S+0x100, /* 62-63 */ DLOC+0x100, DLOC+0x100, /* 64-65 */ DLOC+0x100, DLOCBRAK+0x100, /* 66-67 */ IMPLY+0x000, IMMED+0x400, /* 68-69 */ ACCUM+0x000, IMPLY+0x000, /* 6a-6b */ ABSIND+0x200, ABS+0x200, /* 6c-6d */ ABS+0x200, LONG+0x300, /* 6e-6f */ DISP8+0x100, DLOCINDY+0x100, /* 70-71 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 72-73 */ DLOCX+0x100, DLOCX+0x100, /* 74-75 */ DLOCX+0x100, DLOCBRAKY+0x100, /* 76-77 */ IMPLY+0x000, ABSY+0x200, /* 78-79 */ IMPLY+0x000, IMPLY+0x000, /* 7a-7b */ ABSXIND+0x200, ABSX+0x200, /* 7c-7d */ ABSX+0x200, LONGX+0x300, /* 7e-7f */ DISP8+0x100, DLOCXIND+0x100, /* 80-81 */ DISP16+0x200, DISP8S+0x100, /* 82-83 */ DLOC+0x100, DLOC+0x100, /* 84-85 */ DLOC+0x100, DLOCBRAK+0x100, /* 86-87 */ IMPLY+0x000, IMMED+0x400, /* 88-89 */ IMPLY+0x000, IMPLY+0x000, /* 8a-8b */ ABS+0x200, ABS+0x200, /* 8c-8d */ ABS+0x200, LONG+0x300, /* 8e-8f */ DISP8+0x100, DLOCINDY+0x100, /* 90-91 */ DLOCIND+0x100, DISP8SINDY+0x100, /* 92-93 */ DLOCX+0x100, DLOCX+0x100, /* 94-95 */ DLOCY+0x100, DLOCBRAKY+0x100, /* 96-97 */ IMPLY+0x000, ABSY+0x200, /* 98-99 */ IMPLY+0x000, IMPLY+0x000, /* 9a-9b */ ABS+0x200, ABSX+0x200, /* 9c-9d */ ABSX+0x200, LONGX+0x300, /* 9e-9f */ IMMED+0x500, DLOCXIND+0x100, /* a0-a1 */ IMMED+0x500, DISP8S+0x100, /* a2-a3 */ DLOC+0x100, DLOC+0x100, /* a4-a5 */ DLOC+0x100, DLOCBRAK+0x100, /* a6-a7 */ IMPLY+0x000, IMMED+0x400, /* a8-a9 */ IMPLY+0x000, IMPLY+0x000, /* aa-ab */ ABS+0x200, ABS+0x200, /* ac-ad */ ABS+0x200, LONG+0x300, /* ae-af */ DISP8+0x100, DLOCINDY+0x100, /* b0-b1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* b2-b3 */ DLOCX+0x100, DLOCX+0x100, /* b4-b5 */ DLOCY+0x100, DLOCBRAKY+0x100, /* b6-b7 */ IMPLY+0x000, ABSY+0x200, /* b8-b9 */ IMPLY+0x000, IMPLY+0x000, /* ba-bb */ ABSX+0x200, ABSX+0x200, /* bc-bd */ ABSY+0x200, LONGX+0x300, /* be-bf */ IMMED+0x500, DLOCXIND+0x100, /* c0-c1 */ REPVAL+0x100, DISP8S+0x100, /* c2-c3 */ DLOC+0x100, DLOC+0x100, /* c4-c5 */ DLOC+0x100, DLOCBRAK+0x100, /* c6-c7 */ IMPLY+0x000, IMMED+0x400, /* c8-c9 */ IMPLY+0x000, IMPLY+0x000, /* ca-cb */ ABS+0x200, ABS+0x200, /* cc-cd */ ABS+0x200, LONG+0x300, /* ce-cf */ DISP8+0x100, DLOCINDY+0x100, /* d0-d1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* d2-d3 */ DLOC+0x100, DLOCX+0x100, /* d4-d5 */ DLOCX+0x100, DLOCBRAKY+0x100, /* d6-d7 */ IMPLY+0x000, ABSY+0x200, /* d8-d9 */ IMPLY+0x000, IMPLY+0x000, /* da-db */ ABSIND+0x200, ABSX+0x200, /* dc-dd */ ABSX+0x200, LONGX+0x300, /* de-df */ IMMED+0x500, DLOCXIND+0x100, /* e0-e1 */ SEPVAL+0x100, DISP8S+0x100, /* e2-e3 */ DLOC+0x100, DLOC+0x100, /* e4-e5 */ DLOC+0x100, DLOCBRAK+0x100, /* e6-e7 */ IMPLY+0x000, IMMED+0x400, /* e8-e9 */ IMPLY+0x000, IMPLY+0x000, /* ea-eb */ ABS+0x200, ABS+0x200, /* ec-ed */ ABS+0x200, LONG+0x300, /* ee-ef */ DISP8+0x100, DLOCINDY+0x100, /* f0-f1 */ DLOCIND+0x100, DISP8SINDY+0x100, /* f2-f3 */ IMMED+0x200, DLOCX+0x100, /* f4-f5 */ DLOCX+0x100, DLOCBRAKY+0x100, /* f6-f7 */ IMPLY+0x000, ABSY+0x200, /* f8-f9 */ IMPLY+0x000, IMPLY+0x000, /* fa-fb */ ABSXIND+0x200, ABSX+0x200, /* fc-fd */ ABSX+0x200, LONGX+0x300, /* fe-ff */ }; ================================================ FILE: upstream/kegs/src/doc.c ================================================ const char rcsid_doc_c[] = "@(#)$KmKId: doc.c,v 1.2 2023-06-05 18:59:51+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Ensoniq 5503 DOC routines // The Ensoniq DOC is controlled through the Sound GLU at $C03C-$C03F, // where $C03E-$C03F are a 16-bit pointer to "what" to access, $C03D is // the data register, and $C03C is a control register with volume. // The DOC operates at 894.886KHZ (7M clock divided by 8). It visits each // oscillator (up to 32) once per clock, plus 2 extra clocks for DOC RAM // refresh. // KEGS cheats and pretends the DOC runs at 48KHz, and visits all oscillators // each "sample". KEGS adjusts the internal accumulators so the right // frequency is achieved. This allows the sample calculations to be // greatly simplified, and achieves higher fidelity when all 32 osc are // enabled (which is generally how most Apple IIgs code works). #include "defc.h" #include "sound.h" #define DOC_LOG(a,b,c,d) extern int Verbose; extern int g_use_shmem; extern word32 g_vbl_count; extern int g_preferred_rate; extern word32 g_c03ef_doc_ptr; extern dword64 g_last_vbl_dfcyc; byte doc_ram[0x10000 + 16]; word32 g_doc_sound_ctl = 0; word32 g_doc_saved_val = 0; int g_doc_num_osc_en = 1; double g_drecip_osc_en_plus_2 = 1.0 / (double)(1 + 2); int g_doc_vol = 8; int g_doc_saved_ctl = 0; int g_num_osc_interrupting = 0; Doc_reg g_doc_regs[32]; word32 doc_reg_e0 = 0xff; extern double g_drecip_audio_rate; extern double g_dsamps_per_dfcyc; extern double g_fcyc_per_samp; /* local function prototypes */ void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); #define DOC_SCAN_RATE (DCYCS_28_MHZ/32.0) #define UPDATE_G_DCYCS_PER_DOC_UPDATE(osc_en) \ g_drecip_osc_en_plus_2 = 1.0 / (double)(osc_en + 2); #define SND_PTR_SHIFT 14 #define SND_PTR_SHIFT_DBL ((double)(1 << SND_PTR_SHIFT)) void doc_init() { Doc_reg *rptr; int i; for(i = 0; i < 32; i++) { rptr = &(g_doc_regs[i]); rptr->dsamp_ev = 0.0; rptr->dsamp_ev2 = 0.0; rptr->complete_dsamp = 0.0; rptr->samps_left = 0; rptr->cur_acc = 0; rptr->cur_inc = 0; rptr->cur_start = 0; rptr->cur_end = 0; rptr->cur_mask = 0; rptr->size_bytes = 0; rptr->event = 0; rptr->running = 0; rptr->has_irq_pending = 0; rptr->freq = 0; rptr->vol = 0; rptr->waveptr = 0; rptr->ctl = 1; rptr->wavesize = 0; rptr->last_samp_val = 0; } } void doc_reset(dword64 dfcyc) { int i; for(i = 0; i < 32; i++) { doc_write_ctl_reg(dfcyc, i, g_doc_regs[i].ctl | 1); doc_reg_e0 = 0xff; if(g_doc_regs[i].has_irq_pending) { halt_printf("reset: has_irq[%02x] = %d\n", i, g_doc_regs[i].has_irq_pending); } g_doc_regs[i].has_irq_pending = 0; } if(g_num_osc_interrupting) { halt_printf("reset: num_osc_int:%d\n", g_num_osc_interrupting); } g_num_osc_interrupting = 0; g_doc_num_osc_en = 1; UPDATE_G_DCYCS_PER_DOC_UPDATE(1); } int doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, int snd_buf_init, int *outptr_start) { Doc_reg *rptr; int *outptr; double complete_dsamp, cur_dsamp; word32 cur_acc, cur_pos, cur_mask, cur_inc, cur_end; int val, val2, imul, off, ctl, num_osc_en; int samps_left, samps_to_do, samp_offset, pos, osc, done; int i, j; num_osc_en = g_doc_num_osc_en; dbg_log_info(dfcyc, num_samps, 0, 0xd0c0); done = 0; // Do Ensoniq oscillators while(!done) { done = 1; for(j = 0; j < num_osc_en; j++) { osc = j; rptr = &(g_doc_regs[osc]); complete_dsamp = rptr->complete_dsamp; samps_left = rptr->samps_left; cur_acc = rptr->cur_acc; cur_mask = rptr->cur_mask; cur_inc = rptr->cur_inc; cur_end = rptr->cur_end; if(!rptr->running || cur_inc == 0 || (complete_dsamp >= dsamp_now)) { continue; } done = 0; ctl = rptr->ctl; samp_offset = 0; if(complete_dsamp > last_dsamp) { samp_offset = (int)(complete_dsamp- last_dsamp); if(samp_offset > num_samps) { rptr->complete_dsamp = dsamp_now; continue; } } outptr = outptr_start + 2 * samp_offset; if(ctl & 0x10) { /* other channel */ outptr += 1; } imul = (rptr->vol * g_doc_vol); off = imul * 128; samps_to_do = MY_MIN(samps_left, num_samps - samp_offset); if(imul == 0 || samps_to_do == 0) { /* produce no sound */ samps_left = samps_left - samps_to_do; cur_acc += cur_inc * samps_to_do; rptr->samps_left = samps_left; rptr->cur_acc = cur_acc; cur_dsamp = last_dsamp + (double)(samps_to_do + samp_offset); DOC_LOG("nosnd", osc, cur_dsamp, samps_to_do); rptr->complete_dsamp = dsamp_now; cur_pos = rptr->cur_start+(cur_acc & cur_mask); if(samps_left <= 0) { doc_sound_end(osc, 1, cur_dsamp, dsamp_now); val = 0; j--; } else { val = doc_ram[cur_pos >> SND_PTR_SHIFT]; } rptr->last_samp_val = val; continue; } if(snd_buf_init == 0) { memset(outptr_start, 0, 2*sizeof(outptr_start[0])*num_samps); snd_buf_init++; } val = 0; rptr->complete_dsamp = dsamp_now; cur_pos = rptr->cur_start + (cur_acc & cur_mask); pos = 0; for(i = 0; i < samps_to_do; i++) { pos = cur_pos >> SND_PTR_SHIFT; cur_pos += cur_inc; cur_acc += cur_inc; val = doc_ram[pos]; val2 = (val * imul - off) >> 4; if((val == 0) || (cur_pos >= cur_end)) { cur_dsamp = last_dsamp + (double)(samp_offset + i + 1); rptr->cur_acc = cur_acc; rptr->samps_left = 0; DOC_LOG("end or 0", osc, cur_dsamp, ((pos & 0xffffU) << 16) | ((i &0xff) << 8) | val); doc_sound_end(osc, val, cur_dsamp, dsamp_now); val = 0; break; } val2 = outptr[0] + val2; samps_left--; *outptr = val2; outptr += 2; } rptr->last_samp_val = val; if(val != 0) { rptr->cur_acc = cur_acc; rptr->samps_left = samps_left; rptr->complete_dsamp = dsamp_now; } DOC_LOG("splayed", osc, dsamp_now, (samps_to_do << 16) + (pos & 0xffff)); } } return snd_buf_init; } void doc_handle_event(int osc, dword64 dfcyc) { /* handle osc stopping and maybe interrupting */ //DOC_LOG("doc_ev", osc, dsamps, 0); g_doc_regs[osc].event = 0; sound_play(dfcyc); } void doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps) { Doc_reg *rptr, *orptr; int mode, omode; int other_osc; int one_shot_stop; int ctl; /* handle osc stopping and maybe interrupting */ if(osc < 0 || osc > 31) { printf("doc_handle_event: osc: %d!\n", osc); return; } rptr = &(g_doc_regs[osc]); ctl = rptr->ctl; if(rptr->event) { remove_event_doc(osc); } rptr->event = 0; rptr->cur_acc = 0; /* reset internal accumulator*/ /* check to make sure osc is running */ if(ctl & 0x01) { /* Oscillator already stopped. */ halt_printf("Osc %d interrupt, but it was already stop!\n",osc); #ifdef HPUX U_STACK_TRACE(); #endif return; } if(ctl & 0x08) { if(rptr->has_irq_pending == 0) { doc_add_sound_irq(osc); } } if(!rptr->running) { halt_printf("Doc event for osc %d, but ! running\n", osc); } rptr->running = 0; mode = (ctl >> 1) & 3; other_osc = osc ^ 1; orptr = &(g_doc_regs[other_osc]); omode = (orptr->ctl >> 1) & 3; /* If either this osc or it's partner is in swap mode, treat the */ /* pair as being in swap mode. This Ensoniq feature pointed out */ /* by Ian Schmidt */ if(mode == 0 && can_repeat) { /* free-running mode with no 0 byte! */ /* start doing it again */ doc_start_sound(osc, eff_dsamps, dsamps); return; } else if((mode == 3) || (omode == 3)) { /* swap mode (even if we're one_shot and partner is swap)! */ /* unless we're one-shot and we hit a 0-byte--then */ /* Olivier Goguel says just stop */ rptr->ctl |= 1; one_shot_stop = (mode == 1) && (!can_repeat); if(!one_shot_stop && !orptr->running && (orptr->ctl & 0x1)) { orptr->ctl = orptr->ctl & (~1); doc_start_sound(other_osc, eff_dsamps, dsamps); } return; } else { /* stop the oscillator */ rptr->ctl |= 1; } return; } void doc_add_sound_irq(int osc) { int num_osc_interrupting; if(g_doc_regs[osc].has_irq_pending) { halt_printf("Adding sound_irq for %02x, but irq_p: %d\n", osc, g_doc_regs[osc].has_irq_pending); } num_osc_interrupting = g_num_osc_interrupting + 1; g_doc_regs[osc].has_irq_pending = num_osc_interrupting; g_num_osc_interrupting = num_osc_interrupting; add_irq(IRQ_PENDING_DOC); if(num_osc_interrupting == 1) { doc_reg_e0 = 0x00 + (osc << 1); } DOC_LOG("add_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); } void doc_remove_sound_irq(int osc, int must) { Doc_reg *rptr; int num_osc_interrupt; int has_irq_pending; int first; int i; doc_printf("remove irq for osc: %d, has_irq: %d\n", osc, g_doc_regs[osc].has_irq_pending); num_osc_interrupt = g_doc_regs[osc].has_irq_pending; first = 0; if(num_osc_interrupt) { g_num_osc_interrupting--; g_doc_regs[osc].has_irq_pending = 0; DOC_LOG("rem_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); if(g_num_osc_interrupting == 0) { remove_irq(IRQ_PENDING_DOC); } first = 0x40 | (doc_reg_e0 >> 1); /* if none found, then def = no ints */ for(i = 0; i < g_doc_num_osc_en; i++) { rptr = &(g_doc_regs[i]); has_irq_pending = rptr->has_irq_pending; if(has_irq_pending > num_osc_interrupt) { has_irq_pending--; rptr->has_irq_pending = has_irq_pending; } if(has_irq_pending == 1) { first = i; } } if(num_osc_interrupt == 1) { doc_reg_e0 = (first << 1); } else { #if 0 halt_printf("remove_sound_irq[%02x]=%d, first:%d\n", osc, num_osc_interrupt, first); #endif } } else { #if 0 /* make sure no int pending */ if(doc_reg_e0 != 0xff) { halt_printf("remove_sound_irq[%02x]=0, but e0: %02x\n", osc, doc_reg_e0); } #endif if(must) { halt_printf("REMOVE_sound_irq[%02x]=0, but e0: %02x\n", osc, doc_reg_e0); } } if(doc_reg_e0 & 0x80) { for(i = 0; i < 0x20; i++) { has_irq_pending = g_doc_regs[i].has_irq_pending; if(has_irq_pending) { halt_printf("remove_sound_irq[%02x], but " "[%02x]=%d!\n", osc,i,has_irq_pending); printf("num_osc_int: %d, first: %02x\n", num_osc_interrupt, first); } } } } void doc_start_sound2(int osc, dword64 dfcyc) { double dsamps; dsamps = dfcyc * g_dsamps_per_dfcyc; doc_start_sound(osc, dsamps, dsamps); } void doc_start_sound(int osc, double eff_dsamps, double dsamps) { Doc_reg *rptr; int ctl; int mode; word32 sz; word32 size; word32 wave_size; if(osc < 0 || osc > 31) { halt_printf("start_sound: osc: %02x!\n", osc); } rptr = &(g_doc_regs[osc]); if(osc >= g_doc_num_osc_en) { rptr->ctl |= 1; return; } ctl = rptr->ctl; mode = (ctl >> 1) & 3; wave_size = rptr->wavesize; sz = ((wave_size >> 3) & 7) + 8; size = 1 << sz; if(size < 0x100) { halt_printf("size: %08x is too small, sz: %08x!\n", size, sz); } if(rptr->running) { halt_printf("start_sound osc: %d, already running!\n", osc); } rptr->running = 1; rptr->complete_dsamp = eff_dsamps; doc_printf("Starting osc %02x, dsamp: %f\n", osc, dsamps); doc_printf("size: %04x\n", size); if((mode == 2) && ((osc & 1) == 0)) { printf("Sync mode osc %d starting!\n", osc); /* set_halt(1); */ /* see if we should start our odd partner */ if((rptr[1].ctl & 7) == 5) { /* odd partner stopped in sync mode--start him */ rptr[1].ctl &= (~1); doc_start_sound(osc + 1, eff_dsamps, dsamps); } else { printf("Osc %d starting sync, but osc %d ctl: %02x\n", osc, osc+1, rptr[1].ctl); } } doc_wave_end_estimate(osc, eff_dsamps, dsamps); DOC_LOG("st playing", osc, eff_dsamps, size); #if 0 if(rptr->cur_acc != 0) { halt_printf("Start osc %02x, acc: %08x\n", osc, rptr->cur_acc); } #endif } void doc_wave_end_estimate2(int osc, dword64 dfcyc) { double dsamps; dsamps = dfcyc * g_dsamps_per_dfcyc; doc_wave_end_estimate(osc, dsamps, dsamps); } void doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps) { Doc_reg *rptr; byte *ptr1; dword64 event_dfcyc; double event_dsamp, dfcycs_per_samp, dsamps_per_byte, num_dsamps; double dcur_inc; word32 tmp1, cur_inc, save_val; int save_size, pos, size, estimate; dfcycs_per_samp = g_fcyc_per_samp; rptr = &(g_doc_regs[osc]); cur_inc = rptr->cur_inc; dcur_inc = (double)cur_inc; dsamps_per_byte = 0.0; if(cur_inc) { dsamps_per_byte = SND_PTR_SHIFT_DBL / (double)dcur_inc; } /* see if there's a zero byte */ tmp1 = rptr->cur_start + (rptr->cur_acc & rptr->cur_mask); pos = tmp1 >> SND_PTR_SHIFT; size = ((rptr->cur_end) >> SND_PTR_SHIFT) - pos; ptr1 = &doc_ram[pos]; estimate = 0; if(rptr->ctl & 0x08 || g_doc_regs[osc ^ 1].ctl & 0x08) { estimate = 1; } #if 0 estimate = 1; #endif if(estimate) { save_size = size; save_val = ptr1[size]; ptr1[size] = 0; size = (int)strlen((char *)ptr1); ptr1[save_size] = save_val; } /* calc samples to play */ num_dsamps = (dsamps_per_byte * (double)size) + 1.0; rptr->samps_left = (int)num_dsamps; if(rptr->event) { remove_event_doc(osc); } rptr->event = 0; event_dsamp = eff_dsamps + num_dsamps; if(estimate) { rptr->event = 1; rptr->dsamp_ev = event_dsamp; rptr->dsamp_ev2 = dsamps; event_dfcyc = (dword64)(event_dsamp * dfcycs_per_samp) + 65536LL; add_event_doc(event_dfcyc, osc); } } void doc_remove_sound_event(int osc) { if(g_doc_regs[osc].event) { g_doc_regs[osc].event = 0; remove_event_doc(osc); } } void doc_write_ctl_reg(dword64 dfcyc, int osc, int val) { Doc_reg *rptr; word32 old_halt, new_halt; int old_val; if(osc < 0 || osc >= 0x20) { halt_printf("doc_write_ctl_reg: osc: %02x, val: %02x\n", osc, val); return; } rptr = &(g_doc_regs[osc]); old_val = rptr->ctl; g_doc_saved_ctl = old_val; if(old_val == val) { return; } //DOC_LOG("ctl_reg", osc, dsamps, (old_val << 16) + val); old_halt = (old_val & 1); new_halt = (val & 1); /* bits are: 28: old int bit */ /* 29: old halt bit */ /* 30: new int bit */ /* 31: new halt bit */ #if 0 if(osc == 0x10) { printf("osc %d new_ctl: %02x, old: %02x\n", osc, val, old_val); } #endif /* no matter what, remove any pending IRQs on this osc */ doc_remove_sound_irq(osc, 0); #if 0 if(old_halt) { printf("doc_write_ctl to osc %d, val: %02x, old: %02x\n", osc, val, old_val); } #endif if(new_halt != 0) { /* make sure sound is stopped */ doc_remove_sound_event(osc); if(old_halt == 0) { /* it was playing, finish it up */ #if 0 halt_printf("Aborted osc %d at eff_dsamps: %f, ctl: " "%02x, oldctl: %02x\n", osc, eff_dsamps, val, old_val); #endif sound_play(dfcyc); } if(((old_val >> 1) & 3) > 0) { /* don't clear acc if free-running */ g_doc_regs[osc].cur_acc = 0; } g_doc_regs[osc].ctl = val; g_doc_regs[osc].running = 0; } else { /* new halt == 0 = make sure sound is running */ if(old_halt != 0) { /* start sound */ //DOC_LOG("ctl_sound_play", osc, eff_dsamps, val); sound_play(dfcyc); g_doc_regs[osc].ctl = val; doc_start_sound2(osc, dfcyc); } else { /* was running, and something changed */ doc_printf("osc %d old ctl:%02x new:%02x!\n", osc, old_val, val); sound_play(dfcyc); g_doc_regs[osc].ctl = val; if((old_val ^ val) & val & 0x8) { /* now has ints on */ doc_wave_end_estimate2(osc, dfcyc); } } } } void doc_recalc_sound_parms(dword64 dfcyc, int osc) { Doc_reg *rptr; double dfreq, dtmp1, dacc, dacc_recip; word32 res, sz, size, wave_size, cur_start, shifted_size; rptr = &(g_doc_regs[osc]); wave_size = rptr->wavesize; dfreq = (double)rptr->freq; sz = ((wave_size >> 3) & 7) + 8; size = 1 << sz; rptr->size_bytes = size; res = wave_size & 7; shifted_size = size << SND_PTR_SHIFT; cur_start = (rptr->waveptr << (8 + SND_PTR_SHIFT)) & (0-shifted_size); dtmp1 = dfreq * (DOC_SCAN_RATE * g_drecip_audio_rate); dacc = (double)(1 << (20 - (17 - sz + res))); dacc_recip = (SND_PTR_SHIFT_DBL) / ((double)(1 << 20)); dtmp1 = dtmp1 * g_drecip_osc_en_plus_2 * dacc * dacc_recip; rptr->cur_inc = (int)(dtmp1); rptr->cur_start = cur_start; rptr->cur_end = cur_start + shifted_size; rptr->cur_mask = (shifted_size - 1); dbg_log_info(dfcyc, (rptr->waveptr << 16) + wave_size, osc, 0xd0cf); } int doc_read_c03c() { return g_doc_sound_ctl; } int doc_read_c03d(dword64 dfcyc) { Doc_reg *rptr; int osc, type, ret; ret = g_doc_saved_val; if(g_doc_sound_ctl & 0x40) { /* Read RAM */ g_doc_saved_val = doc_ram[g_c03ef_doc_ptr]; } else { /* Read DOC */ g_doc_saved_val = 0; osc = g_c03ef_doc_ptr & 0x1f; type = (g_c03ef_doc_ptr >> 5) & 0x7; rptr = &(g_doc_regs[osc]); switch(type) { case 0x0: /* freq lo */ g_doc_saved_val = rptr->freq & 0xff; break; case 0x1: /* freq hi */ g_doc_saved_val = rptr->freq >> 8; break; case 0x2: /* vol */ g_doc_saved_val = rptr->vol; break; case 0x3: /* data register */ sound_play(dfcyc); // Fix for Paperboy GS g_doc_saved_val = rptr->last_samp_val; break; case 0x4: /* wave ptr register */ g_doc_saved_val = rptr->waveptr; break; case 0x5: /* control register */ g_doc_saved_val = rptr->ctl; break; case 0x6: /* control register */ g_doc_saved_val = rptr->wavesize; break; case 0x7: /* 0xe0-0xff */ switch(osc) { case 0x00: /* 0xe0 */ g_doc_saved_val = doc_reg_e0; doc_printf("Reading doc 0xe0, ret: %02x\n", g_doc_saved_val); /* Clear IRQ on read of e0, if any irq pend */ if((doc_reg_e0 & 0x80) == 0) { doc_remove_sound_irq(doc_reg_e0 >> 1, 1); } break; case 0x01: /* 0xe1 */ g_doc_saved_val = (g_doc_num_osc_en - 1) << 1; break; case 0x02: /* 0xe2 */ g_doc_saved_val = 0x80; #if 0 halt_printf("Reading doc 0xe2, ret: %02x\n", g_doc_saved_val); #endif break; default: g_doc_saved_val = 0; halt_printf("Reading bad doc_reg[%04x]: %02x\n", g_c03ef_doc_ptr, g_doc_saved_val); } break; default: g_doc_saved_val = 0; halt_printf("Reading bad doc_reg[%04x]: %02x\n", g_c03ef_doc_ptr, g_doc_saved_val); } } doc_printf("read c03d, doc_ptr: %04x, ret: %02x, saved: %02x\n", g_c03ef_doc_ptr, ret, g_doc_saved_val); //DOC_LOG("read c03d", -1, dsamps, (g_c03ef_doc_ptr << 16) + // (g_doc_saved_val << 8) + ret); if(g_doc_sound_ctl & 0x20) { g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; } return ret; } void doc_write_c03c(dword64 dfcyc, word32 val) { int vol; vol = val & 0xf; dbg_log_info(dfcyc, val, g_doc_vol, 0xc03c); if(g_doc_vol != vol) { sound_play(dfcyc); g_doc_vol = vol; doc_printf("Setting doc vol to 0x%x at %016llx\n", vol, dfcyc); } g_doc_sound_ctl = val; } void doc_write_c03d(dword64 dfcyc, word32 val) { Doc_reg *rptr; int osc, type, ctl, tmp; int i; val = val & 0xff; doc_printf("write c03d, doc_ptr: %04x, val: %02x\n", g_c03ef_doc_ptr, val); dbg_log_info(dfcyc, g_c03ef_doc_ptr, val, 0xc03d); if(g_doc_sound_ctl & 0x40) { /* RAM */ doc_ram[g_c03ef_doc_ptr] = val; } else { /* DOC */ osc = g_c03ef_doc_ptr & 0x1f; type = (g_c03ef_doc_ptr >> 5) & 0x7; rptr = &(g_doc_regs[osc]); ctl = rptr->ctl; #if 0 if((ctl & 1) == 0) { if(type < 2 || type == 4 || type == 6) { halt_printf("Osc %d is running, old ctl: %02x, " "but write reg %02x=%02x\n", osc, ctl, g_c03ef_doc_ptr & 0xff, val); } } #endif switch(type) { case 0x0: /* freq lo */ if((rptr->freq & 0xff) == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("flo_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->freq = (rptr->freq & 0xff00) + val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x1: /* freq hi */ if((rptr->freq >> 8) == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("fhi_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->freq = (rptr->freq & 0xff) + (val << 8); doc_recalc_sound_parms(dfcyc, osc); break; case 0x2: /* vol */ if(rptr->vol == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("vol_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->vol = val; break; case 0x3: /* data register */ #if 0 printf("Writing %02x into doc_data_reg[%02x]!\n", val, osc); #endif break; case 0x4: /* wave ptr register */ if(rptr->waveptr == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("wptr_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->waveptr = val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x5: /* control register */ #if 0 printf("doc_write ctl osc %d, val: %02x\n", osc, val); #endif if(rptr->ctl == (word32)val) { break; } doc_write_ctl_reg(dfcyc, osc, val); break; case 0x6: /* wavesize register */ if(rptr->wavesize == (word32)val) { break; } if((ctl & 1) == 0) { /* play through current status */ //DOC_LOG("wsz_sound_play", osc, dsamps, val); sound_play(dfcyc); } rptr->wavesize = val; doc_recalc_sound_parms(dfcyc, osc); break; case 0x7: /* 0xe0-0xff */ switch(osc) { case 0x00: /* 0xe0 */ doc_printf("writing doc 0xe0 with %02x, " "was:%02x\n", val, doc_reg_e0); #if 0 if(val != doc_reg_e0) { halt_printf("writing doc 0xe0 with " "%02x, was:%02x\n", val, doc_reg_e0); } #endif break; case 0x01: /* 0xe1 */ doc_printf("Writing doc 0xe1 with %02x\n", val); tmp = val & 0x3e; tmp = (tmp >> 1) + 1; if(tmp < 1) { tmp = 1; } if(tmp > 32) { halt_printf("doc 0xe1: %02x!\n", val); tmp = 32; } g_doc_num_osc_en = tmp; UPDATE_G_DCYCS_PER_DOC_UPDATE(tmp); /* Stop any oscs that were running but now */ /* are disabled */ for(i = g_doc_num_osc_en; i < 0x20; i++) { doc_write_ctl_reg(dfcyc, i, g_doc_regs[i].ctl | 1); } break; default: /* this should be illegal, but Turkey Shoot */ /* and apparently TaskForce, OOTW, etc */ /* writes to e2-ff, for no apparent reason */ doc_printf("Writing doc 0x%x with %02x\n", g_c03ef_doc_ptr, val); break; } break; default: halt_printf("Writing %02x into bad doc_reg[%04x]\n", val, g_c03ef_doc_ptr); } } if(g_doc_sound_ctl & 0x20) { g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; } g_doc_saved_val = val; } void doc_show_ensoniq_state() { Doc_reg *rptr; int i; printf("Ensoniq state\n"); printf("c03c doc_sound_ctl: %02x, doc_saved_val: %02x\n", g_doc_sound_ctl, g_doc_saved_val); printf("doc_ptr: %04x, num_osc_en: %02x, e0: %02x\n", g_c03ef_doc_ptr, g_doc_num_osc_en, doc_reg_e0); for(i = 0; i < 32; i += 8) { printf("irqp: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", i, g_doc_regs[i].has_irq_pending, g_doc_regs[i + 1].has_irq_pending, g_doc_regs[i + 2].has_irq_pending, g_doc_regs[i + 3].has_irq_pending, g_doc_regs[i + 4].has_irq_pending, g_doc_regs[i + 5].has_irq_pending, g_doc_regs[i + 6].has_irq_pending, g_doc_regs[i + 7].has_irq_pending); } for(i = 0; i < 32; i += 8) { printf("freq: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", i, g_doc_regs[i].freq, g_doc_regs[i + 1].freq, g_doc_regs[i + 2].freq, g_doc_regs[i + 3].freq, g_doc_regs[i + 4].freq, g_doc_regs[i + 5].freq, g_doc_regs[i + 6].freq, g_doc_regs[i + 7].freq); } for(i = 0; i < 32; i += 8) { printf("vol: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].vol, g_doc_regs[i + 1].vol, g_doc_regs[i + 2].vol, g_doc_regs[i + 3].vol, g_doc_regs[i + 4].vol, g_doc_regs[i + 5].vol, g_doc_regs[i + 6].vol, g_doc_regs[i + 6].vol); } for(i = 0; i < 32; i += 8) { printf("wptr: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].waveptr, g_doc_regs[i + 1].waveptr, g_doc_regs[i + 2].waveptr, g_doc_regs[i + 3].waveptr, g_doc_regs[i + 4].waveptr, g_doc_regs[i + 5].waveptr, g_doc_regs[i + 6].waveptr, g_doc_regs[i + 7].waveptr); } for(i = 0; i < 32; i += 8) { printf("ctl: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].ctl, g_doc_regs[i + 1].ctl, g_doc_regs[i + 2].ctl, g_doc_regs[i + 3].ctl, g_doc_regs[i + 4].ctl, g_doc_regs[i + 5].ctl, g_doc_regs[i + 6].ctl, g_doc_regs[i + 7].ctl); } for(i = 0; i < 32; i += 8) { printf("wsize: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i, g_doc_regs[i].wavesize, g_doc_regs[i + 1].wavesize, g_doc_regs[i + 2].wavesize, g_doc_regs[i + 3].wavesize, g_doc_regs[i + 4].wavesize, g_doc_regs[i + 5].wavesize, g_doc_regs[i + 6].wavesize, g_doc_regs[i + 7].wavesize); } for(i = 0; i < 32; i++) { rptr = &(g_doc_regs[i]); printf("%2d: ctl:%02x wp:%02x ws:%02x freq:%04x vol:%02x " "ev:%d run:%d irq:%d sz:%04x\n", i, rptr->ctl, rptr->waveptr, rptr->wavesize, rptr->freq, rptr->vol, rptr->event, rptr->running, rptr->has_irq_pending, rptr->size_bytes); printf(" acc:%08x inc:%08x st:%08x end:%08x m:%08x\n", rptr->cur_acc, rptr->cur_inc, rptr->cur_start, rptr->cur_end, rptr->cur_mask); printf(" compl_ds:%f samps_left:%d ev:%f ev2:%f\n", rptr->complete_dsamp, rptr->samps_left, rptr->dsamp_ev, rptr->dsamp_ev2); } #if 0 for(osc = 0; osc < 32; osc++) { fmax = 0.0; printf("osc %d has %d samps\n", osc, g_fsamp_num[osc]); for(i = 0; i < g_fsamp_num[osc]; i++) { printf("%4d: %f\n", i, g_fsamps[osc][i]); fmax = MY_MAX(fmax, g_fsamps[osc][i]); } printf("osc %d, fmax: %f\n", osc, fmax); } #endif } ================================================ FILE: upstream/kegs/src/dyna_filt.c ================================================ const char rcsid_dynafilt_c[] = "@(#)$KmKId: dyna_filt.c,v 1.1 2021-08-09 03:13:55+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" // Provide filters for Dynapro to use for copying files from the host to // a ProDOS volume, and for writing changes to the ProDOS volume back to // host files. ================================================ FILE: upstream/kegs/src/dyna_type.c ================================================ const char rcsid_dynatype_c[] = "@(#)$KmKId: dyna_type.c,v 1.9 2023-05-19 13:52:30+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2021-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" // Provide routines for Dynapro to use for detecting the file type on the // host system. Host files can be "basic1.bas", "basic2,tbas,a$801" STRUCT(Dynatype_extensions) { char str[16]; word16 file_type; word16 aux_type; }; Dynatype_extensions g_dynatype_extensions[] = { { "applesingle", 0xfff, 0xffff }, { "txt", 0x04, 0 }, { "c", 0x04, 0 }, // ,ttxt { "s", 0x04, 0 }, // ,ttxt { "h", 0x04, 0 }, // ,ttxt { "bin", 0x06, 0x2000 }, // ,tbin { "bas", 0xfc, 0x0801 }, // ,tbas { "system", 0xff, 0x2000 }, // ,tsys //{ "shr", 0xc0, 0x0002 }, // ,t$c0 { "shk", 0xe0, 0x8002 }, // ,t$e0 { "sdk", 0xe0, 0x8002 }, // ,t$e0 { "", 0, 0 } }; STRUCT(Dynatype_types) { char str[16]; word16 file_type; word16 aux_type; }; Dynatype_types g_dynatype_types[] = { { "non", 0x00, 0 }, { "bad", 0x01, 0 }, { "txt", 0x04, 0 }, { "bin", 0x06, 0x2000 }, { "pnt", 0xc0, 0x0002 }, { "fnd", 0xc9, 0 }, { "icn", 0xca, 0 }, { "cmd", 0xf0, 0 }, { "bas", 0xfc, 0x0801 }, { "sys", 0xff, 0x2000 }, { "", 0, 0 } }; word32 dynatype_scan_extensions(const char *str) { int len; int i; len = (int)(sizeof(g_dynatype_extensions) / sizeof(g_dynatype_extensions[0])); for(i = 0; i < len; i++) { if(cfgcasecmp(str, g_dynatype_extensions[i].str) == 0) { return (g_dynatype_extensions[i].file_type << 16) | g_dynatype_extensions[i].aux_type | 0x1000000; } } return 0; } word32 dynatype_find_prodos_type(const char *str) { word32 file_type; int len; int i; len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); for(i = 0; i < len; i++) { if(cfgcasecmp(str, g_dynatype_types[i].str) == 0) { file_type = g_dynatype_types[i].file_type; return (file_type << 16) | g_dynatype_types[i].aux_type; } } return 0; } const char * dynatype_find_file_type(word32 file_type) { int len; int i; len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); for(i = 0; i < len; i++) { if(g_dynatype_types[i].file_type == file_type) { return g_dynatype_types[i].str; } } return 0; } word32 dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, word32 storage_type) { char ext_buf[32]; const char *str; char *endstr; word32 file_type, aux_type, type_or_aux; int len, this_len, c, pos; // Look for ,tbas and ,a$2000 to get filetype and aux_type info str = cfg_str_basename(path_ptr); len = (int)strlen(str); // Look for .ext and ,tbas, etc. pos = 0; ext_buf[0] = 0; file_type = 0x06; // Default to BIN aux_type = 0; while(pos < len) { c = str[pos++]; if(c == '.') { this_len = dynatype_get_extension(&str[pos], &ext_buf[0], 30); pos += this_len; continue; } else if(c == ',') { this_len = dynatype_comma_arg(&str[pos], &type_or_aux); if(type_or_aux & 0x1000000) { file_type = type_or_aux; } else if(type_or_aux & 0x2000000) { aux_type = type_or_aux; } else { printf("Unknown , extension, %s ignored\n", &str[pos]); } pos += this_len; continue; } else if(c == '#') { // Cadius style encoding: #ff2000 is type=$ff, aux=$2000 type_or_aux = strtol(&str[pos], &endstr, 16); file_type = (type_or_aux & 0xffffff) | 0x1000000; aux_type = 0; pos += (int)(endstr - str); continue; } } // Handle extensions and type. First do extension mapping if(ext_buf[0]) { type_or_aux = dynatype_scan_extensions(&ext_buf[0]); if((type_or_aux) >= 0x0f000000UL) { // AppleSingle storage_type = 0x50; // Forked file } if(file_type < 0x1000000) { file_type = type_or_aux; } if(aux_type < 0x1000000) { aux_type = type_or_aux; } } #if 0 printf("After parsing ext, file_type:%08x, aux_type:%08x\n", file_type, aux_type); #endif fileptr->file_type = (file_type >> 16) & 0xff; if(aux_type == 0) { aux_type = file_type & 0xffff; } fileptr->aux_type = aux_type & 0xffff; return storage_type; } int dynatype_get_extension(const char *str, char *out_ptr, int buf_len) { int c, len; // Will write up to buf_len chars to out_ptr if(buf_len < 1) { return 0; } buf_len--; len = 0; while(1) { c = *str++; *out_ptr = c; if((c == 0) || (c == '.') || (c == ',') || (c == '#') || (len >= buf_len)) { *out_ptr = 0; return len; } out_ptr++; len++; } } int dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr) { char type_buf[8]; char *endstr; word32 val, type_or_aux; int c, len, base, this_len; int i; // Read next char *type_or_aux_ptr = 0; c = *str++; if(c == 0) { return 0; } len = 1; c = tolower(c); type_or_aux = c; // See if next char is $ for hex c = *str; base = 0; if(c == '$') { base = 16; str++; len++; } val = strtol(str, &endstr, base); this_len = (int)(endstr - str); if((val == 0) && (this_len < 2) && (base == 0) && (type_or_aux == 't')) { // Not a valid number for(i = 0; i < 3; i++) { c = *str++; if(c == 0) { return len; } type_buf[i] = c; len++; } type_buf[3] = 0; val = dynatype_find_prodos_type(&type_buf[0]); *type_or_aux_ptr = 0x1000000 | val; } else { len += this_len; } if(type_or_aux == 't') { if(val < 0x100) { *type_or_aux_ptr = 0x1000000 | ((val << 16) & 0xffffff); } } else if(type_or_aux == 'a') { *type_or_aux_ptr = 0x2000000 | (val & 0xffff); } return len; } void dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max) { char buf[16]; Dynapro_file tmpfile; const char *str; word32 aux_type; #if 0 printf("Looking at %s ftype:%02x aux:%04x\n", outbuf_ptr, fileptr->file_type, fileptr->aux_type); #endif if(fileptr->prodos_name[0] >= 0xd0) { return; // Directory, or Dir/Volume Header } if((fileptr->prodos_name[0] & 0xf0) == 0x50) { // Forked file, add .applesingle cfg_strlcat(outbuf_ptr, ".applesingle", path_max); return; } memset(&tmpfile, 0, sizeof(Dynapro_file)); // See what this file defaults to as to type/aux (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); // Otherwise, add ,ttype and ,a$aux as needed if(tmpfile.file_type != fileptr->file_type) { str = dynatype_find_file_type(fileptr->file_type); if(str) { aux_type = dynatype_find_prodos_type(str); } else { str = &buf[0]; buf[15] = 0; snprintf(&buf[0], 15, "$%02x", fileptr->file_type); } cfg_strlcat(outbuf_ptr, ",t", path_max); cfg_strlcat(outbuf_ptr, str, path_max); } (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); aux_type = fileptr->aux_type; if(aux_type != tmpfile.aux_type) { buf[15] = 0; snprintf(&buf[0], 15, ",a$%04x", aux_type & 0xffff); cfg_strlcat(outbuf_ptr, &buf[0], path_max); } // printf("dynatype_new_unix_name: %s\n", outbuf_ptr); // Check that it succeeded (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); if((tmpfile.file_type != fileptr->file_type) || (tmpfile.aux_type != fileptr->aux_type)) { halt_printf("File %s want ftype:%02x aux:%04x, got:%02x %04x\n", outbuf_ptr, fileptr->file_type, fileptr->aux_type, tmpfile.file_type, tmpfile.aux_type); exit(1); } } ================================================ FILE: upstream/kegs/src/dyna_validate.c ================================================ const char rcsid_dyna_validate_c[] = "@(#)$KmKId: dyna_validate.c,v 1.8 2023-05-21 20:06:24+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2021-2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Main information is from Beneath Apple ProDOS which has disk layout // descriptions. Forked files are described in Technote tn-pdos-025. #include "defc.h" word32 dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, word32 parent_dir_byte) { word32 storage_type, exp_type, val, parent_block, exp_val; storage_type = fileptr->prodos_name[0] & 0xf0; exp_type = 0xe0; if(dir_byte == 0x0404) { exp_type = 0xf0; // Volume header } if(storage_type != exp_type) { printf("Volume/Dir header is %02x at %07x\n", storage_type, dir_byte); return 0; } if(fileptr->aux_type != 0x0d27) { printf("entry_length, entries_per_block:%04x at %07x\n", fileptr->aux_type, dir_byte); return 0; } if(exp_type == 0xf0) { // Volume header val = fileptr->lastmod_time >> 16; if(val != 6) { printf("bit_map_ptr:%04x, should be 6\n", val); return 0; } val = fileptr->header_pointer; if(val != (dsk->dimage_size >> 9)) { printf("Num blocks at %07x is wrong: %04x\n", dir_byte, val); return 0; } } else { // Directory header val = fileptr->lastmod_time >> 16; // parent_pointer parent_block = parent_dir_byte >> 9; if(val != parent_block) { printf("Dir at %07x parent:%04x should be %04x\n", dir_byte, val, parent_block); return 0; } val = fileptr->header_pointer; exp_val = ((parent_dir_byte & 0x1ff) - 4) / 0x27; exp_val = (exp_val + 1) | 0x2700; if(val != exp_val) { printf("Parent entry at %07x is:%04x, should be:%04x\n", dir_byte, val, exp_val); return 0; } } return 1; } void dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks) { word32 num_map_blocks, mask; int pos; word32 ui; for(ui = 0; ui < (num_blocks + 7)/8; ui++) { freeblks_ptr[ui] = 0xff; } freeblks_ptr[0] &= 0x3f; if(num_blocks & 7) { freeblks_ptr[num_blocks / 8] = 0xff00 >> (num_blocks & 7); } num_map_blocks = (num_blocks + 4095) >> 12; // 4096 bits per block for(ui = 0; ui < num_map_blocks; ui++) { // Mark blocks used in the bitmap as in use pos = (ui + 6) >> 3; mask = 0x80 >> ((ui + 6) & 7); freeblks_ptr[pos] &= (~mask); } } word32 dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block) { word32 mask, ret; int pos; // Return != 0 if block is free (which is success), returns == 0 // if it is in use (which is an error). Marks block as in use pos = block >> 3; if(block >= (dsk->dimage_size >> 9)) { return 0x100; // Out of range } mask = 0x80 >> (block & 7); ret = freeblks_ptr[pos] & mask; freeblks_ptr[pos] &= (~mask); if(!ret) { printf("Block %04x was already in use\n", block); } return ret; } word32 dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof, int level_first) { byte *bptr; word32 num_blocks, tmp, ret, exp_blocks, extra_blocks; int level, first; int i; level = level_first & 0xf; first = level_first & 0x10; if(!dynapro_validate_freeblk(dsk, freeblks_ptr, block_num)) { return 0; } if(level_first == 0x15) { return dynapro_validate_forked_file(dsk, freeblks_ptr, block_num, eof); } if((level < 1) || (level >= 4)) { printf("level %d out of range, %08x\n", level, level_first); return 0; } if(level == 1) { return 1; } num_blocks = 1; bptr = &(dsk->raw_data[block_num * 0x200]); for(i = 0; i < 256; i++) { tmp = bptr[i] + (bptr[256 + i] << 8); if(tmp == 0) { if(first) { printf("First block is spare, illegal!\n"); return 0; } continue; } ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, first | (level - 1)); if(ret == 0) { return 0; } num_blocks += ret; first = 0; } if(level_first & 0x10) { // Try to estimate exp_blocks based on eof exp_blocks = (eof + 0x1ff) >> 9; if(exp_blocks == 0) { exp_blocks = 1; } else if(exp_blocks > 1) { // Add in sapling blocks extra_blocks = ((exp_blocks + 255) >> 8); if(exp_blocks > 256) { extra_blocks++; } exp_blocks += extra_blocks; } if(num_blocks > exp_blocks) { printf("blocks_used:%04x, eof:%07x, exp:%04x\n", num_blocks, eof, exp_blocks); return 0; } } return num_blocks; } word32 dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof) { byte *bptr; word32 num_blocks, tmp, ret, size, type, exp_blocks; int level; int i; bptr = &(dsk->raw_data[block_num * 0x200]); if(eof != 0x200) { printf("In forked file block %04x, eof in dir:%08x, exp 0200\n", block_num, eof); return 0; } // Check that most of the block is 0 for(i = 44; i < 512; i++) { if((i >= 0x100) && (i < 0x108)) { continue; } if(bptr[i] != 0) { printf("In forked file block:%04x, byte %03x is %02x\n", block_num, i, bptr[i]); return 0; } } // Check for basic Finder Info format for(i = 0; i < 2; i++) { size = bptr[8 + 18*i]; type = bptr[9 + 18*i]; if(((size != 0) && (size != 18)) || (type > 2)) { printf("Finder Info size %04x+%03x=%02x, type:%02x\n", block_num, 8 + 18*i, size, type); return 0; } } num_blocks = 1; for(i = 0; i < 2; i++) { tmp = bptr[1 + 0x100*i] + (bptr[2 + 0x100*i] << 8); if(tmp == 0) { printf("First fork %d block is spare, illegal!\n", i); return 0; } eof = bptr[5 + 0x100*i] + (bptr[6 + 0x100*i] << 8) + (bptr[7 + 0x100*i] << 16); level = bptr[0 + 0x100*i]; ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, 0x10 | level); if(ret == 0) { printf("Fork %d failed, eof:%08x, block:%04x " "fork:%04x, level:%d\n", i, eof, block_num, tmp, level); return 0; } exp_blocks = bptr[3 + 0x100*i] + (bptr[4 + 0x100*i] << 8); if(ret != exp_blocks) { printf("Fork %d at %04x, blocks:%04x, exp:%04x\n", i, block_num, ret, exp_blocks); } num_blocks += ret; } return num_blocks; } word32 dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, word32 parent_dir_byte, word32 exp_blocks_used) { char buf32[32]; Dynapro_file localfile; byte *bptr; word32 start_dir_block, last_block, max_block, tmp_byte, sub_blocks; word32 ret, act_entries, exp_entries, blocks_used, prev, next; int cnt, is_header; // Read directory, make sure each entry is consistent // Return 0 if there is damage, != 0 if OK. bptr = dsk->raw_data; start_dir_block = dir_byte >> 9; last_block = 0; max_block = (word32)(dsk->dimage_size >> 9); cnt = 0; is_header = 1; exp_entries = 0xdeadbeef; act_entries = 0; blocks_used = 0; while(dir_byte) { if((dir_byte & 0x1ff) == 4) { // First entry in this block, check prev/next tmp_byte = dir_byte & -0x200; // Block align prev = dynapro_get_word16(&bptr[tmp_byte + 0]); next = dynapro_get_word16(&bptr[tmp_byte + 2]); if((prev != last_block) || (next >= max_block)) { printf("dir at %07x is damaged in links\n", dir_byte); return 0; } last_block = dir_byte >> 9; ret = dynapro_validate_freeblk(dsk, freeblks_ptr, dir_byte >> 9); if(!ret) { return 0; } blocks_used++; } if(cnt++ >= 65536) { printf("Loop detected, dir_byte:%07x\n", dir_byte); return 0; } ret = dynapro_fill_fileptr_from_prodos(dsk, &localfile, &buf32[0], dir_byte); if(ret == 0) { return 0; } if(ret != 1) { act_entries = act_entries + 1 - is_header; } if(is_header) { if(ret == 1) { printf("Volume/Dir header is erased\n"); return 0; } ret = dynapro_validate_header(dsk, &localfile, dir_byte, parent_dir_byte); if(ret == 0) { return 0; } exp_entries = localfile.lastmod_time & 0xffff; } else if(ret != 1) { if(localfile.header_pointer != start_dir_block) { printf("At %07x, header_ptr:%04x != %04x\n", dir_byte, localfile.header_pointer, start_dir_block); return 0; } if(localfile.prodos_name[0] >= 0xd0) { sub_blocks = localfile.blocks_used; if(localfile.eof != (sub_blocks * 0x200UL)) { printf("At %07x, eof:%08x != %08x\n", dir_byte, localfile.eof, sub_blocks * 0x200U); return 0; } ret = dynapro_validate_dir(dsk, freeblks_ptr, (localfile.key_block * 0x200) + 4, dir_byte, sub_blocks); if(ret == 0) { return 0; } } else { ret = dynapro_validate_file(dsk, freeblks_ptr, localfile.key_block, localfile.eof, 0x10 | (localfile.prodos_name[0] >> 4)); if(ret == 0) { printf("At %07x, bad file\n", dir_byte); return 0; } if(localfile.blocks_used != ret) { printf("At %07x, blocks_used prodos " "%04x != %04x calc\n", dir_byte, localfile.blocks_used, ret); return 0; } } } is_header = 0; dir_byte = dir_byte + 0x27; tmp_byte = (dir_byte & 0x1ff) + 0x27; if(tmp_byte < 0x200) { continue; } tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; if(dir_byte == 0) { if(act_entries != exp_entries) { printf("act_entries:%04x != exp:%04x, " "dir_block:%04x\n", act_entries, exp_entries, start_dir_block); return 0; } if(blocks_used != exp_blocks_used) { printf("At dir %07x, blocks_used:%04x!=%04x " "exp\n", tmp_byte, blocks_used, exp_blocks_used); return 0; } return 1; } dir_byte += 4; if(dir_byte >= (max_block * 0x200L)) { printf(" invalid link pointer %07x\n", dir_byte); return 0; } } return 0; } int dynapro_validate_disk(Disk *dsk) { byte freeblks[65536/8]; // 8KB byte *bptr; word32 num_blocks, ret; word32 ui; num_blocks = (word32)(dsk->dimage_size >> 9); printf("******************************\n"); printf("Validate disk: %s, blocks:%05x\n", dsk->name_ptr, num_blocks); dynapro_validate_init_freeblks(&freeblks[0], num_blocks); // Validate starting at directory in block 2 ret = dynapro_validate_dir(dsk, &freeblks[0], 0x0404, 0, 4); if(!ret) { printf("Disk does not validate!\n"); exit(1); return ret; } // Check freeblks bptr = &(dsk->raw_data[6*0x200]); for(ui = 0; ui < (num_blocks + 7)/8; ui++) { if(freeblks[ui] != bptr[ui]) { printf("Expected free mask for blocks %04x-%04x:%02x, " "but it is %02x\n", ui*8, ui*8 + 7, freeblks[ui], bptr[ui]); exit(1); return 0; } } return 1; } void dynapro_validate_any_image(Disk *dsk) { byte *bufptr; dword64 dsize; int ret; // If dsk->raw_data already set, just use it. Otherwise, we need to // temporarily read in entire image, set it, do validate, and then // free it if(dsk->fd < 0) { return; // No disk } if(dsk->wozinfo_ptr) { return; } dsize = dsk->dimage_size; bufptr = 0; if((dsize >> 31) != 0) { printf("Disk is too large, not valid\n"); ret = 0; } else if(dsk->raw_data == 0) { bufptr = malloc((size_t)dsize); dsk->raw_data = bufptr; cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); ret = dynapro_validate_disk(dsk); dsk->raw_data = 0; free(bufptr); } else { ret = dynapro_validate_disk(dsk); } printf("validate_disk returned is_good: %d (0 is bad)\n", ret); } ================================================ FILE: upstream/kegs/src/dynapro.c ================================================ const char rcsid_dynapro_c[] = "@(#)$KmKId: dynapro.c,v 1.49 2023-09-23 17:53:24+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2021-2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Main information is from Beneath Apple ProDOS which has disk layout // descriptions. Upper/lowercase is from Technote tn-gsos-008, and // forked files are storage_type $5 from Technote tn-pdos-025. #include "defc.h" #ifdef _WIN32 # include "win_dirent.h" #else # include #endif #include extern int Verbose; #define DYNAPRO_PATH_MAX 2048 char g_dynapro_path_buf[DYNAPRO_PATH_MAX]; extern word32 g_vbl_count, g_iwm_dynapro_last_vbl_count; byte g_prodos_block0[512] = { // From Beagle Bros Pro-Byter disk // This is a ProDOS boot block, able to load PRODOS and jump to it 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x32, 0xa1, 0x86, // 0x000 0x43, 0xc9, 0x03, 0x08, 0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0, // 0x010 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e, 0xa9, 0x03, 0x8d, 0x00, 0x08, // 0x020 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, 0x48, 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x63, 0xb1, // 0x030 0x48, 0x99, 0x94, 0x09, 0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x1d, 0x09, 0xbd, 0x24, // 0x040 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x2b, 0x09, 0x9d, 0x7f, 0x0a, 0xca, 0x10, 0xee, 0xa9, 0x09, 0x85, // 0x050 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, 0xb0, 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, // 0x060 0x4c, 0x84, 0x4e, 0x84, 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85, // 0x070 0x4b, 0x20, 0x12, 0x09, 0xb0, 0x68, 0xe6, 0x61, 0xe6, 0x61, 0xe6, 0x46, 0xa5, 0x46, 0xc9, 0x06, // 0x080 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, 0x0c, 0xd0, 0x6d, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, // 0x090 0x18, 0x6d, 0x23, 0x0c, 0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a, // 0x0a0 0xf0, 0x55, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x02, 0x09, 0x29, 0x0f, 0xa8, 0xb1, 0x4a, 0xd9, 0x02, // 0x0b0 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0x29, 0xf0, 0xc9, 0x20, 0xd0, 0x3b, 0xa0, 0x10, 0xb1, 0x4a, // 0x0c0 0xc9, 0xff, 0xd0, 0x33, 0xc8, 0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, // 0x0d0 0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20, 0x12, 0x09, 0xb0, 0x17, // 0x0e0 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, 0xb1, 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, // 0x0f0 0x11, 0x4a, 0xd0, 0xe7, 0x4c, 0x00, 0x20, 0x4c, 0x3f, 0x09, 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, // 0x100 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa5, 0x60, 0x85, 0x44, 0xa5, 0x61, // 0x110 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, 0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, // 0x120 0x4b, 0xb4, 0xac, 0xa6, 0x2b, 0x18, 0x60, 0x4c, 0xbc, 0x09, 0xa9, 0x9f, 0x48, 0xa9, 0xff, 0x48, // 0x130 0xa9, 0x01, 0xa2, 0x00, 0x4c, 0x79, 0xf4, 0x20, 0x58, 0xfc, 0xa0, 0x1c, 0xb9, 0x50, 0x09, 0x99, // 0x140 0xae, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x4d, 0x09, 0xaa, 0xaa, 0xaa, 0xa0, 0xd5, 0xce, 0xc1, 0xc2, // 0x150 0xcc, 0xc5, 0xa0, 0xd4, 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf, // 0x160 0xd3, 0xa0, 0xaa, 0xaa, 0xaa, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, 0x80, 0xc0, // 0x170 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, // 0x180 0x29, 0x07, 0xc9, 0x04, 0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, // 0x190 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85, 0x27, 0xa6, 0x2b, 0xbd, // 0x1a0 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, // 0x1b0 0xbc, 0x88, 0xc0, 0x60, 0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, // 0x1c0 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02, 0xc6, 0x53, 0x38, 0x20, // 0x1d0 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, // 0x1e0 0x38, 0xc6, 0x52, 0xf0, 0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x00, // 0x1f0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; word32 dynapro_get_word32(byte *bptr) { return (bptr[3] << 24) | (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; } word32 dynapro_get_word24(byte *bptr) { return (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; } word32 dynapro_get_word16(byte *bptr) { return (bptr[1] << 8) | bptr[0]; } void dynapro_set_word24(byte *bptr, word32 val) { // Write 3 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); *bptr++ = (val >> 16); } void dynapro_set_word32(byte *bptr, word32 val) { // Write 4 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); *bptr++ = (val >> 16); *bptr++ = (val >> 24); } void dynapro_set_word16(byte *bptr, word32 val) { // Write 2 bytes in little-endian form *bptr++ = val; *bptr++ = (val >> 8); } void dynapro_error(Disk *dsk, const char *fmt, ...) { Dynapro_info *info_ptr; va_list args; va_start(args, fmt); cfg_err_vprintf("Dynapro", fmt, args); va_end(args); info_ptr = dsk->dynapro_info_ptr; if(info_ptr) { cfg_err_printf("", "Path: %s\n", info_ptr->root_path); } } Dynapro_file * dynapro_alloc_file() { Dynapro_file *fileptr; fileptr = calloc(1, sizeof(Dynapro_file)); return fileptr; } void dynapro_free_file(Dynapro_file *fileptr, int check_map) { if(!fileptr) { return; } if(fileptr->subdir_ptr) { dynapro_free_recursive_file(fileptr->subdir_ptr, check_map); } fileptr->subdir_ptr = 0; free(fileptr->unix_path); fileptr->unix_path = 0; free(fileptr->buffer_ptr); fileptr->buffer_ptr = 0; fileptr->next_ptr = 0; // printf("FREE %p\n", fileptr); if(check_map && (fileptr->map_first_block != 0)) { printf(" ERROR: map_first_block is %08x\n", fileptr->map_first_block); exit(1); } free(fileptr); } void dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map) { Dynapro_file *nextptr; if(!fileptr) { return; } // printf("free_recursive %s\n", fileptr->unix_path); while(fileptr) { nextptr = fileptr->next_ptr; dynapro_free_file(fileptr, check_map); fileptr = nextptr; }; } void dynapro_free_dynapro_info(Disk *dsk) { Dynapro_info *info_ptr; info_ptr = dsk->dynapro_info_ptr; if(info_ptr) { free(info_ptr->root_path); dynapro_free_recursive_file(info_ptr->volume_ptr, 0); info_ptr->volume_ptr = 0; } free(info_ptr); dsk->dynapro_info_ptr = 0; } word32 dynapro_find_free_block_internal(Disk *dsk) { byte *bptr; word32 num_blocks, bitmap_size_bytes, val, mask; word32 ui; int j; num_blocks = (word32)(dsk->raw_dsize >> 9); bitmap_size_bytes = (num_blocks + 7) >> 3; bptr = &(dsk->raw_data[6 * 512]); // Block 6 for(ui = 0; ui < bitmap_size_bytes; ui++) { val = bptr[ui]; if(val == 0) { continue; } mask = 0x80; for(j = 0; j < 8; j++) { if(val & mask) { bptr[ui] = val & (~mask); return 8*ui + j; } mask = mask >> 1; } return 0; } return 0; } word32 dynapro_find_free_block(Disk *dsk) { byte *bptr; word32 block; int i; // Find first free block, and zero it out block = dynapro_find_free_block_internal(dsk); if(block == 0) { return 0; } bptr = &(dsk->raw_data[block * 512]); for(i = 0; i < 512; i++) { bptr[i] = 0; } return block; } byte * dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size) { byte *bptr; dword64 dsize, dpos; int fd; int i; *dsize_ptr = 0; fd = open(path_ptr, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return 0; } dsize = cfg_get_fd_size(fd); if((size_t)(dsize + extra_size) != (dsize + extra_size)) { return 0; } bptr = malloc((size_t)(dsize + extra_size)); if(bptr == 0) { return bptr; } // printf("dynapro_malloc_file %p, size:%08lld\n", bptr, dsize); for(i = 0; i < extra_size; i++) { bptr[dsize + i] = 0; } dpos = cfg_read_from_fd(fd, bptr, 0, dsize); close(fd); if(dpos != dsize) { free(bptr); return 0; } *dsize_ptr = dsize; return bptr; } void dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, int path_max) { int len; // Create "unix_path" + "/" + "str" in outstr (which has size path_max) cfg_strncpy(outstr, unix_path, path_max); len = (int)strlen(outstr); if((len > 0) && (outstr[len - 1] != '/')) { cfg_strlcat(outstr, "/", path_max); } cfg_strlcat(outstr, str, path_max); } word32 dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, char *buf32_ptr, word32 dir_byte) { byte *bptr; word32 upper_lower; int len, c; int i; buf32_ptr[0] = 0; if((dir_byte < 0x400) || (dir_byte >= dsk->dimage_size)) { return 0; // Directory is damaged } if(!fileptr) { return 0; } bptr = &(dsk->raw_data[dir_byte]); memset(fileptr, 0, sizeof(Dynapro_file)); fileptr->dir_byte = dir_byte; fileptr->file_type = bptr[0x10]; fileptr->key_block = dynapro_get_word16(&bptr[0x11]); fileptr->blocks_used = dynapro_get_word16(&bptr[0x13]); fileptr->eof = dynapro_get_word24(&bptr[0x15]); //printf("Filling from entry %07x, eof:%06x\n", dir_byte, fileptr->eof); fileptr->creation_time = dynapro_get_word32(&bptr[0x18]); fileptr->upper_lower = dynapro_get_word16(&bptr[0x1c]); fileptr->aux_type = dynapro_get_word16(&bptr[0x1f]); fileptr->lastmod_time = dynapro_get_word32(&bptr[0x21]); fileptr->header_pointer = dynapro_get_word16(&bptr[0x25]); if(dir_byte == 0x404) { // Volume header fileptr->upper_lower = dynapro_get_word32(&bptr[0x1a]); fileptr->creation_time &= 0xffff; } len = (bptr[0] & 0xf) + 1; upper_lower = fileptr->upper_lower; if((upper_lower & 0x8000) == 0) { // Not valid upper_lower = 0; } for(i = 0; i < 16; i++) { c = bptr[i]; if(i > len) { c = 0; } fileptr->prodos_name[i] = c; if(i > 0) { if(upper_lower & 0x4000) { if((c >= 'A') && (c <= 'Z')) { c = c - 'A' + 'a'; // Make lower } } upper_lower = upper_lower << 1; buf32_ptr[i - 1] = c; buf32_ptr[i] = 0; } } if(((bptr[0] & 0xf0) == 0) || ((bptr[0] & 0xf) == 0)) { fileptr->prodos_name[0] = 0; return 1; // Invalid entry } if(fileptr->prodos_name[0] >= 0xe0) { // Dir/Volume header fileptr->key_block = dir_byte >> 9; if((dir_byte & 0x1ff) != 4) { printf("Header at dir_byte:%07x != 4\n", dir_byte); return 0; // Not in first pos } if(bptr[-4] || bptr[-3]) { printf("prev_link %02x,%02x should be 0\n", bptr[-4], bptr[-3]); return 0; // Not first dir block } if(fileptr->prodos_name[0] >= 0xf0) { if(dir_byte != 0x0404) { printf("Volume head dir_byte:%07x\n", dir_byte); return 0; } } else if(dir_byte == 0x0404) { printf("Directory head dir_byte 0x0404\n"); return 0; // 0xe0 in block 2->bad } } else { // Normal entry. Make sure it's not the first entry in a dir if((bptr[-4] == 0) && (bptr[-3] == 0) && ((dir_byte & 0x1ff) == 4)) { printf("dir_byte:%07x, normal, prev:0\n", dir_byte); return 0; // This is a dir/volume header! } } #if 0 printf("Fill resulted in buf32:%s, upper_lower:%04x\n", buf32_ptr, fileptr->upper_lower); #endif return 2; // OK } word32 dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr) { word32 ret, new_storage, old_storage; int i; // Return 0 if the directory is damaged // Return 1 if the entry is invalid (and not case 3!) // Return 3 if the entry was valid and is now deleted // Return 4 if no changes are needed // Return 5 if oldfileptr needs to be rewritten // Return 7 if oldfileptr needs to be erased and replaced with newfile old_storage = oldfileptr->prodos_name[0]; new_storage = newfileptr->prodos_name[0]; if(new_storage == 0) { // Erased if(old_storage >= 0xe0) { // Vol/Dir header return 0; } if(oldfileptr->dir_byte == newfileptr->dir_byte) { return 3; // Entry just deleted } return 1; // Just an invalid entry } if(oldfileptr->dir_byte != newfileptr->dir_byte) { return 0; } ret = 4; // No changes needed // Handle file expanding from seedling to tree if((new_storage >= 0x10) && (new_storage < 0x40) && (old_storage >= 0x10) && (old_storage < 0x40)) { // Copy upper 4 bits from new_storage to old_storage old_storage = (old_storage & 0x0f) | (new_storage & 0xf0); if(oldfileptr->prodos_name[0] != old_storage) { // Storage type changed, rewrite the file oldfileptr->prodos_name[0] = old_storage; ret |= 5; } } for(i = 0; i < 16; i++) { if(oldfileptr->prodos_name[i] != newfileptr->prodos_name[i]) { ret |= 7; // Name changed } oldfileptr->prodos_name[i] = newfileptr->prodos_name[i]; } if(oldfileptr->file_type != newfileptr->file_type) { ret |= 7; // Filetype changed oldfileptr->file_type = newfileptr->file_type; } if(newfileptr->prodos_name[0] < 0xe0) { // Not a directory or volume header if(oldfileptr->key_block != newfileptr->key_block) { ret |= 5; // Key block has changed oldfileptr->key_block = newfileptr->key_block; } if(oldfileptr->blocks_used != newfileptr->blocks_used) { // ret stays 1, we don't care about this field oldfileptr->blocks_used = newfileptr->blocks_used; } if(oldfileptr->eof != newfileptr->eof) { ret |= 5; // eof has changed oldfileptr->eof = newfileptr->eof; } } else { // Directory or volume header // Ignore key_block (used internally by dynapro.c, but not in // the ProDOS disk image), blocks_used, eof. // We ignore file_count at +0x21,0x22. But bitmap_ptr matters // and if it moves, we are damaged if((oldfileptr->lastmod_time >> 16) != (newfileptr->lastmod_time >> 16)) { return 0; // Bitmap_ptr moved, we are damaged } } if(oldfileptr->upper_lower != newfileptr->upper_lower) { ret |= 7; // lowercase flags have changed oldfileptr->upper_lower = newfileptr->upper_lower; } if(oldfileptr->aux_type != newfileptr->aux_type) { ret |= 5; // aux_type has changed oldfileptr->aux_type = newfileptr->aux_type; } if(oldfileptr->header_pointer != newfileptr->header_pointer) { return 0; // We are damaged } if(newfileptr->prodos_name[0] >= 0xe0) { if(ret > 5) { ret = 5; // No renaming volume or dir headers } } return ret; } word32 dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte) { word32 ret, diffs; ret = dynapro_fill_fileptr_from_prodos(dsk, localfile_ptr, buf32_ptr, dir_byte); if((ret == 0) || ((ret == 1) && !fileptr)) { return ret; // Damaged or not valid } if(!fileptr) { return 2; // must allocate new } // Now, head_ptr must be non-null diffs = dynapro_diff_fileptrs(fileptr, localfile_ptr); return diffs; } void dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr) { if(fileptr->prodos_name[0] >= 0xe0) { // This is a volume/directory header. Re-parse entire dir dynapro_handle_write_dir(dsk, fileptr->parent_ptr, fileptr, (fileptr->key_block * 0x200UL) + 4); } else if(fileptr->prodos_name[0] >= 0xd0) { // This is a directory entry. dynapro_handle_write_dir(dsk, fileptr, fileptr->subdir_ptr, (fileptr->key_block * 0x200UL) + 4); } else { dynapro_handle_write_file(dsk, fileptr); } } void dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr) { // Walk entire tree (recursing to dynapro_try_fix_damage) if(!fileptr) { return; } while(fileptr) { if(fileptr->damaged) { dyna_printf("try_fix_damage %p %s\n", fileptr, fileptr->unix_path); dynapro_fix_damaged_entry(dsk, fileptr); } dynapro_try_fix_damage(dsk, fileptr->subdir_ptr); fileptr = fileptr->next_ptr; } } void dynapro_try_fix_damaged_disk(Disk *dsk) { Dynapro_info *info_ptr; info_ptr = dsk->dynapro_info_ptr; if(!info_ptr) { // This is impossible return; } if(info_ptr->damaged == 0) { return; } dyna_printf("************************************\n"); dyna_printf("try_fix_damaged_dsk called, damaged:%d\n", info_ptr->damaged); dyna_printf(" vbl_count:%d, g_iwm_dynapro_last_vbl_count:%d\n", g_vbl_count, g_iwm_dynapro_last_vbl_count); info_ptr->damaged = 0; dynapro_try_fix_damage(dsk, info_ptr->volume_ptr); dyna_printf("try_fix_damaged_dsk, damaged:%d\n", info_ptr->damaged); } void dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, const char *name_str) { if(fileptr->unix_path) { free(fileptr->unix_path); } dynapro_join_path_and_file(&g_dynapro_path_buf[0], path_str, name_str, DYNAPRO_PATH_MAX); dynatype_fix_unix_name(fileptr, &g_dynapro_path_buf[0], DYNAPRO_PATH_MAX); fileptr->unix_path = kegs_malloc_str(&g_dynapro_path_buf[0]); } Dynapro_file * dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file **head_ptr_ptr, word32 dir_byte) { char buf32[32]; Dynapro_file localfile; Dynapro_file *fileptr, *prev_ptr, *head_ptr; byte *bptr; const char *str; word32 tmp_byte, prev, next, ret, last_block, parent_dir_byte; int cnt, error, iret, is_header; head_ptr = *head_ptr_ptr; // head_ptr_ptr must be valid // but head_ptr can be 0 // We can be called with parent_ptr=0, head_ptr != 0: this is for the // volume header. Otherwise, parent_ptr should be valid. // If head_ptr==0, it means we need to allocate directory header and // all other dir entries // head_ptr is a pointer to a directory or volume header. // For all entries, see if anything changed. We need to also // possibly update head_ptr->parent_ptr // Return 0 if the directory is damaged. If directory only contains // damaged files, try to fix them, and always return success // First, unmap the directory blocks (this is done even if nothing // changed, we'll map them back at the end). if(head_ptr) { dynapro_unmap_file(dsk, head_ptr); } parent_dir_byte = 0; if(parent_ptr) { str = parent_ptr->unix_path; parent_dir_byte = parent_ptr->dir_byte; if(head_ptr == 0) { // Do mkdir to make sure it exists #ifdef _WIN32 iret = _mkdir(str); #else iret = mkdir(str, 0x1ff); #endif error = errno; dyna_printf("Did mkdir %s, iret:%d\n", str, iret); if(iret < 0) { if((error == EEXIST) || (error == EISDIR)) { error = 0; // These are OK errors } if(error) { printf("mkdir(%s) failed, error=%d\n", str, error); } } } } else { str = head_ptr->unix_path; // volume header } #if 0 printf("process_write_dir str:%s %p parent:%p\n", str, head_ptr, parent_ptr); #endif // The directory blocks have already been unmapped // Then, walk the directory, noting if anything changed. If new // files appear in the directory, add then to the chain. We may need // to erase existing entries which no longer exist (or their directory // entry was changed to a different file) bptr = dsk->raw_data; prev_ptr = 0; fileptr = head_ptr; last_block = 0; cnt = 0; is_header = 1; while(dir_byte) { //printf("process_write_dir, dir_byte:%07x, prev_ptr:%p\n", // dir_byte, prev_ptr); if((dir_byte & 0x1ff) == 4) { // First entry in this block: check prev/next tmp_byte = dir_byte & -0x200; // Block align prev = dynapro_get_word16(&bptr[tmp_byte + 0]); next = dynapro_get_word16(&bptr[tmp_byte + 2]); if((prev != last_block) || (next >= (dsk->raw_dsize >> 9))) { // This is a damaged directory printf("dir %s is damaged in the link fields\n", str); return 0; } last_block = dir_byte >> 9; } if(cnt++ >= 65536) { printf("dir %s has a loop in block pointers\n", head_ptr->unix_path); return 0; } ret = dynapro_do_one_dir_entry(dsk, fileptr, &localfile, &buf32[0], dir_byte); #if 0 printf(" do_one_dir_entry ret:%08x fileptr:%p, &localfile:%p\n", ret, fileptr, &localfile); #endif if((ret == 7) && !is_header) { // Entry dramatically changed // Erase this file dynapro_mark_damaged(dsk, fileptr); } if(ret == 0) { return 0; } else if((ret == 1) || (ret == 3)) { if((ret == 3) && fileptr) { // This entry was valid and is now deleted. // Erase it right now and fix links dyna_printf("fileptr %p deleted\n", fileptr); prev_ptr->next_ptr = fileptr->next_ptr; dynapro_erase_free_entry(dsk, fileptr); } if(head_ptr == 0) { printf("return, head_ptr==0, deleted file at " "%07x\n", dir_byte); return 0; // Directory damaged } fileptr = prev_ptr; } if(ret == 2) { // prev_ptr->next_ptr is 0, this is a new entry we // need to put on the list if(fileptr) { halt_printf("file %s was ignored!\n", fileptr->unix_path); exit(1); } fileptr = dynapro_alloc_file(); if(!fileptr) { return 0; } *fileptr = localfile; // STRUCT copy! dyna_printf("Allocated new fileptr:%p\n", fileptr); fileptr->parent_ptr = parent_ptr; if(head_ptr) { fileptr->parent_ptr = head_ptr; } } if((ret == 2) || (ret == 7)) { // New entry, or dramatically changed, update path if(!head_ptr) { if(!parent_ptr) { printf("parent_ptr is 0!\n"); return 0; } parent_ptr->subdir_ptr = fileptr; printf("2/7 set %p %s subdir=%p\n", parent_ptr, parent_ptr->unix_path, fileptr); fileptr->unix_path = kegs_malloc_str(str); head_ptr = fileptr; *head_ptr_ptr = head_ptr; } else { dyna_printf("Forming new path: %s buf32:%s\n", str, buf32); dynapro_new_unix_path(fileptr, str, buf32); } // If we are a directory entry (fileptr->subdir_ptr!=0) // then now fileptr->unix_path != subdirptr->unix_path // The subdir will be erased in // dynapro_handle_changed_entry() if(prev_ptr) { prev_ptr->next_ptr = fileptr; } } if((ret == 5) || (ret == 2) || (ret == 7)) { // Changed, or new entry if(is_header) { ret = dynapro_validate_header(dsk, fileptr, dir_byte, parent_dir_byte); if(ret == 0) { return 0; } } else { dynapro_handle_changed_entry(dsk, fileptr); } } prev_ptr = fileptr; fileptr = prev_ptr->next_ptr; dir_byte = dir_byte + 0x27; tmp_byte = (dir_byte & 0x1ff) + 0x27; is_header = 0; if(tmp_byte < 0x200) { continue; } tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; dyna_printf(" dir link at %07x = %04x\n", tmp_byte + 2, dir_byte); if(dir_byte == 0) { if(fileptr) { printf("At dir end, fileptr: %p\n", fileptr); prev_ptr->next_ptr = 0; dynapro_erase_free_dir(dsk, fileptr); return 0; } ret = dynapro_map_dir_blocks(dsk, head_ptr); // printf("process_write_dir %s done, remap ret:%d\n", // head_ptr->unix_path, ret); if(ret == 0) { return 0; } return head_ptr; // Success } dir_byte += 4; if(dir_byte >= dsk->dimage_size) { // printf(" invalid link pointer, dir_byte:%08x\n", // dir_byte); return 0; // Bad link, get out } } dyna_printf("At end of process_write_dir, returning 0\n"); return 0; } void dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file *head_ptr, word32 dir_byte) { Dynapro_file *fileptr; //save_headptr = head_ptr; dyna_printf("handle_write_dir parent_ptr:%p, head_ptr:%p, dir_b:%07x\n", parent_ptr, head_ptr, dir_byte); if(parent_ptr && head_ptr) { dyna_printf(" parent:%s head:%s\n", parent_ptr->unix_path, head_ptr->unix_path); if(parent_ptr->subdir_ptr != head_ptr) { printf("parent subdir:%p does not match %p\n", parent_ptr->subdir_ptr, head_ptr); exit(1); } } if(parent_ptr == 0) { if(head_ptr != dsk->dynapro_info_ptr->volume_ptr) { printf("handle_write_dir %p %p %07x\n", parent_ptr, head_ptr, dir_byte); exit(1); } } fileptr = dynapro_process_write_dir(dsk, parent_ptr, &head_ptr, dir_byte); if(fileptr == 0) { // Directory is damaged. Free and erase it dynapro_erase_free_dir(dsk, head_ptr); head_ptr = 0; } //printf("handle_write_dir, process returned %p (was %p), parent:%p, " // "save:%p\n", fileptr, head_ptr, parent_ptr, save_headptr); if(parent_ptr) { parent_ptr->subdir_ptr = head_ptr; if(!fileptr) { parent_ptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; } dyna_printf("hwd set parent %p %s subdir=%p\n", parent_ptr, parent_ptr->unix_path, head_ptr); } } word32 dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr) { word32 ret; //printf("dynapro_process_write_file %p %s\n", fileptr, // fileptr->unix_path); if(fileptr->subdir_ptr) { printf("dynapro_handle_write_file, has subdir: %s\n", fileptr->unix_path); halt_printf("dynapro_handle_write_file, has subdir: %s\n", fileptr->unix_path); return 0; } // First, unmap the file (the sapling/tree blocks may have changed). dynapro_unmap_file(dsk, fileptr); // Then remap the blocks. This will copy the new data to buffer_ptr dynapro_debug_map(dsk, "handle_write_file, right before re-map"); ret = dynapro_map_file(dsk, fileptr, 1); // printf("dynapro_handle_write_file ending, ret:%d\n", ret); return ret; } void dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr) { word32 ret; ret = dynapro_process_write_file(dsk, fileptr); if(ret == 0) { dynapro_mark_damaged(dsk, fileptr); } } void dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr) { // printf("handle_changed_entry with fileptr:%p\n", fileptr); fileptr->damaged = 0; if(fileptr->prodos_name[0] >= 0xe0) { // Directory header, not valid to be called here fileptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; } else if(fileptr->prodos_name[0] >= 0xd0) { // Directory entry if(fileptr->subdir_ptr) { dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); fileptr->subdir_ptr = 0; } dynapro_handle_write_dir(dsk, fileptr, 0, (fileptr->key_block * 0x200UL) + 4); } else { dynapro_handle_write_file(dsk, fileptr); #if 0 printf("handle_changed_entry called handle_write_file for %p, " "%s\n", fileptr, fileptr->unix_path); #endif } } word32 dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size) { dword64 dret; int fd; fd = open(unix_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); if(fd < 0) { printf("Open %s for writing failed\n", unix_path); exit(1); return 0; } dret = cfg_write_to_fd(fd, data_ptr, 0, size); close(fd); dyna_printf("dynapro_write_to_unix: %s size:%d, dret:%lld\n", unix_path, size, dret); if(size == 0) { return 1; } return (word32)dret; } void dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr) { Dynapro_file *this_fileptr; Dynapro_map *map_ptr; //const char *str; word32 map_block, next_map_block, max_blocks; int i; //printf("File %p: %s is unmapped\n", fileptr, fileptr->unix_path); dynapro_debug_map(dsk, "start unmap file"); // Unmap all blocks to this file/dir map_ptr = dsk->dynapro_info_ptr->block_map_ptr; max_blocks = (word32)(dsk->dimage_size >> 9); map_block = fileptr->map_first_block; //printf(" map_block:%04x, fileptr:%p %s\n", map_block, fileptr, // fileptr->unix_path); fileptr->map_first_block = 0; //printf(" unmap starting, map_block:%08x, max_blocks:%07x\n", // map_block, max_blocks); for(i = 0; i < 65536; i++) { if((map_block == 0) || (map_block >= max_blocks)) { break; } next_map_block = map_ptr[map_block].next_map_block; this_fileptr = map_ptr[map_block].file_ptr; if(this_fileptr != fileptr) { //str = "??"; if(this_fileptr) { //str = this_fileptr->unix_path; this_fileptr->damaged = 1; } #if 0 printf("Found map[%04x]=%s while walking %s\n", map_block, str, fileptr->unix_path); #endif } map_ptr[map_block].file_ptr = 0; map_ptr[map_block].next_map_block = 0; map_ptr[map_block].modified = 0; //printf(" just unmapped block %05x\n", map_block); map_block = next_map_block; } // printf(" unmap ending\n"); } void dynapro_unlink_file(Dynapro_file *fileptr) { const char *str; int ret, err; // Try to unlink unix_path dyna_printf("Unlink %s (%p)\n", fileptr->unix_path, fileptr); if(fileptr->unix_path == 0) { printf("unix_path of %p is null!\n", fileptr); exit(1); } if(fileptr->subdir_ptr != 0) { printf("unlink_file %s, but subdirptr is valid!\n", fileptr->unix_path); exit(1); } ret = unlink(fileptr->unix_path); if(ret != 0) { // Maybe it's a directory, rmdir ret = rmdir(fileptr->unix_path); } if(ret != 0) { // Cannot erase, try to rename cfg_strncpy_dirname(&g_dynapro_path_buf[0], fileptr->unix_path, DYNAPRO_PATH_MAX); cfg_strlcat(&g_dynapro_path_buf[0], ".kegsrm_", DYNAPRO_PATH_MAX); str = cfg_str_basename(fileptr->unix_path); cfg_strlcat(&g_dynapro_path_buf[0], str, DYNAPRO_PATH_MAX); printf("Could not erase %s, renaming to: %s\n", fileptr->unix_path, &g_dynapro_path_buf[0]); ret = rename(fileptr->unix_path, &g_dynapro_path_buf[0]); err = errno; if(ret != 0) { printf("Rename of %s failed, err:%d\n", fileptr->unix_path, err); } } } void dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr) { if(!fileptr) { return; } dynapro_mark_damaged(dsk, fileptr); fileptr->next_ptr = 0; if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { // Free everything--except the volume header dyna_printf("erase_free_entry erasing %p since it != %p\n", fileptr, dsk->dynapro_info_ptr->volume_ptr); dynapro_free_file(fileptr, 1); } } void dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr) { Dynapro_file *nextptr, *parent_ptr, *save_fileptr; dyna_printf("dynapro_erase_free_dir of %p\n", fileptr); if(fileptr == 0) { return; } dyna_printf(" dynapro_erase_free_dir of %s\n", fileptr->unix_path); dsk->dynapro_info_ptr->damaged = 1; parent_ptr = fileptr->parent_ptr; if(parent_ptr) { if(parent_ptr->subdir_ptr) { parent_ptr->damaged = 1; } } save_fileptr = fileptr; nextptr = fileptr->next_ptr; fileptr->next_ptr = 0; fileptr = nextptr; while(fileptr) { nextptr = fileptr->next_ptr; dynapro_erase_free_entry(dsk, fileptr); fileptr = nextptr; } dynapro_erase_free_entry(dsk, save_fileptr); } void dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr) { if(fileptr == 0) { return; } dyna_printf("dynapro_mark_damaged: %s damaged\n", fileptr->unix_path); fileptr->damaged = 1; dsk->dynapro_info_ptr->damaged = 1; dynapro_unmap_file(dsk, fileptr); if(fileptr->subdir_ptr) { dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); fileptr->subdir_ptr = 0; } if((fileptr->prodos_name[0] >= 0xe0) && fileptr->parent_ptr) { // We are a directory header, mark the directory entry of our // parent as damaged (but don't actually damage it) fileptr->parent_ptr->damaged = 1; } else if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { dynapro_unlink_file(fileptr); } } int dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) { Dynapro_info *info_ptr; Dynapro_map *map_ptr; Dynapro_file *fileptr; byte *bptr; word32 ui, block; int num; int i; // Return 1 if write was done. Return < 0 if an error occurs dyna_printf("\n"); dyna_printf("------------------------------------------------\n"); dyna_printf("dynapro_write to %08llx, size:%08x\n", doffset, size); dynapro_debug_update(dsk); bptr = dsk->raw_data; if((doffset + size) > dsk->dimage_size) { printf("Write past end of disk, ignored\n"); return -1; } for(ui = 0; ui < size; ui++) { #if 0 if((bptr[doffset + ui] != bufptr[ui]) && (diffs < 500)) { printf("%07llx:%02x (was %02x)\n", doffset+ui, bufptr[ui], bptr[doffset + ui]); diffs++; } #endif bptr[doffset + ui] = bufptr[ui]; } info_ptr = dsk->dynapro_info_ptr; if(info_ptr == 0) { printf("dynapro_info_ptr==0\n"); return -1; } num = (size + 511) >> 9; block = (word32)(doffset >> 9); dyna_printf("Marking blocks %05x-%05x modified\n", block, block + num - 1); for(i = 0; i < num; i++) { map_ptr = &(info_ptr->block_map_ptr[block + i]); map_ptr->modified = 1; } for(i = 0; i < num; i++) { map_ptr = &(info_ptr->block_map_ptr[block + i]); if(!map_ptr->modified) { continue; // Already cleared } fileptr = map_ptr->file_ptr; if(fileptr == 0) { continue; } if(fileptr->prodos_name[0] >= 0xe0) { dynapro_handle_write_dir(dsk, fileptr->parent_ptr, fileptr, fileptr->dir_byte); } else { dynapro_handle_write_file(dsk, fileptr); } } return 1; } void dynapro_debug_update(Disk *dsk) { dyna_printf("Writing out DYNAPRO_IMAGE, %p\n", dsk); #if 0 // This causes the file DYNAPRO_IMAGE to be written out as the raw // image after any write to the Dynapro volume. This is for manual // debugging. dynapro_write_to_unix_file("DYNAPRO_IMAGE", dsk->raw_data, (word32)dsk->dimage_size); #endif } void dynapro_debug_map(Disk *dsk, const char *str) { Dynapro_map *map_ptr; Dynapro_file *lastfileptr, *fileptr; const char *newstr; int num_blocks; int i; return; // HACK! num_blocks = (word32)((dsk->dimage_size + 511) >> 9); map_ptr = dsk->dynapro_info_ptr->block_map_ptr; lastfileptr = 0; printf(" Showing map for %s, %05x blocks, %s\n", dsk->dynapro_info_ptr->root_path, num_blocks, str); for(i = 0; i < num_blocks; i++) { fileptr = map_ptr[i].file_ptr; if(fileptr != lastfileptr) { newstr = ""; if(fileptr) { newstr = fileptr->unix_path; } printf(" %04x (%07x): %p %s\n", i, i << 9, fileptr, newstr); } lastfileptr = fileptr; } printf("Recursive file map:\n"); dynapro_debug_recursive_file_map(dsk->dynapro_info_ptr->volume_ptr, 1); } void dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start) { if(!fileptr) { return; } while(fileptr) { printf(" file %p %s map_first_block:%05x, storage:%02x key:" "%04x\n", fileptr, fileptr->unix_path, fileptr->map_first_block, fileptr->prodos_name[0], fileptr->key_block); printf(" n:%p, sub:%p, eof:%06x, parent:%p dam:%d\n", fileptr->next_ptr, fileptr->subdir_ptr, fileptr->eof, fileptr->parent_ptr, fileptr->damaged); if(fileptr->unix_path == 0) { printf("Filename is invalid, exiting\n"); exit(1); } if(!fileptr->parent_ptr && !start) { printf("parent_ptr is 0, exiting\n"); exit(1); } dynapro_debug_recursive_file_map(fileptr->subdir_ptr, 0); start = 0; fileptr = fileptr->next_ptr; } } word32 dynapro_unix_to_prodos_time(const time_t *time_ptr) { struct tm *tm_ptr; word32 ymd, hours_mins, date_time; int year; tm_ptr = localtime(time_ptr); hours_mins = (tm_ptr->tm_hour << 8) | tm_ptr->tm_min; year = tm_ptr->tm_year; // years since 1900 if(year < 80) { year = 80; } else if(year >= 100) { year -= 100; if(year >= 80) { year = 79; } } ymd = (year << 9) | ((tm_ptr->tm_mon + 1) << 5) | (tm_ptr->tm_mday); date_time = (ymd & 0xffff) | (hours_mins << 16); // printf("Unix time:%s results in:%08x\n", asctime(tm_ptr), date_time); return date_time; } int dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, word32 storage_type) { Dynapro_file *thisptr; char *str; word32 upper_lower; int len, outpos, inpos, max_inpos, c, done, dot_pos, inc_pos; int i; #if 0 printf("dynapro_create_prodos_name to %s, match:%p, st:%03x\n", newfileptr->unix_path, matchptr, storage_type); #endif for(i = 0; i < 17; i++) { newfileptr->prodos_name[i] = 0; } str = newfileptr->unix_path; if(!str) { return 0; } inpos = (int)strlen(str); max_inpos = inpos + 1; while(inpos >= 1) { inpos--; if(str[inpos] == '/') { inpos++; break; } } if(storage_type == 0x50) { // printf("max_inpos:%d, inpos:%d\n", max_inpos, inpos); } if((storage_type == 0x50) && ((max_inpos - inpos) > 12)) { // Remove .applesingle extension max_inpos -= 13; } //printf(" inpos:%d max_inpos:%d str:%s\n", inpos, max_inpos, // &(str[inpos])); outpos = 0; while(outpos < (DYNAPRO_PATH_MAX - 1)) { c = 0; if(inpos < max_inpos) { c = str[inpos++]; } g_dynapro_path_buf[outpos] = c; if(c == 0) { break; } outpos++; if((c >= 'A') && (c <= 'Z')) { continue; // This is legal } if((c >= 'a') && (c <= 'z')) { continue; // Also legal } if((outpos > 1) && (c >= '0') && (c <= '9')) { continue; // Also legal } if((outpos > 1) && (c == '.')) { continue; // Also legal } // If this is the first character, make it "A" and continue if(outpos == 1) { g_dynapro_path_buf[0] = 'A'; continue; } if((c == ',') || (c == '#')) { // ,ttxt,a$2000, ignore outpos--; break; // All done } // This is not legal. Make it a '.' if((c >= 0x20) && (c <= 0x7e)) { g_dynapro_path_buf[outpos - 1] = '@'; // do '.' later } else { outpos--; // Ignore it } } g_dynapro_path_buf[outpos] = 0; // printf(" initial path_buf:%s, %d\n", &g_dynapro_path_buf[0], outpos); while((outpos >= 0) && (g_dynapro_path_buf[outpos-1] == '@')) { // Remove trailing '@' since they are not useful outpos--; g_dynapro_path_buf[outpos] = 0; } for(i = 1; i < outpos; i++) { // Convert '@' to '.' to make name legal if(g_dynapro_path_buf[i] == '@') { g_dynapro_path_buf[i] = '.'; } } if(outpos == 0) { // Not a valid file, just skip it return 0; } // Now, it's valid. Squeeze it to 15 character but saving extension len = (int)strlen(&g_dynapro_path_buf[0]); if(len > 15) { // Copy last 8 characters to be in positions 7..14 for(i = 7; i < 16; i++) { g_dynapro_path_buf[i] = g_dynapro_path_buf[len-15 + i]; } } len = (int)strlen(&g_dynapro_path_buf[0]); if((len > 15) || (len == 0)) { printf("Bad filename handling: %s\n", &g_dynapro_path_buf[0]); return 0; } // See if it conflicts with matchptr thisptr = matchptr; for(i = 0; i < 10000; i++) { if(!thisptr || (thisptr == newfileptr)) { thisptr = 0; break; } //printf("Comparing %s to %s\n", &g_dynapro_path_buf[0], // (char *)&(thisptr->prodos_name[1])); len = (int)strlen(&g_dynapro_path_buf[0]); if((len == (thisptr->prodos_name[0] & 0xf)) && (cfgcasecmp(&g_dynapro_path_buf[0], (char *)&(thisptr->prodos_name[1])) == 0)) { dyna_printf(" that was a match\n"); dot_pos = 0; inc_pos = len - 1; for(i = len - 2; i >= 1; i--) { if(g_dynapro_path_buf[i] == '.') { dot_pos = i; } } if(len < 15) { // Append "1" to the end len++; inc_pos = len - 1; g_dynapro_path_buf[len] = 0; g_dynapro_path_buf[len - 1] = '1'; if(dot_pos > 1) { for(i = len - 1; i >= dot_pos; i--) { g_dynapro_path_buf[i] = g_dynapro_path_buf[i-1]; } inc_pos = dot_pos; } g_dynapro_path_buf[inc_pos] = '1'; } else if(dot_pos > 3) { inc_pos = dot_pos - 1; } done = 0; for(i = inc_pos; i >= 1; i--) { c = g_dynapro_path_buf[i]; c++; if(c == ('9' + 1)) { c = '0'; } else if(c == ('z' + 1)) { c = 'a'; } else if(c == ('Z' + 1)) { c = 'A'; } else { done = 1; } g_dynapro_path_buf[i] = c; if(done) { break; } } thisptr = matchptr; } else { thisptr = thisptr->next_ptr; } } if(thisptr) { // File could not be made unique printf("Could not make a unique ProDOS filename: %s\n", newfileptr->unix_path); return 0; } upper_lower = 0; for(i = 0; i < len; i++) { c = g_dynapro_path_buf[i]; if((c >= 'a') && (c <= 'z')) { c = c - 'a' + 'A'; upper_lower |= 0x8000 | (0x4000 >> i); } newfileptr->prodos_name[1 + i] = c; } newfileptr->prodos_name[0] = len | storage_type; newfileptr->upper_lower = upper_lower; return len; } Dynapro_file * dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, Dynapro_file *match_ptr, word32 storage_type) { Dynapro_file *fileptr; int len; int i; #if 0 printf("dynapro_new_unix_file for %s, parent:%p, m:%p, st:%03x\n", path, parent_ptr, match_ptr, storage_type); #endif fileptr = dynapro_alloc_file(); if(!fileptr) { return 0; } fileptr->next_ptr = 0; fileptr->parent_ptr = parent_ptr; fileptr->subdir_ptr = 0; fileptr->buffer_ptr = 0; fileptr->unix_path = kegs_malloc_str(path); for(i = 0; i < 17; i++) { fileptr->prodos_name[i] = 0; } fileptr->dir_byte = 0; fileptr->eof = 0; fileptr->blocks_used = 0; fileptr->creation_time = 0; fileptr->lastmod_time = 0; fileptr->upper_lower = 0; fileptr->key_block = 0; fileptr->aux_type = 0; fileptr->header_pointer = 0; fileptr->map_first_block = 0; fileptr->file_type = 0x0f; // Default to "DIR" fileptr->modified_flag = 0; fileptr->damaged = 0; len = (int)strlen(fileptr->unix_path); for(i = len - 1; i >= 0; i--) { if(fileptr->unix_path[i] == '/') { fileptr->unix_path[i] = 0; // Strip trailing / } else { break; } } if(storage_type < 0xd0) { storage_type = dynatype_detect_file_type(fileptr, fileptr->unix_path, storage_type); } len = dynapro_create_prodos_name(fileptr, match_ptr, storage_type); if(len == 0) { printf("Could not create prodos name for: %s\n", path); free(fileptr); return 0; } #if 0 printf("dynapro_create_new_unix_file: %s prodos:%s, st:%02x, ft:%02x, " "aux:%04x\n", fileptr->unix_path, &(fileptr->prodos_name[1]), fileptr->prodos_name[0], fileptr->file_type, fileptr->aux_type); #endif return fileptr; } int dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, word32 dir_byte) { struct stat stat_buf; struct dirent *direntptr; DIR *opendirptr; Dynapro_file *fileptr, *head_ptr, *prev_ptr; mode_t fmt; word32 storage_type, val; int ret; // Create a directory entry at dir_byte first #if 0 printf("\n"); printf("dynapro_add_files to %s, %p dir_byte:%08x\n", unix_path, parent_ptr, dir_byte); #endif storage_type = 0xe0; // Directory header if(dir_byte < 0x600) { // Block 2: volume header storage_type = 0xf0; } head_ptr = dynapro_new_unix_file(unix_path, parent_ptr, 0, storage_type); if(parent_ptr) { parent_ptr->subdir_ptr = head_ptr; #if 0 printf("set parent %s subdir_ptr=%p\n", parent_ptr->unix_path, head_ptr); #endif } if(dsk->dynapro_info_ptr->volume_ptr == 0) { dsk->dynapro_info_ptr->volume_ptr = head_ptr; } if(head_ptr == 0) { printf("new_file returned 0, skipping %s\n", unix_path); return dir_byte; } head_ptr->key_block = dir_byte >> 9; head_ptr->aux_type = 0x0d27; // 0x27,0x0d head_ptr->file_type = 0x75; // Directory header type if(storage_type >= 0xf0) { head_ptr->file_type = 0x00; } ret = cfg_stat(unix_path, &stat_buf, 0); if(ret != 0) { printf("stat %s ret %d, errno:%d\n", unix_path, ret, errno); return 0; } head_ptr->creation_time = dynapro_unix_to_prodos_time( &stat_buf.st_ctime); dir_byte = dynapro_add_file_entry(dsk, head_ptr, 0, dir_byte, 0); opendirptr = opendir(unix_path); if(opendirptr == 0) { printf("Could not open %s as a dir\n", unix_path); return 0; } prev_ptr = head_ptr; while(1) { direntptr = readdir(opendirptr); if(direntptr == 0) { break; } if(direntptr->d_name[0] == '.') { continue; // Ignore all '.' files } dynapro_join_path_and_file(&(g_dynapro_path_buf[0]), unix_path, direntptr->d_name, DYNAPRO_PATH_MAX); ret = cfg_stat(&(g_dynapro_path_buf[0]), &stat_buf, 0); if(ret != 0) { printf("stat %s ret %d, errno:%d\n", &g_dynapro_path_buf[0], ret, errno); continue; // skip it } fmt = stat_buf.st_mode & S_IFMT; storage_type = 0; if(fmt == S_IFDIR) { // Ignore symlinks to directories (since they may point // outside the base directory, and so dynamically // removing files could be a security issue). ret = cfg_stat(&g_dynapro_path_buf[0], &stat_buf, 1); if(ret != 0) { printf("lstat %s ret %d, errno:%d\n", &g_dynapro_path_buf[0], ret, errno); continue; } storage_type = 0xd0; // Directory } else if(fmt != S_IFREG) { continue; // Skip this } #if 0 printf("GOT file: %s, is_dir:%d (%s), parent:%p\n", &(g_dynapro_path_buf[0]), is_dir, direntptr->d_name, parent_ptr); #endif fileptr = dynapro_new_unix_file(&(g_dynapro_path_buf[0]), head_ptr, head_ptr->next_ptr, storage_type); if(fileptr == 0) { closedir(opendirptr); return 0; } prev_ptr->next_ptr = fileptr; fileptr->key_block = dynapro_find_free_block(dsk); fileptr->creation_time = dynapro_unix_to_prodos_time( &stat_buf.st_ctime); fileptr->lastmod_time = dynapro_unix_to_prodos_time( &stat_buf.st_mtime); if(fileptr->key_block == 0) { printf("Allocating directory block failed\n"); closedir(opendirptr); return 0; } fileptr->blocks_used = 1; fileptr->eof = 1*0x200; dir_byte = dynapro_add_file_entry(dsk, fileptr, head_ptr, dir_byte, 0x27); if(dir_byte == 0) { closedir(opendirptr); return 0; } if(fmt == S_IFDIR) { val = dynapro_create_dir(dsk, fileptr->unix_path, fileptr, (fileptr->key_block << 9) + 4); if(val == 0) { closedir(opendirptr); return 0; } } else { val = dynapro_file_from_unix(dsk, fileptr); if(val == 0) { closedir(opendirptr); return 0; } } prev_ptr = fileptr; } closedir(opendirptr); return dir_byte; } word32 dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, word32 dir_byte, word32 inc) { Dynapro_file *parent_ptr; byte *bptr, *pkeyptr; word32 storage_type, val, ent, new_dir_blk, new_dir_byte; word32 header_pointer; int i; #if 0 printf("dynapro_add_file_entry: %p %p %s head:%p dir_byte:%08x " "inc:%03x\n", dsk, fileptr, fileptr->unix_path, head_ptr, dir_byte, inc); #endif bptr = dsk->raw_data; if(((dir_byte & 0x1ff) + inc + inc) >= 0x200) { // This entry will not fit in this directory block. // Try to step to next block, otherwise allocate a new one new_dir_byte = dir_byte & -0x200L; new_dir_blk = dynapro_get_word16(&bptr[new_dir_byte + 2]); dyna_printf(" Entry does not fit, new_dir_blk:%04x\n", new_dir_blk); if(new_dir_blk != 0) { // Follow to the next block dir_byte = (new_dir_blk * 0x200) + 4; } else if(dir_byte < (6 * 0x200)) { // Otherwise, allocate a new block (not for volume dir) // This is a volume header, always 4 blocks, don't // allocate any more, this is now full printf("Too many file in volume directory\n"); return 0; // Out of space } else { new_dir_blk = dynapro_find_free_block(dsk); if(new_dir_blk == 0) { return 0; } new_dir_byte = new_dir_blk * 512; dynapro_set_word16(&bptr[new_dir_byte], dir_byte >> 9); dynapro_set_word16(&bptr[new_dir_byte + 2], 0); dir_byte = (dir_byte >> 9) << 9; dynapro_set_word16(&bptr[dir_byte + 2], new_dir_blk); dir_byte = new_dir_byte + 4; if(!head_ptr) { dyna_printf("No head:%s\n", fileptr->unix_path); } parent_ptr = head_ptr->parent_ptr; if(!parent_ptr) { printf("No parent: %s\n", fileptr->unix_path); return 0; } parent_ptr->blocks_used++; parent_ptr->eof += 0x200; new_dir_byte = parent_ptr->dir_byte; if(new_dir_byte == 0) { printf("Invalid dir_byte for %s\n", parent_ptr->unix_path); return 0; } dynapro_set_word16(&bptr[new_dir_byte + 0x13], parent_ptr->blocks_used); dynapro_set_word24(&bptr[new_dir_byte + 0x15], parent_ptr->eof); } } else { dir_byte += inc; } bptr = &(dsk->raw_data[dir_byte]); fileptr->dir_byte = dir_byte; for(i = 0; i < 0x27; i++) { bptr[i] = 0; } for(i = 0; i < 16; i++) { bptr[i] = fileptr->prodos_name[i]; // [0] = len,storage_t } bptr[0x10] = fileptr->file_type; dynapro_set_word16(&bptr[0x11], fileptr->key_block); dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); dynapro_set_word24(&bptr[0x15], fileptr->eof); dynapro_set_word32(&bptr[0x18], fileptr->creation_time); // creation date&time bptr[0x1c] = fileptr->upper_lower & 0xff; // Version bptr[0x1d] = fileptr->upper_lower >> 8; // Min_Version bptr[0x1e] = 0xe3; // Access dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); storage_type = bptr[0]; if(storage_type >= 0xf0) { // Volume header dynapro_set_word16(&bptr[0x11], 0); fileptr->lastmod_time = 0x00060000; // low 16 bits: file_count, upper 16 bits: bitmap_block fileptr->header_pointer = (word32)(dsk->raw_dsize >> 9); // Total blocks dynapro_set_word16(&bptr[0x1c], 0x0005); dynapro_set_word16(&bptr[0x16], fileptr->upper_lower); } else if(storage_type >= 0xe0) { // Directory header dynapro_set_word16(&bptr[0x11], 0); dynapro_set_word16(&bptr[0x1c], 0x0005); parent_ptr = fileptr->parent_ptr; // subdir entry if(parent_ptr == 0) { printf("parent_ptr of %s is 0\n", fileptr->unix_path); return 0; } val = parent_ptr->dir_byte >> 9; fileptr->lastmod_time = (val << 16); // Parent block val = parent_ptr->dir_byte & 0x1ff; ent = (val - 4) / 0x27; fileptr->header_pointer = 0x2700 | (ent + 1); } else { // Directory entry, or normal file if(head_ptr == 0) { printf("head_ptr of %s is 0\n", fileptr->unix_path); return 0; } header_pointer = head_ptr->key_block; fileptr->header_pointer = header_pointer; dynapro_set_word16(&bptr[0x25], header_pointer); pkeyptr = &(dsk->raw_data[header_pointer << 9]); val = head_ptr->lastmod_time + 1; head_ptr->lastmod_time = val; dynapro_set_word16(&pkeyptr[4 + 0x21], val); // File count } dynapro_set_word32(&bptr[0x21], fileptr->lastmod_time); // Last Modified date&time (or header info) dynapro_set_word16(&bptr[0x25], fileptr->header_pointer); #if 0 printf("Set dir_byte %07x=%04x\n", dir_byte + 0x25, fileptr->header_pointer); #endif return dir_byte; } // When creating sparse files, always ensure first block is not sparse. GS/OS // does not treat the first block as sparse, it will actually read block 0 // This handles normal files, and one fork of a forked file word32 dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, word32 key_block, dword64 dsize) { byte *bptr; word32 sap_block, tree_block, sap_byte, tree_byte, sparse, block_num; word32 num_blocks, blocks_used, block_off, storage_type; int num_bytes; int i; bptr = &(dsk->raw_data[0]); *storage_type_ptr = 0; sap_block = 0; tree_block = 0; num_blocks = (word32)((dsize + 511) >> 9); if(num_blocks == 0) { // 0-length file num_blocks = 1; } else if(num_blocks > 0x8000) { // >= 16MB (32K*512) printf("File is too large, failing\n"); return 0; } block_off = 0; blocks_used = 1; while(block_off < num_blocks) { sparse = (block_off > 0); // sparse=0 for first block for(i = 0; i < 0x200; i++) { if(fptr[(block_off << 9) + i] != 0) { sparse = 0; break; } } if(sparse) { block_off++; continue; } if((tree_block == 0) && (num_blocks > 256)) { tree_block = dynapro_find_free_block(dsk); if(tree_block == 0) { return 0; } blocks_used++; } tree_byte = (tree_block << 9) + ((block_off >> 8) & 0xff); if(tree_block) { sap_block = bptr[tree_byte + 0] | (bptr[tree_byte + 256] << 8); } if((sap_block == 0) && (num_blocks > 1)) { sap_block = dynapro_find_free_block(dsk); if(sap_block == 0) { return 0; } blocks_used++; if(tree_block) { bptr[tree_byte + 0] = sap_block; bptr[tree_byte + 256] = sap_block >> 8; } } if(block_off == 0) { block_num = key_block; } else { block_num = dynapro_find_free_block(dsk); if(block_num == 0) { return 0; } blocks_used++; } sap_byte = (sap_block << 9) | (block_off & 0xff); if(sap_block) { bptr[sap_byte + 0] = block_num; bptr[sap_byte + 256] = block_num >> 8; } num_bytes = 0x200; if(block_off == (dsize >> 9)) { // Last block num_bytes = dsize & 0x1ff; } for(i = 0; i < num_bytes; i++) { bptr[(block_num << 9) + i] = fptr[(block_off << 9) + i]; } block_off++; } storage_type = 0x10; if(tree_block) { storage_type = 0x30; key_block = tree_block; } else if(sap_block) { storage_type = 0x20; key_block = sap_block; } *storage_type_ptr = storage_type; return (blocks_used << 16) | key_block; } word32 dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr) { byte *bptr, *fptr; dword64 dsize; word32 storage_type, blocks_out, dir_byte; fptr = dynapro_malloc_file(fileptr->unix_path, &dsize, 0x200); fileptr->eof = (word32)dsize; #if 0 printf("file_from_unix %s, size:%08llx, file_type:%02x, dir_byte:" "%07x, storage:%02x\n", fileptr->unix_path, dsize, fileptr->file_type, fileptr->dir_byte, fileptr->prodos_name[0]); #endif storage_type = 0; if((fileptr->prodos_name[0] & 0xf0) == 0x50) { // .applesingle file with data and/or resource forks blocks_out = applesingle_from_unix(dsk, fileptr, fptr, dsize); } else { // Normal file fileptr->prodos_name[0] = (fileptr->prodos_name[0] & 0xf); blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, fileptr->key_block, dsize); } free(fptr); fileptr->prodos_name[0] |= storage_type; fileptr->key_block = blocks_out & 0xffff; fileptr->blocks_used = (blocks_out >> 16) & 0xffff; // Update dir_byte information for this file dir_byte = fileptr->dir_byte; if(dir_byte == 0) { dyna_printf("dir_byte is 0 for %s\n", fileptr->unix_path); } bptr = &(dsk->raw_data[dir_byte]); bptr[0] = fileptr->prodos_name[0]; bptr[0x10] = fileptr->file_type; dynapro_set_word16(&bptr[0x11], fileptr->key_block); dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); dynapro_set_word24(&bptr[0x15], fileptr->eof); dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); #if 0 printf("Set %s dir_byte:%07x+0x10=%02x (file_type)\n", fileptr->unix_path, dir_byte, fileptr->file_type); #endif return blocks_out; } word32 dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks) { Dynapro_info *infoptr; byte *bptr; word32 bitmap_size_bytes, bitmap_size_blocks; int pos; word32 ui; int i; dsk->raw_data = calloc(num_blocks, 512); if(dsk->raw_data == 0) { dynapro_error(dsk, "Could not allocate %d bytes\n", num_blocks * 512); return 0; } dsk->dimage_size = num_blocks * 512LL; dsk->dimage_start = 0; dsk->raw_dsize = num_blocks * 512LL; bptr = &(dsk->raw_data[0]); for(i = 0; i < 512; i++) { bptr[i] = g_prodos_block0[i]; } // Directory is from blocks 2 through 5. Set up prev and next ptrs bptr = &(dsk->raw_data[2 * 0x200]); for(i = 0; i < 3; i++) { // Blocks 2,3,4 (or 3,4,5) dynapro_set_word16(&bptr[(i + 1)*0x200], i + 2); // Prev_blk dynapro_set_word16(&bptr[i*0x200 + 2], i + 3); // Next_blk } // Calculate bitmap to go in blocks 6... bitmap_size_bytes = (num_blocks + 7) >> 3; bitmap_size_blocks = (bitmap_size_bytes + 512 - 1) >> 9; bptr = &(dsk->raw_data[6 * 512]); // Block 6 bptr[0] = 0; for(ui = (6 + bitmap_size_blocks); ui < num_blocks; ui++) { pos = (ui >> 3); bptr[pos] |= (0x80U >> (ui & 7)); } infoptr = calloc(sizeof(Dynapro_info), 1); if(!infoptr) { return 0; } infoptr->root_path = kegs_malloc_str(dir_path); infoptr->volume_ptr = 0; infoptr->block_map_ptr = calloc(num_blocks * sizeof(Dynapro_map), 1); infoptr->damaged = 0; if((infoptr->root_path == 0) || (infoptr->block_map_ptr == 0)) { dynapro_error(dsk, "Could not allocate memory!\n"); return 0; } dsk->dynapro_info_ptr = infoptr; return 1; } word32 dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, word32 file_offset, word32 eof) { Dynapro_info *info_ptr; Dynapro_map *map_ptr; byte *buffer_ptr; word32 size, size_to_end; info_ptr = dsk->dynapro_info_ptr; if(!info_ptr || (block_num >= (dsk->dimage_size >> 9))) { printf(" mapping file %s, block %04x is invalid\n", fileptr->unix_path, block_num); return 0; } if(info_ptr->block_map_ptr == 0) { return 0; } if(block_num == 0) { return 1; } map_ptr = &(info_ptr->block_map_ptr[block_num]); if((map_ptr->file_ptr != 0) || (map_ptr->next_map_block != 0)) { dyna_printf("Mapping %s to block %04x, already has file_ptr:" "%p, next_map:%04x, mod:%d\n", fileptr->unix_path, block_num, map_ptr->file_ptr, map_ptr->next_map_block, map_ptr->modified); if(map_ptr->file_ptr) { dyna_printf(" Existing file: %s\n", map_ptr->file_ptr->unix_path); } return 0; } //printf(" map file %s block %05x off:%08x\n", fileptr->unix_path, // block_num, file_offset); map_ptr->next_map_block = fileptr->map_first_block; fileptr->map_first_block = block_num; map_ptr->modified = 0; map_ptr->file_ptr = fileptr; if(file_offset >= eof) { return 1; // This block was an "overhead" block } buffer_ptr = fileptr->buffer_ptr; if(buffer_ptr) { // Copy this block in at file_offset size = 0x200; size_to_end = eof - file_offset; if(size_to_end < size) { size = size_to_end; } #if 0 printf("mofb: Write to %p + %07x from block %04x, size:%04x\n", buffer_ptr, file_offset, block_num, size); #endif memcpy(buffer_ptr + file_offset, &(dsk->raw_data[block_num * 0x200]), size); } return 1; } word32 dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, int level, word32 file_offset, word32 eof) { byte *bptr; word32 entry_inc, tmp, ret; int i; #if 0 printf("dynapro_map_file_blocks %s block_num %05x level:%d off:%08x\n", fileptr->unix_path, block_num, level, file_offset); #endif if(level == 0) { return 0; // Bad value, should not happen } if(level == 1) { return dynapro_map_one_file_block(dsk, fileptr, block_num, file_offset, eof); } ret = dynapro_map_one_file_block(dsk, fileptr, block_num, 1U << 30, 0); if(ret == 0) { return ret; } entry_inc = 512; if(level == 3) { // Tree entry_inc = 256*512; } bptr = &(dsk->raw_data[block_num * 0x200]); for(i = 0; i < 256; i++) { tmp = bptr[i] + (bptr[256 + i] << 8); if(tmp == 0) { continue; } ret = dynapro_map_file_blocks(dsk, fileptr, tmp, level - 1, file_offset + i*entry_inc, eof); if(ret == 0) { return ret; } } dynapro_debug_map(dsk, "post map_file_blocks"); return 1; } word32 dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data) { word32 block_num, ret; int level; level = (fileptr->prodos_name[0] >> 4) & 0xf; block_num = fileptr->key_block; if(level == 5) { // Forked file return applesingle_map_from_prodos(dsk, fileptr, do_file_data); } else if((level < 0) || (level >= 4)) { printf("Storage_type: %02x for %s is bad\n", level, fileptr->unix_path); return 0; } fileptr->buffer_ptr = 0; if(do_file_data) { // Create a place for data. We will free before returning fileptr->buffer_ptr = calloc(1, fileptr->eof + 0x200); if(fileptr->buffer_ptr == 0) { printf("malloc failed!\n"); return 0; } } // Must not return now before free'ing fileptr->buffer_ptr! ret = dynapro_map_file_blocks(dsk, fileptr, block_num, level, 0, fileptr->eof); // printf(" dynapro_map_file, map_file_blocks ret:%04x\n", ret); if((ret != 0) && (do_file_data)) { // Then, write buffer_ptr to the unix file ret = dynapro_write_to_unix_file(fileptr->unix_path, fileptr->buffer_ptr, fileptr->eof); // printf(" map_file, write_to_unix_file ret:%04x\n", ret); } // And free the buffer_ptr free(fileptr->buffer_ptr); fileptr->buffer_ptr = 0; return ret; } word32 dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr) { byte *bptr; word32 block_num, ret; int cnt; // Loop over all directory blocks marking the map block_num = fileptr->key_block; if(block_num == 0) { printf("dynapro_map_dir_blocks, block_num is 0\n"); return 0; } bptr = &(dsk->raw_data[0]); cnt = 0; fileptr->map_first_block = 0; while(block_num != 0) { ret = dynapro_map_one_file_block(dsk, fileptr, block_num, 1U << 30, 0); if(ret == 0) { printf("dynapro_map_dir_on_block, ret 0, block:%04x\n", block_num); return 0; } block_num = dynapro_get_word16(&bptr[(block_num * 0x200) + 2]); cnt++; if(cnt > 1000) { printf("Directory had loop in it, error\n"); return 0; } } dynapro_debug_map(dsk, "post map_dir_blocks"); return 1; } word32 dynapro_build_map(Disk *dsk, Dynapro_file *fileptr) { word32 ret; if(fileptr == 0) { return 0; } // printf("### dynapro_build_map for dir:%s\n", fileptr->unix_path); // fileptr points to a directory header (volume or subdir). Walk // all siblings and build a map ret = 1; while(fileptr && ret) { if(fileptr->prodos_name[0] >= 0xe0) { // Directory/Volume header ret = dynapro_map_dir_blocks(dsk, fileptr); } else if(fileptr->subdir_ptr) { // Recurse to handle subdirectory ret = dynapro_build_map(dsk, fileptr->subdir_ptr); } else { ret = dynapro_map_file(dsk, fileptr, 0); } fileptr = fileptr->next_ptr; } dynapro_debug_map(dsk, "post build_map"); return ret; } int dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks) { word32 ret; #if 0 printf("dynapro_mount: %p, %s %08x\n", dsk, dir_path, num_blocks); #endif if(num_blocks >= 65536) { num_blocks = 65535; } ret = dynapro_prep_image(dsk, dir_path, num_blocks); if(ret == 0) { return -1; } ret = dynapro_create_dir(dsk, dir_path, 0, 0x404); // Block 2, +4 // printf("dynapro_mount will end with ret:%05x\n", ret); if(ret != 0) { ret = dynapro_build_map(dsk, dsk->dynapro_info_ptr->volume_ptr); } // dynapro_debug_update(dsk); if(ret == 0) { dynapro_error(dsk, "Folder too large. dynapro_build_map " "ret:0\n"); } else { ret = dynapro_validate_disk(dsk); } if(ret == 0) { dynapro_error(dsk, "dynapro_validate_disk ret:0\n"); dynapro_free_dynapro_info(dsk); return -1; } #ifndef _WIN32 setvbuf(stdout, 0, _IOLBF, 0); #endif dsk->fd = 0; return 0; } ================================================ FILE: upstream/kegs/src/engine.h ================================================ // "@(#)$KmKId: engine.h,v 1.9 2023-09-11 12:55:16+00 kentd Exp $" /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ int ENGINE_TYPE (Engine_reg *engine_ptr) { register byte *ptr; byte *arg_ptr; Pc_log *tmp_pc_ptr; Fplus *fplus_ptr; byte *stat; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; register word32 kpc, acc, xreg, yreg, direct, psr, zero, neg7, addr; word32 wstat, arg, stack, dbank, opcode, addr_latch, tmp1, tmp2; word32 getmem_tmp, save_addr, pull_tmp, tmp_bytes, dummy1; tmp_pc_ptr = 0; dummy1 = 0; if(tmp_pc_ptr || dummy1) { // "use" tmp_pc_ptr to avoid warning } kpc = engine_ptr->kpc; acc = engine_ptr->acc; xreg = engine_ptr->xreg; yreg = engine_ptr->yreg; stack = engine_ptr->stack; dbank = engine_ptr->dbank; direct = engine_ptr->direct; psr = engine_ptr->psr; fplus_ptr = engine_ptr->fplus_ptr; zero = !(psr & 2); neg7 = psr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; dfcyc = engine_ptr->dfcyc; g_ret1 = 0; while(dfcyc <= g_dcycles_end) { FETCH_OPCODE; LOG_PC_MACRO(); switch(opcode) { default: halt_printf("acc8 unk op: %02x\n", opcode); arg = 9 #include "defs_instr.h" * 2; break; #include "instable.h" break; } LOG_PC_MACRO2(); } engine_ptr->kpc = kpc; engine_ptr->acc = acc; engine_ptr->xreg = xreg; engine_ptr->yreg = yreg; engine_ptr->stack = stack; engine_ptr->dbank = dbank; engine_ptr->direct = direct; engine_ptr->dfcyc = dfcyc; psr = psr & (~0x82); psr |= (neg7 & 0x80); psr |= ((!zero) << 1); engine_ptr->psr = psr; return g_ret1; } ================================================ FILE: upstream/kegs/src/engine_c.c ================================================ const char rcsid_engine_c_c[] = "@(#)$KmKId: engine_c.c,v 1.99 2025-04-27 18:54:08+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" // PSR[8:0] is E_NVMX_DIZC extern int g_limit_speed; extern int g_halt_sim; extern int g_engine_recalc_event; extern int g_code_red; extern int g_ignore_halts; extern int g_user_halt_bad; extern dword64 g_dcycles_end; extern dword64 g_last_vbl_dfcyc; extern dword64 g_cur_dfcyc; extern int g_wait_pending; extern int g_irq_pending; extern int g_num_brk; extern int g_num_cop; extern int g_emul_6502_ind_page_cross_bug; extern byte *g_slow_memory_ptr; extern byte *g_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern byte *g_dummy_memory1_ptr; extern int g_num_breakpoints; extern Break_point g_break_pts[]; extern Kimage g_debugwin_kimage; extern word32 g_log_pc_enable; extern Pc_log *g_log_pc_ptr; extern Pc_log *g_log_pc_start_ptr; extern Pc_log *g_log_pc_end_ptr; extern Data_log *g_log_data_ptr; extern Data_log *g_log_data_start_ptr; extern Data_log *g_log_data_end_ptr; int g_ret1 = 0; int size_tab[] = { #include "size_c.h" }; int bogus[] = { 0, #include "op_routs.h" }; #define INC_KPC_1 kpc = (kpc & 0xff0000) + ((kpc + 1) & 0xffff); #define INC_KPC_2 kpc = (kpc & 0xff0000) + ((kpc + 2) & 0xffff); #define INC_KPC_3 kpc = (kpc & 0xff0000) + ((kpc + 3) & 0xffff); #define INC_KPC_4 kpc = (kpc & 0xff0000) + ((kpc + 4) & 0xffff); #define CYCLES_PLUS_1 dfcyc += dplus_1; #define CYCLES_PLUS_2 dfcyc += dplus_1 * 2; #define CYCLES_PLUS_3 dfcyc += dplus_1 * 3; #define CYCLES_PLUS_4 dfcyc += dplus_1 * 4; #define CYCLES_PLUS_5 dfcyc += dplus_1 * 5; #define CYCLES_MINUS_1 dfcyc -= dplus_1; #define CYCLES_MINUS_2 dfcyc -= dplus_1 * 2; #define FCYCLES_ROUND dfcyc = dfcyc + dplus_x_m1; \ dfcyc = (dfcyc >> 16) << 16; #define GET_1BYTE_ARG arg = arg_ptr[1]; #define GET_2BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8); #define GET_3BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8) + (arg_ptr[3]<<16); #define LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) \ g_log_data_ptr->dfcyc = dfcyc; \ g_log_data_ptr->stat = in_stat; \ g_log_data_ptr->addr = in_addr; \ g_log_data_ptr->val = in_val; \ g_log_data_ptr->size = in_size; \ g_log_data_ptr++; \ if(g_log_data_ptr >= g_log_data_end_ptr) { \ g_log_data_ptr = g_log_data_start_ptr; \ } /* HACK HACK HACK */ #define UPDATE_PSR(dummy, old_psr) \ if(psr & 0x100) { \ psr |= 0x30; \ stack = 0x100 + (stack & 0xff); \ } \ if((~old_psr & psr) & 0x10) { \ xreg = xreg & 0xff; \ yreg = yreg & 0xff; \ } \ if(((psr & 4) == 0) && g_irq_pending) { \ FINISH(RET_IRQ, 0); \ } \ if((old_psr ^ psr) & 0x20) { \ FINISH(RET_PSR, 0); \ } extern Page_info page_info_rd_wr[]; extern word32 g_slow_mem_changed[]; #define GET_MEMORY8(addr,dest) \ addr_latch = (addr); \ CYCLES_PLUS_1; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if(wstat & (1 << (31 - BANK_IO_BIT))) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory8_io_stub((addr), stat, \ &dcycles_tmp1, dplus_x_m1); \ dfcyc = dcycles_tmp1; \ } else { \ dest = *ptr; \ } #define GET_MEMORY(addr,dest) GET_MEMORY8(addr, dest) #define GET_MEMORY16(addr, dest, in_bank) \ save_addr = addr; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xff) == 0xff)) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory16_pieces_stub((addr), stat, \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_2; \ dest = ptr[0] + (ptr[1] << 8); \ } \ addr_latch = save_addr; #define GET_MEMORY24(addr, dest, in_bank) \ save_addr = addr; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xfe) == 0xfe)) { \ dcycles_tmp1 = dfcyc; \ dest = get_memory24_pieces_stub((addr), stat, \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_3; \ dest = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16); \ } \ addr_latch = save_addr; #define GET_MEMORY_DIRECT_PAGE16(addr, dest, dloc_x_wrap) \ save_addr = addr; \ if(psr & 0x100) { \ if((direct & 0xff) == 0) { \ save_addr = (save_addr & 0xff) + direct; \ } \ } \ if((psr & 0x100) && (((addr) & 0xff) == 0xff)) { \ GET_MEMORY8(save_addr, getmem_tmp); \ if(dloc_x_wrap) { \ save_addr = (save_addr & 0xff00) | \ ((save_addr + 1) & 0xff); \ } else { \ save_addr = (save_addr + 1) & 0xffff; \ } \ if((direct & 0xff) == 0) { \ save_addr = (save_addr & 0xff) + direct; \ } \ GET_MEMORY8(save_addr, dest); \ dest = (dest << 8) + getmem_tmp; \ } else { \ GET_MEMORY16(save_addr, dest, 1); \ } #define PUSH8(arg) \ SET_MEMORY8(stack, arg); \ stack = (stack - 1) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PUSH16(arg) \ if((stack & 0xfe) == 0) { \ /* stack will cross page! */ \ PUSH8((arg) >> 8); \ PUSH8(arg); \ } else { \ stack = (stack - 2) & 0xffff; \ SET_MEMORY16(stack + 1, arg, 1); \ } #define PUSH16_UNSAFE(arg) \ save_addr = (stack - 1) & 0xffff; \ stack = (stack - 2) & 0xffff; \ SET_MEMORY16(save_addr, arg, 1); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PUSH24_UNSAFE(arg) \ save_addr = (stack - 2) & 0xffff; \ stack = (stack - 3) & 0xffff; \ SET_MEMORY24(save_addr, arg, 1); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL8(dest) \ stack++; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ stack = stack & 0xffff; \ GET_MEMORY8(stack, dest); #define PULL8_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY8(stack, dest); \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL16(dest) \ if((stack & 0xfe) == 0xfe) { /* page cross */ \ PULL8(dest); \ PULL8(pull_tmp); \ dest = (pull_tmp << 8) + dest; \ } else { \ GET_MEMORY16(stack + 1, dest, 1); \ stack = (stack + 2) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ } #define PULL16_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY16(stack, dest, 1); \ stack = (stack + 1) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define PULL24(dest) \ if((stack & 0xfc) == 0xfc) { /* page cross */ \ PULL8(dest); \ PULL8(pull_tmp); \ pull_tmp = (pull_tmp << 8) + dest; \ PULL8(dest); \ dest = (dest << 16) + pull_tmp; \ } else { \ GET_MEMORY24(stack + 1, dest, 1); \ stack = (stack + 3) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } \ } #define PULL24_UNSAFE(dest) \ stack = (stack + 1) & 0xffff; \ GET_MEMORY24(stack, dest, 1); \ stack = (stack + 2) & 0xffff; \ if(psr & 0x100) { \ stack = 0x100 | (stack & 0xff); \ } #define SET_MEMORY8(addr, val) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 8, stat); \ CYCLES_PLUS_1; \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if(wstat) { \ dcycles_tmp1 = dfcyc; \ set_memory8_io_stub((addr), val, stat, &dcycles_tmp1, \ dplus_x_m1); \ dfcyc = dcycles_tmp1; \ } else { \ *ptr = val; \ } #define SET_MEMORY16(addr, val, in_bank) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 16, stat); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat) || (((addr) & 0xff) == 0xff)) { \ dcycles_tmp1 = dfcyc; \ set_memory16_pieces_stub((addr), (val), \ &dcycles_tmp1, dplus_1, dplus_x_m1, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_2; \ ptr[0] = (val); \ ptr[1] = (val) >> 8; \ } #define SET_MEMORY24(addr, val, in_bank) \ stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ LOG_DATA_MACRO(addr, val, 24, stat); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ if((wstat) || (((addr) & 0xfe) == 0xfe)) { \ dcycles_tmp1 = dfcyc; \ set_memory24_pieces_stub((addr), (val), \ &dcycles_tmp1, fplus_ptr, in_bank); \ dfcyc = dcycles_tmp1; \ } else { \ CYCLES_PLUS_3; \ ptr[0] = (val); \ ptr[1] = (val) >> 8; \ ptr[2] = (val) >> 16; \ } word32 get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1) { dword64 dfcyc; word32 wstat; byte *ptr; wstat = PTR2WORD(stat) & 0xff; dfcyc = *dcycs_ptr; if(wstat & BANK_BREAK) { check_breakpoints(addr, dfcyc, 0, 1); } if(wstat & BANK_IO2_TMP) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; return get_memory_io((addr), dcycs_ptr); } else { ptr = stat - wstat + (addr & 0xff); return *ptr; } } word32 get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, wstat, ret, tmp1, addr_latch; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; GET_MEMORY8(addr, tmp1); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } GET_MEMORY8(addrp1, ret); *dcycs_ptr = dfcyc; return (ret << 8) + (tmp1); } word32 get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, addrp2, wstat, addr_latch, ret, tmp1, tmp2; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; GET_MEMORY8(addr, tmp1); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } GET_MEMORY8(addrp1, tmp2); addrp2 = addr + 2; if(in_bank) { addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); } GET_MEMORY8(addrp2, ret); *dcycs_ptr = dfcyc; return (ret << 16) + (tmp2 << 8) + tmp1; } void set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1) { byte *ptr; dword64 dfcyc; word32 setmem_tmp1, tmp1, tmp2, wstat; wstat = PTR2WORD(stat) & 0xff; dfcyc = *dcycs_ptr; if(wstat & (1 << (31 - BANK_BREAK_BIT))) { check_breakpoints(addr, dfcyc, 0, 2); } ptr = stat - wstat + ((addr) & 0xff); if(wstat & (1 << (31 - BANK_IO2_BIT))) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; set_memory_io((addr), val, dcycs_ptr); } else if(wstat & (1 << (31 - BANK_SHADOW_BIT))) { if(g_limit_speed) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; } tmp1 = (addr & 0xffff); setmem_tmp1 = g_slow_memory_ptr[tmp1]; *ptr = val; g_slow_memory_ptr[tmp1] = val; if(setmem_tmp1 != ((val) & 0xff)) { g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); } } else if(wstat & (1 << (31 - BANK_SHADOW2_BIT))) { if(g_limit_speed) { FCYCLES_ROUND; *dcycs_ptr = dfcyc; } tmp2 = (addr & 0xffff); tmp1 = 0x10000 + tmp2; setmem_tmp1 = g_slow_memory_ptr[tmp1]; *ptr = val; g_slow_memory_ptr[tmp1] = val; if(setmem_tmp1 != ((val) & 0xff)) { g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); if((tmp1 & 0xff00) == 0x9d00) { scb_changed(dfcyc, tmp1, val, setmem_tmp1); } } } else { /* breakpoint only */ *ptr = val; } } #define LOG_PC_MACRO() #define LOG_PC_MACRO2() #define LOG_DATA_MACRO(addr, val, size, in_stat) void set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, dword64 dplus_1, dword64 dplus_x_m1, int in_bank) { byte *ptr; byte *stat; dword64 dfcyc, dcycles_tmp1; word32 addrp1, wstat; dfcyc = *dcycs_ptr; SET_MEMORY8(addr, val); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } SET_MEMORY8(addrp1, val >> 8); *dcycs_ptr = dfcyc; } void set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank) { byte *ptr; byte *stat; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addrp1, addrp2; word32 wstat; dfcyc = *dcycs_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; SET_MEMORY8(addr, val); addrp1 = addr + 1; if(in_bank) { addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); } SET_MEMORY8(addrp1, val >> 8); addrp2 = addr + 2; if(in_bank) { addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); } SET_MEMORY8(addrp2, val >> 16); *dcycs_ptr = dfcyc; } word32 get_memory_c(word32 addr) { byte *stat, *ptr; dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; word32 addr_latch, wstat, ret; dfcyc = 0; dplus_1 = 0; dplus_x_m1 = 0; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } GET_MEMORY8(addr, ret); return ret; } word32 get_memory16_c(word32 addr) { return get_memory_c(addr) + (get_memory_c(addr+1) << 8); } word32 get_memory24_c(word32 addr) { return get_memory_c(addr) + (get_memory_c(addr+1) << 8) + (get_memory_c(addr+2) << 16); } void set_memory_c(word32 addr, word32 val, int do_log) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 wstat; dfcyc = g_cur_dfcyc; dplus_1 = 0; dplus_x_m1 = 0; SET_MEMORY8(addr, val); if(g_log_pc_enable && do_log) { LOG_DATA_MACRO_ACT(addr, val, 8, stat) } } void set_memory16_c(word32 addr, word32 val, int do_log) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 wstat; dfcyc = g_cur_dfcyc; dplus_1 = 0; dplus_x_m1 = 0; SET_MEMORY16(addr, val, 0); if(g_log_pc_enable && do_log) { LOG_DATA_MACRO_ACT(addr, val, 16, stat) } } void set_memory24_c(word32 addr, word32 val) { set_memory_c(addr, val, 1); set_memory_c(addr + 1, val >> 8, 1); set_memory_c(addr + 2, val >> 16, 1); } word32 do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub) { word32 sum, carry, overflow; word32 zero; int decimal; overflow = 0; decimal = psr & 8; if(sub) { in2 = (in2 ^ 0xff); } if(!decimal) { sum = (in1 & 0xff) + in2 + (psr & 1); overflow = ((sum ^ in2) >> 1) & 0x40; } else { /* decimal */ sum = (in1 & 0xf) + (in2 & 0xf) + (psr & 1); if(sub) { if(sum < 0x10) { sum = (sum - 0x6) & 0xf; } } else { if(sum >= 0xa) { sum = (sum - 0xa) | 0x10; } } sum = (in1 & 0xf0) + (in2 & 0xf0) + sum; overflow = ((sum >> 2) ^ (sum >> 1)) & 0x40; if(sub) { if(sum < 0x100) { sum = (sum + 0xa0) & 0xff; } } else { if(sum >= 0xa0) { sum += 0x60; } } } zero = ((sum & 0xff) == 0); carry = (sum >= 0x100); if((in1 ^ in2) & 0x80) { overflow = 0; } psr = psr & (~0xc3); psr = psr + (sum & 0x80) + overflow + (zero << 1) + carry; return (psr << 16) + (sum & 0xff); } word32 do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub) { word32 sum, carry, overflow; word32 tmp1, tmp2; word32 zero; int decimal; overflow = 0; decimal = psr & 8; if(!decimal) { if(sub) { in2 = (in2 ^ 0xffff); } sum = in1 + in2 + (psr & 1); overflow = ((sum ^ in2) >> 9) & 0x40; } else { /* decimal */ if(sub) { tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); psr = (tmp1 >> 16); tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, (in2 >> 8) & 0xff, psr, sub); in2 = (in2 ^ 0xfffff); } else { tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); psr = (tmp1 >> 16); tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, (in2 >> 8) &0xff, psr, sub); } sum = ((tmp2 & 0xff) << 8) + (tmp1 & 0xff) + (((tmp2 >> 16) & 1) << 16); overflow = (tmp2 >> 16) & 0x40; } zero = ((sum & 0xffff) == 0); carry = (sum >= 0x10000); if((in1 ^ in2) & 0x8000) { overflow = 0; } psr = psr & (~0xc3); psr = psr + ((sum & 0x8000) >> 8) + overflow + (zero << 1) + carry; return (psr << 16) + (sum & 0xffff); } void fixed_memory_ptrs_init() { /* set g_slow_memory_ptr, g_rom_fc_ff_ptr, g_dummy_memory1_ptr, */ /* and rom_cards_ptr */ g_slow_memory_ptr = memalloc_align(128*1024, 0, 0); g_dummy_memory1_ptr = memalloc_align(256, 1024, 0); g_rom_fc_ff_ptr = memalloc_align(256*1024, 512, 0); g_rom_cards_ptr = memalloc_align(16*256, 256, 0); #if 0 printf("g_memory_ptr: %08x, dummy_mem: %08x, slow_mem_ptr: %08x\n", (word32)g_memory_ptr, (word32)g_dummy_memory1_ptr, (word32)g_slow_memory_ptr); printf("g_rom_fc_ff_ptr: %08x, g_rom_cards_ptr: %08x\n", (word32)g_rom_fc_ff_ptr, (word32)g_rom_cards_ptr); printf("page_info_rd = %08x, page_info_wr end = %08x\n", (word32)&(page_info_rd_wr[0]), (word32)&(page_info_rd_wr[PAGE_INFO_PAD_SIZE+0x1ffff].rd_wr)); #endif } word32 get_itimer() { #if defined(__i386) && defined(__GNUC__) /* Here's my bad ia32 asm code to do rdtsc */ /* Linux source uses: */ /* asm volatile("rdtsc" : "=a"(ret) : : "edx"); */ /* asm volatile("rdtsc" : "=%eax"(ret) : : "%edx"); */ /* GCC bug report 2001-03/msg00786.html used: */ /*register dword64 dtmp; */ /*asm volatile ("rdtsc" : "=A" (dtmp)); */ /*return (word32)dtmp; */ register word32 ret; asm volatile ("rdtsc;movl %%eax,%0" : "=r"(ret) : : "%eax","%edx"); return ret; #else # if defined(__POWERPC__) && defined(__GNUC__) register word32 ret; asm volatile ("mftb %0" : "=r"(ret)); return ret; # else # if defined(__x86_64__) register word32 ret, hi; //ret = __rdtsc(); asm volatile ("rdtsc" : "=a" (ret), "=d" (hi)); return ret; # else # if defined(__aarch64__) /* 64-bit ARM architecture */ register dword64 ret; asm volatile("mrs %0,CNTVCT_EL0" : "=r"(ret)); return ret; # else return 0; # endif # endif # endif #endif } void engine_recalc_events() { g_dcycles_end = 0; // End inner loop g_engine_recalc_event++; } void set_halt_act(int val) { if((val == 1) && g_ignore_halts && !g_user_halt_bad) { g_code_red++; } else { if(g_halt_sim == 0) { debugger_update_list_kpc(); } g_halt_sim |= val; if(g_halt_sim) { video_set_active(&g_debugwin_kimage, 1); } g_dcycles_end = 0; } } void clr_halt_act() { g_halt_sim = 0; } word32 get_remaining_operands(word32 addr, word32 opcode, word32 psr, dword64 *dcyc_ptr, Fplus *fplus_ptr) { byte *stat, *ptr; dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; word32 addr_latch, wstat, save_addr, arg, addrp1; int size; dfcyc = *dcyc_ptr; dplus_1 = fplus_ptr->dplus_1; dplus_x_m1 = fplus_ptr->dplus_x_minus_1; addr_latch = 0; if(addr_latch != 0) { // "Use" addr_latch to avoid warning } size = size_tab[opcode]; addrp1 = (addr & 0xff0000) + ((addr + 1) & 0xffff); switch(size) { case 0: // Always read pc+1 for single-byte opcodes GET_MEMORY8(addrp1, arg); arg = 0; /* no args */ break; case 1: GET_MEMORY8(addrp1, arg); break; /* 1 arg, already done */ case 2: GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; break; case 3: GET_MEMORY24(addrp1, arg, 1); dfcyc = dfcyc - dplus_1 - dplus_1; break; case 4: if(psr & 0x20) { GET_MEMORY8(addrp1, arg); } else { GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; } break; case 5: if(psr & 0x10) { GET_MEMORY8(addrp1, arg); } else { GET_MEMORY16(addrp1, arg, 1); dfcyc -= dplus_1; } break; default: printf("Unknown size: %d\n", size); arg = 0; exit(-2); } *dcyc_ptr = dfcyc; return arg; } #define FETCH_OPCODE \ addr = kpc; \ CYCLES_PLUS_2; \ stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ wstat = PTR2WORD(stat) & 0xff; \ ptr = stat - wstat + ((addr) & 0xff); \ arg_ptr = ptr; \ opcode = *ptr; \ if((wstat & (1 << (31-BANK_IO_BIT))) || ((addr & 0xff) > 0xfc)) {\ CYCLES_MINUS_1; \ if(wstat & BANK_BREAK) { \ check_breakpoints(addr, dfcyc, stack, 4); \ } \ if(wstat & (1 << (31 - BANK_IO2_BIT))) { \ FCYCLES_ROUND; \ dcycles_tmp1 = dfcyc; \ opcode = get_memory_io((addr), &dcycles_tmp1); \ dfcyc = dcycles_tmp1; \ } else { \ opcode = *ptr; \ } \ dcycles_tmp1 = dfcyc; \ arg = get_remaining_operands(addr, opcode, psr, \ &dcycles_tmp1, fplus_ptr); \ dfcyc = dcycles_tmp1; \ arg_ptr = (byte *)&tmp_bytes; \ arg_ptr[1] = arg; \ arg_ptr[2] = arg >> 8; \ arg_ptr[3] = arg >> 16; \ } #define ACC8 #define IS_ACC16 0 #define ENGINE_TYPE enter_engine_acc8 #include "engine.h" // The above creates enter_engine_acc8 #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define IS_ACC16 1 #define ENGINE_TYPE enter_engine_acc16 #include "engine.h" // The above creates enter_engine_acc16 #undef LOG_PC_MACRO #undef LOG_PC_MACRO2 #undef LOG_DATA_MACRO #define LOG_PC_MACRO() \ tmp_pc_ptr = g_log_pc_ptr; \ tmp_pc_ptr->dbank_kpc = (dbank << 24) + kpc; \ tmp_pc_ptr->instr = (opcode << 24) + arg_ptr[1] + \ (arg_ptr[2] << 8) + (arg_ptr[3] << 16); \ tmp_pc_ptr->dfcyc = dfcyc - dplus_1 * 2; #define LOG_PC_MACRO2() \ tmp_pc_ptr->psr_acc = ((psr & ~(0x82)) << 16) | acc | \ ((neg7 & 0x80) << 16) | ((!zero) << 17); \ tmp_pc_ptr->xreg_yreg = (xreg << 16) + yreg; \ tmp_pc_ptr->stack_direct = (stack << 16) + direct; \ tmp_pc_ptr++; \ if(tmp_pc_ptr >= g_log_pc_end_ptr) { \ tmp_pc_ptr = g_log_pc_start_ptr; \ } \ g_log_pc_ptr = tmp_pc_ptr; #define LOG_DATA_MACRO(in_addr, in_val, in_size, in_stat) \ LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define ACC8 #define IS_ACC16 0 #define ENGINE_TYPE enter_engine_acc8_log #include "engine.h" // The above creates enter_engine_acc8_log #undef ACC8 #undef IS_ACC16 #undef ENGINE_TYPE #define IS_ACC16 1 #define ENGINE_TYPE enter_engine_acc16_log #include "engine.h" // The above creates enter_engine_acc16_log int enter_engine(Engine_reg *engine_ptr) { dword64 dcycles_end_save; int ret; dcycles_end_save = g_dcycles_end; while(1) { if(g_log_pc_enable) { if(engine_ptr->psr & 0x20) { // 8-bit accumulator ret = enter_engine_acc8_log(engine_ptr); } else { ret = enter_engine_acc16_log(engine_ptr); } } else { if(engine_ptr->psr & 0x20) { // 8-bit accumulator ret = enter_engine_acc8(engine_ptr); } else { ret = enter_engine_acc16(engine_ptr); } } if((ret == RET_PSR) && !g_halt_sim) { g_dcycles_end = dcycles_end_save; continue; } return ret; } } ================================================ FILE: upstream/kegs/src/instable.h ================================================ // "@(#)$KmKId: instable.h,v 1.121 2023-11-12 15:31:14+00 kentd Exp $" /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ case 0x00: /* brk */ GET_1BYTE_ARG; g_num_brk++; INC_KPC_2; psr = (psr & (~0x82)) | (neg7 & 0x80) | ((!zero) << 1); if(psr & 0x100) { PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xfffffe; dbank = 0; } else { PUSH8(kpc >> 16); PUSH16(kpc); PUSH8(psr & 0xff); tmp1 = 0xffffe6; halt_printf("Halting for native break!\n"); } tmp1 = moremem_fix_vector_pull(tmp1); GET_MEMORY16(tmp1, kpc, 0); kpc = kpc & 0xffff; psr |= 0x4; psr &= ~(0x8); break; case 0x01: /* ORA (Dloc,X) */ GET_DLOC_X_IND_RD(); ORA_INST(); break; case 0x02: /* COP */ g_num_cop++; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); if(psr & 0x100) { halt_printf("Halting for emul COP at %04x\n", kpc); PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xfffff4; dbank = 0; } else { PUSH8(kpc >> 16); PUSH16(kpc & 0xffff); PUSH8(psr & 0xff); tmp1 = 0xffffe4; } tmp1 = moremem_fix_vector_pull(tmp1); GET_MEMORY16(tmp1, kpc, 0); kpc = kpc & 0xffff; psr |= 4; psr &= ~(0x8); break; case 0x03: /* ORA Disp8,S */ GET_DISP8_S_RD(); ORA_INST(); break; case 0x04: /* TSB Dloc */ GET_DLOC_RD_RMW(); TSB_INST(1); break; case 0x05: /* ORA Dloc */ GET_DLOC_RD(); ORA_INST(); break; case 0x06: /* ASL Dloc */ GET_DLOC_RD_RMW(); ASL_INST(1); break; case 0x07: /* ORA [Dloc] */ GET_DLOC_L_IND_RD(); ORA_INST(); break; case 0x08: /* PHP */ INC_KPC_1; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); PUSH8(psr); break; case 0x09: /* ORA #imm */ GET_IMM_MEM(); ORA_INST(); break; case 0x0a: /* ASL a */ INC_KPC_1; tmp1 = acc + acc; #ifdef ACC8 SET_CARRY8(tmp1); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(acc & 0xff); #else SET_CARRY16(tmp1); acc = tmp1 & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x0b: /* PHD */ INC_KPC_1; PUSH16_UNSAFE(direct); break; case 0x0c: /* TSB abs */ GET_ABS_RD_RMW(); TSB_INST(0); break; case 0x0d: /* ORA abs */ GET_ABS_RD(); ORA_INST(); break; case 0x0e: /* ASL abs */ GET_ABS_RD_RMW(); ASL_INST(0); break; case 0x0f: /* ORA long */ GET_LONG_RD(); ORA_INST(); break; case 0x10: /* BPL disp8 */ BRANCH_DISP8((neg7 & 0x80) == 0); break; case 0x11: /* ORA (Dloc),y */ GET_DLOC_IND_Y_RD(); ORA_INST(); break; case 0x12: /* ORA (Dloc) */ GET_DLOC_IND_RD(); ORA_INST(); break; case 0x13: /* ORA (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); ORA_INST(); break; case 0x14: /* TRB Dloc */ GET_DLOC_RD_RMW(); TRB_INST(1); break; case 0x15: /* ORA Dloc,x */ GET_DLOC_X_RD(); ORA_INST(); break; case 0x16: /* ASL Dloc,X */ GET_DLOC_X_RD_RMW(); ASL_INST(1); break; case 0x17: /* ORA [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); ORA_INST(); break; case 0x18: /* CLC */ psr = psr & (~1); INC_KPC_1; break; case 0x19: /* ORA abs,y */ GET_ABS_Y_RD(); ORA_INST(); break; case 0x1a: /* INC a */ INC_KPC_1; #ifdef ACC8 acc = (acc & 0xff00) | ((acc + 1) & 0xff); SET_NEG_ZERO8(acc & 0xff); #else acc = (acc + 1) & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x1b: /* TCS */ stack = acc; INC_KPC_1; if(psr & 0x100) { stack = (stack & 0xff) + 0x100; } break; case 0x1c: /* TRB Abs */ GET_ABS_RD_RMW(); TRB_INST(0); break; case 0x1d: /* ORA Abs,X */ GET_ABS_X_RD(); ORA_INST(); break; case 0x1e: /* ASL Abs,X */ GET_ABS_X_RD_RMW(); ASL_INST(0); break; case 0x1f: /* ORA Long,X */ GET_LONG_X_RD(); ORA_INST(); break; case 0x20: /* JSR abs */ GET_2BYTE_ARG; INC_KPC_2; PUSH16(kpc); kpc = (kpc & 0xff0000) + arg; CYCLES_PLUS_2; break; case 0x21: /* AND (Dloc,X) */ GET_DLOC_X_IND_RD(); AND_INST(); break; case 0x22: /* JSL Long */ GET_3BYTE_ARG; tmp1 = arg; CYCLES_PLUS_3; INC_KPC_3; PUSH24_UNSAFE(kpc); kpc = tmp1 & 0xffffff; break; case 0x23: /* AND Disp8,S */ GET_DISP8_S_RD(); AND_INST(); break; case 0x24: /* BIT Dloc */ GET_DLOC_RD(); BIT_INST(); break; case 0x25: /* AND Dloc */ GET_DLOC_RD(); AND_INST(); break; case 0x26: /* ROL Dloc */ GET_DLOC_RD_RMW(); ROL_INST(1); break; case 0x27: /* AND [Dloc] */ GET_DLOC_L_IND_RD(); AND_INST(); break; case 0x28: /* PLP */ PULL8(tmp1); tmp2 = psr; CYCLES_PLUS_1; INC_KPC_1; psr = (psr & ~0xff) | (tmp1 & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0x29: /* AND #imm */ GET_IMM_MEM(); AND_INST(); break; case 0x2a: /* ROL a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) << 1) + (psr & 1); SET_CARRY8(tmp1); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc << 1) + (psr & 1); SET_CARRY16(tmp1); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x2b: /* PLD */ INC_KPC_1; PULL16_UNSAFE(direct); CYCLES_PLUS_1; SET_NEG_ZERO16(direct); break; case 0x2c: /* BIT abs */ GET_ABS_RD(); BIT_INST(); break; case 0x2d: /* AND abs */ GET_ABS_RD(); AND_INST(); break; case 0x2e: /* ROL abs */ GET_ABS_RD_RMW(); ROL_INST(0); break; case 0x2f: /* AND long */ GET_LONG_RD(); AND_INST(); break; case 0x30: /* BMI disp8 */ BRANCH_DISP8(neg7 & 0x80); break; case 0x31: /* AND (Dloc),y */ GET_DLOC_IND_Y_RD(); AND_INST(); break; case 0x32: /* AND (Dloc) */ GET_DLOC_IND_RD(); AND_INST(); break; case 0x33: /* AND (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); AND_INST(); break; case 0x34: /* BIT Dloc,x */ GET_DLOC_X_RD(); BIT_INST(); break; case 0x35: /* AND Dloc,x */ GET_DLOC_X_RD(); AND_INST(); break; case 0x36: /* ROL Dloc,X */ GET_DLOC_X_RD_RMW(); ROL_INST(1); break; case 0x37: /* AND [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); AND_INST(); break; case 0x38: /* SEC */ psr = psr | 1; INC_KPC_1; break; case 0x39: /* AND abs,y */ GET_ABS_Y_RD(); AND_INST(); break; case 0x3a: /* DEC a */ INC_KPC_1; #ifdef ACC8 acc = (acc & 0xff00) | ((acc - 1) & 0xff); SET_NEG_ZERO8(acc & 0xff); #else acc = (acc - 1) & 0xffff; SET_NEG_ZERO16(acc); #endif break; case 0x3b: /* TSC */ /* set N,Z according to 16 bit acc */ INC_KPC_1; acc = stack; SET_NEG_ZERO16(acc); break; case 0x3c: /* BIT Abs,x */ GET_ABS_X_RD(); BIT_INST(); break; case 0x3d: /* AND Abs,X */ GET_ABS_X_RD(); AND_INST(); break; case 0x3e: /* ROL Abs,X */ GET_ABS_X_RD_RMW(); ROL_INST(0); break; case 0x3f: /* AND Long,X */ GET_LONG_X_RD(); AND_INST(); break; case 0x40: /* RTI */ CYCLES_PLUS_1 if(psr & 0x100) { PULL24(tmp1); kpc = (kpc & 0xff0000) + ((tmp1 >> 8) & 0xffff); tmp2 = psr; psr = (psr & ~0xff) + (tmp1 & 0xff); neg7 = psr; zero = !(psr & 2); UPDATE_PSR(psr, tmp2); } else { PULL8(tmp1); tmp2 = psr; psr = (tmp1 & 0xff); neg7 = psr; zero = !(psr & 2); PULL24(kpc); UPDATE_PSR(psr, tmp2); } break; case 0x41: /* EOR (Dloc,X) */ GET_DLOC_X_IND_RD(); EOR_INST(); break; case 0x42: /* WDM */ GET_2BYTE_ARG; INC_KPC_2; if(arg < 0x100) { // Next byte is 00 INC_KPC_1; // Skip over the BRK } FINISH(RET_WDM, arg); break; case 0x43: /* EOR Disp8,S */ GET_DISP8_S_RD(); EOR_INST(); break; case 0x44: /* MVP */ GET_2BYTE_ARG; /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ if(psr & 0x100) { halt_printf("MVP in emulation!\n"); break; } dbank = arg & 0xff; tmp1 = (arg >> 8) & 0xff; CYCLES_PLUS_1; GET_MEMORY8((tmp1 << 16) + xreg, arg); SET_MEMORY8((dbank << 16) + yreg, arg); CYCLES_PLUS_2; xreg = (xreg - 1) & 0xffff; yreg = (yreg - 1) & 0xffff; if(psr & 0x10) { // 8-bit index registers xreg = xreg & 0xff; yreg = yreg & 0xff; } acc = (acc - 1) & 0xffff; if(acc == 0xffff) { INC_KPC_3; } break; case 0x45: /* EOR Dloc */ GET_DLOC_RD(); EOR_INST(); break; case 0x46: /* LSR Dloc */ GET_DLOC_RD_RMW(); LSR_INST(1); break; case 0x47: /* EOR [Dloc] */ GET_DLOC_L_IND_RD(); EOR_INST(); break; case 0x48: /* PHA */ INC_KPC_1; #ifdef ACC8 PUSH8(acc); #else PUSH16(acc); #endif break; case 0x49: /* EOR #imm */ GET_IMM_MEM(); EOR_INST(); break; case 0x4a: /* LSR a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) >> 1); SET_CARRY8(acc << 8); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc >> 1); SET_CARRY8((acc << 8)); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x4b: /* PHK */ PUSH8(kpc >> 16); INC_KPC_1; break; case 0x4c: /* JMP abs */ GET_2BYTE_ARG; CYCLES_PLUS_1; kpc = (kpc & 0xff0000) + arg; break; case 0x4d: /* EOR abs */ GET_ABS_RD(); EOR_INST(); break; case 0x4e: /* LSR abs */ GET_ABS_RD_RMW(); LSR_INST(0); break; case 0x4f: /* EOR long */ GET_LONG_RD(); EOR_INST(); break; case 0x50: /* BVC disp8 */ BRANCH_DISP8((psr & 0x40) == 0); break; case 0x51: /* EOR (Dloc),y */ GET_DLOC_IND_Y_RD(); EOR_INST(); break; case 0x52: /* EOR (Dloc) */ GET_DLOC_IND_RD(); EOR_INST(); break; case 0x53: /* EOR (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); EOR_INST(); break; case 0x54: /* MVN */ GET_2BYTE_ARG; /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ if(psr & 0x100) { halt_printf("MVN in emulation!\n"); break; } dbank = arg & 0xff; tmp1 = (arg >> 8) & 0xff; CYCLES_PLUS_1; GET_MEMORY8((tmp1 << 16) + xreg, arg); SET_MEMORY8((dbank << 16) + yreg, arg); CYCLES_PLUS_2; xreg = (xreg + 1) & 0xffff; yreg = (yreg + 1) & 0xffff; if(psr & 0x10) { // 8-bit index registers xreg = xreg & 0xff; yreg = yreg & 0xff; } acc = (acc - 1) & 0xffff; if(acc == 0xffff) { INC_KPC_3; } break; case 0x55: /* EOR Dloc,x */ GET_DLOC_X_RD(); EOR_INST(); break; case 0x56: /* LSR Dloc,X */ GET_DLOC_X_RD_RMW(); LSR_INST(1); break; case 0x57: /* EOR [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); EOR_INST(); break; case 0x58: /* CLI */ psr = psr & (~4); INC_KPC_1; if(((psr & 0x4) == 0) && g_irq_pending) { FINISH(RET_IRQ, 0); } break; case 0x59: /* EOR abs,y */ GET_ABS_Y_RD(); EOR_INST(); break; case 0x5a: /* PHY */ INC_KPC_1; if(psr & 0x10) { PUSH8(yreg); } else { PUSH16(yreg); } break; case 0x5b: /* TCD */ INC_KPC_1; direct = acc; SET_NEG_ZERO16(acc); break; case 0x5c: /* JMP Long */ GET_3BYTE_ARG; CYCLES_PLUS_2; kpc = arg; break; case 0x5d: /* EOR Abs,X */ GET_ABS_X_RD(); EOR_INST(); break; case 0x5e: /* LSR Abs,X */ GET_ABS_X_RD_RMW(); LSR_INST(0); break; case 0x5f: /* EOR Long,X */ GET_LONG_X_RD(); EOR_INST(); break; case 0x60: /* RTS */ CYCLES_PLUS_2 PULL16(tmp1); kpc = (kpc & 0xff0000) + ((tmp1 + 1) & 0xffff); break; case 0x61: /* ADC (Dloc,X) */ GET_DLOC_X_IND_RD(); ADC_INST(); break; case 0x62: /* PER */ GET_2BYTE_ARG; CYCLES_PLUS_2; INC_KPC_3; PUSH16_UNSAFE(kpc + arg); break; case 0x63: /* ADC Disp8,S */ GET_DISP8_S_RD(); ADC_INST(); break; case 0x64: /* STZ Dloc */ GET_DLOC_ADDR(); STZ_INST(1); break; case 0x65: /* ADC Dloc */ GET_DLOC_RD(); ADC_INST(); break; case 0x66: /* ROR Dloc */ GET_DLOC_RD_RMW(); ROR_INST(1); break; case 0x67: /* ADC [Dloc] */ GET_DLOC_L_IND_RD(); ADC_INST(); break; case 0x68: /* PLA */ INC_KPC_1; CYCLES_PLUS_1; #ifdef ACC8 PULL8(tmp1); acc = (acc & 0xff00) + tmp1; SET_NEG_ZERO8(tmp1); #else PULL16(tmp1); acc = tmp1; SET_NEG_ZERO16(tmp1); #endif break; case 0x69: /* ADC #imm */ GET_IMM_MEM(); ADC_INST(); break; case 0x6a: /* ROR a */ INC_KPC_1; #ifdef ACC8 tmp1 = ((acc & 0xff) >> 1) + ((psr & 1) << 7); SET_CARRY8((acc << 8)); acc = (acc & 0xff00) + (tmp1 & 0xff); SET_NEG_ZERO8(tmp1 & 0xff); #else tmp1 = (acc >> 1) + ((psr & 1) << 15); SET_CARRY16((acc << 16)); acc = (tmp1 & 0xffff); SET_NEG_ZERO16(acc); #endif break; case 0x6b: /* RTL */ CYCLES_PLUS_1; PULL24_UNSAFE(tmp1); kpc = (tmp1 & 0xff0000) + ((tmp1 + 1) & 0xffff); break; case 0x6c: /* JMP (abs) */ GET_2BYTE_ARG; CYCLES_PLUS_1; GET_MEMORY16(arg, tmp1, 1); if((psr & 0x100) && g_emul_6502_ind_page_cross_bug && ((arg & 0xff) == 0xff)) { GET_MEMORY8(arg - 0xff, tmp2); tmp1 = (tmp1 & 0xff) + (tmp2 << 8); halt_printf("Halting for emul ind jmp\n"); } kpc = (kpc & 0xff0000) + tmp1; break; case 0x6d: /* ADC abs */ GET_ABS_RD(); ADC_INST(); break; case 0x6e: /* ROR abs */ GET_ABS_RD_RMW(); ROR_INST(0); break; case 0x6f: /* ADC long */ GET_LONG_RD(); ADC_INST(); break; case 0x70: /* BVS disp8 */ BRANCH_DISP8((psr & 0x40)); break; case 0x71: /* ADC (Dloc),y */ GET_DLOC_IND_Y_RD(); ADC_INST(); break; case 0x72: /* ADC (Dloc) */ GET_DLOC_IND_RD(); ADC_INST(); break; case 0x73: /* ADC (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); ADC_INST(); break; case 0x74: /* STZ Dloc,x */ GET_1BYTE_ARG; GET_DLOC_X_WR(); STZ_INST(1); break; case 0x75: /* ADC Dloc,x */ GET_DLOC_X_RD(); ADC_INST(); break; case 0x76: /* ROR Dloc,X */ GET_DLOC_X_RD_RMW(); ROR_INST(1); break; case 0x77: /* ADC [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); ADC_INST(); break; case 0x78: /* SEI */ psr = psr | 4; INC_KPC_1; break; case 0x79: /* ADC abs,y */ GET_ABS_Y_RD(); ADC_INST(); break; case 0x7a: /* PLY */ INC_KPC_1; CYCLES_PLUS_1 if(psr & 0x10) { PULL8(yreg); SET_NEG_ZERO8(yreg); } else { PULL16(yreg); SET_NEG_ZERO16(yreg); } break; case 0x7b: /* TDC */ INC_KPC_1; acc = direct; SET_NEG_ZERO16(direct); break; case 0x7c: /* JMP (Abs,x) */ /* always access kbank, xreg cannot wrap into next bank */ GET_2BYTE_ARG; arg = (kpc & 0xff0000) + ((xreg + arg) & 0xffff); CYCLES_PLUS_2; GET_MEMORY16(arg, tmp1, 1); kpc = (kpc & 0xff0000) + tmp1; break; case 0x7d: /* ADC Abs,X */ GET_ABS_X_RD(); ADC_INST(); break; case 0x7e: /* ROR Abs,X */ GET_ABS_X_RD_RMW(); ROR_INST(0); break; case 0x7f: /* ADC Long,X */ GET_LONG_X_RD(); ADC_INST(); break; case 0x80: /* BRA */ BRANCH_DISP8(1); break; case 0x81: /* STA (Dloc,X) */ GET_DLOC_X_IND_ADDR(); STA_INST(0); break; case 0x82: /* BRL disp16 */ GET_2BYTE_ARG; CYCLES_PLUS_1; kpc = (kpc & 0xff0000) + ((kpc + 3 + arg) & 0xffff); break; case 0x83: /* STA Disp8,S */ GET_DISP8_S_ADDR(); STA_INST(1); break; case 0x84: /* STY Dloc */ GET_DLOC_ADDR(); STY_INST(1); break; case 0x85: /* STA Dloc */ GET_DLOC_ADDR(); STA_INST(1); break; case 0x86: /* STX Dloc */ GET_DLOC_ADDR(); STX_INST(1); break; case 0x87: /* STA [Dloc] */ GET_DLOC_L_IND_ADDR(); STA_INST(0); break; case 0x88: /* DEY */ INC_KPC_1; SET_INDEX_REG(yreg - 1, yreg); break; case 0x89: /* BIT #imm */ GET_IMM_MEM(); #ifdef ACC8 zero = (acc & arg) & 0xff; #else zero = (acc & arg) & 0xffff; #endif break; case 0x8a: /* TXA */ INC_KPC_1; arg = xreg; LDA_INST(); break; case 0x8b: /* PHB */ INC_KPC_1; PUSH8(dbank); break; case 0x8c: /* STY abs */ GET_ABS_ADDR(); STY_INST(0); break; case 0x8d: /* STA abs */ GET_ABS_ADDR(); STA_INST(0); break; case 0x8e: /* STX abs */ GET_ABS_ADDR(); STX_INST(0); break; case 0x8f: /* STA long */ GET_LONG_ADDR(); STA_INST(0); break; case 0x90: /* BCC disp8 */ BRANCH_DISP8((psr & 0x01) == 0); break; case 0x91: /* STA (Dloc),y */ GET_DLOC_IND_Y_ADDR(1); STA_INST(0); break; case 0x92: /* STA (Dloc) */ GET_DLOC_IND_ADDR(); STA_INST(0); break; case 0x93: /* STA (Disp8,s),y */ GET_DISP8_S_IND_Y_ADDR(); STA_INST(0); break; case 0x94: /* STY Dloc,x */ GET_DLOC_X_ADDR(); STY_INST(1); break; case 0x95: /* STA Dloc,x */ GET_DLOC_X_ADDR(); STA_INST(1); break; case 0x96: /* STX Dloc,Y */ GET_DLOC_Y_ADDR(); STX_INST(1); break; case 0x97: /* STA [Dloc],Y */ GET_DLOC_L_IND_Y_ADDR(); STA_INST(0); break; case 0x98: /* TYA */ INC_KPC_1; arg = yreg; LDA_INST(); break; case 0x99: /* STA abs,y */ GET_ABS_INDEX_ADDR(yreg, 1) STA_INST(0); break; case 0x9a: /* TXS */ stack = xreg; if(psr & 0x100) { stack = 0x100 | (stack & 0xff); } INC_KPC_1; break; case 0x9b: /* TXY */ SET_INDEX_REG(xreg, yreg); INC_KPC_1; break; case 0x9c: /* STZ Abs */ GET_ABS_ADDR(); STZ_INST(0); break; case 0x9d: /* STA Abs,X */ GET_ABS_INDEX_ADDR(xreg, 1); STA_INST(0); break; case 0x9e: /* STZ Abs,X */ GET_ABS_INDEX_ADDR(xreg, 1); STZ_INST(0); break; case 0x9f: /* STA Long,X */ GET_LONG_X_ADDR_FOR_WR(); STA_INST(0); break; case 0xa0: /* LDY #imm */ INC_KPC_2; if((psr & 0x10) == 0) { GET_2BYTE_ARG; CYCLES_PLUS_1 INC_KPC_1; } else { GET_1BYTE_ARG; } SET_INDEX_REG(arg, yreg); break; case 0xa1: /* LDA (Dloc,X) */ GET_DLOC_X_IND_RD(); LDA_INST(); break; case 0xa2: /* LDX #imm */ INC_KPC_2; if((psr & 0x10) == 0) { GET_2BYTE_ARG; CYCLES_PLUS_1 INC_KPC_1; } else { GET_1BYTE_ARG; } SET_INDEX_REG(arg, xreg); break; case 0xa3: /* LDA Disp8,S */ GET_DISP8_S_RD(); LDA_INST(); break; case 0xa4: /* LDY Dloc */ C_LDY_DLOC(); break; case 0xa5: /* LDA Dloc */ GET_DLOC_RD(); LDA_INST(); break; case 0xa6: /* LDX Dloc */ C_LDX_DLOC(); break; case 0xa7: /* LDA [Dloc] */ GET_DLOC_L_IND_RD(); LDA_INST(); break; case 0xa8: /* TAY */ INC_KPC_1; SET_INDEX_REG(acc, yreg); break; case 0xa9: /* LDA #imm */ GET_IMM_MEM(); LDA_INST(); break; case 0xaa: /* TAX */ INC_KPC_1; SET_INDEX_REG(acc, xreg); break; case 0xab: /* PLB */ INC_KPC_1; CYCLES_PLUS_1 PULL8_UNSAFE(dbank); SET_NEG_ZERO8(dbank); break; case 0xac: /* LDY abs */ C_LDY_ABS(); break; case 0xad: /* LDA abs */ GET_ABS_RD(); LDA_INST(); break; case 0xae: /* LDX abs */ C_LDX_ABS(); break; case 0xaf: /* LDA long */ GET_LONG_RD(); LDA_INST(); break; case 0xb0: /* BCS disp8 */ BRANCH_DISP8((psr & 0x01)); break; case 0xb1: /* LDA (Dloc),y */ GET_DLOC_IND_Y_RD(); LDA_INST(); break; case 0xb2: /* LDA (Dloc) */ GET_DLOC_IND_RD(); LDA_INST(); break; case 0xb3: /* LDA (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); LDA_INST(); break; case 0xb4: /* LDY Dloc,x */ C_LDY_DLOC_X(); break; case 0xb5: /* LDA Dloc,x */ GET_DLOC_X_RD(); LDA_INST(); break; case 0xb6: /* LDX Dloc,y */ C_LDX_DLOC_Y(); break; case 0xb7: /* LDA [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); LDA_INST(); break; case 0xb8: /* CLV */ psr = psr & ~0x40; INC_KPC_1; break; case 0xb9: /* LDA abs,y */ GET_ABS_Y_RD(); LDA_INST(); break; case 0xba: /* TSX */ INC_KPC_1; SET_INDEX_REG(stack, xreg); break; case 0xbb: /* TYX */ INC_KPC_1; SET_INDEX_REG(yreg, xreg); break; case 0xbc: /* LDY Abs,X */ C_LDY_ABS_X(); break; case 0xbd: /* LDA Abs,X */ GET_ABS_X_RD(); LDA_INST(); break; case 0xbe: /* LDX Abs,y */ C_LDX_ABS_Y(); break; case 0xbf: /* LDA Long,X */ GET_LONG_X_RD(); LDA_INST(); break; case 0xc0: /* CPY #imm */ C_CPY_IMM(); break; case 0xc1: /* CMP (Dloc,X) */ GET_DLOC_X_IND_RD(); CMP_INST(); break; case 0xc2: /* REP #imm */ GET_1BYTE_ARG; tmp2 = psr; CYCLES_PLUS_1; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); psr = psr & ~(arg & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0xc3: /* CMP Disp8,S */ GET_DISP8_S_RD(); CMP_INST(); break; case 0xc4: /* CPY Dloc */ C_CPY_DLOC(); break; case 0xc5: /* CMP Dloc */ GET_DLOC_RD(); CMP_INST(); break; case 0xc6: /* DEC Dloc */ GET_DLOC_RD_RMW(); DEC_INST(1); break; case 0xc7: /* CMP [Dloc] */ GET_DLOC_L_IND_RD(); CMP_INST(); break; case 0xc8: /* INY */ INC_KPC_1; SET_INDEX_REG(yreg + 1, yreg); break; case 0xc9: /* CMP #imm */ GET_IMM_MEM(); CMP_INST(); break; case 0xca: /* DEX */ INC_KPC_1; SET_INDEX_REG(xreg - 1, xreg); break; case 0xcb: /* WAI */ if(g_irq_pending) { g_wait_pending = 0; INC_KPC_1; } else { g_wait_pending = 1; } break; case 0xcc: /* CPY abs */ C_CPY_ABS(); break; case 0xcd: /* CMP abs */ GET_ABS_RD(); CMP_INST(); break; case 0xce: /* DEC abs */ GET_ABS_RD_RMW(); DEC_INST(0); break; case 0xcf: /* CMP long */ GET_LONG_RD(); CMP_INST(); break; case 0xd0: /* BNE disp8 */ BRANCH_DISP8(zero != 0); break; case 0xd1: /* CMP (Dloc),y */ GET_DLOC_IND_Y_RD(); CMP_INST(); break; case 0xd2: /* CMP (Dloc) */ GET_DLOC_IND_RD(); CMP_INST(); break; case 0xd3: /* CMP (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); CMP_INST(); break; case 0xd4: /* PEI Dloc */ GET_DLOC_ADDR() GET_MEMORY16(arg, arg, 1); CYCLES_PLUS_1; PUSH16_UNSAFE(arg); break; case 0xd5: /* CMP Dloc,x */ GET_DLOC_X_RD(); CMP_INST(); break; case 0xd6: /* DEC Dloc,x */ GET_DLOC_X_RD_RMW(); DEC_INST(1); break; case 0xd7: /* CMP [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); CMP_INST(); break; case 0xd8: /* CLD */ psr = psr & (~0x8); INC_KPC_1; break; case 0xd9: /* CMP abs,y */ GET_ABS_Y_RD(); CMP_INST(); break; case 0xda: /* PHX */ INC_KPC_1; if(psr & 0x10) { PUSH8(xreg); } else { PUSH16(xreg); } break; case 0xdb: /* STP */ FINISH(RET_STP, 0); break; case 0xdc: /* JML (Abs) */ GET_2BYTE_ARG; CYCLES_PLUS_1; GET_MEMORY24(arg, kpc, 1); break; case 0xdd: /* CMP Abs,X */ GET_ABS_X_RD(); CMP_INST(); break; case 0xde: /* DEC Abs,X */ GET_ABS_X_RD_RMW(); DEC_INST(0); break; case 0xdf: /* CMP Long,X */ GET_LONG_X_RD(); CMP_INST(); break; case 0xe0: /* CPX #imm */ C_CPX_IMM(); break; case 0xe1: /* SBC (Dloc,X) */ GET_DLOC_X_IND_RD(); SBC_INST(); break; case 0xe2: /* SEP #imm */ GET_1BYTE_ARG; tmp2 = psr; CYCLES_PLUS_1; INC_KPC_2; psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); psr = psr | (arg & 0xff); zero = !(psr & 2); neg7 = psr; UPDATE_PSR(psr, tmp2); break; case 0xe3: /* SBC Disp8,S */ GET_DISP8_S_RD(); SBC_INST(); break; case 0xe4: /* CPX Dloc */ C_CPX_DLOC(); break; case 0xe5: /* SBC Dloc */ GET_DLOC_RD(); SBC_INST(); break; case 0xe6: /* INC Dloc */ GET_DLOC_RD_RMW(); INC_INST(1); break; case 0xe7: /* SBC [Dloc] */ GET_DLOC_L_IND_RD(); SBC_INST(); break; case 0xe8: /* INX */ INC_KPC_1; SET_INDEX_REG(xreg + 1, xreg); break; case 0xe9: /* SBC #imm */ GET_IMM_MEM(); SBC_INST(); break; case 0xea: /* NOP */ INC_KPC_1; break; case 0xeb: /* XBA */ tmp1 = acc & 0xff; CYCLES_PLUS_1 acc = (tmp1 << 8) + (acc >> 8); INC_KPC_1; SET_NEG_ZERO8(acc & 0xff); break; case 0xec: /* CPX abs */ C_CPX_ABS(); break; case 0xed: /* SBC abs */ GET_ABS_RD(); SBC_INST(); break; case 0xee: /* INC abs */ GET_ABS_RD_RMW(); INC_INST(0); break; case 0xef: /* SBC long */ GET_LONG_RD(); SBC_INST(); break; case 0xf0: /* BEQ disp8 */ BRANCH_DISP8(zero == 0); break; case 0xf1: /* SBC (Dloc),y */ GET_DLOC_IND_Y_RD(); SBC_INST(); break; case 0xf2: /* SBC (Dloc) */ GET_DLOC_IND_RD(); SBC_INST(); break; case 0xf3: /* SBC (Disp8,s),y */ GET_DISP8_S_IND_Y_RD(); SBC_INST(); break; case 0xf4: /* PEA Abs */ GET_2BYTE_ARG; CYCLES_PLUS_1; INC_KPC_3; PUSH16_UNSAFE(arg); break; case 0xf5: /* SBC Dloc,x */ GET_DLOC_X_RD(); SBC_INST(); break; case 0xf6: /* INC Dloc,x */ GET_DLOC_X_RD_RMW(); INC_INST(1); break; case 0xf7: /* SBC [Dloc],Y */ GET_DLOC_L_IND_Y_RD(); SBC_INST(); break; case 0xf8: /* SED */ INC_KPC_1; psr |= 0x8; break; case 0xf9: /* SBC abs,y */ GET_ABS_Y_RD(); SBC_INST(); break; case 0xfa: /* PLX */ INC_KPC_1; CYCLES_PLUS_1; if(psr & 0x10) { PULL8(xreg); SET_NEG_ZERO8(xreg); } else { PULL16(xreg); SET_NEG_ZERO16(xreg); } break; case 0xfb: /* XCE */ tmp2 = psr; INC_KPC_1; psr = (tmp2 & 0xfe) | ((tmp2 & 1) << 8) | ((tmp2 >> 8) & 1); UPDATE_PSR(psr, tmp2); break; case 0xfc: /* JSR (Abs,X) */ GET_2BYTE_ARG; INC_KPC_2; tmp1 = kpc; arg = (kpc & 0xff0000) + ((arg + xreg) & 0xffff); GET_MEMORY16(arg, tmp2, 1); kpc = (kpc & 0xff0000) + tmp2; CYCLES_PLUS_2 PUSH16_UNSAFE(tmp1); break; case 0xfd: /* SBC Abs,X */ GET_ABS_X_RD(); SBC_INST(); break; case 0xfe: /* INC Abs,X */ GET_ABS_X_RD_RMW(); INC_INST(0); break; case 0xff: /* SBC Long,X */ GET_LONG_X_RD(); SBC_INST(); break; ================================================ FILE: upstream/kegs/src/iwm.c ================================================ const char rcsid_iwm_c[] = "@(#)$KmKId: iwm.c,v 1.207 2023-09-26 02:59:52+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Information from Beneath Apple DOS, Apple IIgs HW reference, Apple IIgs // Firmware reference, and Inside Macintosh Vol 3 (for status35 info). // Neil Parker wrote about the Apple 3.5 drive using IWM at // http://nparker.llx.com/a2/disk and it is very useful. // http://nparker.llx.com/a2/sethook lists hooks for IWM routines, from // $e1/0c00 - 0fab. // ff/5fa4 is STAT35. ff/5fae is CONT35 // When testing DOS3.3, set bp at $b942, which is bad sector detected // IWM mode register: 7:reserved; 6:5: 0; 4: 1=8MHz,0=7MHz (should always be 0); // 3: bit-cells are 2usec, 2: 1sec timer off; 1: async handshake (writes) // 0: latch mode enabled for reads (holds data for 8 bit times) // dsk->fd >= 0 indicates a valid disk. dsk->raw_data != 0 indicates this is // a compressed disk mounted read-only, and ignore dsk->fd (which will always // be 0, to indicate a valid disk). // fbit_pos encodes head position on track in increments of 1/64 usecs for 5.25" // { byte_offset, bit[2:0], sub_bit[8:0] }, so fbit_pos >> 12 gives byte offset // fbit_mult turns dfcyc into fbit_pos, where (512/fbit_mult) = the bit cell // 5.25" bit cell of 4usec has fbit_mult=128; cell of 3.75usec has mult=136. // For 3.5", fbit_mult=256 indicates a 2usec bit cell. // https://support.apple.com/kb/TA39910?locale=en_US&viewlocale=en_US gives // the RPMs of 800KB disks #include "defc.h" int g_halt_arm_move = 0; extern int Verbose; extern word32 g_vbl_count; extern word32 g_c036_val_speed; extern int g_config_kegs_update_needed; extern Engine_reg engine; const byte phys_to_dos_sec[] = { 0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04, 0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f }; const byte phys_to_prodos_sec[] = { 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b, 0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f }; const byte to_disk_byte[] = { 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, /* 0x10 */ 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, /* 0x20 */ 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, /* 0x30 */ 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; int g_track_bytes_35[] = { 0x200*12, 0x200*11, 0x200*10, 0x200*9, 0x200*8 }; int g_track_bits_35[] = { // Do 1.001 * (1020484 * (60/rpm)) / 2 usec = bits per track 77779, // Trks 0-15: 394 rpm 71433, // Trks 16-31: 429 rpm 64926, // Trks 32-47: 472 rpm 58371, // Trks 48-63: 525 rpm 51940 // Trks 64-79: 590 rpm }; int g_fast_disk_emul_en = 1; int g_fast_disk_emul = 0; int g_slow_525_emul_wr = 0; dword64 g_dfcyc_end_emul_wr = 0; int g_fast_disk_unnib = 0; int g_iwm_fake_fast = 0; word32 g_from_disk_byte[256]; int g_from_disk_byte_valid = 0; Iwm g_iwm; int g_iwm_motor_on = 0; // g_iwm_motor_on is set when drive turned on, 0 when $c0e8 is touched. // Use this to throttle speed to 1MHz. // g_iwm.motor_on=1 means drive is spinning. g_iwm.motor_off=1 means we're // in the 1-second countdown of g_iwm.motor_off_vbl_count. int g_check_nibblization = 1; void iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525) { int num_tracks; int i; dsk->dfcyc_last_read = 0; dsk->raw_data = 0; dsk->wozinfo_ptr = 0; dsk->dynapro_info_ptr = 0; dsk->name_ptr = 0; dsk->partition_name = 0; dsk->partition_num = -1; dsk->fd = -1; dsk->dynapro_blocks = 0; dsk->raw_dsize = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->smartport = smartport; dsk->disk_525 = disk_525; dsk->drive = drive; dsk->cur_frac_track = 0; dsk->image_type = 0; dsk->vol_num = 254; dsk->write_prot = 1; dsk->write_through_to_unix = 0; dsk->disk_dirty = 0; dsk->just_ejected = 0; dsk->last_phases = 0; dsk->cur_fbit_pos = 0; dsk->fbit_mult = 128; // 128 for 5.25", 256 for 3.5" dsk->cur_track_bits = 0; dsk->raw_bptr_malloc = 0; dsk->cur_trk_ptr = 0; dsk->num_tracks = 0; dsk->trks = 0; if(!smartport) { // 3.5" and 5.25" drives. This is only called at init time, // so one-time malloc can be done now num_tracks = 2*80; // 3.5" needs 160 dsk->trks = (Trk *)malloc(num_tracks * sizeof(Trk)); for(i = 0; i < num_tracks; i++) { dsk->trks[i].raw_bptr = 0; dsk->trks[i].sync_ptr = 0; dsk->trks[i].dunix_pos = 0; dsk->trks[i].unix_len = 0; dsk->trks[i].dirty = 0; dsk->trks[i].track_bits = 0; } // num_tracks != 0 indicates a valid disk, do not set it here } } void iwm_init() { word32 val; int i; memset(&g_iwm, 0, sizeof(g_iwm)); for(i = 0; i < 2; i++) { iwm_init_drive(&(g_iwm.drive525[i]), 0, i, 1); iwm_init_drive(&(g_iwm.drive35[i]), 0, i, 0); } for(i = 0; i < MAX_C7_DISKS; i++) { iwm_init_drive(&(g_iwm.smartport[i]), 1, i, 0); } if(g_from_disk_byte_valid == 0) { for(i = 0; i < 256; i++) { g_from_disk_byte[i] = 0x100 + i; } for(i = 0; i < 64; i++) { val = to_disk_byte[i]; g_from_disk_byte[val] = i; } g_from_disk_byte_valid = 1; } else { halt_printf("iwm_init called twice!\n"); } } void iwm_reset() { int i; g_iwm.state = 0; g_iwm.motor_off_vbl_count = 0; g_iwm.forced_sync_bit = 32*12345; g_iwm.last_rd_bit = 32*12345; g_iwm.write_val = 0; for(i = 0; i < 5; i++) { g_iwm.wr_last_bit[i] = 0; g_iwm.wr_qtr_track[i] = 0; g_iwm.wr_num_bits[i] = 0; g_iwm.wr_prior_num_bits[i] = 0; g_iwm.wr_delta[i] = 0; } g_iwm.num_active_writes = 0; g_iwm_motor_on = 0; } void disk_set_num_tracks(Disk *dsk, int num_tracks) { if(num_tracks <= 2*80) { dsk->num_tracks = num_tracks; } else { halt_printf("num_tracks out of range: %d\n", num_tracks); } } word32 iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk) { word32 track_bits, extra; extra = (qtr_trk + (qtr_trk >> 5)) & 0xf; // up to 15 extra bits if(dsk->disk_525) { track_bits = NIB_LEN_525 * 8; // 0x18f2 = 51088 } else { track_bits = g_track_bits_35[qtr_trk >> 5]; } return track_bits + extra; } void draw_iwm_status(int line, char *buf) { char *flag[2][2]; int apple35_sel, drive_select; flag[0][0] = " "; flag[0][1] = " "; flag[1][0] = " "; flag[1][1] = " "; apple35_sel = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; drive_select = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; if(g_iwm.state & IWM_STATE_MOTOR_ON) { flag[apple35_sel][drive_select] = "*"; } sprintf(buf, "s6d1:%2d%s s6d2:%2d%s s5d1:%2d/%d%s " "s5d2:%2d/%d%s fast_disk_emul:%d,%d c036:%02x", g_iwm.drive525[0].cur_frac_track >> 18, flag[0][0], g_iwm.drive525[1].cur_frac_track >> 18, flag[0][1], g_iwm.drive35[0].cur_frac_track >> 17, (g_iwm.drive35[0].cur_frac_track >> 16) & 1, flag[1][0], g_iwm.drive35[1].cur_frac_track >> 17, (g_iwm.drive35[1].cur_frac_track >> 16) & 1, flag[1][1], g_fast_disk_emul, g_slow_525_emul_wr, g_c036_val_speed); video_update_status_line(line, buf); } void iwm_flush_cur_disk() { Disk *dsk; dsk = iwm_get_dsk(g_iwm.state); iwm_flush_disk_to_unix(dsk); } void iwm_flush_disk_to_unix(Disk *dsk) { byte buffer[0x4000]; int ret, did_write; int i; if((dsk->disk_dirty == 0) || (dsk->write_through_to_unix == 0)) { return; } if((dsk->raw_data != 0) && !dsk->dynapro_info_ptr) { return; } printf("Writing disk %s to Unix\n", dsk->name_ptr); for(i = 0; i < 160; i++) { ret = iwm_track_to_unix(dsk, i, &(buffer[0])); if((ret != 1) && (ret != 0)) { printf("iwm_flush_disk_to_unix ret: %d, cannot write " "image to unix, qtrk:%04x\n", ret, i); halt_printf("Converting to WOZ format!\n"); woz_reparse_woz(dsk); return; // Don't clear dsk->disk_dirty } if(ret == 1) { did_write = 1; } } if(dsk->wozinfo_ptr && did_write) { woz_check_file(dsk); } dsk->disk_dirty = 0; } /* Check for dirty disk 3 times a second */ word32 g_iwm_dynapro_last_vbl_count = 0; void iwm_vbl_update() { word32 state; int i; state = g_iwm.state; if((state & IWM_STATE_MOTOR_ON) && (state & IWM_STATE_MOTOR_OFF)) { if(g_iwm.motor_off_vbl_count <= g_vbl_count) { printf("Disk timer expired, drive off: %08x\n", g_vbl_count); iwm_flush_cur_disk(); g_iwm.state = state & (~(IWM_STATE_MOTOR_ON | IWM_STATE_MOTOR_OFF)); g_iwm.drive525[0].dfcyc_last_phases = 0; g_iwm.drive525[1].dfcyc_last_phases = 0; } } if((g_vbl_count - g_iwm_dynapro_last_vbl_count) >= 60) { for(i = 0; i < 2; i++) { dynapro_try_fix_damaged_disk(&(g_iwm.drive525[i])); dynapro_try_fix_damaged_disk(&(g_iwm.drive35[i])); } for(i = 0; i < MAX_C7_DISKS; i++) { dynapro_try_fix_damaged_disk(&(g_iwm.smartport[i])); } g_iwm_dynapro_last_vbl_count = g_vbl_count; } } void iwm_update_fast_disk_emul(int fast_disk_emul_en) { if(g_iwm_motor_on == 0) { g_fast_disk_emul = fast_disk_emul_en; } } void iwm_show_stats(int slot_drive) { Trk *trkptr; Disk *dsk; int slot, drive; int i; dbg_printf("IWM state: %07x (slot_drive:%d)\n", g_iwm.state, slot_drive); dbg_printf("g_iwm.drive525[0].fd: %d, [1].fd: %d\n", g_iwm.drive525[0].fd, g_iwm.drive525[1].fd); dbg_printf("g_iwm.drive525[0].last_phases: %d, [1].last_phases: %d\n", g_iwm.drive525[0].last_phases, g_iwm.drive525[1].last_phases); dbg_printf("g_iwm.write_val:%02x, wr_num_bits[0]:%d, last_rd_bit:" "%06x, forced_sync_bit:%06x\n", g_iwm.write_val, g_iwm.wr_num_bits[0], g_iwm.last_rd_bit, g_iwm.forced_sync_bit); if(slot_drive < 0) { return; } drive = slot_drive & 1; slot = 5 + ((slot_drive >> 1) & 1); dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->trks == 0) { return; } for(i = 0; i < 160; i++) { trkptr = &(dsk->trks[i]); printf("Qtrk:%02x: bits:%05x dunix_pos:%08llx unix_len:%06x, " "d:%d %p,%p\n", i, trkptr->track_bits, trkptr->dunix_pos, trkptr->unix_len, trkptr->dirty, trkptr->raw_bptr, trkptr->sync_ptr); } } Disk * iwm_get_dsk(word32 state) { Disk *dsk; int drive; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; if(state & IWM_STATE_C031_APPLE35SEL) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); } return dsk; } Disk * iwm_touch_switches(int loc, dword64 dfcyc) { Disk *dsk; dword64 dadd, dcycs_passed; word32 track_bits, fbit_pos, forced_sync_bit, mask, mask_on, state; int phase, on, motor_on; if(g_iwm.state & IWM_STATE_RESET) { iwm_printf("IWM under reset: %06x\n", g_iwm.state); } dsk = iwm_get_dsk(g_iwm.state); track_bits = dsk->cur_track_bits; fbit_pos = track_bits * 4096; // Set to an illegal value motor_on = g_iwm.state & IWM_STATE_MOTOR_ON; if(motor_on) { dcycs_passed = (dfcyc - dsk->dfcyc_last_read) >> 16; dsk->dfcyc_last_read = dfcyc; // track_bits can be 0, indicating no valid data dadd = dcycs_passed * dsk->fbit_mult; if(track_bits) { if(dadd >= (track_bits * 512)) { dadd = dadd % (track_bits * 512); } } fbit_pos = dsk->cur_fbit_pos + (word32)dadd; if(fbit_pos >= (track_bits * 512)) { fbit_pos -= (track_bits * 512); } if(g_slow_525_emul_wr || (g_iwm.state & IWM_STATE_Q7) || !g_fast_disk_emul || !track_bits) { dsk->cur_fbit_pos = fbit_pos; } else { fbit_pos = track_bits << 12; // out of range value } #if 0 printf("dsk->cur_fbit:%08x, fbit_pos:%08x\n", dsk->cur_fbit_pos, fbit_pos); #endif } dbg_log_info(dfcyc, dsk->cur_fbit_pos, ((loc & 0xff) << 24) | g_iwm.state, ((dsk->cur_frac_track + 0x8000) & 0x1ff0000) | 0xe0); if(loc < 0) { // Not a real access, just a way to update fbit_pos return dsk; } if(dsk->dfcyc_last_phases) { iwm525_update_head(dsk, dfcyc); } on = loc & 1; phase = loc >> 1; state = g_iwm.state; if(loc < 8) { /* phase adjustments. See if motor is on */ mask = (1 << (phase & 3)) << IWM_BIT_PHASES; mask_on = (on << (phase & 3)) << IWM_BIT_PHASES; state = (state & (~mask)) | mask_on; g_iwm.state = state; iwm_printf("Iwm phase %d=%d, all phases: %06x (%016llx)\n", phase, on, state, dfcyc); if(state & IWM_STATE_MOTOR_ON) { if(state & IWM_STATE_C031_APPLE35SEL) { if((phase == 3) && on) { iwm_do_action35(dfcyc); } } else { // Move apple525 head iwm525_update_phases(dsk, dfcyc); } } state = g_iwm.state; /* See if enable or reset is asserted */ if(((state >> IWM_BIT_PHASES) & 5) == 5) { // Ph 0, 2 set state |= IWM_STATE_RESET; iwm_printf("IWM reset active\n"); } else { state &= (~IWM_STATE_RESET); } if(((state >> IWM_BIT_PHASES) & 0xa) == 0xa) { // Ph 1, 3 set state |= IWM_STATE_ENABLE2; iwm_printf("IWM ENABLE2 active\n"); } else { state &= (~IWM_STATE_ENABLE2); } g_iwm.state = state; } else { /* loc >= 8 */ switch(loc) { case 0x8: iwm_printf("Turning IWM motor off!\n"); if(g_iwm.state & 0x04) { // iwm MODE /* Turn off immediately */ state &= (~(IWM_STATE_MOTOR_ON | IWM_STATE_MOTOR_OFF)); } else { /* 1 second delay */ if((state & IWM_STATE_MOTOR_ON) && !(state & IWM_STATE_MOTOR_OFF)){ state |= IWM_STATE_MOTOR_OFF; g_iwm.motor_off_vbl_count = g_vbl_count + 60; } } g_iwm.state = state; #if 0 printf("IWM motor off, g_iwm_motor_on:%d, slow:%d\n", g_iwm_motor_on, g_slow_525_emul_wr); #endif if(g_iwm_motor_on || g_slow_525_emul_wr) { /* recalc current speed */ g_fast_disk_emul = g_fast_disk_emul_en; engine_recalc_events(); iwm_flush_cur_disk(); } g_iwm_motor_on = 0; g_slow_525_emul_wr = 0; break; case 0x9: iwm_printf("Turning IWM motor on!\n"); state |= IWM_STATE_MOTOR_ON; state &= (~IWM_STATE_MOTOR_OFF); state &= (~IWM_STATE_LAST_SEL35); state |= ((state >> 6) & 1) << IWM_BIT_LAST_SEL35; g_iwm.state = state; dsk->dfcyc_last_read = dfcyc; if(g_iwm_motor_on == 0) { /* recalc current speed */ if(dsk->wozinfo_ptr) { g_fast_disk_emul = 0; } engine_recalc_events(); } g_iwm_motor_on = 1; break; case 0xa: case 0xb: if(((state >> IWM_BIT_DRIVE_SEL) & 1) != on) { iwm_flush_cur_disk(); } state &= (~IWM_STATE_DRIVE_SEL); state |= (on << IWM_BIT_DRIVE_SEL); g_iwm.state = state; dsk = iwm_get_dsk(state); if(dsk->wozinfo_ptr && g_iwm_motor_on) { g_fast_disk_emul = 0; } else { g_fast_disk_emul = g_fast_disk_emul_en; } break; case 0xc: case 0xd: mask = IWM_STATE_Q6 | IWM_STATE_Q7 | IWM_STATE_MOTOR_ON | IWM_STATE_ENABLE2; mask_on = IWM_STATE_Q6 | IWM_STATE_MOTOR_ON; if(((state & mask) == mask_on) && !on) { // q6 on, q7 off, !on, motor_on, !enable2 // Switched from $c08d to $c08c, resync now // If latch mode, back up one more bit // (for The School Speller - Tools.woz) forced_sync_bit = iwm_calc_bit_sum( fbit_pos >> 9, 0 - (state & 1), track_bits); g_iwm.forced_sync_bit = forced_sync_bit; g_iwm.last_rd_bit = forced_sync_bit; dbg_log_info(dfcyc, forced_sync_bit*2, 0, 0x500ec); } state &= (~IWM_STATE_Q6); state |= (on << IWM_BIT_Q6); g_iwm.state = state; break; case 0xe: case 0xf: mask = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON | IWM_STATE_ENABLE2; mask_on = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON; if(((state & mask) == mask_on) && !on) { // q7, !on, motor_on, !enable2 #if 0 printf("state:%04x and new q7:%d, write_end\n", state, on); #endif dbg_log_info(dfcyc, state, mask, 0xed); iwm_write_end(dsk, 1, dfcyc); // printf("write end complete, the track:\n"); // iwm_check_nibblization(dfcyc); } state &= (~IWM_STATE_Q7); state |= (on << IWM_BIT_Q7); g_iwm.state = state; break; default: printf("iwm_touch_switches: loc: %02x unknown!\n", loc); exit(2); } } if(!(state & IWM_STATE_Q7)) { g_iwm.num_active_writes = 0; if(g_slow_525_emul_wr) { g_slow_525_emul_wr = 0; engine_recalc_events(); } } return dsk; } void iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc) { Trk *trkptr; word32 track_bits, cur_frac_track, wr_last_bit, write_val; int disk_525, drive, new_track, cur_track, max_track; int num_active_writes; if(dsk->smartport) { return; } cur_frac_track = dsk->cur_frac_track; if(delta != 0) { // 3.5" move, clear out lower fractional track bits new_frac_track = new_frac_track & (-0x10000); if((delta < 0) && (new_frac_track < (word32)(-delta))) { // Moving down past track 0...stop, but preserve side new_frac_track = cur_frac_track & 0x10000; delta = 0; } } new_frac_track = new_frac_track + delta; disk_525 = dsk->disk_525; #if 0 printf("iwm_move_to_track: %07x, num_tracks:%03x (cur:%07x) %016llx\n", new_frac_track, dsk->num_tracks, dsk->cur_frac_track, dfcyc); #endif max_track = 36*4 - 1; // Go to track 35.75 on 5.25" if(dsk->num_tracks >= (max_track + 1)) { max_track = dsk->num_tracks - 1; } if(max_track >= 159) { max_track = 159; // Limit to 159 always } if(new_frac_track > (word32)(max_track << 16)) { new_frac_track = max_track << 16; } new_track = (new_frac_track + 0x8000) >> 16; cur_track = (cur_frac_track + 0x8000) >> 16; num_active_writes = g_iwm.num_active_writes; wr_last_bit = g_iwm.wr_last_bit[0]; write_val = g_iwm.write_val; if(num_active_writes) { iwm_write_end(dsk, 0, dfcyc); printf("moving arm to new_track:%d, write active:%d, bit:%08x, " "write_val:%02x\n", new_track, num_active_writes, wr_last_bit, write_val); } if((cur_track != new_track) || (dsk->cur_trk_ptr == 0)) { drive = dsk->drive + 1; if(1) { // Don't do this printf } else if(disk_525) { printf("s6d%d Track: %d.%02d\n", drive, new_track >> 2, 25* (new_track & 3)); } else { printf("s5d%d Track: %d Side: %d\n", drive, new_track >> 1, new_track & 1); } trkptr = &(dsk->trks[new_track]); track_bits = trkptr->track_bits; if((track_bits == 0) && disk_525) { // Use an adjacent .25 track if it is valid if((new_track > 0) && (trkptr[-1].track_bits != 0)) { new_track--; // printf("Moved dn to qtrk:%04x\n", new_track); } else if((new_track < max_track) && (trkptr[1].track_bits != 0)) { new_track++; // printf("Moved up to qtrk:%04x\n", new_track); } } iwm_flush_disk_to_unix(dsk); iwm_move_to_qtr_track(dsk, new_track); if(dfcyc != 0) { dbg_log_info(dfcyc, cur_frac_track, new_frac_track, (g_iwm.state << 16) | 0x00f000e1); #if 0 printf("Just moved from track %08x to %08x %016llx\n", cur_frac_track, new_frac_track, dfcyc); #endif } } dsk->cur_frac_track = new_frac_track; if(num_active_writes) { iwm_start_write(dsk, wr_last_bit, write_val, 1); } } void iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track) { Trk *trkptr; word32 track_bits, fbit_pos; trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; dsk->cur_trk_ptr = trkptr; dsk->cur_track_bits = track_bits; fbit_pos = dsk->cur_fbit_pos; if(track_bits) { // Moving to a valid track. Ensure fbit_pos in range fbit_pos = fbit_pos % (track_bits * 512); } dsk->cur_fbit_pos = fbit_pos; } dword64 g_iwm_last_phase_dfcyc = 0; void iwm525_update_phases(Disk *dsk, dword64 dfcyc) { word32 ign_mask; int last_phases, new_phases, eff_last_phases, eff_new_phases; int my_phase; // Decide if dsk->last_phases needs to change. last_phases = dsk->last_phases; new_phases = (g_iwm.state >> IWM_BIT_PHASES) & 0xf; if(last_phases != new_phases) { iwm_printf("Phases changing %02x -> %02x, ftrack:%08x at %lld, " "diff:%.2fmsec\n", last_phases, new_phases, dsk->cur_frac_track, dfcyc >> 16, ((dfcyc - g_iwm_last_phase_dfcyc) >> 16) / 1000.0); g_iwm_last_phase_dfcyc = dfcyc; } my_phase = (dsk->cur_frac_track >> 17) & 3; ign_mask = 1 << ((2 + my_phase) & 3); eff_last_phases = last_phases & (~ign_mask); eff_new_phases = new_phases & (~ign_mask); if(eff_last_phases == eff_new_phases) { dsk->last_phases = new_phases; return; // Nothing to do } // Update last_phases iwm525_update_head(dsk, dfcyc); dsk->last_phases = new_phases; dsk->dfcyc_last_phases = dfcyc; dbg_log_info(dfcyc, new_phases, dsk->cur_frac_track, 0x100e1); } void iwm525_update_head(Disk *dsk, dword64 dfcyc) { double dinc; dword64 diff_dusec, dfcyc_last_phases; word32 cur_frac_track, frac_track, sum, num, phases, diff; int new_qtrk, cur_qtrk, my_phase, prev_phase, grind, inc; int i; cur_frac_track = dsk->cur_frac_track; my_phase = (dsk->cur_frac_track >> 17) & 3; // If my_phase is 1, then phase 0 being on should decrement the trk // number, and phase 2 being on should increment the trk number phases = dsk->last_phases & 0xf; // one bit for each phase phases = (phases << 4) | phases; prev_phase = (my_phase + 4 - 1) & 3; phases = (phases >> prev_phase) & 0xf; // Now, phases[0] means decrement trk, phases[1] is where we are, // phases[2] means increment trk, and phases[3] MIGHT mean increment sum = 0; num = 0; grind = 0; for(i = 0; i < 4; i++) { if(((phases >> i) & 1) == 0) { continue; } frac_track = cur_frac_track & -0x20000; switch(i) { case 0: // Previous phase is on, decrement trk if(frac_track >= 0x20000) { frac_track -= 0x20000; } else { frac_track = 0; } sum += frac_track; num++; if(cur_frac_track == 0) { grind++; } break; case 1: // "our" phase, pull to aligned track sum += frac_track; num++; break; case 2: // Next phase, increment track sum += frac_track + 0x20000; num++; break; case 3: // Next next phase: might pull // Phase is nominally 2 away...but if cur_frac_track // is just a little less than a new halftrack, // (0xxx1ffff), then it may be within a half track // and we'll let it pull us frac_track += 0x20000; if((frac_track - cur_frac_track) < 0x30000) { sum += frac_track; num++; } } } frac_track = cur_frac_track; dfcyc_last_phases = dsk->dfcyc_last_phases; dsk->dfcyc_last_phases = 0; if(num) { frac_track = sum / num; // New desired track // Now see if enough time has elapsed that we got to frac_track diff_dusec = (dfcyc - dfcyc_last_phases) >> 16; dinc = 0x20000 / 2800.0; // 2.8msec to move one phase inc = (int)dinc; if(cur_frac_track >= frac_track) { diff = cur_frac_track - frac_track; inc = -inc; } else { diff = frac_track - cur_frac_track; } if(g_fast_disk_emul) { diff = 0; // Always moved enough } // Add inc*diff_dusec to cur_frac_track, unless we already reach if(diff > (dinc * diff_dusec)) { // Enough time has NOT elapsed, so calc where head is // Update frac_track to be cur_frac_track + inc * dusec frac_track = cur_frac_track + (word32)(inc * diff_dusec); dsk->dfcyc_last_phases = dfcyc; } } new_qtrk = (frac_track + 0x8000) >> 16; cur_qtrk = (cur_frac_track + 0x8000) >> 16; if(new_qtrk != cur_qtrk) { if(g_halt_arm_move) { halt_printf("Halt on arm move\n"); g_halt_arm_move = 0; } if(grind) { printf("GRIND GRIND GRIND\n"); } dbg_log_info(dfcyc, frac_track, dsk->cur_frac_track, (phases << 24) | 0x00e1); iwm_move_to_ftrack(dsk, frac_track, 0, dfcyc); if(new_qtrk > 2) { #if 0 printf("Moving to qtr track: %04x (trk:%d.%02d), %d, " "%02x, %08x dfcyc:%lld\n", new_qtrk, new_qtrk >> 2, 25*(new_qtrk & 3), my_phase, phases, g_iwm.state, dfcyc >> 16); #endif } } else { // On the same qtr_track, but update the fraction dsk->cur_frac_track = frac_track; } #if 0 /* sanity check stepping algorithm */ if((qtr_track & 7) == 0) { /* check for just access phase 0 */ if(last_phase != 0) { halt_printf("last_phase: %d!\n", last_phase); } } #endif } int iwm_read_status35(dword64 dfcyc) { Disk *dsk; word32 state; int drive, stat35, tmp; // This is usually done by STAT35 at ff/5fa4 // This code is called on the rising edge of Q6 asserted state = g_iwm.state; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; dsk = &(g_iwm.drive35[drive]); if(state & IWM_STATE_MOTOR_ON) { /* Read status: ph[1], ph[0], disk35, ph[2] */ stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | ((state >> 6) & 2) | (((state >> IWM_BIT_PHASES) >> 2) & 1); iwm_printf("Iwm status read state: %02x\n", state); dbg_log_info(dfcyc, state, stat35, 0xe7); switch(stat35) { case 0x00: /* step direction */ return (state >> IWM_BIT_STEP_DIRECTION35) & 1; break; case 0x01: /* lower head activate */ /* also return instantaneous data from head */ iwm_move_to_ftrack(dsk, (dsk->cur_frac_track & (-0x20000)), 0, dfcyc); return (dsk->cur_fbit_pos >> 15) & 1; break; case 0x02: /* disk in place */ /* 1 = no disk, 0 = disk */ iwm_printf("read disk in place, num_tracks: %d\n", dsk->num_tracks); tmp = (dsk->num_tracks <= 0); dbg_log_info(dfcyc, 0, dsk->num_tracks, 0x100e7); return tmp; break; case 0x03: /* upper head activate */ /* also return instantaneous data from head */ iwm_move_to_ftrack(dsk, dsk->cur_frac_track | 0x10000, 0, dfcyc); return (dsk->cur_fbit_pos >> 15) & 1; break; case 0x04: /* disk is stepping? */ /* 1 = not stepping, 0 = stepping */ return 1; break; case 0x05: /* Unknown function of ROM 03? */ /* 1 = or $20 into 0xe1/f24+drive, 0 = don't */ return 1; break; case 0x06: /* disk is locked */ /* 0 = locked, 1 = unlocked */ return (!dsk->write_prot); break; case 0x08: /* motor on */ /* 0 = on, 1 = off */ return !(state & IWM_STATE_MOTOR_ON35); break; case 0x09: /* number of sides */ /* 1 = 2 sides, 0 = 1 side */ return 1; break; case 0x0a: /* at track 0 */ /* 1 = not at track 0, 0 = there */ tmp = (dsk->cur_frac_track != 0); iwm_printf("Read at track0_35: %d\n", tmp); return tmp; break; case 0x0b: /* disk ready??? */ /* 0 = ready, 1 = not ready? */ tmp = !(state & IWM_STATE_MOTOR_ON35); iwm_printf("Read disk ready, ret: %d\n", tmp); return tmp; break; case 0x0c: /* disk switched?? */ /* 0 = not switched, 1 = switched? */ tmp = (dsk->just_ejected != 0); iwm_printf("Read disk switched: %d\n", tmp); return tmp; break; case 0x0d: /* false read when ejecting disk */ return 1; case 0x0e: /* tachometer */ halt_printf("Reading tachometer!\n"); return (dsk->cur_fbit_pos >> 11) & 1; break; case 0x0f: /* drive installed? */ /* 0 = drive exists, 1 = no drive */ if(drive) { /* pretend no drive 1 */ return 1; } return 0; break; default: halt_printf("Read 3.5 status, stat35: %02x\n", stat35); return 1; } } else { iwm_printf("Read 3.5 status with drive off!\n"); return 1; } } void iwm_do_action35(dword64 dfcyc) { Disk *dsk; word32 state; int drive, stat35; // Actions done by CONT35 routine at ff/5fae // This code is called on the rising edge of phase[3] asserted state = g_iwm.state; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; dsk = &(g_iwm.drive35[drive]); if(state & IWM_STATE_MOTOR_ON) { /* Perform action */ /* stat35: ph[1], ph[0], disk35_ctrl, ph[2] */ stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | ((state >> 6) & 2) | (((state >> IWM_BIT_PHASES) >> 2) & 1); dbg_log_info(dfcyc, state, stat35, 0xf00e7); switch(stat35) { case 0x00: /* Set step direction inward (higher tracks) */ /* towards higher tracks, clear STEP_DIRECTION35 */ state &= (~IWM_STATE_STEP_DIRECTION35); iwm_printf("Iwm set step dir35 = 0\n"); break; case 0x01: /* Set step direction outward (lower tracks) */ /* towards lower tracks */ state |= IWM_STATE_STEP_DIRECTION35; dbg_log_info(dfcyc, state, stat35, 0x300e7); iwm_printf("Iwm set step dir35 = 1\n"); break; case 0x03: /* reset disk-switched flag? */ iwm_printf("Iwm reset disk switch\n"); dsk->just_ejected = 0; break; case 0x04: /* step disk */ if(state & IWM_STATE_STEP_DIRECTION35) { iwm_move_to_ftrack(dsk, dsk->cur_frac_track, -0x20000, dfcyc); } else { iwm_move_to_ftrack(dsk, dsk->cur_frac_track, 0x20000, dfcyc); } break; case 0x08: /* turn motor on */ iwm_printf("Iwm set motor_on35 = 1\n"); state |= IWM_STATE_MOTOR_ON35; break; case 0x09: /* turn motor off */ iwm_printf("Iwm set motor_on35 = 0\n"); state &= (~IWM_STATE_MOTOR_ON35); break; case 0x0d: /* eject disk */ printf("Action 0x0d, will eject disk\n"); iwm_eject_disk(dsk); break; case 0x02: case 0x07: case 0x0b: /* hacks to allow AE 1.6MB driver to not crash me */ break; default: halt_printf("Do 3.5 action, state: %02x\n", state); return; } } else { halt_printf("Set 3.5 status with drive off!\n"); return; } g_iwm.state = state; dbg_log_info(dfcyc, state, stat35, 0x400e7); } int read_iwm(int loc, dword64 dfcyc) { Disk *dsk; word32 status, bit_diff, bit_pos, state; int on, q7_q6, val; loc = loc & 0xf; on = loc & 1; dsk = iwm_touch_switches(loc, dfcyc); state = g_iwm.state; q7_q6 = (state >> IWM_BIT_Q6) & 3; if(on) { /* odd address, return 0 */ return 0; } else { /* even address */ switch(q7_q6) { case 0x00: /* q7 = 0, q6 = 0 */ if(state & IWM_STATE_ENABLE2) { return iwm_read_enable2(dfcyc); } else { if(state & IWM_STATE_MOTOR_ON) { return iwm_read_data(dsk, dfcyc); } else { iwm_printf("read iwm st 0, m off!\n"); /* HACK!!!! */ return 0xff; //return (((int)dfcyc) & 0x7f) + 0x80; } } break; case 0x01: /* q7 = 0, q6 = 1 */ /* read IWM status reg */ if(state & IWM_STATE_ENABLE2) { iwm_printf("Read status under enable2: 1\n"); status = 1; } else { if(state & IWM_STATE_C031_APPLE35SEL) { status = iwm_read_status35(dfcyc); } else { status = dsk->write_prot; } } val = ((status & 1) << 7) | (state & 0x3f); // bit 5 is motor_on, bits[4:0] are iwm_mode iwm_printf("Read status: %02x\n", val); return val; break; case 0x02: /* q7 = 1, q6 = 0 */ /* read handshake register */ if(state & IWM_STATE_ENABLE2) { return iwm_read_enable2_handshake(dfcyc); } else { status = 0xc0; bit_pos = dsk->cur_fbit_pos >> 9; bit_diff = iwm_calc_bit_diff(bit_pos, g_iwm.wr_last_bit[0], dsk->cur_track_bits); if(bit_diff > 8) { iwm_printf("Write underrun!\n"); status = status & 0xbf; } return status; } break; case 0x03: /* q7 = 1, q6 = 1 */ iwm_printf("read iwm q7_q6=3!\n"); return 0; break; } } halt_printf("Got to end of read_iwm, loc: %02x!\n", loc); return 0; } void write_iwm(int loc, int val, dword64 dfcyc) { Disk *dsk; word32 state; int on, q7_q6, drive, fast_writes; loc = loc & 0xf; on = loc & 1; dsk = iwm_touch_switches(loc, dfcyc); state = g_iwm.state; q7_q6 = (state >> IWM_BIT_Q6) & 3; drive = (state >> IWM_BIT_DRIVE_SEL) & 1; fast_writes = g_fast_disk_emul; if(state & IWM_STATE_C031_APPLE35SEL) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); fast_writes = !g_slow_525_emul_wr && fast_writes; } if(on) { /* odd address, write something */ if(q7_q6 == 3) { /* q7, q6 = 1,1 */ if(state & IWM_STATE_MOTOR_ON) { if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { iwm_write_data(dsk, val, dfcyc); } } else { /* write mode register */ // bit 0: latch mode (should set if async hand) // bit 1: async handshake // bit 2: immediate motor off (no 1 sec delay) // bit 3: 2us bit timing // bit 4: Divide input clock by 8 (instead of 7) val = val & 0x1f; state = (state & (~0x1f)) | val; g_iwm.state = state; if(val & 0x10) { iwm_printf("set iwm_mode:%02x!\n",val); } } } else { if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { #if 0 // Flobynoid writes to 0xc0e9 causing these messages... printf("Write iwm1, st: %02x, loc: %x: %02x\n", q7_q6, loc, val); #endif } } return; } else { /* even address */ if(state & IWM_STATE_ENABLE2) { iwm_write_enable2(val); } else { iwm_printf("Write iwm2, st: %02x, loc: %x: %02x\n", q7_q6, loc, val); } return; } return; } int iwm_read_enable2(dword64 dfcyc) { iwm_printf("Read under enable2 %016llx!\n", dfcyc); return 0xff; } int g_cnt_enable2_handshake = 0; int iwm_read_enable2_handshake(dword64 dfcyc) { int val; iwm_printf("Read handshake under enable2, %016llx!\n", dfcyc); val = 0xc0; g_cnt_enable2_handshake++; if(g_cnt_enable2_handshake > 3) { g_cnt_enable2_handshake = 0; val = 0x80; } return val; } void iwm_write_enable2(int val) { // Smartport is selected (PH3=1, PH1=1, Sel35=0), just ignore this data iwm_printf("Write under enable2: %02x!\n", val); return; } word32 iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc) { double new_fast_cycs; dword64 dfcyc_passed; word32 fbit_pos, fbit_diff, track_bits; // Nox Archaist doesn't finish reading sector header's checksum, but // instead waits for 7 bytes to pass and then writes. This code // tries to allow fast_disk_emul mode to not overwrite the checksum. // Move the fbit_pos forward to try to account for a delay from the // last read to the current write. But accesses to I/O locations // would still take a lot of time. Let's assume there were // 2 slow cycles, and clamp the skip to a min of 1 byte, max 3 bytes. dfcyc_passed = dfcyc - g_iwm.dfcyc_last_fastemul_read; new_fast_cycs = (((double)dfcyc_passed) - 0x20000) / ((double)engine.fplus_ptr->dplus_1); // new_fast_cycs approximates the number of fast cycles that have // passed, so 4.0 means 4 fast cycles have passed iwm_printf("start write, dfcyc:%016llx, new_f_cycs:%f, plus_1:%08llx\n", dfcyc_passed, new_fast_cycs, engine.fplus_ptr->dplus_1); fbit_diff = (word32)(new_fast_cycs * dsk->fbit_mult); if(new_fast_cycs < 0.0) { fbit_diff = 8*512; // 8 bits } else { if(fbit_diff < 8*512) { // 8 bits fbit_diff = 8*512; } else if(fbit_diff > (32*512)) { fbit_diff = 32*512; } } track_bits = dsk->cur_track_bits; fbit_pos = dsk->cur_fbit_pos; if(track_bits == 0) { return fbit_pos; } fbit_pos = fbit_pos + fbit_diff; if(fbit_pos >= (track_bits * 512)) { fbit_pos = fbit_pos - (track_bits * 512); } #if 0 printf(" adjusted fbit_pos from %07x to %07x\n", dsk->cur_fbit_pos, fbit_pos); #endif dbg_log_info(dfcyc, fbit_pos, dsk->cur_fbit_pos, 0xee); dsk->cur_fbit_pos = fbit_pos; return fbit_pos; } word32 iwm_read_data_fast(Disk *dsk, dword64 dfcyc) { dword64 dval, dsync_val_tmp, dsync_val; word32 bit_pos, new_bit_pos, track_bits, val; int msb, dec; if(!g_fast_disk_unnib) { g_iwm.dfcyc_last_fastemul_read = dfcyc; } track_bits = dsk->cur_track_bits; bit_pos = dsk->cur_fbit_pos >> 9; // fbit_pos points just after the LSB of the last nibble. Read +8 // past to get the next nibble. Get more to ensure a valid nibble new_bit_pos = iwm_calc_bit_sum(bit_pos, 15, track_bits); dval = iwm_get_raw_bits(dsk, new_bit_pos, 16, &dsync_val_tmp); dsync_val = dsync_val_tmp; #if 0 printf("fast %010llx syncs:%010llx bit:%07x\n", dval, dsync_val, bit_pos * 2); #endif if(!g_fast_disk_unnib) { //dbg_log_info(dfcyc, dval, dsync_val, 0xeb); } // Find the sync val closest to 15 without going over msb = (dsync_val >> 8) & 0xff; if((msb > 15) || (msb == 0)) { msb = dsync_val & 0xff; } if(msb > 15) { msb = 8; // Just return something } if(msb < 7) { // This can happen when the arm is moved from a long track to a // shorter track, fbit_pos at an arbitrary position at the // track start, placing us at dsync_val=0x1006. Just ignore msb = 7; } val = (word32)(dval >> (msb - 7)); // We've moved new_fbit_pos forward 15 bits, move back 7 bits for msb=15 dec = 15 - msb - 7; if(!g_iwm_motor_on && (msb == 15) && !g_fast_disk_unnib) { // Return this byte properly...but not the next one for the // DOS3.3 RWTS motor on detect code, which reads the drive // without enabling the motor, to see if the motor is on dec--; } if((msb != 15) && !g_fast_disk_unnib) { // Pull a trick to make the disk motor-on test pass ($bd34 in // DOS 3.3 RWTS): if this is a sync byte, don't return whole // byte, but return whole byte next time val = val & 0x7f; dec = dec - 8; } dsk->cur_fbit_pos = iwm_calc_bit_sum(new_bit_pos, dec, track_bits) << 9; #if 0 printf(" val:%02x fbit:%07x new_fbit:%07x dec:%d, msb:%d\n", val & 0xff, dsk->cur_fbit_pos, new_fbit_pos, dec, msb); #endif if(!g_fast_disk_unnib) { dbg_log_info(dfcyc, dsk->cur_fbit_pos, (dec << 24) | (bit_pos * 2), (val << 16) | 0xeb); } return val & 0xff; } dword64 iwm_return_rand_data(Disk *dsk, dword64 dfcyc) { dword64 dval, dval2; if(dsk) { // Use dsk } dval = dfcyc >> 16; dval2 = dval ^ (dval >> 16) ^ (dval << 10) ^ (dval << 26) ^ (dval << 41); dval = dval2 & ((dval >> 6) ^ (dval >> 17)); return dval; } dword64 iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr) { byte *bptr, *sync_ptr; dword64 dval, dtmp_val, sync_dval; word32 track_bits; int pos, bits, total_bits, sync_pos, sync; track_bits = dsk->cur_track_bits; if(track_bits == 0) { halt_printf("iwm_get_raw_bits track_bits 0, %08x\n", dsk->cur_frac_track); return 0; } total_bits = 0; bits = 1 + (bit_pos & 7); // 1..8 pos = bit_pos >> 3; dval = 0; sync_dval = 0; bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); sync_pos = 0; if((bptr == 0) || (sync_ptr == 0)) { halt_printf("bptr:%p, sync:%p, bit_pos:%08x, track_bits:%08x, " "cur_trk_ptr eff:%05lx\n", bptr, sync_ptr, bit_pos, track_bits, dsk->cur_trk_ptr - &(dsk->trks[0])); *dsyncs_ptr = 0xffffffffffffffULL; return 0; } while(total_bits < num_bits) { dtmp_val = bptr[pos]; dtmp_val = dtmp_val >> (8 - bits); dval = dval | (dtmp_val << total_bits); sync = sync_ptr[pos]; if((sync < 8) && (sync >= (8 - bits))) { // MSB is here sync = sync - (8 - bits); sync = sync + total_bits; if((sync_pos < 64) && (sync != (sync_dval & 0xff))){ sync_dval |= ((dword64)sync & 0xff) << sync_pos; sync_pos += 8; } } total_bits += bits; bits = 8; pos--; if(pos < 0) { pos = (track_bits - 1) >> 3; bits = 1 + ((track_bits - 1) & 7); } } if(sync_pos == 0) { sync_dval = 0xff; } *dsyncs_ptr = sync_dval; return dval; } word32 iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits) { word32 val; val = first - last; if(val >= track_bits) { val = val + track_bits; } return val; } word32 iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits) { word32 val; val = bit_pos + add_ival; if(val >= track_bits) { if(add_ival < 0) { val = val + track_bits; } else { val = val - track_bits; } } return val; } dword64 iwm_calc_forced_sync(dword64 dval, int forced_bit) { dword64 sync_dval; // Something like the "E7" protection scheme has toggled // $c0ed in the middle of reading a nibble, causing a new sync. This // is used to "move" sync bits inside disk nibbles, to defeat // nibble copiers (since a 36-cycle sync nibble cannot be // differentiated from a 40-cycle sync nibble in one read pass) // Return these misaligned nibbles until we re-sync (then clear // g_iwm.forced_sync_bit. Toggling $c0ed can set the data latch // to $ff is the disk is write-protected, but this gets cleared // to $00 within 4 CPU cycles always (or less). A phantom 1 will // also shift in dval |= (1ULL << forced_bit); sync_dval = 30; // A default for the previous nibble while(forced_bit >= 0) { // Scan until this bit is set if((dval >> forced_bit) & 1) { // this bit is the MSB of a nibble, note it sync_dval = (sync_dval << 8) | forced_bit; forced_bit -= 7; } forced_bit--; } return sync_dval; } int iwm_calc_forced_sync_0s(dword64 sync_dval, int bits) { int sync, forced_bits, done; int i; // Return number between 7 and bits as to the oldest valid sync bit // in the sync_dval array. 0xff in the first position means no syncs if(bits <= 8) { return bits; } forced_bits = 8; done = 0; if((sync_dval & 0xff) <= 7) { sync_dval = sync_dval >> 8; } for(i = 0; i < 8; i++) { sync = sync_dval & 0xff; if(sync == 0xff) { return bits; } sync_dval = sync_dval >> 8; while(sync > bits) { sync -= 8; // Bring it back into range done = 1; } if(sync < forced_bits) { break; } if(sync < bits) { forced_bits = sync; } if(done) { break; } } return forced_bits; } word32 iwm_read_data(Disk *dsk, dword64 dfcyc) { dword64 dval, sync_dval, dsync_val_tmp, dval0, dval2; word32 track_bits, fbit_pos, bit_pos, val, forced_sync_bit; int msb_bit, bits, forced_bits, diff; track_bits = dsk->cur_track_bits; fbit_pos = dsk->cur_fbit_pos; bit_pos = fbit_pos >> 9; if((track_bits == 0) || (dsk->cur_trk_ptr == 0)) { val = ((fbit_pos * 25) >> 11) & 0xff; iwm_printf("Reading c0ec, track_len 0, returning %02x\n", val); return val; } if(g_fast_disk_emul) { return iwm_read_data_fast(dsk, dfcyc); } // First, get the last few bytes of data bits = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, track_bits) + 10; if(bits < 25) { bits = 25; } else if(bits > 60) { bits = 60; } forced_sync_bit = g_iwm.forced_sync_bit; forced_bits = -1; if(forced_sync_bit < track_bits) { forced_bits = iwm_calc_bit_diff(bit_pos, forced_sync_bit, track_bits); if(forced_bits >= 64) { forced_bits = 63; } if(forced_bits > bits) { bits = forced_bits; } } dval = iwm_get_raw_bits(dsk, bit_pos, bits, &dsync_val_tmp); sync_dval = dsync_val_tmp; // See if there are runs of more than three 0 bits...introduce noise dval0 = (1ULL << bits) | dval; dval0 = dval0 | (dval0 >> 1) | (dval0 >> 2) | (dval0 >> 3); dval0 = (~dval0) & ((1ULL << bits) - 1); if(dval0) { // Each set bit of dval0 indicates the previous 3 bits are 0 dval2 = dval0 | (dval0 << 1); dval2 = dval0 & iwm_return_rand_data(dsk, dfcyc); #if 0 printf("dval0 is %016llx, dval2:%016llx, bits:%d\n", dval0, dval2, bits); #endif dval = dval ^ dval2; if(forced_bits < 0) { forced_bits = iwm_calc_forced_sync_0s(sync_dval, bits); g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, 0 - forced_bits, track_bits); dbg_log_info(dfcyc, g_iwm.forced_sync_bit * 2, (forced_bits << 24) | (bit_pos * 2), 0x400ec); #if 0 printf("Forced bits are %d at %06x, %016llx " "%016llx\n", forced_bits, bit_pos * 2, dval, sync_dval); #endif } } if(forced_bits >= 0) { sync_dval = iwm_calc_forced_sync(dval, forced_bits); dval = dval & ((2ULL << forced_bits) - 1LL); dbg_log_info(dfcyc, (word32)dval, forced_sync_bit * 32, (forced_bits << 16) | 0xea); if(((dsync_val_tmp & 0xff) == (sync_dval & 0xff)) && (!dval0)) { // Only clear it if there are no 0's in the dval, // otherwise we'll reenter and could calc a diff sync g_iwm.forced_sync_bit = 234567*4; //printf("cleared forced_sync\n"); } else { g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, 0 - (sync_dval & 0xf), track_bits); } #if 0 printf("Forced_bits were %d, sync_was %016llx\n", forced_bits, dsync_val_tmp); #endif } #if 0 printf(" Raw bits: %016llx, dsync:%016llx, fbit:%08x\n", dval, sync_dval, fbit_pos); #endif dbg_log_info(dfcyc, (word32)dval, (bits << 24) | ((word32)sync_dval & 0x00ffffffUL), 0x300ec); msb_bit = sync_dval & 0xff; if(g_iwm.state & 1) { // Latch mode (3.5" disk) // last_rd_bit points just past the last bit read (it is // fbit_pos normally, which is one bit past the read bit). // Use latched data if that bit is before the latched LSB diff = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, track_bits); diff = diff + 8; // Calc to the MSB msb_bit = (sync_dval >> 8) & 0xff; if((msb_bit >= 8) && (diff > msb_bit)) { // We've not seen the LSB of this data: ret latched data dbg_log_info(dfcyc, bit_pos * 2, (msb_bit << 16) | (diff & 0xffff), 0x2200ec); bit_pos = iwm_calc_bit_sum(bit_pos, -msb_bit + 8, track_bits); dbg_log_info(dfcyc, bit_pos * 2, g_iwm.last_rd_bit*2, 0x200ec); } else { msb_bit = sync_dval & 0xff; if(diff <= msb_bit) { // Handle case of 10-bit nibble which we've // already returned...don't return it again! msb_bit = 1; } } #if 0 printf("Latch mode diff:%d, msb_bit:%d, new_msb:%d\n", diff, msb_bit, (int)(sync_dval & 0xff)); #endif dbg_log_info(dfcyc, ((word32)diff << 12) | (msb_bit & 0xff), (word32)sync_dval, 0x100ec); } else { if((sync_dval & 0xff) <= 1) { // The LSbit or the next one is a sync bit--but we need // to ignore it. The LSbit is not valid yet, and // the next bit being the MSB of the next nibble will // be ignored to make the 7usec hold time of the // previous nibble work sync_dval = sync_dval >> 8; msb_bit = sync_dval & 0xff; } } val = (word32)dval; if(msb_bit < 8) { // We only have a partial byte val = val & ((2ULL << msb_bit) - 1); } else if(msb_bit < 63) { val = (word32)(dval >> (msb_bit - 8)); } // val is now valid from bit 8 down to bit 1 // We've allowed in to dval an extra bit which we must toss val = val >> 1; g_iwm.last_rd_bit = bit_pos; dbg_log_info(dfcyc, (msb_bit << 24) | (fbit_pos >> 8), (word32)(dval0 << 8) | (val & 0xff), 0xec); #if 0 if(forced_bits >= 0) { printf("Forced bits, msb:%d, val:%02x, dval:%016llx b_p:%06x\n", msb_bit, val, dval, bit_pos * 2); } #endif return val & 0xff; } void iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc) { Trk *trk; word32 track_bits, bit_pos, fbit_pos, bit_last, tmp_val; int bits; trk = dsk->cur_trk_ptr; if((trk == 0) || dsk->write_prot) { return; } track_bits = dsk->cur_track_bits; bit_pos = (dsk->cur_fbit_pos + 511) >> 9; if(track_bits && (bit_pos >= track_bits)) { bit_pos = bit_pos - track_bits; if(bit_pos >= track_bits) { bit_pos = bit_pos % track_bits; } } #if 0 printf("iwm_write_data: %02x %016llx, bit*2:%06x\n", val, dfcyc, bit_pos *2); #endif if(dsk->disk_525) { if(!g_slow_525_emul_wr) { g_slow_525_emul_wr = 1; engine_recalc_events(); if(track_bits && g_fast_disk_emul) { fbit_pos = iwm_fastemul_start_write(dsk, dfcyc); bit_pos = fbit_pos >> 9; } } } dsk->cur_fbit_pos = bit_pos * 512; if(g_iwm.num_active_writes == 0) { // No write was pending, enter write mode now // printf("Starting write of data to the track, track now:\n"); // iwm_show_track(-1, -1, dfcyc); // printf("Write data to track at bit*2:%06x\n", bit_pos); iwm_start_write(dsk, bit_pos, val, 0); return; } if(track_bits == 0) { halt_printf("Impossible: track_bits: 0\n"); return; } if(g_iwm.state & 2) { // async handshake mode, 3.5" iwm_write_data35(dsk, val, dfcyc); return; } // From here on, it's 5.25" disks only bit_last = g_iwm.wr_last_bit[0]; bits = iwm_calc_bit_diff(bit_pos, bit_last, track_bits); if(bits >= 500) { halt_printf("bits are %d. bit*2:%06x, bit_last*2:%06x\n", bits, bit_pos * 2, bit_last * 2); bits = 40; } iwm_write_one_nib(dsk, bits, dfcyc); tmp_val = g_iwm.write_val; dbg_log_info(dfcyc, (g_iwm.wr_last_bit[0] << 25) | (bit_last * 2), (bits << 16) | ((val & 0xff) << 8) | (tmp_val & 0xff), 0xed); g_iwm.write_val = val; } void iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior) { int num; g_iwm.write_val = val; num = 0; num = iwm_start_write_act(dsk, bit_pos, num, no_prior, 0); num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -1); // -0.25 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +1); // +0.25 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -2); // -0.50 num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +2); // +0.50 g_iwm.num_active_writes = num; woz_maybe_reparse(dsk); } int iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta) { Trk *trkptr; word32 qtr_track, track_bits, bit_diff, prior_sum, allow_diff; qtr_track = (dsk->cur_frac_track + 0x8000) >> 16; qtr_track += delta; if(qtr_track >= 160) { // Could be unsigned wrap around if(delta == 0) { halt_printf("iwm_start_write_act0, qtr_track:%04x\n", qtr_track); g_iwm.num_active_writes = 0; } return num; } if(!dsk->disk_525 && delta) { return num; // 3.5" does not affect adjacent trks } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { if((delta == -2) || (delta == 2)) { return num; // Nothing to do } if((delta == -1) && (qtr_track > 0)) { if(trkptr[-1].track_bits == 0) { return num; // Nothing to do } } if((delta == 1) && (qtr_track < 159)) { if(trkptr[1].track_bits == 0) { return num; // Nothing to do } } // Otherwise, we need to create this track and write to it track_bits = woz_add_a_track(dsk, qtr_track); } if(track_bits) { bit_pos = bit_pos % track_bits; } bit_diff = iwm_calc_bit_diff(bit_pos, g_iwm.wr_last_bit[num], track_bits); prior_sum = 0; allow_diff = 16 + (16 * g_fast_disk_emul); if((g_iwm.wr_qtr_track[num] == qtr_track) && (bit_diff < allow_diff)) { // consider this write a continuation of the previous write prior_sum = g_iwm.wr_prior_num_bits[num] + g_iwm.wr_num_bits[num] + bit_diff; #if 0 printf("prior_sum is %d, qtr_track:%04x, bit_diff:%d\n", prior_sum, qtr_track, bit_diff); #endif } else { #if 0 printf("No prior_sum, qtr_track:%04x vs %04x, bit_diff:%08x, " "bit_pos:%05x wr_last_bit:%05x\n", g_iwm.wr_qtr_track[num], qtr_track, bit_diff, bit_pos * 2, g_iwm.wr_last_bit[num]*2); #endif } if(no_prior) { prior_sum = 0; } if(delta == 0) { dsk->cur_fbit_pos = bit_pos << 9; iwm_move_to_qtr_track(dsk, qtr_track); } g_iwm.wr_last_bit[num] = bit_pos; g_iwm.wr_qtr_track[num] = qtr_track; g_iwm.wr_num_bits[num] = 0; g_iwm.wr_prior_num_bits[num] = prior_sum; g_iwm.wr_delta[num] = abs(delta); // 0, 1 or 2 return num + 1; } void iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc) { // Just always write 8 bits to the track iwm_write_one_nib(dsk, 8, dfcyc); g_iwm.write_val = val; dsk->cur_fbit_pos = g_iwm.wr_last_bit[0] * 512; } void iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc) { Trk *trkptr; word32 last_bit, qtr_track, num_bits, bit_start, delta, track_bits; word32 prior_sum; int num_active_writes; int i; // Flush out previous write, then turn writing off num_active_writes = g_iwm.num_active_writes; #if 0 printf("In iwm_write_end at %016llx, num:%d, %d\n", dfcyc, num_active_writes, dsk->disk_dirty); #endif if(num_active_writes == 0) { return; // Invalid, not in a write } if(write_through_now) { iwm_write_data(dsk, 0, dfcyc); } for(i = 0; i < num_active_writes; i++) { last_bit = g_iwm.wr_last_bit[i]; qtr_track = g_iwm.wr_qtr_track[i]; num_bits = g_iwm.wr_num_bits[i]; delta = g_iwm.wr_delta[i]; #if 0 printf(" end %d, last_bit:%05x, qtrk:%04x, num_b:%d, " "delta:%d\n", i, last_bit * 2, qtr_track, num_bits, delta); #endif if((num_bits == 0) || (qtr_track >= 160)) { continue; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; prior_sum = g_iwm.wr_prior_num_bits[i]; if((num_bits + prior_sum) >= track_bits) { // Full track write. If delta != 0, erase this track printf("Full track write at qtrk:%04x %016llx\n", qtr_track, dfcyc); #if 0 if(qtr_track == 4) { halt_printf("Full track at qtr_trk:4\n"); } #endif if(delta != 0) { woz_remove_a_track(dsk, qtr_track); printf("TRACK %04x REMOVED\n", qtr_track); continue; } g_iwm.wr_prior_num_bits[i] = 0; } // Otherwise, recalc sync for this track if(num_bits >= track_bits) { num_bits = num_bits % track_bits; } bit_start = iwm_calc_bit_sum(last_bit, -(int)num_bits - 24, track_bits); iwm_recalc_sync_from(dsk, qtr_track, bit_start, dfcyc); #if 0 printf("Wrote %d bits to qtrk:%04x at %05x %016llx, i:%d,%d, " "%d\n", num_bits, qtr_track, bit_start*2, dfcyc, i, num_active_writes, dsk->disk_dirty); #endif } g_iwm.num_active_writes = 0; woz_maybe_reparse(dsk); } void iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc) { word32 qtr_track, bit_pos, delta, val, track_bits; int num; int i; num = g_iwm.num_active_writes; val = g_iwm.write_val; for(i = 0; i < num; i++) { qtr_track = g_iwm.wr_qtr_track[i]; bit_pos = g_iwm.wr_last_bit[i]; delta = g_iwm.wr_delta[i]; dbg_log_info(dfcyc, val, bit_pos * 2, 0x200ed); if(delta == 2) { // Trk +0.50 and -0.50: corrupt val = (val & 0x7f) ^ i ^ 0x0c; } bit_pos = disk_nib_out_raw(dsk, qtr_track, val, bits, bit_pos, dfcyc); track_bits = dsk->trks[qtr_track].track_bits; if(bit_pos >= track_bits) { bit_pos = bit_pos - track_bits; } g_iwm.wr_last_bit[i] = bit_pos; g_iwm.wr_num_bits[i] += bits; dbg_log_info(dfcyc, (bits << 24) | (bit_pos * 2), 2*iwm_calc_bit_sum(bit_pos, 0-g_iwm.wr_num_bits[i], track_bits), 0x300ed); } } void iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc) { Trk *trkptr; byte *bptr, *sync_ptr; word32 track_bits, val, this_sync, sync0; int pos, next_pos, wrap, bit, last_bit, last_byte, match, this_bits; // We are called with a bit_pos 3 bytes before the desired byte. // Look at pos, pos-1, pos+1 in a safe way, and find a valid sync_num if(dfcyc) { //printf("new sync from %d, %016llx %p\n", bit_pos, dfcyc, dsk); } if(qtr_track >= 160) { halt_printf("iwm_recalc_sync_from bad qtr:%04x bit_pos:%06x\n", qtr_track, bit_pos); return; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { halt_printf("iwm_recalc_sync_from track_bits 0 for %04x\n", qtr_track); return; } last_byte = (track_bits - 1) >> 3; last_bit = ((track_bits - 1) & 7) + 1; // 1...8 sync_ptr = &(trkptr->sync_ptr[0]); bptr = &(trkptr->raw_bptr[0]); pos = bit_pos >> 3; bit = bit_pos & 7; // 0...7 sync0 = sync_ptr[pos]; if(sync0 >= 8) { if(pos > 0) { pos--; } else { pos++; // pos is now 1 } sync0 = sync_ptr[pos]; } bit = (7 - sync0) & 7; match = 0; wrap = 0; next_pos = pos + 1; // cnt = 0; // printf("recalc_sync_from pos:%04x, bit:%d\n", pos, bit); while(1) { val = bptr[pos]; this_bits = 8; this_sync = sync_ptr[pos]; sync_ptr[pos] = 0x20; next_pos = pos + 1; if(pos >= last_byte) { #if 0 printf("At last_byte, val:%02x, pos:%04x, last_bit:%d, " "bit:%d\n", val, pos, last_bit, bit); #endif this_bits = last_bit; val = val & (0xff00 >> last_bit); next_pos = 0; wrap++; if(wrap >= 3) { halt_printf("no stable sync found\n"); return; } if(bit >= this_bits) { // Skip over this partial byte to byte 0 bit -= this_bits; pos = 0; continue; } } #if 0 if(wrap || (pos >= 0x2630)) { printf("pos:%04x, bit:%d, val:%04x, this_bits:%d\n", pos, bit, val, this_bits); } #endif #if 0 if((cnt++ < 10) && (dsk->cur_frac_track == 0)) { printf("sync[%04x]=%02x, val:%02x bit:%d, this_b:%d\n", pos, this_sync, val, bit, this_bits); } #endif // bit is within this byte. Find next set bit while(bit < this_bits) { if(((val << bit) & 0x80) == 0) { // Slide to next bit bit++; continue; } sync_ptr[pos] = 7 - bit; if(this_sync == (word32)(7 - bit)) { match++; if(match >= 7) { #if 0 printf("match %d at pos:%04x wrap:%d\n", match, pos, wrap); #endif return; } } else { match = 0; } break; } bit = (bit + 8 - this_bits) & 7; pos = next_pos; } } /* c600 */ void sector_to_partial_nib(byte *in, byte *nib_ptr) { byte *aux_buf, *nib_out; word32 val, val2; int x; int i; /* Convert 256(+1) data bytes to 342+1 disk nibbles */ aux_buf = nib_ptr; nib_out = nib_ptr + 0x56; for(i = 0; i < 0x56; i++) { aux_buf[i] = 0; } x = 0x55; for(i = 0x101; i >= 0; i--) { val = in[i]; if(i >= 0x100) { val = 0; } val2 = (aux_buf[x] << 1) + (val & 1); val = val >> 1; val2 = (val2 << 1) + (val & 1); val = val >> 1; nib_out[i] = val; aux_buf[x] = val2; x--; if(x < 0) { x = 0x55; } } } int disk_unnib_4x4(Disk *dsk) { int val1; int val2; val1 = iwm_read_data_fast(dsk, 0); val2 = iwm_read_data_fast(dsk, 0); return ((val1 << 1) + 1) & val2; } int iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf) { byte aux_buf[0x80]; int sector_done[16]; byte *buf; word32 val, val2, prev_val, save_frac_track; int track_len, vol, track, phys_sec, log_sec, cksum, x, my_nib_cnt; int save_fbit_pos, tmp_fbit_pos, status, ret, num_sectors_done; int i; //printf("iwm_denib_track525\n"); save_fbit_pos = dsk->cur_fbit_pos; save_frac_track = dsk->cur_frac_track; iwm_move_to_qtr_track(dsk, qtr_track); dsk->cur_fbit_pos = 0; g_fast_disk_unnib = 1; track_len = (dsk->cur_track_bits + 7) >> 3; for(i = 0; i < 16; i++) { sector_done[i] = 0; } num_sectors_done = 0; val = 0; status = -1; my_nib_cnt = 0; while(my_nib_cnt++ < 2*track_len) { /* look for start of a sector */ if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0x96) { continue; } /* It's a sector start */ vol = disk_unnib_4x4(dsk); track = disk_unnib_4x4(dsk); phys_sec = disk_unnib_4x4(dsk); if(phys_sec < 0 || phys_sec > 15) { printf("Track %02x, read sec as %02x\n", qtr_track >> 2, phys_sec); break; } if(dsk->image_type == DSK_TYPE_DOS33) { log_sec = phys_to_dos_sec[phys_sec]; } else { log_sec = phys_to_prodos_sec[phys_sec]; } cksum = disk_unnib_4x4(dsk); if((vol ^ track ^ phys_sec ^ cksum) != 0) { /* not correct format */ printf("Track %02x not DOS 3.3 since hdr cksum, %02x " "%02x %02x %02x\n", qtr_track >> 2, vol, track, phys_sec, cksum); break; } /* see what sector it is */ if(track != (qtr_track >> 2) || (phys_sec < 0) || (phys_sec > 15)) { printf("Track %02x bad since track: %02x, sec: %02x\n", qtr_track>>2, track, phys_sec); break; } if(sector_done[phys_sec]) { printf("Already done sector %02x on track %02x!\n", phys_sec, qtr_track>>2); break; } /* So far so good, let's do it! */ val = 0; i = 0; while(i < NIBS_FROM_ADDR_TO_DATA) { i++; if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xad) { continue; } /* got it, just break */ break; } if(i >= NIBS_FROM_ADDR_TO_DATA) { printf("No data header, track %02x, sec %02x at %07x\n", qtr_track>>2, phys_sec, dsk->cur_fbit_pos); break; } buf = outbuf + 0x100*log_sec; /* Data start! */ prev_val = 0; for(i = 0x55; i >= 0; i--) { val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area1, val:%02x,val2:%03x\n", val, val2); printf(" i:%03x, fbit_pos:%08x\n", i, dsk->cur_fbit_pos); break; } prev_val = val2 ^ prev_val; aux_buf[i] = prev_val; } /* rest of data area */ for(i = 0; i < 0x100; i++) { val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area2, read: %02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } prev_val = val2 ^ prev_val; buf[i] = prev_val; } /* checksum */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area3, read: %02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } if(val2 != prev_val) { printf("Bad data cksum, got %02x, wanted: %02x\n", val2, prev_val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xde) { printf("No 0xde at end of sector data:%02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("No 0xde,0xaa at end of sector:%02x\n", val); printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); break; } /* Got this far, data is good, merge aux_buf into buf */ x = 0x55; for(i = 0; i < 0x100; i++) { val = aux_buf[x]; val2 = (buf[i] << 1) + (val & 1); val = val >> 1; val2 = (val2 << 1) + (val & 1); buf[i] = val2; val = val >> 1; aux_buf[x] = val; x--; if(x < 0) { x = 0x55; } } sector_done[phys_sec] = 1; num_sectors_done++; if(num_sectors_done >= 16) { status = 0; break; } } tmp_fbit_pos = dsk->cur_fbit_pos; g_fast_disk_unnib = 0; ret = 0; if(status != 0) { printf("Nibblization not done, %02x sectors found qtrk %04x, " "drive:%d, slot:%d\n", num_sectors_done, qtr_track, dsk->drive, dsk->disk_525 + 5); printf("my_nib_cnt: %05x, fbit_pos:%07x, trk_len:%05x\n", my_nib_cnt, tmp_fbit_pos, track_len); ret = 16; for(i = 0; i < 16; i++) { printf("sector_done[%d] = %d\n", i, sector_done[i]); if(sector_done[i]) { ret--; } } iwm_show_a_track(dsk, dsk->cur_trk_ptr, 0); if(!ret) { ret = -1; } } else { //printf("iwm_denib_track525 succeeded\n"); } dsk->cur_fbit_pos = save_fbit_pos; iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); return ret; } int iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf) { word32 buf_c00[0x100]; word32 buf_d00[0x100]; word32 buf_e00[0x100]; int sector_done[16]; byte *buf; word32 tmp_5c, tmp_5d, tmp_5e, tmp_66, tmp_67, val, val2; word32 save_frac_track; int num_sectors_done, track_len, phys_track, phys_sec, phys_side; int phys_capacity, cksum, tmp, track, side, num_sectors, x, y; int carry, my_nib_cnt, save_fbit_pos, status, ret; int i; save_fbit_pos = dsk->cur_fbit_pos; save_frac_track = dsk->cur_frac_track; iwm_move_to_qtr_track(dsk, qtr_track); if(dsk->cur_trk_ptr == 0) { return 0; } dsk->cur_fbit_pos = 0; g_fast_disk_unnib = 1; track_len = dsk->cur_track_bits >> 3; num_sectors = g_track_bytes_35[qtr_track >> 5] >> 9; for(i = 0; i < num_sectors; i++) { sector_done[i] = 0; } num_sectors_done = 0; val = 0; status = -1; my_nib_cnt = 0; track = qtr_track >> 1; side = qtr_track & 1; while(my_nib_cnt++ < 2*track_len) { /* look for start of a sector */ if(val != 0xd5) { val = iwm_read_data_fast(dsk, 0); continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { continue; } val = iwm_read_data_fast(dsk, 0); if(val != 0x96) { continue; } /* It's a sector start */ val = iwm_read_data_fast(dsk, 0); phys_track = g_from_disk_byte[val]; if(phys_track != (track & 0x3f)) { printf("Track %02x.%d, read track %02x, %02x\n", track, side, phys_track, val); break; } phys_sec = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if((phys_sec < 0) || (phys_sec >= num_sectors)) { printf("Track %02x.%d, read sector %02x??\n", track, side, phys_sec); break; } phys_side = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(phys_side != ((side << 5) + (track >> 6))) { printf("Track %02x.%d, read side %02x??\n", track, side, phys_side); break; } phys_capacity = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(phys_capacity != 0x24 && phys_capacity != 0x22) { printf("Track %02x.%x capacity: %02x != 0x24/22\n", track, side, phys_capacity); } cksum = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; tmp = phys_track ^ phys_sec ^ phys_side ^ phys_capacity; if(cksum != tmp) { printf("Track %02x.%d, sector %02x, cksum: %02x.%02x\n", track, side, phys_sec, cksum, tmp); break; } if(sector_done[phys_sec]) { printf("Already done sector %02x on track %02x.%x!\n", phys_sec, track, side); break; } /* So far so good, let's do it! */ val = 0; for(i = 0; i < 38; i++) { val = iwm_read_data_fast(dsk, 0); if(val == 0xd5) { break; } } if(val != 0xd5) { printf("No data header, track %02x.%x, sec %02x\n", track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("Bad data hdr1,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xad) { printf("Bad data hdr2,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("dsk->cur_fbit_pos:%07x\n", dsk->cur_fbit_pos); break; } buf = outbuf + (phys_sec << 9); /* check sector again */ tmp = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; if(tmp != phys_sec) { printf("Bad data hdr3,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Data start! */ tmp_5c = 0; tmp_5d = 0; tmp_5e = 0; y = 0xaf; carry = 0; while(y > 0) { /* 626f */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area1b, read: %02x\n", val); printf(" i:%03x, fbit_pos:%07x\n", i, dsk->cur_fbit_pos); break; } tmp_66 = val2; tmp_5c = tmp_5c << 1; carry = (tmp_5c >> 8); tmp_5c = (tmp_5c + carry) & 0xff; val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; if(val2 >= 0x100) { printf("Bad data area2, read: %02x\n", val); break; } val2 = val2 + ((tmp_66 << 2) & 0xc0); val2 = val2 ^ tmp_5c; buf_c00[y] = val2; tmp_5e = val2 + tmp_5e + carry; carry = (tmp_5e >> 8); tmp_5e = tmp_5e & 0xff; /* 62b8 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; val2 = val2 + ((tmp_66 << 4) & 0xc0); val2 = val2 ^ tmp_5e; buf_d00[y] = val2; tmp_5d = val2 + tmp_5d + carry; carry = (tmp_5d >> 8); tmp_5d = tmp_5d & 0xff; y--; if(y <= 0) { break; } /* 6274 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; val2 = val2 + ((tmp_66 << 6) & 0xc0); val2 = val2 ^ tmp_5d; buf_e00[y+1] = val2; tmp_5c = val2 + tmp_5c + carry; carry = (tmp_5c >> 8); tmp_5c = tmp_5c & 0xff; } /* 62d0 */ val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val]; tmp_66 = (val2 << 6) & 0xc0; tmp_67 = (val2 << 4) & 0xc0; val2 = (val2 << 2) & 0xc0; val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + val2; if(tmp_5e != (word32)val2) { printf("Checksum 5e bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + tmp_67; if(tmp_5d != (word32)val2) { printf("Checksum 5d bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } val = iwm_read_data_fast(dsk, 0); val2 = g_from_disk_byte[val] + tmp_66; if(tmp_5c != (word32)val2) { printf("Checksum 5c bad: %02x vs %02x\n", tmp_5e, val2); printf("val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Whew, got it!...check for DE AA */ val = iwm_read_data_fast(dsk, 0); if(val != 0xde) { printf("Bad data epi1,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); break; } val = iwm_read_data_fast(dsk, 0); if(val != 0xaa) { printf("Bad data epi2,val:%02x trk %02x.%x, sec %02x\n", val, track, side, phys_sec); break; } /* Now, convert buf_c/d/e to output */ /* 6459 */ y = 0; for(x = 0xab; x >= 0; x--) { *buf++ = buf_c00[x]; y++; if(y >= 0x200) { break; } *buf++ = buf_d00[x]; y++; if(y >= 0x200) { break; } *buf++ = buf_e00[x]; y++; if(y >= 0x200) { break; } } sector_done[phys_sec] = 1; num_sectors_done++; if(num_sectors_done >= num_sectors) { status = 0; break; } val = 0; } g_fast_disk_unnib = 0; ret = 0; if(status != 0) { printf("dsk->fbit_pos: %07x, status: %d\n", dsk->cur_fbit_pos, status); for(i = 0; i < num_sectors; i++) { printf("sector done[%d] = %d\n", i, sector_done[i]); } printf("Nibblization not done, %02x blocks found qtrk %04x\n", num_sectors_done, qtr_track); ret = -1; } dsk->cur_fbit_pos = save_fbit_pos; iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); return ret; } /* ret = 1 -> dirty data written out */ /* ret = 0 -> not dirty, no error */ /* ret < 0 -> error */ int iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf) { Trk *trk; dword64 dunix_pos, dret, unix_len; int ret; trk = &(dsk->trks[qtr_track]); if((trk->track_bits == 0) || (trk->dirty == 0)) { return 0; } printf("iwm_track_to_unix dirty qtr:%04x, dirty:%d\n", qtr_track, trk->dirty); #if 0 if((qtr_track & 3) && disk_525) { halt_printf("You wrote to phase %02x! Can't wr bk to unix!\n", qtr_track); dsk->write_through_to_unix = 0; return -1; } #endif if(dsk->wozinfo_ptr) { // WOZ disk outbuf = trk->raw_bptr; ret = 0; } else { if(dsk->disk_525) { if(qtr_track & 3) { // Not a valid track ret = -1; } else { ret = iwm_denib_track525(dsk, qtr_track, outbuf); } } else { ret = iwm_denib_track35(dsk, qtr_track, outbuf); } } if(ret != 0) { return -1; } /* Write it out */ dunix_pos = trk->dunix_pos; unix_len = trk->unix_len; if(unix_len < 0x1000) { halt_printf("Disk:%s trk:%06x, dunix_pos:%08llx, len:%08llx\n", dsk->name_ptr, dsk->cur_frac_track, dunix_pos, unix_len); return -1; } trk->dirty = 0; if(dsk->dynapro_info_ptr) { return dynapro_write(dsk, outbuf, dunix_pos, (word32)unix_len); } dret = cfg_write_to_fd(dsk->fd, outbuf, dunix_pos, unix_len); #if 0 printf("Write: qtr_trk:%04x, dunix_pos:%08llx, %s\n", qtr_track, dunix_pos, dsk->name_ptr); #endif if(dret != unix_len) { printf("write: %08llx, errno:%d, trk: %06x, disk: %s\n", dret, errno, dsk->cur_frac_track, dsk->name_ptr); return -1; } if(dsk->wozinfo_ptr) { // WOZ disk printf("Wrote track %07x to fd:%d off:%08llx, len:%07llx\n", dsk->cur_frac_track, dsk->fd, dunix_pos, unix_len); woz_rewrite_crc(dsk, 0); } return 1; } void show_hex_data(byte *buf, int count) { int i; for(i = 0; i < count; i += 16) { printf("%04x: %02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x\n", i, buf[i+0], buf[i+1], buf[i+2], buf[i+3], buf[i+4], buf[i+5], buf[i+6], buf[i+7], buf[i+8], buf[i+9], buf[i+10], buf[i+11], buf[i+12], buf[i+13], buf[i+14], buf[i+15]); } } void iwm_check_nibblization(dword64 dfcyc) { Disk *dsk; int slot, drive, sel35; drive = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; slot = 6; if(g_iwm.state & IWM_STATE_MOTOR_ON) { sel35 = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; } else { sel35 = (g_iwm.state >> IWM_BIT_LAST_SEL35) & 1; } if(sel35) { dsk = &(g_iwm.drive35[drive]); slot = 5; } else { dsk = &(g_iwm.drive525[drive]); } printf("iwm_check_nibblization, s%d d%d\n", slot, drive); disk_check_nibblization(dsk, 0, 4096, dfcyc); } void disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc) { byte buffer[0x3000]; word32 qtr_track; int ret, ret2; int i; if(size > 0x3000) { printf("size %08x is > 0x3000, disk_check_nibblization\n",size); exit(3); } for(i = 0; i < size; i++) { buffer[i] = 0; } //printf("About to call iwm_denib_track*, here's the track:\n"); //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); qtr_track = (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])); if(qtr_track >= 160) { halt_printf("cur_trk_ptr points to bad qtr_track:%08x\n", qtr_track); return; } if(dsk->disk_525) { ret = iwm_denib_track525(dsk, qtr_track, &(buffer[0])); } else { ret = iwm_denib_track35(dsk, qtr_track, &(buffer[0])); } ret2 = -1; if(in_buf) { for(i = 0; i < size; i++) { if(buffer[i] != in_buf[i]) { printf("buffer[%04x]: %02x != %02x\n", i, buffer[i], in_buf[i]); ret2 = i; break; } } } if((ret != 0) || (ret2 >= 0)) { printf("disk_check_nib ret:%d, ret2:%d for track %06x\n", ret, ret2, dsk->cur_frac_track); if(in_buf) { show_hex_data(in_buf, 0x1000); } show_hex_data(buffer, size); iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); if(ret == 16) { printf("No sectors found, ignore it\n"); return; } halt_printf("Stop\n"); exit(2); } } #define TRACK_BUF_LEN 0x2000 void disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, int len_bits, dword64 dfcyc) { byte track_buf[TRACK_BUF_LEN]; Trk *trk; byte *bptr; dword64 dret, dlen; word32 num_bytes, must_clear_track; int i; /* Read track from dsk int track_buf */ #if 0 printf("disk_unix_to_nib: qtr:%04x, unix_pos:%08llx, unix_len:%08x, " "len_bits:%06x\n", qtr_track, dunix_pos, unix_len, len_bits); #endif must_clear_track = 0; if(unix_len > TRACK_BUF_LEN) { printf("diks_unix_to_nib: requested len of image %s = %05x\n", dsk->name_ptr, unix_len); } bptr = dsk->raw_data; if(bptr != 0) { // raw_data is valid, so use it if((dunix_pos + unix_len) > dsk->raw_dsize) { must_clear_track = 1; } else { bptr += dunix_pos; for(i = 0; i < (int)unix_len; i++) { track_buf[i] = bptr[i]; } } } else if(unix_len > 0) { dret = kegs_lseek(dsk->fd, dunix_pos, SEEK_SET); if(dret != dunix_pos) { printf("lseek of disk %s len 0x%llx ret: %lld, errno:" "%d\n", dsk->name_ptr, dunix_pos, dret, errno); must_clear_track = 1; } dlen = read(dsk->fd, track_buf, unix_len); if(dlen != unix_len) { printf("read of disk %s q_trk %d ret: %lld, errno:%d\n", dsk->name_ptr, qtr_track, dlen, errno); must_clear_track = 1; } } if(must_clear_track) { for(i = 0; i < TRACK_BUF_LEN; i++) { track_buf[i] = 0; } } #if 0 printf("Q_track %02x dumped out\n", qtr_track); for(i = 0; i < 4096; i += 32) { printf("%04x: %02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x " "%02x%02x%02x%02x%02x%02x%02x%02x\n", i, track_buf[i+0], track_buf[i+1], track_buf[i+2], track_buf[i+3], track_buf[i+4], track_buf[i+5], track_buf[i+6], track_buf[i+7], track_buf[i+8], track_buf[i+9], track_buf[i+10], track_buf[i+11], track_buf[i+12], track_buf[i+13], track_buf[i+14], track_buf[i+15], track_buf[i+16], track_buf[i+17], track_buf[i+18], track_buf[i+19], track_buf[i+20], track_buf[i+21], track_buf[i+22], track_buf[i+23], track_buf[i+24], track_buf[i+25], track_buf[i+26], track_buf[i+27], track_buf[i+28], track_buf[i+29], track_buf[i+30], track_buf[i+31]); } #endif dsk->cur_fbit_pos = 0; /* for consistency */ dsk->raw_bptr_malloc = 1; trk = &(dsk->trks[qtr_track]); num_bytes = 2 + ((len_bits + 7) >> 3) + 4; trk->track_bits = len_bits; trk->dunix_pos = dunix_pos; trk->unix_len = unix_len; trk->dirty = 0; trk->raw_bptr = (byte *)malloc(num_bytes); trk->sync_ptr = (byte *)malloc(num_bytes); iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); /* create nibblized image */ if(dsk->disk_525 && (dsk->image_type == DSK_TYPE_NIB)) { iwm_nibblize_track_nib525(dsk, track_buf, qtr_track, unix_len); } else if(dsk->disk_525) { iwm_nibblize_track_525(dsk, track_buf, qtr_track, dfcyc); } else { iwm_nibblize_track_35(dsk, track_buf, qtr_track, unix_len, dfcyc); } //printf("For qtr_track:%04x, trk->dirty:%d\n", qtr_track, trk->dirty); trk->dirty = 0; } void iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len) { byte *bptr, *sync_ptr; int len; int i; // This is the old, dumb .nib format. It consists of 0x1a00 bytes // per track, but there's no sync information. Just mark each byte // as being sync=7 len = unix_len; bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); for(i = 0; i < len; i++) { bptr[i] = track_buf[i]; } for(i = 0; i < len; i++) { sync_ptr[i] = 7; } if(dsk->cur_track_bits != (unix_len * 8)) { fatal_printf("Track %d.%02d of nib image should be bits:%06x " "but it is: %06x\n", qtr_track >> 2, (qtr_track & 3)*25, unix_len * 8, dsk->cur_track_bits); } iwm_printf("Nibblized q_track %02x\n", qtr_track); } void iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc) { byte partial_nib_buf[0x300]; word32 val, last_val; int phys_sec, log_sec, num_sync; int i; #if 0 printf("nibblize track 525, qtr_track:%04x, trk:%p, trk->raw_bptr:%p, " "sync_ptr:%p\n", qtr_track, trk, trk->raw_bptr, trk->sync_ptr); #endif for(phys_sec = 0; phys_sec < 16; phys_sec++) { if(dsk->image_type == DSK_TYPE_DOS33) { log_sec = phys_to_dos_sec[phys_sec]; } else { log_sec = phys_to_prodos_sec[phys_sec]; } /* Create sync headers */ if(phys_sec == 0) { num_sync = 70; } else { num_sync = 22; } for(i = 0; i < num_sync; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); // prolog: d5,aa,96 disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0x96, 8); disk_4x4_nib_out(dsk, dsk->vol_num); disk_4x4_nib_out(dsk, qtr_track >> 2); disk_4x4_nib_out(dsk, phys_sec); disk_4x4_nib_out(dsk, dsk->vol_num ^ (qtr_track>>2) ^ phys_sec); disk_nib_out(dsk, 0xde, 8); // epilog: de,aa,eb disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xeb, 8); /* Inter sync */ disk_nib_out(dsk, 0xff, 10); for(i = 0; i < 6; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); // data prolog: d5,aa,ad disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xad, 8); sector_to_partial_nib( &(track_buf[log_sec*256]), &(partial_nib_buf[0])); last_val = 0; for(i = 0; i < 0x156; i++) { val = partial_nib_buf[i]; disk_nib_out(dsk, to_disk_byte[last_val ^ val], 8); last_val = val; } disk_nib_out(dsk, to_disk_byte[last_val], 8); /* data epilog */ disk_nib_out(dsk, 0xde, 8); // data epilog: de,aa,eb disk_nib_out(dsk, 0xaa, 8); disk_nib_out(dsk, 0xeb, 8); } /* finish nibblization */ disk_nib_end_track(dsk, dfcyc); iwm_printf("Nibblized q_track %02x\n", qtr_track); if(g_check_nibblization) { disk_check_nibblization(dsk, &(track_buf[0]), 0x1000, dfcyc); } //printf("Showing track after nibblization:\n"); //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); } void iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len, dword64 dfcyc) { int phys_to_log_sec[16]; word32 buf_c00[0x100]; word32 buf_d00[0x100]; word32 buf_e00[0x100]; byte *buf; word32 val, phys_track, phys_side, capacity, cksum, acc_hi; word32 tmp_5c, tmp_5d, tmp_5e, tmp_5f, tmp_63, tmp_64, tmp_65; int num_sectors, log_sec, track, side, num_sync, carry; int interleave, x, y; int i, phys_sec; if(dsk->cur_fbit_pos & 511) { halt_printf("fbit_pos:%07x is not bit-aligned!\n", dsk->cur_fbit_pos); } num_sectors = (unix_len >> 9); for(i = 0; i < num_sectors; i++) { phys_to_log_sec[i] = -1; } phys_sec = 0; interleave = 2; for(log_sec = 0; log_sec < num_sectors; log_sec++) { while(phys_to_log_sec[phys_sec] >= 0) { phys_sec++; if(phys_sec >= num_sectors) { phys_sec = 0; } } phys_to_log_sec[phys_sec] = log_sec; phys_sec += interleave; if(phys_sec >= num_sectors) { phys_sec -= num_sectors; } } track = qtr_track >> 1; side = qtr_track & 1; for(phys_sec = 0; phys_sec < num_sectors; phys_sec++) { log_sec = phys_to_log_sec[phys_sec]; if(log_sec < 0) { printf("Track: %02x.%x phys_sec: %02x = %d!\n", track, side, phys_sec, log_sec); exit(2); } /* Create sync headers */ if(phys_sec == 0) { num_sync = 400; } else { num_sync = 54; } for(i = 0; i < num_sync; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); /* prolog */ disk_nib_out(dsk, 0xaa, 8); /* prolog */ disk_nib_out(dsk, 0x96, 8); /* prolog */ phys_track = track & 0x3f; phys_side = (side << 5) + (track >> 6); capacity = 0x22; disk_nib_out(dsk, to_disk_byte[phys_track], 8); /* trk */ disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec */ disk_nib_out(dsk, to_disk_byte[phys_side], 8); /* sides+trk */ disk_nib_out(dsk, to_disk_byte[capacity], 8); /* capacity*/ cksum = (phys_track ^ log_sec ^ phys_side ^ capacity) & 0x3f; disk_nib_out(dsk, to_disk_byte[cksum], 8); /* cksum*/ disk_nib_out(dsk, 0xde, 8); /* epi */ disk_nib_out(dsk, 0xaa, 8); /* epi */ /* Inter sync */ for(i = 0; i < 5; i++) { disk_nib_out(dsk, 0xff, 10); } disk_nib_out(dsk, 0xd5, 8); /* data prolog */ disk_nib_out(dsk, 0xaa, 8); /* data prolog */ disk_nib_out(dsk, 0xad, 8); /* data prolog */ disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec again */ /* do nibblizing! */ buf = track_buf + (log_sec << 9); /* 6320 */ tmp_5e = 0; tmp_5d = 0; tmp_5c = 0; y = 0; x = 0xaf; buf_c00[0] = 0; buf_d00[0] = 0; buf_e00[0] = 0; buf_e00[1] = 0; for(y = 0x4; y > 0; y--) { buf_c00[x] = 0; buf_d00[x] = 0; buf_e00[x] = 0; x--; } while(x >= 0) { /* 6338 */ tmp_5c = tmp_5c << 1; carry = (tmp_5c >> 8); tmp_5c = (tmp_5c + carry) & 0xff; val = buf[y]; tmp_5e = val + tmp_5e + carry; carry = (tmp_5e >> 8); tmp_5e = tmp_5e & 0xff; val = val ^ tmp_5c; buf_c00[x] = val; y++; /* 634c */ val = buf[y]; tmp_5d = tmp_5d + val + carry; carry = (tmp_5d >> 8); tmp_5d = tmp_5d & 0xff; val = val ^ tmp_5e; buf_d00[x] = val; y++; x--; if(x <= 0) { break; } /* 632a */ val = buf[y]; tmp_5c = tmp_5c + val + carry; carry = (tmp_5c >> 8); tmp_5c = tmp_5c & 0xff; val = val ^ tmp_5d; buf_e00[x+1] = val; y++; } /* 635f */ val = ((tmp_5c >> 2) ^ tmp_5d) & 0x3f; /* 6367 */ val = (val ^ tmp_5d) >> 2; /* 636b */ val = (val ^ tmp_5e) & 0x3f; /* 636f */ val = (val ^ tmp_5e) >> 2; /* 6373 */ tmp_5f = val; /* 6375 */ tmp_63 = 0; tmp_64 = 0; tmp_65 = 0; acc_hi = 0; y = 0xae; while(y >= 0) { /* 63e4 */ /* write out acc_hi */ val = to_disk_byte[acc_hi & 0x3f]; disk_nib_out(dsk, val, 8); /* 63f2 */ val = to_disk_byte[tmp_63 & 0x3f]; tmp_63 = buf_c00[y]; acc_hi = tmp_63 >> 6; disk_nib_out(dsk, val, 8); /* 640b */ val = to_disk_byte[tmp_64 & 0x3f]; tmp_64 = buf_d00[y]; acc_hi = (acc_hi << 2) + (tmp_64 >> 6); disk_nib_out(dsk, val, 8); y--; if(y < 0) { break; } /* 63cb */ val = to_disk_byte[tmp_65 & 0x3f]; tmp_65 = buf_e00[y+1]; acc_hi = (acc_hi << 2) + (tmp_65 >> 6); disk_nib_out(dsk, val, 8); } /* 6429 */ val = to_disk_byte[tmp_5f & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5e & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5d & 0x3f]; disk_nib_out(dsk, val, 8); val = to_disk_byte[tmp_5c & 0x3f]; disk_nib_out(dsk, val, 8); /* 6440 */ /* data epilog */ disk_nib_out(dsk, 0xde, 8); /* epi */ disk_nib_out(dsk, 0xaa, 8); /* epi */ disk_nib_out(dsk, 0xff, 8); } disk_nib_end_track(dsk, dfcyc); if(g_check_nibblization) { disk_check_nibblization(dsk, &(track_buf[0]), unix_len, dfcyc); } } void disk_4x4_nib_out(Disk *dsk, word32 val) { disk_nib_out(dsk, 0xaa | (val >> 1), 8); disk_nib_out(dsk, 0xaa | val, 8); } void disk_nib_out(Disk *dsk, word32 val, int size) { word32 bit_pos; bit_pos = dsk->cur_fbit_pos >> 9; dsk->cur_fbit_pos = disk_nib_out_raw(dsk, (dsk->cur_frac_track + 0x8000) >> 16, val, size, bit_pos, 0) * 512; } void disk_nib_end_track(Disk *dsk, dword64 dfcyc) { // printf("disk_nib_end_track %p\n", dsk); dsk->cur_fbit_pos = 0; dsk->disk_dirty = 0; iwm_recalc_sync_from(dsk, (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])), 0, dfcyc); } word32 disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, word32 bit_pos, dword64 dfcyc) { Trk *trkptr; byte *bptr, *sync_ptr; word32 track_bits, tmp, mask; int pos, next_pos, bit, to_do, shift_left, shift_right, last_byte; int this_bits; int do_print; // write bits from val[7:x]. If bits=3 and val=0xaf, write bits 101. // If bits=10 and val=0xaf, write 0xaf then bits 00. if(qtr_track >= 160) { return bit_pos; } trkptr = &(dsk->trks[qtr_track]); track_bits = trkptr->track_bits; if(track_bits == 0) { halt_printf("disk_nib_out_raw track_bits=0, %04x\n", qtr_track); return bit_pos; } last_byte = (track_bits - 1) >> 3; bit = bit_pos & 7; pos = bit_pos >> 3; bptr = &(trkptr->raw_bptr[0]); sync_ptr = &(trkptr->sync_ptr[0]); if(dfcyc != 0) { dbg_log_info(dfcyc, (bits << 24) | (bit_pos << 1), (track_bits << 16) | (val & 0xffff), 0x100ed); } dsk->disk_dirty = 1; trkptr->dirty = 1; do_print = ((pos <= 0x10) || (pos >= 0x18e0)) && (dsk->cur_frac_track == 0xb0000); do_print = 0; if(do_print) { printf("disk_nib_out %02x, %d, %06x\n", val, bits, bit_pos*2); } while(1) { this_bits = 8; next_pos = pos + 1; if(pos >= last_byte) { this_bits = ((track_bits - 1) & 7) + 1; // 1..8 next_pos = 0; } this_bits = (this_bits - bit); // 1..8 to_do = bits; // 1...inf if(to_do > this_bits) { to_do = this_bits; // 1..8 } shift_left = (8 - bit - to_do) & 7; shift_right = 8 - to_do; mask = (1U << to_do) - 1; tmp = (val >> shift_right) & mask; mask = mask << shift_left; if(do_print) { printf(" pos:%04x bit:%d tmp:%02x mask:%02x bits:%d " "bptr[]=%02x new:%02x todo:%d, r:%d l:%d\n", pos, bit, tmp, mask, bits, bptr[pos], (bptr[pos] & (~mask)) | ((tmp << shift_left) & mask), to_do, shift_right, shift_left); } bptr[pos] = (bptr[pos] & (~mask)) | ((tmp << shift_left) & mask); sync_ptr[pos] = 0xff; bits -= to_do; if(bits <= 0) { pos = (pos * 8) + bit + to_do; if((bit + to_do) >= 8) { pos = next_pos * 8; } if((word32)pos >= track_bits) { pos -= track_bits; } if(do_print) { printf(" returning %05x, bits:%d orig:%05x " "%05x\n", pos*2, bits, bit_pos*2, last_byte); } return pos; } val = (val << to_do) & 0xff; bit = 0; pos = next_pos; } } Disk * iwm_get_dsk_from_slot_drive(int slot, int drive) { Disk *dsk; int max_drive; // pass in slot=5,6,7 drive=0,1 (or more for slot 7) max_drive = 2; switch(slot) { case 5: dsk = &(g_iwm.drive35[drive]); break; case 6: dsk = &(g_iwm.drive525[drive]); break; default: // slot 7 max_drive = MAX_C7_DISKS; dsk = &(g_iwm.smartport[drive]); } if(drive >= max_drive) { dsk -= drive; // Move back to drive 0 effectively } return dsk; } void iwm_toggle_lock(Disk *dsk) { printf("iwm_toggle_lock: write_prot:%d, write_through:%d\n", dsk->write_prot, dsk->write_through_to_unix); if(dsk->write_prot == 2) { // nothing to do return; } if(dsk->write_prot) { dsk->write_prot = 0; } else { dsk->write_prot = 1; } printf("New dsk->write_prot: %d\n", dsk->write_prot); if(dsk->wozinfo_ptr && dsk->write_through_to_unix) { woz_rewrite_lock(dsk); printf("Called woz_rewrite_lock()\n"); return; } } void iwm_eject_named_disk(int slot, int drive, const char *name, const char *partition_name) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive(slot, drive); if(dsk->fd < 0) { return; } /* If name matches, eject the disk! */ if(!strcmp(dsk->name_ptr, name)) { /* It matches, eject it */ if((partition_name != 0) && (dsk->partition_name != 0)) { /* If both have partitions, and they differ, then */ /* don't eject. Otherwise, eject */ if(strcmp(dsk->partition_name, partition_name) != 0) { /* Don't eject */ return; } } iwm_eject_disk(dsk); } } void iwm_eject_disk_by_num(int slot, int drive) { Disk *dsk; dsk = iwm_get_dsk_from_slot_drive(slot, drive); iwm_eject_disk(dsk); } void iwm_eject_disk(Disk *dsk) { Woz_info *wozinfo_ptr; word32 state; int motor_on; int i; printf("Ejecting dsk: %s, fd:%d\n", dsk->name_ptr, dsk->fd); if(dsk->fd < 0) { return; } g_config_kegs_update_needed = 1; state = g_iwm.state; motor_on = state & IWM_STATE_MOTOR_ON; if(state & IWM_STATE_C031_APPLE35SEL) { motor_on = state & IWM_STATE_MOTOR_ON35; } if(motor_on) { halt_printf("Try eject dsk:%s, but motor_on!\n", dsk->name_ptr); } dynapro_try_fix_damaged_disk(dsk); iwm_flush_disk_to_unix(dsk); printf("Ejecting disk: %s\n", dsk->name_ptr); /* Free all memory, close file */ /* free the tracks first */ if(dsk->trks != 0) { for(i = 0; i < MAX_TRACKS; i++) { if(dsk->raw_bptr_malloc) { free(dsk->trks[i].raw_bptr); } free(dsk->trks[i].sync_ptr); dsk->trks[i].raw_bptr = 0; dsk->trks[i].sync_ptr = 0; dsk->trks[i].track_bits = 0; } } dsk->num_tracks = 0; dsk->raw_bptr_malloc = 0; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { if(dsk->raw_data == 0) { free(wozinfo_ptr->wozptr); } wozinfo_ptr->wozptr = 0; free(wozinfo_ptr); } dsk->wozinfo_ptr = 0; dynapro_free_dynapro_info(dsk); /* close file, clean up dsk struct */ if(dsk->raw_data) { free(dsk->raw_data); } else { close(dsk->fd); } dsk->fd = -1; dsk->raw_dsize = 0; dsk->raw_data = 0; dsk->dimage_start = 0; dsk->dimage_size = 0; dsk->cur_fbit_pos = 0; dsk->cur_track_bits = 0; dsk->disk_dirty = 0; dsk->write_through_to_unix = 0; dsk->write_prot = 1; dsk->just_ejected = 1; /* Leave name_ptr valid */ } void iwm_show_track(int slot_drive, int track, dword64 dfcyc) { Disk *dsk; Trk *trk; word32 state; int drive, sel35, qtr_track; state = g_iwm.state; if(slot_drive < 0) { drive = (state >> IWM_BIT_DRIVE_SEL) & 1; if(state & IWM_STATE_MOTOR_ON) { sel35 = (state >> IWM_BIT_C031_APPLE35SEL) & 1; } else { sel35 = (state >> IWM_BIT_LAST_SEL35) & 1; } } else { drive = slot_drive & 1; sel35 = !((slot_drive >> 1) & 1); } if(sel35) { dsk = &(g_iwm.drive35[drive]); } else { dsk = &(g_iwm.drive525[drive]); } if(track < 0) { qtr_track = dsk->cur_frac_track >> 16; } else { qtr_track = track; } if((dsk->trks == 0) || (qtr_track >= 160)) { return; } trk = &(dsk->trks[qtr_track]); if(trk->track_bits == 0) { dbg_printf("Track_bits: %d\n", trk->track_bits); dbg_printf("No track for type: %d, drive: %d, qtrk:0x%02x\n", sel35, drive, qtr_track); return; } dbg_printf("Current s%dd%d, q_track:0x%02x\n", 6 - sel35, drive + 1, qtr_track); iwm_show_a_track(dsk, trk, dfcyc); } void iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc) { byte *bptr; byte *sync_ptr; word32 val, track_bits, len, shift, line_len, sync; int i, j; track_bits = trk->track_bits; dbg_printf(" Showtrack:track_bits*2: %06x, fbit_pos: %07x, " "trk:%06x, dfcyc:%016llx\n", track_bits*2, dsk->cur_fbit_pos, dsk->cur_frac_track, dfcyc); dbg_printf(" disk_525:%d, drive:%d name:%s fd:%d, dimage_start:" "%08llx, dimage_size:%08llx\n", dsk->disk_525, dsk->drive, dsk->name_ptr, dsk->fd, dsk->dimage_start, dsk->dimage_size); dbg_printf(" image_type:%d, vol_num:%02x, write_prot:%d, " "write_through:%d, disk_dirty:%d\n", dsk->image_type, dsk->vol_num, dsk->write_prot, dsk->write_through_to_unix, dsk->disk_dirty); dbg_printf(" just_ejected:%d, last_phases:%d, num_tracks:%d\n", dsk->just_ejected, dsk->last_phases, dsk->num_tracks); len = (track_bits + 7) >> 3; if(len >= 0x3000) { len = 0x3000; dbg_printf("len too big, using %04x\n", len); } bptr = trk->raw_bptr; sync_ptr = trk->sync_ptr; len = len + 2; // Show an extra 2 bytes for(i = 0; i < (int)len; i += 16) { line_len = 16; if((i + line_len) > len) { line_len = len - i; } // First, print raw bptr bytes dbg_printf("%04x: ", i); for(j = 0; j < (int)line_len; j++) { dbg_printf(" %02x", bptr[i + j]); if(((i + j) * 8U) >= track_bits) { dbg_printf("*"); } } dbg_printf("\n"); dbg_printf(" sync:"); for(j = 0; j < (int)line_len; j++) { dbg_printf(" %2d", sync_ptr[i + j]); } dbg_printf("\n"); dbg_printf(" nibs:"); for(j = 0; j < (int)line_len; j++) { sync = sync_ptr[i+j]; if(sync >= 8) { dbg_printf(" XX"); } else { shift = (7 - sync) & 7; val = (bptr[i + j] << 8) | bptr[i + j + 1]; val = ((val << shift) >> 8) & 0xff; dbg_printf(" %02x", val); } } dbg_printf("\n"); } } void dummy1(word32 psr) { printf("dummy1 psr: %05x\n", psr); } void dummy2(word32 psr) { printf("dummy2 psr: %05x\n", psr); } ================================================ FILE: upstream/kegs/src/iwm.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_iwm_h[] = "@(#)$KmKId: iwm.h,v 1.46 2023-09-23 17:53:09+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #define MAX_TRACKS (2*80) #define MAX_C7_DISKS 16 #define NIB_LEN_525 0x18f2 /* 51088 bits per track */ // Expected bits per track: (1020484/5)/4 = 51024. A little extra seems good #define NIBS_FROM_ADDR_TO_DATA 28 // Copy II+ Manual Sector Copy fails if this is 20, so make it 28 // image_type settings. 0 means unknown type #define DSK_TYPE_PRODOS 1 #define DSK_TYPE_DOS33 2 #define DSK_TYPE_DYNAPRO 3 #define DSK_TYPE_NIB 4 #define DSK_TYPE_WOZ 5 // Note: C031_APPLE35SEL must be 6, C031_CTRL must be 7, MOTOR_ON must be 5! // Q7 needs to be adjacent and higher than Q6 // Bits 4:0 are IWM mode register: 0: latch mode; 1: async handshake; // 2: immediate motor off (no 1 sec delay); 3: 2us bit timing; // 4: Divide input clock by 8 (instead of 7) #define IWM_BIT_MOTOR_ON 5 #define IWM_BIT_C031_APPLE35SEL 6 #define IWM_BIT_C031_CTRL 7 #define IWM_BIT_STEP_DIRECTION35 8 #define IWM_BIT_MOTOR_ON35 9 #define IWM_BIT_MOTOR_OFF 10 #define IWM_BIT_DRIVE_SEL 11 #define IWM_BIT_Q6 12 #define IWM_BIT_Q7 13 #define IWM_BIT_ENABLE2 14 #define IWM_BIT_LAST_SEL35 15 #define IWM_BIT_PHASES 16 #define IWM_BIT_RESET 20 #define IWM_STATE_MOTOR_ON (1 << IWM_BIT_MOTOR_ON) #define IWM_STATE_C031_APPLE35SEL (1 << IWM_BIT_C031_APPLE35SEL) #define IWM_STATE_C031_CTRL (1 << IWM_BIT_C031_CTRL) #define IWM_STATE_STEP_DIRECTION35 (1 << IWM_BIT_STEP_DIRECTION35) #define IWM_STATE_MOTOR_ON35 (1 << IWM_BIT_MOTOR_ON35) #define IWM_STATE_MOTOR_OFF (1 << IWM_BIT_MOTOR_OFF) #define IWM_STATE_DRIVE_SEL (1 << IWM_BIT_DRIVE_SEL) #define IWM_STATE_Q6 (1 << IWM_BIT_Q6) #define IWM_STATE_Q7 (1 << IWM_BIT_Q7) #define IWM_STATE_ENABLE2 (1 << IWM_BIT_ENABLE2) #define IWM_STATE_LAST_SEL35 (1 << IWM_BIT_LAST_SEL35) #define IWM_STATE_PHASES (1 << IWM_BIT_PHASES) #define IWM_STATE_RESET (1 << IWM_BIT_RESET) STRUCT(Trk) { byte *raw_bptr; byte *sync_ptr; dword64 dunix_pos; word16 unix_len; word16 dirty; word32 track_bits; }; STRUCT(Woz_info) { byte *wozptr; word32 woz_size; int version; int reparse_needed; word32 max_trk_blocks; int meta_size; int trks_size; int tmap_offset; int trks_offset; int info_offset; int meta_offset; }; typedef struct Dynapro_map_st Dynapro_map; STRUCT(Dynapro_file) { Dynapro_file *next_ptr; Dynapro_file *parent_ptr; Dynapro_file *subdir_ptr; char *unix_path; byte *buffer_ptr; byte prodos_name[17]; // +0x00-0x0f: [0] is len, nul at end word32 dir_byte; // Byte address of this file's dir ent word32 eof; // +0x15-0x17 word32 blocks_used; // +0x13-0x14 word32 creation_time; // +0x18-0x1b word32 lastmod_time; // +0x21-0x24 word16 upper_lower; // +0x1c-0x1d: Versions: lowercase flags word16 key_block; // +0x11-0x12 word16 aux_type; // +0x1f-0x20 word16 header_pointer; // +0x25-0x26 word16 map_first_block; byte file_type; // +0x10 byte modified_flag; byte damaged; }; struct Dynapro_map_st { Dynapro_file *file_ptr; word16 next_map_block; word16 modified; }; STRUCT(Dynapro_info) { char *root_path; Dynapro_file *volume_ptr; Dynapro_map *block_map_ptr; int damaged; }; STRUCT(Disk) { dword64 dfcyc_last_read; byte *raw_data; Woz_info *wozinfo_ptr; Dynapro_info *dynapro_info_ptr; char *name_ptr; char *partition_name; int partition_num; int fd; word32 dynapro_blocks; dword64 raw_dsize; dword64 dimage_start; dword64 dimage_size; int smartport; int disk_525; int drive; word32 cur_frac_track; int image_type; int vol_num; int write_prot; int write_through_to_unix; int disk_dirty; int just_ejected; int last_phases; dword64 dfcyc_last_phases; word32 cur_fbit_pos; word32 fbit_mult; word32 cur_track_bits; int raw_bptr_malloc; Trk *cur_trk_ptr; int num_tracks; Trk *trks; }; STRUCT(Iwm) { Disk drive525[2]; Disk drive35[2]; Disk smartport[MAX_C7_DISKS]; dword64 dfcyc_last_fastemul_read; word32 state; word32 motor_off_vbl_count; word32 forced_sync_bit; word32 last_rd_bit; word32 write_val; word32 wr_last_bit[5]; word32 wr_qtr_track[5]; word32 wr_num_bits[5]; word32 wr_prior_num_bits[5]; word32 wr_delta[5]; int num_active_writes; }; STRUCT(Driver_desc) { word16 sig; word16 blk_size; word32 blk_count; word16 dev_type; word16 dev_id; word32 data; word16 drvr_count; }; STRUCT(Part_map) { word16 sig; word16 sigpad; word32 map_blk_cnt; word32 phys_part_start; word32 part_blk_cnt; char part_name[32]; char part_type[32]; word32 data_start; word32 data_cnt; word32 part_status; word32 log_boot_start; word32 boot_size; word32 boot_load; word32 boot_load2; word32 boot_entry; word32 boot_entry2; word32 boot_cksum; char processor[16]; char junk[128]; }; ================================================ FILE: upstream/kegs/src/joystick_driver.c ================================================ const char rcsid_joystick_driver_c[] = "@(#)$KmKId: joystick_driver.c,v 1.23 2023-09-26 02:59:00+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" #ifdef __linux__ # include #endif #ifdef _WIN32 # include # include #endif extern int g_joystick_native_type1; /* in paddles.c */ extern int g_joystick_native_type2; /* in paddles.c */ extern int g_joystick_native_type; /* in paddles.c */ extern int g_paddle_buttons; extern int g_paddle_val[]; const char *g_joystick_dev = "/dev/js0"; /* default joystick dev file */ #define MAX_JOY_NAME 128 int g_joystick_native_fd = -1; int g_joystick_num_axes = 0; int g_joystick_num_buttons = 0; int g_joystick_callback_buttons = 0; int g_joystick_callback_x = 32767; int g_joystick_callback_y = 32767; #ifdef __linux__ # define JOYSTICK_DEFINED void joystick_init() { char joy_name[MAX_JOY_NAME]; int version, fd; int i; fd = open(g_joystick_dev, O_RDONLY | O_NONBLOCK); if(fd < 0) { printf("Unable to open joystick dev file: %s, errno: %d\n", g_joystick_dev, errno); printf("Defaulting to mouse joystick\n"); return; } strcpy(&joy_name[0], "Unknown Joystick"); version = 0x800; ioctl(fd, JSIOCGNAME(MAX_JOY_NAME), &joy_name[0]); ioctl(fd, JSIOCGAXES, &g_joystick_num_axes); ioctl(fd, JSIOCGBUTTONS, &g_joystick_num_buttons); ioctl(fd, JSIOCGVERSION, &version); printf("Detected joystick: %s [%d axes, %d buttons vers: %08x]\n", joy_name, g_joystick_num_axes, g_joystick_num_buttons, version); g_joystick_native_type1 = 1; g_joystick_native_type2 = -1; g_joystick_native_fd = fd; for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; } /* joystick_update_linux() called from paddles.c. Update g_paddle_val[] */ /* and g_paddle_buttons with current information */ void joystick_update(dword64 dfcyc) { struct js_event js; /* the linux joystick event record */ int mask, val, num, type, ret, len; int i; /* suck up to 20 events, then give up */ len = sizeof(struct js_event); for(i = 0; i < 20; i++) { ret = read(g_joystick_native_fd, &js, len); if(ret != len) { /* just get out */ break; } type = js.type & ~JS_EVENT_INIT; val = js.value; num = js.number & 3; /* clamp to 0-3 */ switch(type) { case JS_EVENT_BUTTON: mask = 1 << num; if(val) { val = mask; } g_paddle_buttons = (g_paddle_buttons & ~mask) | val; break; case JS_EVENT_AXIS: /* val is -32767 to +32767 */ g_paddle_val[num] = val; break; } } if(i > 0) { paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { } #endif /* LINUX */ #ifdef _WIN32 # define JOYSTICK_DEFINED #undef JOYSTICK_DEFINED // HACK: remove #if 0 void joystick_init() { JOYINFO info; JOYCAPS joycap; MMRESULT ret1, ret2; int i; // Check that there is a joystick device if(joyGetNumDevs() <= 0) { printf("No joystick hardware detected\n"); g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; return; } g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; // Check that at least joystick 1 or joystick 2 is available ret1 = joyGetPos(JOYSTICKID1, &info); ret2 = joyGetDevCaps(JOYSTICKID1, &joycap, sizeof(joycap)); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_joystick_native_type1 = JOYSTICKID1; printf("Joystick #1 = %s\n", joycap.szPname); g_joystick_native_type = JOYSTICKID1; } ret1 = joyGetPos(JOYSTICKID2, &info); ret2 = joyGetDevCaps(JOYSTICKID2, &joycap, sizeof(joycap)); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_joystick_native_type2 = JOYSTICKID2; printf("Joystick #2 = %s\n", joycap.szPname); if(g_joystick_native_type < 0) { g_joystick_native_type = JOYSTICKID2; } } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; } void joystick_update(dword64 dfcyc) { JOYCAPS joycap; JOYINFO info; UINT id; MMRESULT ret1, ret2; id = g_joystick_native_type; ret1 = joyGetDevCaps(id, &joycap, sizeof(joycap)); ret2 = joyGetPos(id, &info); if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { g_paddle_val[0] = (info.wXpos - joycap.wXmin) * 32768 / (joycap.wXmax - joycap.wXmin); g_paddle_val[1] = (info.wYpos - joycap.wYmin) * 32768 / (joycap.wYmax - joycap.wYmin); if(info.wButtons & JOY_BUTTON1) { g_paddle_buttons = g_paddle_buttons | 1; } else { g_paddle_buttons = g_paddle_buttons & (~1); } if(info.wButtons & JOY_BUTTON2) { g_paddle_buttons = g_paddle_buttons | 2; } else { g_paddle_buttons = g_paddle_buttons & (~2); } paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { JOYINFOEX info; UINT id; id = g_joystick_native_type; info.dwSize = sizeof(JOYINFOEX); info.dwFlags = JOY_RETURNBUTTONS; if(joyGetPosEx(id, &info) == JOYERR_NOERROR) { if(info.dwButtons & JOY_BUTTON1) { g_paddle_buttons = g_paddle_buttons | 1; } else { g_paddle_buttons = g_paddle_buttons & (~1); } if(info.dwButtons & JOY_BUTTON2) { g_paddle_buttons = g_paddle_buttons | 2; } else { g_paddle_buttons = g_paddle_buttons & (~2); } } } #endif #endif #ifdef MAC # define JOYSTICK_DEFINED #include #include #include #include #include // Headers are at: /Applications/Xcode.app/Contents/Developer/Platforms/ // MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ // Frameworks/IOKit.framework/Headers // Thanks to VirtualC64 and hidapi library for coding example CFIndex g_joystick_min = 0; CFIndex g_joystick_range = 256; int g_joystick_minmax_valid = 0; int g_joystick_dummy = 0; void hid_device_callback(void *ptr, IOReturn result, void *sender, IOHIDValueRef value) { IOHIDElementRef element; word32 mask; int usage_page, usage, ival, button; // This is a callback routine, and it's unclear to me what the state is // For safety, do no printfs() (other than for debug). if((ptr || result || sender || 1) == 0) { printf("Bad\n"); // Avoid unused var warning } element = IOHIDValueGetElement(value); usage_page = IOHIDElementGetUsagePage(element); usage = IOHIDElementGetUsage(element); ival = IOHIDValueGetIntegerValue(value); #if 0 printf(" usage_page:%d, usage:%d, value:%d\n", usage_page, usage, ival); #endif if((usage_page == kHIDPage_GenericDesktop) && ((usage >= kHIDUsage_GD_X) && (usage <= kHIDUsage_GD_Y)) && !g_joystick_minmax_valid) { g_joystick_min = IOHIDElementGetLogicalMin(element); g_joystick_range = IOHIDElementGetLogicalMax(element) + 1 - g_joystick_min; // printf("min:%lld range:%lld\n", (dword64)g_joystick_min, // (dword64)g_joystick_range); if(g_joystick_range == 0) { g_joystick_range = 1; } g_joystick_minmax_valid = 1; } if((usage_page == kHIDPage_GenericDesktop) && (usage == kHIDUsage_GD_X)) { g_joystick_callback_x = ((ival * 65536) / g_joystick_range) - 32768; //printf("g_joystick_callback_x = %d\n", g_joystick_callback_x); } if((usage_page == kHIDPage_GenericDesktop) && (usage == kHIDUsage_GD_Y)) { g_joystick_callback_y = ((ival * 65536) / g_joystick_range) - 32768; //printf("g_joystick_callback_y = %d\n", g_joystick_callback_y); } if((usage_page == kHIDPage_Button) && (usage >= 1) && (usage <= 10)) { // Buttons: usage=1 is button 0, usage=2 is button 1, etc. button = (~usage) & 1; mask = 1 << button; //printf("Button %d (%d) pressed:%d\n", button, usage, ival); if(ival == 0) { // Button released g_joystick_callback_buttons &= (~mask); } else { // Button pressed g_joystick_callback_buttons |= mask; } } } int hid_get_int_property(IOHIDDeviceRef device, CFStringRef key_cfstr) { CFTypeRef ref; Boolean bret; int int_val; ref = IOHIDDeviceGetProperty(device, key_cfstr); if(ref) { bret = CFNumberGetValue((CFNumberRef)ref, kCFNumberIntType, &int_val); if(bret) { return int_val; } } return 0; } void joystick_init() { IOHIDManagerRef hid_mgr; CFSetRef devices_set; CFIndex num_devices; IOHIDDeviceRef *devices_array, device; int vendor, usage_page, usage; int i; g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); IOHIDManagerSetDeviceMatching(hid_mgr, 0); IOHIDManagerOpen(hid_mgr, kIOHIDOptionsTypeNone); devices_set = IOHIDManagerCopyDevices(hid_mgr); num_devices = CFSetGetCount(devices_set); // Sets are hashtables, so we cannot directly access it. The only way // to iterate over all values is to use CFSetGetValues to get a simple // array of the values, and iterate over that devices_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); CFSetGetValues(devices_set, (const void **)devices_array); for(i = 0; i < num_devices; i++) { device = devices_array[i]; vendor = hid_get_int_property(device, CFSTR(kIOHIDVendorIDKey)); // printf(" vendor: %d\n", vendor); usage_page = hid_get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); usage = hid_get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); // printf(" usage_page:%d, usage:%d\n", usage_page, usage); usage_page = hid_get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); usage = hid_get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); // printf(" primary_usage_page:%d, usage:%d\n", usage_page, // usage); if(usage_page != kHIDPage_GenericDesktop) { continue; } if((usage != kHIDUsage_GD_Joystick) && (usage != kHIDUsage_GD_GamePad) && (usage != kHIDUsage_GD_MultiAxisController)) { continue; } printf(" JOYSTICK FOUND, vendor:%08x!\n", vendor); IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone); IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); IOHIDDeviceRegisterInputValueCallback(device, hid_device_callback, 0); g_joystick_native_type1 = 1; return; // Now, hid_device_callback will be called whenever a joystick // value changes. Only set global variables for joystick. } } void joystick_update(dword64 dfcyc) { int i; if(dfcyc) { // Avoid unused parameter warnings } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); g_paddle_val[0] = g_joystick_callback_x; g_paddle_val[1] = g_joystick_callback_y; paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); } } #endif #ifndef JOYSTICK_DEFINED /* stubs for the routines */ void joystick_init() { g_joystick_native_type1 = -1; g_joystick_native_type2 = -1; g_joystick_native_type = -1; } void joystick_update(dword64 dfcyc) { int i; if(dfcyc) { // Avoid unused parameter warnings } for(i = 0; i < 4; i++) { g_paddle_val[i] = 32767; } g_paddle_buttons = 0xc; if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); g_paddle_val[0] = g_joystick_callback_x; g_paddle_val[1] = g_joystick_callback_y; paddle_update_trigger_dcycs(dfcyc); } } void joystick_update_buttons() { if(g_joystick_native_type1 >= 0) { g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); } } #endif void joystick_callback_init(int native_type) { g_joystick_native_type1 = native_type; } void joystick_callback_update(word32 buttons, int paddle_x, int paddle_y) { g_joystick_callback_buttons = (g_paddle_buttons & (~3)) | (buttons & 3); g_joystick_callback_x = paddle_x; g_joystick_callback_y = paddle_y; } ================================================ FILE: upstream/kegs/src/kegsfont.h ================================================ /* $KmKId: kegsfont.h,v 1.1 2002-11-10 03:31:51-05 kadickey Exp $ */ /* char 0x00 (raw 0x40) */ { 0xc7, 0xbb, 0xab, 0xa3, 0xa7, 0xbf, 0xc3, 0xff }, /* char 0x01 (raw 0x41) */ { 0xef, 0xd7, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xff }, /* char 0x02 (raw 0x42) */ { 0x87, 0xbb, 0xbb, 0x87, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x03 (raw 0x43) */ { 0xc7, 0xbb, 0xbf, 0xbf, 0xbf, 0xbb, 0xc7, 0xff }, /* char 0x04 (raw 0x44) */ { 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x05 (raw 0x45) */ { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0x83, 0xff }, /* char 0x06 (raw 0x46) */ { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x07 (raw 0x47) */ { 0xc3, 0xbf, 0xbf, 0xbf, 0xb3, 0xbb, 0xc3, 0xff }, /* char 0x08 (raw 0x48) */ { 0xbb, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x09 (raw 0x49) */ { 0xc7, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x0a (raw 0x4a) */ { 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x0b (raw 0x4b) */ { 0xbb, 0xb7, 0xaf, 0x9f, 0xaf, 0xb7, 0xbb, 0xff }, /* char 0x0c (raw 0x4c) */ { 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x83, 0xff }, /* char 0x0d (raw 0x4d) */ { 0xbb, 0x93, 0xab, 0xab, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x0e (raw 0x4e) */ { 0xbb, 0xbb, 0x9b, 0xab, 0xb3, 0xbb, 0xbb, 0xff }, /* char 0x0f (raw 0x4f) */ { 0xc7, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x10 (raw 0x50) */ { 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x11 (raw 0x51) */ { 0xc7, 0xbb, 0xbb, 0xbb, 0xab, 0xb7, 0xcb, 0xff }, /* char 0x12 (raw 0x52) */ { 0x87, 0xbb, 0xbb, 0x87, 0xaf, 0xb7, 0xbb, 0xff }, /* char 0x13 (raw 0x53) */ { 0xc7, 0xbb, 0xbf, 0xc7, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x14 (raw 0x54) */ { 0x83, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xff }, /* char 0x15 (raw 0x55) */ { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x16 (raw 0x56) */ { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, /* char 0x17 (raw 0x57) */ { 0xbb, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xbb, 0xff }, /* char 0x18 (raw 0x58) */ { 0xbb, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xbb, 0xff }, /* char 0x19 (raw 0x59) */ { 0xbb, 0xbb, 0xd7, 0xef, 0xef, 0xef, 0xef, 0xff }, /* char 0x1a (raw 0x5a) */ { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x83, 0xff }, /* char 0x1b (raw 0x5b) */ { 0x83, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x83, 0xff }, /* char 0x1c (raw 0x5c) */ { 0xff, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xff, 0xff }, /* char 0x1d (raw 0x5d) */ { 0x83, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0x83, 0xff }, /* char 0x1e (raw 0x5e) */ { 0xff, 0xff, 0xef, 0xd7, 0xbb, 0xff, 0xff, 0xff }, /* char 0x1f (raw 0x5f) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 }, /* char 0x20 (raw 0x20) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x21 (raw 0x21) */ { 0xef, 0xef, 0xef, 0xef, 0xef, 0xff, 0xef, 0xff }, /* char 0x22 (raw 0x22) */ { 0xd7, 0xd7, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x23 (raw 0x23) */ { 0xd7, 0xd7, 0x83, 0xd7, 0x83, 0xd7, 0xd7, 0xff }, /* char 0x24 (raw 0x24) */ { 0xef, 0xc3, 0xaf, 0xc7, 0xeb, 0x87, 0xef, 0xff }, /* char 0x25 (raw 0x25) */ { 0x9f, 0x9b, 0xf7, 0xef, 0xdf, 0xb3, 0xf3, 0xff }, /* char 0x26 (raw 0x26) */ { 0xdf, 0xaf, 0xaf, 0xdf, 0xab, 0xb7, 0xcb, 0xff }, /* char 0x27 (raw 0x27) */ { 0xef, 0xef, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x28 (raw 0x28) */ { 0xef, 0xdf, 0xbf, 0xbf, 0xbf, 0xdf, 0xef, 0xff }, /* char 0x29 (raw 0x29) */ { 0xef, 0xf7, 0xfb, 0xfb, 0xfb, 0xf7, 0xef, 0xff }, /* char 0x2a (raw 0x2a) */ { 0xef, 0xab, 0xc7, 0xef, 0xc7, 0xab, 0xef, 0xff }, /* char 0x2b (raw 0x2b) */ { 0xff, 0xef, 0xef, 0x83, 0xef, 0xef, 0xff, 0xff }, /* char 0x2c (raw 0x2c) */ { 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xdf, 0xff }, /* char 0x2d (raw 0x2d) */ { 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff }, /* char 0x2e (raw 0x2e) */ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff }, /* char 0x2f (raw 0x2f) */ { 0xff, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0xff, 0xff }, /* char 0x30 (raw 0x30) */ { 0xc7, 0xbb, 0xb3, 0xab, 0x9b, 0xbb, 0xc7, 0xff }, /* char 0x31 (raw 0x31) */ { 0xef, 0xcf, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x32 (raw 0x32) */ { 0xc7, 0xbb, 0xfb, 0xe7, 0xdf, 0xbf, 0x83, 0xff }, /* char 0x33 (raw 0x33) */ { 0x83, 0xfb, 0xf7, 0xe7, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x34 (raw 0x34) */ { 0xf7, 0xe7, 0xd7, 0xb7, 0x83, 0xf7, 0xf7, 0xff }, /* char 0x35 (raw 0x35) */ { 0x83, 0xbf, 0x87, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, /* char 0x36 (raw 0x36) */ { 0xe3, 0xdf, 0xbf, 0x87, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x37 (raw 0x37) */ { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xdf, 0xdf, 0xff }, /* char 0x38 (raw 0x38) */ { 0xc7, 0xbb, 0xbb, 0xc7, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x39 (raw 0x39) */ { 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xf7, 0x8f, 0xff }, /* char 0x3a (raw 0x3a) */ { 0xff, 0xff, 0xef, 0xff, 0xef, 0xff, 0xff, 0xff }, /* char 0x3b (raw 0x3b) */ { 0xff, 0xff, 0xef, 0xff, 0xef, 0xef, 0xdf, 0xff }, /* char 0x3c (raw 0x3c) */ { 0xf7, 0xef, 0xdf, 0xbf, 0xdf, 0xef, 0xf7, 0xff }, /* char 0x3d (raw 0x3d) */ { 0xff, 0xff, 0x83, 0xff, 0x83, 0xff, 0xff, 0xff }, /* char 0x3e (raw 0x3e) */ { 0xdf, 0xef, 0xf7, 0xfb, 0xf7, 0xef, 0xdf, 0xff }, /* char 0x3f (raw 0x3f) */ { 0xc7, 0xbb, 0xf7, 0xef, 0xef, 0xff, 0xef, 0xff }, /* char 0x40 (raw 0x14) */ { 0x08, 0x10, 0x6c, 0xfe, 0xfc, 0xfc, 0x7e, 0x6c }, /* char 0x41 (raw 0x11) */ { 0x08, 0x10, 0x6c, 0x82, 0x84, 0x84, 0x52, 0x6c }, /* char 0x42 (raw 0xf5) */ { 0x00, 0x00, 0x40, 0x60, 0x70, 0x78, 0x6c, 0x42 }, /* char 0x43 (raw 0x82) */ { 0xfe, 0x44, 0x28, 0x10, 0x10, 0x28, 0x54, 0xfe }, /* char 0x44 (raw 0xeb) */ { 0x00, 0x02, 0x04, 0x88, 0x50, 0x20, 0x20, 0x00 }, /* char 0x45 (raw 0xe4) */ { 0xfe, 0xfc, 0xfa, 0x36, 0xae, 0xde, 0xde, 0xfe }, /* char 0x46 (raw 0xec) */ { 0xfc, 0xfc, 0xfc, 0xdc, 0x9c, 0x00, 0x9e, 0xde }, /* char 0x47 (raw 0xed) */ { 0xfe, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x00, 0xfe }, /* char 0x48 (raw 0xee) */ { 0x10, 0x20, 0x40, 0xfe, 0x40, 0x20, 0x10, 0x00 }, /* char 0x49 (raw 0xe9) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54 }, /* char 0x4a (raw 0xef) */ { 0x10, 0x10, 0x10, 0x10, 0x92, 0x54, 0x38, 0x10 }, /* char 0x4b (raw 0xf0) */ { 0x10, 0x38, 0x54, 0x92, 0x10, 0x10, 0x10, 0x10 }, /* char 0x4c (raw 0xf1) */ { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0x4d (raw 0xf7) */ { 0x02, 0x02, 0x02, 0x22, 0x62, 0xfe, 0x60, 0x20 }, /* char 0x4e (raw 0xf6) */ { 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc }, /* char 0x4f (raw 0xaf) */ { 0xc8, 0x18, 0x38, 0x7e, 0x38, 0x18, 0x08, 0xf6 }, /* char 0x50 (raw 0xb8) */ { 0x26, 0x30, 0x38, 0xfc, 0x38, 0x30, 0x20, 0xde }, /* char 0x51 (raw 0xce) */ { 0x02, 0x12, 0x10, 0xfe, 0x7c, 0x38, 0x12, 0x02 }, /* char 0x52 (raw 0xe5) */ { 0x02, 0x12, 0x38, 0x7c, 0xfe, 0x10, 0x12, 0x02 }, /* char 0x53 (raw 0xea) */ { 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00 }, /* char 0x54 (raw 0xe6) */ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfe }, /* char 0x55 (raw 0xe8) */ { 0x10, 0x08, 0x04, 0xfe, 0x04, 0x08, 0x10, 0x00 }, /* char 0x56 (raw 0xd7) */ { 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa }, /* char 0x57 (raw 0xe3) */ { 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54 }, /* char 0x58 (raw 0xf4) */ { 0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0xfe, 0x00 }, /* char 0x59 (raw 0xe7) */ { 0x00, 0x00, 0xfc, 0x02, 0x02, 0x02, 0xfe, 0x00 }, /* char 0x5a (raw 0xf3) */ { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, /* char 0x5b (raw 0xd2) */ { 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00 }, /* char 0x5c (raw 0xc7) */ { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0x5d (raw 0xd4) */ { 0x28, 0x28, 0xee, 0x00, 0xee, 0x28, 0x28, 0x00 }, /* char 0x5e (raw 0xdf) */ { 0xfe, 0x02, 0x02, 0x32, 0x32, 0x02, 0x02, 0xfe }, /* char 0x5f (raw 0xd1) */ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /* char 0x60 (raw 0x60) */ { 0xdf, 0xef, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x61 (raw 0x61) */ { 0xff, 0xff, 0xc7, 0xfb, 0xc3, 0xbb, 0xc3, 0xff }, /* char 0x62 (raw 0x62) */ { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, /* char 0x63 (raw 0x63) */ { 0xff, 0xff, 0xc3, 0xbf, 0xbf, 0xbf, 0xc3, 0xff }, /* char 0x64 (raw 0x64) */ { 0xfb, 0xfb, 0xc3, 0xbb, 0xbb, 0xbb, 0xc3, 0xff }, /* char 0x65 (raw 0x65) */ { 0xff, 0xff, 0xc7, 0xbb, 0x83, 0xbf, 0xc3, 0xff }, /* char 0x66 (raw 0x66) */ { 0xe7, 0xdb, 0xdf, 0x87, 0xdf, 0xdf, 0xdf, 0xff }, /* char 0x67 (raw 0x67) */ { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, /* char 0x68 (raw 0x68) */ { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x69 (raw 0x69) */ { 0xef, 0xff, 0xcf, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x6a (raw 0x6a) */ { 0xf7, 0xff, 0xe7, 0xf7, 0xf7, 0xf7, 0xb7, 0xcf }, /* char 0x6b (raw 0x6b) */ { 0xbf, 0xbf, 0xbb, 0xb7, 0x8f, 0xb7, 0xbb, 0xff }, /* char 0x6c (raw 0x6c) */ { 0xcf, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, /* char 0x6d (raw 0x6d) */ { 0xff, 0xff, 0x93, 0xab, 0xab, 0xab, 0xbb, 0xff }, /* char 0x6e (raw 0x6e) */ { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, /* char 0x6f (raw 0x6f) */ { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, /* char 0x70 (raw 0x70) */ { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf }, /* char 0x71 (raw 0x71) */ { 0xff, 0xff, 0xc3, 0xbb, 0xbb, 0xc3, 0xfb, 0xfb }, /* char 0x72 (raw 0x72) */ { 0xff, 0xff, 0xa3, 0x9f, 0xbf, 0xbf, 0xbf, 0xff }, /* char 0x73 (raw 0x73) */ { 0xff, 0xff, 0xc3, 0xbf, 0xc7, 0xfb, 0x87, 0xff }, /* char 0x74 (raw 0x74) */ { 0xdf, 0xdf, 0x87, 0xdf, 0xdf, 0xdb, 0xe7, 0xff }, /* char 0x75 (raw 0x75) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xb3, 0xcb, 0xff }, /* char 0x76 (raw 0x76) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, /* char 0x77 (raw 0x77) */ { 0xff, 0xff, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xff }, /* char 0x78 (raw 0x78) */ { 0xff, 0xff, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xff }, /* char 0x79 (raw 0x79) */ { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, /* char 0x7a (raw 0x7a) */ { 0xff, 0xff, 0x83, 0xf7, 0xef, 0xdf, 0x83, 0xff }, /* char 0x7b (raw 0x7b) */ { 0xe3, 0xcf, 0xcf, 0x9f, 0xcf, 0xcf, 0xe3, 0xff }, /* char 0x7c (raw 0x7c) */ { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }, /* char 0x7d (raw 0x7d) */ { 0x8f, 0xe7, 0xe7, 0xf3, 0xe7, 0xe7, 0x8f, 0xff }, /* char 0x7e (raw 0x7e) */ { 0xcb, 0xa7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, /* char 0x7f (raw 0x7f) */ { 0xff, 0xab, 0xd7, 0xab, 0xd7, 0xab, 0xff, 0xff }, /* char 0x80 (raw 0x40) */ { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, /* char 0x81 (raw 0x41) */ { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, /* char 0x82 (raw 0x42) */ { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, /* char 0x83 (raw 0x43) */ { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, /* char 0x84 (raw 0x44) */ { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0x85 (raw 0x45) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, /* char 0x86 (raw 0x46) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0x87 (raw 0x47) */ { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, /* char 0x88 (raw 0x48) */ { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, /* char 0x89 (raw 0x49) */ { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0x8a (raw 0x4a) */ { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0x8b (raw 0x4b) */ { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, /* char 0x8c (raw 0x4c) */ { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, /* char 0x8d (raw 0x4d) */ { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, /* char 0x8e (raw 0x4e) */ { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, /* char 0x8f (raw 0x4f) */ { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0x90 (raw 0x50) */ { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0x91 (raw 0x51) */ { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, /* char 0x92 (raw 0x52) */ { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, /* char 0x93 (raw 0x53) */ { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, /* char 0x94 (raw 0x54) */ { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0x95 (raw 0x55) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0x96 (raw 0x56) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0x97 (raw 0x57) */ { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, /* char 0x98 (raw 0x58) */ { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, /* char 0x99 (raw 0x59) */ { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0x9a (raw 0x5a) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, /* char 0x9b (raw 0x5b) */ { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, /* char 0x9c (raw 0x5c) */ { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, /* char 0x9d (raw 0x5d) */ { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, /* char 0x9e (raw 0x5e) */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, /* char 0x9f (raw 0x5f) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0xa0 (raw 0x20) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa1 (raw 0x21) */ { 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00 }, /* char 0xa2 (raw 0x22) */ { 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa3 (raw 0x23) */ { 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00 }, /* char 0xa4 (raw 0x24) */ { 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00 }, /* char 0xa5 (raw 0x25) */ { 0x60, 0x64, 0x08, 0x10, 0x20, 0x4c, 0x0c, 0x00 }, /* char 0xa6 (raw 0x26) */ { 0x20, 0x50, 0x50, 0x20, 0x54, 0x48, 0x34, 0x00 }, /* char 0xa7 (raw 0x27) */ { 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xa8 (raw 0x28) */ { 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00 }, /* char 0xa9 (raw 0x29) */ { 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00 }, /* char 0xaa (raw 0x2a) */ { 0x10, 0x54, 0x38, 0x10, 0x38, 0x54, 0x10, 0x00 }, /* char 0xab (raw 0x2b) */ { 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00 }, /* char 0xac (raw 0x2c) */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00 }, /* char 0xad (raw 0x2d) */ { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00 }, /* char 0xae (raw 0x2e) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00 }, /* char 0xaf (raw 0x2f) */ { 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 }, /* char 0xb0 (raw 0x30) */ { 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00 }, /* char 0xb1 (raw 0x31) */ { 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xb2 (raw 0x32) */ { 0x38, 0x44, 0x04, 0x18, 0x20, 0x40, 0x7c, 0x00 }, /* char 0xb3 (raw 0x33) */ { 0x7c, 0x04, 0x08, 0x18, 0x04, 0x44, 0x38, 0x00 }, /* char 0xb4 (raw 0x34) */ { 0x08, 0x18, 0x28, 0x48, 0x7c, 0x08, 0x08, 0x00 }, /* char 0xb5 (raw 0x35) */ { 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0xb6 (raw 0x36) */ { 0x1c, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00 }, /* char 0xb7 (raw 0x37) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00 }, /* char 0xb8 (raw 0x38) */ { 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00 }, /* char 0xb9 (raw 0x39) */ { 0x38, 0x44, 0x44, 0x3c, 0x04, 0x08, 0x70, 0x00 }, /* char 0xba (raw 0x3a) */ { 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00 }, /* char 0xbb (raw 0x3b) */ { 0x00, 0x00, 0x10, 0x00, 0x10, 0x10, 0x20, 0x00 }, /* char 0xbc (raw 0x3c) */ { 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00 }, /* char 0xbd (raw 0x3d) */ { 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00 }, /* char 0xbe (raw 0x3e) */ { 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00 }, /* char 0xbf (raw 0x3f) */ { 0x38, 0x44, 0x08, 0x10, 0x10, 0x00, 0x10, 0x00 }, /* char 0xc0 (raw 0x40) */ { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, /* char 0xc1 (raw 0x41) */ { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, /* char 0xc2 (raw 0x42) */ { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, /* char 0xc3 (raw 0x43) */ { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, /* char 0xc4 (raw 0x44) */ { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0xc5 (raw 0x45) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, /* char 0xc6 (raw 0x46) */ { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0xc7 (raw 0x47) */ { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, /* char 0xc8 (raw 0x48) */ { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, /* char 0xc9 (raw 0x49) */ { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xca (raw 0x4a) */ { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, /* char 0xcb (raw 0x4b) */ { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, /* char 0xcc (raw 0x4c) */ { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, /* char 0xcd (raw 0x4d) */ { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, /* char 0xce (raw 0x4e) */ { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, /* char 0xcf (raw 0x4f) */ { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xd0 (raw 0x50) */ { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, /* char 0xd1 (raw 0x51) */ { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, /* char 0xd2 (raw 0x52) */ { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, /* char 0xd3 (raw 0x53) */ { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, /* char 0xd4 (raw 0x54) */ { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0xd5 (raw 0x55) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xd6 (raw 0x56) */ { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0xd7 (raw 0x57) */ { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, /* char 0xd8 (raw 0x58) */ { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, /* char 0xd9 (raw 0x59) */ { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, /* char 0xda (raw 0x5a) */ { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, /* char 0xdb (raw 0x5b) */ { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, /* char 0xdc (raw 0x5c) */ { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, /* char 0xdd (raw 0x5d) */ { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, /* char 0xde (raw 0x5e) */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, /* char 0xdf (raw 0x5f) */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, /* char 0xe0 (raw 0x60) */ { 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xe1 (raw 0x61) */ { 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00 }, /* char 0xe2 (raw 0x62) */ { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00 }, /* char 0xe3 (raw 0x63) */ { 0x00, 0x00, 0x3c, 0x40, 0x40, 0x40, 0x3c, 0x00 }, /* char 0xe4 (raw 0x64) */ { 0x04, 0x04, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x00 }, /* char 0xe5 (raw 0x65) */ { 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00 }, /* char 0xe6 (raw 0x66) */ { 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x20, 0x00 }, /* char 0xe7 (raw 0x67) */ { 0x00, 0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x38 }, /* char 0xe8 (raw 0x68) */ { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, /* char 0xe9 (raw 0x69) */ { 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xea (raw 0x6a) */ { 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x48, 0x30 }, /* char 0xeb (raw 0x6b) */ { 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x00 }, /* char 0xec (raw 0x6c) */ { 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, /* char 0xed (raw 0x6d) */ { 0x00, 0x00, 0x6c, 0x54, 0x54, 0x54, 0x44, 0x00 }, /* char 0xee (raw 0x6e) */ { 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, /* char 0xef (raw 0x6f) */ { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, /* char 0xf0 (raw 0x70) */ { 0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40 }, /* char 0xf1 (raw 0x71) */ { 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x04 }, /* char 0xf2 (raw 0x72) */ { 0x00, 0x00, 0x5c, 0x60, 0x40, 0x40, 0x40, 0x00 }, /* char 0xf3 (raw 0x73) */ { 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00 }, /* char 0xf4 (raw 0x74) */ { 0x20, 0x20, 0x78, 0x20, 0x20, 0x24, 0x18, 0x00 }, /* char 0xf5 (raw 0x75) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x4c, 0x34, 0x00 }, /* char 0xf6 (raw 0x76) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, /* char 0xf7 (raw 0x77) */ { 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x00 }, /* char 0xf8 (raw 0x78) */ { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, /* char 0xf9 (raw 0x79) */ { 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x38 }, /* char 0xfa (raw 0x7a) */ { 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00 }, /* char 0xfb (raw 0x7b) */ { 0x1c, 0x30, 0x30, 0x60, 0x30, 0x30, 0x1c, 0x00 }, /* char 0xfc (raw 0x7c) */ { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }, /* char 0xfd (raw 0x7d) */ { 0x70, 0x18, 0x18, 0x0c, 0x18, 0x18, 0x70, 0x00 }, /* char 0xfe (raw 0x7e) */ { 0x34, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* char 0xff (raw 0x7f) */ { 0x00, 0x54, 0x28, 0x54, 0x28, 0x54, 0x00, 0x00 }, ================================================ FILE: upstream/kegs/src/kegswin.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kegswin", "kegswin.vcxproj", "{C24D318A-501E-4B8C-901A-15977CB9C00C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.ActiveCfg = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.Build.0 = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.ActiveCfg = Debug|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.Build.0 = Debug|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.ActiveCfg = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.Build.0 = Release|x64 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.ActiveCfg = Release|Win32 {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {32BE13D0-68A7-486D-874C-EA158125775B} EndGlobalSection EndGlobal ================================================ FILE: upstream/kegs/src/kegswin.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 17.0 {C24D318A-501E-4B8C-901A-15977CB9C00C} Win32Proj Application true v143 Application false v143 Application true v143 Application false v143 true true WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreadedDebugDLL Level3 ProgramDatabase Disabled MachineX86 true Windows WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL Level3 ProgramDatabase MachineX86 true Console true true wsock32.lib;dsound.lib;winmm.lib;%(AdditionalDependencies) kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;wsock32.lib;dsound.lib;winmm.lib;odbccp32.lib;%(AdditionalDependencies) Console true Level3 ================================================ FILE: upstream/kegs/src/ldvars ================================================ OBJECTS = adb.o engine_c.o clock.o config.o debugger.o scc.o scc_socket_driver.o scc_windriver.o scc_unixdriver.o iwm.o joystick_driver.o moremem.o paddles.o mockingboard.o sim65816.o smartport.o doc.o sound.o sound_driver.o woz.o unshk.o undeflate.o dynapro.o dyna_type.o dyna_filt.o dyna_validate.o applesingle.o video.o voc.o PROJROOT = .. ================================================ FILE: upstream/kegs/src/macsnd_driver.c ================================================ const char rcsid_macsnd_driver_c[] = "@(#)$KmKId: macsnd_driver.c,v 1.20 2022-04-03 13:38:47+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Headers at: /Applications/Xcode.app/Contents/Developer/Platforms/ // MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ // Frameworks/AudioToolbox.framework/Headers // Some ideas from SDL code: // http://www-personal.umich.edu/~bazald/l/api/_s_d_l__coreaudio_8c_source.html #include "defc.h" #include "sound.h" #include #include #include // Mac OS X 12.0 deprecates kAudioObjectPropertyElementMaster. So now we // need to use kAudioObjectPropertyElementMain, and set it to ...Master if it // isn't already set. This is beyond dumb #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) # if __MAC_OS_X_VERSION_MAX_ALLOWED < 120000 # define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster # endif #endif #define MACSND_REBUF_SIZE (64*1024) word32 g_macsnd_rebuf[MACSND_REBUF_SIZE]; volatile int g_macsnd_rd_pos; volatile int g_macsnd_wr_pos; volatile int g_macsnd_playing = 0; int g_macsnd_channel_warn = 0; extern int g_sound_min_samples; // About 33ms extern int g_sound_max_multiplier; // About 6, so 6*33 ~= 200ms. extern int Verbose; extern int g_preferred_rate; extern word32 *g_sound_shm_addr; extern int g_sound_size; int g_call_num = 0; AURenderCallbackStruct g_callback_struct = { 0 }; static OSStatus audio_callback(void *in_ref_ptr, AudioUnitRenderActionFlags *aura_flags_ptr, const AudioTimeStamp *a_timestamp_ptr, UInt32 bus_number, UInt32 in_num_frame, AudioBufferList *abuflist_ptr) { word32 *wptr; int num_buffers, num_channels, num_samps, sample_num, samps_avail; int max_samples, min_samples; int i, j; if(in_ref_ptr || aura_flags_ptr || a_timestamp_ptr || bus_number) { // Avoid unused parameter warning } #if 0 printf("CB: audio_callback called, in_ref:%p, bus:%d, in_frame:%d\n", in_ref_ptr, bus_number, in_num_frame); #endif num_buffers = abuflist_ptr->mNumberBuffers; min_samples = g_sound_min_samples; max_samples = min_samples * g_sound_max_multiplier; #if 0 printf("CB: num_buffers: %d. sample_time:%lf, host_time:%016llx " "rate_scalar:%lf, word_clock_time:%016llx, flags:%04x\n", num_buffers, a_timestamp_ptr->mSampleTime, a_timestamp_ptr->mHostTime, a_timestamp_ptr->mRateScalar, a_timestamp_ptr->mWordClockTime, a_timestamp_ptr->mFlags); #endif sample_num = g_macsnd_rd_pos; samps_avail = (g_macsnd_wr_pos - sample_num) & (MACSND_REBUF_SIZE - 1); if((int)in_num_frame > samps_avail) { // We don't have enough samples, must pause g_macsnd_playing = 0; sample_num = g_macsnd_wr_pos; // Eat remaining samps } if((g_macsnd_playing == 0) && (samps_avail > (min_samples + (int)in_num_frame))) { // We can unpause g_macsnd_playing = 1; } if(g_macsnd_playing && (samps_avail > max_samples)) { printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); sample_num += (max_samples / 2); sample_num = sample_num & (MACSND_REBUF_SIZE - 1); } #if 0 printf("CB: in_frame:%d, samps_avail:%d, playing:%d\n", in_num_frame, samps_avail, g_macsnd_playing); #endif for(i = 0; i < num_buffers; i++) { num_channels = abuflist_ptr->mBuffers[i].mNumberChannels; if((num_channels != 2) && !g_macsnd_channel_warn) { printf("mNumberChannels:%d\n", num_channels); g_macsnd_channel_warn = 1; } num_samps = abuflist_ptr->mBuffers[i].mDataByteSize / 4; wptr = (word32 *)abuflist_ptr->mBuffers[i].mData; #if 0 printf("CB buf[%d]: num_ch:%d, num_samps:%d, wptr:%p\n", i, num_channels, num_samps, wptr); #endif #if 0 if((g_call_num & 31) == 0) { printf("%d play %d samples, samps_avail:%d\n", g_call_num, num_samps, samps_avail); } #endif if(g_macsnd_playing) { for(j = 0; j < num_samps; j++) { wptr[j] = g_macsnd_rebuf[sample_num]; sample_num++; if(sample_num >= MACSND_REBUF_SIZE) { sample_num = 0; } } } else { for(j = 0; j < num_samps; j++) { wptr[j] = 0; } } } g_call_num++; g_macsnd_rd_pos = sample_num; return 0; } int mac_send_audio(byte *ptr, int in_size) { word32 *wptr, *macptr; int samps, sample_num; int i; samps = in_size / 4; wptr = (word32 *)ptr; sample_num = g_macsnd_wr_pos; macptr = &(g_macsnd_rebuf[0]); for(i = 0; i < samps; i++) { macptr[sample_num] = *wptr++; sample_num++; if(sample_num >= MACSND_REBUF_SIZE) { sample_num = 0; } } g_macsnd_wr_pos = sample_num; return in_size; } AudioObjectPropertyAddress g_aopa = { 0 }; void macsnd_init() { AudioComponentInstance ac_inst; AudioStreamBasicDescription *str_desc_ptr; AudioComponentDescription *ac_descr_ptr; AudioComponent ac; AudioDeviceID device; OSStatus result; UInt32 size; g_macsnd_rd_pos = 0; g_macsnd_wr_pos = 0; mac_printf("macsnd_init called\n"); g_aopa.mSelector = kAudioHardwarePropertyDefaultOutputDevice; g_aopa.mScope = kAudioObjectPropertyScopeGlobal; g_aopa.mElement = kAudioObjectPropertyElementMain; size = 4; result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &g_aopa, 0, 0, &size, &device); if(result != 0) { printf("AudioObjectGetPropertData on DefaultOutputDevice:%d\n", result); return; } mac_printf("Audio Device number: %d\n", device); str_desc_ptr = calloc(1, sizeof(AudioStreamBasicDescription)); str_desc_ptr->mFormatID = kAudioFormatLinearPCM; str_desc_ptr->mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; str_desc_ptr->mChannelsPerFrame = 2; str_desc_ptr->mSampleRate = g_preferred_rate; str_desc_ptr->mFramesPerPacket = 1; str_desc_ptr->mBitsPerChannel = 16; // 16-bit samples str_desc_ptr->mBytesPerFrame = (16 * 2) / 8; str_desc_ptr->mBytesPerPacket = str_desc_ptr->mBytesPerFrame; ac_descr_ptr = calloc(1, sizeof(AudioComponentDescription)); ac_descr_ptr->componentType = kAudioUnitType_Output; ac_descr_ptr->componentManufacturer = kAudioUnitManufacturer_Apple; ac_descr_ptr->componentSubType = kAudioUnitSubType_DefaultOutput; ac = AudioComponentFindNext(0, ac_descr_ptr); mac_printf("AudioComponentFindNext ret: %p\n", ac); if(ac == 0) { exit(1); } result = AudioComponentInstanceNew(ac, &ac_inst); mac_printf("AudioComponentInstanceNew ret:%d\n", result); if(result != 0) { exit(1); } result = AudioUnitSetProperty(ac_inst, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device, sizeof(device)); mac_printf("AudioUnitSetProperty CurrentDevice ret: %d\n", result); if(result != 0) { exit(1); } result = AudioUnitSetProperty(ac_inst, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, str_desc_ptr, sizeof(*str_desc_ptr)); mac_printf("AudioUnitSetProperty StreamFormat ret: %d\n", result); if(result != 0) { exit(1); } g_callback_struct.inputProc = audio_callback; g_callback_struct.inputProcRefCon = (void *)1; result = AudioUnitSetProperty(ac_inst, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &g_callback_struct, sizeof(g_callback_struct)); mac_printf("AudioUnitSetProperty SetRenderCallback ret: %d\n", result); if(result != 0) { exit(1); } result = AudioUnitInitialize(ac_inst); mac_printf("AudioUnitInitialize ret: %d\n", result); if(result != 0) { exit(1); } result = AudioOutputUnitStart(ac_inst); mac_printf("AudioOutputUnitStart ret: %d\n", result); if(result != 0) { exit(1); } sound_set_audio_rate(g_preferred_rate); mac_printf("End of child_sound_init_mac\n"); fflush(stdout); } ================================================ FILE: upstream/kegs/src/mockingboard.c ================================================ const char rcsid_mockingboard_c[] = "@(#)$KmKId: mockingboard.c,v 1.26 2023-06-13 16:54:18+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" #include "sound.h" // Mockingboard contains two pairs of a 6522/AY-8913, where the 6522 interfaces // the AY-8913 (which makes the sounds) to the Apple II. Each AY-8913 // contains 3 channels of sound: A,B,C. Model each pair separately. // The AY-8913 has 16 registers. The documentation numbers them using octal! // The AY8913 is accessed using ORB of the 6522 as control: 0 = Reset, 4=Idle // 5=Reg read; 6=Write reg; 7=Latch reg address extern Mockingboard g_mockingboard; dword64 g_mockingboard_last_int_dusec = 0ULL; dword64 g_mockingboard_event_int_dusec = 0ULL; // 0 -> no event pending extern int g_irq_pending; extern double g_dsamps_per_dfcyc; void mock_ay8913_reset(int pair_num, dword64 dfcyc) { Ay8913 *ay8913ptr; int i; if(dfcyc) { // Avoid unused parameter warning } ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); ay8913ptr->reg_addr_latch = 16; // out-of-range, mb-audit1.3 for(i = 0; i < 16; i++) { ay8913ptr->regs[i] = 0; } for(i = 0; i < 3; i++) { ay8913ptr->toggle_tone[i] = 0; ay8913ptr->tone_samp[i] = 0; } ay8913ptr->noise_val = 0x12345678; ay8913ptr->noise_samp = 0; ay8913ptr->env_dsamp = 0; } void mockingboard_reset(dword64 dfcyc) { word32 timer1_latch; timer1_latch = g_mockingboard.pair[0].mos6522.timer1_latch; memset(&g_mockingboard, 0, sizeof(g_mockingboard)); g_mockingboard_last_int_dusec = (dfcyc >> 16) << 16; if(g_mockingboard_event_int_dusec != 0) { (void)remove_event_mockingboard(); } g_mockingboard_event_int_dusec = 0; printf("At reset, timer1_latch: %08x\n", timer1_latch); if((timer1_latch & 0xffff) == 0x234) { // MB-audit g_mockingboard.pair[0].mos6522.timer1_latch = timer1_latch; } else { g_mockingboard.pair[0].mos6522.timer1_latch = 0xff00; } g_mockingboard.pair[0].mos6522.timer1_counter = 0x2ff00; g_mockingboard.pair[0].mos6522.timer2_counter = 0x2ff00; g_mockingboard.pair[1].mos6522.timer1_latch = 0xff00; g_mockingboard.pair[1].mos6522.timer1_counter = 0x2ff00; g_mockingboard.pair[1].mos6522.timer2_counter = 0x2ff00; mock_ay8913_reset(0, dfcyc); mock_ay8913_reset(1, dfcyc); } void mock_show_pair(int pair_num, dword64 dfcyc, const char *str) { Mos6522 *mos6522ptr; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); printf("Mock %d %s, t1_lat:%05x, t1_c:%05x, t2_l:%05x t2_c:%05x, ifr:" "%02x, acr:%02x, ier:%02x\n", pair_num, str, mos6522ptr->timer1_latch, mos6522ptr->timer1_counter, mos6522ptr->timer2_latch, mos6522ptr->timer2_counter, mos6522ptr->ifr, mos6522ptr->acr, mos6522ptr->ier); printf(" dfcyc:%016llx, event_int:%lld\n", dfcyc, g_mockingboard_event_int_dusec); } // Timers work as follows: if written with '10' in cycle 0, on cycle 1 the // counters loads with '10', and counts down to 9 on cycle 2...down to 1 // on cycle 10, then 0 on cycle 11, and then signals an interrupt on cycle 12 // To handle this, timers are "read value + 1" so that timer==0 means the // timer should interrupt right now. So to write '10' to a timer, the code // needs to write 10+2=12 to the variable, so that on the next cycle, it is // 11, which will be returned as 10 to software reading the reg. void mock_update_timers(int doit, dword64 dfcyc) { Mos6522 *mos6522ptr; dword64 dusec, ddiff, dleft, timer1_int_dusec, timer2_int_dusec; dword64 closest_int_dusec, event_int_dusec; word32 timer_val, ier, timer_eff, timer_latch, log_ifr; int i; dbg_log_info(dfcyc, doit, g_mockingboard.pair[0].mos6522.ifr | (g_mockingboard.pair[0].mos6522.ier << 8), 0xc0); dusec = dfcyc >> 16; ddiff = dusec - g_mockingboard_last_int_dusec; if(!doit && (dusec <= g_mockingboard_last_int_dusec)) { return; // Nothing more to do } // printf("mock_update_timers at %lf %016llx, ddiff:%llx\n", dfcyc, // dusec, ddiff); // Update timers by ddiff integer cycles, calculate next event time g_mockingboard_last_int_dusec = dusec; closest_int_dusec = 0; for(i = 0; i < 2; i++) { // pair_num mos6522ptr = &(g_mockingboard.pair[i].mos6522); timer1_int_dusec = 0; timer_val = mos6522ptr->timer1_counter; ier = mos6522ptr->ier; timer_eff = (timer_val & 0x1ffff); dleft = ddiff; timer_latch = mos6522ptr->timer1_latch + 2; dbg_log_info(dfcyc, (word32)dleft, timer_val, 0xcb); dbg_log_info(dfcyc, ier, timer_latch, 0xcb); if(dleft < timer_eff) { // Move ahead only a little, no triggering timer_val = timer_val - (word32)dleft; if(ddiff) { // printf("New timer1_val:%05x, dleft:%08llx\n", // timer_val, dleft); } if(((mos6522ptr->ifr & 0x40) == 0) && (ier & 0x40)) { // IFR not set yet, prepare an event timer1_int_dusec = dusec + (timer_val & 0x1ffff); dbg_log_info(dfcyc, (word32)timer1_int_dusec, timer_val, 0xcd); // printf("t1_int_dusec: %016llx\n", // timer1_int_dusec); } } else { // Timer1 has triggered now (maybe rolled over more // than once). log_ifr = 0; if((timer_val & 0x20000) == 0) { // Either free-running, or not one-shot already // triggered // Set interrupt: Ensure IFR | 0x40 is set mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, mos6522ptr->ifr | 0x40, ier); log_ifr = 1; } dleft -= timer_eff; if(dleft >= timer_latch) { // It's rolled over several times, remove those dleft = dleft % timer_latch; dbg_log_info(dfcyc, (word32)dleft, timer_latch, 0xcc); } if(dleft == 0) { dleft = timer_latch; } timer_val = (timer_latch - dleft) & 0x1ffff; if((mos6522ptr->acr & 0x40) == 0) { // ACR6=0: One shot mode, mark it as triggered timer_val |= 0x20000; } if(log_ifr) { dbg_log_info(dfcyc, mos6522ptr->ifr | (ier << 8), timer_val, 0xc3); } } #if 0 printf("%016llx ch%d timer1 was %05x, now %05x\n", dfcyc, i, mos6522ptr->timer1_counter, timer_val); #endif mos6522ptr->timer1_counter = timer_val; dbg_log_info(dfcyc, timer_val, timer_latch, 0xce); // Handle timer2 timer2_int_dusec = 0; timer_val = mos6522ptr->timer2_counter; timer_eff = timer_val & 0x1ffff; dleft = ddiff; if(mos6522ptr->acr & 0x20) { // Count pulses mode. Just don't count dleft = 0; } if(dleft < timer_eff) { // Move ahead only a little, no triggering timer_val = timer_val - (word32)dleft; if(((mos6522ptr->ifr & 0x20) == 0) && (ier & 0x20)) { // IFR not set yet, prepare an event timer2_int_dusec = dusec + (timer_val & 0x1ffff); //printf("t2_int_dusec: %016llx\n", // timer1_int_dusec); } } else if(timer_val & 0x20000) { // And already triggered once, just update count timer_val = ((timer_eff - dleft) & 0xffff) | 0x20000; } else { // Has not triggered once yet, but it will now mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, mos6522ptr->ifr | 0x20, ier); timer_val = ((timer_val - dleft) & 0xffff) | 0x20000; dbg_log_info(dfcyc, mos6522ptr->ifr | (ier << 8), timer_val, 0xc4); } // printf("ch%d timer2 was %05x, now %05x\n", i, // mos6522ptr->timer2_counter, timer_val); mos6522ptr->timer2_counter = timer_val; if(timer1_int_dusec && timer2_int_dusec) { timer1_int_dusec = MY_MIN(timer1_int_dusec, timer2_int_dusec); } if(timer1_int_dusec) { if(closest_int_dusec) { closest_int_dusec = MY_MIN(closest_int_dusec, timer1_int_dusec); } else { closest_int_dusec = timer1_int_dusec; } } } event_int_dusec = g_mockingboard_event_int_dusec; if(closest_int_dusec) { // See if this is sooner than the current pending event // printf("closest_int_dusec: %016llx, event_int:%016llx\n", // closest_int_dusec, event_int_dusec); doit = 0; if(event_int_dusec && (closest_int_dusec < event_int_dusec)) { // There was a pending event. Discard it // printf("Call remove_event_mockingboard\n"); remove_event_mockingboard(); doit = 1; } if(!event_int_dusec || doit) { //printf("Call add_event_mockingboard: %016llx %lld\n", // closest_int_dusec, closest_int_dusec); add_event_mockingboard(closest_int_dusec << 16); g_mockingboard_event_int_dusec = closest_int_dusec; dbg_log_info(dfcyc, (word32)(closest_int_dusec - (dfcyc >> 16)), 0, 0xc1); } } } void mockingboard_event(dword64 dfcyc) { // Received an event--we believe we may need to set an IRQ now. // Event was already removed from the event queue // printf("Mockingboard_event received at %016llx\n", dfcyc); dbg_log_info(dfcyc, 0, 0, 0xc2); g_mockingboard_event_int_dusec = 0; mock_update_timers(1, dfcyc); } word32 mockingboard_read(word32 loc, dword64 dfcyc) { int pair_num; // printf("mockingboard read: %04x\n", loc); pair_num = (loc >> 7) & 1; // 0 or 1 return mock_6522_read(pair_num, loc & 0xf, dfcyc); } void mockingboard_write(word32 loc, word32 val, dword64 dfcyc) { int pair_num; // printf("mockingboard write: %04x=%02x\n", loc, val); pair_num = (loc >> 7) & 1; // 0 or 1 mock_6522_write(pair_num, loc & 0xf, val, dfcyc); } word32 mock_6522_read(int pair_num, word32 loc, dword64 dfcyc) { Mos6522 *mos6522ptr; word32 val; // Read from 6522 #pair_num loc (0-15) mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); val = 0; switch(loc) { case 0x0: // ORB/IRB // Connected to AY8913 { RESET, BDIR, BC1 } val = mos6522ptr->orb; // There are no outputs from AY8913 to the 6522 B Port break; case 0x1: // ORA case 0xf: // ORA, no handshake val = mos6522ptr->ora; break; case 0x2: // DDRB val = mos6522ptr->ddrb; break; case 0x3: // DDRA val = mos6522ptr->ddra; break; case 0x4: // T1C-L (timer[0]) mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_counter - 1) & 0xff; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); // Prepare another int (maybe) dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc5); break; case 0x5: // T1C-H mock_update_timers(1, dfcyc); val = ((mos6522ptr->timer1_counter - 1) >> 8) & 0xff; break; case 0x6: // T1L-L val = mos6522ptr->timer1_latch & 0xff; break; case 0x7: // T1L-H val = (mos6522ptr->timer1_latch >> 8) & 0xff; break; case 0x8: // T2C-L mock_update_timers(1, dfcyc); val = (mos6522ptr->timer2_counter - 1) & 0xff; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x20), mos6522ptr->ier); // Clear Bit 5 mock_update_timers(1, dfcyc); // Prepare another int (maybe) dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc6); break; case 0x9: // T2C-H mock_update_timers(1, dfcyc); val = ((mos6522ptr->timer2_counter - 1) >> 8) & 0xff; break; case 0xa: // SR val = mos6522ptr->sr; //halt_printf("Reading SR %d %02x\n", pair_num, val); break; case 0xb: // ACR val = mos6522ptr->acr; break; case 0xc: // PCR val = mos6522ptr->pcr; break; case 0xd: // IFR mock_update_timers(1, dfcyc); val = mos6522ptr->ifr; break; case 0xe: // IER val = mos6522ptr->ier | 0x80; // Despite MOS6522 break; // datasheet, bit 7 = 1 } // printf("6522 %d loc:%x ret:%02x\n", pair_num, loc, val); return val; } void mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc) { Mos6522 *mos6522ptr; word32 ora, orb, new_val, mask; // Write to 6522 #num6522 loc (0-15) // printf("6522 %d loc:%x write:%02x\n", pair_num, loc, val); mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); switch(loc) { case 0x0: // ORB mask = mos6522ptr->ddrb; orb = mos6522ptr->orb; new_val = (val & mask) | (orb & (~mask)); if(orb != new_val) { mock_ay8913_control_update(pair_num, new_val, orb, dfcyc); } mos6522ptr->orb = new_val; break; case 0x1: // ORA case 0xf: // ORA, no handshake mask = mos6522ptr->ddra; ora = mos6522ptr->ora; new_val = (val & mask) | (ora & (~mask)); mos6522ptr->ora = new_val; break; case 0x2: // DDRB orb = mos6522ptr->orb; new_val = (orb & val) | (orb & (~val)); if(orb != new_val) { mock_ay8913_control_update(pair_num, new_val, orb, dfcyc); } mos6522ptr->orb = new_val; mos6522ptr->ddrb = val; return; case 0x3: // DDRA ora = mos6522ptr->ora; mos6522ptr->ora = (ora & val) | (ora & (~val)); mos6522ptr->ddra = val; return; case 0x4: // T1C-L mock_update_timers(0, dfcyc); mos6522ptr->timer1_latch = (mos6522ptr->timer1_latch & 0x1ff00) | val; // printf("Set T1C-L, timer1_latch=%05x\n", // mos6522ptr->timer1_latch); break; case 0x5: // T1C-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); mos6522ptr->timer1_latch = val; mos6522ptr->timer1_counter = val + 2; // The actual timer1_counter update happens next cycle, // so we want val+1, plus another 1 // printf("Set T1C-H, timer1_latch=%05x\n", // mos6522ptr->timer1_latch); mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc7); break; case 0x6: // T1L-L mock_update_timers(0, dfcyc); mos6522ptr->timer1_latch = (mos6522ptr->timer1_latch & 0x1ff00) | val; break; case 0x7: // T1L-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); mos6522ptr->timer1_latch = val; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x40), mos6522ptr->ier); // Clear Bit 6 mock_update_timers(1, dfcyc); // mock_show_pair(pair_num, dfcyc, "Wrote T1L-H"); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc8); break; case 0x8: // T2C-L mos6522ptr->timer2_latch = (mos6522ptr->timer2_latch & 0xff00) | val; break; case 0x9: // T2C-H mock_update_timers(1, dfcyc); val = (mos6522ptr->timer2_latch & 0xff) | (val << 8); mos6522ptr->timer2_latch = val; mos6522ptr->timer2_counter = val + 2; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~0x20), mos6522ptr->ier); // Clear bit 5 mock_update_timers(1, dfcyc); break; case 0xa: // SR mos6522ptr->sr = val; halt_printf("Wrote SR reg: %d %02x\n", pair_num, val); break; case 0xb: // ACR mock_update_timers(0, dfcyc); mos6522ptr->acr = val; mock_update_timers(1, dfcyc); break; case 0xc: // PCR mos6522ptr->pcr = val; break; case 0xd: // IFR mock_update_timers(1, dfcyc); mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr & (~val), mos6522ptr->ier); mock_update_timers(1, dfcyc); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc9); break; case 0xe: // IER // Recalculate effective IFR with new IER mock_update_timers(1, dfcyc); if(val & 0x80) { // Set EIR bits val = mos6522ptr->ier | val; } else { // Clear EIR bits val = mos6522ptr->ier & (~val); } val = val & 0x7f; mos6522ptr->ier = val; mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, mos6522ptr->ifr, val); mock_update_timers(1, dfcyc); // mock_show_pair(pair_num, dfcyc, "Wrote IER"); dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xca); break; } } word32 mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier) { word32 irq_mask; // Determine if there are any interrupts pending now irq_mask = IRQ_PENDING_MOCKINGBOARDA << pair_num; if((ifr & ier & 0x7f) == 0) { // No IRQ pending anymore ifr = ifr & 0x7f; // Clear bit 7 if(g_irq_pending & irq_mask) { // printf("MOCK INT OFF\n"); } remove_irq(irq_mask); dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); } else { // IRQ is pending ifr = ifr | 0x80; // Set bit 7 if(!(g_irq_pending & irq_mask)) { // printf("MOCK INT ON\n"); } add_irq(irq_mask); dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); } return ifr; } void mock_ay8913_reg_read(int pair_num) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; word32 reg_addr_latch, mask, val, ora; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); reg_addr_latch = ay8913ptr->reg_addr_latch; val = 0; if(reg_addr_latch < 16) { val = ay8913ptr->regs[reg_addr_latch]; } // ORA at 6522 is merge of ORA using DDRA mask = mos6522ptr->ddra; ora = mos6522ptr->ora; mos6522ptr->ora = (ora & mask) | (val & (~mask)); } word32 g_mock_channel_regs[3] = { 0x39c3, // channel A: regs 0,1,6,7,8,11,12,13 0x3acc, // channel B: regs 2,3,6,7,9,11,12,13 0x3cf0 // channel C: regs 4,5,6,7,10,11,12,13 }; void mock_ay8913_reg_write(int pair_num, dword64 dfcyc) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; double dsamps; word32 reg_addr_latch, ora, mask, rmask, do_print; int i; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); reg_addr_latch = ay8913ptr->reg_addr_latch; ora = mos6522ptr->ora; dsamps = dfcyc * g_dsamps_per_dfcyc; if(reg_addr_latch < 16) { mask = (g_mockingboard.disable_mask >> (3*pair_num)) & 7; rmask = 0; do_print = 0; for(i = 0; i < 3; i++) { if(((mask >> i) & 1) == 0) { rmask |= g_mock_channel_regs[i]; } } do_print = (rmask >> reg_addr_latch) & 1; if((ora != ay8913ptr->regs[reg_addr_latch]) || (reg_addr_latch == 13)) { // New value, or writing to Envelope control do_print = 0; if(do_print) { printf("%.2lf %016llx mock pair%d reg[%d]=" "%02x. [2,3]=%02x_%02x [67]=%02x,%02x, " "[9]=%02x, [12,11]=%02x_%02x [13]=" "%02x\n", dsamps, dfcyc, pair_num, reg_addr_latch, ora, ay8913ptr->regs[3], ay8913ptr->regs[2], ay8913ptr->regs[6], ay8913ptr->regs[7], ay8913ptr->regs[9], ay8913ptr->regs[12], ay8913ptr->regs[11], ay8913ptr->regs[13]); } sound_play(dfcyc); } ay8913ptr->regs[reg_addr_latch] = ora; if(reg_addr_latch == 13) { // Envelope control ay8913ptr->env_dsamp &= 0x1fffffffffffULL; // Clear "hold" in (env_val & (0x80 << 40)) } } } void mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, dword64 dfcyc) { Mos6522 *mos6522ptr; Ay8913 *ay8913ptr; mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); // printf("ay8913 %d control now:%02x\n", pair_num, new_val); // new_val and prev_val are { reset_l, BDIR, BC1 } // 4=Idle; 5=Read; 6=Write; 7=Latch_addr // Latch new address and write data at the time the ctl changes to Idle // Do read as soon as the ctl indicates to do a read. if((new_val & 4) == 0) { mock_ay8913_reset(pair_num, dfcyc); return; } new_val = new_val & 7; prev_val = prev_val & 7; if(prev_val == 7) { // Latch new address, latch it now ay8913ptr->reg_addr_latch = mos6522ptr->ora; } else if(prev_val == 6) { // Write data, do it now mock_ay8913_reg_write(pair_num, dfcyc); } if(new_val == 5) { mock_ay8913_reg_read(pair_num); } } void mockingboard_show(int got_num, word32 disable_mask) { int i, j; if(got_num) { g_mockingboard.disable_mask = disable_mask; } printf("g_mockingboard.disable_mask:%02x\n", g_mockingboard.disable_mask); for(i = 0; i < 2; i++) { for(j = 0; j < 14; j++) { printf("Mockingboard pair[%d].reg[%d]=%02x\n", i, j, g_mockingboard.pair[i].ay8913.regs[j]); } } } ================================================ FILE: upstream/kegs/src/moremem.c ================================================ const char rcsid_moremem_c[] = "@(#)$KmKId: moremem.c,v 1.306 2024-09-15 13:56:12+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" extern char g_kegs_version_str[]; extern byte *g_memory_ptr; extern byte *g_dummy_memory1_ptr; extern byte *g_slow_memory_ptr; extern byte *g_rom_fc_ff_ptr; extern byte *g_rom_cards_ptr; extern word32 g_slow_mem_changed[]; extern int g_num_breakpoints; extern Break_point g_break_pts[]; extern Page_info page_info_rd_wr[]; extern int Verbose; extern int g_rom_version; extern int g_user_page2_shadow; extern Iwm g_iwm; extern int g_halt_sim; extern int g_config_control_panel; /* from iwm.c */ int g_num_shadow_all_banks = 0; #define IOR(val) ( (val) ? 0x80 : 0x00 ) extern int g_cur_a2_stat; int g_em_emubyte_cnt = 0; int g_paddle_buttons = 0; int g_irq_pending = 0; word32 g_c023_val = 0; word32 g_c029_val_some = 0x41; word32 g_c02b_val = 0x08; word32 g_c02d_int_crom = 0; word32 g_c033_data = 0; word32 g_c034_val = 0; word32 g_c035_shadow_reg = 0x08; // A bit set inhibits that shadowing // [6]=Inhibit I/O and LC, [4]=Inhibit Aux hires, [3]=Inh SHR // [2]=Inh hires pg 2, [1]=Inh hires pg 1, [0]=Inh text pages word32 g_c036_val_speed = 0x80; // [7]=Fast, [4]=Shadow in all banks, // [3:0]=slot 7..4 disk motor detect word32 g_c03ef_doc_ptr = 0; word32 g_c041_val = 0; /* C041_EN_25SEC_INTS, C041_EN_MOVE_INTS */ word32 g_c046_val = 0; word32 g_c05x_annuncs = 0; word32 g_c068_statereg = 0; // [7]=ALTZP, [6]=PAGE2, [5]=RAMRD, [4]=RAMWRT // [3]=RDROM, [2]=LCBANK2, [1]=ROMBANK, [0]=INTCXROM // KEGS special: [8]=PREWRITE_LC, [9]=WRDEFRAM, // [10]=INTC8ROM word32 g_c06d_val = 0; word32 g_c06f_val = 0; word32 g_zipgs_unlock = 0; word32 g_zipgs_reg_c059 = 0x5f; // 7=LC cache dis, 6==5ms paddle del en, 5==5ms ext del en, // 4==5ms c02e enab, 3==CPS follow enab, 2-0: 111 word32 g_zipgs_reg_c05a = 0x0f; // 7:4 = current ZIP speed, 0=100%, 1=93.75%, F=6.25% // 3:0: always 1111 word32 g_zipgs_reg_c05b = 0x40; // 7==1ms clock, 6==cshupd: tag data at c05f updated // 5==LC cache disable, 4==bd is disabled, 3==delay in effect, // 2==rombank, 1-0==ram size (00:8K, 01=16K, 10=32K, 11=64K) word32 g_zipgs_reg_c05c = 0x00; // 7:1==slot delay enable (for 52-54ms), 0==speaker 5ms delay word32 g_c06c_latched_cyc = 0; #define SLINKY_RAM_SIZE 0x100000 /* 1MB */ word32 g_slinky_addr = 0; byte g_slinky_ram[SLINKY_RAM_SIZE]; #define EMUSTATE(a) { #a, &a } Emustate_intlist g_emustate_intlist[] = { // EMUSTATE(g_cur_a2_stat), // EMUSTATE(g_paddle_buttons), // EMUSTATE(g_em_emubyte_cnt), // EMUSTATE(g_irq_pending), EMUSTATE(g_c023_val), EMUSTATE(g_c029_val_some), EMUSTATE(g_c02b_val), EMUSTATE(g_c02d_int_crom), EMUSTATE(g_c033_data), EMUSTATE(g_c034_val), EMUSTATE(g_c035_shadow_reg), EMUSTATE(g_c036_val_speed), EMUSTATE(g_c041_val), EMUSTATE(g_c046_val), EMUSTATE(g_c05x_annuncs), EMUSTATE(g_c068_statereg), EMUSTATE(g_zipgs_unlock), EMUSTATE(g_zipgs_reg_c059), EMUSTATE(g_zipgs_reg_c05a), EMUSTATE(g_zipgs_reg_c05b), EMUSTATE(g_zipgs_reg_c05c), { 0, 0, } }; extern dword64 g_paddle_trig_dfcyc; extern dword64 g_last_vbl_dfcyc; Emustate_dword64list g_emustate_dword64list[] = { EMUSTATE(g_paddle_trig_dfcyc), EMUSTATE(g_last_vbl_dfcyc), { 0, 0, } }; extern word32 g_mem_size_total; Emustate_word32list g_emustate_word32list[] = { EMUSTATE(g_mem_size_total), EMUSTATE(g_c03ef_doc_ptr), { 0, 0, } }; #define UNIMPL_READ \ halt_printf("UNIMP READ to addr %08x\n", loc); \ return 0; #define UNIMPL_WRITE \ halt_printf("UNIMP WRITE to addr %08x, val: %04x\n", loc, val); \ return; void fixup_brks() { word32 page, tmp, tmp2, end_page; Pg_info val; int is_wr_only, num; int i; num = g_num_breakpoints; for(i = 0; i < num; i++) { page = (g_break_pts[i].start_addr >> 8) & 0xffff; end_page = (g_break_pts[i].end_addr >> 8) & 0xffff; is_wr_only = (g_break_pts[i].start_addr >> 24) & 1; while(page <= end_page) { if(!is_wr_only) { val = GET_PAGE_INFO_RD(page); tmp = PTR2WORD(val) & 0xff; tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; SET_PAGE_INFO_RD(page, val - tmp + tmp2); } val = GET_PAGE_INFO_WR(page); tmp = PTR2WORD(val) & 0xff; tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; SET_PAGE_INFO_WR(page, val - tmp + tmp2); page++; } } } void fixup_hires_on() { if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { return; } fixup_bank0_2000_4000(); fixup_brks(); } void fixup_bank0_2000_4000() { byte *mem0rd; byte *mem0wr; word32 start_page; int i; // Do banks $00 and $E0 for(i = 0; i < 2; i++) { mem0rd = &(g_memory_ptr[0x2000]); start_page = 0x20; if(i) { start_page += 0xe000; // Bank $E0 mem0rd = &(g_slow_memory_ptr[0x2000]); } mem0wr = mem0rd; if((g_cur_a2_stat & ALL_STAT_ST80) && (g_cur_a2_stat & ALL_STAT_HIRES)) { if(g_cur_a2_stat & ALL_STAT_PAGE2) { mem0rd += 0x10000; mem0wr += 0x10000; if(((g_c035_shadow_reg & 0x12) == 0) || ((g_c035_shadow_reg & 0x8) == 0) || i) { mem0wr += BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { mem0wr += BANK_SHADOW; } } else { if(RAMRD) { mem0rd += 0x10000; } if(RAMWRT) { mem0wr += 0x10000; if(((g_c035_shadow_reg & 0x12) == 0) || ((g_c035_shadow_reg & 0x8) == 0) || i) { mem0wr += BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { mem0wr += BANK_SHADOW; } } fixup_any_bank_any_page(start_page, 0x20, mem0rd, mem0wr); } } void fixup_bank0_0400_0800() { byte *mem0rd; byte *mem0wr; word32 start_page, shadow; int i; for(i = 0; i < 2; i++) { mem0rd = &(g_memory_ptr[0x400]); start_page = 4; if(i) { start_page += 0xe000; // Bank $E0 mem0rd = &(g_slow_memory_ptr[0x400]); } mem0wr = mem0rd; shadow = BANK_SHADOW; if(g_cur_a2_stat & ALL_STAT_ST80) { if(g_cur_a2_stat & ALL_STAT_PAGE2) { shadow = BANK_SHADOW2; mem0rd += 0x10000; mem0wr += 0x10000; } } else { if(RAMWRT) { shadow = BANK_SHADOW2; mem0wr += 0x10000; } if(RAMRD) { mem0rd += 0x10000; } } if(((g_c035_shadow_reg & 0x01) == 0) || i) { mem0wr += shadow; } fixup_any_bank_any_page(start_page, 4, mem0rd, mem0wr); } } void fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, byte *mem0wr) { int i; for(i = 0; i < num_pages; i++) { SET_PAGE_INFO_RD(i + start_page, mem0rd); mem0rd += 0x100; } for(i = 0; i < num_pages; i++) { SET_PAGE_INFO_WR(i + start_page, mem0wr); mem0wr += 0x100; } } void fixup_intcx() { byte *rom10000; byte *rom_inc; word32 intcx, mask; int no_io_shadow, off, start_k; int j, k; rom10000 = &(g_rom_fc_ff_ptr[0x30000]); no_io_shadow = (g_c035_shadow_reg & 0x40); start_k = 0; if(no_io_shadow) { /* if not shadowing, banks 0 and 1 are not affected by intcx */ start_k = 2; } intcx = (g_c068_statereg & 1); // set means use internal rom // printf("fixup_intcx, intcx:%d, no_io:%d, c02d:%02x\n", intcx, // no_io_shadow, g_c02d_int_crom); for(k = start_k; k < 4; k++) { off = k; if(k >= 2) { off += (0xe0 - 2); } /* step off through 0x00, 0x01, 0xe0, 0xe1 */ off = off << 8; // Now 0x0000, 0x0100, 0xe000, 0xe100 SET_PAGE_INFO_RD(0xc0 + off, SET_BANK_IO); for(j = 0xc1; j < 0xc8; j++) { mask = 1 << (j & 0xf); rom_inc = SET_BANK_IO; if(((g_c02d_int_crom & mask) == 0) || intcx) { rom_inc = rom10000 + (j << 8); } else { // User-slot rom rom_inc = &(g_rom_cards_ptr[0]) + ((j - 0xc0) << 8); if(j == 0xc4) { // Mockingboard rom_inc = SET_BANK_IO; } } if((j == 0xc3) && !(g_c068_statereg & 0x400)) { // If INTC8ROM not set, we need to // watch for reads from C3xx to set it rom_inc = SET_BANK_IO; } SET_PAGE_INFO_RD(j + off, rom_inc); } for(j = 0xc8; j < 0xd0; j++) { /* c800 - cfff */ if(intcx || (g_c068_statereg & 0x400)) { // INTCXROM or INTC8ROM is set rom_inc = rom10000 + (j << 8); if((j == 0xcf) && (g_c068_statereg & 0x400)) { rom_inc = SET_BANK_IO; } } else { // c800 space not necessarily mapped rom_inc = SET_BANK_IO; } SET_PAGE_INFO_RD(j + off, rom_inc); } for(j = 0xc0; j < 0xd0; j++) { SET_PAGE_INFO_WR(j + off, SET_BANK_IO); } } fixup_brks(); } void fixup_st80col(dword64 dfcyc) { int cur_a2_stat; cur_a2_stat = g_cur_a2_stat; fixup_bank0_0400_0800(); if(cur_a2_stat & ALL_STAT_HIRES) { /* fixup no matter what PAGE2 since PAGE2 and RAMRD/WR */ /* can work against each other */ fixup_bank0_2000_4000(); } if(cur_a2_stat & ALL_STAT_PAGE2) { change_display_mode(dfcyc); } fixup_brks(); } void fixup_altzp() { byte *mem0rd; word32 start_page, altzp; int i; altzp = ALTZP; for(i = 0; i < 2; i++) { start_page = 0; mem0rd = &(g_memory_ptr[0]); if(i) { start_page = 0xe000; mem0rd = &(g_slow_memory_ptr[0]); } if(altzp) { mem0rd += 0x10000; } fixup_any_bank_any_page(start_page, 2, mem0rd, mem0rd); } /* No need for fixup_brks since called from set_statereg() */ } void fixup_page2(dword64 dfcyc) { if((g_cur_a2_stat & ALL_STAT_ST80)) { fixup_bank0_0400_0800(); if((g_cur_a2_stat & ALL_STAT_HIRES)) { fixup_bank0_2000_4000(); } } else { change_display_mode(dfcyc); } } void fixup_ramrd() { byte *mem0rd; word32 start_page, cur_a2_stat; int i, j; cur_a2_stat = g_cur_a2_stat; if((cur_a2_stat & ALL_STAT_ST80) == 0) { fixup_bank0_0400_0800(); } if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { fixup_bank0_2000_4000(); } for(i = 0; i < 2; i++) { start_page = 0; mem0rd = &(g_memory_ptr[0x0000]); if(i) { start_page = 0xe000; mem0rd = &(g_slow_memory_ptr[0]); } if(RAMRD) { mem0rd += 0x10000; } SET_PAGE_INFO_RD(start_page + 2, mem0rd + 0x200); SET_PAGE_INFO_RD(start_page + 3, mem0rd + 0x300); for(j = 8; j < 0x20; j++) { SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); } for(j = 0x40; j < 0xc0; j++) { SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); } } /* No need for fixup_brks since only called from set_statereg() */ } void fixup_ramwrt() { byte *mem0wr; word32 start_page, cur_a2_stat, shadow, ramwrt; int i, j; cur_a2_stat = g_cur_a2_stat; if((cur_a2_stat & ALL_STAT_ST80) == 0) { fixup_bank0_0400_0800(); } if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { fixup_bank0_2000_4000(); } for(i = 0; i < 2; i++) { start_page = 0; mem0wr = &(g_memory_ptr[0x0000]); if(i) { start_page = 0xe000; mem0wr = &(g_slow_memory_ptr[0]); } ramwrt = RAMWRT; if(ramwrt) { mem0wr += 0x10000; } SET_PAGE_INFO_WR(start_page + 2, mem0wr + 0x200); SET_PAGE_INFO_WR(start_page + 3, mem0wr + 0x300); shadow = BANK_SHADOW; if(ramwrt) { shadow = BANK_SHADOW2; } if( ((g_c035_shadow_reg & 0x20) != 0) || ((g_rom_version == 1) && !g_user_page2_shadow)) { if(!i) { shadow = 0; } } for(j = 8; j < 0x0c; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } for(j = 0xc; j < 0x20; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); } shadow = 0; if(ramwrt) { if(((g_c035_shadow_reg & 0x14) == 0) || ((g_c035_shadow_reg & 0x08) == 0) || i) { shadow = BANK_SHADOW2; } } else if(((g_c035_shadow_reg & 0x04) == 0) || i) { shadow = BANK_SHADOW; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } shadow = 0; if(ramwrt && (((g_c035_shadow_reg & 0x08) == 0) || i)) { /* shr shadowing */ shadow = BANK_SHADOW2; } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100 + shadow); } for(j = 0xa0; j < 0xc0; j++) { SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); } } /* No need for fixup_brks() since only called from set_statereg() */ } void fixup_lc() { byte *mem0rd, *mem0wr; word32 bank, lcbank2, c08x_wrdefram, rdrom; int k; // Fixup memory from 0xd000 - 0xffff in banks 0, 1, $e0, $e1 for(k = 0; k < 4; k++) { bank = k; mem0rd = &(g_memory_ptr[k << 16]); if(k >= 2) { bank += (0xe0 - 2); //k==2->bank=$e0, k==3->bank=$e1 mem0rd = &(g_slow_memory_ptr[(k & 1) << 16]); } /* step bank through 0x00, 0x01, 0xe0, 0xe1 */ if(((k & 1) == 0) && ALTZP) { mem0rd += 0x10000; } lcbank2 = LCBANK2; c08x_wrdefram = g_c068_statereg & 0x200; rdrom = RDROM; if((k < 2) && (g_c035_shadow_reg & 0x40)) { lcbank2 = 0; c08x_wrdefram = 1; rdrom = 0; } mem0wr = mem0rd; if((k < 2) && !c08x_wrdefram) { mem0wr += (BANK_IO_TMP | BANK_IO2_TMP); } if((k < 2) && rdrom) { mem0rd = &(g_rom_fc_ff_ptr[0x30000]); } fixup_any_bank_any_page(bank*0x100 + 0xe0, 0x20, mem0rd + 0xe000, mem0wr + 0xe000); if(! lcbank2) { // lcbank1, 0xd000 is ram at 0xc000-cfff if(!rdrom) { mem0rd -= 0x1000; } mem0wr -= 0x1000; } fixup_any_bank_any_page(bank*0x100 + 0xd0, 0x10, mem0rd + 0xd000, mem0wr + 0xd000); } /* No need for fixup_brks() since only called from set_statereg(), */ /* or from other routines which will handle it */ } void set_statereg(dword64 dfcyc, word32 val) { word32 xor; dbg_log_info(dfcyc, val, g_c068_statereg, 0xc068); xor = val ^ g_c068_statereg; g_c068_statereg = val; if(xor == 0) { return; } if(xor & 0x28c) { // WRDEFRAM, ALTZP, RDROM, LCBANK2 fixup_lc(); if(xor & 0x80) { /* altzp */ fixup_altzp(); } } if(xor & 0x40) { // page2 g_cur_a2_stat = (g_cur_a2_stat & ~ALL_STAT_PAGE2) | (val & ALL_STAT_PAGE2); fixup_page2(dfcyc); } if(xor & 0x20) { // RAMRD fixup_ramrd(); } if(xor & 0x10) { // RAMWRT fixup_ramwrt(); } if(xor & 0x02) { // ROMBANK halt_printf("Just set rombank = %d\n", ROMB); } if(xor & 0x401) { // INTC8ROM, INTCX fixup_intcx(); } if(xor) { fixup_brks(); } } void fixup_shadow_txt1() { byte *mem0wr; int j; fixup_bank0_0400_0800(); mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x01) == 0) { mem0wr += BANK_SHADOW2; } for(j = 4; j < 8; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_txt2() { byte *mem0wr; int shadow; int j; /* bank 0 */ mem0wr = &(g_memory_ptr[0x00000]); shadow = BANK_SHADOW; if(RAMWRT) { mem0wr += 0x10000; shadow = BANK_SHADOW2; } if(((g_c035_shadow_reg & 0x20) == 0) && ((g_rom_version != 1) || g_user_page2_shadow)) { mem0wr += shadow; } for(j = 8; j < 0xc; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if(((g_c035_shadow_reg & 0x20) == 0) && ((g_rom_version != 1) || g_user_page2_shadow)) { mem0wr += BANK_SHADOW2; } for(j = 8; j < 0xc; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_hires1() { byte *mem0wr; int j; fixup_bank0_2000_4000(); /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x12) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x20; j < 0x40; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_hires2() { byte *mem0wr; int j; /* bank 0 */ mem0wr = &(g_memory_ptr[0x00000]); if(RAMWRT) { mem0wr += 0x10000; if((g_c035_shadow_reg & 0x14) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } } else if((g_c035_shadow_reg & 0x04) == 0) { mem0wr += BANK_SHADOW; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x14) == 0 || (g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x40; j < 0x60; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_shr() { byte *mem0wr; int j; /* bank 0, only pages 0x60 - 0xa0 */ mem0wr = &(g_memory_ptr[0x00000]); if(RAMWRT) { mem0wr += 0x10000; if((g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(j, mem0wr + j*0x100); } /* and bank 1, only pages 0x60 - 0xa0 */ mem0wr = &(g_memory_ptr[0x10000]); if((g_c035_shadow_reg & 0x8) == 0) { mem0wr += BANK_SHADOW2; } for(j = 0x60; j < 0xa0; j++) { SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); } } void fixup_shadow_iolc() { byte *mem0rd; int k; if(g_c035_shadow_reg & 0x40) { /* Disable language card area */ for(k = 0; k < 2; k++) { mem0rd = &(g_memory_ptr[k << 16]); fixup_any_bank_any_page((k << 8) + 0xc0, 0x10, mem0rd + 0xd000, mem0rd + 0xd000); if(k == 0 && ALTZP) { mem0rd += 0x10000; } fixup_any_bank_any_page((k << 8) + 0xd0, 0x10, mem0rd + 0xc000, mem0rd + 0xc000); fixup_any_bank_any_page((k << 8) + 0xe0, 0x20, mem0rd + 0xe000, mem0rd + 0xe000); } } else { /* 0xc000 area */ fixup_intcx(); // Fix 0xd000-0xffff banks 0, 1, 0xe0, 0xe1 fixup_lc(); } } void update_shadow_reg(dword64 dfcyc, word32 val) { int xor; dbg_log_info(dfcyc, val, g_c035_shadow_reg, 0xc035); if(g_c035_shadow_reg == val) { return; } xor = g_c035_shadow_reg ^ val; g_c035_shadow_reg = val; if(xor & 8) { fixup_shadow_hires1(); fixup_shadow_hires2(); fixup_shadow_shr(); xor = xor & (~0x16); } if(xor & 0x10) { fixup_shadow_hires1(); fixup_shadow_hires2(); xor = xor & (~0x6); } if(xor & 2) { fixup_shadow_hires1(); } if(xor & 4) { fixup_shadow_hires2(); } if(xor & 1) { fixup_shadow_txt1(); } if((xor & 0x20) && ((g_rom_version != 1) || g_user_page2_shadow)) { fixup_shadow_txt2(); } if(xor & 0x40) { fixup_shadow_iolc(); } if(xor) { fixup_brks(); } } void fixup_shadow_all_banks() { byte *mem0rd; int shadow; int num_banks; int j, k; /* Assume Ninja Force Megademo */ /* only do banks 3 - num_banks by 2, shadowing into e1 */ shadow = 0; if((g_c036_val_speed & 0x10) && ((g_c035_shadow_reg & 0x08) == 0)) { shadow = BANK_SHADOW2; } num_banks = g_mem_size_total >> 16; for(k = 3; k < num_banks; k += 2) { mem0rd = &(g_memory_ptr[k*0x10000 + 0x2000]) + shadow; for(j = 0x20; j < 0xa0; j++) { SET_PAGE_INFO_WR(k*0x100 + j, mem0rd); mem0rd += 0x100; } } fixup_brks(); } void setup_pageinfo() { byte *mem0rd; word32 mem_size_pages; /* first, set all of memory to point to itself */ mem_size_pages = g_mem_size_total >> 8; mem0rd = &(g_memory_ptr[0]); fixup_any_bank_any_page(0, mem_size_pages, mem0rd, mem0rd); /* mark unused memory as BAD_MEM */ fixup_any_bank_any_page(mem_size_pages, 0xfc00-mem_size_pages, BANK_BAD_MEM, BANK_BAD_MEM); fixup_shadow_all_banks(); /* ROM */ mem0rd = &(g_rom_fc_ff_ptr[0]); fixup_any_bank_any_page(0xfc00, 0x400, mem0rd, mem0rd + (BANK_IO_TMP | BANK_IO2_TMP)); /* banks e0, e1 */ mem0rd = &(g_slow_memory_ptr[0]); fixup_any_bank_any_page(0xe000, 0x200, mem0rd, mem0rd); // First, did all 128KB fixup_any_bank_any_page(0xe004, 0x08, mem0rd + 0x0400, mem0rd + 0x0400 + BANK_SHADOW); fixup_any_bank_any_page(0xe020, 0x80, mem0rd + 0x2000, mem0rd + 0x2000 + BANK_SHADOW); mem0rd = &(g_slow_memory_ptr[0x10000]); fixup_any_bank_any_page(0xe104, 0x08, mem0rd + 0x0400, mem0rd + 0x0400 + BANK_SHADOW2); fixup_any_bank_any_page(0xe120, 0x80, mem0rd + 0x2000, mem0rd + 0x2000 + BANK_SHADOW2); fixup_intcx(); /* correct banks 0xe0,0xe1, 0xc000-0xcfff area */ fixup_lc(); /* correct 0xd000-0xdfff area */ fixup_bank0_2000_4000(); fixup_bank0_0400_0800(); fixup_altzp(); fixup_ramrd(); fixup_ramwrt(); fixup_shadow_txt1(); fixup_shadow_txt2(); fixup_shadow_hires1(); fixup_shadow_hires2(); fixup_shadow_shr(); fixup_shadow_iolc(); fixup_brks(); } void show_bankptrs_bank0rdwr() { show_bankptrs(0); show_bankptrs(1); show_bankptrs(0xe0); show_bankptrs(0xe1); printf("statereg: %02x\n", g_c068_statereg); } void show_bankptrs(int bnk) { int i; Pg_info rd, wr; byte *ptr_rd, *ptr_wr; printf("g_memory_ptr: %p, dummy_mem: %p, slow_mem_ptr: %p\n", g_memory_ptr, g_dummy_memory1_ptr, g_slow_memory_ptr); printf("g_rom_fc_ff_ptr: %p\n", g_rom_fc_ff_ptr); printf("Showing bank_info array for %02x\n", bnk); for(i = 0; i < 256; i++) { rd = GET_PAGE_INFO_RD(bnk*0x100 + i); wr = GET_PAGE_INFO_WR(bnk*0x100 + i); ptr_rd = (byte *)rd; ptr_wr = (byte *)wr; printf("%04x rd: ", bnk*256 + i); show_addr(ptr_rd); printf(" wr: "); show_addr(ptr_wr); printf("\n"); } } void show_addr(byte *ptr) { word32 mem_size; mem_size = g_mem_size_total; if(ptr >= g_memory_ptr && ptr < &g_memory_ptr[mem_size]) { printf("%p--memory[%06x]", ptr, (word32)(ptr - g_memory_ptr)); } else if(ptr >= g_rom_fc_ff_ptr && ptr < &g_rom_fc_ff_ptr[256*1024]) { printf("%p--rom_fc_ff[%06x]", ptr, (word32)(ptr - g_rom_fc_ff_ptr)); } else if(ptr >= g_slow_memory_ptr && ptr<&g_slow_memory_ptr[128*1024]){ printf("%p--slow_memory[%06x]", ptr, (word32)(ptr - g_slow_memory_ptr)); } else if(ptr >=g_dummy_memory1_ptr && ptr < &g_dummy_memory1_ptr[256]){ printf("%p--dummy_memory[%06x]", ptr, (word32)(ptr - g_dummy_memory1_ptr)); } else { printf("%p--unknown", ptr); } } word32 moremem_fix_vector_pull(word32 addr) { // Default vector for BRK will come from 0xfffffe. But if // I/O shadowing is off, or we're a //e, then get from bank0 if((g_c035_shadow_reg & 0x40) || (g_rom_version == 0)) { // I/O shadowing off, or Apple //e: use RAM loc addr = addr & 0xffff; } return addr; } word32 io_read(word32 loc, dword64 *cyc_ptr) { dword64 dfcyc; word32 val; word32 new_lcbank2, new_wrdefram, new_prewrite, new_rdrom, tmp; int i; dfcyc = *cyc_ptr; /* IO space */ switch((loc >> 8) & 0xf) { case 0: /* 0xc000 - 0xc0ff */ switch(loc & 0xff) { /* 0xc000 - 0xc00f */ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: return(adb_read_c000()); /* 0xc010 - 0xc01f */ case 0x10: /* c010 */ return(adb_access_c010()); case 0x11: /* c011 = RDLCBANK2 */ return IOR(LCBANK2); case 0x12: /* c012= RDLCRAM */ return IOR(!RDROM); case 0x13: /* c013=rdramd */ return IOR(RAMRD); case 0x14: /* c014=rdramwrt */ return IOR(RAMWRT); case 0x15: /* c015 = RDCXROM = INTCX */ return IOR(g_c068_statereg & 1); case 0x16: /* c016: Read ALTZP, 0 = Read Main ZP; 1 = Alt ZP */ return IOR(ALTZP); case 0x17: /* c017: rdc3rom */ return IOR(g_c02d_int_crom & (1 << 3)); case 0x18: /* c018: rd80c0l */ return IOR((g_cur_a2_stat & ALL_STAT_ST80)); case 0x19: /* c019: rdvblbar: MSB set if in VBL */ tmp = in_vblank(dfcyc); if(g_rom_version == 0) { // Apple //e tmp = tmp ^ 1; // 1=not in VBL on //e } return IOR(tmp); case 0x1a: /* c01a: rdtext */ return IOR(g_cur_a2_stat & ALL_STAT_TEXT); case 0x1b: /* c01b: rdmix */ return IOR(g_cur_a2_stat & ALL_STAT_MIX_T_GR); case 0x1c: /* c01c: rdpage2 */ return IOR(g_cur_a2_stat & ALL_STAT_PAGE2); case 0x1d: /* c01d: rdhires */ return IOR(g_cur_a2_stat & ALL_STAT_HIRES); case 0x1e: /* c01e: altcharset on? */ return IOR(g_cur_a2_stat & ALL_STAT_ALTCHARSET); case 0x1f: /* c01f: rd80vid */ return IOR(g_cur_a2_stat & ALL_STAT_VID80); /* 0xc020 - 0xc02f */ case 0x20: /* 0xc020 */ /* Click cassette port */ return float_bus(dfcyc); case 0x21: /* 0xc021 */ /* Not documented, but let's return COLOR_C021 */ return IOR(g_cur_a2_stat & ALL_STAT_COLOR_C021); case 0x22: /* 0xc022 */ return (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; case 0x23: /* 0xc023 */ return g_c023_val; case 0x24: /* 0xc024 */ return mouse_read_c024(dfcyc); case 0x25: /* 0xc025 */ return adb_read_c025(); case 0x26: /* 0xc026 */ return adb_read_c026(); case 0x27: /* 0xc027 */ return adb_read_c027(); case 0x28: /* 0xc028 */ return 0; case 0x29: /* 0xc029 */ return((g_cur_a2_stat & 0xa0) | g_c029_val_some); case 0x2a: /* 0xc02a */ #if 0 printf("Reading c02a...returning 0\n"); #endif return 0; case 0x2b: /* 0xc02b */ return g_c02b_val; case 0x2c: /* 0xc02c */ /* printf("reading c02c, returning 0\n"); */ if(g_c06d_val == 0x40) { // Test mode $40 return read_video_data(dfcyc); } return 0; case 0x2d: /* 0xc02d */ return g_c02d_int_crom; case 0x2e: /* 0xc02e */ case 0x2f: /* 0xc02f */ return read_vid_counters(loc, dfcyc); /* 0xc030 - 0xc03f */ case 0x30: /* 0xc030 */ /* click speaker */ return sound_read_c030(dfcyc); case 0x31: /* 0xc031 */ /* 3.5" control */ return g_iwm.state & 0xc0; case 0x32: /* 0xc032 */ /* scan int */ return 0; case 0x33: /* 0xc033 = CLOCKDATA*/ return g_c033_data; case 0x34: /* 0xc034 = CLOCKCTL */ return g_c034_val; case 0x35: /* 0xc035 */ return g_c035_shadow_reg; case 0x36: /* 0xc036 = CYAREG */ return g_c036_val_speed; case 0x37: /* 0xc037 */ return 0; case 0x38: /* 0xc038 */ return scc_read_reg(dfcyc, 1); case 0x39: /* 0xc039 */ return scc_read_reg(dfcyc, 0); case 0x3a: /* 0xc03a */ return scc_read_data(dfcyc, 1); case 0x3b: /* 0xc03b */ return scc_read_data(dfcyc, 0); case 0x3c: /* 0xc03c */ /* doc control */ return doc_read_c03c(); case 0x3d: /* 0xc03d */ return doc_read_c03d(dfcyc); case 0x3e: /* 0xc03e */ return (g_c03ef_doc_ptr & 0xff); case 0x3f: /* 0xc03f */ return (g_c03ef_doc_ptr >> 8); /* 0xc040 - 0xc04f */ case 0x40: /* 0xc040 */ /* cassette */ return 0; case 0x41: /* 0xc041 */ return g_c041_val; case 0x45: /* 0xc045 */ //halt_printf("Mega II mouse read: c045\n"); return 0; case 0x46: /* 0xc046 */ tmp = g_c046_val; g_c046_val = (tmp & 0xbf) + ((tmp & 0x80) >> 1); return tmp; case 0x47: /* 0xc047 */ remove_irq(IRQ_PENDING_C046_25SEC | IRQ_PENDING_C046_VBL); g_c046_val &= 0xe7; /* clear vbl_int, 1/4sec int*/ return 0; case 0x42: /* 0xc042 */ case 0x43: /* 0xc043 */ return 0; case 0x4f: /* 0xc04f */ /* for information on c04f, see: */ /* www.sheppyware.net/tech/hardware/softswitches.html */ /* write to $c04f to start. Then read $c04f to get */ /* emulator ($16=sweet16, $fe=bernie II). */ /* Then read again to get version: $21 == 2.1 */ switch(g_em_emubyte_cnt) { case 1: g_em_emubyte_cnt = 2; return 'K'; case 2: g_em_emubyte_cnt = 0; tmp = g_kegs_version_str[0] - '0'; i = g_kegs_version_str[2] - '0'; return ((tmp & 0xf) << 4) + (i & 0xf); default: g_em_emubyte_cnt = 0; return 0; } case 0x44: /* 0xc044 */ case 0x48: /* 0xc048 */ case 0x49: /* 0xc049 */ case 0x4a: /* 0xc04a */ case 0x4b: /* 0xc04b */ case 0x4c: /* 0xc04c */ case 0x4d: /* 0xc04d */ case 0x4e: /* 0xc04e */ UNIMPL_READ; /* 0xc050 - 0xc05f */ case 0x50: /* 0xc050 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_TEXT) { g_cur_a2_stat &= (~ALL_STAT_TEXT); change_display_mode(dfcyc); } return val; case 0x51: /* 0xc051 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { g_cur_a2_stat |= (ALL_STAT_TEXT); change_display_mode(dfcyc); } return val; case 0x52: /* 0xc052 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return val; case 0x53: /* 0xc053 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return val; case 0x54: /* 0xc054 */ val = float_bus(dfcyc); set_statereg(dfcyc, g_c068_statereg & (~0x40)); return val; return float_bus(dfcyc); case 0x55: /* 0xc055 */ val = float_bus(dfcyc); set_statereg(dfcyc, g_c068_statereg | 0x40); return val; case 0x56: /* 0xc056 */ val = float_bus(dfcyc); if(g_cur_a2_stat & ALL_STAT_HIRES) { g_cur_a2_stat &= (~ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return val; case 0x57: /* 0xc057 */ val = float_bus(dfcyc); if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { g_cur_a2_stat |= (ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return val; case 0x58: /* 0xc058 */ if(g_zipgs_unlock < 4) { g_c05x_annuncs &= (~1); } return 0; case 0x59: /* 0xc059 */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c059; } else { g_c05x_annuncs |= 1; } return 0; case 0x5a: /* 0xc05a */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c05a; } else { g_c05x_annuncs &= (~2); } return 0; case 0x5b: /* 0xc05b */ if(g_zipgs_unlock >= 4) { // Bit 7 is 1msclk, clock with 1ms period. tmp = (dfcyc >> 25) & 1; return (tmp << 7) + (g_zipgs_reg_c05b & 0x7f); } else { g_c05x_annuncs |= 2; } return 0; case 0x5c: /* 0xc05c */ if(g_zipgs_unlock >= 4) { return g_zipgs_reg_c05c; } else { g_c05x_annuncs &= (~4); } return 0; case 0x5d: /* 0xc05d */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05d!\n"); } else { g_c05x_annuncs |= 4; } return 0; case 0x5e: /* 0xc05e */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05e!\n"); } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return 0; case 0x5f: /* 0xc05f */ if(g_zipgs_unlock >= 4) { halt_printf("Reading ZipGS $c05f!\n"); } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { g_cur_a2_stat |= (ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return 0; /* 0xc060 - 0xc06f */ case 0x60: /* 0xc060 */ return IOR(g_paddle_buttons & 8); case 0x61: /* 0xc061 */ return IOR(adb_is_cmd_key_down() || g_paddle_buttons & 1); case 0x62: /* 0xc062 */ return IOR(adb_is_option_key_down() || g_paddle_buttons & 2); case 0x63: /* 0xc063 */ return IOR(g_paddle_buttons & 4); case 0x64: /* 0xc064 */ return read_paddles(dfcyc, 0); case 0x65: /* 0xc065 */ return read_paddles(dfcyc, 1); case 0x66: /* 0xc066 */ return read_paddles(dfcyc, 2); case 0x67: /* 0xc067 */ return read_paddles(dfcyc, 3); case 0x68: /* 0xc068 = STATEREG */ return g_c068_statereg & 0xff; case 0x69: /* 0xc069 */ /* Reserved reg, return 0 */ return 0; case 0x6a: /* 0xc06a */ case 0x6b: /* 0xc06b */ UNIMPL_READ; case 0x6c: /* 0xc06c */ case 0x6d: /* 0xc06d */ case 0x6e: /* 0xc06e */ case 0x6f: /* 0xc06f */ return g_c06c_latched_cyc >> (8 * (loc & 3)) & 0xff; /* 0xc070 - 0xc07f */ case 0x70: /* c070 */ paddle_trigger(dfcyc); return float_bus(dfcyc); case 0x71: /* 0xc071 */ case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: return g_rom_fc_ff_ptr[3*65536 + 0xc000 + (loc & 0xff)]; /* 0xc080 - 0xc08f */ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; // new_rdrom is set if loc[0] ^ loc[1] is true // 8=RDROM, 0=read from LC RAM new_prewrite = (loc & 1) << 8; // Odd read set PREWRITE new_wrdefram = g_c068_statereg & 0x200; if((loc & 1) == 0) { new_wrdefram = 0; // Any even access clrs } else { new_wrdefram |= (g_c068_statereg << 1) & 0x200; // Odd read also makes Ram wr if PREWR was set } set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | new_lcbank2 | new_rdrom | new_prewrite | new_wrdefram); // access 0-7: lcbank1, 8-f: lcbank0 // a0!=a1: set rdrom (c081,c082,c085,c086, etc.) // a0=1 && rd set prewrite. a0=0, or wr: clear prewrite // wrdefram is clr if a0=0, set if prewrite and // old_prewrite are set, otherwise stays same. // From Apple language card schematics: // wr_def = (wr_def_ff || (prewr && prewr_ff)) && a0; // prewr = read && a0 return float_bus(dfcyc); /* 0xc090 - 0xc09f */ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: /* UNIMPL_READ; */ return 0; /* 0xc0a0 - 0xc0af */ case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: return 0; /* UNIMPL_READ; */ /* 0xc0b0 - 0xc0bf */ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: return voc_devsel_read(loc, dfcyc); /* 0xc0c0 - 0xc0cf */ case 0xc0: case 0xc1: case 0xc2: case 0xc3: // Slot 4 has a Slinky RAM card return slinky_devsel_read(dfcyc, loc); case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: return 0; /* 0xc0d0 - 0xc0df */ case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7: case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: return 0; /* 0xc0e0 - 0xc0ef */ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: return read_iwm(loc, dfcyc); /* 0xc0f0 - 0xc0ff */ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: return 0; default: printf("loc: %04x bad\n", loc); UNIMPL_READ; } case 1: case 2: case 5: case 6: case 7: /* c100 - c7ff, (except c3xx, c4xx) */ return float_bus(dfcyc); case 3: // c300 return c3xx_read(dfcyc, loc); case 4: return mockingboard_read(loc, dfcyc); case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: return float_bus(dfcyc); // UNIMPL_READ; case 0xf: // $CFxx if(g_c068_statereg & 0x401) { // INTC8ROM or INTCX val = g_rom_fc_ff_ptr[0x3cf00 + (loc & 0xff)]; } else { val = float_bus(dfcyc); } if((loc & 0xfff) == 0xfff) { if(g_c068_statereg & 0x400) { set_statereg(dfcyc, g_c068_statereg & (~0x400)); } } return val; // UNIMPL_READ; } halt_printf("io_read: hit end, loc: %06x\n", loc); return 0xff; } void io_write(word32 loc, word32 val, dword64 *cyc_ptr) { dword64 dfcyc; word32 new_lcbank2, new_wrdefram, new_rdrom, new_prewrite, tmp; word32 new_tmp; int fixup; dfcyc = *cyc_ptr; val = val & 0xff; switch((loc >> 8) & 0xf) { case 0: /* 0xc000 - 0xc0ff */ switch(loc & 0xff) { /* 0xc000 - 0xc00f */ case 0x00: /* 0xc000: CLR80COL */ if(g_cur_a2_stat & ALL_STAT_ST80) { g_cur_a2_stat &= (~ALL_STAT_ST80); fixup_st80col(dfcyc); } return; case 0x01: /* 0xc001: SET80COL, enable 80-column store */ if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { g_cur_a2_stat |= (ALL_STAT_ST80); fixup_st80col(dfcyc); } return; case 0x02: /* 0xc002: Set RDMAINRAM */ set_statereg(dfcyc, g_c068_statereg & ~0x20); return; case 0x03: /* 0xc003: Set RDCARDRAM (alt) */ set_statereg(dfcyc, g_c068_statereg | 0x20); return; case 0x04: /* 0xc004: Set WRMAINRAM */ set_statereg(dfcyc, g_c068_statereg & ~0x10); return; case 0x05: /* 0xc005: Set WRCARDRAM (alt) */ set_statereg(dfcyc, g_c068_statereg | 0x10); return; case 0x06: /* 0xc006 = SETSLOTCXROM, use slot rom c800 */ set_statereg(dfcyc, g_c068_statereg & ~0x01); return; case 0x07: /* 0xc007 = SETINTXROM, use int rom c800 */ set_statereg(dfcyc, g_c068_statereg | 0x01); return; case 0x08: /* 0xc008: SETSTDZP (main ZP/LC) */ set_statereg(dfcyc, g_c068_statereg & ~0x80); return; case 0x09: /* 0xc009: SETALTZP (alt ZP/LC) */ set_statereg(dfcyc, g_c068_statereg | 0x80); return; case 0x0a: /* 0xc00a = SETINTC3ROM, use internal ROM slot 3*/ tmp = 1 << 3; if((g_c02d_int_crom & tmp) != 0) { g_c02d_int_crom &= ~tmp; fixup_intcx(); } return; case 0x0b: /* 0xc00b = SETSLOTC3ROM, use slot rom slot 3 */ tmp = 1 << 3; if((g_c02d_int_crom & tmp) == 0) { g_c02d_int_crom |= tmp; fixup_intcx(); } return; case 0x0c: /* 0xc00c = CLR80VID, disable 80 col hardware */ if(g_cur_a2_stat & ALL_STAT_VID80) { g_cur_a2_stat &= (~ALL_STAT_VID80); change_display_mode(dfcyc); } return; case 0x0d: /* 0xc00d: SET80VID, enable 80 col hardware */ if((g_cur_a2_stat & ALL_STAT_VID80) == 0) { g_cur_a2_stat |= (ALL_STAT_VID80); change_display_mode(dfcyc); } return; case 0x0e: /* 0xc00e: CLRALTCHAR */ if(g_cur_a2_stat & ALL_STAT_ALTCHARSET) { g_cur_a2_stat &= (~ALL_STAT_ALTCHARSET); change_display_mode(dfcyc); } return; case 0x0f: /* 0xc00f: SETALTCHAR */ if((g_cur_a2_stat & ALL_STAT_ALTCHARSET) == 0) { g_cur_a2_stat |= (ALL_STAT_ALTCHARSET); change_display_mode(dfcyc); } return; /* 0xc010 - 0xc01f */ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: adb_access_c010(); return; /* 0xc020 - 0xc02f */ case 0x20: /* 0xc020 */ /* WRITE CASSETTE?? */ return; case 0x21: /* 0xc021 */ new_tmp = ((val >> 7) & 1) << (31 - BIT_ALL_STAT_COLOR_C021); if((g_cur_a2_stat & ALL_STAT_COLOR_C021) != new_tmp) { g_cur_a2_stat ^= new_tmp; change_display_mode(dfcyc); } return; case 0x22: /* 0xc022 */ /* change text color */ tmp = (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; if(val != tmp) { /* change text/bg color! */ g_cur_a2_stat &= ~(ALL_STAT_TEXT_COLOR | ALL_STAT_BG_COLOR); g_cur_a2_stat += (val << BIT_ALL_STAT_BG_COLOR); change_display_mode(dfcyc); } return; case 0x23: /* 0xc023 */ if((val & 0x19) != 0) { halt_printf("c023 write of %02x!!!\n", val); } tmp = (g_c023_val & 0x70) | (val & 0x0f); if((tmp & 0x22) == 0x22) { add_irq(IRQ_PENDING_C023_SCAN); } if(!(tmp & 2)) { remove_irq(IRQ_PENDING_C023_SCAN); } if((tmp & 0x44) == 0x44) { add_irq(IRQ_PENDING_C023_1SEC); } if(!(tmp & 0x4)) { remove_irq(IRQ_PENDING_C023_1SEC); } if(g_irq_pending & (IRQ_PENDING_C023_SCAN | IRQ_PENDING_C023_1SEC)) { tmp |= 0x80; } g_c023_val = tmp; return; case 0x24: /* 0xc024 */ /* Write to mouse reg: Throw it away */ return; case 0x26: /* 0xc026 */ adb_write_c026(val); return; case 0x27: /* 0xc027 */ adb_write_c027(val); return; case 0x29: /* 0xc029 */ g_c029_val_some = val & 0x41; if((val & 1) == 0) { halt_printf("c029: %02x\n", val); } new_tmp = val & 0xa0; if(new_tmp != (g_cur_a2_stat & 0xa0)) { g_cur_a2_stat = (g_cur_a2_stat & (~0xa0)) + new_tmp; change_display_mode(dfcyc); } return; case 0x2a: /* 0xc02a */ #if 0 printf("Writing c02a with %02x\n", val); #endif return; case 0x2b: /* 0xc02b */ g_c02b_val = val; if((val != 0x08) && (val != 0x00)) { printf("Writing c02b with %02x\n", val); } return; case 0x2d: /* 0xc02d = Slot Reg. Bit set means use slot rom */ if((val & 0x9) != 0) { halt_printf("Illegal c02d write: %02x!\n", val); } fixup = (val != g_c02d_int_crom); g_c02d_int_crom = val; if(fixup) { vid_printf("Write c02d of %02x\n", val); fixup_intcx(); } return; case 0x28: /* 0xc028 */ case 0x2c: /* 0xc02c */ UNIMPL_WRITE; case 0x25: /* 0xc025 */ /* Space Shark writes to c025--ignore */ case 0x2e: /* 0xc02e */ case 0x2f: /* 0xc02f */ /* Modulae writes to this--just ignore them */ return; break; /* 0xc030 - 0xc03f */ case 0x30: /* 0xc030 */ #if 0 printf("Write speaker?\n"); #endif sound_write_c030(dfcyc); return; case 0x31: /* 0xc031 */ tmp = val ^ g_iwm.state; iwm_flush_cur_disk(); // In case APPLE35SEL changes g_iwm.state = (g_iwm.state & (~0xc0)) | (val & 0xc0); if(tmp & IWM_STATE_C031_APPLE35SEL) { /* apple35_sel changed, maybe speed change */ engine_recalc_events(); } return; case 0x32: /* 0xc032 */ tmp = g_c023_val & 0x7f; if(((val & 0x40) == 0) && (tmp & 0x40)) { /* clear 1 sec int */ remove_irq(IRQ_PENDING_C023_1SEC); tmp &= 0xbf; g_c023_val = tmp; } if(((val & 0x20) == 0) && (tmp & 0x20)) { /* clear scan line int */ remove_irq(IRQ_PENDING_C023_SCAN); g_c023_val = tmp & 0xdf; check_for_new_scan_int(dfcyc); } if(g_irq_pending & (IRQ_PENDING_C023_1SEC | IRQ_PENDING_C023_SCAN)) { g_c023_val |= 0x80; } if((val & 0x9f) != 0x9f) { irq_printf("c032: wrote %02x!\n", val); } return; case 0x33: /* 0xc033 = CLOCKDATA*/ g_c033_data = val; return; case 0x34: /* 0xc034 = CLOCKCTL */ tmp = val ^ g_c034_val; clock_write_c034(val); if(tmp & 0xf) { change_border_color(dfcyc, val & 0xf); } return; case 0x35: /* 0xc035 */ update_shadow_reg(dfcyc, val); return; case 0x36: /* 0xc036 = CYAREG */ tmp = val ^ g_c036_val_speed; g_c036_val_speed = (val & ~0x20); /* clr bit 5 */ if(tmp & 0x80) { /* to recalculate times since speed changing */ engine_recalc_events(); } if(tmp & 0xf) { /* slot_motor_detect changed */ engine_recalc_events(); } if((val & 0x60) != 0) { /* for ROM 03, 0x40 is the power-on status */ /* and can be read/write */ if(((val & 0x60) != 0x40) || (g_rom_version < 3)) { g_c036_val_speed &= (~0x60); halt_printf("c036: %2x\n", val); } } if(tmp & 0x10) { /* shadow in all banks! */ if(g_num_shadow_all_banks++ == 0) { printf("Shadowing all banks...This " "must be the NFC Megademo\n"); } fixup_shadow_all_banks(); } return; case 0x37: /* 0xc037 */ /* just ignore, probably someone writing c036 m=0 */ return; case 0x38: /* 0xc038 */ scc_write_reg(dfcyc, 1, val); return; case 0x39: /* 0xc039 */ scc_write_reg(dfcyc, 0, val); return; case 0x3a: /* 0xc03a */ scc_write_data(dfcyc, 1, val); return; case 0x3b: /* 0xc03b */ scc_write_data(dfcyc, 0, val); return; case 0x3c: /* 0xc03c */ /* doc ctl */ doc_write_c03c(dfcyc, val); return; case 0x3d: /* 0xc03d */ /* doc data reg */ doc_write_c03d(dfcyc, val); return; case 0x3e: /* 0xc03e */ g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff00) + val; return; case 0x3f: /* 0xc03f */ g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff) + (val << 8); return; /* 0xc040 - 0xc04f */ case 0x41: /* c041 */ g_c041_val = val & 0x1f; if((val & 0xe7) != 0) { halt_printf("write c041: %02x\n", val); } if(!(val & C041_EN_VBL_INTS)) { /* no more vbl interrupt */ remove_irq(IRQ_PENDING_C046_VBL); } if(!(val & C041_EN_25SEC_INTS)) { remove_irq(IRQ_PENDING_C046_25SEC); } return; case 0x46: /* c046 */ /* ignore writes to c046 */ return; case 0x47: /* c047 */ remove_irq(IRQ_PENDING_C046_VBL | IRQ_PENDING_C046_25SEC); g_c046_val &= 0xe7; /* clear vblint, 1/4sec int*/ return; case 0x48: /* c048 */ /* diversitune writes this--ignore it */ return; case 0x42: /* c042 */ case 0x43: /* c043 */ return; case 0x4f: /* c04f */ g_em_emubyte_cnt = 1; return; case 0x45: /* c045 */ return; case 0x40: /* c040 */ case 0x44: /* c044 */ case 0x49: /* c049 */ case 0x4a: /* c04a */ case 0x4b: /* c04b */ case 0x4c: /* c04c */ case 0x4d: /* c04d */ case 0x4e: /* c04e */ UNIMPL_WRITE; /* 0xc050 - 0xc05f */ case 0x50: /* 0xc050 */ if(g_cur_a2_stat & ALL_STAT_TEXT) { g_cur_a2_stat &= (~ALL_STAT_TEXT); change_display_mode(dfcyc); } return; case 0x51: /* 0xc051 */ if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { g_cur_a2_stat |= (ALL_STAT_TEXT); change_display_mode(dfcyc); } return; case 0x52: /* 0xc052 */ if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return; case 0x53: /* 0xc053 */ if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); change_display_mode(dfcyc); } return; case 0x54: /* 0xc054 */ set_statereg(dfcyc, g_c068_statereg & (~0x40)); return; case 0x55: /* 0xc055 */ set_statereg(dfcyc, g_c068_statereg | 0x40); return; case 0x56: /* 0xc056 */ if(g_cur_a2_stat & ALL_STAT_HIRES) { g_cur_a2_stat &= (~ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return; case 0x57: /* 0xc057 */ if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { g_cur_a2_stat |= (ALL_STAT_HIRES); fixup_hires_on(); change_display_mode(dfcyc); } return; case 0x58: /* 0xc058 */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c059 &= 0x4; /* last reset cold */ } else { g_c05x_annuncs &= (~1); } return; case 0x59: /* 0xc059 */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c059 = (val & 0xf8) | (g_zipgs_reg_c059 & 0x7); } else { g_c05x_annuncs |= 1; } return; case 0x5a: /* 0xc05a */ g_c05x_annuncs &= (~2); if((val & 0xf0) == 0x50) { g_zipgs_unlock++; } else if((val & 0xf0) == 0xa0) { g_zipgs_unlock = 0; } else if(g_zipgs_unlock >= 4) { if((g_zipgs_reg_c05b & 0x10) == 0) { /* to recalculate times */ engine_recalc_events(); } g_zipgs_reg_c05b |= 0x10; // disable } return; case 0x5b: /* 0xc05b */ if(g_zipgs_unlock >= 4) { if((g_zipgs_reg_c05b & 0x10) != 0) { /* to recalculate times */ engine_recalc_events(); } g_zipgs_reg_c05b &= (~0x10); // enable } else { g_c05x_annuncs |= 2; } return; case 0x5c: /* 0xc05c */ if(g_zipgs_unlock >= 4) { g_zipgs_reg_c05c = val; } else { g_c05x_annuncs &= (~4); } return; case 0x5d: /* 0xc05d */ if(g_zipgs_unlock >= 4) { if(((g_zipgs_reg_c05a ^ val) >= 0x10) && ((g_zipgs_reg_c05b & 0x10) == 0)) { engine_recalc_events(); } g_zipgs_reg_c05a = val | 0xf; } else { g_c05x_annuncs |= 4; } return; case 0x5e: /* 0xc05e: SETAN3, clear AN3, double-hires on */ if(g_zipgs_unlock >= 4) { /* Zippy writes 0x80 and 0x00 here... */ } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return; case 0x5f: /* 0xc05f: CLRAN3, set AN3, double-hires off */ if(g_zipgs_unlock >= 4) { halt_printf("Wrote ZipGS $c05f: %02x\n", val); } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { g_cur_a2_stat |= (ALL_STAT_ANNUNC3); change_display_mode(dfcyc); } return; /* 0xc060 - 0xc06f */ case 0x60: /* 0xc060 */ case 0x61: /* 0xc061 */ case 0x62: /* 0xc062 */ case 0x63: /* 0xc063 */ case 0x64: /* 0xc064 */ case 0x65: /* 0xc065 */ case 0x66: /* 0xc066 */ case 0x67: /* 0xc067 */ /* all the above do nothing--return */ return; case 0x68: /* 0xc068 = STATEREG */ set_statereg(dfcyc, (g_c068_statereg & ~0xff) | val); return; case 0x69: /* 0xc069 */ /* just ignore, someone writing c068 with m=0 */ return; case 0x6a: /* 0xc06a */ case 0x6b: /* 0xc06b */ UNIMPL_WRITE; case 0x6c: /* 0xc06c */ g_c06c_latched_cyc = (word32)(dfcyc >> 16); return; case 0x6d: /* 0xc06d */ // Affect what reads to $C02C can see, only $40 now if((g_c06f_val & 0x100) == 0) { // Locked val = 0; } g_c06d_val = val; // Test mode return; case 0x6e: /* 0xc06e */ g_c06f_val = 0; return; case 0x6f: /* 0xc06f */ if((g_c06f_val == 0xda) && (val == 0x61)) { val |= 0x100; // Unlock } g_c06f_val = val; return; /* 0xc070 - 0xc07f */ case 0x70: /* 0xc070 = Trigger paddles */ paddle_trigger(dfcyc); return; case 0x73: /* 0xc073 = multibank ram card bank addr? */ return; case 0x71: /* 0xc071 = another multibank ram card enable? */ case 0x7e: /* 0xc07e */ case 0x7f: /* 0xc07f */ return; case 0x72: /* 0xc072 */ case 0x74: /* 0xc074 */ case 0x75: /* 0xc075 */ case 0x76: /* 0xc076 */ case 0x77: /* 0xc077 */ case 0x78: /* 0xc078 */ case 0x79: /* 0xc079 */ case 0x7a: /* 0xc07a */ case 0x7b: /* 0xc07b */ case 0x7c: /* 0xc07c */ case 0x7d: /* 0xc07d */ return; /* 0xc080 - 0xc08f */ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; // new_rdrom is set if loc0 ^ loc1 is set // 8=RDROM, 0=read from LC RAM new_prewrite = 0; // Writes clear PREWRITE new_wrdefram = g_c068_statereg & 0x200; if((loc & 1) == 0) { new_wrdefram = 0; // Any even access clrs } set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | new_lcbank2 | new_rdrom | new_prewrite | new_wrdefram); return; /* 0xc090 - 0xc09f */ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: UNIMPL_WRITE; /* 0xc0a0 - 0xc0af */ case 0xa0: case 0xa1: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: UNIMPL_WRITE; case 0xa2: /* Burger Times writes here on error */ case 0xa8: /* Kurzweil SMP writes to 0xc0a8, ignore it */ return; /* 0xc0b0 - 0xc0bf */ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: voc_devsel_write(loc, val, dfcyc); return; /* 0xc0c0 - 0xc0cf */ case 0xc0: case 0xc1: case 0xc2: case 0xc3: // Slot 4 has a Slinky RAM card slinky_devsel_write(dfcyc, loc, val); return; case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: UNIMPL_WRITE; /* 0xc0d0 - 0xc0df */ case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7: case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: UNIMPL_WRITE; /* 0xc0e0 - 0xc0ef */ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: write_iwm(loc, val, dfcyc); return; /* 0xc0f0 - 0xc0ff */ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: UNIMPL_WRITE; default: printf("Write loc: %x\n",loc); exit(-300); } break; case 1: case 2: case 5: case 6: case 7: /* c1000 - c7ff (but not c3xx,c4xx) */ if((loc & 0xff) == 0) { // Frogger writes $ff to $Cx00 return; } UNIMPL_WRITE; case 3: if(((g_c02d_int_crom & 8) == 0) && ((g_c068_statereg & 0x400) == 0)) { // SLOTC3ROM clear, INTC8rom clear: Set INTC8ROM set_statereg(dfcyc, g_c068_statereg | 0x400); } return; case 4: if((g_c02d_int_crom & 0x10) && !(g_c068_statereg & 1)) { // Slot 4 is set to Your Card and not INTCX mockingboard_write(loc, val, dfcyc); return; } return; case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: // UNIMPL_WRITE; return; case 0xf: if((loc & 0xfff) == 0xfff) { /* cfff */ if(g_c068_statereg & 0x400) { set_statereg(dfcyc, g_c068_statereg & (~0x400)); } return; } // UNIMPL_WRITE; // Wings of Fury writes to $cf00-$cfff when reading a 0x300 // sector where it wants to load 0x200 to 0xd000-0xd1ff return; } printf("Huh2? Write loc: %x\n", loc); exit(-290); } word32 slinky_devsel_read(dword64 dfcyc, word32 loc) { word32 val; loc = loc & 0xf; val = 0; if(loc <= 2) { val = (g_slinky_addr >> (8*loc)) & 0xff; } if(loc == 3) { val = g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)]; dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c3); g_slinky_addr++; } return val; } void slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val) { word32 mask; loc = loc & 0xf; dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c0 + loc); if(loc <= 2) { mask = 0xff << (8 * loc); val = val * 0x010101; g_slinky_addr = (g_slinky_addr & (~mask)) | (val & mask); } if(loc == 3) { g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)] = val; g_slinky_addr++; } } word32 c3xx_read(dword64 dfcyc, word32 loc) { // We may have been marked IO so that we could set INTC8ROM, // but we still need to return ROM if(((g_c02d_int_crom & 8) == 0) && ((g_c068_statereg & 0x400) == 0)) { // SLOTC3ROM is not set: Set INTC8ROM set_statereg(dfcyc, g_c068_statereg | 0x400); } if(((g_c02d_int_crom & 8) == 0) || (g_c068_statereg & 1) || (g_rom_version == 0)) { // Access ROM for slot 3 return g_rom_fc_ff_ptr[0x3c300 + (loc & 0xff)]; } return float_bus(dfcyc); } // IIgs vertical line counters // 0x7d - 0x7f: in vbl, top of screen // 0x80 - 0xdf: not in vbl, drawing screen // 0xe0 - 0xff: in vbl, bottom of screen // Note: lines are then 0-0x60 effectively, for 192 lines, from 0x80-0xdf // vertical blanking engages on line 192, even if in super hires mode // (Last 8 lines in SHR are drawn with vbl_active set word32 get_lines_since_vbl(dword64 dfcyc) { double dusecs_since_last_vbl, dlines_since_vbl, dcyc_line_start; word32 lines_since_vbl; int offset; dusecs_since_last_vbl = (double)((dfcyc >> 16) - (g_last_vbl_dfcyc >> 16)); dlines_since_vbl = dusecs_since_last_vbl * (1.0 / 65.0); lines_since_vbl = (int)dlines_since_vbl; dcyc_line_start = (double)lines_since_vbl * 65.0; offset = ((int)(dusecs_since_last_vbl - dcyc_line_start)) & 0xff; lines_since_vbl = (lines_since_vbl << 8) + offset; if(!g_halt_sim && !g_config_control_panel) { dbg_log_info(dfcyc, (word32)dusecs_since_last_vbl, lines_since_vbl, 0xc02e); } if(lines_since_vbl < 0x10541) { return lines_since_vbl; } else { // We've entered the next frame, but update_60hz() hasn't been // called yet. Produce the proper c02e/c02f counter values // for the first line of the display lines_since_vbl = lines_since_vbl - 0x10600; #if 0 printf("lines_since_vbl was:%08x, now:%08x\n", lines_since_vbl + 0x10600, lines_since_vbl); halt_printf("lines_since_vbl: %08x!\n", lines_since_vbl); printf("du_s_l_v: %f, dfcyc: %016llx, last_vbl_cycs: %016llx\n", dusecs_since_last_vbl, dfcyc, g_last_vbl_dfcyc); show_dtime_array(); show_all_events(); /* U_STACK_TRACE(); */ #endif } return lines_since_vbl; } int in_vblank(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); // Testing indicates $c019 is a cycle delayed from $C02F counter if(lines_since_vbl > 0xc000) { // Exclude line 192 first cycle! return 1; // 1=in VBL } if(lines_since_vbl == 0) { return 1; // Handle 1-cycle delay in reading c019 } return 0; } // horizontal video counter goes from 0x00,0x40 - 0x7f, then 0x80,0xc0-0xff // over 2*65 cycles. The last visible screen pos is 0x7f and 0xff // KEGS starts it's "line 0" at the start of the right border for line -1 // See get_lines_since_vbl comment for the format of the vertical counter int read_vid_counters(int loc, dword64 dfcyc) { word32 mask; int lines_since_vbl; loc = loc & 0xf; lines_since_vbl = get_lines_since_vbl(dfcyc); lines_since_vbl += 0x10000; if(lines_since_vbl >= 0x20000) { lines_since_vbl = lines_since_vbl - 0x20000 + 0xfa00; } if(lines_since_vbl > 0x1ffff) { halt_printf("lines_since_vbl: %04x, dfcyc: %016llx, last_vbl:" "%016llx\n", lines_since_vbl, dfcyc, g_last_vbl_dfcyc); } if(loc == 0xe) { // c02e: Vertical count return (lines_since_vbl >> 9) & 0xff; } mask = (lines_since_vbl >> 1) & 0x80; lines_since_vbl = (lines_since_vbl & 0xff); if(lines_since_vbl >= 0x01) { lines_since_vbl = (lines_since_vbl + 0x3f) & 0x7f; } return (mask | (lines_since_vbl & 0xff)); } ================================================ FILE: upstream/kegs/src/op_routs.h ================================================ // $KmKId: op_routs.h,v 1.47 2023-11-05 16:21:51+00 kentd Exp $ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #define GET_DLOC_X_IND_WR() \ CYCLES_PLUS_1; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ arg = arg + xreg + direct; \ GET_MEMORY_DIRECT_PAGE16(arg & 0xffff, arg, 1); \ arg = (dbank << 16) + arg; #define GET_DLOC_X_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_X_IND_WR() #define GET_DISP8_S_WR() \ CYCLES_PLUS_1; \ arg = (arg + stack) & 0xffff; \ INC_KPC_2; #define GET_DISP8_S_ADDR() \ GET_1BYTE_ARG; \ GET_DISP8_S_WR() #define GET_DLOC_WR() \ arg = (arg + direct) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; #define GET_DLOC_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_WR() #define GET_DLOC_L_IND_WR() \ arg = (arg + direct) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ INC_KPC_2; \ GET_MEMORY24(arg, arg, 1); #define GET_DLOC_L_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_WR() #define GET_DLOC_IND_WR() \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ arg = (dbank << 16) + arg; #define GET_DLOC_IND_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_IND_WR(); #define GET_DLOC_INDEX_WR(index_reg) \ CYCLES_PLUS_1; \ arg = (arg & 0xff) + index_reg; \ INC_KPC_2; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ if((psr & 0x100) && ((direct & 0xff) == 0)) { \ arg = (arg & 0xff); \ } \ arg = (arg + direct) & 0xffff; #define GET_DLOC_X_WR() \ GET_DLOC_INDEX_WR(xreg) #define GET_DLOC_Y_WR() \ GET_DLOC_INDEX_WR(yreg) #define GET_DLOC_X_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_INDEX_WR(xreg) #define GET_DLOC_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_INDEX_WR(yreg) #define GET_DISP8_S_IND_Y_WR() \ arg = (stack + arg) & 0xffff; \ GET_MEMORY16(arg,arg,1); \ CYCLES_PLUS_2; \ arg += (dbank << 16); \ INC_KPC_2; \ arg = (arg + yreg) & 0xffffff; #define GET_DISP8_S_IND_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DISP8_S_IND_Y_WR() #define GET_DLOC_L_IND_Y_WR() \ arg = (direct + arg) & 0xffff; \ if(direct & 0xff) { \ CYCLES_PLUS_1; \ } \ GET_MEMORY24(arg,arg,1); \ INC_KPC_2; \ arg = (arg + yreg) & 0xffffff; #define GET_DLOC_L_IND_Y_ADDR() \ GET_1BYTE_ARG; \ GET_DLOC_L_IND_Y_WR() #define GET_ABS_ADDR() \ GET_2BYTE_ARG; \ CYCLES_PLUS_1; \ arg = arg + (dbank << 16); \ INC_KPC_3; #define GET_LONG_ADDR() \ GET_3BYTE_ARG; \ CYCLES_PLUS_2; \ INC_KPC_4; #define GET_LONG_X_ADDR_FOR_WR() \ GET_3BYTE_ARG; \ INC_KPC_4; \ arg = (arg + xreg) & 0xffffff; \ CYCLES_PLUS_2; ================================================ FILE: upstream/kegs/src/paddles.c ================================================ const char rcsid_paddles_c[] = "@(#)$KmKId: paddles.c,v 1.21 2023-05-19 13:52:54+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" extern int g_mouse_raw_x; /* from adb.c */ extern int g_mouse_raw_y; dword64 g_paddle_trig_dfcyc = 0; int g_swap_paddles = 0; int g_invert_paddles = 0; int g_joystick_scale_factor_x = 0x100; int g_joystick_scale_factor_y = 0x100; int g_joystick_trim_amount_x = 0; int g_joystick_trim_amount_y = 0; int g_joystick_type = 0; /* 0 = Keypad Joystick */ int g_joystick_native_type1 = -1; int g_joystick_native_type2 = -1; int g_joystick_native_type = -1; extern int g_paddle_buttons; int g_paddle_val[4] = { 0, 0, 0, 0 }; /* g_paddle_val[0]: Joystick X coord, [1]:Y coord */ dword64 g_paddle_dfcyc[4] = { 0, 0, 0, 0 }; /* g_paddle_dfcyc are the dfcyc the paddle goes to 0 */ void paddle_fixup_joystick_type() { /* If g_joystick_type points to an illegal value, change it */ if(g_joystick_type == 2) { g_joystick_native_type = g_joystick_native_type1; if(g_joystick_native_type1 < 0) { g_joystick_type = 0; } } if(g_joystick_type == 3) { g_joystick_native_type = g_joystick_native_type2; if(g_joystick_native_type2 < 0) { g_joystick_type = 0; } } } void paddle_trigger(dword64 dfcyc) { /* Called by read/write to $c070 */ g_paddle_trig_dfcyc = dfcyc; /* Determine what all the paddle values are right now */ paddle_fixup_joystick_type(); switch(g_joystick_type) { case 0: /* Keypad Joystick */ paddle_trigger_keypad(dfcyc); break; case 1: /* Mouse Joystick */ paddle_trigger_mouse(dfcyc); break; default: joystick_update(dfcyc); } } void paddle_trigger_mouse(dword64 dfcyc) { int val_x, val_y; int mouse_x, mouse_y; val_x = 0; mouse_x = g_mouse_raw_x; mouse_y = g_mouse_raw_y; /* mous_phys_x is 0->560, convert that to -32768 to + 32767 cyc */ /* So subtract 280 then mult by 117 */ val_x = (mouse_x - 280) * 117; /* mous_phys_y is 0->384, convert that to -32768 to + 32767 cyc */ /* so subtract 192 then mult by 180 to overscale it a bit */ val_y = (mouse_y - 192) * 180; g_paddle_val[0] = val_x; g_paddle_val[1] = val_y; g_paddle_val[2] = 32767; g_paddle_val[3] = 32767; g_paddle_buttons |= 0xc; paddle_update_trigger_dcycs(dfcyc); } void paddle_trigger_keypad(dword64 dfcyc) { int get_y, val_x, val_y; val_x = adb_get_keypad_xy(get_y=0); val_y = adb_get_keypad_xy(get_y=1); /* val_x and val_y are already scale -32768 to +32768 */ g_paddle_val[0] = val_x; g_paddle_val[1] = val_y; g_paddle_val[2] = 32767; g_paddle_val[3] = 32767; g_paddle_buttons |= 0xc; paddle_update_trigger_dcycs(dfcyc); } void paddle_update_trigger_dcycs(dword64 dfcyc) { dword64 trig_dfcyc; int val, paddle_num, scale, trim; int i; for(i = 0; i < 4; i++) { paddle_num = i; if(g_swap_paddles) { paddle_num = i ^ 1; } val = g_paddle_val[paddle_num]; if(g_invert_paddles) { val = -val; } /* convert -32768 to +32768 into 0->2816.0 cycles (the */ /* paddle delay const) */ /* First multiply by the scale factor to adjust range */ if(paddle_num & 1) { scale = g_joystick_scale_factor_y; trim = g_joystick_trim_amount_y; } else { scale = g_joystick_scale_factor_x; trim = g_joystick_trim_amount_x; } #if 0 if(i == 0) { printf("val was %04x(%d) * scale %03x = %d\n", val, val, scale, (val*scale)>>16); } #endif val = (val * scale) >> 16; /* Val is now from -128 to + 128 since scale is */ /* 256=1.0, 128 = 0.5 */ val = val + 128 + trim; if(val >= 255) { val = 280; /* increase range */ } trig_dfcyc = dfcyc + (dword64)((val * (2816/255.0)) * 65536); g_paddle_dfcyc[i] = trig_dfcyc; if(i < 2) { dbg_log_info(dfcyc, (scale << 16) | (val & 0xffff), (trim << 16) | i, 0x70); } } } int read_paddles(dword64 dfcyc, int paddle) { dword64 trig_dfcyc; trig_dfcyc = g_paddle_dfcyc[paddle & 3]; if(dfcyc < trig_dfcyc) { return 0x80; } else { return 0x00; } } void paddle_update_buttons() { paddle_fixup_joystick_type(); joystick_update_buttons(); } ================================================ FILE: upstream/kegs/src/protos.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2019 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #ifdef INCLUDE_RCSID_C const char rcsid_protos_h[] = "@(#)$KmKId: protos.h,v 1.190 2019-12-16 02:02:53+00 kentd Exp $"; #endif #include "protos_base.h" ================================================ FILE: upstream/kegs/src/protos_base.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #ifdef INCLUDE_RCSID_C const char rcsid_protos_base_h[] = "@(#)$KmKId: protos_base.h,v 1.161 2024-09-15 13:56:41+00 kentd Exp $"; #endif #ifdef __GNUC__ void halt_printf(const char *fmt, ...) __attribute__ (( __format__(printf, 1, 2))); void cfg_err_printf(const char *pre_str, const char *fmt, ...) __attribute__ (( __format__(printf, 2, 3))); void dynapro_error(Disk *dsk, const char *fmt, ...) __attribute__ (( __format__(printf, 2, 3))); #endif /* xdriver.c and macdriver.c and windriver.c */ int win_nonblock_read_stdin(int fd, char *bufptr, int len); /* special scc_unixdriver.c prototypes */ void scc_serial_unix_open(int port); void scc_serial_unix_close(int port); void scc_serial_unix_change_params(int port); void scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_serial_unix_empty_writebuf(int port); /* special scc_windriver.c prototypes */ void scc_serial_win_open(int port); void scc_serial_win_close(int port); void scc_serial_win_change_params(int port); void scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_serial_win_empty_writebuf(int port); /* special joystick_driver.c prototypes */ void joystick_init(void); void joystick_update(dword64 dfcyc); void joystick_update_buttons(void); /* END_HDR */ /* adb.c */ int adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr); int adb_get_copy_requested(void); void adb_nonmain_check(void); void adb_init(void); void adb_reset(void); void adb_log(word32 addr, int val); void show_adb_log(void); void adb_error(void); void adb_add_kbd_srq(void); void adb_clear_kbd_srq(void); void adb_add_data_int(void); void adb_add_mouse_int(void); void adb_clear_data_int(void); void adb_clear_mouse_int(void); void adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2); void adb_send_1byte(word32 val); void adb_response_packet(int num_bytes, word32 val); void adb_kbd_reg0_data(int a2code, int is_up); void adb_kbd_talk_reg0(void); void adb_set_config(word32 val0, word32 val1, word32 val2); void adb_set_new_mode(word32 val); int adb_read_c026(void); void adb_write_c026(int val); void do_adb_cmd(void); int adb_read_c027(void); void adb_write_c027(int val); int read_adb_ram(word32 addr); void write_adb_ram(word32 addr, int val); int adb_get_keypad_xy(int get_y); int adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, int buttons_valid); int mouse_read_c024(dword64 dfcyc); void mouse_compress_fifo(dword64 dfcyc); void adb_paste_update_state(void); int adb_paste_add_buf(word32 key); void adb_key_event(int a2code, int is_up); word32 adb_read_c000(void); word32 adb_access_c010(void); word32 adb_read_c025(void); int adb_is_cmd_key_down(void); int adb_is_option_key_down(void); void adb_increment_speed(void); void adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask); int adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr); void adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, int is_up); void adb_maybe_virtual_key_update(int a2code, int is_up); void adb_virtual_key_update(int a2code, int is_up); void adb_kbd_repeat_off(void); void adb_mainwin_focus(int has_focus); /* engine_c.c */ word32 get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); word32 get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); word32 get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); void set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); void set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, dword64 dplus_1, dword64 dplus_x_m1, int in_bank); void set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); word32 get_memory_c(word32 addr); word32 get_memory16_c(word32 addr); word32 get_memory24_c(word32 addr); void set_memory_c(word32 addr, word32 val, int do_log); void set_memory16_c(word32 addr, word32 val, int do_log); void set_memory24_c(word32 addr, word32 val); word32 do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub); word32 do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub); void fixed_memory_ptrs_init(void); word32 get_itimer(void); void engine_recalc_events(void); void set_halt_act(int val); void clr_halt_act(void); word32 get_remaining_operands(word32 addr, word32 opcode, word32 psr, dword64 *dcyc_ptr, Fplus *fplus_ptr); int enter_engine(Engine_reg *engine_ptr); /* clock.c */ double get_dtime(void); int micro_sleep(double dtime); void clk_bram_zero(void); void clk_bram_set(int bram_num, int offset, int val); void clk_setup_bram_version(void); void clk_write_bram(FILE *fconf); void update_cur_time(void); void clock_update(void); void clock_update_if_needed(void); void clock_write_c034(word32 val); void do_clock_data(void); /* compile_time.c */ /* config.c */ int config_add_argv_override(const char *str1, const char *str2); void config_set_config_kegs_name(const char *str1); void config_init_menus(Cfg_menu *menuptr); void config_init(void); void cfg_find_config_kegs_file(void); int config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr); int config_expand_path(char *out_ptr, const char *in_ptr, int maxlen); char *cfg_exit(int get_status); void cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap); void cfg_err_printf(const char *pre_str, const char *fmt, ...); void cfg_toggle_config_panel(void); void cfg_set_config_panel(int panel); char *cfg_text_screen_dump(int get_status); char *cfg_text_screen_str(void); char *cfg_get_serial0_status(int get_status); char *cfg_get_serial1_status(int get_status); char *cfg_get_current_copy_selection(void); void config_vbl_update(int doit_3_persec); void cfg_file_update_rom(const char *str); void cfg_file_update_ptr(char **strptr, const char *str, int need_update); void cfg_int_update(int *iptr, int new_val); void cfg_load_charrom(void); void config_load_roms(void); void config_parse_config_kegs_file(void); void cfg_parse_one_line(char *buf, int line); void cfg_parse_bram(char *buf, int pos, int len); void cfg_parse_sxdx(char *buf, int pos, int len); void config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, int with_extras); char *config_write_config_kegs_file(int get_status); void insert_disk(int slot, int drive, const char *name, int ejected, const char *partition_name, int part_num, word32 dynamic_blocks); dword64 cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot); dword64 cfg_get_fd_size(int fd); dword64 cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); dword64 cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); int cfg_partition_maybe_add_dotdot(void); int cfg_partition_name_check(const byte *name_ptr, int name_len); int cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size); int cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, int part_num); int cfg_partition_make_list_from_name(const char *namestr); int cfg_partition_make_list(int fd); int cfg_maybe_insert_disk(int slot, int drive, const char *namestr); void cfg_insert_disk_dynapro(int slot, int drive, const char *name); int cfg_stat(char *path, struct stat *sb, int do_lstat); word32 cfg_get_le16(byte *bptr); word32 cfg_get_le32(byte *bptr); dword64 cfg_get_le64(byte *bptr); word32 cfg_get_be_word16(word16 *ptr); word32 cfg_get_be_word32(word32 *ptr); void cfg_set_le32(byte *bptr, word32 val); void config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr); void cfg_htab_vtab(int x, int y); void cfg_home(void); void cfg_cleol(void); void cfg_putchar(int c); void cfg_printf(const char *fmt, ...); void cfg_print_dnum(dword64 dnum, int max_len); int cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras); int cfg_get_disk_locked(int type_ext); void cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change); void cfg_get_base_path(char *pathptr, const char *inptr, int go_up); char *cfg_name_new_image(int get_status); void cfg_dup_existing_image(word32 slotdrive); void cfg_dup_image_selected(void); void cfg_validate_image(word32 slotdrive); void cfg_toggle_lock_disk(word32 slotdrive); int cfg_create_new_image_act(const char *str, int type, int size_blocks); void cfg_create_new_image(void); void cfg_file_init(void); void cfg_free_alldirents(Cfg_listhdr *listhdrptr); void cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); void cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); int cfg_dirent_sortfn(const void *obj1, const void *obj2); int cfg_str_match(const char *str1, const char *str2, int len); int cfg_str_match_maybecase(const char *str1, const char *str2, int len, int ignorecase); int cfgcasecmp(const char *str1, const char *str2); int cfg_strlcat(char *dstptr, const char *srcptr, int dstsize); char *cfg_strncpy(char *dstptr, const char *srcptr, int dstsize); const char *cfg_str_basename(const char *str); char *cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize); void cfg_file_readdir(const char *pathptr); char *cfg_shorten_filename(const char *in_ptr, int maxlen); void cfg_fix_topent(Cfg_listhdr *listhdrptr); void cfg_file_draw(void); void cfg_partition_select_all(void); void cfg_partition_selected(void); void cfg_file_selected(void); void cfg_file_handle_key(int key); void cfg_draw_menu(void); void cfg_newdisk_pick_menu(word32 slotdrive); int cfg_control_panel_update(void); void cfg_edit_mode_key(int key); int cfg_control_panel_update1(void); /* debugger.c */ void debugger_init(void); void check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type); void debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, int pos); int debugger_run_16ms(void); void dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type); void debugger_update_list_kpc(void); void debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up); void debugger_page_updown(int isup); void debugger_redraw_screen(Kimage *kimage_ptr); void debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line); void debugger_help(void); void dbg_help_show_strs(int help_depth, const char *str, const char *help_str); const char *debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, int help_depth); void do_debug_cmd(const char *in_str); word32 dis_get_memory_ptr(word32 addr); void show_one_toolset(FILE *toolfile, int toolnum, word32 addr); void show_toolset_tables(word32 a2bank, word32 addr); word32 debug_getnum(const char **str_ptr); char *debug_get_filename(const char **str_ptr); void debug_help(const char *str); void debug_bp(const char *str); void debug_bp_set(const char *str); void debug_bp_clear(const char *str); void debug_bp_clear_all(const char *str); void debug_bp_setclr(const char *str, int is_set_clear); void debug_soundfile(const char *cmd_str); void debug_logpc(const char *str); void debug_logpc_on(const char *str); void debug_logpc_off(const char *str); void debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc); Data_log *debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, int *count_ptr); void debug_logpc_save(const char *cmd_str); void set_bp(word32 addr, word32 end_addr, word32 acc_type); void show_bp(void); void delete_bp(word32 addr, word32 end_addr); void debug_iwm(const char *str); void debug_iwm_check(const char *str); int do_blank(int mode, int old_mode); void do_go(void); void do_step(void); void xam_mem(int count); void show_hex_mem(word32 startbank, word32 start, word32 end, int count); void do_debug_list(void); void dis_do_memmove(void); void dis_do_pattern_search(void); void dis_do_compare(void); const char *do_debug_unix(const char *str, int old_mode); void do_debug_load(void); char *do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, int *size_ptr); int debug_get_view_line(int back); int debug_add_output_line(char *in_str); void debug_add_output_string(char *in_str, int len); void debug_add_output_chars(char *str); int dbg_printf(const char *fmt, ...); int dbg_vprintf(const char *fmt, va_list args); void halt_printf(const char *fmt, ...); void halt2_printf(const char *fmt, ...); /* scc.c */ void scc_init(void); void scc_reset(void); void scc_hard_reset_port(int port); void scc_reset_port(int port); void scc_regen_clocks(int port); void scc_port_close(int port); void scc_port_open(dword64 dfcyc, int port); int scc_is_port_closed(dword64 dfcyc, int port); char *scc_get_serial_status(int get_status, int port); void scc_config_changed(int port, int cfg_changed, int remote_changed, int serial_dev_changed); void scc_update(dword64 dfcyc); void scc_try_to_empty_writebuf(dword64 dfcyc, int port); void scc_try_fill_readbuf(dword64 dfcyc, int port); void scc_do_event(dword64 dfcyc, int type); void show_scc_state(void); word32 scc_read_reg(dword64 dfcyc, int port); void scc_write_reg(dword64 dfcyc, int port, word32 val); word32 scc_read_data(dword64 dfcyc, int port); void scc_write_data(dword64 dfcyc, int port, word32 val); word32 scc_do_read_rr2b(void); void scc_maybe_br_event(dword64 dfcyc, int port); void scc_evaluate_ints(int port); void scc_maybe_rx_event(dword64 dfcyc, int port); void scc_maybe_rx_int(int port); void scc_clr_rx_int(int port); void scc_handle_tx_event(int port); void scc_maybe_tx_event(dword64 dfcyc, int port); void scc_clr_tx_int(int port); void scc_set_zerocnt_int(int port); void scc_clr_zerocnt_int(int port); void scc_add_to_readbuf(dword64 dfcyc, int port, word32 val); void scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...); void scc_transmit(dword64 dfcyc, int port, word32 val); void scc_add_to_writebuf(dword64 dfcyc, int port, word32 val); /* scc_socket_driver.c */ void scc_socket_open(dword64 dfcyc, int port, int cfg); void scc_socket_close(int port); void scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry); void scc_socket_maybe_open(dword64 dfcyc, int port, int must); void scc_socket_open_incoming(dword64 dfcyc, int port); void scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, int remote_port); void scc_socket_make_nonblock(dword64 dfcyc, int port); void scc_accept_socket(dword64 dfcyc, int port); void scc_socket_telnet_reqs(dword64 dfcyc, int port); void scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left); void scc_socket_recvd_char(dword64 dfcyc, int port, int c); void scc_socket_empty_writebuf(dword64 dfcyc, int port); void scc_socket_modem_write(dword64 dfcyc, int port, int c); void scc_socket_do_cmd_str(dword64 dfcyc, int port); void scc_socket_send_modem_code(dword64 dfcyc, int port, int code); void scc_socket_modem_connect(dword64 dfcyc, int port); void scc_socket_modem_do_ring(dword64 dfcyc, int port); void scc_socket_do_answer(dword64 dfcyc, int port); /* scc_windriver.c */ /* scc_unixdriver.c */ /* iwm.c */ void iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525); void iwm_init(void); void iwm_reset(void); void disk_set_num_tracks(Disk *dsk, int num_tracks); word32 iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk); void draw_iwm_status(int line, char *buf); void iwm_flush_cur_disk(void); void iwm_flush_disk_to_unix(Disk *dsk); void iwm_vbl_update(void); void iwm_update_fast_disk_emul(int fast_disk_emul_en); void iwm_show_stats(int slot_drive); Disk *iwm_get_dsk(word32 state); Disk *iwm_touch_switches(int loc, dword64 dfcyc); void iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc); void iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track); void iwm525_update_phases(Disk *dsk, dword64 dfcyc); void iwm525_update_head(Disk *dsk, dword64 dfcyc); int iwm_read_status35(dword64 dfcyc); void iwm_do_action35(dword64 dfcyc); int read_iwm(int loc, dword64 dfcyc); void write_iwm(int loc, int val, dword64 dfcyc); int iwm_read_enable2(dword64 dfcyc); int iwm_read_enable2_handshake(dword64 dfcyc); void iwm_write_enable2(int val); word32 iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc); word32 iwm_read_data_fast(Disk *dsk, dword64 dfcyc); dword64 iwm_return_rand_data(Disk *dsk, dword64 dfcyc); dword64 iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr); word32 iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits); word32 iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits); dword64 iwm_calc_forced_sync(dword64 dval, int forced_bit); int iwm_calc_forced_sync_0s(dword64 sync_dval, int bits); word32 iwm_read_data(Disk *dsk, dword64 dfcyc); void iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc); void iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior); int iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta); void iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc); void iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc); void iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc); void iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc); void sector_to_partial_nib(byte *in, byte *nib_ptr); int disk_unnib_4x4(Disk *dsk); int iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf); int iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf); int iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf); void show_hex_data(byte *buf, int count); void iwm_check_nibblization(dword64 dfcyc); void disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc); void disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, int len_bits, dword64 dfcyc); void iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len); void iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc); void iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len, dword64 dfcyc); void disk_4x4_nib_out(Disk *dsk, word32 val); void disk_nib_out(Disk *dsk, word32 val, int size); void disk_nib_end_track(Disk *dsk, dword64 dfcyc); word32 disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, word32 bit_pos, dword64 dfcyc); Disk *iwm_get_dsk_from_slot_drive(int slot, int drive); void iwm_toggle_lock(Disk *dsk); void iwm_eject_named_disk(int slot, int drive, const char *name, const char *partition_name); void iwm_eject_disk_by_num(int slot, int drive); void iwm_eject_disk(Disk *dsk); void iwm_show_track(int slot_drive, int track, dword64 dfcyc); void iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc); void dummy1(word32 psr); void dummy2(word32 psr); /* joystick_driver.c */ void joystick_callback_init(int native_type); void joystick_callback_update(word32 buttons, int paddle_x, int paddle_y); /* moremem.c */ void fixup_brks(void); void fixup_hires_on(void); void fixup_bank0_2000_4000(void); void fixup_bank0_0400_0800(void); void fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, byte *mem0wr); void fixup_intcx(void); void fixup_st80col(dword64 dfcyc); void fixup_altzp(void); void fixup_page2(dword64 dfcyc); void fixup_ramrd(void); void fixup_ramwrt(void); void fixup_lc(void); void set_statereg(dword64 dfcyc, word32 val); void fixup_shadow_txt1(void); void fixup_shadow_txt2(void); void fixup_shadow_hires1(void); void fixup_shadow_hires2(void); void fixup_shadow_shr(void); void fixup_shadow_iolc(void); void update_shadow_reg(dword64 dfcyc, word32 val); void fixup_shadow_all_banks(void); void setup_pageinfo(void); void show_bankptrs_bank0rdwr(void); void show_bankptrs(int bnk); void show_addr(byte *ptr); word32 moremem_fix_vector_pull(word32 addr); word32 io_read(word32 loc, dword64 *cyc_ptr); void io_write(word32 loc, word32 val, dword64 *cyc_ptr); word32 slinky_devsel_read(dword64 dfcyc, word32 loc); void slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val); word32 c3xx_read(dword64 dfcyc, word32 loc); word32 get_lines_since_vbl(dword64 dfcyc); int in_vblank(dword64 dfcyc); int read_vid_counters(int loc, dword64 dfcyc); /* paddles.c */ void paddle_fixup_joystick_type(void); void paddle_trigger(dword64 dfcyc); void paddle_trigger_mouse(dword64 dfcyc); void paddle_trigger_keypad(dword64 dfcyc); void paddle_update_trigger_dcycs(dword64 dfcyc); int read_paddles(dword64 dfcyc, int paddle); void paddle_update_buttons(void); /* mockingboard.c */ void mock_ay8913_reset(int pair_num, dword64 dfcyc); void mockingboard_reset(dword64 dfcyc); void mock_show_pair(int pair_num, dword64 dfcyc, const char *str); void mock_update_timers(int doit, dword64 dfcyc); void mockingboard_event(dword64 dfcyc); word32 mockingboard_read(word32 loc, dword64 dfcyc); void mockingboard_write(word32 loc, word32 val, dword64 dfcyc); word32 mock_6522_read(int pair_num, word32 loc, dword64 dfcyc); void mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc); word32 mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier); void mock_ay8913_reg_read(int pair_num); void mock_ay8913_reg_write(int pair_num, dword64 dfcyc); void mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, dword64 dfcyc); void mockingboard_show(int got_num, word32 disable_mask); /* sim65816.c */ int sim_get_force_depth(void); int sim_get_use_shmem(void); void sim_set_use_shmem(int use_shmem); word32 toolbox_debug_4byte(word32 addr); void toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr); void show_toolbox_log(void); word32 get_memory_io(word32 loc, dword64 *dcyc_ptr); void set_memory_io(word32 loc, int val, dword64 *dcyc_ptr); void show_regs_act(Engine_reg *eptr); void show_regs(void); void my_exit(int ret); void do_reset(void); byte *memalloc_align(int size, int skip_amt, void **alloc_ptr); void memory_ptr_init(void); int parse_argv(int argc, char **argv, int slashes_to_find); int kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window); void load_roms_init_memory(void); void initialize_events(void); void check_for_one_event_type(int type, word32 mask); void add_event_entry(dword64 dfcyc, int type); dword64 remove_event_entry(int type, word32 mask); void add_event_stop(dword64 dfcyc); void add_event_doc(dword64 dfcyc, int osc); void add_event_scc(dword64 dfcyc, int type); void add_event_vbl(void); void add_event_vid_upd(int line); void add_event_mockingboard(dword64 dfcyc); void add_event_scan_int(dword64 dfcyc, int line); dword64 remove_event_doc(int osc); dword64 remove_event_scc(int type); void remove_event_mockingboard(void); void show_all_events(void); void show_pmhz(void); void setup_zip_speeds(void); int run_16ms(void); int run_a2_one_vbl(void); void add_irq(word32 irq_mask); void remove_irq(word32 irq_mask); void take_irq(void); void show_dtime_array(void); void update_60hz(dword64 dfcyc, double dtime_now); void do_vbl_int(void); void do_scan_int(dword64 dfcyc, int line); void check_scan_line_int(int cur_video_line); void check_for_new_scan_int(dword64 dfcyc); void scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val); void init_reg(void); void handle_action(word32 ret); void do_break(word32 ret); void do_cop(word32 ret); void do_wdm(word32 arg); void do_wai(void); void do_stp(void); void do_wdm_emulator_id(void); void size_fail(int val, word32 v1, word32 v2); int fatal_printf(const char *fmt, ...); int kegs_vprintf(const char *fmt, va_list ap); dword64 must_write(int fd, byte *bufptr, dword64 dsize); void clear_fatal_logs(void); char *kegs_malloc_str(const char *in_str); dword64 kegs_lseek(int fd, dword64 offs, int whence); /* smartport.c */ void smartport_error(void); void smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list); void do_c70d(word32 arg0); void do_c70a(word32 arg0); int do_read_c7(int unit_num, word32 buf, word32 blk); int do_write_c7(int unit_num, word32 buf, word32 blk); int smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); int do_format_c7(int unit_num); void do_c700(word32 ret); /* doc.c */ void doc_init(void); void doc_reset(dword64 dfcyc); int doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, int snd_buf_init, int *outptr_start); void doc_handle_event(int osc, dword64 dfcyc); void doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps); void doc_add_sound_irq(int osc); void doc_remove_sound_irq(int osc, int must); void doc_start_sound2(int osc, dword64 dfcyc); void doc_start_sound(int osc, double eff_dsamps, double dsamps); void doc_wave_end_estimate2(int osc, dword64 dfcyc); void doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps); void doc_remove_sound_event(int osc); void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); void doc_recalc_sound_parms(dword64 dfcyc, int osc); int doc_read_c03c(void); int doc_read_c03d(dword64 dfcyc); void doc_write_c03c(dword64 dfcyc, word32 val); void doc_write_c03d(dword64 dfcyc, word32 val); void doc_show_ensoniq_state(void); /* sound.c */ void sound_init(void); void sound_set_audio_rate(int rate); void sound_reset(dword64 dfcyc); void sound_shutdown(void); void sound_update(dword64 dfcyc); void sound_file_start(char *filename); void sound_file_open(void); void sound_file_close(void); void send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps); void show_c030_state(dword64 dfcyc); void show_c030_samps(dword64 dfcyc, int *outptr, int num); int sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps); void sound_play(dword64 dfcyc); void sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr); void sound_mock_noise(int pair, byte *noise_ptr, int num_samps); void sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, byte *noise_ptr, int *vol_ptr, int num_samps); word32 sound_read_c030(dword64 dfcyc); void sound_write_c030(dword64 dfcyc); /* sound_driver.c */ void snddrv_init(void); void sound_child_fork(int size); void parent_sound_get_sample_rate(int read_fd); void snddrv_shutdown(void); void snddrv_send_sound(int real_samps, int size); void child_sound_playit(word32 tmp); void reliable_buf_write(word32 *shm_addr, int pos, int size); void reliable_zero_write(int amt); int child_send_samples(byte *ptr, int size); void child_sound_loop(int read_fd, int write_fd, word32 *shm_addr); /* woz.c */ void woz_crc_init(void); word32 woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip); void woz_rewrite_crc(Disk *dsk, int min_write_size); void woz_rewrite_lock(Disk *dsk); void woz_check_file(Disk *dsk); void woz_parse_meta(Disk *dsk, int offset, int size); void woz_parse_info(Disk *dsk, int offset, int size); void woz_parse_tmap(Disk *dsk, int offset, int size); void woz_parse_trks(Disk *dsk, int offset, int size); int woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc); int woz_parse_header(Disk *dsk); Woz_info *woz_malloc(byte *wozptr, word32 woz_size); int woz_reopen(Disk *dsk, dword64 dfcyc); int woz_open(Disk *dsk, dword64 dfcyc); byte *woz_append_bytes(byte *wozptr, byte *in_bptr, int len); byte *woz_append_word32(byte *wozptr, word32 val); int woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, byte *bptr); byte *woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, word32 *num_blocks_ptr, dword64 *tmap_dptr); Woz_info *woz_new_from_woz(Disk *dsk, int disk_525); int woz_new(int fd, const char *str, int size_kb); void woz_maybe_reparse(Disk *dsk); void woz_set_reparse(Disk *dsk); void woz_reparse_woz(Disk *dsk); void woz_remove_a_track(Disk *dsk, word32 qtr_track); word32 woz_add_a_track(Disk *dsk, word32 qtr_track); /* unshk.c */ word32 unshk_get_long4(byte *bptr); word32 unshk_get_word2(byte *bptr); word32 unshk_calc_crc(byte *bptr, int size, word32 start_crc); int unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr); void unshk_lzw_clear(Lzw_state *lzw_ptr); byte *unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen); void unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, word32 uncompr_size, word32 thread_format, byte *base_cptr); void unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr); void unshk(Disk *dsk, const char *name_str); void unshk_dsk_raw_data(Disk *dsk); /* undeflate.c */ void *undeflate_realloc(void *ptr, dword64 dsize); void *undeflate_malloc(dword64 dsize); void show_bits(unsigned *llptr, int nl); void show_huftb(unsigned *tabptr, int bits); void undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start); void undeflate_init_bit_rev_tab(word32 *tabptr, int num); word32 undeflate_bit_reverse(word32 val, word32 bits); word32 undeflate_calc_crc32(byte *bptr, word32 len); byte *undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len); void undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, word32 entry); word32 *undeflate_init_fixed_tabs(void); word32 *undeflate_init_tables(void); void undeflate_free_tables(void); void undeflate_check_bit_reverse(void); word32 *undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, word32 *bl_count_ptr, int max_bits); word32 *undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base); byte *undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, byte *cptr_end); byte *undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size); void undeflate_gzip(Disk *dsk, const char *name_str); byte *undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size); int undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, dword64 uncompr_dsize, dword64 compr_dsize); int undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, int min_size); int undeflate_zipfile_make_list(int fd); /* dynapro.c */ word32 dynapro_get_word32(byte *bptr); word32 dynapro_get_word24(byte *bptr); word32 dynapro_get_word16(byte *bptr); void dynapro_set_word24(byte *bptr, word32 val); void dynapro_set_word32(byte *bptr, word32 val); void dynapro_set_word16(byte *bptr, word32 val); void dynapro_error(Disk *dsk, const char *fmt, ...); Dynapro_file *dynapro_alloc_file(void); void dynapro_free_file(Dynapro_file *fileptr, int check_map); void dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map); void dynapro_free_dynapro_info(Disk *dsk); word32 dynapro_find_free_block_internal(Disk *dsk); word32 dynapro_find_free_block(Disk *dsk); byte *dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size); void dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, int path_max); word32 dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, char *buf32_ptr, word32 dir_byte); word32 dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr); word32 dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte); void dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr); void dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr); void dynapro_try_fix_damaged_disk(Disk *dsk); void dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, const char *name_str); Dynapro_file *dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file **head_ptr_ptr, word32 dir_byte); void dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file *head_ptr, word32 dir_byte); word32 dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size); void dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr); void dynapro_unlink_file(Dynapro_file *fileptr); void dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr); void dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr); void dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr); int dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); void dynapro_debug_update(Disk *dsk); void dynapro_debug_map(Disk *dsk, const char *str); void dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start); word32 dynapro_unix_to_prodos_time(const time_t *time_ptr); int dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, word32 storage_type); Dynapro_file *dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, Dynapro_file *match_ptr, word32 storage_type); int dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, word32 dir_byte); word32 dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, word32 dir_byte, word32 inc); word32 dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, word32 key_block, dword64 dsize); word32 dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks); word32 dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, word32 file_offset, word32 eof); word32 dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, int level, word32 file_offset, word32 eof); word32 dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data); word32 dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr); word32 dynapro_build_map(Disk *dsk, Dynapro_file *fileptr); int dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks); /* dyna_type.c */ word32 dynatype_scan_extensions(const char *str); word32 dynatype_find_prodos_type(const char *str); const char *dynatype_find_file_type(word32 file_type); word32 dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, word32 storage_type); int dynatype_get_extension(const char *str, char *out_ptr, int buf_len); int dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr); void dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max); /* dyna_filt.c */ /* dyna_validate.c */ word32 dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, word32 parent_dir_byte); void dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks); word32 dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block); word32 dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof, int level_first); word32 dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof); word32 dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, word32 parent_dir_byte, word32 exp_blocks_used); int dynapro_validate_disk(Disk *dsk); void dynapro_validate_any_image(Disk *dsk); /* applesingle.c */ word32 applesingle_get_be32(const byte *bptr); word32 applesingle_get_be16(const byte *bptr); void applesingle_set_be32(byte *bptr, word32 val); void applesingle_set_be16(byte *bptr, word32 val); word32 applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data); word32 applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, dword64 dsize); word32 applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length); /* video.c */ void video_set_red_mask(word32 red_mask); void video_set_green_mask(word32 green_mask); void video_set_blue_mask(word32 blue_mask); void video_set_alpha_mask(word32 alpha_mask); void video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, int *shift_right_ptr); void video_set_palette(void); void video_set_redraw_skip_amt(int amt); Kimage *video_get_kimage(int win_id); char *video_get_status_ptr(int line); void video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh); int video_get_active(Kimage *kimage_ptr); void video_set_active(Kimage *kimage_ptr, int active); void video_init(int mdepth, int screen_width, int screen_height, int no_scale_window); void video_init_kimage(Kimage *kimage_ptr, int width, int height, int screen_width, int screen_height); void show_a2_line_stuff(void); void video_reset(void); void video_update(void); word32 video_all_stat_to_filt_stat(int line, word32 new_all_stat); void change_display_mode(dword64 dfcyc); void video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl); void change_border_color(dword64 dfcyc, int val); void update_border_info(void); void update_border_line(int st_line_offset, int end_line_offset, int color); void video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, int color, int st_off, int end_off); word32 video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse); void video_update_edges(int line, int left, int right, const char *str); void redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); void redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, word32 *in_wptr, word32 bg_pixel, word32 fg_pixel, int pixels_per_line, int dbl); void redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); void video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, int end_byte, int pixels_per_line, word32 filt_stat); void redraw_changed_hgr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); int video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, int reparse); word32 redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, int pixels_per_line, int y, int scan, word32 ch_mask); void redraw_changed_super_hires_bank(int bank, int start_line, int reparse, word32 *wptr, int pixels_per_line); void redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, int pixels_per_line, word32 filt_stat); void video_copy_changed2(void); void video_update_event_line(int line); void video_force_reparse(void); void video_update_through_line(int line); void video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat); void video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat); void prepare_a2_font(void); void prepare_a2_romx_font(byte *font_ptr); void video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height); void video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix); void video_form_change_rects(void); int video_get_a2_width(Kimage *kimage_ptr); int video_get_x_width(Kimage *kimage_ptr); int video_get_a2_height(Kimage *kimage_ptr); int video_get_x_height(Kimage *kimage_ptr); int video_get_x_xpos(Kimage *kimage_ptr); int video_get_x_ypos(Kimage *kimage_ptr); void video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos); int video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height); void video_update_status_enable(Kimage *kimage_ptr); int video_out_query(Kimage *kimage_ptr); void video_out_done(Kimage *kimage_ptr); int video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr, int pos); int video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); int video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); word32 video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv); void video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, int must_update); int video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width); int video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height); int video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width); int video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height); void video_update_color_raw(int bank, int col_num, int a2_color); void video_update_status_line(int line, const char *string); void video_draw_a2_string(int line, const byte *bptr); void video_show_debug_info(void); word32 read_video_data(dword64 dfcyc); word32 float_bus(dword64 dfcyc); word32 float_bus_lines(dword64 dfcyc, word32 lines_since_vbl); /* voc.c */ word32 voc_devsel_read(word32 loc, dword64 dfcyc); void voc_devsel_write(word32 loc, word32 val, dword64 dfcyc); void voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc); void voc_reset(void); word32 voc_read_reg0(dword64 dfcyc); void voc_update_interlace(dword64 dfcyc); ================================================ FILE: upstream/kegs/src/protos_macdriver.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2019 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ const char rcsid_protos_mac_h[] = "@(#)$KmKId: protos_macdriver.h,v 1.12 2019-12-16 02:02:53+00 kentd Exp $"; /* END_HDR */ /* macdriver.c */ pascal OSStatus quit_event_handler(EventHandlerCallRef call_ref, EventRef event, void *ignore); void show_simple_alert(char *str1, char *str2, char *str3, int num); void x_dialog_create_kegs_conf(const char *str); int x_show_alert(int is_fatal, const char *str); pascal OSStatus my_cmd_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); void update_window(void); void show_event(UInt32 event_class, UInt32 event_kind, int handled); pascal OSStatus my_win_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); pascal OSStatus dummy_event_handler(EventHandlerCallRef call_ref, EventRef in_event, void *ignore); void mac_update_modifiers(word32 state); void mac_warp_mouse(void); void check_input_events(void); void temp_run_application_event_loop(void); int main(int argc, char *argv[]); void x_update_color(int col_num, int red, int green, int blue, word32 rgb); void x_update_physical_colormap(void); void show_xcolor_array(void); void xdriver_end(void); void x_get_kimage(Kimage *kimage_ptr); void dev_video_init(void); void x_redraw_status_lines(void); void x_push_kimage(Kimage *kimage_ptr, int destx, int desty, int srcx, int srcy, int width, int height); void x_push_done(void); void x_auto_repeat_on(int must); void x_auto_repeat_off(int must); void x_hide_pointer(int do_hide); void x_full_screen(int do_full); void update_main_window_size(void); ================================================ FILE: upstream/kegs/src/protos_macsnd_driver.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ const char rcsid_protos_macsnd_driver_h[] = "@(#)$KmKId: protos_macsnd_driver.h,v 1.9 2023-05-04 19:35:29+00 kentd Exp $"; /* END_HDR */ /* macsnd_driver.c */ int mac_send_audio(byte *ptr, int in_size); void macsnd_init(void); ================================================ FILE: upstream/kegs/src/protos_pulseaudio_driver.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2020 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ const char rcsid_protos_pulseaudio_driver_h[] = "@(#)$KmKId: protos_pulseaudio_driver.h,v 1.4 2020-12-02 22:35:39+00 kentd Exp $"; /* END_HDR */ /* pulseaudio_driver.c */ int pulse_audio_send_audio(byte *ptr, int in_size); void pulse_audio_main_events(void); void pulse_audio_write_to_stream(int dbg_count, int in_sz); int pulse_audio_start_stream(void); int pulse_audio_do_init(void); int pulse_audio_init(void); void pulse_audio_shutdown(void); ================================================ FILE: upstream/kegs/src/protos_windriver.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // $KmKId: protos_windriver.h,v 1.15 2023-05-17 22:37:57+00 kentd Exp $ /* END_HDR */ /* windriver.c */ Window_info *win_find_win_info_ptr(HWND hwnd); void win_hide_pointer(Window_info *win_info_ptr, int do_hide); int win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); void win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam); void win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down); void win_event_redraw(HWND hwnd); void win_event_destroy(HWND hwnd); void win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam); void win_event_minmaxinfo(HWND hwnd, LPARAM lParam); void win_event_focus(HWND hwnd, int gain_focus); LRESULT CALLBACK win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam); int main(int argc, char **argv); void check_input_events(void); void win_video_init(int mdepth); void win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, int mdepth); void win_create_window(Window_info *win_info_ptr); void xdriver_end(void); void win_resize_window(Window_info *win_info_ptr); void x_update_display(Window_info *win_info_ptr); void x_hide_pointer(int do_hide); int opendir_int(DIR *dirp, const char *in_filename); DIR *opendir(const char *in_filename); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); int lstat(const char *path, struct stat *bufptr); int ftruncate(int fd, word32 length); /* win32snd_driver.c */ void win32snd_init(word32 *shmaddr); void win32snd_shutdown(void); void CALLBACK handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); void check_wave_error(int res, char *str); void child_sound_init_win32(void); void win32snd_set_playing(int snd_playing); void win32_send_audio2(byte *ptr, int size); int win32_send_audio(byte *ptr, int in_size); ================================================ FILE: upstream/kegs/src/protos_xdriver.h ================================================ /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ const char rcsid_protos_x_h[] = "@(#)$KmKId: protos_xdriver.h,v 1.40 2023-06-16 19:32:26+00 kentd Exp $"; /* END_HDR */ /* xdriver.c */ int main(int argc, char **argv); int my_error_handler(Display *display, XErrorEvent *ev); void xdriver_end(void); void x_try_xset_r(void); void x_badpipe(int signum); int kegs_x_io_error_handler(Display *display); int x_video_get_mdepth(void); int x_try_find_visual(int depth, int screen_num); void x_video_init(void); void x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str); void x_create_window(Window_info *win_info_ptr); int xhandle_shm_error(Display *display, XErrorEvent *event); void x_allocate_window_data(Window_info *win_info_ptr); void get_shm(Window_info *win_info_ptr, int width, int height); void get_ximage(Window_info *win_info_ptr, int width, int height); void x_set_size_hints(Window_info *win_info_ptr); void x_resize_window(Window_info *win_info_ptr); void x_update_display(Window_info *win_info_ptr); Window_info *x_find_xwin(Window in_win); void x_send_copy_data(Window_info *win_info_ptr); void x_handle_copy(XSelectionRequestEvent *req_ev_ptr); void x_handle_targets(XSelectionRequestEvent *req_ev_ptr); void x_request_paste_data(Window_info *win_info_ptr); void x_handle_paste(Window w, Atom property); int x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); void x_input_events(void); void x_hide_pointer(Window_info *win_info_ptr, int do_hide); void x_handle_keysym(XEvent *xev_in); int x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up); void x_update_modifier_state(Window_info *win_info_ptr, int state); void x_auto_repeat_on(int must); void x_auto_repeat_off(int must); void x_full_screen(int do_full); ================================================ FILE: upstream/kegs/src/pulseaudio_driver.c ================================================ const char rcsid_pulseaudio_driver_c[] = "@(#)$KmKId: pulseaudio_driver.c,v 1.10 2021-12-16 22:41:59+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2020 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #ifdef PULSE_AUDIO // Ignore entire file if PULSE_AUDIO is not defined! // Some ideas from Sample code: // https://github.com/gavv/snippets/blob/master/pa/pa_play_async_poll.c #include "defc.h" #include "sound.h" #include "protos_pulseaudio_driver.h" #include #include #define PULSE_REBUF_SIZE (64*1024) word32 g_pulseaudio_rebuf[PULSE_REBUF_SIZE]; volatile int g_pulseaudio_rd_pos; volatile int g_pulseaudio_wr_pos; volatile int g_pulseaudio_playing = 0; extern int Verbose; extern int g_preferred_rate; extern int g_audio_enable; extern word32 *g_sound_shm_addr; extern int g_sound_min_samples; extern int g_sound_max_multiplier; extern int g_sound_size; extern word32 g_vbl_count; int g_call_num = 0; pa_mainloop *g_pa_mainloop_ptr = 0; pa_context *g_pa_context_ptr = 0; pa_stream *g_pa_stream_ptr = 0; pa_sample_spec g_pa_sample_spec = { 0 }; pa_buffer_attr g_pa_buffer_attr = { 0 }; int pulse_audio_send_audio(byte *ptr, int in_size) { word32 *wptr, *pa_wptr; int samps, sample_num; int i; samps = in_size / 4; wptr = (word32 *)ptr; sample_num = g_pulseaudio_wr_pos; pa_wptr = &(g_pulseaudio_rebuf[0]); for(i = 0; i < samps; i++) { pa_wptr[sample_num] = *wptr++; sample_num++; if(sample_num >= PULSE_REBUF_SIZE) { sample_num = 0; } } g_pulseaudio_wr_pos = sample_num; pulse_audio_main_events(); return in_size; } void pulse_audio_main_events() { pa_stream_state_t stream_state; pa_context_state_t context_state; int count, num, sz, do_write; count = 0; do_write = 1; while(1) { // Do a few mainloop cycles to see if samples are needed num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); //printf("pa_mainloop_iterate ret:%d count:%d\n", num, count); if(num < 0) { return; } context_state = pa_context_get_state(g_pa_context_ptr); if((context_state == PA_CONTEXT_FAILED) || (context_state == PA_CONTEXT_TERMINATED)) { printf("context_state is bad: %d\n", context_state); g_audio_enable = 0; return; } stream_state = pa_stream_get_state(g_pa_stream_ptr); if((stream_state == PA_STREAM_FAILED) || (stream_state == PA_STREAM_TERMINATED)) { printf("stream state bad: %d\n", stream_state); g_audio_enable = 0; return; } if(do_write) { sz = pa_stream_writable_size(g_pa_stream_ptr); if(sz > 0) { pulse_audio_write_to_stream(count, sz); do_write = 0; } } count++; if(count > 50) { printf("pulse_audio_main_events() looped %d times\n", count); return; } if(num == 0) { // Nothing else to do return; } } } void pulse_audio_write_to_stream(int dbg_count, int in_sz) { word32 *wptr; void *vptr; // const pa_timing_info *pa_timing_info_ptr; // pa_usec_t pa_latency; size_t sz; int num_samps, sample_num, samps_avail, err, samps_needed; int min_samples, max_samples; int i; samps_needed = in_sz / 4; sample_num = g_pulseaudio_rd_pos; samps_avail = (g_pulseaudio_wr_pos - sample_num) & (PULSE_REBUF_SIZE - 1); min_samples = g_sound_min_samples; max_samples = min_samples * g_sound_max_multiplier; if(samps_needed > min_samples) { min_samples = samps_needed; } #if 0 if(samps_needed > samps_avail) { // We don't have enough samples, must pause g_pulseaudio_playing = 0; sample_num = g_pulseaudio_wr_pos; // Eat remaining samps } #endif if((g_pulseaudio_playing == 0) && (samps_avail > min_samples)) { // We can unpause g_pulseaudio_playing = 1; } if(g_pulseaudio_playing && (samps_avail > max_samples)) { printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); sample_num += (max_samples / 2); sample_num = sample_num & (PULSE_REBUF_SIZE - 1); } #if 0 if(g_call_num < 100) { printf("call_num:%d playing:%d g_vbl_count:%d samps_needed:%d, " "samps_avail:%d\n", g_call_num, g_pulseaudio_playing, g_vbl_count, samps_needed, samps_avail); } #endif g_call_num++; num_samps = MIN(samps_avail, samps_needed); if(g_pulseaudio_playing) { vptr = 0; // Let it allocate for us sz = num_samps * 4; err = pa_stream_begin_write(g_pa_stream_ptr, &vptr, &sz); wptr = vptr; if(err) { g_audio_enable = 0; printf("pa_stream_begin_write failed: %s\n", pa_strerror(err)); return; } num_samps = sz / 4; for(i = 0; i < num_samps; i++) { wptr[i] = g_pulseaudio_rebuf[sample_num]; sample_num++; if(sample_num >= PULSE_REBUF_SIZE) { sample_num = 0; } } } else { err = pa_stream_cancel_write(g_pa_stream_ptr); // Just get out...don't let us get further behind by sending // silence frames. return; } g_pulseaudio_rd_pos = sample_num; #if 0 pa_timing_info_ptr = pa_stream_get_timing_info(g_pa_stream_ptr); err = pa_stream_get_latency(g_pa_stream_ptr, &pa_latency, 0); printf(" will send %d samples to the stream, write_index:%lld, " "latency:%lld\n", num_samps * 4, (word64)pa_timing_info_ptr->write_index, (word64)pa_latency); #endif err = pa_stream_write(g_pa_stream_ptr, wptr, num_samps * 4, 0, 0, PA_SEEK_RELATIVE); if(err) { printf("pa_stream_write: %s\n", pa_strerror(err)); g_audio_enable = 0; } } int pulse_audio_start_stream() { int flags, ret; g_pa_sample_spec.format = PA_SAMPLE_S16LE; g_pa_sample_spec.rate = g_preferred_rate; g_pa_sample_spec.channels = 2; printf("Set requested rate=%d\n", g_pa_sample_spec.rate); g_pa_stream_ptr = pa_stream_new(g_pa_context_ptr, "KEGS", &g_pa_sample_spec, 0); if(!g_pa_stream_ptr) { printf("pa_stream_new failed\n"); return 1; } g_pa_buffer_attr.maxlength = -1; // Maximum server buffer g_pa_buffer_attr.tlength = 4*g_preferred_rate/10; // 1/10th sec //g_pa_buffer_attr.prebuf = 4*g_preferred_rate/100; // 1/100th sec g_pa_buffer_attr.prebuf = -1; g_pa_buffer_attr.minreq = 4*g_preferred_rate/60; // 1/60th sec flags = PA_STREAM_ADJUST_LATENCY; //flags = PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | // PA_STREAM_ADJUST_LATENCY; // PA_STREAM_AUTO_TIMING_UPDATE and PA_STREAM_INTERPOLATE_TIMING are // to get latency info from the server. PA_STREAM_ADJUST_LATENCY // means the total latency including the server output sink buffer // tries to be tlength ret = pa_stream_connect_playback(g_pa_stream_ptr, 0, &g_pa_buffer_attr, flags, 0, 0); if(ret) { printf("pa_stream_connect_playback failed: %d\n", ret); return ret; } return 0; // Success! } int pulse_audio_do_init() { pa_stream_state_t stream_state; pa_context_state_t context_state; int ret, count, num; g_pa_mainloop_ptr = pa_mainloop_new(); g_pa_context_ptr = pa_context_new( pa_mainloop_get_api(g_pa_mainloop_ptr), "KEGS"); if(!g_pa_context_ptr) { printf("Pulse Audio pa_context_new() failed\n"); return 1; } ret = pa_context_connect(g_pa_context_ptr, 0, 0, 0); if(ret != 0) { printf("pa_context_connect failed: %d\n", ret); return 1; } count = 0; while(1) { // Do a few mainloop cycles to get stream initialized num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); #if 0 printf("pa_mainloop_iterate ret: %d, count:%d g_pa_stream_ptr:" "%p\n", num, count, g_pa_stream_ptr); #endif if(num < 0) { return 1; } if(num == 0) { usleep(10*1000); if(count++ > 50) { // Waited more than 500ms, just give up printf("Timed out waiting for Pulse Audio to " "start\n"); return 1; } } // See if context is ready context_state = pa_context_get_state(g_pa_context_ptr); if((context_state == PA_CONTEXT_FAILED) || (context_state == PA_CONTEXT_TERMINATED)) { printf("context_state is bad: %d\n", context_state); return 1; } if((context_state == PA_CONTEXT_READY) && !g_pa_stream_ptr) { ret = pulse_audio_start_stream(); if(ret) { return ret; } } if(g_pa_stream_ptr) { stream_state = pa_stream_get_state(g_pa_stream_ptr); if((stream_state == PA_STREAM_FAILED) || (stream_state == PA_STREAM_TERMINATED)){ printf("stream state bad: %d\n", stream_state); return 1; } if(stream_state == PA_STREAM_READY) { printf("Pulse Audio stream is now ready!\n"); return 0; } } } } int pulse_audio_init() { int ret; g_pulseaudio_rd_pos = 0; g_pulseaudio_wr_pos = 0; ret = pulse_audio_do_init(); // printf("pulse_audio_init ret:%d\n", ret); if(ret != 0) { // Free structures, disable sound if(g_pa_stream_ptr) { pa_stream_disconnect(g_pa_stream_ptr); pa_stream_unref(g_pa_stream_ptr); g_pa_stream_ptr = 0; } if(g_pa_context_ptr) { pa_context_disconnect(g_pa_context_ptr); pa_context_unref(g_pa_context_ptr); g_pa_context_ptr = 0; } if(g_pa_mainloop_ptr) { pa_mainloop_free(g_pa_mainloop_ptr); g_pa_mainloop_ptr = 0; } return ret; } sound_set_audio_rate(g_preferred_rate); return 0; } void pulse_audio_shutdown() { printf("pulse_audio_shutdown\n"); } #endif /* PULSE_AUDIO */ ================================================ FILE: upstream/kegs/src/scc.c ================================================ const char rcsid_scc_c[] = "@(#)$KmKId: scc.c,v 1.74 2025-04-29 22:17:05+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Driver for the Zilog SCC Z8530, which implements two channels (A,B) of // serial ports, controlled by $C038-$C03B #include "defc.h" extern int Verbose; extern int g_code_yellow; extern dword64 g_cur_dfcyc; extern int g_serial_cfg[2]; extern int g_serial_mask[2]; extern char *g_serial_remote_ip[2]; extern int g_serial_remote_port[2]; extern char *g_serial_device[2]; extern int g_serial_win_device[2]; extern int g_irq_pending; /* scc port 0 == channel A = slot 1 = c039/c03b */ /* port 1 == channel B = slot 2 = c038/c03a */ #include "scc.h" #define SCC_R14_DPLL_SOURCE_BRG 0x100 #define SCC_R14_DPLL_SOURCE_RTXC 0x200 #define SCC_DCYCS_PER_PCLK ((DCYCS_1_MHZ) / ((DCYCS_28_MHZ) /8)) #define SCC_DCYCS_PER_XTAL ((DCYCS_1_MHZ) / 3686400.0) // PCLK is 3.5795MHz #define SCC_BR_EVENT 1 #define SCC_TX_EVENT 2 #define SCC_RX_EVENT 3 #define SCC_MAKE_EVENT(port, a) (((a) << 1) + (port)) Scc g_scc[2]; int g_baud_table[] = { 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; int g_scc_overflow = 0; int g_scc_init = 0; // cur_state >= 0 and matches g_serial_cfg[port]: port is in that mode // cur_state = -1: port is in no particular mode and should go to g_serial_cfg[] // cur_state = -2: port failed to enter g_serial_cfg[], do not try again until // something changes void scc_init() { Scc *scc_ptr; int i, j; for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); memset(scc_ptr, 0, sizeof(*scc_ptr)); scc_ptr->cur_state = -1; scc_ptr->modem_state = 0; scc_ptr->sockfd = INVALID_SOCKET; scc_ptr->rdwrfd = INVALID_SOCKET; scc_ptr->sockaddr_ptr = 0; scc_ptr->sockaddr_size = 0; scc_ptr->unix_dev_fd = -1; scc_ptr->win_com_handle = 0; scc_ptr->win_dcb_ptr = 0; scc_ptr->br_event_pending = 0; scc_ptr->rx_event_pending = 0; scc_ptr->tx_event_pending = 0; scc_ptr->char_size = 8; scc_ptr->baud_rate = 9600; scc_ptr->telnet_mode = 0; scc_ptr->telnet_iac = 0; scc_ptr->out_char_dfcyc = 0; scc_ptr->socket_error = 0; scc_ptr->socket_num_rings = 0; scc_ptr->socket_last_ring_dfcyc = 0; scc_ptr->modem_mode = 0; scc_ptr->modem_plus_mode = 0; scc_ptr->modem_s0_val = 0; scc_ptr->modem_s2_val = '+'; scc_ptr->modem_cmd_len = 0; scc_ptr->modem_out_portnum = 23; scc_ptr->modem_cmd_str[0] = 0; for(j = 0; j < 2; j++) { scc_ptr->telnet_local_mode[j] = 0; scc_ptr->telnet_remote_mode[j] = 0; scc_ptr->telnet_reqwill_mode[j] = 0; scc_ptr->telnet_reqdo_mode[j] = 0; } } g_scc_init = 1; } void scc_reset() { Scc *scc_ptr; int i; if(!g_scc_init) { halt_printf("scc_reset called before init\n"); return; } for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); scc_ptr->mode = 0; scc_ptr->reg_ptr = 0; scc_ptr->in_rdptr = 0; scc_ptr->in_wrptr = 0; scc_ptr->out_rdptr = 0; scc_ptr->out_wrptr = 0; scc_ptr->dcd = 1 - i; // 1 for slot 1, 0 for slot 2 scc_ptr->wantint_rx = 0; scc_ptr->wantint_tx = 0; scc_ptr->wantint_zerocnt = 0; scc_ptr->read_called_this_vbl = 0; scc_ptr->write_called_this_vbl = 0; scc_evaluate_ints(i); scc_hard_reset_port(i); } } void scc_hard_reset_port(int port) { Scc *scc_ptr; scc_reset_port(port); scc_ptr = &(g_scc[port]); scc_ptr->reg[14] = 0; /* zero bottom two bits */ scc_ptr->reg[13] = 0; scc_ptr->reg[12] = 0; scc_ptr->reg[11] = 0x08; scc_ptr->reg[10] = 0; scc_ptr->reg[7] = 0; scc_ptr->reg[6] = 0; scc_ptr->reg[5] = 0; scc_ptr->reg[4] = 0x04; scc_ptr->reg[3] = 0; scc_ptr->reg[2] = 0; scc_ptr->reg[1] = 0; /* HACK HACK: */ g_scc[0].reg[9] = 0; /* Clear all interrupts */ scc_evaluate_ints(port); scc_regen_clocks(port); } void scc_reset_port(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); scc_ptr->reg[15] = 0xf8; scc_ptr->reg[14] &= 0x03; /* 0 most (including >= 0x100) bits */ scc_ptr->reg[10] = 0; scc_ptr->reg[5] &= 0x65; /* leave tx bits and sdlc/crc bits */ scc_ptr->reg[4] |= 0x04; /* Set async mode */ scc_ptr->reg[3] &= 0xfe; /* clear receiver enable */ scc_ptr->reg[1] &= 0xfe; /* clear ext int enable */ scc_ptr->br_is_zero = 0; scc_ptr->tx_buf_empty = 1; scc_ptr->wantint_rx = 0; scc_ptr->wantint_tx = 0; scc_ptr->wantint_zerocnt = 0; scc_ptr->rx_queue_depth = 0; scc_evaluate_ints(port); scc_regen_clocks(port); scc_clr_tx_int(port); scc_clr_rx_int(port); } void scc_regen_clocks(int port) { Scc *scc_ptr; double br_dcycs, tx_dcycs, rx_dcycs, rx_char_size, tx_char_size; double clock_mult, dpll_dcycs; word32 reg4, reg11, reg14, br_const, max_diff, diff; int baud, cur_state, baud_entries, pos; int i; /* Always do baud rate generator */ scc_ptr = &(g_scc[port]); br_const = (scc_ptr->reg[13] << 8) + scc_ptr->reg[12]; br_const += 2; /* counts down past 0 */ reg4 = scc_ptr->reg[4]; // Transmit/Receive misc params clock_mult = 1.0; switch((reg4 >> 6) & 3) { case 0: /* x1 */ clock_mult = 1.0; break; case 1: /* x16 */ clock_mult = 16.0; break; case 2: /* x32 */ clock_mult = 32.0; break; case 3: /* x64 */ clock_mult = 64.0; break; } br_dcycs = 0.01; reg14 = scc_ptr->reg[14]; if(reg14 & 0x1) { br_dcycs = SCC_DCYCS_PER_XTAL; if(reg14 & 0x2) { br_dcycs = SCC_DCYCS_PER_PCLK; } } br_dcycs = br_dcycs * (double)br_const * 2.0; dpll_dcycs = 0.1; if(reg14 & SCC_R14_DPLL_SOURCE_BRG) { dpll_dcycs = br_dcycs; } else if(reg14 & SCC_R14_DPLL_SOURCE_RTXC) { dpll_dcycs = SCC_DCYCS_PER_XTAL; } tx_dcycs = 1; reg11 = scc_ptr->reg[11]; switch((reg11 >> 3) & 3) { case 0: // /RTxC pin tx_dcycs = SCC_DCYCS_PER_XTAL; break; case 2: // BR generator output tx_dcycs = br_dcycs; break; case 3: // DPLL output tx_dcycs = dpll_dcycs; break; } tx_dcycs = tx_dcycs * clock_mult; rx_dcycs = 1; switch((reg11 >> 5) & 3) { case 0: // /RTxC pin rx_dcycs = SCC_DCYCS_PER_XTAL; break; case 2: // BR generator output rx_dcycs = br_dcycs; break; case 3: // DPLL output rx_dcycs = dpll_dcycs; break; } rx_dcycs = rx_dcycs * clock_mult; tx_char_size = 8.0; switch((scc_ptr->reg[5] >> 5) & 0x3) { case 0: // 5 bits tx_char_size = 5.0; break; case 1: // 7 bits tx_char_size = 7.0; break; case 2: // 6 bits tx_char_size = 6.0; break; } scc_ptr->char_size = (int)tx_char_size; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 1: // 1 stop bit tx_char_size += 2.0; // 1 stop + 1 start bit break; case 2: // 1.5 stop bit tx_char_size += 2.5; // 1.5 stop + 1 start bit break; case 3: // 2 stop bits tx_char_size += 3.0; // 2.0 stop + 1 start bit break; } if(scc_ptr->reg[4] & 1) { // parity enabled tx_char_size += 1.0; } if(scc_ptr->reg[14] & 0x10) { /* loopback mode, make it go faster...*/ rx_char_size = 1.0; tx_char_size = 1.0; } rx_char_size = tx_char_size; /* HACK */ baud = (int)(DCYCS_1_MHZ / tx_dcycs); max_diff = 5000000; pos = 0; baud_entries = sizeof(g_baud_table)/sizeof(g_baud_table[0]); for(i = 0; i < baud_entries; i++) { diff = abs(g_baud_table[i] - baud); if(diff < max_diff) { pos = i; max_diff = diff; } } scc_ptr->baud_rate = g_baud_table[pos]; scc_ptr->br_dcycs = br_dcycs; scc_ptr->tx_dcycs = tx_dcycs * tx_char_size; scc_ptr->rx_dcycs = rx_dcycs * rx_char_size; cur_state = scc_ptr->cur_state; if(cur_state == 0) { // real serial ports #ifdef _WIN32 scc_serial_win_change_params(port); #else scc_serial_unix_change_params(port); #endif } } void scc_port_close(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); #ifdef _WIN32 scc_serial_win_close(port); #else scc_serial_unix_close(port); #endif scc_socket_close(port); scc_ptr->cur_state = -1; // Nothing open } void scc_port_open(dword64 dfcyc, int port) { int cfg; cfg = g_serial_cfg[port]; printf("scc_port_open port:%d cfg:%d\n", port, cfg); if(cfg == 0) { // Real host serial port #ifdef _WIN32 scc_serial_win_open(port); #else scc_serial_unix_open(port); #endif } else if(cfg >= 1) { scc_socket_open(dfcyc, port, cfg); } printf(" open socketfd:%ld\n", (long)g_scc[port].sockfd); } int scc_is_port_closed(dword64 dfcyc, int port) { Scc *scc_ptr; // Returns 1 is the port is closed (not working). Returns 0 // if the port is open. Tries to open the port if it is not in error scc_ptr = &(g_scc[port]); if(scc_ptr->cur_state == -1) { scc_port_open(dfcyc, port); } if(scc_ptr->cur_state < 0) { scc_ptr->cur_state = -2; //printf("scc_is_port_closed p:%d returning 0\n", port); return 1; // Not open } return 0; // Port is open! } char * scc_get_serial_status(int get_status, int port) { char buf[80]; char *str; int cur_state; if(get_status == 0) { return 0; } cur_state = g_scc[port].cur_state; str = ""; switch(cur_state) { case -1: str = "Not initialized yet"; break; case 0: snprintf(buf, 80, "Opened %s OK", g_serial_device[port]); str = buf; break; case 1: snprintf(buf, 80, "Virtual modem, sockfd:%d", (int)g_scc[port].sockfd); str = buf; break; case 2: snprintf(buf, 80, "Outgoing to %s:%d", g_serial_remote_ip[port], g_serial_remote_port[port]); str = buf; break; case 3: snprintf(buf, 80, "Opened %d, sockfd:%d", 6501 + port, (int)g_scc[port].sockfd); str = buf; break; default: str = "Open failed, port is closed"; } return kegs_malloc_str(str); } void scc_config_changed(int port, int cfg_changed, int remote_changed, int serial_dev_changed) { Scc *scc_ptr; int must_change; // Check if scc_init() was called, if not get out if(!g_scc_init) { return; } // F4 may have changed the serial port config. If so, close old // port, open new one. scc_ptr = &(g_scc[port]); must_change = cfg_changed; switch(scc_ptr->cur_state) { case 0: // Using serial port must_change |= serial_dev_changed; break; case 2: // Using remote connection must_change |= remote_changed; break; } if(must_change) { scc_port_close(port); } } void scc_update(dword64 dfcyc) { int i; // called each VBL update for(i = 0; i < 2; i++) { g_scc[i].write_called_this_vbl = 0; g_scc[i].read_called_this_vbl = 0; // These calls will try to open the port if it's closed scc_try_to_empty_writebuf(dfcyc, i); scc_try_fill_readbuf(dfcyc, i); g_scc[i].write_called_this_vbl = 0; g_scc[i].read_called_this_vbl = 0; } } void scc_try_to_empty_writebuf(dword64 dfcyc, int port) { Scc *scc_ptr; int cur_state; scc_ptr = &(g_scc[port]); cur_state = scc_ptr->cur_state; if(scc_ptr->write_called_this_vbl) { return; } if(scc_is_port_closed(dfcyc, port)) { return; // Port is not open } scc_ptr->write_called_this_vbl = 1; if(cur_state == 0) { #if defined(_WIN32) scc_serial_win_empty_writebuf(port); #else scc_serial_unix_empty_writebuf(port); #endif } else if(cur_state >= 1) { scc_socket_empty_writebuf(dfcyc, port); } } void scc_try_fill_readbuf(dword64 dfcyc, int port) { Scc *scc_ptr; int space_used, space_left, cur_state; scc_ptr = &(g_scc[port]); space_used = scc_ptr->in_wrptr - scc_ptr->in_rdptr; if(space_used < 0) { space_used += SCC_INBUF_SIZE; } space_left = (7*SCC_INBUF_SIZE/8) - space_used; if(space_left < 1) { /* Buffer is pretty full, don't try to get more */ return; } if(scc_is_port_closed(dfcyc, port)) { return; // Port is not open } #if 0 if(scc_ptr->read_called_this_vbl) { return; } #endif scc_ptr->read_called_this_vbl = 1; cur_state = scc_ptr->cur_state; if(cur_state == 0) { #if defined(_WIN32) scc_serial_win_fill_readbuf(dfcyc, port, space_left); #else scc_serial_unix_fill_readbuf(dfcyc, port, space_left); #endif } else if(cur_state >= 1) { scc_socket_fill_readbuf(dfcyc, port, space_left); } } void scc_do_event(dword64 dfcyc, int type) { Scc *scc_ptr; int port; port = type & 1; type = (type >> 1); scc_ptr = &(g_scc[port]); if(type == SCC_BR_EVENT) { /* baud rate generator counted down to 0 */ scc_ptr->br_event_pending = 0; scc_set_zerocnt_int(port); scc_maybe_br_event(dfcyc, port); } else if(type == SCC_TX_EVENT) { scc_ptr->tx_event_pending = 0; scc_ptr->tx_buf_empty = 1; scc_handle_tx_event(port); } else if(type == SCC_RX_EVENT) { scc_ptr->rx_event_pending = 0; scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } else { halt_printf("scc_do_event: %08x!\n", type); } return; } void show_scc_state() { Scc *scc_ptr; int i, j; for(i = 0; i < 2; i++) { scc_ptr = &(g_scc[i]); printf("SCC port: %d\n", i); for(j = 0; j < 16; j += 4) { printf("Reg %2d-%2d: %02x %02x %02x %02x\n", j, j+3, scc_ptr->reg[j], scc_ptr->reg[j+1], scc_ptr->reg[j+2], scc_ptr->reg[j+3]); } printf("state: %d, sockfd:%llx rdwrfd:%llx, win_com:%p, " "win_dcb:%p\n", scc_ptr->cur_state, (dword64)scc_ptr->sockfd, (dword64)scc_ptr->rdwrfd, scc_ptr->win_com_handle, scc_ptr->win_dcb_ptr); printf("in_rdptr: %04x, in_wr:%04x, out_rd:%04x, out_wr:%04x\n", scc_ptr->in_rdptr, scc_ptr->in_wrptr, scc_ptr->out_rdptr, scc_ptr->out_wrptr); printf("rx_queue_depth: %d, queue: %02x, %02x, %02x, %02x\n", scc_ptr->rx_queue_depth, scc_ptr->rx_queue[0], scc_ptr->rx_queue[1], scc_ptr->rx_queue[2], scc_ptr->rx_queue[3]); printf("want_ints: rx:%d, tx:%d, zc:%d\n", scc_ptr->wantint_rx, scc_ptr->wantint_tx, scc_ptr->wantint_zerocnt); printf("ev_pendings: rx:%d, tx:%d, br:%d\n", scc_ptr->rx_event_pending, scc_ptr->tx_event_pending, scc_ptr->br_event_pending); printf("br_dcycs: %f, tx_dcycs: %f, rx_dcycs: %f\n", scc_ptr->br_dcycs, scc_ptr->tx_dcycs, scc_ptr->rx_dcycs); printf("char_size: %d, baud_rate: %d, mode: %d\n", scc_ptr->char_size, scc_ptr->baud_rate, scc_ptr->mode); printf("modem_state: %dtelnet_mode:%d iac:%d, " "modem_cmd_len:%d\n", scc_ptr->modem_state, scc_ptr->telnet_mode, scc_ptr->telnet_iac, scc_ptr->modem_cmd_len); printf("telnet_loc_modes:%08x %08x, telnet_rem_motes:" "%08x %08x\n", scc_ptr->telnet_local_mode[0], scc_ptr->telnet_local_mode[1], scc_ptr->telnet_remote_mode[0], scc_ptr->telnet_remote_mode[1]); printf("modem_mode:%08x plus_mode:%d, out_char_dfcyc:%016llx\n", scc_ptr->modem_mode, scc_ptr->modem_plus_mode, scc_ptr->out_char_dfcyc); } } word32 scc_read_reg(dword64 dfcyc, int port) { Scc *scc_ptr; word32 ret; int regnum; scc_ptr = &(g_scc[port]); scc_ptr->mode = 0; regnum = scc_ptr->reg_ptr; /* port 0 is channel A, port 1 is channel B */ switch(regnum) { case 0: case 4: ret = 0x60; /* 0x44 = no dcd, no cts,0x6c = dcd ok, cts ok*/ if(scc_ptr->dcd) { ret |= 0x08; } //ret |= 0x8; /* HACK HACK */ if(scc_ptr->rx_queue_depth) { ret |= 0x01; } if(scc_ptr->tx_buf_empty) { ret |= 0x04; } if(scc_ptr->br_is_zero) { ret |= 0x02; } //printf("Read scc[%d] stat: %016llx : %02x dcd:%d\n", port, // dfcyc, ret, scc_ptr->dcd); break; case 1: case 5: /* HACK: residue codes not right */ ret = 0x07; /* all sent */ break; case 2: case 6: if(port == 0) { ret = scc_ptr->reg[2]; } else { // Port B, read RR2B int stat // The TELNET.SYSTEM by Colin Leroy-Mira uses RR2B ret = scc_do_read_rr2b() << 1; if(g_scc[0].reg[9] & 0x10) { // wr9 status high // Map bit 3->4, 2->5, 1->6 ret = ((ret << 1) & 0x10) | ((ret << 3) & 0x20) | ((ret << 5) & 0x40); } } break; case 3: case 7: if(port == 0) { ret = (g_irq_pending & 0x3f); } else { ret = 0; } break; case 8: ret = scc_read_data(dfcyc, port); break; case 9: case 13: ret = scc_ptr->reg[13]; break; case 10: case 14: ret = 0; break; case 11: case 15: ret = scc_ptr->reg[15]; break; case 12: ret = scc_ptr->reg[12]; break; default: halt_printf("Tried reading c03%x with regnum: %d!\n", 8+port, regnum); ret = 0; } scc_ptr->reg_ptr = 0; scc_printf("Read c03%x, rr%d, ret: %02x\n", 8+port, regnum, ret); dbg_log_info(dfcyc, 0, ret, (regnum << 20) | (0xc039 - port)); return ret; } void scc_write_reg(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; word32 old_val, changed_bits, irq_mask; int regnum, mode, tmp1; scc_ptr = &(g_scc[port]); regnum = scc_ptr->reg_ptr & 0xf; mode = scc_ptr->mode; if(mode == 0) { if((val & 0xf0) == 0) { /* Set reg_ptr */ scc_ptr->reg_ptr = val & 0xf; regnum = 0; scc_ptr->mode = 1; } } else { scc_ptr->reg_ptr = 0; scc_ptr->mode = 0; } old_val = scc_ptr->reg[regnum]; changed_bits = (old_val ^ val) & 0xff; dbg_log_info(dfcyc, (mode << 16) | scc_ptr->reg_ptr, (changed_bits << 16) | val, (regnum << 20) | (0x1c039 - port)); /* Set reg reg */ switch(regnum) { case 0: /* wr0 */ tmp1 = (val >> 3) & 0x7; switch(tmp1) { case 0x0: case 0x1: break; case 0x2: /* reset ext/status ints */ /* should clear other ext ints */ scc_clr_zerocnt_int(port); break; case 0x5: /* reset tx int pending */ scc_clr_tx_int(port); break; case 0x6: /* reset rr1 bits */ break; case 0x7: /* reset highest pri int pending */ irq_mask = g_irq_pending; if(port == 0) { /* Move SCC0 ints into SCC1 positions */ irq_mask = irq_mask >> 3; } if(irq_mask & IRQ_PENDING_SCC1_RX) { scc_clr_rx_int(port); } else if(irq_mask & IRQ_PENDING_SCC1_TX) { scc_clr_tx_int(port); } else if(irq_mask & IRQ_PENDING_SCC1_ZEROCNT) { scc_clr_zerocnt_int(port); } break; case 0x4: /* enable int on next rx char */ default: halt_printf("Wr c03%x to wr0 of %02x, bad cmd cd:%x!\n", 9-port, val, tmp1); } tmp1 = (val >> 6) & 0x3; switch(tmp1) { case 0x0: /* null code */ break; case 0x1: /* reset rx crc */ case 0x2: /* reset tx crc */ printf("Wr c03%x to wr0 of %02x!\n", 9-port, val); break; case 0x3: /* reset tx underrun/eom latch */ /* if no extern status pending, or being reset now */ /* and tx disabled, ext int with tx underrun */ /* ah, just do nothing */ break; } return; case 1: /* wr1 */ /* proterm sets this == 0x10, which is int on all rx */ scc_ptr->reg[regnum] = val; return; case 2: /* wr2 */ /* All values do nothing, let 'em all through! */ scc_ptr->reg[regnum] = val; return; case 3: /* wr3 */ if((val & 0x1e) != 0x0) { halt_printf("Wr c03%x to wr3 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 4: /* wr4 */ if((val & 0x30) != 0x00 || (val & 0x0c) == 0) { halt_printf("Wr c03%x to wr4 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 5: /* wr5 */ if((val & 0x15) != 0x0) { halt_printf("Wr c03%x to wr5 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits & 0x60) { scc_regen_clocks(port); } if(changed_bits & 0x80) { scc_printf("SCC port %d DTR:%d\n", port, val & 0x80); } return; case 6: /* wr6 */ if(val != 0) { halt_printf("Wr c03%x to wr6 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 7: /* wr7 */ if(val != 0) { halt_printf("Wr c03%x to wr7 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 8: /* wr8 */ scc_write_data(dfcyc, port, val); return; case 9: /* wr9 */ if((val & 0xc0)) { if(val & 0x80) { scc_reset_port(0); } if(val & 0x40) { scc_reset_port(1); } if((val & 0xc0) == 0xc0) { scc_hard_reset_port(0); scc_hard_reset_port(1); } } // Bit 5 is software interrupt ack, which does not exist on NMOS // Bit 2 sets IEO pin low, which doesn't exist either old_val = g_scc[0].reg[9]; g_scc[0].reg[9] = val; scc_evaluate_ints(0); scc_evaluate_ints(1); return; case 10: /* wr10 */ if((val & 0xff) != 0x00) { printf("Wr c03%x to wr10 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; return; case 11: /* wr11 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 12: /* wr12 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 13: /* wr13 */ scc_ptr->reg[regnum] = val; if(changed_bits) { scc_regen_clocks(port); } return; case 14: /* wr14 */ val = val | (old_val & (~0xffU)); switch((val >> 5) & 0x7) { case 0x0: // Null command (change nothing) break; case 0x1: // Enter search mode case 0x2: // Reset missing clock case 0x3: // Disable PLL case 0x6: // Set FM mode case 0x7: // Set NRZI mode // Disable the PLL effectively val = val & 0xff; // Clear all upper bits break; case 0x4: // DPLL source is BR gen val = (val & 0xff) | SCC_R14_DPLL_SOURCE_BRG; break; case 0x5: // DPLL source is RTxC val = (val & 0xff) | SCC_R14_DPLL_SOURCE_RTXC; break; default: halt_printf("Wr c03%x to wr14 of %02x, bad dpll cd!\n", 8+port, val); } if((val & 0x0c) != 0x0) { halt_printf("Wr c03%x to wr14 of %02x!\n", 8+port, val); } scc_ptr->reg[regnum] = val; if(changed_bits || (val != old_val)) { scc_regen_clocks(port); } scc_maybe_br_event(dfcyc, port); return; case 15: /* wr15 */ /* ignore all accesses since IIgs self test messes with it */ if((val & 0xff) != 0x0) { scc_printf("Write c03%x to wr15 of %02x!\n", 8+port, val); } if((g_scc[0].reg[9] & 0x8) && (val != 0)) { printf("Write wr15:%02x and master int en = 1!\n",val); /* set_halt(1); */ } scc_ptr->reg[regnum] = val; scc_maybe_br_event(dfcyc, port); scc_evaluate_ints(port); return; default: halt_printf("Wr c03%x to wr%d of %02x!\n", 8+port, regnum, val); return; } } // scc_read_data: Read from 0xc03b or 0xc03a word32 scc_read_data(dword64 dfcyc, int port) { Scc *scc_ptr; word32 ret; int depth; int i; scc_ptr = &(g_scc[port]); scc_try_fill_readbuf(dfcyc, port); depth = scc_ptr->rx_queue_depth; ret = 0; if(depth != 0) { ret = scc_ptr->rx_queue[0]; for(i = 1; i < depth; i++) { scc_ptr->rx_queue[i-1] = scc_ptr->rx_queue[i]; } scc_ptr->rx_queue_depth = depth - 1; scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } scc_printf("SCC read %04x: ret %02x, depth:%d\n", 0xc03b-port, ret, depth); dbg_log_info(dfcyc, 0, ret, 0xc03b - port); return ret; } void scc_write_data(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; scc_printf("SCC write %04x: %02x\n", 0xc03b-port, val); dbg_log_info(dfcyc, val, 0, 0x1c03b - port); scc_ptr = &(g_scc[port]); if(scc_ptr->reg[14] & 0x10) { /* local loopback! */ scc_add_to_readbuf(dfcyc, port, val); } else { scc_transmit(dfcyc, port, val); } scc_try_to_empty_writebuf(dfcyc, port); scc_maybe_tx_event(dfcyc, port); } word32 scc_do_read_rr2b() { word32 val; val = g_irq_pending & 0x3f; if(val == 0) { return 3; // 011 if no interrupts pending } // Do Channel A first. Priority order from SCC documentation if(val & IRQ_PENDING_SCC0_RX) { return 6; // 110 Ch A Rx char available } if(val & IRQ_PENDING_SCC0_TX) { return 4; // 100 Ch A Tx buffer empty } if(val & IRQ_PENDING_SCC0_ZEROCNT) { return 5; // 101 Ch A External/Status change } if(val & IRQ_PENDING_SCC1_RX) { return 2; // 010 Ch B Rx char available } if(val & IRQ_PENDING_SCC1_TX) { return 0; // 000 Ch B Tx buffer empty } if(val & IRQ_PENDING_SCC1_ZEROCNT) { return 1; // 001 Ch B External/Status change } return 3; } void scc_maybe_br_event(dword64 dfcyc, int port) { Scc *scc_ptr; double br_dcycs; scc_ptr = &(g_scc[port]); if(((scc_ptr->reg[14] & 0x01) == 0) || scc_ptr->br_event_pending) { return; } /* also, if ext ints not enabled, don't do baud rate ints */ if(((scc_ptr->reg[15] & 0x02) == 0) || ((scc_ptr->reg[9] & 8) == 0)) { return; } br_dcycs = scc_ptr->br_dcycs; if(br_dcycs < 1.0) { halt_printf("br_dcycs: %f!\n", br_dcycs); #if 0 printf("br_dcycs: %f!\n", br_dcycs); dbg_log_info(dfcyc, (word32)(br_dcycs * 65536), (scc_ptr->reg[15] << 24) | (scc_ptr->reg[14] << 16) | (scc_ptr->reg[13] << 8) | scc_ptr->reg[12], 0xdc1c); dbg_log_info(dfcyc, (scc_ptr->reg[11] << 24) | (scc_ptr->reg[10] << 16) | (scc_ptr->reg[9] << 8) | scc_ptr->reg[5], (scc_ptr->reg[4] << 24) | (scc_ptr->reg[3] << 16) | (scc_ptr->reg[1] << 8) | scc_ptr->reg[0], 0xdc1b); #endif } scc_ptr->br_event_pending = 1; add_event_scc(dfcyc + (dword64)(br_dcycs * 65536.0), SCC_MAKE_EVENT(port, SCC_BR_EVENT)); } void scc_evaluate_ints(int port) { Scc *scc_ptr; word32 irq_add_mask, irq_remove_mask; int mie; scc_ptr = &(g_scc[port]); mie = g_scc[0].reg[9] & 0x8; /* Master int en */ if(!mie) { /* There can be no interrupts if MIE=0 */ remove_irq(IRQ_PENDING_SCC1_RX | IRQ_PENDING_SCC1_TX | IRQ_PENDING_SCC1_ZEROCNT | IRQ_PENDING_SCC0_RX | IRQ_PENDING_SCC0_TX | IRQ_PENDING_SCC0_ZEROCNT); return; } irq_add_mask = 0; irq_remove_mask = 0; if(scc_ptr->wantint_rx) { irq_add_mask |= IRQ_PENDING_SCC1_RX; } else { irq_remove_mask |= IRQ_PENDING_SCC1_RX; } if(scc_ptr->wantint_tx) { irq_add_mask |= IRQ_PENDING_SCC1_TX; } else { irq_remove_mask |= IRQ_PENDING_SCC1_TX; } if(scc_ptr->wantint_zerocnt) { irq_add_mask |= IRQ_PENDING_SCC1_ZEROCNT; } else { irq_remove_mask |= IRQ_PENDING_SCC1_ZEROCNT; } if(port == 0) { /* Port 1 is in bits 0-2 and port 0 is in bits 3-5 */ irq_add_mask = irq_add_mask << 3; irq_remove_mask = irq_remove_mask << 3; } if(irq_add_mask) { add_irq(irq_add_mask); } if(irq_remove_mask) { remove_irq(irq_remove_mask); } } void scc_maybe_rx_event(dword64 dfcyc, int port) { Scc *scc_ptr; double rx_dcycs; int in_rdptr, in_wrptr, depth; scc_ptr = &(g_scc[port]); if(scc_ptr->rx_event_pending) { /* one pending already, wait for the event to arrive */ return; } in_rdptr = scc_ptr->in_rdptr; in_wrptr = scc_ptr->in_wrptr; depth = scc_ptr->rx_queue_depth; if((in_rdptr == in_wrptr) || (depth >= 3)) { /* no more chars or no more space, just get out */ return; } if(depth < 0) { depth = 0; } /* pull char from in_rdptr into queue */ scc_ptr->rx_queue[depth] = scc_ptr->in_buf[in_rdptr]; scc_ptr->in_rdptr = (in_rdptr + 1) & (SCC_INBUF_SIZE - 1); scc_ptr->rx_queue_depth = depth + 1; scc_maybe_rx_int(port); rx_dcycs = scc_ptr->rx_dcycs; scc_ptr->rx_event_pending = 1; add_event_scc(dfcyc + (dword64)(rx_dcycs*65536.0), SCC_MAKE_EVENT(port, SCC_RX_EVENT)); } void scc_maybe_rx_int(int port) { Scc *scc_ptr; int depth; int rx_int_mode; scc_ptr = &(g_scc[port]); depth = scc_ptr->rx_queue_depth; if(depth <= 0) { /* no more chars, just get out */ scc_clr_rx_int(port); return; } rx_int_mode = (scc_ptr->reg[1] >> 3) & 0x3; if((rx_int_mode == 1) || (rx_int_mode == 2)) { scc_ptr->wantint_rx = 1; } scc_evaluate_ints(port); } void scc_clr_rx_int(int port) { g_scc[port].wantint_rx = 0; scc_evaluate_ints(port); } void scc_handle_tx_event(int port) { Scc *scc_ptr; int tx_int_mode; scc_ptr = &(g_scc[port]); /* nothing pending, see if ints on */ tx_int_mode = (scc_ptr->reg[1] & 0x2); if(tx_int_mode) { scc_ptr->wantint_tx = 1; } scc_evaluate_ints(port); } void scc_maybe_tx_event(dword64 dfcyc, int port) { Scc *scc_ptr; double tx_dcycs; scc_ptr = &(g_scc[port]); if(scc_ptr->tx_event_pending) { /* one pending already, tx_buf is full */ scc_ptr->tx_buf_empty = 0; } else { /* nothing pending, see ints on */ scc_ptr->tx_buf_empty = 0; scc_evaluate_ints(port); tx_dcycs = scc_ptr->tx_dcycs; scc_ptr->tx_event_pending = 1; add_event_scc(dfcyc + (dword64)(tx_dcycs * 65536.0), SCC_MAKE_EVENT(port, SCC_TX_EVENT)); } } void scc_clr_tx_int(int port) { g_scc[port].wantint_tx = 0; scc_evaluate_ints(port); } void scc_set_zerocnt_int(int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); if(scc_ptr->reg[15] & 0x2) { scc_ptr->wantint_zerocnt = 1; } scc_evaluate_ints(port); } void scc_clr_zerocnt_int(int port) { g_scc[port].wantint_zerocnt = 0; scc_evaluate_ints(port); } void scc_add_to_readbuf(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int in_wrptr, in_wrptr_next, in_rdptr, safe_val; scc_ptr = &(g_scc[port]); if((scc_ptr->reg[5] & 0x60) != 0x60) { // HACK: this is tx char size! val = val & 0x7f; } in_wrptr = scc_ptr->in_wrptr; in_rdptr = scc_ptr->in_rdptr; in_wrptr_next = (in_wrptr + 1) & (SCC_INBUF_SIZE - 1); if(in_wrptr_next != in_rdptr) { scc_ptr->in_buf[in_wrptr] = val; scc_ptr->in_wrptr = in_wrptr_next; safe_val = val & 0x7f; if((safe_val < 0x20) || (safe_val >= 0x7f)) { safe_val = '.'; } scc_printf("scc in port[%d] add 0x%02x (%c), %d,%d != %d\n", port, val, safe_val, in_wrptr, in_wrptr_next, in_rdptr); g_scc_overflow = 0; } else { if(g_scc_overflow == 0) { g_code_yellow++; printf("scc inbuf overflow port %d\n", port); } g_scc_overflow = 1; } scc_maybe_rx_event(dfcyc, port); scc_maybe_rx_int(port); } void scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...) { va_list ap; char *bufptr; int len, c; int i; va_start(ap, fmt); bufptr = malloc(4096); bufptr[0] = 0; vsnprintf(bufptr, 4090, fmt, ap); len = (int)strlen(bufptr); for(i = 0; i < len; i++) { c = bufptr[i]; if(c == 0x0a) { scc_add_to_readbuf(dfcyc, port, 0x0d); } scc_add_to_readbuf(dfcyc, port, c); } va_end(ap); } void scc_transmit(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int out_wrptr, out_rdptr; scc_ptr = &(g_scc[port]); // printf("scc_transmit port:%d val:%02x \n", port, val); /* See if port initialized, if not, do so now */ if(scc_is_port_closed(dfcyc, port)) { printf(" port %d is closed, cur_state:%d\n", port, scc_ptr->cur_state); return; // No working serial port, just toss it and go } if(!scc_ptr->tx_buf_empty) { /* toss character! */ printf("Tossing char\n"); return; } out_wrptr = scc_ptr->out_wrptr; out_rdptr = scc_ptr->out_rdptr; if(scc_ptr->tx_dcycs < 1.0) { if(out_wrptr != out_rdptr) { /* do just one char, then get out */ printf("tx_dcycs < 1\n"); return; } } if(g_serial_mask[port] || (scc_ptr->reg[5] & 0x60) != 0x60) { val = val & 0x7f; } scc_add_to_writebuf(dfcyc, port, val); } void scc_add_to_writebuf(dword64 dfcyc, int port, word32 val) { Scc *scc_ptr; int out_wrptr, out_wrptr_next, out_rdptr; // printf("scc_add_to_writebuf p:%d, val:%02x\n", port, val); if(scc_is_port_closed(dfcyc, port)) { return; // Port is closed } scc_ptr = &(g_scc[port]); out_wrptr = scc_ptr->out_wrptr; out_rdptr = scc_ptr->out_rdptr; out_wrptr_next = (out_wrptr + 1) & (SCC_OUTBUF_SIZE - 1); if(out_wrptr_next != out_rdptr) { scc_ptr->out_buf[out_wrptr] = val; scc_ptr->out_wrptr = out_wrptr_next; scc_printf("scc wrbuf port %d had char 0x%02x added\n", port, val); g_scc_overflow = 0; } else { if(g_scc_overflow == 0) { g_code_yellow++; printf("scc outbuf overflow port %d\n", port); } g_scc_overflow = 1; } } ================================================ FILE: upstream/kegs/src/scc.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_scc_h[] = "@(#)$KmKId: scc.h,v 1.27 2025-01-11 18:45:06+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include #ifdef _WIN32 # include #else # include # include # include #endif #if defined(HPUX) || defined(__linux__) || defined(SOLARIS) # define SCC_SOCKETS #endif #if defined(MAC) || defined(__MACH__) || defined(_WIN32) # define SCC_SOCKETS #endif /* my scc port 0 == channel A, port 1 = channel B */ #define SCC_INBUF_SIZE 512 /* must be a power of 2 */ #define SCC_OUTBUF_SIZE 512 /* must be a power of 2 */ #define SCC_MODEM_MAX_CMD_STR 128 #ifndef _WIN32 # define SOCKET int /* For non-Windows */ # define INVALID_SOCKET (-1) /* For non-Windows */ #endif STRUCT(Scc) { int cur_state; int modem_state; SOCKET sockfd; SOCKET rdwrfd; void *sockaddr_ptr; // Socket: pointer to sockaddr struct int sockaddr_size; // Socket: sizeof(sockaddr_in) int unix_dev_fd; // Unix fd to real serial device void *win_com_handle; // Win32 handle to COMx port void *win_dcb_ptr; // Win32 ptr to COMx DCB int read_called_this_vbl; int write_called_this_vbl; byte dcd; byte reg_ptr; byte br_is_zero; byte tx_buf_empty; word32 mode; byte reg[16]; int rx_queue_depth; byte rx_queue[4]; int in_rdptr; int in_wrptr; byte in_buf[SCC_INBUF_SIZE]; int out_rdptr; int out_wrptr; byte out_buf[SCC_OUTBUF_SIZE]; int wantint_rx; int wantint_tx; int wantint_zerocnt; double br_dcycs; double tx_dcycs; double rx_dcycs; int br_event_pending; int rx_event_pending; int tx_event_pending; int char_size; int baud_rate; dword64 out_char_dfcyc; int socket_error; int socket_num_rings; dword64 socket_last_ring_dfcyc; word32 modem_mode; int modem_plus_mode; int modem_s0_val; int modem_s2_val; int telnet_mode; int telnet_iac; word32 telnet_local_mode[2]; word32 telnet_remote_mode[2]; word32 telnet_reqwill_mode[2]; word32 telnet_reqdo_mode[2]; word32 modem_out_portnum; int modem_cmd_len; byte modem_cmd_str[SCC_MODEM_MAX_CMD_STR + 5]; }; #define SCCMODEM_NOECHO 0x0001 #define SCCMODEM_NOVERBOSE 0x0002 ================================================ FILE: upstream/kegs/src/scc_socket_driver.c ================================================ const char rcsid_scc_socket_driver_c[] = "@(#)$KmKId: scc_socket_driver.c,v 1.37 2025-04-29 22:17:38+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // This file contains the socket calls for Win32 and Mac/Linux // Win32: see: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ // Modem init string GBBS GSPORT.HST: ats0=1s2=128v0 // Modem init string Warp6: atm0e0v0s0=0s7=25 atx4h0 at&c1&d2 #include "defc.h" #include "scc.h" #include extern int Verbose; extern Scc g_scc[2]; extern int g_serial_cfg[2]; extern char *g_serial_remote_ip[2]; extern int g_serial_remote_port[2]; extern int g_serial_modem_response_code; extern int g_serial_modem_allow_incoming; extern int g_serial_modem_init_telnet; #ifdef _WIN32 #include typedef int socklen_t; #endif #ifndef _WIN32 extern int h_errno; #endif int g_wsastartup_called = 0; #ifndef _WIN32 // SOCKET is defined in scc.h, and is an "int" for non-Windows systems // INVALID_SOCKET is -1 for non-WINDOWS # define closesocket(s) close(s) #endif /* Usage: scc_socket_open() called to init socket mode */ /* At all times, we try to have a listen running on the incoming socket */ /* If we want to dial out, we close the incoming socket and create a new */ /* outgoing socket. Any hang-up causes the socket to be closed and it will */ /* then re-open on a subsequent call to scc_socket_open */ void scc_socket_open(dword64 dfcyc, int port, int cfg) { Scc *scc_ptr; // printf("scc_socket_open p:%d cfg:%d\n", port, cfg); #if defined(_WIN32) && defined(SCC_SOCKETS) WSADATA wsadata; int ret; if(g_wsastartup_called == 0) { ret = WSAStartup(MAKEWORD(2,0), &wsadata); printf("WSAStartup ret: %d\n", ret); g_wsastartup_called = 1; } #endif scc_ptr = &(g_scc[port]); scc_ptr->cur_state = cfg; // successful socket scc_ptr->sockfd = INVALID_SOCKET; /* Indicate no socket open yet */ scc_ptr->rdwrfd = INVALID_SOCKET; /* Indicate no socket open yet */ scc_ptr->modem_state = 0; /* 0 means talk to socket */ /* 1 means talk to modem */ scc_ptr->socket_error = 0; scc_ptr->socket_num_rings = 0; scc_ptr->socket_last_ring_dfcyc = 0; scc_ptr->dcd = 1; /* 1 means carrier */ scc_ptr->modem_s0_val = 0; // Number of rings before answer scc_ptr->sockaddr_size = sizeof(struct sockaddr_in); free(scc_ptr->sockaddr_ptr); scc_ptr->sockaddr_ptr = malloc(scc_ptr->sockaddr_size); // cfg==1: Virtual Modem. All set up, wait for AT commands now // cfg==2: Outgoing connection to g_serial_remote_ip[], do it now // cfg==3: Incoming connection on 6501/6502. Do it now if(cfg == 2) { scc_ptr->modem_state = 0; // Do not open it now, it will be opened when output is // sent to the port } if(cfg == 1) { scc_ptr->modem_state = 1; scc_ptr->dcd = 0; scc_socket_open_incoming(dfcyc, port); } else if(cfg == 3) { scc_ptr->modem_state = 0; scc_socket_open_incoming(dfcyc, port); } } void scc_socket_close(int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET rdwrfd; SOCKET sockfd; int do_init; int i; scc_ptr = &(g_scc[port]); // printf("In scc_socket_close, %d\n", port); rdwrfd = scc_ptr->rdwrfd; do_init = 0; if(rdwrfd != INVALID_SOCKET) { printf("socket_close: rdwrfd=%llx, closing\n", (dword64)rdwrfd); closesocket(rdwrfd); do_init = 1; } sockfd = scc_ptr->sockfd; if((sockfd != INVALID_SOCKET) && (rdwrfd != sockfd)) { printf("socket_close: sockfd=%llx, closing\n", (dword64)sockfd); closesocket(sockfd); do_init = 1; } scc_ptr->modem_state = 0; scc_ptr->socket_num_rings = 0; if(scc_ptr->cur_state == 1) { // Virtual modem scc_ptr->modem_state = 1; scc_ptr->dcd = 0; } if(do_init) { scc_ptr->modem_cmd_len = 0; scc_ptr->telnet_iac = 0; for(i = 0; i < 2; i++) { scc_ptr->telnet_local_mode[i] = 0; scc_ptr->telnet_remote_mode[i] = 0; scc_ptr->telnet_reqwill_mode[i] = 0; scc_ptr->telnet_reqdo_mode[i] = 0; } scc_ptr->rdwrfd = INVALID_SOCKET; scc_ptr->sockfd = INVALID_SOCKET; } #endif } void scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry) { #ifdef SCC_SOCKETS Scc *scc_ptr; // printf("In scc_socket_close_extended, %d, %016llx\n", port, dfcyc); scc_socket_close(port); scc_ptr = &(g_scc[port]); if(scc_ptr->cur_state == 1) { // Virtual modem mode scc_socket_send_modem_code(dfcyc, port, 3); scc_ptr->modem_state = 1; // and go back to modem mode } else if((scc_ptr->cur_state == 2) && !allow_retry) { // Remote IP scc_ptr->cur_state = -2; // Error, give up } #endif } void scc_socket_maybe_open(dword64 dfcyc, int port, int must) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd != INVALID_SOCKET) { // Valid socket. See if we should hang up if((scc_ptr->reg[5] & 0x80) && (scc_ptr->cur_state == 1)) { // Handle DTR forcing modem hang-up now printf("DTR is deasserted, hanging up\n"); scc_socket_close(port); } return; } if(scc_ptr->cur_state == 2) { if(must) { scc_socket_open_outgoing(dfcyc, port, g_serial_remote_ip[port], g_serial_remote_port[port]); } } else { scc_socket_open_incoming(dfcyc, port); } } void scc_socket_open_incoming(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS SOCKET sockfd; struct sockaddr_in sa_in; Scc *scc_ptr; int on, ret, inc; inc = 0; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd != INVALID_SOCKET) { /* it's already open, get out */ return; } //printf("scc_socket_open_incoming: scc socket close being called\n"); scc_socket_close(port); scc_ptr->socket_num_rings = 0; memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); if(scc_ptr->cur_state == 2) { // Outgoing connection only, never accept an incoming connect return; } if(scc_ptr->cur_state == 1) { if(!g_serial_modem_allow_incoming) { // Virtual modem with incoming connections disallowed return; } if(scc_ptr->reg[5] & 0x80) { // DTR is forcing hang-up. Don't open incoming socket return; } } while(1) { sockfd = socket(AF_INET, SOCK_STREAM, 0); //printf("sockfd ret: %llx\n", (dword64)sockfd); if(sockfd == INVALID_SOCKET) { printf("socket ret: -1, errno: %d\n", errno); scc_socket_close(port); scc_ptr->socket_error = -1; return; } /* printf("socket ret: %d\n", sockfd); */ on = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if(ret < 0) { printf("setsockopt REUSEADDR ret: %d, err:%d\n", ret, errno); scc_socket_close(port); return; } memset(&sa_in, 0, sizeof(sa_in)); sa_in.sin_family = AF_INET; sa_in.sin_port = htons(6501 + port + inc); sa_in.sin_addr.s_addr = htonl(INADDR_ANY); ret = bind(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); printf("bind ret:%d\n", ret); if(ret >= 0) { ret = listen(sockfd, 1); break; } /* else ret to bind was < 0 */ printf("bind or listen ret: %d, errno: %d\n", ret, errno); inc++; closesocket(sockfd); printf("Trying next port: %d\n", 6501 + port + inc); if(inc >= 10) { printf("Too many retries, quitting\n"); scc_socket_close(port); scc_ptr->socket_error = -1; return; } } printf("SCC port %d is at unix port %d\n", port, 6501 + port + inc); scc_ptr->sockfd = sockfd; scc_ptr->socket_error = 0; scc_socket_make_nonblock(dfcyc, port); #endif } void scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, int remote_port) { #ifdef SCC_SOCKETS Scc *scc_ptr; struct sockaddr_in sa_in; struct hostent *hostentptr; int on; int ret; SOCKET sockfd; scc_ptr = &(g_scc[port]); // printf("scc socket close being called from socket_open_out\n"); scc_socket_close(port); memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); sockfd = socket(AF_INET, SOCK_STREAM, 0); // printf("outgoing sockfd ret: %llx\n", (dword64)sockfd); if(sockfd == INVALID_SOCKET) { printf("socket ret: %llx, errno: %d\n", (dword64)sockfd, errno); scc_socket_close_extended(dfcyc, port, 0); return; } /* printf("socket ret: %d\n", sockfd); */ on = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if(ret < 0) { printf("setsockopt REUSEADDR ret: %d, err:%d\n", ret, errno); scc_socket_close_extended(dfcyc, port, 0); return; } memset(&sa_in, 0, sizeof(sa_in)); sa_in.sin_family = AF_INET; sa_in.sin_port = htons(remote_port); while(*remote_ip_str == ' ') { remote_ip_str++; // Skip leading blanks } hostentptr = gethostbyname(remote_ip_str); printf("Connecting to %s, port:%d\n", remote_ip_str, remote_port); if(hostentptr == 0) { #ifdef _WIN32 fatal_printf("Lookup host %s failed\n", &scc_ptr->modem_cmd_str[0]); #else fatal_printf("Lookup host %s failed, herrno: %d\n", &scc_ptr->modem_cmd_str[0], h_errno); #endif closesocket(sockfd); scc_socket_close_extended(dfcyc, port, 0); return; } memcpy(&sa_in.sin_addr.s_addr, hostentptr->h_addr, hostentptr->h_length); /* The above copies the 32-bit internet address into */ /* sin_addr.s_addr. It's in correct network format */ ret = connect(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); if(ret < 0) { printf("connect ret: %d, errno: %d\n", ret, errno); closesocket(sockfd); scc_socket_close_extended(dfcyc, port, 0); return; } scc_socket_modem_connect(dfcyc, port); scc_ptr->dcd = 1; /* carrier on */ scc_ptr->modem_state = 0; /* talk to socket */ scc_ptr->socket_num_rings = 0; printf("SCC port %d is now outgoing to %s:%d\n", port, remote_ip_str, remote_port); scc_ptr->sockfd = sockfd; scc_socket_make_nonblock(dfcyc, port); scc_ptr->rdwrfd = scc_ptr->sockfd; #endif } void scc_socket_make_nonblock(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET sockfd; int ret; # ifdef _WIN32 u_long flags; # else int flags; # endif scc_ptr = &(g_scc[port]); sockfd = scc_ptr->sockfd; # ifdef _WIN32 flags = 1; ret = ioctlsocket(sockfd, FIONBIO, &flags); if(ret != 0) { printf("ioctlsocket ret: %d\n", ret); } # else flags = fcntl(sockfd, F_GETFL, 0); if(flags == -1) { printf("fcntl GETFL ret: %d, errno: %d\n", flags, errno); scc_socket_close_extended(dfcyc, port, 0); return; } ret = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); if(ret == -1) { printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); scc_socket_close_extended(dfcyc, port, 0); return; } # endif #endif } void scc_accept_socket(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS Scc *scc_ptr; SOCKET rdwrfd; socklen_t address_len; int flags, num_rings, ret; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd == INVALID_SOCKET) { // printf("in accept_socket, call socket_open\n"); scc_socket_open_incoming(dfcyc, port); } if(scc_ptr->sockfd == INVALID_SOCKET) { return; /* just give up */ } if(scc_ptr->rdwrfd == INVALID_SOCKET) { address_len = scc_ptr->sockaddr_size; rdwrfd = accept(scc_ptr->sockfd, scc_ptr->sockaddr_ptr, &address_len); scc_ptr->sockaddr_size = (int)address_len; if(rdwrfd == INVALID_SOCKET) { return; } flags = 0; ret = 0; #ifndef _WIN32 /* For Linux, we need to set O_NONBLOCK on the rdwrfd */ flags = fcntl(rdwrfd, F_GETFL, 0); if(flags == -1) { printf("fcntl GETFL ret: %d, errno: %d\n", flags,errno); return; } ret = fcntl(rdwrfd, F_SETFL, flags | O_NONBLOCK); if(ret == -1) { printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); return; } #endif scc_ptr->rdwrfd = rdwrfd; printf("Set port[%d].rdwrfd = %llx\n", port, (dword64)rdwrfd); num_rings = 4; if(scc_ptr->modem_s0_val > 0) { num_rings = scc_ptr->modem_s0_val; } scc_ptr->socket_num_rings = num_rings; scc_ptr->socket_last_ring_dfcyc = 0; /* do ring now*/ /* and send some telnet codes */ if(g_serial_modem_init_telnet) { scc_ptr->telnet_reqwill_mode[0] = 0xa; scc_ptr->telnet_reqdo_mode[0] = 0xa; /* 3=GO_AH and 1=ECHO */ scc_ptr->telnet_reqdo_mode[1] = 0x4; // 34=LINEMODE } printf("Telnet reqwill and reqdo's initialized: %08x_%08x," "%08x_%08x\n", scc_ptr->telnet_reqwill_mode[1], scc_ptr->telnet_reqwill_mode[0], scc_ptr->telnet_reqdo_mode[1], scc_ptr->telnet_reqdo_mode[0]); scc_socket_modem_do_ring(dfcyc, port); } #endif } void scc_socket_telnet_reqs(dword64 dfcyc, int port) { Scc *scc_ptr; word32 mask, willmask, domask; int i, j; scc_ptr = &(g_scc[port]); for(i = 0; i < 64; i++) { j = i >> 5; mask = 1 << (i & 31); willmask = scc_ptr->telnet_reqwill_mode[j]; if(willmask & mask) { scc_printf("Telnet reqwill %d\n", i); scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfb); /* WILL */ scc_add_to_writebuf(dfcyc, port, i); } domask = scc_ptr->telnet_reqdo_mode[j]; if(domask & mask) { scc_printf("Telnet reqdo %d\n", i); scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfd); /* DO */ scc_add_to_writebuf(dfcyc, port, i); } } } void scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left) { #ifdef SCC_SOCKETS byte tmp_buf[256]; Scc *scc_ptr; SOCKET rdwrfd; int ret; int i; scc_ptr = &(g_scc[port]); scc_accept_socket(dfcyc, port); scc_socket_modem_do_ring(dfcyc, port); if(scc_ptr->modem_state) { /* Just get out, this is modem mode */ /* The transmit function stuffs any bytes in receive buf */ return; } rdwrfd = scc_ptr->rdwrfd; if(rdwrfd == INVALID_SOCKET) { return; /* just get out */ } /* Try reading some bytes */ space_left = MY_MIN(space_left, 256); ret = (int)recv(rdwrfd, tmp_buf, space_left, 0); if(ret > 0) { for(i = 0; i < ret; i++) { if(tmp_buf[i] == 0) { /* Skip null chars */ continue; } scc_socket_recvd_char(dfcyc, port, tmp_buf[i]); } } else if(ret == 0) { /* assume socket close */ printf("recv got 0 from rdwrfd=%llx, closing\n", (dword64)rdwrfd); scc_socket_close_extended(dfcyc, port, 1); } #endif } void scc_socket_recvd_char(dword64 dfcyc, int port, int c) { Scc *scc_ptr; word32 locmask, remmask, mask, reqwillmask, reqdomask; int telnet_mode, telnet_iac, eff_c, cpos, reply; scc_ptr = &(g_scc[port]); telnet_mode = scc_ptr->telnet_mode; telnet_iac = scc_ptr->telnet_iac; eff_c = c; if(scc_ptr->cur_state == 2) { // Outgoing only connection, support 8-bit transfers with // no telnet command support telnet_mode = 0; telnet_iac = 0; } else if(telnet_iac == 0) { if(c == 0xff) { scc_ptr->telnet_iac = 0xff; return; /* and just get out */ } } else { /* last char was 0xff, see if this is 0xff */ if(c != 0xff) { /* this is some kind of command */ eff_c = eff_c | 0x100; /* indicate prev char IAC */ } } scc_ptr->telnet_iac = 0; mask = 1 << (c & 31); cpos = (c >> 5) & 1; locmask = scc_ptr->telnet_local_mode[cpos] & mask; remmask = scc_ptr->telnet_remote_mode[cpos] & mask; reqwillmask = scc_ptr->telnet_reqwill_mode[cpos] & mask; reqdomask = scc_ptr->telnet_reqdo_mode[cpos] & mask; switch(telnet_mode) { case 0: /* just passing through bytes */ switch(eff_c) { case 0x1fe: /* DON'T */ case 0x1fd: /* DO */ case 0x1fc: /* WON'T */ case 0x1fb: /* WILL */ case 0x1fa: /* SB */ telnet_mode = c; break; default: if(eff_c < 0x100) { scc_add_to_readbuf(dfcyc, port, c); } break; } break; case 3: /* LINEMODE SB SLC, octet 0 */ if(eff_c == 0x1f0) { /* SE, the end */ telnet_mode = 0; } scc_printf("LINEMODE SLC octet 0: %02x\n", c); telnet_mode = 4; break; case 4: /* LINEMODE SB SLC, octet 1 */ scc_printf("LINEMODE SLC octet 1: %02x\n", c); telnet_mode = 5; if(eff_c == 0x1f0) { /* SE, the end */ scc_printf("Got SE at octet 1...\n"); telnet_mode = 0; } break; case 5: /* LINEMODE SB SLC, octet 2 */ scc_printf("LINEMODE SLC octet 2: %02x\n", c); telnet_mode = 3; if(eff_c == 0x1f0) { /* SE, the end */ printf("Got Telnet SE at octet 2...\n"); telnet_mode = 0; } break; case 34: /* LINEMODE SB beginning */ switch(c) { case 3: /* SLC */ telnet_mode = 3; break; default: telnet_mode = 0xee; /* go to SB eat */ break; } break; case 0xfa: /* in 0xfa = SB mode, eat up subcommands */ switch(c) { case 34: /* LINEMODE */ telnet_mode = 34; break; default: telnet_mode = 0xee; /* SB eat mode */ break; } break; case 0xee: /* in SB eat mode */ if(eff_c == 0x1f0) { /* SE, end of sub-command */ telnet_mode = 0; } else { /* just stay in eat mode */ } break; case 0xfe: /* previous char was "DON'T" */ if(locmask && (reqwillmask == 0)) { /* it's a mode change */ /* always OK to turn off a mode that we had on */ scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfc); /* WON'T */ scc_add_to_writebuf(dfcyc, port, c); } scc_ptr->telnet_local_mode[cpos] &= ~mask; scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfd: /* previous char was "DO" */ reply = 0xfc; if(locmask == 0 && (reqwillmask == 0)) { /* it's a mode change, send some response */ reply = 0xfc; /* nack it with WON'T */ if(c == 0x03 || c == 0x01) { reply = 0xfb; /* Ack with WILL */ } scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, reply); scc_add_to_writebuf(dfcyc, port, c); } if(reqwillmask || (reply == 0xfb)) { scc_ptr->telnet_local_mode[cpos] |= mask; } scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfc: /* previous char was "WON'T" */ if(remmask && (reqdomask == 0)) { /* it's a mode change, ack with DON'T */ scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, 0xfe); /* DON'T */ scc_add_to_writebuf(dfcyc, port, c); } scc_ptr->telnet_remote_mode[cpos] &= ~mask; scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; telnet_mode = 0; break; case 0xfb: /* previous char was "WILL" */ reply = 0xfe; /* nack it with DON'T */ if(remmask == 0 && (reqdomask == 0)) { /* it's a mode change, send some response */ if(c == 0x03 || c == 0x01) { reply = 0xfd; /* Ack with DO */ } scc_add_to_writebuf(dfcyc, port, 0xff); scc_add_to_writebuf(dfcyc, port, reply); scc_add_to_writebuf(dfcyc, port, c); } if(reqdomask || (reply == 0xfd)) { scc_ptr->telnet_remote_mode[cpos] |= mask; } scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; telnet_mode = 0; break; default: telnet_mode = 0; break; } scc_ptr->telnet_mode = telnet_mode; } void scc_socket_empty_writebuf(dword64 dfcyc, int port) { #ifdef SCC_SOCKETS # ifndef _WIN32 struct sigaction newact, oldact; # endif Scc *scc_ptr; dword64 diff_dusec; SOCKET rdwrfd; int plus_mode, plus_char, rdptr, wrptr, done, ret, len, c; int i; scc_socket_maybe_open(dfcyc, port, 0); scc_ptr = &(g_scc[port]); /* See if +++ done and we should go to command mode */ diff_dusec = (dfcyc - scc_ptr->out_char_dfcyc) >> 16; if((diff_dusec > 900LL*1000) && (scc_ptr->modem_plus_mode == 3) && (scc_ptr->modem_state == 0) && (scc_ptr->cur_state == 1)) { scc_ptr->modem_state = 1; /* go modem mode, stay connect*/ scc_ptr->modem_plus_mode = 0; scc_socket_send_modem_code(dfcyc, port, 0); // "OK" } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { done = 1; break; } rdwrfd = scc_ptr->rdwrfd; len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } //printf("Writing data, %d bytes, modem_state:%d\n", len, // scc_ptr->modem_state); if(len <= 0) { done = 1; break; } scc_socket_maybe_open(dfcyc, port, 1); if(scc_ptr->modem_state) { len = 1; scc_socket_modem_write(dfcyc, port, scc_ptr->out_buf[rdptr]); scc_ptr->write_called_this_vbl = 0; ret = 1; } else { if(rdwrfd == INVALID_SOCKET) { return; } plus_char = scc_ptr->modem_s2_val; if((plus_char == 0) || (plus_char >= 128)) { plus_char = 0xfff; // Invalid scc_ptr->modem_plus_mode = 0; } // Look for '+++' within .8 seconds for(i = 0; i < len; i++) { c = scc_ptr->out_buf[rdptr + i]; plus_mode = scc_ptr->modem_plus_mode; diff_dusec = (dfcyc - scc_ptr->out_char_dfcyc) >> 16; if(c == plus_char && plus_mode == 0) { if(diff_dusec > 500LL*1000) { scc_ptr->modem_plus_mode = 1; } } else if(c == plus_char) { if(diff_dusec < 800LL*1000) { plus_mode++; scc_ptr->modem_plus_mode = plus_mode; } } else { scc_ptr->modem_plus_mode = 0; } scc_ptr->out_char_dfcyc = dfcyc; } # ifndef _WIN32 // ignore SIGPIPE around writes to the socket, so we // can catch a closed socket and prepare to accept // a new connection. Otherwise, SIGPIPE kills KEGS sigemptyset(&newact.sa_mask); newact.sa_handler = SIG_IGN; newact.sa_flags = 0; sigaction(SIGPIPE, &newact, &oldact); # endif ret = (int)send(rdwrfd, &(scc_ptr->out_buf[rdptr]), len, 0); # ifndef _WIN32 sigaction(SIGPIPE, &oldact, 0); /* restore previous SIGPIPE behavior */ # endif #if 0 printf("sock output: %02x, len:%d\n", scc_ptr->out_buf[rdptr], len); #endif } if(ret == 0) { done = 1; /* give up for now */ break; } else if(ret < 0) { /* assume socket is dead */ printf("socket write failed, resuming modem mode\n"); scc_socket_close_extended(dfcyc, port, 1); done = 1; break; } else { rdptr = rdptr + ret; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } #endif } void scc_socket_modem_write(dword64 dfcyc, int port, int c) { Scc *scc_ptr; char *str; word32 modem_mode; int do_echo, got_at, len; scc_ptr = &(g_scc[port]); if(scc_ptr->sockfd == INVALID_SOCKET) { scc_socket_open_incoming(dfcyc, port); } modem_mode = scc_ptr->modem_mode; str = (char *)&(scc_ptr->modem_cmd_str[0]); do_echo = ((modem_mode & SCCMODEM_NOECHO) == 0); len = scc_ptr->modem_cmd_len; got_at = 0; #if 0 printf("T:%016llx M[%d][%d]: %02x\n", dfcyc, port, len, c); #endif if(len >= 2 && str[0] == 'a' && str[1] == 't') { /* we've got an 'at', do not back up past it */ got_at = 1; } if(c == 0x0d) { if(do_echo) { scc_add_to_readbuf(dfcyc, port, c); /* echo cr */ scc_add_to_readbuf(dfcyc, port, 0x0a); /* echo lf */ } do_echo = 0; /* already did the echo */ scc_socket_do_cmd_str(dfcyc, port); scc_ptr->modem_cmd_len = 0; len = 0; str[0] = 0; } else if(c == 0x08) { if(len <= 0) { do_echo = 0; /* do not go past left margin */ } else if(len == 2 && got_at) { do_echo = 0; /* do not erase "AT" */ } else { /* erase a character */ len--; str[len] = 0; } } else if(c < 0x20) { /* ignore all control characters, don't echo */ /* includes line feeds and nulls */ do_echo = 0; } else { /* other characters */ if(len < SCC_MODEM_MAX_CMD_STR) { str[len] = tolower(c); str[len+1] = 0; len++; } } scc_ptr->modem_cmd_len = len; if(do_echo) { scc_add_to_readbuf(dfcyc, port, c); /* echo */ } } void scc_socket_do_cmd_str(dword64 dfcyc, int port) { Scc *scc_ptr; char *str; int pos, len, ret_val, reg, reg_val, was_amp, out_port, c; int i; scc_ptr = &(g_scc[port]); str = (char *)&(scc_ptr->modem_cmd_str[0]); printf("Got modem string :%s:=%02x %02x %02x\n", str, str[0], str[1], str[2]); len = scc_ptr->modem_cmd_len; str[len] = 0; str[len+1] = 0; str[len+2] = 0; pos = -1; if(len < 2) { /* just ignore it */ return; } if(str[0] != 'a' || str[1] != 't') { return; } /* Some AT command received--make sure socket 6501/6502 is open */ printf("Some AT command received, sockfd=%llx\n", (dword64)scc_ptr->sockfd); pos = 2 - 1; ret_val = 0; /* "OK" */ was_amp = 0; while(++pos < len) { c = str[pos] + was_amp; was_amp = 0; switch(c) { case '&': /* at& */ was_amp = 0x100; break; case 'z': /* atz */ scc_ptr->modem_mode = 0; scc_ptr->modem_s0_val = 0; pos = len; /* ignore any other commands */ break; case 'e': /* ate = echo */ c = str[pos+1]; if(c == '1') { scc_ptr->modem_mode &= ~SCCMODEM_NOECHO; pos++; } else { scc_ptr->modem_mode |= SCCMODEM_NOECHO; pos++; } break; case 'v': /* atv = verbose */ c = str[pos+1]; if(c == '1') { scc_ptr->modem_mode &= ~SCCMODEM_NOVERBOSE; pos++; } else { scc_ptr->modem_mode |= SCCMODEM_NOVERBOSE; pos++; } break; case 'o': /* ato = go online */ printf("ato\n"); if(scc_ptr->dcd && scc_ptr->modem_state && (scc_ptr->rdwrfd != INVALID_SOCKET)) { printf("Going back online\n"); scc_socket_modem_connect(dfcyc, port); scc_ptr->modem_state = 0; // talk to socket ret_val = -1; } break; case 'h': /* ath = hang up */ printf("ath, hanging up\n"); scc_socket_close(port); if(scc_ptr->rdwrfd != INVALID_SOCKET) { scc_socket_close_extended(dfcyc, port, 0); } /* scc_socket_maybe_open_incoming(dfcyc, port); */ /* reopen listen */ break; case 'a': /* ata */ printf("Doing ATA\n"); scc_socket_do_answer(dfcyc, port); ret_val = -1; break; case 'd': /* atd */ scc_ptr->modem_out_portnum = 23; pos++; c = str[pos]; if(c == 't' || c == 'p') { /* skip tone or pulse */ pos++; } /* see if it is 111 */ if(strcmp(&str[pos], "111") == 0) { /* Do PPP! */ } else { /* get string to connect to */ /* Shift string so hostname moves to str[0] */ for(i = 0; i < len; i++) { c = str[pos]; if(c == ':') { /* get port number now */ out_port = (int)strtol( &str[pos+1], 0, 10); if(out_port <= 1) { out_port = 23; } scc_ptr->modem_out_portnum = out_port; c = 0; } str[i] = c; if((pos >= len) || (c == 0)) { break; } pos++; } } scc_socket_open_outgoing(dfcyc, port, (char *)&scc_ptr->modem_cmd_str[0], scc_ptr->modem_out_portnum); ret_val = -1; pos = len; /* always eat rest of the line */ break; case 's': /* atsnn=yy */ pos++; reg = 0; while(1) { c = str[pos]; if(c < '0' || c > '9') { break; } reg = (reg * 10) + c - '0'; pos++; } if(c == '?') { /* display S-register */ if(reg == 0) { scc_add_to_readbufv(dfcyc, port, "S0=%d\n", scc_ptr->modem_s0_val); } break; } if(c != '=') { break; } pos++; reg_val = 0; while(1) { c = str[pos]; if(c < '0' || c >'9') { break; } reg_val = (reg_val * 10) + c - '0'; pos++; } printf("ats%d = %d\n", reg, reg_val); if(reg == 0) { scc_ptr->modem_s0_val = reg_val; } if(reg == 2) { scc_ptr->modem_s2_val = reg_val; } pos--; break; default: /* some command--peek into next chars to finish it */ while(1) { c = str[pos+1]; if(c >= '0' && c <= '9') { /* eat numbers */ pos++; continue; } if(c == '=') { /* eat this as well */ pos++; continue; } /* else get out */ break; } } } if(ret_val >= 0) { scc_socket_send_modem_code(dfcyc, port, ret_val); } } void scc_socket_send_modem_code(dword64 dfcyc, int port, int code) { Scc *scc_ptr; char *str; word32 modem_mode; scc_ptr = &(g_scc[port]); switch(code) { case 0: str = "OK"; break; case 1: str = "CONNECT"; break; case 2: str = "RING"; break; case 3: str = "NO CARRIER"; break; case 4: str = "ERROR"; break; case 5: str = "CONNECT 1200"; break; case 10: str = "CONNECT 2400"; break; case 12: str = "CONNECT 9600"; break; // Generic AT docs/Warp6 BBS case 13: str = "CONNECT 9600"; break; // US Robotics Sportster/HST case 14: str = "CONNECT 19200"; break; // Warp6 BBS case 16: str = "CONNECT 19200"; break; // Generic AT docs case 25: str = "CONNECT 14400"; break; // US Robotics Sportster case 85: str = "CONNECT 19200"; break; // US Robotics Sportster case 18: str = "CONNECT 57600"; break; // Generic AT docs/Warp6 BBS case 28: str = "CONNECT 38400"; break; // Warp6 BBS/Hayes default: str = "ERROR"; } printf("Sending modem code %d = %s\n", code, str); modem_mode = scc_ptr->modem_mode; if(modem_mode & SCCMODEM_NOVERBOSE) { /* just the number */ scc_add_to_readbufv(dfcyc, port, "%d", code); scc_add_to_readbuf(dfcyc, port, 0x0d); } else { scc_add_to_readbufv(dfcyc, port, "%s\n", str); } } void scc_socket_modem_connect(dword64 dfcyc, int port) { // Send code telling Term program the connect speed if(g_scc[port].cur_state == 1) { // Virtual modem scc_socket_send_modem_code(dfcyc, port, g_serial_modem_response_code); /*28=38400*/ } } void scc_socket_modem_do_ring(dword64 dfcyc, int port) { Scc *scc_ptr; dword64 diff_dusecs; int num_rings; scc_ptr = &(g_scc[port]); num_rings = scc_ptr->socket_num_rings; if((num_rings > 0) && scc_ptr->modem_state) { num_rings--; diff_dusecs = (dfcyc - scc_ptr->socket_last_ring_dfcyc) >> 16; if(diff_dusecs < 2LL*1000*1000) { return; /* nothing more to do */ } scc_ptr->socket_num_rings = num_rings; scc_ptr->socket_last_ring_dfcyc = dfcyc; if(num_rings <= 0) { /* decide on answering */ if(scc_ptr->modem_s0_val) { scc_socket_do_answer(dfcyc, port); } else { printf("No answer, closing socket\n"); scc_socket_close(port); } } else { scc_socket_send_modem_code(dfcyc, port, 2); /* RING */ } } } void scc_socket_do_answer(dword64 dfcyc, int port) { Scc *scc_ptr; scc_ptr = &(g_scc[port]); scc_accept_socket(dfcyc, port); if(scc_ptr->rdwrfd == INVALID_SOCKET) { printf("Answer when rdwrfd=-1, closing\n"); scc_socket_close_extended(dfcyc, port, 0); /* send NO CARRIER message */ } else { scc_socket_telnet_reqs(dfcyc, port); printf("Send telnet reqs\n"); if(scc_ptr->modem_state) { scc_socket_modem_connect(dfcyc, port); } scc_ptr->modem_state = 0; // Talk to socket scc_ptr->dcd = 1; // carrier on scc_ptr->socket_num_rings = 0; } } ================================================ FILE: upstream/kegs/src/scc_unixdriver.c ================================================ const char rcsid_scc_macdriver_c[] = "@(#)$KmKId: scc_unixdriver.c,v 1.4 2025-01-07 16:45:35+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ /* This file contains the Mac/Linux calls to a real serial port */ #include "defc.h" #include "scc.h" #ifndef _WIN32 # include #endif extern Scc g_scc[2]; extern word32 g_c025_val; extern char *g_serial_device[2]; #ifndef _WIN32 void scc_serial_unix_open(int port) { Scc *scc_ptr; int fd; scc_ptr = &(g_scc[port]); fd = open(&g_serial_device[port][0], O_RDWR | O_NONBLOCK); scc_ptr->unix_dev_fd = fd; printf("scc_serial_unix_init %d called, fd: %d\n", port, fd); if(fd < 0) { scc_ptr->unix_dev_fd = -1; scc_ptr->cur_state = -1; // Failed to open return; } scc_serial_unix_change_params(port); scc_ptr->cur_state = 0; // Actual Serial device } void scc_serial_unix_close(int port) { Scc *scc_ptr; int fd; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd >= 0) { close(fd); } scc_ptr->unix_dev_fd = -1; } void scc_serial_unix_change_params(int port) { struct termios termios_buf; Scc *scc_ptr; int fd, csz, ret; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; printf("scc_serial_unix_change_parms port: %d, fd: %d\n", port, fd); if(fd <= 0) { return; } ret = tcgetattr(fd, &termios_buf); if(ret != 0) { printf("tcgetattr port%d ret: %d\n", port, ret); } #if 1 printf("baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, (int)termios_buf.c_lflag); #endif memset(&termios_buf, 0, sizeof(struct termios)); cfmakeraw(&termios_buf); cfsetspeed(&termios_buf, scc_ptr->baud_rate); csz = scc_ptr->char_size; termios_buf.c_cflag = CREAD | CLOCAL; termios_buf.c_cflag |= (csz == 5) ? CS5 : (csz == 6) ? CS6 : (csz == 7) ? CS7 : CS8; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 2: // 1.5 stop bits termios_buf.c_cflag |= CSTOPB; /* no 1.5 stop bit setting.*/ break; case 3: // 2 stop bits termios_buf.c_cflag |= CSTOPB; break; } switch((scc_ptr->reg[4]) & 0x3) { case 1: // Odd parity termios_buf.c_cflag |= (PARENB | PARODD); break; case 3: // Even parity termios_buf.c_cflag |= PARENB; break; } /* always enabled DTR and RTS control */ #ifdef CRTSCTS termios_buf.c_cflag |= CRTSCTS; // Linux: CTS/RTS #endif #ifdef CRTS_IFLOW termios_buf.c_cflag |= CDTR_IFLOW | CRTS_IFLOW; // Mac: CTS/RTS #endif printf("fd: %d, baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", fd, (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, (int)termios_buf.c_lflag); ret = tcsetattr(fd, TCSANOW, &termios_buf); if(ret != 0) { printf("tcsetattr ret: %d\n", ret); } } void scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left) { byte tmp_buf[256]; Scc *scc_ptr; int fd, ret, flags, dcd; int i; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd <= 0) { return; } /* Try reading some bytes */ space_left = MY_MIN(space_left, 256); ret = read(fd, tmp_buf, space_left); if(ret > 0) { for(i = 0; i < ret; i++) { scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); } } flags = 0; dcd = 0; #if defined(TIOCMGET) && defined(TIOCM_CAR) ret = ioctl(fd, TIOCMGET, &flags); if(ret == 0) { dcd = 0; if(flags & TIOCM_CAR) { // DCD dcd = 1; } scc_ptr->dcd = dcd; } #endif } void scc_serial_unix_empty_writebuf(int port) { Scc *scc_ptr; int fd, rdptr, wrptr, done, ret, len; scc_ptr = &(g_scc[port]); fd = scc_ptr->unix_dev_fd; if(fd <= 0) { return; } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { //printf("...rdptr == wrptr\n"); done = 1; break; } len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } if(len <= 0) { done = 1; break; } ret = write(fd, &(scc_ptr->out_buf[rdptr]), len); if(ret <= 0) { done = 1; break; } else { rdptr = rdptr + ret; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } } #endif /* !_WIN32 */ ================================================ FILE: upstream/kegs/src/scc_windriver.c ================================================ const char rcsid_scc_windriver_c[] = "@(#)$KmKId: scc_windriver.c,v 1.13 2023-08-28 18:11:05+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ /* This file contains the Win32 COM1/COM2 calls */ #include "defc.h" #include "scc.h" extern Scc g_scc[2]; extern word32 g_c025_val; extern int g_serial_win_device[2]; #ifdef _WIN32 void scc_serial_win_open(int port) { COMMTIMEOUTS commtimeouts; char str_buf[32]; Scc *scc_ptr; HANDLE com_handle; int ret; scc_ptr = &(g_scc[port]); snprintf(&str_buf[0], sizeof(str_buf), "COM%d", g_serial_win_device[port]); com_handle = CreateFile(&str_buf[0], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); scc_ptr->win_com_handle = com_handle; printf("scc_serial_win_init %d called, com_handle: %p\n", port, com_handle); if(com_handle == INVALID_HANDLE_VALUE) { scc_ptr->cur_state = -1; // Failed to open return; } scc_ptr->win_dcb_ptr = malloc(sizeof(DCB)); scc_serial_win_change_params(port); commtimeouts.ReadIntervalTimeout = MAXDWORD; commtimeouts.ReadTotalTimeoutMultiplier = 0; commtimeouts.ReadTotalTimeoutConstant = 0; commtimeouts.WriteTotalTimeoutMultiplier = 0; commtimeouts.WriteTotalTimeoutConstant = 10; ret = SetCommTimeouts(com_handle, &commtimeouts); if(ret == 0) { printf("setcommtimeout ret: %d\n", ret); } scc_ptr->cur_state = 0; // COM* is open } void scc_serial_win_close(int port) { Scc *scc_ptr; HANDLE com_handle; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; scc_ptr->win_com_handle = INVALID_HANDLE_VALUE; if(com_handle == INVALID_HANDLE_VALUE) { return; } CloseHandle(com_handle); free(scc_ptr->win_dcb_ptr); scc_ptr->win_dcb_ptr = 0; } void scc_serial_win_change_params(int port) { DCB *dcbptr; HANDLE com_handle; Scc *scc_ptr; int ret; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; dcbptr = scc_ptr->win_dcb_ptr; if((com_handle == INVALID_HANDLE_VALUE) || (scc_ptr->cur_state != 0)) { return; } ret = GetCommState(com_handle, dcbptr); if(ret == 0) { printf("getcomm port%d ret: %d\n", port, ret); } #if 1 printf("dcb.baudrate: %d, bytesize:%d, stops:%d, parity:%d\n", (int)dcbptr->BaudRate, (int)dcbptr->ByteSize, (int)dcbptr->StopBits, (int)dcbptr->Parity); printf("dcb.binary: %d, ctsflow: %d, dsrflow: %d, dtr: %d, dsr: %d\n", (int)dcbptr->fBinary, (int)dcbptr->fOutxCtsFlow, (int)dcbptr->fOutxDsrFlow, (int)dcbptr->fDtrControl, (int)dcbptr->fDsrSensitivity); printf("dcb.txonxoff:%d, outx:%d, inx: %d, null: %d, rts: %d\n", (int)dcbptr->fTXContinueOnXoff, (int)dcbptr->fOutX, (int)dcbptr->fInX, (int)dcbptr->fNull, (int)dcbptr->fRtsControl); printf("dcb.fAbortOnErr:%d, fParity:%d\n", (int)dcbptr->fAbortOnError, (int)dcbptr->fParity); #endif dcbptr->fAbortOnError = 0; dcbptr->BaudRate = scc_ptr->baud_rate; dcbptr->ByteSize = scc_ptr->char_size; dcbptr->StopBits = ONESTOPBIT; switch((scc_ptr->reg[4] >> 2) & 0x3) { case 2: // 1.5 stop bits dcbptr->StopBits = ONE5STOPBITS; break; case 3: // 2 stop bits dcbptr->StopBits = TWOSTOPBITS; break; } dcbptr->Parity = NOPARITY; switch((scc_ptr->reg[4]) & 0x3) { case 1: // Odd parity dcbptr->Parity = ODDPARITY; break; case 3: // Even parity dcbptr->Parity = EVENPARITY; break; } dcbptr->fNull = 0; dcbptr->fDtrControl = DTR_CONTROL_ENABLE; dcbptr->fDsrSensitivity = 0; dcbptr->fOutxCtsFlow = 0; dcbptr->fOutxDsrFlow = 0; dcbptr->fParity = 0; dcbptr->fInX = 0; dcbptr->fOutX = 0; dcbptr->fRtsControl = RTS_CONTROL_ENABLE; ret = SetCommState(com_handle, dcbptr); if(ret == 0) { printf("SetCommState ret: %d, new baud: %d\n", ret, (int)dcbptr->BaudRate); } } void scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left) { byte tmp_buf[256]; Scc *scc_ptr; HANDLE com_handle; DWORD bytes_read; int ret; int i; scc_ptr = &(g_scc[port]); com_handle = scc_ptr->win_com_handle; if(com_handle == INVALID_HANDLE_VALUE) { return; } /* Try reading some bytes */ space_left = MY_MIN(256, space_left); ret = ReadFile(com_handle, tmp_buf, space_left, &bytes_read, NULL); if(ret == 0) { printf("ReadFile ret 0\n"); } if(ret && (bytes_read > 0)) { for(i = 0; i < (int)bytes_read; i++) { scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); } } } void scc_serial_win_empty_writebuf(int port) { Scc *scc_ptr; HANDLE com_handle; DWORD bytes_written; word32 err_code; int rdptr, wrptr, done, ret, len; scc_ptr = &(g_scc[port]); //printf("win_empty_writebuf, com_h: %d\n", scc_ptr->win_com_handle); com_handle = scc_ptr->win_com_handle; if(com_handle == INVALID_HANDLE_VALUE) { return; } /* Try writing some bytes */ done = 0; while(!done) { rdptr = scc_ptr->out_rdptr; wrptr = scc_ptr->out_wrptr; if(rdptr == wrptr) { //printf("...rdptr == wrptr\n"); done = 1; break; } len = wrptr - rdptr; if(len < 0) { len = SCC_OUTBUF_SIZE - rdptr; } if(len > 32) { len = 32; } if(len <= 0) { done = 1; break; } bytes_written = 1; ret = WriteFile(com_handle, &(scc_ptr->out_buf[rdptr]), len, &bytes_written, NULL); printf("WriteFile ret: %d, bytes_written:%d, len:%d\n", ret, (int)bytes_written, len); err_code = (word32)-1; if(ret == 0) { err_code = (word32)GetLastError(); printf("WriteFile ret:0, err_code: %08x\n", err_code); } if(ret == 0 || (bytes_written == 0)) { done = 1; break; } else { rdptr = rdptr + bytes_written; if(rdptr >= SCC_OUTBUF_SIZE) { rdptr = rdptr - SCC_OUTBUF_SIZE; } scc_ptr->out_rdptr = rdptr; } } } #endif ================================================ FILE: upstream/kegs/src/sim65816.c ================================================ const char rcsid_sim65816_c[] = "@(#)$KmKId: sim65816.c,v 1.491 2025-04-27 18:03:43+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include #define INCLUDE_RCSID_C #include "defc.h" #undef INCLUDE_RCSID_C double g_dtime_sleep = 0; double g_dtime_in_sleep = 0; extern char *g_argv0_path; #define MAX_EVENTS 64 /* All EV_* must be less than 256, since upper bits reserved for other use */ /* e.g., DOC_INT uses upper bits to encode oscillator */ #define EV_60HZ 1 #define EV_STOP 2 #define EV_SCAN_INT 3 #define EV_DOC_INT 4 #define EV_VBL_INT 5 #define EV_SCC 6 #define EV_VID_UPD 7 #define EV_MOCKINGBOARD 8 extern int g_stepping; extern word32 g_c068_statereg; extern int g_cur_a2_stat; extern word32 g_c02d_int_crom; extern word32 g_c035_shadow_reg; extern word32 g_c036_val_speed; extern word32 g_c023_val; extern word32 g_c041_val; extern word32 g_c046_val; extern word32 g_zipgs_reg_c059; extern word32 g_zipgs_reg_c05a; extern word32 g_zipgs_reg_c05b; extern word32 g_zipgs_unlock; extern Iwm g_iwm; Engine_reg engine; extern word32 table8[]; extern word32 table16[]; extern byte doc_ram[]; extern int g_iwm_motor_on; extern int g_fast_disk_emul; extern int g_slow_525_emul_wr; extern int g_config_control_panel; extern int g_audio_enable; extern int g_preferred_rate; int g_a2_fatal_err = 0; dword64 g_dcycles_end = 0; int g_halt_sim = 0; int g_rom_version = -1; int g_user_halt_bad = 0; int g_halt_on_bad_read = 0; int g_ignore_bad_acc = 1; int g_ignore_halts = 1; int g_code_red = 0; int g_code_yellow = 0; int g_emul_6502_ind_page_cross_bug = 0; int g_config_iwm_vbl_count = 0; const char g_kegs_version_str[] = "1.38"; dword64 g_last_vbl_dfcyc = 0; dword64 g_cur_dfcyc = 1; double g_last_vbl_dadjcycs = 0; double g_dadjcycs = 0; int g_wait_pending = 0; int g_stp_pending = 0; extern int g_irq_pending; int g_num_irq = 0; int g_num_brk = 0; int g_num_cop = 0; int g_num_enter_engine = 0; int g_io_amt = 0; int g_engine_action = 0; int g_engine_recalc_event = 0; int g_engine_scan_int = 0; int g_engine_doc_int = 0; #define MAX_FATAL_LOGS 20 int g_debug_file_fd = -1; int g_fatal_log = -1; char *g_fatal_log_strs[MAX_FATAL_LOGS]; word32 stop_run_at; int g_25sec_cntr = 0; int g_1sec_cntr = 0; double g_dnatcycs_1sec = 0.0; word32 g_natcycs_lastvbl = 0; int Verbose = 0; int Halt_on = 0; word32 g_mem_size_base = 128*1024; /* size of motherboard memory */ word32 g_mem_size_exp = 8*1024*1024; /* size of expansion RAM card */ word32 g_mem_size_total = 128*1024; /* Total contiguous RAM from 0 */ extern word32 g_slow_mem_changed[]; byte *g_slow_memory_ptr = 0; byte *g_memory_ptr = 0; byte *g_dummy_memory1_ptr = 0; byte *g_rom_fc_ff_ptr = 0; byte *g_rom_cards_ptr = 0; void *g_memory_alloc_ptr = 0; /* for freeing memory area */ Page_info page_info_rd_wr[2*65536 + PAGE_INFO_PAD_SIZE]; word32 g_word32_tmp = 0; int g_force_depth = -1; int g_use_shmem = 1; extern word32 g_cycs_in_40col; extern word32 g_cycs_in_xredraw; extern word32 g_cycs_in_refresh_line; extern word32 g_cycs_outside_run_16ms; extern word32 g_refresh_bytes_xfer; extern int g_num_snd_plays; extern int g_num_recalc_snd_parms; extern int g_doc_vol; extern int g_status_refresh_needed; int sim_get_force_depth() { return g_force_depth; } int sim_get_use_shmem() { return g_use_shmem; } void sim_set_use_shmem(int use_shmem) { g_use_shmem = use_shmem; } #define TOOLBOX_LOG_LEN 64 int g_toolbox_log_pos = 0; word32 g_toolbox_log_array[TOOLBOX_LOG_LEN][8]; word32 toolbox_debug_4byte(word32 addr) { word32 part1, part2; /* If addr looks safe, use it */ if(addr > 0xbffc) { return (word32)-1; } part1 = get_memory16_c(addr); part1 = (part1 >> 8) + ((part1 & 0xff) << 8); part2 = get_memory16_c(addr+2); part2 = (part2 >> 8) + ((part2 & 0xff) << 8); return (part1 << 16) + part2; } void toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr) { int pos; pos = g_toolbox_log_pos; stack += 9; g_toolbox_log_array[pos][0] = (word32) ((g_last_vbl_dfcyc + *dcyc_ptr) >> 16); g_toolbox_log_array[pos][1] = stack+1; g_toolbox_log_array[pos][2] = xreg; g_toolbox_log_array[pos][3] = toolbox_debug_4byte(stack+1); g_toolbox_log_array[pos][4] = toolbox_debug_4byte(stack+5); g_toolbox_log_array[pos][5] = toolbox_debug_4byte(stack+9); g_toolbox_log_array[pos][6] = toolbox_debug_4byte(stack+13); g_toolbox_log_array[pos][7] = toolbox_debug_4byte(stack+17); pos++; if(pos >= TOOLBOX_LOG_LEN) { pos = 0; } g_toolbox_log_pos = pos; } void show_toolbox_log() { int pos; int i; pos = g_toolbox_log_pos; for(i = TOOLBOX_LOG_LEN - 1; i >= 0; i--) { printf("%2d:%2d: %08x %06x %04x: %08x %08x %08x %08x %08x\n", i, pos, g_toolbox_log_array[pos][0], g_toolbox_log_array[pos][1], g_toolbox_log_array[pos][2], g_toolbox_log_array[pos][3], g_toolbox_log_array[pos][4], g_toolbox_log_array[pos][5], g_toolbox_log_array[pos][6], g_toolbox_log_array[pos][7]); pos++; if(pos >= TOOLBOX_LOG_LEN) { pos = 0; } } } word32 get_memory_io(word32 loc, dword64 *dcyc_ptr) { int tmp; if(loc > 0xffffff) { halt_printf("get_memory_io:%08x out of range==halt!\n", loc); return 0; } tmp = loc & 0xfef000; if(tmp == 0xc000 || tmp == 0xe0c000) { return(io_read(loc & 0xfff, dcyc_ptr)); } /* Else it's an illegal addr...skip if memory sizing */ if(loc >= g_mem_size_total) { if((loc & 0xfffe) == 0) { //printf("get_io assuming mem sizing, not halting\n"); return 0; } } /* Skip reads to f80000 and f00000, just return 0 */ if((loc & 0xf70000) == 0xf00000) { return 0; } if((loc & 0xff0000) == 0xef0000) { /* DOC RAM */ return (doc_ram[loc & 0xffff]); } if((loc & 0xffff00) == 0xbcff00) { /* TWGS mapped some ROM here, we'll force in all 0's */ /* If user has selected >= 12MB of memory, then mem will be */ /* returned and we won't ever get here */ return 0; } g_code_yellow++; if(g_ignore_bad_acc && !g_user_halt_bad) { /* print no message, just get out. User doesn't want */ /* to be bothered by buggy programs */ return 0; } printf("get_memory_io for addr: %06x\n", loc); printf("stat for addr: %06x = %p\n", loc, GET_PAGE_INFO_RD((loc >> 8) & 0xffff)); set_halt(g_halt_on_bad_read | g_user_halt_bad); return 0; } void set_memory_io(word32 loc, int val, dword64 *dcyc_ptr) { word32 tmp; tmp = loc & 0xfef000; if(tmp == 0xc000 || tmp == 0xe0c000) { io_write(loc, val, dcyc_ptr); return; } /* Else it's an illegal addr */ if(loc >= g_mem_size_total) { if((loc & 0xfffe) == 0) { //printf("set_io assuming mem sizing, not halting\n"); return; } } /* ignore writes to ROM */ if((loc & 0xfc0000) == 0xfc0000) { return; } if((loc & 0xff0000) == 0xef0000) { /* DOC RAM */ doc_ram[loc & 0xffff] = val; return; } if(g_ignore_bad_acc && !g_user_halt_bad) { /* print no message, just get out. User doesn't want */ /* to be bothered by buggy programs */ return; } if((loc & 0xffc000) == 0x00c000) { printf("set_memory %06x = %02x, warning\n", loc, val); return; } halt_printf("set_memory %06x = %02x, stopping\n", loc, val); return; } void show_regs_act(Engine_reg *eptr) { int tmp_acc, tmp_x, tmp_y, tmp_psw, kpc, direct_page, dbank, stack; kpc = eptr->kpc; tmp_acc = eptr->acc; direct_page = eptr->direct; dbank = eptr->dbank; stack = eptr->stack; tmp_x = eptr->xreg; tmp_y = eptr->yreg; tmp_psw = eptr->psr; dbg_printf(" PC=%02x.%04x A=%04x X=%04x Y=%04x P=%03x", kpc>>16, kpc & 0xffff ,tmp_acc,tmp_x,tmp_y,tmp_psw); dbg_printf(" S=%04x D=%04x B=%02x,cyc:%016llx\n", stack, direct_page, dbank, g_cur_dfcyc); } void show_regs() { show_regs_act(&engine); } void my_exit(int ret) { g_a2_fatal_err = 0x10 + ret; printf("exiting\n"); } void do_reset() { g_c035_shadow_reg = 0; g_c068_statereg = 0x200 | 0x08 | 0x04; // Set wrdefram, rdrom, lcbank2 g_c02d_int_crom = 0xff; if(g_rom_version != 0) { // IIgs ROM01 or ROM03 g_c068_statereg |= 0x01; // also set intcx g_c02d_int_crom = 0; } g_c023_val = 0; g_c041_val = 0; engine.psr = (engine.psr | 0x134) & ~(0x08); engine.stack = 0x100 + (engine.stack & 0xff); engine.dbank = 0; engine.direct = 0; engine.xreg &= 0xff; engine.yreg &= 0xff; g_wait_pending = 0; g_stp_pending = 0; video_reset(); adb_reset(); iwm_reset(); scc_reset(); sound_reset(g_cur_dfcyc); setup_pageinfo(); change_display_mode(g_cur_dfcyc); g_irq_pending = 0; g_code_yellow = 0; g_code_red = 0; engine.kpc = get_memory16_c(0x00fffc); g_stepping = 0; } #define CHECK(start, var, value, var1, var2) \ var2 = PTR2WORD(&(var)); \ var1 = PTR2WORD((start)); \ if((var2 - var1) != value) { \ printf("CHECK: " #var " is 0x%x, but " #value " is 0x%x\n", \ (var2 - var1), value); \ exit(5); \ } byte * memalloc_align(int size, int skip_amt, void **alloc_ptr) { byte *bptr; word32 addr; word32 offset; skip_amt = MY_MAX(256, skip_amt); bptr = calloc(size + skip_amt, 1); if(alloc_ptr) { /* Save allocation address */ *alloc_ptr = bptr; } addr = PTR2WORD(bptr) & 0xff; /* must align bptr to be 256-byte aligned */ /* this code should work even if ptrs are > 32 bits */ offset = ((addr + skip_amt - 1) & (~0xff)) - addr; return (bptr + offset); } void memory_ptr_init() { word32 mem_size; /* This routine may be called several times--each time the ROM file */ /* changes this will be called */ mem_size = MY_MIN(0xdf0000, g_mem_size_base + g_mem_size_exp); if(g_rom_version == 0) { // Apple //e mem_size = g_mem_size_base; } g_mem_size_total = mem_size; if(g_memory_alloc_ptr) { free(g_memory_alloc_ptr); g_memory_alloc_ptr = 0; } g_memory_ptr = memalloc_align(mem_size, 256, &g_memory_alloc_ptr); printf("RAM size is 0 - %06x (%.2fMB)\n", mem_size, (double)mem_size/(1024.0*1024.0)); } extern int g_screen_redraw_skip_amt; extern int g_use_dhr140; extern int g_use_bw_hires; char g_display_env[512]; int g_screen_depth = 8; int parse_argv(int argc, char **argv, int slashes_to_find) { byte *bptr; char *str, *arg2_str; int skip_amt, tmp1, len; int i; #if 0 // If launched from Finder, no stdout, so send it to /tmp/out1 fflush(stdout); setvbuf(stdout, 0, _IONBF, 0); close(1); (void)open("/tmp/out1", O_WRONLY | O_CREAT | O_TRUNC, 0x1b6); #endif printf("Starting KEGS v%s\n", &g_kegs_version_str[0]); // parse arguments // First, Check if KEGS_BIG_ENDIAN is set correctly g_word32_tmp = 0x01020304; bptr = (byte *)&(g_word32_tmp); #ifdef KEGS_BIG_ENDIAN bptr[0] = 6; bptr[3] = 5; #else bptr[0] = 5; bptr[3] = 6; #endif if(g_word32_tmp != 0x06020305) { fatal_printf("KEGS_BIG_ENDIAN is not properly set\n"); return 1; } str = kegs_malloc_str(argv[0]); len = (int)strlen(str); for(i = len; i > 0; i--) { if(str[i] == '/') { if(--slashes_to_find <= 0) { str[i] = 0; g_argv0_path = str; break; } } } printf("g_argv0_path: %s\n", g_argv0_path); i = 0; while(++i < argc) { printf("argv[%d] = %s\n", i, argv[i]); if(!strcmp("-badrd", argv[i])) { printf("Halting on bad reads\n"); g_halt_on_bad_read = 2; } else if(!strcmp("-noignbadacc", argv[i])) { printf("Not ignoring bad memory accesses\n"); g_ignore_bad_acc = 0; } else if(!strcmp("-noignhalt", argv[i])) { printf("Not ignoring code red halts\n"); g_ignore_halts = 0; } else if(!strcmp("-24", argv[i])) { printf("Using 24-bit visual\n"); g_force_depth = 24; } else if(!strcmp("-16", argv[i])) { printf("Using 16-bit visual\n"); g_force_depth = 16; } else if(!strcmp("-15", argv[i])) { printf("Using 15-bit visual\n"); g_force_depth = 15; } else if(!strcmp("-mem", argv[i])) { if((i+1) >= argc) { printf("Missing argument\n"); return 1; } g_mem_size_exp = strtol(argv[i+1], 0, 0) & 0x00ff0000; printf("Using %d as memory size\n", g_mem_size_exp); i++; } else if(!strcmp("-skip", argv[i])) { if((i+1) >= argc) { printf("Missing to skip argument\n"); return 1; } skip_amt = (int)strtol(argv[i+1], 0, 0); printf("Using %d as skip_amt\n", skip_amt); g_screen_redraw_skip_amt = skip_amt; i++; } else if(!strcmp("-audio", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -audio\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Using %d as audio enable val\n", tmp1); g_audio_enable = tmp1; i++; } else if(!strcmp("-arate", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -arate\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Using %d as preferred audio rate\n", tmp1); g_preferred_rate = tmp1; i++; } else if(!strcmp("-v", argv[i])) { if((i+1) >= argc) { printf("Missing argument to -v\n"); return 1; } tmp1 = (int)strtol(argv[i+1], 0, 0); printf("Setting Verbose = 0x%03x\n", tmp1); Verbose = tmp1; i++; } else if(!strcmp("-display", argv[i])) { if((i+1) >= argc) { printf("Missing argument\n"); exit(1); } printf("Using %s as display\n", argv[i+1]); snprintf(g_display_env, sizeof(g_display_env), "DISPLAY=%s", argv[i+1]); putenv(&g_display_env[0]); i++; } else if(!strcmp("-noshm", argv[i])) { printf("Not using X shared memory\n"); g_use_shmem = 0; } else if(!strcmp("-joystick", argv[i])) { printf("Ignoring -joystick option\n"); } else if(!strcmp("-dhr140", argv[i])) { printf("Using simple dhires color map\n"); g_use_dhr140 = 1; } else if(!strcmp("-bw", argv[i])) { printf("Forcing black-and-white hires modes\n"); g_cur_a2_stat |= ALL_STAT_COLOR_C021; g_use_bw_hires = 1; } else if(!strncmp("-NS", argv[i], 3)) { // Some Mac argument, just ignore it if((i + 1) < argc) { // If the next argument doesn't start with '-', // then ignore it, too if(argv[i+1][0] != '-') { i++; } } } else if(!strcmp("-logpc", argv[i])) { printf("Force logpc enable\n"); debug_logpc_on("on"); } else if(!strncmp("-cfg", argv[i], 4)) { if((i + 1) < argc) { config_set_config_kegs_name(argv[i+1]); } i++; } else if(argv[i][0] == '-') { arg2_str = 0; if((i + 1) < argc) { arg2_str = argv[i+1]; } i += config_add_argv_override(&argv[i][1], arg2_str); } else { printf("Bad option: %s\n", argv[i]); return 3; } } return 0; } int kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window) { g_config_control_panel = 0; woz_crc_init(); fixed_memory_ptrs_init(); if(sizeof(word32) != 4) { printf("sizeof(word32) = %d, must be 4!\n", (int)sizeof(word32)); return 1; } prepare_a2_font(); // Prepare default built-in font scc_init(); iwm_init(); init_reg(); adb_init(); initialize_events(); debugger_init(); setup_pageinfo(); config_init(); load_roms_init_memory(); clear_halt(); video_init(mdepth, screen_width, screen_height, no_scale_window); sound_init(); joystick_init(); if(g_rom_version >= 3) { g_c036_val_speed |= 0x40; /* set power-on bit */ } do_reset(); g_stepping = 0; clear_halt(); cfg_set_config_panel(g_config_control_panel); return 0; } void load_roms_init_memory() { config_load_roms(); memory_ptr_init(); clk_setup_bram_version(); /* Must be after config_load_roms */ if(g_rom_version >= 3) { g_c036_val_speed |= 0x40; /* set power-on bit */ } else { g_c036_val_speed &= (~0x40); /* clear the bit */ } // Do not call do_reset(), caller is responsible for that /* if user booted ROM 01, switches to ROM 03, then switches back */ /* to ROM 01, then the reset routines call to Tool $0102 looks */ /* at uninitialized $e1/15fe and if it is negative it will JMP */ /* through $e1/1688 which ROM 03 left pointing to fc/0199 */ /* So set e1/15fe = 0 */ set_memory16_c(0xe115fe, 0, 1); } Event g_event_list[MAX_EVENTS]; Event g_event_free; Event g_event_start; void initialize_events() { int i; for(i = 1; i < MAX_EVENTS; i++) { g_event_list[i-1].next = &g_event_list[i]; } g_event_free.next = &g_event_list[0]; g_event_list[MAX_EVENTS-1].next = 0; g_event_start.next = 0; g_event_start.dfcyc = 0; add_event_entry(CYCLES_IN_16MS_RAW << 16, EV_60HZ); } void check_for_one_event_type(int type, word32 mask) { Event *ptr; int count, depth; type = type & 0xff; count = 0; depth = 0; ptr = g_event_start.next; while(ptr != 0) { depth++; if((ptr->type & mask) == (word32)type) { count++; if(count != 1) { halt_printf("in check_for_1, type %04x found " "at depth: %d, count: %d, at %016llx\n", ptr->type, depth, count, ptr->dfcyc); } } ptr = ptr->next; } } void add_event_entry(dword64 dfcyc, int type) { Event *this_event; Event *ptr, *prev_ptr; int done; this_event = g_event_free.next; if(this_event == 0) { halt_printf("Out of queue entries!\n"); show_all_events(); return; } g_event_free.next = this_event->next; this_event->type = type; if((dfcyc > (g_cur_dfcyc + (50LL*1000*1000 << 16))) || (dfcyc < g_cur_dfcyc)) { halt_printf("add_event bad dfcyc:%016llx, type:%05x, " "cur_dfcyc: %016llx!\n", dfcyc, type, g_cur_dfcyc); dfcyc = g_cur_dfcyc + (1000LL << 16); } ptr = g_event_start.next; if(ptr && (dfcyc < ptr->dfcyc)) { /* create event before next expected event */ /* do this by calling engine_recalc_events */ engine_recalc_events(); } prev_ptr = &g_event_start; ptr = g_event_start.next; done = 0; while(!done) { if(ptr == 0) { this_event->next = ptr; this_event->dfcyc = dfcyc; prev_ptr->next = this_event; break; } else { if(ptr->dfcyc < dfcyc) { // step across this guy prev_ptr = ptr; ptr = ptr->next; } else { // go before this guy */ this_event->dfcyc = dfcyc; this_event->next = ptr; prev_ptr->next = this_event; break; } } } check_for_one_event_type(type, 0xffff); } extern int g_doc_saved_ctl; dword64 remove_event_entry(int type, word32 mask) { Event *ptr, *prev_ptr; Event *next_ptr; ptr = g_event_start.next; prev_ptr = &g_event_start; while(ptr != 0) { if((ptr->type & mask) == (word32)type) { // got it next_ptr = ptr->next; // remove it prev_ptr->next = next_ptr; /* Add ptr to free list */ ptr->next = g_event_free.next; g_event_free.next = ptr; return ptr->dfcyc; } prev_ptr = ptr; ptr = ptr->next; } halt_printf("remove event_entry: %08x, but not found!\n", type); if((type & 0xff) == EV_DOC_INT) { printf("DOC, g_doc_saved_ctl = %02x\n", g_doc_saved_ctl); } show_all_events(); return 0; } void add_event_stop(dword64 dfcyc) { add_event_entry(dfcyc, EV_STOP); } void add_event_doc(dword64 dfcyc, int osc) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; //halt_printf("add_event_doc: dfcyc:%016llx, cur_dfcyc:" // "%016llx\n", dfcyc, g_cur_dfcyc); } add_event_entry(dfcyc, EV_DOC_INT + (osc << 8)); } void add_event_scc(dword64 dfcyc, int type) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_SCC + (type << 8)); } void add_event_vbl() { dword64 dfcyc; dfcyc = g_last_vbl_dfcyc + ((192*65LL) << 16); add_event_entry(dfcyc, EV_VBL_INT); } void add_event_vid_upd(int line) { dword64 dfcyc; dfcyc = g_last_vbl_dfcyc + (((line + 1) * 65LL) << 16); add_event_entry(dfcyc, EV_VID_UPD + (line << 8)); // Redraw line when video counters first read video data } void add_event_mockingboard(dword64 dfcyc) { if(dfcyc < g_cur_dfcyc) { dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_MOCKINGBOARD); } dword64 g_dfcyc_scan_int = 0; void add_event_scan_int(dword64 dfcyc, int line) { dword64 dfcyc_scan_int; dfcyc_scan_int = g_dfcyc_scan_int; if(dfcyc_scan_int) { // Event is pending if(dfcyc >= g_dfcyc_scan_int) { // We are after (or the same) as current, do nothing return; } remove_event_entry(EV_SCAN_INT, 0xff); } if(dfcyc < g_cur_dfcyc) { // scan_int for line 0 is found during EV_60HZ, and some // cycles may have passed before the EV_60HZ was handled. // We need it to happen now, so just adjust dfcyc dfcyc = g_cur_dfcyc; } add_event_entry(dfcyc, EV_SCAN_INT + (line << 8)); g_dfcyc_scan_int = dfcyc; check_for_one_event_type(EV_SCAN_INT, 0xff); } dword64 remove_event_doc(int osc) { return remove_event_entry(EV_DOC_INT + (osc << 8), 0xffff); } dword64 remove_event_scc(int type) { return remove_event_entry(EV_SCC + (type << 8), 0xffff); } void remove_event_mockingboard() { (void)remove_event_entry(EV_MOCKINGBOARD, 0xff); } void show_all_events() { Event *ptr; dword64 dfcyc; int count; count = 0; ptr = g_event_start.next; while(ptr != 0) { dfcyc = ptr->dfcyc; printf("Event: %02x: type: %05x, dfcyc: %016llx (%08llx)\n", count, ptr->type, dfcyc, dfcyc - g_cur_dfcyc); ptr = ptr->next; count++; } } word32 g_vbl_count = 0; int g_vbl_index_count = 0; double dtime_array[60]; double g_dadjcycs_array[60]; double g_dtime_diff3_array[60]; double g_dtime_this_vbl_array[60]; double g_dtime_exp_array[60]; double g_dtime_pmhz_array[60]; double g_dtime_eff_pmhz_array[60]; double g_dtime_in_run_16ms = 0; double g_dtime_outside_run_16ms = 0; double g_dtime_end_16ms = 0; int g_limit_speed = 3; int g_zip_speed_mhz = 16; // 16MHz default double sim_time[60]; double g_sim_sum = 0.0; double g_cur_sim_dtime = 0.0; double g_projected_pmhz = 1.0; double g_zip_pmhz = 8.0; double g_sim_mhz = 100.0; int g_line_ref_amt = 1; int g_video_line_update_interval = 0; dword64 g_video_pixel_dcount = 0; Fplus g_recip_projected_pmhz_slow; Fplus g_recip_projected_pmhz_fast; Fplus g_recip_projected_pmhz_zip; Fplus g_recip_projected_pmhz_unl; void show_pmhz() { printf("Pmhz: %f, c036:%02x, limit: %d\n", g_projected_pmhz, g_c036_val_speed, g_limit_speed); } void setup_zip_speeds() { dword64 drecip; double fmhz; int mult; mult = 16 - ((g_zipgs_reg_c05a >> 4) & 0xf); // 16 = full speed, 1 = 1/16th speed fmhz = (g_zip_speed_mhz * mult) / 16.0; #if 0 if(mult == 16) { /* increase full speed by 19% to make zipgs freq measuring */ /* programs work correctly */ fmhz = fmhz * 1.19; } #endif drecip = (dword64)(65536 / fmhz); g_zip_pmhz = fmhz; g_recip_projected_pmhz_zip.dplus_1 = drecip; if(fmhz <= 2.0) { g_recip_projected_pmhz_zip.dplus_x_minus_1 = (dword64)(1.01 * 65536); } else { g_recip_projected_pmhz_zip.dplus_x_minus_1 = (dword64)(1.01 * 65536 - drecip); } } word32 g_cycs_end_16ms = 0; int run_16ms() { double dtime_start, dtime_end, dtime_end2, dtime_outside; int ret; dtime_start = get_dtime(); ret = 0; fflush(stdout); g_dtime_sleep = 1.0/61.0; // For control_panel/debugger if(g_config_control_panel) { ret = cfg_control_panel_update(); if(!g_config_control_panel) { return 0; // Was just switched off, get out } } else { if(g_halt_sim) { ret = debugger_run_16ms(); } else { ret = run_a2_one_vbl(); } } video_update(); g_vbl_count++; dtime_end = get_dtime(); g_dtime_in_run_16ms += (dtime_end - dtime_start); // If we are ahead, then do the sleep now micro_sleep(g_dtime_sleep); dtime_end2 = get_dtime(); //printf("Did sleep for %f, dtime passed:%f\n", g_dtime_sleep, // dtime_end2 - dtime_end); g_dtime_sleep = 0.0; g_dtime_in_sleep += (dtime_end2 - dtime_end); dtime_outside = 0.0; if(g_vbl_count > 1) { dtime_outside += (dtime_start - g_dtime_end_16ms); } g_dtime_outside_run_16ms += dtime_outside; g_dtime_end_16ms = dtime_end2; #if 0 if((g_vbl_count & 0xf) == 0) { printf("run_16ms end at %.3lf, dtime_16ms:%1.5lf out:%1.5lf\n", dtime_end, dtime_end - dtime_start, dtime_outside); } #endif return ret | g_a2_fatal_err; } int run_a2_one_vbl() { Fplus *fplus_ptr; Event *this_event, *db1; double now_dtime, prev_dtime, fspeed_mult; dword64 dfcyc, dfcyc_stop, prerun_dfcyc; word32 ret, zip_speed_0tof, zip_speed_0tof_new; int zip_en, zip_follow_cps, type, motor_on, iwm_1, iwm_25, fast; int limit_speed, apple35_sel, zip_speed, faster_than_28, unl_speed; fflush(stdout); g_cur_sim_dtime = 0.0; g_recip_projected_pmhz_slow.dplus_1 = 0x10000; g_recip_projected_pmhz_slow.dplus_x_minus_1 = (dword64)(0.9 * 0x10000); g_recip_projected_pmhz_fast.dplus_1 = (dword64)(0x10000 / 2.8); g_recip_projected_pmhz_fast.dplus_x_minus_1 = (dword64) ((1.98 - (1.0/2.8)) * 0x10000); zip_speed_0tof = g_zipgs_reg_c05a & 0xf0; setup_zip_speeds(); if(engine.fplus_ptr == 0) { g_recip_projected_pmhz_unl = g_recip_projected_pmhz_slow; } while(1) { fflush(stdout); if(g_irq_pending && !(engine.psr & 0x4)) { irq_printf("taking an irq!\n"); take_irq(); /* Interrupt! */ } motor_on = g_iwm_motor_on; limit_speed = g_limit_speed; apple35_sel = g_iwm.state & IWM_STATE_C031_APPLE35SEL; zip_en = ((g_zipgs_reg_c05b & 0x10) == 0); zip_follow_cps = ((g_zipgs_reg_c059 & 0x8) != 0); zip_speed_0tof_new = g_zipgs_reg_c05a & 0xf0; fast = (g_c036_val_speed & 0x80) || (zip_en && !zip_follow_cps); if(zip_speed_0tof_new != zip_speed_0tof) { zip_speed_0tof = zip_speed_0tof_new; setup_zip_speeds(); } iwm_1 = motor_on && !apple35_sel && (g_c036_val_speed & 0x4) && (g_slow_525_emul_wr || !g_fast_disk_emul); iwm_25 = (motor_on && apple35_sel) && !g_fast_disk_emul; faster_than_28 = fast && (!iwm_1 && !iwm_25) && zip_en && ((limit_speed == 0) || (limit_speed == 3)); zip_speed = faster_than_28 && ((zip_speed_0tof != 0) || (limit_speed == 3) || (g_zipgs_unlock >= 4) ); unl_speed = faster_than_28 && !zip_speed; if(unl_speed) { /* use unlimited speed */ fspeed_mult = g_projected_pmhz; fplus_ptr = &g_recip_projected_pmhz_unl; } else if(zip_speed) { fspeed_mult = g_zip_pmhz; fplus_ptr = &g_recip_projected_pmhz_zip; } else if(fast && !iwm_1 && (iwm_25 || (limit_speed != 1))) { fspeed_mult = 2.5; fplus_ptr = &g_recip_projected_pmhz_fast; } else { /* else run slow */ fspeed_mult = 1.0; fplus_ptr = &g_recip_projected_pmhz_slow; } engine.fplus_ptr = fplus_ptr; prerun_dfcyc = g_cur_dfcyc; engine.dfcyc = prerun_dfcyc; dfcyc_stop = g_event_start.next->dfcyc + 1; if(g_stepping) { dfcyc_stop = prerun_dfcyc + 1; } g_dcycles_end = dfcyc_stop; #if 0 printf("Enter engine, fcycs: %f, stop: %f\n", prerun_fcycles, fcycles_stop); printf("g_cur_dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", g_cur_dfcyc, g_last_vbl_dfcyc); #endif g_num_enter_engine++; prev_dtime = get_dtime(); ret = enter_engine(&engine); now_dtime = get_dtime(); g_cur_sim_dtime += (now_dtime - prev_dtime); dfcyc = engine.dfcyc; g_cur_dfcyc = dfcyc; g_dadjcycs += (engine.dfcyc - prerun_dfcyc) * (1/65536.0) * fspeed_mult; #if 0 printf("...back, engine.dfcyc: %016llx, dfcyc: %016llx\n", (double)engine.dfcyc, dfcyc); #endif if(ret != 0) { g_engine_action++; handle_action(ret); } this_event = g_event_start.next; while(dfcyc >= this_event->dfcyc) { /* Pop this guy off of the queue */ g_event_start.next = this_event->next; type = this_event->type; this_event->next = g_event_free.next; g_event_free.next = this_event; dbg_log_info(dfcyc, type, 0, 0x101); switch(type & 0xff) { case EV_60HZ: update_60hz(dfcyc, now_dtime); return 0; break; case EV_STOP: printf("type: EV_STOP\n"); printf("next: %p, dfcyc: %016llx\n", g_event_start.next, dfcyc); db1 = g_event_start.next; halt_printf("next.dfcyc:%016llx\n", db1->dfcyc); break; case EV_SCAN_INT: g_engine_scan_int++; irq_printf("type: scan int\n"); do_scan_int(dfcyc, type >> 8); break; case EV_DOC_INT: g_engine_doc_int++; doc_handle_event(type >> 8, dfcyc); break; case EV_VBL_INT: do_vbl_int(); break; case EV_SCC: scc_do_event(dfcyc, type >> 8); break; case EV_VID_UPD: video_update_event_line(type >> 8); break; case EV_MOCKINGBOARD: mockingboard_event(dfcyc); break; default: printf("Unknown event: %d!\n", type); exit(3); } this_event = g_event_start.next; } if(g_event_start.next == 0) { halt_printf("ERROR...run_prog, event_start.n=0!\n"); } if(g_halt_sim) { break; } if(g_stepping) { break; } } printf("leaving run_prog, g_halt_sim:%d\n", g_halt_sim); return 0; } void add_irq(word32 irq_mask) { if(g_irq_pending & irq_mask) { /* Already requested, just get out */ return; } g_irq_pending |= irq_mask; engine_recalc_events(); } void remove_irq(word32 irq_mask) { g_irq_pending = g_irq_pending & (~irq_mask); } void take_irq() { word32 new_kpc, va; irq_printf("Taking irq, at: %02x/%04x, psw: %02x, dfcyc:%016llx\n", engine.kpc >> 16, engine.kpc & 0xffff, engine.psr, g_cur_dfcyc); g_num_irq++; if(g_wait_pending) { /* step over WAI instruction */ engine.kpc = (engine.kpc & 0xff0000) | ((engine.kpc + 1) & 0xffff); g_wait_pending = 0; } if(engine.psr & 0x100) { /* Emulation */ set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xff) + 0x100; set_memory_c(engine.stack, engine.kpc & 0xff, 1); engine.stack = ((engine.stack -1) & 0xff) + 0x100; set_memory_c(engine.stack, (engine.psr & 0xef), 1); /* Clear B bit in psr on stack */ engine.stack = ((engine.stack -1) & 0xff) + 0x100; va = 0xfffffe; } else { /* native */ set_memory_c(engine.stack, (engine.kpc >> 16) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, engine.kpc & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); set_memory_c(engine.stack, engine.psr & 0xff, 1); engine.stack = ((engine.stack -1) & 0xffff); va = 0xffffee; } va = moremem_fix_vector_pull(va); new_kpc = get_memory_c(va); new_kpc = new_kpc + (get_memory_c(va + 1) << 8); engine.psr = ((engine.psr & 0x1f3) | 0x4); engine.kpc = new_kpc; HALT_ON(HALT_ON_IRQ, "Halting on IRQ\n"); } double g_dtime_last_vbl = 0.0; double g_dtime_expected = (1.0/VBL_RATE); // Approximately 1.0/60.0 int g_scan_int_events = 0; void show_dtime_array() { double dfirst_time; double first_total_cycs; int i; int pos; dfirst_time = 0.0; first_total_cycs = 0.0; for(i = 0; i < 60; i++) { pos = (g_vbl_index_count + i) % 60; printf("%2d:%2d dt:%.5f adjc:%9.1f this_vbl:%.6f " "exp:%.5f p:%2.2f ep:%2.2f\n", i, pos, dtime_array[pos] - dfirst_time, g_dadjcycs_array[pos] - first_total_cycs, g_dtime_this_vbl_array[pos], g_dtime_exp_array[pos] - dfirst_time, g_dtime_pmhz_array[pos], g_dtime_eff_pmhz_array[pos]); dfirst_time = dtime_array[pos]; first_total_cycs = g_dadjcycs_array[pos]; } } void update_60hz(dword64 dfcyc, double dtime_now) { register word32 end_time; char status_buf[1024]; char sim_mhz_buf[128]; char total_mhz_buf[128]; char sp_buf[128]; char *sim_mhz_ptr, *total_mhz_ptr, *code_str1, *code_str2, *sp_str; dword64 planned_dcyc; double eff_pmhz, predicted_pmhz, recip_predicted_pmhz; double dtime_this_vbl_sim, dtime_diff_1sec, dratio, dtime_diff; double dtime_till_expected, dtime_this_vbl, dadjcycs_this_vbl; double dadj_cycles_1sec, dnatcycs_1sec; int tmp, doit_3_persec, cur_vbl_index, prev_vbl_index; /* NOTE: this event is defined to occur before line 0 */ /* It's actually happening at the start of the border for line (-1) */ /* All other timings should be adjusted for this */ irq_printf("vbl_60hz: vbl: %d, dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", g_vbl_count, dfcyc, g_last_vbl_dfcyc); planned_dcyc = CYCLES_IN_16MS_RAW << 16; g_last_vbl_dfcyc = g_last_vbl_dfcyc + planned_dcyc; add_event_entry(g_last_vbl_dfcyc + planned_dcyc, EV_60HZ); check_for_one_event_type(EV_60HZ, 0xff); cur_vbl_index = g_vbl_index_count; /* figure out dtime spent running SIM, not all the overhead */ dtime_this_vbl_sim = g_cur_sim_dtime; g_cur_sim_dtime = 0.0; g_sim_sum = g_sim_sum - sim_time[cur_vbl_index] + dtime_this_vbl_sim; sim_time[cur_vbl_index] = dtime_this_vbl_sim; dadj_cycles_1sec = g_dadjcycs - g_dadjcycs_array[cur_vbl_index]; /* dtime_diff_1sec is dtime total spent over the last 60 ticks */ dtime_diff_1sec = dtime_now - dtime_array[cur_vbl_index]; dtime_array[cur_vbl_index] = dtime_now; g_dadjcycs_array[cur_vbl_index] = g_dadjcycs; prev_vbl_index = cur_vbl_index; cur_vbl_index = prev_vbl_index + 1; if(cur_vbl_index >= 60) { cur_vbl_index = 0; } g_vbl_index_count = cur_vbl_index; GET_ITIMER(end_time); g_dnatcycs_1sec += (double)(end_time - g_natcycs_lastvbl); g_natcycs_lastvbl = end_time; if(prev_vbl_index == 0) { if(g_sim_sum < (1.0/250.0)) { sim_mhz_ptr = "???"; g_sim_mhz = 250.0; } else { g_sim_mhz = (dadj_cycles_1sec / g_sim_sum) / (1000.0*1000.0); if(g_sim_mhz > 8000.0) { g_sim_mhz = 8000.0; } snprintf(sim_mhz_buf, sizeof(sim_mhz_buf), "%6.2f", g_sim_mhz); sim_mhz_ptr = sim_mhz_buf; } if(dtime_diff_1sec < (1.0/250.0)) { total_mhz_ptr = "???"; } else { snprintf(total_mhz_buf, sizeof(total_mhz_buf), "%6.2f", (dadj_cycles_1sec / dtime_diff_1sec) / (1000000.0)); total_mhz_ptr = total_mhz_buf; } switch(g_limit_speed) { case 1: sp_str = "1Mhz"; break; case 2: sp_str = "2.8Mhz"; break; case 3: sp_str = "8.0Mhz"; break; default: sp_str = "Unlimited"; break; } if(g_limit_speed == 3) { // ZipGS snprintf(sp_buf, sizeof(sp_buf), "%1.1fMHz", g_zip_pmhz); sp_str = sp_buf; } snprintf(status_buf, sizeof(status_buf), "dfcyc:%7.1f sim " "MHz:%s Eff MHz:%s, sec:%1.3f vol:%02x Limit:%s", (double)(dfcyc >> 20)/65536.0, sim_mhz_ptr, total_mhz_ptr, dtime_diff_1sec, g_doc_vol, sp_str); video_update_status_line(0, status_buf); if(g_video_line_update_interval == 0) { if(g_sim_mhz > 12.0) { /* just set video line_ref_amt to 1 */ g_line_ref_amt = 1; } else if(g_line_ref_amt == 1 && g_sim_mhz < 4.0) { g_line_ref_amt = 8; } } else { g_line_ref_amt = g_video_line_update_interval; } dnatcycs_1sec = g_dnatcycs_1sec; if(g_dnatcycs_1sec < (1000.0*1000.0)) { /* make it so large that all %'s become 0 */ dnatcycs_1sec = 800.0*1000.0*1000.0*1000.0; } dnatcycs_1sec = dnatcycs_1sec / 100.0; /* eff mult by 100 */ g_video_pixel_dcount = 0; code_str1 = ""; code_str2 = ""; if(g_code_yellow) { code_str1 = "Code: Yellow"; code_str2 = "Emulated state suspect"; } if(g_code_red) { code_str1 = "Code: RED"; code_str2 = "Emulated state corrupt?"; } snprintf(status_buf, sizeof(status_buf), "sleep_dtime:%8.6f, " "out_16ms:%8.6f, in_16ms:%8.6f, snd_pl:%d", g_dtime_in_sleep, g_dtime_outside_run_16ms, g_dtime_in_run_16ms, g_num_snd_plays); video_update_status_line(1, status_buf); draw_iwm_status(2, status_buf); snprintf(status_buf, sizeof(status_buf), " KEGS v%-6s " "Press F4 for Config Menu %s %s", g_kegs_version_str, code_str1, code_str2); video_update_status_line(3, status_buf); g_status_refresh_needed = 1; g_num_irq = 0; g_num_brk = 0; g_num_cop = 0; g_num_enter_engine = 0; g_io_amt = 0; g_engine_action = 0; g_engine_recalc_event = 0; g_engine_scan_int = 0; g_engine_doc_int = 0; g_cycs_in_40col = 0; g_cycs_in_xredraw = 0; g_cycs_in_refresh_line = 0; g_dnatcycs_1sec = 0.0; g_dtime_outside_run_16ms = 0.0; g_dtime_in_run_16ms = 0.0; g_refresh_bytes_xfer = 0; g_dtime_in_sleep = 0; g_num_snd_plays = 0; g_num_recalc_snd_parms = 0; } dtime_this_vbl = dtime_now - g_dtime_last_vbl; if(dtime_this_vbl < 0.001) { dtime_this_vbl = 0.001; } g_dtime_last_vbl = dtime_now; dadjcycs_this_vbl = g_dadjcycs - g_last_vbl_dadjcycs; g_last_vbl_dadjcycs = g_dadjcycs; g_dtime_expected += (1.0/VBL_RATE); // Approx. 1/60 eff_pmhz = (dadjcycs_this_vbl / dtime_this_vbl) / DCYCS_1_MHZ; /* using eff_pmhz, predict how many cycles can be run by */ /* g_dtime_expected */ dtime_till_expected = g_dtime_expected - dtime_now; dratio = VBL_RATE * dtime_till_expected; // Approx. 60*dtime_exp predicted_pmhz = eff_pmhz * dratio; if(! (predicted_pmhz < (1.4 * g_projected_pmhz))) { predicted_pmhz = 1.4 * g_projected_pmhz; } if(! (predicted_pmhz > (0.7 * g_projected_pmhz))) { predicted_pmhz = 0.7 * g_projected_pmhz; } if(!(predicted_pmhz >= 1.0)) { irq_printf("predicted: %f, setting to 1.0\n", predicted_pmhz); predicted_pmhz = 1.0; } if(!(predicted_pmhz < 4500.0)) { irq_printf("predicted: %f, set to 1900.0\n", predicted_pmhz); predicted_pmhz = 4500.0; } recip_predicted_pmhz = 1.0/predicted_pmhz; g_projected_pmhz = predicted_pmhz; g_recip_projected_pmhz_unl.dplus_1 = (dword64) (65536 * recip_predicted_pmhz); g_recip_projected_pmhz_unl.dplus_x_minus_1 = (dword64)(65536 * (1.01 - recip_predicted_pmhz)); if(dtime_till_expected < -0.125) { /* If we were way off, get back on track */ /* this happens because our sim took much longer than */ /* expected, so we're going to skip some VBL */ irq_printf("adj1: dtexp:%f, dt_new:%f\n", g_dtime_expected, dtime_now); dtime_diff = -dtime_till_expected; irq_printf("dtime_till_exp:%f, dtime_diff:%f, dfcyc:%016llx\n", dtime_till_expected, dtime_diff, dfcyc); g_dtime_expected += dtime_diff; } g_dtime_sleep = 0.0; if(dtime_till_expected > (1.0/VBL_RATE)) { /* we're running fast, usleep */ g_dtime_sleep = dtime_till_expected - (1.0/VBL_RATE); } #if 0 printf("Sleep %f, till_exp:%f, dtime_now:%f, exp:%f\n", g_dtime_sleep, dtime_till_expected, dtime_now, g_dtime_expected); #endif g_dtime_this_vbl_array[prev_vbl_index] = dtime_this_vbl; g_dtime_exp_array[prev_vbl_index] = g_dtime_expected; g_dtime_pmhz_array[prev_vbl_index] = predicted_pmhz; g_dtime_eff_pmhz_array[prev_vbl_index] = eff_pmhz; if(g_c041_val & C041_EN_VBL_INTS) { add_event_vbl(); } g_25sec_cntr++; if(g_25sec_cntr >= 16) { g_25sec_cntr = 0; if(g_c041_val & C041_EN_25SEC_INTS) { add_irq(IRQ_PENDING_C046_25SEC); g_c046_val |= 0x10; irq_printf("Setting c046 .25 sec int, g_irq_pend:%d\n", g_irq_pending); } } g_1sec_cntr++; if(g_1sec_cntr >= 60) { g_1sec_cntr = 0; tmp = g_c023_val; tmp |= 0x40; /* set 1sec int */ if(tmp & 0x04) { tmp |= 0x80; add_irq(IRQ_PENDING_C023_1SEC); irq_printf("Setting c023 to %02x irq_pend: %d\n", tmp, g_irq_pending); } g_c023_val = tmp; } if(!g_scan_int_events) { check_scan_line_int(0); } doit_3_persec = 0; if(g_config_iwm_vbl_count > 0) { g_config_iwm_vbl_count--; } else { g_config_iwm_vbl_count = 20; doit_3_persec = 1; } iwm_vbl_update(); config_vbl_update(doit_3_persec); sound_update(dfcyc); clock_update(); scc_update(dfcyc); paddle_update_buttons(); } void do_vbl_int() { if(g_c041_val & C041_EN_VBL_INTS) { g_c046_val |= 0x08; add_irq(IRQ_PENDING_C046_VBL); irq_printf("Setting c046 vbl_int_status to 1, irq_pend: %d\n", g_irq_pending); } } void do_scan_int(dword64 dfcyc, int line) { int c023_val; if(dfcyc) { // Avoid unused param warning } g_scan_int_events = 0; g_dfcyc_scan_int = 0; c023_val = g_c023_val; if(c023_val & 0x20) { halt_printf("c023 scan_int and another on line %03x\n", line); } #if 0 dvbl = (dfcyc >> 16) / 17030; dline = ((dfcyc >> 16) - (dvbl * 17030)) / 65; printf("do_scan_int at time %lld (%lld,line %lld), line:%d, SCB:%02x, " "a2_stat_ok:%d, c023:%02x\n", dfcyc >> 16, dvbl, dline, line, (g_slow_memory_ptr[0x19d00 + line] & 0x40), (g_cur_a2_stat & ALL_STAT_SUPER_HIRES) != 0, c023_val); for(i = 0; i < 200; i++) { if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { printf(" Line %d has SCB:%02x\n", i, g_slow_memory_ptr[0x19d00 + i]); } } #endif /* make sure scan int is still enabled for this line */ if((g_slow_memory_ptr[0x19d00 + line] & 0x40) && (g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { /* valid interrupt, do it */ c023_val |= 0xa0; /* vgc_int and scan_int */ if(c023_val & 0x02) { add_irq(IRQ_PENDING_C023_SCAN); irq_printf("Setting c023 to %02x, irq_pend: %d\n", c023_val, g_irq_pending); } g_c023_val = c023_val; HALT_ON(HALT_ON_SCAN_INT, "In do_scan_int\n"); } else { /* scan int bit cleared on scan line control byte */ /* look for next line, if any */ check_scan_line_int(line+1); } } void check_scan_line_int(int cur_video_line) { dword64 ddelay; int start, line; int i; /* Called during VBL interrupt phase */ if(!(g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { return; } if(g_c023_val & 0x20) { /* don't check for any more */ return; } start = cur_video_line; if(start < 0) { halt_printf("check_scan_line_int: cur_video_line: %d\n", cur_video_line); start = 0; } for(line = start; line < 200; line++) { i = line; if(i < 0 || i >= 200) { halt_printf("check_new_scan_int:i:%d, line:%d, st:%d\n", i, line, start); i = 0; } if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { irq_printf("Adding scan_int for line %d\n", i); ddelay = (65ULL * line) << 16; add_event_scan_int(g_last_vbl_dfcyc + ddelay, line); g_scan_int_events = 1; break; } } } void check_for_new_scan_int(dword64 dfcyc) { int cur_video_line; cur_video_line = get_lines_since_vbl(dfcyc) >> 8; check_scan_line_int(cur_video_line); } void scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val) { if(new_val & (~old_val) & 0x40) { check_for_new_scan_int(dfcyc); } if(addr) { } } void init_reg() { memset(&engine, 0, sizeof(engine)); engine.acc = 0; engine.xreg = 0; engine.yreg = 0; engine.stack = 0x1ff; engine.direct = 0; engine.psr = 0x134; engine.fplus_ptr = 0; } void handle_action(word32 ret) { int type, arg; type = ret & 0xff; arg = ret >> 8; switch(type) { case RET_BREAK: do_break(arg); break; case RET_COP: do_cop(arg); break; case RET_IRQ: irq_printf("Special fast IRQ response. irq_pending: %x\n", g_irq_pending); break; case RET_WDM: do_wdm(arg); break; case RET_STP: do_stp(); break; case RET_TOOLTRACE: dbg_log_info(g_cur_dfcyc, engine.kpc, engine.xreg, (engine.stack << 16) | 0xe100); break; default: halt_printf("Unknown special action: %08x!\n", ret); } } void do_break(word32 ret) { printf("I think I got a break, second byte: %02x!\n", ret); printf("kpc: %06x\n", engine.kpc); halt_printf("do_break, kpc: %06x\n", engine.kpc); } void do_cop(word32 ret) { halt_printf("COP instr %02x!\n", ret); fflush(stdout); } void do_wdm(word32 arg) { if(arg == 0x00c7) { // WDM, 0xc7, 0x00: WDM in Slot 7 if(engine.psr & 0x40) { // Overflow set: $C700 called do_c700(arg); } else if(engine.psr & 1) { // V=0, C=1: $C70D called do_c70d(arg); } else { // V=0, C=0, $C70A called do_c70a(arg); } return; } if(arg == 0x00ea) { // WDM, 0xea, 0x00: WDM emulator ID do_wdm_emulator_id(); return; } if((arg == 0xeaea) && ((engine.psr & 0x171) == 0x41) && (engine.acc == 0x4d44)) { // WDM $EA,$EA with V=1,C=1 and ACC=0x4d44 ("EM") engine.psr = engine.psr & 0x1bf; // V=0 // printf("WDM $EA,$EA, cleared V=0, psr:%04x\n", engine.psr); return; } switch(arg & 0xff) { case 0x8d: /* Bouncin Ferno does WDM 8d */ break; case 0xea: // Detectiong feature, don't flag an error break; case 0xfc: // HOST.FST "head_call" for ATINIT for ProDOS 8 case 0xfd: // HOST.FST "tail_call" for ATINIT for ProDOS 8 case 0xff: // HOST.FST "call_host" for GS/OS driver break; default: halt_printf("do_wdm: %04x!\n", arg); } } void do_wai() { halt_printf("do_wai!\n"); } void do_stp() { if(!g_stp_pending) { g_stp_pending = 1; halt_printf("Hit STP instruction at: %06x, press RESET to " "continue\n", engine.kpc); } } char g_emulator_name[64]; void do_wdm_emulator_id() { word32 addr, version, subvers; int maxlen, len, c, got_dot; int i; // WDM, $EA, $00: WDM emulator ID // dbank.acc = address to write emulator description string // X = size of buffer to hold string // Y = 0 (not checked) // Returns: X: actual length of emulator string (always <= // value in X at call) // ACC: emulator version as: $VVMN as BCD, so 1.32 is $0132 // Y: Emulation feature flags. bit 0: $c06c-$c06f timer available // Works in emulation mode printf("WDM EA at %06x. acc:%04x, dbank:%02x xreg:%04x\n", engine.kpc, engine.acc, engine.dbank, engine.xreg); maxlen = engine.xreg; cfg_strncpy(&g_emulator_name[0], "KEGS v", 64); cfg_strlcat(&g_emulator_name[0], &g_kegs_version_str[0], 64); len = (int)strlen(&g_emulator_name[0]); addr = engine.acc; engine.xreg = 0; for(i = 0; i < len; i++) { if(i >= maxlen) { break; } addr = (engine.dbank << 8) | (addr & 0xffff); set_memory_c(addr, 0x80 | g_emulator_name[i], 1); addr++; engine.xreg = i + 1; } version = 0; subvers = 0; len = (int)strlen(&g_kegs_version_str[0]); got_dot = 0; for(i = 0; i < len; i++) { c = g_kegs_version_str[i]; if(c == '.') { got_dot++; } if(got_dot >= 3) { break; } if((c >= '0') && (c <= '9')) { c = c - '0'; if(got_dot) { subvers = (subvers << 4) | c; got_dot++; } else { version = (version << 4) | c; } } } engine.acc = ((version & 0xff) << 8) | (subvers & 0xff); engine.yreg = 0x01; // $C06C timer available } void size_fail(int val, word32 v1, word32 v2) { halt_printf("Size failure, val: %08x, %08x %08x\n", val, v1, v2); } int fatal_printf(const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); if(g_fatal_log < 0) { g_fatal_log = 0; } ret = kegs_vprintf(fmt, ap); va_end(ap); return ret; } int kegs_vprintf(const char *fmt, va_list ap) { char *bufptr, *buf2ptr; int len, ret; bufptr = malloc(4096); ret = vsnprintf(bufptr, 4090, fmt, ap); len = (int)strlen(bufptr); if(g_fatal_log >= 0 && g_fatal_log < MAX_FATAL_LOGS) { buf2ptr = malloc(len+1); memcpy(buf2ptr, bufptr, len+1); g_fatal_log_strs[g_fatal_log++] = buf2ptr; } (void)must_write(1, (byte *)bufptr, len); if(g_debug_file_fd >= 0) { (void)must_write(g_debug_file_fd, (byte *)bufptr, len); } free(bufptr); return ret; } dword64 must_write(int fd, byte *bufptr, dword64 dsize) { dword64 dlen; long long ret; word32 this_len; dlen = dsize; while(dlen != 0) { // Support Windows64, which can only rd/wr 2GB max per call this_len = (1UL << 30); if(dlen < this_len) { this_len = (word32)dlen; } ret = write(fd, bufptr, this_len); if(ret >= 0) { dlen -= ret; bufptr += ret; } else if((errno != EAGAIN) && (errno != EINTR)) { return 0; // just get out } } return dsize; } void clear_fatal_logs() { int i; for(i = 0; i < g_fatal_log; i++) { free(g_fatal_log_strs[i]); g_fatal_log_strs[i] = 0; } g_fatal_log = -1; } char * kegs_malloc_str(const char *in_str) { char *str; int len; len = (int)strlen(in_str) + 1; str = malloc(len); memcpy(str, in_str, len); return str; } dword64 kegs_lseek(int fd, dword64 offs, int whence) { #ifdef _WIN32 return _lseeki64(fd, offs, whence); #else return lseek(fd, offs, whence); #endif } ================================================ FILE: upstream/kegs/src/size_c.h ================================================ // "@(#)$KmKId: size_c.h,v 1.2 2023-11-12 15:32:17+00 kentd Exp $" /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2020 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ 0x1, /* 00 */ /* brk */ 0x1, /* 01 */ /* ORA (Dloc,X) */ 0x1, /* 02 */ /* COP */ 0x1, /* 03 */ /* ORA Disp8,S */ 0x1, /* 04 */ /* TSB Dloc */ 0x1, /* 05 */ /* ORA Dloc */ 0x1, /* 06 */ /* ASL Dloc */ 0x1, /* 07 */ /* ORA [Dloc] */ 0x0, /* 08 */ /* PHP */ 0x4, /* 09 */ /* ORA #imm */ 0x0, /* 0a */ /* ASL a */ 0x0, /* 0b */ /* PHD */ 0x2, /* 0c */ /* TSB abs */ 0x2, /* 0d */ /* ORA abs */ 0x2, /* 0e */ /* ASL abs */ 0x3, /* 0f */ /* ORA long */ 0x1, /* 10 */ /* BPL disp8 */ 0x1, /* 11 */ /* ORA (),y */ 0x1, /* 12 */ /* ORA () */ 0x1, /* 13 */ /* ORA (disp8,s),y */ 0x1, /* 14 */ /* TRB Dloc */ 0x1, /* 15 */ /* ORA Dloc,x */ 0x1, /* 16 */ /* ASL Dloc,x */ 0x1, /* 17 */ /* ORA [],y */ 0x0, /* 18 */ /* clc */ 0x2, /* 19 */ /* ORA abs,y */ 0x0, /* 1a */ /* INC a */ 0x0, /* 1b */ /* TCS */ 0x2, /* 1c */ /* TRB Abs */ 0x2, /* 1d */ /* ORA Abs,X */ 0x2, /* 1e */ /* ASL abs,x */ 0x3, /* 1f */ /* ORA Long,x */ 0x2, /* 20 */ /* JSR abs */ 0x1, /* 21 */ /* AND (Dloc,X) */ 0x3, /* 22 */ /* JSL Abslong */ 0x1, /* 23 */ /* AND Disp8,S */ 0x1, /* 24 */ /* BIT Dloc */ 0x1, /* 25 */ /* AND Dloc */ 0x1, /* 26 */ /* ROL Dloc */ 0x1, /* 27 */ /* AND [Dloc] */ 0x0, /* 28 */ /* PLP */ 0x4, /* 29 */ /* AND #imm */ 0x0, /* 2a */ /* ROL a */ 0x0, /* 2b */ /* PLD */ 0x2, /* 2c */ /* BIT abs */ 0x2, /* 2d */ /* AND abs */ 0x2, /* 2e */ /* ROL abs */ 0x3, /* 2f */ /* AND long */ 0x1, /* 30 */ /* BMI disp8 */ 0x1, /* 31 */ /* AND (),y */ 0x1, /* 32 */ /* AND () */ 0x1, /* 33 */ /* AND (disp8,s),y */ 0x1, /* 34 */ /* BIT Dloc,X */ 0x1, /* 35 */ /* AND Dloc,x */ 0x1, /* 36 */ /* ROL Dloc,x */ 0x1, /* 37 */ /* AND [],y */ 0x0, /* 38 */ /* SEC */ 0x2, /* 39 */ /* AND abs,y */ 0x0, /* 3a */ /* DEC a */ 0x0, /* 3b */ /* TSC */ 0x2, /* 3c */ /* BIT Abs,X */ 0x2, /* 3d */ /* AND Abs,X */ 0x2, /* 3e */ /* ROL abs,x */ 0x3, /* 3f */ /* AND Long,x */ 0x0, /* 40 */ /* RTI */ 0x1, /* 41 */ /* EOR (Dloc,X) */ 0x2, /* 42 */ /* WDM HACK: uses 2 args */ 0x1, /* 43 */ /* EOR Disp8,S */ 0x2, /* 44 */ /* MVP I,J */ 0x1, /* 45 */ /* EOR Dloc */ 0x1, /* 46 */ /* LSR Dloc */ 0x1, /* 47 */ /* EOR [Dloc] */ 0x0, /* 48 */ /* PHA */ 0x4, /* 49 */ /* EOR #imm */ 0x0, /* 4a */ /* LSR a */ 0x0, /* 4b */ /* PHK */ 0x2, /* 4c */ /* JMP abs */ 0x2, /* 4d */ /* EOR abs */ 0x2, /* 4e */ /* LSR abs */ 0x3, /* 4f */ /* EOR long */ 0x1, /* 50 */ /* BVC disp8 */ 0x1, /* 51 */ /* EOR (),y */ 0x1, /* 52 */ /* EOR () */ 0x1, /* 53 */ /* EOR (disp8,s),y */ 0x2, /* 54 */ /* MVN I,J */ 0x1, /* 55 */ /* EOR Dloc,x */ 0x1, /* 56 */ /* LSR Dloc,x */ 0x1, /* 57 */ /* EOR [],y */ 0x0, /* 58 */ /* CLI */ 0x2, /* 59 */ /* EOR abs,y */ 0x0, /* 5a */ /* PHY */ 0x0, /* 5b */ /* TCD */ 0x3, /* 5c */ /* JMP Long */ 0x2, /* 5d */ /* EOR Abs,X */ 0x2, /* 5e */ /* LSR abs,x */ 0x3, /* 5f */ /* EOR Long,x */ 0x0, /* 60 */ /* RTS */ 0x1, /* 61 */ /* ADC (Dloc,X) */ 0x2, /* 62 */ /* PER DISP16 */ 0x1, /* 63 */ /* ADC Disp8,S */ 0x1, /* 64 */ /* STZ Dloc */ 0x1, /* 65 */ /* ADC Dloc */ 0x1, /* 66 */ /* ROR Dloc */ 0x1, /* 67 */ /* ADC [Dloc] */ 0x0, /* 68 */ /* PLA */ 0x4, /* 69 */ /* ADC #imm */ 0x0, /* 6a */ /* ROR a */ 0x0, /* 6b */ /* RTL */ 0x2, /* 6c */ /* JMP (abs) */ 0x2, /* 6d */ /* ADC abs */ 0x2, /* 6e */ /* ROR abs */ 0x3, /* 6f */ /* ADC long */ 0x1, /* 70 */ /* BVS disp8 */ 0x1, /* 71 */ /* ADC (),y */ 0x1, /* 72 */ /* ADC () */ 0x1, /* 73 */ /* ADC (disp8,s),y */ 0x1, /* 74 */ /* STZ Dloc,X */ 0x1, /* 75 */ /* ADC Dloc,x */ 0x1, /* 76 */ /* ROR Dloc,x */ 0x1, /* 77 */ /* ADC [],y */ 0x0, /* 78 */ /* SEI */ 0x2, /* 79 */ /* ADC abs,y */ 0x0, /* 7a */ /* PLY */ 0x0, /* 7b */ /* TDC */ 0x2, /* 7c */ /* JMP (abs,x) */ 0x2, /* 7d */ /* ADC Abs,X */ 0x2, /* 7e */ /* ROR abs,x */ 0x3, /* 7f */ /* ADC Long,x */ 0x1, /* 80 */ /* BRA Disp8 */ 0x1, /* 81 */ /* STA (Dloc,X) */ 0x2, /* 82 */ /* BRL DISP16 */ 0x1, /* 83 */ /* STA Disp8,S */ 0x1, /* 84 */ /* STY Dloc */ 0x1, /* 85 */ /* STA Dloc */ 0x1, /* 86 */ /* STX Dloc */ 0x1, /* 87 */ /* STA [Dloc] */ 0x0, /* 88 */ /* DEY */ 0x4, /* 89 */ /* BIT #imm */ 0x0, /* 8a */ /* TXA */ 0x0, /* 8b */ /* PHB */ 0x2, /* 8c */ /* STY abs */ 0x2, /* 8d */ /* STA abs */ 0x2, /* 8e */ /* STX abs */ 0x3, /* 8f */ /* STA long */ 0x1, /* 90 */ /* BCC disp8 */ 0x1, /* 91 */ /* STA (),y */ 0x1, /* 92 */ /* STA () */ 0x1, /* 93 */ /* STA (disp8,s),y */ 0x1, /* 94 */ /* STY Dloc,X */ 0x1, /* 95 */ /* STA Dloc,x */ 0x1, /* 96 */ /* STX Dloc,y */ 0x1, /* 97 */ /* STA [],y */ 0x0, /* 98 */ /* TYA */ 0x2, /* 99 */ /* STA abs,y */ 0x0, /* 9a */ /* TXS */ 0x0, /* 9b */ /* TXY */ 0x2, /* 9c */ /* STX abs */ 0x2, /* 9d */ /* STA Abs,X */ 0x2, /* 9e */ /* STZ abs,x */ 0x3, /* 9f */ /* STA Long,x */ 0x5, /* a0 */ /* LDY #imm */ 0x1, /* a1 */ /* LDA (Dloc,X) */ 0x5, /* a2 */ /* LDX #imm */ 0x1, /* a3 */ /* LDA Disp8,S */ 0x1, /* a4 */ /* LDY Dloc */ 0x1, /* a5 */ /* LDA Dloc */ 0x1, /* a6 */ /* LDX Dloc */ 0x1, /* a7 */ /* LDA [Dloc] */ 0x0, /* a8 */ /* TAY */ 0x4, /* a9 */ /* LDA #imm */ 0x0, /* aa */ /* TAX */ 0x0, /* ab */ /* PLB */ 0x2, /* ac */ /* LDY abs */ 0x2, /* ad */ /* LDA abs */ 0x2, /* ae */ /* LDX abs */ 0x3, /* af */ /* LDA long */ 0x1, /* b0 */ /* BCS disp8 */ 0x1, /* b1 */ /* LDA (),y */ 0x1, /* b2 */ /* LDA () */ 0x1, /* b3 */ /* LDA (disp8,s),y */ 0x1, /* b4 */ /* LDY Dloc,X */ 0x1, /* b5 */ /* LDA Dloc,x */ 0x1, /* b6 */ /* LDX Dloc,y */ 0x1, /* b7 */ /* LDA [],y */ 0x0, /* b8 */ /* CLV */ 0x2, /* b9 */ /* LDA abs,y */ 0x0, /* ba */ /* TSX */ 0x0, /* bb */ /* TYX */ 0x2, /* bc */ /* LDY abs,x */ 0x2, /* bd */ /* LDA Abs,X */ 0x2, /* be */ /* LDX abs,y */ 0x3, /* bf */ /* LDA Long,x */ 0x5, /* c0 */ /* CPY #Imm */ 0x1, /* c1 */ /* CMP (Dloc,X) */ 0x1, /* c2 */ /* REP #8bit */ 0x1, /* c3 */ /* CMP Disp8,S */ 0x1, /* c4 */ /* CPY Dloc */ 0x1, /* c5 */ /* CMP Dloc */ 0x1, /* c6 */ /* DEC Dloc */ 0x1, /* c7 */ /* CMP [Dloc] */ 0x0, /* c8 */ /* INY */ 0x4, /* c9 */ /* CMP #imm */ 0x0, /* ca */ /* DEX */ 0x0, /* cb */ /* WAI */ 0x2, /* cc */ /* CPY abs */ 0x2, /* cd */ /* CMP abs */ 0x2, /* ce */ /* DEC abs */ 0x3, /* cf */ /* CMP long */ 0x1, /* d0 */ /* BNE disp8 */ 0x1, /* d1 */ /* CMP (),y */ 0x1, /* d2 */ /* CMP () */ 0x1, /* d3 */ /* CMP (disp8,s),y */ 0x1, /* d4 */ /* PEI Dloc */ 0x1, /* d5 */ /* CMP Dloc,x */ 0x1, /* d6 */ /* DEC Dloc,x */ 0x1, /* d7 */ /* CMP [],y */ 0x0, /* d8 */ /* CLD */ 0x2, /* d9 */ /* CMP abs,y */ 0x0, /* da */ /* PHX */ 0x0, /* db */ /* STP */ 0x2, /* dc */ /* JML (Abs) */ 0x2, /* dd */ /* CMP Abs,X */ 0x2, /* de */ /* DEC abs,x */ 0x3, /* df */ /* CMP Long,x */ 0x5, /* e0 */ /* CPX #Imm */ 0x1, /* e1 */ /* SBC (Dloc,X) */ 0x1, /* e2 */ /* SEP #8bit */ 0x1, /* e3 */ /* SBC Disp8,S */ 0x1, /* e4 */ /* CPX Dloc */ 0x1, /* e5 */ /* SBC Dloc */ 0x1, /* e6 */ /* INC Dloc */ 0x1, /* e7 */ /* SBC [Dloc] */ 0x0, /* e8 */ /* INX */ 0x4, /* e9 */ /* SBC #imm */ 0x0, /* ea */ /* NOP */ 0x0, /* eb */ /* XBA */ 0x2, /* ec */ /* CPX abs */ 0x2, /* ed */ /* SBC abs */ 0x2, /* ee */ /* INC abs */ 0x3, /* ef */ /* SBC long */ 0x1, /* f0 */ /* BEQ disp8 */ 0x1, /* f1 */ /* SBC (),y */ 0x1, /* f2 */ /* SBC () */ 0x1, /* f3 */ /* SBC (disp8,s),y */ 0x2, /* f4 */ /* PEA Imm */ 0x1, /* f5 */ /* SBC Dloc,x */ 0x1, /* f6 */ /* INC Dloc,x */ 0x1, /* f7 */ /* SBC [],y */ 0x0, /* f8 */ /* SED */ 0x2, /* f9 */ /* SBC abs,y */ 0x0, /* fa */ /* PLX */ 0x0, /* fb */ /* XCE */ 0x2, /* fc */ /* JSR (Abs,x) */ 0x2, /* fd */ /* SBC Abs,X */ 0x2, /* fe */ /* INC abs,x */ 0x3, /* ff */ /* SBC Long,x */ ================================================ FILE: upstream/kegs/src/smartport.c ================================================ const char rcsid_smartport_c[] = "@(#)$KmKId: smartport.c,v 1.61 2024-09-15 13:53:46+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" extern int Verbose; extern int Halt_on; extern int g_rom_version; extern int g_io_amt; extern int g_highest_smartport_unit; extern dword64 g_cur_dfcyc; extern Engine_reg engine; extern Iwm g_iwm; #define LEN_SMPT_LOG 16 STRUCT(Smpt_log) { word32 start_addr; int cmd; int rts_addr; int cmd_list; int extras; int unit; int buf; int blk; }; Smpt_log g_smpt_log[LEN_SMPT_LOG]; int g_smpt_log_pos = 0; void smartport_error(void) { int pos; int i; pos = g_smpt_log_pos; printf("Smartport log pos: %d\n", pos); for(i = 0; i < LEN_SMPT_LOG; i++) { pos--; if(pos < 0) { pos = LEN_SMPT_LOG - 1; } printf("%d:%d: t:%04x, cmd:%02x, rts:%04x, " "cmd_l:%04x, x:%d, unit:%d, buf:%04x, blk:%04x\n", i, pos, g_smpt_log[pos].start_addr, g_smpt_log[pos].cmd, g_smpt_log[pos].rts_addr, g_smpt_log[pos].cmd_list, g_smpt_log[pos].extras, g_smpt_log[pos].unit, g_smpt_log[pos].buf, g_smpt_log[pos].blk); } } void smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list) { int pos; pos = g_smpt_log_pos; if(start_addr != 0) { g_smpt_log[pos].start_addr = start_addr; g_smpt_log[pos].cmd = cmd; g_smpt_log[pos].rts_addr = rts_addr; g_smpt_log[pos].cmd_list = cmd_list; g_smpt_log[pos].extras = 0; g_smpt_log[pos].unit = 0; g_smpt_log[pos].buf = 0; g_smpt_log[pos].blk = 0; } else { pos--; if(pos < 0) { pos = LEN_SMPT_LOG - 1; } g_smpt_log[pos].extras = 1; g_smpt_log[pos].unit = cmd; g_smpt_log[pos].buf = rts_addr; g_smpt_log[pos].blk = cmd_list; } pos++; if(pos >= LEN_SMPT_LOG) { pos = 0; } g_smpt_log_pos = pos; } void do_c70d(word32 arg0) { dword64 dsize; word32 status_ptr, rts_addr, cmd_list, cmd_list_lo, cmd_list_mid; word32 cmd_list_hi, status_ptr_lo, status_ptr_mid, status_ptr_hi; word32 rts_lo, rts_hi, buf_ptr_lo, buf_ptr_hi, buf_ptr, mask, cmd; word32 block_lo, block_mid, block_hi, block_hi2, unit, ctl_code; word32 ctl_ptr_lo, ctl_ptr_hi, ctl_ptr, block, stat_val; int param_cnt, ret, ext, slot; int i; slot = (engine.kpc >> 8) & 7; set_memory_c(0x7f8, 0xc0 | slot, 1); if((engine.psr & 0x100) == 0) { disk_printf("c70d %02x called in native mode!\n", arg0); if((engine.psr & 0x30) != 0x30) { halt_printf("c70d called native, psr: %03x!\n", engine.psr); } } engine.stack = ((engine.stack + 1) & 0xff) + 0x100; rts_lo = get_memory_c(engine.stack); engine.stack = ((engine.stack + 1) & 0xff) + 0x100; rts_hi = get_memory_c(engine.stack); rts_addr = (rts_lo + (256*rts_hi) + 1) & 0xffff; disk_printf("rts_addr: %04x\n", rts_addr); cmd = get_memory_c(rts_addr); cmd_list_lo = get_memory_c((rts_addr + 1) & 0xffff); cmd_list_mid = get_memory_c((rts_addr + 2) & 0xffff); cmd_list_hi = 0; mask = 0xffff; ext = 0; if(cmd & 0x40) { ext = 2; mask = 0xffffff; cmd_list_hi = get_memory_c((rts_addr + 3) & 0xffff); } cmd_list = cmd_list_lo + (256*cmd_list_mid) + (65536*cmd_list_hi); disk_printf("cmd: %02x, cmd_list: %06x\n", cmd, cmd_list); param_cnt = get_memory_c(cmd_list); unit = get_memory_c((cmd_list + 1) & mask); ctl_code = get_memory_c((cmd_list + 4 + ext) & mask); smartport_log(0xc70d, cmd, rts_addr, cmd_list); dbg_log_info(g_cur_dfcyc, (rts_addr << 16) | (unit << 8) | cmd, cmd_list, 0xc70d); #if 0 if(cmd != 0x41) { printf("SMTPT: c70d %08x, %08x at %016llx\n", (rts_addr << 16) | (unit << 8) | cmd, cmd_list, g_cur_dfcyc); } #endif ret = 0; if((unit >= 1) && (unit <= MAX_C7_DISKS) && ext) { if(g_iwm.smartport[unit-1].just_ejected) { ret = 0x2e; // DISKSW error } g_iwm.smartport[unit-1].just_ejected = 0; } switch(cmd & 0x3f) { case 0x00: /* Status == 0x00 and 0x40 */ if(param_cnt != 3) { disk_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } status_ptr_lo = get_memory_c((cmd_list+2) & mask); status_ptr_mid = get_memory_c((cmd_list+3) & mask); status_ptr_hi = 0; if(cmd & 0x40) { status_ptr_hi = get_memory_c((cmd_list+4) & mask); } status_ptr = status_ptr_lo + (256*status_ptr_mid) + (65536*status_ptr_hi); smartport_log(0, unit, status_ptr, ctl_code); dbg_log_info(g_cur_dfcyc, (ctl_code << 16) | unit, cmd_list, 0xc700); disk_printf("unit: %02x, status_ptr: %06x, code: %02x\n", unit, status_ptr, ctl_code); if((unit == 0) && (ctl_code == 0)) { /* Smartport driver status */ /* see technotes/smpt/tn-smpt-002 */ set_memory_c(status_ptr, MAX_C7_DISKS, 1); set_memory_c(status_ptr+1, 0xff, 1); // intrpt stat set_memory16_c(status_ptr+2, 0x004b, 1); // vendor id set_memory16_c(status_ptr+4, 0x1000, 1); // version set_memory16_c(status_ptr+6, 0x0000, 1); //printf(" driver status, highest_unit:%02x\n", // g_highest_smartport_unit+1); engine.xreg = 8; engine.yreg = 0; } else if((unit > 0) && (ctl_code == 0)) { /* status for unit x */ if((unit > MAX_C7_DISKS) || (g_iwm.smartport[unit-1].fd < 0)) { stat_val = 0x80; dsize = 0; ret = 0; // Not DISK_SWITCHed error } else { stat_val = 0xf8; dsize = g_iwm.smartport[unit-1].dimage_size; dsize = (dsize+511) / 512; if(g_iwm.smartport[unit-1].write_prot) { stat_val |= 4; // Write prot } } #if 0 printf(" status unit:%02x just_ejected:%d, " "stat_val:%02x\n", unit, g_iwm.smartport[unit-1].just_ejected, stat_val); #endif set_memory_c(status_ptr, stat_val, 1); set_memory24_c(status_ptr + 1, (word32)dsize); engine.xreg = 4; if(cmd & 0x40) { set_memory_c(status_ptr + 4, (dsize >> 24) & 0xff, 1); engine.xreg = 5; } engine.yreg = 0; disk_printf("just finished unit %d, stat 0\n", unit); } else if(ctl_code == 3) { if((unit > MAX_C7_DISKS) || (g_iwm.smartport[unit-1].fd < 0)) { stat_val = 0x80; dsize = 0; ret = 0; // Not a disk-switched error } else { stat_val = 0xf8; dsize = g_iwm.smartport[unit-1].dimage_size; dsize = (dsize + 511) / 512; } if(cmd & 0x40) { disk_printf("extended for stat_code 3!\n"); } /* DIB for unit 1 */ set_memory_c(status_ptr, stat_val, 1); set_memory24_c(status_ptr + 1, (word32)dsize); if(cmd & 0x40) { set_memory_c(status_ptr + 4, (dsize >> 24) & 0xff, 1); status_ptr++; } set_memory_c(status_ptr + 4, 4, 1); for(i = 5; i < 21; i++) { set_memory_c(status_ptr + i, 0x20, 1); } set_memory_c(status_ptr + 5, 'K', 1); set_memory_c(status_ptr + 6, 'E', 1); set_memory_c(status_ptr + 7, 'G', 1); set_memory_c(status_ptr + 8, 'S', 1); // Profile hard disk supporting extended calls+disk_sw set_memory16_c(status_ptr + 21, 0xc002, 1); set_memory16_c(status_ptr + 23, 0x0000, 1); if(cmd & 0x40) { engine.xreg = 26; } else { engine.xreg = 25; } #if 0 printf(" DIB unit:%02x just_ejected:%d, " "stat_val:%02x\n", unit, g_iwm.smartport[unit-1].just_ejected, stat_val); #endif engine.yreg = 0; disk_printf("Just finished unit %d, stat 3\n", unit); if(unit == 0 || unit > MAX_C7_DISKS) { ret = 0x28; // NODRIVE error } } else { printf("cmd: 00, unknown unit/status code %02x!\n", ctl_code); ret = 0x21; // BADCTL } break; case 0x01: /* Read Block == 0x01 and 0x41 */ if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } buf_ptr_lo = get_memory_c((cmd_list+2) & mask); buf_ptr_hi = get_memory_c((cmd_list+3) & mask); buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); if(cmd & 0x40) { buf_ptr_lo = get_memory_c((cmd_list+4) & mask); buf_ptr_hi = get_memory_c((cmd_list+5) & mask); buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; cmd_list += 2; } block_lo = get_memory_c((cmd_list+4) & mask); block_mid = get_memory_c((cmd_list+5) & mask); block_hi = get_memory_c((cmd_list+6) & mask); block_hi2 = 0; if(cmd & 0x40) { block_hi2 = get_memory_c((cmd_list+7) & mask); } block = (block_hi2 << 24) | (block_hi << 16) | (block_mid << 8) | block_lo; disk_printf("smartport read unit %d of block %06x to %06x\n", unit, block, buf_ptr); if(unit < 1 || unit > MAX_C7_DISKS) { halt_printf("Unknown unit #: %d\n", unit); } smartport_log(0, unit - 1, buf_ptr, block); if(ret == 0) { ret = do_read_c7(unit - 1, buf_ptr, block); } engine.xreg = 0; engine.yreg = 2; break; case 0x02: /* Write Block == 0x02 and 0x42 */ if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); ret = 0x04; // BADPCNT break; } buf_ptr_lo = get_memory_c((cmd_list+2) & mask); buf_ptr_hi = get_memory_c((cmd_list+3) & mask); buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); if(cmd & 0x40) { buf_ptr_lo = get_memory_c((cmd_list+4) & mask); buf_ptr_hi = get_memory_c((cmd_list+5) & mask); buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; cmd_list += 2; } block_lo = get_memory_c((cmd_list+4) & mask); block_mid = get_memory_c((cmd_list+5) & mask); block_hi = get_memory_c((cmd_list+6) & mask); block_hi2 = 0; if(cmd & 0x40) { block_hi2 = get_memory_c((cmd_list+7) & mask); } block = (block_hi2 << 24) | (block_hi << 16) | (block_mid << 8) | block_lo; disk_printf("smartport write unit %d of block %04x from %04x\n", unit, block, buf_ptr); if(unit < 1 || unit > MAX_C7_DISKS) { halt_printf("Unknown unit #: %d\n", unit); } smartport_log(0, unit - 1, buf_ptr, block); if(ret == 0) { ret = do_write_c7(unit - 1, buf_ptr, block); } engine.xreg = 0; engine.yreg = 2; HALT_ON(HALT_ON_C70D_WRITES, "c70d Write done\n"); break; case 0x03: /* Format == 0x03 and 0x43 */ if(param_cnt != 1) { halt_printf("param_cnt %d is != 1!\n", param_cnt); ret = 0x04; // BADPCNT break; } if((unit < 1) || (unit > MAX_C7_DISKS)) { halt_printf("Unknown unit #: %d\n", unit); ret = 0x11; // BADUNIT } smartport_log(0, unit - 1, 0, 0); if(ret == 0) { ret = do_format_c7(unit - 1); } engine.xreg = 0; engine.yreg = 2; HALT_ON(HALT_ON_C70D_WRITES, "c70d Format done\n"); break; case 0x04: /* Control == 0x04 and 0x44 */ if(cmd == 0x44) { halt_printf("smartport code 0x44 not supported\n"); } if(param_cnt != 3) { halt_printf("param_cnt %d is != 3!\n", param_cnt); break; } ctl_ptr_lo = get_memory_c((cmd_list+2) & mask); ctl_ptr_hi = get_memory_c((cmd_list+3) & mask); ctl_ptr = (ctl_ptr_hi << 8) + ctl_ptr_lo; if(cmd & 0x40) { ctl_ptr_lo = get_memory_c((cmd_list+4) & mask); ctl_ptr_hi = get_memory_c((cmd_list+5) & mask); ctl_ptr += ((ctl_ptr_hi << 8) + ctl_ptr_lo) << 16; cmd_list += 2; } switch(ctl_code) { case 0x00: printf("Performing a reset on unit %d\n", unit); break; default: halt_printf("control code: %02x ptr:%06x unknown!\n", ctl_code, ctl_ptr); } // printf("CONTROL, ctl_code:%02x\n", ctl_code); engine.xreg = 0; engine.yreg = 2; break; default: /* Unknown command! */ /* set acc = 1, and set carry, and set kpc */ engine.xreg = (rts_addr) & 0xff; engine.yreg = (rts_addr >> 8) & 0xff; ret = 0x01; // BADCMD error if((cmd != 0x4b) && (cmd != 0x48) && (cmd != 0x4a)) { // Finder does 0x4a before dialog for formatting disk // Finder does 0x4b call before formatting disk // Many things do 0x48 call to see online drives // So: ignore those, just return BADCMD halt_printf("Just did smtport cmd:%02x rts_addr:%04x, " "cmdlst:%06x\n", cmd, rts_addr, cmd_list); } } engine.acc = (engine.acc & 0xff00) | (ret & 0xff); engine.psr &= ~1; if(ret) { engine.psr |= 1; printf("Smtport cmd:%02x unit:%02x ctl_code:%02x ret:%02x\n", cmd, unit, ctl_code, ret); } engine.kpc = (rts_addr + 3 + ext) & 0xffff; // printf(" ret:%02x psr_c:%d\n", ret & 0xff, engine.psr & 1); } // $C70A is the ProDOS entry point, documented in ProDOS 8 Technical Ref // Manual, section 6.3. void do_c70a(word32 arg0) { dword64 dsize; word32 cmd, unit, buf_lo, buf_hi, blk_lo, blk_hi, blk, buf; word32 prodos_unit; int ret, slot; slot = (engine.kpc >> 8) & 7; set_memory_c(0x7f8, 0xc0 | slot, 1); cmd = get_memory_c((engine.direct + 0x42) & 0xffff); prodos_unit = get_memory_c((engine.direct + 0x43) & 0xffff); buf_lo = get_memory_c((engine.direct + 0x44) & 0xffff); buf_hi = get_memory_c((engine.direct + 0x45) & 0xffff); blk_lo = get_memory_c((engine.direct + 0x46) & 0xffff); blk_hi = get_memory_c((engine.direct + 0x47) & 0xffff); blk = (blk_hi << 8) + blk_lo; buf = (buf_hi << 8) + buf_lo; disk_printf("c70a %02x cmd:%02x, pro_unit:%02x, buf:%04x, blk:%04x\n", arg0, cmd, prodos_unit, buf, blk); unit = 0 + (prodos_unit >> 7); // units 0,1 if((prodos_unit & 0x7f) != (slot << 4)) { unit += 2; // units 2,3 } smartport_log(0xc70a, cmd, blk, buf); dbg_log_info(g_cur_dfcyc, (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk, 0xc70a); #if 0 if(cmd != 0x1ff) { printf("SMTPT: c70a %08x %08x\n", (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk); } #endif engine.psr &= ~1; /* clear carry */ ret = 0x27; /* I/O error */ if(cmd == 0x00) { dsize = g_iwm.smartport[unit].dimage_size; dsize = (dsize + 511) / 512; smartport_log(0, unit, (word32)dsize, 0); dbg_log_info(g_cur_dfcyc, ((unit & 0xff) << 8) | (cmd & 0xff), (word32)dsize, 0x1c700); ret = 0; engine.xreg = dsize & 0xff; engine.yreg = (word32)(dsize >> 8); } else if(cmd == 0x01) { smartport_log(0, unit, buf, blk); ret = do_read_c7(unit, buf, blk); } else if(cmd == 0x02) { smartport_log(0, unit, buf, blk); ret = do_write_c7(unit, buf, blk); } else if(cmd == 0x03) { /* format */ smartport_log(0, unit, buf, blk); ret = do_format_c7(unit); } engine.acc = (engine.acc & 0xff00) | (ret & 0xff); if(ret != 0) { engine.psr |= 1; // Set carry } return; } int do_read_c7(int unit_num, word32 buf, word32 blk) { byte local_buf[0x200]; Disk *dsk; byte *bptr; dword64 dimage_start, dimage_size, dret; word32 val; int len, fd; int i; dbg_log_info(g_cur_dfcyc, (buf << 8) | (unit_num & 0xff), blk, 0xc701); if((unit_num < 0) || (unit_num > MAX_C7_DISKS)) { halt_printf("do_read_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { printf("c7_fd == %d!\n", fd); #if 0 if(blk != 2 && blk != 0) { /* don't print error if only reading directory */ smartport_error(); halt_printf("Read unit:%02x blk:%04x\n", unit_num, blk); } #endif return 0x2f; } if(((blk + 1) * 0x200ULL) > (dimage_start + dimage_size)) { halt_printf("Tried to read past %08llx on disk (blk:%04x)\n", dimage_start + dimage_size, blk); smartport_error(); return 0x27; } if(dsk->raw_data) { // image was compressed and is in dsk->raw_data bptr = dsk->raw_data + dimage_start + (blk*0x200ULL); for(i = 0; i < 0x200; i++) { local_buf[i] = bptr[i]; } } else { dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); if(dret != (dimage_start + blk*0x200ULL)) { halt_printf("lseek ret %08llx, errno:%d\n", dret, errno); smartport_error(); return 0x27; } len = (int)read(fd, &local_buf[0], 0x200); if(len != 0x200) { printf("read returned %08x, errno:%d, blk:%04x, unit:" "%02x\n", len, errno, blk, unit_num); halt_printf("name: %s\n", dsk->name_ptr); smartport_error(); return 0x27; } } g_io_amt += 0x200; if(buf >= 0xfc0000) { disk_printf("reading into ROM, just returning\n"); return 0; } for(i = 0; i < 0x200; i += 2) { val = (local_buf[i+1] << 8) + local_buf[i]; set_memory16_c(buf + i, val, 0); } return 0; } int do_write_c7(int unit_num, word32 buf, word32 blk) { byte local_buf[0x200]; Disk *dsk; dword64 dret, dimage_start, dimage_size; int len, fd, ret; int i; dbg_log_info(g_cur_dfcyc, (buf << 16) | (unit_num & 0xff), blk, 0xc702); if(unit_num < 0 || unit_num > MAX_C7_DISKS) { halt_printf("do_write_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { halt_printf("c7_fd == %d!\n", fd); smartport_error(); return 0x28; } for(i = 0; i < 0x200; i++) { local_buf[i] = get_memory_c(buf + i); } if(dsk->write_prot) { printf("Write, but s7d%d %s is write protected!\n", unit_num + 1, dsk->name_ptr); return 0x2b; } if(dsk->write_through_to_unix == 0) { //halt_printf("Write to %s, but not wr_thru!\n", dsk->name_ptr); if(dsk->raw_data) { // Update the memory copy ret = smartport_memory_write(dsk, &local_buf[0], blk * 0x200ULL, 0x200); if(ret) { return 0x27; // I/O Error } } return 0x00; } if(dsk->dynapro_info_ptr) { dynapro_write(dsk, &local_buf[0], blk*0x200UL, 0x200); } else { dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); if(dret != (dimage_start + blk*0x200ULL)) { halt_printf("lseek returned %08llx, errno: %d\n", dret, errno); smartport_error(); return 0x27; } if(dret >= (dimage_start + dimage_size)) { halt_printf("Tried to write to %08llx\n", dret); smartport_error(); return 0x27; } len = (int)write(fd, &local_buf[0], 0x200); if(len != 0x200) { halt_printf("write ret %08x bytes, errno: %d\n", len, errno); smartport_error(); dsk->write_prot = 1; return 0x2b; // Write protected } } g_io_amt += 0x200; return 0; } int smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) { byte *bptr; word32 ui; bptr = dsk->raw_data; if((bptr == 0) || ((doffset + size) > dsk->dimage_size)) { printf("Write to %s failed, %08llx past end %08llx\n", dsk->name_ptr, doffset, dsk->dimage_size); return -1; } for(ui = 0; ui < size; ui++) { bptr[doffset + ui] = bufptr[ui]; } return 0; } int do_format_c7(int unit_num) { byte local_buf[0x1000]; Disk *dsk; dword64 dimage_start, dimage_size, dret, dtotal, dsum; int len, max, fd, ret; int i; dbg_log_info(g_cur_dfcyc, (unit_num & 0xff), 0, 0xc703); if(unit_num < 0 || unit_num > MAX_C7_DISKS) { halt_printf("do_format_c7: unit_num: %d\n", unit_num); smartport_error(); return 0x28; } dsk = &(g_iwm.smartport[unit_num]); fd = dsk->fd; dimage_start = dsk->dimage_start; dimage_size = dsk->dimage_size; if(fd < 0) { halt_printf("c7_fd == %d!\n", fd); smartport_error(); return 0x28; } if(dsk->write_prot || (dsk->raw_data && !dsk->dynapro_info_ptr)) { printf("Format, but %s is write protected!\n", dsk->name_ptr); return 0x2b; } if(dsk->write_through_to_unix == 0) { if(!dsk->raw_data) { printf("Format of %s ignored\n", dsk->name_ptr); return 0x00; } } for(i = 0; i < 0x1000; i++) { local_buf[i] = 0; } if(!dsk->dynapro_info_ptr) { dret = kegs_lseek(fd, dimage_start, SEEK_SET); if(dret != dimage_start) { halt_printf("lseek returned %08llx, errno: %d\n", dret, errno); smartport_error(); return 0x27; } } dsum = 0; dtotal = dimage_size; while(dsum < dtotal) { max = (int)MY_MIN(0x1000, dtotal - dsum); len = max; if(dsk->dynapro_info_ptr) { dynapro_write(dsk, &local_buf[0], dsum, max); } else if(dsk->raw_data) { ret = smartport_memory_write(dsk, &local_buf[0], dsum, max); if(ret) { return 0x27; // I/O Error } } else { len = (int)write(fd, &local_buf[0], max); } if(len != max) { halt_printf("write ret %08x, errno:%d\n", len, errno); smartport_error(); dsk->write_prot = 1; return 0x2b; // Write-protected } dsum += len; } return 0; } void do_c700(word32 ret) { int slot; disk_printf("do_c700 called, ret: %08x\n", ret); dbg_log_info(g_cur_dfcyc, 0, 0, 0xc700); slot = (engine.kpc >> 8) & 7; ret = do_read_c7(0, 0x800, 0); // Always read unit 0, block 0 set_memory_c(0x7f8, slot, 1); set_memory16_c(0x42, (slot << 12) | 1, 1); set_memory16_c(0x44, 0x0800, 1); set_memory16_c(0x46, 0x0000, 1); engine.xreg = slot << 4; // 0x70 for slot 7 engine.kpc = 0x801; if(ret != 0) { printf("Failure reading boot disk in s7d1, trying slot 5!\n"); engine.kpc = 0xc500; // Try to boot slot 5 if((slot == 5) || (g_rom_version == 0)) { engine.kpc = 0xc600; // Try to boot slot 6 } } } ================================================ FILE: upstream/kegs/src/sound.c ================================================ const char rcsid_sound_c[] = "@(#)$KmKId: sound.c,v 1.155 2023-08-19 17:45:33+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include "defc.h" #define INCLUDE_RCSID_C #include "sound.h" #undef INCLUDE_RCSID_C #define DOC_LOG(a,b,c,d) extern int Verbose; extern int g_use_shmem; extern word32 g_vbl_count; extern int g_preferred_rate; extern word32 g_c03ef_doc_ptr; extern int g_doc_vol; extern dword64 g_last_vbl_dfcyc; int g_queued_samps = 0; int g_queued_nonsamps = 0; #if defined(HPUX) || defined(__linux__) || defined(_WIN32) || defined(MAC) int g_audio_enable = -1; #else int g_audio_enable = 0; /* Not supported: default to off */ #endif int g_sound_min_msecs = 32; // 32 msecs int g_sound_min_msecs_pulse = 150; // 150 msecs int g_sound_max_multiplier = 6; // 6*32 = ~200 msecs int g_sound_min_samples = 48000 * 32/1000; // 32 msecs Mockingboard g_mockingboard; // The AY8913 chip has non-linear amplitudes (it has 16 levels) and the // documentation does not match measured results. But all the measurements // should really be done at the final speaker/jack since all the stuff in // the path affects it. But: no one's done this for Mockingboard that I // have found, so I'm taking measurements from the AY8913 chip itself. // AY8913 amplitudes from https://groups.google.com/forum/#!original/ // comp.sys.sinclair/-zCR2kxMryY/XgvaDICaldUJ // by Matthew Westcott on December 21, 2001. double g_ay8913_ampl_factor_westcott[16] = { // NOT USED 0.000, // level[0] 0.010, // level[1] 0.015, // level[2] 0.022, // level[3] 0.031, // level[4] 0.046, // level[5] 0.064, // level[6] 0.106, // level[7] 0.132, // level[8] 0.216, // level[9] 0.297, // level[10] 0.391, // level[11] 0.513, // level[12] 0.637, // level[13] 0.819, // level[14] 1.000, // level[15] }; // https://sourceforge.net/p/fuse-emulator/mailman/message/34065660/ // refers to some Russian-language measurements at: // http://forum.tslabs.info/viewtopic.php?f=6&t=539 (translate from // Russian), they give: // 0000,028F,03B3,0564, 07DC,0BA9,1083,1B7C, // 2068,347A,4ACE,5F72, 7E16,A2A4,CE3A,FFFF double g_ay8913_ampl_factor[16] = { 0.000, // level[0] 0.010, // level[1] 0.014, // level[2] 0.021, // level[3] 0.031, // level[4] 0.046, // level[5] 0.064, // level[6] 0.107, // level[7] 0.127, // level[8] 0.205, // level[9] 0.292, // level[10] 0.373, // level[11] 0.493, // level[12] 0.635, // level[13] 0.806, // level[14] 1.000, // level[15] }; // MAME also appears to try to figure out how the channels get "summed" // together. KEGS code adds them in a completely independent way, and due // to the circuit used on the AY8913, this is certainly incorrect. #define MAX_MOCK_ENV_SAMPLES 2000 int g_mock_env_vol[MAX_MOCK_ENV_SAMPLES]; byte g_mock_noise_bytes[MAX_MOCK_ENV_SAMPLES]; int g_mock_volume[16]; // Sample for each of the 16 amplitudes word32 g_last_mock_vbl_count = 0; #define VAL_MOCK_RANGE (39000) int g_audio_rate = 0; double g_daudio_rate = 0.0; double g_drecip_audio_rate = 0.0; double g_dsamps_per_dfcyc = 0.0; double g_fcyc_per_samp = 0.0; double g_last_sound_play_dsamp = 0.0; #define VAL_C030_POS_VAL (20400*16/15) // C030_POS_VAL is multiplied by g_doc_vol (0-15) and then // divided by 16. So scale this value up by 16/15 so that // g_doc_vol==15 gives the intended value (+/-20400) #define MAX_C030_TIMES 18000 float g_c030_fsamps[MAX_C030_TIMES + 2]; int g_num_c030_fsamps = 0; int g_c030_state = 0; int g_c030_val = (VAL_C030_POS_VAL / 2); dword64 g_c030_dsamp_last_toggle = 0; word32 *g_sound_shm_addr = 0; int g_sound_shm_pos = 0; extern dword64 g_cur_dfcyc; #define MAX_SND_BUF 65536 int g_samp_buf[2*MAX_SND_BUF]; byte g_zero_buf[4096]; double g_doc_dsamps_extra = 0.0; int g_num_snd_plays = 0; int g_num_recalc_snd_parms = 0; char *g_sound_file_str = 0; int g_sound_file_fd = -1; int g_sound_file_bytes = 0; // WAV file information: // From https://docs.fileformat.com/audio/wav/ // left channel is first: https://web.archive.org/web/20080113195252/ // http://www.borg.com/~jglatt/tech/wave.htm byte g_wav_hdr[44] = { 'R', 'I', 'F', 'F', 0xff, 0xff, 0xff, 0xff, // 0x00-0x07 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ', // 0x08-0x0f 16, 0, 0, 0, 1, 0, 2, 0, // 0x10-0x17 // 16=length of 'fmt ' chunk, 1=PCM, 2=stereo. 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0x18-0x1f 4, 0, 16, 0, 'd', 'a', 't', 'a', // 0x20-0x27 0xff, 0xff, 0xff, 0xff // 0x28-0x2b }; // Bytes 4-7 are the total file size-8, so [0x28:0x2b]+0x24 // Bytes [0x18-0x1b]is the sample rate (so 44100 or so) // [0x1c-0x1f] is bytes-per-second: [0x18-0x1b]*[0x10]*[0x16]/8 void sound_init() { doc_init(); snddrv_init(); } void sound_set_audio_rate(int rate) { g_audio_rate = rate; g_daudio_rate = (rate)*1.0; g_drecip_audio_rate = 1.0/(rate); g_dsamps_per_dfcyc = ((rate*1.0) / (DCYCS_1_MHZ * 65536.0)); g_fcyc_per_samp = (DCYCS_1_MHZ * 65536.0 / (rate*1.0)); g_sound_min_samples = rate * g_sound_min_msecs / 1000; printf("Set g_audio_rate = %d in main KEGS process, min_samples:%d\n", rate, g_sound_min_samples); } void sound_reset(dword64 dfcyc) { doc_reset(dfcyc); mockingboard_reset(dfcyc); } void sound_shutdown() { snddrv_shutdown(); } void sound_update(dword64 dfcyc) { /* Called every VBL time to update sound status */ /* "play" sounds for this vbl */ //DOC_LOG("do_snd_pl", -1, dsamps, 0); sound_play(dfcyc); } void sound_file_start(char *filename) { sound_file_close(); g_sound_file_str = filename; // Can be NULL, if so, do not start if(filename) { printf("Set audio save file to: %s\n", filename); } } void sound_file_open() { char *filename; word32 exp_size; int fd; filename = g_sound_file_str; if(!filename) { return; } fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); if(fd < 0) { printf("open_sound_file open ret: %d, errno: %d\n", fd, errno); sound_file_close(); return; } exp_size = 1024*1024; // Default to 1MB, changed at close dynapro_set_word32(&g_wav_hdr[4], exp_size + 44 - 8); // File size dynapro_set_word32(&g_wav_hdr[0x28], exp_size); // data size dynapro_set_word32(&g_wav_hdr[0x18], g_audio_rate); // Sample rate dynapro_set_word32(&g_wav_hdr[0x1c], g_audio_rate * 2 * 2); // bytes-per-sec (void)cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); g_sound_file_fd = fd; g_sound_file_bytes = 0; printf("Opened file %s for sound\n", filename); } void sound_file_close() { int fd; fd = g_sound_file_fd; if(fd >= 0) { dynapro_set_word32(&g_wav_hdr[0x28], g_sound_file_bytes); dynapro_set_word32(&g_wav_hdr[4], g_sound_file_bytes + 44 - 8); cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); // Rewrite first 44 bytes with WAV header printf("Close sound file %s, fd:%d\n", g_sound_file_str, fd); close(fd); } free(g_sound_file_str); g_sound_file_fd = -1; g_sound_file_str = 0; } void send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps) { int size, this_size; if(!real_samps && g_sound_file_bytes) { // Don't do anything return; } if(g_sound_file_fd < 0) { sound_file_open(); } if(!wptr) { // No real samps size = real_samps * 4; while(size) { this_size = size; if(this_size > 4096) { this_size = 4096; } must_write(g_sound_file_fd, &g_zero_buf[0], this_size); size -= this_size; } return; } size = 0; if((num_samps + shm_pos) > SOUND_SHM_SAMP_SIZE) { size = SOUND_SHM_SAMP_SIZE - shm_pos; g_sound_file_bytes += (size * 4); must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*size); shm_pos = 0; num_samps -= size; } g_sound_file_bytes += (num_samps * 4); must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*num_samps); } void show_c030_state(dword64 dfcyc) { show_c030_samps(dfcyc, &(g_samp_buf[0]), 100); } void show_c030_samps(dword64 dfcyc, int *outptr, int num) { int last; int i; if(!g_num_c030_fsamps) { return; } printf("c030_fsamps[]: %d, dfcyc:%015llx\n", g_num_c030_fsamps, dfcyc); for(i = 0; i < g_num_c030_fsamps+2; i++) { printf("%3d: %5.3f\n", i, g_c030_fsamps[i]); } printf("Samples[] = %d\n", num); last = 0x0dadbeef; for(i = 0; i < num; i++) { if((last != outptr[0]) || (i == (num - 1))) { printf("Samp[%4d]: %d\n", i, outptr[0]); last = outptr[0]; } outptr += 2; } } int sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps) { int *outptr; dword64 dsamp_min; float ftmp, fsampnum, next_fsampnum, fpercent; int val, num, c030_state, c030_val, pos, sampnum, next_sampnum; int doc_vol, min_i, mul; int i, j; // Handle $C030 speaker clicks. Clicks for the past num_samps are // in g_c030_fsamps[] giving the sample position when the click // occurred. Turn this into samples, tracking multiple clicks per // sample into an intermediate value. After 500ms of no clicks, // transition the speakers from +/-20400 to 0, so it's idle. // The speaker is affected by the DOC volume in g_doc_vol, like a real // IIgs (this is used during the system beep). This code reacts // to DOC volume changes when they happen by causing sound_play() // to be called, so all samples with the old volume are played then // new clicks are collected. num = g_num_c030_fsamps; // Number of clicks if(!num) { if(g_c030_val == 0) { return 0; // Speaker is at rest } } pos = 0; outptr = outptr_start; c030_state = g_c030_state; c030_val = g_c030_val; // c030_val may be less than max due decay after 500ms. // Always use it first, until speaker toggles, which should // restore the full speaker range doc_vol = g_doc_vol; if(!num) { // No clicks. See if we should begin transitioning the // speaker output to 0. I tried multiplying by .9999 but // that seemed to take too long at the end, so just use a // linear ramp down. Do this ramp based on the last click // time, not VBL, since this is more consistent dsamp_min = g_c030_dsamp_last_toggle + (g_audio_rate >> 4); if(dsamp >= dsamp_min) { min_i = 0; } else { min_i = (int)(dsamp_min - dsamp); } mul = (2 * c030_state - 1) * doc_vol; val = (c030_val * mul) >> 4; for(i = 0; i < num_samps; i++) { if(i >= min_i) { if(c030_val > 4) { c030_val -= 4; } else { c030_val = 0; } val = (c030_val * mul) >> 4; } outptr[0] = val; outptr[1] = val; outptr += 2; } #if 0 printf("at %015llx, num_samps:%d val at start:%d, at end:%d, " "min_i:%d\n", dfcyc, num_samps, g_c030_val, c030_val, min_i); #endif g_c030_val = c030_val; if(c030_val == 0) { //printf("Speaker at rest\n"); } return 1; } g_c030_fsamps[num] = (float)(num_samps); num++; fsampnum = g_c030_fsamps[0]; sampnum = (int)fsampnum; fpercent = (float)0.0; i = 0; while(i < num) { if(sampnum < 0 || sampnum > num_samps) { halt_printf("play c030: [%d]:%f is %d, > %d\n", i, fsampnum, sampnum, num_samps); break; } /* write in samples to all samps < me */ val = ((2 * c030_state) - 1) * ((c030_val * doc_vol) >> 4); if(num <= 1) { printf("num:%d i:%d pos:%d, sampnum:%d c030_state:%d " " at %015llx\n", num, i, pos, sampnum, c030_state, dfcyc); } for(j = pos; j < sampnum; j++) { outptr[0] = val; outptr[1] = val; outptr += 2; pos++; } if((sampnum >= num_samps) || ((i + 1) >= num)) { break; // All done } /* now, calculate me */ fpercent = (float)0.0; if(c030_state) { fpercent = (fsampnum - (float)sampnum); } c030_state = !c030_state; c030_val = VAL_C030_POS_VAL; g_c030_dsamp_last_toggle = dsamp + i; next_fsampnum = g_c030_fsamps[i+1]; next_sampnum = (int)next_fsampnum; // Handle all the changes during this one sample while(next_sampnum == sampnum) { if(c030_state) { fpercent += (next_fsampnum - fsampnum); } i++; fsampnum = next_fsampnum; if(i > num) { break; // This should not happen! } next_fsampnum = g_c030_fsamps[i+1]; next_sampnum = (int)next_fsampnum; c030_state = !c030_state; } if(c030_state) { // add in time until next sample ftmp = (float)(int)(fsampnum + (float)1.0); fpercent += (ftmp - fsampnum); } if((fpercent < (float)0.0) || (fpercent > (float)1.0)) { halt_printf("fpercent: %d = %f\n", i, fpercent); show_c030_samps(dfcyc, outptr_start, num_samps); break; } val = (int)((2*fpercent - 1) * ((c030_val * doc_vol) >> 4)); outptr[0] = val; outptr[1] = val; outptr += 2; pos++; i++; sampnum = next_sampnum; fsampnum = next_fsampnum; } g_c030_state = c030_state; g_c030_val = c030_val; #if 0 if(g_sound_file_str) { show_c030_samps(dfcyc, outptr_start, num_samps); } #endif // See if there are any entries >= fsampnum, copy them back down // to the beginning of the array pos = 0; num--; fsampnum = (float)num_samps; while(i < num) { g_c030_fsamps[pos] = g_c030_fsamps[i] - fsampnum; #if 0 printf("Copied [%d] %f to [%d] as %f\n", i, g_c030_fsamps[i], pos, g_c030_fsamps[pos]); #endif i++; pos++; } g_num_c030_fsamps = pos; return 1; } int g_sound_play_depth = 0; // sound_play(): forms the samples from the last sample time to the current // time. Can be called anytime from anywhere. This is how KEGS handles // dynamic sound changes (say, disabling an Ensoniq oscillator manually): // when it's turned off, call sound_play() to play up to this moment, then // the next time sound_play() is called, it will just know this osc is off // So, on any sound-related state change, call sound_play(). void sound_play(dword64 dfcyc) { Ay8913 *ay8913ptr; int *outptr, *outptr_start; word32 *sndptr; double last_dsamp, dsamp_now, dvolume, dsamps; word32 uval1, uval0; int val, val0, val1, pos, snd_buf_init, num_samps, num_pairs; int sound_mask, ivol; int i, j; g_num_snd_plays++; if(g_sound_play_depth) { halt_printf("Nested sound_play!\n"); } g_sound_play_depth++; /* calc sample num */ dsamps = dfcyc * g_dsamps_per_dfcyc; last_dsamp = g_last_sound_play_dsamp; num_samps = (int)(dsamps - g_last_sound_play_dsamp); dsamp_now = last_dsamp + (double)num_samps; if(num_samps < 1) { /* just say no */ g_sound_play_depth--; return; } dbg_log_info(dfcyc, (word32)(dword64)dsamp_now, num_samps, 0x200); if(num_samps > MAX_SND_BUF) { printf("num_samps: %d, too big!\n", num_samps); g_sound_play_depth--; return; } outptr_start = &(g_samp_buf[0]); outptr = outptr_start; snd_buf_init = sound_play_c030(dfcyc, (dword64)dsamp_now, outptr_start, num_samps); snd_buf_init = doc_play(dfcyc, last_dsamp, dsamp_now, num_samps, snd_buf_init, outptr_start); num_pairs = 0; // Do Mockinboard channels for(i = 0; i < 2; i++) { // Pair: 0 or 1 ay8913ptr = &(g_mockingboard.pair[i].ay8913); for(j = 0; j < 3; j++) { // Channels: A, B, or C if((ay8913ptr->regs[8 + j] & 0x1f) == 0) { continue; } num_pairs = 2; g_last_mock_vbl_count = g_vbl_count; break; } } if((g_vbl_count - g_last_mock_vbl_count) < 120) { // Keep playing for 2 seconds, to avoid some static issues num_pairs = 2; } if(num_pairs) { sound_mask = -1; if(snd_buf_init == 0) { sound_mask = 0; snd_buf_init++; } outptr = outptr_start; ivol = -((VAL_MOCK_RANGE * 3 / (8 * 15)) * g_doc_vol); // Do 3/8 of range below 0, leaving 5/8 above 0 for(i = 0; i < num_samps; i++) { outptr[0] = (outptr[0] & sound_mask) + ivol; outptr[1] = (outptr[1] & sound_mask) + ivol; outptr += 2; } for(i = 0; i < 16; i++) { dvolume = (g_doc_vol * VAL_MOCK_RANGE) / (15.0 * 3.0); ivol = (int)(g_ay8913_ampl_factor[i] * dvolume); g_mock_volume[i] = ivol; } } for(i = 0; i < num_pairs; i++) { if(g_mockingboard.disable_mask) { printf("dsamp:%lf\n", dsamps); } sound_mock_envelope(i, &(g_mock_env_vol[0]), num_samps, &(g_mock_volume[0])); sound_mock_noise(i, &(g_mock_noise_bytes[0]), num_samps); for(j = 0; j < 3; j++) { sound_mock_play(i, j, outptr_start, &(g_mock_env_vol[0]), &(g_mock_noise_bytes[0]), &(g_mock_volume[0]), num_samps); } } g_last_sound_play_dsamp = dsamp_now; outptr = outptr_start; pos = g_sound_shm_pos; sndptr = g_sound_shm_addr; #if 0 printf("samps_left: %d, num_samps: %d\n", samps_left, num_samps); #endif if(g_audio_enable != 0) { if(snd_buf_init) { /* convert sound buf */ for(i = 0; i < num_samps; i++) { val0 = outptr[0]; val1 = outptr[1]; val = val0; if(val0 > 32767) { val = 32767; } if(val0 < -32768) { val = -32768; } uval0 = val & 0xffffU; val = val1; if(val1 > 32767) { val = 32767; } if(val1 < -32768) { val = -32768; } uval1 = val & 0xffffU; outptr += 2; #if defined(__linux__) || defined(OSS) /* Linux seems to expect little-endian */ /* samples always, even on PowerPC */ # ifdef KEGS_BIG_ENDIAN sndptr[pos] = ((uval1 & 0xff) << 24) + ((uval1 & 0xff00) << 8) + ((uval0 & 0xff) << 8) + ((uval0 >> 8) & 0xff); # else sndptr[pos] = (uval1 << 16) + (uval0 & 0xffff); # endif #else # ifdef KEGS_BIG_ENDIAN sndptr[pos] = (uval0 << 16) + uval1; # else sndptr[pos] = (uval1 << 16) + uval0; # endif #endif pos++; if(pos >= SOUND_SHM_SAMP_SIZE) { pos = 0; } } if(g_queued_nonsamps) { /* force out old 0 samps */ snddrv_send_sound(0, g_queued_nonsamps); g_queued_nonsamps = 0; } if(g_sound_file_str) { send_sound_to_file(g_sound_shm_addr, g_sound_shm_pos, num_samps, 1); } g_queued_samps += num_samps; } else { /* move pos */ pos += num_samps; while(pos >= SOUND_SHM_SAMP_SIZE) { pos -= SOUND_SHM_SAMP_SIZE; } if(g_sound_file_str) { send_sound_to_file(0, g_sound_shm_pos, num_samps, 0); } if(g_queued_samps) { /* force out old non-0 samps */ snddrv_send_sound(1, g_queued_samps); g_queued_samps = 0; } g_queued_nonsamps += num_samps; } } g_sound_shm_pos = pos; if(g_audio_enable != 0) { if(g_queued_samps >= (g_audio_rate/60)) { snddrv_send_sound(1, g_queued_samps); g_queued_samps = 0; } if(g_queued_nonsamps >= (g_audio_rate/60)) { snddrv_send_sound(0, g_queued_nonsamps); g_queued_nonsamps = 0; } } g_last_sound_play_dsamp = dsamp_now; g_sound_play_depth--; } void sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr) { Ay8913 *ay8913ptr; double dmul, denv_period, dusecs_per_samp; dword64 env_dsamp, dsamp_inc; word32 ampl, eff_ampl, reg13, env_val, env_period; int i; // This routine calculates a fixed-point increment to apply // to env_dsamp, where the envelope value is in bits 44:40 (bit // 44 is to track the alternating waveform, 43:40 is the env_ampl). // This algorithm does not properly handle dynamically changing the // envelope period in the middle of a step. In the AY8913, the // part counts up to the env_period, and if the period is changed // to a value smaller than the current count, it steps immediately // to the next step. This routine will wait for enough fraction // to accumulate before stepping. At most, this can delay the step // by almost the new count time (if the new period is smaller), but // no more. I suspect this is not noticeable. if(num_samps > MAX_MOCK_ENV_SAMPLES) { halt_printf("envelope overflow!: %d\n", num_samps); return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); ampl = ay8913ptr->regs[8] | ay8913ptr->regs[9] | ay8913ptr->regs[10]; if((ampl & 0x10) == 0) { // No one uses the envelope return; } env_dsamp = ay8913ptr->env_dsamp; env_period = ay8913ptr->regs[11] + (256 * ay8913ptr->regs[12]); if(env_period == 0) { denv_period = 0.5; // To match MAME } else { denv_period = (double)env_period; } dmul = (1.0 / 16.0) * (1 << 20) * (1 << 20); // (1ULL << 40) / 16.0 // Calculate amount counter will count in one sample. // inc_per_tick 62.5KHz tick: (1/env_period) // inc_per_dfcyc: (1/(16*env_period)) // inc_per_samp = inc_per_dfcyc * g_fcyc_per_samp dusecs_per_samp = g_fcyc_per_samp / 65536.0; dsamp_inc = (dword64)((dmul * dusecs_per_samp / denv_period)); // Amount to inc per sample, fixed point, 40 bit frac reg13 = ay8913ptr->regs[13]; // "reg15", env ctrl eff_ampl = 0; for(i = 0; i < num_samps; i++) { env_dsamp = (env_dsamp + dsamp_inc) & 0x9fffffffffffULL; env_val = (env_dsamp >> 40) & 0xff; eff_ampl = env_val & 0xf; if((reg13 & 4) == 0) { eff_ampl = 15 - eff_ampl; // not attack } if((reg13 & 8) && (reg13 & 2)) { // continue and alternate if(env_val & 0x10) { eff_ampl = 15 - eff_ampl; } } if(((reg13 & 8) == 0) && (env_val >= 0x10)) { eff_ampl = 0; ampl = 0; // Turn off envelope env_dsamp |= (0x80ULL << 40); } else if((reg13 & 1) && (env_val >= 0x10)) { eff_ampl = ((reg13 >> 1) ^ (reg13 >> 2)) & 1; eff_ampl = eff_ampl * 15; ampl = eff_ampl; // Turn off envelope env_dsamp |= (0x80ULL << 40); } *env_ptr++ = vol_ptr[eff_ampl & 0xf]; } ay8913ptr->env_dsamp = env_dsamp; } void sound_mock_noise(int pair, byte *noise_ptr, int num_samps) { Ay8913 *ay8913ptr; word32 ampl, mix, noise_val, noise_samp, noise_period, xor, samp_inc; int doit; int i; if(num_samps > MAX_MOCK_ENV_SAMPLES) { halt_printf("noise overflow!: %d\n", num_samps); return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); doit = 0; for(i = 0; i < 3; i++) { ampl = ay8913ptr->regs[8 + i]; mix = ay8913ptr->regs[7] >> i; if((ampl != 0) && ((mix & 8) == 0)) { doit = 1; break; } } if(!doit) { // No channel looks at noise, don't bother return; } noise_val = ay8913ptr->noise_val; noise_samp = ay8913ptr->noise_samp; noise_period = (ay8913ptr->regs[6] & 0x1f); noise_period = noise_period << 16; samp_inc = (word32)(g_fcyc_per_samp / 16.0); // Amount to inc per sample if(noise_samp >= noise_period) { // Period changed during sound, reset noise_samp = noise_period; } for(i = 0; i < num_samps; i++) { noise_samp += samp_inc; if(noise_samp >= noise_period) { // HACK: handle fraction // 17-bit LFSR, algorithm from MAME:sound/ay8910.cpp // val = val ^ (((val & 1) ^ ((val >> 3) & 1)) << 17) xor = 0; xor = (noise_val ^ (noise_val >> 3)) & 1; noise_val = (noise_val ^ (xor << 17)) >> 1; noise_samp -= noise_period; } noise_ptr[i] = noise_val & 1; } ay8913ptr->noise_samp = noise_samp; ay8913ptr->noise_val = noise_val; } int g_did_mock_print = 100; void sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, byte *noise_ptr, int *vol_ptr, int num_samps) { Ay8913 *ay8913ptr; word32 ampl, mix, tone_samp, tone_period, toggle_tone; word32 samp_inc, noise_val; int out, ival, do_print; int i; if((g_mockingboard.disable_mask >> ((pair * 3) + channel)) & 1) { // This channel is disabled return; } ay8913ptr = &(g_mockingboard.pair[pair].ay8913); ampl = ay8913ptr->regs[8 + channel] & 0x1f; if(ampl == 0) { return; } toggle_tone = ay8913ptr->toggle_tone[channel]; // 0 or 1 mix = (ay8913ptr->regs[7] >> channel) & 9; if(mix == 9) { // constant tone: output will be ampl for this channel. if(ampl & 0x10) { // Envelope! // The envelope can make the tone, so must calculate it } else { // HACK: do nothing for now return; } } outptr += pair; // pair[1] is right tone_samp = ay8913ptr->tone_samp[channel]; tone_period = ay8913ptr->regs[2*channel] + (256 * ay8913ptr->regs[2*channel + 1]); tone_period = tone_period << 16; samp_inc = (word32)(g_fcyc_per_samp / 8.0); // Amount to inc per sample do_print = 0; if(g_mockingboard.disable_mask) { printf("Doing %d samps, mix:%d, ampl:%02x\n", num_samps, mix, ampl); do_print = 1; g_did_mock_print = 0; } if((num_samps > 500) && (g_did_mock_print == 0)) { do_print = 1; g_did_mock_print = 1; printf("Start of %d sample, channel %d mix:%02x ampl:%02x " "toggle_tone:%02x\n", num_samps, channel, mix, ampl, toggle_tone); printf(" tone_period:%08x, tone_samp:%08x, samp_inc:%08x\n", tone_period, tone_samp, samp_inc); } if(tone_samp >= tone_period) { // Period changed during sound, reset it tone_samp = tone_period; } for(i = 0; i < num_samps; i++) { tone_samp += samp_inc; if(tone_samp >= tone_period) { // HACK: handle toggling mid-sample... toggle_tone ^= 1; if(do_print) { printf("i:%d tone_toggled to %d, tone_period:" "%04x, pre tone_samp:%08x\n", i, toggle_tone, tone_period, tone_samp); } tone_samp -= tone_period; if(do_print) { printf("post tone_samp:%08x\n", tone_samp); } } noise_val = noise_ptr[i] & 1; out = (toggle_tone || (mix & 1)) & ((noise_val & 1) || (mix & 8)); // Careful mix of || and & above... ival = vol_ptr[ampl & 0xf]; if(ampl & 0x10) { // Envelope ival = env_ptr[i]; } *outptr += ival*out; outptr += 2; } ay8913ptr->tone_samp[channel] = tone_samp; ay8913ptr->toggle_tone[channel] = toggle_tone; } word32 sound_read_c030(dword64 dfcyc) { sound_write_c030(dfcyc); return float_bus(dfcyc); } void sound_write_c030(dword64 dfcyc) { int num; num = g_num_c030_fsamps; if(num >= MAX_C030_TIMES) { halt_printf("Too many clicks per vbl: %d\n", num); return; } g_c030_fsamps[num] = (float)(dfcyc * g_dsamps_per_dfcyc - g_last_sound_play_dsamp); g_num_c030_fsamps = num + 1; dbg_log_info(dfcyc, num, 0, 0xc030); doc_printf("touch c030, num this vbl: %04x\n", num); } ================================================ FILE: upstream/kegs/src/sound.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_sound_h[] = "@(#)$KmKId: sound.h,v 1.31 2023-05-04 19:35:29+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #if !defined(_WIN32) && !defined(__CYGWIN__) # include # include #endif #define SOUND_SHM_SAMP_SIZE (32*1024) #define SAMPLE_SIZE 2 #define NUM_CHANNELS 2 #define SAMPLE_CHAN_SIZE (SAMPLE_SIZE * NUM_CHANNELS) STRUCT(Doc_reg) { double dsamp_ev; double dsamp_ev2; double complete_dsamp; int samps_left; word32 cur_acc; word32 cur_inc; word32 cur_start; word32 cur_end; word32 cur_mask; int size_bytes; int event; int running; int has_irq_pending; word32 freq; word32 vol; word32 waveptr; word32 ctl; word32 wavesize; word32 last_samp_val; }; // Mockingboard contains two pairs. Each pair is a 6522 interfacing // to an AY-8913 to generate sounds. Eacho AY-8913 contains 3 channels of // sound. Model each pair separately. STRUCT(Mos6522) { byte orb; byte ora; byte ddrb; byte ddra; word32 timer1_latch; word32 timer1_counter; word32 timer2_latch; word32 timer2_counter; byte sr; byte acr; byte pcr; byte ifr; byte ier; }; STRUCT(Ay8913) { byte regs[16]; byte reg_addr_latch; byte toggle_tone[3]; // Channel A,B,C: 0 = low, 1 = high word32 tone_samp[3]; word32 noise_val; word32 noise_samp; dword64 env_dsamp; }; STRUCT(Mock_pair) { Mos6522 mos6522; Ay8913 ay8913; }; STRUCT(Mockingboard) { Mock_pair pair[2]; word32 disable_mask; }; /* prototypes for win32snd_driver.c functions */ void win32snd_init(word32 *); void win32snd_shutdown(void); void child_sound_init_win32(void); int win32_send_audio(byte *ptr, int size); /* Prototypes for macsnd_driver.c functions */ int mac_send_audio(byte *ptr, int in_size); void macsnd_init(); /* Prototypes for pulseaudio_driver.c functions */ int pulse_audio_init(); int pulse_audio_send_audio(byte *ptr, int in_size); void pulse_audio_shutdown(void); ================================================ FILE: upstream/kegs/src/sound_driver.c ================================================ const char rcsid_sound_driver_c[] = "@(#)$KmKId: sound_driver.c,v 1.32 2023-06-05 18:41:09+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Routines for managing sending sound samples to the hardware. The // primary routines are snddrv_init() for initializing the sound hardware, // and snddrv_send_sound() which calls the driver for the sound hardware // in use to play samples. // Linux forks a child process to manage /dev/dsp (so KEGS will not block) // so the lowerlevel routines for all sound hardware start with child_(). #include "defc.h" #include "sound.h" #if defined(__linux__) || defined(OSS) # include #endif #ifndef _WIN32 # include # include #endif #include #if defined(_WIN32) || defined(__CYGWIN__) || defined(MAC) # define KEGS_CAN_FORK 0 #else // Linux, or other Unix, we may fork and run sound in the child # define KEGS_CAN_FORK 1 #endif extern int Verbose; extern int g_audio_rate; extern int g_audio_enable; extern double g_last_sound_play_dsamp; extern word32 *g_sound_shm_addr; int g_preferred_rate = 48000; int g_audio_socket = -1; int g_bytes_written = 0; int g_pulse_audio = 0; int g_pipe_fd[2] = { -1, -1 }; int g_pipe2_fd[2] = { -1, -1 }; #define ZERO_BUF_SIZE 2048 word32 g_snd_zero_buf[ZERO_BUF_SIZE]; #define ZERO_PAUSE_SAFETY_SAMPS (g_audio_rate >> 5) #define ZERO_PAUSE_NUM_SAMPS (4*g_audio_rate) int g_zeroes_buffered = 0; int g_zeroes_seen = 0; int g_sound_paused = 0; int g_childsnd_vbl = 0; int g_childsnd_pos = 0; int child_sound_init_linux(void); void snddrv_init() { word32 *shmaddr; int size, ret, use_shm; ret = 0; if(ret) { // Avoid unused var warning } g_zeroes_buffered = 0; g_zeroes_seen = 0; g_sound_paused = 0; g_childsnd_pos = 0; g_childsnd_vbl = 0; if(g_audio_enable == 0) { sound_set_audio_rate(g_preferred_rate); return; } printf("snddrv_init, g_audio_enable:%d\n", g_audio_enable); size = SOUND_SHM_SAMP_SIZE * SAMPLE_CHAN_SIZE; use_shm = KEGS_CAN_FORK; #ifdef PULSE_AUDIO use_shm = 0; #endif if(!use_shm) { /* windows and mac, and Linux Pulse Audio */ shmaddr = malloc(size); memset(shmaddr, 0, size); g_sound_shm_addr = shmaddr; #ifdef MAC macsnd_init(); return; #endif #ifdef _WIN32 win32snd_init(shmaddr); return; #endif #ifdef PULSE_AUDIO ret = pulse_audio_init(shmaddr); if(ret == 0) { g_pulse_audio = 1; return; // Success! } free(shmaddr); g_sound_shm_addr = 0; use_shm = 1; // Otherwise, fall back on /dev/dsp #endif } if(use_shm) { sound_child_fork(size); } } void sound_child_fork(int size) { #if KEGS_CAN_FORK word32 *shmaddr; int shmid, pid, tmp, ret; int i; doc_printf("In sound_child_fork, size:%d\n", size); shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); if(shmid < 0) { printf("sound_init: shmget ret: %d, errno: %d\n", shmid, errno); exit(2); } shmaddr = shmat(shmid, 0, 0); tmp = (int)PTR2WORD(shmaddr); if(tmp == -1) { printf("sound_init: shmat ret: %p, errno: %d\n", shmaddr, errno); exit(3); } ret = shmctl(shmid, IPC_RMID, 0); if(ret < 0) { printf("sound_init: shmctl ret: %d, errno: %d\n", ret, errno); exit(4); } g_sound_shm_addr = shmaddr; printf("shmaddr: %p\n", shmaddr); fflush(stdout); /* prepare pipe so parent can signal child each other */ /* pipe[0] = read side, pipe[1] = write end */ ret = pipe(&g_pipe_fd[0]); if(ret < 0) { printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); exit(5); } ret = pipe(&g_pipe2_fd[0]); if(ret < 0) { printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); exit(5); } doc_printf("pipes: pipe_fd = %d, %d pipe2_fd: %d,%d\n", g_pipe_fd[0], g_pipe_fd[1], g_pipe2_fd[0], g_pipe2_fd[1]); fflush(stdout); pid = fork(); switch(pid) { case 0: /* child */ /* close stdin and write-side of pipe */ close(0); /* Close other fds to make sure X window fd is closed */ for(i = 3; i < 100; i++) { if((i != g_pipe_fd[0]) && (i != g_pipe2_fd[1])) { close(i); } } close(g_pipe_fd[1]); /*make sure write pipe closed*/ close(g_pipe2_fd[0]); /*make sure read pipe closed*/ child_sound_loop(g_pipe_fd[0], g_pipe2_fd[1], g_sound_shm_addr); printf("Child sound loop returned\n"); exit(0); case -1: /* error */ printf("sound_init: fork ret: -1, errno: %d\n", errno); exit(6); default: /* parent */ /* close read-side of pipe1, and the write side of pipe2 */ close(g_pipe_fd[0]); close(g_pipe2_fd[1]); doc_printf("Child is pid: %d\n", pid); } parent_sound_get_sample_rate(g_pipe2_fd[0]); #endif if(size) { // Avoid unused param warning } } void parent_sound_get_sample_rate(int read_fd) { word32 audio_rate, tmp; int ret; ret = (int)read(read_fd, &audio_rate, 4); if(ret != 4) { printf("parent dying, could not get sample rate from child\n"); printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); exit(1); } ret = (int)read(read_fd, &tmp, 4); if(ret != 4) { printf("parent dying, could not get audio status from child\n"); printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); exit(1); } if(tmp == 0) { g_audio_enable = 0; printf("Failed to init Sound, turning off audio\n"); } close(read_fd); sound_set_audio_rate(audio_rate); } void snddrv_shutdown() { #ifdef _WIN32 win32snd_shutdown(); #else if((g_audio_enable != 0) && (g_pipe_fd[1] >= 0)) { close(g_pipe_fd[1]); } #endif #ifdef PULSE_AUDIO if(g_pulse_audio) { pulse_audio_shutdown(); } #endif } void snddrv_send_sound(int real_samps, int size) { word32 tmp; int ret, call_playit; if(g_audio_enable == 0) { printf("Entered send_sound but audio off!\n"); exit(2); } if(real_samps) { tmp = size + 0xa2000000; } else { tmp = size + 0xa1000000; } //doc_log_rout("send_sound", -1, g_last_sound_play_dsamp, // (real_samps << 30) + size); call_playit = 0; #if defined(MAC) || defined(_WIN32) call_playit = 1; // Never fork child mac/windows #endif if(call_playit || g_pulse_audio) { child_sound_playit(tmp); return; } /* Although this looks like a big/little-endian issue, since the */ /* child is also reading an int, it just works with no byte swap */ ret = (int)write(g_pipe_fd[1], &tmp, 4); if(ret != 4) { halt_printf("send_sound, wr ret: %d, errno: %d\n", ret, errno); } } void child_sound_playit(word32 tmp) { int size; size = tmp & 0xffffff; //printf("child_sound_playit: %08x\n", tmp); if((tmp >> 24) == 0xa2) { // play sound if(g_zeroes_buffered) { reliable_zero_write(g_zeroes_buffered); } g_zeroes_buffered = 0; g_zeroes_seen = 0; if((size + g_childsnd_pos) > SOUND_SHM_SAMP_SIZE) { reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, SOUND_SHM_SAMP_SIZE - g_childsnd_pos); size = (g_childsnd_pos + size) - SOUND_SHM_SAMP_SIZE; g_childsnd_pos = 0; } reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, size); if(g_sound_paused) { printf("Unpausing sound, zb: %d\n", g_zeroes_buffered); g_sound_paused = 0; } } else if((tmp >> 24) == 0xa1) { // play zeroes if(g_sound_paused) { if(g_zeroes_buffered < ZERO_PAUSE_SAFETY_SAMPS) { g_zeroes_buffered += size; } } else { /* not paused, send it through */ g_zeroes_seen += size; reliable_zero_write(size); if(g_zeroes_seen >= ZERO_PAUSE_NUM_SAMPS) { printf("Pausing sound\n"); g_sound_paused = 1; } } } else { printf("tmp received bad: %08x\n", tmp); exit(3); } g_childsnd_pos += size; while(g_childsnd_pos >= SOUND_SHM_SAMP_SIZE) { g_childsnd_pos -= SOUND_SHM_SAMP_SIZE; } g_childsnd_vbl++; if(g_childsnd_vbl >= 60) { g_childsnd_vbl = 0; #if 0 printf("sound bytes written: %06x\n", g_bytes_written); #endif g_bytes_written = 0; } } void reliable_buf_write(word32 *shm_addr, int pos, int size) { byte *ptr; int ret; if(size < 1 || pos < 0 || pos > SOUND_SHM_SAMP_SIZE || size > SOUND_SHM_SAMP_SIZE || (pos + size) > SOUND_SHM_SAMP_SIZE) { printf("reliable_buf_write: pos: %04x, size: %04x\n", pos, size); exit(1); } ptr = (byte *)&(shm_addr[pos]); size = size * 4; while(size > 0) { ret = child_send_samples(ptr, size); if(ret < 0) { printf("audio write, errno: %d %s\n", errno, strerror(errno)); exit(1); } size = size - ret; ptr += ret; g_bytes_written += ret; } } void reliable_zero_write(int amt) { int len; while(amt > 0) { len = MY_MIN(amt, ZERO_BUF_SIZE); reliable_buf_write(g_snd_zero_buf, 0, len); amt -= len; } } int child_send_samples(byte *ptr, int size) { #ifdef _WIN32 return win32_send_audio(ptr, size); #else # ifdef MAC return mac_send_audio(ptr, size); # else # ifdef PULSE_AUDIO if(g_pulse_audio) { return pulse_audio_send_audio(ptr, size); } # endif return (int)write(g_audio_socket, ptr, size); # endif #endif } // child_sound_loop(): used by Linux child process as the main loop, to read // from pipe to get sample info every VBL, and use shm_addr to get samples void child_sound_loop(int read_fd, int write_fd, word32 *shm_addr) { word32 tmp, did_init; int ret, ret1, ret2; doc_printf("Child pipe fd: %d, shm_addr:%p\n", read_fd, shm_addr); g_audio_rate = g_preferred_rate; did_init = 0; #if defined(__linux__) || defined(OSS) did_init = child_sound_init_linux(); #endif tmp = g_audio_rate; ret1 = (int)write(write_fd, &tmp, 4); tmp = did_init; ret2 = (int)write(write_fd, &tmp, 4); if((ret1) != 4 || (ret2 != 4)) { printf("Unable to send back audio rate to parent\n"); printf("ret1: %d,%d fd: %d, errno:%d\n", ret1, ret2, write_fd, errno); exit(1); } doc_printf("Wrote to fd %d the audio rate\n", write_fd); close(write_fd); while(1) { errno = 0; ret = (int)read(read_fd, &tmp, 4); if(ret <= 0) { printf("child dying from ret: %d, errno: %d\n", ret, errno); break; } child_sound_playit(tmp); } close(g_audio_socket); exit(0); } #if defined(__linux__) || defined(OSS) int child_sound_init_linux() { int stereo, sample_size, rate, fmt, ret; g_audio_socket = open("/dev/dsp", O_WRONLY, 0); if(g_audio_socket < 0) { printf("open /dev/dsp failed, ret: %d, errno:%d\n", g_audio_socket, errno); return 0; } #if 0 fragment = 0x00200009; ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFRAGMENT, &fragment); if(ret < 0) { printf("ioctl SETFRAGEMNT failed, ret:%d, errno:%d\n", ret, errno); return 0; } #endif sample_size = 16; ret = ioctl(g_audio_socket, SNDCTL_DSP_SAMPLESIZE, &sample_size); if(ret < 0) { printf("ioctl SNDCTL_DSP_SAMPLESIZE failed, ret:%d, errno:%d\n", ret, errno); return 0; } #ifdef KEGS_BIG_ENDIAN fmt = AFMT_S16_BE; #else fmt = AFMT_S16_LE; #endif ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFMT, &fmt); if(ret < 0) { printf("ioctl SNDCTL_DSP_SETFMT failed, ret:%d, errno:%d\n", ret, errno); return 0; } stereo = 1; ret = ioctl(g_audio_socket, SNDCTL_DSP_STEREO, &stereo); if(ret < 0) { printf("ioctl SNDCTL_DSP_STEREO failed, ret:%d, errno:%d\n", ret, errno); return 0; } rate = g_audio_rate; ret = ioctl(g_audio_socket, SNDCTL_DSP_SPEED, &rate); if(ret < 0) { printf("ioctl SNDCTL_DSP_SPEED failed, ret:%d, errno:%d\n", ret, errno); return 0; } if(ret > 0) { rate = ret; /* rate is returned value */ } if(rate < 8000) { printf("Audio rate of %d which is < 8000!\n", rate); return 0; } g_audio_rate = rate; printf("Sound initialized\n"); return 1; } #endif ================================================ FILE: upstream/kegs/src/style_check ================================================ #!/usr/bin/perl -w # $KmKId: style_check,v 1.1 2020-06-14 02:52:13+00 kentd Exp $ # Perl script to check for coding conventions use English; $some_bad = 0; while($#ARGV >= 0) { $file = shift; $check_spaces = 0; if($file =~ /\.c$/) { $check_spaces = 1; } elsif($file =~ /\.s$/) { $check_spaces = 1; } elsif($file =~ /\.k$/) { $check_spaces = 1; } elsif($file =~ /\.h$/) { $check_spaces = 1; if($file =~ /^protos.*.h$/) { next; # skip global_names.h } if($file =~ /^global_names.h$/) { next; # skip global_names.h } if($file =~ /^knobs.h$/) { next; # skip global_names.h } } else { next; # skip this file } print "Style check: $file\n"; if(-x $file) { print "File mode is executable\n"; $some_bad++; } open(FILE, "<$file") || die "Open: $file: $1\n"; $line_num = 1; foreach $line () { chomp($line); $ign_tab_space = 0; if($line =~ m:^[/\t]+{.*}:) { $ign_tab_space = 1; } $len = 0; $prev = 0; $pprev = 0; $bad = 0; $last_tab = -10; if($check_spaces) { if($line =~ / $/ || $line =~ / $/) { print "Line ends in tab or space\n"; $bad++; } } if($line =~ /\r$/) { # Line ends with Ctrl-M print "Windows linebreak detected\n"; $bad = 101; } while($line =~ m/^([^"]*)("[^"]*")(.*)$/) { # Convert text in strings to '-' to avoid space checks $prev = $1; $post = $3; $quot = &dotify($2); $line = $prev . $quot . $post; } while($line =~ m:^(.*)//(.*)$:) { # Convert text in comments to '-' to avoid space checks $prev = $1; $quot = &dotify($2); $line = $prev . "::" . $quot; } @chars = split(//, $line); foreach $char (@chars) { $len++; if(!$check_spaces) { # do nothing } elsif($char eq "\t") { $len = (($len + 7) >> 3) * 8; if($prev eq ' ') { print "Space followed by tab\n"; $bad++; } $last_tab = $len; } elsif($char eq " ") { if($prev eq "\t" && !$ign_tab_space) { print "Tab followed by space\n"; $bad++; } if($prev eq " " && $pprev eq " " && (($len - $last_tab) > 4)) { print "Too many spaces\n"; $bad++; last; } } $pprev = $prev; $prev = $char } if($check_spaces) { if(($len > 80) && ($line_num > 2)) { print "Line more than 80 columns\n"; $bad++; } #print "line $line has len $len\n"; } if($bad) { $some_bad++; print "...at line $line_num in file $file\n"; if($some_bad > 20) { die "Too many style errors\n"; } if($bad >= 100) { next; # Skip to next file } } $line_num++; } } if($some_bad) { die "Style errors\n"; } exit 0; sub dotify { my ($str) = @_; my @chars = (); my @outchars = (); my ($char, $result); @chars = split(//, $str); @outchars = (); foreach $char (@chars) { if($char ne "\t") { $char = "-"; } push(@outchars, $char); } $result = join('', @outchars); #print "Old quote :$str:\n"; #print "New quote :$result:\n"; return $result; } ================================================ FILE: upstream/kegs/src/undeflate.c ================================================ const char rcsid_undeflate_c[] = "@(#)$KmKId: undeflate.c,v 1.20 2023-09-26 00:20:53+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // This file has routines for the undeflate uncompression algorithm for // gzip/zip, and routines for reading .zip files. // Based on https://www.ietf.org/rfc/rfc1951.txt for Deflate algorithm, // and https://www.ietf.org/rfc/rfc1952.txt for gzip file format. // .zip file format from: // https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT #include "defc.h" // FILE *g_outf = 0; #define LENGTH_ENCODED 0xffffff444449ULL // LENGTH_ENCODED encodes the first table of section 3.2.5 for // fixed Huffman: 257-264 = 0 extra bits, length=3-10 (so 8 entries) // then 265-268 = 1 extra bit, length=11... (so 4 entries), etc. // which is encoded in each nibble of this word. Code 285 (entry 29) // has extra_bits=0, indicated by the encoded nibble being 0xf. #define DIST_ENCODED 0xff22222222222224ULL // DIST_ENCODED encodes the second table of section 3.2.5 for the // fixed Huffman table: Codes 0-3 have 0 extra bits, dist=1,2,3,4 // (so 4 entries), codes 4,5 have 1 extra bit, dest=5... (so 2 // entries), etc. 0xf indicates an invalid entry word32 g_undeflate_fixed_len_tab[512+1]; // extrabits << 20 | bits << 16 | len/literal word32 g_undeflate_fixed_dist_tab[32+1]; // extrabits << 20 | bits << 16 | distance word32 g_undeflate_length_tab[32+1]; // extra_bits << 20 | len word32 g_undeflate_dist_tab[32+1]; // extra_bits << 20 | dist word32 g_undeflate_bit_rev[512]; word32 g_undeflate_lencode_positions[19] = { 0x200310, 0x300311, 0x700b12, 0x100, 0x108, 0x107, 0x109, 0x106, 0x10a, 0x105, 0x10b, 0x104, 0x10c, 0x103, 0x10d, 0x102, 0x10e, 0x101, 0x10f }; word32 g_undeflate_lencode_tab[128 + 1]; word32 *g_undeflate_dynamic_tabptr = 0; word32 g_undeflate_dynamic_bits = 0; word32 *g_undeflate_dynamic_dist_tabptr = 0; word32 g_undeflate_dynamic_dist_bits = 0; void * undeflate_realloc(void *ptr, dword64 dsize) { if((size_t)dsize != dsize) { return 0; } return realloc(ptr, (size_t)dsize); } void * undeflate_malloc(dword64 dsize) { if((size_t)dsize != dsize) { return 0; } return malloc((size_t)dsize); } void show_bits(unsigned *llptr, int nl) { int i; fprintf(stdout, "Showing %03x bits entries\n", nl); for(i = 0; i < nl; i++) { fprintf(stdout, "%03x: %03x\n", i, (llptr[i] >> 16) & 0xf); } } void show_huftb(unsigned *tabptr, int bits) { unsigned char seen[512]; word32 entry, code, val; int i, j; printf("Showing hufftab of %d bits\n", bits); for(i = 0; i < 256+32; i++) { seen[i] = 0; } for(i = 0; i < (1 << bits); i++) { entry = tabptr[i]; code = entry & 0xff; if((entry >> 24) & 1) { val = entry & 0xfff0ffffUL; for(j = 0; j < 32; j++) { if(val == g_undeflate_length_tab[j]) { code = 256 + j; break; } } if(code < 256) { printf("entry %08x (%08x) not found, [0]=%08x " "[1]=%08x\n", entry, val, g_undeflate_length_tab[0], g_undeflate_length_tab[1]); code = 256 + 31; } } if(!seen[code]) { printf("code %03x has bits:%d huffcode:%04x\n", code, (entry >> 16) & 0xf, i); seen[code] = 1; } } } void undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start) { word32 pos, repeats, extra_bits; int i; // Initializes g_undeflate_length_tab[] and g_underflate_dist_tab[] // printf("undeflate len_dist_tab repeats:%016llx\n", drepeats); pos = 0; extra_bits = 0; while(pos < 30) { repeats = drepeats & 0xf; drepeats = drepeats >> 4; if(repeats == 0xf) { // Special handling for code=285 (pos=29) extra_bits = 0; start--; // 258, not 259 repeats = 1; } for(i = 0; i < (int)repeats; i++) { tabptr[pos] = start | (extra_bits << 20) | (1 << 24); //printf("Set table[%d]=%08x (%d) i:%d out of %d\n", // pos, tabptr[pos], start, i, repeats); pos++; start += (1 << extra_bits); } extra_bits++; } } void undeflate_init_bit_rev_tab(word32 *tabptr, int num) { word32 pos, val; int i, j; // Initializes g_undeflate_bit_rev[] for(i = 0; i < num; i++) { pos = i; val = 0; for(j = 0; j < 9; j++) { val = (val << 1) | (pos & 1); pos = pos >> 1; } tabptr[i] = val; // printf("Bit reverse[%03x]=%03x\n", i, val); } } word32 undeflate_bit_reverse(word32 val, word32 bits) { word32 new_val, val2, shift; new_val = g_undeflate_bit_rev[val & 0x1ff]; // at most 9 bits shift = 9 - bits; if(bits <= 9) { return new_val >> shift; } else if(bits <= 18) { shift += 9; // bits:10->shift=8, bits:11->shift=7,.. val2 = g_undeflate_bit_rev[(val >> 9) & 0x1ff] >> shift; shift = bits - 9; return (new_val << shift) | val2; } printf("Cannot reverse %08x bits:%d!\n", val, bits); return 0; } word32 undeflate_calc_crc32(byte *bptr, word32 len) { word32 crc, c, xor; int i; // Old version, don't use other than for testing purposes. // Use woz_calc_crc32() instead. // Generate CCITT-32 CRC, with remainder initialized to -1 and return // the complement of the CRC value // This is slow--but it doesn't matter for KEGS, where the images // are generally only 800KB max. crc = (word32)-1; while(len != 0) { c = *bptr++; len--; for(i = 0; i < 8; i++) { xor = 0; if((crc ^ c) & 1) { xor = 0xedb88320UL; } crc = (crc >> 1) ^ xor; c = c >> 1; } } return (~crc); } byte * undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len) { byte *raw_ptr; dword64 raw_dsize, dimage_size; word32 new_image_size; raw_dsize = dsk->raw_dsize; dimage_size = dsk->dimage_size; if(ucptr) { new_image_size = (word32)(ucptr - dsk->raw_data); if(new_image_size < dimage_size) { printf("ucptr moved backwards!\n"); return 0; } if((new_image_size >> 31) != 0) { printf("Output file > 2GB, failing\n"); return 0; } dimage_size = new_image_size; dsk->dimage_size = dimage_size; } if(dimage_size > raw_dsize) { printf("dimage_size %08llx overflowed raw_dsize %08llx\n", dimage_size, raw_dsize); return 0; } if((dimage_size + len) > raw_dsize) { raw_dsize = ((dimage_size + len) * 3ULL) / 2; raw_ptr = undeflate_realloc(dsk->raw_data, raw_dsize); //printf("Did realloc to %08x, new new_data:%p, was %p\n", // raw_size, raw_ptr, dsk->raw_data); if(raw_ptr == 0) { printf("undeflate realloc failed\n"); free(dsk->raw_data); dsk->raw_data = 0; return 0; } dsk->raw_data = raw_ptr; dsk->raw_dsize = raw_dsize; } #if 0 printf("undeflate_ensure_dest_len will ret %p, dsk->raw_data:%p, " "image_size:%08llx, raw_dsize:%08llx\n", dsk->raw_data + dimage_size, dsk->raw_data, dimage_size, raw_dsize); #endif return dsk->raw_data + dimage_size; } void undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, word32 entry) { word32 rev_code, bits, pos, tab_size; int num; int i; if(tabsz_lg2 > 15) { printf("tabsz_lg2: %04x is not supported\n", tabsz_lg2); return; } tab_size = 1 << tabsz_lg2; rev_code = 0; bits = (entry >> 16) & 0xf; rev_code = undeflate_bit_reverse(code, bits); if(rev_code >= tab_size) { printf("rev_code:%04x out of range for entry %08x\n", rev_code, entry); tabptr[tab_size] = 1; return; } num = 1 << (tabsz_lg2 - bits); if(num < 0) { printf("num %d out of range for entry %08x\n", num, entry); tabptr[tab_size] = 1; return; } for(i = 0; i < num; i++) { pos = rev_code | (i << bits); if(tabptr[pos] != 0) { printf("Overwriting old [%04x]=%08x with %08x\n", pos, tabptr[pos], entry); tabptr[tab_size] = 1; } #if 0 if(i >= 0) { printf("Set code tab[%04x]=%08x (code:%04x)\n", pos, entry, save_code); } #endif tabptr[pos] = entry; } } word32 * undeflate_init_fixed_tabs() { word32 *tabptr; int i; tabptr = &(g_undeflate_fixed_len_tab[0]); // Init g_undeflate_fixed_len_tab[] for the fixed Huffman code for(i = 0; i < 513; i++) { tabptr[i] = 0; } // printf("Add fixed_len_tab for literals 0 - 143\n"); for(i = 0; i < 144; i++) { undeflate_add_tab_code(tabptr, 9, 0x30 + i, (8 << 16) | i); } // printf("Add fixed_len_tab for literals 144 - 255\n"); for(i = 144; i < 256; i++) { undeflate_add_tab_code(tabptr, 9, 0x190 + i - 144, (9 << 16) | i); } // printf("Add fixed_len_tab for length codes 256 - 279\n"); for(i = 256; i < 280; i++) { // printf("code: %03x fixed_len_tab[%03x]=%08x\n", i, i - 256, // g_undeflate_length_tab[i - 256]); undeflate_add_tab_code(tabptr, 9, 0 + i - 256, (7 << 16) | g_undeflate_length_tab[i - 256]); } // printf("Add fixed_len_tab for length codes 280 - 287\n"); for(i = 280; i < 288; i++) { undeflate_add_tab_code(tabptr, 9, 0xc0 + i - 280, (8 << 16) | g_undeflate_length_tab[i - 256]); } if(tabptr[512]) { return 0; } // And init g_undeflate_fixed_dist_tab[] tabptr = &(g_undeflate_fixed_dist_tab[0]); for(i = 0; i < 33; i++) { tabptr[i] = 0; } // printf("Add fixed_dist_tab for codes 0 - 29\n"); for(i = 0; i < 30; i++) { undeflate_add_tab_code(tabptr, 5, i, (5 << 16) | g_undeflate_dist_tab[i]); } if(tabptr[32]) { return 0; } return tabptr; } word32 * undeflate_init_tables() { undeflate_init_len_dist_tab(&(g_undeflate_length_tab[0]), LENGTH_ENCODED, 2); // code=257 has length 3, but the first entry is really code=256 // so set 256 to length=2 undeflate_init_len_dist_tab(&(g_undeflate_dist_tab[0]), DIST_ENCODED, 1); undeflate_init_bit_rev_tab(&(g_undeflate_bit_rev[0]), 512); // undeflate_check_bit_reverse(); return undeflate_init_fixed_tabs(); } void undeflate_free_tables() { free(g_undeflate_dynamic_tabptr); g_undeflate_dynamic_tabptr = 0; free(g_undeflate_dynamic_dist_tabptr); g_undeflate_dynamic_dist_tabptr = 0; } void undeflate_check_bit_reverse() { word32 rev, tmp, checked; int i, j, bits; // Check bit-reverse function. Reverse all values from 0-32767 checked = 0; for(bits = 1; bits <= 16; bits++) { // printf("Checking bit reverse bits=%d\n", bits); for(i = 0; i < 65536; i++) { if(i >= (1 << bits)) { break; } tmp = i; rev = 0; for(j = 0; j < bits; j++) { rev = (rev << 1) | (tmp & 1); tmp = tmp >> 1; } tmp = undeflate_bit_reverse(i, bits); if(tmp != rev) { printf("Reverse %04x bits:%d ret:%04x, " "exp:%04x\n", i, bits, tmp, rev); exit(2); } checked++; } } printf("Checked %08x values\n", checked); } word32 * undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, word32 *bl_count_ptr, int max_bits) { word32 next_code[16]; word32 code, tab_size, bits, entry; int i; tab_size = (1 << max_bits); if(max_bits > 15) { printf("max_bits: %d out of range\n", max_bits); return 0; } next_code[0] = 0; bl_count_ptr[0] = 0; // Force number of 0-bit lengths to 0 code = 0; // printf("build_huff_tab, max_bits:%d, tab_size:%08x\n", max_bits, // tab_size); for(i = 1; i <= max_bits; i++) { // printf("bl_count[%d] = %03x\n", i - 1, bl_count_ptr[i-1]); code = (code + bl_count_ptr[i - 1]) << 1; next_code[i] = code; // printf("Set next_code[%d] = %03x\n", i, code); } for(i = 0; i < (int)tab_size; i++) { tabptr[i] = 0; } tabptr[tab_size] = 0; for(i = 0; i < (int)len_size; i++) { entry = entry_ptr[i]; bits = (entry >> 16) & 0xf; //printf("i:%03x, bits:%d, entry:%08x\n", i, bits, entry); if(!bits) { continue; } code = next_code[bits]++; //printf("Set tab code:%03x = %08x\n", code, entry); undeflate_add_tab_code(tabptr, max_bits, code, entry); } // printf("All done, returning tabptr\n"); return tabptr; } word32 * undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base) { word32 code_list[256+32+32+1]; word32 len_codes[19]; word32 bl_count[19], bl_count_dist[16]; word32 *tabptr, *tabptr_dist; byte *cptr_start; word32 bit_pos, val, hlit, hdist, hclen, pos, max_bits, code_pos; word32 total_codes_needed, repeat, mask, entry, bits; word32 max_length_bits, max_distance_bits, extra_bits; int i; // This is compressed compressed huffman lengths. First // get the length codes, then get the actual lengths // Get 14 bits, which always fits in 3 bytes bit_pos = *bit_pos_ptr; cptr_start = cptr; val = (cptr[0] + (cptr[1] << 8) + (cptr[2] << 16)) >> bit_pos; hlit = (val & 0x1f) + 257; // 257 - 288 hdist = ((val >> 5) & 0x1f) + 1; hclen = (val >> 10) & 0xf; #if 0 printf("At +%06x, bit:%d, hlit:%02x hdist:%02x, hclen:%02x\n", (word32)(cptr - cptr_base), bit_pos, hlit, hdist, hclen); #endif if(cptr_base) { // Avoid unused parameter warning } bit_pos += 14; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; for(i = 0; i < 19; i++) { len_codes[i] = 0; bl_count[i] = 0; } hclen += 4; // 19*3 = 57 bits, at most max_bits = 0; for(i = 0; i < (int)hclen; i++) { val = ((cptr[0] + (cptr[1] << 8)) >> bit_pos) & 7; entry = g_undeflate_lencode_positions[i]; entry = entry & (~0xf0000); // clear bits from entry pos = entry & 0x1f; len_codes[pos] = entry | (val << 16); // printf("len_codes[%d]=%08x\n", pos, len_codes[pos]); bl_count[val]++; if(val > max_bits) { max_bits = val; } // printf("Num bits for len code %02x = %d\n", pos, val); bit_pos += 3; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } // Build huffman table tabptr = undeflate_build_huff_tab(&(g_undeflate_lencode_tab[0]), &(len_codes[0]), 19, &(bl_count[0]), max_bits); if(tabptr == 0) { printf("Bad table\n"); return 0; } // Now we've made the table in tabptr. Read the length codes now total_codes_needed = hlit + hdist; // printf("Getting %04x total codes\n", total_codes_needed); code_pos = 0; mask = (1 << max_bits) - 1; if(total_codes_needed > (256+32+32)) { printf("total_codes_needed high: %04x\n", total_codes_needed); return 0; } for(i = 0; i < 16; i++) { bl_count[i] = 0; bl_count_dist[i] = 0; } while(code_pos < total_codes_needed) { pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; pos = pos & mask; entry = tabptr[pos & mask]; #if 0 printf("At +%06x, bit:%d: Raw code: %02x, entry:%08x\n", (word32)(cptr - cptr_base), bit_pos, pos, entry); #endif val = entry & 0x1f; bits = (entry >> 16) & 7; extra_bits = (entry >> 20) & 7; repeat = (entry >> 8) & 0xf; entry = (val << 16); // Set bits bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; #if 0 printf("At +%06x, bit:%d: Raw pos:%04x\n", (word32)(cptr - cptr_base), bit_pos, pos); #endif pos = pos & ((1 << extra_bits) - 1); repeat = repeat + pos; bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(!repeat) { printf("Bad repeat value\n"); return 0; } if(val >= 0x10) { entry = 0; if(val == 0x10) { // Repeat prev entry entry = code_list[code_pos - 1]; if(!code_pos) { printf("Got repeat code 0x10 at 0!\n"); return 0; } } } for(i = 0; i < (int)repeat; i++) { code_list[code_pos] = entry; // printf("Added code_list[%03x] = %08x\n", code_pos, // entry); code_pos++; } } // Fix lengths and literals max_length_bits = 0; for(i = 0; i < (int)hlit; i++) { entry = code_list[i]; bits = (entry >> 16) & 0xf; bl_count[bits]++; if(i >= 256) { entry |= g_undeflate_length_tab[i - 256]; } else { entry |= i; } code_list[i] = entry; if(bits > max_length_bits) { max_length_bits = bits; } } // Fix distances max_distance_bits = 0; for(i = 0; i < (int)hdist; i++) { entry = code_list[i + hlit]; bits = (entry >> 16) & 0xf; bl_count_dist[bits]++; entry |= g_undeflate_dist_tab[i]; code_list[i + hlit] = entry; if(bits > max_distance_bits) { max_distance_bits = bits; } } if(code_pos != total_codes_needed) { printf("Got %03x codes, needed %03x codes\n", code_pos, total_codes_needed); return 0; } // printf("max_length_bits: %d, max_distance_bits: %d\n", // max_length_bits, max_distance_bits); tabptr = g_undeflate_dynamic_tabptr; if(!tabptr) { tabptr = malloc(sizeof(word32)*((1 << 15) + 1)); g_undeflate_dynamic_tabptr = tabptr; // printf("malloc literal table\n"); } g_undeflate_dynamic_bits = max_length_bits; //printf("Building literal/length table, %d entries, %d bits\n", hlit, // max_length_bits); //show_bits(&(code_list[0]), hlit); tabptr = undeflate_build_huff_tab(tabptr, &(code_list[0]), hlit, &(bl_count[0]), max_length_bits); if(tabptr == 0) { printf("Building literal table failed\n"); return 0; } //show_huftb(tabptr, max_length_bits); tabptr_dist = g_undeflate_dynamic_dist_tabptr; if(!tabptr_dist) { tabptr_dist = malloc(sizeof(word32) * ((1 << 15) + 1)); g_undeflate_dynamic_dist_tabptr = tabptr_dist; // printf("malloc dist table\n"); } g_undeflate_dynamic_dist_bits = max_distance_bits; tabptr_dist = undeflate_build_huff_tab(tabptr_dist, &(code_list[hlit]), hdist, &(bl_count_dist[0]), max_distance_bits); if(tabptr_dist == 0) { printf("Building dist table failed\n"); return 0; } // Update *bit_pos_ptr to skip over the table *bit_pos_ptr = bit_pos + (int)(8*(cptr - cptr_start)); return tabptr; } byte * undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, byte *cptr_end) { word32 *lit_tabptr, *dist_tabptr; byte *ucptr, *ucptr_end; word32 bfinal, btype, bit_pos, len, pos, extra_bits, entry, dist_entry; word32 bits, is_len, dist, lit_mask, dist_mask, tmp; int i; bit_pos = *bit_pos_ptr; // printf("At file offset %08x,bit %d cptr[0]:%02x %02x\n", // (word32)(cptr - cptr_base), bit_pos, cptr[0], cptr[1]); bfinal = (cptr[0] >> bit_pos) & 1; bit_pos++; btype = (((cptr[1] << 8) | cptr[0]) >> bit_pos) & 3; bit_pos += 2; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // printf("bfinal:%d, btype:%d\n", bfinal, btype); if(bfinal) { dsk->fd = 0; // Last block } if(btype == 3) { // Reserved: error return 0; } else if(btype == 0) { // uncompressed // Align cptr to next byte bit_pos += 7; cptr += (bit_pos >> 3); *bit_pos_ptr = 0; len = cptr[0] + (cptr[1] << 8); ucptr = undeflate_ensure_dest_len(dsk, 0, len); if(!ucptr) { return 0; } cptr += 4; for(i = 0; i < (int)len; i++) { *ucptr++ = *cptr++; } dsk->dimage_size += len; return cptr; } if(btype == 1) { // Fixed Huffman codes lit_tabptr = &(g_undeflate_fixed_len_tab[0]); dist_tabptr = &(g_undeflate_fixed_dist_tab[0]); lit_mask = 0x1ff; dist_mask = 0x1f; } else { // Dynamic Huffman codes *bit_pos_ptr = bit_pos; lit_tabptr = undeflate_dynamic_table(cptr, bit_pos_ptr, cptr_base); dist_tabptr = g_undeflate_dynamic_dist_tabptr; // printf("dynamic table used %d bits\n", // *bit_pos_ptr - bit_pos); lit_mask = (1 << g_undeflate_dynamic_bits) - 1; dist_mask = (1 << g_undeflate_dynamic_dist_bits) - 1; bit_pos = *bit_pos_ptr; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } if(!lit_tabptr || !dist_tabptr) { printf("Code table failure\n"); return 0; } ucptr = undeflate_ensure_dest_len(dsk, 0, 65536); // Just a guess if(!ucptr) { return 0; } ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; while(cptr < cptr_end) { #if 0 printf("Top of loop, cptr:%p, lit_tabptr:%p, dsk->raw:%p\n", cptr, lit_tabptr, dsk->raw_data); #endif if(ucptr > ucptr_end) { ucptr = undeflate_ensure_dest_len(dsk, ucptr, 65536); ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; // printf("Update ucptr to %p\n", ucptr); if(!ucptr) { return 0; } } pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); pos = pos >> bit_pos; entry = lit_tabptr[pos & lit_mask]; bits = (entry >> 16) & 0xf; is_len = (entry >> 24) & 1; len = entry & 0xffff; #if 0 printf("At offset +%08x bit:%d, huffcode=%04x, is %d bits, " "entry=%08x\n", (int)(cptr - cptr_base), bit_pos, pos & lit_mask, bits, entry); #endif if(bits == 0) { printf("bits=0, %08x bad table\n", lit_mask); return 0; } bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(!is_len) { // Literal // literal byte // printf(" Out +%06x: %02x\n", // (int)(ucptr - dsk->raw_data), len & 0xff); //putc(len, g_outf); *ucptr++ = len; } else { if(len == 2) { // Code=0x100, end block // All done // printf("Got the 0x100 code! All done!\n"); *bit_pos_ptr = bit_pos; dsk->dimage_size = ucptr - dsk->raw_data; // printf("Set dsk->image_size = %08x\n", // dsk->image_size); return cptr; } extra_bits = (entry >> 20) & 7; if(extra_bits) { pos = cptr[0] | (cptr[1] << 8); pos = pos >> bit_pos; pos = pos & ((1 << extra_bits) - 1); len += pos; } #if 0 printf("At offset +%08x, bit:%d got extra_bits=%d, " "len=%08x\n", (int)(cptr - cptr_base), bit_pos, extra_bits, len); #endif bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // Get distance code pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); pos = pos >> bit_pos; #if 0 printf("At offset +%08x, bit:%d raw distance code: " "%02x\n", (int)(cptr - cptr_base), bit_pos, pos & dist_mask); #endif dist_entry = dist_tabptr[pos & dist_mask]; bits = (dist_entry >> 16) & 0xf; if(bits == 0) { printf("bits=0 for dist_entry:%08x %08x\n", dist_entry, pos); } extra_bits = (dist_entry >> 20) & 0xf; dist = dist_entry & 0xffff; //printf("dist_entry:%08x, extra_bits:%d, dist:%05x\n", // dist_entry, extra_bits, dist); bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; if(extra_bits) { pos = (cptr[0] | (cptr[1] << 8) | (cptr[2] << 16)) >> bit_pos; #if 0 printf(" At offset +%08x, bit:%d, raw ex:" "%08x\n", (int)(cptr - cptr_base), bit_pos, pos); #endif tmp = pos & ((1 << extra_bits) - 1); dist += tmp; #if 0 printf("at offset +%08x, got %d extra dist " "for total dist=%d (%05x)\n", (int)(cptr - cptr_base), extra_bits, dist, pos); #endif bit_pos += extra_bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; } //printf("Repeating %d bytes from dist:%05x\n", len, // dist); if(ucptr < (dsk->raw_data + dist)) { printf("Dist out of bounds:%04x %p %p\n", dist, ucptr, dsk->raw_data); return 0; } for(i = 0; i < (int)len; i++) { ucptr[0] = ucptr[0-(int)dist]; #if 0 putc(ucptr[0], g_outf); printf(" Out +%06x: %02x\n", (int)(ucptr - dsk->raw_data), ucptr[0]); #endif ucptr++; } } } printf("Ran out of compressed data, bad gzip file\n"); return 0; } byte * undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size) { word32 *wptr; byte *cptr_base, *cptr_end; word32 flg, xfl, xlen, bit_offset, exp_crc, len, crc; cptr_base = cptr; cptr_end = cptr + compr_size; if((cptr[0] != 0x1f) || (cptr[1] != 0x8b) || (cptr[2] != 0x08)) { printf("Not gzip file, exiting\n"); return 0; } flg = cptr[3]; xfl = cptr[8]; printf("flg:%02x and xflags:%02x\n", flg, xfl); cptr += 10; if(flg & 4) { // FEXTRA set xlen = cptr[0] + (cptr[1] * 256); printf("FEXTRA XLEN is %d, skipping that many bytes\n", xlen); cptr += 2 + xlen; } if(flg & 8) { // FNAME set cptr += strlen((char *)cptr) + 1; } if(flg & 0x10) { // FCOMMENT set cptr += strlen((char *)cptr) + 1; } if(flg & 2) { // FHCRC set cptr += 2; } printf("gzip header was %02x bytes long\n", (int)(cptr - cptr_base)); dsk->raw_dsize = 140*1024; // Just a guess, alloc size dsk->raw_data = undeflate_malloc(dsk->raw_dsize); if(dsk->raw_data == 0) { return 0; } printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); dsk->dimage_size = 0; // Used size wptr = undeflate_init_tables(); if(wptr == 0) { return 0; // Some sort of error, get out } bit_offset = 0; while(cptr < cptr_end) { cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, cptr_end); if(cptr == 0) { // Failed break; } if(dsk->fd == 0) { printf("undeflate_block set fd=0, success\n"); // Done, success! // Check crc if(bit_offset) { cptr++; } if((cptr + 8) > cptr_end) { printf("No CRC or LEN fields at end\n"); break; } exp_crc = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16) | (cptr[3] << 24); len = cptr[4] | (cptr[5] << 8) | (cptr[6] << 16) | (cptr[7] << 24); if(len != dsk->dimage_size) { printf("Len mismatch: exp %08x != %08llx\n", len, dsk->dimage_size); break; } crc = woz_calc_crc32(dsk->raw_data, len, 0); if(crc != exp_crc) { printf("CRC mismatch: %08x != exp %08x\n", crc, exp_crc); break; } // Real success, set raw_dsize dsk->raw_data = undeflate_realloc(dsk->raw_data, dsk->dimage_size); dsk->raw_dsize = dsk->dimage_size; return cptr; } } printf("Failed\n"); // Disk image thread not found, get out free(dsk->raw_data); dsk->fd = -1; dsk->dimage_size = 0; dsk->raw_data = 0; dsk->raw_dsize = 0; return 0; } void undeflate_gzip(Disk *dsk, const char *name_str) { byte *cptr; dword64 compr_dsize, dret; word32 compr_size; int fd; int i; // On success, set dsk->fd=0 and dsk->raw_data,raw_dsize properly. printf("undeflate_gzip on file %s\n", name_str); fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return; } compr_dsize = cfg_get_fd_size(fd); printf("size: %lld\n", compr_dsize); if((compr_dsize >> 31) != 0) { // > 2GB...too big for this code printf("gzip file is too large\n"); dsk->fd = -1; return; } compr_size = (word32)compr_dsize; cptr = malloc(compr_size + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } dret = cfg_read_from_fd(fd, cptr, 0, compr_size); if(dret != compr_size) { compr_size = 0; // Make header searching fail } //g_outf = fopen("out.dbg", "w"); undeflate_gzip_header(dsk, cptr, compr_size); free(cptr); undeflate_free_tables(); } byte * undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size) { word32 *wptr; byte *cptr_base, *cptr_end; word32 bit_offset; cptr_base = cptr; cptr_end = cptr + dcompr_size; dsk->raw_data = undeflate_malloc(dsk->raw_dsize); if(dsk->raw_data == 0) { return 0; } printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); dsk->dimage_size = 0; // Used size wptr = undeflate_init_tables(); if(wptr == 0) { return 0; // Some sort of error, get out } bit_offset = 0; while(cptr < cptr_end) { cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, cptr_end); if(cptr == 0) { // Failed break; } if(dsk->fd == 0) { printf("undeflate_block set fd=0, success\n"); // Done, success! // Check crc if(bit_offset) { cptr++; } // Real success, set raw_dsize dsk->raw_data = undeflate_realloc(dsk->raw_data, dsk->dimage_size); dsk->raw_dsize = dsk->dimage_size; return cptr; } } printf("Failed\n"); // Disk image thread not found, get out free(dsk->raw_data); dsk->fd = -1; dsk->dimage_size = 0; dsk->raw_data = 0; dsk->raw_dsize = 0; return 0; } byte g_zip_local_file_header[] = { 0x50, 0x4b, 0x03, 0x04 }; byte g_zip_central_file_header[] = { 0x50, 0x4b, 0x01, 0x02 }; byte g_zip_end_central_dir_header[] = { 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0 }; byte g_zip64_end_central_dir_locator[] = { 0x50, 0x4b, 0x06, 0x07, 0, 0, 0, 0 }; byte g_zip64_end_central_dir_header[] = { 0x50, 0x4b, 0x06, 0x06 }; extern Cfg_listhdr g_cfg_partitionlist; int undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, dword64 uncompr_dsize, dword64 compr_dsize) { byte buf[64]; byte *cptr, *cptr2; dword64 dret, compr_doffset; word32 compr_method, name_len, extra_len; word32 bit_flags; int ret; int i; // return -1 on failure, >= 0 on success printf("undeflate_zipfile called, fd:%d, offset:%08llx, unc:%lld " "compr:%lld\n", fd, dlocal_header_off, uncompr_dsize, compr_dsize); dret = cfg_read_from_fd(fd, &buf[0], dlocal_header_off, 64); if(dret != 64) { printf("read dret:%08llx != 64\n", dret); return -1; } for(i = 0; i < 4; i++) { if(buf[i] != g_zip_local_file_header[i]) { printf("hdr[%d]=%02x\n", i, buf[i]); return -1; } } if(((uncompr_dsize | compr_dsize) >> 31) != 0) { printf("Size >2GB, not supported\n"); return -1; } bit_flags = cfg_get_le16(&(buf[6])); compr_method = cfg_get_le16(&(buf[8])); // compr_size = cfg_get_le32(&(buf[18])); // Probably 0 // uncompr_size = cfg_get_le32(&(buf[22])); // Probably 0 name_len = cfg_get_le16(&(buf[26])); extra_len = cfg_get_le16(&(buf[28])); // The ZIP file format is annoying, the local header doesn't have // compr_size and uncompr_size generally (if bit_flags bit 3 is set). // Even if it does, it's fine to always use the central directory printf("bit_flags: %04x\n", bit_flags); compr_doffset = dlocal_header_off + 30 + name_len + extra_len; cptr = undeflate_malloc(compr_dsize + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_dsize + i] = 0; } dret = cfg_read_from_fd(fd, cptr, compr_doffset, compr_dsize); if(dret != compr_dsize) { return -1; } dsk->raw_dsize = uncompr_dsize; dsk->dimage_size = uncompr_dsize; ret = -1; if(compr_method == 0) { // Stored, just use cptr dsk->raw_data = cptr; dsk->raw_dsize = uncompr_dsize; dsk->dimage_start = 0; close(fd); dsk->fd = 0; cptr = 0; // So free(cptr) does nothing ret = 0; } else if(compr_method == 8) { // Deflate cptr2 = undeflate_zipfile_blocks(dsk, cptr, compr_dsize); printf("undeflate_zipfile_blocks ret:%p\n", cptr2); if(cptr2 != 0) { ret = 0; } } else { printf("Unknown compr_method:%04x\n", compr_method); } free(cptr); undeflate_free_tables(); return ret; } int undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, int min_size) { int pos, good; int i; // Search for cmp_ptr in the bptr buffer (basically, look for "PKxx" // header strings). pos = size - min_size; good = 0; while(pos >= 0) { good = 1; for(i = 0; i < cmp_len; i++) { if(bptr[pos + i] != cmp_ptr[i]) { good = 0; break; } } if(good) { break; } pos--; } if(!good) { return -1; } return pos; } int undeflate_zipfile_make_list(int fd) { byte buf[1024]; dword64 dret, dsize, dir_doff, dir_dsize, unc_dsize, compr_dsize; dword64 local_dheader, dneg1, dval, doff, dpos, dlen; byte *dirptr, *name_ptr, *bptr, *bptr2; char *str; word32 extra_len, comment_len, ent, entries, part_len, inc; word32 tmp_off, ex_off, this_size, this_id; int pos, good, add_it, need_compr, need_unc, need_dheader; int name_len; int i; dret = cfg_read_from_fd(fd, &buf[0], 0, 64); if(dret != 64) { return 0; // Not a ZIP file } // See if it's a PKZIP file, starting 0x50, 0x4b, 0x03, 0x04 for(i = 0; i < 4; i++) { if(buf[i] != g_zip_local_file_header[i]) { return 0; } } printf("This looks like a .zip file\n"); // Find end of central directory record in last 1024 bytes. If it's // not there, this is too complex of a ZIP file for us, give up dsize = cfg_get_fd_size(fd); for(i = 0; i < 1024; i++) { buf[i] = 0; } dpos = 0; dlen = dsize; if(dsize > 1024) { dpos = dsize - 1024; dlen = 1024; } dret = cfg_read_from_fd(fd, &buf[0], dpos, dlen); if(dret != dlen) { return 0; // Unknown problem } pos = undeflate_zipfile_search(&buf[0], &g_zip_end_central_dir_header[0], 1024, 8, 22); // End of Central Directory is at least 22 bytes if(pos < 0) { printf("Cannot parse this .zip file\n"); return 0; } entries = cfg_get_le16(&(buf[pos + 8])); dir_dsize = cfg_get_le32(&(buf[pos + 12])); dir_doff = cfg_get_le32(&(buf[pos + 16])); #if 0 printf(".zip entries:%04x, dir_dsize:%06llx, dir_doff:%08llx\n", entries, dir_dsize, dir_doff); #endif dneg1 = 0xffffffffULL; if(dir_doff == dneg1) { printf("We must look for the ZIP64 end dir locator\n"); pos = undeflate_zipfile_search(&buf[0], &g_zip64_end_central_dir_locator[0], 1024, 8, 20); if(pos < 0) { printf("Cannot parse this ZIP64 file\n"); return 0; } doff = cfg_get_le64(&(buf[pos + 8])); printf("ZIP64 end of central dir record at 0x%08llx\n", doff); if((doff + 64) > dsize) { printf("End Central Dir record out of bounds\n"); return 0; } // Now read end of central directory record. Just read 64 bytes // It has to be at least 56 bytes, and the locator had to be // after, so it must fit dret = cfg_read_from_fd(fd, &buf[0], doff, 64); if(dret != 64) { return 0; // Unknown problem } pos = undeflate_zipfile_search(&buf[0], &g_zip64_end_central_dir_header[0], 64, 4, 64); if(pos != 0) { printf("ZIP64 end of central dir record not found\n"); return 0; } entries = cfg_get_le32(&(buf[32])); dir_dsize = cfg_get_le64(&(buf[40])); dir_doff = cfg_get_le64(&(buf[48])); } if((entries < 1) || (dir_dsize > dsize) || (dir_dsize > (1L << 20)) || ((dir_doff + dir_dsize) > dsize)) { printf("Malformed zip file\n"); return 0; } dirptr = undeflate_malloc(dir_dsize); dret = cfg_read_from_fd(fd, dirptr, dir_doff, dir_dsize); if(dret != dir_dsize) { printf("Couldn't read central dir\n"); return 0; } part_len = cfg_partition_maybe_add_dotdot(); // part_len is strlen(g_cfg_part_path[]); pos = 0; ent = 0; while(pos < (int)dir_dsize) { #if 0 printf("Working on ent %d at pos %d\n", ent, pos); #endif if(ent >= entries) { break; // all done } good = 1; for(i = 0; i < 4; i++) { if(dirptr[pos + i] != g_zip_central_file_header[i]) { // corrupt index, get out printf("At pos %04x, i:%d bad hdr\n", pos, i); good = 0; break; } } if(!good) { break; } compr_dsize = cfg_get_le32(&dirptr[pos + 20]); unc_dsize = cfg_get_le32(&dirptr[pos + 24]); name_len = cfg_get_le16(&dirptr[pos + 28]); extra_len = cfg_get_le16(&dirptr[pos + 30]); comment_len = cfg_get_le16(&dirptr[pos + 32]); local_dheader = cfg_get_le32(&dirptr[pos + 42]); if((pos + 46UL + name_len) > dir_dsize) { printf("Corrupt entry: pos:%04x, name_len:%04x, " "dir_dsize:%05llx\n", pos, name_len, dir_dsize); break; } need_unc = (unc_dsize == dneg1); need_compr = (compr_dsize == dneg1); need_dheader = (local_dheader == dneg1); // Walk extras to update unc/compr size and file offset, if // the standard fields are 0xffffffff. bptr = &(dirptr[pos + 46 + name_len]); ex_off = 0; add_it = 1; while(ex_off < extra_len) { #if 0 printf("Working on ex_off:%d out of %d\n", ex_off, extra_len); #endif this_id = cfg_get_le16(&bptr[ex_off]); this_size = cfg_get_le16(&bptr[ex_off + 2]); if((this_size + ex_off + pos + 46UL + name_len) > dir_dsize) { printf("Corrupt ZIP64 extra info entry\n"); add_it = 0; break; } ex_off += 4; if(this_id == 0x0001) { tmp_off = 0; bptr2 = &(bptr[ex_off]); while(tmp_off < this_size) { dval = cfg_get_le64(bptr2); #if 0 printf("tmp_off %d of %d, dval:" "%016llx\n", tmp_off, this_size, dval); #endif if(need_compr) { compr_dsize = dval; need_compr = 0; } else if(need_unc) { unc_dsize = dval; need_unc = 0; } else if(need_dheader) { local_dheader = dval; need_dheader = 0; } else { printf("Corrupt ZIP64\n"); add_it = 0; } tmp_off += 8; bptr += 8; } } ex_off += this_size; } if(need_unc || need_compr || need_dheader) { printf("Bad ZIP64 overrides\n"); add_it = 0; } // See if filename is at the proper depth name_ptr = &(dirptr[pos + 46]); if(add_it) { add_it = cfg_partition_name_check(name_ptr, name_len); } //printf("ent:%d name:%s len:%d had add_it:%d, part_len:%d\n", // ent, name_ptr, name_len, add_it, part_len); inc = 46 + name_len + extra_len + comment_len; if(add_it) { // Handle directories either explicitly listed, as // foo/, foo/bar/, foo/bar/1, foo/bar/2 ; or // implied: foo/bar/1 and foo/bar/2 as entries // implies foo/ and foo/bar/ are directories. // Add any name at the current part_len level, but // make sure it's unique (don't add lots of "foo"s). name_ptr += part_len; name_len -= part_len; if(name_len <= 0) { add_it = 0; } for(i = 0; i < name_len; i++) { if(name_ptr[i] == '/') { // This ends this name at this level if(i > 0) { add_it = 2; name_len = i + 1; } else { add_it = 0; } break; } } } if((add_it < 2) && (unc_dsize < 140*1024)) { add_it = 0; } if(add_it) { str = malloc(name_len + 1); cfg_strncpy(str, (char *)&name_ptr[0], name_len + 1); cfg_file_add_dirent_unique(&g_cfg_partitionlist, str, add_it - 1, unc_dsize, local_dheader, compr_dsize, ent); free(str); } pos += inc; ent++; } free(dirptr); printf("Returning %d, pos:%05x, dir_dsize:%05llx\n", ent, pos, dir_dsize); return g_cfg_partitionlist.last; } ================================================ FILE: upstream/kegs/src/unshk.c ================================================ const char rcsid_unshk_c[] = "@(#)$KmKId: unshk.c,v 1.14 2023-05-19 13:57:52+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2021 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // This code is based on the official NuFX documentation in Apple II FTN.e08002 // available at: http://nulib.com/library/FTN.e08002.htm. Andy McFadden has // reverse-engineered GSHK (and its quirks) in Nulib, at: http://nulib.com/, // and that code was very helpful in getting the basic algorithms correct. #include "defc.h" word32 unshk_get_long4(byte *bptr) { word32 val; int i; // Get 4 bytes in little-endian form val = 0; for(i = 3; i >=0 ; i--) { val = (val << 8) | bptr[i]; } return val; } word32 unshk_get_word2(byte *bptr) { // Get 2 bytes in little-endian form return (bptr[1] << 8) | bptr[0]; } word32 unshk_calc_crc(byte *bptr, int size, word32 start_crc) { word32 crc; int i, j; // No table used: do basic CRC operation on size bytes. For CCITT-16 // (the one used in ShrinkIt), xor the byte into the upper 8 bits of // the current crc, then xor in 0x1021 for each '1' bit shifted out // the top. Use a 32-bit crc variable to get the bit shifted out. crc = start_crc & 0xffff; for(i = 0; i < size; i++) { crc = crc ^ (bptr[i] << 8); for(j = 0; j < 8; j++) { crc = crc << 1; if(crc & 0x10000) { crc = crc ^ 0x11021; // XOR in 0x1021, and clear bit 16 as well. } } // printf("CRC after [%04x]=%02x is %04x\n", i, bptr[i], crc); } return crc & 0xffff; } int unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr) { byte *start_ucptr; word32 c; int outlen, count; int i; // RLE is 3 bytes: { 0xdb, char, count}, where count==0 means output // one char. start_ucptr = ucptr; while(len > 0) { c = *cptr++; len--; if(c == rle_delim) { c = *cptr++; count = *cptr++; len -= 2; for(i = 0; i <= count; i++) { *ucptr++ = c; } } else { *ucptr++ = c; } } outlen = (int)(ucptr - start_ucptr); if(outlen != 0x1000) { printf("RLE failed, output %d bytes\n", outlen); return 1; } return 0; } void unshk_lzw_clear(Lzw_state *lzw_ptr) { int i; lzw_ptr->entry = 0x100; // First expected table pos lzw_ptr->bits = 9; for(i = 0; i < 256; i++) { lzw_ptr->table[i] = i << 12; // Encodes depth==0 as well } lzw_ptr->table[0x100] = 0; } // LZW Table format in 32-bit word: { depth[11:0], finalc[7:0], code[11:0] } byte * unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen) { byte *end_ucptr, *bptr; word32 mask, val, entry, newcode, finalc_code; int bit_pos, depth, bits; // This routine handles ShrinkIt LZW/1 and LZW/2 streams. It expects // the caller has set entry=0x100 and bits=9 at the start of each // LZW/1 chunk entry = lzw_ptr->entry; bits = lzw_ptr->bits; end_ucptr = ucptr + uclen; //printf("unlzw block: format:%d, uclen:%04x\n", thread_format, uclen); mask = (1 << bits) - 1; bit_pos = 0; while(ucptr < end_ucptr) { newcode = (cptr[2] << 16) | (cptr[1] << 8) | cptr[0]; newcode = (newcode >> bit_pos) & mask; bit_pos += bits; cptr += (bit_pos >> 3); bit_pos = bit_pos & 7; // printf("At entry:%04x, bits:%d newcode:%04x\n", entry, bits, // newcode); if((entry + 1) >= mask) { bits++; mask = (mask << 1) | 1; // Note this is one too early, but this is needed to // match ShrinkIt } // Newcode is up to 12-bits, where <= 0xff means just this // char, and >= 0x101 means chase down that code, output the // character in that table entry, and repeat if(newcode == 0x100) { // printf("Got clear code\n"); entry = 0x100; bits = 9; mask = 0x1ff; continue; } if(newcode > entry) { printf("Bad code: %04x, entry:%04x\n", newcode, entry); return 0; } #if 0 if(newcode == entry) { // KwKwK case: operate on oldcode printf("KwKwK case!\n"); } #endif finalc_code = newcode; depth = lzw_ptr->table[newcode & 0xfff] >> 20; // depth will be 0 for 1 character, 1 for 2 characters, etc. bptr = ucptr + depth; while(bptr >= ucptr) { finalc_code = lzw_ptr->table[finalc_code & 0xfff]; *bptr-- = (finalc_code >> 12) & 0xff; } val = lzw_ptr->table[entry]; lzw_ptr->table[entry] = (val & (~0xff000)) | (finalc_code & 0xff000); // [entry] has code from last iteration (which stuck in // the last finalc char, which we need to toss now), // and update it with the correct finalc character. // printf("Table[%04x]=%08x\n", entry, lzw_ptr->table[entry]); depth++; ucptr += depth; #if 0 bptr = ucptr - depth; printf("src:%04x, out+%06x: ", (int)(cptr - cptr_start), (int)(ucptr - depth - (end_ucptr - uclen))); for(i = 0; i < depth; i++) { printf(" %02x", *bptr++); } printf("\n"); #endif lzw_ptr->table[entry + 1] = (depth << 20) | (finalc_code & 0xff000) | newcode; // Set tab[entry+1] for KwKwK case, with this newcode, // and this finalc character. This also saves this // newcode when the next code is received. entry++; } lzw_ptr->entry = entry; lzw_ptr->bits = bits; if(bit_pos) { // We used part of this byte, use it cptr++; } return cptr; } void unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, word32 uncompr_size, word32 thread_format, byte *base_cptr) { Lzw_state lzw_state; byte *end_cptr, *end_ucptr, *rle_inptr; word32 rle_delim, len, use_lzw, lzw_len, crc, chunk_crc; int ret; int i; printf("Uncompress %d compress bytes into %d bytes, source offset:" "%08x\n", compr_size, uncompr_size, (word32)(cptr - base_cptr)); // LZW/1 format: crc_lo, crc_hi, vol, rle_delim then start the chunk // each chunk: rle_len_lo, rle_len_hi, lzw_used // LZW/2 format: vol, rle_delim then start the chunk // each chunk: rle_len_lo, rle_len_hi, lzw_len_lo, lzw_len_hi // where rle_len_hi[7]==1 means LZW was used end_cptr = cptr + compr_size; end_ucptr = ucptr + uncompr_size; chunk_crc = 0; if(thread_format != 3) { // LZW/1 chunk_crc = (cptr[1] << 8) | cptr[0]; cptr += 2; // Skip over CRC bytes } dsk->vol_num = cptr[0]; // LZW/1 rle_delim = cptr[1]; cptr += 2; unshk_lzw_clear(&lzw_state); // printf("vol_num:%02x, rle_delim:%02x\n", dsk->vol_num, rle_delim); // LZW/1 format for each chunk: len_lo, len_hi, use_lzw // LZW/2 format for each chunk: len_lo, len_hi. If len_hi[7]=1, then // two more bytes: lzw_len_lo, lzw_len_hi while(cptr < (end_cptr - 4)) { if(ucptr >= end_ucptr) { break; } len = (cptr[1] << 8) | cptr[0]; #if 0 printf("chunk at +%08x, len:%04x, dest offset:%08x\n", (word32)(cptr - base_cptr), len, (word32)(ucptr - (end_ucptr - uncompr_size))); #endif cptr += 2; use_lzw = (len >> 15) & 1; if(len & 0x6000) { printf("Illegal length: %04x\n", len); return; // Ilegal length } len = len & 0x1fff; lzw_len = 0; if(thread_format == 3) { // LZW/2 if(use_lzw) { lzw_len = (cptr[1] << 8) | cptr[0]; if(lzw_len > 0x1004) { printf("Bad lzw_len: %04x\n", lzw_len); return; // Illegal } cptr += 2; lzw_len -= 4; // Counts from [-4] } } else { // LZW/1 use_lzw = *cptr++; if(use_lzw >= 2) { printf("Bad use_lzw:%02x\n", use_lzw); return; // Bad format } } rle_inptr = cptr; if(use_lzw) { //printf("lzw on %02x.%02x.%02x.., %d bytes (rle:%d)\n", // cptr[0], cptr[1], cptr[2], lzw_len, len); rle_inptr = ucptr; if(len != 0x1000) { // RLE pass is needed: Write to ucptr+0x1000, // and then UnRLE down to ucptr; rle_inptr = ucptr + 0x1000; } cptr = unshk_unlzw(cptr, &lzw_state, rle_inptr, len); if(cptr == 0) { printf("Bad LZW stream\n"); return; } if(thread_format != 3) { // LZW/1 lzw_state.entry = 0x100; // Reset table lzw_state.bits = 9; } } else { lzw_state.entry = 0x100; // Reset table lzw_state.bits = 9; } if(len != 0x1000) { // printf("RLE on %02x.%02x.%02x... %d bytes\n", // cptr[0], cptr[1], cptr[2], len); ret = unshk_unrle(rle_inptr, len, rle_delim, ucptr); if(ret) { printf("unRLE failed\n"); return; } if(!use_lzw) { cptr += len; } } else if(!use_lzw) { // Uncompressed // printf("Uncompressed %02x.%02x.%02x....%d bytes\n", // cptr[0], cptr[1], cptr[2], len); for(i = 0; i < 0x1000; i++) { ucptr[i] = *cptr++; } } // write(g_out_fd, ucptr, 0x1000); ucptr += 0x1000; } printf("cptr:%p, end_cptr:%p, uncompr_size:%08x\n", cptr, end_cptr, uncompr_size); if(thread_format != 3) { // LZW/1 crc = unshk_calc_crc(ucptr - uncompr_size, uncompr_size, 0); //printf("LZW/1 calc CRC %04x vs CRC %04x\n", crc, chunk_crc); if(crc != chunk_crc) { printf("Bad LZW/1 CRC: %04x != %04x\n", crc, chunk_crc); return; } } dsk->fd = 0; } void unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr) { byte *cptr_end, *dptr, *ucptr; word32 total_records, attrib_count, total_threads, thread_class; word32 thread_format, thread_kind, thread_eof, comp_thread_eof; word32 thread_crc, crc, version, disk_size, block_size, num_blocks; word32 filename_length; int i; cptr_end = cptr + compr_size; if(compr_size < 0xa0) { printf("Didn't read everything\n"); return; } // Parse NuFX format: "NuFile" with alternating high bits if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || (cptr[3] != 0xe9) || (cptr[4] != 0x6c) || (cptr[5] != 0xe5)) { printf("Not NuFile, exiting\n"); return; } total_records = unshk_get_long4(&cptr[8]); if(total_records < 1) { return; } // Master Header to NuFile is apparently 48 bytes. Look for "NuFX" // Header to describe threads cptr += 0x30; if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || (cptr[3] != 0xd8)) { return; } attrib_count = unshk_get_word2(&cptr[6]); version = unshk_get_word2(&cptr[8]); // >= 3 means File CRC total_threads = unshk_get_long4(&cptr[10]); num_blocks = unshk_get_long4(&cptr[26]); // extra_type block_size = unshk_get_word2(&cptr[30]); // storage_type // P8 ShrinkIt is riddled with bugs. Disk archives have incorrect // thread_eof for the uncompressed total size. So we need to do // num_blocks * block_size to get the real size. But this can be // buggy too! These fixes are from NuFxLib::Thread.c actualThreadEOF // comments // First, fix block_size. SHK v3.0.1 stored it as a small value if(block_size < 256) { block_size = 512; } disk_size = block_size * num_blocks; if(disk_size == (70*1024)) { // Old GSHK apparently set block_size==256 but blocks=280 for // 5.25" DOS 3.3 disks...block size must be 512 to equal 140K. disk_size = 140*1024; } if(disk_size < 140*1024) { printf("disk_size %dK is invalid\n", disk_size >> 10); return; } cptr += attrib_count; filename_length = unshk_get_word2(&cptr[-2]); // filename_length cptr += filename_length; dptr = cptr + 16*total_threads; // Each thread is 16 bytes, so the data is at +16*total_threads // The data is in the same order as the header for the threads // We ignore anything other than a data thread for SDK for(i = 0; i < (int)total_threads; i++) { if((dptr >= cptr_end) || (cptr >= cptr_end)) { return; } thread_class = unshk_get_word2(&cptr[0]); thread_format = unshk_get_word2(&cptr[2]); thread_kind = unshk_get_word2(&cptr[4]); thread_crc = unshk_get_word2(&cptr[6]); //thread_eof = unshk_get_long4(&cptr[8]); // thread_eof is wrong in P8 ShrinkIt, so just use disk_size thread_eof = disk_size; comp_thread_eof = unshk_get_long4(&cptr[12]); if((dptr + comp_thread_eof) > cptr_end) { return; // Corrupt } if((thread_class == 2) && (thread_kind == 1)) { // Disk image! ucptr = malloc(thread_eof + 0x1000); unshk_data(dsk, dptr, comp_thread_eof, ucptr, thread_eof, thread_format, base_cptr); if(dsk->fd == 0) { // Success, so far. Check CRC printf("Version:%d, thread_crc:%04x\n", version, thread_crc); if(version >= 3) { // CRC is valid crc = unshk_calc_crc(ucptr, thread_eof, 0xffff); #if 0 printf("Thread CRC:%04x, exp:%04x\n", crc, thread_crc); #endif if(crc != thread_crc) { printf("Bad CRC: %04x != exp " "%04x\n", crc, thread_crc); dsk->fd = -1; } } } if(dsk->fd < 0) { free(ucptr); } else { // Real success, set raw_size dsk->raw_dsize = thread_eof; dsk->raw_data = ucptr; return; } } else { dptr += comp_thread_eof; cptr += 16; } } // Disk image thread not found, get out } void unshk(Disk *dsk, const char *name_str) { byte *cptr; int compr_size, fd, pos, ret; int i; printf("unshk %s\n", name_str); // Handle .sdk inside a .zip if(dsk->raw_data) { unshk_dsk_raw_data(dsk); return; } // File is not opened yet, try to open it fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); if(fd < 0) { return; } compr_size = (int)cfg_get_fd_size(fd); printf("size: %d\n", compr_size); cptr = malloc(compr_size + 0x1000); pos = 0; for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } while(1) { if(pos >= compr_size) { break; } ret = read(fd, cptr + pos, compr_size - pos); if(ret <= 0) { break; } pos += ret; } close(fd); if(pos != compr_size) { compr_size = 0; // Make header searching fail } unshk_parse_header(dsk, cptr, compr_size, cptr); free(cptr); } void unshk_dsk_raw_data(Disk *dsk) { byte *save_raw_data, *cptr; dword64 save_raw_dsize; int save_fd, compr_size; int i; // This code handles the case of .sdk inside a .zip (for example). // Since unshk() code uses dsk->fd, dsk->raw_data, and dsk->raw_dsize // to communicate success in unshk'ing the disk, we need to copy // those, and restore them, if the unshk fails save_fd = dsk->fd; save_raw_data = dsk->raw_data; save_raw_dsize = dsk->raw_dsize; if(save_raw_dsize >= (1ULL << 30)) { return; // Too large } dsk->fd = -1; dsk->raw_data = 0; dsk->raw_dsize = 0; compr_size = (int)save_raw_dsize; cptr = malloc(compr_size + 0x1000); for(i = 0; i < 0x1000; i++) { cptr[compr_size + i] = 0; } for(i = 0; i < compr_size; i++) { cptr[i] = save_raw_data[i]; } unshk_parse_header(dsk, cptr, compr_size, cptr); free(cptr); if(dsk->raw_data) { // Success, free the old raw data free(save_raw_data); return; } dsk->fd = save_fd; dsk->raw_data = save_raw_data; dsk->raw_dsize = save_raw_dsize; } ================================================ FILE: upstream/kegs/src/vars ================================================ TARGET = kegsmac OBJECTS1 = macsnd_driver.o CCOPTS = -Wall -O2 -DMAC SUFFIX = NAME = kegsmac XOPTS = ================================================ FILE: upstream/kegs/src/vars_mac ================================================ TARGET = kegsmac OBJECTS1 = macsnd_driver.o CCOPTS = -Wall -O2 -DMAC SUFFIX = NAME = kegsmac XOPTS = ================================================ FILE: upstream/kegs/src/vars_mac_x ================================================ TARGET = xkegs OBJECTS1 = macsnd_driver.o xdriver.o CCOPTS = -O2 -DMAC -Wall -I/usr/X11/include SUFFIX = NAME = xkegs XOPTS = LDOPTS = -Wl,-framework,CoreAudio -Wl,-framework,CoreFoundation -Wl,-framework,AudioToolbox ================================================ FILE: upstream/kegs/src/vars_x86linux ================================================ TARGET = xkegs OBJECTS1 = pulseaudio_driver.o xdriver.o CCOPTS = -O2 -Wall -fomit-frame-pointer -DPULSE_AUDIO NAME = xkegs LD = $(CC) EXTRA_LIBS = -lXext -lpulse EXTRA_SPECIALS = XOPTS = -I/usr/X11R6/include ================================================ FILE: upstream/kegs/src/video.c ================================================ const char rcsid_video_c[] = "@(#)$KmKId: video.c,v 1.213 2025-04-27 18:03:43+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ #include #include "defc.h" extern int Verbose; word32 g_a2_filt_stat[200]; int g_a2_line_left_edge[200]; int g_a2_line_right_edge[200]; byte g_cur_border_colors[270]; word32 g_a2_screen_buffer_changed = (word32)-1; word32 g_full_refresh_needed = (word32)-1; word32 g_cycs_in_40col = 0; word32 g_cycs_in_xredraw = 0; word32 g_refresh_bytes_xfer = 0; extern byte *g_slow_memory_ptr; extern int g_fatal_log; extern dword64 g_cur_dfcyc; extern int g_line_ref_amt; extern word32 g_c034_val; extern int g_config_control_panel; extern int g_halt_sim; word32 g_slow_mem_changed[SLOW_MEM_CH_SIZE]; word32 g_slow_mem_ch2[SLOW_MEM_CH_SIZE]; word32 g_a2font_bits[0x100][8]; word32 g_superhires_scan_save[2][256]; Kimage g_mainwin_kimage = { 0 }; Kimage g_debugwin_kimage = { 0 }; int g_debugwin_last_total = 0; extern int g_debug_lines_total; extern dword64 g_last_vbl_dfcyc; extern dword64 g_video_pixel_dcount; dword64 g_video_dfcyc_check_input = 0; int g_video_act_margin_left = BASE_MARGIN_LEFT; int g_video_act_margin_right = BASE_MARGIN_RIGHT; int g_video_act_margin_top = BASE_MARGIN_TOP; int g_video_act_margin_bottom = BASE_MARGIN_BOTTOM; int g_video_act_width = X_A2_WINDOW_WIDTH; int g_video_act_height = X_A2_WINDOW_HEIGHT; int g_mainwin_width = X_A2_WINDOW_WIDTH; int g_mainwin_height = X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2; int g_mainwin_xpos = 100; int g_mainwin_ypos = 300; int g_video_no_scale_window = 0; word32 g_palette_change_cnt[2][16]; int g_border_sides_refresh_needed = 1; int g_border_special_refresh_needed = 1; int g_border_line24_refresh_needed = 1; int g_status_refresh_needed = 1; int g_vbl_border_color = 0; int g_border_last_vbl_changes = 0; int g_border_reparse = 0; int g_use_dhr140 = 0; int g_use_bw_hires = 0; int g_vid_update_last_line = 0; int g_video_save_all_stat_pos = 0; int g_cur_a2_stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | (0xf << BIT_ALL_STAT_TEXT_COLOR); word32 g_palette_8to1624[2][256]; word32 g_a2palette_1624[16]; word32 g_saved_line_palettes[2][200][8]; word32 g_cycs_in_refresh_line = 0; word32 g_cycs_in_refresh_ximage = 0; word32 g_cycs_in_run_16ms = 0; int g_num_lines_superhires = 0; int g_num_lines_superhires640 = 0; int g_num_lines_prev_superhires = 0; int g_num_lines_prev_superhires640 = 0; int g_screen_redraw_skip_count = 0; int g_screen_redraw_skip_amt = -1; word32 g_alpha_mask = 0; word32 g_red_mask = 0xff; word32 g_green_mask = 0xff; word32 g_blue_mask = 0xff; int g_red_left_shift = 16; int g_green_left_shift = 8; int g_blue_left_shift = 0; int g_red_right_shift = 0; int g_green_right_shift = 0; int g_blue_right_shift = 0; int g_status_enable = 1; int g_status_enable_previous = 1; char g_status_buf[MAX_STATUS_LINES][STATUS_LINE_LENGTH + 1]; char *g_status_ptrs[MAX_STATUS_LINES] = { 0 }; word16 g_pixels_widened[128]; int g_video_scale_algorithm = 0; STRUCT(Video_all_stat) { word32 lines_since_vbl; word32 cur_all_stat; }; #define MAX_VIDEO_ALL_STAT ((200*42) + 40) int g_video_all_stat_pos = 0; Video_all_stat g_video_all_stat[MAX_VIDEO_ALL_STAT]; STRUCT(Video_filt_stat) { word32 line_bytes; word32 filt_stat; }; #define MAX_VIDEO_FILT_STAT 10000 int g_video_filt_stat_pos = 0; Video_filt_stat g_video_filt_stat[MAX_VIDEO_FILT_STAT]; int g_video_stat_old_pos = 0; Video_filt_stat g_video_filt_stat_old[MAX_VIDEO_FILT_STAT]; word16 g_dhires_convert[4096]; /* look up { next4, this4, prev 4 } */ const byte g_dhires_colors_16[] = { // Convert dhires to lores color 0x00, /* 0x0 black */ 0x02, /* 0x1 dark blue */ 0x04, /* 0x2 dark green */ 0x06, /* 0x3 medium blue */ 0x08, /* 0x4 brown */ 0x0a, /* 0x5 light gray */ 0x0c, /* 0x6 green */ 0x0e, /* 0x7 aquamarine */ 0x01, /* 0x8 deep red */ 0x03, /* 0x9 purple */ 0x05, /* 0xa dark gray */ 0x07, /* 0xb light blue */ 0x09, /* 0xc orange */ 0x0b, /* 0xd pink */ 0x0d, /* 0xe yellow */ 0x0f /* 0xf white */ }; const int g_lores_colors[] = { // From IIgs Technote #63 /* rgb */ 0x000, /* 0x0 black */ 0xd03, /* 0x1 deep red */ 0x009, /* 0x2 dark blue */ 0xd2d, /* 0x3 purple */ 0x072, /* 0x4 dark green */ 0x555, /* 0x5 dark gray */ 0x22f, /* 0x6 medium blue */ 0x6af, /* 0x7 light blue */ 0x850, /* 0x8 brown */ 0xf60, /* 0x9 orange */ 0xaaa, /* 0xa light gray */ 0xf98, /* 0xb pink */ 0x1d0, /* 0xc green */ 0xff0, /* 0xd yellow */ 0x4f9, /* 0xe aquamarine */ 0xfff /* 0xf white */ }; const byte g_hires_lookup[64] = { // Indexed by { next_bit, this_bit, prev_bit, hibit, odd_byte, odd_col }. // Return lores colors: 0, 3, 6, 9, 0xc, 0xf 0x00, // 00,0000 // black: this and next are 0 0x00, // 00,0001 0x00, // 00,0010 0x00, // 00,0011 0x00, // 00,0100 0x00, // 00,0101 0x00, // 00,0110 0x00, // 00,0111 0x00, // 00,1000 0x00, // 00,1001 0x00, // 00,1010 0x00, // 00,1011 0x00, // 00,1100 0x00, // 00,1101 0x00, // 00,1110 0x00, // 00,1111 0x03, // 01,0000 // purple 0x03, // 01,0001 // purple 0x0c, // 01,0010 // green (odd column) 0x0c, // 01,0011 // green 0x06, // 01,0100 // blue 0x06, // 01,0101 // blue 0x09, // 01,0110 // orange 0x09, // 01,0111 // orange 0x0f, // 01,1000 // white: this and prev are 1 0x0f, // 01,1001 0x0f, // 01,1010 0x0f, // 01,1011 0x0f, // 01,1100 0x0f, // 01,1101 0x0f, // 01,1110 0x0f, // 01,1111 0x00, // 10,0000 // black 0x00, // 10,0001 // black 0x00, // 10,0010 // black 0x00, // 10,0011 // black 0x00, // 10,0100 // black 0x00, // 10,0101 // black 0x00, // 10,0110 // black 0x00, // 10,0111 // black 0x0c, // 10,1000 // green 0x0c, // 10,1001 // green 0x03, // 10,1010 // purple 0x03, // 10,1011 // purple 0x09, // 10,1100 // orange 0x09, // 10,1101 // orange 0x06, // 10,1110 // blue 0x06, // 10,1111 // blue 0x0f, // 11,0000 // white 0x0f, // 11,0001 0x0f, // 11,0010 0x0f, // 11,0011 0x0f, // 11,0100 0x0f, // 11,0101 0x0f, // 11,0110 0x0f, // 11,0111 0x0f, // 11,1000 0x0f, // 11,1001 0x0f, // 11,1010 0x0f, // 11,1011 0x0f, // 11,1100 0x0f, // 11,1101 0x0f, // 11,1110 0x0f // 11,1111 }; const int g_screen_index[] = { 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x028, 0x0a8, 0x128, 0x1a8, 0x228, 0x2a8, 0x328, 0x3a8, 0x050, 0x0d0, 0x150, 0x1d0, 0x250, 0x2d0, 0x350, 0x3d0, 0x078, 0x0f8, 0x178, 0x1f8, 0x278, 0x2f8, 0x378, 0x3f8 // Last row is for float_bus() during VBL }; byte g_font_array[256][8] = { #include "kegsfont.h" }; void video_set_red_mask(word32 red_mask) { video_set_mask_and_shift(red_mask, &g_red_mask, &g_red_left_shift, &g_red_right_shift); } void video_set_green_mask(word32 green_mask) { video_set_mask_and_shift(green_mask, &g_green_mask, &g_green_left_shift, &g_green_right_shift); } void video_set_blue_mask(word32 blue_mask) { video_set_mask_and_shift(blue_mask, &g_blue_mask, &g_blue_left_shift, &g_blue_right_shift); } void video_set_alpha_mask(word32 alpha_mask) { g_alpha_mask = alpha_mask; printf("Set g_alpha_mask=%08x\n", alpha_mask); } void video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, int *shift_right_ptr) { int shift; int i; /* Shift until we find first set bit in mask, then remember mask,shift*/ shift = 0; for(i = 0; i < 32; i++) { if(x_mask & 1) { /* we're done! */ break; } x_mask = x_mask >> 1; shift++; } *mask_ptr = x_mask; *shift_left_ptr = shift; /* Now, calculate shift_right_ptr */ shift = 0; x_mask |= 1; // make sure at least one bit is set for(i = 0; i < 32; i++) { if(x_mask >= 0x80) { break; } shift++; x_mask = x_mask << 1; } *shift_right_ptr = shift; } void video_set_palette() { int i; for(i = 0; i < 16; i++) { video_update_color_raw(0, i, g_lores_colors[i]); g_a2palette_1624[i] = g_palette_8to1624[0][i]; } } void video_set_redraw_skip_amt(int amt) { if(g_screen_redraw_skip_amt < amt) { g_screen_redraw_skip_amt = amt; printf("Set g_screen_redraw_skip_amt = %d\n", amt); } } Kimage * video_get_kimage(int win_id) { if(win_id == 0) { return &g_mainwin_kimage; } if(win_id == 1) { return &g_debugwin_kimage; } printf("win_id: %d not supported\n", win_id); exit(1); } char * video_get_status_ptr(int line) { if(line < MAX_STATUS_LINES) { return g_status_ptrs[line]; } return 0; } #if 0 int video_get_x_refresh_needed(Kimage *kimage_ptr) { int ret; ret = kimage_ptr->x_refresh_needed; kimage_ptr->x_refresh_needed = 0; return ret; } #endif void video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh) { kimage_ptr->x_refresh_needed = do_refresh; } int video_get_active(Kimage *kimage_ptr) { return kimage_ptr->active; } void video_set_active(Kimage *kimage_ptr, int active) { kimage_ptr->active = active; if(kimage_ptr != &g_mainwin_kimage) { adb_nonmain_check(); } } void video_init(int mdepth, int screen_width, int screen_height, int no_scale_window) { word32 col[4]; word32 val0, val1, val2, val3, next_col, next2_col; word32 val, cur_col; int i, j; // Initialize video system (called one-time only) g_video_no_scale_window = no_scale_window; for(i = 0; i < 200; i++) { g_a2_line_left_edge[i] = 0; g_a2_line_right_edge[i] = 0; } for(i = 0; i < 200; i++) { g_a2_filt_stat[i] = -1; for(j = 0; j < 8; j++) { g_saved_line_palettes[0][i][j] = (word32)-1; g_saved_line_palettes[1][i][j] = (word32)-1; } } for(i = 0; i < 262; i++) { g_cur_border_colors[i] = -1; } for(i = 0; i < 128; i++) { val0 = i; val1 = 0; for(j = 0; j < 7; j++) { val1 = val1 << 2; if(val0 & 0x40) { val1 |= 3; } val0 = val0 << 1; } g_pixels_widened[i] = val1; } vid_printf("Zeroing out video memory, mdepth:%d\n", mdepth); for(i = 0; i < SLOW_MEM_CH_SIZE; i++) { g_slow_mem_changed[i] = (word32)-1; g_slow_mem_ch2[i] = 0; } // create g_dhires_convert[] array // TODO: Look at patent #4786893 for details on VGC and dhr for(i = 0; i < 4096; i++) { /* Convert index bits 11:0 where 3:0 is the previous color */ /* and 7:4 is the current color to translate */ /* Bit 4 will be the first pixel displayed on the screen */ for(j = 0; j < 4; j++) { cur_col = (i >> (1 + j)) & 0xf; next_col = (i >> (2 + j)) & 0xf; next2_col = (i >> (3 + j)) & 0xf; cur_col = (((cur_col << 4) + cur_col) >> (3 - j)) & 0xf; if((cur_col == 0xf) || (next_col == 0xf) || (next2_col == 0xf)) { cur_col = 0xf; col[j] = cur_col; } else if((cur_col == 0) || (next_col == 0) || (next2_col == 0)) { cur_col = 0; col[j] = cur_col; } else { col[j] = cur_col; } } if(g_use_dhr140) { for(j = 0; j < 4; j++) { col[j] = (i >> 4) & 0xf; } } val0 = g_dhires_colors_16[col[0] & 0xf]; val1 = g_dhires_colors_16[col[1] & 0xf]; val2 = g_dhires_colors_16[col[2] & 0xf]; val3 = g_dhires_colors_16[col[3] & 0xf]; val = val0 | (val1 << 4) | (val2 << 8) | (val3 << 12); g_dhires_convert[i] = val; if((i == 0x7bc) || (i == 0xfff)) { //printf("g_dhires_convert[%03x] = %04x\n", i, val); } } video_init_kimage(&g_mainwin_kimage, X_A2_WINDOW_WIDTH, X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2, screen_width, screen_height); video_init_kimage(&g_debugwin_kimage, 80*8 + 8 + 8, 25*16 + 8 + 8, screen_width, screen_height); change_display_mode(g_cur_dfcyc); video_reset(); g_vid_update_last_line = 0; g_video_all_stat_pos = 1; g_video_all_stat[0].cur_all_stat = 0; g_video_all_stat[0].lines_since_vbl = 0; g_video_save_all_stat_pos = 0; g_video_filt_stat_pos = 0; video_update_status_enable(&g_mainwin_kimage); video_update_through_line(262); printf("g_mainwin_kimage created and init'ed\n"); fflush(stdout); } int video_clamp(int value, int min, int max) { // Ensure value is >= min and <= max. If max <= min, return min if(value > max) { value = max; } if(value <= min) { value = min; } return value; } void video_init_kimage(Kimage *kimage_ptr, int width, int height, int screen_width, int screen_height) { int x_width, x_height, a2_height, x_xpos, x_ypos; int i; if(screen_width < width) { screen_width = width; } if(screen_height < height) { screen_width = height; } x_width = width; x_height = height; a2_height = height; x_xpos = 100; x_ypos = 300; if(kimage_ptr == &g_mainwin_kimage) { x_width = g_mainwin_width; x_height = g_mainwin_height; x_xpos = g_mainwin_xpos; x_ypos = g_mainwin_ypos; // Handle status lines now if(!g_status_enable) { a2_height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; } } x_width = video_clamp(x_width, width, screen_width); x_height = video_clamp(x_height, height, screen_height); x_xpos = video_clamp(x_xpos, 0, screen_width - 640); x_ypos = video_clamp(x_ypos, 0, screen_height - 420); kimage_ptr->wptr = (word32 *)calloc(1, width * (height + 2) * 4); // Scaling routines read from line+1, expect it to be 0 kimage_ptr->a2_width_full = width; kimage_ptr->a2_height_full = height; kimage_ptr->a2_width = width; kimage_ptr->a2_height = a2_height; kimage_ptr->x_width = x_width; kimage_ptr->x_height = x_height; kimage_ptr->x_refresh_needed = 1; kimage_ptr->x_max_width = screen_width; kimage_ptr->x_max_height = screen_height; kimage_ptr->x_xpos = x_xpos; kimage_ptr->x_ypos = x_ypos; kimage_ptr->active = 0; kimage_ptr->vbl_of_last_resize = 0; kimage_ptr->c025_val = 0; //printf("Created window, width:%d x_width:%d height:%d x_height:%d\n", // width, x_width, height, x_height); kimage_ptr->scale_width_to_a2 = 0x10000; kimage_ptr->scale_width_a2_to_x = 0x10000; kimage_ptr->scale_height_to_a2 = 0x10000; kimage_ptr->scale_height_a2_to_x = 0x10000; kimage_ptr->num_change_rects = 0; for(i = 0; i <= MAX_SCALE_SIZE; i++) { kimage_ptr->scale_width[i] = i; kimage_ptr->scale_height[i] = i; } video_update_scale(kimage_ptr, x_width, x_height, 1); } void show_a2_line_stuff() { int num, num_filt; int i; for(i = 0; i < 200; i++) { printf("line: %d: stat: %07x, " "left_edge:%d, right_edge:%d\n", i, g_a2_filt_stat[i], g_a2_line_left_edge[i], g_a2_line_right_edge[i]); } num = g_video_all_stat_pos; num_filt = g_video_stat_old_pos; printf("cur_a2_stat:%04x, all_stat_pos:%d, num_filt:%d\n", g_cur_a2_stat, num, num_filt); for(i = 0; i < num; i++) { printf("all_stat[%3d]=%08x stat:%08x\n", i, g_video_all_stat[i].lines_since_vbl, g_video_all_stat[i].cur_all_stat); } for(i = 0; i < num_filt; i++) { printf("filt[%3d]=%08x filt_stat:%08x\n", i, g_video_filt_stat_old[i].line_bytes, g_video_filt_stat_old[i].filt_stat); } } int g_flash_count = 0; void video_reset() { int stat; int i; voc_reset(); stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | (0xf << BIT_ALL_STAT_TEXT_COLOR); if(g_use_bw_hires) { stat |= ALL_STAT_COLOR_C021; } if(g_config_control_panel) { /* Don't update cur_a2_stat when in configuration panel */ //g_save_cur_a2_stat = stat; } else { g_cur_a2_stat = stat; } for(i = 0; i < 16; i++) { g_palette_change_cnt[0][i] = 0; g_palette_change_cnt[1][i] = 0; } } word32 g_cycs_in_check_input = 0; void video_update() { int did_video; if(g_fatal_log > 0) { // NOT IMPLEMENTED YET //adb_all_keys_up(); clear_fatal_logs(); } if(g_status_enable != g_status_enable_previous) { g_status_enable_previous = g_status_enable; video_update_status_enable(&g_mainwin_kimage); } debugger_redraw_screen(&g_debugwin_kimage); if(g_config_control_panel) { return; // Nothing else to do } g_screen_redraw_skip_count--; did_video = 0; if(g_screen_redraw_skip_count < 0) { did_video = 1; video_copy_changed2(); video_update_event_line(262); update_border_info(); g_screen_redraw_skip_count = g_screen_redraw_skip_amt; } /* update flash */ g_flash_count++; if(g_flash_count >= 16) { g_flash_count = 0; g_cur_a2_stat ^= ALL_STAT_FLASH_STATE; change_display_mode(g_cur_dfcyc); } if(did_video) { g_vid_update_last_line = 0; g_video_all_stat_pos = 1; g_video_all_stat[0].cur_all_stat = g_cur_a2_stat; g_video_all_stat[0].lines_since_vbl = 0; g_video_save_all_stat_pos = 0; g_video_filt_stat_pos = 0; } } word32 video_all_stat_to_filt_stat(int line, word32 new_all_stat) { word32 filt_stat, merge_mask, mix_t_gr; filt_stat = new_all_stat & ALL_STAT_TEXT; merge_mask = 0; if((new_all_stat & ALL_STAT_ST80) == 0) { merge_mask = ALL_STAT_PAGE2; } mix_t_gr = new_all_stat & ALL_STAT_MIX_T_GR; if(new_all_stat & ALL_STAT_SUPER_HIRES) { filt_stat = ALL_STAT_SUPER_HIRES; merge_mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; } else if(line >= 192) { filt_stat = ALL_STAT_BORDER; } else if(filt_stat || (line >= 160 && mix_t_gr)) { // text mode filt_stat |= ALL_STAT_TEXT; merge_mask |= ALL_STAT_ALTCHARSET | ALL_STAT_BG_COLOR | ALL_STAT_TEXT_COLOR | ALL_STAT_VID80; if((new_all_stat & ALL_STAT_ALTCHARSET) == 0) { merge_mask |= ALL_STAT_FLASH_STATE; } } else { // GR or Hires merge_mask |= ALL_STAT_ANNUNC3 | ALL_STAT_HIRES; if((new_all_stat & ALL_STAT_ANNUNC3) == 0) { // AN3 must be 0 to enable dbl-lores or dbl-hires merge_mask |= ALL_STAT_VID80; } if(new_all_stat & ALL_STAT_HIRES) { merge_mask |= ALL_STAT_COLOR_C021 | ALL_STAT_DIS_COLOR_DHIRES; } } filt_stat = filt_stat | (new_all_stat & merge_mask); return filt_stat; } void change_display_mode(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); video_add_new_all_stat(dfcyc, lines_since_vbl); } void video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl) { word32 my_start, first_start, prev_lines_since_vbl; int pos, prev; pos = g_video_all_stat_pos; my_start = lines_since_vbl & 0x1ff00; first_start = my_start + 24; if(lines_since_vbl >= (200 << 8)) { return; // In VBL, don't log this } if(pos && (lines_since_vbl < first_start)) { prev = pos - 1; prev_lines_since_vbl = g_video_all_stat[prev].lines_since_vbl; // If the previous toggle has the same line, and it is before // offset 24, then ignore it and overwrite it if((my_start <= prev_lines_since_vbl) && (prev_lines_since_vbl < first_start)) { // needless toggling during HBL, just toss earlier pos = prev; } } g_video_all_stat[pos].lines_since_vbl = lines_since_vbl; g_video_all_stat[pos].cur_all_stat = g_cur_a2_stat; if(!g_halt_sim || g_config_control_panel) { dbg_log_info(dfcyc, g_cur_a2_stat, lines_since_vbl, (pos << 16) | 0x102); } pos++; if(pos >= MAX_VIDEO_ALL_STAT) { pos--; } g_video_all_stat_pos = pos; } #define MAX_BORDER_CHANGES 16384 STRUCT(Border_changes) { word32 usec; int val; }; Border_changes g_border_changes[MAX_BORDER_CHANGES]; int g_num_border_changes = 0; void change_border_color(dword64 dfcyc, int val) { int pos; pos = g_num_border_changes; g_border_changes[pos].usec = (word32)((dfcyc - g_last_vbl_dfcyc) >> 16); g_border_changes[pos].val = val; pos++; g_num_border_changes = pos; if(pos >= MAX_BORDER_CHANGES) { halt_printf("num border changes: %d\n", pos); g_num_border_changes = 0; } } void update_border_info() { dword64 drecip_usec, dline; word32 usec; int offset, new_line_offset, last_line_offset, new_line, new_val; int limit, color_now; int i; /* to get this routine to redraw the border, change */ /* g_vbl_border_color, set g_border_last_vbl_changes = 1 */ /* and change the cur_border_colors[] array */ color_now = g_vbl_border_color; drecip_usec = (65536LL * 65536LL) / 65; limit = g_num_border_changes; if(g_border_last_vbl_changes || limit || g_border_reparse) { /* add a dummy entry */ g_border_changes[limit].usec = CYCLES_IN_16MS_RAW + 21; g_border_changes[limit].val = (g_c034_val & 0xf); limit++; } last_line_offset = (((word32)-1L) << 8) + 44; for(i = 0; i < limit; i++) { usec = g_border_changes[i].usec; dline = usec * drecip_usec; new_line = dline >> 32; offset = ((dword64)(word32)dline * 65ULL) >> 32; /* here comes the tricky part */ /* offset is from 0 to 65, where 0-3 is the right border of */ /* the previous line, 4-20 is horiz blanking, 21-24 is the */ /* left border and 25-64 is the main window */ /* Convert this to a new notation which is 0-3 is the left */ /* border, 4-43 is the main window, and 44-47 is the right */ /* basically, add -21 to offset, and wrap < 0 to previous ln */ /* note this makes line -1 offset 44-47 the left hand border */ /* for true line 261 on the screen */ offset -= 21; if(offset < 0) { new_line--; offset += 64; } new_val = g_border_changes[i].val; new_line_offset = (new_line << 8) + offset; if((new_line_offset < -256) || (new_line_offset > (262*256 + 0x80))) { printf("new_line_offset: %05x\n", new_line_offset); new_line_offset = last_line_offset; } while(last_line_offset < new_line_offset) { /* see if this will finish it */ if((last_line_offset & -256)==(new_line_offset & -256)){ update_border_line(last_line_offset, new_line_offset, color_now); last_line_offset = new_line_offset; } else { update_border_line(last_line_offset, (last_line_offset & -256) + 65, color_now); last_line_offset =(last_line_offset & -256)+256; } } color_now = new_val; } #if 0 if(g_num_border_changes) { printf("Border changes: %d\n", g_num_border_changes); } #endif if(limit > 1) { g_border_last_vbl_changes = 1; } else { g_border_last_vbl_changes = 0; } g_num_border_changes = 0; g_border_reparse = 0; g_vbl_border_color = (g_c034_val & 0xf); } void update_border_line(int st_line_offset, int end_line_offset, int color) { word32 filt_stat; int st_offset, end_offset, left, right, width, line; line = st_line_offset >> 8; if(line != (end_line_offset >> 8)) { halt_printf("ubl, %04x %04x %02x!\n", st_line_offset, end_line_offset, color); } if(line < -1 || line >= 262) { halt_printf("ubl-b, mod line is %d\n", line); line = 0; } if(line < 0 || line >= 262) { line = 0; } st_offset = st_line_offset & 0xff; end_offset = end_line_offset & 0xff; if((st_offset == 0) && (end_offset >= 0x41) && !g_border_reparse) { /* might be the same as last time, save some work */ if(g_cur_border_colors[line] == color) { return; } g_cur_border_colors[line] = color; } else { g_cur_border_colors[line] = -1; } /* 0-3: left border, 4-43: main window, 44-47: right border */ /* 48-65: horiz blanking */ /* first, do the sides from line 0 to line 199 */ if((line < 200) || (line >= 262)) { if(line >= 262) { line = 0; } if(st_offset < 4) { /* left side */ left = st_offset; right = MY_MIN(4, end_offset); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, (left * BORDER_WIDTH)/4, (right * BORDER_WIDTH) / 4); g_border_sides_refresh_needed = 1; } if((st_offset < 48) && (end_offset >= 44)) { /* right side */ filt_stat = g_a2_filt_stat[line]; width = BORDER_WIDTH; if((filt_stat & ALL_STAT_SUPER_HIRES) == 0) { width += 80; } left = MY_MAX(0, st_offset - 44); right = MY_MIN(4, end_offset - 44); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, X_A2_WINDOW_WIDTH - width + (left * width/4), X_A2_WINDOW_WIDTH - width + (right * width/4)); g_border_sides_refresh_needed = 1; } } if((line >= 192) && (line < 200)) { filt_stat = g_a2_filt_stat[line]; if((filt_stat & ALL_STAT_BORDER) && (st_offset < 44) && (end_offset > 4)) { left = MY_MAX(0, st_offset - 4); right = MY_MIN(40, end_offset - 4); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 2*line, 2, color, g_video_act_margin_left + (left * 640 / 40), g_video_act_margin_left + (right * 640 / 40)); g_border_line24_refresh_needed = 1; } } /* now do the bottom, lines 200 to 215 */ if((line >= 200) && (line < (200 + BASE_MARGIN_BOTTOM/2)) ) { line -= 200; left = st_offset; right = MY_MIN(48, end_offset); video_border_pixel_write(&g_mainwin_kimage, g_video_act_margin_top + 200*2 + 2*line, 2, color, (left * X_A2_WINDOW_WIDTH / 48), (right * X_A2_WINDOW_WIDTH / 48)); g_border_special_refresh_needed = 1; } /* and top, lines 236 to 262 */ if((line >= (262 - BASE_MARGIN_TOP/2)) && (line < 262)) { line -= (262 - BASE_MARGIN_TOP/2); left = st_offset; right = MY_MIN(48, end_offset); video_border_pixel_write(&g_mainwin_kimage, 2*line, 2, color, (left * X_A2_WINDOW_WIDTH / 48), (right * X_A2_WINDOW_WIDTH / 48)); g_border_special_refresh_needed = 1; } } void video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, int color, int st_off, int end_off) { word32 *wptr, *wptr0; word32 pixel; int width, width_full, offset; int i, j; if(end_off <= st_off) { return; } width = end_off - st_off; width_full = kimage_ptr->a2_width_full; if(width > width_full) { halt_printf("border write but width %d > act %d\n", width, width_full); return; } if((starty + num_lines) > kimage_ptr->a2_height) { halt_printf("border write line %d, > act %d\n", starty+num_lines, kimage_ptr->a2_height); return; } pixel = g_a2palette_1624[color & 0xf]; offset = starty * width_full; wptr0 = kimage_ptr->wptr; wptr0 += offset; for(i = 0; i < num_lines; i++) { wptr = wptr0 + st_off; for(j = 0; j < width; j++) { *wptr++ = pixel; } wptr0 += width_full; } } word32 video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse) { word32 ch_mask, mask; int shift; if(reparse) { return (word32)-1; } shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; mask = (1 << (40 >> SHIFT_PER_CHANGE)) - 1; ch_mask = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]; if(filt_stat & ALL_STAT_VID80) { mem_ptr += 0x10000; ch_mask |= (g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT]); ch_mask |= (g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]); } ch_mask = (ch_mask >> shift) & mask; return ch_mask; } void video_update_edges(int line, int left, int right, const char *str) { g_a2_line_left_edge[line] = MY_MIN(left, g_a2_line_left_edge[line]); g_a2_line_right_edge[line] = MY_MAX(right, g_a2_line_right_edge[line]); if((left < 0) || (right < 0) || (left > 640) || (right > 640)) { printf("video_update_edges: %s: line %d: %d (left) >= %d " "(right)\n", str, line, left, right); } } void redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { byte str_buf[81]; byte *slow_mem_ptr; word32 ch_mask, line_mask, mem_ptr, val0, val1, bg_pixel, fg_pixel; int flash_state, y, bg_color, fg_color, start_line; int x1, x2; // Redraws a single line, will be called over 8 lines to finish a byte. start_line = line_bytes >> 16; bg_color = (filt_stat >> BIT_ALL_STAT_BG_COLOR) & 0xf; fg_color = (filt_stat >> BIT_ALL_STAT_TEXT_COLOR) & 0xf; bg_pixel = g_a2palette_1624[bg_color]; fg_pixel = g_a2palette_1624[fg_color]; y = start_line >> 3; line_mask = 1 << y; mem_ptr = 0x400 + g_screen_index[y]; if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x400; } if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { halt_printf("redraw_changed_text: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(!ch_mask) { return; } g_a2_screen_buffer_changed |= line_mask; x2 = 0; slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); flash_state = -0x40; if(g_cur_a2_stat & ALL_STAT_FLASH_STATE) { flash_state = 0x40; } for(x1 = 0; x1 < 40; x1++) { val0 = slow_mem_ptr[0x10000]; val1 = *slow_mem_ptr++; if(!(filt_stat & ALL_STAT_ALTCHARSET)) { if((val0 >= 0x40) && (val0 < 0x80)) { val0 += flash_state; } if((val1 >= 0x40) && (val1 < 0x80)) { val1 += flash_state; } } if(filt_stat & ALL_STAT_VID80) { str_buf[x2++] = val0; // aux mem } str_buf[x2++] = val1; // main mem } str_buf[x2] = 0; // null terminate redraw_changed_string(&str_buf[0], line_bytes, ch_mask, in_wptr, bg_pixel, fg_pixel, pixels_per_line, (filt_stat & ALL_STAT_VID80)); } void redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, word32 *in_wptr, word32 bg_pixel, word32 fg_pixel, int pixels_per_line, int dbl) { register word32 start_time, end_time; word32 *wptr; word32 val0, val1, val2, val3, pixel; int left, right, st_line_mod8, offset, pos, shift, start_line; int start_byte, end_byte; int x1, j; left = 40; right = 0; GET_ITIMER(start_time); start_line = line_bytes >> 16; start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; st_line_mod8 = start_line & 7; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + 1, right); offset = (start_line * 2 * pixels_per_line) + x1*14; pos = x1; if(dbl) { pos = pos * 2; } wptr = in_wptr + offset; val0 = bptr[pos]; if(dbl) { pos++; } val1 = bptr[pos++]; val2 = g_a2font_bits[val0][st_line_mod8]; val3 = g_a2font_bits[val1][st_line_mod8]; // val2, [6:0] is 80-column character bits, and // [21:8] are the 40-column char bits (double-wide) if(dbl) { val2 = (val3 << 7) | (val2 & 0x7f); } else { val2 = val3 >> 8; // 40-column format } for(j = 0; j < 14; j++) { pixel = bg_pixel; if(val2 & 1) { // LSB is first pixel pixel = fg_pixel; } wptr[pixels_per_line] = pixel; *wptr++ = pixel; val2 = val2 >> 1; } } GET_ITIMER(end_time); if(start_line < 200) { video_update_edges(start_line, left * 14, right * 14, "text"); } if((left >= right) || (left < 0) || (right < 0)) { printf("str line %d, 40: left >= right: %d >= %d\n", start_line, left, right); printf(" line_bytes:%08x ch_mask:%08x\n", line_bytes, ch_mask); } g_cycs_in_40col += (end_time - start_time); } // gr with an3=0: // 0=0 // 1,0=3 (purple). 1,1=0 // 2,0=c (green). 2,1=0 // 3,0=f (white). 3,1=0 // 4,0=0. 4,1=c (green) // 5,0=3 (purple). 5,1=c (green) // 6,0=c (green). 6,1=c (green) // 7,0=f (white). 7,1=c (green) // 8,0=0 (black). 7,1=3 (purple) // 9,0=3 (purple). 9,1=3 (purple) // a,0=c (green). a,1=3 (purple) // b,0=f (white). b,1=3 (purple) // c,0=0 (black). c,1=f (white) // d,0=3 (purple). d,1=f (white) // e,0=c (green). e,1=f (white) // f,0=f (white). e,1=f (white) void redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { word32 *wptr; byte *slow_mem_ptr; word32 line_mask, mem_ptr, val0, val1, pixel0, pixel1, ch_mask; int y, shift, left, right, st_line_mod8, start_line, offset; int start_byte, end_byte; int x1, i; start_line = line_bytes >> 16; st_line_mod8 = start_line & 7; y = start_line >> 3; line_mask = 1 << (y); mem_ptr = 0x400 + g_screen_index[y]; if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x400; } if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { halt_printf("redraw_changed_gr: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(!ch_mask) { return; } g_a2_screen_buffer_changed |= line_mask; left = 40; right = 0; slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); offset = (start_line * 2 * pixels_per_line); start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + 1, right); wptr = in_wptr + offset + x1*14; val0 = slow_mem_ptr[0x10000 + x1]; val1 = slow_mem_ptr[x1]; if(st_line_mod8 >= 4) { val0 = val0 >> 4; val1 = val1 >> 4; } if(filt_stat & ALL_STAT_VID80) { // aux pixel is { [2:0],[3] } val0 = (val0 << 1) | ((val0 >> 3) & 1); } else if((filt_stat & ALL_STAT_ANNUNC3) == 0) { if(x1 & 1) { // odd cols val0 = ((val1 >> 1) & 2) | ((val1 >> 3) & 1); } else { val0 = val1 & 3; // even cols } // map val0: 0->0, 1->3, 2->c, 3->f val1 = 0; if(val0 & 1) { val1 |= 3; } if(val0 & 2) { val1 |= 0xc; } val0 = val1; } else { val0 = val1; } pixel0 = g_a2palette_1624[val0 & 0xf]; pixel1 = g_a2palette_1624[val1 & 0xf]; for(i = 0; i < 7; i++) { wptr[pixels_per_line] = pixel0; wptr[pixels_per_line + 7] = pixel1; wptr[0] = pixel0; wptr[7] = pixel1; wptr++; } } video_update_edges(start_line, left * 14, right * 14, "gr"); } void video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, int end_byte, int pixels_per_line, word32 filt_stat) { word32 val0, val1, val2, prev_bits, val1_hi, dbl_step, pixel, color; word32 monochrome; int shift; int x2, i; monochrome = filt_stat & (ALL_STAT_COLOR_C021 | ALL_STAT_DIS_COLOR_DHIRES); prev_bits = 0; if(start_byte) { prev_bits = (slow_mem_ptr[-1] >> 3) & 0xf; if(!(filt_stat & ALL_STAT_VID80)) { // prev_bits is 4 bits, widen to 8 for std HGR prev_bits = g_pixels_widened[prev_bits] >> 4; } prev_bits = prev_bits & 0xf; } for(x2 = start_byte; x2 < end_byte; x2++) { val0 = slow_mem_ptr[0x10000]; val1 = *slow_mem_ptr++; val2 = slow_mem_ptr[0x10000]; // next pixel, aux mem if(x2 >= 39) { val2 = 0; } val1_hi = ((val1 >> 5) & 4) | ((x2 & 1) << 1); // Hi-order bit in bit 2, odd pixel is in bit 0 dbl_step = 3; if(filt_stat & ALL_STAT_VID80) { // aux+1[6:0], main[6:0], aux[6:0], prev[3:0] val0 = (val2 << 18) | ((val1 & 0x7f) << 11) | ((val0 & 0x7f) << 4); if(!monochrome && (x2 & 1)) { // Get 6 bits from prev val0 = (val0 << 2); dbl_step = 1; } val0 = val0 | prev_bits; } else if(monochrome) { val0 = g_pixels_widened[val1 & 0x7f] << 4; } else { // color, normal hgr val2 = g_pixels_widened[*slow_mem_ptr & 0x7f]; if(x2 >= 39) { val2 = 0; } val0 = ((val1 & 0x7f) << 4) | prev_bits | (val2 << 11); if((filt_stat & ALL_STAT_ANNUNC3) == 0) { val1_hi = val1_hi & 3; } } #if 0 if(st_line < 8) { printf("hgrl %d c:%d,d:%d, off:%03x val0:%02x 1:%02x\n", st_line, monochrome, dbl, x1 + x2, val0, val1); } #endif for(i = 0; i < 14; i++) { color = 0; // black if(monochrome) { if(val0 & 0x10) { color = 0xf; // white } val0 = val0 >> 1; } else { // color if(filt_stat & ALL_STAT_VID80) { color = g_dhires_convert[val0 & 0xfff]; shift = (x2 + x2 + i) & 3; color = color >> (4 * shift); if((i & 3) == dbl_step) { val0 = val0 >> 4; } } else { val2 = (val0 & 0x38) ^ val1_hi ^(i & 3); color = g_hires_lookup[val2 & 0x7f]; if(i & 1) { val0 = val0 >> 1; } } } pixel = g_a2palette_1624[color & 0xf]; wptr[pixels_per_line] = pixel; *wptr++ = pixel; } if((filt_stat & ALL_STAT_VID80) && ((x2 & 1) == 0)) { prev_bits = val0 & 0x3f; } else { prev_bits = val0 & 0xf; } } } void redraw_changed_hgr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat) { word32 *wptr; byte *slow_mem_ptr; word32 ch_mask, line_mask, mem_ptr; int y, shift, st_line_mod8, start_line, offset, start_byte; int end_byte; int x1; start_line = line_bytes >> 16; start_byte = line_bytes & 0x3f; end_byte = (line_bytes >> 8) & 0x3f; // Usually '40' y = start_line >> 3; st_line_mod8 = start_line & 7; line_mask = 1 << y; mem_ptr = 0x2000 + g_screen_index[y] + (st_line_mod8 * 0x400); if(filt_stat & ALL_STAT_PAGE2) { mem_ptr += 0x2000; } if((mem_ptr < 0x2000) || (mem_ptr >= 0x6000)) { halt_printf("redraw_changed_hgr: mem_ptr: %08x, y:%d\n", mem_ptr, y); return; } ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); if(ch_mask == 0) { return; } // Hires depends on adjacent bits, so also reparse adjacent regions // to handle redrawing of pixels on the boundaries ch_mask = ch_mask | (ch_mask >> 1) | (ch_mask << 1); g_a2_screen_buffer_changed |= line_mask; for(x1 = start_byte; x1 < end_byte; x1++) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); offset = (start_line * 2 * pixels_per_line) + x1*14; wptr = in_wptr + offset; video_hgr_line_segment(slow_mem_ptr, wptr, x1, end_byte, pixels_per_line, filt_stat); video_update_edges(start_line, x1 * 14, end_byte * 14, "hgr"); break; } } int video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, int reparse) { word32 *word_ptr; byte *byte_ptr; word32 ch_mask, mem_ptr, scan, old_scan, val0, val1; int diffs, palette; int j; palette = scan_info & 0xf; mem_ptr = (bank << 16) + 0x9e00 + (palette * 0x20); ch_mask = video_get_ch_mask(mem_ptr, 0, 0); old_scan = g_superhires_scan_save[bank][line]; scan = (scan_info & 0xfaf) + (g_palette_change_cnt[bank][palette] << 12); g_superhires_scan_save[bank][line] = scan; #if 0 if(line == 1) { word_ptr = (word32 *)&(g_slow_memory_ptr[0x19e00+palette*0x20]); printf("y1vrshp, ch:%08x, s:%08x,os:%08x %d = %08x %08x %08x " "%08x %08x %08x %08x %08x\n", ch_mask, scan, old_scan, reparse, word_ptr[0], word_ptr[1], word_ptr[2], word_ptr[3], word_ptr[4], word_ptr[5], word_ptr[6], word_ptr[7]); } #endif diffs = reparse | ((scan ^ old_scan) & 0xf0f); /* we must do full reparse if palette changed for this line */ if(!diffs && (ch_mask == 0) && (((scan ^ old_scan) & (~0xf0)) == 0)) { /* nothing changed, get out fast */ return 0; } if(ch_mask) { /* indicates the palette has changed, and other scan lines */ /* using this palette need to do a full 32-byte compare to */ /* decide if they need to update or not */ g_palette_change_cnt[bank][palette]++; } word_ptr = (word32 *)&(g_slow_memory_ptr[(bank << 16) + 0x9e00 + palette*0x20]); for(j = 0; j < 8; j++) { if(word_ptr[j] != g_saved_line_palettes[bank][line][j]) { diffs = 1; break; } } if(diffs == 0) { return 0; } /* first, save this word_ptr into saved_line_palettes */ byte_ptr = (byte *)word_ptr; for(j = 0; j < 8; j++) { g_saved_line_palettes[bank][line][j] = word_ptr[j]; } byte_ptr = (byte *)word_ptr; /* this palette has changed */ for(j = 0; j < 16; j++) { val0 = *byte_ptr++; val1 = *byte_ptr++; video_update_color_raw(bank, palette*16 + j, (val1<<8) + val0); } return 1; } word32 redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, int pixels_per_line, int y, int scan, word32 ch_mask) { word32 *palptr, *wptr; byte *slow_mem_ptr; word32 mem_ptr, val0, pal, pix0, pix1, pix2, pix3, save_pix; int offset, shift_per, left, right, shift; int x1, x2; mem_ptr = (bank << 16) + 0x2000 + (0xa0 * y); shift_per = (1 << SHIFT_PER_CHANGE); pal = (scan & 0xf); save_pix = 0; if(scan & 0x20) { // Fill mode ch_mask = (word32)-1; } palptr = &(g_palette_8to1624[bank][pal * 16]); left = 160; right = 0; for(x1 = 0; x1 < 0xa0; x1 += shift_per) { shift = x1 >> SHIFT_PER_CHANGE; if(((ch_mask >> shift) & 1) == 0) { continue; } left = MY_MIN(x1, left); right = MY_MAX(x1 + shift_per, right); slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); offset = x1*4; wptr = in_wptr + offset; for(x2 = 0; x2 < shift_per; x2++) { val0 = *slow_mem_ptr++; if(scan & 0x80) { // 640 mode pix0 = (val0 >> 6) & 3; pix1 = (val0 >> 4) & 3; pix2 = (val0 >> 2) & 3; pix3 = val0 & 3; pix0 = palptr[pix0 + 8]; pix1 = palptr[pix1 + 12]; pix2 = palptr[pix2 + 0]; pix3 = palptr[pix3 + 4]; } else { /* 320 mode */ pix0 = (val0 >> 4); pix2 = (val0 & 0xf); if(scan & 0x20) { // Fill mode if(!pix0) { // 0 = repeat last color pix0 = save_pix; } if(!pix2) { pix2 = pix0; } save_pix = pix2; } pix0 = palptr[pix0]; pix1 = pix0; pix2 = palptr[pix2]; pix3 = pix2; } wptr[pixels_per_line] = pix0; *wptr++ = pix0; wptr[pixels_per_line] = pix1; *wptr++ = pix1; wptr[pixels_per_line] = pix2; *wptr++ = pix2; wptr[pixels_per_line] = pix3; *wptr++ = pix3; } } return (left << 16) | (right & 0xffff); } void redraw_changed_super_hires_bank(int bank, int start_line, int reparse, word32 *wptr, int pixels_per_line) { dword64 dval, dval1; word32 this_check, mask, tmp, scan, old_scan, mem_ptr; int left, right, ret, shift; mem_ptr = (bank << 16) + 0x2000 + (160 * start_line); dval1 = g_slow_mem_changed[(mem_ptr >> CHANGE_SHIFT) + 1] | g_slow_mem_ch2[(mem_ptr >> CHANGE_SHIFT) + 1]; dval = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT] | (dval1 << 32); shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; mask = (1 << (160 >> SHIFT_PER_CHANGE)) - 1; this_check = (dval >> shift) & mask; scan = g_slow_memory_ptr[(bank << 16) + 0x9d00 + start_line]; old_scan = g_superhires_scan_save[bank][start_line]; ret = video_rebuild_super_hires_palette(bank, scan, start_line, reparse); if(ret || reparse || ((scan ^ old_scan) & 0xa0)) { /* 0x80 == mode640, 0x20 = fill */ this_check = (word32)-1; } if(!this_check) { return; // Nothing to do, get out } if(scan & 0x80) { // 640 mode g_num_lines_superhires640++; } if((scan >> 5) & 1) { // fill mode--redraw whole line this_check = (word32)-1; } g_a2_screen_buffer_changed |= (1 << (start_line >> 3)); tmp = redraw_changed_super_hires_oneline(bank, wptr, pixels_per_line, start_line, scan, this_check); left = tmp >> 16; right = tmp & 0xffff; video_update_edges(start_line, left * 4, right * 4, "shr"); } void redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, int pixels_per_line, word32 filt_stat) { int bank, start_line; start_line = line_bytes >> 16; wptr += start_line * 2 * pixels_per_line; if(filt_stat & ALL_STAT_VOC_INTERLACE) { // Do 400 interlaced lines. Do aux first, then main mem redraw_changed_super_hires_bank(1, start_line, reparse, wptr, 0); redraw_changed_super_hires_bank(0, start_line, reparse, wptr + pixels_per_line, 0); } else { bank = 1; if(filt_stat & ALL_STAT_VOC_MAIN) { bank = 0; // VOC SHR in main memory } redraw_changed_super_hires_bank(bank, start_line, reparse, wptr, pixels_per_line); } } void video_copy_changed2() { word32 *ch_ptr, *ch2_ptr; int bank1_off; int i; // Copy entries from g_slow_mem_changed[] to g_slow_mem_ch2[] and // clear g_slow_mem_changed[] ch_ptr = &g_slow_mem_changed[0]; ch2_ptr = &g_slow_mem_ch2[0]; bank1_off = 0x10000 >> CHANGE_SHIFT; for(i = 4; i < 0xa0; i++) { // Pages 0x0400 through 0x9fff ch2_ptr[i] = ch_ptr[i]; ch2_ptr[i + bank1_off] = ch_ptr[i + bank1_off]; ch_ptr[i] = 0; ch_ptr[i + bank1_off] = 0; } } void video_update_event_line(int line) { int new_line; video_update_through_line(line); new_line = line + g_line_ref_amt; if(new_line < 200) { if(!g_config_control_panel && !g_halt_sim) { add_event_vid_upd(new_line); } } else if(line >= 262) { if(!g_config_control_panel && !g_halt_sim) { add_event_vid_upd(0); /* add event for new screen */ } } } void video_force_reparse() { word32 *wptr; int height, width_full; int i, j; g_video_stat_old_pos = 1; g_video_filt_stat_old[0].filt_stat = (word32)-1; height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; height = MY_MIN(height, g_mainwin_kimage.a2_height); width_full = g_mainwin_kimage.a2_width_full; wptr = g_mainwin_kimage.wptr; for(i = 0; i < height; i++) { for(j = 0; j < width_full; j++) { *wptr++ = 0; } } g_border_reparse = 1; } void video_update_through_line(int line) { register word32 start_time; register word32 end_time; word32 my_start_lines, my_end_lines, prev_all_stat, next_all_stat; word32 prev_lines_since_vbl, next_lines_since_vbl; int last_line, pos, last_pos, end, num; int i; #if 0 vid_printf("\nvideo_upd for line %d, lines: %06x\n", line, get_lines_since_vbl(g_cur_dfcyc)); #endif GET_ITIMER(start_time); last_line = MY_MIN(200, line+1); /* go through line, but not past 200 */ pos = g_video_save_all_stat_pos; last_pos = g_video_all_stat_pos; prev_all_stat = g_video_all_stat[pos].cur_all_stat; prev_lines_since_vbl = g_video_all_stat[pos].lines_since_vbl; g_video_all_stat[last_pos].cur_all_stat = g_cur_a2_stat; g_video_all_stat[last_pos].lines_since_vbl = (line + 1) << 8; next_all_stat = g_video_all_stat[pos+1].cur_all_stat; next_lines_since_vbl = g_video_all_stat[pos+1].lines_since_vbl; for(i = g_vid_update_last_line; i < last_line; i++) { // We need to step through pos in g_video_all_stat[] and find // the start/end pairs for each line g_a2_line_left_edge[i] = 640; g_a2_line_right_edge[i] = 0; my_start_lines = (i << 8) + 25; my_end_lines = (i << 8) + 65; if(prev_lines_since_vbl > my_start_lines) { printf("prev:%08x > %08x start at i:%d\n", prev_lines_since_vbl, my_start_lines, i); } while(my_start_lines < my_end_lines) { while(next_lines_since_vbl <= my_start_lines) { // Step into next entry prev_all_stat = next_all_stat; prev_lines_since_vbl = next_lines_since_vbl; pos++; g_video_save_all_stat_pos = pos; next_all_stat = g_video_all_stat[pos+1].cur_all_stat; next_lines_since_vbl = g_video_all_stat[pos+1].lines_since_vbl; if(pos >= last_pos) { printf("FELL OFF %d %d!\n", pos, last_pos); pos--; break; } } end = 65; if(next_lines_since_vbl < my_end_lines) { end = (next_lines_since_vbl & 0xff); if(end < 25) { printf("i:%d next_lines_since_vbl:" "%08x!\n", i, next_lines_since_vbl); end = 25; } } video_do_partial_line(my_start_lines, end, prev_all_stat); my_start_lines = (i << 8) + end; } } g_vid_update_last_line = last_line; g_video_save_all_stat_pos = pos; /* deal with border and forming rects for xdriver.c to use */ if(line >= 262) { if(g_num_lines_prev_superhires != g_num_lines_superhires) { /* switched in/out from superhires--refresh borders */ g_border_sides_refresh_needed = 1; } video_form_change_rects(); g_num_lines_prev_superhires = g_num_lines_superhires; g_num_lines_prev_superhires640 = g_num_lines_superhires640; g_num_lines_superhires = 0; g_num_lines_superhires640 = 0; num = g_video_filt_stat_pos; g_video_stat_old_pos = num; for(i = 0; i < num; i++) { g_video_filt_stat_old[i] = g_video_filt_stat[i]; } g_video_filt_stat_pos = 0; } GET_ITIMER(end_time); g_cycs_in_refresh_line += (end_time - start_time); } extern word32 g_vbl_count; void video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat) { word32 filt_stat, old_filt_stat, line_bytes, old_line_bytes; int pos, old_pos, reparse, line; pos = g_video_filt_stat_pos; old_pos = g_video_stat_old_pos; filt_stat = video_all_stat_to_filt_stat(lines_since_vbl >> 8, cur_all_stat); line_bytes = ((lines_since_vbl & 0x1ff00) << 8) | ((end - 25) << 8) | ((lines_since_vbl - 25) & 0x3f); g_video_filt_stat[pos].line_bytes = line_bytes; g_video_filt_stat[pos].filt_stat = filt_stat; reparse = 1; old_filt_stat = (word32)-1; old_line_bytes = (word32)-1; if(pos < old_pos) { old_filt_stat = g_video_filt_stat_old[pos].filt_stat; old_line_bytes = g_video_filt_stat_old[pos].line_bytes; } if((old_filt_stat == filt_stat) && (line_bytes == old_line_bytes)) { reparse = 0; } else if((old_filt_stat ^ filt_stat) & ALL_STAT_SUPER_HIRES) { g_border_reparse = 1; } video_refresh_line(line_bytes, reparse, filt_stat); line = lines_since_vbl >> 8; if(line < 200) { g_a2_filt_stat[line] = filt_stat; } else { printf("partial_line %08x %d %08x out of range!\n", lines_since_vbl, end, cur_all_stat); } if((end <= 25) || (end < (int)(lines_since_vbl & 0xff))) { printf("Bad lsv:%08x, end:%d, stat:%08x\n", lines_since_vbl, end, filt_stat); } pos++; if(pos >= MAX_VIDEO_FILT_STAT) { pos--; } g_video_filt_stat_pos = pos; } void video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat) { word32 *wptr; int pixels_per_line, offset, line; line = line_bytes >> 16; if((word32)line >= 200) { printf("video_refresh %08x %d %08x!\n", line_bytes, must_reparse, filt_stat); return; } wptr = g_mainwin_kimage.wptr; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top) + g_video_act_margin_left; wptr = wptr + offset; if(filt_stat & ALL_STAT_SUPER_HIRES) { g_num_lines_superhires++; redraw_changed_super_hires(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else if(filt_stat & ALL_STAT_BORDER) { if(line < 192) { halt_printf("Border line not 192: %d\n", line); } g_a2_line_left_edge[line] = 0; g_a2_line_right_edge[line] = 560; if(g_border_line24_refresh_needed) { g_border_line24_refresh_needed = 0; g_a2_screen_buffer_changed |= (1 << 24); } } else if(filt_stat & ALL_STAT_TEXT) { redraw_changed_text(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else if(filt_stat & ALL_STAT_HIRES) { redraw_changed_hgr(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } else { redraw_changed_gr(line_bytes, must_reparse, wptr, pixels_per_line, filt_stat); } } void prepare_a2_font() { word32 val0, val1, val2; int i, j, k; // Prepare g_a2font_bits[char][line] where each entry indicates the // set pixels in this line of the character. Bits 6:0 are an // 80-column character, and bits 21:8 are the 14 expanded bits of a // 40-columns character, both with the first visible bit at the // rightmost bit address. But g_font_array[] is in big-endian bit // order, which is less useful for(i = 0; i < 256; i++) { for(j = 0; j < 8; j++) { val0 = g_font_array[i][j] >> 1; val1 = 0; // 80-column bits val2 = 0; // 40-column bits (doubled) for(k = 0; k < 7; k++) { val1 = val1 << 1; val2 = val2 << 2; if(val0 & 1) { val1 |= 1; val2 |= 3; } val0 = val0 >> 1; } g_a2font_bits[i][j] = (val2 << 8) | val1; } } } void prepare_a2_romx_font(byte *font_ptr) { word32 val0, val1, val2; int i, j, k; // ROMX file for(i = 0; i < 256; i++) { for(j = 0; j < 8; j++) { val0 = font_ptr[i*8 + j]; val1 = 0; // 80-column bits val2 = 0; // 40-column bits (doubled) for(k = 0; k < 7; k++) { val1 = val1 << 1; val2 = val2 << 2; if((val0 & 0x40) == 0) { val1 |= 1; val2 |= 3; } val0 = val0 << 1; } g_a2font_bits[i][j] = (val2 << 8) | val1; } } } void video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height) { int pos; pos = kimage_ptr->num_change_rects++; if(pos >= MAX_CHANGE_RECTS) { return; // This will be handled later } kimage_ptr->change_rect[pos].x = x; kimage_ptr->change_rect[pos].y = y; kimage_ptr->change_rect[pos].width = width; kimage_ptr->change_rect[pos].height = height; g_video_pixel_dcount += (width * height); #if 0 printf("Add rect %d, x:%d y:%d, w:%d h:%d\n", pos, x, y, width, height); #endif } void video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix) { int srcy; if((left_pix >= right_pix) || (left_pix < 0) || (right_pix <= 0)) { halt_printf("video_push_lines: lines %d to %d, pix %d to %d\n", start_line, end_line, left_pix, right_pix); printf("a2_screen_buf_ch:%08x, g_full_refr:%08x\n", g_a2_screen_buffer_changed, g_full_refresh_needed); return; } srcy = 2*start_line; video_add_rect(&g_mainwin_kimage, g_video_act_margin_left + left_pix, g_video_act_margin_top + srcy, (right_pix - left_pix), 2*(end_line - start_line)); } void video_form_change_rects() { Kimage *kimage_ptr; register word32 start_time; register word32 end_time; dword64 save_pixel_dcount; word32 mask; int start, line, left_pix, right_pix, left, right, line_div8; int x, y, width, height; kimage_ptr = &g_mainwin_kimage; if(g_border_sides_refresh_needed) { g_border_sides_refresh_needed = 0; // Add left side border video_add_rect(kimage_ptr, 0, g_video_act_margin_top, BORDER_WIDTH, A2_WINDOW_HEIGHT); // Add right-side border. Resend x from 560 through // X_A2_WINDOW_WIDTH x = g_video_act_margin_left + 560; width = X_A2_WINDOW_WIDTH - x; video_add_rect(kimage_ptr, x, g_video_act_margin_top, width, A2_WINDOW_HEIGHT); } if(g_border_special_refresh_needed) { g_border_special_refresh_needed = 0; // Do top border width = g_video_act_width; height = g_video_act_margin_top; video_add_rect(kimage_ptr, 0, 0, width, height); // Do bottom border height = g_video_act_margin_bottom; y = g_video_act_margin_top + A2_WINDOW_HEIGHT; video_add_rect(kimage_ptr, 0, y, width, height); } if(g_status_refresh_needed) { g_status_refresh_needed = 0; width = g_mainwin_kimage.a2_width; y = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; height = kimage_ptr->a2_height - y; if(height > 0) { save_pixel_dcount = g_video_pixel_dcount; video_add_rect(kimage_ptr, 0, y, width, height); g_video_pixel_dcount = save_pixel_dcount; } } if(g_a2_screen_buffer_changed == 0) { return; } GET_ITIMER(start_time); start = -1; left_pix = 640; right_pix = 0; for(line = 0; line < 200; line++) { line_div8 = line >> 3; mask = 1 << (line_div8); if((g_full_refresh_needed & mask) != 0) { left = 0; right = 640; } else { left = g_a2_line_left_edge[line]; right = g_a2_line_right_edge[line]; } if(!(g_a2_screen_buffer_changed & mask) || (left >= right)) { /* No need to update this line */ /* Refresh previous chunks of lines, if any */ if(start >= 0) { video_add_a2_rect(start, line, left_pix, right_pix); start = -1; left_pix = 640; right_pix = 0; } } else { /* Need to update this line */ if(start < 0) { start = line; } left_pix = MY_MIN(left, left_pix); right_pix = MY_MAX(right, right_pix); } } if(start >= 0) { video_add_a2_rect(start, 200, left_pix, right_pix); } g_a2_screen_buffer_changed = 0; g_full_refresh_needed = 0; GET_ITIMER(end_time); g_cycs_in_xredraw += (end_time - start_time); } int video_get_a2_width(Kimage *kimage_ptr) { return kimage_ptr->a2_width; } int video_get_x_width(Kimage *kimage_ptr) { return kimage_ptr->x_width; } int video_get_a2_height(Kimage *kimage_ptr) { return kimage_ptr->a2_height; } int video_get_x_height(Kimage *kimage_ptr) { return kimage_ptr->x_height; } int video_get_x_xpos(Kimage *kimage_ptr) { return kimage_ptr->x_xpos; } int video_get_x_ypos(Kimage *kimage_ptr) { return kimage_ptr->x_ypos; } void video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos) { x_xpos = video_clamp(x_xpos, 0, kimage_ptr->x_max_width - 640); x_ypos = video_clamp(x_ypos, 0, kimage_ptr->x_max_height - 420); kimage_ptr->x_xpos = x_xpos; kimage_ptr->x_ypos = x_ypos; if(kimage_ptr == &g_mainwin_kimage) { g_mainwin_xpos = x_xpos; g_mainwin_ypos = x_ypos; // printf("Set g_mainwin_xpos:%d, ypos:%d\n", x_xpos, x_ypos); } } int video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height) { // Return 1 if the passed in height, width do not match the kimage // aspect-corrected version, and at least 2 VBL periods have passed if((kimage_ptr->vbl_of_last_resize + 6) > g_vbl_count) { return 0; } if((kimage_ptr->x_height != x_height) || (kimage_ptr->x_width != x_width)) { #if 0 printf("change_aspect_needed, vbl:%d kimage width:%d height:%d " "but x width:%d height:%d\n", g_vbl_count, kimage_ptr->x_width, kimage_ptr->x_height, x_width, x_height); #endif return 1; } return 0; } void video_update_status_enable(Kimage *kimage_ptr) { int height, a2_height; height = g_video_act_margin_top + A2_WINDOW_HEIGHT + g_video_act_margin_bottom; a2_height = height; if(g_status_enable) { a2_height = kimage_ptr->a2_height_full; } kimage_ptr->a2_height = a2_height; height = (a2_height * kimage_ptr->scale_width_a2_to_x) >> 16; if(height > kimage_ptr->x_max_height) { height = kimage_ptr->x_max_height; } kimage_ptr->x_height = height; #if 0 printf("new a2_height:%d, x_height:%d\n", kimage_ptr->a2_height, kimage_ptr->x_height); #endif //printf("Calling video_update_scale from video_update_status_en\n"); video_update_scale(kimage_ptr, kimage_ptr->x_width, height, 0); } // video_out_query: return 0 if no screen drawing at all is needed. // returns 1 or the number of change_rects if any drawing is needed int video_out_query(Kimage *kimage_ptr) { int num_change_rects, x_refresh_needed; num_change_rects = kimage_ptr->num_change_rects; x_refresh_needed = kimage_ptr->x_refresh_needed; if(x_refresh_needed) { return 1; } return num_change_rects; } // video_out_done: used by specialize xdriver platform code which needs to // clear the num_change_rects=0. void video_out_done(Kimage *kimage_ptr) { kimage_ptr->num_change_rects = 0; kimage_ptr->x_refresh_needed = 0; } // Called by xdriver.c to copy KEGS's kimage data to the vptr buffer int video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr, int pos) { word32 *out_wptr, *wptr; int a2_width, a2_width_full, width, a2_height, height, x, eff_y; int x_width, x_height, num_change_rects, x_refresh_needed; int i, j; // Copy from kimage_ptr->wptr to vptr num_change_rects = kimage_ptr->num_change_rects; x_refresh_needed = kimage_ptr->x_refresh_needed; if(((pos >= num_change_rects) || (pos >= MAX_CHANGE_RECTS)) && !x_refresh_needed) { kimage_ptr->num_change_rects = 0; return 0; } a2_width = kimage_ptr->a2_width; a2_width_full = kimage_ptr->a2_width_full; a2_height = kimage_ptr->a2_height; if((num_change_rects >= MAX_CHANGE_RECTS) || x_refresh_needed) { // Table overflow, just copy everything in one go kimage_ptr->x_refresh_needed = 0; if(pos >= 1) { kimage_ptr->num_change_rects = 0; return 0; // No more to do } // Force full update rectptr->x = 0; rectptr->y = 0; rectptr->width = a2_width; rectptr->height = a2_height; } else { *rectptr = kimage_ptr->change_rect[pos]; // Struct copy } #if 0 printf("video_out_data, %p rectptr:%p, pos:%d, x:%d y:%d w:%d h:%d, " "wptr:%p\n", vptr, rectptr, pos, rectptr->x, rectptr->y, rectptr->width, rectptr->height, kimage_ptr->wptr); #endif width = rectptr->width; height = rectptr->height; x = rectptr->x; x_width = kimage_ptr->x_width; x_height = kimage_ptr->x_height; if(!g_video_no_scale_window && ((a2_width != x_width) || (a2_height != x_height))) { #if 0 printf("a2_width:%d, x_width:%d, a2_height:%d, x_height:" "%d\n", a2_width, x_width, a2_height, x_height); #endif return video_out_data_scaled(vptr, kimage_ptr, out_width_act, rectptr); } else { out_wptr = (word32 *)vptr; for(i = 0; i < height; i++) { eff_y = rectptr->y + i; wptr = kimage_ptr->wptr + (eff_y * a2_width_full) + x; out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + x; for(j = 0; j < width; j++) { *out_wptr++ = *wptr++; } } } return 1; } int video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr) { word32 *out_wptr, *wptr; word32 pos_scale, alpha_mask; int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; int i, j; // Faster scaling routine which does simple pixel replication rather // than blending. Intended for scales >= 3.0 (or so) since at // these scales, replication looks fine. x = rectptr->x; y = rectptr->y; max_x = rectptr->width + x; max_y = rectptr->height + y; max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); x = MY_MAX(0, x - 1); y = MY_MAX(0, y - 1); a2_width_full = kimage_ptr->a2_width_full; out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; out_max_x = MY_MIN(out_max_x, out_width_act); out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); out_width = out_max_x - out_x; out_height = out_max_y - out_y; out_wptr = (word32 *)vptr; rectptr->x = out_x; rectptr->y = out_y; rectptr->width = out_width; rectptr->height = out_height; alpha_mask = g_alpha_mask; for(i = 0; i < out_height; i++) { eff_y = out_y + i; pos_scale = kimage_ptr->scale_height[eff_y]; src_y = pos_scale >> 16; wptr = kimage_ptr->wptr + (src_y * a2_width_full); out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; for(j = 0; j < out_width; j++) { new_x = j + out_x; pos_scale = kimage_ptr->scale_width[new_x]; pos = pos_scale >> 16; *out_wptr++ = wptr[pos] | alpha_mask; } } rectptr->width = kimage_ptr->x_width - rectptr->x; return 1; } int video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr) { word32 *out_wptr, *wptr; dword64 dval0a, dval0b, dval1a, dval1b, dscale, dscale_y, dval; word32 new_val, pos_scale, alpha_mask; int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; int i, j; if((kimage_ptr->scale_width_a2_to_x >= 0x34000) || (kimage_ptr->scale_height_a2_to_x >= 0x34000)) { return video_out_data_intscaled(vptr, kimage_ptr, out_width_act, rectptr); } x = rectptr->x; y = rectptr->y; max_x = rectptr->width + x; max_y = rectptr->height + y; max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); x = MY_MAX(0, x - 1); y = MY_MAX(0, y - 1); a2_width_full = kimage_ptr->a2_width_full; out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; out_max_x = MY_MIN(out_max_x, out_width_act); out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); out_width = out_max_x - out_x; out_height = out_max_y - out_y; #if 0 printf("scaled: in %d,%d %d,%d becomes %d,%d %d,%d\n", x, y, width, height, out_x, out_y, out_width, out_height); #endif out_wptr = (word32 *)vptr; rectptr->x = out_x; rectptr->y = out_y; rectptr->width = out_width; rectptr->height = out_height; alpha_mask = g_alpha_mask; for(i = 0; i < out_height; i++) { eff_y = out_y + i; pos_scale = kimage_ptr->scale_height[eff_y]; src_y = pos_scale >> 16; dscale_y = (pos_scale & 0xffff) >> 8; wptr = kimage_ptr->wptr + (src_y * a2_width_full); out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; for(j = 0; j < out_width; j++) { new_x = j + out_x; pos_scale = kimage_ptr->scale_width[new_x]; pos = pos_scale >> 16; dscale = (pos_scale & 0xffff) >> 8; dval0a = wptr[pos]; dval0a = (dval0a & 0x00ff00ffULL) | ((dval0a & 0xff00ff00ULL) << 24); dval0b = wptr[pos + 1]; dval0b = (dval0b & 0x00ff00ffULL) | ((dval0b & 0xff00ff00ULL) << 24); dval1a = wptr[pos + a2_width_full]; dval1a = (dval1a & 0x00ff00ffULL) | ((dval1a & 0xff00ff00ULL) << 24); dval1b = wptr[pos + 1 + a2_width_full]; dval1b = (dval1b & 0x00ff00ffULL) | ((dval1b & 0xff00ff00ULL) << 24); dval0a = ((0x100 - dscale) * dval0a) + (dscale * dval0b); dval1a = ((0x100 - dscale) * dval1a) + (dscale * dval1b); dval0a = (dval0a >> 8) & 0x00ff00ff00ff00ffULL; dval1a = (dval1a >> 8) & 0x00ff00ff00ff00ffULL; dval = ((0x100 - dscale_y) * dval0a) + (dscale_y * dval1a); new_val = ((dval >> 8) & 0x00ff00ffULL) | ((dval >> 32) & 0xff00ff00ULL); *out_wptr++ = new_val | alpha_mask; #if 0 if((pos == 300) && (eff_y == 100)) { printf("x:%d pos:%d %08x. %016llx,%016llx " "pos_sc:%08x, %08x\n", new_x, pos, new_val, dval0a, dval0b, pos_scale, wptr[pos]); } #endif } } rectptr->width = kimage_ptr->x_width - rectptr->x; #if 0 for(i = 0; i < kimage_ptr->x_height; i++) { out_wptr = ((word32 *)vptr) + (i * out_width_act) + kimage_ptr->x_width - 1; *out_wptr = 0x00ff00ff; # if 0 for(j = 0; j < 10; j++) { if(*out_wptr != 0) { printf("out_wptr:%p is %08x at %d,%d\n", out_wptr, *out_wptr, out_width_act - 1 - j, i); } out_wptr--; } # endif } #endif return 1; } word32 video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv) { word32 frac, frac_to_next, new_frac; frac = pos * frac_inc; if(frac >= max) { return max; // Clear frac bits } if(g_video_scale_algorithm == 2) { return frac & -65536; // nearest neighbor } if(g_video_scale_algorithm == 1) { return frac; // bilinear interp } // Do proper scaling. fraction=0 means 100% this pixel, fraction=ffff // means 99.99% the next pixel frac_to_next = frac_inc + (frac & 0xffff); if(frac_to_next < 65536) { frac_to_next = 0; } frac_to_next = (frac_to_next & 0xffff) * frac_inc_inv; frac_to_next = frac_to_next >> 16; new_frac = (frac & -65536) | (frac_to_next & 0xffff); #if 0 if((frac >= (30 << 16)) && (frac < (38 << 16))) { printf("scale %d (%02x) -> %08x (was %08x) %08x %08x\n", pos, pos, new_frac, frac, frac_inc, frac_inc_inv); } #endif return new_frac; } void video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, int must_update) { word32 frac_inc, frac_inc_inv, new_frac, max; int a2_width, a2_height, exp_width, exp_height; int i; out_width = video_clamp(out_width, 1, kimage_ptr->x_max_width); out_width = video_clamp(out_width, 1, MAX_SCALE_SIZE); out_height = video_clamp(out_height, 1, kimage_ptr->x_max_height); out_height = video_clamp(out_height, 1, MAX_SCALE_SIZE); a2_width = kimage_ptr->a2_width; a2_height = kimage_ptr->a2_height; kimage_ptr->vbl_of_last_resize = g_vbl_count; // Handle aspect ratio. Calculate height/width based on the other's // aspect ratio, and pick the smaller value exp_width = (a2_width * out_height) / a2_height; exp_height = (a2_height * out_width) / a2_width; if(exp_width < a2_width) { exp_width = a2_width; } if(exp_height < a2_height) { exp_height = a2_height; } if(exp_width < out_width) { // Allow off-by-one to be OK, so window doesn't keep resizing if((exp_width + 1) != out_width) { out_width = exp_width; } } if(exp_height < out_height) { if((exp_height + 1) != out_height) { out_height = exp_height; } } if(out_width <= 0) { out_width = 1; } if(out_height <= 0) { out_height = 1; } // See if anything changed. If it's unchanged, don't do anything if((kimage_ptr->x_width == out_width) && !must_update && (kimage_ptr->x_height == out_height)) { return; } kimage_ptr->x_width = out_width; kimage_ptr->x_height = out_height; kimage_ptr->x_refresh_needed = 1; if(kimage_ptr == &g_mainwin_kimage) { g_mainwin_width = out_width; g_mainwin_height = out_height; //printf("Set g_mainwin_width=%d, g_mainwin_height=%d\n", // out_width, out_height); } // the per-pixel inc = a2_width / out_width. Scale by 65536 frac_inc = (a2_width * 65536UL) / out_width; kimage_ptr->scale_width_to_a2 = frac_inc; frac_inc_inv = (out_width * 65536UL) / a2_width; kimage_ptr->scale_width_a2_to_x = frac_inc_inv; #if 0 printf("scale_width_to_a2: %08x, a2_to_x:%08x, is_debugwin:%d\n", kimage_ptr->scale_width_to_a2, kimage_ptr->scale_width_a2_to_x, (kimage_ptr == &g_debugwin_kimage)); #endif max = (a2_width - 1) << 16; for(i = 0; i < out_width + 1; i++) { new_frac = video_scale_calc_frac(i, max, frac_inc, frac_inc_inv); kimage_ptr->scale_width[i] = new_frac; } frac_inc = (a2_height * 65536UL) / out_height; kimage_ptr->scale_height_to_a2 = frac_inc; frac_inc_inv = (out_height * 65536UL) / a2_height; kimage_ptr->scale_height_a2_to_x = frac_inc_inv; #if 0 printf("scale_height_to_a2: %08x, a2_to_x:%08x. w:%d h:%d\n", kimage_ptr->scale_height_to_a2, kimage_ptr->scale_height_a2_to_x, out_width, out_height); #endif max = (a2_height - 1) << 16; for(i = 0; i < out_height + 1; i++) { new_frac = video_scale_calc_frac(i, max, frac_inc, frac_inc_inv); kimage_ptr->scale_height[i] = new_frac; } } int video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width) { int x; // raw_x is in output coordinates. Scale down to a2 coordinates if(x_width == 0) { x = (kimage_ptr->scale_width_to_a2 * raw_x) / 65536; } else { // Scale raw_x using x_width x = (raw_x * kimage_ptr->a2_width_full) / x_width; } x = x - BASE_MARGIN_LEFT; return x; } int video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height) { int y; // raw_y is in output coordinates. Scale down to a2 coordinates if(y_height == 0) { y = (kimage_ptr->scale_height_to_a2 * raw_y) / 65536; } else { // Scale raw_y using y_height y = (raw_y * kimage_ptr->a2_height) / y_height; } y = y - BASE_MARGIN_TOP; return y; } int video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width) { int x; // Convert a2_x to output coordinates x = a2_x + BASE_MARGIN_LEFT; if(x_width == 0) { x = (kimage_ptr->scale_width_a2_to_x * x) / 65536; } else { // Scale a2_x using x_width x = (x * x_width) / kimage_ptr->a2_width_full; } return x; } int video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height) { int y; // Convert a2_y to output coordinates y = a2_y + BASE_MARGIN_TOP; if(y_height == 0) { y = (kimage_ptr->scale_height_a2_to_x * y) / 65536; } else { // Scale a2_y using y_height y = (y * y_height) / kimage_ptr->a2_height; } return y; } void video_update_color_raw(int bank, int col_num, int a2_color) { word32 tmp; int red, green, blue, newred, newgreen, newblue; if(col_num >= 256 || col_num < 0) { halt_printf("video_update_color_raw: col: %03x\n", col_num); return; } red = (a2_color >> 8) & 0xf; green = (a2_color >> 4) & 0xf; blue = (a2_color) & 0xf; red = ((red << 4) + red); green = ((green << 4) + green); blue = ((blue << 4) + blue); newred = red >> g_red_right_shift; newgreen = green >> g_green_right_shift; newblue = blue >> g_blue_right_shift; tmp = ((newred & g_red_mask) << g_red_left_shift) + ((newgreen & g_green_mask) << g_green_left_shift) + ((newblue & g_blue_mask) << g_blue_left_shift); g_palette_8to1624[bank][col_num] = tmp; } void video_update_status_line(int line, const char *string) { byte a2_str_buf[STATUS_LINE_LENGTH+1]; word32 *wptr; char *buf; const char *ptr; word32 line_bytes; int start_line, c, pixels_per_line, offset; int i; if(line >= MAX_STATUS_LINES || line < 0) { printf("update_status_line: line: %d!\n", line); exit(1); } ptr = string; buf = &(g_status_buf[line][0]); g_status_ptrs[line] = buf; for(i = 0; i < STATUS_LINE_LENGTH; i++) { if(*ptr) { c = *ptr++; } else { c = ' '; } buf[i] = c; a2_str_buf[i] = c | 0x80; } buf[STATUS_LINE_LENGTH] = 0; a2_str_buf[STATUS_LINE_LENGTH] = 0; start_line = (200 + 2*8) + line*8; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top); wptr = g_mainwin_kimage.wptr; wptr += offset; for(i = 0; i < 8; i++) { line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; redraw_changed_string(&(a2_str_buf[0]), line_bytes, -1L, wptr, 0, 0x00ffffff, pixels_per_line, 1); } // Don't add rectangle here, video_form_change_rects will do it //video_add_a2_rect(start_line, start_line + 8, 0, 640); } void video_draw_a2_string(int line, const byte *bptr) { word32 *wptr; word32 line_bytes; int start_line, pixels_per_line, offset; int i; start_line = line*8; pixels_per_line = g_mainwin_kimage.a2_width_full; offset = (pixels_per_line * g_video_act_margin_top) + g_video_act_margin_left; wptr = g_mainwin_kimage.wptr; wptr += offset; for(i = 0; i < 8; i++) { line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; redraw_changed_string(bptr, line_bytes, -1L, wptr, 0, 0x00ffffff, pixels_per_line, 1); } g_mainwin_kimage.x_refresh_needed = 1; } void video_show_debug_info() { word32 tmp1; printf("g_cur_dfcyc: %016llx, last_vbl: %016llx\n", g_cur_dfcyc, g_last_vbl_dfcyc); tmp1 = get_lines_since_vbl(g_cur_dfcyc); printf("lines since vbl: %06x\n", tmp1); printf("Last line updated: %d\n", g_vid_update_last_line); } word32 read_video_data(dword64 dfcyc) { word32 val, val2; int lines_since_vbl, line; // Return Charrom data at $C02C for SuperConvert 4 TDM mode lines_since_vbl = get_lines_since_vbl(dfcyc); val = float_bus_lines(dfcyc, lines_since_vbl); line = lines_since_vbl >> 8; if(line < 192) { // Always do the character ROM val2 = g_a2font_bits[val & 0xff][line & 7]; dbg_log_info(dfcyc, val, (lines_since_vbl << 8) | (val2 & 0xff), 0xc02c); val = ~val2; // Invert it, maybe } return val & 0xff; } word32 float_bus(dword64 dfcyc) { word32 lines_since_vbl; lines_since_vbl = get_lines_since_vbl(dfcyc); return float_bus_lines(dfcyc, lines_since_vbl); } word32 float_bus_lines(dword64 dfcyc, word32 lines_since_vbl) { word32 val; int line, eff_line, line24, all_stat, byte_offset; int hires, page2, addr; /* For floating bus, model hires style: Visible lines 0-191 are simply the */ /* data being displayed at that time. Lines 192-255 are lines 0 - 63 again */ /* and lines 256-261 are lines 58-63 again */ /* For each line, figure out starting byte at -25 mod 128 bytes from this */ /* line's start */ /* This emulates an Apple II style floating bus. A real IIgs does not */ /* drive anything meaningful during the 25 horizontal blanking cycles, */ /* nor during veritical blanking. The data seems to be 0 or related to */ /* the instruction fetches on a real IIgs during blankings */ line = lines_since_vbl >> 8; byte_offset = lines_since_vbl & 0xff; // byte offset is from 0 through 64, where the visible screen is drawn // from 25 through 64 eff_line = line; if(eff_line >= 0x100) { eff_line = (eff_line - 6) & 0xff; } if(byte_offset == 0) { byte_offset = 1; } all_stat = g_cur_a2_stat; hires = (all_stat & ALL_STAT_HIRES) && !(all_stat & ALL_STAT_TEXT); if((all_stat & ALL_STAT_MIX_T_GR) && (line >= 160)) { hires = 0; } page2 = EXTRU(all_stat, 31 - BIT_ALL_STAT_PAGE2, 1); if(all_stat & ALL_STAT_ST80) { page2 = 0; } line24 = (eff_line >> 3) & 0x1f; addr = g_screen_index[line24] & 0x3ff; addr = (addr & 0x380) + (((addr & 0x7f) - 25 + byte_offset) & 0x7f); if(hires) { addr = 0x2000 + addr + ((eff_line & 7) << 10) + (page2 << 13); } else { addr = 0x400 + addr + (page2 << 10); } val = g_slow_memory_ptr[addr]; #if 0 printf("For %04x (%d) addr=%04x, val=%02x, dfcyc:%016llx\n", lines_since_vbl, eff_line, addr, val, dfcyc - g_last_vbl_dfcyc); #endif dbg_log_info(dfcyc, ((lines_since_vbl >> 11) << 24) | (lines_since_vbl - 25), (addr << 8) | val, 0xff); return val; } ================================================ FILE: upstream/kegs/src/voc.c ================================================ const char rcsid_voc_c[] = "@(#)$KmKId: voc.c,v 1.12 2023-09-23 17:52:44+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // This file provides emulation of the Apple Video Overlay Card, which // will appear to be in slot 3 if g_voc_enable=1 (there's a config.c // setting to control enabling VOC). The only currently supported VOC // feature is the SHR interlaced display using both Main and Aux memory // to provide a 640x400 (or 320x400) pixel display. #include "defc.h" extern word32 g_c02b_val; extern int g_cur_a2_stat; extern word32 g_vbl_count; int g_voc_enable = 0; // Default to disabled for now word32 g_voc_reg1 = 0x09; word32 g_voc_reg3 = 0; word32 g_voc_reg4 = 0; word32 g_voc_reg5 = 0; word32 g_voc_reg6 = 0; word32 voc_devsel_read(word32 loc, dword64 dfcyc) { // Reads to $c0b0-$c0bf. loc = loc & 0xf; switch(loc) { case 0: // 0xc0b0 return voc_read_reg0(dfcyc); break; case 1: // 0xc0b1 return g_voc_reg1; break; case 3: // 0xc0b3 return g_voc_reg3; break; case 4: // 0xc0b4 return g_voc_reg4; break; case 5: // 0xc0b5 return g_voc_reg5; break; case 6: // 0xc0b6 return g_voc_reg6; break; case 7: // 0xc0b7, possible Uthernet 2 detection return 0x00; break; case 8: // 0xc0b8, Second Sight detection by jpeGS program return 0x00; // Second Sight returns 0x01 break; case 0xd: // 0xc0bd, A2OSX Uthernet 1 detection code return 0x00; break; } halt_printf("Tried to read: %04x\n", 0xc0b0 + loc); return 0; } void voc_devsel_write(word32 loc, word32 val, dword64 dfcyc) { // Writes to $c0b0-$c0bf. loc = loc & 0xf; switch(loc) { case 0: // 0xc0b0 // Write 0 to clear VBL interrupts if(val != 0) { halt_printf("VOC write %04x = %02x\n", loc, val); } return; break; case 1: // 0xc0b1 // bit 0: R/W: 1=GG Bus Enable // When 0, I think VOC ignores writes to $c023,etc. // bit 2: R/W: 0=OutChromaFilter enabled, 1=ChromaFilter disab // bit 2 is also TextMonoOver somehow using bit[5]==1 // bit 3: R/W: 1=MainPageLin // bits 5:4: R/W: 00=Aux mem; 01=Main Memory; 11=Interlaced // bit 6: R/W: 1=Enable VBL Interrupt // bit 7: R/W: 1=Enable Line interrupts if(!g_voc_enable) { val = 0; } if(val & 0xc0) { halt_printf("VOC write %04x = %02x\n", loc, val); } #if 0 if(val != g_voc_reg1) { printf("$c0b1:%02x (was %02x)\n", val, g_voc_reg1); } #endif g_voc_reg1 = val; voc_update_interlace(dfcyc); return; break; case 3: // 0xc0b3 // bits 2:0: R/W: Key Dissolve, 0=100% graphics, 7=100% video // bit 3: R/W: 1=Enhanced Dissolve enabled // bits 6:4: R/W: Non-Key Dissolve, 0=100% graphics, 7=100% vid // bit 7: R/W: 0=Output Setup Enabled, 1=Output Setup Disabled g_voc_reg3 = val; return; break; case 4: // 0xc0b4 // bits 3:0: R/W: KeyColor Blue // bits 7:4: R/W: KeyColor Green g_voc_reg4 = val; return; break; case 5: // 0xc0b5 // bits 3:0: R/W: KeyColor Red // bit 4: R/W: OutExtBlank: 0=Graphics, 1=External // bit 5: R/W: 0=GenLock enabled, 1=GenLock disabled // bit 6: R/W: 0=KeyColor enabled, 1=KeyColor disabled // bit 7: R/W: 1=Interlace mode enabled g_voc_reg5 = val; voc_update_interlace(dfcyc); return; break; case 6: // 0xc0b6 // Write 0 to cause AdjSave to occur // Write 8, then 9, then 8 again to cause AdjInc for Hue // Write a, then b, then a again to cause AdjDec for Hue // Write 4, then 5, then 4 again to cause AdjInc for Saturation // Write 6, then 7, then 6 again to cause AdjDec for Saturation // bit 3: hue, bit 2: saturation g_voc_reg6 = val; return; break; case 7: // 0xc0b7 // Written by System Disk 1.1 Desktop.sys to 0xfd, ignore if(val == 0xfd) { return; } break; case 0xa: case 0xb: // 0xc0ba,0xc0bb written to 0 by A2OSX Uthernet1 detect if(val == 0) { return; } break; } halt_printf("Unknown Write %04x = %02x %016llx\n", 0xc0b0 + loc, val, dfcyc); } void voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc) { // Writes to $c300-$c3ff halt_printf("Wrote VOC %04x = %02x %016llx\n", 0xc300 + (loc & 0xff), val, dfcyc); } void voc_reset() { g_voc_reg1 = 0x0d; // [0]: GG Bus enable, [3]:MainPageLin g_voc_reg3 = 0x07; g_voc_reg4 = 0; g_voc_reg5 = 0x40; g_voc_reg6 = 0; } double g_voc_last_pal_vbl = 0; word32 voc_read_reg0(dword64 dfcyc) { word32 frame, in_vbl; if(!g_voc_enable) { return 0; } // Reading $c0b0. // c0b0: bit 2: R/O: 1=In VBL // c0b0: bit 3: R/O: 0=No Video Detected, 1=Video Detected // c0b0: bit 4: R/O: 1=Video Genlocked // c0b0: bit 5: R/O: 0=showing Field 0, 1=showing Field 1 // c0b0: bit 6: R/O: 1=VBL Int Request pending // c0b0: bit 7: R/O: 1=Line Int Request pending in_vbl = in_vblank(dfcyc); dbg_log_info(dfcyc, 0, in_vbl, 0x1c0b0); frame = g_vbl_count & 1; return (frame << 5) | (in_vbl << 2); } void voc_update_interlace(dword64 dfcyc) { word32 new_stat, mask; new_stat = 0; if(((g_voc_reg1 & 0x30) == 0x30) && (g_voc_reg5 & 0x80)) { new_stat = ALL_STAT_VOC_INTERLACE; } if((g_voc_reg1 & 0x30) == 0x10) { // Draw SHR from mainmem new_stat = ALL_STAT_VOC_MAIN; } mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; if((g_cur_a2_stat ^ new_stat) & mask) { // Interlace mode has changed g_cur_a2_stat &= (~mask); g_cur_a2_stat |= new_stat; printf("Change VOC interlace mode: %08x\n", new_stat); change_display_mode(dfcyc); } } ================================================ FILE: upstream/kegs/src/win32snd_driver.c ================================================ const char rcsid_win32snd_driver_c[] = "@(#)$KmKId: win32snd_driver.c,v 1.12 2023-05-19 14:01:33+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2023 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Audio is sent every 1/60th of a second to win32_send_audio(). Play it // using waveOutWrite() as long as we've got at least 2/60th of a second // buffered--otherwise waveOutPause(). If we get more than 10 buffers // queued, drop buffers until we get down to 6 buffers queued again. // Track headers completed in g_wavehdr_rd_pos using callback function. #include "defc.h" #include "sound.h" #include #include extern int Verbose; extern int g_audio_rate; unsigned int __stdcall child_sound_loop_win32(void *param); void check_wave_error(int res, char *str); #define NUM_WAVE_HEADERS 32 HWAVEOUT g_wave_handle; WAVEHDR g_wavehdr[NUM_WAVE_HEADERS]; // Each header is for 1/60th of a second of sound (generally). Pause // until 2 headers are available, then unpause. Experimentally it appears // we keep about 5 headers (5/60th of a second = 80msec) ahead, which is // excellent latency extern int g_audio_enable; extern word32 *g_sound_shm_addr; extern int g_preferred_rate; int g_win32snd_buflen = 0x1000; int g_win32_snd_playing = 0; int g_win32_snd_to_drop = 0; word32 g_win32_snd_dropped = 0; volatile int g_wavehdr_rd_pos = 0; volatile int g_wavehdr_wr_pos = 0; void win32snd_init(word32 *shmaddr) { printf("win32snd_init\n"); child_sound_init_win32(); } void win32snd_shutdown() { /* hmm */ } void CALLBACK handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { LPWAVEHDR lpwavehdr; int pos; /* Only service "buffer done playing messages */ if(uMsg == WOM_DONE) { lpwavehdr = (LPWAVEHDR)dwParam1; if(lpwavehdr->dwFlags == (WHDR_DONE | WHDR_PREPARED)) { lpwavehdr->dwUser = FALSE; } pos = (int)(lpwavehdr - &g_wavehdr[0]); // printf("At %.3f, pos %d is done\n", get_dtime(), pos); if(pos == g_wavehdr_rd_pos) { pos = (pos + 1) % NUM_WAVE_HEADERS; g_wavehdr_rd_pos = pos; } else { printf("wavehdr %d finished, exp %d\n", pos, g_wavehdr_rd_pos); } } return; } void check_wave_error(int res, char *str) { char buf[256]; if(res == MMSYSERR_NOERROR) { return; } waveOutGetErrorText(res, &buf[0], sizeof(buf)); printf("%s: %s\n", str, buf); exit(1); } void child_sound_init_win32() { WAVEFORMATEX wavefmt; WAVEOUTCAPS caps; byte *bptr; UINT wave_id; int bits_per_sample, channels, block_align, blen, res; int i; memset(&wavefmt, 0, sizeof(WAVEFORMATEX)); wavefmt.wFormatTag = WAVE_FORMAT_PCM; bits_per_sample = 16; channels = 2; wavefmt.wBitsPerSample = bits_per_sample; wavefmt.nChannels = channels; wavefmt.nSamplesPerSec = g_preferred_rate; block_align = channels * (bits_per_sample / 8); wavefmt.nBlockAlign = block_align; wavefmt.nAvgBytesPerSec = block_align * g_audio_rate; res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, 0, 0, WAVE_FORMAT_QUERY); if(res != MMSYSERR_NOERROR) { printf("Cannot open audio device, res:%d, g_audio_rate:%d\n", res, g_preferred_rate); g_audio_enable = 0; return; } res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, (DWORD_PTR)handle_wav_snd, 0, CALLBACK_FUNCTION | WAVE_ALLOWSYNC); if(res != MMSYSERR_NOERROR) { printf("Cannot register audio\n"); g_audio_enable = 0; return; } g_audio_rate = wavefmt.nSamplesPerSec; blen = (((g_audio_rate * block_align) / 60) * 5) / 4; // Size buffer 25% larger than expected, to add some margin blen = (blen + 15) & -16L; g_win32snd_buflen = blen; bptr = malloc(blen * NUM_WAVE_HEADERS); if(bptr == NULL) { printf("Unabled to allocate sound buffer\n"); exit(1); } for(i = 0; i < NUM_WAVE_HEADERS; i++) { memset(&g_wavehdr[i], 0, sizeof(WAVEHDR)); g_wavehdr[i].dwUser = FALSE; g_wavehdr[i].lpData = (char *)&(bptr[i * blen]); g_wavehdr[i].dwBufferLength = blen; g_wavehdr[i].dwFlags = 0; g_wavehdr[i].dwLoops = 0; res = waveOutPrepareHeader(g_wave_handle, &g_wavehdr[i], sizeof(WAVEHDR)); check_wave_error(res, "waveOutPrepareHeader"); } res = waveOutGetID(g_wave_handle, &wave_id); res = waveOutGetDevCaps(wave_id, &caps, sizeof(caps)); check_wave_error(res, "waveOutGetDevCaps"); printf("Using %s, buflen:%d\n", caps.szPname, g_win32snd_buflen); printf(" Bits per Sample = %d. Channels = %d\n", wavefmt.wBitsPerSample, wavefmt.nChannels); printf(" Sampling rate = %d, avg_bytes_per_sec = %d\n", (int)wavefmt.nSamplesPerSec, (int)wavefmt.nAvgBytesPerSec); sound_set_audio_rate(g_audio_rate); } void win32snd_set_playing(int snd_playing) { g_win32_snd_playing = snd_playing; if(snd_playing) { waveOutRestart(g_wave_handle); #if 0 printf("win32 restarted sound wr:%d rd:%d\n", g_wavehdr_wr_pos, g_wavehdr_rd_pos); #endif } else { waveOutPause(g_wave_handle); #if 0 printf("win32 paused sound wr:%d rd:%d\n", g_wavehdr_wr_pos, g_wavehdr_rd_pos); #endif } } void win32_send_audio2(byte *ptr, int size) { int res, wr_pos, rd_pos, new_pos, bufs_in_use; wr_pos = g_wavehdr_wr_pos; rd_pos = g_wavehdr_rd_pos; #if 0 if(wr_pos == 0) { printf("send_audio2 wr:%d rd:%d sz:%d at %.3f\n", wr_pos, rd_pos, size, get_dtime()); } #endif if(g_wavehdr[wr_pos].dwUser != FALSE) { // Audio buffer busy...should not happen! printf("Audio buffer %d is busy!\n", wr_pos); return; } bufs_in_use = (NUM_WAVE_HEADERS + wr_pos - rd_pos) % NUM_WAVE_HEADERS; if(g_win32_snd_to_drop) { g_win32_snd_to_drop--; g_win32_snd_dropped += size; if((bufs_in_use < 4) && (g_win32_snd_to_drop != 0)) { #if 0 printf("bufs_in_use:%d, snd_to_drop:%d\n", bufs_in_use, g_win32_snd_to_drop); #endif g_win32_snd_to_drop = 0; } if(g_win32_snd_to_drop == 0) { printf("Dropped %d bytes of sound\n", g_win32_snd_dropped); } return; } #if 0 if(g_win32_snd_playing && (bufs_in_use <= 2)) { printf("bufs_in_use:%d, wr:%d, rd:%d\n", bufs_in_use, wr_pos, rd_pos); } #endif if(bufs_in_use == 0) { #if 0 printf("bufs_in_use:%d, wr_pos:%d rd_pos:%d\n", bufs_in_use, wr_pos, rd_pos); #endif // We've underflowed, so pause sound until we get some buffered win32snd_set_playing(0); } else if(g_win32_snd_playing == 0) { if(bufs_in_use >= 2) { //printf("bufs_in_use:%d, will start\n", bufs_in_use); win32snd_set_playing(1); } } else { if(bufs_in_use >= 14) { // About 230msec // Drop 6 buffers to get us back down to 100msec delay printf("bufs_in_use:%d, wr:%d will drop 6\n", bufs_in_use, wr_pos); g_win32_snd_to_drop = 6; g_win32_snd_dropped = 0; } } memcpy(g_wavehdr[wr_pos].lpData, ptr, size); g_wavehdr[wr_pos].dwBufferLength = size; g_wavehdr[wr_pos].dwUser = TRUE; new_pos = (wr_pos + 1) % NUM_WAVE_HEADERS; g_wavehdr_wr_pos = new_pos; res = waveOutWrite(g_wave_handle, &g_wavehdr[wr_pos], sizeof(g_wavehdr)); check_wave_error(res, "waveOutWrite"); return; } int win32_send_audio(byte *ptr, int in_size) { int size; int tmpsize; // printf("send_audio %d bytes at %.3f\n", in_size, get_dtime()); size = in_size; while(size > 0) { tmpsize = size; if(size > g_win32snd_buflen) { tmpsize = g_win32snd_buflen; } win32_send_audio2(ptr, tmpsize); ptr += tmpsize; if(size != tmpsize) { #if 0 printf("Orig size:%d, reduced to %d\n", in_size, tmpsize); #endif } size = size - tmpsize; } return in_size; } ================================================ FILE: upstream/kegs/src/win_dirent.h ================================================ #ifdef INCLUDE_RCSID_C const char rcsid_kegswin_dirent_h[] = "@(#)$KmKId: win_dirent.h,v 1.2 2022-02-11 04:13:45+00 kentd Exp $"; #endif /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2022 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Hacky defines to get something to compile for now typedef unsigned short mode_t; struct dirent { char d_name[1024]; }; struct DIR_t { int find_data_valid; void *win_handle; void *find_data_ptr; struct dirent dirent; }; typedef struct DIR_t DIR; DIR *opendir(const char *filename); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); ================================================ FILE: upstream/kegs/src/windriver.c ================================================ const char rcsid_windriver_c[] = "@(#)$KmKId: windriver.c,v 1.26 2024-09-15 13:55:35+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2024 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Based on code from Chea Chee Keong from KEGS32, which was available at // http://www.geocities.com/akilgard/kegs32 (geocities is gone now) #define WIN32_LEAN_AND_MEAN /* Tell windows we want less header gunk */ #define STRICT /* Tell Windows we want compile type checks */ #include #include #include #include #include #include /* For _get_osfhandle */ #include "defc.h" #include "win_dirent.h" extern int Verbose; typedef struct windowinfo { HWND win_hwnd; HDC win_dc; HDC win_cdc; BITMAPINFO *win_bmapinfo_ptr; BITMAPINFOHEADER *win_bmaphdr_ptr; HBITMAP win_dev_handle; Kimage *kimage_ptr; // KEGS Image pointer for window content char *name_str; byte *data_ptr; int motion; int mdepth; int active; int pixels_per_line; int x_xpos; int x_ypos; int width; int height; int extra_width; int extra_height; } Window_info; #include "protos_windriver.h" Window_info g_mainwin_info = { 0 }; Window_info g_debugwin_info = { 0 }; int g_win_max_width = 0; int g_win_max_height = 0; int g_num_a2_keycodes = 0; int g_win_button_states = 0; int g_win_hide_pointer = 0; int g_win_warp_pointer = 0; int g_win_warp_x = 0; int g_win_warp_y = 0; /* this table is used to search for the Windows VK_* in col 1 or 2 */ /* flags bit 8 is or'ed into the VK, so we can distinguish keypad keys */ /* regardless of numlock */ int g_a2_key_to_wsym[][3] = { { 0x35, VK_ESCAPE, 0 }, { 0x7a, VK_F1, 0 }, { 0x78, VK_F2, 0 }, { 0x63, VK_F3, 0 }, { 0x76, VK_F4, 0 }, { 0x60, VK_F5, 0 }, { 0x61, VK_F6, 0 }, { 0x62, VK_F7, 0 }, { 0x64, VK_F8, 0 }, { 0x65, VK_F9, 0 }, { 0x6d, VK_F10, 0 }, { 0x67, VK_F11, 0 }, { 0x6f, VK_F12, 0 }, { 0x69, VK_F13, 0 }, { 0x6b, VK_F14, 0 }, { 0x71, VK_F15, 0 }, { 0x7f, VK_PAUSE, VK_CANCEL+0x100 }, // Reset { 0x12, '1', 0 }, { 0x13, '2', 0 }, { 0x14, '3', 0 }, { 0x15, '4', 0 }, { 0x17, '5', 0 }, { 0x16, '6', 0 }, { 0x1a, '7', 0 }, { 0x1c, '8', 0 }, { 0x19, '9', 0 }, { 0x1d, '0', 0 }, { 0x1b, 0xbd, 0 }, /* '-' */ { 0x18, 0xbb, 0 }, /* '=' */ { 0x33, VK_BACK, 0 }, /* backspace */ { 0x72, VK_INSERT+0x100, 0 }, /* Insert key */ { 0x74, VK_PRIOR+0x100, 0 }, /* pageup */ { 0x47, VK_NUMLOCK, VK_NUMLOCK+0x100 }, /* clear */ { 0x51, VK_HOME+0x100, 0 }, /* KP_equal is HOME key */ { 0x4b, VK_DIVIDE, VK_DIVIDE+0x100 }, // KP / { 0x43, VK_MULTIPLY, VK_MULTIPLY+0x100 }, // KP * { 0x30, VK_TAB, 0 }, { 0x32, 0xc0, 0 }, /* '`' */ { 0x0c, 'Q', 0 }, { 0x0d, 'W', 0 }, { 0x0e, 'E', 0 }, { 0x0f, 'R', 0 }, { 0x11, 'T', 0 }, { 0x10, 'Y', 0 }, { 0x20, 'U', 0 }, { 0x22, 'I', 0 }, { 0x1f, 'O', 0 }, { 0x23, 'P', 0 }, { 0x21, 0xdb, 0 }, /* [ */ { 0x1e, 0xdd, 0 }, /* ] */ { 0x2a, 0xdc, 0 }, /* backslash, bar */ { 0x75, VK_DELETE+0x100, 0 }, { 0x77, VK_END+0x100, VK_END }, { 0x79, VK_NEXT+0x100, 0 }, { 0x59, VK_NUMPAD7, VK_HOME }, { 0x5b, VK_NUMPAD8, VK_UP }, { 0x5c, VK_NUMPAD9, VK_PRIOR }, { 0x4e, VK_SUBTRACT, VK_SUBTRACT+0x100 }, { 0x39, VK_CAPITAL, 0 }, // Capslock { 0x00, 'A', 0 }, { 0x01, 'S', 0 }, { 0x02, 'D', 0 }, { 0x03, 'F', 0 }, { 0x05, 'G', 0 }, { 0x04, 'H', 0 }, { 0x26, 'J', 0 }, { 0x28, 'K', 0 }, { 0x25, 'L', 0 }, { 0x29, 0xba, 0 }, /* ; */ { 0x27, 0xde, 0 }, /* single quote */ { 0x24, VK_RETURN, 0 }, { 0x56, VK_NUMPAD4, VK_LEFT }, { 0x57, VK_NUMPAD5, VK_CLEAR }, { 0x58, VK_NUMPAD6, VK_RIGHT }, { 0x45, VK_ADD, 0 }, { 0x38, VK_SHIFT, 0 }, { 0x06, 'Z', 0 }, { 0x07, 'X', 0 }, { 0x08, 'C', 0 }, { 0x09, 'V', 0 }, { 0x0b, 'B', 0 }, { 0x2d, 'N', 0 }, { 0x2e, 'M', 0 }, { 0x2b, 0xbc, 0 }, /* , */ { 0x2f, 0xbe, 0 }, /* . */ { 0x2c, 0xbf, 0 }, /* / */ { 0x3e, VK_UP+0x100, 0 }, { 0x53, VK_NUMPAD1, VK_END }, { 0x54, VK_NUMPAD2, VK_DOWN }, { 0x55, VK_NUMPAD3, VK_NEXT }, { 0x36, VK_CONTROL, VK_CONTROL+0x100 }, { 0x37, VK_SCROLL, VK_MENU+0x100 }, // Command=scr_lock or alt-r { 0x3a, VK_SNAPSHOT+0x100, VK_MENU }, // Opt=prntscrn or alt-l { 0x31, ' ', 0 }, { 0x3b, VK_LEFT+0x100, 0 }, { 0x3d, VK_DOWN+0x100, 0 }, { 0x3c, VK_RIGHT+0x100, 0 }, { 0x52, VK_NUMPAD0, VK_INSERT }, { 0x41, VK_DECIMAL, VK_DELETE }, { 0x4c, VK_RETURN+0x100, 0 }, { -1, -1, -1 } }; #if 0 int win_nonblock_read_stdin(int fd, char *bufptr, int len) { HANDLE oshandle; DWORD dwret; int ret; errno = EAGAIN; oshandle = (HANDLE)_get_osfhandle(fd); // get stdin handle dwret = WaitForSingleObject(oshandle, 1); // wait 1msec for data ret = -1; if(dwret == WAIT_OBJECT_0) { ret = read(fd, bufptr, len); } return ret; } #endif Window_info * win_find_win_info_ptr(HWND hwnd) { if(hwnd == g_mainwin_info.win_hwnd) { return &g_mainwin_info; } if(hwnd == g_debugwin_info.win_hwnd) { return &g_debugwin_info; } return 0; } void win_hide_pointer(Window_info *win_info_ptr, int do_hide) { ShowCursor(!do_hide); // printf("Doing ShowCursor(%d)\n", !do_hide); } int win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid) { Kimage *kimage_ptr; int buttons_changed, x, y; kimage_ptr = win_info_ptr->kimage_ptr; x = video_scale_mouse_x(kimage_ptr, raw_x, 0); y = video_scale_mouse_y(kimage_ptr, raw_y, 0); // printf("wum: %d,%d -> %d,%d\n", raw_x, raw_y, x, y); buttons_changed = ((g_win_button_states & buttons_valid) != button_states); g_win_button_states = (g_win_button_states & ~buttons_valid) | (button_states & buttons_valid); if(g_win_warp_pointer && (raw_x == g_win_warp_x) && (raw_y == g_win_warp_y) && (!buttons_changed) ) { /* tell adb routs to recenter but ignore this motion */ adb_update_mouse(kimage_ptr, x, y, 0, -1); return 0; } return adb_update_mouse(kimage_ptr, x, y, button_states, buttons_valid & 7); } void win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; word32 flags; int buttons, x, y, hide, warp; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } flags = (word32)wParam; x = LOWORD(lParam); y = HIWORD(lParam); buttons = (flags & 1) | (((flags >> 1) & 1) << 2) | (((flags >> 4) & 1) << 1); #if 0 printf("Mouse at %d, %d fl: %08x, but: %d\n", x, y, flags, buttons); #endif win_info_ptr->motion |= win_update_mouse(win_info_ptr, x, y, buttons, 7); hide = 0; warp = 0; hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); if(warp != g_win_warp_pointer) { win_info_ptr->motion = 1; } g_win_warp_pointer = warp; if(g_win_hide_pointer != hide) { win_hide_pointer(win_info_ptr, hide); } g_win_hide_pointer = hide; } void win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down) { Window_info *win_info_ptr; Kimage *kimage_ptr; word32 vk, raw_vk, flags, capslock_state; int a2code, is_up; int i; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } kimage_ptr = win_info_ptr->kimage_ptr; raw_vk = (word32)wParam; flags = HIWORD(lParam); #if 0 printf("win_event_key: raw:%04x lParam:%08x d:%d flags:%08x\n", raw_vk, (word32)lParam, down, flags); #endif if((flags & 0x4000) && down) { /* auto-repeating, just ignore it */ return; } vk = raw_vk + (flags & 0x100); #if 0 printf("Key event, vk=%04x, down:%d, repeat: %d, flags: %08x\n", vk, down, repeat, flags); #endif /* remap a few keys here.. sigh */ if((vk & 0xff) == VK_APPS) { /* remap to command */ vk = VK_MENU; } if((vk & 0xff) == VK_CAPITAL) { // Fix up capslock info: Windows gives us a down, then up event // when the capslock key itself is pressed and released. We // need to ask for the true toggle state instead capslock_state = (GetKeyState(VK_CAPITAL) & 1); down = capslock_state; } /* search a2key_to_wsym to find wsym in col 1 or 2 */ i = 0; is_up = !down; for(i = g_num_a2_keycodes-1; i >= 0; i--) { a2code = g_a2_key_to_wsym[i][0]; if((vk == g_a2_key_to_wsym[i][1]) || (vk == g_a2_key_to_wsym[i][2])) { vid_printf("Found vk:%04x = %02x\n", vk, a2code); adb_physical_key_update(kimage_ptr, a2code, 0, is_up); return; } } printf("VK: %04x unknown\n", vk); } void win_event_redraw(HWND hwnd) { Window_info *win_info_ptr; win_info_ptr = win_find_win_info_ptr(hwnd); if(win_info_ptr) { video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); } } void win_event_destroy(HWND hwnd) { Window_info *win_info_ptr; win_info_ptr = win_find_win_info_ptr(hwnd); if(win_info_ptr == 0) { return; } video_set_active(win_info_ptr->kimage_ptr, 0); win_info_ptr->active = 0; if(win_info_ptr == &g_mainwin_info) { my_exit(0); } else { ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); ReleaseDC(hwnd, win_info_ptr->win_dc); DeleteDC(win_info_ptr->win_cdc); DeleteObject(win_info_ptr->win_dev_handle); GlobalFree(win_info_ptr->win_bmapinfo_ptr); win_info_ptr->win_hwnd = 0; win_info_ptr->data_ptr = 0; } } void win_event_move(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; int x_xpos, x_ypos; // These WM_MOVE events indicate the window is being moved win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } // printf("WM_MOVE: %04x %08x\n", (word32)wParam, (word32)lParam); x_xpos = lParam & 0xffff; x_ypos = (lParam >> 16) & 0xffff; video_update_xpos_ypos(win_info_ptr->kimage_ptr, x_xpos, x_ypos); } void win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam) { Window_info *win_info_ptr; int width, height; // These WM_SIZE events indicate the window is being resized win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } // printf("WM_SIZE: %04x %08x\n", (word32)wParam, (word32)lParam); width = lParam & 0xffff; height = (lParam >> 16) & 0xffff; video_update_scale(win_info_ptr->kimage_ptr, width, height, 0); #if 0 printf("Frac width: %f\n", win_info_ptr->kimage_ptr->scale_width_a2_to_x / 65536.0); #endif // The following try to do "live updating" of the resize win_info_ptr->kimage_ptr->x_refresh_needed = 1; x_update_display(win_info_ptr); } void win_event_minmaxinfo(HWND hwnd, LPARAM lParam) { Window_info *win_info_ptr; MINMAXINFO *minmax_ptr; int a2_width, a2_height; // Windows sends WM_GETMINMAXINFO events when resizing is occurring, // and we can modify the *lParam MINMAXINFO structure to set the // minimum and maximum Track size (the size of the window) // This code forces the minimum to be the A2 window size, and the // maximum to be the screen size win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } minmax_ptr = (MINMAXINFO *)lParam; #if 0 printf("MinMax: mintrack.x:%d, mintrack.y:%d\n", minmax_ptr->ptMinTrackSize.x, minmax_ptr->ptMinTrackSize.y); #endif a2_width = video_get_a2_width(win_info_ptr->kimage_ptr); a2_height = video_get_a2_height(win_info_ptr->kimage_ptr); minmax_ptr->ptMinTrackSize.x = a2_width + win_info_ptr->extra_width; minmax_ptr->ptMinTrackSize.y = a2_height + win_info_ptr->extra_height; minmax_ptr->ptMaxTrackSize.x = g_win_max_width + win_info_ptr->extra_width; minmax_ptr->ptMaxTrackSize.y = g_win_max_height + win_info_ptr->extra_height; } void win_event_focus(HWND hwnd, int gain_focus) { Window_info *win_info_ptr; word32 c025_val, info; win_info_ptr = win_find_win_info_ptr(hwnd); if(!win_info_ptr) { return; } if(gain_focus) { // printf("Got focus on %p\n", hwnd); // Get shift, ctrl, capslock state c025_val = 0; info = GetKeyState(VK_SHIFT); // left or right if(info & 0x8000) { c025_val |= 1; // Shift key is down } info = GetKeyState(VK_CONTROL); // left or right if(info & 0x8000) { c025_val |= 2; } info = GetKeyState(VK_CAPITAL); // Capslock? if(info & 1) { c025_val |= 4; // Capslock key is down } //printf("Calling update_c025 with %03x\n", c025_val); adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); } else { // printf("Lost focus on %p\n", hwnd); } if(win_info_ptr == &g_mainwin_info) { adb_kbd_repeat_off(); adb_mainwin_focus(gain_focus); } } LRESULT CALLBACK win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { #if 0 printf("Message: umsg: %04x, wparam:%04x lParam:%08x\n", umsg, (word32)wParam, (word32)lParam); #endif switch(umsg) { case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: win_event_mouse(hwnd, wParam, lParam); return 0; case WM_KEYUP: case WM_SYSKEYUP: win_event_key(hwnd, wParam, lParam, 0); return 0; case WM_KEYDOWN: case WM_SYSKEYDOWN: win_event_key(hwnd, wParam, lParam, 1); return 0; case WM_SYSCOMMAND: // Alt key press can cause this. Return 0 for SC_KEYMENU if(wParam == SC_KEYMENU) { return 0; } break; case WM_KILLFOCUS: win_event_focus(hwnd, 0); break; case WM_SETFOCUS: win_event_focus(hwnd, 1); break; case WM_DESTROY: win_event_destroy(hwnd); return 0; case WM_PAINT: win_event_redraw(hwnd); break; case WM_MOVE: win_event_move(hwnd, wParam, lParam); break; case WM_SIZE: win_event_size(hwnd, wParam, lParam); break; case WM_GETMINMAXINFO: win_event_minmaxinfo(hwnd, lParam); break; } #if 0 switch(umsg) { HANDLE_MSG(hwnd, WM_KEYUP, win_event_key); HANDLE_MSG(hwnd, WM_KEYDOWN, win_event_key); HANDLE_MSG(hwnd, WM_SYSKEYUP, win_event_key); HANDLE_MSG(hwnd, WM_SYSKEYDOWN, win_event_key); HANDLE_MSG(hwnd, WM_DESTROY, win_event_destroy); } #endif #if 0 switch(umsg) { case WM_NCACTIVATE: case WM_NCHITTEST: case WM_NCMOUSEMOVE: case WM_SETCURSOR: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_CONTEXTMENU: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_PAINT: break; default: printf("Got umsg2: %d\n", umsg); } #endif return DefWindowProc(hwnd, umsg, wParam, lParam); } int main(int argc, char **argv) { int ret, mdepth; ret = parse_argv(argc, argv, 1); if(ret) { printf("parse_argv ret: %d, stopping\n", ret); exit(1); } mdepth = 32; video_set_blue_mask(0x0000ff); video_set_green_mask(0x00ff00); video_set_red_mask(0xff0000); g_win_max_width = GetSystemMetrics(SM_CXSCREEN); g_win_max_height = GetSystemMetrics(SM_CYSCREEN); vid_printf("g_win_max_width:%d, g_win_max_height:%d\n", g_win_max_width, g_win_max_height); ret = kegs_init(mdepth, g_win_max_width, g_win_max_height, 0); printf("kegs_init done\n"); if(ret) { printf("kegs_init ret: %d, stopping\n", ret); exit(1); } win_video_init(mdepth); printf("Entering main loop!\n"); fflush(stdout); while(1) { ret = run_16ms(); if(ret != 0) { printf("run_16ms returned: %d\n", ret); break; } x_update_display(&g_mainwin_info); x_update_display(&g_debugwin_info); check_input_events(); } xdriver_end(); exit(0); } void check_input_events() { MSG msg; POINT pt; BOOL ret; Window_info *win_info_ptr; while(PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) { if(GetMessage(&msg, 0, 0, 0) > 0) { //TranslateMessage(&msg); DispatchMessage(&msg); } else { printf("GetMessage returned <= 0\n"); my_exit(2); } } win_info_ptr = &g_mainwin_info; if(win_info_ptr->motion == 0) { return; } // ONLY look at g_mainwin_info! win_info_ptr->motion = 0; if(g_win_warp_pointer) { /* move mouse to center of screen */ g_win_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, BASE_MARGIN_LEFT + (A2_WINDOW_WIDTH/2), 0); g_win_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); pt.x = g_win_warp_x; pt.y = g_win_warp_y; ClientToScreen(win_info_ptr->win_hwnd, &pt); ret = SetCursorPos(pt.x, pt.y); #if 0 printf("Did SetCursorPos(%d, %d) warp_x:%d,y:%d, ret:%d\n", pt.x, pt.y, g_win_warp_x, g_win_warp_y, ret); #endif } return; } void win_video_init(int mdepth) { WNDCLASS wndclass; int a2code; int i; video_set_palette(); g_num_a2_keycodes = 0; for(i = 0; i < 0x7f; i++) { a2code = g_a2_key_to_wsym[i][0]; if(a2code < 0) { g_num_a2_keycodes = i; break; } } wndclass.style = 0; wndclass.lpfnWndProc = (WNDPROC)win_event_handler; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = GetModuleHandle(NULL); wndclass.hIcon = LoadIcon((HINSTANCE)NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "kegswin"; // Register the window if(!RegisterClass(&wndclass)) { printf("Registering window failed\n"); exit(1); } win_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS", mdepth); win_init_window(&g_debugwin_info, video_get_kimage(1), "KEGS Debugger", mdepth); win_create_window(&g_mainwin_info); } void win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, int mdepth) { int height, width, x_xpos, x_ypos; height = video_get_x_height(kimage_ptr); width = video_get_x_width(kimage_ptr); x_xpos = video_get_x_xpos(kimage_ptr); x_ypos = video_get_x_ypos(kimage_ptr); win_info_ptr->win_hwnd = 0; win_info_ptr->win_dc = 0; win_info_ptr->win_cdc = 0; win_info_ptr->win_bmapinfo_ptr = 0; win_info_ptr->win_bmaphdr_ptr = 0; win_info_ptr->win_dev_handle = 0; win_info_ptr->kimage_ptr = kimage_ptr; win_info_ptr->name_str = name_str; win_info_ptr->data_ptr = 0; win_info_ptr->motion = 0; win_info_ptr->mdepth = mdepth; win_info_ptr->active = 0; win_info_ptr->pixels_per_line = width; win_info_ptr->x_xpos = x_xpos; win_info_ptr->x_ypos = x_ypos; win_info_ptr->width = width; win_info_ptr->height = height; } void win_create_window(Window_info *win_info_ptr) { HWND win_hwnd; RECT rect; BITMAPINFO *bmapinfo_ptr; BITMAPINFOHEADER *bmaphdr_ptr; HBITMAP win_dev_handle; Kimage *kimage_ptr; int height, width, extra_width, extra_height; int extra_size, w_flags; kimage_ptr = win_info_ptr->kimage_ptr; height = win_info_ptr->height; width = win_info_ptr->width; printf("Got height: %d, width:%d\n", height, width); // We must call CreateWindow with a width,height that accounts for // the title bar and any other stuff. Use AdjustWindowRect() to // calculate this info for us w_flags = WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX; rect.left = 0; rect.top = 0; rect.right = width; rect.bottom = height; (void)AdjustWindowRect(&rect, w_flags, 0); extra_width = rect.right - rect.left - width; extra_height = rect.bottom - rect.top - height; win_info_ptr->extra_width = extra_width; win_info_ptr->extra_height = extra_height; win_hwnd = CreateWindow("kegswin", win_info_ptr->name_str, w_flags, win_info_ptr->x_xpos, win_info_ptr->x_ypos, width + extra_width, height + extra_height, NULL, NULL, GetModuleHandle(NULL), NULL); win_info_ptr->win_hwnd = win_hwnd; win_info_ptr->active = 0; video_set_active(kimage_ptr, 1); video_update_scale(kimage_ptr, win_info_ptr->width, win_info_ptr->height, 1); printf("win_hwnd = %p, height = %d\n", win_hwnd, height); GetWindowRect(win_hwnd, &rect); printf("...rect is: %ld, %ld, %ld, %ld\n", rect.left, rect.top, rect.right, rect.bottom); win_info_ptr->win_dc = GetDC(win_hwnd); SetTextColor(win_info_ptr->win_dc, 0); SetBkColor(win_info_ptr->win_dc, 0xffffff); win_info_ptr->win_cdc = CreateCompatibleDC(win_info_ptr->win_dc); printf("win_cdc: %p, win_dc:%p\n", win_info_ptr->win_cdc, win_info_ptr->win_dc); printf("Getting height, kimage_ptr:%p\n", kimage_ptr); fflush(stdout); win_info_ptr->data_ptr = 0; extra_size = sizeof(RGBQUAD); bmapinfo_ptr = (BITMAPINFO *)GlobalAlloc(GPTR, sizeof(BITMAPINFOHEADER) + extra_size); win_info_ptr->win_bmapinfo_ptr = bmapinfo_ptr; bmaphdr_ptr = (BITMAPINFOHEADER *)bmapinfo_ptr; win_info_ptr->win_bmaphdr_ptr = bmaphdr_ptr; bmaphdr_ptr->biSize = sizeof(BITMAPINFOHEADER); bmaphdr_ptr->biWidth = g_win_max_width; bmaphdr_ptr->biHeight = -g_win_max_height; bmaphdr_ptr->biPlanes = 1; bmaphdr_ptr->biBitCount = win_info_ptr->mdepth; bmaphdr_ptr->biCompression = BI_RGB; bmaphdr_ptr->biClrUsed = 0; /* Use g_bmapinfo_ptr, adjusting width, height */ printf("bmaphdr_ptr:%p\n", bmaphdr_ptr); printf("About to call CreateDIBSection, win_dc:%p\n", win_info_ptr->win_dc); fflush(stdout); win_dev_handle = CreateDIBSection(win_info_ptr->win_dc, win_info_ptr->win_bmapinfo_ptr, DIB_RGB_COLORS, (VOID **)&(win_info_ptr->data_ptr), NULL, 0); win_info_ptr->win_dev_handle = win_dev_handle; win_info_ptr->pixels_per_line = g_win_max_width; printf("kim: %p, dev:%p data: %p\n", kimage_ptr, win_dev_handle, win_info_ptr->data_ptr); fflush(stdout); } void xdriver_end() { printf("xdriver_end\n"); } void win_resize_window(Window_info *win_info_ptr) { RECT rect1, rect2; BOOL ret; Kimage *kimage_ptr; int x_width, x_height; kimage_ptr = win_info_ptr->kimage_ptr; x_width = video_get_x_width(kimage_ptr); x_height = video_get_x_height(kimage_ptr); // printf("win_resize_window, x_w:%d, x_h:%d\n", x_width, x_height); ret = GetWindowRect(win_info_ptr->win_hwnd, &rect1); ret = GetClientRect(win_info_ptr->win_hwnd, &rect2); #if 0 printf("window_rect: l:%d, t:%d, r:%d, b:%d\n", rect1.left, rect1.top, rect1.right, rect1.bottom); printf("client_rect: l:%d, t:%d, r:%d, b:%d\n", rect2.left, rect2.top, rect2.right, rect2.bottom); #endif ret = MoveWindow(win_info_ptr->win_hwnd, rect1.left, rect1.top, x_width + win_info_ptr->extra_width, x_height + win_info_ptr->extra_height, TRUE); // printf("MoveWindow ret:%d\n", ret); win_info_ptr->width = x_width; win_info_ptr->height = x_height; } void x_update_display(Window_info *win_info_ptr) { Change_rect rect; void *bitm_old; //POINT point; int valid, a2_active, x_active; int i; a2_active = video_get_active(win_info_ptr->kimage_ptr); x_active = win_info_ptr->active; if(x_active && !a2_active) { // We need to SW_HIDE this window ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); x_active = 0; win_info_ptr->active = x_active; } if(!x_active && a2_active) { // We need to SW_SHOWDEFAULT this window (and maybe create it) if(win_info_ptr->win_hwnd == 0) { win_create_window(win_info_ptr); } ShowWindow(win_info_ptr->win_hwnd, SW_SHOWDEFAULT); UpdateWindow(win_info_ptr->win_hwnd); x_active = 1; win_info_ptr->active = x_active; } if(x_active == 0) { return; } if(video_change_aspect_needed(win_info_ptr->kimage_ptr, win_info_ptr->width, win_info_ptr->height)) { win_resize_window(win_info_ptr); } for(i = 0; i < MAX_CHANGE_RECTS; i++) { valid = video_out_data(win_info_ptr->data_ptr, win_info_ptr->kimage_ptr, win_info_ptr->pixels_per_line, &rect, i); if(!valid) { break; } #if 0 point.x = 0; point.y = 0; ClientToScreen(win_info_ptr->win_hwnd, &point); #endif bitm_old = SelectObject(win_info_ptr->win_cdc, win_info_ptr->win_dev_handle); BitBlt(win_info_ptr->win_dc, rect.x, rect.y, rect.width, rect.height, win_info_ptr->win_cdc, rect.x, rect.y, SRCCOPY); SelectObject(win_info_ptr->win_cdc, bitm_old); } } void x_hide_pointer(int do_hide) { if(do_hide) { ShowCursor(0); } else { ShowCursor(1); } } int opendir_int(DIR *dirp, const char *in_filename) { HANDLE handle1; wchar_t *wcstr; char *filename; size_t ret_val; int buflen, len; int i; printf("opendir on %s\n", in_filename); len = (int)strlen(in_filename); buflen = len + 8; if(buflen >= sizeof(dirp->dirent.d_name)) { printf("buflen %d >= d_name %d\n", buflen, (int)sizeof(dirp->dirent.d_name)); return 1; } filename = &dirp->dirent.d_name[0]; memcpy(filename, in_filename, len + 1); while(len && (filename[len-1] == '/')) { filename[len - 1] = 0; len--; } cfg_strlcat(filename, "/*.*", buflen); for(i = 0; i < len; i++) { if(filename[i] == '/') { filename[i] = '\\'; } } len = (int)strlen(filename); wcstr = malloc(buflen * 2); (void)mbstowcs_s(&ret_val, wcstr, buflen, filename, _TRUNCATE); handle1 = FindFirstFileW(wcstr, dirp->find_data_ptr); dirp->win_handle = handle1; free(wcstr); if(handle1) { dirp->find_data_valid = 1; return 0; } return 1; } DIR * opendir(const char *in_filename) { DIR *dirp; int ret; dirp = calloc(1, sizeof(DIR)); if(!dirp) { return 0; } dirp->find_data_valid = 1; dirp->find_data_ptr = calloc(1, sizeof(WIN32_FIND_DATAW)); ret = 1; if(dirp->find_data_ptr) { ret = opendir_int(dirp, in_filename); } if(ret) { // Bad free(dirp->find_data_ptr); // free(0) is OK free(dirp); return 0; } return dirp; } struct dirent * readdir(DIR *dirp) { WIN32_FIND_DATAW *find_data_ptr; HANDLE handle1; size_t ret_val; BOOL ret; handle1 = dirp->win_handle; find_data_ptr = dirp->find_data_ptr; if(!handle1 || !find_data_ptr) { return 0; } ret = 1; if(!dirp->find_data_valid) { if(handle1) { find_data_ptr->cFileName[MAX_PATH-1] = 0; ret = FindNextFileW(handle1, find_data_ptr); } } dirp->find_data_valid = 0; if(!ret) { return 0; } (void)wcstombs_s(&ret_val, &(dirp->dirent.d_name[0]), (int)sizeof(dirp->dirent.d_name), &(find_data_ptr->cFileName[0]), _TRUNCATE); printf("Returning file %s\n", &(dirp->dirent.d_name[0])); return &(dirp->dirent);; } int closedir(DIR *dirp) { FindClose(dirp->win_handle); free(dirp->find_data_ptr); free(dirp); return 0; } int lstat(const char *path, struct stat *bufptr) { return stat(path, bufptr); } int ftruncate(int fd, word32 length) { HANDLE handle1; handle1 = (HANDLE)_get_osfhandle(fd); SetFilePointer(handle1, length, 0, FILE_BEGIN); SetEndOfFile(handle1); return 0; } ================================================ FILE: upstream/kegs/src/woz.c ================================================ const char rcsid_woz_c[] = "@(#)$KmKId: woz.c,v 1.34 2025-01-07 16:45:35+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ // Based on WOZ 2.0/1.0 spec at https://applesaucefdc.com/woz/reference2/ #include "defc.h" extern const char g_kegs_version_str[]; byte g_woz_hdr_bytes[12] = { 0x57, 0x4f, 0x5a, 0x32, // "WOZ2" 0xff, 0x0a, 0x0d, 0x0a, 0, 0, 0, 0 }; word32 g_woz_crc32_tab[256]; void woz_crc_init() { word32 crc, val, xor; int i, j; for(i = 0; i < 256; i++) { crc = 0; val = i; for(j = 0; j < 8; j++) { xor = 0; if((val ^ crc) & 1) { xor = 0xedb88320UL; } crc = (crc >> 1) ^ xor; val = val >> 1; } g_woz_crc32_tab[i] = crc; // printf("crc32_tab[%d] = %08x\n", i, crc); } } word32 woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip) { word32 crc, c; crc = (~0U); if(bytes_to_skip > dlen) { dlen = 0; } else { bptr += bytes_to_skip; dlen -= bytes_to_skip; } while(dlen != 0) { c = *bptr++; dlen--; crc = g_woz_crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); } return (~crc); } void woz_rewrite_crc(Disk *dsk, int min_write_size) { Woz_info *wozinfo_ptr; byte *wozptr; word32 crc; // Recalculate WOZ image CRC and write it to disk wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr || (dsk->fd < 0)) { return; } wozptr = wozinfo_ptr->wozptr; crc = woz_calc_crc32(&wozptr[0], wozinfo_ptr->woz_size, 12); cfg_set_le32(&wozptr[8], crc); if(min_write_size < 12) { min_write_size = 12; } cfg_write_to_fd(dsk->fd, wozptr, 0, min_write_size); } void woz_rewrite_lock(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr; int offset; wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr || (dsk->fd < 0)) { return; } wozptr = wozinfo_ptr->wozptr; offset = wozinfo_ptr->info_offset; wozptr[offset + 2] = (dsk->write_prot != 0); // Update locked woz_rewrite_crc(dsk, offset + 2 + 1); } void woz_check_file(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr, *newwozptr, *bptr; word32 fdval, memval, crc, mem_crc, crcnew, file_size; int woz_size; int i; wozinfo_ptr = dsk->wozinfo_ptr; if(!wozinfo_ptr) { return; } woz_size = wozinfo_ptr->woz_size; wozptr = wozinfo_ptr->wozptr; crc = woz_calc_crc32(wozptr, woz_size, 12); mem_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | (wozptr[11] << 24); if(crc != mem_crc) { halt_printf("WOZ CRC calc:%08x, from mem:%08x\n", crc, mem_crc); } if((dsk->fd < 0) || dsk->raw_data || !dsk->write_through_to_unix) { printf("woz_check_file: CRC check done, cannot check fd\n"); return; } file_size = (word32)cfg_get_fd_size(dsk->fd); if(file_size != (word32)woz_size) { halt_printf("woz_size:%08x != file_size %08x\n", woz_size, file_size); if((word32)woz_size > file_size) { woz_size = file_size; } } newwozptr = malloc(woz_size); cfg_read_from_fd(dsk->fd, newwozptr, 0, woz_size); for(i = 0; i < woz_size; i++) { fdval = newwozptr[i]; memval = wozptr[i]; if(fdval == memval) { continue; } halt_printf("byte %07x of %07x: mem %02x != %02x fd\n", i, woz_size, memval, fdval); } crcnew = woz_calc_crc32(newwozptr, woz_size, 12); free(newwozptr); printf("Woz check file complete. mem %08x vs fd %08x, freed %p\n", crc, crcnew, newwozptr); bptr = dsk->cur_trk_ptr->raw_bptr; printf("dsk->cur_trk_ptr->raw_bptr = %p. offset:%d\n", bptr, (int)(bptr - wozptr)); } void woz_parse_meta(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; byte *wozptr, *bptr; int c; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->meta_offset) { printf("Bad WOZ file, 2 META chunks\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->meta_offset = offset; wozinfo_ptr->meta_size = size; printf("META field, %d bytes:\n", size); bptr = &(wozptr[offset]); for(i = 0; i < size; i++) { c = bptr[i]; if(c == 0) { break; } putchar(c); } putchar('\n'); } void woz_parse_info(Disk *dsk, int offset, int size) { byte new_buf[36]; Woz_info *wozinfo_ptr; byte *wozptr, *bptr; int info_version, disk_type, write_protect, synchronized; int cleaned, ram, largest_track; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->info_offset) { printf("Two INFO chunks, bad WOZ file\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->info_offset = offset; bptr = &(wozptr[offset]); if(size < 60) { printf("INFO field is %d, too short\n", size); wozinfo_ptr->woz_size = 0; return; } info_version = bptr[0]; // Only "1" or "2" is supported disk_type = bptr[1]; // 1==5.25", 2=3.5" write_protect = bptr[2]; // 1==write protected synchronized = bptr[3]; // 1==cross track sync during imaging cleaned = bptr[4]; // 1==MC3470 fake bits have been removed memcpy(&new_buf[0], &(bptr[5]), 32); new_buf[32] = 0; // Null terminate printf("INFO, %d bytes. info_version:%d, disk_type:%d, wp:%d, sync:" "%d, cleaned:%d\n", size, info_version, disk_type, write_protect, synchronized, cleaned); printf("Creator: %s\n", (char *)&new_buf[0]); if(info_version >= 2) { ram = bptr[42] + (bptr[43] << 8); largest_track = (bptr[44] + (bptr[45] << 8)) * 512; printf("Disk sides:%d, boot_format:%d bit_timing:%d, hw:" "%02x%02x, ram:%d, largest_track:0x%07x\n", bptr[37], bptr[38], bptr[39], bptr[41], bptr[40], ram, largest_track); } if(write_protect) { printf("Write protected\n"); dsk->write_prot = 1; } } void woz_parse_tmap(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; byte *bptr, *wozptr; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(wozinfo_ptr->tmap_offset) { printf("Second TMAP chunk, bad WOZ file!\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->tmap_offset = offset; printf("TMAP field, %d bytes\n", size); bptr = &(wozptr[offset]); for(i = 0; i < 40; i++) { printf("Track %2d.00: %02x, %2d.25:%02x %2d.50:%02x %2d.75:" "%02x\n", i, bptr[0], i, bptr[1], i, bptr[2], i, bptr[3]); bptr += 4; } } void woz_parse_trks(Disk *dsk, int offset, int size) { Woz_info *wozinfo_ptr; printf("TRKS field, %d bytes, offset: %d\n", size, offset); wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr->trks_offset) { printf("Second TRKS chunk, illegal Woz file\n"); wozinfo_ptr->woz_size = 0; return; } wozinfo_ptr->trks_offset = offset; wozinfo_ptr->trks_size = size; } int woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc) { Woz_info *wozinfo_ptr; Trk *trk; byte *wozptr, *sync_ptr, *bptr; word32 raw_bytes, num_bytes, trks_size, len_bits, offset, num_blocks; word32 block; int trks_offset; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; trks_offset = wozinfo_ptr->trks_offset; trks_size = wozinfo_ptr->trks_size; if(wozinfo_ptr->version == 1) { offset = tmap * 6656; if((offset + 6656) > trks_size) { printf("Trk %d is out of range!\n", tmap); return 0; } offset = trks_offset + offset; bptr = &(wozptr[offset]); len_bits = bptr[6648] | (bptr[6649] << 8); if(len_bits > (6656*8)) { printf("Trk bits: %d too big\n", len_bits); return 0; } } else { bptr = &(wozptr[trks_offset + (tmap * 8)]); // This is a TRK 8-byte structure block = cfg_get_le16(&bptr[0]); // Starting Block num_blocks = cfg_get_le16(&bptr[2]); // Block Count len_bits = cfg_get_le32(&bptr[4]); // Bits Count #if 0 printf("qtr_track:%02x, block:%04x, num_blocks:%04x, " "len_bits:%06x\n", qtr_track, block, num_blocks, len_bits); printf("File offset: %05lx\n", bptr - wozinfo_ptr->wozptr); #endif if(block < 3) { printf("block %04x is < 3\n", block); return 0; } offset = (block * 512); // Offset from wozptr if((offset + (num_blocks * 512)) > (trks_size + trks_offset)) { printf("Trk %d is out of range!\n", tmap); return 0; } bptr = &(wozptr[offset]); #if 0 printf("Qtr_track %03x offset:%06x, bptr:%p, trks_bptr:%p\n", qtr_track, offset, bptr, trks_bptr); printf(" len_bits:%d %06x bptr-wozptr: %07lx\n", len_bits, len_bits, bptr - wozinfo_ptr->wozptr); #endif if(len_bits > (num_blocks * 512 * 8)) { printf("Trk bits: %d too big\n", len_bits); return 0; } } dsk->raw_bptr_malloc = 0; raw_bytes = (len_bits + 7) >> 3; num_bytes = raw_bytes + 8; trk = &(dsk->trks[qtr_track]); trk->raw_bptr = bptr; trk->dunix_pos = offset; trk->unix_len = raw_bytes; trk->dirty = 0; trk->track_bits = len_bits; #if 0 printf("track %d.%d dunix_pos:%08llx\n", qtr_track >> 2, (qtr_track & 3) * 25, trk->dunix_pos); #endif trk->sync_ptr = (byte *)malloc(num_bytes); dsk->cur_trk_ptr = 0; iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); sync_ptr = &(trk->sync_ptr[0]); for(i = 0; i < (int)raw_bytes; i++) { sync_ptr[i] = 0xff; } iwm_recalc_sync_from(dsk, qtr_track, 0, dfcyc); if(qtr_track == 0) { printf("Track 0 data begins: %02x %02x %02x, offset:%d\n", bptr[0], bptr[1], bptr[2], offset); } for(i = 0; i < qtr_track; i++) { trk = &(dsk->trks[i]); if(trk->track_bits && (trk->raw_bptr == bptr)) { // Multiple tracks point to the same woz track // This is not allowed, reparse wozinfo_ptr->reparse_needed = 1; printf("Track %04x matchs track %04x, reparse needed\n", qtr_track, i); break; } } return 1; } int woz_parse_header(Disk *dsk) { Woz_info *wozinfo_ptr; byte *wozptr, *bptr; word32 chunk_id, size, woz_size; int pos, version; int i; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; version = 2; if(woz_size < 8) { return 0; } for(i = 0; i < 8; i++) { if(wozptr[i] != g_woz_hdr_bytes[i]) { if(i == 3) { // Check for WOZ1 if(wozptr[i] == 0x31) { // WOZ1 version = 1; continue; } } printf("WOZ header[%d]=%02x, invalid\n", i, wozptr[i]); return 0; } } wozinfo_ptr->version = version; printf("WOZ version: %d\n", version); pos = 12; while(pos < (int)woz_size) { bptr = &(wozptr[pos]); chunk_id = bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | (bptr[3] << 24); size = bptr[4] | (bptr[5] << 8) | (bptr[6] << 16) | (bptr[7] << 24); pos += 8; printf("chunk_id: %08x, size:%08x\n", chunk_id, size); if(((pos + size) > woz_size) || (size < 8) || ((size >> 30) != 0)) { return 0; } bptr = &(wozptr[pos]); if(chunk_id == 0x4f464e49) { // "INFO" woz_parse_info(dsk, pos, size); } else if(chunk_id == 0x50414d54) { // "TMAP" woz_parse_tmap(dsk, pos, size); } else if(chunk_id == 0x534b5254) { // "TRKS" woz_parse_trks(dsk, pos, size); } else if(chunk_id == 0x4154454d) { // "META" woz_parse_meta(dsk, pos, size); } else { printf("Chunk header %08x is unknown\n", chunk_id); } pos += size; } return 1; // Good so far } Woz_info * woz_malloc(byte *wozptr, word32 woz_size) { Woz_info *wozinfo_ptr; wozinfo_ptr = malloc(sizeof(Woz_info)); printf("malloc wozinfo_ptr:%p\n", wozinfo_ptr); if(!wozinfo_ptr) { fprintf(stderr, "Out of memory\n"); exit(1); } wozinfo_ptr->wozptr = wozptr; wozinfo_ptr->woz_size = woz_size; wozinfo_ptr->version = 0; wozinfo_ptr->reparse_needed = 0; wozinfo_ptr->max_trk_blocks = 0; wozinfo_ptr->meta_size = 0; wozinfo_ptr->trks_size = 0; wozinfo_ptr->tmap_offset = 0; wozinfo_ptr->trks_offset = 0; wozinfo_ptr->info_offset = 0; wozinfo_ptr->meta_offset = 0; if(woz_size < 12) { if(wozptr) { free(wozptr); } wozptr = 0; } if(!wozptr) { wozptr = malloc(12); woz_append_bytes(wozptr, &g_woz_hdr_bytes[0], 12); wozinfo_ptr->wozptr = wozptr; wozinfo_ptr->woz_size = 12; } return wozinfo_ptr; } int woz_reopen(Disk *dsk, dword64 dfcyc) { byte act_tmap[160]; Woz_info *wozinfo_ptr; byte *wozptr, *tmap_bptr; word32 tmap, prev_tmap, last_act; int ret, num_tracks, num_match; int i, j; wozinfo_ptr = dsk->wozinfo_ptr; wozptr = wozinfo_ptr->wozptr; if(!wozinfo_ptr->tmap_offset) { printf("No TMAP found\n"); return 0; } if(!wozinfo_ptr->trks_offset) { printf("No TRKS found\n"); return 0; } if(!wozinfo_ptr->info_offset) { printf("No INFO found\n"); return 0; } if(wozinfo_ptr->woz_size == 0) { printf("woz_size is 0!\n"); return 0; } tmap_bptr = wozptr + wozinfo_ptr->tmap_offset; dsk->cur_fbit_pos = 0; num_tracks = 4*35; for(i = num_tracks; i < 160; i++) { // See what the largest track is, go to track 39.50 if(tmap_bptr[i] != 0xff) { num_tracks = i + 1; //printf("Will set num_tracks=%d\n", num_tracks); } } dsk->fbit_mult = 128; // 5.25" multipler value if(!dsk->disk_525) { num_tracks = 160; dsk->fbit_mult = 256; // 3.5" multipler value } disk_set_num_tracks(dsk, num_tracks); for(i = 0; i < 160; i++) { act_tmap[i] = 0xff; } for(i = 0; i < 160; i++) { tmap = tmap_bptr[i]; if(tmap >= 0xff) { continue; // Skip } // WOZ format adds dup entries for adjacent qtr tracks, so // track 2.0 has entries at 1.75 and 2.25 pointing to 2.0. // KEGS doesn't want these, it handles this itself, so remove // them. if(dsk->disk_525) { num_match = 1; for(j = i + 1; j < 160; j++) { if(tmap_bptr[j] != tmap) { break; } num_match++; } // From i, num_match is the number of time tmap repeats prev_tmap = 0xff; last_act = 0xff; if(i) { prev_tmap = tmap_bptr[i - 1]; last_act = act_tmap[i - 1]; } else if(num_match == 3) { // Weird case where WOZ starts with 0,0,0, we // should treat track 0.25 as the real track continue; } if(num_match >= 3) { // long run of repeats, add a track every other // one. if(last_act == tmap) { // Just did trk continue; } if(prev_tmap != tmap) { // Start of run continue; } } else if(num_match == 2) { // Handle A, B, [B], B, C and A, B, B, [B], B, C // ALWAYS add this track } else { // num_match==1 if(prev_tmap == tmap) { continue; } // Otherwise, a lone track, always add it } } ret = woz_add_track(dsk, i, tmap, dfcyc); if(ret == 0) { printf("woz_add_track i:%04x tmap:%04x ret 0\n", i, tmap); return ret; } } return 1; // WOZ file is good! } int woz_open(Disk *dsk, dword64 dfcyc) { Woz_info *wozinfo_ptr; byte *wozptr; dword64 doff; word32 woz_size, crc, file_crc; int fd, ret; // return 0 for bad WOZ file, 1 for success // We set dsk->wozinfo_ptr, and caller will free it if we return 0 printf("woz_open on file %s, write_prot:%d\n", dsk->name_ptr, dsk->write_prot); if(dsk->trks == 0) { return 0; // Smartport? } if(dsk->raw_data) { wozptr = dsk->raw_data; woz_size = (word32)dsk->raw_dsize; dsk->write_prot = 1; dsk->write_through_to_unix = 0; } else { fd = dsk->fd; if(fd < 0) { return 0; } woz_size = (word32)cfg_get_fd_size(fd); printf("size: %d\n", woz_size); wozptr = malloc(woz_size); doff = cfg_read_from_fd(fd, wozptr, 0, woz_size); if(doff != woz_size) { close(fd); return 0; } } wozinfo_ptr = woz_malloc(wozptr, woz_size); dsk->wozinfo_ptr = wozinfo_ptr; if(woz_size < 16) { return 0; } crc = woz_calc_crc32(wozptr, woz_size, 12); file_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | (wozptr[11] << 24); if((crc != file_crc) && (file_crc != 0)) { printf("Bad Woz CRC:%08x in file, calc:%08x\n", file_crc, crc); return 0; } ret = woz_parse_header(dsk); printf("woz_parse_header ret:%d, write_prot:%d\n", ret, dsk->write_prot); if(ret == 0) { return ret; } ret = woz_reopen(dsk, dfcyc); printf("woz_reopen ret:%d\n", ret); woz_maybe_reparse(dsk); return ret; } byte * woz_append_bytes(byte *wozptr, byte *in_bptr, int len) { int i; for(i = 0; i < len; i++) { *wozptr++ = *in_bptr++; } return wozptr; } byte * woz_append_word32(byte *wozptr, word32 val) { int i; for(i = 0; i < 4; i++) { *wozptr++ = val & 0xff; val = val >> 8; } return wozptr; } int woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, byte *bptr) { byte *wozptr, *new_wozptr, *save_wozptr; word32 woz_size, new_size; int offset; wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; new_size = woz_size + 4 + 4 + length; new_wozptr = realloc(wozptr, new_size); if(new_wozptr == 0) { return 0; } wozptr = new_wozptr + woz_size; wozptr = woz_append_word32(wozptr, chunk_id); save_wozptr = woz_append_word32(wozptr, length); wozptr = woz_append_bytes(save_wozptr, bptr, length); wozinfo_ptr->wozptr = new_wozptr; wozinfo_ptr->woz_size = new_size; offset = (int)(save_wozptr - new_wozptr); switch(chunk_id) { case 0x4f464e49: // "INFO" wozinfo_ptr->info_offset = offset; break; case 0x50414d54: // "TMAP" wozinfo_ptr->tmap_offset = offset; break; case 0x534b5254: // "TRKS" wozinfo_ptr->trks_offset = offset; wozinfo_ptr->trks_size = new_size; break; } if(wozptr != (new_wozptr + new_size)) { halt_printf("wozptr:%p != %p + %08x\n", wozptr, new_wozptr, new_size); return 0; } return 1; } byte * woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, word32 *num_blocks_ptr, dword64 *tmap_dptr) { byte *new_bptr; word32 num_blocks, blocks_start, this_blocks, size_bytes, track_bits; int i; // Align trks_buf_size to 512 bytes blocks_start = *num_blocks_ptr; track_bits = dsk->trks[trk_num].track_bits; size_bytes = (dsk->trks[trk_num].track_bits + 7) >> 3; this_blocks = (size_bytes + 511) >> 9; if(wozinfo_ptr->max_trk_blocks < this_blocks) { wozinfo_ptr->max_trk_blocks = this_blocks; printf("max_trk_blocks=%d from trk %03x\n", this_blocks, trk_num); } num_blocks = blocks_start + this_blocks; *num_blocks_ptr = num_blocks; *tmap_dptr = (((dword64)track_bits) << 32) | (this_blocks << 16) | blocks_start; new_bptr = realloc(bptr, num_blocks << 9); if(new_bptr == 0) { return 0; } // Zero out last 512 byte block, to ensure a partial track has all 0's // in the last block for(i = 0; i < 512; i++) { new_bptr[(num_blocks << 9) - 512 + i] = 0; } woz_append_bytes(new_bptr + (blocks_start << 9), dsk->trks[trk_num].raw_bptr, size_bytes); return new_bptr; } Woz_info * woz_new_from_woz(Disk *dsk, int disk_525) { dword64 tmap_dvals[160]; int tmap_qtrk[160]; byte buf[160]; Woz_info *wozinfo_ptr, *in_wozinfo_ptr; Trk *trkptr; byte *in_wozptr, *wozptr, *trks_bufptr; dword64 dval; word32 type, woz_size, num_blocks, crc, track_bits; int c, num_valid_tmap, pos, offset, raw_bytes; int i, j; wozinfo_ptr = woz_malloc(0, 0); // New wozinfo in_wozptr = 0; in_wozinfo_ptr = 0; if(dsk) { in_wozinfo_ptr = dsk->wozinfo_ptr; if(!in_wozinfo_ptr) { halt_printf("Changing to WOZ format!\n"); } } if(in_wozinfo_ptr) { in_wozptr = in_wozinfo_ptr->wozptr; } printf(" START woz_new_from_woz, in_wozinfo_ptr:%p, in_wozptr:%p\n", in_wozinfo_ptr, in_wozptr); for(i = 0; i < 60; i++) { buf[i] = 0; } if(in_wozptr) { // Output the INFO chunk memcpy(&buf[0], &in_wozptr[20], 60); } else { // Output an INFO chunk for KEGS buf[3] = 1; // 1=synchronized tracks buf[4] = 1; // 1=MC3470 fake bits removed for(i = 0; i < 32; i++) { buf[5 + i] = ' '; // Creator field } memcpy(&buf[5], "KEGS", 4); // And put the revision number after it for(i = 0; i < 20; i++) { c = g_kegs_version_str[i]; if(c == 0) { break; } buf[5 + 5 + i] = c; } } buf[0] = 2; // WOZ2 version type = 2 - disk_525; // 1=5.25, 2=3.5 buf[1] = type; buf[2] = 0; // write_prot=0 if(buf[37] == 0) { buf[37] = type; // sides, re-use type } if(buf[39] == 0) { buf[39] = 16 + 16 * (type & 1); // 5.25=32, 3.5=16 } woz_append_chunk(wozinfo_ptr, 0x4f464e49, 60, &buf[0]); // INFO // TMAP for(i = 0; i < 160; i++) { buf[i] = 0xff; } num_blocks = 6; // 1280 + 256 = 6x512 trks_bufptr = malloc(num_blocks << 9); printf("trk_bufptr = %p\n", trks_bufptr); for(i = 0; i < (int)(num_blocks << 9); i++) { trks_bufptr[i] = 0; } num_valid_tmap = 0; if((dsk != 0) && (dsk->trks != 0)) { // Output all valid tracks, and set the TMAP for adjacent .25 for(i = 0; i < 160; i++) { trkptr = &(dsk->trks[i]); if(trkptr->track_bits >= 10000) { buf[i] = num_valid_tmap; if(i && (buf[i-1] == 0xff)) { buf[i-1] = num_valid_tmap; } if((i < 159) && (trkptr[1].track_bits < 10000)){ buf[i+1] = num_valid_tmap; } trks_bufptr = woz_append_a_trk(wozinfo_ptr, dsk, i, trks_bufptr, &num_blocks, &tmap_dvals[num_valid_tmap]); tmap_qtrk[num_valid_tmap] = i; printf("Did append, tmap:%04x qtrk:%04x dval:" "%016llx\n", num_valid_tmap, i, tmap_dvals[num_valid_tmap]); num_valid_tmap++; } } } woz_append_chunk(wozinfo_ptr, 0x50414d54, 160, &buf[0]); // TMAP // TRKS. Already all 0 if there are no tracks. Fill in tmap_dvals[] for(i = 0; i < num_valid_tmap; i++) { pos = 256 + i*8; dval = tmap_dvals[i]; for(j = 0; j < 8; j++) { trks_bufptr[pos + j] = dval & 0xff; dval = dval >> 8; } } woz_append_chunk(wozinfo_ptr, 0x534b5254, (num_blocks << 9) - 256, trks_bufptr + 256); // TRKS, 1280 minimum size free(trks_bufptr); // Final META chunk...just remove, we've added or removed tracks from // an original WOZ image, so the Meta data is no longer accurate wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; printf(" new wozptr:%p, woz_size:%08x\n", wozptr, woz_size); wozptr[64] = wozinfo_ptr->max_trk_blocks; // largest track crc = woz_calc_crc32(wozptr, woz_size, 12); cfg_set_le32(&wozptr[8], crc); if(dsk) { pos = 0; for(i = 0; i < 160; i++) { // Go through and fix up trks structure to match new WOZ trkptr = &(dsk->trks[i]); if((pos < num_valid_tmap) && (tmap_qtrk[pos] == i)) { // This is a valid track dval = tmap_dvals[pos]; track_bits = dval >> 32; offset = (dval & 0xffff) << 9; raw_bytes = (track_bits + 7) >> 3; if(trkptr->unix_len == 0) { free(trkptr->raw_bptr); } trkptr->raw_bptr = &wozptr[offset]; trkptr->dunix_pos = offset; trkptr->unix_len = raw_bytes; trkptr->dirty = 0; trkptr->track_bits = track_bits; if(trkptr->sync_ptr == 0) { halt_printf("sync_ptr 0 qtrk:%04x\n", i); } pos++; } else { // No longer a valid track, free any ptrs if(dsk->raw_bptr_malloc) { free(trkptr->raw_bptr); } free(trkptr->sync_ptr); trkptr->raw_bptr = 0; trkptr->sync_ptr = 0; trkptr->dunix_pos = 0; trkptr->unix_len = 0; trkptr->dirty = 0; trkptr->track_bits = 0; } } dsk->raw_bptr_malloc = 0; if(dsk->raw_data) { free(dsk->raw_data); dsk->raw_data = wozptr; dsk->raw_dsize = woz_size; dsk->dimage_start = 0; dsk->dimage_size = woz_size; in_wozptr = 0; } free(in_wozptr); free(in_wozinfo_ptr); if(in_wozinfo_ptr == 0) { dsk->write_through_to_unix = 0; halt_printf("Force write_through_to_unix since image " "changed to WOZ format\n"); } } printf(" END woz_new_from_woz, wozinfo_ptr:%p wozptr:%p\n", wozinfo_ptr, wozinfo_ptr->wozptr); return wozinfo_ptr; } int woz_new(int fd, const char *str, int size_kb) { Woz_info *wozinfo_ptr; byte *wozptr; word32 size, woz_size; int disk_525; disk_525 = 0; if(size_kb <= 140) { disk_525 = 1; } wozinfo_ptr = woz_new_from_woz(0, disk_525); wozptr = wozinfo_ptr->wozptr; woz_size = wozinfo_ptr->woz_size; size = (word32)must_write(fd, wozptr, woz_size); free(wozptr); free(wozinfo_ptr); if(size != woz_size) { return -1; } if(str) { // Avoid unused var warning } return 0; } void woz_maybe_reparse(Disk *dsk) { Woz_info *wozinfo_ptr; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { if(wozinfo_ptr->reparse_needed) { woz_reparse_woz(dsk); } } } void woz_set_reparse(Disk *dsk) { Woz_info *wozinfo_ptr; wozinfo_ptr = dsk->wozinfo_ptr; if(wozinfo_ptr) { wozinfo_ptr->reparse_needed = 1; } else { woz_reparse_woz(dsk); } } void woz_reparse_woz(Disk *dsk) { Woz_info *wozinfo_ptr; #if 0 printf("In woz_reparse_woz, showing track 0\n"); iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); iwm_show_stats(3); #endif wozinfo_ptr = woz_new_from_woz(dsk, dsk->disk_525); // This wozinfo_ptr has reparse_needed==0 dsk->wozinfo_ptr = wozinfo_ptr; if(!dsk->raw_data && dsk->write_through_to_unix) { (void)!ftruncate(dsk->fd, wozinfo_ptr->woz_size); (void)cfg_write_to_fd(dsk->fd, wozinfo_ptr->wozptr, 0, wozinfo_ptr->woz_size); printf("did ftruncate and write of WOZ to %s\n", dsk->name_ptr); } // Need to recalculate dsk->cur_track_bits, cur_trk_ptr dsk->cur_trk_ptr = 0; iwm_move_to_ftrack(dsk, dsk->cur_frac_track, 0, 0); #if 0 printf("End of woz_reparse_woz, showing track 0\n"); iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); iwm_show_stats(3); #endif woz_check_file(dsk); printf("woz_reparse_woz complete!\n"); } void woz_remove_a_track(Disk *dsk, word32 qtr_track) { Trk *trkptr; printf("woz_remove_track: %s qtr_track:%03x\n", dsk->name_ptr, qtr_track); trkptr = &(dsk->trks[qtr_track]); trkptr->track_bits = 0; // Track invalid woz_set_reparse(dsk); } word32 woz_add_a_track(Disk *dsk, word32 qtr_track) { Trk *trkptr, *other_trkptr; byte *bptr, *other_bptr, *sync_ptr, *other_sync_ptr; word32 track_bits, val; int raw_bytes; int i; // Return track_bits for the new track trkptr = &(dsk->trks[qtr_track]); other_trkptr = 0; if((qtr_track > 0) && (trkptr[-1].track_bits > 0)) { // Copy this track other_trkptr = trkptr - 1; } else if((qtr_track < 159) && (trkptr[1].track_bits > 0)) { other_trkptr = trkptr + 1; } other_trkptr = 0; // HACK if(dsk->disk_525 && other_trkptr) { // We're .25 tracks away from a valid track, copy it's data track_bits = other_trkptr->track_bits; raw_bytes = (track_bits + 7) >> 3; trkptr->track_bits = track_bits; trkptr->raw_bptr = malloc(raw_bytes + 8); trkptr->sync_ptr = malloc(raw_bytes + 8); printf(" add a track, copy bptr:%p sync_ptr:%p size:%08x\n", trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); bptr = trkptr->raw_bptr; sync_ptr = trkptr->sync_ptr; other_bptr = other_trkptr->raw_bptr; other_sync_ptr = other_trkptr->sync_ptr; for(i = 0; i < raw_bytes; i++) { bptr[i] = other_bptr[i]; sync_ptr[i] = other_sync_ptr[i]; } } else { track_bits = iwm_get_default_track_bits(dsk, qtr_track); raw_bytes = (track_bits + 7) >> 3; trkptr->track_bits = track_bits; trkptr->raw_bptr = malloc(raw_bytes + 8); trkptr->sync_ptr = malloc(raw_bytes + 8); printf(" add a track, raw_bptr:%p sync_ptr:%p size:%08x\n", trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); bptr = trkptr->raw_bptr; sync_ptr = trkptr->sync_ptr; for(i = 0; i < raw_bytes; i++) { val = ((i >> 6) ^ i) & 0x7f; if(((val & 0xf0) == 0) || ((val & 0x0f) == 0)) { val |= 0x21; } bptr[i] = val; sync_ptr[i] = 0xff; } bptr[raw_bytes - 1] = 0; iwm_recalc_sync_from(dsk, qtr_track, 0, 0); } trkptr->dunix_pos = 0; trkptr->unix_len = 0; // Mark as a newly created trk trkptr->dirty = 0; printf("woz_add_new_track: %s qtr_track:%03x\n", dsk->name_ptr, qtr_track); woz_set_reparse(dsk); return track_bits; } ================================================ FILE: upstream/kegs/src/xdriver.c ================================================ const char rcsid_xdriver_c[] = "@(#)$KmKId: xdriver.c,v 1.244 2025-01-07 16:40:09+00 kentd Exp $"; /************************************************************************/ /* KEGS: Apple //gs Emulator */ /* Copyright 2002-2025 by Kent Dickey */ /* */ /* This code is covered by the GNU GPL v3 */ /* See the file COPYING.txt or https://www.gnu.org/licenses/ */ /* This program is provided with no warranty */ /* */ /* The KEGS web page is kegs.sourceforge.net */ /* You may contact the author at: kadickey@alumni.princeton.edu */ /************************************************************************/ # if !defined(__CYGWIN__) && !defined(__POWERPC__) /* No shared memory on Cygwin */ # define X_SHARED_MEM #endif /* CYGWIN */ #include #include #include #include #include #include #include #ifdef X_SHARED_MEM # include # include # include #endif int XShmQueryExtension(Display *display); void _XInitImageFuncPtrs(XImage *xim); #include "defc.h" extern int g_video_scale_algorithm; extern int g_audio_enable; typedef struct windowinfo { XShmSegmentInfo *seginfo; XImage *xim; Kimage *kimage_ptr; // KEGS Image pointer for window content char *name_str; Window x_win; GC x_winGC; Atom delete_atom; int x_use_shmem; int active; int width_req; int pixels_per_line; int main_height; int full_min_width; int full_min_height; } Window_info; #include "protos_xdriver.h" int g_x_warp_x = 0; int g_x_warp_y = 0; int g_x_warp_pointer = 0; int g_x_hide_pointer = 0; extern int Verbose; int g_x_screen_depth = 24; int g_x_screen_mdepth = 32; int g_x_max_width = 0; int g_x_max_height = 0; int g_screen_num = 0; extern int _Xdebug; int g_auto_repeat_on = -1; Display *g_display = 0; Visual *g_vis = 0; Colormap g_default_colormap = 0; Window_info g_mainwin_info = { 0 }; Window_info g_debugwin_info = { 0 }; Cursor g_cursor; Pixmap g_cursor_shape; Pixmap g_cursor_mask; XColor g_xcolor_black = { 0, 0x0000, 0x0000, 0x0000, DoRed|DoGreen|DoBlue, 0 }; XColor g_xcolor_white = { 0, 0xffff, 0xffff, 0xffff, DoRed|DoGreen|DoBlue, 0 }; const char *g_x_selection_strings[3] = { // If we get SelectionRequests with target=Atom("TARGETS"), then // send XA_STRING plus this list to say what we can provide for copy "UTF8_STRING", "text/plain", "text/plain;charset=utf-8" }; int g_x_num_targets = 0; Atom g_x_targets_array[5] = { 0 }; Atom g_x_atom_targets = None; // Will be set to "TARGETS" int g_depth_attempt_list[] = { 24, 16, 15 }; #define X_EVENT_LIST_ALL_WIN \ (ExposureMask | ButtonPressMask | ButtonReleaseMask | \ OwnerGrabButtonMask | KeyPressMask | KeyReleaseMask | \ KeymapStateMask | FocusChangeMask) #define X_BASE_WIN_EVENT_LIST \ (X_EVENT_LIST_ALL_WIN | PointerMotionMask | ButtonMotionMask | \ StructureNotifyMask) #define X_A2_WIN_EVENT_LIST \ (X_BASE_WIN_EVENT_LIST) int g_num_a2_keycodes = 0; int g_x_a2_key_to_xsym[][3] = { { 0x35, XK_Escape, 0 }, { 0x7a, XK_F1, 0 }, { 0x78, XK_F2, 0 }, { 0x63, XK_F3, 0 }, { 0x76, XK_F4, 0 }, { 0x60, XK_F5, 0 }, { 0x61, XK_F6, 0 }, { 0x62, XK_F7, 0 }, { 0x64, XK_F8, 0 }, { 0x65, XK_F9, 0 }, { 0x6d, XK_F10, 0 }, { 0x67, XK_F11, 0 }, { 0x6f, XK_F12, 0 }, { 0x69, XK_F13, 0 }, { 0x6b, XK_F14, 0 }, { 0x71, XK_F15, 0 }, { 0x7f, XK_Pause, XK_Break }, { 0x32, '`', '~' }, /* Key number 18? */ { 0x12, '1', '!' }, { 0x13, '2', '@' }, { 0x14, '3', '#' }, { 0x15, '4', '$' }, { 0x17, '5', '%' }, { 0x16, '6', '^' }, { 0x1a, '7', '&' }, { 0x1c, '8', '*' }, { 0x19, '9', '(' }, { 0x1d, '0', ')' }, { 0x1b, '-', '_' }, { 0x18, '=', '+' }, { 0x33, XK_BackSpace, 0 }, { 0x72, XK_Insert, XK_Help }, /* Help? */ /* { 0x73, XK_Home, 0 }, alias XK_Home to be XK_KP_Equal! */ { 0x74, XK_Page_Up, 0 }, { 0x47, XK_Num_Lock, XK_Clear }, /* Clear */ { 0x51, XK_KP_Equal, XK_Home }, /* Note XK_Home alias! */ { 0x4b, XK_KP_Divide, 0 }, { 0x43, XK_KP_Multiply, 0 }, { 0x30, XK_Tab, 0 }, { 0x0c, 'q', 'Q' }, { 0x0d, 'w', 'W' }, { 0x0e, 'e', 'E' }, { 0x0f, 'r', 'R' }, { 0x11, 't', 'T' }, { 0x10, 'y', 'Y' }, { 0x20, 'u', 'U' }, { 0x22, 'i', 'I' }, { 0x1f, 'o', 'O' }, { 0x23, 'p', 'P' }, { 0x21, '[', '{' }, { 0x1e, ']', '}' }, { 0x2a, 0x5c, '|' }, /* backslash, bar */ { 0x75, XK_Delete, 0 }, { 0x77, XK_End, 0 }, { 0x79, XK_Page_Down, 0 }, { 0x59, XK_KP_7, XK_KP_Home }, { 0x5b, XK_KP_8, XK_KP_Up }, { 0x5c, XK_KP_9, XK_KP_Page_Up }, { 0x4e, XK_KP_Subtract, 0 }, { 0x39, XK_Caps_Lock, 0 }, { 0x00, 'a', 'A' }, { 0x01, 's', 'S' }, { 0x02, 'd', 'D' }, { 0x03, 'f', 'F' }, { 0x05, 'g', 'G' }, { 0x04, 'h', 'H' }, { 0x26, 'j', 'J' }, { 0x28, 'k', 'K' }, { 0x25, 'l', 'L' }, { 0x29, ';', ':' }, { 0x27, 0x27, '"' }, /* single quote */ { 0x24, XK_Return, 0 }, { 0x56, XK_KP_4, XK_KP_Left }, { 0x57, XK_KP_5, XK_KP_Begin }, { 0x58, XK_KP_6, XK_KP_Right }, { 0x45, XK_KP_Add, 0 }, { 0x38, XK_Shift_L, XK_Shift_R }, { 0x06, 'z', 'Z' }, { 0x07, 'x', 'X' }, { 0x08, 'c', 'C' }, { 0x09, 'v', 'V' }, { 0x0b, 'b', 'B' }, { 0x2d, 'n', 'N' }, { 0x2e, 'm', 'M' }, { 0x2b, ',', '<' }, { 0x2f, '.', '>' }, { 0x2c, '/', '?' }, { 0x3e, XK_Up, 0 }, { 0x53, XK_KP_1, XK_KP_End }, { 0x54, XK_KP_2, XK_KP_Down }, { 0x55, XK_KP_3, XK_KP_Page_Down }, { 0x36, XK_Control_L, XK_Control_R }, { 0x3a, XK_Print, XK_Sys_Req }, /* Option */ { 0x37, XK_Scroll_Lock, 0 }, /* Command */ { 0x31, ' ', 0 }, { 0x3b, XK_Left, 0 }, { 0x3d, XK_Down, 0 }, { 0x3c, XK_Right, 0 }, { 0x52, XK_KP_0, XK_KP_Insert }, { 0x41, XK_KP_Decimal, XK_KP_Separator }, { 0x4c, XK_KP_Enter, 0 }, { -1, -1, -1 } }; int main(int argc, char **argv) { int ret, mdepth; ret = parse_argv(argc, argv, 1); if(ret) { printf("kegsmain ret: %d, stopping\n", ret); exit(1); } mdepth = x_video_get_mdepth(); ret = kegs_init(mdepth, g_x_max_width, g_x_max_height, 0); printf("kegs_init done\n"); if(ret) { printf("kegs_init ret: %d, stopping\n", ret); exit(1); } x_video_init(); // This is the main loop of KEGS, when this exits, KEGS exits // run_16ms() does one video frame worth of instructions and video // updates: 17030 1MHz clock cycles. while(1) { ret = run_16ms(); if(ret != 0) { printf("run_16ms returned: %d\n", ret); break; } x_input_events(); x_update_display(&g_mainwin_info); x_update_display(&g_debugwin_info); } xdriver_end(); exit(0); } int my_error_handler(Display *display, XErrorEvent *ev) { char msg[1024]; XGetErrorText(display, ev->error_code, msg, 1000); printf("X Error code %s\n", msg); fflush(stdout); return 0; } void xdriver_end() { printf("xdriver_end\n"); if(g_display) { x_auto_repeat_on(1); XFlush(g_display); } } void x_try_xset_r() { /* attempt "xset r" */ (void)!system("xset r"); xdriver_end(); exit(5); } void x_badpipe(int signum) { /* restore normal sigpipe handling */ signal(SIGPIPE, SIG_DFL); x_try_xset_r(); } int kegs_x_io_error_handler(Display *display) { printf("kegs_x_io_error_handler called (likely window closed)\n"); g_display = 0; x_try_xset_r(); return 0; } int x_video_get_mdepth() { XWindowAttributes get_attr; int depth, len, ret, force_depth; int i; printf("Preparing X Windows graphics system\n"); ret = 0; signal(SIGPIPE, x_badpipe); signal(SIGPIPE, x_badpipe); #if 0 printf("Setting _Xdebug = 1, makes X synchronous\n"); _Xdebug = 1; #endif g_display = XOpenDisplay(NULL); if(g_display == NULL) { fprintf(stderr, "Can't open display\n"); exit(1); } vid_printf("Just opened display = %p\n", g_display); fflush(stdout); g_screen_num = DefaultScreen(g_display); get_attr.width = 0; get_attr.height = 0; ret = XGetWindowAttributes(g_display, DefaultRootWindow(g_display), &get_attr); printf("XGetWindowAttributes ret: %d\n", ret); g_x_max_width = get_attr.width; g_x_max_height = get_attr.height; printf("get_attr.width:%d, height:%d\n", g_x_max_width, g_x_max_height); len = sizeof(g_depth_attempt_list)/sizeof(int); force_depth = sim_get_force_depth(); if(force_depth > 0) { /* Only use the requested user depth */ len = 1; g_depth_attempt_list[0] = force_depth; } for(i = 0; i < len; i++) { depth = g_depth_attempt_list[i]; g_x_screen_mdepth = x_try_find_visual(depth, g_screen_num); if(g_x_screen_mdepth != 0) { break; } } if(g_x_screen_mdepth == 0) { fprintf(stderr, "Couldn't find any visuals at any depth!\n"); exit(2); } return g_x_screen_mdepth; } int x_try_find_visual(int depth, int screen_num) { XVisualInfo *visualList; XVisualInfo *v_chosen; XVisualInfo vTemplate; int visualsMatched, visual_chosen, mdepth; int i; vTemplate.screen = screen_num; vTemplate.depth = depth; visualList = XGetVisualInfo(g_display, (VisualScreenMask | VisualDepthMask), &vTemplate, &visualsMatched); vid_printf("visuals matched: %d\n", visualsMatched); if(visualsMatched == 0) { return 0; } visual_chosen = -1; for(i = 0; i < visualsMatched; i++) { printf("Visual %d\n", i); printf(" id: %08x, screen: %d, depth: %d, class: %d\n", (word32)visualList[i].visualid, visualList[i].screen, visualList[i].depth, visualList[i].class); printf(" red: %08lx, green: %08lx, blue: %08lx\n", visualList[i].red_mask, visualList[i].green_mask, visualList[i].blue_mask); printf(" cmap size: %d, bits_per_rgb: %d\n", visualList[i].colormap_size, visualList[i].bits_per_rgb); if((depth != 8) && (visualList[i].class == TrueColor)) { visual_chosen = i; break; } } if(visual_chosen < 0) { printf("Couldn't find any good visuals at depth %d!\n", depth); return 0; } printf("Chose visual: %d\n", visual_chosen); v_chosen = &(visualList[visual_chosen]); video_set_red_mask((word32)v_chosen->red_mask); video_set_green_mask((word32)v_chosen->green_mask); video_set_blue_mask((word32)v_chosen->blue_mask); video_set_palette(); // Uses above masks to initialize palettes g_x_screen_depth = depth; mdepth = depth; if(depth > 8) { mdepth = 16; } if(depth > 16) { mdepth = 32; } // XFree(visualList); -- Cannot free, still using g_vis... g_vis = v_chosen->visual; return mdepth; } void x_video_init() { int tmp_array[0x80]; Atom target; char cursor_data; int keycode, num_targets, max_targets, num; int i; printf("Opening X Window now\n"); g_num_a2_keycodes = 0; for(i = 0; i <= 0x7f; i++) { tmp_array[i] = 0; } for(i = 0; i < 0x7f; i++) { keycode = g_x_a2_key_to_xsym[i][0]; if(keycode < 0) { g_num_a2_keycodes = i; break; } else if(keycode > 0x7f) { printf("a2_key_to_xsym[%d] = %02x!\n", i, keycode); exit(2); } else { if(tmp_array[keycode]) { printf("a2_key_to_x[%d] = %02x used by %d\n", i, keycode, tmp_array[keycode] - 1); } tmp_array[keycode] = i + 1; } } g_default_colormap = XDefaultColormap(g_display, g_screen_num); if(!g_default_colormap) { printf("g_default_colormap == 0!\n"); exit(4); } /* and define cursor */ cursor_data = 0; g_cursor_shape = XCreatePixmapFromBitmapData(g_display, RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); g_cursor_mask = XCreatePixmapFromBitmapData(g_display, RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); g_cursor = XCreatePixmapCursor(g_display, g_cursor_shape, g_cursor_mask, &g_xcolor_black, &g_xcolor_white, 0, 0); XFreePixmap(g_display, g_cursor_shape); XFreePixmap(g_display, g_cursor_mask); XFlush(g_display); g_x_atom_targets = XInternAtom(g_display, "TARGETS", 0); num = sizeof(g_x_selection_strings)/sizeof(g_x_selection_strings[0]); g_x_targets_array[0] = XA_STRING; num_targets = 1; for(i = 0; i < num; i++) { target = XInternAtom(g_display, g_x_selection_strings[i], 0); if(target != None) { g_x_targets_array[num_targets++] = target; } } g_x_num_targets = num_targets; max_targets = sizeof(g_x_targets_array)/sizeof(g_x_targets_array[0]); if(num_targets > max_targets) { printf("Overflowed g_x_targets_array: %d out of %d\n", num_targets, max_targets); exit(-1); } x_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS"); x_init_window(&g_debugwin_info, video_get_kimage(1), "KEGS Debugger"); x_create_window(&g_mainwin_info); vid_printf("Set error handler to my_x_handler\n"); XSetIOErrorHandler(kegs_x_io_error_handler); } void x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str) { int width, height, x_use_shmem, ret; height = video_get_x_height(kimage_ptr); width = video_get_x_width(kimage_ptr); win_info_ptr->seginfo = 0; win_info_ptr->xim = 0; win_info_ptr->kimage_ptr = kimage_ptr; win_info_ptr->name_str = name_str; win_info_ptr->x_win = 0; win_info_ptr->x_winGC = 0; win_info_ptr->delete_atom = 0; win_info_ptr->active = 0; win_info_ptr->x_use_shmem = 0; win_info_ptr->width_req = width; win_info_ptr->pixels_per_line = width; win_info_ptr->main_height = height; win_info_ptr->full_min_width = width; win_info_ptr->full_min_height = height; vid_printf("init win %p (main:%p) width:%d, height:%d\n", win_info_ptr, &g_mainwin_info, width, height); x_use_shmem = 0; // Allow cmd-line args to force shmem off /* Check for XShm */ #ifdef X_SHARED_MEM if(sim_get_use_shmem()) { ret = XShmQueryExtension(g_display); if(ret == 0) { printf("XShmQueryExt ret: %d, not using shared mem\n", ret); } else { vid_printf("Will use shared memory for X\n"); x_use_shmem = 1; } } #endif win_info_ptr->x_use_shmem = x_use_shmem; video_update_scale(kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height, 1); } void x_create_window(Window_info *win_info_ptr) { XGCValues new_gc; XSetWindowAttributes win_attr; Window x_win; GC x_winGC; word32 create_win_list; int width, height, x_xpos, x_ypos; win_attr.event_mask = X_A2_WIN_EVENT_LIST; win_attr.colormap = g_default_colormap; win_attr.backing_store = WhenMapped; win_attr.border_pixel = 1; win_attr.background_pixel = 0; if(g_x_warp_pointer) { win_attr.cursor = g_cursor; } else { win_attr.cursor = None; } vid_printf("About to win, depth: %d\n", g_x_screen_depth); fflush(stdout); create_win_list = CWEventMask | CWBackingStore | CWCursor; create_win_list |= CWColormap | CWBorderPixel | CWBackPixel; x_xpos = video_get_x_xpos(win_info_ptr->kimage_ptr); x_ypos = video_get_x_ypos(win_info_ptr->kimage_ptr); width = win_info_ptr->width_req; height = win_info_ptr->main_height; x_win = XCreateWindow(g_display, RootWindow(g_display, g_screen_num), x_xpos, x_ypos, width, height, 0, g_x_screen_depth, InputOutput, g_vis, create_win_list, &win_attr); vid_printf("x_win = %d, width:%d, height:%d\n", (int)x_win, width, height); XFlush(g_display); win_info_ptr->x_win = x_win; win_info_ptr->active = 0; video_set_active(win_info_ptr->kimage_ptr, 1); x_allocate_window_data(win_info_ptr); if(!win_info_ptr->x_use_shmem) { printf("Not using shared memory, setting skip_amt = 2, " "g_audio_enable=0\n"); video_set_redraw_skip_amt(2); g_audio_enable = 0; } x_set_size_hints(win_info_ptr); XMapRaised(g_display, x_win); if(win_info_ptr != &g_mainwin_info) { // Debugger window win_info_ptr->delete_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False); XSetWMProtocols(g_display, x_win, &(win_info_ptr->delete_atom), 1); } XSync(g_display, False); x_winGC = XCreateGC(g_display, x_win, 0, (XGCValues *) 0); win_info_ptr->x_winGC = x_winGC; win_info_ptr->active = 1; new_gc.fill_style = FillSolid; XChangeGC(g_display, x_winGC, GCFillStyle, &new_gc); /* XSync(g_display, False); */ XFlush(g_display); fflush(stdout); } int g_xshm_error = 0; int xhandle_shm_error(Display *display, XErrorEvent *event) { g_xshm_error = 1; return 0; } void x_allocate_window_data(Window_info *win_info_ptr) { int width, height; width = g_x_max_width; height = g_x_max_height; if(win_info_ptr->x_use_shmem) { win_info_ptr->x_use_shmem = 0; // Default to no shmem get_shm(win_info_ptr, width, height); } if(!win_info_ptr->x_use_shmem) { get_ximage(win_info_ptr, width, height); } } void get_shm(Window_info *win_info_ptr, int width, int height) { #ifdef X_SHARED_MEM XShmSegmentInfo *seginfo; XImage *xim; int (*old_x_handler)(Display *, XErrorEvent *); int depth, size; depth = g_x_screen_depth; // 24, actual bits per pixel seginfo = (XShmSegmentInfo *)malloc(sizeof(XShmSegmentInfo)); xim = XShmCreateImage(g_display, g_vis, depth, ZPixmap, (char *)0, seginfo, width, height); /* check mdepth, which should be 32 */ if(xim->bits_per_pixel != g_x_screen_mdepth) { printf("get_shm bits_per_pix: %d != %d\n", xim->bits_per_pixel, g_x_screen_mdepth); } vid_printf("xim: %p, DO_VERBOSE:%d, Verbose:%d, VERBOSE_VIDEO:%d\n", xim, DO_VERBOSE, Verbose, VERBOSE_VIDEO); vid_printf("xim: %p\n", xim); win_info_ptr->seginfo = seginfo; if(xim == 0) { return; } vid_printf("bytes_per_line:%d, height:%d\n", xim->bytes_per_line, xim->height); size = xim->bytes_per_line * xim->height; vid_printf("size: %d\n", size); /* It worked, we got it */ seginfo->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); vid_printf("seginfo->shmid = %d, errno:%d, %s\n", seginfo->shmid, errno, strerror(errno)); if(seginfo->shmid < 0) { XDestroyImage(xim); return; } /* Still working */ seginfo->shmaddr = (char *)shmat(seginfo->shmid, 0, 0); vid_printf("seginfo->shmaddr: %p\n", seginfo->shmaddr); if(seginfo->shmaddr == ((char *) -1)) { XDestroyImage(xim); return; } /* Still working */ xim->data = seginfo->shmaddr; seginfo->readOnly = False; vid_printf("xim->data is %p, size:%08x\n", xim->data, size); /* XShmAttach will trigger X error if server is remote, so catch it */ g_xshm_error = 0; old_x_handler = XSetErrorHandler(xhandle_shm_error); XShmAttach(g_display, seginfo); XSync(g_display, False); vid_printf("about to RMID the shmid\n"); shmctl(seginfo->shmid, IPC_RMID, 0); XFlush(g_display); XSetErrorHandler(old_x_handler); if(g_xshm_error) { XDestroyImage(xim); /* We could release the shared mem segment, but by doing the */ /* RMID, it will go away when we die now, so just leave it */ printf("Not using shared memory\n"); return; } width = xim->bytes_per_line; win_info_ptr->pixels_per_line = width / 4; win_info_ptr->xim = xim; win_info_ptr->x_use_shmem = 1; vid_printf("Sharing memory. xim: %p, xim->data: %p, width:%d\n", xim, xim->data, win_info_ptr->pixels_per_line); #endif /* X_SHARED_MEM */ } void get_ximage(Window_info *win_info_ptr, int width, int height) { XImage *xim; byte *ptr; int depth, mdepth, size; depth = g_x_screen_depth; mdepth = g_x_screen_mdepth; size = (width * height * mdepth) >> 3; printf("Get_ximage, w:%d, h:%d, mdepth:%d, size:%08x\n", width, height, mdepth, size); ptr = (byte *)malloc(size); vid_printf("ptr: %p\n", ptr); if(ptr == 0) { printf("malloc for data failed, mdepth: %d\n", mdepth); exit(2); } win_info_ptr->pixels_per_line = width; xim = XCreateImage(g_display, g_vis, depth, ZPixmap, 0, (char *)ptr, width, height, 8, 0); #ifdef KEGS_BIG_ENDIAN xim->byte_order = MSBFirst; #else xim->byte_order = LSBFirst; #endif _XInitImageFuncPtrs(xim); /* adjust to new byte order */ /* check mdepth! */ if(xim->bits_per_pixel != mdepth) { printf("shm_ximage bits_per_pix: %d != %d\n", xim->bits_per_pixel, mdepth); } vid_printf("xim: %p\n", xim); win_info_ptr->xim = xim; win_info_ptr->x_use_shmem = 0; return; } void x_set_size_hints(Window_info *win_info_ptr) { XSizeHints my_winSizeHints; XClassHint my_winClassHint; XTextProperty my_winText; Kimage *kimage_ptr; int width, height, a2_width, a2_height; width = win_info_ptr->width_req; height = win_info_ptr->main_height; kimage_ptr = win_info_ptr->kimage_ptr; video_update_scale(kimage_ptr, width, height, 0); a2_width = video_get_a2_width(kimage_ptr); a2_height = video_get_a2_height(kimage_ptr); XStringListToTextProperty(&(win_info_ptr->name_str), 1, &my_winText); my_winSizeHints.flags = PSize | PMinSize | PMaxSize | PAspect; my_winSizeHints.width = width; my_winSizeHints.height = height; my_winSizeHints.min_width = a2_width; my_winSizeHints.min_height = a2_height; my_winSizeHints.max_width = g_x_max_width; my_winSizeHints.max_height = g_x_max_height; my_winSizeHints.min_aspect.x = a2_width - 1; my_winSizeHints.min_aspect.y = a2_height; my_winSizeHints.max_aspect.x = a2_width + 1; my_winSizeHints.max_aspect.y = a2_height; my_winClassHint.res_name = win_info_ptr->name_str; my_winClassHint.res_class = win_info_ptr->name_str; XSetWMProperties(g_display, win_info_ptr->x_win, &my_winText, &my_winText, 0, 0, &my_winSizeHints, 0, &my_winClassHint); // printf("Did XSetWMProperties w:%d h:%d\n", width, height); } void x_resize_window(Window_info *win_info_ptr) { Kimage *kimage_ptr; int x_width, x_height, ret; kimage_ptr = win_info_ptr->kimage_ptr; x_width = video_get_x_width(kimage_ptr); x_height = video_get_x_height(kimage_ptr); win_info_ptr->main_height = MY_MIN(x_height, g_x_max_height); win_info_ptr->width_req = MY_MIN(x_width, g_x_max_width); ret = XResizeWindow(g_display, win_info_ptr->x_win, x_width, x_height); if(0) { printf("XResizeWindow ret:%d, w:%d, h:%d\n", ret, x_width, x_height); } } void x_update_display(Window_info *win_info_ptr) { Change_rect rect; int did_copy, valid, x_active, a2_active; int i; // Update active state a2_active = video_get_active(win_info_ptr->kimage_ptr); x_active = win_info_ptr->active; if(x_active && !a2_active) { // We need to unmap this window XUnmapWindow(g_display, win_info_ptr->x_win); x_active = 0; win_info_ptr->active = x_active; } if(!x_active && a2_active) { // We need to map this window (and maybe create it) if(win_info_ptr->xim == 0) { x_create_window(win_info_ptr); } XMapWindow(g_display, win_info_ptr->x_win); x_active = 1; win_info_ptr->active = x_active; } if(x_active == 0) { return; } if(video_change_aspect_needed(win_info_ptr->kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height)) { x_resize_window(win_info_ptr); } did_copy = 0; for(i = 0; i < MAX_CHANGE_RECTS; i++) { valid = video_out_data(win_info_ptr->xim->data, win_info_ptr->kimage_ptr, win_info_ptr->pixels_per_line, &rect, i); if(!valid) { break; } #if 0 if(win_info_ptr == &g_debugwin_info) { printf(" i:%d valid:%d, w:%d h:%d\n", i, valid, rect.width, rect.height); } #endif did_copy = 1; #ifdef X_SHARED_MEM if(win_info_ptr->x_use_shmem) { XShmPutImage(g_display, win_info_ptr->x_win, win_info_ptr->x_winGC, win_info_ptr->xim, rect.x, rect.y, rect.x, rect.y, rect.width, rect.height, False); } #endif if(!win_info_ptr->x_use_shmem) { XPutImage(g_display, win_info_ptr->x_win, win_info_ptr->x_winGC, win_info_ptr->xim, rect.x, rect.y, rect.x, rect.y, rect.width, rect.height); } } if(did_copy) { XFlush(g_display); } } Window_info * x_find_xwin(Window in_win) { if(g_mainwin_info.kimage_ptr) { if(g_mainwin_info.x_win == in_win) { return &g_mainwin_info; } } if(g_debugwin_info.kimage_ptr) { if(g_debugwin_info.x_win == in_win) { return &g_debugwin_info; } } printf("in_win:%d not found\n", (int)in_win); exit(1); } #define KEYBUFLEN 128 int g_num_check_input_calls = 0; int g_check_input_flush_rate = 2; // https://stackoverflow.com/questions/72236711/trouble-with-xsetselectionowner // See answer from "n.m" on May 17th. void x_send_copy_data(Window_info *win_info_ptr) { printf("x_send_copy_data!\n"); XSetSelectionOwner(g_display, XA_PRIMARY, win_info_ptr->x_win, CurrentTime); (void)cfg_text_screen_str(); } void x_handle_copy(XSelectionRequestEvent *req_ev_ptr) { byte *bptr; int ret; bptr = (byte *)cfg_get_current_copy_selection(); ret = XChangeProperty(g_display, req_ev_ptr->requestor, req_ev_ptr->property, req_ev_ptr->target, 8, PropModeReplace, bptr, strlen((char *)bptr)); // req_ev_ptr->target is either XA_STRING, or equivalent if(0) { // Seems to return 1, BadRequest always, but it works printf("XChangeProperty ret: %d\n", ret); } } void x_handle_targets(XSelectionRequestEvent *req_ev_ptr) { int ret; // Tell the other client what targets we can supply from // g_x_targets_array[] ret = XChangeProperty(g_display, req_ev_ptr->requestor, req_ev_ptr->property, XA_ATOM, 32, PropModeReplace, (byte *)&g_x_targets_array[0], g_x_num_targets); if(0) { // Seems to return 1, BadRequest always, but it works printf("XChangeProperty TARGETS ret: %d\n", ret); } } void x_request_paste_data(Window_info *win_info_ptr) { printf("Pasting selection\n"); // printf("Calling XConvertSelection\n"); XConvertSelection(g_display, XA_PRIMARY, XA_STRING, XA_STRING, win_info_ptr->x_win, CurrentTime); // This will cause a SelectionNotify event, and we get the data // by using XGetWindowProperty on our own window. This will eventually // call x_handle_paste(). } void x_handle_paste(Window w, Atom property) { byte *bptr; Atom sel_type; unsigned long sel_nitems, sel_bytes_after; long sel_length; int sel_format, ret, ret2, c; int i; sel_length = 16384; sel_type = 0; sel_format = 0; sel_nitems = 0; sel_bytes_after = 0; bptr = 0; ret = XGetWindowProperty(g_display, w, property, 0, sel_length, 1, AnyPropertyType, &sel_type, &sel_format, &sel_nitems, &sel_bytes_after, &bptr); #if 0 printf("XGetWindowProperty ret:%d, sel_type:%ld, sel_format:%d, " "sel_nitems:%ld, sel_bytes_after:%ld, bptr:%p\n", ret, sel_type, sel_format, sel_nitems, sel_bytes_after, bptr); #endif if(bptr && (sel_type == property) && sel_nitems && (sel_format == 8)) { //printf("bptr: %s\n", (char *)bptr); for(i = 0; i < sel_nitems; i++) { c = bptr[i]; ret2 = adb_paste_add_buf(c); if(ret2) { printf("Paste buffer full!\n"); break; } } } if(ret == 0) { XFree(bptr); } } int x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid) { Kimage *kimage_ptr; int x, y, ret; if((button_states & buttons_valid & 2) == 2) { // Middle button: Paste request x_request_paste_data(win_info_ptr); button_states = button_states & (~2); } kimage_ptr = win_info_ptr->kimage_ptr; x = video_scale_mouse_x(kimage_ptr, raw_x, 0); y = video_scale_mouse_y(kimage_ptr, raw_y, 0); if(g_x_warp_pointer && (raw_x == g_x_warp_x) && (raw_y == g_x_warp_y) && (buttons_valid == 0) ) { /* tell adb routs to recenter but ignore this motion */ adb_update_mouse(kimage_ptr, x, y, 0, -1); return 0; } ret = adb_update_mouse(kimage_ptr, x, y, button_states, buttons_valid & 7); return ret; } void x_input_events() { XEvent ev, response; XSelectionRequestEvent *req_ev_ptr; Window_info *win_info_ptr; char *str; int len, motion, key_or_mouse, refresh_needed, buttons, hide, warp; int width, height, resp_property, match, x_xpos, x_ypos; int i; str = 0; if(str) { // Use str } if(adb_get_copy_requested()) { x_send_copy_data(&g_mainwin_info); } g_num_check_input_calls--; if(g_num_check_input_calls < 0) { len = XPending(g_display); g_num_check_input_calls = g_check_input_flush_rate; } else { len = QLength(g_display); } motion = 0; win_info_ptr = 0; refresh_needed = 0; key_or_mouse = 0; while(len > 0) { XNextEvent(g_display, &ev); len--; if(len == 0) { /* Xaccel on linux only buffers one X event */ /* must look for more now */ len = XPending(g_display); } switch(ev.type) { case FocusIn: case FocusOut: win_info_ptr = x_find_xwin(ev.xfocus.window); if(ev.xfocus.type == FocusOut) { /* Allow keyrepeat again! */ vid_printf("Left window, auto repeat on\n"); x_auto_repeat_on(0); } else if(ev.xfocus.type == FocusIn && (win_info_ptr == &g_mainwin_info)) { /* Allow keyrepeat again! */ vid_printf("Enter window, auto repeat off\n"); x_auto_repeat_off(0); } break; case EnterNotify: case LeaveNotify: /* These events are disabled now */ printf("Enter/Leave event for winow %08x, sub: %08x\n", (word32)ev.xcrossing.window, (word32)ev.xcrossing.subwindow); printf("Enter/L mode: %08x, detail: %08x, type:%02x\n", ev.xcrossing.mode, ev.xcrossing.detail, ev.xcrossing.type); break; case ButtonPress: win_info_ptr = x_find_xwin(ev.xbutton.window); vid_printf("Got button press of button %d!\n", ev.xbutton.button); buttons = (1 << ev.xbutton.button) >> 1; motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, ev.xbutton.y, buttons, buttons & 7); key_or_mouse = 1; break; case ButtonRelease: win_info_ptr = x_find_xwin(ev.xbutton.window); buttons = (1 << ev.xbutton.button) >> 1; motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, ev.xbutton.y, 0, buttons & 7); key_or_mouse = 1; break; case MotionNotify: win_info_ptr = x_find_xwin(ev.xmotion.window); motion |= x_update_mouse(win_info_ptr, ev.xmotion.x, ev.xmotion.y, 0, 0); key_or_mouse = 1; break; case Expose: win_info_ptr = x_find_xwin(ev.xexpose.window); refresh_needed = -1; //printf("Got an Expose event\n"); break; case NoExpose: /* do nothing */ break; case KeyPress: case KeyRelease: x_handle_keysym(&ev); key_or_mouse = 1; break; case KeymapNotify: break; case DestroyNotify: win_info_ptr = x_find_xwin(ev.xdestroywindow.window); video_set_active(win_info_ptr->kimage_ptr, 0); win_info_ptr->active = 0; printf("Destroy %s\n", win_info_ptr->name_str); if(win_info_ptr == &g_mainwin_info) { x_try_xset_r(); // Mainwin: quit BURST } break; case ReparentNotify: case UnmapNotify: case MapNotify: break; case ClientMessage: win_info_ptr = x_find_xwin(ev.xclient.window); if(ev.xclient.data.l[0] == win_info_ptr->delete_atom) { // This is a WM_DELETE_WINDOW event // Just unmap the window win_info_ptr->kimage_ptr->active = 0; } else { printf("unknown ClientMessage\n"); } break; case ConfigureNotify: win_info_ptr = x_find_xwin(ev.xconfigure.window); width = ev.xconfigure.width; height = ev.xconfigure.height; #if 0 printf("ConfigureNotify, width:%d, height:%d\n", width, height); #endif video_update_scale(win_info_ptr->kimage_ptr, width, height, 0); x_xpos = ev.xconfigure.x; x_ypos = ev.xconfigure.y; video_update_xpos_ypos(win_info_ptr->kimage_ptr, x_xpos, x_ypos); break; case SelectionRequest: //printf("SelectionRequest received\n"); req_ev_ptr = &(ev.xselectionrequest); // This is part of the dance for copy: Another client // is asking us what format we can supply (TARGETS), // or is doing to tell us one at a time what types // it would like. #if 0 printf("req:%ld, property:%ld, target:%ld, " "selection:%ld\n", req_ev_ptr->requestor, req_ev_ptr->property, req_ev_ptr->target, req_ev_ptr->selection); str = XGetAtomName(g_display, req_ev_ptr->target); printf("XAtom target str: %s\n", str); XFree(str); str = XGetAtomName(g_display, req_ev_ptr->property); printf("XAtom property str: %s\n", str); XFree(str); #endif resp_property = None; match = 0; for(i = 0; i < g_x_num_targets; i++) { if(req_ev_ptr->target == g_x_targets_array[i]) { match = 1; break; } } if(match) { x_handle_copy(req_ev_ptr); resp_property = req_ev_ptr->property; } else if(req_ev_ptr->target == g_x_atom_targets) { // Some other agent is asking us "TARGETS", // so send our list of targets x_handle_targets(req_ev_ptr); } // But no matter what the request target was, respond // so it will send an eventual request for XA_STRING response.xselection.type = SelectionNotify; response.xselection.display = req_ev_ptr->display; response.xselection.requestor = req_ev_ptr->requestor; response.xselection.selection = req_ev_ptr->selection; response.xselection.target = req_ev_ptr->target; response.xselection.property = resp_property; response.xselection.time = req_ev_ptr->time; XSendEvent(g_display, req_ev_ptr->requestor, 0, 0, &response); XFlush(g_display); // Speed up getting more resp break; case SelectionNotify: // We get this event after we requested the PRIMARY // selection, so paste this to adb(). vid_printf("SelectionNotify received\n"); vid_printf("req:%ld, selection:%ld, target:%ld, " "property:%ld\n", ev.xselection.requestor, ev.xselection.selection, ev.xselection.target, ev.xselection.property); if(ev.xselection.property == None) { printf("No selection\n"); break; } x_handle_paste(ev.xselection.requestor, ev.xselection.property); break; default: printf("X event 0x%08x is unknown!\n", ev.type); break; } } if(key_or_mouse && (win_info_ptr == &g_mainwin_info)) { hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); if(warp != g_x_warp_pointer) { motion = 1; } g_x_warp_pointer = warp; if(g_x_hide_pointer != hide) { x_hide_pointer(&g_mainwin_info, hide); } g_x_hide_pointer = hide; } if(motion && g_x_warp_pointer && (win_info_ptr == &g_mainwin_info)) { // Calculate where to warp to g_x_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, BASE_MARGIN_LEFT + (BASE_WINDOW_WIDTH/2), 0); g_x_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); XWarpPointer(g_display, None, win_info_ptr->x_win, 0, 0, 0, 0, g_x_warp_x, g_x_warp_y); } if(refresh_needed && win_info_ptr) { //printf("...at end, refresh_needed:%d\n", refresh_needed); video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); } } void x_hide_pointer(Window_info *win_info_ptr, int do_hide) { if(do_hide) { // invisible XDefineCursor(g_display, win_info_ptr->x_win, g_cursor); } else { // Default cursor XDefineCursor(g_display, win_info_ptr->x_win, None); } } void x_handle_keysym(XEvent *xev_in) { Window_info *win_info_ptr; KeySym keysym; word32 state; int keycode, a2code, type, is_up; win_info_ptr = x_find_xwin(xev_in->xkey.window); keycode = xev_in->xkey.keycode; type = xev_in->xkey.type; keysym = XLookupKeysym(&(xev_in->xkey), 0); state = xev_in->xkey.state; vid_printf("keycode: %d, type: %d, state:%d, sym: %08x\n", keycode, type, state, (word32)keysym); x_update_modifier_state(win_info_ptr, state); is_up = 0; if(type == KeyRelease) { is_up = 1; } /* first, do conversions */ switch(keysym) { case XK_Alt_L: case XK_Meta_R: // Windows key on right side case XK_Super_R: case XK_Mode_switch: case XK_Cancel: keysym = XK_Print; /* option */ break; case XK_Meta_L: // Windows key on left side case XK_Alt_R: case XK_Super_L: case XK_Menu: keysym = XK_Scroll_Lock; /* cmd */ break; case XK_F5: break; case XK_F10: if(!is_up) { g_video_scale_algorithm++; if(g_video_scale_algorithm >= 3) { g_video_scale_algorithm = 0; } printf("g_video_scale_algorithm = %d\n", g_video_scale_algorithm); video_update_scale(win_info_ptr->kimage_ptr, win_info_ptr->width_req, win_info_ptr->main_height, 1); } break; case 0x1000003: if(keycode == 0x3c) { /* enter key on Mac OS X laptop--make it option */ keysym = XK_Print; } break; case NoSymbol: switch(keycode) { /* 94-95 are for my PC101 kbd + windows keys on HPUX */ case 0x0095: /* left windows key = option */ keysym = XK_Print; break; case 0x0096: case 0x0094: /* right windows key = cmd */ keysym = XK_Scroll_Lock; break; /* 0072 is for cra@WPI.EDU who says it's Break under XFree86 */ case 0x0072: /* 006e is break according to mic@research.nj.nec.com */ case 0x006e: keysym = XK_Break; break; /* 0x0042, 0x0046, and 0x0048 are the windows keys according */ /* to Geoff Weiss on Solaris x86 */ case 0x0042: case 0x0046: /* flying windows == open apple */ keysym = XK_Scroll_Lock; break; case 0x0048: case 0x0076: /* Windows menu key on Mac OS X */ /* menu windows == option */ keysym = XK_Print; break; } } a2code = x_keysym_to_a2code(win_info_ptr, (int)keysym, is_up); if(a2code >= 0) { adb_physical_key_update(win_info_ptr->kimage_ptr, a2code, 0, is_up); } else if(a2code != -2) { printf("Keysym: %04x of keycode: %02x unknown\n", (word32)keysym, keycode); } } int x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up) { int i; if(keysym == 0) { return -1; } /* Look up Apple 2 keycode */ for(i = g_num_a2_keycodes - 1; i >= 0; i--) { if((keysym == g_x_a2_key_to_xsym[i][1]) || (keysym == g_x_a2_key_to_xsym[i][2])) { vid_printf("Found keysym:%04x = a[%d] = %04x or %04x\n", (int)keysym, i, g_x_a2_key_to_xsym[i][1], g_x_a2_key_to_xsym[i][2]); return g_x_a2_key_to_xsym[i][0]; } } return -1; } void x_update_modifier_state(Window_info *win_info_ptr, int state) { word32 c025_val; c025_val = 0; if(state & ShiftMask) { c025_val |= 1; } if(state & ControlMask) { c025_val |= 2; } if(state & LockMask) { c025_val |= 4; } adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); } void x_auto_repeat_on(int must) { if((g_auto_repeat_on <= 0) || must) { g_auto_repeat_on = 1; XAutoRepeatOn(g_display); XFlush(g_display); adb_kbd_repeat_off(); } } void x_auto_repeat_off(int must) { if((g_auto_repeat_on != 0) || must) { XAutoRepeatOff(g_display); XFlush(g_display); g_auto_repeat_on = 0; adb_kbd_repeat_off(); } } void x_full_screen(int do_full) { return; }