Compare commits

...

35 Commits

Author SHA1 Message Date
mgcaret 55c9a71e0d
mention XModem feature 2018-10-17 14:47:04 -07:00
mgcaret 4e38bdf5de
caveat regarding receiver memory 2018-10-15 15:53:58 -07:00
mgcaret a14af25f96 Docs updates for xmodem 2018-10-13 14:18:13 -07:00
mgcaret 65c2a4c7d0 xmodem: don't set up ACIA if closed apple held 2018-10-07 12:41:08 -07:00
mgcaret 464ce2e99d XModem: remove a couple of debug insns 2018-10-05 23:52:32 -07:00
mgcaret 7383c05f9e xmodem: 'C02 optimizations, applesoft routine error handing improved 2018-10-05 23:43:31 -07:00
mgcaret 4affcb82e4 XModem 2018-10-01 16:35:21 -07:00
mgcaret 264bf5ce0d reorganize dispatching to work like 5X 2018-09-29 23:56:04 -07:00
mgcaret ee1faf6cc7 modify dist.sh 2018-09-29 15:51:50 -07:00
mgcaret 8ea78bd613 update reade for 4X 2018-09-18 09:11:47 -07:00
mgcaret 40f2ae5ac3 update reade for 4X 2018-09-18 09:10:01 -07:00
mgcaret ea0b4acb9d
Clean up README.md some more for ROM 5X. 2018-08-02 14:16:21 -07:00
mgcaret 8e0a7007a3
Update README.md, referencing information from #8. 2018-08-02 14:04:31 -07:00
mgcaret a7c0aa2163 READMEs updated as request in #7 2018-01-02 12:51:27 -08:00
mgcaret 6ba882ffb9 5X: release date 2017-12-10 19:37:00 -08:00
mgcaret 11a799569d 5X: The Merlin fix. (cleanup) 2017-12-10 18:02:54 -08:00
mgcaret d5969104cd 5X: the Merlin fix 2017-12-10 18:01:37 -08:00
mgcaret e04fa4d5ce remove cruft from other branch from MAME boot patch 2017-11-26 21:03:14 -08:00
mgcaret 1c5a03e3ea optional patches to let MAME IIc+ emulation boot 2017-11-26 20:59:56 -08:00
mgcaret 0c442f1d46 add build metadata 2017-11-26 20:58:34 -08:00
mgcaret 2e97ca4fc8 5X: untested patch delete key to work like left arrow in monitor GETLN1 2017-10-11 12:58:02 -07:00
mgcaret 4f38bbccb2 Add patch to make delete key work like backspace in monitor GETLN/GETLN1 2017-10-11 12:38:29 -07:00
mgcaret 145d82bbcc update .gitignore 2017-10-02 12:35:36 -07:00
mgcaret 3991773e06 minor changes to main README 2017-10-02 12:33:21 -07:00
mgcaret 7ed3e10a22 reorganize documentation 2017-10-02 12:27:09 -07:00
mgcaret df08db5fce 4X: fix assembler warning; 4X+5X: Prepare for binary distribution 2017-10-02 11:21:09 -07:00
mgcaret 094e9d98fa commentary 2017-08-30 20:49:07 -07:00
mgcaret e0d2159aad 4X: Merge config branch to master after good user reports; 5X: Fix early mistake regarding accelerator code, thanks to Michael J Mahon for spotting it 2017-08-30 20:48:58 -07:00
mgcaret ea801bed1f add block counter safety check to config finder 2017-05-27 17:08:08 -07:00
mgcaret fdbb458ec6 4X: create README.md specifically for 4X, add experimental config file support 2017-04-25 20:10:02 -07:00
mgcaret ce5f476f64 Update README.md 2017-04-23 01:27:55 -07:00
mgcaret 34ff8a48bf Reformat boot5x, fix #2, and shorten code all at the same time 2017-04-08 19:20:36 -07:00
mgcaret 740044802d allow disabling accelerator menu to actually build 2017-04-08 14:43:31 -07:00
mgcaret f489231464 experimental accelerator reverse for accel5x 2017-04-08 14:31:52 -07:00
mgcaret c0b6f2b229 move accelerator reverse to new obsolete directory 2017-04-08 14:21:40 -07:00
47 changed files with 2470 additions and 910 deletions

3
.gitignore vendored
View File

@ -9,4 +9,7 @@
*.po
copyrom.sh
rom5x/accel5x
make_rom.sh
rom.sha256
**/.DS_Store

346
README.md
View File

