mirror of
https://github.com/mgcaret/rom4x.git
synced 2024-09-30 07:56:22 +00:00
Compare commits
No commits in common. "master" and "r20170408" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,7 +9,4 @@
|
||||
*.po
|
||||
copyrom.sh
|
||||
rom5x/accel5x
|
||||
make_rom.sh
|
||||
rom.sha256
|
||||
**/.DS_Store
|
||||
|
||||
|
346
README.md
346
README.md
@ -11,32 +11,344 @@ 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 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.
|
||||
|
||||
- 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.
|
||||
|
||||
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 firmware or 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 ProDOS from re-initializing it.
|
||||
|
||||
## Build / Install / Documentation
|
||||
# User Guide
|
||||
|
||||
### ROM 4X
|
||||
## Installation
|
||||
|
||||
See the [ROM 4X README.md](rom4x/README.md).
|
||||
### Real System
|
||||
|
||||
### ROM 5X
|
||||
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.
|
||||
|
||||
See the [ROM 5X README.md](rom5x/README.md).
|
||||
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.
|
||||
|
||||
### Emulator
|
||||
|
||||
#### //c
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
## The Whole Story
|
||||
It may work with other ROM dumps, it will *not* work with any other ROM version, including ROM 3 and earlier.
|
||||
|
||||
Why did I do this? See [story.md](story.md).
|
||||
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.
|
||||
|
||||
|
9
dist.sh
9
dist.sh
@ -3,14 +3,7 @@
|
||||
rm -f "romXx_dist-*.zip"
|
||||
ROM4X="rom4x/iic_rom4x.bin"
|
||||
ROM5X="rom5x/iic+_rom5x.bin"
|
||||
case `uname -s` in
|
||||
Linux)
|
||||
FNAME="romXx_dist-`date --rfc-3339=date`.zip"
|
||||
;;
|
||||
*)
|
||||
FNAME="romXx_dist-`date '+%Y-%M-%d'`.zip"
|
||||
;;
|
||||
esac
|
||||
FNAME="romXx_dist-`date --rfc-3339=date`.zip"
|
||||
[ -f "${ROM4X}" ] && zip "${FNAME}" "${ROM4X}"
|
||||
[ -f "${ROM5X}" ] && zip "${FNAME}" "${ROM5X}"
|
||||
|
||||
|
@ -2,15 +2,16 @@ Inventory of zeros of 5 bytes or more in the IIc Plus ROM
|
||||
|
||||
Main Bank
|
||||
C1FB - 5 bytes
|
||||
C4EE - 12 bytes - delete key fix
|
||||
C4EE - 12 bytes
|
||||
C5F3 - 5 bytes
|
||||
C6FB - 5 bytes - but Disk II ID here at C6FF
|
||||
C9A1 - 9 bytes - beep merlin fix part 2
|
||||
C6FB - 5 bytes
|
||||
C9A1 - 9 bytes
|
||||
CFF9 - 7 bytes - ROM 5X switcher
|
||||
|
||||
Aux Bank
|
||||
C572 - 8 bytes
|
||||
C7FC - 7 bytes - ROM 5X dispatch jump at $C7FF
|
||||
C7FB - 8 bytes
|
||||
C7FC - 7 bytes
|
||||
CE00 - 512 bytes not usable (MIG space)
|
||||
D3B5 - 75 bytes - Accelerator menu text
|
||||
D516 - 234 bytes - ROM 5X boot
|
||||
|
@ -1,11 +0,0 @@
|
||||
; 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
|
||||
|
@ -10,5 +10,9 @@
|
||||
.byte 0 ; rom4x present
|
||||
cbtfail: jsr setvid
|
||||
jsr setkbd
|
||||
lda #$01 ; cmd = boot fail1
|
||||
jmp $c7fc ; to dispatcher
|
||||
lda #>(nbtfail-1)
|
||||
pha
|
||||
lda #<(nbtfail-1)
|
||||
pha
|
||||
jmp swrts2
|
||||
|
||||
|
12
rom4x/B0_C763_switcher.s
Normal file
12
rom4x/B0_C763_switcher.s
Normal file
@ -0,0 +1,12 @@
|
||||
.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
|
||||
|
@ -1,10 +0,0 @@
|
||||
; 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
|
||||
|
@ -1,12 +0,0 @@
|
||||
.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
|
@ -1,20 +0,0 @@
|
||||
; 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
|
@ -1,15 +0,0 @@
|
||||
; 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.
|
||||
|
@ -1,10 +0,0 @@
|
||||
|
||||
.pc02
|
||||
XM_LOAD = $C9A1
|
||||
XM_SAVE = $C9A4
|
||||
.include "iic.defs"
|
||||
.org $D06C
|
||||
.ifdef EN_XMODEM
|
||||
.addr XM_LOAD-1
|
||||
.addr XM_SAVE-1
|
||||
.endif
|
@ -1,7 +1,6 @@
|
||||
; patch PWRUP to call boot4x
|
||||
|
||||
.include "iic.defs"
|
||||
.code
|
||||
.include "iic.defs"
|
||||
.org $fab4
|
||||
nop
|
||||
jmp gobt4x
|
||||
|
@ -1,7 +1,6 @@
|
||||
; patch RESET.X to call reset4x
|
||||
|
||||
.include "iic.defs"
|
||||
.code
|
||||
.include "iic.defs"
|
||||
.org $fac8
|
||||
jmp gorst4x
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
; patch GETLN1 to call delete key handler
|
||||
.code
|
||||
.include "iic.defs"
|
||||
.org $fd75
|
||||
jsr $c2f0
|
||||
|
@ -1,7 +0,0 @@
|
||||
.pc02
|
||||
.include "iic.defs"
|
||||
.code
|
||||
.org $FF73
|
||||
.ifdef EN_XMODEM
|
||||
jsr $CFE5 ; R/W patch
|
||||
.endif
|
13
rom4x/B1_C763_switcher.s
Normal file
13
rom4x/B1_C763_switcher.s
Normal file
@ -0,0 +1,13 @@
|
||||
.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
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
; See B0 C7FC patch for description
|
||||
.include "iic.defs"
|
||||
.code
|
||||
.org $c7ff
|
||||
jmp rom4x_disp
|
||||
|
@ -1,453 +1,216 @@
|
||||
.psc02
|
||||
.code
|
||||
.include "iic.defs"
|
||||
|
||||
; 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
|
||||
.org reset4x
|
||||
stz power2 + rx_mslot ; action = normal boot
|
||||
asl butn1 ; closed apple
|
||||
bcs ckdiag
|
||||
exitrst: jmp gorst4x ; return to RESET.X
|
||||
; 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 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
|
||||
|
||||
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
|
||||
; display message, input x = message start relative to msg1
|
||||
.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
|
||||
|
||||
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
|
||||
; 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 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
|
||||
|
||||
.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
|
||||
; zero ram card space
|
||||
.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
|
||||
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 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
|
||||
|
||||
|
19
rom4x/B1_E300_new_bootfail.s
Normal file
19
rom4x/B1_E300_new_bootfail.s
Normal file
@ -0,0 +1,19 @@
|
||||
.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
255
rom4x/README.md
@ -1,255 +0,0 @@
|
||||
# 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)
|
||||
|
||||
|
@ -1,53 +1,21 @@
|
||||
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'
|
||||
source_rom = "iic_rom4.bin"
|
||||
dest_rom = "iic_rom4x.bin"
|
||||
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
|
||||
|
||||
task :zip => [:clean, :assemble, :build_zip] do
|
||||
sh "ls -l #{dest_rom}"
|
||||
end
|
||||
|
||||
desc 'Clean object files'
|
||||
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"
|
||||
@ -62,7 +30,7 @@ rule ".b" => ".o" do |t|
|
||||
end
|
||||
|
||||
desc "Build ROM"
|
||||
task :build_rom => [:assemble, :checksum_rom] do
|
||||
task :build_rom => [:assemble] do
|
||||
puts "Building ROM image..."
|
||||
obj_files = Rake::FileList.new('*.b')
|
||||
rom = File.read(source_rom)
|
||||
@ -94,60 +62,3 @@ 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
|
||||
|
@ -1,6 +1,3 @@
|
||||
; options
|
||||
EN_XMODEM = 1 ; comment out to disable XModem support
|
||||
|
||||
; hardware
|
||||
rombank = $c028
|
||||
butn0 = $c061
|
||||
@ -42,11 +39,15 @@ rx_mslot = rx_slot + $c0
|
||||
rx_devno = rx_slot * $10 + $88
|
||||
|
||||
; entry points
|
||||
gorst4x = $cff9 ; reset patch destination
|
||||
gobt4x = $cffa ; boot patch destination
|
||||
gorst4x = $c763 ; switch to/from reset4x
|
||||
gobt4x = gorst4x + 6 ; switch to/from boot4x
|
||||
gobanner = gobt4x + 6 ; banner from bank 1
|
||||
rst4xrtn = $facb ; where to return from reset4x
|
||||
;bt4xrtn = $fb12 ; where to return from boot4x
|
||||
bt4xrtn = $fb19 ; where to return from boot4x
|
||||
rom4x_disp = $e000 ; main dispatch code
|
||||
reset4x = $e000 ; reset routine
|
||||
boot4x = $e180 ; boot routine
|
||||
nbtfail = $e300 ; new boot fail
|
||||
banner = $fb60 ; 'Apple //c'
|
||||
testsize = $d99f ; test ramdisk size (rom 3 = $d995)
|
||||
monitor = $ff59 ; monitor
|
||||
|
@ -1,719 +0,0 @@
|
||||
; 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
|
||||
;
|
@ -1,53 +0,0 @@
|
||||
# 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).
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
; 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
|
@ -1,11 +0,0 @@
|
||||
; 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
|
||||
|
@ -1,10 +0,0 @@
|
||||
; 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
|
||||
|
@ -1,7 +0,0 @@
|
||||
; 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
|
@ -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 $c7fc ; jump to 5X dispatcher
|
||||
jmp $fbdf ; jump to bell1 hijack
|
||||
; total 6 bytes.
|
||||
|
||||
|
21
rom5x/B0_FBDF_bell1_hijack.s
Normal file
21
rom5x/B0_FBDF_bell1_hijack.s
Normal file
@ -0,0 +1,21 @@
|
||||
; 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
|
||||
|
@ -1,13 +0,0 @@
|
||||
; 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
|
||||
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
; patch GETLN1 to call delete key handler
|
||||
.code
|
||||
.include "iic+.defs"
|
||||
.org $fd75
|
||||
jsr $c4ee
|
||||
|
@ -1,5 +0,0 @@
|
||||
; See B0 C7FC patch for description
|
||||
.code
|
||||
.org $c7ff
|
||||
jmp $fb3c ; to 5X dispatch
|
||||
|
@ -1,110 +1,106 @@
|
||||
.code
|
||||
.psc02
|
||||
.include "iic+.defs"
|
||||
.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
|
||||
.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
|
||||
; zero ram card space
|
||||
.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
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,56 +1,55 @@
|
||||
.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
|
||||
@ -58,32 +57,30 @@ confirm:
|
||||
; 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 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
|
||||
.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
|
||||
|
||||
|
@ -1,44 +1,47 @@
|
||||
.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
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -1,51 +1,45 @@
|
||||
.code
|
||||
.psc02
|
||||
.include "iic+.defs"
|
||||
.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
|
||||
|
||||
.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
|
||||
; "classic air raid beep"
|
||||
.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
|
||||
|
||||
; 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
|
||||
; old wait - no ACIA access to enforce delay at
|
||||
; accelerated speeds, speaker delay takes care of it
|
||||
; accelerated speeds, speaker delay tkes care of it
|
||||
; when we do the old beep
|
||||
.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"
|
||||
owait: sec
|
||||
owait2: pha
|
||||
owait3: sbc #$01
|
||||
bne owait3
|
||||
pla
|
||||
sbc #$01
|
||||
bne owait2
|
||||
rts
|
||||
|
5
rom5x/B1_FBE2_bell1_hijack_b1.s
Normal file
5
rom5x/B1_FBE2_bell1_hijack_b1.s
Normal file
@ -0,0 +1,5 @@
|
||||
.code
|
||||
.include "iic+.defs"
|
||||
.org $fbe2 ; ~29 bytes free here
|
||||
jmp $fb3c
|
||||
|
@ -24,7 +24,6 @@ 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
|
||||
@ -99,11 +98,7 @@ SWRTS2 = $C784
|
||||
.endif
|
||||
.proc ACCEL
|
||||
bra accel1
|
||||
.if ::ACCMENU
|
||||
jmp AMENU ; entry point for menu
|
||||
.else
|
||||
rts ; otherwise return to caller if they want menu
|
||||
.endif
|
||||
jmp AMENU
|
||||
accel1: php
|
||||
sei
|
||||
phy
|
||||
@ -173,13 +168,10 @@ acceldn:
|
||||
plx
|
||||
ply
|
||||
plp
|
||||
; original code:
|
||||
; clc ; ...
|
||||
; cmp #$00 ; sets carry, derp
|
||||
; beq doexit
|
||||
; sec ; carry was set anyway...
|
||||
|
||||
cmp #$01 ; better, carry reflect error status
|
||||
clc
|
||||
cmp #$00
|
||||
beq doexit
|
||||
sec
|
||||
doexit:
|
||||
.if ::TESTBLD
|
||||
rts
|
||||
@ -412,13 +404,9 @@ 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
|
||||
@ -607,6 +595,6 @@ msg1 = ::amenu1
|
||||
msg2 = ::amenu2
|
||||
.endif
|
||||
.endproc ; AMENU
|
||||
; check for run into vector and ROM checksum area
|
||||
; check for run into vector area
|
||||
.assert * < $ffe0, error, "accel5x overran $ffe0"
|
||||
.endif
|
||||
|
229
rom5x/README.md
229
rom5x/README.md
@ -1,214 +1,45 @@
|
||||
# ROM 5X by MG
|
||||
|
||||
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.
|
||||
## PRELIMINARY, NEEDS MORE TESTING
|
||||
|
||||
It adds the following features to the Apple //c Plus:
|
||||
*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.*
|
||||
|
||||
- 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.
|
||||
This is ROM 5X, providing the ROM 4X functionality to the Apple IIc Plus ROM
|
||||
version 5.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
# User Guide
|
||||
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.
|
||||
|
||||
## Obtaining
|
||||
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.
|
||||
|
||||
**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!**
|
||||
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.
|
||||
|
||||
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.
|
||||
## Options
|
||||
|
||||
### Binary Releases
|
||||
The options directory contains one or more subdirectories with optional patches
|
||||
that change the default behavior of the Apple IIc Plus.
|
||||
|
||||
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.
|
||||
Enable options by copying the files to the main directory. Disable the by removing
|
||||
the copied files.
|
||||
|
||||
The shell script will perform the following:
|
||||
### Accelerator Reverse
|
||||
|
||||
* 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.)
|
||||
The accelerator reverse patch is a 1-byte patch that causes the IIc Plus to boot up
|
||||
at the normal speed.
|
||||
|
||||
Pressing ESC at reset time will make the machine run at 4 MHz.
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
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'
|
||||
source_rom = "iic+_rom5.bin"
|
||||
dest_rom = "iic+_rom5x.bin"
|
||||
rom_base = 0xc000
|
||||
|
||||
source_files = Rake::FileList.new('*.s')
|
||||
@ -12,10 +9,6 @@ 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}"
|
||||
@ -25,31 +18,6 @@ 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"
|
||||
@ -64,7 +32,7 @@ rule ".b" => ".o" do |t|
|
||||
end
|
||||
|
||||
desc "Build ROM"
|
||||
task :build_rom => [:assemble, :checksum_rom] do
|
||||
task :build_rom => [:assemble] do
|
||||
puts "Building ROM image..."
|
||||
obj_files = Rake::FileList.new('*.b')
|
||||
rom = File.read(source_rom)
|
||||
@ -108,61 +76,3 @@ 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
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
; *** 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
|
@ -1,12 +0,0 @@
|
||||
; 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)
|
@ -1,8 +0,0 @@
|
||||
; 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
|
||||
|
@ -1,6 +0,0 @@
|
||||
; 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
|
116
story.md
116
story.md
@ -1,116 +0,0 @@
|
||||
# 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.
|
||||
|
Loading…
Reference in New Issue
Block a user