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;
}