@ -11,344 +11,32 @@ It adds the following features to the Apple //c and IIc Plus firmware:
- Zero the RAM card, in case it is corrupted.
- Execute the machine and RAM card diagnostics.
- Tell the machine to boot the SmartPort, the internal floppy drive, or an external floppy drive.
- IIc: The system drops to BASIC if no bootable device is found (this is the default behavior in the IIc Plus).
- IIc Plus: Optionally default the system to 1 MHz.
- IIc only:
- The system drops to BASIC if no bootable device is found (this is the default behavior in the IIc Plus).
- Configure default boot device by saving a file on the RAM Disk.
- [XModem-CRC](rom4x/xmodem.md) support.
- IIc Plus only:
- Menu control the built-in accelerator.
- Accelerator settings persist across resets.
- Build option to default the system to 1 MHz.
The first feature listed above is the *raison d'etre* for this project. The larger story is down below but in short: The Apple //c memory card driver keeps certain information in the "screen holes" in main memory, which are required to use the memory card as a RAM disk. Should these screen hole values disappear, the card is re-initialized to empty when ProDOS boots. This happens even when the card is battery-backed and already has a RAM disk. The card data is not damaged until ProDOS boots, but if you attempt to manually boot the RAM disk it will say "UNABLE TO START FROM MEMORY CARD" because the screen hole values are not initialized.
This firmware enhancement identifies a ProDOS boot block on the RAM disk and, if found, restores the appropriate screen holes to make the RAM disk bootable and prevent ProDOS from re-initializing it.
This firmware enhancement identifies a ProDOS boot block on the RAM disk and, if found, restores the appropriate screen holes to make the RAM disk bootable and prevent firmware or ProDOS from re-initializing it.
# User Guide
## Build / Install / Documentation
## Installation
### ROM 4X
### Real System
See the [ROM 4X README.md](rom4x/README.md).
Assuming you already have it burned onto a chip (I use SST27SF512s flash chips which hold 64K and Atmel 27C256, which hold 32K, and program with a TL866), generally the instructions [here](http://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Computers/Apple%20II/Apple%20IIc/Manuals/Apple%20IIc%20v4%20ROM%20Upgrade%20Installation.pdf) are relevant. You won't need to cut any traces or solder a jumper unless, you are installing this ROM in an original //c. I don't recommend installing it on a non-memory expansion //c unless you have expansion memory that looks like the 'slinky' memory of the later models. ROM 4X/5X doesn't know about RAMWorks-style expansions.
### ROM 5X
Cards known to work with ROM 4X include the Apple Memory Expansion Card (but no battery!), and the A2Heaven [RAM Express II](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=146) for the original //c, and the [RAM Express II+](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=144) for the memory-expandable //c and IIc Plus.
See the [ROM 5X README.md](rom5x/README.md).
### Emulator
#### //c
## The Whole Story
Copy the ROM into the appropriate location for your emulator. At the time of writing the only emulator I am aware of that can emulate the //c with memory expansion is [Catakig](http://catakig.sourceforge.net/) for MacOS. It's a bit older of an emulator but it runs fine on newer MacOSes.
#### IIc Plus
Unfortunatly the current IIc Plus emulators out there are incomplete.
## Operation
Power on your machine. Everything should look and work *almost* like it did before. If there is a bootable device somewhere, the machine will boot it. If there is not (and this is one of the noticable changes), you will get dropped to BASIC without the need to press ctrl+reset. If things don't go well, revisit your installation.
If you don't have an initialized RAM disk, format the card RAM disk with something like Copy II Plus. Put ProDOS and BASIC.SYSTEM on it. Power off the machine, and power it on after a few minutes. You should boot off of the RAM disk. You might notice an "R" flash on the screen for an instant before ProDOS loads.
Now, press Control+Closed-Apple+Reset, holding down Closed-Apple after releasing reset. You should see the following menu appear (on a //c, IIc Plus menu is more compact to save firmware space):
```
0 Monitor
1 Reboot
2 Zero RAM Card and Reboot
3 Diagnostics
4 RAM Card Diagnostics
5 Boot SmartPort
6 Boot Int. 5.25
7 Boot Ext. 5.25
```
Picking any of the menu options besides 0 results in the menu being cleared, but the bottom line 'ROM 4X mm/dd/yy' immediately reappears to confirm that the new code is taking action.
What each option does is detailed below.
### 0 Monitor
This drops you unconditionally into the monitor.
### 1 Reboot
This carries out the normal boot sequence, which is to try the RAM disk first, then the internal 5.25 floppy drive, then the first connected smartport device. Some of the other options let you skip over one or more of this ordering.
### 2 Zero RAM Card and Reboot
This zeros out the RAM card memory and the screen holes. This is a nuclear option if the RAM disk is corrupt and the system fails to boot. After selecting 2 the word "SURE?" appears on the screen. At this point you must type `Y` or `y` to continue with the zeroing, or any other key to cancel.
If there is no card RAM, you are immediately rebooted. Otherwise an 'A' will appear in the upper left corner of the screen and will follow the alphabet as each 64K of the card is cleared. After it completes the letter will disappear and the machine will try booting.
### 3 Diagnostics
This jumps to the //c internal diagnostics that are also run when you press control+both-apples+reset.
### 4 RAM Card Diagnostics
This runs the RAM card diagnostics. When the diagnostics are finished either by user cancel or error, you are dropped into the monitor.
Since the test may damage data on the card, you are asked to confirm as per option 2 above.
### 5 Boot SmartPort
This attempts to boot the first smartport device, such as a UniDisk 3.5.
### 6 Boot Internal 5.25
This skips the RAM disk and starts booting with the internal 5.25 drive.
**IIc Plus**: There is no interal 5.25 drive, so this option boots an external 5.25 drive and the option to boot an external 5.25 drive is removed.
### 7 Boot External 5.25
This is like option 6, but using an external 5.25 drive. The only OS I am aware of that supports booting this way is ProDOS.
This destructively copies a short routine to $800 so if you need to preserve what's there don't use this option.
*This option is not available on the IIc Plus per the above comment.*
# Build/Develop Guide
## Build
To build the new firmware, you must start with a copy of the repository containing this file, and obtain a copy of the Apple //c version 4 or IIc Plus version 5 ROM. The patches to the firmware work with the ROM dump that has sha256sums:
//c:
```
8ad5e6c4ed15d09b62183965b6c04762610d4b26510afc1896bdb4ecc55da883
```
IIc Plus:
```
5a62070f6a0b07784681d4df4bf2ce88b2809bec0cbaa65fcb963e804ed60374
```
It may work with other ROM dumps, it will *not* work with any other ROM version, including ROM 3 and earlier.
Place the ROM dump in the directory with the other files and name it `iic_rom4.bin` for ROM 4X and `iic+_rom5.bin` for ROM 5X.
Now you will need a 65C02 cross assembler. The current codebase is developed using ca65 from the [cc65](http://www.cc65.org/) project. (Note: The code was developed originally using [xa](http://www.floodgap.com/retrotech/xa/)).
Finally you will need [Ruby](https://www.ruby-lang.org/en/) and [Rake](https://github.com/ruby/rake).
Once you have it all together change to the directory with the source files and original ROM image and type `rake`.
If all goes well, you will have a shiny new `iic_rom4x.bin` or `iic+_rom5x.bin`.
If you intend to build an image for a 512-kbit chip such as the SST27SF512, do `rake sf512`.
## Develop
### First Thing's First
First and foremost, it is most helpful to have an emulator. The only one that I have found that can be used for (almost) thorough testing is [Catakig](http://catakig.sourceforge.net/) for MacOS. It can emulate the //c and the Expansion Card (though not battery-backed).
If you plan to test on a real machine, be aware that the ROM socket is not rated for a large number of insertions and you *will* break something after a while. You may consider putting a machine-pin DIP socket or a ZIF socket into the CPU socket position. This can be done by desoldering the original socket if you have the skills, or by plugging the new socket into the existing CPU socket. If you do do the latter you should consider the new socket permanent as the socket pins are thicker than a ROM chip's and removing it may leave the socket in such a state as to not be able to make good contact with a subsequent chip.
As for me, I just use the emulator and then I am very careful with changing the ROM when I want to test on the real hardware. For heavy development/testing I insert a low-profile solder-tail ZIF socket into the existing chip socket..
### Apple //c Technical Reference and other Documentation
You need this.
The Apple //c Technical Reference Manual that is available on the internet has the firmware listing for ROM 3. ROM 4 fixes a few bugs that were in ROM 3, including with the memory card driver. The changes are minor and affect some of the offsets of routines in the RAM card support, but it is easy to figure them out.
[This](http://www.1000bit.it/support/manuali/apple/technotes/memx/tn.memx.1.html) tech note is also helpful as it documents the screen holes and some of the card behavior including under what conditions it reformats. Though the power2 byte is *not* used by the Apple //c code -- it is commented out in the firmware listings in the Technical Reference. ROM 4X uses it for the menu function.
[This](http://www.1000bit.it/support/manuali/apple/technotes/aiic/tn.aiic.5.html) technical note is a little less helpful for this project.
### Magic File Names
The main source files are named after a pattern, `B#_####_something.s` where the first # represents the bank number (0 = main, 1 = aux), and #### is the location in the bank to patch the code into. E.G. the `B1_E000_rom4.bin`'s object code is loaded into bank 1 at E000. Generally the origin address of the code in the file matches the #### portion of the file name.
The Rakefile uses this information to patch the original ROM 4 and produce the ROM 4X version.
### Defs
One file, `iic.defs` is included by all of the other source files. This has entry points, origins, and various RAM locations defined in it for use by the other source code.
### Test Scenarios
#### Basic Functional Tests
1. Boot ProDOS from power off. Run SlotScan 1.62 and confirm that the slots are identified as expected, see below.
2. With no bootable ProDOS RAMdisk, boot the system from power off or ctrl-oa-reset.
1. With the drop-to-basic patch:
- Expected: The system says "No bootable device" and drops to BASIC.
2. Without to drop-to-basic patch:
- Expected: The system boots the same as an unmodified ROM 4.
3. With a bootable ProDOS RAMdisk containing ProDOS, boot the system from power off or ctrl-oa-reset.
- Expected: The system boots from RAM disk, an inverse or flashing R may appear on the left of line 24 of the display.
4. Power on the system with the ca key pressed or use ctrl-ca-reset.
- Expected: The menu is displayed.
5. RAM disk recovery:
1. Battery-backed RAM present with bootable RAM disk: Power off the machine and leave it for 1 hr. Power on.
- Expected: The system boots from RAM disk.
2. Non-battery-backed RAM present with bootable RAM disk: Erase main RAM from 0300 up (e.g. in monitor: `300:00` then `301<300.BFFEM`) and press ctrl-reset.
- Expected: The system boots from RAM disk.
Expected SlotScan output:
```
SlotScan Version 1.62 Copyright 1989-1994 by Robert S. Claney
--------------------------------------------------------------------------------
Apple Computer Type: //c, ROM Ver 4 (Newer Mem. Exp.)
Processor type: 65c02
Total RAM: 128K
-----Scanning for peripherals-----
Port 1: Serial Port (#1)
Port 2: Serial Port (#1)
Port 3: 80-Column Port (#8)
Port 4: RamCard SmartPort: 1 device found
Manufacturer #0 (Unknown)
Device 1: "RAMCARD", Size: 2048 Blocks (1024K, 1 Meg)
Type: Mem. expansion Version: 0.102
Addl. info: (None)
Port 5: SmartPort: 0 devices found
Port 6: Disk ][ Port
Device Size: 280 Blocks (140K)
Port 7: Mouse Port (#0)
Done. Press any key to continue, or Control-P to get a printout
```
#### Menu Item Functional Tests
All cases: When any menu option is selected, the "ROM 4X MM/DD/YY" message is displayed on the bottom of the screen.
0. Monitor
- Expected: We are dropped into the monitor immediately.
1. Reboot
- Expected: System boots as normal.
2. Zero RAM Card and Reboot
- Expected: Reboot if no card RAM present. Otherwise, counter appears in upper left corner and card RAM is cleared.
3. Diagnostics
- Expected: System enters built-in diagnostics as if ctrl-oa-ca-reset was pressed.
4. RAM Card Diagnostics
- Expected: System enters RAM card diagnostics if card RAM present, then/or (no mem) drops to monitor when exited by failure or user escape key.
5. Boot SmartPort
- Expected: The system boots from a SmartPort device, skipping the RAM card and 5.25 floppy drives.
6. Boot Internal 5.25
- Expected: The system boots from the internal 5.25 drive, skipping the RAM card. The system may proceed to the SmartPort if no disk is found.
7. Boot External 5.25
- Expected: The system boots from the external 5.25 drive, skipping the RAM card. The system may proceed to the SmartPort if no disk is found.
### Ideas for Future
- Replace Apple Slinky code with RamFactor code. (Difficulty: Hard)
# The Whole Story
The Apple II Plus was the first computer my family owned. It's what I learned to program on. We spent hours at the keyboard typing in programs from magazines, and eventually I learned to modify them and write my own. As technology progressed, I switched to PCs like almost everyone else and largely forgot about the Apple II after the 90s. I still had an interest in my Apple IIs and managed to get hold of some more gear, including a //c and a couple of IIgs machines. The prices bottomed out a few years after Apple discontinued the line. Eventually I moved on and boxed it all up, sold a bit, but I held on to most of the interesting stuff I'd acquired.
## The Beginning and the New Old
I got back into the Apple II a few months ago after I read [this story](http://www.osnews.com/story/29400/Why_the_Apple_II_ProDOS_2_4_release_is_the_OS_news_of_the_year). What? A new ProDOS? I must try it! So I dusted off some of my old Apple II gear and the next thing you know I had ProDOS 2.4.1 running on my Apple //c.
So then I go searching around the net only to discover that not only is there a pretty active user community, but that people had been making *new hardware* for it, the coolest of which emulate floppy and hard drives. There are Ethernet cards, memory expansions, VGA adapters, FPGA cards, and all kinds of other hardware.
Jumping into this new hardware for old computers craze, I bought a [Ram Express II+](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=144#review) from [A2 Heaven](http://www.a2heaven.com/) and was excited to try out the whopping (really!) 1 MB of battery-backed memory in it, as well as the clock. I formatted the card, loaded up ProDOS, and rebooted... Instant-on! Much fast!
Then I powered down for a while to do non-hobby things. Upon return, instead of a super fast boot to ProDOS, I got the the familiar clunking of the Apple 5 1/4 floppy drive recalibrating. I thought to myself that surely I was doing something wrong, so I rebooted ProDOS and found the card in its initial state without what I had copied on it.
Bummer.
## Resetting My Expectations
Assuming the worst - that the battery wasn't working for the memory, I replaced the battery and checked that the clock had been working. Still the same results.
I emailed the maker, Plamen, and asked about it. He told me that something about the //c causes the card to reformat after power off. To prove it, he had me write some values to the card manually and then read them back after powering off for an hour.
Turns out my expectations were wrong. I followed up with a thanks and a "maybe I can find out in the firmware where that happens and fix it." Another user got a card a few days later, and we discussed the matter over FaceBook, where I joked that it would be a "few NOPs" and when another user suggested adding boot from external drive back to the firmware I said that that would be much harder. The exact opposite of what it was.
## Down the Rabbit Hole
I am a reasonably competent 8-bit assembly language programmer, always had been since I was a kid working on my Apple II Plus. But I know the Z80 better these days, it'd been a long time since I touched the 6502.
Armed with a copy of Apple //c Technical Reference in PDF format, I printed out the firmware listings (computers used to have schematics and firmware listings available!) and started to look for where the few NOPs would have to go.
## pwerdup <> pwrbyte and numbanks == 0
After looking through the firmware listing, it was clear that the reason that the RAM disk was being reinitialized was because certain memory locations did not contain the contents needed to tell the firmware that it was already initialized.
These memory locations are in what are called "screen holes" in the Apple II. Veteran Apple II users know what these are, but if you are unaware: These are areas of the text screen that do not result in display output. The Apple II text screen is not linearly organized and there are 8-byte "holes" after each 120 displayable locations. Each of these holes is allocated to one of the card slots for slotted Apple IIs, or to the various ports of the unslotted Apple IIs.
In particular the firmware checks $77c (pwerdup) for the value $a5 (pwrbyte), and $47c for the number of banks detected on the card. After some experimenting I determined that it is enough to set both of these to the proper values and the card will be recovered. Setting these on an uninitialized card can result in all kinds of bad behavior, from crashes to perpetual "UNABLE TO LOAD PRODOS" depending on what is in the boot block. Changing the firmware to ignore them is not possible because we cannot assume the user is using the card as a RAM disk.
That means that instead of disabling code that purposely trashed the RAM disk, I had to add code to find an existing RAM disk and prevent re-initialization.
## From NOP codes to opcodes
Well, as we know, adding new code to existing firmware is a lot harder than disabling existing code. We can't change where things already are, and any patches have to be at places that wouldn't break existing software.
But now I at least have some initial requirements:
> 1: Identify an existing, hopefully bootable RAM disk.
> 2: If it exists, prevent it from being re-initialized by setting the two screen holes to the proper values.
> 3: If no special action is taken, everything should "look normal."
The Apple II RAM card code is more simple than, say, the Applied Engineering RamFactor card, and I haven't seen anything that documents being able to boot DOS 3.3 or Pascal from it, so I decided that I only needed to see if the RAM disk was ProDOS and bootable. So I would solve the first requirement by checking for a ProDOS boot block, conveniently starting at location zero on the card.
The second requirement we already sorted out above. The third is that if you don't have a bootable RAM disk, booting the system should look and work just like it did before. That's just good practice to not change things for the sake of change.
## Feature Creep
Then I thought to myself... what if the RAM disk is screwed up, and we keep re-initializing it and attempting to boot? That's kind of a pain, but could be solved with documentation. "Hit ctrl+reset and then pr#6 to boot a disk, then format the card with a program."
Nah. Why not detect if the closed-apple key is pressed with ctrl+reset and clear out the RAM disk?
> 4: Provide a way to erase a messed up RAM disk.
Well, that wasn't so bad. But what if the user doesn't want to erase it outright? Perhaps they want to try to recover some data. Maybe we can leave it corrupted and just boot something else.
> 5: Provide a way to skip booting the RAM disk.
Well that's at least two options, and between the two apple key+reset combinations, no room for more than one additional action, so maybe that action should be a menu.
> 6: Present a menu to the user to decide what action to take.
Well since we are doing that, it's probably easy to get the IIc to try to boot whatever device you want, and maybe the user doesn't want to do any of the things on the menu.
> 7: Let the user select a variety of boot devices.
Hell, while we are at it, let's give some easy access to internal functionality that requires more keys to be held down or calling routines in memory with BASIC or monitor commands.
> 8: Let the user get access to the internal diagnostic routines.
Then, there was that guy that wanted to be able to boot an external 5.25 drive like the original //c firmware had. This turned out to be fairly easy to do.
> 9: Let the user boot an external 5.25 drive.
Well, I coded all the above up over the course of two days (short story, keep reading for longer version) and then I had another feature:
> 10: Don't make it too easy to trash the RAM disk accidentally by picking menu option 2 and 4.
Sigh. Now it's a real software project.
## Banking on It
After examining the //c ROM, it was clear that there was not any room in the main ROM bank to implement the above code, so the code had to run in the alternate bank.
When switching the ROM bank from ROM, you have to pull some tricks to make the switch safely and continue execution on the right code path. The most compact way is to have a free place in both banks, which we have in the //c. The less compact ways are to make use of subroutines already in the ROMs that can switch based on a memory location in RAM or by pushing the address to jump to on the stack and making use of the routine that switches banks and then does an RTS instruction (this is known as the 'RTS trick'). The latter are more useful for one-way jumps.
The second bank is also missing features that we take for granted, such as the various routines for displaying characters on the screen, clearing the screen, etc. so we need to find out which of these we should write ourselves and which ones to make available via a bank switch.
In the end the most difficult routine we might need from the firmware that we have to call and get back from is to clear the screen, since it has to be done without overwriting the screen holes. Putting stuff on the screen is easy and there are compact ways of doing it. The other functionality is pretty much one-way and for the common functions like jumping to an address for booting, the main needed functionality is in the firmware already.
In the end, I only needed the two-way switchers to enter and exit the reset handler (detects closed-apple and shows the menu), the boot handler (which handles the function selected by the user), and clearing the screen. I decided to use the banner screen clear that prints 'Apple //c' at the top for me. That meant that I needed 22 bytes of common unused address space in both banks for these, which I found.
The remaining switcher is one-way from alt bank to main bank, and I use the RTS trick.
## Patching Pants that Don't Have Holes
So now we have to patch the boot process to recover the RAM disk, and the reset process to capture the closed apple key and present the menu.
Well, firmware programmers for resource-limited machines don't exactly leave a bunch of space available for inserting your own routines, so I had to figure out where I could replace existing code with jumps and no-op padding. Ideally you patch instructons in a 1:1 manner, a 3 byte instruction is replaced by another 3-byte instruction so that code that makes assumptions about where it can jump or call can continue to rely on those assumptions.
With the //c firmware I did not have that luxury for one of the patches, so hopefully it does not break things. I think the chances are pretty low.
I searched over the course of an hour to figure out the best place to put the patches, and ROM 4X is the result.
## Conclusion
So now you have it and you know how it got here. Enjoy ROM 4X.
Why did I do this? See [story.md](story.md).

View File

@ -3,7 +3,14 @@
rm -f "romXx_dist-*.zip"
ROM4X="rom4x/iic_rom4x.bin"
ROM5X="rom5x/iic+_rom5x.bin"
FNAME="romXx_dist-`date --rfc-3339=date`.zip"
case `uname -s` in
Linux)
FNAME="romXx_dist-`date --rfc-3339=date`.zip"
;;
*)
FNAME="romXx_dist-`date '+%Y-%M-%d'`.zip"
;;
esac
[ -f "${ROM4X}" ] && zip "${FNAME}" "${ROM4X}"
[ -f "${ROM5X}" ] && zip "${FNAME}" "${ROM5X}"

View File

@ -2,16 +2,15 @@ Inventory of zeros of 5 bytes or more in the IIc Plus ROM
Main Bank
C1FB - 5 bytes
C4EE - 12 bytes
C4EE - 12 bytes - delete key fix
C5F3 - 5 bytes
C6FB - 5 bytes
C9A1 - 9 bytes
C6FB - 5 bytes - but Disk II ID here at C6FF
C9A1 - 9 bytes - beep merlin fix part 2
CFF9 - 7 bytes - ROM 5X switcher
Aux Bank
C572 - 8 bytes
C7FB - 8 bytes
C7FC - 7 bytes
C7FC - 7 bytes - ROM 5X dispatch jump at $C7FF
CE00 - 512 bytes not usable (MIG space)
D3B5 - 75 bytes - Accelerator menu text
D516 - 234 bytes - ROM 5X boot

11
rom4x/B0_C2F0_del_key.s Normal file
View File

@ -0,0 +1,11 @@
; call RDCHAR, convert DEL to space
; for patching into GETLN1/NXTCHAR (at $FD75)
.code
.include "iic.defs"
.org $c2f0
jsr $cced
cmp #$ff
bne :+
lda #$88
: rts

View File

@ -10,9 +10,5 @@
.byte 0 ; rom4x present
cbtfail: jsr setvid
jsr setkbd
lda #>(nbtfail-1)
pha
lda #<(nbtfail-1)
pha
jmp swrts2
lda #$01 ; cmd = boot fail1
jmp $c7fc ; to dispatcher

View File

@ -1,12 +0,0 @@
.code
.include "iic.defs"
.org gorst4x
sta rombank ; gorst4x
jmp rst4xrtn ; in other bank jmp reset4x
sta rombank ; gobt4x
jmp bt4xrtn ; in other bank jmp boot4x
sta rombank ; gobanner
jsr banner ; in other bank jmp $c784 for now
sta rombank ; return to other bank
rts ; should never get here

10
rom4x/B0_C7FC_4x_switch.s Normal file
View File

@ -0,0 +1,10 @@
; There's a bit of luck in the firmware
; there are 4 $00 bytes at $C7FC in the main bank, of which the last
; is "AppleTalk version" according to the ROM $03 source code
; in the tech ref, and should be left at $00.
; There are also 7 $00 bytes at $C7FC in the aux bank. So if we switch at
; $C7FC, then we get a 4 bytes in the aux bank, just enough for a jump.
.code
.org $c7fc
sta $c028

12
rom4x/B0_C9A1_load_save.s Normal file
View File

@ -0,0 +1,12 @@
.pc02
DISPATCH = $C7FC
.include "iic.defs"
.code
.org $C9A1 ; 9 bytes here
.ifdef EN_XMODEM
load: lda #$03
.byte $2C ; BIT abs
save: lda #$02
jmp DISPATCH
.endif

20
rom4x/B0_CFE5_monrw.s Normal file
View File

@ -0,0 +1,20 @@
; Monitor R and W command handler for XModem
GETNUM = $FFA7
DISPATCH = $C7FC
.pc02
.include "iic.defs"
.code
.org $CFE5 ; 20 bytes here
.ifdef EN_XMODEM
lp: jsr GETNUM
;sta $00 ; DEBUG, comment out for release
cmp #$EB ; 'R'
beq :+
cmp #$F0 ; 'W'
beq :+
rts
: jsr DISPATCH
bra lp
.endif

View File

@ -0,0 +1,15 @@
; This is the dispatch routine to call the primary ROM 4X
; functions of intercepting RESET and the boot process.
; If one enters at $CFF9, the command $A9 is loaded and
; we go to the BELL1 hijack. If entering at $CFFA, we
; load the command $EA and proceed the same way.
; thus we get two dispatch codes in 6 bytes.
.include "iic.defs"
.code
.org $cff9 ; 7 bytes available here, but don't count on $CFFF
lda #$a9 ; lda opcode
nop ; jmp/jsr $cffa does lda #$ea
jmp $c7fc ; jump to 4X dispatcher
; total 6 bytes.

10
rom4x/B0_D06C_asft_ldsv.s Normal file
View File

@ -0,0 +1,10 @@
.pc02
XM_LOAD = $C9A1
XM_SAVE = $C9A4
.include "iic.defs"
.org $D06C
.ifdef EN_XMODEM
.addr XM_LOAD-1
.addr XM_SAVE-1
.endif

View File

@ -1,6 +1,7 @@
; patch PWRUP to call boot4x
.code
.include "iic.defs"
.code
.org $fab4
nop
jmp gobt4x

View File

@ -1,6 +1,7 @@
; patch RESET.X to call reset4x
.code
.include "iic.defs"
.code
.org $fac8
jmp gorst4x

View File

@ -0,0 +1,6 @@
; patch GETLN1 to call delete key handler
.code
.include "iic.defs"
.org $fd75
jsr $c2f0

View File

@ -0,0 +1,7 @@
.pc02
.include "iic.defs"
.code
.org $FF73
.ifdef EN_XMODEM
jsr $CFE5 ; R/W patch
.endif

View File

@ -1,13 +0,0 @@
.code
.include "iic.defs"
.org gorst4x
sta rombank ; gorst4x
jmp reset4x ; in other bank jmp rstxrtn
sta rombank ; gobt4x
jmp boot4x
sta rombank ; gobanner
jmp $c784 ; in other bank jsr appleii
sta rombank
rts ; not in other bank

6
rom4x/B1_C7FF_4x_jump.s Normal file
View File

@ -0,0 +1,6 @@
; See B0 C7FC patch for description
.include "iic.defs"
.code
.org $c7ff
jmp rom4x_disp

View File

@ -1,216 +1,453 @@
.psc02
.code
.include "iic.defs"
.org reset4x
stz power2 + rx_mslot ; action = normal boot
asl butn1 ; closed apple
bcs ckdiag
exitrst: jmp gorst4x ; return to RESET.X
; to enable/disable XModem, see EN_XMODEM in iic.defs
.org rom4x_disp
.proc dispatch
cmp #$a9 ; reset patch
bne :+
bra reset4x
: cmp #$ea ; boot patch
bne :+
jmp boot4x
: cmp #$01 ; $01 = new boot fail routine
bne :+
jmp nbtfail
.ifdef EN_XMODEM
; XModem functions
: cmp #$02 ; $02 = AppleSoft SAVE
bne :+
jmp asftsave
: cmp #$03 ; $03 = AppleSoft LOAD
bne :+
jmp asftload
: cmp #$F0 ; $F0 = monitor W(rite)
bne :+
jmp monwrite
: cmp #$EB ; $EB = monitor R(ead)
bne :+
jmp monread
.endif
: sta $00 ; for debug, comment if not using
lda #>(monitor-1)
pha
lda #<(monitor-1)
pha
jmp swrts2 ; jump to monitor
.endproc
; next is snippet of code to boot external 5.25
.proc bootext
lda #$e0
ldy #$01 ; unit #
ldx #$60 ; slot #
jmp $c60b ; jump into Disk II code
.endproc
.proc reset4x
stz power2 + rx_mslot ; action = normal boot
asl butn1 ; closed apple
bcs ckdiag
exitrst: lda #>(rst4xrtn-1)
pha
lda #<(rst4xrtn-1)
pha
jmp swrts2
; check to see if both apples are down
ckdiag: bit butn0 ; open apple
bmi exitrst ; return to RESET.X
ckdiag: bit butn0 ; open apple
bmi exitrst ; return to RESET.X
; present menu because only closed apple is down
menu4x: jsr gobanner ; "Apple //c"
ldx #$00 ; menu start
jsr disp ; show it
jsr gtkey
cmp #$b0 ; "0"
bne ckkey1
ldx #$ff ; reset stack
txs
lda #>(monitor-1) ; monitor entry on stack
pha
lda #<(monitor-1)
pha
jmp swrts2 ; rts to enter monitor
ckkey1: cmp #$b2 ; "2"
beq doconf
cmp #$b4 ; "4"
bne ckkey2
doconf: jsr confirm
bne menu4x ; go back to menu4x
ckkey2: sec
sbc #$b0 ; ascii->number
bmi menu4x ; < 0 not valid
cmp #$08
bpl menu4x ; > 7 not valid
sta power2 + rx_mslot ; for boot4x
stz softev + 1 ; deinit coldstart
stz pwerdup ; ditto
bra exitrst
gtkey: lda #$60
sta ($0),y ; cursor
sta kbdstrb ; clr keyboard
kbdin: lda kbd ; get key
bpl kbdin
sta kbdstrb ; clear keyboard
sta ($0),y ; put it on screen
rts
menu4x: jsr ntitle ; "Apple //c"
ldx #$00 ; menu start
jsr disp ; show it
jsr gtkey
cmp #$b0 ; "0"
bne ckkey1
ldx #$ff ; reset stack
txs
lda #>(monitor-1) ; monitor entry on stack
pha
lda #<(monitor-1)
pha
jmp swrts2 ; rts to enter monitor
ckkey1: cmp #$b2 ; "2"
beq doconf
cmp #$b4 ; "4"
bne ckkey2
doconf: jsr confirm
bne menu4x ; go back to menu4x
ckkey2: sec
sbc #$b0 ; ascii->number
bmi menu4x ; < 0 not valid
cmp #$08
bpl menu4x ; > 7 not valid
sta power2 + rx_mslot ; for boot4x
stz softev + 1 ; deinit coldstart
stz pwerdup ; ditto
bra exitrst
.endproc
.proc gtkey
gtkey: lda #$60
sta ($0),y ; cursor
sta kbdstrb ; clr keyboard
kbdin: lda kbd ; get key
bpl kbdin
sta kbdstrb ; clear keyboard
sta ($0),y ; put it on screen
rts
.endproc
; display message, input x = message start relative to msg1
disp: stz $0 ; load some safe defaults
lda #$04
sta $1
ldy #$0 ; needs to be zero
disp0: lda msg1,x ; get message byte
bne disp1 ; proceed if nonzero
rts ; exit if 0
disp1: inx ; next byte either way
cmp #$20 ; ' '
bcc disp2 ; start of ptr if < 20
eor #$80 ; invert high bit
sta ($0),y ; write to mem
inc $0 ; inc address low byte
bra disp0 ; back to the beginning
disp2: sta $1 ; write address high
lda msg1,x ; get it
sta $0 ; write address low
inx ; set next msg byte
bra disp0 ; back to the beginning
confirm: pha
ldx #(msg3-msg1) ; ask confirm
jsr disp
jsr gtkey
plx
ora #$20 ; to lower
cmp #$f9 ; "y"
php
txa
plp
rts
.proc disp
disp: stz $0 ; load some safe defaults
lda #$04
sta $1
ldy #$0 ; needs to be zero
disp0: lda msg1,x ; get message byte
bne disp1 ; proceed if nonzero
rts ; exit if 0
disp1: inx ; next byte either way
cmp #$20 ; ' '
bcc disp2 ; start of ptr if < 20
eor #$80 ; invert high bit
sta ($0),y ; write to mem
inc $0 ; inc address low byte
bra disp0 ; back to the beginning
disp2: sta $1 ; write address high
lda msg1,x ; get it
sta $0 ; write address low
inx ; set next msg byte
bra disp0 ; back to the beginning
.endproc
.proc confirm
pha
ldx #(msg3-msg1) ; ask confirm
jsr disp
jsr gtkey
plx
ora #$20 ; to lower
cmp #$f9 ; "y"
php
txa
plp
rts
.endproc
; msg format
; A byte < $20 indicates high byte of address.
; Next byte must be low byte of address. Anything
; else are characters to display and will have their
; upper bit inverted before being written to the screen.
msg1 = *
.byte $05,$06,"0 Monitor"
.byte $05,$86,"1 Reboot"
.byte $06,$06,"2 Zero RAM Card and Reboot"
.byte $06,$86,"3 Diagnostics"
.byte $07,$06,"4 RAM Card Diagnostics"
.byte $07,$86,"5 Boot SmartPort"
.byte $04,$2e,"6 Boot Int. 5.25"
.byte $04,$ae,"7 Boot Ext. 5.25"
.byte $07,$5f,"By M.G."
msg2: .byte $07,$db,"ROM 4X 04/08/17"
.byte $05,$ae,$00 ; cursor pos in menu
msg3: .byte $05,$b0,"SURE? ",$00
.res boot4x - *, 0
.org boot4x
jsr gobanner ; "Apple //c"
jsr rdrecov ; try to recover ramdisk
lda power2 + rx_mslot ; get action saved by reset4x
beq boot4 ; if zero, continue boot
ldx #(msg2-msg1) ; short banner offset
jsr disp ; display it
lda power2 + rx_mslot ; boot selection
btc2: cmp #$02 ; clear ramcard
bne btc3
jsr rdclear ; do clear
bra boot4
btc3: cmp #$03 ; Diags
bne btc4
jmp $c7c4
btc4: cmp #$04 ; RX diags
bne btc5
ldx #$ff
txs ; reset stack
jsr rdinit ; get x and y loaded
stx sl_devno ; diags need this
jsr testsize ; compute card size
lda #>(monitor-1) ; load "return" address
pha ; into stack so that we
lda #<(monitor-1) ; exit card test into
pha ; the monitor
lda numbanks,y ; get the card size in banks
bne dordiag ; do diag if memory present
jmp swrts2 ; otherwise jump to monitor
dordiag: jmp $db3a ; diags
;bra boot4
btc5: cmp #$05 ; boot smartport
beq boot5
btc6: cmp #$06 ; boot int drive
beq boot6
btc7: cmp #$07 ; boot ext drive
bne boot4 ; none of the above
; copy small routine to $800 to boot
; external 5.25
ldy #(bt4xend-bootext+1)
btc7lp: lda bootext,y
sta $800,y
dey
bpl btc7lp
lda #$08 ; copy done
bra bootsl
boot4: lda #rx_mslot ; boot slot 4
bra bootsl
boot5: lda #$c5 ; boot slot 5
bra bootsl
boot6: lda #$c6 ; boot slot 6
bootsl: ldx #$00 ; low byte of slot
bootadr: stx $0 ; store address
sta $1 ; return to bank 0 does jmp (0)
endbt4x: jmp gobt4x ; continue boot
rdrecov: jsr rdinit ; init ramcard
lda pwrup,y ; get power up flag
cmp #pwrbyte ; already initialized?
beq recovdn ; exit if initialized
jsr testsize ; does not wreck x or y
lda numbanks,y ; get discovered # banks
beq recovdn ; no mem
stz addrl,x ; set slinky address 0
stz addrm,x
stz addrh,x
lda data,x ; start check for bootable ramdisk
cmp #$01
bne recovdn ; not bootable
lda data,x ; next byte should be nonzero and not $ff
beq recovdn ; not bootable
cmp #$ff
beq recovdn ; not bootable
lda #pwrbyte
sta pwrup,y ; set power byte
lda #'R' ; tell user
sta $7d0 ; on screen
recovdn: rts
.byte $05,$06,"0 Monitor"
.byte $05,$86,"1 Reboot"
.byte $06,$06,"2 Zero RAM Card and Reboot"
.byte $06,$86,"3 Diagnostics"
.byte $07,$06,"4 RAM Card Diagnostics"
.byte $07,$86,"5 Boot SmartPort"
.byte $04,$2e,"6 Boot Int. 5.25"
.byte $04,$ae,"7 Boot Ext. 5.25"
.byte $07,$5f,"By M.G."
msg2: .byte $07,$db,"ROM 4X 10/01/18"
.byte $05,$ae,$00 ; cursor pos in menu
msg3: .byte $05,$b0,"SURE? ",$00
.dword .time ; embed POSIX build time
; Boot4X - the boot portion of the program
.proc boot4x
jsr ntitle ; "Apple //c"
jsr rdrecov ; try to recover ramdisk
lda power2 + rx_mslot ; get action saved by reset4x
beq :+ ; unset, go look for config on ram card
pha ; save it
bra selboot ; now go do it
: lda numbanks,y ; (y should be set in rdrecov) ram card present?
beq boot6 ; nope, boot slot 6
jsr getcfg ; try to get config
bcs boot4 ; no config, normal boot
;stx $7d2
;sty $7d3
phx ; config present, save it and move on
lda #'C' ; tell user
sta $7d1 ; on screen
selboot: ldx #(msg2-msg1) ; short offset
jsr disp ; display it
pla ; get boot selection from stack
;sta $7d2
btc2: cmp #$02 ; clear ramcard
bne btc3
jsr rdclear ; do clear
bra boot4
btc3: cmp #$03 ; Diags
bne btc4
jmp $c7c4
btc4: cmp #$04 ; RX diags
bne btc5
ldx #$ff
txs ; reset stack
jsr rdinit ; get x and y loaded
stx sl_devno ; diags need this
jsr testsize ; compute card size
lda #>(monitor-1) ; load "return" address
pha ; into stack so that we
lda #<(monitor-1) ; exit card test into
pha ; the monitor
lda numbanks,y ; get the card size in banks
bne dordiag ; do diag if memory present
jmp swrts2 ; otherwise jump to monitor
dordiag: jmp $db3a ; diags
;bra boot4
btc5: cmp #$05 ; boot smartport
beq boot5
btc6: cmp #$06 ; boot int drive
beq boot6
btc7: cmp #$07 ; boot ext drive
bne boot4 ; none of the above
; copy small routine to $800 to boot
; external 5.25
ldy #.sizeof(bootext)
btc7lp: lda bootext,y
sta $800,y
dey
bpl btc7lp
lda #$08 ; copy done
bra bootsl
boot4: lda #rx_mslot ; boot slot 4
bra bootsl
boot5: lda #$c5 ; boot slot 5
bra bootsl
boot6: lda #$c6 ; boot slot 6
bootsl: ldx #$00 ; low byte of slot
bootadr: stx $0 ; store address
sta $1 ; return to bank 0 does jmp (0)
endbt4x: lda #>(bt4xrtn-1)
pha
lda #<(bt4xrtn-1)
pha
jmp swrts2
.endproc
.proc rdrecov
jsr rdinit ; init ramcard
lda pwrup,y ; get power up flag
cmp #pwrbyte ; already initialized?
beq recovdn ; exit if initialized
jsr testsize ; does not wreck x or y
lda numbanks,y ; get discovered # banks
beq recovdn ; no mem
stz addrl,x ; set slinky address 0
stz addrm,x
stz addrh,x
lda data,x ; start check for bootable ramdisk
cmp #$01
bne recovdn ; not bootable
lda data,x ; next byte should be nonzero and not $ff
beq recovdn ; not bootable
cmp #$ff
beq recovdn ; not bootable
lda #pwrbyte
sta pwrup,y ; set power byte
lda #'R' ; tell user
sta $7d0 ; on screen
recovdn: rts
.endproc
; zero ram card space
rdclear: jsr rdinit ; init ramcard
jsr testsize ; get size
lda numbanks,y ; # of 64Ks to write
beq clrdone ; no memory
lda #$c0 ; 'A' - 1
sta $400 ; upper left corner
stz addrl,x ; slinky address 0
stz addrm,x
stz addrh,x
clbnklp: inc $400 ; poor mans progress meter
ldy #$00
cl64klp: ldx #$00 ; loop for all pages in bank
cl256lp: txa ; loop for all bytes in page
ldx #rx_devno
stz data,x ; write a zero to card
tax
dex
bne cl256lp ; 256 byte loop
dey
bne cl64klp ; 64K loop
ldx #rx_mslot
dec numbanks,x
bne clbnklp ; if more banks
clrdone: ldx #rx_mslot
stz pwrup,x ; zero powerup byte
lda #$a0 ; ' '
sta $400 ; clear progress
rts
rdinit: bit rx_mslot*$100 ; activate registers
ldy #rx_mslot ; slot offset
ldx #rx_devno ; register offset
rts
; next is snippet of code to boot external 5.25
bootext: lda #$e0
ldy #$01
ldx #$60
jmp $c60b
bt4xend = *
.proc rdclear
jsr rdinit ; init ramcard
jsr testsize ; get size
lda numbanks,y ; # of 64Ks to write
beq clrdone ; no memory
lda #$c0 ; 'A' - 1
sta $400 ; upper left corner
stz addrl,x ; slinky address 0
stz addrm,x
stz addrh,x
clbnklp: inc $400 ; poor mans progress meter
ldy #$00
cl64klp: ldx #$00 ; loop for all pages in bank
cl256lp: txa ; loop for all bytes in page
ldx #rx_devno
stz data,x ; write a zero to card
tax
dex
bne cl256lp ; 256 byte loop
dey
bne cl64klp ; 64K loop
ldx #rx_mslot
dec numbanks,x
bne clbnklp ; if more banks
clrdone: ldx #rx_mslot
stz pwrup,x ; zero powerup byte
lda #$a0 ; ' '
sta $400 ; clear progress
rts
.endproc
.proc rdinit
bit rx_mslot*$100 ; activate registers
ldy #rx_mslot ; slot offset
ldx #rx_devno ; register offset
rts
.endproc
; arrange a sequence of RTS tricks to display title screen
.proc ntitle
lda #>(swrts2-1) ; put return addr of swrts/swrts2 on stack
pha
lda #<(swrts2-1)
pha
lda #>(banner-1) ; put addr of the Title routine on the stack
pha
lda #<(banner-1)
pha
jmp swrts2 ; jump to swrts2
.endproc
; --------------------------------------------------
; config getter
; values and locs
;chktype = $5a ; 'CFG'
chktype = $06 ; 'BIN' - easy to set auxtype with bsave
entbuf = $0280
; zp locs, safe to use under our circumstances
blkptrl = $06 ; block we are going to read
blkptrh = blkptrl + 1
entryl = $08 ; length of an entry
nentries = $09 ; number of entries per block
blkcnt = $0a ; block counter for safety
.proc getcfg
jsr rdinit
lda #$02 ; first block of volume directory
sta blkptrl
stz blkptrh
jsr setblk
lda #$03 ; want this to EOR out to $00
ldy #$04 ; check 4 bytes
: eor data,x ; check previous blk ptr for zero
dey
bne :-
cmp #$00 ; see if A is 0
bne nocfg ; not vol dir key block, end of mission
lda #$04 ; where we expect to find volume dir header
sta addrl,x ; set data ptr
lda data,x ; volume directory header first byte
and #$f0 ; mask off length
cmp #$f0 ; storage type is $f?
bne nocfg ; nope, eom
lda #$23 ; offset of directory entry length in block
sta blkcnt ; may as well use this for the safety check
sta addrl,x ; set data pointer
lda data,x ; grab entry length
sta entryl ; save it
ldy data,x ; entries per block
sty nentries ; save it for later
bra nxtbl1 ; skip setting slinky block and nentries, already there
nxtblk: jsr setblk
beq nocfg ; just set block 0, eom
dec blkcnt ; decrease safety counter
beq nocfg ; and if we hit zero, bail out
ldy nentries ; restore # entries per block+1 into y
nxtbl1: jsr gnxtblk ; set next block pointer, leave data ptr at offset $04
nxtent: dey ; next entry, assumes y has # of entries remaining to check
bmi nxtblk ; next block if we dont have any more (hope we don't have more than $7F of them)
jsr rentry ; read directory entry
; now check storage type and name length
lda entbuf
and #$f0
beq nxtent ; if storage type = $0
and #$c0 ; mask out values $1-$3 (normal files)
bne nxtent ; if any other bits set
lda entbuf+$10 ; get type
cmp #chktype ; and check
bne nxtent ; no match, try next entry
lda entbuf ; get storage type and length
and #$0f ; mask in length
cmp #(fname_-fname) ; check length
bne nxtent
phy ; save num entries
tay ; a still has length
mtchlp: lda entbuf,y ; get file name char
cmp fname-1,y ; compare to what we are looking for
bne nomtch ; if no match
dey
bne mtchlp ; check next char
; we have a match
ply ; discard saved num entries
ldx entbuf+$1f ; low byte of aux type
ldy entbuf+$20 ; high byte of aux type
clc
rts
nomtch: ply ; restore saved num entries
bra nxtent
; no config found
nocfg: sec
rts
fname: .byte "BOOTX"
fname_ = *
.endproc
; get next block pointer into blkptrl and blkptrh
.proc gnxtblk
lda #$02 ; assumes we are in first half of block
sta addrl,x ; set byte offset
lda data,x ; next block low byte
sta blkptrl
lda data,x ; next block high byte
sta blkptrh
rts
.endproc
; set slinky address to block pointer, address = blk num * 2 * $100
.proc setblk
stz addrl,x ; zero low byte of slinky address
lda blkptrl ; low byte of block pointer
; sta $7e0
asl ; shift left, high bit to c
sta addrm,x ; put into middle byte of slinky address
lda blkptrh ; get high byte of block pointer
; sta $7e1
rol ; rotate left, c into bit 0
sta addrh,x ; set high byte of slinky address
ora addrm,x ; set z flag if we just set block 0
rts
.endproc
; read a ProDOS directory entry from slinky
.proc rentry
phy ; preserve y
ldy #$00
: lda data,x
sta entbuf,y
iny
cpy entryl
bne :-
ply
rts
.endproc
; Display new boot failure message and jump to BASIC
.proc nbtfail
ldx #msglen
lp1: lda bootmsg,x
ora #$80
sta $7d0+19-(<msglen/2),x
dex
bpl lp1
lda #23 ; last line
sta cv
lda #>(basic-1)
pha
lda #<(basic-1)
pha
jmp swrts2
bootmsg:
.byte "No bootable device."
msglen = * - bootmsg - 1
.endproc
.ifdef EN_XMODEM
.include "inc/xmodem.s"
.endif

View File

@ -1,19 +0,0 @@
.code
.include "iic.defs"
.org nbtfail
ldx #msglen
lp1: lda bootmsg,x
ora #$80
sta $7d0+19-msglen/2,x
dex
bpl lp1
lda #23 ; last line
sta cv
lda #>(basic-1)
pha
lda #<(basic-1)
pha
jmp swrts2
bootmsg: .byte "No bootable device."
msglen = * - bootmsg - 1

255
rom4x/README.md Normal file
View File

@ -0,0 +1,255 @@
# ROM 4X by MG
ROM 4X is a collection of enhancements to the Apple //c version 4. See the top level [README.md](../README.md) for more general information on ROM 4X and ROM 5X.
It adds the following features to the Apple //c:
- Enter the monitor unconditionally.
- Reboot the machine (enter standard boot sequence).
- Zero the RAM card, in case it is corrupted.
- Execute the machine and RAM card diagnostics.
- Tell the machine to boot the SmartPort, the internal floppy drive, or an external floppy drive.
- The system drops to BASIC if no bootable device is found (this is the default behavior in the IIc Plus).
- Configure default boot device by saving a file on the RAM Disk.
- As of the 10/01/2018 release, provides XModem-CRC features (see [xmodem.md](xmodem.md)).
RAM expansion cards known to work with ROM 4X include the Apple Memory Expansion Card (but no battery!), and the A2Heaven [RAM Express II](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=146) for the original //c, and the [RAM Express II+](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=144) for the memory-expandable //c and IIc Plus.
# User Guide
## Obtaining
**Due to copyright law, I do NOT provide full ready-to-burn binaries at this time. Some assembly (but not necessarily an assembler) is required!**
You may either build it yourself which guarantees that you have the latest version and feature branch that you want, or you can check the [web site for ROM 4X/5X](http://apple2.guidero.us/doku.php?id=projects:rom_4x_and_5x) for binary releases.
### Binary Releases
The binary releases consist of a zip file with the assembled and linked patches, a checksum file, and a Bash script. You must have a unix-like system (MacOS, Linux, etc.) or, on Windows, Cygwin or the Windows Subsystem for Linux configured.
The shell script will perform the following:
* Download the original Apple ROM image from a well-known location.
* Apply the patches.
* Validate the checksums of both the original ROM image and the patched ROM image.
## Installation
### Real System
Burn the ROM image (generally named iic_rom4x.bin) onto a 27C256 chip, or burn twice (into the lower and upper halves) of a 27C512 chip. If you can obtain an SST27SF512 flash EEPROM, that is a great option.
Once you have a ROM chip, generally the instructions [here](http://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Computers/Apple%20II/Apple%20IIc/Manuals/Apple%20IIc%20v4%20ROM%20Upgrade%20Installation.pdf) are relevant. You won't need to cut any traces or solder a jumper unless you are installing this ROM in an original //c.
### Emulator
The following emulators are known to run ROM 4X:
* MAME (replace the image for the apple2c4 machine).
* [Leon Bottou](https://github.com/leonbottou)'s "universal" versions of GSPlus and KEGS.
* Catakig (older emulator for MacOS, will stop working on Mojave).
Copy the ROM image into the appropriate location for your emulator.
## Operation
### Menu
Power on your machine. Everything should look and work *almost* like it did before. If there is a bootable device somewhere, the machine will boot it. If there is not (and this is one of the noticable changes), you will get dropped to BASIC without the need to press ctrl+reset. If things don't go well, revisit your installation.
If you don't have an initialized RAM disk, format the card RAM disk with something like Copy II Plus. Put ProDOS and BASIC.SYSTEM on it. Power off the machine, and power it on after a few minutes. You should boot off of the RAM disk. You might notice an "R" flash on the screen for an instant before ProDOS loads.
Now, press Control+Closed-Apple+Reset, holding down Closed-Apple after releasing reset. You should see the following menu appear:
```
0 Monitor
1 Reboot
2 Zero RAM Card and Reboot
3 Diagnostics
4 RAM Card Diagnostics
5 Boot SmartPort
6 Boot Int. 5.25
7 Boot Ext. 5.25
```
Picking any of the menu options besides 0 results in the menu being cleared, but the bottom line 'ROM 4X mm/dd/yy' immediately reappears to confirm that the new code is taking action.
What each option does is detailed below. Note that the various device boot options will try that device and any remaining devices in the boot order, which for the Apple //c is RAM card, 5.25 drive, and finally SmartPort.
#### 0 Monitor
This drops you unconditionally into the monitor.
#### 1 Reboot
This carries out the normal boot sequence, which is to try the RAM disk first, then the internal 5.25 floppy drive, then the first connected smartport device. Some of the other options let you skip over one or more of this ordering.
#### 2 Zero RAM Card and Reboot
This zeros out the RAM card memory and the screen holes. This is a nuclear option if the RAM disk is corrupt and the system fails to boot. After selecting 2 the word "SURE?" appears on the screen. At this point you must type `Y` or `y` to continue with the zeroing, or any other key to cancel.
If there is no card RAM, you are immediately rebooted. Otherwise an 'A' will appear in the upper left corner of the screen and will follow the alphabet as each 64K of the card is cleared. After it completes the letter will disappear and the machine will try booting.
#### 3 Diagnostics
This jumps to the //c internal diagnostics that are also run when you press control+both-apples+reset.
#### 4 RAM Card Diagnostics
This runs the RAM card diagnostics. When the diagnostics are finished either by user cancel or error, you are dropped into the monitor.
Since the test may damage data on the card, you are asked to confirm as per option 2 above.
#### 5 Boot SmartPort
This attempts to boot the first smartport device, such as a UniDisk 3.5.
#### 6 Boot Internal 5.25
This skips the RAM disk and starts booting with the internal 5.25 drive.
#### 7 Boot External 5.25
This is like option 6, but using an external 5.25 drive. The only OS I am aware of that supports booting this way is ProDOS.
This destructively copies a short routine to $800, which under most circumstances is also immediately overwritten by the boot sector, so should not be a problem..
#### XModem-CRC
See [xmodem.md](xmodem.md).
### Configuration File
If the RAM card is ProDOS-formatted, you can save a binary file in the volume directory called `BOOTX`. ROM 4X will find this file and use the Aux Type field (the load address) to set a default of the menu options above when no option has been selected using the menu. For example, `BSAVE /RAM4/BOOTX,A6,L0` will cause ROM 4X to skip booting the RAM card and go straight to booting the internal floppy drive (menu item 6). The contents of `BOOTX` are irrelevant, only the Aux Type is used. You cannot set it to jump into the monitor because that action happens before the boot code takes over.
You will know the configuration file is being used because the ROM 4X line will appear on the bottom of the screen and a flashing 'C' will appear in the lower-left corner.
**WARNING**: You *can* set the `BOOTX` file to clear the RAM card or run the RAM card diagnostics. This will happen exactly once and your RAM disk will be gone. *Caveat emptor*.
# Build/Develop Guide
## Build
To build the new firmware, you must start with a copy of the repository, and obtain a copy of the Apple //c version 4 ROM. The patches to the firmware work with the ROM dump that has sha256sum:
```
8ad5e6c4ed15d09b62183965b6c04762610d4b26510afc1896bdb4ecc55da883
```
It may work with other ROM dumps, it will *not* work with any other ROM versions, including ROM 3 and earlier. You must build ROM 4X using a ROM 4 dump.
The Rakefile will download the file from a well-known location if it is not already present. It also verifies the checksum.
Now you will need a 65C02 cross assembler. The current codebase is developed using ca65 from the [cc65](http://www.cc65.org/) project. Only the assembler and linker are required. Older versions may complain about argument order, generally versions identifying as "2.16" built from the ca65 git master branch work fine.
Finally you will need [Ruby](https://www.ruby-lang.org/en/) and [Rake](https://github.com/ruby/rake).
Once you have it all together change to the directory with the source files and original ROM image and type `rake`.
If all goes well, you will have a shiny new `iic_rom4x.bin`.
If you intend to build an image for a 512-kbit chip such as the SST27SF512, do `rake sf512`.
## Develop
### First Thing's First
First and foremost, it is most helpful to have an emulator. The only one that I have found that can be used for (almost) thorough testing is [Catakig](http://catakig.sourceforge.net/) for MacOS. It can emulate the //c and the Expansion Card (though not battery-backed).
If you plan to test on a real machine, be aware that the ROM socket is not rated for a large number of insertions and you *will* break something after a while. You may consider putting a machine-pin DIP socket or a ZIF socket into the CPU socket position. This can be done by desoldering the original socket if you have the skills, or by plugging the new socket into the existing CPU socket. If you do do the latter you should consider the new socket permanent as the socket pins are thicker than a ROM chip's and removing it may leave the socket in such a state as to not be able to make good contact with a subsequent chip.
As for me, I just use the emulator and then I am very careful with changing the ROM when I want to test on the real hardware. For heavy development/testing I insert a low-profile solder-tail ZIF socket into the existing chip socket..
### Apple //c Technical Reference and other Documentation
You need this.
The Apple //c Technical Reference Manual that is available on the internet has the firmware listing for ROM 3. ROM 4 fixes a few bugs that were in ROM 3, including with the memory card driver. The changes are minor and affect some of the offsets of routines in the RAM card support, but it is easy to figure them out.
[This](http://www.1000bit.it/support/manuali/apple/technotes/memx/tn.memx.1.html) tech note is also helpful as it documents the screen holes and some of the card behavior including under what conditions it reformats. Though the power2 byte is *not* used by the Apple //c code -- it is commented out in the firmware listings in the Technical Reference. ROM 4X uses it for the menu function.
[This](http://www.1000bit.it/support/manuali/apple/technotes/aiic/tn.aiic.5.html) technical note is a little less helpful for this project.
### Magic File Names
The main source files are named after a pattern, `B#_####_something.s` where the first # represents the bank number (0 = main, 1 = aux), and #### is the location in the bank to patch the code into. E.G. the `B1_E000_rom4.bin`'s object code is loaded into bank 1 at E000. Generally the origin address of the code in the file matches the #### portion of the file name.
The Rakefile uses this information to patch the original ROM 4 and produce the ROM 4X version.
### Defs
One file, `iic.defs` is included by all of the other source files. This has entry points, origins, and various RAM locations defined in it for use by the other source code.
### Test Scenarios
#### Basic Functional Tests
1. Boot ProDOS from power off. Run SlotScan 1.62 and confirm that the slots are identified as expected, see below.
2. With no bootable ProDOS RAMdisk, boot the system from power off or ctrl-oa-reset.
1. With the drop-to-basic patch:
- Expected: The system says "No bootable device" and drops to BASIC.
2. Without to drop-to-basic patch:
- Expected: The system boots the same as an unmodified ROM 4.
3. With a bootable ProDOS RAMdisk containing ProDOS, boot the system from power off or ctrl-oa-reset.
- Expected: The system boots from RAM disk, an inverse or flashing R may appear on the left of line 24 of the display.
4. Power on the system with the ca key pressed or use ctrl-ca-reset.
- Expected: The menu is displayed.
5. RAM disk recovery:
1. Battery-backed RAM present with bootable RAM disk: Power off the machine and leave it for 1 hr. Power on.
- Expected: The system boots from RAM disk.
2. Non-battery-backed RAM present with bootable RAM disk: Erase main RAM from 0300 up (e.g. in monitor: `300:00` then `301<300.BFFEM`) and press ctrl-reset.
- Expected: The system boots from RAM disk.
Expected SlotScan output:
```
SlotScan Version 1.62 Copyright 1989-1994 by Robert S. Claney
--------------------------------------------------------------------------------
Apple Computer Type: //c, ROM Ver 4 (Newer Mem. Exp.)
Processor type: 65c02
Total RAM: 128K
-----Scanning for peripherals-----
Port 1: Serial Port (#1)
Port 2: Serial Port (#1)
Port 3: 80-Column Port (#8)
Port 4: RamCard SmartPort: 1 device found
Manufacturer #0 (Unknown)
Device 1: "RAMCARD", Size: 2048 Blocks (1024K, 1 Meg)
Type: Mem. expansion Version: 0.102
Addl. info: (None)
Port 5: SmartPort: 0 devices found
Port 6: Disk ][ Port
Device Size: 280 Blocks (140K)
Port 7: Mouse Port (#0)
Done. Press any key to continue, or Control-P to get a printout
```
#### Menu Item Functional Tests
All cases: When any menu option is selected, the "ROM 4X MM/DD/YY" message is displayed on the bottom of the screen.
0. Monitor
- Expected: We are dropped into the monitor immediately.
1. Reboot
- Expected: System boots as normal.
2. Zero RAM Card and Reboot
- Expected: Reboot if no card RAM present. Otherwise, counter appears in upper left corner and card RAM is cleared.
3. Diagnostics
- Expected: System enters built-in diagnostics as if ctrl-oa-ca-reset was pressed.
4. RAM Card Diagnostics
- Expected: System enters RAM card diagnostics if card RAM present, then/or (no mem) drops to monitor when exited by failure or user escape key.
5. Boot SmartPort
- Expected: The system boots from a SmartPort device, skipping the RAM card and 5.25 floppy drives.
6. Boot Internal 5.25
- Expected: The system boots from the internal 5.25 drive, skipping the RAM card. The system may proceed to the SmartPort if no disk is found.
7. Boot External 5.25
- Expected: The system boots from the external 5.25 drive, skipping the RAM card. The system may proceed to the SmartPort if no disk is found.
### Ideas for Future
- Replace Apple Slinky code with RamFactor code. (Difficulty: Hard)

View File

@ -1,21 +1,53 @@
source_rom = "iic_rom4.bin"
dest_rom = "iic_rom4x.bin"
rom_url = 'https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Computers/Apple%20II/Apple%20IIc/ROM%20Images/Apple%20IIc%20ROM%2004%20-%20341-0445-B.bin'
source_rom = 'iic_rom4.bin'
source_rom_sha256 = '8ad5e6c4ed15d09b62183965b6c04762610d4b26510afc1896bdb4ecc55da883'
dest_rom = 'iic_rom4x.bin'
distzip = 'iic_rom4x.zip'
rom_base = 0xc000
source_files = Rake::FileList.new('*.s')
desc "Default: clean and build it"
desc 'Default: clean and build it'
task :default => [:clean, :assemble, :build_rom] do
sh "ls -l #{dest_rom}"
end
desc "Clean object files"
task :zip => [:clean, :assemble, :build_zip] do
sh "ls -l #{dest_rom}"
end
desc 'Clean object files'
task :clean do
sh "rm -f #{dest_rom}"
sh "rm -f sf512_#{dest_rom}"
sh "rm -f *.o"
sh "rm -f *.lst"
sh "rm -f *.b"
sh "rm -f rom.sha256"
sh "rm -f make_rom.sh"
sh "rm -f #{distzip}"
end
desc 'Obtain ROM'
rule source_rom do
require 'open-uri'
puts "Downloading ROM..."
File.open(source_rom, "wb") do |romfile|
open(rom_url) do |wwwfile|
romfile.write(wwwfile.read)
end
end
end
desc "Verify ROM Checksum"
task :checksum_rom => source_rom do
require 'digest'
sha256 = Digest::SHA256.file source_rom
fail "ROM checksum failed" unless sha256.hexdigest == source_rom_sha256
puts "Source ROM appears correct!"
end
desc "Assemble all source files"
@ -30,7 +62,7 @@ rule ".b" => ".o" do |t|
end
desc "Build ROM"
task :build_rom => [:assemble] do
task :build_rom => [:assemble, :checksum_rom] do
puts "Building ROM image..."
obj_files = Rake::FileList.new('*.b')
rom = File.read(source_rom)
@ -62,3 +94,60 @@ task :sf512 => [:build_rom] do
sh "cat #{dest_rom} #{dest_rom} > sf512_#{dest_rom}"
end
desc "Build disributable ZIP"
task :build_zip => [:build_rom] do
require 'digest'
require 'date'
sha256 = Digest::SHA256.file dest_rom
shafile = <<EOF
#{source_rom_sha256} #{source_rom}
#{sha256.hexdigest} #{dest_rom}
EOF
dd_cmds = []
puts "Building distributable ZIP..."
obj_files = Rake::FileList.new('*.b')
obj_files.each do |t|
if t =~ /B(\h)_(\h{4})/
bnum = $1.to_i(16)
badd = $2.to_i(16)
addr = bnum * 16384 + badd - rom_base
dd_cmds << "dd if=#{t} of=#{dest_rom} bs=1 seek=#{addr} conv=notrunc"
sh "zip #{distzip} #{t}"
end
end
puts "Creating maker script..."
script = <<EOF
#!/bin/bash
set -e
BDATE="#{DateTime.now.to_s}"
ROM_URL="#{rom_url}"
echo ${BDATE}
if [ -e `which curl` ]; then
curl -s "${ROM_URL}" > #{source_rom}
elif [ -e `which wget` ]; then
wget -O #{source_rom} "${ROM_URL}"
else
echo "Can't download source ROM image!"
fi
cp #{source_rom} #{dest_rom}
#{dd_cmds.join("\n")}
if [ -e `which shasum` ]; then
shasum -a 256 -c rom.sha256
elif [ -e "sha256sum" ]; then
sha256sum -c rom.sha256
else
echo "Please check the .bin files against rom.sha256"
fi
echo "#{dest_rom} created!"
EOF
File.write('rom.sha256', shafile)
File.write('make_rom.sh', script)
sh "zip #{distzip} rom.sha256 make_rom.sh"
end

View File

@ -1,3 +1,6 @@
; options
EN_XMODEM = 1 ; comment out to disable XModem support
; hardware
rombank = $c028
butn0 = $c061
@ -39,15 +42,11 @@ rx_mslot = rx_slot + $c0
rx_devno = rx_slot * $10 + $88
; entry points
gorst4x = $c763 ; switch to/from reset4x
gobt4x = gorst4x + 6 ; switch to/from boot4x
gobanner = gobt4x + 6 ; banner from bank 1
gorst4x = $cff9 ; reset patch destination
gobt4x = $cffa ; boot patch destination
rst4xrtn = $facb ; where to return from reset4x
;bt4xrtn = $fb12 ; where to return from boot4x
bt4xrtn = $fb19 ; where to return from boot4x
reset4x = $e000 ; reset routine
boot4x = $e180 ; boot routine
nbtfail = $e300 ; new boot fail
rom4x_disp = $e000 ; main dispatch code
banner = $fb60 ; 'Apple //c'
testsize = $d99f ; test ramdisk size (rom 3 = $d995)
monitor = $ff59 ; monitor

719
rom4x/inc/xmodem.s Normal file
View File

@ -0,0 +1,719 @@
; XMODEM/CRC Sender/Receiver for the 65C02/ROM 4X
;
; Adapted from Routines by By Daryl Rictor Aug 2002
; originally found at http://www.6502.org/source/io/xmodem/xmodem.txt
;
; Original source header commentary:
;
; A simple file transfer program to allow transfers between the SBC and a
; console device utilizing the x-modem/CRC transfer protocol. Requires
; ~1200 bytes of either RAM or ROM, 132 bytes of RAM for the receive buffer,
; and 12 bytes of zero page RAM for variable storage.
;
;**************************************************************************
; This implementation of XMODEM/CRC does NOT conform strictly to the
; XMODEM protocol standard in that it (1) does not accurately time character
; reception or (2) fall back to the Checksum mode.
; (1) For timing, it uses a crude timing loop to provide approximate
; delays. These have been calibrated against a 1MHz CPU clock. I have
; found that CPU clock speed of up to 5MHz also work but may not in
; every case. Windows HyperTerminal worked quite well at both speeds!
;
; (2) Most modern terminal programs support XMODEM/CRC which can detect a
; wider range of transmission errors so the fallback to the simple checksum
; calculation was not implemented to save space.
;**************************************************************************
;
; Files transferred via XMODEM-CRC will have the load address contained in
; the first two bytes in little-endian format:
; FIRST BLOCK
; offset(0) = lo(load start address),
; offset(1) = hi(load start address)
; offset(2) = data byte (0)
; offset(n) = data byte (n-2)
;
; Subsequent blocks
; offset(n) = data byte (n)
;
; One note, XMODEM send 128 byte blocks. If the block of memory that
; you wish to save is smaller than the 128 byte block boundary, then
; the last block will be padded with zeros. Upon reloading, the
; data will be written back to the original location. In addition, the
; padded zeros WILL also be written into RAM, which could overwrite other
; data.
;
;**************************************************************************
; MG's changes & notes for this implementation:
; entry/exit routines
; support routines to support AppleSoft and monitor load/save and r/w commands
; Receive address in header only respected if A1L/H = 0
; length is written in header as well, in order to not leak AppleSoft RAM
; Requires 8 bytes of additional stack to save $00-$07, total about 9+10=19 bytes
; needed. The main entry to the xmodem routine checks this.
; No prompting or messages, but status codes written on upper right corner of screen
; Use 'C02 opcodes in places
; Optimize code.
; Allow user to cancel xfer with ESC key.
; Send ETX when done with send.
; move magic numers out of code
;-------------------------- The Code ----------------------------
;
; zero page variables (adjust these to suit your needs)
;
;
; we will save and restore ZPSTART+n values to stack
ZPSTART = $00
lastblk = ZPSTART+0 ; flag for last block
blkno = ZPSTART+1 ; block number
errcnt = ZPSTART+2 ; error counter 10 is the limit
bflag = ZPSTART+3 ; block flag, indicate block 1 received
crc = ZPSTART+4 ; CRC lo byte (two byte variable)
crch = ZPSTART+5 ; CRC hi byte
retry = ZPSTART+6 ; retry counter
retry2 = ZPSTART+7 ; 2nd counter
ZPEND = retry2-ZPSTART
A1L = $3C
A1H = $3D
ptr = A1L ; data pointer (two byte variable)
ptrh = A1H ; " "
A2L = $3E
A2H = $3F
eofp = A2L ; end of file address pointer (2 bytes)
eofph = A2H ; " " " "
; AppleSoft ZPs
PRGEND = $AF ; end of program
TXTTAB = $67 ; start of program
VARTAB = $69 ; start of variables (usually same as PRGEND)
LINNUM = $50 ; line number, usable as temporary storage
FIXLINKS = $D4F2 ; AppleSoft routine to re-link program
SCRTCH = $D64B ; AppleSoft "SCRTCH" - erase program, reset everything
PRERR = $FF2D ; print "ERR" and beep
STLOC = $427 ; text page location for status
KBD = $C000 ; keyboard
KBDSTR = $C010 ; keyboard strobe
; Error values
ENOERR = $00 ; no error
ENOSTK = $D0 ; not enough free stack
EFAILED = $FF ; failed due to too many errors
ECANCEL = $FE ; transfer cancelled, receive memory changed
EBLKMM = $FD ; block number mismatch
EMMBLK = $FC ; block number complement mismatch
ECANCELOK = $FB ; transfer cancelled, receive memory unchanged or send
ACIA_SLOT = 2 ; slot # of the ACIA, //c: 1=printer, 2=modem
; defining STANDALONE allow one to build a test version of the code .orged
; at $4000 in order to test the basic bits of the code without doing a full
; firmware build. Even works on the enhanced //e with SSC.
; comment out if going to //c firmware
;STANDALONE = 1
start_retries = 20 ; number of ~3s timeouts to allow for transfer to start
; 20 = ~1 minute, which is more than enough.
;
;
; non-zero page variables and buffers
;
;
Rbuff = $0200+256-132 ; temp 132 byte receive buffer, don't cross page boundary
; placed over input buffer.
;
;
; tables and constants
;
; see further down for CRC tables
;
; XMODEM Control Character Constants
SOH = $01 ; start block
EOT = $04 ; end of text marker
ACK = $06 ; good block acknowledged
NAK = $15 ; bad block acknowledged
CAN = $18 ; cancel (not standard, not supported)
CR = $0d ; carriage return
LF = $0a ; line feed
ESC = $1b ; ESC to exit
; XMODEM other constants
MAXERRS = 10 ; max # of errors before failure (from spec)
CPMEOF = $1A ; CP/M EOF char, which is the correct padding
;
;^^^^^^^^^^^^^^^^^^^^^^ Start of Program ^^^^^^^^^^^^^^^^^^^^^^
;
; Xmodem/CRC transfer routines
; By Daryl Rictor, August 8, 2002
;
; v1.0 released on Aug 8, 2002.
; MG's IIc version released Oct 2018
;
;
.ifdef STANDALONE
.pc02
.org $4000
; use jmps here for testing on IIe/SSC because IIe mon doesn't
; disassemble 'C02 opcodes.
jmp monread
jmp monwrite
jmp asftload
; asftsave follows
.endif
; AppleSoft BASIC save/load
asftsave: jsr asftprgio
sec
jsr xmentry ; AppleSoft tape SAVE doesn't check for errors
bcs monerr ; but we will
.ifdef STANDALONE
rts
.else
jmp swrts2
.endif
asftload: jsr asftprgio ; current length does not matter
clc
jsr xmentry
; sta $300 ; DEBUG, comment out for release
bcs loaderr ; if error, execute NEW
lda A2L ; otherwise A2L/H have exact end address
ldy A2H ; regardless of appended junk
sta VARTAB ; mark end of program/start of vars
sty VARTAB+1
.ifdef STANDALONE
jmp FIXLINKS ; finally, re-link the program
.else
; //c aux bank code
lda #>(FIXLINKS-1) ; indirectly if in aux ROM
pha
lda #<(FIXLINKS-1)
pha
jmp swrts2
.endif
loaderr: cmp #ECANCELOK ; program memory unchanged?
beq :+ ; yes, don't clear program
; (in case user accidentally used LOAD)
lda #>(SCRTCH-1) ; otherwise set up RTS trick for SCRTCH ('NEW')
pha ; whether we are in aux ROM or RAM
lda #<(SCRTCH-1)
pha
: bra monerr ; print ERR first
; set up A1L/H and A2L/H for applesoft program save/load
; this is exactly what AppleSoft does for tapes
asftprgio: lda TXTTAB
ldy TXTTAB+1
sta A1L
sty A1H
lda VARTAB
ldy VARTAB+1
sta A2L
sty A2H
rts
monwrite: sec
phy
jsr xmentry
ply
bcs monerr
monok:
.ifdef STANDALONE
rts
.else
; //c aux bank code
jmp swrts2
.endif
monread: clc
phy
jsr xmentry
ply
bcc monok
monerr:
.ifdef STANDALONE
jmp PRERR
.else
; //c aux bank code
lda #>(PRERR-1)
pha
lda #<(PRERR-1)
pha
jmp swrts2
.endif
;
; Enter this routine with the beginning address stored in the zero page address
; pointed to by ptr & ptrh and the ending address stored in the zero page address
; pointed to by eofp & eofph.
;
; Carry set = save, clear = load
xmentry: php ; Save carry status to select op
tsx ; see if enough room on stack
cpx #ZPEND+10
bcs :+ ; yes, go ahead
plp ; otherwise restore saved status
lda #ENOSTK ; flag not enough stack
sec ; and error out
rts
: bit butn1 ; check closed apple key
bmi :+ ; and if held, don't set up ACIA
jsr ACIA_Init ; set up ACIA
: plp ; and get carry status back
ldx #ZPEND ; save ZP locations we are trashing on stack
savelp: lda ZPSTART,x
pha
dex
bpl savelp
lda STLOC ; and save char in the screen loc we use
pha
bcs dosend
jsr XModemRcv
bra skip1
dosend: jsr XModemSend
skip1: tay
pla
sta STLOC ; fix screen
ldx #ZPEND ; restore ZP locations
restlp: pla
sta ZPSTART,x
dex
bpl restlp
tya
cmp #$01 ; carry reflects error status
rts
; MG's modified Daryl Rictor XModem routines follow:
XModemSend: lda #$17 ; 'W' inverse
sta STLOC
lda #start_retries
sta errcnt ; will count retries left
stz lastblk ; set flag to false
lda #$01
sta bflag ; if user cancels, we haven't changed memory
sta blkno ; set block # to 1
Wait4CRC: lda #$ff ; 3 seconds
sta retry2 ;
jsr GetByte ;
bcs :+
lda #'W' ; 'W' flashing
sta STLOC
jsr ChkCancel ; see if user wants to cancel
dec errcnt
bne Wait4CRC
beq can1
: bcc Wait4CRC ; wait for something to come in...
cmp #'C' ; is it the "C" to start a CRC xfer?
beq SetstAddr ; yes
cmp #ESC ; is it a cancel? <Esc> Key
bne Wait4CRC ; No, wait for another character
can1: jmp SxCancel ; cancel
SetstAddr: lda #'S'
sta STLOC
lda #MAXERRS ; max # of errors
sta errcnt ; initialize error counter
ldy #$00 ; init data block offset to 0
ldx #$06 ; preload X to Receive buffer
lda #$01 ; manually load blk number
sta Rbuff ; into 1st byte
lda #$FE ; load 1's comp of block #
sta Rbuff+1 ; into 2nd byte
lda ptr ; load low byte of start address
sta Rbuff+2 ; into 3rd byte
lda ptrh ; load hi byte of start address
sta Rbuff+3 ; into 4th byte
sec ; now compute length - 1
lda eofp
sbc ptr
sta Rbuff+4
lda eofph
sbc ptrh
sta Rbuff+5
bra LdBuff1 ; jump into buffer load routine
LdBuffer: lda lastblk ; Was the last block sent?
beq LdBuff0 ; no, send the next one
lda #EOT ; yes, done!
jsr Put_Chr ; send EOT
lda #ENOERR ; flag no error
rts
LdBuff0: ldx #$02 ; init pointers
ldy #$00 ;
inc blkno ; inc block counter
jsr BlkStatus ; put current block on screen
lda blkno ;
sta Rbuff ; save in 1st byte of buffer
eor #$FF ;
sta Rbuff+1 ; save 1's comp of blkno next
LdBuff1: lda (ptr),y ; save 128 bytes of data
sta Rbuff,x ;
LdBuff2: sec ;
lda eofp ;
sbc ptr ; Are we at the last address?
bne LdBuff4 ; no, inc pointer and continue
lda eofph ;
sbc ptrh ;
bne LdBuff4 ;
inc lastblk ; Yes, Set last byte flag
LdBuff3: inx ;
cpx #$82 ; Are we at the end of the 128 byte block?
beq SCalcCRC ; Yes, calc CRC
lda #CPMEOF ; Fill rest of 128 bytes with CP/M EOF (^Z)
sta Rbuff,x ;
beq LdBuff3 ; Branch always
LdBuff4: inc ptr ; Inc address pointer
bne LdBuff5 ;
inc ptrh ;
LdBuff5: inx ;
cpx #$82 ; last byte in block?
bne LdBuff1 ; no, get the next
SCalcCRC: jsr CalcCRC
lda crch ; save Hi byte of CRC to buffer
sta Rbuff,y ;
iny ;
lda crc ; save lo byte of CRC to buffer
sta Rbuff,y ;
Resend: ldx #$00 ;
jsr ChkCancel ; see if user wants to cancel
lda #SOH
jsr Put_Chr ; send SOH
SendBlk: lda Rbuff,x ; Send 132 bytes in buffer to the console
jsr Put_Chr ;
inx ;
cpx #$84 ; last byte?
bne SendBlk ; no, get next
lda #$FF ; yes, set 3 second delay
sta retry2 ; and
jsr GetByte ; Wait for Ack/Nack
bcc Seterror ; No chr received after 3 seconds, resend
cmp #ACK ; Chr received... is it:
beq LdBuffer ; ACK, send next block
cmp #NAK ;
beq Seterror ; NAK, inc errors and resend
cmp #ESC ;
beq SxCancel ; Esc pressed to abort
; fall through to error counter
Seterror: lda #'E'
sta STLOC
dec errcnt ; dec error counter
bne Resend ; resend block if not zero
jsr Flush ; yes, too many errors, flush buffer,
lda #EFAILED
rts
ChkCancel: lda KBD ; see if ESC has been hit by user
cmp #ESC|$80
beq :+ ; yep, so cancel transfer
rts ; nope, as we were
: pla ; drop caller address
pla
sta KBDSTR ; clear keyboard strobe
lda #ESC ; send escape
jsr Put_Chr ; to the remote end
SxCancel: jsr Flush ; flush receive stream
lda bflag ; did user memory change?
beq :+ ; other error code if so
lda #ECANCELOK ; otherwise tell user all is OK
rts
: lda #ECANCEL ; and indicate a transfer cancelled
rts
;
;
;
XModemRcv: lda #$12 ; 'R' inverse
sta STLOC
lda #start_retries
sta errcnt ; will count retries left
lda #$01
sta blkno ; set block # to 1
sta bflag ; set flag to get address from block 1
StartCrc: lda #'C' ; "C" start with CRC mode
jsr Put_Chr ; send it
lda #$FF
sta retry2 ; set loop counter for ~3 sec delay
;lda #$00
;sta crc
;sta crch ; init CRC value
stz crc
stz crch
jsr GetByte ; wait for input
bcs GotByte ; byte received, process it
jsr ChkCancel
lda #'R' ; flashing
sta STLOC
dec errcnt ; next try
bne StartCrc ; resend "C" if more tries
beq SxCancel ; otherwise cancel it
StartBlk: jsr ChkCancel ; see if user wants to quit first
lda #$FF ;
sta retry2 ; set loop counter for ~3 sec delay
jsr GetByte ; get first byte of block
bcc StartBlk ; timed out, keep waiting...
GotByte: cmp #ESC ; quitting?
bne GotByte1 ; no
lda #ECANCEL ; Error code in "A" of desired
rts ; YES - do BRK or change to RTS if desired
GotByte1: cmp #SOH ; start of block?
beq BegBlk ; yes
cmp #EOT ;
bne BadCrc ; Not SOH or EOT, so flush buffer & send NAK
jmp RDone ; EOT - all done!
BegBlk: ldx #$00
GetBlk: lda #$ff ; 3 sec window to receive characters
sta retry2 ;
GetBlk1: jsr GetByte ; get next character
bcc BadCrc ; chr rcv error, flush and send NAK
GetBlk2: sta Rbuff,x ; good char, save it in the rcv buffer
inx ; inc buffer pointer
cpx #$84 ; <01> <FE> <128 bytes> <CRCH> <CRCL>
bne GetBlk ; get 132 characters
ldx #$00 ;
lda Rbuff,x ; get block # from buffer
cmp blkno ; compare to expected block #
beq GoodBlk1 ; matched!
jsr Flush ; mismatched - flush buffer and then exit
lda #EBLKMM ; put error code in "A" if desired
rts ; unexpected block # - fatal error - BRK or RTS
GoodBlk1: eor #$ff ; 1's comp of block #
inx ;
cmp Rbuff,x ; compare with expected 1's comp of block #
beq GoodBlk2 ; matched!
jsr Flush ; mismatched - flush buffer and then do BRK
lda #EMMBLK ; put error code in "A" if desired
rts ; bad 1's comp of block#
GoodBlk2: jsr CalcCRC ; calc CRC
lda Rbuff,y ; get hi CRC from buffer
cmp crch ; compare to calculated hi CRC
bne BadCrc ; bad crc, send NAK
iny ;
lda Rbuff,y ; get lo CRC from buffer
cmp crc ; compare to calculated lo CRC
beq GoodCrc ; good CRC
BadCrc: jsr Flush ; flush the input port
lda #'E'
sta STLOC
lda #NAK ;
jsr Put_Chr ; send NAK to resend block
jmp StartBlk ; start over, get the block again
GoodCrc: jsr BlkStatus ; Display block # mod 8
ldx #$02 ;
lda blkno ; get the block number
cmp #$01 ; 1st block?
bne CopyBlk ; no, copy all 128 bytes
lda bflag ; is it really block 1, not block 257, 513 etc.
beq CopyBlk ; no, copy all 128 bytes
lda ptr ; check if ptr is 0
eor ptr+1
beq DoAddr ; it is, take address from block
inx ; otherwise skip first byte of load address
bne Blk1Done ; and go skip second and keep user's load address
DoAddr: lda Rbuff,x ; get target address from 1st 2 bytes of blk 1
sta ptr ; save lo address
inx ;
lda Rbuff,x ; get hi address
sta ptr+1 ; save it
Blk1Done: inx ; move to length lo byte
clc
lda Rbuff,x ; get lo byte
adc ptr ; and compute end address
sta eofp ; put where caller can get to it
inx ; move to length hi byte
lda Rbuff,x ; get hi byte
adc ptrh ; finish end address compute
sta eofph ; put where caller can get to it
inx ; point to first byte of data
dec bflag ; set the flag so we won't get another address
CopyBlk: ldy #$00 ; set offset to zero
CopyBlk3: lda Rbuff,x ; get data byte from buffer
sta (ptr),y ; save to target
inc ptr ; point to next address
bne CopyBlk4 ; did it step over page boundary?
inc ptr+1 ; adjust high address for page crossing
CopyBlk4: inx ; point to next data byte
cpx #$82 ; is it the last byte
bne CopyBlk3 ; no, get the next one
IncBlk: inc blkno ; done. Inc the block #
lda #ACK ; send ACK
jsr Put_Chr ;
jmp StartBlk ; get next block
RDone: lda #ACK ; last block, send ACK and exit.
jsr Put_Chr ;
jsr Flush ; get leftover characters, if any
lda #ENOERR
rts ;
BlkStatus: lda blkno ; put block # mod 8 in status display
and #$07 ; mod 8
ora #$30 ; convert to inverse digit
sta STLOC ; and put on screen
rts
;
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;======================================================================
; I/O Device Specific Routines
;
; Two routines are used to communicate with the I/O device.
;
; "Get_Chr" routine will scan the input port for a character. It will
; return without waiting with the Carry flag CLEAR if no character is
; present or return with the Carry flag SET and the character in the "A"
; register if one was present.
;
; "Put_Chr" routine will write one byte to the output port. Its alright
; if this routine waits for the port to be ready. its assumed that the
; character was send upon return from this routine.
;
; Here is an example of the routines used for a standard 6551 ACIA.
; You would call the ACIA_Init prior to running the xmodem transfer
; routine.
;
ACIA_Data = $C088+(ACIA_SLOT*$10)
ACIA_Status = $C089+(ACIA_SLOT*$10)
ACIA_Command = $C08A+(ACIA_SLOT*$10)
ACIA_Control = $C08B+(ACIA_SLOT*$10)
SPD_19_2_8N1 = $1F
SPD_115_2_8N1 = $10
ACIA_Init: lda #SPD_115_2_8N1 ; speed/bits/etc.
sta ACIA_Control ; control reg
lda #$0B ; N parity/echo off/rx int off/ dtr active low
sta ACIA_Command ; command reg
rts ; done
;
; input chr from ACIA (no waiting)
;
Get_Chr: clc ; no chr present
lda ACIA_Status ; get Serial port status
and #$08 ; mask rcvr full bit
beq Get_Chr2 ; if not chr, done
Lda ACIA_Data ; else get chr
sec ; and set the Carry Flag
Get_Chr2: rts ; done
;
; output to OutPut Port
;
Put_Chr: PHA ; save registers
Put_Chr1: lda ACIA_Status ; serial port status
and #$10 ; is tx buffer empty
beq Put_Chr1 ; no, go back and test it again
PLA ; yes, get chr to send
sta ACIA_Data ; put character to Port
RTS ; done
;=========================================================================
;
; subroutines
;
;
;
GetByte: ;lda #$00 ; wait for chr input and cycle timing loop
;sta retry ; set low value of timing loop
stz retry
StartCrcLp: jsr Get_Chr ; get chr from serial port, don't wait
bcs GetByte1 ; got one, so exit
dec retry ; no character received, so dec counter
bne StartCrcLp ;
dec retry2 ; dec hi byte of counter
bne StartCrcLp ; look for character again
clc ; if loop times out, CLC, else SEC and return
GetByte1: rts ; with character in "A"
;
Flush: lda #$70 ; flush receive buffer
sta retry2 ; flush until empty for ~1 sec.
Flush1: jsr GetByte ; read the port
bcs Flush ; if chr recvd, wait for another
rts ; else done
;
;=========================================================================
;
;
; CRC subroutines
;
;
CalcCRC: ;lda #$00 ; yes, calculate the CRC for the 128 bytes
;sta crc ;
;sta crch ;
stz crc ; save 2 bytes with 'C02 code
stz crch ;
ldy #$02 ;
CalcCRC1: lda Rbuff,y ;
eor crc+1 ; Quick CRC computation with lookup tables
tax ; updates the two bytes at crc & crc+1
lda crc ; with the byte send in the "A" register
eor crchi,X
sta crc+1
lda crclo,X
sta crc
iny ;
cpy #$82 ; done yet?
bne CalcCRC1 ; no, get next
rts ; y=82 on exit
;
; The following tables are used to calculate the CRC for the 128 bytes
; in the xmodem data blocks. You can use these tables if you plan to
; store this program in ROM. If you choose to build them at run-time,
; then just delete them and define the two labels: crclo & crchi.
;
.res $100-(* & $FF)
; low byte CRC lookup table (should be page aligned)
crclo:
.byte $00,$21,$42,$63,$84,$A5,$C6,$E7,$08,$29,$4A,$6B,$8C,$AD,$CE,$EF
.byte $31,$10,$73,$52,$B5,$94,$F7,$D6,$39,$18,$7B,$5A,$BD,$9C,$FF,$DE
.byte $62,$43,$20,$01,$E6,$C7,$A4,$85,$6A,$4B,$28,$09,$EE,$CF,$AC,$8D
.byte $53,$72,$11,$30,$D7,$F6,$95,$B4,$5B,$7A,$19,$38,$DF,$FE,$9D,$BC
.byte $C4,$E5,$86,$A7,$40,$61,$02,$23,$CC,$ED,$8E,$AF,$48,$69,$0A,$2B
.byte $F5,$D4,$B7,$96,$71,$50,$33,$12,$FD,$DC,$BF,$9E,$79,$58,$3B,$1A
.byte $A6,$87,$E4,$C5,$22,$03,$60,$41,$AE,$8F,$EC,$CD,$2A,$0B,$68,$49
.byte $97,$B6,$D5,$F4,$13,$32,$51,$70,$9F,$BE,$DD,$FC,$1B,$3A,$59,$78
.byte $88,$A9,$CA,$EB,$0C,$2D,$4E,$6F,$80,$A1,$C2,$E3,$04,$25,$46,$67
.byte $B9,$98,$FB,$DA,$3D,$1C,$7F,$5E,$B1,$90,$F3,$D2,$35,$14,$77,$56
.byte $EA,$CB,$A8,$89,$6E,$4F,$2C,$0D,$E2,$C3,$A0,$81,$66,$47,$24,$05
.byte $DB,$FA,$99,$B8,$5F,$7E,$1D,$3C,$D3,$F2,$91,$B0,$57,$76,$15,$34
.byte $4C,$6D,$0E,$2F,$C8,$E9,$8A,$AB,$44,$65,$06,$27,$C0,$E1,$82,$A3
.byte $7D,$5C,$3F,$1E,$F9,$D8,$BB,$9A,$75,$54,$37,$16,$F1,$D0,$B3,$92
.byte $2E,$0F,$6C,$4D,$AA,$8B,$E8,$C9,$26,$07,$64,$45,$A2,$83,$E0,$C1
.byte $1F,$3E,$5D,$7C,$9B,$BA,$D9,$F8,$17,$36,$55,$74,$93,$B2,$D1,$F0
; hi byte CRC lookup table (should be page aligned)
crchi:
.byte $00,$10,$20,$30,$40,$50,$60,$70,$81,$91,$A1,$B1,$C1,$D1,$E1,$F1
.byte $12,$02,$32,$22,$52,$42,$72,$62,$93,$83,$B3,$A3,$D3,$C3,$F3,$E3
.byte $24,$34,$04,$14,$64,$74,$44,$54,$A5,$B5,$85,$95,$E5,$F5,$C5,$D5
.byte $36,$26,$16,$06,$76,$66,$56,$46,$B7,$A7,$97,$87,$F7,$E7,$D7,$C7
.byte $48,$58,$68,$78,$08,$18,$28,$38,$C9,$D9,$E9,$F9,$89,$99,$A9,$B9
.byte $5A,$4A,$7A,$6A,$1A,$0A,$3A,$2A,$DB,$CB,$FB,$EB,$9B,$8B,$BB,$AB
.byte $6C,$7C,$4C,$5C,$2C,$3C,$0C,$1C,$ED,$FD,$CD,$DD,$AD,$BD,$8D,$9D
.byte $7E,$6E,$5E,$4E,$3E,$2E,$1E,$0E,$FF,$EF,$DF,$CF,$BF,$AF,$9F,$8F
.byte $91,$81,$B1,$A1,$D1,$C1,$F1,$E1,$10,$00,$30,$20,$50,$40,$70,$60
.byte $83,$93,$A3,$B3,$C3,$D3,$E3,$F3,$02,$12,$22,$32,$42,$52,$62,$72
.byte $B5,$A5,$95,$85,$F5,$E5,$D5,$C5,$34,$24,$14,$04,$74,$64,$54,$44
.byte $A7,$B7,$87,$97,$E7,$F7,$C7,$D7,$26,$36,$06,$16,$66,$76,$46,$56
.byte $D9,$C9,$F9,$E9,$99,$89,$B9,$A9,$58,$48,$78,$68,$18,$08,$38,$28
.byte $CB,$DB,$EB,$FB,$8B,$9B,$AB,$BB,$4A,$5A,$6A,$7A,$0A,$1A,$2A,$3A
.byte $FD,$ED,$DD,$CD,$BD,$AD,$9D,$8D,$7C,$6C,$5C,$4C,$3C,$2C,$1C,$0C
.byte $EF,$FF,$CF,$DF,$AF,$BF,$8F,$9F,$6E,$7E,$4E,$5E,$2E,$3E,$0E,$1E
;
;
; End of File
;

53
rom4x/xmodem.md Normal file
View File

@ -0,0 +1,53 @@
# ROM 4X XModem-CRC
The 10/01/2018 release of ROM 4X includes XModem-CRC functionality.
The feature restores functionality to the SAVE and LOAD commands in AppleSoft BASIC, and re-introduces the W and R commands in the monitor.
The commands send/receive data through the Modem Port of the Apple //c.
The XModem-CRC functionality has been tested between the Apple //c and a PC running [Qodem](http://qodem.sourceforge.net) and between two Apple //cs using a null modem cable.
## Use of the XModem-CRC features
By default the data is sent at 115,200 bps 8N1. If you want to keep the current serial port speed/bits setting, hold the Closed Apple key while pressing the RETURN key for the below commands. Note that if the serial port is configured for 7 bit data, the transfer will fail.
**Caveats**: An XModem block is 128 bytes, and that is the minimum size that will be transmitted (extra filled with Ctrl+Z) and received (extra is copied to memory!). In the case of the receiver, multiples of 128 bytes (124 for first block) of memory will be overwritten as each block is received. 4 bytes are added to the transmitted data for a header (see below). So sending between 1 and 124 bytes of data will overwrite 124 bytes in the target machine's memory, but sending 125 bytes will overwrite 252 bytes! Keep this in mind!
### AppleSoft BASIC
To send the current program through the modem port, type `SAVE`. A 'W' will appear in the upper right corner of the screen, and after several seconds will start flashing. The save routine will wait approximately one minute for a receiver to be ready, otherwise it will exit and say "ERR". If you accidentally type SAVE, hit the ESC key and you will be returned to AppleSoft (again, with 'ERR'). Once the transfer starts, the upper right corner will cycle from 0 through 7 as each block is received (this is the lower 3 bits of the block number).
To receive a program through the modem port, type `LOAD`. A 'R' will appear in the upper right corner, and start flashing after a few seconds. The load routine will attempt to initiate the transfer every 3 seconds for one minute, otherwise exit with 'ERR'. If you wish to cancel the transfer, hit the ESC key. If you hit ESC before any blocks are received, your current program will stay intact. Once blocks are received, if the transfer fails or is cancelled, it will be as if NEW had been executed. A successful transfer results in the downloaded program being in memory.
### Monitor
To send data, type `xxxx.yyyyW`, as if you were using a machine with a cassette port, to send the data between addresses `xxxx` and `yyyy` (inclusive). The routine has the same wait/error conditions as the AppleSoft `SAVE` command, above.
To receive data, you can type `0R` to receive at the same address from which the data was written, or `zzzzR` to receive at address `zzzz` instead. The routine has the same receive/error conditions as the AppleSoft `LOAD` command, above.
As an example, you can copy the hi-res graphics page 1 from one machine to another via null modem cable by typing `2000.3FFFW` on the machine with the graphics and `0R` on the other machine.
## Troubleshooting
Occasionally the serial port gets finicky and receiving will instantly result in an ERR. Doing a trivial `SAVE` or `W` on the receiver, and then canceling and restarting the receive, will usually resolve this.
## Data Format
The data is sent/received via the XModem-CRC protocol, **without fallback to plain XModem**. Most modern terminal programs support XModem-CRC.
The data is sent with a 4-byte header in the first block that contains the address and length (minus one) of the data being sent. The exact length is sent in order to avoid leaking memory when receiving an AppleSoft program, as XModem does not have a built-in means of providing the length of the transfer.
When receiving, the machine will continue to receive blocks regardless of the length it receives in the first block, until the sender indicates it is done sending.
When transferring between an Apple //c and a PC, the 4-byte header must be included when sending a file to the //c. This will be present in any file originally received from a //c via XModem-CRC, but you will need to find a way to add it to anything that did not.
## Credits
I got the idea to implement this while discussing on Facebook the fact that `SAVE` and `LOAD` on the //c jump to the ampersand vector. The Facebook "Apple II Enthusiasts" group has inspired a lot of what I put into ROM 4X/5X.
I'd like to thank Reactive Micro for stepping up to deliver the ROM to people who can't build/burn it on their own. I have no desire to be in the taking orders/shipping business, and I am grateful, as are others, that there is someone who is.
Finally, the XModem-CRC routines are modified versions of Daryl Rictor's routines available at [6502.org](http://www.6502.org/source/io/xmodem/xmodem.htm).

View File

@ -0,0 +1,8 @@
; Code to fix merlin incompatibility with the beep patch
; this arises because switching to the aux firmware messes up the
; memory map slightly. The IIc Plus WAIT routine has a fix, we adopt it here.
.code
.pc02
.org $c2fc
phx
jmp $c9a1 ; next step

11
rom5x/B0_C4EE_del_key.s Normal file
View File

@ -0,0 +1,11 @@
; call RDCHAR, convert DEL to space
; for patching into GETLN1/NXTCHAR (at $FD75)
.code
.include "iic+.defs"
.org $c4ee
jsr $cced
cmp #$ff
bne :+
lda #$88
: rts

10
rom5x/B0_C7FC_5x_switch.s Normal file
View File

@ -0,0 +1,10 @@
; There's a bit of luck in the IIc Plus firmware
; there are 4 $00 bytes at $C7FC in the main bank, of which the last
; is "AppleTalk version" according to the ROM $03 source code
; in the tech ref, and should be left at $00.
; There are also 7 $00 bytes at $C7FC in the aux bank. So if we switch at
; $C7FC, then we get a 4 bytes in the aux bank, just enough for a jump.
.code
.org $c7fc
sta $c028

View File

@ -0,0 +1,7 @@
; see B0 C2FC patch for commentary
.code
.pc02
.org $c9a1
jsr $cfe5 ; get memory config we need to fix
jsr $c7fc ; call ROM 5X dispatch
jmp $c2f5 ; fix memory, restore x, a=$00

View File

@ -10,6 +10,6 @@
.org $cff9 ; 7 bytes available here, but don't count on $CFFF
lda #$a9 ; lda opcode
nop ; jmp/jsr $cffa does lda #$ea
jmp $fbdf ; jump to bell1 hijack
jmp $c7fc ; jump to 5X dispatcher
; total 6 bytes.

View File

@ -1,21 +0,0 @@
; Hijack the BELL1 monitor routine to do our bidding.
; BELL1 implements the beep sound heard on reset or
; Ctrl-G, etc. It starts with
; LDA #$40
; JSR WAIT ; delay .1 sec
; followed by code to actually beep the speaker
; In our case, BELL1 always loads the accumulator with
; a fixed number, and executes a 3-byte instruction
; Well, it turns out that to switch banks we need
; 3 bytes, and as luck would have it the other bank
; is empty here.
; So the routine on the other side is the ROM 5X
; dispatcher. It will take what is in the accumulator
; and use that to determine the next action.
; Obviously, $40 should beep the speaker, anything
; else can do whatever we want.
.code
.org $fbdf
sta $c028

13
rom5x/B0_FBE6_beep_fix.s Normal file
View File

@ -0,0 +1,13 @@
; Fix the beep
; inspired by http://quinndunki.com/blondihacks/?p=2471
; see commentary in B1 FB3C patch
.code
.pc02
.org $fbe6
jmp $c2fc ; to 5X beep with merlin fix
.res $fbef-*,$ea ; fill up the rest with NOPs
.assert * = $fbef, error, "ROM 5X beep fix alignment problem"
; the rts at $fbef is sacred

View File

@ -0,0 +1,6 @@
; patch GETLN1 to call delete key handler
.code
.include "iic+.defs"
.org $fd75
jsr $c4ee

5
rom5x/B1_C7FF_5x_jump.s Normal file
View File

@ -0,0 +1,5 @@
; See B0 C7FC patch for description
.code
.org $c7ff
jmp $fb3c ; to 5X dispatch

View File

@ -1,106 +1,110 @@
.code
.psc02
.include "iic+.defs"
.org boot5x ; 234 bytes available, code assembles to 222
jsr titl5x ; "Apple IIc +"
jsr rdrecov ; try to recover ramdisk
lda power2 + rx_mslot ; get action saved by reset5x
beq boot4 ; if zero, continue boot
jsr bann5x ; display ROM 5X footer
lda power2 + rx_mslot ; boot selection
btc2: cmp #$02 ; clear ramcard
bne btc3
jsr rdclear ; do clear
bra boot4
btc3: cmp #$03 ; Diags
bne btc4
jmp $c7c4
btc4: cmp #$04 ; RX diags
bne btc5
ldx #$ff
txs ; reset stack
jsr rdinit ; get x and y loaded
stx sl_devno ; diags need this
jsr testsize ; compute card size
lda #>(monitor-1) ; load "return" address
pha ; into stack so that we
lda #<(monitor-1) ; exit card test into
pha ; the monitor
lda numbanks,y ; get the card size in banks
bne dordiag ; do diag if memory present
jmp swrts2 ; otherwise jump to monitor
dordiag: jmp $db3a ; diags
btc5: cmp #$05 ; boot smartport
beq boot5
; fall through if none of the above
boot4: lda #rx_mslot ; boot slot 4
bra bootsl
boot5: lda #$c5 ; boot slot 5
bra bootsl
boot6: lda #$c6 ; boot slot 6
bootsl: ldx #$00 ; low byte of slot
bootadr: stx $0 ; store address
sta $1 ; return to bank 0 does jmp (0)
endbt4x: lda #>(bt5xrtn-1)
pha
lda #<(bt5xrtn-1)
pha
lda $1
jmp swrts2
rdrecov: jsr rdinit ; init ramcard
lda pwrup,y ; get power up flag
cmp #pwrbyte ; already initialized?
beq recovdn ; exit if initialized
jsr testsize ; does not wreck x or y
lda numbanks,y ; get discovered # banks
beq recovdn ; no mem
stz addrl,x ; set slinky address 0
stz addrm,x
stz addrh,x
lda data,x ; start check for bootable ramdisk
cmp #$01
bne recovdn ; not bootable
lda data,x ; next byte should be nonzero and not $ff
beq recovdn ; not bootable
cmp #$ff
beq recovdn ; not bootable
lda #pwrbyte
sta pwrup,y ; set power byte
lda #'R' ; tell user
sta $7d0 ; on screen
recovdn: rts
.org boot5x ; 234 bytes available, code assembles to 220
jsr titl5x ; "Apple IIc +"
jsr rdrecov ; try to recover ramdisk
lda power2 + rx_mslot ; get action saved by reset5x
beq boot4 ; if zero, continue boot
jsr bann5x ; display ROM 5X footer
lda power2 + rx_mslot ; boot selection
btc2: cmp #$02 ; clear ramcard
bne btc3
jsr rdclear ; do clear
bra boot4
btc3: cmp #$03 ; Diags
bne btc4
jmp $c7c4
btc4: cmp #$04 ; RX diags
bne btc5
ldx #$ff
txs ; reset stack
jsr rdinit ; get x and y loaded
stx sl_devno ; diags need this
jsr testsize ; compute card size
lda #>(monitor-1) ; load "return" address
pha ; into stack so that we
lda #<(monitor-1) ; exit card test into
pha ; the monitor
lda numbanks,y ; get the card size in banks
bne dordiag ; do diag if memory present
jmp swrts2 ; otherwise jump to monitor
dordiag: jmp $db3a ; diags
btc5: cmp #$05 ; boot smartport
beq bootcx
cmp #$06 ; boot 5.25
beq bootcx
; fall through to default boot if none of the above
boot4: lda #rx_mslot ; boot slot 4 (should be, anyway)
bootcx: ora #$c0 ; convert to slot addr high byte if needed
ldx #$00 ; low byte of slot
bootadr: stx $0 ; store address
sta $1 ; return to bank 0 does jmp (0)
endbt4x: lda #>(bt5xrtn-1)
pha
lda #<(bt5xrtn-1)
pha
lda $1
jmp swrts2
; try to recover RAM disk
.proc rdrecov
jsr rdinit ; init ramcard
lda pwrup,y ; get power up flag
cmp #pwrbyte ; already initialized?
beq :+ ; exit if initialized
jsr testsize ; does not wreck x or y
lda numbanks,y ; get discovered # banks
beq :+ ; no mem
stz addrl,x ; set slinky address 0
stz addrm,x
stz addrh,x
lda data,x ; start check for bootable ramdisk
cmp #$01
bne :+ ; not bootable
lda data,x ; next byte should be nonzero and not $ff
beq :+ ; not bootable
cmp #$ff
beq :+ ; not bootable
lda #pwrbyte
sta pwrup,y ; set power byte
lda #'R' ; tell user
sta $7d0 ; on screen
: rts
.endproc
; zero ram card space
rdclear: jsr rdinit ; init ramcard
jsr testsize ; get size
lda numbanks,y ; # of 64Ks to write
beq clrdone ; no memory
lda #$c0 ; 'A' - 1
sta $400 ; upper left corner
stz addrl,x ; slinky address 0
stz addrm,x
stz addrh,x
clbnklp: inc $400 ; poor mans progress meter
ldy #$00
cl64klp: ldx #$00 ; loop for all pages in bank
cl256lp: txa ; loop for all bytes in page
ldx #rx_devno
stz data,x ; write a zero to card
tax
dex
bne cl256lp ; 256 byte loop
dey
bne cl64klp ; 64K loop
ldx #rx_mslot
dec numbanks,x
bne clbnklp ; if more banks
clrdone: ldx #rx_mslot
stz pwrup,x ; zero powerup byte
lda #$a0 ; ' '
sta $400 ; clear progress
rts
rdinit: bit rx_mslot*$100 ; activate registers
ldy #rx_mslot ; slot offset
ldx #rx_devno ; register offset
rts
.proc rdclear
jsr rdinit ; init ramcard
jsr testsize ; get size
lda numbanks,y ; # of 64Ks to write
beq clrdone ; no memory
lda #$c0 ; 'A' - 1
sta $400 ; upper left corner
stz addrl,x ; slinky address 0
stz addrm,x
stz addrh,x
clbnklp: inc $400 ; poor mans progress meter
ldy #$00
cl64klp: ldx #$00 ; loop for all pages in bank
cl256lp: txa ; loop for all bytes in page
ldx #rx_devno
stz data,x ; write a zero to card
tax
dex
bne cl256lp ; 256 byte loop
dey
bne cl64klp ; 64K loop
ldx #rx_mslot
dec numbanks,x
bne clbnklp ; if more banks
clrdone: ldx #rx_mslot
stz pwrup,x ; zero powerup byte
lda #$a0 ; ' '
sta $400 ; clear progress
rts
.endproc
.proc rdinit
bit rx_mslot*$100 ; activate registers
ldy #rx_mslot ; slot offset
ldx #rx_devno ; register offset
rts
.endproc

View File

@ -1,55 +1,56 @@
.code
.psc02
.include "iic+.defs"
.org misc5x ; max 306 bytes
bra domenu ; Display menu
bra dobann ; Display banner (title + By MG)
bra gtkey ; get a key
bra confirm ; ask SURE?
bra ntitle ; display "Apple IIc +"
.org misc5x ; max 306 bytes
bra domenu ; Display menu
bra dobann ; Display banner (title + By MG)
bra gtkey ; get a key
bra confirm ; ask SURE?
bra ntitle ; display "Apple IIc +"
dobann: jsr ntitle
ldx #(msg2-msg1) ; msg display entry point
jmp disp
ldx #(msg2-msg1) ; msg display entry point
jmp disp
domenu: jsr ntitle ; "Apple ||c +"
ldx #$00 ; menu start
jsr disp ; show it
rts
ldx #$00 ; menu start
jsr disp ; show it
rts
gtkey: lda #$60
sta ($0),y ; cursor
sta kbdstrb ; clr keyboard
sta ($0),y ; cursor
sta kbdstrb ; clr keyboard
kbdin: lda kbd ; get key
bpl kbdin
sta kbdstrb ; clear keyboard
sta ($0),y ; put it on screen
rts
bpl kbdin
sta kbdstrb ; clear keyboard
sta ($0),y ; put it on screen
rts
; display message, input x = message start relative to msg1
disp: ldy #$0 ; needs to be zero
disp: ldy #$0 ; needs to be zero
disp0: lda msg1,x ; get message byte
bne disp1 ; proceed if nonzero
rts ; exit if 0
bne disp1 ; proceed if nonzero
rts ; exit if 0
disp1: inx ; next byte either way
cmp #$20 ; ' '
bcc disp2 ; start of ptr if < 20
eor #$80 ; invert high bit
sta ($0),y ; write to mem
inc $0 ; inc address low byte
bra disp0 ; back to the beginning
cmp #$20 ; ' '
bcc disp2 ; start of ptr if < 20
eor #$80 ; invert high bit
sta ($0),y ; write to mem
inc $0 ; inc address low byte
bra disp0 ; back to the beginning
disp2: sta $1 ; write address high
lda msg1,x ; get it
sta $0 ; write address low
inx ; set next msg byte
bra disp0 ; back to the beginning
confirm: pha
ldx #(msg3-msg1) ; ask confirm
jsr disp
jsr gtkey
plx
ora #$20 ; to lower
cmp #$f9 ; "y"
php
txa
plp
rts
lda msg1,x ; get it
sta $0 ; write address low
inx ; set next msg byte
bra disp0 ; back to the beginning
confirm:
pha
ldx #(msg3-msg1) ; ask confirm
jsr disp
jsr gtkey
plx
ora #$20 ; to lower
cmp #$f9 ; "y"
php
txa
plp
rts
; display "Apple IIc +" in a convoluted manner
; we push the address of swrts/swrts2 onto the stack
; and then the address of the title routine
@ -57,30 +58,32 @@ confirm: pha
; display "Apple IIc +", which then RTS to swrts, which
; switches banks back to here and RTS to our caller.
ntitle: lda #>(swrts2-1) ; put return addr of swrts/swrts2 on stack
pha
lda #<(swrts2-1)
pha
lda #>(banner-1) ; put addr of the Title routine on the stack
pha
lda #<(banner-1)
pha
jmp swrts2 ; jump to swrts2
pha
lda #<(swrts2-1)
pha
lda #>(banner-1) ; put addr of the Title routine on the stack
pha
lda #<(banner-1)
pha
jmp swrts2 ; jump to swrts2
; msg format
; A byte < $20 indicates high byte of address.
; Next byte must be low byte of address. Anything
; else are characters to display and will have their
; upper bit inverted before being written to the screen.
msg1 = *
.byte $05,$06,"0 Monitor"
.byte $05,$86,"1 Reboot"
.byte $06,$06,"2 Zero RAM Card"
.byte $06,$86,"3 Sys Diags"
.byte $07,$06,"4 RAM Card Diags"
.byte $07,$86,"5 Boot 3.5/SmartPort"
.byte $04,$2e,"6 Boot 5.25"
.byte $04,$ae,"7 Accelerator"
.byte $07,$5f,"By M.G."
msg2: .byte $07,$db,"ROM 5X 04/08/17"
.byte $05,$ae,$00 ; cursor pos in menu
msg3: .byte $05,$b0,"SURE? ",$00
.byte $05,$06,"0 Monitor"
.byte $05,$86,"1 Reboot"
.byte $06,$06,"2 Zero RAM Card"
.byte $06,$86,"3 Sys Diags"
.byte $07,$06,"4 RAM Card Diags"
.byte $07,$86,"5 Boot 3.5/SmartPort"
.byte $04,$2e,"6 Boot 5.25"
.byte $04,$ae,"7 Accelerator"
.byte $07,$5f,"By M.G."
msg2: .byte $07,$db,"ROM 5X 12/10/17"
.byte $05,$ae,$00 ; cursor pos in menu
msg3: .byte $05,$b0,"SURE? ",$00
; metadata to identify build conditions
.dword .time
.word .version

View File

@ -1,47 +1,44 @@
.code
.psc02
.include "iic+.defs"
.org reset5x ; max 157 bytes
stz power2 + rx_mslot ; action = normal reset
lda #>(rst5xrtn-1) ; common case
pha
lda #<(rst5xrtn-1)
pha ; note that this stays on stack
asl butn1 ; option (closed apple)
bcs ckdiag
exitrst: jmp swrts2
.org reset5x ; max 157 bytes
stz power2 + rx_mslot ; action = normal reset
lda #>(rst5xrtn-1) ; common case
pha
lda #<(rst5xrtn-1)
pha ; note that this stays on stack
asl butn1 ; option (closed apple)
bcs ckdiag
exitrst: jmp swrts2
; check to see if cmd_option (both apples) are down
ckdiag: bit butn0 ; command (open apple)
bmi exitrst ; return to RESET.X
ckdiag: bit butn0 ; command (open apple)
bmi exitrst ; return to RESET.X
; present menu because only closed apple is down
menu: jsr menu5x ; display menu
jsr gkey5x
cmp #$b0 ; "0"
bne ckkey1
ldx #$ff ; reset stack
txs
lda #>(monitor-1) ; monitor entry on stack
pha
lda #<(monitor-1)
pha
jmp swrts2 ; rts to enter monitor
ckkey1: cmp #$b2 ; "2"
beq doconf
cmp #$b4 ; "4"
bne ckkey2
doconf: jsr conf5x
bne menu ; go back to menu4x
ckkey2: cmp #$b7 ; "7"
bne ckkey3
jsr $fd02 ; accelerator menu
bra menu
ckkey3: sec
sbc #$b0 ; ascii->number
bmi menu ; < 0 not valid
cmp #$07 ; we will use 7 for accelerator later
bpl menu ; > 7 not valid
sta power2 + rx_mslot ; for boot5x
stz softev + 1 ; deinit coldstart
stz pwerdup ; ditto
bra exitrst
menu: jsr menu5x ; display menu
jsr gkey5x
cmp #$b0 ; "0"
bne ckkey1
ldx #$ff ; reset stack
txs
txa
jmp $fb3c ; now has crash-to-monitor function
ckkey1: cmp #$b2 ; "2"
beq doconf
cmp #$b4 ; "4"
bne ckkey2
doconf: jsr conf5x
bne menu ; go back to menu4x
ckkey2: cmp #$b7 ; "7"
bne ckkey3
jsr $fd02 ; accelerator menu
bra menu
ckkey3: sec
sbc #$b0 ; ascii->number
bmi menu ; < 0 not valid
cmp #$07 ; we will use 7 for accelerator later
bpl menu ; > 7 not valid
sta power2 + rx_mslot ; for boot5x
stz softev + 1 ; deinit coldstart
stz pwerdup ; ditto
bra exitrst

View File

@ -1,45 +1,51 @@
.code
.psc02
.include "iic+.defs"
.org $fb3c ; ~165 bytes free here
cmp #$a9 ; reset patch
bne chk2
jmp reset5x
chk2: cmp #$ea ; boot patch
bne chk3
jmp boot5x
.if newbeep
chk3:
.else
chk3: cmp #$40 ; beep
bne dowait
.org $fb3c ; ~165 bytes free here
.proc dispatch
cmp #$a9 ; reset patch
bne :+
jmp reset5x
: cmp #$ea ; boot patch
bne :+
jmp boot5x
: cmp #$0c
beq oldbelle1
; jump to monitor
lda #>(monitor-1) ; monitor entry on stack as return address
pha
lda #<(monitor-1)
pha
jmp swrts2 ; switch bank and rts to monitor
.endproc
; "classic air raid beep"
; inspired by http://quinndunki.com/blondihacks/?p=2471
; jsr $fcb5 ; (new) WAIT for .1 sec delay
jsr owait
ldy #$c0
obell2: lda #$0c
jsr owait ; old wait for correct sound
lda $c030
dey
bne obell2
bra dexit ; back to caller
.endif
dowait: jsr $fcb5 ; do delay if anything else
lda #>($fbe2-1) ; return to other bank here (in BELL1)
pha ; by pushing address onto
lda #<($fbe2-1) ; the stack
pha
lda #$00 ; in case someone assumes this
dexit: jmp swrts2 ; back to other bank
.proc oldbell
ldy #$c0
obell2: lda #$0c
dowait: jsr oldwait ; old wait for correct sound, we also enter here
lda $c030
dey
bne obell2
jmp swrts2
.endproc
; We jump into the old bell routine mid-way because it's possible for
; someone to want to call the bell routine with a different duration
; by putting a custom value in Y and calling fbe4 (BELL2)
oldbelle1 := oldbell::dowait
; old wait - no ACIA access to enforce delay at
; accelerated speeds, speaker delay tkes care of it
; accelerated speeds, speaker delay takes care of it
; when we do the old beep
owait: sec
owait2: pha
owait3: sbc #$01
bne owait3
pla
sbc #$01
bne owait2
rts
.proc oldwait
sec
owait2: pha
owait3: sbc #$01
bne owait3
pla
sbc #$01
bne owait2
rts
.endproc
.assert * <= $fc00, error, "ROM 5X dispatch overruns $fc00"

View File

@ -1,5 +0,0 @@
.code
.include "iic+.defs"
.org $fbe2 ; ~29 bytes free here
jmp $fb3c

View File

@ -24,6 +24,7 @@ TESTBLD = 0 ; set to 1 to enable test code that runs in random
XTRACMD = 0 ; set to 1 to enable extra accelerator speed commands
ACCMENU = 1 ; set to 1 to enable accelerator menu
ADEBUG = 0 ; turn on debugging (copies registers to $300 whenever they are set)
AOFFDFL = 0 ; accelerator off by default
.psc02
.if TESTBLD
@ -98,7 +99,11 @@ SWRTS2 = $C784
.endif
.proc ACCEL
bra accel1
jmp AMENU
.if ::ACCMENU
jmp AMENU ; entry point for menu
.else
rts ; otherwise return to caller if they want menu
.endif
accel1: php
sei
phy
@ -168,10 +173,13 @@ acceldn:
plx
ply
plp
clc
cmp #$00
beq doexit
sec
; original code:
; clc ; ...
; cmp #$00 ; sets carry, derp
; beq doexit
; sec ; carry was set anyway...
cmp #$01 ; better, carry reflect error status
doexit:
.if ::TESTBLD
rts
@ -404,9 +412,13 @@ cmdtable:
.endproc ; AWSPD
.endif
IACWL: .byte %01100111 ; initial ACWL - same as $C05C
.if ::AOFFDFL
IACWH: .byte %01011000 ; initial ACWH - accelerator OFF. See below for bits
.else
IACWH: .byte %01010000 ; initial ACWH - b6 = 1=paddle slow, b4 = reg 1=lock/0=unlock
; b3 = 1=accel disable, rest reserved by apple
; rom5x: b7 = state of DHiRes when accelerator was unlocked
.endif
IREGV: .byte %01100111 ; Initial $C05C - slots & speaker: b7-b1 = slot speed. b0 = speaker delay
.byte %00000000 ; Initial $C05D - $00 = 4MHz
.byte %01000000 ; Initial $C05E - b7=0 enable I/O sync, b6=undoc
@ -595,6 +607,6 @@ msg1 = ::amenu1
msg2 = ::amenu2
.endif
.endproc ; AMENU
; check for run into vector area
; check for run into vector and ROM checksum area
.assert * < $ffe0, error, "accel5x overran $ffe0"
.endif

View File

@ -1,45 +1,214 @@
# ROM 5X by MG
## PRELIMINARY, NEEDS MORE TESTING
ROM 5X is a collection of enhancements to the Apple //c Plus (ROM version 5). See the top level [README.md](../README.md) for more general information on ROM 4X and ROM 5X.
*ll of the functionality, including the RAM Disk recovery and other functions,
appear to work correctly, but I would appreciate any feedback if something isn't
workign for you.*
It adds the following features to the Apple //c Plus:
This is ROM 5X, providing the ROM 4X functionality to the Apple IIc Plus ROM
version 5.
- Enter the monitor unconditionally.
- Reboot the machine (enter standard boot sequence).
- Zero the RAM card, in case it is corrupted.
- Execute the machine and RAM card diagnostics.
- Tell the machine to boot the SmartPort/3.5 drive or the internal floppy drive.
- Menu control the built-in accelerator (via main menu or ctrl+tab+reset).
- Accelerator settings persist across resets.
- Build option to default the system to 1 MHz.
- Changes ctrl+esc+reset to toggle the accelerator rather than turn it off only.
There are almost no free bytes in the main bank of the IIc Plus firmware, so
I had to get creative to get into the alternate bank, where I then had to split
the code up across multiple smaller free spaces due to the massive 3.5 drive
handling code. Ironically this makes the code larger as well.
RAM expansion cards known to work with ROM 5X include the AE RAM Express Cards (but no battery!), and the A2Heaven [RAM Express II+](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=144) for the memory-expandable //c and IIc Plus.
For those interested, I hijack the monitor BEEP1 routine. The beep routine has
an LDA #$40 and then calls WAIT with this value for a .1 second delay,
presumably so that multiple beeps are distinct from each other.
# User Guide
I patch the JSR WAIT to be STA $C028, which switches to the other bank.
The code in the other bank checks the accumulator and for two values calls
either reset5x or boot5x, for a third value ($40 loaded by BEEP1) does the
classic Apple II "air raid" beep sound, and for any other value executes the WAIT
(assuming that we got there from BEEP1) and returns back to BEEP1.
## Obtaining
Then, in only 6 bytes I can create two entry points that load the right values
into the A register that we need for the reset or boot routines, and then jump
to the above patch.
**Due to copyright law, I do NOT provide full ready-to-burn binaries at this time. Some assembly (but not necessarily an assembler) is required!**
## Options
You may either build it yourself which guarantees that you have the latest version and feature branch that you want, or you can check the [web site for ROM 4X/5X](http://apple2.guidero.us/doku.php?id=projects:rom_4x_and_5x) for binary releases.
The options directory contains one or more subdirectories with optional patches
that change the default behavior of the Apple IIc Plus.
### Binary Releases
Enable options by copying the files to the main directory. Disable the by removing
the copied files.
The binary releases consist of a zip file with the assembled and linked patches, a checksum file, and a Bash script. You must have a unix-like system (MacOS, Linux, etc.) or, on Windows, Cygwin or the Windows Subsystem for Linux configured.
### Accelerator Reverse
The shell script will perform the following:
The accelerator reverse patch is a 1-byte patch that causes the IIc Plus to boot up
at the normal speed.
* Download the original Apple ROM image from a well-known location.
* Apply the patches.
* Validate the checksums of both the original ROM image and the patched ROM image.
## Installation
### Real System
Burn the ROM image (generally named iic_rom4x.bin) onto a 27C256 chip, or burn twice (into the lower and upper halves) of a 27C512 chip. If you can obtain an SST27SF512 flash EEPROM, that is a great option.
Once you have a ROM chip, generally the instructions [here](http://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Computers/Apple%20II/Apple%20IIc/Manuals/Apple%20IIc%20v4%20ROM%20Upgrade%20Installation.pdf) are relevant. You won't need to cut any traces or solder a jumper unless you are installing this ROM in an original //c.
### Emulator
Copy the ROM into the appropriate location for your emulator. As of July 2018, the following emulators are known to successfully emulate the Apple IIc Plus:
* [Leon Bottou](https://github.com/leonbottou)'s "universal" versions of GSPlus and KEGS.
* MAME after [this commit](https://github.com/mamedev/mame/commit/31aaae7491ea4233de75456af178054e650f4344).
## Operation
### Menu
Power on your machine. Everything should look and work *almost* like it did before. If there is a bootable device somewhere, the machine will boot it. If things don't go well, revisit your installation.
If you don't have an initialized RAM disk, format the card RAM disk with something like Copy II Plus. Put ProDOS and BASIC.SYSTEM on it. Power off the machine, and power it on after a few minutes. You should boot off of the RAM disk. You might notice an "R" flash on the screen for an instant before ProDOS loads.
Now, press Control+Option+Reset, holding down Option after releasing reset. You should see the following menu appear:
```
0 Monitor
1 Reboot
2 Zero RAM Card
3 Sys Diags
4 RAM Card Diags
5 Boot 3.5/SmartPort
6 Boot 5.25
7 Accelerator
```
Picking any of the menu options besides 0 results in the menu being cleared, but the bottom line 'ROM 5X mm/dd/yy' immediately reappears to confirm that the new code is taking action.
What each option does is detailed below. Note that the various device boot options will try that device and any remaining devices in the boot order, which for the Apple IIc Plus is RAM card, 3.5 or SmartPort, and finally the first 5.25 drive, if present.
#### 0 Monitor
This drops you unconditionally into the monitor.
#### 1 Reboot
This carries out the normal boot sequence, which is to try the RAM disk first, then the internal 5.25 floppy drive, then the first connected smartport device. Some of the other options let you skip over one or more of this ordering.
#### 2 Zero RAM Card
This zeros out the RAM card memory and the screen holes. This is a nuclear option if the RAM disk is corrupt and the system fails to boot. After selecting 2 the word "SURE?" appears on the screen. At this point you must type `Y` or `y` to continue with the zeroing, or any other key to cancel.
If there is no card RAM, you are immediately rebooted. Otherwise an 'A' will appear in the upper left corner of the screen and will follow the alphabet as each 64K of the card is cleared. After it completes the letter will disappear and the machine will try booting.
#### 3 Sys Diags
This jumps to the //c Plus internal diagnostics that are also run when you press control+apple+option+reset.
#### 4 RAM Card Diags
This runs the RAM card diagnostics. When the diagnostics are finished either by user cancel or error, you are dropped into the monitor.
Since the test may damage data on the card, you are asked to confirm as per option 2 above.
#### 5 Boot 3.5/SmartPort
This attempts to boot the first bootable smartport device, such as a the built-in 3.5" drive.
#### 6 Boot 5.25
This skips the RAM disk and starts an attached 5.25 drive.
#### 7 Accelerator
This opens the accelerator menu.
### Accelerator Menu
The Accelerator menu can be accessed by selecting "Accelerator" from the boot menu, or by pressing the tab key during or within 1 second of pressing ctrl+reset.
The accelerator menu will allow you to enable or disable the accelerator, set the speed, and control speaker and paddle delay.
The settings will persist through resets.
# Build/Develop Guide
## Build
To build the new firmware, you must start with a copy of the repository, and obtain a copy of the Apple IIc Plus version 5 ROM. The patches to the firmware work with the ROM dump that has sha256sum:
```
5a62070f6a0b07784681d4df4bf2ce88b2809bec0cbaa65fcb963e804ed60374
```
It may work with other ROM dumps, it will *not* work with any other ROM versions, including ROM 4 and earlier from the original //c. You must build ROM 4X using a ROM 5 dump.
The Rakefile will download the file from a well-known location if it is not already present. It also verifies the checksum.
Now you will need a 65C02 cross assembler. The current codebase is developed using ca65 from the [cc65](http://www.cc65.org/) project. Only the assembler and linker are required. Older versions may complain about argument order, generally versions identifying as "2.16" built from the ca65 git master branch work fine.
Finally you will need [Ruby](https://www.ruby-lang.org/en/) and [Rake](https://github.com/ruby/rake).
Once you have it all together change to the directory with the source files and original ROM image and type `rake`.
If all goes well, you will have a shiny new `iic_rom5x.bin`.
If you intend to build an image for a 512-kbit chip such as the SST27SF512, do `rake sf512`.
### Build Options
There are some build options in accel5x.s - some functional, others needing more
work, the most popular of which will no doubt be the option to reset the system
with the accelerator in the disabled state. The "extra commands" option will
currently fail to build because the code gets too large, and is really only for
experimental purposes.
## Develop
### First Thing's First
See above for the list of working emulators that can run the Apple IIc Plus firmware.
If you will test on a real machine, be aware that the ROM socket is not rated for a large number of insertions and you *will* break something after a while. You may consider putting a machine-pin DIP socket or a ZIF socket into the CPU socket position. This can be done by desoldering the original socket if you have the skills, or by plugging the new socket into the existing CPU socket. If you do do the latter you should consider the new socket permanent as the socket pins are thicker than a ROM chip's and removing it may leave the socket in such a state as to not be able to make good contact with a subsequent chip.
### Nitty Gritty
There are almost no free bytes in the main bank of the IIc Plus firmware, so I had to get creative to get into the alternate bank, where I then had to split the code up across multiple smaller free spaces due to the massive 3.5 drive handling code. Ironically this makes the code larger as well.
### Apple //c Technical Reference and other Documentation
You need this.
The Apple //c Technical Reference Manual that is available on the internet has the firmware listing for ROM 3. ROM 4 fixes a few bugs that were in ROM 3, including with the memory card driver. The changes are minor and affect some of the offsets of routines in the RAM card support, but it is easy to figure them out. ROM 5 adds the 3.5 drive code, but largely leaves the main firmwware bank untouched.
[This](http://www.1000bit.it/support/manuali/apple/technotes/memx/tn.memx.1.html) tech note is also helpful as it documents the screen holes and some of the card behavior including under what conditions it reformats. Though the power2 byte is *not* used by the Apple //c code -- it is commented out in the firmware listings in the Technical Reference. ROM 5X uses it for the menu function.
[This](http://www.1000bit.it/support/manuali/apple/technotes/aiic/tn.aiic.5.html) technical note is a little less helpful for this project.
### Magic File Names
The main source files are named after a pattern, `B#_####_something.s` where the first # represents the bank number (0 = main, 1 = aux), and #### is the location in the bank to patch the code into. E.G. the `B1_E000_rom4.bin`'s object code is loaded into bank 1 at E000. Generally the origin address of the code in the file matches the #### portion of the file name.
The Rakefile uses this information to patch the original ROM 5 and produce the ROM 5X version.
### Defs
One file, `iic+.defs` is included by all of the other source files. This has entry points, origins, and various RAM locations defined in it for use by the other source code.
### Test Scenarios
#### Basic Functional Tests
1. Boot ProDOS from power off. Run SlotScan 1.62 and confirm that the slots are identified as expected, see below.
2. With no bootable ProDOS RAMdisk, boot the system from power off or ctrl-oa-reset.
- Expected: The system says "No bootable device" and drops to BASIC.
3. With a bootable ProDOS RAMdisk containing ProDOS, boot the system from power off or ctrl-oa-reset.
- Expected: The system boots from RAM disk, an inverse or flashing R may appear on the left of line 24 of the display.
4. Power on the system with the ca key pressed or use ctrl-ca-reset.
- Expected: The menu is displayed.
5. RAM disk recovery:
1. Battery-backed RAM present with bootable RAM disk: Power off the machine and leave it for 1 hr. Power on.
- Expected: The system boots from RAM disk.
2. Non-battery-backed RAM present with bootable RAM disk: Erase main RAM from 0300 up (e.g. in monitor: `300:00` then `301<300.BFFEM`) and press ctrl-reset.
- Expected: The system boots from RAM disk.
#### Menu Item Functional Tests
All cases: When any menu option is selected, the "ROM 5X MM/DD/YY" message is displayed on the bottom of the screen.
Check each item, the expectation is that the sytem does what is listed in the menu.
### Ideas for Future
- Replace Apple Slinky code with RamFactor code. (Difficulty: Hard. May require sacrificing the diagnostics.)
Pressing ESC at reset time will make the machine run at 4 MHz.

View File

@ -1,5 +1,8 @@
source_rom = "iic+_rom5.bin"
dest_rom = "iic+_rom5x.bin"
rom_url = 'https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Computers/Apple%20II/Apple%20IIc%20plus/ROM%20Images/Apple%20IIc%20plus%20ROM%2005%20-%20342-0625-A%20-%201988.bin'
source_rom = 'iic+_rom5.bin'
source_rom_sha256 = '5a62070f6a0b07784681d4df4bf2ce88b2809bec0cbaa65fcb963e804ed60374'
dest_rom = 'iic+_rom5x.bin'
distzip = 'iic+_rom5x.zip'
rom_base = 0xc000
source_files = Rake::FileList.new('*.s')
@ -9,6 +12,10 @@ task :default => [:clean, :assemble, :build_rom] do
sh "ls -l #{dest_rom}"
end
task :zip => [:clean, :assemble, :build_zip] do
sh "ls -l #{dest_rom}"
end
desc "Clean object files"
task :clean do
sh "rm -f #{dest_rom}"
@ -18,6 +25,31 @@ task :clean do
sh "rm -f *.b"
sh "rm -f accel5x"
sh "rm -f POOF1 *.po"
sh "rm -f rom.sha256"
sh "rm -f make_rom.sh"
sh "rm -f #{distzip}"
end
desc 'Obtain ROM'
rule source_rom do
require 'open-uri'
puts "Downloading ROM..."
File.open(source_rom, "wb") do |romfile|
open(rom_url) do |wwwfile|
romfile.write(wwwfile.read)
end
end
end
desc "Verify ROM Checksum"
task :checksum_rom => source_rom do
require 'digest'
sha256 = Digest::SHA256.file source_rom
fail "ROM checksum failed" unless sha256.hexdigest == source_rom_sha256
puts "Source ROM appears correct!"
end
desc "Assemble all source files"
@ -32,7 +64,7 @@ rule ".b" => ".o" do |t|
end
desc "Build ROM"
task :build_rom => [:assemble] do
task :build_rom => [:assemble, :checksum_rom] do
puts "Building ROM image..."
obj_files = Rake::FileList.new('*.b')
rom = File.read(source_rom)
@ -76,3 +108,61 @@ task :"accel5x.po" => [:accel5x] do
sh "mv -f POOF1 accel5x.po"
end
desc "Build disributable ZIP"
task :build_zip => [:build_rom] do
require 'digest'
require 'date'
sha256 = Digest::SHA256.file dest_rom
shafile = <<EOF
#{source_rom_sha256} #{source_rom}
#{sha256.hexdigest} #{dest_rom}
EOF
dd_cmds = []
puts "Building distributable ZIP..."
obj_files = Rake::FileList.new('*.b')
obj_files.each do |t|
if t =~ /B(\h)_(\h{4})/
bnum = $1.to_i(16)
badd = $2.to_i(16)
addr = bnum * 16384 + badd - rom_base
dd_cmds << "dd if=#{t} of=#{dest_rom} bs=1 seek=#{addr} conv=notrunc"
sh "zip #{distzip} #{t}"
end
end
puts "Creating maker script..."
script = <<EOF
#!/bin/bash
set -e
BDATE="#{DateTime.now.to_s}"
ROM_URL="#{rom_url}"
echo ${BDATE}
if [ -e `which curl` ]; then
curl -s "${ROM_URL}" > #{source_rom}
elif [ -e `which wget` ]; then
wget -O #{source_rom} "${ROM_URL}"
else
echo "Can't download source ROM image!"
fi
cp #{source_rom} #{dest_rom}
#{dd_cmds.join("\n")}
if [ -e `which shasum` ]; then
shasum -a 256 -c rom.sha256
elif [ -e "sha256sum" ]; then
sha256sum -c rom.sha256
else
echo "Please check the .bin files against rom.sha256"
fi
echo "#{dest_rom} created!"
EOF
File.write('rom.sha256', shafile)
File.write('make_rom.sh', script)
sh "zip #{distzip} rom.sha256 make_rom.sh"
end

View File

@ -0,0 +1,12 @@
; remove 3.5" functionality for MAME
; this bit causes slot 5 boot to go to do 6 boot instead
; and also overwrites the firmware protocol bits to not
; identify as a block device.
.code
.include "iic+.defs"
.org $c500
ldx #$00
lda #$c6
stx $00
sta $01
jmp ($00)

View File

@ -0,0 +1,8 @@
; remove 3.5" functionality for MAME
; this bit removes protocol converter initialization
; so the 3.5" drive code is never called
.code
.include "iic+.defs"
.org $c5f8
rts

View File

@ -0,0 +1,6 @@
; remove 3.5" functionality for MAME
; this bit removes some 3.5" cruft added to the 5.25" boot code
.code
.include "iic+.defs"
.org $c61a
lda $c089,x

View File

@ -1,3 +1,6 @@
; *** This option only works with the orginial Apple accelerator ***
; *** code. Do not apply this option to the current release. ***
; this reverses the accelerator to always start off at 1MHz
; <ESC> at reset then selects 4 MHz
; This is useful if you spend more time at 1 MHz, for games

116
story.md Normal file
View File

@ -0,0 +1,116 @@
# ROM 4X and 5X: The Whole Story
The Apple II Plus was the first computer my family owned. It's what I learned to program on. We spent hours at the keyboard typing in programs from magazines, and eventually I learned to modify them and write my own. As technology progressed, I switched to PCs like almost everyone else and largely forgot about the Apple II after the 90s. I still had an interest in my Apple IIs and managed to get hold of some more gear, including a //c and a couple of IIgs machines. The prices bottomed out a few years after Apple discontinued the line. Eventually I moved on and boxed it all up, sold a bit, but I held on to most of the interesting stuff I'd acquired.
## The Beginning and the New Old
I got back into the Apple II a few months ago after I read [this story](http://www.osnews.com/story/29400/Why_the_Apple_II_ProDOS_2_4_release_is_the_OS_news_of_the_year). What? A new ProDOS? I must try it! So I dusted off some of my old Apple II gear and the next thing you know I had ProDOS 2.4.1 running on my Apple //c.
So then I go searching around the net only to discover that not only is there a pretty active user community, but that people had been making *new hardware* for it, the coolest of which emulate floppy and hard drives. There are Ethernet cards, memory expansions, VGA adapters, FPGA cards, and all kinds of other hardware.
Jumping into this new hardware for old computers craze, I bought a [Ram Express II+](http://a2heaven.com/webshop/index.php?rt=product/product&product_id=144#review) from [A2 Heaven](http://www.a2heaven.com/) and was excited to try out the whopping (really!) 1 MB of battery-backed memory in it, as well as the clock. I formatted the card, loaded up ProDOS, and rebooted... Instant-on! Much fast!
Then I powered down for a while to do non-hobby things. Upon return, instead of a super fast boot to ProDOS, I got the the familiar clunking of the Apple 5 1/4 floppy drive recalibrating. I thought to myself that surely I was doing something wrong, so I rebooted ProDOS and found the card in its initial state without what I had copied on it.
Bummer.
## Resetting My Expectations
Assuming the worst - that the battery wasn't working for the memory, I replaced the battery and checked that the clock had been working. Still the same results.
I emailed the maker, Plamen, and asked about it. He told me that something about the //c causes the card to reformat after power off. To prove it, he had me write some values to the card manually and then read them back after powering off for an hour.
Turns out my expectations were wrong. I followed up with a thanks and a "maybe I can find out in the firmware where that happens and fix it." Another user got a card a few days later, and we discussed the matter over FaceBook, where I joked that it would be a "few NOPs" and when another user suggested adding boot from external drive back to the firmware I said that that would be much harder. The exact opposite of what it was.
## Down the Rabbit Hole
I am a reasonably competent 8-bit assembly language programmer, always had been since I was a kid working on my Apple II Plus. But I know the Z80 better these days, it'd been a long time since I touched the 6502.
Armed with a copy of Apple //c Technical Reference in PDF format, I printed out the firmware listings (computers used to have schematics and firmware listings available!) and started to look for where the few NOPs would have to go.
## pwerdup <> pwrbyte and numbanks == 0
After looking through the firmware listing, it was clear that the reason that the RAM disk was being reinitialized was because certain memory locations did not contain the contents needed to tell the firmware that it was already initialized.
These memory locations are in what are called "screen holes" in the Apple II. Veteran Apple II users know what these are, but if you are unaware: These are areas of the text screen that do not result in display output. The Apple II text screen is not linearly organized and there are 8-byte "holes" after each 120 displayable locations. Each of these holes is allocated to one of the card slots for slotted Apple IIs, or to the various ports of the unslotted Apple IIs.
In particular the firmware checks $77c (pwerdup) for the value $a5 (pwrbyte), and $47c for the number of banks detected on the card. After some experimenting I determined that it is enough to set both of these to the proper values and the card will be recovered. Setting these on an uninitialized card can result in all kinds of bad behavior, from crashes to perpetual "UNABLE TO LOAD PRODOS" depending on what is in the boot block. Changing the firmware to ignore them is not possible because we cannot assume the user is using the card as a RAM disk.
That means that instead of disabling code that purposely trashed the RAM disk, I had to add code to find an existing RAM disk and prevent re-initialization.
## From NOP codes to opcodes
Well, as we know, adding new code to existing firmware is a lot harder than disabling existing code. We can't change where things already are, and any patches have to be at places that wouldn't break existing software.
But now I at least have some initial requirements:
> 1: Identify an existing, hopefully bootable RAM disk.
> 2: If it exists, prevent it from being re-initialized by setting the two screen holes to the proper values.
> 3: If no special action is taken, everything should "look normal."
The Apple II RAM card code is more simple than, say, the Applied Engineering RamFactor card, and I haven't seen anything that documents being able to boot DOS 3.3 or Pascal from it, so I decided that I only needed to see if the RAM disk was ProDOS and bootable. So I would solve the first requirement by checking for a ProDOS boot block, conveniently starting at location zero on the card.
The second requirement we already sorted out above. The third is that if you don't have a bootable RAM disk, booting the system should look and work just like it did before. That's just good practice to not change things for the sake of change.
## Feature Creep
Then I thought to myself... what if the RAM disk is screwed up, and we keep re-initializing it and attempting to boot? That's kind of a pain, but could be solved with documentation. "Hit ctrl+reset and then pr#6 to boot a disk, then format the card with a program."
Nah. Why not detect if the closed-apple key is pressed with ctrl+reset and clear out the RAM disk?
> 4: Provide a way to erase a messed up RAM disk.
Well, that wasn't so bad. But what if the user doesn't want to erase it outright? Perhaps they want to try to recover some data. Maybe we can leave it corrupted and just boot something else.
> 5: Provide a way to skip booting the RAM disk.
Well that's at least two options, and between the two apple key+reset combinations, no room for more than one additional action, so maybe that action should be a menu.
> 6: Present a menu to the user to decide what action to take.
Well since we are doing that, it's probably easy to get the IIc to try to boot whatever device you want, and maybe the user doesn't want to do any of the things on the menu.
> 7: Let the user select a variety of boot devices.
Hell, while we are at it, let's give some easy access to internal functionality that requires more keys to be held down or calling routines in memory with BASIC or monitor commands.
> 8: Let the user get access to the internal diagnostic routines.
Then, there was that guy that wanted to be able to boot an external 5.25 drive like the original //c firmware had. This turned out to be fairly easy to do.
> 9: Let the user boot an external 5.25 drive.
Well, I coded all the above up over the course of two days (short story, keep reading for longer version) and then I had another feature:
> 10: Don't make it too easy to trash the RAM disk accidentally by picking menu option 2 and 4.
Sigh. Now it's a real software project.
## Banking on It
After examining the //c ROM, it was clear that there was not any room in the main ROM bank to implement the above code, so the code had to run in the alternate bank.
When switching the ROM bank from ROM, you have to pull some tricks to make the switch safely and continue execution on the right code path. The most compact way is to have a free place in both banks, which we have in the //c. The less compact ways are to make use of subroutines already in the ROMs that can switch based on a memory location in RAM or by pushing the address to jump to on the stack and making use of the routine that switches banks and then does an RTS instruction (this is known as the 'RTS trick'). The latter are more useful for one-way jumps.
The second bank is also missing features that we take for granted, such as the various routines for displaying characters on the screen, clearing the screen, etc. so we need to find out which of these we should write ourselves and which ones to make available via a bank switch.
In the end the most difficult routine we might need from the firmware that we have to call and get back from is to clear the screen, since it has to be done without overwriting the screen holes. Putting stuff on the screen is easy and there are compact ways of doing it. The other functionality is pretty much one-way and for the common functions like jumping to an address for booting, the main needed functionality is in the firmware already.
In the end, I only needed the two-way switchers to enter and exit the reset handler (detects closed-apple and shows the menu), the boot handler (which handles the function selected by the user), and clearing the screen. I decided to use the banner screen clear that prints 'Apple //c' at the top for me. That meant that I needed 22 bytes of common unused address space in both banks for these, which I found.
The remaining switcher is one-way from alt bank to main bank, and I use the RTS trick.
## Patching Pants that Don't Have Holes
So now we have to patch the boot process to recover the RAM disk, and the reset process to capture the closed apple key and present the menu.
Well, firmware programmers for resource-limited machines don't exactly leave a bunch of space available for inserting your own routines, so I had to figure out where I could replace existing code with jumps and no-op padding. Ideally you patch instructons in a 1:1 manner, a 3 byte instruction is replaced by another 3-byte instruction so that code that makes assumptions about where it can jump or call can continue to rely on those assumptions.
With the //c firmware I did not have that luxury for one of the patches, so hopefully it does not break things. I think the chances are pretty low.
I searched over the course of an hour to figure out the best place to put the patches, and ROM 4X is the result.
## Conclusion
So now you have it and you know how it got here. Enjoy ROM 4X.